diff --git a/Sharem.java b/Sharem.java new file mode 100644 index 0000000..669762d --- /dev/null +++ b/Sharem.java @@ -0,0 +1,467 @@ +//Runs SHAREM and gets the resulting output, which is then annotated within Ghidra. This fixes dissassembly mistakes, adds context and arguments to function calls, and creates data points. +//@author Trellix (by Max ' Libra' Kersten) +//@category shellcode analysis +//@keybinding +//@menupath +//@toolbar + +import java.io.File; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.util.Arrays; + +import com.google.gson.Gson; + +import ghidra.app.script.GhidraScript; +import ghidra.program.model.address.Address; +import ghidra.program.model.data.ByteDataType; +import ghidra.program.model.data.DWordDataType; +import ghidra.program.model.data.PointerDataType; +import ghidra.program.model.data.QWordDataType; +import ghidra.program.model.data.StringDataType; +import ghidra.program.model.data.WordDataType; +import ghidra.program.model.listing.Data; + +public class Sharem extends GhidraScript { + + @Override + protected void run() throws Exception { + // The directory in which the command is executed, to be edited prior to using the script + File workingDirectory = new File("C:\\path\\to\\sharem"); + // The command to execute within said working directory, to be edited prior to using the script + String command = "C:\\path\\to\\python.exe main.py [architecture as in -r32 or -r64] C:\\path\\to\\shellcode.bin"; + + try { + /* + * Executes the given command from within the given working directory. This + * function only returns once the command has been executed. + */ + execute(workingDirectory, command); + } catch (Exception ex) { + // Print the error message + printerr(ex.getMessage()); + // Return early + return; + } + + // Instantiate a new Gson object for later use + Gson gson = new Gson(); + + // Declare the JSON file variable, based on the working directory + File jsonFile = new File( + workingDirectory.getAbsolutePath() + "\\sharem\\sharem\\sharem\\logs\\default\\jsondefaultdisasm.json"); + + // Read the file and store the result in a string + String json = Files.readString(jsonFile.toPath()); + + // Convert the raw JSON into a Java object + SharemObject result = gson.fromJson(json, SharemObject.class); + + // Iterate over the objects + for (SharemSubObject object : result.getObjects()) { + // If a given object's comment is not null, empty, nor white space + if (object.getComment().isBlank() == false) { + // Get the offset in hexadecimal format + long offset = Long.parseLong(object.getAddress().substring(2), 16); + // Set a comment at the given offset, with the given comment + setPreComment(toAddr(offset), object.getComment()); + // Create a string to print debug information + String message = "Commented \"" + object.getComment() + "\" at " + object.getAddress(); + // Print the message + println(message); + } + + // If the type is CODE + if (object.getBytes().equalsIgnoreCase("CODE")) { + // Get the value of the bytes + byte[] sharemValues = getBytesFromSharemObject(object); + if (sharemValues == null) { + continue; + } + // Get the bytes from Ghidra's listing + byte[] ghidraValues = getBytes(toAddr(object.getAddress()), object.getSize()); + // If the values aren't equal + if (Arrays.compare(sharemValues, ghidraValues) > 0) { + // Clear the listing + clearListing(toAddr(object.getAddress())); + // Set the bytes as provided by SHAREM + setBytes(toAddr(object.getAddress()), sharemValues); + // Disassemble the bytes + disassemble(toAddr(object.getAddress())); + } + } else if (object.getBytes().equalsIgnoreCase("DATA")) { // If the type is DATA + if (object.getDataType().equalsIgnoreCase("String")) { // if the type is a string + // Get the data at the given address + Data data = getDataAt(toAddr(object.getAddress())); + // If the data is not null + if (data != null) { + // Compare the length of the data and the size mentioned in the SHAREM object + if (data.getLength() != object.getSize()) { + // Get the end address + Address end = toAddr(object.getAddress()).add(toAddr(object.getSize()).getOffset() - 1); + // Get the start address + Address start = toAddr(object.getAddress()); + // Clear the listing + clearListing(start, end); + // Create a string at the given address + createAsciiString(toAddr(object.getAddress())); + } + } else { + // Get the end address + Address end = toAddr(object.getAddress()).add(toAddr(object.getSize()).getOffset() - 1); + // Get the start address + Address start = toAddr(object.getAddress()); + // Clear the listing + clearListing(start, end); + // If no data is present, simply create a string at the address + createData(toAddr(object.getAddress()), StringDataType.dataType); + } + } else if (object.getDataType().equalsIgnoreCase("API Pointer")) { // If the type is a pointer + // Get the data at the address + Data data = getDataAt(toAddr(object.getAddress())); + // If the data is not present + if (data == null) { + // Create the pointer + createData(toAddr(object.getAddress()), PointerDataType.dataType); + } + // Set a comment with the instruction to provide context + setPreComment(toAddr(object.getAddress()), object.getInstruction()); + } else if (object.getDataType().equalsIgnoreCase("DATA")) { + // Get the data at the address + Data data = getDataAt(toAddr(object.getAddress())); + + /* + * If the data is not null, it exists, and can thus be removed, since it will be + * overwritten + */ + if (data != null) { + // Get the end address + Address end = toAddr(object.getAddress()).add(toAddr(object.getSize()).getOffset() - 1); + // Get the start address + Address start = toAddr(object.getAddress()); + // Clear the listing + clearListing(start, end); + } + + // Handle different sizes with regards to data creation + switch (object.getSize()) { + case 1: + createData(toAddr(object.getAddress()), ByteDataType.dataType); + break; + case 2: + createData(toAddr(object.getAddress()), WordDataType.dataType); + break; + case 4: + createData(toAddr(object.getAddress()), DWordDataType.dataType); + break; + case 8: + createData(toAddr(object.getAddress()), QWordDataType.dataType); + break; + } + + } + } + } + } + + /** + * A helper function to get the bytes from the given disassembly object's hex + * value field which contains the instruction in hexadecimal format + * + * @param object the disassembly object + * @return the raw bytes of the given instruction, if any + */ + private byte[] getBytesFromSharemObject(SharemSubObject object) { + byte[] bytes = new byte[object.getSize()]; + String[] values = object.getHex().split(" "); + + for (int i = 0; i < values.length; i++) { + try { + bytes[i] = (byte) Integer.parseInt(values[i], 16); + } catch (Exception ex) { + /* + * Ignore entries where the value ends with dots, as those aren't hex values. + * Returning null ensures the caller to omit these bytes + */ + return null; + } + + } + return bytes; + } + + /** + * Determines if the current operating system is Windows. + * + * @return true if the operating system is Windows, false if it is not. + */ + public static boolean isWindows() { + /* + * If the os.name property of the Java VM contains "windows", the system is + * Windows based + */ + if (System.getProperty("os.name").toLowerCase().contains("windows")) { + return true; + } + return false; + } + + /** + * A helper function to launch a new process via the system's shell + * + * @param workingDirectory the working directory of the process + * @param command the command to execute + * @throws Exception if anything goes wrong + */ + public void execute(File workingDirectory, String command) throws Exception { + try { + String[] processName = new String[1]; + // Check if the OS is windows + if (isWindows()) { + processName[0] = "cmd"; + } else { // Use the shell if the OS is not Windows + processName[0] = "sh"; + } + // Start a new shell + Process p = Runtime.getRuntime().exec(processName, null, workingDirectory); + // Get the standard input + PrintWriter stdin = new PrintWriter(p.getOutputStream()); + // Start the command via the shell + stdin.println(command); + // Close the stream + stdin.close(); + // Wait until the process terminates + p.waitFor(); + } catch (Exception ex) { + /* + * Throw an exception if anything goes wrong, which is used to notify the + * analyst + */ + throw new Exception("Error while launching SHAREM! Error:\n\n" + ex.getMessage()); + } + } + + class SharemSubObject { + + /** + * The starting address within the binary where the reference to is made + */ + private String address; + + /** + * The instruction, as extracted by SHAREM + */ + private String instruction; + + /** + * The hex value of the instruction + */ + private String hex; + + /** + * The size of the object (i.e. the length of a string, or the size of an + * instruction) + */ + private String size; + + /** + * Is either CODE or DATA, indicating what the type of + * the object is + */ + private String bytes; + + /** + * Provides further information about the data type, if this object references + * DATA. The optional values are String or API Pointer + */ + private String dataType; + + /** + * Defines how data is accessed + */ + private String dataAccessed; + + /** + * Gets the string representation of the data (at the given address with the + * given length). This can be garbage when not dealing with strings (i.e. + * instructions) + */ + private String string; + + /** + * Gets a SHAREM made comment for this object + */ + private String comment; + + /** + * A label which contains an address, if present. Redundant field. + */ + private String label; + + /** + * Creates an instance of a single instruction/piece of data that is emitted by + * SHAREM. + * + * @param address the starting address within the binary where the + * reference to is made + * @param instruction the instruction, as extracted by SHAREM + * @param hex the hex value of the instruction + * @param size the size of the object (i.e. the length of a string, or + * the size of an instruction) + * @param bytes is either CODE or DATA, + * indicating what the type of the object is + * @param dataType provides further information about the data type, if this + * object references DATA. The optional values are + * String or API Pointer + * @param dataAccessed defines how data is accessed + * @param string gets the string representation of the data (at the given + * address with the given length). This can be garbage when + * not dealing with strings (i.e. instructions) + * @param comment gets a SHAREM made comment for this object + * @param label a label which contains an address, if present. Redundant + * field. + */ + public SharemSubObject(String address, String instruction, String hex, String size, String bytes, + String dataType, String dataAccessed, String string, String comment, String label) { + this.address = address; + this.instruction = instruction; + this.hex = hex; + this.size = size; + this.bytes = bytes; + this.dataType = dataType; + this.dataAccessed = dataAccessed; + this.string = string; + this.comment = comment; + this.label = label; + } + + /** + * The starting address within the binary where the reference to is made + * + * @return the address as a string + */ + public String getAddress() { + return address; + } + + /** + * The instruction, as extracted by SHAREM + * + * @return the instruction as a string + */ + public String getInstruction() { + return instruction; + } + + /** + * The hex value of the instruction + * + * @return the hex value as a string (split by spaces, not denoted by "0x") + */ + public String getHex() { + return hex; + } + + /** + * The size of the object (i.e. the length of a string, or the size of an + * instruction) + * + * @return the size as a string + */ + public int getSize() { + return Integer.parseInt(size); + } + + /** + * Is either CODE or DATA, indicating what the type of + * the object is + * + * @return the type, as a string + */ + public String getBytes() { + return bytes; + } + + /** + * Provides further information about the data type, if this object references + * DATA. The optional values are String or API Pointer + * + * @return the more granular type, as a string + */ + public String getDataType() { + return dataType; + } + + /** + * Defines how data is accessed + * + * @return the way the data is accessed, as a string + */ + public String getDataAccessed() { + return dataAccessed; + } + + /** + * Gets the string representation of the data (at the given address with the + * given length). This can be garbage when not dealing with strings (i.e. + * instructions) + * + * @return the value of the object + */ + public String getString() { + return string; + } + + /** + * Gets a SHAREM made comment for this object + * + * @return the comment + */ + public String getComment() { + return comment; + } + + /** + * A label which contains an address, if present. Redundant field. + * + * @return the label as a string + */ + public String getLabel() { + return label; + } + } + + /** + * The JSON output from SHAREM is an array of items. For ease-of-access and + * ease-of-handling, a Java class is used as a wrapper around the array + * + * @author Max 'Libra' Kersten for Trellix + * + */ + class SharemObject { + + /** + * The array of entries from SHAREM + */ + private SharemSubObject[] disassembly; + + /** + * Creates an instance of this wrapper object + * + * @param disassembly the array of objects which contain the disassembly of the + * analysed shellcode + */ + public SharemObject(SharemSubObject[] disassembly) { + this.disassembly = disassembly; + } + + /** + * Gets the SHAREM objects + * + * @return the SHAREM objects + */ + public SharemSubObject[] getObjects() { + return disassembly; + } + } +}