//Recovers Golang types within the given binary //@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/type_extract.py) //@category Golang //@keybinding //@menupath //@toolbar import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import ghidra.app.script.GhidraScript; import ghidra.program.model.address.Address; import ghidra.program.model.listing.Data; import ghidra.program.model.mem.MemoryAccessException; import ghidra.program.model.mem.MemoryBlock; import ghidra.program.model.symbol.Reference; public class GolangTypeRecovery 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 numeric value for the function kind, as defined in the Golang language * specification * * @see The Golang source code for * the Type class, see the constant list under type Kind uint * for the complete list */ private static final int KIND_FUNCTION = 0x13; /** * The numeric value for the interface kind, as defined in the Golang language * specification * * @see The Golang source code for * the Type class, see the constant list under type Kind uint * for the complete list */ private static final int KIND_INTERFACE = 0x14; /** * The numeric value for the pointer kind, as defined in the Golang language * specification * * @see The Golang source code for * the Type class, see the constant list under type Kind uint * for the complete list */ private static final int KIND_POINTER = 0x16; /** * The numeric value for the struct kind, as defined in the Golang language * specification * * @see The Golang source code for * the Type class, see the constant list under type Kind uint * for the complete list */ private static final int KIND_STRUCT = 0x19; /** * The default PC Quantum size (minimal instruction size), used in x86, x86_64, * and WASM * * @see Golang * architecture documentation */ private static final int INSTRUCTION_SIZE_ONE = 1; /** * The PC Quantum size (minimal instruction size), used in RISCV, RISCV x64, * S390, S390X, SH, and SHbe * * @see Golang * architecture documentation */ private static final int INSTRUCTION_SIZE_TWO = 2; /** * The PC Quantum size (minimal instruction size), used in ALPHA, ARM, ARMbe, * M68K, MIPS, MIPSle, MIPS64p32, MIPS64p32le, MIPS64, MIPS64le, NIOS2, PPC, * PPC64, PPC64le, SPARC, and SPARC64 * * @see Golang * architecture documentation */ private static final int INSTRUCTION_SIZE_FOUR = 4; /** * 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 human readable version strings that are to be used to determine the * version of a Golang binary. These will be converted to byte strings during * the script's runtime, and then stored in the {@link #versions} variable. The * sole purpose of this additional step is to more easily update this script * when fixing bugs and/or adding support for later Golang versions. */ private static final String[] HUMAN_READABLE_VERSIONS = { "go1.20", "go1.19", "go1.18", "go1.17", "go1.16", "go1.15", "go1.14", "go1.13", "go1.12", "go1.11", "go1.10", "go1.9", "go1.8", "go1.7", "go1.6", "go1.5", "go1.4", "go1.3", "go1.2" }; /** * Byte strings of the versions in the {@link #HUMAN_READABLE_VERSIONS} * variable. This variable is to be initialised during runtime. */ private String[] versions; /** * The Golang version of the analysed binary, stored globally for later use to * avoid looking for the version multiple times. This value is one of the * {@link #versions}, which is more easily read in its human readable format as * defined within the {@link #HUMAN_READABLE_VERSIONS}. */ private String currentVersion; /** * The magic values for the pclntab, as found in the Golang source code. Note * that version 1.2 is less than 1.16, since the value * after the dot is to be seen as an incrementing value number, not as the usual * mathematical way of writing. The values in the array are for the following * Golang versions, in order: * *
    *
  1. Version 1.2
  2. *
  3. Version 1.16
  4. *
  5. Version 1.18
  6. *
  7. Version 1.20
  8. *
