Release of the Golang related Ghidra scripts

This commit is contained in:
ThisIsLibra
2023-06-06 14:39:46 +02:00
parent dc608023af
commit 83a7924bd1
5 changed files with 2734 additions and 0 deletions
+642
View File
@@ -0,0 +1,642 @@
//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;
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.</br>
* </br>
* The default value of this field is <code>true</code>.
*/
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 <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);
}
}
/**
* 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 {
// 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, disregarding the
* used casing. The list can be empty, but never null.
*
* @return all .text named memory blocks, disregarding the used casing
*/
private List<MemoryBlock> getTextMemoryBlocks() {
// Declare and initialise the list
List<MemoryBlock> 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")) {
// 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);
}
}
}
}
+628
View File
@@ -0,0 +1,628 @@
//Finds and creates functions with their original names, in Golang based PE and ELF files. Functions which have already been found by Ghidra will be renamed if a suitable name is found.
//@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/go_func.py)
//@category Golang
//@keybinding
//@menupath
//@toolbar
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressOutOfBoundsException;
import ghidra.program.model.listing.Data;
import ghidra.program.model.listing.Function;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.symbol.SourceType;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.exception.InvalidInputException;
public class GolangFunctionRecovery 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 mask to perform the logical AND with on the magic value
*/
private static final int MAGIC_MASK = 0xffffffff;
/**
* The magic value for Golang 1.20 and above
*
* @see <a href=
* "https://github.com/golang/go/blob/master/src/debug/gosym/pclntab.go">Golang
* pclntab source code</a>
*/
private static final int GO_120 = 0xfffffff1;
/**
* The magic value for Golang 1.18
*
* @see <a href=
* "https://github.com/golang/go/blob/master/src/debug/gosym/pclntab.go">Golang
* pclntab source code</a>
*/
private static final int GO_118 = 0xfffffff0;
/**
* The magic value for Golang 1.16 through version 1.17
*
* @see <a href=
* "https://github.com/golang/go/blob/master/src/debug/gosym/pclntab.go">Golang
* pclntab source code</a>
*/
private static final int GO_116 = 0xfffffffa;
/**
* The magic value for Golang 1.2 through version 1.15
*
* @see <a href=
* "https://github.com/golang/go/blob/master/src/debug/gosym/pclntab.go">Golang
* pclntab source code</a>
*/
private static final int GO_12 = 0xfffffffb;
/**
* 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 amount of functions which were recovered
*/
private static int functionCount = 0;
@Override
protected void run() throws Exception {
// Get the executable format of the sample
String executableFormat = currentProgram.getExecutableFormat();
// Declare the pclntab variable
Address pclntab;
// Check if the executable format is a PE file
if (executableFormat.equalsIgnoreCase("Portable Executable (PE)")) {
// Optionally print a message to state the file type which has been detected
log("PE file found");
// The declaration and initialisation of potential pclntab magic values
String[] pclntabMagicValues = { "\\xfb\\xff\\xff\\xff\\x00\\x00", "\\xfa\\xff\\xff\\xff\\x00\\x00",
"\\xf0\\xff\\xff\\xff\\x00\\x00" };
// Get the gopclntab address by magic value
pclntab = getGopclntabByMagicValue(pclntabMagicValues);
} else if (executableFormat.equalsIgnoreCase("Executable and Linking Format (ELF)")) { // Check if the
// executable format is
// an ELF file
// Optionally print a message to state the file type which has been detected
log("ELF file found");
// Get the gopclntab address by section name
pclntab = getGopclntabBySectionName();
} else {
/*
* Print an error message informing the user of the failure to find a suitable
* executable format
*/
printerr("Unspported file format: " + executableFormat);
// Return, thus ending the script's execution
return;
}
// If the pclntab could not be found, the script ends
if (pclntab == null) {
// Print an error message with the reason of the failure
printerr("Cannot find the pclntab!");
// End the script's execution
return;
}
/*
* If execution continues, the pclntab was found. Optionally print a message to inform the
* user of the progress
*/
log(String.format("pclntab found at 0x%x!", pclntab.getOffset()));
// Declare and initialise the pclntab magic value
int magic = getInt(pclntab) & MAGIC_MASK;
// Recover function names for functions in Golang version 1.20 and above
if (magic == GO_120) {
println("Golang 1.20 found, note that this script is experimental for this Golang version!");
recoverFunctionNamesGo118Plus(pclntab);
} else if (magic == GO_118) {
// Recover function names for functions in Golang version 1.18 and above
recoverFunctionNamesGo118Plus(pclntab);
} else if (magic == GO_116) { // Determine if the magic value matches Golang version 1.16 and 1.17
// Recover function names for functions in Golang versions 1.16 and 1.17
renameFunc116(pclntab);
} else if (magic == GO_12) {// Determine if the magic value matches Golang 1.15 through version 1.2
/*
* Recover function names for functions in Golang version 1.15 through version
* 1.2
*/
recoverFunctionNamesGo12(pclntab);
} else {
// No matching magic value was found, of which the user is informed
println("Unable to determine the .gopclntab magic value, so the assumption is made that it is Go 1.2 compatible");
// Recover function names for functions in Golang version 1.15 through version
// 1.2
recoverFunctionNamesGo12(pclntab);
}
/*
* Inform the analyst of the total number of functions which has been renamed
* and/or created
*/
println("Total number of functions renamed and/or created: " + functionCount);
}
/**
* 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 gopclntab starting address based on a found magic value, if any
*
* @param pclntabMagicValues possible magic values as byte strings written as
* strings (i.e. "\xab\xbc")
* @return the starting address of the gopclntab if it is found, null if it is
* not found
* @throws MemoryAccessException
* @throws AddressOutOfBoundsException
*/
private Address getGopclntabByMagicValue(String[] pclntabMagicValues)
throws MemoryAccessException, AddressOutOfBoundsException {
// Iterate over all magic values
for (String magic : pclntabMagicValues) {
/*
* Look for the magic bytes within the current program, starting at the minimum
* address, with a maximum of 100 results
*/
Address[] pclntabs = findBytes(null, magic, 100);
// Iterate over all results
for (Address pclntab : pclntabs) {
/*
* Bytes have been found based on the given magic value
*/
// Get the instruction's size quantum
byte instructionSizeQuantum = getByte(pclntab.add(6));
// Get the pointer size
byte pointerSize = getByte(pclntab.add(7));
/*
* Verify if both the instruction's quantum size and pointer size match the
* conditions, meaning the pclntab has been found
*/
if ((instructionSizeQuantum != INSTRUCTION_SIZE_ONE && instructionSizeQuantum != INSTRUCTION_SIZE_TWO
&& instructionSizeQuantum != INSTRUCTION_SIZE_FOUR) == false
|| (pointerSize != POINTER_SIZE_X86 && pointerSize != POINTER_SIZE_X64) == false) {
return pclntab;
}
}
}
// If no results match the criteria, null is returned
return null;
}
/**
* Gets the gopclntab by its section name (being ".gopclntab")
*
* @return the starting address of the ".gopclntab" section
*/
private Address getGopclntabBySectionName() {
// Iterate over all memory blocks within the program
for (MemoryBlock memoryBlock : getMemoryBlocks()) {
// Check if the block's name equals (ignoring the casing) the gopclntab section
if (memoryBlock.getName().equalsIgnoreCase(".gopclntab")) {
// Return the starting address of this section if it is found
return memoryBlock.getStart();
}
}
// Return null if the section is not found
return null;
}
/**
* Creates a new function, or renames the function if it already exists, based
* on the newly found name, which is obtained via the name address variable
*
* @param functionAddress the address of the function
* @param nameAddress the address of the function's new name
* @throws DuplicateNameException
* @throws InvalidInputException
*/
private void createOrRenameFunction(Address functionAddress, Address nameAddress)
throws DuplicateNameException, InvalidInputException {
// Check if the variable is instantiated
if (nameAddress == null) {
// Return from the function if this is the case
return;
}
// Get the data at the given address
Data functionNameData = getDataAt(nameAddress);
// If no data resides at this address
if (functionNameData == null) {
try {
// Create an ASCII string within Ghidra
functionNameData = createAsciiString(nameAddress);
} catch (Exception e) {
// Print an error if the ASCII string creation fails
printerr(String.format("Unable to create an ASCII string at 0x%x!", nameAddress.getOffset()));
// Return from the function if this fails
return;
}
}
/*
* Get the function name by getting the data's value, which in this case is a
* String but needs to be cast as the getValue function returns an Object
*/
String functionName = (String) functionNameData.getValue();
// If the function name is null, blank, or empty
if (functionName == null || functionName.isBlank()) {
// Print an error
printerr(String.format("No function name found at 0x%x!", Long.toHexString(nameAddress.getOffset())));
// Return from the function
return;
}
// Gets the function at the given address
Function func = getFunctionAt(functionAddress);
// If there is a function at the given address
if (func != null) {
// Get the old name
String functionNameOld = func.getName();
// Rename the function with the new name, without spaces
func.setName(functionName.replace(" ", ""), SourceType.USER_DEFINED);
// Optionally print the function name change, along with the location
log("Function renamed from \"" + functionNameOld + "\" to \"" + functionName + "\", located at 0x"
+ Long.toHexString(functionAddress.getOffset()));
} else {
// If no function exists at the given address, create one
func = createFunction(functionAddress, functionName);
// Optionally print the function name and address
log("Function \"" + functionName + "\" created at 0x" + Long.toHexString(functionAddress.getOffset()));
}
// Increment the function count
functionCount++;
}
/**
* Recovers function names for functions in Golang version 1.15 through version
* 1.2
*
* @param pclntab the start address of the pclntab
* @throws MemoryAccessException
* @throws AddressOutOfBoundsException
* @throws DuplicateNameException
* @throws InvalidInputException
*/
private void recoverFunctionNamesGo12(Address pclntab)
throws MemoryAccessException, AddressOutOfBoundsException, DuplicateNameException, InvalidInputException {
// Get the pointer size
byte pointerSize = getByte(pclntab.add(7));
// Declare the number of functions tab variable
long nFunctionTab;
// If the pointer size fits a x64 system
if (pointerSize == POINTER_SIZE_X64) {
// Get a long value from the given address
nFunctionTab = getLong(pclntab.add(8));
} else { // Assume x86, meaning 4 bytes in size
// Get an integer value from the given address
nFunctionTab = getInt(pclntab.add(8));
}
// Get the function tab address
Address functionTab = pclntab.add(8 + pointerSize);
// Declare a copy of the function tab, named p
Address p = functionTab;
// Declare the function address variable
Address functionAddress;
// Declare the name offset variable
long nameOffset;
// Iterate over the number of functions
for (int i = 0; i < nFunctionTab; i++) {
// Check if the script's execution is cancelled
if (monitor.isCancelled()) {
// Break this loop, thus exiting the script's execution early
break;
}
// If the pointer size fits a x64 system
if (pointerSize == POINTER_SIZE_X64) {
// Get the function address
functionAddress = currentProgram.getAddressFactory().getAddress(Long.toHexString(getLong(p)).trim());
// Increment p with the pointer size to move it to the next usable address
p = p.add(pointerSize);
// Get the name offset as a long, since the architecture is x64
nameOffset = getLong(p);
} else { // Assume x86, meaning 4 bytes in size
// Get the function address
functionAddress = currentProgram.getAddressFactory().getAddress(Long.toHexString(getInt(p)));
// Increment p with the pointer size to move it to the next usable address
p = p.add(pointerSize);
// Get the name offset as an integer, since the architecture is x86
nameOffset = getInt(p);
}
// Increment p with the pointer size to move it to the next usable address
p = p.add(pointerSize);
/*
* Gets the name pointer, which is located directly after the function name,
* hence the addition of the name offset and the pointer size to move to the
* next usable address
*/
Address namePointer = pclntab.add(nameOffset + pointerSize);
// Get the address of the name, based on the pointer
/*
* TODO is this a mistake as it uses getInt instead of getInt OR getLong based
* on the pointer size?
*/
Address nameAddress = pclntab.add(getInt(namePointer));
// Address nameAddress = pclntab.add(namePointer.getOffset());
/*
* Create or rename the function at the address, with the name at the given
* address
*/
createOrRenameFunction(functionAddress, nameAddress);
}
}
/**
* Recovers function names for functions in Golang version 1.16 and version 1.17
*
* @param pclntab the start address of the pclntab
* @throws MemoryAccessException
* @throws AddressOutOfBoundsException
* @throws DuplicateNameException
* @throws InvalidInputException
*/
private void renameFunc116(Address pclntab)
throws MemoryAccessException, AddressOutOfBoundsException, DuplicateNameException, InvalidInputException {
// Get the size of the pointer
byte pointerSize = getByte(pclntab.add(7));
// Declare variables, whose value will depend on the architecture
long nFunctionTab;
long offset;
Address functionNameTab;
// If the pointer's size is equal to the size of a pointer on a x64 system
if (pointerSize == POINTER_SIZE_X64) {
// Get the corresponding long value
nFunctionTab = getLong(pclntab.add(8));
// Calculate the next offset
offset = getLong(pclntab.add(8 + 2 * pointerSize));
// Get the function name tab's address
functionNameTab = pclntab.add(offset);
// Calculate the next offset
offset = getLong(pclntab.add(8 + 6 * pointerSize));
} else { // Assume x86, meaning 4 bytes in size
// Get the corresponding integer value
nFunctionTab = getInt(pclntab.add(8));
// Calculate the next offset
offset = getInt(pclntab.add(8 + 2 * pointerSize));
// Get the function name tab's address
functionNameTab = pclntab.add(offset);
// Calculate the next offset
offset = getInt(pclntab.add(8 + 6 * pointerSize));
}
// Get the address of the function tab
Address functionTab = pclntab.add(offset);
// Declare and initiate a copy of the function tab
Address p = functionTab;
// Declare several variables for later use
Address functionAddress;
long functionDataOffset;
Address namePointer;
Address nameAddress;
// Iterate over the number of functions in the tab
for (int i = 0; i < nFunctionTab; i++) {
// Check if the script's execution is cancelled
if (monitor.isCancelled()) {
// Break this loop, thus exiting the script's execution early
break;
}
// If the pointer size is one of a x64 system
if (pointerSize == POINTER_SIZE_X64) {
// Get the function's address
functionAddress = currentProgram.getAddressFactory().getAddress(Long.toHexString(getLong(p)).trim());
// Adjust the offset
p = p.add(pointerSize);
// Get the function data's offset
functionDataOffset = getLong(p);
} else { // Assume x86, meaning 4 bytes in size
// Get the function's address
functionAddress = currentProgram.getAddressFactory().getAddress(Long.toHexString(getInt(p)).trim());
// Adjust the offset
p = p.add(pointerSize);
// Get the function data's offset
functionDataOffset = getInt(p);
}
// Move p to the next address
p = p.add(pointerSize);
// Get the function name pointer
namePointer = functionTab.add(functionDataOffset + pointerSize);
// Get the address of the function name
/*
* TODO check if the getInt needs to just get the offset from the name pointer
* instead?
*/
nameAddress = functionNameTab.add(getInt(namePointer));
/*
* Create or rename the function at the address, with the name at the given
* address
*/
createOrRenameFunction(functionAddress, nameAddress);
}
}
/**
* Recovers function names for functions in Golang version 1.18 and above
*
* @param pclntab the start address of the pclntab
* @throws MemoryAccessException
* @throws AddressOutOfBoundsException
* @throws DuplicateNameException
* @throws InvalidInputException
*/
private void recoverFunctionNamesGo118Plus(Address pclntab)
throws MemoryAccessException, AddressOutOfBoundsException, DuplicateNameException, InvalidInputException {
// Get the pointer size
byte pointerSize = getByte(pclntab.add(7));
// Declare several variables
long nFunctionTab;
long textStart;
long offset;
Address functionNameTab;
// Check if the pointer size matches a x64 system's pointer size
if (pointerSize == POINTER_SIZE_X64) {
// Get the number of functions tab address
nFunctionTab = getLong(pclntab.add(8));
// Get the start of the text
textStart = getLong(pclntab.add(8 + 2 * pointerSize));
// Calculate the next offset
offset = getLong(pclntab.add(8 + 3 * pointerSize));
// Get the address of the function name tab
functionNameTab = pclntab.add(offset);
// Calculate the next offset
offset = getLong(pclntab.add(8 + 7 * pointerSize));
} else { // Assume x86, meaning 4 bytes in size
// Get the number of functions tab address
nFunctionTab = getInt(pclntab.add(8));
// Get the start of the text
textStart = getInt(pclntab.add(8 + 2 * pointerSize));
// Calculate the next offset
offset = getInt(pclntab.add(8 + 3 * pointerSize));
// Get the address of the function name tab
functionNameTab = pclntab.add(offset);
// Calculate the next offset
offset = getInt(pclntab.add(8 + 7 * pointerSize));
}
// Get the address of the function tab
Address functionTab = pclntab.add(offset);
// Instantiate a copy of the function tab for later use
Address p = functionTab;
// Define the field size within the function tab, which is always 4
int functabFieldSize = 4;
// Declare several variables for later use
Address functionAddress;
int functionDataOffset;
Address namePointer;
Address nameAddress;
// Iterate over all functions
for (int i = 0; i < nFunctionTab; i++) {
// Check if the script's execution is cancelled
if (monitor.isCancelled()) {
// Break this loop, thus exiting the script's execution early
break;
}
// Get the address for the current function
/*
* TODO check if the split needs to be made here as well, since it uses getInt
* instead
*/
functionAddress = currentProgram.getAddressFactory()
.getAddress(Long.toHexString(getInt(p) + textStart).trim());
// Adjust p
p = p.add(functabFieldSize);
// Get the function data offset
// TODO is this a mistake since its an int in all cases?
functionDataOffset = getInt(p);
// Adjust p
p = p.add(functabFieldSize);
// Get the pointer to the name
namePointer = functionTab.add(functionDataOffset + functabFieldSize);
// Get the pointer to the address
/*
* TODO check if the address is correct since it uses getInt instead of a
* pointer value check
*/
nameAddress = functionNameTab.add(getInt(namePointer));
/*
* Create or rename the function at the address, with the name at the given
* address
*/
createOrRenameFunction(functionAddress, nameAddress);
}
}
}
+19
View File
@@ -0,0 +1,19 @@
//Runs all four Golang analysis scripts, based on their names. If no such script is found, an error is printed and the next script is executed.
//@author Max 'Libra' Kersten of Trellix' Advanced Research Center
//@category Golang
//@keybinding
//@menupath
//@toolbar
import ghidra.app.script.GhidraScript;
public class GolangRecovery extends GhidraScript {
@Override
protected void run() throws Exception {
runScript("GolangFunctionRecovery");
runScript("GolangStaticStringRecovery");
runScript("GolangDynamicStringRecovery");
runScript("GolangTypeRecovery");
}
}
+254
View File
@@ -0,0 +1,254 @@
//Finds and creates statically 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_static_strings.py)
//@category Golang
//@keybinding
//@menupath
//@toolbar
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.IntegerDataType;
import ghidra.program.model.data.PointerDataType;
import ghidra.program.model.listing.Data;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.mem.MemoryBlock;
public class GolangStaticStringRecovery 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 size of a pointer on X64
*/
private static final int POINTER_SIZE_X64 = 8;
/*
* #x86
*
* #LEA REG, [STRING_ADDRESS]
*
* #MOV [ESP + ..], REG
*
* #MOV [ESP + ..], STRING_SIZE
*/
@Override
protected void run() throws Exception {
// Declare and initialise the number of recovered static strings
int stringCount = 0;
/*
* Store the imagebase's offset and the pointer size as they are reused multiple
* times
*/
long imageBaseOffset = currentProgram.getImageBase().getOffset();
int pointerSize = currentProgram.getDefaultPointerSize();
// Iterate over all memory blocks
for (MemoryBlock block : getMemoryBlocks()) {
/*
* If the block name is not .data or .rodata, it can be skipped, as static
* strings are only present in the data sections
*/
if (block.getName().equalsIgnoreCase(".data") == false
&& block.getName().equalsIgnoreCase(".rodata") == false) {
continue;
}
// Get the start of the block
Address blockStart = block.getStart();
// Get the end of the block
Address blockEnd = block.getEnd();
/*
* Run as long as the start is less than, or equal to the end address, thus
* ensuring the whole block is iterated over
*/
while (blockStart.compareTo(blockEnd) <= 0) {
// Check if the script's execution is cancelled
if (monitor.isCancelled()) {
// Return from the run function, thus exiting the script's execution early
return;
}
// Declare the string address variable
Address stringAddress;
// Declare and initialises the variable
Address stringAddressPointer = blockStart;
// Get the length address
Address lengthAddress = blockStart.add(pointerSize);
// Increment the start of the block
blockStart = blockStart.add(pointerSize);
/*
* The next segment of the code is within a try-catch structure. The reason for
* this is simple: the static string recovery strategy does not work in all
* cases. An exception is simply ignored, as the catch segment simply continues
* to the next step. Since the start of the block is compared to the end of the
* block, the whole section is iterated over, meaning that any error just moves
* over to the next piece of memory within the block.
*/
try {
// Declare the length variable
long length;
// Check if the pointer size matches a x64 pointer's size
if (pointerSize == POINTER_SIZE_X64) {
// Get the long value at the given address
length = getLong(lengthAddress);
} else { // Assume the binary is x86
// Get the integer value at the given address
length = getInt(lengthAddress);
}
/*
* To avoid false positives, strings which have no length, or are longer than
* 100 characters, are considered incorrect and thus skipped
*/
if (length <= 0 || length > 100) {
continue;
}
// Check if the pointer size matches a x64 pointer's size
if (pointerSize == POINTER_SIZE_X64) {
// Get the long at the given string address pointer
stringAddress = currentProgram.getAddressFactory()
.getAddress(Long.toHexString(getLong(stringAddressPointer)));
} else {// Assume the binary is x86
// Get the integer at the given string address pointer
stringAddress = currentProgram.getAddressFactory()
.getAddress(Long.toHexString(getInt(stringAddressPointer)));
}
/*
* If the address offset is less than the image base offset, the current attempt
* is faulty and needs to be skipped
*/
if (stringAddress.getOffset() < imageBaseOffset) {
continue;
}
/*
* Check if the string is printable. If it is not, the current address needs to
* be skipped
*/
if (isPrintable(stringAddress, length) == false) {
continue;
}
// Create a pointer to the string
createData(stringAddressPointer, PointerDataType.dataType);
// Get the length
Data data = getDataAt(lengthAddress);
/*
* If there is no data type defined at the given address, it needs to be created
*/
if (data == null) {
data = createData(lengthAddress, IntegerDataType.dataType);
}
// Get the type of the data
DataType dataType = data.getDataType();
// Get the name of the data type
String dataTypeName = dataType.getName();
/*
* If the data type is an undefined type of 4 or 8 bytes in size, it is to be
* removed, as a new type is to be set
*/
if (dataTypeName.equalsIgnoreCase("undefined4") || dataTypeName.equalsIgnoreCase("undefined8")) {
removeData(getDataAt(lengthAddress));
}
// Create an integer at the given address
createData(lengthAddress, IntegerDataType.dataType);
/*
* Create the ASCII string at the given address with the given length (cast to a
* boxed long to use the intValue function)
*/
Data stringData = createAsciiString(stringAddress, ((Long) length).intValue());
// Get the string value as a string
String string = (String) stringData.getValue();
// Optionally pPrint the location and the string value for the user
log("0x" + Long.toHexString(stringAddress.getOffset()) + " : \"" + string + "\"");
// Increment the number of recovered strings
stringCount++;
} catch (Exception e) {
/*
* Exceptions are bound to happen due to some of the what more crude approaches,
* but they can simply be skipped
*/
continue;
}
}
}
// Inform the analyst of the number of recovered strings
println("Total number of recovered static strings: " + stringCount);
}
/**
* Checks if a string, starting at the given address with the given length, is
* printable. Printable in this context means that the value of each byte of the
* string is between 32 and 126
*
* @param start the address of the start of the string
* @param length the length of the string
* @return true if the complete string is printable, false if not
* @throws MemoryAccessException
*/
private boolean isPrintable(Address start, long length) throws MemoryAccessException {
// Iterate over the complete string
for (int i = 0; i < length; i++) {
// Get the current byte
byte b = getByte(start);
// Check the byte's value
if (b < 32 || b > 126) {
// If any of the bytes has the wrong value, return false early
return false;
}
// Increment the string's address by one
start = start.add(1);
}
/*
* If the early return isn't hit and the complete string has been iterated over,
* it means the complete string is printable, thus true needs to be returned
*/
return true;
}
/**
* 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);
}
}
}
File diff suppressed because it is too large Load Diff