/* ### * IP: GHIDRA * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * Given certain Key windows API calls, tries to create references at the use of windows Resources. * This script uses the decompiler and the simplified Pcode AST to locate constant values passed to key * functions like LoadStringW, LoadIconW, etc... * * The guts of this script past the main could be used to analyze * constants passed to any function on any processor. * It is not restricted to windows. * * The assumption is made that default program analysis has already been run in order to retrieve * the best results from this script. * @category Windows */ import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import ghidra.app.decompiler.*; import ghidra.app.decompiler.component.DecompilerUtils; import ghidra.app.script.GhidraScript; import ghidra.program.model.address.*; import ghidra.program.model.listing.*; import ghidra.program.model.pcode.*; import ghidra.program.model.symbol.*; import ghidra.program.model.util.AddressSetPropertyMap; import ghidra.util.UndefinedFunction; import ghidra.util.exception.*; public class WindowsResourceReference extends GhidraScript { private static final String WINDOWS_RESOURCE_CHECKED_PROPERTYMAP = "WindowsResourceChecked"; private DecompInterface decomplib; ArrayList
routines = new ArrayList<>(); //Holds the address of found resource routines ArrayList paramIndexes = new ArrayList<>(); //Holds the index of resource arguments on the stack ArrayList> defUseLists = new ArrayList<>(); protected AddressSetPropertyMap alreadyDoneAddressSetPropertyMap; // set of functions that decompilation failed on protected AddressSet badDecompFunctions = new AddressSet(); public AddressSetPropertyMap getOrCreatePropertyMap(Program program, String mapName) { if (alreadyDoneAddressSetPropertyMap != null) { return alreadyDoneAddressSetPropertyMap; } alreadyDoneAddressSetPropertyMap = program.getAddressSetPropertyMap(mapName); if (alreadyDoneAddressSetPropertyMap != null) { return alreadyDoneAddressSetPropertyMap; } try { alreadyDoneAddressSetPropertyMap = program.createAddressSetPropertyMap(mapName); } catch (DuplicateNameException e) { throw new AssertException( "Can't get DuplicateNameException since we tried to get it first"); } return alreadyDoneAddressSetPropertyMap; } @Override public void run() throws Exception { // This code was added so that the analyzer (which calls a script) would not print script messages but if // run as a script it would still show output in the console. // It was also added to get the createBookmark option from the analyzer options. // The printScriptMsgs flag is checked every time the script tries to print. // The createBookmarks flag is checked every time the script tries to make a bookmark. // This also allows headless scripts to print if no args are passed but if they want no messages they // should pass the argument "false". // This also allows headless scripts to create bookmarks if no arguments are passed but if they want no // bookmarks they should pass the argument "false". // These are the default values if no arguments set them. boolean printScriptMsgs = true; boolean createBookmarks = true; // This gets the first argument if there are one or more arguments. String[] scriptArgs = getScriptArgs(); if (scriptArgs.length >= 1) { if ("false".equals(scriptArgs[0])) { printScriptMsgs = false; } } // This gets the second argument if there is one. if (scriptArgs.length == 2) { if ("false".equals(scriptArgs[1])) { createBookmarks = false; } } AddressSetView restrictedSet = currentSelection; getOrCreatePropertyMap(currentProgram, WINDOWS_RESOURCE_CHECKED_PROPERTYMAP); // If this is the whole address space, look at everything // and ignore already done property if (restrictedSet == null || restrictedSet.isEmpty() || restrictedSet.hasSameAddresses(currentProgram.getMemory())) { restrictedSet = null; alreadyDoneAddressSetPropertyMap.clear(); } // If this is a partial address set, then ignore anywhere with a done it property try { decomplib = setUpDecompiler(currentProgram); if (decomplib == null) { if (printScriptMsgs) { println("Decompile Error: " + decomplib.getLastMessage()); } return; } // Hold address and lookup constant value pairs for each resource lookup HashMap constLocs; // Set of Resource name lookups. If unknown or variable Rsrc_ name // Rsrc_* wildcard allowed except when calling addResourceTableReferences() constLocs = associateResource("AfxMessageBox", 1, restrictedSet, printScriptMsgs); addResourceTableReferences(constLocs, "Rsrc_StringTable", printScriptMsgs, createBookmarks); constLocs = associateResource("CreateDialogParamA", 2, restrictedSet, printScriptMsgs); addResourceReferences(constLocs, "Rsrc_Dialog", printScriptMsgs, createBookmarks); constLocs = associateResource("CreateDialogParamW", 2, restrictedSet, printScriptMsgs); addResourceReferences(constLocs, "Rsrc_Dialog", printScriptMsgs, createBookmarks); constLocs = associateResource("DialogBoxParamA", 2, restrictedSet, printScriptMsgs); addResourceReferences(constLocs, "Rsrc_Dialog", printScriptMsgs, createBookmarks); constLocs = associateResource("DialogBoxParamW", 2, restrictedSet, printScriptMsgs); addResourceReferences(constLocs, "Rsrc_Dialog", printScriptMsgs, createBookmarks); constLocs = associateResource("FindResourceA", 2, restrictedSet, printScriptMsgs); addResourceReferences(constLocs, "Rsrc_*", printScriptMsgs, createBookmarks); constLocs = associateResource("FindResourceW", 2, restrictedSet, printScriptMsgs); addResourceReferences(constLocs, "Rsrc_*", printScriptMsgs, createBookmarks); constLocs = associateResource("FindResourceHandle", 2, restrictedSet, printScriptMsgs); addResourceReferences(constLocs, "Rsrc_*", printScriptMsgs, createBookmarks); constLocs = associateResource("LoadAcceleratorsA", 2, restrictedSet, printScriptMsgs); addResourceReferences(constLocs, "Rsrc_Accelerator", printScriptMsgs, createBookmarks); constLocs = associateResource("LoadAcceleratorsW", 2, restrictedSet, printScriptMsgs); addResourceReferences(constLocs, "Rsrc_Accelerator", printScriptMsgs, createBookmarks); constLocs = associateResource("LoadBitmapA", 2, restrictedSet, printScriptMsgs); addResourceReferences(constLocs, "Rsrc_Bitmap", printScriptMsgs, createBookmarks); constLocs = associateResource("LoadBitmapW", 2, restrictedSet, printScriptMsgs); addResourceReferences(constLocs, "Rsrc_Bitmap", printScriptMsgs, createBookmarks); constLocs = associateResource("LoadCursorA", 2, restrictedSet, printScriptMsgs); addResourceReferences(constLocs, "Rsrc_*", printScriptMsgs, createBookmarks); constLocs = associateResource("LoadCursorW", 2, restrictedSet, printScriptMsgs); addResourceReferences(constLocs, "Rsrc_*", printScriptMsgs, createBookmarks); constLocs = associateResource("LoadIconA", 2, restrictedSet, printScriptMsgs); addResourceReferences(constLocs, "Rsrc_GroupIcon", printScriptMsgs, createBookmarks); constLocs = associateResource("LoadIconW", 2, restrictedSet, printScriptMsgs); addResourceReferences(constLocs, "Rsrc_GroupIcon", printScriptMsgs, createBookmarks); constLocs = associateResource("LoadImageA", 2, restrictedSet, printScriptMsgs); addResourceReferences(constLocs, "Rsrc_*", printScriptMsgs, createBookmarks); constLocs = associateResource("LoadImageW", 2, restrictedSet, printScriptMsgs); addResourceReferences(constLocs, "Rsrc_*", printScriptMsgs, createBookmarks); constLocs = associateResource("RegLoadMUIStringW", 6, restrictedSet, printScriptMsgs); addResourceReferences(constLocs, "Rsrc_MUI", printScriptMsgs, createBookmarks); constLocs = associateResource("LoadMenuA", 2, restrictedSet, printScriptMsgs); addResourceTableReferences(constLocs, "Rsrc_Menu", printScriptMsgs, createBookmarks); constLocs = associateResource("LoadMenuW", 2, restrictedSet, printScriptMsgs); addResourceReferences(constLocs, "Rsrc_Menu", printScriptMsgs, createBookmarks); constLocs = associateResource("LoadRegTypeLib", 2, restrictedSet, printScriptMsgs); addResourceReferences(constLocs, "Rsrc_*", printScriptMsgs, createBookmarks); constLocs = associateResource("LoadStringA", 2, restrictedSet, printScriptMsgs); addResourceTableReferences(constLocs, "Rsrc_StringTable", printScriptMsgs, createBookmarks); constLocs = associateResource("LoadStringW", 2, restrictedSet, printScriptMsgs); addResourceTableReferences(constLocs, "Rsrc_StringTable", printScriptMsgs, createBookmarks); constLocs = associateResource("LoadTypeLib", 2, restrictedSet, printScriptMsgs); addResourceReferences(constLocs, "Rsrc_*", printScriptMsgs, createBookmarks); constLocs = associateResource("LoadTypeLibEx", 2, restrictedSet, printScriptMsgs); addResourceReferences(constLocs, "Rsrc_*", printScriptMsgs, createBookmarks); constLocs = associateResource("PlaySoundW", 1, restrictedSet, printScriptMsgs); addResourceReferences(constLocs, "Rsrc_WAVE", printScriptMsgs, createBookmarks); } finally { decomplib.dispose(); } } /** * Associates a resource name with the name and ID of that resource * @param resourceRoutine - Name of the resource routine * @param paramIndex - Argument index of windows function call for resource lookup * @param restrictedSet - Address space to use * @param printScriptMsgs - if true, print output; if false, do not print any output; * @return HashMap map of addresses */ private HashMap associateResource(String resourceRoutine, int paramIndex, AddressSetView restrictedSet, boolean printScriptMsgs) { HashMap constUse = new HashMap<>(); Symbol symbol = lookupRoutine(resourceRoutine, printScriptMsgs); if (symbol == null) { return constUse; } //Continue along if a symbol was found routines.add(symbol.getAddress()); paramIndexes.add(paramIndex); ArrayList defUseList = new ArrayList<>(); defUseLists.add(defUseList); HashSet
doneRoutines = new HashSet<>(); //Have a list of routines found based on symbol lookups while (routines.size() > 0) { // get the next routine to lookup Address addr = routines.remove(0); paramIndex = paramIndexes.remove(0); defUseList = defUseLists.remove(0); if (doneRoutines.contains(addr)) { continue; } doneRoutines.add(addr); // Get the list of references to this address ReferenceIterator referencesTo = currentProgram.getReferenceManager().getReferencesTo(addr); for (Reference reference : referencesTo) { if (monitor.isCancelled()) { break; } // Get the address of the function which is referenced Address refAddr = reference.getFromAddress(); // if set is null, do no checks if (restrictedSet != null && !restrictedSet.contains(refAddr)) { continue; } // was this location already checked? if (alreadyDoneAddressSetPropertyMap != null) { if (alreadyDoneAddressSetPropertyMap.contains(refAddr)) { continue; } alreadyDoneAddressSetPropertyMap.add(refAddr, refAddr); } Function refFunc = currentProgram.getFunctionManager().getFunctionContaining(refAddr); if (refFunc == null) { refFunc = UndefinedFunction.findFunction(currentProgram, refAddr, monitor); } // this is an indirect reference, need to add the references to here. if (refFunc == null && reference.isExternalReference()) { routines.add(reference.getFromAddress()); paramIndexes.add(paramIndex); defUseLists.add(new ArrayList()); continue; } if (refFunc == null) { continue; } // decompile function // look for call to this function // display call @SuppressWarnings("unchecked") ArrayList localDefUseList = (ArrayList) defUseList.clone(); monitor.setMessage( "Analyzing : " + refFunc.getName() + " for refs to " + resourceRoutine); analyzeFunction(constUse, decomplib, currentProgram, refFunc, refAddr, paramIndex, localDefUseList); } } return constUse; } /** * Checks to see if the current resource routine is found in the * programs symbol table. * @param resourceRoutine - Name of the resource routine * @param printScriptMsgs - if true, print output; if false, do not print any output; * @return Symbol - found symbol based on resourceRoutine */ private Symbol lookupRoutine(String resourceRoutine, boolean printScriptMsgs) { Symbol foundSym = null; // Get the symbols that match the current resource routine SymbolIterator symbols = currentProgram.getSymbolTable().getSymbols(resourceRoutine); while (symbols.hasNext()) { //If a match is found get the function at the address of the found symbol foundSym = symbols.next(); Function functionAt = currentProgram.getFunctionManager().getFunctionAt(foundSym.getAddress()); if (functionAt != null) { return foundSym; } } if (foundSym != null && printScriptMsgs) { println("References to the " + resourceRoutine + " routine:"); } return foundSym; } /** * Analyze a functions references. * Populates the address/value pairs of resource address and ID * @param constUse - resource address/value pairs */ public void analyzeFunction(HashMap constUse, DecompInterface decompiler, Program prog, Function f, Address refAddr, int paramIndex, ArrayList defUseList) { if (f == null) { return; } // check if decompilation of this function failed previously if (badDecompFunctions.contains(f.getEntryPoint())) { return; } Instruction instr = prog.getListing().getInstructionAt(refAddr); if (instr == null) { return; } decompileFunction(f, decompiler); if (hfunction == null) { // failed to decompile, add to bad list badDecompFunctions.add(f.getEntryPoint()); return; } Iterator ops = hfunction.getPcodeOps(refAddr); while (ops.hasNext()) { if (monitor.isCancelled()) { break; } PcodeOpAST pcodeOpAST = ops.next(); if (pcodeOpAST.getOpcode() == PcodeOp.CALL) { // get the second parameter Varnode parm = pcodeOpAST.getInput(paramIndex); // 1st param is the call dest if (parm == null) { return; } // see if it is a constant if (parm.isConstant()) { // then this is a resource id // lookup the resource and create a reference long value = parm.getOffset(); // TODO: not so fast, if there is a defUseList, must apply it to get the real constant USED! try { value = applyDefUseList(value, defUseList); constUse.put(instr.getAddress(), value); } catch (InvalidInputException exc) { // don't worry about error } } else { followToParam(constUse, defUseList, hfunction, parm, null); } // if this is anything else, get the high variable to see if it can be traced back to a param // then repeat with any calls to this function at whatever the param is } } } /** * Decompile the function * @param f * @param decompiler * @return boolean - true if successful */ public boolean decompileFunction(Function f, DecompInterface decompiler) { // don't decompile the function again if it was the same as the last one if (f.getEntryPoint().equals(lastDecompiledFuncAddr)) { return true; } DecompileResults decompRes = decompiler.decompileFunction(f, decompiler.getOptions().getDefaultTimeout(), monitor); hfunction = decompRes.getHighFunction(); if (hfunction == null) { return false; } lastDecompiledFuncAddr = f.getEntryPoint(); return true; } /** * Prints out the address of a found resource along with the name of * the resource. * @param constLocs - Address value pairs of the resource function * @param resourceName - name of the resource * @param printScriptMsgs - if true, print output; if false, do not print any output; * @param createBookmarks - if true, create bookmarks where references are found; if false, do not create any bookmarks; * @throws CancelledException */ private void addResourceReferences(HashMap constLocs, String resourceName, boolean printScriptMsgs, boolean createBookmarks) throws CancelledException { Set
keys; keys = constLocs.keySet(); for (Address loc : keys) { monitor.checkCancelled(); Instruction instr = currentProgram.getListing().getInstructionAt(loc); long rsrcID = constLocs.get(loc); Address rsrcAddr = findResource(resourceName + "_" + Long.toHexString(rsrcID), 0); if (rsrcAddr != null) { //Get the full symbol name including constant digits String symName = getSymbolAt(rsrcAddr).getName(); //Match on the name without the constant digit values String pattern = "([a-z]|[A-Z])*_?([a-z]|[A-Z])*(_[A-Z]+[a-z]+)?"; Pattern r = Pattern.compile(pattern); Matcher m = r.matcher(symName); //Default to resourceName argument passed in unless found better match below String rsrcName = resourceName; if (m.find()) { rsrcName = m.group(); } instr.addMnemonicReference(rsrcAddr, RefType.DATA, SourceType.ANALYSIS); if (createBookmarks) { currentProgram.getBookmarkManager() .setBookmark(instr.getMinAddress(), BookmarkType.ANALYSIS, "WindowsResourceReference", "Added Resource Reference"); } if (printScriptMsgs) { println(" " + instr.getMinAddress().toString() + " : Found " + rsrcName + " reference"); } } } } /** * Prints out the address of a found resource along with the name of * the resource. * @param constLocs - Address value pairs of the resource function * @param tableName - Name of the resource table * @param printScriptMsgs - if true, print output; if false, do not print any output; * @param createBookmarks - if true, create bookmarks where references are found; if false, do not create any bookmarks; * @throws CancelledException */ private void addResourceTableReferences(HashMap constLocs, String tableName, boolean printScriptMsgs, boolean createBookmarks) throws CancelledException { Set
keys; //Get the set of address locations which call the resource function keys = constLocs.keySet(); //Iterate though the set of address locations for (Address loc : keys) { monitor.checkCancelled(); Instruction instr = currentProgram.getListing().getInstructionAt(loc); Long rsrcID = constLocs.get(loc); Address rsrcAddr = null; if (rsrcID != null) { rsrcAddr = findResource(tableName, rsrcID); } if (rsrcAddr != null) { instr.addMnemonicReference(rsrcAddr, RefType.DATA, SourceType.ANALYSIS); if (createBookmarks) { currentProgram.getBookmarkManager() .setBookmark(instr.getMinAddress(), BookmarkType.ANALYSIS, "WindowsResourceReference", "Added Resource Table Reference"); } if (printScriptMsgs) { println(" " + instr.getMinAddress().toString() + " : Found " + tableName + " table reference " + rsrcID); } } } } /** * Returns the address of the resource located in the program * @param tableName - Name of the resource table * @param rsrcID - ID of the resource to find * @return Address of the found resource * @throws CancelledException */ private Address findResource(String tableName, long rsrcID) throws CancelledException { SymbolIterator siter = currentProgram.getSymbolTable().getSymbolIterator(tableName + "*", true); if (!siter.hasNext()) { return null; } // Search each table while (siter.hasNext()) { monitor.checkCancelled(); Symbol sym = siter.next(); String symName = sym.getName(); long curRsrcID = rsrcID; if (rsrcID != 0) { symName = symName.replaceAll(tableName + "_", ""); //Find the specific table number of the resource String pattern = "_+[0-9]+"; Pattern r = Pattern.compile(pattern); Matcher m = r.matcher(symName); String hexString = ""; if (m.find()) { //Strip off the constant value to leave just the resource number hexString = symName.replaceAll(m.group(), ""); } curRsrcID = Integer.parseInt(hexString, 16); curRsrcID = (curRsrcID - 1) * 0x10; curRsrcID = rsrcID - curRsrcID; if (curRsrcID > 0x10) { continue; // tables have 16 entries } if (curRsrcID < 0) { continue; } } Address currItemAddr = sym.getAddress(); Data data = currentProgram.getListing().getDataAt(currItemAddr); while (curRsrcID > 0) { currItemAddr = data.getAddress(); data = currentProgram.getListing().getDataAfter(currItemAddr); if (data == null || !data.isDefined()) { break; } curRsrcID--; } if (data == null) { continue; } if (curRsrcID == 0) { return data.getAddress(); } } return null; } private long applyDefUseList(long value, ArrayList defUseList) throws InvalidInputException { if (defUseList == null || defUseList.size() <= 0) { return value; } for (PcodeOp pcodeOp : defUseList) { int opcode = pcodeOp.getOpcode(); switch (opcode) { case PcodeOp.INT_AND: if (pcodeOp.getInput(0).isConstant()) { value = value & pcodeOp.getInput(0).getOffset(); } else if (pcodeOp.getInput(1).isConstant()) { value = value & pcodeOp.getInput(1).getOffset(); } else { throw new InvalidInputException( " Unhandled Pcode OP " + pcodeOp.toString()); } break; default: throw new InvalidInputException(" Unhandled Pcode OP " + pcodeOp.toString()); } } return value; } /** * Finds the parameter passed into the resource function call * @param constUse - Key value pairs of the resource function calls * @param defUseList * @param highFunction * @param vnode * @param doneSet */ private void followToParam(HashMap constUse, ArrayList defUseList, HighFunction highFunction, Varnode vnode, HashSet doneSet) { HighVariable hvar = vnode.getHigh(); if (hvar instanceof HighParam) { Function function = highFunction.getFunction(); routines.add(function.getEntryPoint()); paramIndexes.add(((HighParam) hvar).getSlot() + 1); defUseLists.add(defUseList); return; } // follow back up through PcodeOp def = vnode.getDef(); if (def == null) { return; } // have we done this vnode source already? Address vPCAddr = vnode.getPCAddress(); SequenceNumber seqnum = def.getSeqnum(); if (doneSet == null) { doneSet = new HashSet<>(); } if (seqnum != null && doneSet.contains(seqnum)) { return; } doneSet.add(seqnum); int opcode = def.getOpcode(); switch (opcode) { case PcodeOp.COPY: if (def.getInput(0).isConstant()) { long value = def.getInput(0).getOffset(); try { value = applyDefUseList(value, defUseList); constUse.put(def.getOutput().getPCAddress(), value); } catch (InvalidInputException exc) { // ignore } return; } followToParam(constUse, defUseList, highFunction, def.getInput(0), doneSet); return; case PcodeOp.INT_ZEXT: followToParam(constUse, defUseList, highFunction, def.getInput(0), doneSet); return; case PcodeOp.MULTIEQUAL: followToParam(constUse, defUseList, highFunction, def.getInput(0), doneSet); @SuppressWarnings("unchecked") ArrayList splitUseList = (ArrayList) defUseList.clone(); followToParam(constUse, splitUseList, highFunction, def.getInput(1), doneSet); return; case PcodeOp.CAST: // Cast will expose more Pcode, and could be attached to the same address! if (vPCAddr.equals(def.getInput(0).getPCAddress())) { doneSet.remove(vPCAddr); } followToParam(constUse, defUseList, highFunction, def.getInput(0), doneSet); return; case PcodeOp.INDIRECT: if (def.getOutput().getAddress().equals(def.getInput(0).getAddress())) { followToParam(constUse, defUseList, highFunction, def.getInput(0), doneSet); return; } break; case PcodeOp.INT_AND: if (def.getInput(1).isConstant()) { defUseList.add(0, def); followToParam(constUse, defUseList, highFunction, def.getInput(0), doneSet); return; } break; case PcodeOp.INT_ADD: if (def.getInput(1).isConstant()) { defUseList.add(0, def); followToParam(constUse, defUseList, highFunction, def.getInput(0), doneSet); return; } if (vnode.getHigh() instanceof HighParam) { // don't handle non-constants for now } break; } // println(" Lost IT! " + vnode.getPCAddress()); } @SuppressWarnings("unused") private boolean isHexDigit(char charAt) { if (Character.isDigit(charAt)) { return true; } if ("abcdef".indexOf(charAt) >= 0) { return true; } if ("ABCDEF".indexOf(charAt) >= 0) { return true; } return false; } // Decompiler stuff private HighFunction hfunction = null; private Address lastDecompiledFuncAddr = null; private DecompInterface setUpDecompiler(Program program) { DecompileOptions options = DecompilerUtils.getDecompileOptions(state.getTool(), program); DecompInterface decompiler = new DecompInterface(); decompiler.setOptions(options); decompiler.toggleCCode(true); decompiler.toggleSyntaxTree(true); decompiler.setSimplificationStyle("decompile"); decompiler.openProgram(program); return decompiler; } }