* * @see Golang pclntab * source code */ private static String[] pclntab_magic = { "\\xfb\\xff\\xff\\xff\\x00\\x00", "\\xfa\\xff\\xff\\xff\\x00\\x00", "\\xf0\\xff\\xff\\xff\\x00\\x00", "\\xf1\\xff\\xff\\xff\\x00\\x00" }; /** * The size of a pointer, in bytes, within the currently loaded binary */ private int binaryPointerSize; /** * A hash set of addresses with recovered types, used to avoid recovering the * same type twice.
*
* The hash set is used, instead of an {@link java.util.ArrayList}, as its more * efficient. Both the {@link java.util.HashSet#contains(Object)} and * {@link java.util.HashSet#add(Object)} methods function in O(1), * in contrast to the inner workings of other types, which often work in * O(n).
*
* Testing locally shows that the time to complete the script, on a test sample * which contains 3047 to-be recovered types, is nearly three times as much when * using an {@link java.util.ArrayList}, compared to the * {@link java.util.HashSet}. The specifics obviously depend on the used * hardware, but it shows that the hash set implementation is more efficient. */ private static HashSet
recoveredTypes = new HashSet<>(); /** * The script's main function. This script can be executed normally and * headless. */ @Override protected void run() throws Exception { // Initialise the byte string versions, based on the human readable ones versions = createVersionByteStrings(); // Declares and initialises the executable format of the current program String executableFormat = currentProgram.getExecutableFormat(); /* * Initialises the global variable to store the pointer size of the currently * analysed binary in */ binaryPointerSize = currentProgram.getDefaultPointerSize(); // Optionally print the executable format log("Executable format: " + executableFormat); /* * Defines the address array which is to be initialised differently, depending * on the file's type */ Address[] objects; // If the file is of the PE format if (executableFormat.equalsIgnoreCase("Portable Executable (PE)")) { // Initialise the objects array objects = scanPeFile(); } else if (executableFormat.equalsIgnoreCase("Executable and Linking Format (ELF)")) { // If the file is of the // ELF format // Initialise the objects array objects = scanElfFile(); } else if (executableFormat.equalsIgnoreCase("Mac OS X Mach-O")) { objects = scanMachoFile(); } else { // Inform the analyst of the error printerr("Incorrect file format: \"" + executableFormat + "\""); // Exit the script return; } /* * If the objects array is null, an error occurred earlier on, of which the * analyst is already informed via an earlier printerr statement. As such, this * function simply needs to return early to end the script's execution. */ if (objects == null) { return; } // Find the Golang version within the given binary currentVersion = findVersion(); // Check if a version was found if (currentVersion == null) { // Inform the analyst of the failure to find a version printerr("No Golang version could be found in the binary!"); // Return early as the version is required later on return; } /* * Regardless if the file is of the PE or ELF format, the returned addresses are * the same. As such, the array can be used in the same way. To easier read the * function call, read it as: * * getAllTypes(typelinks, endOfTypelinks, type); */ recoverAllTypes(objects[0], objects[1], objects[2]); // Print the number of recovered types for the analyst println("Types recovered: " + recoveredTypes.size()); } /** * 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); } } /** * Gets the value of the bytes as a little endian short * * @param bytes the value to read * @return the value of the bytes as a little endian short */ private short getShort(byte[] bytes) { // Allocate a buffer of two bytes ByteBuffer buffer = ByteBuffer.allocate(2); // Set the order buffer.order(ByteOrder.LITTLE_ENDIAN); // Place the first byte buffer.put(bytes[0]); // Place the second byte buffer.put(bytes[1]); // Get the value as a short, in little endian format return buffer.getShort(0); } /** * Ingests the human readable versions and converts them into byte strings. The * byte strings can be used to searched for using * {@link #findBytes(Address, String)} and overloads thereof.
*
* An example of a byte string is \xfb\xff\xff\xff\x00\x00. * * @return the byte strings of the given versions */ private String[] createVersionByteStrings() { // Declare and initialise the output variable String[] byteStrings = new String[HUMAN_READABLE_VERSIONS.length]; // Loop over all human readable strings for (int i = 0; i < HUMAN_READABLE_VERSIONS.length; i++) { /* * Convert each human readable string into a byte string, and store it in the * output variable */ byteStrings[i] = createByteString(HUMAN_READABLE_VERSIONS[i]); } // Return the newly created byte strings return byteStrings; } /** * Returns a byte string of the given string, assuming the given input is UTF-8 * encoded * * @param input the UTF-8 encoded string * @return a byte string of the given input */ private String createByteString(String input) { // Get the bytes from the given input byte[] bytes = input.getBytes(Charset.forName("UTF-8")); // Declare the byte string and initialise it as an empty string String byteString = ""; // Iterate over all bytes for (byte currentByte : bytes) { // Get the current byte in string form String byteStringByte = Integer.toHexString(currentByte); /* * If the length of the current string is only a single character, prepend a * zero to ensure two digits are present for the given byte in string form */ if (byteStringByte.length() == 1) { byteStringByte = "0" + byteStringByte; } /* * Prepend "\x" to byte string's current byte, which is then added to the * complete byte string */ byteString += "\\x" + byteStringByte; } // Return the complete byte string return byteString; } /** * Gets the address at the given address, essentially dereferencing the given * pointer * * @param address the address to dereference * @return the dereferenced address * @throws MemoryAccessException if the given address cannot be dereferenced */ private Address getAddressAt(Address address) throws MemoryAccessException { return toAddr(Long.toHexString(getInt(address))); } /** * Removes data at the given location, which is required to retype certain * variables * * @param address the address to remove the type from * @param length the length of the data to remove the typing from * @throws Exception if the address is invalid or null */ private void removeData(Address address, int length) throws Exception { // Iterate for the given length for (int i = 0; i < length; i++) { /* * Remove the data at the given address plus the offset of the length, starting * at 0 */ removeDataAt(address.add(i)); } // Return if no exception is thrown } /** * Gets the sections that match (case insensitive) the given section name * * @param sectionName the section name to match, matching is case insensitive * @return the starting addresses of all sections which match the given section * name */ private Address[] getSection(String sectionName) { // Iterate over all memory blocks for (MemoryBlock block : getMemoryBlocks()) { // If the block's name matches the given section name, case insensitive if (block.getName().equalsIgnoreCase(sectionName)) { // Get the start of the block Address start = block.getStart(); // Get the end of the block Address end = block.getEnd(); /* * Optionally print a message for the analyst with the block's name, start * address, and end address */ log(String.format("%s [start: 0x%x, end: 0x%x]", block.getName(), start.getOffset(), end.getOffset())); // Return the start and end addresses return new Address[] { start, end }; } } // Print a message for the analyst indicating the section couldn't be found printerr("Section \"" + sectionName + "\" not found!"); // Return null in the case of an error return null; } /** * Returns the current version from any of the given versions, based on its * presence in the binary. The first version that is found is used. * * @return the byte string version that is found */ private String findVersion() { // Iterate over all versions in the global variable for (int i = 0; i < versions.length; i++) { // Declare a local variable for the currently iterated version String version = versions[i]; // Find the address of the first occurrence of the current version Address[] addresses = findBytes(null, version, 1000); // Check if any results were found if (addresses == null || addresses.length == 0) { // If not, continue to the next attempt to find the version continue; } /* * If the address is not null, inform the analyst with the human readable * version, for all results */ for (Address address : addresses) { // Optionally print the Golang version log("Golang version found at 0x" + Long.toHexString(address.getOffset()) + " : " + HUMAN_READABLE_VERSIONS[i]); } /* * Return the current version based on the first match. This can be wrong, which * is why all matches are printed to the analyst */ return version; } // Return null in the case of a failure to find the version return null; } /** * Until and including Golang 1.16, the length offset was two bytes in size. * From Golang 1.17 onwards, the length offset uses a variable length.
*
* For versions until and including 1.16, the second byte is used, which causes * the script to potentially miss long strings.
*
* For versions 1.17 or later, the first byte is used, which causes the script * to potentially miss long strings. * * @return the length offset */ private int getLengthOffset() { /* * Store the length of the current version byte string locally, which avoids * calling the length() function several times */ int currentVersionLength = currentVersion.length(); /* * Get the first digit of the version (which is the one but last byte in the * byte string), where the "1." part is omitted as its not required */ String firstDigit = currentVersion.substring(currentVersionLength - 6, currentVersionLength - 4); // Convert the value into a hexadecimal integer int integer = Integer.parseInt(firstDigit, 16); // Convert the value into a character char c = (char) integer; /* * Declare and initialise the variable to store the version number in, in string * format */ String versionNumberString = "" + c; /* * Get the last digit of the version (which is the one but last byte in the byte * string), where the "1." part is omitted as its not required */ String lastDigit = currentVersion.substring(currentVersionLength - 2, currentVersionLength); // Convert the value into a hexadecimal integer integer = Integer.parseInt(lastDigit, 16); // Convert the value into a character c = (char) integer; // Append the number versionNumberString = versionNumberString + c; // Convert the version number into an integer int versionNumber = Integer.parseInt(versionNumberString); // Compare the version to the versions of 1.17 or later if (versionNumber >= 17) { // Return one, as only one byte will be used return 1; } /* * If none of the >= 1.17 versions matches, assume the version is lower than * that, and thus the second byte is used */ return 2; } /** * Looks for the pclntab within the binary, without looking for a section with * the same name, but rather for a magic value within the binary * * @param pclntab_magic the byte strings of pclntab magic strings to look for * @return Returns an object array where the pclntab address is at index 0, and * the matching magic byte string is at index 1. The magic value can be * null! * @throws MemoryAccessException if the pclntab memory cannot be accessed */ private Object[] findPclntabPE() throws MemoryAccessException { // Iterate over all plcntab magic byte strings for (String magic : pclntab_magic) { // Search through the program for the given byte string Address[] results = findBytes(null, magic, 1000); // Ensure the results are not null nor empty if (results == null || results.length == 0) { // If no matches are found, continue continue; } // Iterate over all results for (int i = 0; i < results.length; i++) { // Get the currently iterated address Address tempAddress = results[i]; // If the returned value is not found if (tempAddress == null) { // Move to the next result in the address array continue; } // Check if the match is aligned with the pclntab structure if (isPclntab(tempAddress)) { // Optionally inform the analyst of the finding log("pclntab found!"); // Return the pclntab's address and the respective magic value return new Object[] { tempAddress, magic }; } } } // If nothing can be found, inform the analyst and then return null printerr("The pclntab could not be found!"); return null; } /** * Checks if the given address matches the pclntab structure pattern * * @param address the address to check * @return true if the structure matches, false if not * @throws MemoryAccessException if the memory at the given address (and a few * bytes more) cannot be accessed */ private boolean isPclntab(Address address) throws MemoryAccessException { // Get the PC Quantum Size value byte pcQuantum = getByte(address.add(6)); // Get the pointer size from the binary byte pclntabPointerSize = getByte(address.add(7)); // Verify the validity of the values if ((pcQuantum != INSTRUCTION_SIZE_ONE && pcQuantum != INSTRUCTION_SIZE_TWO && pcQuantum != INSTRUCTION_SIZE_FOUR) || (pclntabPointerSize != POINTER_SIZE_X86 && pclntabPointerSize != POINTER_SIZE_X64)) { // Return false if the conditions aren't met return false; } // Return true if the conditions are met return true; } /** * Checks if the given address is the start of the module data * * @param address the address to check * @param magic the magic value of the module data from the pclntab * @return true if the given address is the address of the module data, false if * not * @throws MemoryAccessException if the memory at the given address, and offsets * thereof, aren't accessible for any reason */ private boolean isModuleData(Address address, String magic) throws MemoryAccessException { // Declare the offset int offset; /* * Check if the magic value of Golang version 1.2 is in-use, as the offset * differs in this case */ if (magic.equalsIgnoreCase("\\xfb\\xff\\xff\\xff\\x00\\x00")) { // Golang version 1.2 magic value offset = 12; } else { offset = 22; } // Get the text address based on the given address and the offset Address text = getAddressAt(address.add(offset * binaryPointerSize)); // Get the memory block that corresponds with the .text section MemoryBlock textBlock = currentProgram.getMemory().getBlock(".text"); /* * Verify the module data by ensuring the offset from the module data matches * the offset of the .text block */ if (text != null && textBlock != null && text.getOffset() == textBlock.getStart().getOffset()) { log("Module data found!"); return true; } // Return false if the offset does not match, or if there is no .text section return false; } /** * Finds the address of the module data, based on the given pclntab address and * the pclntab magic value * * @param pclntab the address of the pclntab * @param magic the magic value of the pclntab * @return the address of the module data * @throws MemoryAccessException if the pclntab, or offsets thereof, aren't * accessible for any reason */ private Address findModuleData(Address pclntab, String magic) throws MemoryAccessException { // Declare the module data address Address moduleData; // Get all references to the pclntab Reference[] pclntabReferences = getReferencesTo(pclntab); // Iterate over all references for (Reference reference : pclntabReferences) { // Get the reference-making address moduleData = reference.getFromAddress(); // Check if the address matches the module data structure if (isModuleData(moduleData, magic)) { // Optionally print the module data's address log("Module data address: " + moduleData.toString(false, true)); // Return the address once a match is found return moduleData; } } /* * If no such reference was found, a search through the whole program is to be * started for the usage of the pclntab address, which is referenced in the * module data. Searching only through specific sections is possible, but since * this function is only called once in the beginning, and the improved accuracy * outweighs the marginally extra time that is required */ /* * Create a string of the inverted and hexadecimal representation of the pclntab * address */ String invertedHexPclntab = Long.toHexString(Long.reverseBytes(pclntab.getOffset())); /* * If the binary size is four, the string needs to be 8 characters long (four * times two characters, meaning four time two bytes). */ if (binaryPointerSize == 4) { invertedHexPclntab = invertedHexPclntab.substring(0, 8); } /* * If the pointer size is not four, its assumed to be eight, which is the * default value of the long representation already, meaning no change has to be * made */ String byteStringPclntab = ""; // Iterate over all characters, in sets of two per iteration for (int i = 0; i < invertedHexPclntab.length(); i = i + 2) { // Create the byte in string form, which is appended to the byte string variable byteStringPclntab += "\\x" + invertedHexPclntab.charAt(i) + invertedHexPclntab.charAt(i + 1); } /* * Get all results for the given byte string, starting at the program's * beginning */ Address[] results = findBytes(null, byteStringPclntab, 1000); // If the results are null or empty, return early if (results != null && results.length > 0) { // Iterate over all results for (int i = 0; i < results.length; i++) { // Temporarily store the current result in the module data variable moduleData = results[i]; // Check if the current address is the start of the module data structure if (isModuleData(moduleData, magic)) { // Return the value if that is the case return moduleData; } } } // Inform the analyst of the missing value printerr("The module data cannot be found!"); // Return null if no match is found return null; } /** * Gets the typeStart, typeEnd, typeLinks, and numberOfTypes variables based on * the given module data address and the plcntab magic value.
*
* Returns an array of objects which contains the following types and values, in * order:
*
    *
  1. Address typeStart
  2. *
  3. Address typeEnd
  4. *
  5. Address typeLinks
  6. *
  7. int numberOfTypes
  8. *
* * @param moduleData the address of the module data * @param magic the plcntab magic value * @param pointerSize the size of the pointer in bytes * @return in order Address type, Address etype, Address typelinks, int ntypes * @throws MemoryAccessException if the value at the module data address, or * offsets thereof, cannot be accessed for any * reason */ private Object[] getTypeLinks(Address moduleData, String magic) throws MemoryAccessException { // Declaration of the two offsets int offset; int offset2; // The offsets differ in Golang 1.2 if (magic.equalsIgnoreCase("\\xfb\\xff\\xff\\xff\\x00\\x00")) { // Golang 1.2 magic value offset = 25; offset2 = 30; } else if (magic.equalsIgnoreCase("\\xfa\\xff\\xff\\xff\\x00\\x00") || magic.equalsIgnoreCase("\\xf0\\xff\\xff\\xff\\x00\\x00")) { // Golang 1.16 and Golang 1.18 // respectively offset = 35; offset2 = 42; } else if (magic.equalsIgnoreCase("\\xf1\\xff\\xff\\xff\\x00\\x00")) { // Golang 1.20 offset = 37; offset2 = 44; } else { printerr("No pclntab magic value matched, thus making the offsets unknown!"); return null; } // Get the required values Address typeStart = getAddressAt(moduleData.add(offset * binaryPointerSize)); Address typeEnd = getAddressAt(moduleData.add((offset + 1) * binaryPointerSize)); Address typeLinks = getAddressAt(moduleData.add(offset2 * binaryPointerSize)); int numberOfTypes = getInt(moduleData.add((offset2 + 1) * binaryPointerSize)); // Return the values in an object array return new Object[] { typeStart, typeEnd, typeLinks, numberOfTypes }; } /** * Recover the types, based on the type's address and the type's location * * @param typeAddress * @param type * @return * @throws Exception */ private Address recoverTypes(Address typeAddress, Address type) throws Exception { try { // Check if the type is already recovered if (recoveredTypes.contains(typeAddress)) { // If the type is already recovered, optionally notify the analyst log(String.format("Type already recovered at 0x%x", typeAddress.getOffset())); // Return the type's address return typeAddress; } // Gets the length offset int lengthOffset = getLengthOffset(); // Add the current type to the list of recovered types recoveredTypes.add(typeAddress); // Declare and initialise the temporary offset variable int tempOffset = 2 * binaryPointerSize + 4; // Declare and initialise the temporary address Address tempAddress = typeAddress.add(tempOffset); // Get the byte at the given address byte b = getByte(tempAddress); // Declare and initialise the uncommon flag byte tflagUncommon = (byte) (b & 0x01); // Get the byte for the extra star flag b = getByte(typeAddress.add(2 * binaryPointerSize + 4)); // Declare and initialise the extra star far byte tflagExtraStar = (byte) (b & 0x02); // Get the byte for the kind b = getByte(typeAddress.add(2 * binaryPointerSize + 7)); // Declare and initialise the kind byte kind = (byte) (b & 0x1F); // Get the type name offset int nameOffset = getInt(typeAddress.add(4 * binaryPointerSize + 8)); // Get the type name length byte nameLength = getByte(type.add(nameOffset + lengthOffset)); // Get the name's address Address nameAddress = type.add(nameOffset + lengthOffset + 1); // Remove the data type(s) from the given addresses removeData(nameAddress, nameLength); // Create an ASCII string at the given address with the given length Data name = createAsciiString(nameAddress, nameLength); // Declare the variable String nameType; // Get the type's name if (tflagExtraStar == 1) { nameType = ((String) name.getValue()).substring(1); } else { nameType = ((String) name.getValue()); } // Optionally print the name of the type log("Recovered: \"" + nameType + "\""); // Create a label at the given address for the given name, excluding spaces createLabel(typeAddress, nameType.replace(" ", "_"), true); /* * The function type (funcType) is structured as shown below. More information * can be found here: https://go.dev/src/reflect/type.go * * struct { * * funcType uncommonType * * [2]*rtype // [0] is in, [1] is out * * } * * type funcType struct { * * rtype * * inCount uint16 * * outCount uint16 //the top bit is set if it is the last input parameter * * } */ if (kind == KIND_FUNCTION) { byte[] bytes = getBytes(typeAddress.add(4 * binaryPointerSize + 8 + 8), 2); short inCount = getShort(bytes); byte[] outBytes = getBytes(typeAddress.add(4 * binaryPointerSize + 8 + 8 + 2), 2); // the top bit is set if it is the last input parameter int lastInput = outBytes[1] & 0x80; outBytes[1] = (byte) (outBytes[1] & 0x7F); int outCount = getShort(outBytes); List inputs = new ArrayList<>(); List outputs = new ArrayList<>(); for (int i = 0; i < inCount; i++) { Address input = getAddressAt(typeAddress.add(4 * binaryPointerSize + 8 + 8 + binaryPointerSize + tflagUncommon * 16 + i * binaryPointerSize)); recoverTypes(input, type); inputs.add(getSymbolAt(input).getName()); } for (int i = 0; i < outCount; i++) { Address output = getAddressAt(typeAddress.add(4 * binaryPointerSize + 8 + 8 + binaryPointerSize + tflagUncommon * 16 + inCount * binaryPointerSize + i * binaryPointerSize)); recoverTypes(output, type); outputs.add(getSymbolAt(output).getName()); } if (lastInput == 0x80 && inputs.size() > 0) { String comment = "func("; for (String string : inputs) { comment += string + ", "; } comment = comment.substring(0, comment.length() - 2); comment += ") ("; for (String string : outputs) { comment += string + ", "; } comment = comment.substring(0, comment.length() - 2) + ")"; setPreComment(typeAddress, comment); } } /* * Interface type interfaceType represents an interface type. * * type interfaceType struct { * * rtype pkgPath name // import path methods * * []imethod // sorted by hash * * } * * method represents a method on an interface type * * type imethod struct { * * name nameOff // name of method * * typ typeOff // .(*FuncType) underneath * * } */ if (kind == KIND_INTERFACE) { Address imethodField = getAddressAt(typeAddress.add(5 * binaryPointerSize + 8 + 8)); List methods = new ArrayList<>(); int methodLength = getInt(typeAddress.add(6 * binaryPointerSize + 8 + 8)); for (int i = 0; i < methodLength; i++) { int imethodNameOffset = getInt(imethodField); nameLength = getByte(type.add(imethodNameOffset + lengthOffset)); nameAddress = type.add(imethodNameOffset + lengthOffset + 1); removeData(nameAddress, nameLength); name = createAsciiString(nameAddress, nameLength); String temp = (String) name.getValue(); setEOLComment(imethodField, temp); createLabel(imethodField, temp.replace(" ", "_"), true); int newTypeOffset = getInt(imethodField.add(4)); Address newType = type.add(newTypeOffset); recoverTypes(newType, type); imethodField = imethodField.add(8); methods.add(name.getValue() + " " + getSymbolAt(newType).getName()); String comment = "type " + nameType + " interface{" + "\n\t" + "\n\t"; for (int j = 0; j < methods.size(); j++) { comment += methods.get(j) + "\n\t"; } comment += "\n" + "}"; setPreComment(typeAddress, comment); } } /* * Pointer type is used to represent a pointer type * * type ptrType struct { * * rtype * * elem *rtype // pointer element (pointed at) type * * * } */ if (kind == KIND_POINTER) { Address newAddress = toAddr( Integer.toHexString(getInt(typeAddress.add(4 * binaryPointerSize + 8 + 8)))); recoverTypes(newAddress, type); } /* * Struct type structType represents a struct type. * * type structType struct { * * rtype * * pkgPath name * * fields []structField // sorted by offset * * } * * Struct field * * type structField struct { * * name name // name is always non-empty * * typ *rtype // type of field * * offsetEmbed uintptr // byte offset of field<<1 | isEmbedded * * } */ if (kind == KIND_STRUCT) { Address structField = getAddressAt(typeAddress.add(5 * binaryPointerSize + 8 + 8)); List fields = new ArrayList<>(); int fieldLength = getInt(typeAddress.add(6 * binaryPointerSize + 8 + 8)); for (int i = 0; i < fieldLength; i++) { Address structFieldName = getAddressAt(structField); Address nameLengthAddress = structFieldName.add(lengthOffset); nameLength = getByte(nameLengthAddress); nameAddress = getAddressAt(structField).add(lengthOffset + 1); removeData(nameAddress, nameLength); name = createAsciiString(nameAddress, nameLength); String temp = (String) name.getValue(); setEOLComment(structField, temp); createLabel(structFieldName, temp.replace(" ", "_"), true); Address newType = getAddressAt(structField.add(binaryPointerSize)); structField = structField.add(3 * binaryPointerSize); fields.add(name.getValue() + " " + getSymbolAt(newType).getName()); String comment = "type " + nameType + " struct{" + "\n\t" + "\n\t"; for (int j = 0; j < fields.size(); j++) { comment += fields.get(j) + "\n\t"; } comment += "\n" + "}"; setPreComment(typeAddress, comment); } } } catch (Exception ex) { // Ignore errors } return null; } /** * Fetches the typeLinksStart, typeLinksEnd, and typeStart addresses from the * binary if it is a PE file.
*
* Returns an array of Address objects which contains the following values, in * order:
*
    *
  1. typeLinksStart
  2. *
  3. typeLinksEnd
  4. *
  5. typeStart
  6. *
* * @param pclntab_magic the potential magic values for the pclntab * @return the typeLinksStart, typeLinksEnd, and typeStart addresses * @throws MemoryAccessException if memory within the binary, or offsets * thereof, cannot be properly accessed */ private Address[] scanPeFile() throws MemoryAccessException { // Searches for the pclntab in a PE file Object[] pclntabArray = findPclntabPE(); /* * If the pclntab cannot be found, the analyst is informed in the called * function. Simply return null in order for the script to return back to the * main function, which will return early if null is encountered. */ if (pclntabArray == null) { return null; } // Saves the results in two variables Address pclntab = (Address) pclntabArray[0]; String magic = (String) pclntabArray[1]; // Searches for the module data Address moduleData = findModuleData(pclntab, magic); /** * Similar to the missing pclntab, if the module data cannot be found, the * analyst is informed in the called function, and null is to be handed to the * caller which ensures execution ends early. */ if (moduleData == null) { return null; } // Save the results in four variables Object[] typeLinksArray = getTypeLinks(moduleData, magic); // If an error occurred earlier on, return early if (typeLinksArray == null) { return null; } Address typeStart = (Address) typeLinksArray[0]; // Address typeEnd = (Address) typeLinksArray[1]; Address typeLinksStart = (Address) typeLinksArray[2]; int numberOfTypes = (int) typeLinksArray[3]; // Calculate the end of the type links section Address typeLinksEnd = typeLinksStart.add(numberOfTypes * 4); // Return all addresses in an array return new Address[] { typeLinksStart, typeLinksEnd, typeStart }; } /** * Fetches the typeLinksStart, typeLinksEnd, and typeStart addresses from the * binary if it is an ELF file.
*
* Returns an array of Address objects which contains the following values, in * order:
*
    *
  1. typeLinksStart
  2. *
  3. typeLinksEnd
  4. *
  5. typeStart
  6. *
* * @param pclntab_magic the potential magic values for the pclntab * @return the typelinks start, the typelinks end (plus one byte), and type * start address */ private Address[] scanElfFile() { // Get the type link array Address[] typeLinkArray = getSection(".typelink"); // Get the read-only data section Address typeStart = getSection(".rodata")[0]; /* * If either the type link or read-only sections cannot be found, the analyst is * informed and null is returned by the respective function. To cut the * execution short, this function simply returns null to its caller. */ if (typeLinkArray == null || typeStart == null) { return null; } // Store the start and end in two variables Address typelinksStart = typeLinkArray[0]; Address typeLinksEnd = typeLinkArray[1]; typeLinksEnd = typeLinksEnd.add(1); // Return the values in an array return new Address[] { typelinksStart, typeLinksEnd, typeStart }; } /** * Fetches the typeLinksStart, typeLinksEnd, and typeStart addresses from the * binary if it is a MACH-O file.
*
* Returns an array of Address objects which contains the following values, in * order:
*
    *
  1. typeLinksStart
  2. *
  3. typeLinksEnd
  4. *
  5. typeStart
  6. *
* * @param pclntab_magic the potential magic values for the pclntab * @return the typelinks start, the typelinks end (plus one byte), and type * start address */ private Address[] scanMachoFile() { // Get the type link array Address[] typeLinkArray = getSection("__typelink"); // Get the read-only data section Address typeStart = getSection("__rodata")[0]; /* * If either the type link or read-only sections cannot be found, the analyst is * informed and null is returned by the respective function. To cut the * execution short, this function simply returns null to its caller. */ if (typeLinkArray == null || typeStart == null) { return null; } // Store the start and end in two variables Address typelinksStart = typeLinkArray[0]; Address typeLinksEnd = typeLinkArray[1]; typeLinksEnd = typeLinksEnd.add(1); // Return the values in an array return new Address[] { typelinksStart, typeLinksEnd, typeStart }; } /** * Recovers all types present within the program * * @param typeLinksStart the start of the typelinks * @param typeLinksEnd the end of the typelinks plus one byte * @param type the start of the types * @throws Exception if something goes wrong */ private void recoverAllTypes(Address typeLinksStart, Address typeLinksEnd, Address type) { // Ensure the type link is not null if (typeLinksStart != null) { // Declare and initialise a copy of the start address Address p = typeLinksStart; // Iterate until the end of the type links section is reached while (p.compareTo(typeLinksEnd) <= 0) { // Check if the script's execution is cancelled if (monitor.isCancelled()) { // Break this loop, thus exiting the script's execution early break; } try { // Get the type offset by dereferencing the address pointer int typeOffset = getInt(p); // Get the type address based on the offset Address typeAddress = type.add(typeOffset); // Recover the types for the given address recoverTypes(typeAddress, type); } catch (Exception ex) { // Ignore exceptions } // Increment the address, which is always 32 bits p = p.add(4); } } } }