├── .gitignore ├── AnalyzeOCMsgSend.py ├── DexFile_Parameter_Trace.py ├── LICENSE ├── README.md ├── README.zh-cn.md ├── __init__.py ├── docs ├── images │ ├── DexFile_Parameter_Trace_Logd.png │ ├── DexFile_Parameter_Trace_Script_Param.png │ ├── DexFile_Parameter_trace_Script_Output.png │ ├── analyze_oc_msg_send_pic.png │ ├── ghidra-ollvm-deobf.png │ ├── ghidra-ollvm-obf.png │ ├── trace_function_call_parm_value_pic_1.jpg │ ├── wr886nv7_rename_function_with_error_print_1.jpg │ ├── wr886nv7_rename_function_with_error_print_2.jpg │ ├── wr886nv7_rename_function_with_error_print_3.jpg │ ├── wr886nv7_rename_function_with_error_print_4.jpg │ └── wr886nv7_rename_function_with_error_print_5.jpg ├── wr886nv7_rename_function_with_error_print.md └── wr886nv7_rename_function_with_error_print.zh-cn.md ├── galaxy_utility ├── __init__.py ├── common.py └── function_analyzer.py ├── ollvm_deobf_fla.py ├── trace_function_call_parm_value.py └── wr886nv7_rename_function_with_error_print.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /AnalyzeOCMsgSend.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import ghidra.app.util.bin.format.objectiveC.ObjectiveC1_Constants as OC_CONST 3 | import logging 4 | import re 5 | 6 | OBJC_METHNAME = u'__objc_methname' 7 | OBJC_CLASSNAME = u'__objc_classname' 8 | OBJC_CLASSREFS = u'__objc_classrefs' 9 | OBJC_DATA = u'__objc_data' 10 | SYMBOLTYPE_LABEL = u'Label' 11 | SYMBOLTYPE_FUNCTION = u'Function' 12 | REFERENCETYPE_UNCONDITIONAL_CALL = u'UNCONDITIONAL_CALL' 13 | OBJCCLASSPREFIX_META = u'_OBJC_METACLASS_$_' 14 | 15 | 16 | methName_Dict = {} 17 | className_Dict = {} 18 | symbol_Dict = {} 19 | 20 | functionList = [] 21 | referenceList = [] 22 | 23 | # debug = True 24 | debug = False 25 | # Init Default Logger 26 | logger = logging.getLogger('Default_logger') 27 | logger.setLevel(logging.INFO) 28 | consolehandler = logging.StreamHandler() 29 | console_format = logging.Formatter('[%(levelname)-8s][%(module)s.%(funcName)s] %(message)s') 30 | consolehandler.setFormatter(console_format) 31 | logger.addHandler(consolehandler) 32 | 33 | if debug: 34 | logger.setLevel(logging.DEBUG) 35 | 36 | AdditionalCount = 0 37 | 38 | class Function(object): 39 | """docstring for Function""" 40 | def __init__(self, address, name): 41 | super(Function, self).__init__() 42 | self.address = address 43 | self.name = name 44 | 45 | class Reference(object): 46 | """docstring for Reference""" 47 | def __init__(self, callingAddr, fromAddr, toAddr): 48 | super(Reference, self).__init__() 49 | self.callingAddr = callingAddr 50 | self.fromAddr = fromAddr 51 | self.toAddr = toAddr 52 | 53 | class ObjcMathName(object): 54 | """docstring for ObjcMathName""" 55 | def __init__(self, cp, address, name): 56 | super(ObjcMathName, self).__init__() 57 | self.address = address 58 | self.name = name 59 | self.refTo = [] 60 | self.refIter = cp.getListing().getDataAt(address).getReferenceIteratorTo() 61 | while self.refIter.hasNext(): 62 | self.refTo.append(self.refIter.next()) 63 | 64 | def getMethName(cp, memBlk): 65 | for seg in memBlk: 66 | if seg.name == OBJC_METHNAME: 67 | global methName_Dict 68 | codeUnits = cp.getListing().getCodeUnits(seg.start,True) 69 | while codeUnits.hasNext(): 70 | cu = codeUnits.next() 71 | if cu and cu.address < seg.end: 72 | methName = ObjcMathName(cp,cu.address,cu.value) 73 | methName_Dict[cu.address] = methName 74 | else: 75 | break 76 | break 77 | 78 | class ObjcClassName(object): 79 | """docstring for ObjcClassName""" 80 | def __init__(self, cp, address, name): 81 | super(ObjcClassName, self).__init__() 82 | self.address = address 83 | self.name = name 84 | self.refTo = [] 85 | self.refIter = cp.getListing().getDataAt(address).getReferenceIteratorTo() 86 | while self.refIter.hasNext(): 87 | self.refTo.append(self.refIter.next()) 88 | 89 | def getClassName(cp, memBlk): 90 | for seg in memBlk: 91 | if seg.name == OBJC_CLASSNAME: 92 | global className_Dict 93 | codeUnits = cp.getListing().getCodeUnits(seg.start,True) 94 | while codeUnits.hasNext(): 95 | cu = codeUnits.next() 96 | if cu and cu.address < seg.end: 97 | if cu.value == u'\x01': 98 | continue 99 | className = ObjcClassName(cp,cu.address,cu.value) 100 | className_Dict[cu.address] = className 101 | else: 102 | break 103 | break 104 | 105 | class Symbol(object): 106 | """docstring for Symbol""" 107 | def __init__(self, sID, address, name, stype, parent, refs): 108 | super(Symbol, self).__init__() 109 | self.sID = sID 110 | self.address = address 111 | self.name = name 112 | self.stype = stype 113 | self.parent = parent 114 | self.refs = refs 115 | 116 | def getFuncAddrByAddr(cp,callingAddress): 117 | entryAddr = None 118 | if cp.getListing().getFunctionContaining(callingAddress): 119 | entryAddr = cp.getListing().getFunctionContaining(callingAddress).entryPoint 120 | return entryAddr 121 | 122 | def getSymbolTable(cp): 123 | symbolTable = cp.getSymbolTable() 124 | si = symbolTable.getSymbolIterator() 125 | global symbol_Dict 126 | global functionList 127 | global referenceList 128 | labelDict = {} 129 | while si.hasNext(): 130 | s = si.next() 131 | symbol = Symbol(s.getID(), s.getAddress(), s.getName(), s.getSymbolType(), s.getParentSymbol(), s.getReferences()) 132 | symbol_Dict[symbol.sID] = symbol 133 | if symbol.stype.toString() == SYMBOLTYPE_LABEL: 134 | labelDict[symbol.address.toString()] = symbol.name 135 | elif symbol.stype.toString() == SYMBOLTYPE_FUNCTION: 136 | tmpFunctionName = symbol.name 137 | if labelDict.has_key(symbol.address.toString()) and labelDict[symbol.address.toString()] != u'': # Bingo in Label 138 | funcLabel = labelDict[symbol.address.toString()] 139 | del labelDict[symbol.address.toString()] 140 | if funcLabel.find(symbol.name) != -1 and funcLabel.find(symbol.parent.name) != -1: # Got Class name and Method name 141 | if funcLabel.startswith(u'+') or funcLabel.startswith(u'-'): # Got OC Function Type (Class Func or Instance Func) 142 | tmpFunctionName = funcLabel[:1]+u'['+symbol.parent.name+u' '+symbol.name+u']' 143 | func = Function(symbol.address.toString(),tmpFunctionName) 144 | functionList.append(func) 145 | for ref in symbol.refs: 146 | if ref.referenceType.toString() == REFERENCETYPE_UNCONDITIONAL_CALL: 147 | fromFuncAddr = getFuncAddrByAddr(cp, ref.fromAddress) 148 | if fromFuncAddr: 149 | logger.debug("From: {}; At: {}; To: {}; Type:{}".format(fromFuncAddr, ref.fromAddress, ref.toAddress, ref.referenceType)) 150 | reference = Reference(ref.fromAddress, fromFuncAddr, ref.toAddress) 151 | referenceList.append(reference) 152 | 153 | # Helper to get function info by iterating instructions step by step 154 | class CurrentState(object): 155 | def __init__(self, program): 156 | self.program = program 157 | self.symbolTable = program.getSymbolTable() 158 | self.currentClassName = u'' 159 | self.currentMethodName = u'' 160 | # flag for class and method 161 | self.classFlag = False 162 | self.methodFlag = False 163 | 164 | def isValid(self): 165 | return self.currentMethodName != u'' and self.currentClassName != u'' 166 | 167 | def reset(self): 168 | self.currentClassName = u'' 169 | self.currentMethodName = u'' 170 | 171 | self.classFlag = False 172 | self.methodFlag = False 173 | 174 | def toString(self): 175 | return "[" + self.currentClassName + " " + self.currentMethodName + "]" 176 | 177 | def isCallingObjcMsgSend(instruction): 178 | if instruction.getNumOperands() != 1: 179 | return False 180 | reference = instruction.getPrimaryReference(0) 181 | if reference == None: 182 | return False 183 | if not reference.getReferenceType().isCall() and not reference.getReferenceType().isJump(): 184 | return False 185 | symbolTable = instruction.getProgram().getSymbolTable() 186 | symbol = symbolTable.getPrimarySymbol(reference.getToAddress()) 187 | return isObjcNameMatch(symbol) 188 | 189 | def isObjcNameMatch(symbol): 190 | name = symbol.getName() 191 | return name.startswith(OC_CONST.OBJC_MSG_SEND) or name == OC_CONST.READ_UNIX2003 or name.startswith("thunk" + OC_CONST.OBJC_MSG_SEND) 192 | 193 | def markupInstruction(instruction, state): 194 | fromAddress = instruction.getMinAddress() 195 | function = state.program.getListing().getFunctionContaining(fromAddress) 196 | if function == None: 197 | return 198 | state.reset() 199 | global logger 200 | insIter = state.program.getListing().getInstructions(fromAddress, False) 201 | while insIter.hasNext(): 202 | logger.debug("--Go Up--") 203 | instructionBefore = insIter.next() 204 | if not function.getBody().contains(instructionBefore.getMinAddress()): 205 | break # don't look outside of the function 206 | if not isValidInstruction(instructionBefore): 207 | continue 208 | opRefs = instructionBefore.getOperandReferences(1) 209 | logger.debug("=={} instruction: {}".format(instructionBefore.getMinAddress(),instructionBefore)) 210 | logger.debug("==opRefs: {}".format(opRefs)) 211 | if len(opRefs) != 1: 212 | continue 213 | toAddress = opRefs[0].getToAddress() 214 | block = state.program.getMemory().getBlock(toAddress) 215 | if block == None: 216 | continue 217 | space = currentProgram.getGlobalNamespace() 218 | pullNameThrough(state, toAddress) 219 | 220 | if state.isValid(): 221 | break 222 | 223 | 224 | 225 | ''' 226 | * Objective-C class and method names are stored in the 227 | * "__cstring" memory block. The strings are referenced 228 | * by either the "class" block or the "message" block. 229 | * The references are through n-levels of pointer indirection 230 | * based on the specific target (x86 vs ppc vs arm). 231 | * This method will pull the string through the pointer indirection 232 | * and set the appropriate value in the current state. 233 | ''' 234 | def pullNameThrough(state, address): 235 | block = state.program.getMemory().getBlock(address) 236 | if block == None: 237 | return None 238 | logger.debug("block name: {}".format(block.getName)) 239 | if block.getName() == OBJC_METHNAME: 240 | state.methodFlag = True 241 | return state.program.getListing().getDefinedDataAt(address).getValue() 242 | elif block.getName() == OBJC_DATA: 243 | classRwPointerAddress = state.program.getListing().getDefinedDataAt(address).getComponent(4).getValue() 244 | classRwData = state.program.getListing().getDefinedDataAt(classRwPointerAddress) 245 | classNamePointer = classRwData.getComponent(3).getValue() 246 | className = state.program.getListing().getDefinedDataAt(classNamePointer).getValue() 247 | state.classFlag = True 248 | if className: 249 | return className 250 | elif block.getName() == OBJC_CLASSREFS: 251 | pass 252 | data = state.program.getListing().getDataAt(address) 253 | if data == None: 254 | data = state.program.getListing().getDataContaining(address) 255 | if data == None: 256 | return None 257 | data = data.getComponentAt(int(address.subtract(data.getAddress()))) 258 | if data == None: 259 | return None 260 | references = data.getValueReferences() 261 | if len(references) == 0: 262 | return None 263 | if address == references[0].getToAddress(): 264 | return None # self reference 265 | name = pullNameThrough(state, references[0].getToAddress()) 266 | if state.classFlag: 267 | if state.currentClassName == u'': 268 | logger.debug("class found: {}".format(name)) 269 | state.currentClassName = name 270 | if state.methodFlag: 271 | if state.currentMethodName == u'': 272 | logger.debug("message found: {}".format(name)) 273 | state.currentMethodName = name 274 | return name 275 | 276 | def isMessageBlock(block): 277 | return block.getName() == OBJC_METHNAME 278 | 279 | def isClassBlock(block): 280 | return block.getName() == OC_CONST.OBJC_SECTION_CLASS_REFS or block.getName() == OC_CONST.OBJC_SECTION_CLASS 281 | 282 | def isValidInstruction(instruction): 283 | if instruction.getNumOperands() != 2: 284 | return False 285 | isMOV = instruction.getMnemonicString() == "MOV" # intel 286 | isLWZ = instruction.getMnemonicString() == "lwz" # powerpc 287 | isLDR = instruction.getMnemonicString() == "ldr" # arm 288 | return isMOV or isLWZ or isLDR 289 | 290 | 291 | def analyzeFunction(cp, function): 292 | insIter = cp.getListing().getInstructions(function.getBody(),True) 293 | state = CurrentState(cp) 294 | while insIter.hasNext(): 295 | curIns = insIter.next() 296 | if isCallingObjcMsgSend(curIns): 297 | logger.debug('==========Calling MsgSend==========') 298 | logger.debug("===={}: {} // {}".format(curIns.getAddress(),curIns,curIns.getComment(0))) 299 | hitFlag = False 300 | for ref in referenceList: 301 | if ref.callingAddr == curIns.getAddress(): 302 | hitFlag = True 303 | logger.debug("hit: {}, {}, {}".format(ref.fromAddr, ref.toAddr, ref.callingAddr)) 304 | break 305 | 306 | global AdditionalCount 307 | comment = curIns.getComment(0) 308 | funcClass = u'' 309 | funcMethod = u'' 310 | secondMethod = u'' 311 | if comment and comment.startswith(u'[') and comment.endswith(u']') and len(comment.split(u' ')) > 1: 312 | funcClass = comment.split(u' ')[0][1:] 313 | funcMethod = comment.split(u' ')[1][:-1] 314 | if funcClass.startswith(OBJCCLASSPREFIX_META): 315 | funcClass = funcClass[18:] 316 | funcClass = funcClass.replace(u'undefined', u'') 317 | funcMethod = funcMethod.replace(u'undefined', u'') 318 | if funcMethod == u'': 319 | markupInstruction(curIns, state) 320 | if state.isValid(): 321 | funcClass = state.currentClassName 322 | funcMethod = state.currentMethodName 323 | else: 324 | continue 325 | if funcMethod.startswith(u'performSelector'): 326 | searchObj = re.search(u'(performSelector[a-zA-Z]*:)"([a-zA-Z0-9_]*)"',funcMethod) 327 | if searchObj: 328 | secondMethod = searchObj.group(2) 329 | logger.debug("Second Method: {}".format(secondMethod)) 330 | tmpFunc = u'['+funcClass+u' '+secondMethod+u']' 331 | for func in functionList: 332 | if func.name.startswith(u'+') or func.name.startswith(u'-'): 333 | if func.name[1:] == tmpFunc: 334 | logger.debug("Second Method Found in functionList: {}".format(func.name)) 335 | logger.debug("{}, {}, {}".format(function.entryPoint, curIns.address, func.address)) 336 | ref = Reference(curIns.address, function.entryPoint, func.address) 337 | referenceList.append(ref) 338 | AdditionalCount += 1 339 | foundFlag = True 340 | break 341 | funcMethod = searchObj.group(1) 342 | searchObj = re.search(u'([a-zA-Z0-9]*:)"', funcMethod) 343 | if searchObj: 344 | funcMethod = searchObj.group(1) 345 | logger.debug("Class: {}; Method: {}".format(funcClass, funcMethod)) 346 | tmpFunc = u'' 347 | if funcClass and funcMethod: 348 | tmpFunc = u'['+funcClass+u' '+funcMethod+u']' 349 | elif funcClass == u'' and funcMethod: 350 | tmpFunc = funcMethod 351 | foundFlag = False 352 | for func in functionList: 353 | if func.name.startswith(u'+') or func.name.startswith(u'-'): 354 | if func.name[1:] == tmpFunc: 355 | logger.debug("Found in functionLsit: {}".format(func.name)) 356 | ref = Reference(curIns.address, function.entryPoint, func.address) 357 | referenceList.append(ref) 358 | AdditionalCount += 1 359 | foundFlag = True 360 | break 361 | else: 362 | if func.name == tmpFunc: 363 | logger.debug("Found in functionLsit: {}".format(func.name)) 364 | ref = Reference(curIns.address, function.entryPoint, func.address) 365 | referenceList.append(ref) 366 | AdditionalCount += 1 367 | foundFlag = True 368 | break 369 | if foundFlag: 370 | continue 371 | for item in methName_Dict: 372 | if methName_Dict[item].name == funcMethod: 373 | logger.debug("Found in __objc_methname: {}: {}".format(methName_Dict[item].address, funcMethod)) 374 | ref = Reference(curIns.address, function.entryPoint, methName_Dict[item].address) 375 | referenceList.append(ref) 376 | 377 | AdditionalCount += 1 378 | foundFlag = True 379 | break 380 | 381 | 382 | def analyzeInstructions(cp): 383 | funcIter = cp.getListing().getFunctions(True) 384 | while funcIter.hasNext(): 385 | f = funcIter.next() 386 | fName = f.getName() 387 | entry = f.getEntryPoint() 388 | if entry: 389 | logger.debug("{}: {}".format(entry, fName)) 390 | analyzeFunction(cp, f) 391 | print("Additional Methods Found: {}".format(AdditionalCount)) 392 | 393 | def analyzeFuncsAndRefs(): 394 | global methName_Dict 395 | global className_Dict 396 | global symbol_Dict 397 | global functionList 398 | global referenceList 399 | 400 | cp = currentProgram 401 | memBlk = cp.memory.blocks 402 | if memBlk: 403 | getMethName(cp, memBlk) 404 | if debug and methName_Dict: 405 | print("Method Name") 406 | for item in methName_Dict: 407 | meth = methName_Dict[item] 408 | logger.debug("{}: {}, Ref:{}".format(item,meth.name,meth.refTo)) 409 | 410 | if memBlk: 411 | getClassName(cp, memBlk) 412 | if debug and className_Dict: 413 | print("Class Name") 414 | for item in className_Dict: 415 | _class = className_Dict[item] 416 | logger.debug("{}: {}, Ref:{}".format(item,_class.name,_class.refTo)) 417 | 418 | getSymbolTable(cp) 419 | if debug and symbol_Dict: 420 | print("Symbol Table") 421 | for item in sorted(symbol_Dict): 422 | symbol = symbol_Dict[item] 423 | logger.debug("{}: {}\t{}\t{}\t{}\t{}".format(symbol.sID, symbol.address, symbol.name, symbol.stype, symbol.parent.name, symbol.refs)) 424 | 425 | for item in methName_Dict: 426 | func = Function(methName_Dict[item].address, methName_Dict[item].name) 427 | functionList.append(func) 428 | 429 | analyzeInstructions(cp) 430 | 431 | print("Function List:") 432 | for func in functionList: 433 | print("{}: {}".format(func.address, func.name)) 434 | 435 | print("Reference List:") 436 | for ref in referenceList: 437 | print("From: {}, To: {}, Address: {}".format(ref.fromAddr, ref.toAddr, ref.callingAddr)) 438 | 439 | return functionList, referenceList 440 | 441 | if __name__ == '__main__': 442 | funcList, refList = analyzeFuncsAndRefs() 443 | 444 | print("\naddress,name") 445 | for func in funcList: 446 | print("{},{}".format(func.address, func.name)) 447 | 448 | print("\nfrom,to,address") 449 | for ref in refList: 450 | print("{},{},{}".format(ref.fromAddr, ref.toAddr, ref.callingAddr)) 451 | -------------------------------------------------------------------------------- /DexFile_Parameter_Trace.py: -------------------------------------------------------------------------------- 1 | #Trace parameters of a function 2 | #@author zhuangshao 3 | #@category Dex 4 | #@keybinding 5 | #@menupath 6 | #@toolbar 7 | 8 | from ghidra.app.util.importer import MessageLog 9 | from ghidra.file.formats.android.dex.analyzer import DexHeaderFormatAnalyzer 10 | from ghidra.file.formats.android.dex.analyzer import DexAnalysisState 11 | from ghidra.file.formats.android.dex.util import DexUtil 12 | from ghidra.app.decompiler import DecompInterface, DecompileOptions, DecompileResults 13 | from ghidra.program.model.pcode import HighParam, PcodeOp, PcodeOpAST 14 | from ghidra.program.model.symbol import SymbolType 15 | 16 | import logging 17 | import struct 18 | 19 | 20 | # Init Default Logger 21 | logger = logging.getLogger('Default_logger') 22 | logger.setLevel(logging.DEBUG) 23 | consolehandler = logging.StreamHandler() 24 | console_format = logging.Formatter('[%(levelname)-8s][%(module)s.%(funcName)s] %(message)s') 25 | consolehandler.setFormatter(console_format) 26 | logger.addHandler(consolehandler) 27 | 28 | 29 | # Auxiliary 30 | Check_Funcs = [''] 31 | External_Symbols = {} 32 | Target_Funcs = ['logd', 'url'] 33 | Examined_Funcs = {} 34 | dexAnalysisState = DexAnalysisState.getState(currentProgram) 35 | DexHeader = dexAnalysisState.getHeader() 36 | def getString(string_idx): 37 | StringItem = DexHeader.getStrings().get(string_idx) 38 | return StringItem.getStringDataItem().getString() 39 | def getType(type_idx): 40 | TypeItem = DexHeader.getTypes().get(type_idx) 41 | descriptor_idx = TypeItem.getDescriptorIndex() 42 | return getString(descriptor_idx) 43 | def getField(field_idx): 44 | FieldItem = DexHeader.getFields().get(field_idx) 45 | name_idx = FieldItem.getNameIndex() 46 | field_name = getString(name_idx) 47 | class_idx = FieldItem.getClassIndex() 48 | class_name = getType(class_idx) 49 | return class_name + "." + field_name 50 | def getMethod(method_idx): 51 | MethodItem = DexHeader.getMethods().get(method_idx) 52 | name_idx = MethodItem.getNameIndex() 53 | func_name = getString(name_idx) 54 | class_idx = MethodItem.getClassIndex() 55 | class_name = getType(class_idx) 56 | return class_name + "->" + func_name 57 | def getExternalSymbols(): 58 | ExSymbols = currentProgram.getSymbolTable().getExternalSymbols() 59 | for ExSymbol in ExSymbols: 60 | External_Symbols[ExSymbol.getName()] = ExSymbol 61 | def checkFunctionList(): 62 | for Check_Func in Check_Funcs: 63 | if Check_Func in External_Symbols: 64 | print("Cun Zai: {}".format(Check_Func)) 65 | Check_Func_Symbol = External_Symbols[Check_Func] 66 | Check_Func_Symbol_External_Addr = Check_Func_Symbol.getReferences()[0].getFromAddress() 67 | Check_Func_Symbol_Refs = getReferencesTo(Check_Func_Symbol_External_Addr) 68 | for Check_Func_Symbol_Ref in Check_Func_Symbol_Refs: 69 | if Check_Func_Symbol_Refs.getReferenceType().isCall(): 70 | print("Shi Yong: {}".format(Check_Func)) 71 | break 72 | 73 | 74 | # Main Functions 75 | class FunctionAnalyzer(object): 76 | 77 | def __init__(self, function, logger=logger): 78 | self.function = function 79 | self.logger = logger 80 | self.prepare() 81 | 82 | def prepare(self): 83 | Decompiler = DecompInterface() 84 | Decompiler.openProgram(currentProgram) 85 | Decompiled_Func = Decompiler.decompileFunction(self.function, 30, getMonitor()) 86 | self.highfunction = Decompiled_Func.getHighFunction() 87 | #print("High Function information: {}".format(self.highfunction.getFunctionPrototype())) 88 | 89 | def start_analyse(self, address, param_index): 90 | self.logger.debug("Reference address is: {}".format(address)) 91 | PcodeOps = self.highfunction.getPcodeOps(address) 92 | while PcodeOps.hasNext(): 93 | PcodeOpAST = PcodeOps.next() 94 | print("*****\n{}\n*****".format(PcodeOpAST)) 95 | Opcode = PcodeOpAST.getOpcode() 96 | if Opcode == PcodeOp.CALL or Opcode == PcodeOp.CALLIND: 97 | #print("Found CALL/CALLIND at 0x{}".format(PcodeOpAST.getInput(0).getPCAddress())) 98 | Target_Param_Varnode = PcodeOpAST.getInput(param_index) 99 | #print("Target Param Varnode: {}".format(Target_Param_Varnode)) 100 | Target_Param_Varnode_Analyzer = VarnodeAnalyzer() 101 | Target_Param_Varnode_Analyzer.analyse_node(Target_Param_Varnode, PcodeOpAST) 102 | 103 | 104 | class VarnodeAnalyzer(object): 105 | 106 | def __init__(self, logger=logger): 107 | self.logger = logger 108 | 109 | def analyse_node(self, varnode, pcode): 110 | logger.debug("Varnode: {}".format(varnode)) 111 | Varnode_Type = varnode.isInput() 112 | if Varnode_Type == 1: 113 | Target_Param_Varnode_Index = (varnode.getOffset() - 256) / 4 + 1 114 | Current_Func = getFunctionContaining(pcode.getSeqnum().getTarget()) 115 | References = getReferencesTo(Current_Func.getEntryPoint()) 116 | for Reference in References: 117 | Reference_Addr = Reference.getFromAddress() 118 | if Reference_Addr.toString() != "Entry Point" and Reference.getReferenceType().isCall(): 119 | Reference_Func = getFunctionContaining(Reference_Addr) 120 | logger.debug("Reference function is: {}".format(Reference_Func)) 121 | if Reference_Func not in Examined_Funcs: 122 | Examined_Funcs[Reference_Func] = FunctionAnalyzer(Reference_Func) 123 | Reference_Func_Analyzer = Examined_Funcs[Reference_Func] 124 | Reference_Func_Analyzer.start_analyse(Reference_Addr, Target_Param_Varnode_Index) 125 | else: 126 | Pcode_Def = varnode.getDef() 127 | logger.debug("Pcode Define: {}".format(Pcode_Def)) 128 | self.analyse_pcode(Pcode_Def) 129 | 130 | def analyse_pcode(self, pcode): 131 | Opcode = pcode.getOpcode() 132 | if Opcode == PcodeOp.CAST: 133 | Target_Varnode = pcode.getInput(0) 134 | self.analyse_node(Target_Varnode, pcode) 135 | if Opcode == PcodeOp.COPY: 136 | Target_Varnode = pcode.getInput(0) 137 | self.analyse_node(Target_Varnode, pcode) 138 | elif Opcode == PcodeOp.CALL or Opcode == PcodeOp.CALLIND: 139 | Method_Varnode = pcode.getInput(0) 140 | Pcode_Def_Method_Varnode = Method_Varnode.getDef() 141 | Method_Idx = Pcode_Def_Method_Varnode.getInput(1).getOffset() 142 | Method_Name = getMethod(Method_Idx) 143 | print("Method: {}".format(Method_Name)) 144 | 145 | Pcode_Seqnum = pcode.getSeqnum() 146 | Pcode_Addr = Pcode_Seqnum.getTarget() 147 | Pcode_Ins = getInstructionAt(Pcode_Addr) 148 | Target_Method_Addrs = Pcode_Ins.getFlows() 149 | Target_Method_Addr = Target_Method_Addrs[0] 150 | Target_Method_Symbol_Type = getSymbolAt(Target_Method_Addr).getSymbolType() 151 | if Target_Method_Symbol_Type == SymbolType.FUNCTION: 152 | print("Pcode-Op CALL/CALLIND target method is local function!") 153 | for i in range(2, len(pcode.getInputs())): 154 | Param_Target_Varnode = pcode.getInput(i) 155 | self.analyse_node(Param_Target_Varnode, pcode) 156 | Target_Func = getFunctionAt(Target_Method_Addr) 157 | if Target_Func not in Examined_Funcs: 158 | Examined_Funcs[Target_Func] = FunctionAnalyzer(Target_Func) 159 | Target_Func_Analyzer = Examined_Funcs[Target_Func] 160 | Target_Func_Instruction = getInstructionAt(Target_Method_Addr) 161 | Target_Func_Addresses = Target_Func.getBody() 162 | while Target_Func_Addresses.contains(Target_Func_Instruction.getAddress()): 163 | PcodeOps = Target_Func_Analyzer.highfunction.getPcodeOps(Target_Func_Instruction.getAddress()) 164 | while PcodeOps.hasNext(): 165 | PcodeOpAST = PcodeOps.next() 166 | print("*****\n{}\n*****".format(PcodeOpAST)) 167 | if PcodeOpAST.getOpcode() == PcodeOp.RETURN: 168 | Ret_Varnode = PcodeOpAST.getInput(1) 169 | self.analyse_node(Ret_Varnode, PcodeOpAST) 170 | Target_Func_Instruction = Target_Func_Instruction.getNext() 171 | elif Target_Method_Symbol_Type == SymbolType.LABEL: 172 | print("Pcode-Op CALL/CALLIND target method is external function!") 173 | if len(pcode.getInputs()) > 1: 174 | Fun_Target_Varnode = pcode.getInput(1) 175 | self.analyse_node(Fun_Target_Varnode, pcode) 176 | #for i in range(2, len(pcode.getInputs())): 177 | #Param_Target_Varnode = pcode.getInput(i) 178 | #self.analyse_node(Param_Target_Varnode, pcode) 179 | else: 180 | print("Pcode-Op CALL/CALLIND target method type! {}".format(Target_Method_Symbol_Type)) 181 | elif Opcode == PcodeOp.CPOOLREF: 182 | Pcode_Seqnum = pcode.getSeqnum() 183 | Pcode_Addr = Pcode_Seqnum.getTarget() 184 | Pcode_Ins = getInstructionAt(Pcode_Addr) 185 | Pcode_Ins_Str = Pcode_Ins.toString() 186 | #print("Pcode belongs to Instruction: {}".format(Pcode_Ins_Str)) 187 | if "const_string" in Pcode_Ins_Str: 188 | String_Idx = pcode.getInput(1).getOffset() 189 | logger.debug("String: {}".format(getString(String_Idx))) 190 | elif "new_instance" in Pcode_Ins_Str: 191 | Class_Idx = pcode.getInput(1).getOffset() 192 | elif "get_object" in Pcode_Ins_Str: 193 | Field_Idx = pcode.getInput(1).getOffset() 194 | elif "invoke_" in Pcode_Ins_Str: 195 | Method_Idx = pcode.getInput(1).getOffset() 196 | else: 197 | logger.debug("Pcode-Op CPOOLREF at instruction! {}".format(Pcode_Ins_Str)) 198 | elif Opcode == PcodeOp.NEW: 199 | Class_Varnode = pcode.getInput(0) 200 | Pcode_Def_Class_Varnode = Class_Varnode.getDef() 201 | Class_Idx = Pcode_Def_Class_Varnode.getInput(1).getOffset() 202 | print("Class: {}".format(getType(Class_Idx))) 203 | Pcode_Relates = pcode.getOutput().getDescendants() 204 | while Pcode_Relates.hasNext(): 205 | Pcode_Relate = Pcode_Relates.next() 206 | print("Instance related Pcode: {}".format(Pcode_Relate)) 207 | if Pcode_Relate.getOpcode() == PcodeOp.CALL or Pcode_Relate.getOpcode() == PcodeOp.CALLIND: 208 | Method_Varnode = Pcode_Relate.getInput(0) 209 | Pcode_Def_Method_Varnode = Method_Varnode.getDef() 210 | Method_Idx = Pcode_Def_Method_Varnode.getInput(1).getOffset() 211 | Method_Name = getMethod(Method_Idx) 212 | print("Instance related method: {}".format(Method_Name)) 213 | for i in range(2, len(Pcode_Relate.getInputs())): 214 | Param_Target_Varnode = Pcode_Relate.getInput(i) 215 | self.analyse_node(Param_Target_Varnode, Pcode_Relate) 216 | elif Opcode == PcodeOp.LOAD: 217 | Field_Varnode = pcode.getInput(1) 218 | Pcode_Def_Field_Varnode = Field_Varnode.getDef() 219 | Field_Idx = Pcode_Def_Field_Varnode.getInput(1).getOffset() 220 | print("Field: {}".format(getField(Field_Idx))) 221 | print("ssss {}".format(pcode.getInput(0))) 222 | 223 | def StringBuilder(self, pcode): 224 | Target_Varnode = pcode.getInput(1) 225 | Pcode_Relates = Target_Varnode.getDescendants() 226 | while Pcode_Relates.hasNext(): 227 | Pcode_Relate = Pcode_Relates.next() 228 | Pcode_Relate_Op = Pcode_Relate.getOpcode() 229 | if Pcode_Relate.getOpcode() == PcodeOp.CALL or Pcode_Relate.getOpcode() == Pcode.CALLIND: 230 | Method_Varnode = pcode.getInput(0) 231 | Pcode_Def_Method_Varnode = Method_Varnode.getDef() 232 | Method_Idx = Pcode_Def_Method_Varnode.getInput(1).getOffset() 233 | Method_Name = getMethod(Method_Idx) 234 | print("Method: {}".format(Method_Name)) 235 | 236 | print(getField(0)) 237 | 238 | if __name__ == '__main__': 239 | References = getReferencesTo(toAddr(0x50124204)) # Function's address 240 | logger.debug("Reference functions are: {}".format(References)) 241 | for Reference in References: 242 | Reference_Addr = Reference.getFromAddress() 243 | if Reference.getReferenceType().isCall(): 244 | Reference_Func = getFunctionContaining(Reference_Addr) 245 | logger.debug("Reference function is: {}".format(Reference_Func)) 246 | if Reference_Func not in Examined_Funcs: 247 | Examined_Funcs[Reference_Func] = FunctionAnalyzer(Reference_Func) 248 | Reference_Func_Analyzer = Examined_Funcs[Reference_Func] 249 | Reference_Func_Analyzer.start_analyse(Reference_Addr, 2) # 2 is parameters index 250 | 251 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 PAGalaxyLab 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ghidra_scripts 2 | Scripts for the Ghidra software reverse engineering suite. 3 | 4 | *Readme in other languages: [English](README.md), [简体中文](README.zh-cn.md)* 5 | 6 | 7 | # Installation 8 | In the Ghidra Script Manager click the "Script Directories" icon in the toolbar and add the checked out repository as a path. 9 | 10 | 11 | # galaxy_utility 12 | Some utility used by other Ghidra scripts. 13 | 14 | 15 | # trace_function_call_parm_value.py 16 | Trace Function call parameters value using Ghidra P-Code. 17 | 18 | ![Demo pic](docs/images/trace_function_call_parm_value_pic_1.jpg) 19 | 20 | # wr886nv7_rename_function_with_error_print.py 21 | Example script, rename undefined function with error print string. 22 | 23 | [Detail steps](docs/wr886nv7_rename_function_with_error_print.md) 24 | 25 | ![Demo pic](docs/images/wr886nv7_rename_function_with_error_print_1.jpg) 26 | 27 | 28 | # AnalyzeOCMsgSend.py 29 | Analyze Objective-C MsgSend using this script. 30 | 31 | ![Demo pic](docs/images/analyze_oc_msg_send_pic.png) 32 | 33 | 34 | # DexFile_Parameter_Trace.py 35 | Trace an Android Function parameters value using Ghidra P-Code. 36 | 37 | ## function logd 1st parameter trace 38 | ![Demo pic](docs/images/DexFile_Parameter_Trace_Logd.png) 39 | 40 | Provide target function's address and parameters index 41 | ![Demo pic](docs/images/DexFile_Parameter_Trace_Script_Param.png) 42 | 43 | Output 44 | ![Demo pic](docs/images/DexFile_Parameter_trace_Script_Output.png) 45 | 46 | # ollvm_deobf_fla.py 47 | Deobfuscating OLLVM control flow flattening. 48 | 49 | Select the assembly for state var initialization in Ghidra code listing interface. 50 | 51 | ![Demo pic](docs/images/ghidra-ollvm-obf.png) 52 | 53 | Then run the script for deobfuscation. 54 | 55 | ![Demo pic](docs/images/ghidra-ollvm-deobf.png) 56 | -------------------------------------------------------------------------------- /README.zh-cn.md: -------------------------------------------------------------------------------- 1 | # ghidra_scripts 2 | 我们所使用的一些Ghidra逆向分析脚本. 3 | 4 | *说明文档的其他语言: [English](README.md), [简体中文](README.zh-cn.md)* 5 | 6 | # 安装 7 | 在Ghidra脚本管理器中点击"Script Directories", 添加checked out后的repository路径. 8 | 9 | 10 | # galaxy_utility 11 | 其他Ghidra脚本所使用的实用工具。 12 | 13 | 14 | # trace_function_call_parm_value.py 15 | 使用Ghidra P-Code追踪分析函数调用时的传参值。 16 | 17 | ![Demo pic](docs/images/trace_function_call_parm_value_pic_1.jpg) 18 | 19 | 20 | # wr886nv7_rename_function_with_error_print.py 21 | 样例脚本, 利用函数错误输出中的函数名关键字来重命名未定义的函数。 22 | [详细步骤](docs/wr886nv7_rename_function_with_error_print.zh-cn.md) 23 | 24 | ![Demo pic](docs/images/wr886nv7_rename_function_with_error_print_1.jpg) 25 | 26 | 27 | # AnalyzeOCMsgSend.py 28 | 使用Ghidra脚本分析Objective-C中的MsgSend方法。 29 | 30 | ![Demo pic](docs/images/analyze_oc_msg_send_pic.png) 31 | 32 | 33 | # DexFile_Parameter_Trace.py 34 | 使用Pcode追踪Dex文件中的函数参数。 35 | 36 | ## 使用Ghidra脚本分析Logd函数的第一个参数 37 | ![Demo pic](docs/images/DexFile_Parameter_Trace_Logd.png) 38 | 39 | 提供Logd函数的地址(这里是0x50123cdc)和2(代表第一个参数) 40 | ![Demo pic](docs/images/DexFile_Parameter_Trace_Script_Param.png) 41 | 42 | 输出 43 | ![Demo pic](docs/images/DexFile_Parameter_trace_Script_Output.png) 44 | 45 | # ollvm_deobf_fla.py 46 | 使用Pcode对OLLVM控制流平坦化进行反混淆。 47 | 48 | 在Ghidra的界面中选中用于初始化状态变量的汇编代码。 49 | 50 | ![Demo pic](docs/images/ghidra-ollvm-obf.png) 51 | 52 | 运行脚本,进行反混淆 53 | 54 | ![Demo pic](docs/images/ghidra-ollvm-deobf.png) 55 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PAGalaxyLab/ghidra_scripts/61aee9b61ecb2545e3b5d398b377b98abfd2712b/__init__.py -------------------------------------------------------------------------------- /docs/images/DexFile_Parameter_Trace_Logd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PAGalaxyLab/ghidra_scripts/61aee9b61ecb2545e3b5d398b377b98abfd2712b/docs/images/DexFile_Parameter_Trace_Logd.png -------------------------------------------------------------------------------- /docs/images/DexFile_Parameter_Trace_Script_Param.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PAGalaxyLab/ghidra_scripts/61aee9b61ecb2545e3b5d398b377b98abfd2712b/docs/images/DexFile_Parameter_Trace_Script_Param.png -------------------------------------------------------------------------------- /docs/images/DexFile_Parameter_trace_Script_Output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PAGalaxyLab/ghidra_scripts/61aee9b61ecb2545e3b5d398b377b98abfd2712b/docs/images/DexFile_Parameter_trace_Script_Output.png -------------------------------------------------------------------------------- /docs/images/analyze_oc_msg_send_pic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PAGalaxyLab/ghidra_scripts/61aee9b61ecb2545e3b5d398b377b98abfd2712b/docs/images/analyze_oc_msg_send_pic.png -------------------------------------------------------------------------------- /docs/images/ghidra-ollvm-deobf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PAGalaxyLab/ghidra_scripts/61aee9b61ecb2545e3b5d398b377b98abfd2712b/docs/images/ghidra-ollvm-deobf.png -------------------------------------------------------------------------------- /docs/images/ghidra-ollvm-obf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PAGalaxyLab/ghidra_scripts/61aee9b61ecb2545e3b5d398b377b98abfd2712b/docs/images/ghidra-ollvm-obf.png -------------------------------------------------------------------------------- /docs/images/trace_function_call_parm_value_pic_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PAGalaxyLab/ghidra_scripts/61aee9b61ecb2545e3b5d398b377b98abfd2712b/docs/images/trace_function_call_parm_value_pic_1.jpg -------------------------------------------------------------------------------- /docs/images/wr886nv7_rename_function_with_error_print_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PAGalaxyLab/ghidra_scripts/61aee9b61ecb2545e3b5d398b377b98abfd2712b/docs/images/wr886nv7_rename_function_with_error_print_1.jpg -------------------------------------------------------------------------------- /docs/images/wr886nv7_rename_function_with_error_print_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PAGalaxyLab/ghidra_scripts/61aee9b61ecb2545e3b5d398b377b98abfd2712b/docs/images/wr886nv7_rename_function_with_error_print_2.jpg -------------------------------------------------------------------------------- /docs/images/wr886nv7_rename_function_with_error_print_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PAGalaxyLab/ghidra_scripts/61aee9b61ecb2545e3b5d398b377b98abfd2712b/docs/images/wr886nv7_rename_function_with_error_print_3.jpg -------------------------------------------------------------------------------- /docs/images/wr886nv7_rename_function_with_error_print_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PAGalaxyLab/ghidra_scripts/61aee9b61ecb2545e3b5d398b377b98abfd2712b/docs/images/wr886nv7_rename_function_with_error_print_4.jpg -------------------------------------------------------------------------------- /docs/images/wr886nv7_rename_function_with_error_print_5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PAGalaxyLab/ghidra_scripts/61aee9b61ecb2545e3b5d398b377b98abfd2712b/docs/images/wr886nv7_rename_function_with_error_print_5.jpg -------------------------------------------------------------------------------- /docs/wr886nv7_rename_function_with_error_print.md: -------------------------------------------------------------------------------- 1 | # Detail steps of demo script wr886nv7_rename_function_with_error_print.py 2 | 3 | ## step 1: Download wr886nv7 firmware and extract VxWorks image 4 | You can download example wr886nv7 firmware [here](http://download.tplinkcloud.com.cn/firmware/wr886nv7-ipv6-cn-up_2019-10-25_09.43.28_1572316888807.bin). 5 | 6 | Using binwalk to extract the firmware. 7 | 8 | ![](images/wr886nv7_rename_function_with_error_print_2.jpg) 9 | 10 | Find TP-Link external symbol file. 11 | 12 | ![](images/wr886nv7_rename_function_with_error_print_3.jpg) 13 | 14 | Load VxWorks image "A200" to Ghidra with MIPS Big endian processor type and default load address zero. 15 | 16 | ![](images/wr886nv7_rename_function_with_error_print_4.jpg) 17 | 18 | Don't analyze image this time, since we don't known the correct load address. 19 | 20 | 21 | ## step 2: Run VxHunter load tp-link symbols script 22 | 23 | PS: You need install VxHunter first, VxHunter repository can be found [here](https://github.com/PAGalaxyLab/vxhunter) 24 | 25 | Run VxHunter vxhunter_load_tp-link_symbols.py in script manager and select the TP-Link external symbol file "DBECB". 26 | 27 | This script will load TP-Link external symbol file, rebase image to correct load address and fix the function name. 28 | 29 | ![](images/wr886nv7_rename_function_with_error_print_5.jpg) 30 | 31 | 32 | ## step 3: Run wr886nv7_rename_function_with_error_print.py 33 | 34 | All done, you can now run wr886nv7_rename_function_with_error_print.py script. 35 | 36 | This script will analyze functions error print and use it to rename undefined function. 37 | 38 | ![](images/wr886nv7_rename_function_with_error_print_1.jpg) 39 | -------------------------------------------------------------------------------- /docs/wr886nv7_rename_function_with_error_print.zh-cn.md: -------------------------------------------------------------------------------- 1 | # 使用样例脚本wr886nv7_rename_function_with_error_print.py的详细步骤 2 | 3 | 4 | ## 步骤一: 下载wr886nv7固件并提取VxWorks镜像 5 | 样例wr886nv7固件[下载地址](http://download.tplinkcloud.com.cn/firmware/wr886nv7-ipv6-cn-up_2019-10-25_09.43.28_1572316888807.bin)。 6 | 7 | 使用binwalk来提取固件. 8 | 9 | ![](images/wr886nv7_rename_function_with_error_print_2.jpg) 10 | 11 | 寻找TP-Link外部符号表. 12 | 13 | ![](images/wr886nv7_rename_function_with_error_print_3.jpg) 14 | 15 | 在Ghidra中使用MIPS Big endian处理器及默认加载地址0来导入VxWorks固件"A200"。 16 | 17 | ![](images/wr886nv7_rename_function_with_error_print_4.jpg) 18 | 19 | 先不要对VxWorks镜像进行分析,因为我们此时并不知道正确的加载地址。 20 | 21 | 22 | ## 步骤二: 执行VxHunter load tp-link symbols脚本 23 | 24 | PS: 需要先安装VxHunter, [VxHunter项目地址](https://github.com/PAGalaxyLab/vxhunter) 25 | 26 | 在Ghidra脚本管理器中运行vxhunter_load_tp-link_symbols.py后选择TP-Link外部符号文件"DBECB"。 27 | 28 | 这个脚本会自动加载TP-Link外部符号文件, 将VxWorks镜像rebase到正确的加载地址并利用符号表修复函数名字。 29 | 30 | ![](images/wr886nv7_rename_function_with_error_print_5.jpg) 31 | 32 | 33 | ## step 3: Run wr886nv7_rename_function_with_error_print.py 34 | 35 | 所有前置工作都完成了,现在可以运行wr886nv7_rename_function_with_error_print.py脚本了. 36 | 37 | 这个脚本将会分析函数的错误输出并利用这些输出对未识别的函数进行重命名. 38 | 39 | ![](images/wr886nv7_rename_function_with_error_print_1.jpg) 40 | -------------------------------------------------------------------------------- /galaxy_utility/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PAGalaxyLab/ghidra_scripts/61aee9b61ecb2545e3b5d398b377b98abfd2712b/galaxy_utility/__init__.py -------------------------------------------------------------------------------- /galaxy_utility/common.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from ghidra.app.util.demangler import DemangledException 3 | from ghidra.app.util.demangler.gnu import GnuDemangler 4 | from ghidra.program.model.mem import Memory 5 | from ghidra.util.task import TaskMonitor 6 | import struct 7 | import logging 8 | import time 9 | 10 | # The Python module that Ghidra directly launches is always called __main__. If we import 11 | # everything from that module, this module will behave as if Ghidra directly launched it. 12 | from __main__ import * 13 | 14 | debug = False 15 | process_is_64bit = False 16 | 17 | 18 | # Init Default Logger 19 | def get_logger(name="Default_logger"): 20 | logger = logging.getLogger(name) 21 | logger.setLevel(logging.INFO) 22 | console_handler = logging.StreamHandler() 23 | console_format = logging.Formatter('[%(levelname)-8s][%(module)s.%(funcName)s] %(message)s') 24 | console_handler.setFormatter(console_format) 25 | logger.addHandler(console_handler) 26 | return logger 27 | 28 | 29 | logger = get_logger() 30 | 31 | 32 | if debug: 33 | logger.setLevel(logging.DEBUG) 34 | 35 | endian = currentProgram.domainFile.getMetadata()[u'Endian'] 36 | if endian == u'Big': 37 | is_big_endian = True 38 | else: 39 | is_big_endian = False 40 | 41 | process_type = currentProgram.domainFile.getMetadata()[u'Processor'] 42 | if process_type.endswith(u'64'): 43 | process_is_64bit = True 44 | 45 | demangler = GnuDemangler() 46 | listing = currentProgram.getListing() 47 | can_demangle = demangler.canDemangle(currentProgram) 48 | 49 | 50 | class Timer(object): 51 | def __init__(self): 52 | self.start_time = None 53 | 54 | def reset(self): 55 | self.start_time = time.time() 56 | 57 | def start_timer(self): 58 | if self.start_time: 59 | return False 60 | else: 61 | self.start_time = time.time() 62 | return self.start_time 63 | 64 | def get_timer(self): 65 | if self.start_time: 66 | return time.time() - self.start_time 67 | return False 68 | 69 | 70 | def is_address_in_current_program(address): 71 | for block in currentProgram.memory.blocks: 72 | if block.getStart().offset <= address.offset <= block.getEnd().offset: 73 | return True 74 | return False 75 | 76 | 77 | def get_signed_value(input_data): 78 | pack_format = "" 79 | if is_big_endian: 80 | pack_format += ">" 81 | else: 82 | pack_format += "<" 83 | 84 | if process_is_64bit: 85 | pack_format += "L" 86 | else: 87 | pack_format += "I" 88 | 89 | logger.debug("type(input_data): {}".format(type(input_data))) 90 | data = struct.pack(pack_format.upper(), input_data) 91 | signed_data = struct.unpack(pack_format.lower(), data)[0] 92 | 93 | return signed_data 94 | 95 | 96 | def create_uninitialized_block(block_name, start_address, length, overlay=False): 97 | # createUninitializedBlock 98 | 99 | try: 100 | memory = currentProgram.memory 101 | memory.createUninitializedBlock(block_name, start_address, length, overlay) 102 | return True 103 | 104 | except: 105 | return False 106 | 107 | 108 | def create_initialized_block(block_name, start_address, length, fill=0x00, monitor=TaskMonitor.DUMMY, overlay=False): 109 | # createUninitializedBlock 110 | 111 | try: 112 | memory = currentProgram.memory 113 | memory.createInitializedBlock(block_name, start_address, length, fill, monitor, overlay) 114 | return True 115 | 116 | except: 117 | return False 118 | -------------------------------------------------------------------------------- /galaxy_utility/function_analyzer.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from galaxy_utility.common import * 3 | from ghidra.app.decompiler import DecompInterface 4 | from ghidra.program.model.pcode import PcodeOp, PcodeOpAST 5 | from ghidra.program.model.address import GenericAddress 6 | 7 | # The Python module that Ghidra directly launches is always called __main__. If we import 8 | # everything from that module, this module will behave as if Ghidra directly launched it. 9 | from __main__ import * 10 | 11 | 12 | decompile_function_cache = { 13 | 14 | } 15 | 16 | logger = get_logger(__name__) 17 | 18 | 19 | class FlowNode(object): 20 | def __init__(self, var_node, logger=logger): 21 | """ Used to get VarNode value 22 | 23 | :param var_node: 24 | """ 25 | self.var_node = var_node 26 | if logger is None: 27 | self.logger = get_logger(self.__class__.__name__) 28 | else: 29 | self.logger = logger 30 | 31 | def get_value(self): 32 | """ Get VarNode value depend on it's type. 33 | 34 | :return: 35 | """ 36 | if self.var_node.isAddress(): 37 | self.logger.debug("Var_node isAddress") 38 | # TODO: Return pointer value is address is pointer, this might cause some bug, need more test. 39 | # try: 40 | # if getDataAt(self.var_node.getAddress()).isPointer(): 41 | # self.logger.info("Var_node address is pointer") 42 | # return getDataAt(self.var_node.getAddress()).getValue().offset 43 | # 44 | # except BaseException as err: 45 | # self.logger.err(err) 46 | # return self.var_node.getAddress() 47 | # 48 | return self.var_node.getAddress() 49 | elif self.var_node.isConstant(): 50 | self.logger.debug("Var_node isConstant") 51 | return self.var_node.getAddress() 52 | elif self.var_node.isUnique(): 53 | self.logger.debug("Var_node isUnique") 54 | return calc_pcode_op(self.var_node.getDef()) 55 | elif self.var_node.isRegister(): 56 | self.logger.debug("Var_node isRegister") 57 | self.logger.debug(self.var_node.getDef()) 58 | return calc_pcode_op(self.var_node.getDef()) 59 | 60 | # https://github.com/NationalSecurityAgency/ghidra/issues/2283 61 | elif self.var_node.isPersistent(): 62 | self.logger.debug("Var_node isPersistent") 63 | # TODO: Handler this later 64 | return 65 | elif self.var_node.isAddrTied(): 66 | self.logger.debug("Var_node isAddrTied") 67 | return calc_pcode_op(self.var_node.getDef()) 68 | elif self.var_node.isUnaffected(): 69 | self.logger.debug("Var_node isUnaffected") 70 | # TODO: Handler this later 71 | return 72 | else: 73 | self.logger.debug("self.var_node: {}".format(self.var_node)) 74 | 75 | 76 | def calc_pcode_op(pcode): 77 | logger.debug("pcode: {}, type: {}".format(pcode, type(pcode))) 78 | if isinstance(pcode, PcodeOpAST): 79 | opcode = pcode.getOpcode() 80 | if opcode == PcodeOp.PTRSUB: 81 | logger.debug("PTRSUB") 82 | var_node_1 = FlowNode(pcode.getInput(0)) 83 | var_node_2 = FlowNode(pcode.getInput(1)) 84 | value_1 = var_node_1.get_value() 85 | value_2 = var_node_2.get_value() 86 | if isinstance(value_1, GenericAddress) and isinstance(value_2, GenericAddress): 87 | return value_1.offset + value_2.offset 88 | 89 | else: 90 | logger.debug("value_1: {}".format(value_1)) 91 | logger.debug("value_2: {}".format(value_2)) 92 | return None 93 | 94 | elif opcode == PcodeOp.CAST: 95 | logger.debug("CAST") 96 | var_node_1 = FlowNode(pcode.getInput(0)) 97 | value_1 = var_node_1.get_value() 98 | if isinstance(value_1, GenericAddress): 99 | return value_1.offset 100 | 101 | else: 102 | return None 103 | 104 | elif opcode == PcodeOp.PTRADD: 105 | logger.debug("PTRADD") 106 | var_node_0 = FlowNode(pcode.getInput(0)) 107 | var_node_1 = FlowNode(pcode.getInput(1)) 108 | var_node_2 = FlowNode(pcode.getInput(2)) 109 | try: 110 | value_0_point = var_node_0.get_value() 111 | logger.debug("value_0_point: {}".format(value_0_point)) 112 | if not isinstance(value_0_point, GenericAddress): 113 | return 114 | value_0 = toAddr(getInt(value_0_point)) 115 | logger.debug("value_0: {}".format(value_0)) 116 | logger.debug("type(value_0): {}".format(type(value_0))) 117 | value_1 = var_node_1.get_value() 118 | logger.debug("value_1: {}".format(value_1)) 119 | logger.debug("type(value_1): {}".format(type(value_1))) 120 | if not isinstance(value_1, GenericAddress): 121 | logger.debug("value_1 is not GenericAddress!") 122 | return 123 | value_1 = get_signed_value(value_1.offset) 124 | # TODO: Handle input2 later 125 | value_2 = var_node_2.get_value() 126 | logger.debug("value_2: {}".format(value_2)) 127 | logger.debug("type(value_2): {}".format(type(value_2))) 128 | if not isinstance(value_2, GenericAddress): 129 | return 130 | output_value = value_0.add(value_1) 131 | logger.debug("output_value: {}".format(output_value)) 132 | return output_value.offset 133 | 134 | except Exception as err: 135 | logger.debug("Got something wrong with calc PcodeOp.PTRADD : {}".format(err)) 136 | return None 137 | 138 | except: 139 | logger.error("Got something wrong with calc PcodeOp.PTRADD ") 140 | return None 141 | 142 | elif opcode == PcodeOp.INDIRECT: 143 | logger.debug("INDIRECT") 144 | # TODO: Need find a way to handle INDIRECT operator. 145 | return None 146 | 147 | elif opcode == PcodeOp.MULTIEQUAL: 148 | logger.debug("MULTIEQUAL") 149 | # TODO: Add later 150 | return None 151 | 152 | elif opcode == PcodeOp.COPY: 153 | logger.debug("COPY") 154 | logger.debug("input_0: {}".format(pcode.getInput(0))) 155 | logger.debug("Output: {}".format(pcode.getOutput())) 156 | var_node_0 = FlowNode(pcode.getInput(0)) 157 | value_0 = var_node_0.get_value() 158 | return value_0 159 | 160 | else: 161 | logger.debug("Found Unhandled opcode: {}".format(pcode)) 162 | return None 163 | 164 | 165 | class FunctionAnalyzer(object): 166 | 167 | def __init__(self, function, timeout=30, logger=logger): 168 | """ 169 | 170 | :param function: Ghidra function object. 171 | :param timeout: timeout for decompile. 172 | :param logger: logger. 173 | """ 174 | self.function = function 175 | self.timeout = timeout 176 | if logger is None: 177 | self.logger = get_logger("FunctionAnalyzer") 178 | else: 179 | self.logger = logger 180 | self.hfunction = None 181 | self.call_pcodes = {} 182 | self.prepare() 183 | 184 | def prepare(self): 185 | self.hfunction = self.get_hfunction() 186 | self.get_all_call_pcode() 187 | 188 | def get_hfunction(self): 189 | decomplib = DecompInterface() 190 | decomplib.openProgram(currentProgram) 191 | timeout = self.timeout 192 | dRes = decomplib.decompileFunction(self.function, timeout, getMonitor()) 193 | hfunction = dRes.getHighFunction() 194 | return hfunction 195 | 196 | def get_function_pcode(self): 197 | if self.hfunction: 198 | try: 199 | ops = self.hfunction.getPcodeOps() 200 | 201 | except: 202 | return None 203 | 204 | return ops 205 | 206 | def print_pcodes(self): 207 | ops = self.get_function_pcode() 208 | while ops.hasNext(): 209 | pcodeOpAST = ops.next() 210 | print(pcodeOpAST) 211 | opcode = pcodeOpAST.getOpcode() 212 | print("Opcode: {}".format(opcode)) 213 | if opcode == PcodeOp.CALL: 214 | print("We found Call at 0x{}".format(pcodeOpAST.getInput(0).PCAddress)) 215 | call_addr = pcodeOpAST.getInput(0).getAddress() 216 | print("Calling {}(0x{}) ".format(getFunctionAt(call_addr), call_addr)) 217 | inputs = pcodeOpAST.getInputs() 218 | for i in range(len(inputs)): 219 | parm = inputs[i] 220 | print("parm{}: {}".format(i, parm)) 221 | 222 | def find_perv_call_address(self, address): 223 | try: 224 | address_index = sorted(self.call_pcodes.keys()).index(address) 225 | 226 | except Exception as err: 227 | return 228 | 229 | if address_index > 0: 230 | perv_address = sorted(self.call_pcodes.keys())[address_index - 1] 231 | return self.call_pcodes[perv_address] 232 | 233 | def find_next_call_address(self, address): 234 | try: 235 | address_index = sorted(self.call_pcodes.keys()).index(address) 236 | 237 | except Exception as err: 238 | return 239 | 240 | if address_index < len(self.call_pcodes) - 1: 241 | next_address = sorted(self.call_pcodes.keys())[address_index + 1] 242 | return self.call_pcodes[next_address] 243 | 244 | def get_all_call_pcode(self): 245 | ops = self.get_function_pcode() 246 | if not ops: 247 | return 248 | 249 | while ops.hasNext(): 250 | pcodeOpAST = ops.next() 251 | opcode = pcodeOpAST.getOpcode() 252 | if opcode in [PcodeOp.CALL, PcodeOp.CALLIND]: 253 | op_call_addr = pcodeOpAST.getInput(0).PCAddress 254 | self.call_pcodes[op_call_addr] = pcodeOpAST 255 | 256 | def get_call_pcode(self, call_address): 257 | # TODO: Check call_address is in function. 258 | if call_address in self.call_pcodes: 259 | return self.call_pcodes[call_address] 260 | 261 | return 262 | 263 | def analyze_call_parms(self, call_address): 264 | parms = {} 265 | # TODO: Check call_address is in function. 266 | pcodeOpAST = self.get_call_pcode(call_address) 267 | if pcodeOpAST: 268 | self.logger.debug("We found target call at 0x{} in function {}(0x{})".format( 269 | pcodeOpAST.getInput(0).PCAddress, self.function.name, hex(self.function.entryPoint.offset))) 270 | opcode = pcodeOpAST.getOpcode() 271 | if opcode == PcodeOp.CALL: 272 | target_call_addr = pcodeOpAST.getInput(0).getAddress() 273 | self.logger.debug("target_call_addr: {}".format(target_call_addr)) 274 | 275 | elif opcode == PcodeOp.CALLIND: 276 | target_call_addr = FlowNode(pcodeOpAST.getInput(0)).get_value() 277 | self.logger.debug("target_call_addr: {}".format(target_call_addr)) 278 | 279 | inputs = pcodeOpAST.getInputs() 280 | for i in range(len(inputs))[1:]: 281 | parm = inputs[i] 282 | self.logger.debug("parm{}: {}".format(i, parm)) 283 | parm_node = FlowNode(parm) 284 | self.logger.debug("parm_node: {}".format(parm_node)) 285 | parm_value = parm_node.get_value() 286 | self.logger.debug("parm_value: {}".format(parm_value)) 287 | if isinstance(parm_value, GenericAddress): 288 | parm_value = parm_value.offset 289 | parms[i] = parm_value 290 | if parm_value: 291 | self.logger.debug("parm{} value: {}".format(i, hex(parm_value))) 292 | return parms 293 | return 294 | 295 | def get_call_parm_value(self, call_address): 296 | parms_value = {} 297 | if not call_address in self.call_pcodes: 298 | return 299 | parms = self.analyze_call_parms(call_address) 300 | 301 | if not parms: 302 | return 303 | 304 | for i in parms: 305 | self.logger.debug("parms{}: {}".format(i, parms[i])) 306 | parm_value = parms[i] 307 | self.logger.debug("parm_value: {}".format(parm_value)) 308 | parm_data = None 309 | if parm_value: 310 | if is_address_in_current_program(toAddr(parm_value)): 311 | if getDataAt(toAddr(parm_value)): 312 | parm_data = getDataAt(toAddr(parm_value)) 313 | elif getInstructionAt(toAddr(parm_value)): 314 | parm_data = getFunctionAt(toAddr(parm_value)) 315 | 316 | parms_value["parm_{}".format(i)] = {'parm_value': parm_value, 317 | 'parm_data': parm_data 318 | } 319 | 320 | return parms_value 321 | 322 | 323 | def dump_call_parm_value(call_address, search_functions=None): 324 | """ 325 | 326 | :param call_address: 327 | :param search_functions: function name list to search 328 | :return: 329 | """ 330 | target_function = getFunctionAt(call_address) 331 | parms_data = {} 332 | if target_function: 333 | target_references = getReferencesTo(target_function.getEntryPoint()) 334 | for target_reference in target_references: 335 | # Filter reference type 336 | reference_type = target_reference.getReferenceType() 337 | logger.debug("reference_type: {}".format(reference_type)) 338 | logger.debug("isJump: {}".format(reference_type.isJump())) 339 | logger.debug("isCall: {}".format(reference_type.isCall())) 340 | if not reference_type.isCall(): 341 | logger.debug("skip!") 342 | continue 343 | 344 | call_addr = target_reference.getFromAddress() 345 | logger.debug("call_addr: {}".format(call_addr)) 346 | function = getFunctionContaining(call_addr) 347 | logger.debug("function: {}".format(function)) 348 | if not function: 349 | continue 350 | 351 | # search only targeted function 352 | if search_functions: 353 | if function.name not in search_functions: 354 | continue 355 | 356 | function_address = function.getEntryPoint() 357 | if function_address in decompile_function_cache: 358 | target = decompile_function_cache[function_address] 359 | 360 | else: 361 | target = FunctionAnalyzer(function=function) 362 | decompile_function_cache[function_address] = target 363 | 364 | parms_data[call_addr] = { 365 | 'call_addr': call_addr, 366 | 'refrence_function_addr': function.getEntryPoint(), 367 | 'refrence_function_name': function.name, 368 | 'parms': {} 369 | } 370 | 371 | parms_value = target.get_call_parm_value(call_address=call_addr) 372 | if not parms_value: 373 | continue 374 | 375 | trace_data = parms_data[call_addr] 376 | trace_data['parms'] = parms_value 377 | 378 | return parms_data 379 | -------------------------------------------------------------------------------- /ollvm_deobf_fla.py: -------------------------------------------------------------------------------- 1 | # Ghidra script for deobfuscating OLLVM control flow flattening 2 | # select the assembly for state var initialization in Ghidra code listing interface and run the script 3 | 4 | import os 5 | import binascii 6 | import logging 7 | 8 | from ghidra.app.decompiler import DecompInterface 9 | from ghidra.program.model.mem import * 10 | from ghidra.program.model.pcode import PcodeOp 11 | from ghidra.app.plugin.assembler import Assemblers 12 | 13 | logging.basicConfig(level=logging.INFO, 14 | format='[%(asctime)s][%(levelname)s] - %(message)s', 15 | datefmt='%m/%d/%Y %H:%M:%S %p') 16 | 17 | def get_last_pcode(block): 18 | pcode_iterator = block.getIterator() 19 | while pcode_iterator.hasNext(): 20 | pcode = pcode_iterator.next() 21 | if not pcode_iterator.hasNext(): 22 | return pcode 23 | 24 | # check if the var is state_var 25 | def is_state_var(state_var, var, depth=0): 26 | logging.debug('comparing %s to state var %s, depth %d' % (var, state_var, depth)) 27 | if depth > 1: 28 | logging.warning('reach max depth for is_state_var: %s' % var) 29 | return False 30 | # for temp var, find its definition 31 | if var.isUnique(): 32 | var_def = var.getDef() 33 | logging.debug('temp var def: %s' % var_def) 34 | if var_def.getOpcode() == PcodeOp.COPY: 35 | var = var_def.getInput(0) 36 | logging.debug('update var to %s' % var) 37 | elif var_def.getOpcode() == PcodeOp.MULTIEQUAL: 38 | # include phi node inputs 39 | for input_var in var_def.getInputs().tolist(): 40 | if is_state_var(state_var, input_var, depth+1): 41 | return True 42 | return state_var.getAddress() == var.getAddress() 43 | 44 | # value of state var may need to be updated before compared to const 45 | def const_update(const): 46 | # signed to unsigned 47 | return const & 0xffffffff 48 | 49 | # find blocks setting state var to consts 50 | def find_const_def_blocks(mem, state_var_size, pcode, depth, res, def_block): 51 | if depth > 3: 52 | logging.warning('reaching max depth in find_const_def_blocks') 53 | 54 | elif pcode is None: 55 | logging.warning('pcode is None') 56 | 57 | else: 58 | logging.debug('finding state var def in pcode %s of block %s, depth %d' % (pcode, pcode.getParent(), depth)) 59 | if pcode.getOpcode() == PcodeOp.COPY: 60 | input_var = pcode.getInput(0) 61 | if def_block is None: 62 | # the block of COPY is the def block 63 | def_block = pcode.getParent() 64 | logging.debug('find COPY in block %s' % def_block) 65 | # is copying const to var? 66 | if input_var.isConstant(): 67 | logging.debug('%s defines state var to const: %s' % (def_block, input_var)) 68 | if def_block not in res: 69 | res[def_block] = input_var.getOffset() 70 | else: 71 | logging.warning('%s already defines state var to const %s, skipped' % (def_block, res[def_block])) 72 | else: 73 | # if input var is in ram, read its value 74 | if input_var.getAddress().getAddressSpace().getName() == u'ram': 75 | if input_var.isAddress(): 76 | if state_var_size == 4: 77 | ram_value = mem.getInt(input_var.getAddress()) 78 | res[def_block] = ram_value 79 | elif state_var_size == 8: 80 | ram_value = mem.getLong(input_var.getAddress()) 81 | res[def_block] = ram_value 82 | else: 83 | logging.warning('state var size %d not supported' % state_var_size) 84 | else: 85 | logging.warning('def of non-const input_var %s not found' % input_var) 86 | # not ram or const, trace back to const def 87 | else: 88 | find_const_def_blocks(mem, state_var_size, input_var.getDef(), depth+1, res, def_block) 89 | 90 | elif pcode.getOpcode() == PcodeOp.MULTIEQUAL: 91 | for input_var in pcode.getInputs().tolist(): 92 | find_const_def_blocks(mem, state_var_size, input_var.getDef(), depth+1, res, def_block) 93 | else: 94 | logging.warning('unsupported pcode %s, depth %d' % (pcode, depth)) 95 | 96 | class Patcher(object): 97 | def __init__(self, current_program): 98 | self.listing_db = current_program.getListing() 99 | self.asm = Assemblers.getAssembler(current_program) 100 | 101 | def patch_unconditional_jump(self, addr, target_addr): 102 | return None 103 | 104 | def patch_conditional_jump(self, ins, true_addr, false_addr): 105 | return None 106 | 107 | # patch the binary for updated CFG 108 | def do_patch(self, link): 109 | logging.debug('patching block for CFG %s' % str(link)) 110 | 111 | block = link[0] 112 | ins = self.listing_db.getInstructions(block.getStop(), True).next() 113 | logging.debug('last ins in block to patch at %s: %s' % (block.getStop(), ins)) 114 | 115 | patch_addr = ins.getMinAddress() 116 | 117 | # unconditional jump 118 | if len(link) == 2: 119 | target_addr = link[1].getStart().getOffset() 120 | asm_string = self.patch_unconditional_jump(patch_addr, target_addr) 121 | logging.debug('patching unconditional jump at %s to %s' % (patch_addr, asm_string)) 122 | patched = self.asm.assembleLine(patch_addr, asm_string) 123 | if len(patched) > ins.getLength(): 124 | logging.error('not enough space at %s for patch %s' % (patch_addr, asm_string)) 125 | return None 126 | 127 | # conditional jump 128 | else: 129 | true_addr = link[1].getStart().getOffset() 130 | false_addr = link[2].getStart().getOffset() 131 | asm_string = self.patch_conditional_jump(ins, true_addr, false_addr) 132 | logging.debug('patching conditional jump at %s to %s' % (patch_addr, asm_string)) 133 | 134 | if asm_string is not None: 135 | patch = self.asm.assemble(patch_addr, asm_string) 136 | patch_bytes = bytearray() 137 | patch_ins_iterator = patch.iterator() 138 | while patch_ins_iterator.hasNext(): 139 | patch_bytes += bytearray(patch_ins_iterator.next().getBytes()) 140 | return (patch_addr, patch_bytes) 141 | else: 142 | return None 143 | 144 | 145 | class PatcherX86(Patcher): 146 | def __init__(self, current_program): 147 | super(PatcherX86, self).__init__(current_program) 148 | 149 | def patch_unconditional_jump(self, addr, target_addr): 150 | return 'JMP 0x%x' % target_addr 151 | 152 | def patch_conditional_jump(self, ins, true_addr, false_addr): 153 | op_str = str(ins.getMnemonicString()) 154 | 155 | if op_str.startswith('CMOV'): 156 | return '%s 0x%x\nJMP 0x%x' % (op_str.replace('CMOV', 'J'), true_addr, false_addr) 157 | else: 158 | return None 159 | 160 | class PatcherARM(Patcher): 161 | def __init__(self, current_program): 162 | super(PatcherARM, self).__init__(current_program) 163 | 164 | def patch_unconditional_jump(self, addr, target_addr): 165 | return 'b 0x%x' % target_addr 166 | 167 | def patch_conditional_jump(self, ins, true_addr, false_addr): 168 | op_str = str(ins.getMnemonicString()) 169 | 170 | if op_str.startswith('cpy'): 171 | asm_string = '%s 0x%x\nb 0x%x' % (op_str.replace('cpy', 'b'), true_addr, false_addr) 172 | elif op_str.startswith('mov'): 173 | asm_string = '%s 0x%x\nb 0x%x' % (op_str.replace('mov', 'b'), true_addr, false_addr) 174 | else: 175 | logging.warning('ins %s not supported' % ins) 176 | asm_string = None 177 | 178 | return asm_string 179 | 180 | class PatcherAArch64(PatcherARM): 181 | def __init__(self, current_program): 182 | super(PatcherAArch64, self).__init__(current_program) 183 | 184 | def patch_conditional_jump(self, ins, true_addr, false_addr): 185 | op_str = str(ins.getMnemonicString()) 186 | 187 | if op_str == 'csel': 188 | # get the condition from the last operand 189 | condition = str(ins.getDefaultOperandRepresentation(3)) 190 | # hack for CSEL: its pcode takes the last operand as def 191 | (true_addr, false_addr) = (false_addr, true_addr) 192 | asm_string = 'b.%s 0x%x\nb 0x%x' % (condition, true_addr, false_addr) 193 | return asm_string 194 | else: 195 | logging.warning('ins %s not supported' % ins) 196 | return None 197 | 198 | 199 | def get_high_function(current_program, current_address): 200 | decomplib = DecompInterface() 201 | decomplib.openProgram(current_program) 202 | 203 | current_function = getFunctionContaining(current_address) 204 | decompile_res = decomplib.decompileFunction(current_function, 30, getMonitor()) 205 | 206 | high_function = decompile_res.getHighFunction() 207 | return high_function 208 | 209 | def get_state_var(high_function, current_address): 210 | pcode_iterator = high_function.getPcodeOps(current_address) 211 | pcode = None 212 | 213 | # find the pcode for COPYing const 214 | while pcode_iterator.hasNext(): 215 | pcode = pcode_iterator.next() 216 | logging.debug('finding COPY const pcode: %s' % pcode) 217 | if pcode.getOpcode() == PcodeOp.COPY and pcode.getInput(0).isConstant(): 218 | break 219 | 220 | logging.info('COPY const pcode: %s' % pcode) 221 | 222 | # find the state var in phi node 223 | depth = 0 224 | while pcode is not None and pcode.getOpcode() != PcodeOp.MULTIEQUAL: 225 | logging.debug('finding phi node: %s, depth %d' % (pcode, depth)) 226 | if pcode.getOutput() is None: 227 | logging.warning('output is None in %s' % pcode) 228 | break 229 | pcode = pcode.getOutput().getLoneDescend() 230 | if depth > 5: 231 | break 232 | depth += 1 233 | 234 | if pcode is None or pcode.getOpcode() != PcodeOp.MULTIEQUAL: 235 | logging.error('cannot find phi node') 236 | return None 237 | else: 238 | logging.info('phi node: %s' % pcode) 239 | 240 | state_var = pcode.getOutput() 241 | logging.info('state var is %s' % state_var) 242 | return state_var 243 | 244 | 245 | # map const values of state var to blocks 246 | def compute_const_map(high_function, state_var): 247 | const_map = {} 248 | 249 | for block in high_function.getBasicBlocks(): 250 | # search for conditional jump 251 | if block.getOutSize() != 2: 252 | continue 253 | 254 | last_pcode = get_last_pcode(block) 255 | if last_pcode.getOpcode() != PcodeOp.CBRANCH: 256 | continue 257 | 258 | condition = last_pcode.getInput(1) 259 | 260 | condition_pcode = condition.getDef() 261 | logging.debug('condition pcode: %s' % condition_pcode) 262 | 263 | condition_type = condition_pcode.getOpcode() 264 | 265 | if not condition_type in (PcodeOp.INT_NOTEQUAL, PcodeOp.INT_EQUAL): 266 | continue 267 | 268 | in0 = condition_pcode.getInput(0) 269 | in1 = condition_pcode.getInput(1) 270 | 271 | if in0.isConstant(): 272 | const_var = in0 273 | compared_var = in1 274 | elif in1.isConstant(): 275 | const_var = in1 276 | compared_var = in0 277 | else: 278 | logging.debug('not const var in comparision, skipped') 279 | continue 280 | 281 | if is_state_var(state_var, compared_var): 282 | if condition_type == PcodeOp.INT_NOTEQUAL: 283 | target_block = block.getFalseOut() 284 | else: 285 | target_block = block.getTrueOut() 286 | const_map[const_var.getOffset()] = target_block 287 | else: 288 | logging.debug('state_var not involved in %s' % condition_pcode) 289 | 290 | 291 | logging.info('const_map map:\n%s' % '\n'.join('0x%x: %s' % kv for kv in const_map.items())) 292 | return const_map 293 | 294 | 295 | def find_state_var_defs(mem, state_var): 296 | phi_node = state_var.getDef() 297 | 298 | state_var_defs = {} 299 | 300 | for state_var_def in phi_node.getInputs().tolist(): 301 | if state_var_def == state_var: 302 | continue 303 | pcode = state_var_def.getDef() 304 | logging.debug('output %s of pcode %s in block %s defines state var' % (state_var_def, pcode, pcode.getParent())) 305 | 306 | find_const_def_blocks(mem, state_var.getSize(), pcode, 0, state_var_defs, None) 307 | 308 | logging.info('blocks defining state var:\n%s' % '\n'.join('%s: %s' % (b, hex(v)) for b, v in state_var_defs.items())) 309 | return state_var_defs 310 | 311 | 312 | def gen_cfg(const_map, state_var_defs): 313 | links = [] 314 | 315 | # basic blocks for CMOVXX 316 | cmovbb = [] 317 | 318 | for def_block, const in state_var_defs.items(): 319 | 320 | # unconditional jump 321 | if def_block.getOutSize() == 1: 322 | const = const_update(const) 323 | if const in const_map: 324 | link = (def_block, const_map[const]) 325 | logging.debug('unconditional jump link: %s' % str(link)) 326 | links.append(link) 327 | else: 328 | logging.warning('cannot find const 0x%x in const_map' % const) 329 | 330 | # conditional jump 331 | elif def_block.getOutSize() == 2: 332 | const = const_update(const) 333 | true_out = def_block.getTrueOut() 334 | false_out = def_block.getFalseOut() 335 | logging.debug('%s true out: %s, false out %s' % (def_block, true_out, false_out)) 336 | 337 | # true out block has state var def 338 | if true_out in state_var_defs: 339 | true_out_const = const_update(state_var_defs[true_out]) 340 | if true_out_const not in const_map: 341 | logging.warning('true out cannot find map from const 0x%x to block' % true_out_const) 342 | continue 343 | true_out_block = const_map[true_out_const] 344 | logging.debug('true out to block: %s' % true_out_block) 345 | 346 | if false_out in state_var_defs: 347 | false_out_const = const_update(state_var_defs[false_out]) 348 | if false_out_const not in const_map: 349 | logging.warning('false out cannot find map from const 0x%x to block' % false_out_const) 350 | continue 351 | else: 352 | false_out_block = const_map[false_out_const] 353 | logging.debug('false out to block: %s' % false_out_block) 354 | 355 | # false out doesn't have const def, then use the def in current block for the false out 356 | elif const in const_map: 357 | false_out_block = const_map[const] 358 | else: 359 | logging.warning('mapping of const %s in block %s not found' % (const, def_block)) 360 | continue 361 | 362 | link = (def_block, true_out_block, false_out_block) 363 | logging.debug('conditional jump link: %s' % str(link)) 364 | 365 | # the link from CMOVXX should be ignored since the current conditional jump would do it 366 | cmovbb.append(true_out) 367 | links.append(link) 368 | 369 | # false out block has state var def 370 | elif false_out in state_var_defs: 371 | false_out_const = const_update(state_var_defs[false_out]) 372 | if false_out_const not in const_map: 373 | logging.warning('false out cannot find map from const 0x%x to block' % false_out_const) 374 | continue 375 | false_out_block = const_map[false_out_const] 376 | logging.debug('false out to block: %s' % false_out_block) 377 | 378 | # true out doesn't have const def, then use the def in current block for the true out 379 | if const in const_map: 380 | true_out_block = const_map[const] 381 | link = (def_block, true_out_block, false_out_block) 382 | logging.debug('conditional jump link: %s' % str(link)) 383 | links.append(link) 384 | else: 385 | logging.warning('mapping of const %s in block %s not found' % (const, def_block)) 386 | else: 387 | logging.warning('no state var def in either trueout or falseout of block %s' % def_block) 388 | else: 389 | logging.warning('output block counts %d not supported' % def_block.getOutSize()) 390 | 391 | # skip the link for CMOVXX 392 | links_res = [] 393 | for link in links: 394 | if link[0] not in cmovbb: 395 | links_res.append(link) 396 | else: 397 | logging.debug('skip %s as CMOVXX' % str(link)) 398 | 399 | logging.info('generated CFG links:\n%s' % '\n'.join(str(link) for link in links_res)) 400 | return links_res 401 | 402 | def patch_cfg(current_program, cfg_links): 403 | patches = [] 404 | 405 | arch = current_program.getLanguage().getProcessor().toString() 406 | 407 | if arch == u'x86': 408 | patcher = PatcherX86(current_program) 409 | elif arch == u'ARM': 410 | patcher = PatcherARM(current_program) 411 | elif arch == u'AARCH64': 412 | patcher = PatcherAArch64(current_program) 413 | else: 414 | logging.error('arch %s not supported' % arch) 415 | return patches 416 | 417 | for link in cfg_links: 418 | try: 419 | patch_info = patcher.do_patch(link) 420 | if patch_info is not None: 421 | patches.append(patch_info) 422 | except Exception as e: 423 | logging.warning('failed to patch %s' % str(link)) 424 | logging.warning(e) 425 | 426 | logging.info('patches:\n%s' % '\n'.join('%s: %s' % (addr, binascii.hexlify(patch)) for addr, patch in patches)) 427 | return patches 428 | 429 | def save_patched(current_program, mem, patches): 430 | fpath = current_program.getExecutablePath() 431 | patched_pach = '%s-patched' % fpath 432 | 433 | file_data = None 434 | 435 | if os.path.exists(patched_pach): 436 | fpath = patched_pach 437 | 438 | with open(fpath, 'rb') as fin: 439 | file_data = bytearray(fin.read()) 440 | 441 | for addr, patch_bytes in patches: 442 | offset = mem.getAddressSourceInfo(addr).getFileOffset() 443 | file_data[offset:offset+len(patch_bytes)] = patch_bytes 444 | 445 | with open(patched_pach, 'wb') as fout: 446 | fout.write(file_data) 447 | logging.info('save patched file as %s' % patched_pach) 448 | 449 | if __name__ == '__main__': 450 | current_mem = currentProgram.getMemory() 451 | 452 | current_high_function = get_high_function(currentProgram, currentAddress) 453 | current_state_var = get_state_var(current_high_function, currentAddress) 454 | 455 | current_const_map = compute_const_map(current_high_function, current_state_var) 456 | current_state_var_defs = find_state_var_defs(current_mem, current_state_var) 457 | current_cfg_links = gen_cfg(current_const_map, current_state_var_defs) 458 | 459 | current_patches = patch_cfg(currentProgram, current_cfg_links) 460 | #save_patched(currentProgram, current_mem, current_patches) 461 | -------------------------------------------------------------------------------- /trace_function_call_parm_value.py: -------------------------------------------------------------------------------- 1 | # Trace function call parameters value using Ghidra P-Code. 2 | # @author dark-lbp 3 | # @category 4 | # @keybinding 5 | # @menupath 6 | # @toolbar 7 | from ghidra.app.decompiler import DecompInterface, DecompileOptions, DecompileResults 8 | from ghidra.program.model.pcode import HighParam, PcodeOp, PcodeOpAST 9 | from ghidra.program.model.address import GenericAddress 10 | import logging 11 | import struct 12 | 13 | 14 | # debug = True 15 | debug = False 16 | process_is_64bit = False 17 | 18 | # Init Default Logger 19 | logger = logging.getLogger('Default_logger') 20 | logger.setLevel(logging.INFO) 21 | consolehandler = logging.StreamHandler() 22 | console_format = logging.Formatter('[%(levelname)-8s][%(module)s.%(funcName)s] %(message)s') 23 | consolehandler.setFormatter(console_format) 24 | logger.addHandler(consolehandler) 25 | 26 | if debug: 27 | logger.setLevel(logging.DEBUG) 28 | 29 | endian = currentProgram.domainFile.getMetadata()[u'Endian'] 30 | if endian == u'Big': 31 | is_big_endian = True 32 | else: 33 | is_big_endian = False 34 | 35 | process_type = currentProgram.domainFile.getMetadata()[u'Processor'] 36 | if process_type.endswith(u'64'): 37 | process_is_64bit = True 38 | 39 | decompile_function_cache = {} 40 | 41 | 42 | def is_address_in_current_program(address): 43 | for block in currentProgram.memory.blocks: 44 | if address.offset in range(block.getStart().offset,block.getEnd().offset): 45 | return True 46 | return False 47 | 48 | 49 | def get_signed_value(input_data): 50 | pack_format = "" 51 | if is_big_endian: 52 | pack_format += ">" 53 | else: 54 | pack_format += "<" 55 | 56 | if process_is_64bit: 57 | pack_format += "L" 58 | else: 59 | pack_format += "I" 60 | 61 | logger.debug("type(input_data): {}".format(type(input_data))) 62 | data = struct.pack(pack_format.upper(), input_data.offset) 63 | signed_data = struct.unpack(pack_format.lower(), data)[0] 64 | 65 | return signed_data 66 | 67 | 68 | class FlowNode(object): 69 | def __init__(self, var_node, logger=logger): 70 | """ Used to get VarNode value 71 | 72 | :param var_node: 73 | """ 74 | self.var_node = var_node 75 | if logger is None: 76 | self.logger = logging.getLogger('FlowNode_logger') 77 | self.logger.setLevel(logging.INFO) 78 | consolehandler = logging.StreamHandler() 79 | console_format = logging.Formatter('[%(levelname)-8s][%(module)s.%(funcName)s] %(message)s') 80 | consolehandler.setFormatter(console_format) 81 | self.logger.addHandler(consolehandler) 82 | else: 83 | self.logger = logger 84 | 85 | def get_value(self): 86 | """ Get VarNode value depend on it's type. 87 | 88 | :return: 89 | """ 90 | if self.var_node.isAddress(): 91 | self.logger.debug("Var_node isAddress") 92 | return self.var_node.getAddress() 93 | elif self.var_node.isConstant(): 94 | self.logger.debug("Var_node isConstant") 95 | return self.var_node.getAddress() 96 | elif self.var_node.isUnique(): 97 | self.logger.debug("Var_node isUnique") 98 | return calc_pcode_op(self.var_node.getDef()) 99 | elif self.var_node.isRegister(): 100 | self.logger.debug("Var_node isRegister") 101 | self.logger.debug(self.var_node.getDef()) 102 | return calc_pcode_op(self.var_node.getDef()) 103 | 104 | # https://github.com/NationalSecurityAgency/ghidra/issues/2283 105 | elif self.var_node.isPersistent(): 106 | self.logger.debug("Var_node isPersistent") 107 | # TODO: Handler this later 108 | return 109 | elif self.var_node.isAddrTied(): 110 | self.logger.debug("Var_node isAddrTied") 111 | return calc_pcode_op(self.var_node.getDef()) 112 | elif self.var_node.isUnaffected(): 113 | self.logger.debug("Var_node isUnaffected") 114 | # TODO: Handler this later 115 | return 116 | else: 117 | self.logger.debug("self.var_node: {}".format(self.var_node)) 118 | 119 | 120 | def calc_pcode_op(pcode): 121 | logger.debug("pcode: {}, type: {}".format(pcode, type(pcode))) 122 | if isinstance(pcode, PcodeOpAST): 123 | opcode = pcode.getOpcode() 124 | if opcode == PcodeOp.PTRSUB: 125 | logger.debug("PTRSUB") 126 | var_node_1 = FlowNode(pcode.getInput(0)) 127 | var_node_2 = FlowNode(pcode.getInput(1)) 128 | value_1 = var_node_1.get_value() 129 | value_2 = var_node_2.get_value() 130 | if isinstance(value_1, GenericAddress) and isinstance(value_2, GenericAddress): 131 | return value_1.offset + value_2.offset 132 | 133 | else: 134 | logger.debug("value_1: {}".format(value_1)) 135 | logger.debug("value_2: {}".format(value_2)) 136 | return None 137 | 138 | elif opcode == PcodeOp.CAST: 139 | logger.debug("CAST") 140 | var_node_1 = FlowNode(pcode.getInput(0)) 141 | value_1 = var_node_1.get_value() 142 | if isinstance(value_1, GenericAddress): 143 | return value_1.offset 144 | 145 | else: 146 | return None 147 | 148 | elif opcode == PcodeOp.PTRADD: 149 | logger.debug("PTRADD") 150 | var_node_0 = FlowNode(pcode.getInput(0)) 151 | var_node_1 = FlowNode(pcode.getInput(1)) 152 | var_node_2 = FlowNode(pcode.getInput(2)) 153 | try: 154 | value_0_point = var_node_0.get_value() 155 | logger.debug("value_0_point: {}".format(value_0_point)) 156 | if not isinstance(value_0_point, GenericAddress): 157 | return 158 | value_0 = toAddr(getInt(value_0_point)) 159 | logger.debug("value_0: {}".format(value_0)) 160 | logger.debug("type(value_0): {}".format(type(value_0))) 161 | value_1 = var_node_1.get_value() 162 | logger.debug("value_1: {}".format(value_1)) 163 | logger.debug("type(value_1): {}".format(type(value_1))) 164 | if not isinstance(value_1, GenericAddress): 165 | logger.debug("value_1 is not GenericAddress!") 166 | return 167 | value_1 = get_signed_value(value_1.offset) 168 | # TODO: Handle input2 later 169 | value_2 = var_node_2.get_value() 170 | logger.debug("value_2: {}".format(value_2)) 171 | logger.debug("type(value_2): {}".format(type(value_2))) 172 | if not isinstance(value_2, GenericAddress): 173 | return 174 | output_value = value_0.add(value_1) 175 | logger.debug("output_value: {}".format(output_value)) 176 | return output_value.offset 177 | 178 | except Exception as err: 179 | logger.debug("Got something wrong with calc PcodeOp.PTRADD : {}".format(err)) 180 | return None 181 | 182 | except: 183 | logger.error("Got something wrong with calc PcodeOp.PTRADD ") 184 | return None 185 | 186 | elif opcode == PcodeOp.INDIRECT: 187 | logger.debug("INDIRECT") 188 | # TODO: Need find a way to handle INDIRECT operator. 189 | return None 190 | 191 | elif opcode == PcodeOp.MULTIEQUAL: 192 | logger.debug("MULTIEQUAL") 193 | # TODO: Add later 194 | return None 195 | 196 | elif opcode == PcodeOp.COPY: 197 | logger.debug("COPY") 198 | logger.debug("input_0: {}".format(pcode.getInput(0))) 199 | logger.debug("Output: {}".format(pcode.getOutput())) 200 | var_node_0 = FlowNode(pcode.getInput(0)) 201 | value_0 = var_node_0.get_value() 202 | return value_0 203 | 204 | else: 205 | logger.debug("Found Unhandled opcode: {}".format(pcode)) 206 | return None 207 | 208 | 209 | class FunctionAnalyzer(object): 210 | 211 | def __init__(self, function, timeout=30, logger=logger): 212 | """ 213 | 214 | :param function: Ghidra function object. 215 | :param timeout: timeout for decompile. 216 | :param logger: logger. 217 | """ 218 | self.function = function 219 | self.timeout = timeout 220 | if logger is None: 221 | self.logger = logging.getLogger('target') 222 | self.logger.setLevel(logging.INFO) 223 | consolehandler = logging.StreamHandler() 224 | console_format = logging.Formatter('[%(levelname)-8s][%(module)s.%(funcName)s] %(message)s') 225 | consolehandler.setFormatter(console_format) 226 | self.logger.addHandler(consolehandler) 227 | else: 228 | self.logger = logger 229 | self.hfunction = None 230 | self.call_pcodes = {} 231 | self.prepare() 232 | 233 | def prepare(self): 234 | self.hfunction = self.get_hfunction() 235 | self.get_all_call_pcode() 236 | 237 | def get_hfunction(self): 238 | decomplib = DecompInterface() 239 | decomplib.openProgram(currentProgram) 240 | timeout = self.timeout 241 | dRes = decomplib.decompileFunction(self.function, timeout, getMonitor()) 242 | hfunction = dRes.getHighFunction() 243 | return hfunction 244 | 245 | def get_function_pcode(self): 246 | if self.hfunction: 247 | try: 248 | ops = self.hfunction.getPcodeOps() 249 | 250 | except: 251 | return None 252 | 253 | return ops 254 | 255 | def print_pcodes(self): 256 | ops = self.get_function_pcode() 257 | while ops.hasNext(): 258 | pcodeOpAST = ops.next() 259 | print(pcodeOpAST) 260 | opcode = pcodeOpAST.getOpcode() 261 | print("Opcode: {}".format(opcode)) 262 | if opcode == PcodeOp.CALL: 263 | print("We found Call at 0x{}".format(pcodeOpAST.getInput(0).PCAddress)) 264 | call_addr = pcodeOpAST.getInput(0).getAddress() 265 | print("Calling {}(0x{}) ".format(getFunctionAt(call_addr), call_addr)) 266 | inputs = pcodeOpAST.getInputs() 267 | for i in range(len(inputs)): 268 | parm = inputs[i] 269 | print("parm{}: {}".format(i, parm)) 270 | 271 | def find_perv_call_address(self, address): 272 | try: 273 | address_index = sorted(self.call_pcodes.keys()).index(address) 274 | 275 | except Exception as err: 276 | return 277 | 278 | if address_index > 0: 279 | perv_address = sorted(self.call_pcodes.keys())[address_index - 1] 280 | return self.call_pcodes[perv_address] 281 | 282 | def find_next_call_address(self, address): 283 | try: 284 | address_index = sorted(self.call_pcodes.keys()).index(address) 285 | 286 | except Exception as err: 287 | return 288 | 289 | if address_index < len(self.call_pcodes) - 1: 290 | next_address = sorted(self.call_pcodes.keys())[address_index + 1] 291 | return self.call_pcodes[next_address] 292 | 293 | def get_all_call_pcode(self): 294 | ops = self.get_function_pcode() 295 | if not ops: 296 | return 297 | 298 | while ops.hasNext(): 299 | pcodeOpAST = ops.next() 300 | opcode = pcodeOpAST.getOpcode() 301 | if opcode in [PcodeOp.CALL, PcodeOp.CALLIND]: 302 | op_call_addr = pcodeOpAST.getInput(0).PCAddress 303 | self.call_pcodes[op_call_addr] = pcodeOpAST 304 | 305 | def get_call_pcode(self, call_address): 306 | # TODO: Check call_address is in function. 307 | if call_address in self.call_pcodes: 308 | return self.call_pcodes[call_address] 309 | 310 | return 311 | 312 | def analyze_call_parms(self, call_address): 313 | parms = {} 314 | # TODO: Check call_address is in function. 315 | pcodeOpAST = self.get_call_pcode(call_address) 316 | if pcodeOpAST: 317 | self.logger.debug("We found target call at 0x{} in function {}(0x{})".format( 318 | pcodeOpAST.getInput(0).PCAddress, self.function.name, hex(self.function.entryPoint.offset))) 319 | opcode = pcodeOpAST.getOpcode() 320 | if opcode == PcodeOp.CALL: 321 | target_call_addr = pcodeOpAST.getInput(0).getAddress() 322 | self.logger.debug("target_call_addr: {}".format(target_call_addr)) 323 | 324 | elif opcode == PcodeOp.CALLIND: 325 | target_call_addr = FlowNode(pcodeOpAST.getInput(0)).get_value() 326 | self.logger.debug("target_call_addr: {}".format(target_call_addr)) 327 | 328 | inputs = pcodeOpAST.getInputs() 329 | for i in range(len(inputs))[1:]: 330 | parm = inputs[i] 331 | self.logger.debug("parm{}: {}".format(i, parm)) 332 | parm_node = FlowNode(parm) 333 | self.logger.debug("parm_node: {}".format(parm_node)) 334 | parm_value = parm_node.get_value() 335 | self.logger.debug("parm_value: {}".format(parm_value)) 336 | if isinstance(parm_value, GenericAddress): 337 | parm_value = parm_value.offset 338 | parms[i] = parm_value 339 | if parm_value: 340 | self.logger.debug("parm{} value: {}".format(i, hex(parm_value))) 341 | return parms 342 | return 343 | 344 | def get_call_parm_value(self, call_address): 345 | parms_value = {} 346 | if not call_address in self.call_pcodes: 347 | return 348 | parms = self.analyze_call_parms(call_address) 349 | 350 | if not parms: 351 | return 352 | 353 | for i in parms: 354 | self.logger.debug("parms{}: {}".format(i, parms[i])) 355 | parm_value = parms[i] 356 | self.logger.debug("parm_value: {}".format(parm_value)) 357 | parm_data = None 358 | if parm_value: 359 | if is_address_in_current_program(toAddr(parm_value)): 360 | if getDataAt(toAddr(parm_value)): 361 | parm_data = getDataAt(toAddr(parm_value)) 362 | elif getInstructionAt(toAddr(parm_value)): 363 | parm_data = getFunctionAt(toAddr(parm_value)) 364 | 365 | parms_value["parm_{}".format(i)] = {'parm_value': parm_value, 366 | 'parm_data': parm_data 367 | } 368 | 369 | return parms_value 370 | 371 | 372 | def dump_call_parm_value(call_address, search_functions=None): 373 | """ 374 | 375 | :param call_address: 376 | :param search_functions: function name list to search 377 | :return: 378 | """ 379 | target_function = getFunctionAt(call_address) 380 | parms_data = {} 381 | if target_function: 382 | target_references = getReferencesTo(target_function.getEntryPoint()) 383 | for target_reference in target_references: 384 | # Filter reference type 385 | reference_type = target_reference.getReferenceType() 386 | logger.debug("reference_type: {}".format(reference_type)) 387 | logger.debug("isJump: {}".format(reference_type.isJump())) 388 | logger.debug("isCall: {}".format(reference_type.isCall())) 389 | if not reference_type.isCall(): 390 | logger.debug("skip!") 391 | continue 392 | 393 | call_addr = target_reference.getFromAddress() 394 | logger.debug("call_addr: {}".format(call_addr)) 395 | function = getFunctionContaining(call_addr) 396 | logger.debug("function: {}".format(function)) 397 | if not function: 398 | continue 399 | 400 | # search only targeted function 401 | if search_functions: 402 | if function.name not in search_functions: 403 | continue 404 | 405 | function_address = function.getEntryPoint() 406 | if function_address in decompile_function_cache: 407 | target = decompile_function_cache[function_address] 408 | else: 409 | target = FunctionAnalyzer(function=function) 410 | decompile_function_cache[function_address] = target 411 | 412 | parms_data[call_addr] = { 413 | 'call_addr': call_addr, 414 | 'refrence_function_addr': function.getEntryPoint(), 415 | 'refrence_function_name': function.name, 416 | 'parms': {} 417 | } 418 | 419 | parms_value = target.get_call_parm_value(call_address=call_addr) 420 | if not parms_value: 421 | continue 422 | 423 | trace_data = parms_data[call_addr] 424 | trace_data['parms'] = parms_value 425 | 426 | return parms_data 427 | 428 | 429 | if __name__ == '__main__': 430 | # search_functions = ['FUN_8003028c'] 431 | search_functions = None 432 | function_address = askLong("Input function address to trace", "Please input the function address") 433 | target_function = getFunctionAt(toAddr(function_address)) 434 | if target_function: 435 | print("target_function: {}".format(target_function)) 436 | parms_data = dump_call_parm_value(toAddr(function_address)) 437 | # print("parms: {}".format(parms)) 438 | for call_addr in parms_data: 439 | call_parms = parms_data[call_addr] 440 | parm_data_string = "" 441 | for parm in sorted(call_parms['parms'].keys()): 442 | parm_value = call_parms['parms'][parm]['parm_value'] 443 | parm_data = call_parms['parms'][parm]['parm_data'] 444 | # print("parm_data: {} type:{}".format(parm_data, type(parm_data))) 445 | # print("parm_value: {} type:{}".format(parm_value, type(parm_value))) 446 | if parm_value: 447 | parm_data_string += "{}({:#010x}), ".format(parm_data, parm_value) 448 | else: 449 | # Handle None type 450 | parm_data_string += "{}({}), ".format(parm_data, parm_value) 451 | # remove end ', ' 452 | parm_data_string = parm_data_string.strip(', ') 453 | # print("parm_data_string: {}".format(parm_data_string)) 454 | print("{}({}) at {:#010x} in {}({:#010x})".format(target_function.name, parm_data_string, 455 | call_parms['call_addr'].offset, 456 | call_parms['refrence_function_name'], 457 | call_parms['refrence_function_addr'].offset 458 | )) 459 | else: 460 | print("Can't find function at address: {:#010x}".format(function_address)) 461 | -------------------------------------------------------------------------------- /wr886nv7_rename_function_with_error_print.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from ghidra.program.model.symbol.SourceType import USER_DEFINED 3 | from galaxy_utility.function_analyzer import FunctionAnalyzer 4 | from galaxy_utility.common import get_logger 5 | import sys 6 | 7 | 8 | """ 9 | test_firmware download url. 10 | http://download.tplinkcloud.com.cn/firmware/wr886nv7-ipv6-cn-up_2019-10-25_09.43.28_1572316888807.bin 11 | """ 12 | 13 | # debug = True 14 | debug = False 15 | process_is_64bit = False 16 | logger = get_logger(__name__) 17 | 18 | if debug: 19 | logger.setLevel(10) 20 | 21 | 22 | if __name__ == '__main__': 23 | function_can_rename = {} 24 | # Function error print keywords. 25 | keywords = "Function %s assertion" 26 | 27 | # Get printf function object. 28 | printf_funciton = getFunction("printf") 29 | if not printf_funciton: 30 | sys.exit() 31 | 32 | # Get printf entry_point address object. 33 | printf_entry_point = printf_funciton.entryPoint 34 | 35 | # Get printf call references using getReferencesTo(printf_entry_point) 36 | printf_refs = getReferencesTo(printf_entry_point) 37 | for ref in printf_refs: 38 | # Only check printf call references. 39 | if ref.getReferenceType().isCall(): 40 | ref_from_address = ref.getFromAddress() 41 | logger.debug("ref_from_address: {}".format(ref_from_address)) 42 | ref_from_funciton = getFunctionContaining(ref_from_address) 43 | logger.debug("ref_from_funciton: {}".format(ref_from_funciton)) 44 | if ref_from_funciton: 45 | # Only check unnamed function. 46 | if ref_from_funciton.name.startswith('FUN_'): 47 | logger.debug(ref_from_funciton.name) 48 | # Analyze function. 49 | analyzer = FunctionAnalyzer(function=ref_from_funciton) 50 | # Get printf parms value using PCode Trace. 51 | printf_parms = analyzer.get_call_parm_value(ref_from_address) 52 | logger.debug("printf_parms: {}".format(printf_parms)) 53 | try: 54 | printf_parm1 = printf_parms["parm_1"]["parm_data"] 55 | logger.debug("printf_parm1: {}".format(printf_parm1)) 56 | if not printf_parm1: 57 | continue 58 | printf_parm1_value = printf_parm1.getValue() 59 | 60 | if "parm_2" not in printf_parms.keys(): 61 | continue 62 | printf_parm2 = printf_parms["parm_2"]["parm_data"] 63 | logger.debug("printf_parm2: {}".format(printf_parm2)) 64 | if not printf_parm2: 65 | continue 66 | printf_parm2_value = printf_parm2.getValue() 67 | 68 | # Check is function error print. 69 | if printf_parm1_value.startswith(keywords): 70 | function_name = printf_parm2_value 71 | logger.info("Rename {} to {}".format(ref_from_funciton.name, function_name)) 72 | ref_from_funciton.setName(function_name, USER_DEFINED) 73 | if ref_from_funciton not in function_can_rename.keys(): 74 | function_can_rename[ref_from_funciton.name] = { 75 | "function_entry_point": ref_from_funciton.getEntryPoint(), 76 | "function_name": function_name 77 | } 78 | 79 | except Exception as err: 80 | logger.error(err) 81 | 82 | for function_to_rename in function_can_rename: 83 | print("{}({}): {}".format(function_to_rename, 84 | function_can_rename[function_to_rename]['function_entry_point'], 85 | function_can_rename[function_to_rename]['function_name'] 86 | )) 87 | print("Renamed {} functions using error print.".format(len(function_can_rename))) 88 | --------------------------------------------------------------------------------