// This is a Ghidra script for adding string literals that may not be recognized by Ghidra. // // Usage: // Before the script process, you need to notice the JSON file including necessary information. It's automatically // saved in "<HOMEDIR>/.ghidra_scripts/ShortStringAdder.json". // // The JSON file needs to be deserialized into `ConfigReader` class, here is its format: // // { // "minimum_string_length": 3, // "maximum_search_range": 256, // 0 represents no check for adjacent strings // "separators": [0], // "extra_regex": ["\u001b\\[([0-9]+;)*([0-9]+)m([\\x20-\\x7e]|\\t|\\n|\\r|(\u001b\\[([0-9]+;)*([0-9]+)m))*"] // } // // - minimum_string_length: The minimum length of string that will be recognized and added. // - maximum_search_range: We will search if there exist other strings frontward and backward, it's the maximum range. // - separators: The separators of string literal. In some programming language (like rust), string literals might // not end with "\0". You can add ascii integer value here to add separators. // - extra_regex: some regex strings that you may want to define them as strings (even if they may have some unprintable // chars), like UNIX color control (Added by default). // //@author Hornos - Hornos3.github.com, hornos@hust.edu.cn //@category Strings
if (!has_adjacent_strings(addr)) { printf("Address %#x (%s) has no adjacent string, skipped\n", addr.getOffset(), created); continue; }
try { currentProgram.getListing().createData(addr, dt); printf("Created a string \"%s\" at %#x.\n", created, addr.getOffset()); currentProgram.getListing().setComment(addr, EOL_COMMENT, "Script-added string literal"); } catch (CodeUnitInsertionException e) { printf("Error while adding string at %#x, skipped.\n", addr.getOffset()); errorAddrs.add(addr); e.printStackTrace(); } }
if (errorAddrs.isEmpty()) printf("\n0 exception occurred0.\n"); else { printf("\n%d exception occurred, you may need to handle it manually.\n", errorAddrs.size()); println("At: "); for (Address errorAddr : errorAddrs) printf("%#x\n", errorAddr.getOffset()); } println(); }
// check if the dir exists, if not, create it if (!dir.exists()) { println("[-] ~/.ghidra_scripts not found, will create..."); if (!dir.mkdirs()) { println("[!] Failed to create ~/.ghidra_scripts"); return; } }
// check if the file exists, if not, create it if (!file.exists()) { println("[-] ~/.ghidra_scripts/config.json not found, will create..."); try (FileWriterwriter=newFileWriter(file)) { // default configs, the regex is for UNIX color control (like \033[1;31m...\033[0m) writer.write("{\n" + " \"minimum_string_length\": 3,\n" + " \"maximum_search_range\": 255,\n" + " \"separators\": [0],\n" + " \"extra_regex\": [\"\\u001b\\\\[([0-9]+;)*([0-9]+)m([\\\\x20-\\\\x7e]|\\\\t|\\\\n|\\\\r|(\\u001b\\\\[([0-9]+;)*([0-9]+)m))*\"]\n" + "}"); println("[-] Default config created, you can change it in ~/.ghidra_scripts/ShortStringAdder.json."); } catch (IOException e) { println("[!] Failed to create ~/.ghidra_scripts/ShortStringAdder.json: " + e.getMessage()); thrownewException("Create config file failed"); } }
if (currentProgram.getListing().getDataContaining(addr) != null) { if (currentProgram.getListing().getDataContaining(addr).isDefined()) returnnull; } if (currentProgram.getListing().getInstructionContaining(addr) != null) { // TODO: get strings wrongly identified as codes returnnull; }
try { if (currentProgram.getMemory().contains(addr.subtract(1))){ byteprevious_byte= currentProgram.getMemory().getByte(addr.subtract(1)); if (!this.config.separators.contains(previous_byte)) returnnull; } } catch (MemoryAccessException e) { printf("Failed to read byte located in %#x\n", addr.subtract(1).getOffset()); e.printStackTrace(); returnnull; } catch (AddressOutOfBoundsException ignored) {}
Addressptr= addr; intoffset=0; while (true) { try { ptr = addr.add(offset); byteb= currentProgram.getMemory().getByte(ptr); // all printable character (including \t, \n, \r), if conflicted with separators, the latter is dominant if (((b >= 0x20 && b <= 0x7e) || b == 9 || b == 10 || b == 0xd) && !this.config.separators.contains(b)) { ret.append((char) (b & 0xff)); offset++; } elseif (!this.config.separators.contains(b)) // It ends with neither a printable nor a separator returnnull; else break; } catch (MemoryAccessException e) { printf("Failed to read byte located in %#x\n", ptr.getOffset()); e.printStackTrace(); returnnull; } }
if (offset >= config.minimum_string_length) return ret.toString(); returnnull; }
private TreeMap<Address, String> merge_regex_result(TreeMap<String, TreeMap<Address, String>> results) { // This method is used for selecting the primary matches while discarding overlapping matches // E.g. There are 3 matches at 0x0~0x10, 0x9~0x15, 0x12~0x20, then the second one will be discarded. TreeMap<Address, String> ret = newTreeMap<>(); for (Map.Entry<String, TreeMap<Address, String>> entry: results.entrySet()) ret.putAll(entry.getValue());
// eliminate overlapping matches longlargestRangeUpperLimit= -1; Iterator<Map.Entry<Address, String>> iter = ret.entrySet().iterator(); while (iter.hasNext()) { Map.Entry<Address, String> entry = iter.next(); if (entry.getKey().getOffset() + entry.getValue().length() > largestRangeUpperLimit) largestRangeUpperLimit = entry.getKey().getOffset() + entry.getValue().length(); else { printf("Warning: %s (at %#x) removed due to overlapping memory address.\n", entry.getValue(), entry.getKey().getOffset()); iter.remove(); } }