//Finds and creates dynamically allocated strings based on the Golang stringStruct //@author Max 'Libra' Kersten of Trellix' Advanced Research Center, based on the work by padorka@cujoai (https://github.com/getCUJO/ThreatIntel/blob/master/Scripts/Ghidra/find_dynamic_strings.py) //@category Golang //@keybinding //@menupath //@toolbar import java.util.ArrayList; import java.util.List; import ghidra.app.script.GhidraScript; import ghidra.program.model.address.Address; import ghidra.program.model.lang.OperandType; import ghidra.program.model.lang.Register; import ghidra.program.model.listing.Data; import ghidra.program.model.listing.Instruction; import ghidra.program.model.mem.MemoryBlock; import ghidra.program.model.scalar.Scalar; import ghidra.program.model.util.CodeUnitInsertionException; import ghidra.util.exception.CancelledException; public class GolangDynamicStringRecovery extends GhidraScript { /** * A boolean which defines if logging should be enabled. When prioritising * speed, one might not be interested in getting all messages, but rather only * the concluding message, along with potential error messages. As such, this * boolean specifies if more logging should be enabled or disabled.
*
* The default value of this field is true. */ private static final boolean ENABLE_LOGGING = true; /** * The size of a pointer on X86 */ private static final int POINTER_SIZE_X86 = 4; /** * The size of a pointer on X64 */ private static final int POINTER_SIZE_X64 = 8; /** * The number of recovered dynamic strings */ private static int stringCount = 0; @Override protected void run() throws Exception { /* * Get the language ID and the program's pointer size and store those locally, * as they are re-used multiple times */ String languageId = currentProgram.getLanguageID().toString(); int pointerSize = currentProgram.getDefaultPointerSize(); /* * Based on the language ID, the dynamic strings need to be recovered * differently */ if (languageId.startsWith("ARM")) { // 32-bit ARM resolve32BitArm(); } else if (languageId.startsWith("AARCH64")) { // 64-bit ARM resolve64BitArm(); } else if (languageId.startsWith("x86") && pointerSize == POINTER_SIZE_X86) { // x86 resolveIntel(false); } else if (languageId.startsWith("x86") && pointerSize == POINTER_SIZE_X64) { // x86_64 resolveIntel(true); } else { // Print an error message if the architecture is not supported printerr("Unsupported architecture: " + languageId); return; } // Print the total number of recovered strings println("Total number of recovered dynamic strings: " + stringCount); } /** * A wrapper function for the * {@link ghidra.app.script.GhidraScript#println(String)} which is only called * if the {@link #ENABLE_LOGGING} is true. The logging that is * (potentially) passing through this function, is meant as optional logging. * The final conclusion, as well as the logging of any error messages, should be * printed via direct calls. The easy-to-omit nature of optional messages speeds * up automated analysis by limiting the number of print calls. * * @param message */ private void log(String message) { if (ENABLE_LOGGING) { println(message); } } /** * Creates an ASCII string at the given address with the given length, and * returns the instruction after the given instruction * * @param instruction the current instruction within the program * @param address the address of the ASCII string * @param length the length of the ASCII string * @return the instruction after the instruction variable, or null if there is * no such instruction */ private Instruction createString(Instruction instruction, Address address, Integer length) { try { //Get the data' starting point Data checkData = getDataContaining(address); if (checkData != null) { clearListing(address); } // Create the ASCII string at the given address with the given length Data data = createAsciiString(address, length); // Gets the newly created string as a String object String ascii = (String) data.getValue(); // Optionally print the address (clickable in Ghidra's console) along with the // value log("0x" + Long.toHexString(address.getOffset()) + " : \"" + ascii + "\""); // Increment the number of recovered dynamic strings stringCount++; } catch (Exception ex) { // Ignore exceptions } // Return the next instruction return getInstructionAfter(instruction); } /** * This helper function returns the integer value of a scalar object. The * purpose of this function is to avoid repeated casting in numerous places * within the script. * * @param scalar the object to get the integer value from * @return the integer value of the given scalar object */ private Integer getInteger(Scalar scalar) { return ((Long) scalar.getValue()).intValue(); } /** * Gets all memory blocks which have a name equal to .text or __text (used in PE * and ELF, and Mach-O files respectively), disregarding the used casing. The * list can be empty, but never null. * * @return all .text or __text named memory blocks (used in PE and ELF, and * Mach-O files respectively), disregarding the used casing */ private List getTextMemoryBlocks() { // Declare and initialise the list List blocks = new ArrayList<>(); // Iterate over all blocks for (MemoryBlock block : getMemoryBlocks()) { // Check if the name is equal, disregarding the case if (block.getName().equalsIgnoreCase(".text") || block.getName().equalsIgnoreCase("__text")) { // If it is equal, add it to the list blocks.add(block); } } // Return the list, which might be empty return blocks; } /** * Resolves the dynamic strings for Intel architecture based binaries. This * works for both x86 and x86_64 architectures * * @param is64Bit true if the given binary is 64-bit, false if not */ private void resolveIntel(boolean is64Bit) { // Iterate over all memory blocks for (MemoryBlock block : getTextMemoryBlocks()) { // Get the first instruction from this block Instruction instruction = getInstructionAt(block.getStart()); // Loop as long as an instruction is present and valid while (instruction != null) { // Check if the script's execution is cancelled if (monitor.isCancelled()) { // Return from the recovery function, thus exiting the script's execution early return; } try { // Get the operand type at index 1, which should be an address int operandType = instruction.getOperandType(1); // Get the register at index zero Register register = instruction.getRegister(0); /* * Check the first instruction of a dynamically allocated string: * * LEA REG, [STRING_ADDRESS] * * This is the same for x86 and x86_64, hence no bitness check */ if (instruction.getMnemonicString().equalsIgnoreCase("LEA") == false || register == null || OperandType.isAddress(operandType) == false) { // Get the next instruction instruction = getInstructionAfter(instruction); // Skip this item in the loop continue; } // Get the next instruction Instruction instructionTwo = getInstructionAfter(instruction); /* * Check the second instruction: * * MOV [SP + ..], REG * * Note that the stack pointer is either ESP or RSP, depending on the * architecture (x86 or x86_64 respectively) * * Also note that REG refers to the same register as the first instruction used * * The is64Bit boolean is true if the used architecture is x86_64, false if it * is x86 */ if (instructionTwo.getMnemonicString().equalsIgnoreCase("MOV") == false || instructionTwo.getRegister(1) != register) { if ((is64Bit == false && instructionTwo.getOpObjects(0)[0].toString().equalsIgnoreCase("ESP") == false) || (is64Bit == true && instructionTwo.getOpObjects(0)[0].toString() .equalsIgnoreCase("RSP") == false)) { // Get the next instruction instruction = getInstructionAfter(instruction); // Skip this item in the loop continue; } } // Get the third instruction Instruction instructionThree = getInstructionAfter(instructionTwo); /* * Get the operand type (should be a scalar) at index one of the third * instruction */ operandType = instructionThree.getOperandType(1); /* * Look for the third instruction, which follows either of the following * patterns, depending on the architecture: * * MOV [ESP + ..], STRING_SIZE * * MOV [RSP + ..], STRING_SIZE * * Note that the operand type should be of the scalar type * * The is64Bit boolean is true if the used architecture is x86_64, false if it * is x86 */ if (instructionThree.getMnemonicString().equalsIgnoreCase("MOV") == false || OperandType.isScalar(operandType) == false) { if ((is64Bit == false && instructionThree.getOpObjects(0)[0].toString().equalsIgnoreCase("ESP") == false) || (is64Bit == true && instructionThree.getOpObjects(0)[0].toString() .equalsIgnoreCase("RSP") == false)) { // Get the next instruction instruction = getInstructionAfter(instruction); // Skip this item in the loop continue; } } // Get the address Address address = instruction.getPrimaryReference(1).getToAddress(); /* * Get the instruction's first indexed object, of which the first element (index * 0) is used */ Object object = instructionThree.getOpObjects(1)[0]; // Check if the object is of the scalar type if (object instanceof Scalar == false) { // Get the next instruction instruction = getInstructionAfter(instruction); // Skip this item in the loop continue; } /* * This code can only be reached if the object's type is scalar, so it can * safely be cast */ Scalar scalar = (Scalar) object; // Get the integer value of the scalar object Integer lengthValue = getInteger(scalar); /* * Create a string at the given address with the given length, and increment to * the next instruction */ instruction = createString(instruction, address, lengthValue); } catch (Exception ex) { // Get the next instruction instruction = getInstructionAfter(instruction); // Skip this item in the loop continue; } } } } /** * Resolves the dynamic strings for 32-bit ARM architecture based binaries */ private void resolve32BitArm() { /* * #ARM, 32-bit * * #LDR REG, [STRING_ADDRESS_POINTER] * * #STR REG, [SP, ..] * * #MOV REG, STRING_SIZE * * #STR REG, [SP, ..] */ // Iterate over all memory blocks for (MemoryBlock block : getTextMemoryBlocks()) { // Get the first instruction Instruction instruction = getInstructionAt(block.getStart()); // Loop as long as an instruction is present and valid while (instruction != null) { // Check if the script's execution is cancelled if (monitor.isCancelled()) { // Return from the recovery function, thus exiting the script's execution early return; } try { // Get the operand type, which should be an address or a scalar int operandType = instruction.getOperandType(1); // Check first instruction: LDR REG, [STRING_ADDRESS_POINTER] if (instruction.getMnemonicString().equalsIgnoreCase("ldr") == false || instruction.getRegister(0) == null || OperandType.isAddress(operandType) == false || OperandType.isScalar(operandType) == false) { // Get the next instruction instruction = getInstructionAfter(instruction); // Skip this item in the loop continue; } // Get the register at index 0 Register register = instruction.getRegister(0); // Get the second instruction Instruction instructionTwo = getInstructionAfter(instruction); /* * Check second instruction: * * STR REG, [SP + ..] * * Note that the register REG should be the same as the register that was used * in the first instruction */ if (instructionTwo.getMnemonicString().equalsIgnoreCase("str") == false || instructionTwo.getRegister(0) != register || instructionTwo.getOpObjects(1)[0].toString().equalsIgnoreCase("sp") == false) { // Get the next instruction instruction = getInstructionAfter(instruction); // Skip this item in the loop continue; } // Get the third instruction Instruction instructionThree = getInstructionAfter(instructionTwo); // Get the operand type, which should be a scalar operandType = instructionThree.getOperandType(1); // Check third instruction: MOV REG, STRING_SIZE if (instructionThree.getMnemonicString().equalsIgnoreCase("mov") == false || instructionThree.getRegister(0) == null || OperandType.isScalar(operandType) == false) { // Get the next instruction instruction = getInstructionAfter(instruction); // Skip this item in the loop continue; } // Get the first register from the third instruction register = instructionThree.getRegister(0); // Get the first instruction Instruction instructionFour = getInstructionAfter(instructionThree); /* * Check fourth instruction: * * STR REG, [SP + ..] * * Note that the register REG should be the same register that was used in the * third instruction */ if (instructionFour.getMnemonicString().equalsIgnoreCase("str") == false || instructionFour.getRegister(0) != register || instructionFour.getOpObjects(1)[0].toString().equalsIgnoreCase("sp") == false) { // Get the next instruction instruction = getInstructionAfter(instruction); // Skip this item in the loop continue; } // Get the address pointer int addressPointer = getInt(instruction.getPrimaryReference(1).getToAddress()); // Get the address, essentially dereferencing the pointer Address address = currentProgram.getAddressFactory().getAddress(Long.toHexString(addressPointer)); // Get the second object (index 1) from the third instruction Object object = instructionThree.getOpObjects(1)[0]; // Check if the object is of the scalar type if (object instanceof Scalar == false) { // Get the next instruction instruction = getInstructionAfter(instruction); // Skip this item in the loop continue; } /* * This code is only reachable if the object is of the scalar type, so it can be * cast */ Scalar scalar = (Scalar) object; // Get the scalar's value as an integer Integer length = getInteger(scalar); /* * Create the ASCII string at the given address for the given length, along with * the next instruction */ instruction = createString(instruction, address, length); } catch (Exception ex) { // Ignore exceptions } } } } /* * Resolves the dynamic strings for 64-bit ARM architecture based binaries */ private void resolve64BitArm() { /* * #ARM, 64-bit - version 1 * * #ADRP REG, [STRING_ADDRESS_START] * * #ADD REG, REG, INT * * #STR REG, [SP, ..] * * #ORR REG, REG, STRING_SIZE * * #STR REG, [SP, ..] * * #ARM, 64-bit - version 2 * * #ADRP REG, [STRING_ADDRESS_START] * * #ADD REG, REG, INT * * #STR REG, [SP, ..] * * #MOV REG, STRING_SIZE * * #STR REG, [SP, ..] */ // Iterate over all memory blocks for (MemoryBlock block : getTextMemoryBlocks()) { // Get the first instruction from this block Instruction instruction = getInstructionAt(block.getStart()); // Loop as long as an instruction is present and valid while (instruction != null) { // Check if the script's execution is cancelled if (monitor.isCancelled()) { // Return from the recovery function, thus exiting the script's execution early return; } // Get the operand type at index 1, which should be a scalar int operandType = instruction.getOperandType(1); // Get the register at index zero Register register = instruction.getRegister(0); /* * Check first instruction of a dynamically allocated string * * ADRP REG, [STRING_ADDRESS_START] */ if (instruction.getMnemonicString().equalsIgnoreCase("adrp") == false || instruction.getRegister(0) == null || OperandType.isAddress(operandType) == false || OperandType.isScalar(operandType) == false) { // Get the next instruction instruction = getInstructionAfter(instruction); // Skip this item in the loop continue; } // Get the second instruction Instruction instructionTwo = getInstructionAfter(instruction); /* * Get the operand type of the second instruction at index 2, which should be of * the scalar type */ operandType = instructionTwo.getOperandType(2); /* * Check second instruction: * * ADD REG, REG, INT * * Note that REG refers to the same register as the first instruction used * * Also note that the operand type needs to be of the scalar type */ if (instructionTwo.getMnemonicString().equalsIgnoreCase("add") == false || instructionTwo.getRegister(0) != register || OperandType.isScalar(operandType) == false) { // Get the next instruction instruction = getInstructionAfter(instruction); // Skip this item in the loop continue; } // Get the third instruction Instruction instructionThree = getInstructionAfter(instructionTwo); /* * Check the third instruction: * * STR REG, [SP + ..] * * Note that REG refers to the same register as the first instruction used */ if (instructionThree.getMnemonicString().equalsIgnoreCase("str") == false || instructionThree.getRegister(0) != register || instructionThree.getOpObjects(1)[0].toString().equalsIgnoreCase("sp") == false) { // Get the next instruction instruction = getInstructionAfter(instruction); // Skip this item in the loop continue; } // Get the fourth instruction Instruction instructionFour = getInstructionAfter(instructionThree); // Get the register from the fourth instruction, at index 0 register = instructionFour.getRegister(0); /* * Declare several variables, which are to be initialised at a later stage, * depending on the way it is loaded (version 1 or version 2) */ int length; Object object; Scalar scalar; /* * Check fourth instruction: * * Version 1: ORR REG, REG, STRING_SIZE * * Version 2: MOV REG, STRING_SIZE * * Note that the operand type needs to be a scalar * * Also note that the register from the fourth instruction should not be null */ if (instructionFour.getMnemonicString().equalsIgnoreCase("orr") == false && register != null && OperandType.isScalar(instructionFour.getOperandType(2)) == true) { // Get the relevant object object = instructionFour.getOpObjects(2)[0]; /* * The relevant object is of the scalar type, as defined within the if-statement */ scalar = (Scalar) object; // Get the scalar's value as an integer length = getInteger(scalar); } else if (instructionFour.getMnemonicString().equalsIgnoreCase("mov") && register != null && OperandType.isScalar(instructionFour.getOperandType(1)) == true) { // Get the relevant object object = instructionFour.getOpObjects(1)[0]; /* * The relevant object is of the scalar type, as defined within the if-statement */ scalar = (Scalar) object; // Get the scalar's value as an integer length = getInteger(scalar); } else { // Get the next instruction instruction = getInstructionAfter(instruction); // Skip this item in the loop continue; } // Gets the fifth instruction Instruction instructionFive = getInstructionAfter(instructionFour); /* * Check fifth instruction: * * STR REG, [SP + ..] * * Note that REG refers to the same register as the fourth instruction used */ if (instructionFive.getMnemonicString().equalsIgnoreCase("str") == false || instructionFive.getRegister(0) != register || instructionFive.getOpObjects(1)[0].toString().equalsIgnoreCase("sp") == false) { // Get the next instruction instruction = getInstructionAfter(instruction); // Skip this item in the loop continue; } // Get two objects Object objA = instruction.getOpObjects(1)[0]; Object objB = instructionTwo.getOpObjects(2)[0]; // Ensure that both objects are of the scalar type if (objA instanceof Scalar == false || objB instanceof Scalar == false) { // Gets the next instruction instruction = getInstructionAfter(instruction); // Skip this item in the loop continue; } // Cast the object to the correct type if this code is reached scalar = (Scalar) objA; // Get the scalar's value as an integer Integer addressPointer = getInteger(scalar); // Cast the object to the correct type if this code is reached scalar = (Scalar) objB; /* * Get the scalar's value as an integer. Note the "+=" instead of "=" */ addressPointer += getInteger(scalar); // Dereference the pointer Address address = currentProgram.getAddressFactory().getAddress(Long.toHexString(addressPointer)); /* * Create the ASCII string at the given address for the given length, along with * the next instruction */ instruction = createString(instruction, address, length); } } } }