Files
ThisIsLibra 862c38de5b Ghidra Golang script update
-MachO binary support (MacOS file type)
-Golang 1.20 support
-Fixed numerous to-do statements
2023-10-11 14:55:25 +02:00

1228 lines
42 KiB
Java

//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.</br>
* </br>
* The default value of this field is <code>true</code>.
*/
private static final boolean ENABLE_LOGGING = true;
/**
* The numeric value for the function kind, as defined in the Golang language
* specification
*
* @see <a href="https://go.dev/src/reflect/type.go">The Golang source code for
* the Type class, see the constant list under <code>type Kind uint</code>
* for the complete list</a>
*/
private static final int KIND_FUNCTION = 0x13;
/**
* The numeric value for the interface kind, as defined in the Golang language
* specification
*
* @see <a href="https://go.dev/src/reflect/type.go">The Golang source code for
* the Type class, see the constant list under <code>type Kind uint</code>
* for the complete list</a>
*/
private static final int KIND_INTERFACE = 0x14;
/**
* The numeric value for the pointer kind, as defined in the Golang language
* specification
*
* @see <a href="https://go.dev/src/reflect/type.go">The Golang source code for
* the Type class, see the constant list under <code>type Kind uint</code>
* for the complete list</a>
*/
private static final int KIND_POINTER = 0x16;
/**
* The numeric value for the struct kind, as defined in the Golang language
* specification
*
* @see <a href="https://go.dev/src/reflect/type.go">The Golang source code for
* the Type class, see the constant list under <code>type Kind uint</code>
* for the complete list</a>
*/
private static final int KIND_STRUCT = 0x19;
/**
* The default PC Quantum size (minimal instruction size), used in x86, x86_64,
* and WASM
*
* @see <a href=
* "https://github.com/golang/gofrontend/blob/master/libgo/goarch.sh">Golang
* architecture documentation</a>
*/
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 <a href=
* "https://github.com/golang/gofrontend/blob/master/libgo/goarch.sh">Golang
* architecture documentation</a>
*/
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 <a href=
* "https://github.com/golang/gofrontend/blob/master/libgo/goarch.sh">Golang
* architecture documentation</a>
*/
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 <code>1.2</code> is less than <code>1.16</code>, 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:
*
* <ol>
* <li>Version 1.2</li>
* <li>Version 1.16</li>
* <li>Version 1.18</li>
* <li>Version 1.20</li>
* </ol>
*
* @see <a href="https://go.dev/src/debug/gosym/pclntab.go">Golang pclntab
* source code</a>
*/
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.<br>
* <br>
* 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 <code>O(1)</code>,
* in contrast to the inner workings of other types, which often work in
* <code>O(n)</code>.<br>
* <br>
* 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<Address> 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 <code>true</code>. 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.<br>
* <br>
* An example of a byte string is <code>\xfb\xff\xff\xff\x00\x00</code>.
*
* @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.<br>
* <br>
* For versions until and including 1.16, the second byte is used, which causes
* the script to potentially miss long strings.<br>
* <br>
* 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 <code>length()<code> 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.<br>
* <br>
* Returns an array of objects which contains the following types and values, in
* order:<br>
* <ol>
* <li>Address typeStart</li>
* <li>Address typeEnd</li>
* <li>Address typeLinks</li>
* <li>int numberOfTypes</li>
* </ol>
*
* @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<String> inputs = new ArrayList<>();
List<String> 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<String> 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<String> 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.<br>
* <br>
* Returns an array of Address objects which contains the following values, in
* order:<br>
* <ol>
* <li>typeLinksStart</li>
* <li>typeLinksEnd</li>
* <li>typeStart</li>
* </ol>
*
* @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.<br>
* <br>
* Returns an array of Address objects which contains the following values, in
* order:<br>
* <ol>
* <li>typeLinksStart</li>
* <li>typeLinksEnd</li>
* <li>typeStart</li>
* </ol>
*
* @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.<br>
* <br>
* Returns an array of Address objects which contains the following values, in
* order:<br>
* <ol>
* <li>typeLinksStart</li>
* <li>typeLinksEnd</li>
* <li>typeStart</li>
* </ol>
*
* @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);
}
}
}
}