Programmatically NOP the Current Selection in Ghidra



The Zlob malware contains lots of bogus API calls to hinder analysis. This blag post describes how to use a Java script in Ghidra to automate the process of patching out those calls with NOP instructions. This malware employs some basic obfuscation techniques, one of which is performing lots of API calls without side effects. This makes both the disassembly as well as the decompile view hard to read. The following excerpts where take from a random sample belonging to the Zlob family (SHA256 hash 0b38ca277bbb042d43bd1f17c4e424e167020883526eb2527ba929b2f0990a8f):
ff d5 | CALL EBP=>KERNEL32.DLL::GetCurrentThreadId
ff d5 | CALL EBP=>KERNEL32.DLL::GetCurrentThreadId
ff d6 | CALL ESI=>KERNEL32.DLL::GetLastError      
ff d7 | CALL EDI=>KERNEL32.DLL::GetConsoleCP      
ff d6 | CALL ESI=>KERNEL32.DLL::GetLastError      
ff d6 | CALL ESI=>KERNEL32.DLL::GetLastError      
ff d6 | CALL ESI=>KERNEL32.DLL::GetLastError      
ff d7 | CALL EDI=>KERNEL32.DLL::GetConsoleCP      
ff d6 | CALL ESI=>KERNEL32.DLL::GetLastError      
ff d7 | CALL EDI=>KERNEL32.DLL::GetConsoleCP      
ff d6 | CALL ESI=>KERNEL32.DLL::GetLastError      
And the corresponding decompile view:
GetCurrentThreadId();
GetCurrentThreadId();
GetLastError();
GetConsoleCP();
GetLastError();
GetLastError();
GetLastError();
GetConsoleCP();
GetLastError();
GetConsoleCP();
GetLastError();
[...]
You can "just" hit F2 on every call (and the following byte) to replace it with the NOP instruction (0x90) but this is a more than tedious process. Let's write a handy script to automate this:
public void run() throws Exception {
    if (currentSelection != null) {
        AddressRangeIterator addressRanges = currentSelection.getAddressRanges(true);
        for (AddressRange addressRange : addressRanges) {
            nopOut(addressRange.getMinAddress(), addressRange.getLength());
        }
    }
}

private void nopOut(Address addressStart, long length) throws CancelledException, MemoryAccessException {
    clearListing(addressStart, addressStart.add(length));
    for (int i = 0; i < length; i++) {
        Address address = addressStart.add(i);
        setByte(address, (byte) 0x90);
    }

    disassemble(addressStart);
}
If you assign this script to the keyboard shortcut Ctrl-Alt-Shift-N for example, you can just select regions of disassembly and hit that combination to replace everything with NOP. ## A tiny plot-twist Since selecting calls in the decompile view will also highlight disassembly, one might think that you can as easily select region of the decompile view, execute the same script and come to the same result. Sadly this is not the case: If you select a line in the decompile view, the corresponding selection in the disassembly will always only have length 1. While one might think that in the screenshot above, the selection in the disassembly has a lenght of 6 and ranges from 0x10003a34 to 0x10003a3a (exclusively) it is in fact three selections, each of length 1 starting at 0x10003a34, 0x10003a36 and, 0x10003a38. Executing the above script will hence result in the byte sequence 90 d3 90 d3 90 d3 which cannot even be disassembled. So let's not pass in addressRange.getLength() into the nopOut function but instead extend the length such that the last instruction is always included completely:
/**
 * Searchers backwards for the last assembly instruction and returns the length
 * of the address range, potentially extended to fully include this last
 * instruction.
 */
private long assemblyAlignedLength(AddressRange addressRange) {
	long length = addressRange.getLength();
	for (int i = 1; i <= MAX_ASSEMBLY_INSTRUCTION_LENGTH; i++) {
		Instruction instruction = getInstructionAt(addressRange.getMinAddress().add(length - i));
		if (instruction != null) {
			return length + (instruction.getLength() - i);
		}
	}

	return length;
}
You can find the complete nts.java script on github!

Leave a Reply

Your email address will not be published. Required fields are marked *