mirror of
https://github.com/advanced-threat-research/GhidraScripts
synced 2026-06-08 13:03:27 +00:00
Release of the Golang related Ghidra scripts
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user