├── .gitignore ├── ArmRopDouble.py ├── ArmRopFind.py ├── ArmRopMovR0.py ├── ArmRopRegisterControl.py ├── ArmRopRegisterMove.py ├── ArmRopStackFinder.py ├── ArmRopSummary.py ├── ArmRopSystem.py ├── ArmToThumb.py ├── CallChain.py ├── CodatifyFixupCode.py ├── CodatifyFixupData.py ├── Fluorescence.py ├── FunctionProfiler.py ├── LeafBlowerFormatString.py ├── LeafBlowerLeafFunctions.py ├── LocalXRefs.py ├── MipsRopDouble.py ├── MipsRopEpilogue.py ├── MipsRopFind.py ├── MipsRopIret.py ├── MipsRopLia0.py ├── MipsRopPrologue.py ├── MipsRopShellcode.py ├── MipsRopStackFinder.py ├── MipsRopSummary.py ├── MipsRopSystem.py ├── MipsRopSystemChain.py ├── Operator.py ├── README.md ├── RenameVariables.py ├── RizzoApply.py ├── RizzoSave.py ├── readmes ├── armrop.md ├── callchain.md ├── codatify.md ├── fluorescence.md ├── func_profiler.md ├── img │ ├── after_code.png │ ├── after_data.png │ ├── after_xref.png │ ├── arm_dis.png │ ├── arm_rop_double.png │ ├── arm_rop_mov_r0.png │ ├── armrop_find.png │ ├── armrop_registercontrol.png │ ├── armrop_registermove.png │ ├── armrop_stackfinder.png │ ├── armrop_summary.png │ ├── armrop_system.png │ ├── before_code.png │ ├── before_data.png │ ├── before_xref.png │ ├── bookmark.png │ ├── call_chain_graph.png │ ├── call_chain_text.png │ ├── double.png │ ├── epilogue.png │ ├── epilogue_input.png │ ├── find.png │ ├── find_dialog.png │ ├── fluorescence.png │ ├── format.png │ ├── function_profiler.png │ ├── iret.png │ ├── leaf.png │ ├── lia0.png │ ├── local_xrefs.png │ ├── operator.png │ ├── prologue.png │ ├── rename_variables.png │ ├── rizzo_apply.png │ ├── rizzo_save.png │ ├── shellcode_chain.png │ ├── shellcode_options.png │ ├── stack_finder.png │ ├── summary.png │ ├── system_chain.png │ ├── system_gadget.png │ ├── thumb_dis.png │ └── thumb_gadget.png ├── leafblower.md ├── local_cross_ref.md ├── mips_rop.md ├── operator.md ├── rename_variables.md └── rizzo.md └── utils ├── __init__.py ├── armrop.py ├── functiontable.py ├── graphviz ├── __init__.py ├── _compat.py ├── backend.py ├── dot.py ├── files.py ├── lang.py └── tools.py ├── leafblower.py ├── mipsrop.py ├── mipsropchain.py ├── rizzo.py └── utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | *.pyc 3 | .pydevproject 4 | *$py.class 5 | -------------------------------------------------------------------------------- /ArmRopDouble.py: -------------------------------------------------------------------------------- 1 | # Find ARM ROP gadgets that perform two controllable jumps. 2 | #@author fuzzywalls 3 | #@category TNS 4 | #@menupath TNS.Arm Rop.Double Jumps 5 | 6 | 7 | from utils import armrop, utils 8 | 9 | utils.allowed_processors(currentProgram, 'ARM') 10 | 11 | arm_rop = armrop.ArmRop(currentProgram) 12 | doubles = arm_rop.find_doubles() 13 | 14 | doubles.pretty_print() 15 | -------------------------------------------------------------------------------- /ArmRopFind.py: -------------------------------------------------------------------------------- 1 | # Find ARM ROP gadgets that contain a user specified instruction. 2 | #@author fuzzywalls 3 | #@category TNS 4 | #@menupath TNS.Arm Rop.Find 5 | 6 | 7 | import re 8 | from utils import armrop, utils 9 | 10 | utils.allowed_processors(currentProgram, 'ARM') 11 | 12 | op1 = None 13 | op2 = None 14 | op3 = None 15 | 16 | search = askString( 17 | 'ARM ROP Find', 'What instruction do you want to search for?') 18 | try: 19 | search = re.sub(' +', ' ', search) 20 | mnem, operands = search.split(' ', 1) 21 | operands = operands.replace(' ', '') 22 | operands = operands.split(',') 23 | op1, op2, op3 = operands + [None] * (3 - len(operands)) 24 | except ValueError: 25 | mnem = search 26 | 27 | if not mnem.startswith('.*'): 28 | mnem = '.*' + mnem 29 | 30 | print 'Searching for %s' % search 31 | search_ins = armrop.ArmInstruction(mnem, op1, op2, op3) 32 | 33 | arm_rop = armrop.ArmRop(currentProgram) 34 | results = arm_rop.find_instructions([search_ins]) 35 | 36 | results.pretty_print() 37 | -------------------------------------------------------------------------------- /ArmRopMovR0.py: -------------------------------------------------------------------------------- 1 | # Find ARM ROP gadgets that load a small value into r0. Useful for calling sleep. 2 | #@author fuzzywalls 3 | #@category TNS 4 | #@menupath TNS.Arm Rop.Mov r0 5 | 6 | 7 | from utils import armrop, utils 8 | 9 | utils.allowed_processors(currentProgram, 'ARM') 10 | 11 | move_r0 = armrop.ArmInstruction('mov$', 'r0', '#0x[1-9].*') 12 | 13 | arm_rop = armrop.ArmRop(currentProgram) 14 | small_value_to_r0 = arm_rop.find_instructions([move_r0]) 15 | 16 | small_value_to_r0.pretty_print() 17 | -------------------------------------------------------------------------------- /ArmRopRegisterControl.py: -------------------------------------------------------------------------------- 1 | # Find ARM ROP gadgets that give control of registers by popping them off the stack. 2 | #@author fuzzywalls 3 | #@category TNS 4 | #@menupath TNS.Arm Rop.Register Control 5 | 6 | 7 | from utils import armrop, utils 8 | 9 | utils.allowed_processors(currentProgram, 'ARM') 10 | 11 | reg = askChoice('Source Register', 12 | 'What register do you want to control?', 13 | ['Any', 'r0', 'r1', 'r2', 'r3', 'r4', 'r5', 'r6', 'r7', 'r8', 14 | 'r9', 'r10', 'r11', 'r12'], 15 | 'Any') 16 | 17 | if reg == 'Any': 18 | print 'Searching for gadgets that give control of any register.' 19 | reg = '.*(r[01]?\d).*' 20 | else: 21 | print 'Searching for gadgets that give control of %s.' % reg 22 | 23 | reg = '.*' + reg + ' .*' 24 | 25 | reg_control = armrop.ArmInstruction('ldmia', 'sp!', reg) 26 | 27 | arm_rop = armrop.ArmRop(currentProgram) 28 | control = arm_rop.find_instructions([reg_control], controllable_calls=False) 29 | 30 | control.pretty_print() 31 | -------------------------------------------------------------------------------- /ArmRopRegisterMove.py: -------------------------------------------------------------------------------- 1 | # Find ARM ROP gadgets that move values between registers. 2 | #@author fuzzywalls 3 | #@category TNS 4 | #@menupath TNS.Arm Rop.Register Movement 5 | 6 | 7 | from utils import armrop, utils 8 | 9 | utils.allowed_processors(currentProgram, 'ARM') 10 | 11 | src_reg = askChoice('Source Register', 12 | 'What source register do you want to move?', 13 | ['Any', 'r0', 'r1', 'r2', 'r3', 'r4', 'r5', 14 | 'r6', 'r7', 'r8', 'r9', 'r10', 'r11', 'r12'], 15 | 'Any') 16 | 17 | dest_reg = askChoice('Destination Register', 18 | 'Where do you want the source to be moved?', 19 | ['Any', 'r0', 'r1', 'r2', 'r3', 'r4', 'r5', 20 | 'r6', 'r7', 'r8', 'r9', 'r10', 'r11', 'r12'], 21 | 'Any') 22 | 23 | print "Searching for gadgets that move %s to %s" % (src_reg, dest_reg) 24 | 25 | if src_reg == 'Any': 26 | src_reg = '^(r[01]?\d)' 27 | else: 28 | src_reg += '$' 29 | 30 | if dest_reg == 'Any': 31 | dest_reg = '^(r[01]?\d)' 32 | else: 33 | dest_reg += '$' 34 | 35 | 36 | cpy_reg = armrop.ArmInstruction('cpy', dest_reg, src_reg) 37 | move_reg = armrop.ArmInstruction('mov', dest_reg, src_reg) 38 | 39 | arm_rop = armrop.ArmRop(currentProgram) 40 | stack_finders = arm_rop.find_instructions([cpy_reg, move_reg]) 41 | 42 | stack_finders.pretty_print() 43 | -------------------------------------------------------------------------------- /ArmRopStackFinder.py: -------------------------------------------------------------------------------- 1 | # Find ARM ROP gadgets that put a stack address in a register. Useful for calling functions with a user controlled string. 2 | #@author fuzzywalls 3 | #@category TNS 4 | #@menupath TNS.Arm Rop.Stack Finder 5 | 6 | 7 | from utils import armrop, utils 8 | 9 | utils.allowed_processors(currentProgram, 'ARM') 10 | 11 | sf_saved_reg = armrop.ArmInstruction('add', '^(r[01]?\d)', 'sp') 12 | 13 | arm_rop = armrop.ArmRop(currentProgram) 14 | stack_finders = arm_rop.find_instructions([sf_saved_reg]) 15 | 16 | stack_finders.pretty_print() 17 | -------------------------------------------------------------------------------- /ArmRopSummary.py: -------------------------------------------------------------------------------- 1 | # Print a summary of ROP gadgets that are bookmarked with ropX. 2 | #@author fuzzywalls 3 | #@category TNS 4 | #@menupath TNS.Arm Rop.Summary 5 | 6 | 7 | from utils import armrop, utils 8 | 9 | utils.allowed_processors(currentProgram, 'ARM') 10 | 11 | arm_rop = armrop.ArmRop(currentProgram) 12 | arm_rop.summary() 13 | -------------------------------------------------------------------------------- /ArmRopSystem.py: -------------------------------------------------------------------------------- 1 | # Find ARM ROP gadgets for calling system with a user controlled argument. 2 | #@author fuzzywalls 3 | #@category TNS 4 | #@menupath TNS.Arm Rop.System Calls 5 | 6 | 7 | from utils import armrop, utils 8 | 9 | utils.allowed_processors(currentProgram, 'ARM') 10 | 11 | set_a0 = armrop.ArmInstruction('add', 'r0', 'sp') 12 | 13 | arm_rop = armrop.ArmRop(currentProgram) 14 | system_rops = arm_rop.find_instructions([set_a0], 'a0') 15 | 16 | system_rops.pretty_print() 17 | -------------------------------------------------------------------------------- /ArmToThumb.py: -------------------------------------------------------------------------------- 1 | # Convert executable sections to Thumb instructions to find new gadgets. 2 | #@author fuzzywalls 3 | #@category TNS 4 | #@menupath TNS.Arm Rop.Convert To Thumb 5 | 6 | from utils import utils 7 | 8 | from ghidra.util.task import CancelledListener 9 | from ghidra.app.services import ConsoleService 10 | from ghidra.app.cmd.disassemble import ArmDisassembleCommand 11 | 12 | 13 | utils.allowed_processors(currentProgram, 'ARM') 14 | 15 | 16 | def find_code(): 17 | """ 18 | Find executable code sections and return an address set representing the 19 | range. 20 | """ 21 | code_sections = [] 22 | 23 | addr_factory = currentProgram.getAddressFactory() 24 | memory_manager = currentProgram.getMemory() 25 | addr_view = memory_manager.getExecuteSet() 26 | 27 | for section in addr_view: 28 | new_view = addr_factory.getAddressSet(section.getMinAddress(), 29 | section.getMaxAddress()) 30 | code_sections.append(new_view) 31 | 32 | return code_sections 33 | 34 | 35 | class CancelListener(CancelledListener): 36 | def __init__(self, transaction): 37 | super(CancelledListener, self).__init__() 38 | self.transaction = transaction 39 | 40 | def cancelled(self): 41 | if self.transaction: 42 | print('User cancelled, cleaning up Thumb disassembly.') 43 | currentProgram.endTransaction(self.transaction, False) 44 | self.transaction = None 45 | 46 | 47 | code_manager = currentProgram.getCodeManager() 48 | addr_factory = currentProgram.getAddressFactory() 49 | code_sections = find_code() 50 | 51 | try: 52 | transaction = currentProgram.startTransaction('thumb') 53 | commit_changes = True 54 | 55 | # Create a cancel listener so we can cleanup if the user cancels the 56 | # operation 57 | cancel = CancelListener(transaction) 58 | monitor = getMonitor() 59 | monitor.addCancelledListener(cancel) 60 | 61 | for section in code_sections: 62 | print 'Converting operations to Thumb in section %s' % section 63 | clearListing(section) 64 | undefined = code_manager.getFirstUndefinedData(section, monitor) 65 | while undefined and undefined.getAddress() < section.getMaxAddress(): 66 | curr_address = undefined.getAddress() 67 | 68 | # Create an address set on a single instruction. If more than one 69 | # instruction is performed at a time it will follow jumps and 70 | # disassemble code elsewhere, likely as ARM instructions. 71 | single_ins = addr_factory.getAddressSet(curr_address, curr_address) 72 | disassm = ArmDisassembleCommand(single_ins, single_ins, True) 73 | disassm.enableCodeAnalysis(False) 74 | disassm.applyTo(currentProgram) 75 | undefined = code_manager.getFirstUndefinedDataAfter( 76 | curr_address, monitor) 77 | except Exception as e: 78 | print str(e) 79 | commit_changes = False 80 | finally: 81 | currentProgram.endTransaction(transaction, commit_changes) 82 | monitor.removeCancelledListener(cancel) 83 | -------------------------------------------------------------------------------- /CallChain.py: -------------------------------------------------------------------------------- 1 | # Display call chain graph between two functions and output to the console. 2 | #@author fuzzywalls 3 | #@category TNS 4 | #@menupath TNS.Call Chain 5 | 6 | import os 7 | import sys 8 | import tempfile 9 | from utils import graphviz 10 | 11 | from ghidra.util.graph import DirectedGraph, Vertex, Edge 12 | from ghidra.graph.viewer import GraphComponent 13 | from ghidra.app.services import GraphService 14 | 15 | found_call_chain = False 16 | 17 | 18 | def get_references(caller, callee): 19 | """ 20 | Find reference addresses between a caller function and callee function. 21 | 22 | :param caller: Caller function. 23 | :param callee: Callee function. 24 | 25 | :return: List of addresses where the caller calls the callee. 26 | :rtype: list 27 | """ 28 | function_manager = currentProgram.getFunctionManager() 29 | 30 | ref_list = [] 31 | callee_symbol = callee.getSymbol() 32 | callee_references = callee_symbol.getReferences() 33 | 34 | for ref in callee_references: 35 | addr = ref.getFromAddress() 36 | func = function_manager.getFunctionContaining(addr) 37 | if func == caller: 38 | ref_list.append(addr) 39 | 40 | return ref_list 41 | 42 | 43 | def sanitize_dot(func): 44 | """ 45 | Return a sanitized function name string compatible with DOT representation. 46 | 47 | :param func: Function object 48 | """ 49 | return str(func).replace("::", "\\") 50 | 51 | 52 | def print_call_chain(call_chain, dot): 53 | """ 54 | Print successful call chains to the console and add to the graph. 55 | 56 | :param call_chain: Successful call chain. 57 | :param dot: Call graph. 58 | """ 59 | previous_function = None 60 | function_references = {} 61 | function_chain = [] 62 | 63 | for function in call_chain: 64 | references = [] 65 | 66 | function_name = sanitize_dot(function) 67 | 68 | if function == call_chain[0]: 69 | dot.node(function_name, str(function), style='filled', 70 | color='blue', fontcolor='white') 71 | elif function == call_chain[-1]: 72 | dot.node(function_name, str(function), style='filled', 73 | color='red', fontcolor='white') 74 | else: 75 | dot.node(function_name, str(function)) 76 | if previous_function: 77 | previous_function_name = sanitize_dot(previous_function) 78 | dot.edge(previous_function_name, function_name) 79 | function_references[str(previous_function)] = get_references( 80 | previous_function, function) 81 | 82 | previous_function = function 83 | function_chain.append(str(function)) 84 | 85 | for function in function_chain: 86 | print function, 87 | if function in function_references: 88 | print function_references[function], 89 | print ' -> ', 90 | print '' 91 | 92 | 93 | def call_chain_recurse(call_chain, complete_call, dot): 94 | """ 95 | Recurse from call_chain to complete_call, if found. 96 | 97 | :param call_chain: Current call chain. 98 | :param complete_call: Call that indicates a successfully completed chain. 99 | :param dot: Call graph 100 | """ 101 | global found_call_chain 102 | 103 | function_list = call_chain[0].getCallingFunctions(monitor) 104 | for func in function_list: 105 | if func == complete_call: 106 | print_call_chain([func] + call_chain, dot) 107 | found_call_chain = True 108 | continue 109 | 110 | if func in call_chain: 111 | continue 112 | call_chain_recurse([func] + call_chain, complete_call, dot) 113 | 114 | 115 | def discover_call_chain(from_function, to_function): 116 | """ 117 | Discover call chains between two functions. 118 | 119 | :param from_function: Function start looking for path to next function. 120 | :param to_function: Function that, when/if found, indicates a chain. 121 | """ 122 | dot = graphviz.Digraph('Function Paths', format='png', strict=True) 123 | call_chain_recurse([to_function], from_function, dot) 124 | 125 | if found_call_chain: 126 | tmp_file = tempfile.mktemp() 127 | dot.render(tmp_file, view=True) 128 | 129 | 130 | func_man = currentProgram.getFunctionManager() 131 | function_list = [function for function in func_man.getFunctions(True)] 132 | function_list.sort(key=lambda func: str(func)) 133 | 134 | from_function = askChoice('Select function', 135 | 'Select the starting function', 136 | function_list, 137 | function_list[0]) 138 | 139 | function_list.remove(from_function) 140 | to_function = askChoice('Select function', 141 | 'Select the ending function', 142 | function_list, 143 | function_list[0]) 144 | 145 | print 'Finding x-refs from %s to %s\n' % (from_function, to_function) 146 | 147 | discover_call_chain(from_function, to_function) 148 | -------------------------------------------------------------------------------- /CodatifyFixupCode.py: -------------------------------------------------------------------------------- 1 | # Fixup .text section by defining all undefined code and converting it to a function if applicable. 2 | #@author fuzzywalls 3 | #@category TNS 4 | #@menupath TNS.Codatify.Fixup Code 5 | 6 | import time 7 | import string 8 | 9 | from ghidra.program.flatapi import FlatProgramAPI 10 | from ghidra.program.model.symbol import FlowType 11 | 12 | # Adjust to your own preference. 13 | FUNCTION_PREFIX = 'CFUN_%s' 14 | 15 | 16 | def find_code(): 17 | """ 18 | Find executable code sections and return an address set representing the 19 | range. 20 | """ 21 | code_sections = [] 22 | 23 | addr_factory = currentProgram.getAddressFactory() 24 | memory_manager = currentProgram.getMemory() 25 | addr_view = memory_manager.getExecuteSet() 26 | 27 | for section in addr_view: 28 | new_view = addr_factory.getAddressSet(section.getMinAddress(), 29 | section.getMaxAddress()) 30 | code_sections.append(new_view) 31 | 32 | return code_sections 33 | 34 | def is_aligned_instruction_address(inst_address): 35 | """ 36 | Checks if the address is aligned according to the instruction alignment 37 | defined by the currentProgram's language 38 | 39 | :param inst_address: Address of a potential instruction 40 | :type inst_address: ghidra.program.model.listing.Address 41 | 42 | :returns: True if inst_address is properly aligned, False otherwise. 43 | """ 44 | alignment = currentProgram.getLanguage().getInstructionAlignment() 45 | return inst_address.offset % alignment == 0 46 | 47 | 48 | def is_valid_function_end(last_instruction): 49 | """ 50 | Rudimentary valid function checker. Simply checks the last instruction for a 51 | terminating instruction, taking into account delay slots. 52 | 53 | :param last_instruction: Last instruction in a function. 54 | :type last_instruction: ghidra.program.model.listing.Instruction 55 | 56 | :returns: True if valid function, False otherwise. 57 | """ 58 | if last_instruction is None: 59 | return False 60 | 61 | if last_instruction.isInDelaySlot(): 62 | last_instruction = getInstructionAt(last_instruction.getFallFrom()) 63 | 64 | if last_instruction.getFlowType() == FlowType.TERMINATOR: 65 | return True 66 | return False 67 | 68 | 69 | def find_function_end(function, max_address, instruction_length): 70 | """ 71 | Find the last instruction in a newly created function up to a maximum 72 | address to prevent overrun into previously defined code. 73 | 74 | :param function: Newly created function to find end address. 75 | :type function: ghidra.program.model.listing.Function 76 | 77 | :param max_address: Max address to return. 78 | :type max_address: ghidra.program.model.listing.Address 79 | 80 | :param instruction_length: Instruction length to account for the functions 81 | max address functionality returning a value that 82 | representing the last byte of the last instruction. 83 | :type instruction_length: int 84 | 85 | :returns: Last instruction in the function. 86 | :rtype: ghidra.program.model.listing.Instruction 87 | """ 88 | if not function: 89 | raise Exception('Invalid function provided.') 90 | if not max_address: 91 | raise('Invalid max address provided.') 92 | 93 | # Fix-up function max address to be aligned on the instruction length 94 | # boundary. MIPS at least returns the last byte of the last instruction 95 | # which is not on the required 4 byte boundary. 96 | function_max = function.getBody().getMaxAddress() 97 | function_max = function_max.subtract( 98 | function_max.getOffset() % instruction_length) 99 | 100 | comparison = function_max.compareTo(max_address.getAddress()) 101 | if comparison == 1: 102 | return max_address 103 | return getInstructionAt(function_max) 104 | 105 | 106 | def clear_listing(addr_list, symbols=False, function=False, register=False): 107 | """ 108 | Remove symbols, function, or registers from the list of addresses. 109 | Attempting to remove multiple entries concurrently may lead to an 110 | exception. 111 | 112 | :param addr_list: List of addresses to clear listings from. 113 | :type addr_list: list(ghidra.program.model.listing.Address) 114 | 115 | :param symbols: Remove symbol listing. 116 | :type symbols: bool 117 | 118 | :param function: Remove function listing. 119 | :type function: bool 120 | 121 | :param register: Remove register listing. 122 | :type register: bool 123 | """ 124 | addr_factory = getAddressFactory() 125 | if not addr_factory: 126 | raise Exception("Failed to get address factory.") 127 | 128 | for addr in addr_list: 129 | addr_set = addr_factory.getAddressSet(addr, addr) 130 | clearListing(addr_set, False, symbols, False, False, function, register, 131 | False, False, False, False, False, False) 132 | 133 | 134 | def is_string(addr): 135 | """ 136 | Check if address contains a 3 byte minimum string. 137 | """ 138 | curr_bytes = getBytes(addr, 4) 139 | try: 140 | result = map(chr, curr_bytes.tolist()) 141 | if '\x00' in result[:3]: 142 | return False 143 | for character in result: 144 | if character == '\x00': 145 | continue 146 | if character not in string.printable: 147 | return False 148 | return True 149 | except ValueError: 150 | return False 151 | 152 | 153 | def define_code_and_functions(start_addr, end_addr): 154 | """ 155 | Convert undefined code in the section provided to defined code and a 156 | function if applicable. 157 | 158 | :param section: Section to search for undefined code. 159 | :type section: ghidra.program.model.listing.ProgramFragment 160 | """ 161 | function_count = 0 162 | code_block_count = 0 163 | invalid_functions = [] 164 | 165 | undefined_code = getUndefinedDataAt(start_addr) 166 | if undefined_code is None: 167 | undefined_code = getUndefinedDataAfter(start_addr) 168 | 169 | # Loop through all undefined code in the provided section. 170 | while undefined_code is not None and undefined_code.getAddress() < end_addr: 171 | undefined_addr = undefined_code.getAddress() 172 | next_valid_ins = getInstructionAfter(undefined_addr) 173 | 174 | try: 175 | if is_string(undefined_addr): 176 | # Sometimes strings hang out in the executable code section. 177 | # This can introduce false positives though. 178 | createAsciiString(undefined_addr) 179 | continue 180 | 181 | if not is_aligned_instruction_address(undefined_addr): 182 | continue 183 | disassemble(undefined_addr) 184 | 185 | instruction_length = getInstructionAt(undefined_addr).getLength() 186 | last_invalid_ins = getInstructionBefore( 187 | next_valid_ins.getAddress()) 188 | 189 | # Create a function around the code and check for validity. 190 | new_func = createFunction( 191 | undefined_addr, FUNCTION_PREFIX % undefined_addr) 192 | 193 | if new_func is None: 194 | continue 195 | 196 | last_instruction = find_function_end( 197 | new_func, last_invalid_ins, instruction_length) 198 | 199 | if is_valid_function_end(last_instruction): 200 | function_count += 1 201 | else: 202 | invalid_functions.append(undefined_addr) 203 | code_block_count += 1 204 | except: 205 | continue 206 | finally: 207 | undefined_code = getUndefinedDataAfter(undefined_addr) 208 | 209 | # If the functions are removed immediately it will cause some race condition 210 | # exceptions to be raised with Call Fix-up routine. If all listings are 211 | # removed in one go it raises an exception, sometimes. 212 | clear_listing(invalid_functions, symbols=True) 213 | time.sleep(1) 214 | clear_listing(invalid_functions, function=True) 215 | time.sleep(1) 216 | clear_listing(invalid_functions, register=True) 217 | 218 | print 'Converted {} undefined code block and created {} new functions in range {} -> {}.'.format( 219 | code_block_count, function_count, start_addr, end_addr) 220 | 221 | 222 | # define_code_and_functions() 223 | for executable_section in find_code(): 224 | define_code_and_functions(executable_section.getMinAddress(), 225 | executable_section.getMaxAddress()) 226 | -------------------------------------------------------------------------------- /CodatifyFixupData.py: -------------------------------------------------------------------------------- 1 | # Fixup .data and .rodata sections by defining strings and forcing remaining undefined data to be a DWORD. 2 | #@author fuzzywalls 3 | #@category TNS 4 | #@menupath TNS.Codatify.Fixup Data 5 | 6 | 7 | from utils import functiontable 8 | 9 | from ghidra.program.model.data import PointerDataType 10 | 11 | 12 | def find_data_sections(): 13 | """ 14 | Search for non-executable sections in the memory map. 15 | """ 16 | data_sections = [] 17 | 18 | # Find all memory sections and remove the executable sections. 19 | addr_factory = currentProgram.getAddressFactory() 20 | memory_manager = currentProgram.getMemory() 21 | address_ranges = memory_manager.getLoadedAndInitializedAddressSet() 22 | executable_set = memory_manager.getExecuteSet() 23 | 24 | addr_view = address_ranges.xor(executable_set) 25 | 26 | for section in addr_view: 27 | new_view = addr_factory.getAddressSet(section.getMinAddress(), 28 | section.getMaxAddress()) 29 | data_sections.append(new_view) 30 | 31 | return data_sections 32 | 33 | 34 | def define_strings(section): 35 | """ 36 | Convert undefined strings in the section provided to ascii. 37 | 38 | :param section: Section to search for undefined strings. 39 | :type section: ghidra.program.model.listing.ProgramFragment 40 | """ 41 | if section is None: 42 | return 43 | 44 | strings = findStrings(section, 1, 1, True, True) 45 | 46 | string_count = 0 47 | for string in strings: 48 | if getUndefinedDataAt(string.getAddress()): 49 | try: 50 | createAsciiString(string.getAddress()) 51 | string_count += 1 52 | except: 53 | continue 54 | 55 | print 'Strings - {}'.format(string_count) 56 | 57 | 58 | def get_pointer_type(): 59 | """ 60 | Get the correct pointer size for the current architecture. 61 | """ 62 | return PointerDataType(None, currentProgram.getDefaultPointerSize()) 63 | 64 | 65 | def define_pointers(section): 66 | """ 67 | Convert undefined data to valid pointers. 68 | 69 | :param section: The section to convert pointers in. 70 | :type section: ghidra.program.model.listing.ProgramFragment 71 | """ 72 | if section is None: 73 | return 74 | 75 | start_addr = section.getMinAddress() 76 | end_addr = section.getMaxAddress() 77 | 78 | undefined_data = getUndefinedDataAt(start_addr) 79 | if undefined_data is None: 80 | undefined_data = getUndefinedDataAfter(start_addr) 81 | 82 | pointer_count = 0 83 | pointer_type = get_pointer_type() 84 | memory_manager = currentProgram.getMemory() 85 | 86 | while undefined_data is not None and undefined_data.getAddress() < end_addr: 87 | undefined_addr = undefined_data.getAddress() 88 | try: 89 | # At each undefined byte, convert it to a pointer and see if it 90 | # has any valid references. If it does validate the reference goes 91 | # to a valid memory address using the memory manager. 92 | createData(undefined_addr, pointer_type) 93 | references = getReferencesFrom(undefined_addr) 94 | if len(references): 95 | if memory_manager.contains(references[0].getToAddress()): 96 | pointer_count += 1 97 | else: 98 | removeDataAt(undefined_addr) 99 | else: 100 | removeDataAt(undefined_addr) 101 | except: 102 | pass 103 | finally: 104 | undefined_data = getUndefinedDataAfter(undefined_addr) 105 | 106 | print 'Pointers - {}'.format(pointer_count) 107 | 108 | 109 | def define_data(section): 110 | """ 111 | Convert undefined data to a DWORD. 112 | 113 | :param section: Section to search for undefined data in. 114 | :type section: hidra.program.model.listing.ProgramFragment 115 | """ 116 | if section is None: 117 | return 118 | 119 | start_addr = section.getMinAddress() 120 | end_addr = section.getMaxAddress() 121 | 122 | undefined_data = getUndefinedDataAt(start_addr) 123 | if undefined_data is None: 124 | undefined_data = getUndefinedDataAfter(start_addr) 125 | 126 | data_count = 0 127 | while undefined_data is not None and undefined_data.getAddress() < end_addr: 128 | undefined_addr = undefined_data.getAddress() 129 | undefined_data = getUndefinedDataAfter(undefined_addr) 130 | try: 131 | createDWord(undefined_addr) 132 | data_count += 1 133 | except: 134 | continue 135 | 136 | print 'DWORDS - {}'.format(data_count) 137 | 138 | 139 | def fixup_section(section): 140 | """ 141 | Fixup the section by defining strings and converting undefined data to 142 | DWORDs. 143 | 144 | :param section: Section to fixup. 145 | :type section: str 146 | """ 147 | print 'Section {} - {}'.format(section.getMinAddress(), 148 | section.getMaxAddress()) 149 | print '-' * 30 150 | 151 | define_pointers(section) 152 | define_strings(section) 153 | define_data(section) 154 | 155 | ft_finder = functiontable.Finder(currentProgram, section) 156 | ft_finder.find_function_table() 157 | ft_finder.rename_functions() 158 | 159 | print '\n' 160 | 161 | 162 | # Base address of 0 can really mess things up when fixing up pointers. 163 | # Make sure the user really wants this. 164 | base_addr = currentProgram.getMinAddress() 165 | if base_addr.toString() == u'00000000' and \ 166 | not askYesNo('Base Address Zero', 'The base address is set to 0 which can ' 167 | 'introduce a large amount of false positives when fixing up ' 168 | 'the data section. \nDo you want to continue?'): 169 | exit(0) 170 | 171 | data_sections = find_data_sections() 172 | 173 | print 'Fixing up data...\n' 174 | for section in data_sections: 175 | fixup_section(section) 176 | -------------------------------------------------------------------------------- /Fluorescence.py: -------------------------------------------------------------------------------- 1 | #Highlight or un-highlight function calls. 2 | #@author fuzzywalls 3 | #@category TNS 4 | #@menupath TNS.Un/Highlight Function Calls 5 | 6 | 7 | from java.awt import Color 8 | from ghidra.program.model.symbol import RefType 9 | 10 | answer = askChoice('Highlight?', 11 | 'Highlight or un-highlight function calls?', 12 | ['highlight', 'un-highlight'], 13 | 'highlight') 14 | 15 | # Dull yellow color. 16 | highlight_color = Color(250, 250, 125) 17 | 18 | code_manager = currentProgram.getCodeManager() 19 | image_base = currentProgram.getImageBase() 20 | 21 | instructions = code_manager.getInstructions(image_base, True) 22 | 23 | for ins in instructions: 24 | if ins.getFlowType().isCall(): 25 | if answer == 'highlight': 26 | setBackgroundColor(ins.getAddress(), highlight_color) 27 | else: 28 | clearBackgroundColor(ins.getAddress()) 29 | -------------------------------------------------------------------------------- /FunctionProfiler.py: -------------------------------------------------------------------------------- 1 | # Find all cross references in the current function. 2 | #@author fuzzywalls 3 | #@category TNS 4 | #@menupath TNS.Function Profiler 5 | 6 | from utils import utils 7 | from ghidra.program.model.symbol import RefType,SymbolType 8 | 9 | 10 | class CrossRef(object): 11 | def __init__(self, reference): 12 | to_addr = reference.getToAddress() 13 | symbol = getSymbolAt(to_addr) 14 | 15 | self.from_addr = reference.getFromAddress() 16 | self.symbol_type = str(symbol.getSymbolType()) 17 | 18 | symbol_object = symbol.getObject() 19 | try: 20 | if symbol_object.hasStringValue(): 21 | symbol_name = str(symbol.getObject()) 22 | if symbol_name.startswith('ds '): 23 | self.symbol_name = symbol_name[3:] 24 | self.symbol_type = 'String' 25 | else: 26 | self.symbol_name = symbol.getName() 27 | except AttributeError: 28 | self.symbol_name = symbol.getName() 29 | 30 | def __str__(self): 31 | return '{:12} {}'.format(self.from_addr, self.symbol_name) 32 | 33 | 34 | class FunctionCrossReferences(object): 35 | def __init__(self, function): 36 | self.function = function 37 | self.cross_references = [] 38 | 39 | def append(self, cross_ref): 40 | self.cross_references.append(cross_ref) 41 | 42 | def _loop_print(self, label): 43 | print '\n{}\n{}'.format(label, '-' * len(label)) 44 | for cr in self.cross_references: 45 | if cr.symbol_type == label: 46 | print cr 47 | 48 | def pretty_print(self): 49 | print '\nCross References in {}\n{}'.format(function, '-' * 30) 50 | self._loop_print('String') 51 | self._loop_print('Function') 52 | self._loop_print('Label') 53 | print 54 | 55 | code_manager = currentProgram.getCodeManager() 56 | function_manager = currentProgram.getFunctionManager() 57 | function = utils.get_function(function_manager, currentLocation.address) 58 | 59 | if function is None: 60 | print 'Current selection is not a function.' 61 | exit() 62 | 63 | 64 | cross_references = FunctionCrossReferences(function) 65 | 66 | instructions = utils.get_instruction_list(code_manager, function) 67 | for instruction in instructions: 68 | for reference in instruction.getReferencesFrom(): 69 | ref_type = reference.getReferenceType() 70 | if reference.isMemoryReference() and \ 71 | ref_type != RefType.CONDITIONAL_JUMP and \ 72 | ref_type != RefType.UNCONDITIONAL_JUMP: 73 | cross_references.append(CrossRef(reference)) 74 | 75 | cross_references.pretty_print() 76 | -------------------------------------------------------------------------------- /LeafBlowerFormatString.py: -------------------------------------------------------------------------------- 1 | # Identify potential POSIX functions in the current program such as sprintf, fprintf, sscanf, etc. 2 | #@author fuzzywalls 3 | #@category TNS 4 | #@menupath TNS.Leaf Blower.Find format string functions 5 | 6 | 7 | from utils import leafblower 8 | 9 | print 'Searching for format string functions...' 10 | format_string_finder = leafblower.FormatStringFunctionFinder(currentProgram) 11 | format_string_finder.find_functions() 12 | format_string_finder.display() 13 | -------------------------------------------------------------------------------- /LeafBlowerLeafFunctions.py: -------------------------------------------------------------------------------- 1 | # Identify potential POSIX functions in the current program such as strcpy, strcat, memcpy, atoi, strlen, etc. 2 | #@author fuzzywalls 3 | #@category TNS 4 | #@menupath TNS.Leaf Blower.Find leaf functions 5 | 6 | 7 | from utils import leafblower 8 | 9 | print 'Searching for potential POSIX leaf functions...' 10 | leaf_finder = leafblower.LeafFunctionFinder(currentProgram) 11 | leaf_finder.find_leaves() 12 | leaf_finder.display() 13 | -------------------------------------------------------------------------------- /LocalXRefs.py: -------------------------------------------------------------------------------- 1 | #Find local references to selected registers and local variables in the current function. 2 | #@author fuzzywalls 3 | #@category TNS 4 | #@keybinding 5 | #@menupath TNS.Local X-Refs 6 | 7 | 8 | import re 9 | from ghidra.program.model.listing import CodeUnit 10 | 11 | class InstructionMatch(object): 12 | def __init__(self, address, direction, ref_type, instruction): 13 | self.address = address 14 | self.direction = direction 15 | self.ref_type = ref_type 16 | self.instruction = instruction 17 | 18 | def __str__(self): 19 | return '{:7} {:10} {:12} {}'.format(self.direction, 20 | self.ref_type, 21 | self.address, 22 | self.instruction) 23 | 24 | 25 | def get_function(address): 26 | """ 27 | Return the function that contains the address. 28 | 29 | :param address: Address within function. 30 | 31 | :returns: Function that contains the provided address. 32 | """ 33 | function_manager = currentProgram.getFunctionManager() 34 | return function_manager.getFunctionContaining(address) 35 | 36 | 37 | def get_instruction_list(function): 38 | """ 39 | Get list of instructions in the function. 40 | 41 | :param function: Function to parse for instruction list. 42 | 43 | :returns: List of instructions. 44 | """ 45 | code_manager = currentProgram.getCodeManager() 46 | function_bounds = function.getBody() 47 | function_instructions = code_manager.getInstructions(function_bounds, True) 48 | return function_instructions 49 | 50 | 51 | def get_sub_operation(selection): 52 | """ 53 | Get sub operation within operation. Usually a register offset. 54 | 55 | :param selection: Current mouse selection. 56 | 57 | :returns: Sub operation within current operation. 58 | """ 59 | sub_index = currentLocation.getSubOperandIndex() 60 | split_selection = re.findall(r"[\w']+", selection) 61 | index = 0 if sub_index == 0 else 1 62 | try: 63 | sub_operation = split_selection[index] 64 | except IndexError: 65 | sub_operation = split_selection[0] 66 | 67 | return sub_operation 68 | 69 | 70 | def process_function_call(operation, offset): 71 | """ 72 | Process function calls to determine if the register or function is selected. 73 | 74 | :param operation: Full operation to process. 75 | :param offset: Offset of mouse in the operation. 76 | 77 | :returns: Selected item in the operation. 78 | """ 79 | equal_index = operation.index('=>') 80 | items = operation.split('=>') 81 | if offset < equal_index: 82 | return items[0] 83 | return items[1] 84 | 85 | 86 | def get_selection(current_function, force_address=False): 87 | """ 88 | Get the current selection. 89 | 90 | :param function: Function that contains the selection. 91 | :param force_address: Force the use of the raw address, used to process 92 | labels. 93 | 94 | :returns: String representing the current selection. 95 | """ 96 | # If the selection is a label this will not throw an exception. 97 | try: 98 | selection = currentLocation.getName() 99 | print '\nXrefs to {} in {}:'.format(selection, current_function) 100 | except AttributeError: 101 | try: 102 | is_variable = currentLocation.getVariableOffset() 103 | if is_variable is not None: 104 | variable = currentLocation.getRefAddress() 105 | stack = current_function.getStackFrame() 106 | stack_size = stack.getFrameSize() 107 | stack_variable = stack_size + variable.getOffset() 108 | selection = hex(stack_variable)[:-1] 109 | print '\nXrefs to {}({}) in {}:'.format(selection, 110 | is_variable, 111 | current_function) 112 | elif force_address: 113 | address = currentLocation.getRefAddress().getOffset() 114 | selection = str.format('0x{:08x}', address) 115 | print '\nXrefs to {} in {}:'.format(selection, current_function) 116 | else: 117 | selection = currentLocation.getOperandRepresentation() 118 | if '=>' in selection: 119 | selection = process_function_call( 120 | selection, currentLocation.getCharOffset()) 121 | selection = get_sub_operation(selection) 122 | print '\nXrefs to {} in {}:'.format(selection, current_function) 123 | 124 | except AttributeError: 125 | print 'No value selected.' 126 | exit() 127 | 128 | print '-' * 60 129 | return selection 130 | 131 | 132 | def check_flows(instruction, target): 133 | """ 134 | Search instruction flows to see if they match the target. 135 | 136 | :param instruction: Current instruction. 137 | :param target: Target instruction. 138 | 139 | :returns: True if a flow matches the target, False otherwise. 140 | """ 141 | flows = instruction.getFlows() 142 | for flow in flows: 143 | if flow.toString() in target: 144 | return True 145 | return False 146 | 147 | def create_match(instruction, index=0): 148 | """ 149 | Create instruction match class from instruction and the operand index. 150 | """ 151 | ins_addr = instruction.getAddress() 152 | if ins_addr > currentLocation.address: 153 | direction = 'DOWN' 154 | elif ins_addr < currentLocation.address: 155 | direction = 'UP' 156 | else: 157 | direction = '-' 158 | ref_type = instruction.getOperandRefType(index) 159 | 160 | match = InstructionMatch( 161 | ins_addr, direction, ref_type, instruction) 162 | 163 | return match 164 | 165 | def find_instruction_matches(function, target): 166 | """ 167 | Find instructions that contain the target value. 168 | 169 | :param function: Function to search. 170 | :param target: Target to search for in each operation. 171 | 172 | :returns: List of instruction matches. 173 | """ 174 | instruction_matches = [] 175 | function_instructions = get_instruction_list(function) 176 | for instruction in function_instructions: 177 | # Labels don't show up so check the flow, if its a call see if it 178 | # contains the target. 179 | if check_flows(instruction, target): 180 | match = create_match(instruction) 181 | instruction_matches.append(match) 182 | continue 183 | 184 | # Check each operand of the instruction for the target. 185 | operand_count = instruction.getNumOperands() 186 | for index in range(0, operand_count): 187 | operand = instruction.getDefaultOperandRepresentation(index) 188 | if target in operand: 189 | match = create_match(instruction, index) 190 | instruction_matches.append(match) 191 | break 192 | 193 | return instruction_matches 194 | 195 | 196 | function = get_function(currentLocation.address) 197 | selection = get_selection(function) 198 | matches = find_instruction_matches(function, selection) 199 | 200 | # If no matches were found search again using the raw address of the selection. 201 | if not matches: 202 | print 'No matches found for %s, searching again with the raw address.' % \ 203 | selection 204 | selection = get_selection(function, True) 205 | matches = find_instruction_matches(function, selection) 206 | 207 | if matches: 208 | 209 | for match in matches: 210 | print match 211 | else: 212 | print 'No matches found for {} in {}.'.format(selection, function) -------------------------------------------------------------------------------- /MipsRopDouble.py: -------------------------------------------------------------------------------- 1 | # Find MIPS ROP gadgets that perform two controllable jumps. 2 | #@author fuzzywalls 3 | #@category TNS 4 | #@menupath TNS.Mips Rops.Gadgets.Double Jumps 5 | 6 | 7 | from utils import mipsrop, utils 8 | 9 | utils.allowed_processors(currentProgram, 'MIPS') 10 | 11 | mips_rop = mipsrop.MipsRop(currentProgram) 12 | doubles = mips_rop.find_doubles() 13 | 14 | doubles.pretty_print() 15 | -------------------------------------------------------------------------------- /MipsRopEpilogue.py: -------------------------------------------------------------------------------- 1 | # Find MIPS ROP gadgets for gaining control of more registers through function epilogues. 2 | #@author fuzzywalls 3 | #@category TNS 4 | #@menupath TNS.Mips Rops.Gadgets.Epilogue 5 | 6 | 7 | from utils import mipsrop, utils 8 | 9 | utils.allowed_processors(currentProgram, 'MIPS') 10 | 11 | registers = ['s0', 's1', 's2', 's3', 's4', 's5', 's6', 's7', 's8'] 12 | 13 | 14 | min_reg = askChoice('Minimum Register', 15 | 'What is the lowest register you want to control?', 16 | ['Any'] + registers, 17 | 'Any') 18 | if min_reg == 'Any': 19 | min_reg = None 20 | 21 | if min_reg: 22 | print 'Searching for function epilogues that grant control of registers up to %s...' % min_reg 23 | 24 | epilogue = mipsrop.MipsInstruction('.*lw', 'ra') 25 | 26 | mips_rop = mipsrop.MipsRop(currentProgram) 27 | function_epilogue = mips_rop.find_instructions( 28 | [epilogue], overwrite_register=registers[:registers.index(min_reg) + 1], 29 | controllable_calls=False) 30 | 31 | function_epilogue.pretty_print() 32 | -------------------------------------------------------------------------------- /MipsRopFind.py: -------------------------------------------------------------------------------- 1 | # Find MIPS ROP gadgets that contain a user specified instruction. 2 | #@author fuzzywalls 3 | #@category TNS 4 | #@menupath TNS.Mips Rops.Gadgets.Find 5 | 6 | 7 | import re 8 | from utils import mipsrop, utils 9 | 10 | utils.allowed_processors(currentProgram, 'MIPS') 11 | 12 | op1 = None 13 | op2 = None 14 | op3 = None 15 | 16 | search = askString( 17 | 'MIPS ROP Find', 'What instruction do you want to search for?') 18 | try: 19 | search = re.sub(' +', ' ', search) 20 | mnem, operands = search.split(' ', 1) 21 | operands = operands.replace(' ', '') 22 | operands = operands.split(',') 23 | op1, op2, op3 = operands + [None] * (3 - len(operands)) 24 | except ValueError: 25 | mnem = search 26 | 27 | if not mnem.startswith('.*'): 28 | mnem = '.*' + mnem 29 | 30 | search_ins = mipsrop.MipsInstruction(mnem, op1, op2, op3) 31 | 32 | mips_rop = mipsrop.MipsRop(currentProgram) 33 | results = mips_rop.find_instructions([search_ins]) 34 | 35 | results.pretty_print() 36 | -------------------------------------------------------------------------------- /MipsRopIret.py: -------------------------------------------------------------------------------- 1 | # Find MIPS ROP gadgets that perform an indirect return. (Call t9, return to ra.) 2 | #@author fuzzywalls 3 | #@category TNS 4 | #@menupath TNS.Mips Rops.Gadgets.Indirect Return 5 | 6 | 7 | from utils import mipsrop, utils 8 | 9 | utils.allowed_processors(currentProgram, 'MIPS') 10 | 11 | move_t9 = mipsrop.MipsInstruction('move', 't9', '[sav][012345678]') 12 | 13 | mips_rop = mipsrop.MipsRop(currentProgram) 14 | indirect_returns = mips_rop.find_instructions( 15 | [move_t9], controllable_calls=False, overwrite_register=['ra']) 16 | 17 | indirect_returns.pretty_print() 18 | -------------------------------------------------------------------------------- /MipsRopLia0.py: -------------------------------------------------------------------------------- 1 | # Find MIPS ROP gadgets that load a small value into a0. Useful for calling sleep. 2 | #@author fuzzywalls 3 | #@category TNS 4 | #@menupath TNS.Mips Rops.Gadgets.Li a0 5 | 6 | 7 | from utils import mipsrop, utils 8 | 9 | utils.allowed_processors(currentProgram, 'MIPS') 10 | 11 | li_a0 = mipsrop.MipsInstruction('.*li', 'a0', '0x.*') 12 | 13 | mips_rop = mipsrop.MipsRop(currentProgram) 14 | sleep_calls = mips_rop.find_instructions([li_a0]) 15 | 16 | sleep_calls.pretty_print() 17 | -------------------------------------------------------------------------------- /MipsRopPrologue.py: -------------------------------------------------------------------------------- 1 | # Find MIPS ROP gadgets near the beginning of functions that allow for stack pointer movement. 2 | #@author fuzzywalls 3 | #@category TNS 4 | #@menupath TNS.Mips Rops.Gadgets.Prologue 5 | 6 | 7 | from utils import mipsrop, utils 8 | 9 | utils.allowed_processors(currentProgram, 'MIPS') 10 | 11 | prologue = mipsrop.MipsInstruction('.*addiu', 'sp', 'sp', '-.*') 12 | 13 | mips_rop = mipsrop.MipsRop(currentProgram) 14 | function_prologue = mips_rop.find_instructions([prologue]) 15 | 16 | function_prologue.pretty_print() 17 | -------------------------------------------------------------------------------- /MipsRopShellcode.py: -------------------------------------------------------------------------------- 1 | # Build a ROP chain that can be used to call shellcode. 2 | #@author fuzzywalls 3 | #@category TNS 4 | #@menupath TNS.Mips Rops.ROP Chains.Shellcode 5 | 6 | from utils import mipsropchain, mipsrop, utils 7 | 8 | utils.allowed_processors(currentProgram, 'MIPS') 9 | 10 | 11 | def find_lia0_calls(rop_finder, vebose): 12 | """ 13 | Find calls the load a value smaller than 16 into $a0. 14 | 15 | :param rop_finder: Mips rop finder class. 16 | :type rop_finder: mipsrop.MipsRop 17 | 18 | :returns: Gadgets found. 19 | :rtype: list(mipsrop.RopGadget) 20 | """ 21 | li_a0 = mipsrop.MipsInstruction('.*li', 'a0', '0x[0-9a-f]') 22 | small_value = rop_finder.find_instructions([li_a0]) 23 | if verbose: 24 | print 'Found %d gadgets to load a small value into a0' % \ 25 | len(small_value.gadgets) 26 | return small_value.gadgets 27 | 28 | 29 | def find_stack_finders(rop_finder, verbose): 30 | """ 31 | Find gadgets that move a stack pointer to a register. 32 | 33 | :param rop_finder: Mips rop finder class. 34 | :type rop_finder: mipsrop.MipsRop 35 | 36 | :returns: Gadgets found. 37 | :rtype: list(mipsrop.RopGadget) 38 | """ 39 | sf_saved_reg = mipsrop.MipsInstruction('.*addiu', '[sva][012345678]', 'sp') 40 | stack_finder_gadgets = rop_finder.find_instructions( 41 | [sf_saved_reg], terminating_calls=False) 42 | if verbose: 43 | print 'Found %d gadgets to find shellcode on the stack.' % \ 44 | len(stack_finder_gadgets.gadgets) 45 | return stack_finder_gadgets.gadgets 46 | 47 | 48 | def find_double_jumps(rop_finder, allow_double=True, allow_iret=True, 49 | verbose=False): 50 | """ 51 | Find gadgets that call a function and maintain control to jump to the next 52 | gadget. 53 | 54 | :param rop_finder: Mips rop finder class. 55 | :type rop_finder: mipsrop.MipsRop 56 | 57 | :returns: Gadgets found. 58 | :rtype: list(mipsrop.RopGadget + mipsrop.DoubleGadget) 59 | """ 60 | gadgets = [] 61 | if allow_double: 62 | doubles = rop_finder.find_doubles() 63 | gadgets.extend(doubles.gadgets) 64 | if allow_iret: 65 | move_t9 = mipsrop.MipsInstruction('move', 't9', '[sav][012345678]') 66 | irets = rop_finder.find_instructions( 67 | [move_t9], controllable_calls=False, overwrite_register=['ra']) 68 | gadgets.extend(irets.gadgets) 69 | if verbose: 70 | print 'Found %d gadgets to call sleep and maintain control' % len(gadgets) 71 | return gadgets 72 | 73 | 74 | def custom_shellcode_find(link, controlled_registers, curr_chain): 75 | """ 76 | Custom find to search for gadgets that call a register based on where 77 | the previous gadget stored it. 78 | """ 79 | shell_code_location = curr_chain[-1].get_action_destination()[0] 80 | for jump in link.jump_register: 81 | if jump != shell_code_location: 82 | return False 83 | return True 84 | 85 | 86 | def find_shellcode_jump(rop_finder, verbose): 87 | """ 88 | Find gadgets that call a register. 89 | 90 | :param rop_finder: Mips rop finder class. 91 | :type rop_finder: mipsrop.MipsRop 92 | 93 | :returns: Gadgets found. 94 | :rtype: list(mipsrop.RopGadget) 95 | """ 96 | move_t9 = mipsrop.MipsInstruction('mov', 't9') 97 | call_register = rop_finder.find_instructions( 98 | [move_t9]) 99 | if verbose: 100 | print 'Found %d gadgets to call shellcode.' % len(call_register.gadgets) 101 | return call_register.gadgets 102 | 103 | 104 | def find_epilogue(rop_finder, controlled_registers): 105 | """ 106 | Find epilogues that grant control of each register. Will only return 107 | epilogues that grant control over more registers than originally used. 108 | 109 | :param rop_finder: Mips rop finder class. 110 | :type rop_finder: mipsrop.MipsRop 111 | 112 | :param controlled_registers: Registers controlled. 113 | :type controlled_registers: list(str) 114 | 115 | :returns: Gadgets found. 116 | :rtype: list(mipsrop.RopGadgets) 117 | """ 118 | epilogue = mipsrop.MipsInstruction('.*lw', 'ra') 119 | function_epilogue = [] 120 | 121 | for i in range(0, len(mipsropchain.REGISTERS)): 122 | control_registers = mipsropchain.REGISTERS[:i + 1] 123 | if all(reg in controlled_registers for reg in control_registers): 124 | continue 125 | epilogue_gadget = rop_finder.find_instructions( 126 | [epilogue], controllable_calls=False, 127 | overwrite_register=control_registers, 128 | preserve_register=mipsropchain.REGISTERS[i + 1:]) 129 | if epilogue_gadget.gadgets: 130 | function_epilogue.append(epilogue_gadget.gadgets[0]) 131 | return function_epilogue 132 | 133 | 134 | mips_rop = mipsrop.MipsRop(currentProgram) 135 | 136 | # User request for currently controlled registers. 137 | registers_controlled = askChoices( 138 | 'Registers Controlled', 'Which registers do you control, excluding ra?', 139 | ['s0', 's1', 's2', 's3', 's4', 's5', 's6', 's7', 's8']) 140 | 141 | # User request for how many chains they want returned. 142 | chain_count = askInt('Chains', 'How many chains to you want to find?') 143 | 144 | # User request for special options. 145 | special_options = askChoices( 146 | 'Options', 'Any special requests?', 147 | ['iret', 'double', 'control', 'reuse', 'verbose'], 148 | ['Avoid indirect returns', 'Avoid double jumps', 149 | 'Avoid gadgets that require a control jump.', 'Do not reuse gadgets.', 150 | 'Verbose output.']) 151 | allow_control = 'control' not in special_options 152 | allow_reuse = 'reuse' not in special_options 153 | allow_double = 'double' not in special_options 154 | allow_iret = 'iret' not in special_options 155 | verbose = 'verbose' in special_options 156 | 157 | if verbose: 158 | print 'You control registers: %s' % ', '.join(registers_controlled) 159 | print 'Searching for required gadgets...' 160 | 161 | # Find all required gadgets. 162 | lia0 = find_lia0_calls(mips_rop, verbose) 163 | stack_finders = find_stack_finders(mips_rop, verbose) 164 | doubles = find_double_jumps(mips_rop, allow_double, allow_iret, verbose) 165 | shellcode = find_shellcode_jump(mips_rop, verbose) 166 | 167 | # Set up the chain build with the order the gadgets should be called. 168 | chain_builder = mipsropchain.ChainBuilder(mips_rop, registers_controlled, 169 | chain_count, allow_reuse, verbose) 170 | chain_builder.add_gadgets('Load Immediate to a0', lia0, allow_control) 171 | chain_builder.add_gadgets('Call sleep and maintain control', doubles, 172 | allow_control) 173 | chain_builder.add_gadgets('Shellcode finder', stack_finders, allow_control) 174 | chain_builder.add_gadgets('Call shellcode', shellcode, 175 | False, find_fn=custom_shellcode_find) 176 | chain_builder.generate_chain() 177 | 178 | # If no chains were found or not enough add epilogues and keep searching. 179 | if not chain_builder.chains or len(chain_builder.chains) < chain_count: 180 | if verbose: 181 | print 'Adding epilogues to control more registers.' 182 | epilogues = find_epilogue(mips_rop, registers_controlled) 183 | chain_builder.add_gadgets('Control More Registers', epilogues, 184 | check_control=False, index=0) 185 | chain_builder.generate_chain() 186 | 187 | print 'Found %d chains' % len(chain_builder.chains) 188 | 189 | chain_builder.display_chains(verbose) 190 | -------------------------------------------------------------------------------- /MipsRopStackFinder.py: -------------------------------------------------------------------------------- 1 | # Find MIPS ROP gadgets that put a stack address in a register. 2 | #@author fuzzywalls 3 | #@category TNS 4 | #@menupath TNS.Mips Rops.Gadgets.Stack Finder 5 | 6 | 7 | from utils import mipsrop, utils 8 | 9 | utils.allowed_processors(currentProgram, 'MIPS') 10 | 11 | sf_saved_reg = mipsrop.MipsInstruction('.*addiu', '[sva][012345678]', 'sp') 12 | 13 | mips_rop = mipsrop.MipsRop(currentProgram) 14 | stack_finders = mips_rop.find_instructions([sf_saved_reg]) 15 | 16 | stack_finders.pretty_print() 17 | -------------------------------------------------------------------------------- /MipsRopSummary.py: -------------------------------------------------------------------------------- 1 | # Print a summary of ROP gadgets that are bookmarked with ropX. 2 | #@author fuzzywalls 3 | #@category TNS 4 | #@menupath TNS.Mips Rops.Summary 5 | 6 | 7 | from utils import mipsrop, utils 8 | 9 | utils.allowed_processors(currentProgram, 'MIPS') 10 | 11 | mips_rop = mipsrop.MipsRop(currentProgram) 12 | mips_rop.summary() 13 | -------------------------------------------------------------------------------- /MipsRopSystem.py: -------------------------------------------------------------------------------- 1 | # Find MIPS ROP gadgets for calling system with a user controlled argument. 2 | #@author fuzzywalls 3 | #@category TNS 4 | #@menupath TNS.Mips Rops.Gadgets.System Calls 5 | 6 | 7 | from utils import mipsrop, utils 8 | 9 | utils.allowed_processors(currentProgram, 'MIPS') 10 | 11 | set_a0 = mipsrop.MipsInstruction('.*addiu', 'a0', 'sp') 12 | 13 | mips_rop = mipsrop.MipsRop(currentProgram) 14 | system_rops = mips_rop.find_instructions([set_a0], 'a0', 15 | terminating_calls=False) 16 | 17 | system_rops.pretty_print() 18 | -------------------------------------------------------------------------------- /MipsRopSystemChain.py: -------------------------------------------------------------------------------- 1 | # Build a ROP chain that can be used to call system with a controllable command. 2 | #@author fuzzywalls 3 | #@category TNS 4 | #@menupath TNS.Mips Rops.ROP Chains.System 5 | 6 | from utils import mipsropchain, mipsrop, utils 7 | 8 | utils.allowed_processors(currentProgram, 'MIPS') 9 | 10 | 11 | def find_system_calls(rop_finder, terminating, controllable, verbose): 12 | """ 13 | Find single gadget chains to call system with a controllable string in 14 | a0. 15 | 16 | :param rop_finder: MIPS rop finder class. 17 | :type rop_finder: mipsrop.MipsRop 18 | 19 | :param terminating: Return tail gadgets. 20 | :type terminating: bool 21 | 22 | :param controllable: Return controllable calls. 23 | :type controllable: bool 24 | 25 | :param verbose: Enable verbose output. 26 | :type verbose: bool 27 | 28 | :returns: Discovered gadgets 29 | :rtype: list(mipsrop.RopGadgets) 30 | """ 31 | system_call = mipsrop.MipsInstruction('.*addiu', 'a0', 'sp') 32 | stack_finders = rop_finder.find_instructions( 33 | [system_call], terminating_calls=terminating, 34 | controllable_calls=controllable) 35 | if verbose: 36 | print 'Found %d gadgets to call system.' % \ 37 | len(stack_finders.gadgets) 38 | return stack_finders.gadgets 39 | 40 | 41 | def find_stack_finders(rop_finder, terminating, controllable, verbose): 42 | """ 43 | Find gadgets that move a stack pointer to a register. Movement to a0 is 44 | specifically ignored because the system gadget finder does that. 45 | 46 | :param rop_finder: Mips rop finder class. 47 | :type rop_finder: mipsrop.MipsRop 48 | 49 | :param terminating: Return tail gadgets. 50 | :type terminating: bool 51 | 52 | :param controllable: Return controllable calls. 53 | :type controllable: bool 54 | 55 | :param verbose: Enable verbose output. 56 | :type verbose: bool 57 | 58 | :returns: Gadgets found. 59 | :rtype: list(mipsrop.RopGadget) 60 | """ 61 | sf_saved_reg = mipsrop.MipsInstruction( 62 | '.*addiu', '[sva][012345678]', 'sp') 63 | stack_finders = rop_finder.find_instructions( 64 | [sf_saved_reg], terminating_calls=terminating, 65 | controllable_calls=controllable) 66 | if verbose: 67 | print 'Found %d gadgets to find shellcode on the stack.' % \ 68 | len(stack_finders.gadgets) 69 | return stack_finders.gadgets 70 | 71 | 72 | def find_move_a0(rop_finder, verbose): 73 | """ 74 | Find gadget that moves a register to a0. 75 | 76 | :param rop_finder: MIPS rop finder class. 77 | :type rop_finder: mipsrop.MipsRop 78 | 79 | :param verbose: Enable verbose output. 80 | :type verbose: bool 81 | 82 | :returns: Discovered gadgets 83 | :rtype: list(mipsrop.RopGadgets) 84 | """ 85 | move_a0_ins = mipsrop.MipsInstruction('.*move', 'a0', '[sva][012345678]') 86 | # System cannot be called from an epilogue. $gp is calculated based on the 87 | # call occurring from $t9. 88 | move_a0 = rop_finder.find_instructions([move_a0_ins], 89 | terminating_calls=False) 90 | if verbose: 91 | print 'Found %d gadgets to move a register to $a0.' % \ 92 | len(move_a0.gadgets) 93 | return move_a0.gadgets 94 | 95 | 96 | def find_epilogue(rop_finder, controlled_registers): 97 | """ 98 | Find epilogues that grant control of each register. Ideal will return nine 99 | gadgets one that gives control of s0, one that gives control of s0 and s1, 100 | one that gives control of s0/s1/s2, etc. 101 | 102 | :param rop_finder: Mips rop finder class. 103 | :type rop_finder: mipsrop.MipsRop 104 | 105 | :returns: Gadgets found. 106 | :rtype: list(mipsrop.RopGadgets) 107 | """ 108 | epilogue = mipsrop.MipsInstruction('.*lw', 'ra') 109 | function_epilogue = [] 110 | 111 | for i in range(0, len(mipsropchain.REGISTERS)): 112 | control_registers = mipsropchain.REGISTERS[:i + 1] 113 | if all(reg in controlled_registers for reg in control_registers): 114 | continue 115 | epilogue_gadget = rop_finder.find_instructions( 116 | [epilogue], controllable_calls=False, 117 | overwrite_register=control_registers, 118 | preserve_register=mipsropchain.REGISTERS[i + 1:]) 119 | if epilogue_gadget.gadgets: 120 | function_epilogue.append(epilogue_gadget.gadgets[0]) 121 | return function_epilogue 122 | 123 | 124 | def system_single_simple_jump(chain_builder, mips_rop): 125 | """ 126 | Find traditional system gadgets that move a stack string to a0 then call 127 | a controllable register. Applied gadgets will be cleared from the chain 128 | builder when the function is complete. 129 | 130 | :param chain_builder: Initialized chain builder class. 131 | :type chain_builder: mipsropchain.ChainBuilder 132 | 133 | :param mips_rop: Mips rop finder class. 134 | :type mips_rop: mipsrop.MipsRop 135 | """ 136 | system_calls = find_system_calls(mips_rop, False, True, verbose) 137 | chain_builder.add_gadgets('Call system.', system_calls) 138 | 139 | chain_builder.generate_chain() 140 | chain_builder.gadgets = [] 141 | 142 | 143 | def single_system_custom_find(link, controlled_registers, current_chain): 144 | """ 145 | Custom find command that searches for a single gadget that moves a stack 146 | pointer to a register and moves that resister to a0. 147 | 148 | :param link: Current link to process. 149 | :type link: mipsropchain.ChainLink 150 | """ 151 | if not mipsropchain.default_gadget_search( 152 | link, controlled_registers, current_chain): 153 | return False 154 | 155 | if 'a0' in link.overwritten: 156 | a0_ins = getInstructionAt(link.overwritten['a0'][0]) 157 | a0_src = str(a0_ins.getOpObjects(1)[0]) 158 | action_dest = link.get_action_destination()[0] 159 | if a0_src == action_dest and \ 160 | link.overwritten['a0'] > link.overwritten[action_dest]: 161 | return True 162 | return False 163 | 164 | 165 | def system_single_extended_jump(chain_builder, mips_rop): 166 | """ 167 | Find extended single gadgets that move a stack string to a registers and 168 | then move that register to a0. A custom find function is used to support 169 | this search. 170 | 171 | :param chain_builder: Initialized chain builder class. 172 | :type chain_builder: mipsropchain.ChainBuilder 173 | 174 | :param mips_rop: Mips rop finder class. 175 | :type mips_rop: mipsrop.MipsRop 176 | """ 177 | stack_finders = find_stack_finders(mips_rop, False, True, verbose) 178 | chain_builder.add_gadgets('Find command on the stack', stack_finders, 179 | find_fn=single_system_custom_find) 180 | chain_builder.generate_chain() 181 | chain_builder.gadgets = [] 182 | 183 | 184 | def system_tail_two_jump(chain_builder, mips_rop): 185 | """ 186 | Search for chain that moves a stack string to a0 in a tail call. 187 | 188 | :param chain_builder: Initialized chain builder class. 189 | :type chain_builder: mipsropchain.ChainBuilder 190 | 191 | :param mips_rop: Mips rop finder class. 192 | :type mips_rop: mipsrop.MipsRop 193 | """ 194 | stack_finders = find_system_calls(mips_rop, True, False, verbose) 195 | chain_builder.add_gadgets( 196 | 'Find command on the stack from tail call', stack_finders) 197 | 198 | move_t9 = mipsrop.MipsInstruction('mov', 't9') 199 | call_register = mips_rop.find_instructions( 200 | [move_t9], preserve_register='a0', terminating_calls=False) 201 | chain_builder.add_gadgets('Call system.', call_register.gadgets) 202 | chain_builder.generate_chain() 203 | chain_builder.gadgets = [] 204 | 205 | 206 | def system_two_jump_custom_find(link, controlled_registers, current_chain): 207 | """ 208 | Custom find to search for gadget that moves a register from a previous 209 | jump to a0. 210 | """ 211 | if not mipsropchain.default_gadget_search( 212 | link, controlled_registers, current_chain): 213 | return False 214 | 215 | actions = link.get_action_source() 216 | last_link_actions = current_chain[-1].get_action_destination() 217 | for action in last_link_actions: 218 | if action not in actions: 219 | return False 220 | return True 221 | 222 | 223 | def system_two_jump(chain_builder, mips_rop): 224 | """ 225 | Find chains that move a stack string to a register and move that register 226 | to a0 in the next gadget. 227 | 228 | :param chain_builder: Initialized chain builder class. 229 | :type chain_builder: mipsropchain.ChainBuilder 230 | 231 | :param mips_rop: Mips rop finder class. 232 | :type mips_rop: mipsrop.MipsRop 233 | """ 234 | stack_finders = find_stack_finders(mips_rop, False, True, verbose) 235 | chain_builder.add_gadgets('Find command on the stack', stack_finders) 236 | 237 | move_a0 = find_move_a0(mips_rop, verbose) 238 | chain_builder.add_gadgets( 239 | 'Move command to $a0', move_a0, find_fn=system_two_jump_custom_find) 240 | chain_builder.generate_chain() 241 | chain_builder.gadgets = [] 242 | 243 | 244 | mips_rop = mipsrop.MipsRop(currentProgram) 245 | 246 | # User request for currently controlled registers. 247 | registers_controlled = askChoices( 248 | 'Registers Controlled', 'Which registers do you control, excluding ra?', 249 | ['s0', 's1', 's2', 's3', 's4', 's5', 's6', 's7', 's8']) 250 | 251 | # User request for how many chains they want returned. 252 | chain_count = askInt('Chains', 'How many chains to you want to find?') 253 | 254 | # User request for special options. 255 | special_options = askChoices( 256 | 'Options', 'Any special requests?', 257 | ['control', 'reuse', 'verbose'], 258 | ['Avoid gadgets that require a control jump.', 'Do not reuse gadgets.', 259 | 'Verbose output.']) 260 | allow_control = 'control' not in special_options 261 | allow_reuse = 'reuse' not in special_options 262 | verbose = 'verbose' in special_options 263 | 264 | if verbose: 265 | print 'You control registers: %s' % ', '.join(registers_controlled) 266 | print 'Searching for required gadgets...' 267 | 268 | chain_builder = mipsropchain.ChainBuilder(mips_rop, registers_controlled, 269 | chain_count, allow_reuse, verbose) 270 | 271 | system_single_simple_jump(chain_builder, mips_rop) 272 | system_single_extended_jump(chain_builder, mips_rop) 273 | system_two_jump(chain_builder, mips_rop) 274 | system_tail_two_jump(chain_builder, mips_rop) 275 | 276 | # If no chains were found or not enough add epilogues and keep searching. 277 | if not chain_builder.chains or len(chain_builder.chains) < chain_count: 278 | if verbose: 279 | print 'Adding epilogues to control more registers.' 280 | # 281 | epilogues = find_epilogue(mips_rop, registers_controlled) 282 | for system_find in [system_single_simple_jump, system_single_extended_jump, 283 | system_two_jump, system_tail_two_jump]: 284 | chain_builder.add_gadgets('Control More Registers', epilogues, 285 | check_control=False) 286 | 287 | system_find(chain_builder, mips_rop) 288 | 289 | print 'Found %d chains' % len(chain_builder.chains) 290 | 291 | chain_builder.display_chains(verbose) 292 | -------------------------------------------------------------------------------- /Operator.py: -------------------------------------------------------------------------------- 1 | # Find calls to a function and display source of parameters. 2 | #@author fuzzywalls 3 | #@category TNS 4 | #@menupath TNS.Operator 5 | 6 | from utils import utils 7 | 8 | from ghidra.program.model.symbol import RefType 9 | from ghidra.program.flatapi import FlatProgramAPI 10 | from ghidra.program.model.block import BasicBlockModel 11 | from ghidra.app.decompiler.flatapi import FlatDecompilerAPI 12 | 13 | 14 | def get_argument_registers(): 15 | """ 16 | Get argument registers based on programs processor. 17 | """ 18 | arch = utils.get_processor(currentProgram) 19 | 20 | if arch == 'MIPS': 21 | return ['a0', 'a1', 'a2', 'a3'] 22 | elif arch == 'ARM': 23 | return ['r0', 'r1', 'r2', 'r3'] 24 | return [] 25 | 26 | 27 | def get_return_registers(): 28 | """ 29 | Get return registers for the current processor. 30 | 31 | :returns: List of return registers. 32 | :rtype: list(str) 33 | """ 34 | arch = utils.get_processor(currentProgram) 35 | 36 | if arch == 'MIPS': 37 | return ['v0', 'v1'] 38 | elif arch == 'ARM': 39 | return ['r0'] 40 | return [] 41 | 42 | 43 | def get_destination(instruction): 44 | """ 45 | Find destination register for the current instruction. 46 | 47 | :param instruction: Instruction to find destination register for. 48 | :type instruction: ghidra.program.model.listing.Instruction 49 | 50 | :returns: List of destination registers if found, empty list if not found. 51 | :rtype: list(str) 52 | """ 53 | if not instruction: 54 | return None 55 | 56 | result = instruction.getResultObjects() 57 | if not result: 58 | return [] 59 | return [res.toString() for res in result] 60 | 61 | 62 | def find_call(address): 63 | """ 64 | Find the first call above the address provided taking into account if 65 | the address is a delay slot operation. 66 | 67 | :param address: Address to look above. 68 | :type address: ghidra.program.model.listing.Address 69 | 70 | :returns: Function name with () after it to resemble a function call or None 71 | if not found. 72 | :rtype: str 73 | """ 74 | containing_function = getFunctionContaining(address) 75 | if not containing_function: 76 | return argument 77 | 78 | entry_point = containing_function.getEntryPoint() 79 | 80 | curr_addr = address 81 | curr_ins = getInstructionAt(curr_addr) 82 | 83 | # If the instruction is in the delay slot of a call skip above it. 84 | # This is not the call you are looking for. 85 | if curr_ins.isInDelaySlot() and utils.is_call_instruction(curr_ins.previous): 86 | curr_ins = curr_ins.previous.previous 87 | curr_addr = curr_ins.getAddress() 88 | 89 | while curr_addr and curr_addr > entry_point: 90 | if utils.is_call_instruction(curr_ins): 91 | ref = curr_ins.getReferencesFrom() 92 | if len(ref): 93 | function = getFunctionAt(ref[0].toAddress) 94 | if function: 95 | return '%s()' % function.name 96 | 97 | curr_ins = curr_ins.previous 98 | 99 | if curr_ins: 100 | curr_addr = curr_ins.getAddress() 101 | else: 102 | break 103 | return None 104 | 105 | 106 | def get_source(instruction): 107 | """ 108 | Find source for the current instruction. Limited to strings and single 109 | registers currently. 110 | 111 | :param instruction: Instruction to find source data for. 112 | :type instruction: ghidra.program.model.listing.Instruction 113 | 114 | :returns: String representing the source entry or None 115 | :rtype: str 116 | """ 117 | if not instruction: 118 | return None 119 | 120 | references = getReferencesFrom(instruction.getAddress()) 121 | if references: 122 | for reference in references: 123 | data = getDataAt(reference.toAddress) 124 | if data: 125 | return '"%s"' % str(data.getValue()).encode('string_escape') 126 | 127 | input_objs = instruction.getInputObjects() 128 | if len(input_objs) == 1: 129 | input = input_objs[0].toString() 130 | if input in get_return_registers(): 131 | return find_call(instruction.getAddress()) 132 | return get_argument_source(instruction.getAddress(), input) 133 | else: 134 | return instruction.toString() 135 | 136 | return None 137 | 138 | 139 | def get_argument_source(address, argument): 140 | """ 141 | Find source of argument register. 142 | 143 | :param address: Address to start search. 144 | :type address: ghidra.program.model.listing.Address 145 | 146 | :param argument: Argument to search for. 147 | :type argument: str 148 | 149 | :returns: Source for argument either as register string or full operation 150 | string. 151 | :rtype: str 152 | """ 153 | src = None 154 | containing_function = getFunctionContaining(address) 155 | if not containing_function: 156 | return argument 157 | 158 | entry_point = containing_function.getEntryPoint() 159 | 160 | curr_addr = address 161 | curr_ins = getInstructionAt(curr_addr) 162 | while curr_addr and curr_addr > entry_point: 163 | if curr_ins.getDelaySlotDepth() > 0: 164 | delay_slot = curr_ins.next 165 | destinations = get_destination(delay_slot) 166 | if destinations and argument in destinations: 167 | src = get_source(delay_slot) 168 | if src is None: 169 | src = argument 170 | return src 171 | 172 | destinations = get_destination(curr_ins) 173 | if destinations and argument in destinations: 174 | src = get_source(curr_ins) 175 | break 176 | 177 | # This can cause false positives because of jumps and branches. 178 | # Should eventually convert this to use code blocks. 179 | curr_ins = curr_ins.previous 180 | 181 | if curr_ins: 182 | curr_addr = curr_ins.getAddress() 183 | else: 184 | break 185 | 186 | if src is None: 187 | src = argument 188 | 189 | return src 190 | 191 | 192 | class FunctionPrototype(object): 193 | def __init__(self, function): 194 | self.function = function 195 | self.name = function.name 196 | self.entry_point = function.getEntryPoint() 197 | self.arg_count = 1 198 | self.has_var_args = function.hasVarArgs() 199 | 200 | if function.isExternal() or function.isThunk(): 201 | self.arg_count = function.getAutoParameterCount() 202 | else: 203 | self.arg_count = self._get_argument_count() 204 | 205 | def _get_argument_count_manual(self): 206 | """ 207 | Manual argument identification for function based on used argument 208 | registers in the function prior to setting them. 209 | 210 | :returns: Number of arguments used. 211 | :rtype: int 212 | """ 213 | used_args = [] 214 | arch_args = get_argument_registers() 215 | 216 | min_addr = self.function.body.minAddress 217 | max_addr = self.function.body.maxAddress 218 | 219 | curr_ins = getInstructionAt(min_addr) 220 | 221 | while curr_ins and curr_ins.getAddress() < max_addr: 222 | for op_index in range(0, curr_ins.getNumOperands()): 223 | ref_type = curr_ins.getOperandRefType(op_index) 224 | # We only care about looking at reads and writes. Reads that 225 | # include and index into a register show as 'data' so look 226 | # for those as well. 227 | if ref_type not in [RefType.WRITE, RefType.READ, RefType.DATA]: 228 | continue 229 | 230 | # Check to see if the argument is an argument register. Remove 231 | # that register from the arch_args list so it can be ignored 232 | # from now on. If reading from the register add it to the 233 | # used_args list so we know its a used parameter. 234 | operands = curr_ins.getOpObjects(op_index) 235 | for operand in operands: 236 | op_string = operand.toString() 237 | if op_string in arch_args: 238 | arch_args.remove(op_string) 239 | if ref_type in [RefType.READ, RefType.DATA]: 240 | used_args.append(op_string) 241 | curr_ins = curr_ins.next 242 | 243 | return len(used_args) 244 | 245 | def _get_argument_count(self): 246 | """ 247 | Get argument count through decompiler if possible otherwise try to 248 | determine the argument count manually. Manual approach can miss 249 | arguments if they are used in the first function call of the function. 250 | """ 251 | flat_api = FlatProgramAPI(currentProgram) 252 | decompiler_api = FlatDecompilerAPI(flat_api) 253 | 254 | # Must call decompile first or the decompiler will not be initialized. 255 | decompiler_api.decompile(self.function) 256 | decompiler = decompiler_api.getDecompiler() 257 | 258 | if decompiler: 259 | decompiled_fn = decompiler.decompileFunction(self.function, 260 | 10, 261 | getMonitor()) 262 | if decompiled_fn: 263 | high_level_fn = decompiled_fn.getHighFunction() 264 | if high_level_fn: 265 | prototype = high_level_fn.getFunctionPrototype() 266 | if prototype: 267 | return prototype.getNumParams() 268 | 269 | return self._get_argument_count_manual() 270 | 271 | 272 | class Call(object): 273 | def __init__(self, addr, containing_fn, function): 274 | self.address = addr 275 | self.containing_function = containing_fn 276 | self.function_call = function 277 | self.arguments = [] 278 | 279 | def add_argument(self, argument): 280 | self.arguments.append(argument) 281 | 282 | def to_list(self): 283 | return [self.address.toString(), self.containing_function.name, 284 | self.function_call.name] + self.arguments 285 | 286 | 287 | class Operator(object): 288 | def __init__(self): 289 | func_man = currentProgram.getFunctionManager() 290 | self._ref_man = currentProgram.getReferenceManager() 291 | 292 | self.function = None 293 | self.function_calls = [] 294 | self._function_list = [func for func in func_man.getFunctions(True)] 295 | 296 | def get_callee(self): 297 | """ 298 | Request user defined functions and identify when that function is 299 | called. 300 | """ 301 | self._function_list.sort(key=lambda func: func.name) 302 | 303 | function = askChoice('Select function', 304 | 'Select the starting function', 305 | self._function_list, 306 | self._function_list[0]) 307 | 308 | self.function = FunctionPrototype(function) 309 | self._get_function_calls() 310 | 311 | def list_calls(self): 312 | """ 313 | Display all times the identified function is called and the approximate 314 | registers / strings passed to the function. 315 | """ 316 | if not len(self.function_calls): 317 | print 'No function calls to %s found.' % self.function.name 318 | return 319 | 320 | arg_registers = get_argument_registers() 321 | 322 | title = ['Address', 'Containing Function', 'Function'] 323 | title.extend(arg_registers) 324 | 325 | calls = [] 326 | for call in self.function_calls: 327 | containing_fn = getFunctionContaining(call) 328 | if not containing_fn: 329 | continue 330 | curr_call = Call(call, containing_fn, operator.function) 331 | arguments = arg_registers[:operator.function.arg_count] 332 | sources = [] 333 | for arg in arguments: 334 | source = get_argument_source(call, arg) 335 | curr_call.add_argument(source) 336 | 337 | # Check for variable length arguments and format strings. Add 338 | # arguments if they are found. 339 | if operator.function.has_var_args: 340 | format_string_count = source.count('%') - source.count('\%') 341 | arg_count = operator.function.arg_count 342 | 343 | # Find additional arguments taking into account the number 344 | # of available argument registers. 345 | additional_arguments = arg_registers[ 346 | arg_count: 347 | min(len(arg_registers), 348 | format_string_count + operator.function.arg_count)] 349 | 350 | for arg in additional_arguments: 351 | source = get_argument_source(call, arg) 352 | curr_call.add_argument(source) 353 | 354 | curr_call_list = curr_call.to_list() 355 | calls.append(curr_call_list) 356 | 357 | calls.sort(key=lambda call: call[0]) 358 | utils.table_pretty_print(title, calls) 359 | 360 | def _get_function_calls(self): 361 | """ 362 | Find all calls to function specified by the user. 363 | """ 364 | for ref in self._ref_man.getReferencesTo(self.function.entry_point): 365 | ref_type = ref.getReferenceType() 366 | if ref_type.isCall() or ref_type.isConditional(): 367 | self.function_calls.append(ref.fromAddress) 368 | 369 | 370 | utils.allowed_processors(currentProgram, ['MIPS', 'ARM']) 371 | 372 | operator = Operator() 373 | operator.get_callee() 374 | 375 | print 'Identifying calls to %s...' % operator.function.name 376 | 377 | operator.list_calls() 378 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Ghidra scripts to support IOT exploitation. Some of the scripts are a port 2 | of [devttyS0](https://github.com/devttys0/ida) IDA plugins and others are 3 | new scripts that I found a need for. To install, clone and add the script 4 | directory via Ghidra's Script Manager. If you check the 'In Tool' checkbox they 5 | will appear under a 'TNS' tag. 6 | 7 | ## Scripts 8 | Below is a simple overview of the available scripts. If the scripts are broken up into multiple parts then bullets are given with high level overviews. Click on the link for each to see a more in-depth explanation with screenshots. 9 | 10 | # [ARM ROP Finder](readmes/armrop.md) 11 | Script to find and support finding ARM ROP gadgets. 12 | 13 | - Gadgets 14 | - Find double jumps. 15 | - Move small value to r0. 16 | - Get control of more or different registers. 17 | - Move values between registers. 18 | - Find strings or shellcode on the stack. 19 | - Find custom gadgets based on regular expressions. 20 | - Gadgets to call system with a string argument in r0. 21 | 22 | - Support 23 | - Convert entire program to Thumb instructions. 24 | - List summary of saved gadgets. 25 | 26 | # [Call Chain](readmes/callchain.md) 27 | Find call chains between two user specified functions. Results are displayed in a png. 28 | 29 | # [Codatify](readmes/codatify.md) 30 | - Fixup code - defines all undefined data in the .text section as code and creates a function if it can. 31 | - Fixup data - define uninitialized strings and pointers. Searches for function tables and renames functions based on their discovery. 32 | 33 | # [Fluorescence](readmes/fluorescence.md) 34 | Highlight function calls. 35 | 36 | # [Function Profiler](readmes/func_profiler.md) 37 | Display cross refs from the current function. 38 | 39 | # [Leaf Blower](readmes/leafblower.md) 40 | - Format Strings - Find functions that accept format strings as parameters. 41 | - Leaf Functions - Identify potential leaf functions such as strcpy, strlen, etc. 42 | 43 | # [Local Cross References](readmes/local_cross_ref.md) 44 | Find references to items in the current function. 45 | 46 | # [MIPS ROP Finder](readmes/mips_rop.md) 47 | Scripts to find and support finding MIPS ROP gadgets. 48 | 49 | - Gadgets 50 | - Double Jumps 51 | - Epilogue 52 | - Find custom gadgets 53 | - Indirect Return 54 | - li a0 55 | - Prologue 56 | - System Gadgets 57 | 58 | - Chain Builder 59 | - Build ROP chain to call shellcode 60 | - Build ROP chain to call system with controllable string. 61 | 62 | - Support 63 | - Summary 64 | 65 | # [Operator](readmes/operator.md) 66 | Display all calls to a function and identify the source of the parameters it is called with taking variadic arguments into account if they are present. 67 | 68 | # [Rename Variables](readmes/rename_variables.md) 69 | Rename saved stack variables. (MIPS only) 70 | 71 | # [Rizzo](readmes/rizzo.md) 72 | Create fuzzy function signatures that can be applied to other projects. 73 | 74 | -------------------------------------------------------------------------------- /RenameVariables.py: -------------------------------------------------------------------------------- 1 | # Rename saved stack variables in MIPS programs. 2 | #@author fuzzywalls 3 | #@category TNS 4 | #@keybinding 5 | #@menupath TNS.Rename Variables 6 | 7 | from utils import utils 8 | from ghidra.program.model.symbol import SourceType 9 | 10 | 11 | utils.allowed_processors(currentProgram, 'MIPS') 12 | 13 | func_man = currentProgram.getFunctionManager() 14 | code_man = currentProgram.getCodeManager() 15 | 16 | func_list = func_man.getFunctions(True) 17 | 18 | 19 | for func in func_list: 20 | # Order of this list is important. 21 | savable_registers = ['gp', 's0', 's1', 's2', 22 | 's3', 's4', 's5', 's6', 's7', 's8', 'ra'] 23 | 24 | variables = func.getAllVariables() 25 | for var in variables: 26 | if len(savable_registers) == 0: 27 | break 28 | 29 | symbol = var.getSymbol() 30 | if not symbol: 31 | continue 32 | 33 | references = symbol.getReferences() 34 | 35 | for ref in references: 36 | ins = code_man.getInstructionAt(ref.getFromAddress()) 37 | 38 | if 'sw ' in str(ins) or 'sd ' in str(ins): 39 | saved_register = ins.getRegister(0) 40 | if saved_register: 41 | saved_register = saved_register.toString() 42 | if saved_register in savable_registers: 43 | var.setName('saved_%s' % saved_register, 44 | SourceType.USER_DEFINED) 45 | 46 | # Remove the saved register to avoid renaming registers 47 | # saved later. 48 | savable_registers.remove(saved_register) 49 | -------------------------------------------------------------------------------- /RizzoApply.py: -------------------------------------------------------------------------------- 1 | # Apply "fuzzy" function signatures from a different Ghidra project. 2 | #@author fuzzywalls 3 | #@category TNS 4 | #@menupath TNS.Rizzo.Apply Signatures 5 | 6 | 7 | from utils import rizzo 8 | 9 | file_path = askFile('Load signature file', 'OK').path 10 | 11 | print 'Applying Rizzo signatures, this may take a few minutes...' 12 | 13 | rizz = rizzo.Rizzo(currentProgram) 14 | signatures = rizz.load(file_path) 15 | rizz.apply(signatures) 16 | -------------------------------------------------------------------------------- /RizzoSave.py: -------------------------------------------------------------------------------- 1 | # Create "fuzzy" function signatures that can be shared an applied amongst different Ghidra projects. 2 | #@author fuzzywalls 3 | #@category TNS 4 | #@menupath TNS.Rizzo.Save Signatures 5 | 6 | 7 | from utils import rizzo 8 | 9 | file_path = askFile('Save signature file as', 'OK').path 10 | if not file_path.endswith('.riz'): 11 | file_path += '.riz' 12 | 13 | print 'Building Rizzo signatures, this may take a few minutes...' 14 | 15 | rizz = rizzo.Rizzo(currentProgram) 16 | rizz.save(file_path) 17 | -------------------------------------------------------------------------------- /readmes/armrop.md: -------------------------------------------------------------------------------- 1 | # ARM Rop Finder 2 | Find ROP gadgets in ARM disassembly. 3 | 4 | ## Double Jump 5 | Find back to back controllable gadgets in the form of a register jump near 6 | an epiloge. 7 | 8 | ![ARM Rop Double Jumps](./img/arm_rop_double.png) 9 | 10 | ## ArmToThumb 11 | Convert all executable disassembly to Thumb instructions to search for ROP gadgets. 12 | The output of ROP gadets will account for Thumb instructions and display the jump 13 | address as `ADDRESS + 1 = WHERE_YOU_SHOULD_JUMP`. The operation can be undone 14 | when finished looking for gadgets. 15 | 16 | ### Before 17 | ARM disassembly before running the Arm to Thumb plugin. 18 | 19 | ![Disassembly Before](./img/arm_dis.png) 20 | 21 | ### After 22 | Disassembly after the conversion. 23 | 24 | ![Disassembly After](./img/thumb_dis.png) 25 | 26 | ### Thumb Gadget 27 | Thumb gadgets are shown with their actual address, but when jumping to it from 28 | a ROP gadget you must jump to the address + 1 to switch to Thumb mode. 29 | 30 | ![Disassembly After](./img/thumb_gadget.png) 31 | 32 | ## Find 33 | Find controllable gadgets that contain custom ARM instructions. Regular 34 | expressions are supported. To search for a move to r0 from anything, simply 35 | search for 36 | "`mov r0,.*`". 37 | 38 | ![ARM ROP Find](./img/armrop_find.png) 39 | 40 | ## Move r0 41 | Find ARM ROP gadgets that move a small value into r0. Useful for calling sleep 42 | prior to executing shellcode to flush the buffer to main memory. 43 | 44 | ![ARM ROP Move r0](./img/arm_rop_mov_r0.png) 45 | 46 | ## Register Control 47 | Find ARM ROP gadgets that give control of registers by popping them off the stack. 48 | 49 | ![ARM ROP Register Control](./img/armrop_registercontrol.png) 50 | 51 | ## Register Move 52 | Find ARM ROP gadgets that move values between registers. 53 | 54 | ![ARM ROP Register Move](./img/armrop_registermove.png) 55 | 56 | ## Stack Finder 57 | Find ARM ROP gadgets that put a stack address in a register. Useful for finding shell code and strings on the stack. 58 | 59 | ![ARM ROP Stack Finder](./img/armrop_stackfinder.png) 60 | 61 | ## Summary 62 | Print a summary of gadgets that have been book marked with the string `ropX` 63 | where `X` is the gadgets position in the rop chain. Don't mix ARM And Thumb 64 | gadgets with the summary, it won't work. I will fix this. 65 | 66 | ![Creating a Book mark](./img/bookmark.png) 67 | 68 | ![ARM Gadget Summary](./img/armrop_summary.png) 69 | 70 | ## System 71 | Find ARM ROP gadgets for calling system with a user controlled argument. 72 | 73 | ![ARM ROP System Gadget](./img/armrop_system.png) 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /readmes/callchain.md: -------------------------------------------------------------------------------- 1 | # Call Chain 2 | Display the call chain, if it exists, between two functions. The output will 3 | be display using a modified graphviz library as well as Ghidra's console. 4 | 5 | ![Call Chain Graph](./img/call_chain_graph.png) 6 | 7 | ![Call Chain Text](./img/call_chain_text.png) -------------------------------------------------------------------------------- /readmes/codatify.md: -------------------------------------------------------------------------------- 1 | # Codatify 2 | 3 | ## Fixup Code 4 | Define all undefined data in the .text section as code and covert it to a 5 | function if applicable. 6 | 7 | ### Before 8 | 9 | ![Code Before](./img/before_code.png) 10 | 11 | ### After 12 | 13 | ![Code After](./img/after_code.png) 14 | 15 | ## Fixup Data 16 | Define uninitialized strings and pointers in the code. All other uninitialized 17 | data is converted to a DWORD. Finally, search for function tables and rename 18 | functions based off the discovered tables. 19 | 20 | ### Before 21 | 22 | **Data Section** 23 | 24 | ![Data Before](./img/before_data.png) 25 | 26 | **Cross Reference** 27 | 28 | ![Xref Before](./img/before_xref.png) 29 | 30 | ### After 31 | 32 | **Data Section** 33 | 34 | ![Data After](./img/after_data.png) 35 | 36 | **Cross Reference** 37 | 38 | ![Xref Before](./img/after_xref.png) 39 | -------------------------------------------------------------------------------- /readmes/fluorescence.md: -------------------------------------------------------------------------------- 1 | # Fluorescence 2 | Highlight or un-highlight all function calls in the current binary. 3 | 4 | ![Highlighted function calls](./img/fluorescence.png) -------------------------------------------------------------------------------- /readmes/func_profiler.md: -------------------------------------------------------------------------------- 1 | # Function Profiler 2 | Display all cross references from the current function. Will display all 3 | strings, functions, and labels. Depending on the size of the function, the 4 | console output size may need to be adjusted to view all the text. 5 | 6 | ![Function Profiler Output](./img/function_profiler.png) -------------------------------------------------------------------------------- /readmes/img/after_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/after_code.png -------------------------------------------------------------------------------- /readmes/img/after_data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/after_data.png -------------------------------------------------------------------------------- /readmes/img/after_xref.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/after_xref.png -------------------------------------------------------------------------------- /readmes/img/arm_dis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/arm_dis.png -------------------------------------------------------------------------------- /readmes/img/arm_rop_double.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/arm_rop_double.png -------------------------------------------------------------------------------- /readmes/img/arm_rop_mov_r0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/arm_rop_mov_r0.png -------------------------------------------------------------------------------- /readmes/img/armrop_find.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/armrop_find.png -------------------------------------------------------------------------------- /readmes/img/armrop_registercontrol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/armrop_registercontrol.png -------------------------------------------------------------------------------- /readmes/img/armrop_registermove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/armrop_registermove.png -------------------------------------------------------------------------------- /readmes/img/armrop_stackfinder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/armrop_stackfinder.png -------------------------------------------------------------------------------- /readmes/img/armrop_summary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/armrop_summary.png -------------------------------------------------------------------------------- /readmes/img/armrop_system.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/armrop_system.png -------------------------------------------------------------------------------- /readmes/img/before_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/before_code.png -------------------------------------------------------------------------------- /readmes/img/before_data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/before_data.png -------------------------------------------------------------------------------- /readmes/img/before_xref.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/before_xref.png -------------------------------------------------------------------------------- /readmes/img/bookmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/bookmark.png -------------------------------------------------------------------------------- /readmes/img/call_chain_graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/call_chain_graph.png -------------------------------------------------------------------------------- /readmes/img/call_chain_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/call_chain_text.png -------------------------------------------------------------------------------- /readmes/img/double.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/double.png -------------------------------------------------------------------------------- /readmes/img/epilogue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/epilogue.png -------------------------------------------------------------------------------- /readmes/img/epilogue_input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/epilogue_input.png -------------------------------------------------------------------------------- /readmes/img/find.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/find.png -------------------------------------------------------------------------------- /readmes/img/find_dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/find_dialog.png -------------------------------------------------------------------------------- /readmes/img/fluorescence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/fluorescence.png -------------------------------------------------------------------------------- /readmes/img/format.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/format.png -------------------------------------------------------------------------------- /readmes/img/function_profiler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/function_profiler.png -------------------------------------------------------------------------------- /readmes/img/iret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/iret.png -------------------------------------------------------------------------------- /readmes/img/leaf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/leaf.png -------------------------------------------------------------------------------- /readmes/img/lia0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/lia0.png -------------------------------------------------------------------------------- /readmes/img/local_xrefs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/local_xrefs.png -------------------------------------------------------------------------------- /readmes/img/operator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/operator.png -------------------------------------------------------------------------------- /readmes/img/prologue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/prologue.png -------------------------------------------------------------------------------- /readmes/img/rename_variables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/rename_variables.png -------------------------------------------------------------------------------- /readmes/img/rizzo_apply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/rizzo_apply.png -------------------------------------------------------------------------------- /readmes/img/rizzo_save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/rizzo_save.png -------------------------------------------------------------------------------- /readmes/img/shellcode_chain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/shellcode_chain.png -------------------------------------------------------------------------------- /readmes/img/shellcode_options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/shellcode_options.png -------------------------------------------------------------------------------- /readmes/img/stack_finder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/stack_finder.png -------------------------------------------------------------------------------- /readmes/img/summary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/summary.png -------------------------------------------------------------------------------- /readmes/img/system_chain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/system_chain.png -------------------------------------------------------------------------------- /readmes/img/system_gadget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/system_gadget.png -------------------------------------------------------------------------------- /readmes/img/thumb_dis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/thumb_dis.png -------------------------------------------------------------------------------- /readmes/img/thumb_gadget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/readmes/img/thumb_gadget.png -------------------------------------------------------------------------------- /readmes/leafblower.md: -------------------------------------------------------------------------------- 1 | # Leaf Blower 2 | Identify common POSIX functions such as printf, sprintf, memcmp, strcpy, etc 3 | 4 | ## Identify Leaf Functions 5 | Identify leaf functions such as strcpy, strlen, atoi, etc. 6 | 7 | ![Leaf Functions Output](./img/leaf.png) 8 | 9 | 10 | ## Identify Format Parameter Functions 11 | Identify funtions that accept format parameters to identify sprintf, printf, fscanf, etc. 12 | 13 | 14 | ![Leaf Functions Output](./img/format.png) -------------------------------------------------------------------------------- /readmes/local_cross_ref.md: -------------------------------------------------------------------------------- 1 | # Local Cross References 2 | Find references to the selected item in the current function. 3 | 4 | ![Local Cross References](./img/local_xrefs.png) -------------------------------------------------------------------------------- /readmes/mips_rop.md: -------------------------------------------------------------------------------- 1 | # MIPS ROP Gadget Finder 2 | Find ROP gadgets in MIPS disassembly. 3 | 4 | ## Double Jumps 5 | Search for gadgets that contain double jumps. 6 | 7 | ![Double Jump](./img/double.png) 8 | 9 | ## Epilogue 10 | Find gadgets that give control of saved registers. 11 | 12 | ![User Input](./img/epilogue_input.png) 13 | 14 | ![Epilogue Result](./img/epilogue.png) 15 | 16 | ## Find 17 | Find gadgets that contain custom MIPS instructions. Regular expressions are 18 | supported. To search for a move to a0 from anything, simply search for 19 | "`move a0,.*`". 20 | 21 | ![Find Dialog Box](./img/find_dialog.png) 22 | 23 | ![Find Result](./img/find.png) 24 | 25 | ## Indirect Return 26 | Find indirect return gadgets. Call t9 and then return to ra. 27 | 28 | ![Indirect Return](./img/iret.png) 29 | 30 | ## Li a0 31 | Find gadgets that load a small value into a0. Useful for calling sleep. 32 | 33 | ![Li a0](./img/lia0.png) 34 | 35 | ## Prologue 36 | Find controllable gadgets at the beginning of functions that provide stack pointer movement. 37 | 38 | ![Prologue Result](./img/prologue.png) 39 | 40 | 41 | ## Shellcode ROP Chain 42 | Build rop chain to call shellcode. Chain is built off user intput and attempts 43 | to build the shortest chain. Multiple chains can be requested if the first 44 | is not suitable. If not enough registers are controlled a gadget to gain control 45 | of more register will be used first in the chain. 46 | 47 | ![Shellcode Options](./img/shellcode_options.png) 48 | 49 | - Avoid indirect returns 50 | - Avoid using gadgets that perform jumps to t9 in an epilogue but return to t9. Avoids stack movement. 51 | - Avoid double jumps 52 | - Avoid gadgets that perform back to back jumps. 53 | - Avoid gadgets that require a control jump 54 | - If a gadget jumps by controlling an 'a' or 'v' register a gadget will be added to get control of this register. Click this to avoid those types of gadgets. 55 | - Do not reuse gadgets 56 | - Prevents some gadget reuse. Spices up the results a little bit, but you may not get the shortest chain. 57 | - Verbose Output 58 | - Print all the outputs. 59 | 60 | ![Shellcode Chain](./img/shellcode_chain.png) 61 | 62 | 63 | ## System ROP Chain 64 | Build rop chain to call system with a controllable string. Chain is built from 65 | user input and attempts to build the shortest chain using multiple techniques. 66 | Multiple chains can be requested. If not enough registers are controlled 67 | a gadget to gain control of more register will be used first in the chain. 68 | 69 | ![System Chain](./img/system_chain.png) 70 | 71 | 72 | ## Stack Finder 73 | Find gadgets that place a stack address in a register. 74 | 75 | ![Stack Finders](./img/stack_finder.png) 76 | 77 | ## Summary 78 | Print a summary of gadgets that have been book marked with the string `ropX` 79 | where `X` is the gadgets position in the rop chain. Double jumps can be displayed 80 | by appending `_d` to the `ropX` bookmark name: `ropX_d`. 81 | 82 | ![Creating a Book mark](./img/bookmark.png) 83 | 84 | ![Summary](./img/summary.png) 85 | 86 | ## System Gadgets 87 | Find gadgets suitable for calling system with user controlled arguments. 88 | 89 | ![System Gadgets](./img/system_gadget.png) -------------------------------------------------------------------------------- /readmes/operator.md: -------------------------------------------------------------------------------- 1 | # Operator 2 | Identify calls and the parameters provided to the function when called. The 3 | script will take into account variadic arguments if they can be identified, 4 | however, passing argument via the stack will not. 5 | 6 | ![Function Calls](./img/operator.png) -------------------------------------------------------------------------------- /readmes/rename_variables.md: -------------------------------------------------------------------------------- 1 | # Rename Variables 2 | Rename saved stack variables for easier tracking. Only valid in MIPS. 3 | 4 | ![Rename stack variables](./img/rename_variables.png) -------------------------------------------------------------------------------- /readmes/rizzo.md: -------------------------------------------------------------------------------- 1 | # Rizzo 2 | 3 | Create function signatures that can be shared amongst different projects. There 4 | are multiple sets of signatures that are generated: 5 | 6 | - Formal:# Rizzo 7 | 8 | Create function signatures that can be shared amongst different projects. There 9 | are multiple sets of signatures that are generated: 10 | 11 | - Formal: Function matches entirely 12 | - Fuzzy: Functions resemble each other in terms of data/call references. 13 | - String: Functions contain same string references. 14 | - Immediate: Functions match based on large immediate value references. 15 | 16 | Formal signatures are applied first, followed by string, immediate, and fuzzy. 17 | If a function is considered a match internal calls are also considered for 18 | renaming. 19 | 20 | ## Apply 21 | Apply Rizzo signatures from another project. 22 | 23 | ![Apply Rizzo Signatures](./img/rizzo_apply.png) 24 | 25 | ## Save 26 | Save Rizzo signatures from the current project. 27 | 28 | ![Save Rizzo Signatures](./img/rizzo_save.png) 29 | Function matches entirely 30 | - Fuzzy: Functions resemble each other in terms of data/call references. 31 | - String: Functions contain same string references. 32 | - Immediate: Functions match based on large immediate value references. 33 | 34 | Formal signatures are applied first, followed by string, immediate, and fuzzy. 35 | If a function is considered a match internal calls are also considered for 36 | renaming. 37 | 38 | ## Apply 39 | Apply Rizzo signatures from another project. 40 | 41 | ![Apply Rizzo Signatures](./img/rizzo_apply.png) 42 | 43 | ## Save 44 | Save Rizzo signatures from the current project. 45 | 46 | ![Save Rizzo Signatures](./img/rizzo_save.png) 47 | -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grayhatacademy/ghidra_scripts/5c4d24bc7166f672015003572daeeb04d2e1f30e/utils/__init__.py -------------------------------------------------------------------------------- /utils/functiontable.py: -------------------------------------------------------------------------------- 1 | import string 2 | 3 | from ghidra.program.model.symbol import SourceType 4 | from ghidra.program.flatapi import FlatProgramAPI 5 | 6 | 7 | def is_valid_function_name(function_name): 8 | """ 9 | Determine if a function name is valid. 10 | 11 | :param function_name: Function name to inspect. 12 | :type function_name: unicode 13 | 14 | :returns: True if valid function name, false otherwise. 15 | :rtype: bool 16 | """ 17 | if not isinstance(function_name, unicode): 18 | return False 19 | 20 | if function_name[0] not in string.ascii_letters + '_': 21 | return False 22 | for letter in function_name[1:]: 23 | if letter not in string.ascii_letters + '_' + string.digits: 24 | return False 25 | return True 26 | 27 | 28 | class Element(object): 29 | """ 30 | Single element in a structure. 31 | """ 32 | 33 | def __init__(self, program, address): 34 | self._program = program 35 | self._flat_api = FlatProgramAPI(self._program) 36 | self._data = self._flat_api.getDataAt(address) 37 | 38 | self.address = address 39 | self.type = None 40 | self.value = None 41 | 42 | if not self._data: 43 | return 44 | 45 | # Determine the data's type and set the type/value accordingly. 46 | if self._data.isPointer(): 47 | reference = self._data.getPrimaryReference(0) 48 | if not reference: 49 | return 50 | 51 | to_addr = reference.toAddress 52 | func = self._flat_api.getFunctionAt(to_addr) 53 | if func: 54 | self.type = 'function' 55 | self.value = func 56 | else: 57 | value = self._flat_api.getDataAt(to_addr) 58 | if value: 59 | self.type = 'data' 60 | self.value = value 61 | else: 62 | self.type = self._data.dataType 63 | self.value = self._data 64 | 65 | 66 | class Structure(object): 67 | """ 68 | Declared structure 69 | """ 70 | 71 | def __init__(self, program, address, element_count, length=0): 72 | self._program = program 73 | self._element_size = self._program.getDefaultPointerSize() 74 | 75 | self.address = address 76 | self.elements = [] 77 | self.length = length 78 | 79 | curr_addr = self.address 80 | for i in range(0, element_count): 81 | element = Element(self._program, curr_addr) 82 | self.elements.append(element) 83 | curr_addr = curr_addr.add(self._element_size) 84 | if not curr_addr: 85 | break 86 | 87 | def get_pattern(self): 88 | """ 89 | Get the element pattern for this structure. 90 | 91 | :returns: List of element types. 92 | :rtype: list(objects) 93 | """ 94 | return [element.type for element in self.elements] 95 | 96 | def get_function(self): 97 | """ 98 | Each valid structure *should* contains one function. Find and return it. 99 | 100 | :returns: Function, if found, None otherwise. 101 | :rtype: ghidra.program.model.listing.Function or None 102 | """ 103 | for element in self.elements: 104 | if element.type == 'function': 105 | return element.value 106 | return None 107 | 108 | def get_string(self): 109 | """ 110 | Each valid structure *should* contains one string. Find and return it. 111 | 112 | :returns: String, if found, None otherwise. 113 | :rtype: str or None 114 | """ 115 | for element in self.elements: 116 | if element.type == 'data': 117 | return element.value.getValue() 118 | return None 119 | 120 | def next(self): 121 | """ 122 | Get the next structure in the structure array. 123 | 124 | :returns: Next structure or None if at the end of the array. 125 | :rtype: Structure or None 126 | """ 127 | if self.length <= 0: 128 | return None 129 | 130 | next_addr = self.address.add(len(self.elements) * self._element_size) 131 | 132 | return Structure(self._program, next_addr, len(self.elements), 133 | self.length - 1) 134 | 135 | 136 | class StructureFinder(object): 137 | """ 138 | Search for structures that appear to represent a function table at the 139 | specified address. 140 | """ 141 | 142 | def __init__(self, program, address): 143 | self._program = program 144 | self.address = address 145 | self.pattern_length = 0 146 | self.length = 1 147 | self._element_size = self._program.getDefaultPointerSize() 148 | self.structure = Structure(program, address, 16) 149 | 150 | def has_pattern(self): 151 | """ 152 | Determine if there is a structure pattern within 16 elements of the 153 | provided start address. 154 | 155 | :returns: True if a pattern was found, False otherwise. 156 | :rtype: bool 157 | """ 158 | for i in range(2, len(self.structure.elements) / 2): 159 | blk_one = self.structure.elements[:i] 160 | pattern_one = [element.type for element in blk_one] 161 | 162 | blk_two = self.structure.elements[i:i + i] 163 | pattern_two = [element.type for element in blk_two] 164 | 165 | # Valid patterns can only have one function and one data element. 166 | if 'function' not in pattern_one or 'data' not in pattern_one: 167 | continue 168 | 169 | if 1 != pattern_one.count('function'): 170 | continue 171 | if 1 != pattern_one.count('data'): 172 | continue 173 | 174 | if pattern_one == pattern_two: 175 | self.pattern_length = len(pattern_one) 176 | return True 177 | return False 178 | 179 | def _get_next_struct(self, address): 180 | """ 181 | Get the next apparent structure in the structure array. 182 | 183 | :returns: Next structure in the array. 184 | :rtype: Structure 185 | """ 186 | next_addr = address.add( 187 | self.pattern_length * self._element_size) 188 | return Structure(self._program, next_addr, self.pattern_length) 189 | 190 | def find_length(self): 191 | """ 192 | Determine the length of the structure array by casting the next entry 193 | until the pattern no longer matches. 194 | 195 | :returns: Length of structure array. 196 | :rtype: int 197 | """ 198 | if self.pattern_length == 0: 199 | return 0 200 | 201 | first_entry = Structure( 202 | self._program, self.address, self.pattern_length) 203 | pattern = first_entry.get_pattern() 204 | 205 | next_entry = self._get_next_struct(self.address) 206 | 207 | while next_entry and next_entry.get_pattern() == pattern: 208 | self.length += 1 209 | next_entry = self._get_next_struct(next_entry.address) 210 | 211 | return self.length 212 | 213 | def get_next_search(self): 214 | """ 215 | Get the next structure search. 216 | 217 | :returns: Structure finder object after this one. 218 | :rtype: StructureFinder 219 | """ 220 | curr_addr = self.address 221 | next_addr = self.length * self._element_size 222 | 223 | if self.pattern_length: 224 | next_addr *= self.pattern_length 225 | next_struct = curr_addr.add(next_addr) 226 | return StructureFinder(self._program, next_struct) 227 | 228 | 229 | class Finder(object): 230 | """ 231 | Find function tables in the current program and rename them. 232 | """ 233 | 234 | def __init__(self, program, section): 235 | self._program = program 236 | self._min_address = section.getMinAddress() 237 | self._max_address = section.getMaxAddress() 238 | self._element_size = self._program.getDefaultPointerSize() 239 | 240 | self.structs = [] 241 | 242 | def find_function_table(self): 243 | """ 244 | Find suspected function tables within the current section. 245 | """ 246 | struct = StructureFinder(self._program, self._min_address) 247 | while struct and struct.address < self._max_address: 248 | if struct.has_pattern(): 249 | length = struct.find_length() 250 | new_struct = Structure(self._program, 251 | struct.address, 252 | struct.pattern_length, 253 | length) 254 | 255 | self.structs.append(new_struct) 256 | struct = struct.get_next_search() 257 | 258 | def rename_functions(self): 259 | """ 260 | Rename unnamed functions from suspected function tables. 261 | """ 262 | renames = 0 263 | for struct in self.structs: 264 | curr_struct = struct 265 | while curr_struct: 266 | function = curr_struct.get_function() 267 | if function: 268 | curr_name = function.getName() 269 | if curr_name.startswith('FUN_'): 270 | new_name = curr_struct.get_string() 271 | if new_name and is_valid_function_name(new_name): 272 | function.setName(new_name, SourceType.USER_DEFINED) 273 | renames += 1 274 | 275 | curr_struct = curr_struct.next() 276 | 277 | print 'Function Names - %d' % renames 278 | -------------------------------------------------------------------------------- /utils/graphviz/__init__.py: -------------------------------------------------------------------------------- 1 | # graphviz - create dot, save, render, view 2 | 3 | """Assemble DOT source code and render it with Graphviz. 4 | 5 | >>> dot = Digraph(comment='The Round Table') 6 | 7 | >>> dot.node('A', 'King Arthur') 8 | >>> dot.node('B', 'Sir Bedevere the Wise') 9 | >>> dot.node('L', 'Sir Lancelot the Brave') 10 | 11 | >>> dot.edges(['AB', 'AL']) 12 | 13 | >>> dot.edge('B', 'L', constraint='false') 14 | 15 | >>> print(dot) #doctest: +NORMALIZE_WHITESPACE 16 | // The Round Table 17 | digraph { 18 | A [label="King Arthur"] 19 | B [label="Sir Bedevere the Wise"] 20 | L [label="Sir Lancelot the Brave"] 21 | A -> B 22 | A -> L 23 | B -> L [constraint=false] 24 | } 25 | """ 26 | 27 | from .dot import Graph, Digraph 28 | from .files import Source 29 | from .lang import nohtml 30 | from .backend import (render, pipe, version, view, 31 | ENGINES, FORMATS, RENDERERS, FORMATTERS, 32 | ExecutableNotFound, RequiredArgumentError) 33 | 34 | __all__ = [ 35 | 'Graph', 'Digraph', 36 | 'Source', 37 | 'nohtml', 38 | 'render', 'pipe', 'version', 'view', 39 | 'ENGINES', 'FORMATS', 'RENDERERS', 'FORMATTERS', 40 | 'ExecutableNotFound', 'RequiredArgumentError', 41 | ] 42 | 43 | __title__ = 'graphviz' 44 | __version__ = '0.12' 45 | __author__ = 'Sebastian Bank ' 46 | __license__ = 'MIT, see LICENSE.txt' 47 | __copyright__ = 'Copyright (c) 2013-2019 Sebastian Bank' 48 | 49 | #: Set of known layout commands used for rendering (``'dot'``, ``'neato'``, ...) 50 | ENGINES = ENGINES 51 | 52 | #: Set of known output formats for rendering (``'pdf'``, ``'png'``, ...) 53 | FORMATS = FORMATS 54 | 55 | #: Set of known output formatters for rendering (``'cairo'``, ``'gd'``, ...) 56 | FORMATTERS = FORMATTERS 57 | 58 | #: Set of known output renderers for rendering (``'cairo'``, ``'gd'``, ...) 59 | RENDERERS = RENDERERS 60 | 61 | ExecutableNotFound = ExecutableNotFound 62 | 63 | RequiredArgumentError = RequiredArgumentError 64 | -------------------------------------------------------------------------------- /utils/graphviz/_compat.py: -------------------------------------------------------------------------------- 1 | # _compat.py - Python 2/3 compatibility 2 | 3 | import os 4 | import sys 5 | import operator 6 | import subprocess 7 | 8 | PY2 = (sys.version_info.major == 2) 9 | 10 | 11 | if PY2: 12 | string_classes = (str, unicode) # needed individually for sublassing 13 | text_type = unicode 14 | 15 | iteritems = operator.methodcaller('iteritems') 16 | 17 | def makedirs(name, mode=0o777, exist_ok=False): 18 | try: 19 | os.makedirs(name, mode) 20 | except OSError: 21 | if not exist_ok or not os.path.isdir(name): 22 | raise 23 | 24 | def stderr_write_bytes(data, flush=False): 25 | """Write data str to sys.stderr (flush if requested).""" 26 | sys.stderr.write(data) 27 | if flush: 28 | sys.stderr.flush() 29 | 30 | def Popen_stderr_devnull(*args, **kwargs): # noqa: N802 31 | with open(os.devnull, 'w') as f: 32 | return subprocess.Popen(*args, stderr=f, **kwargs) 33 | 34 | class CalledProcessError(subprocess.CalledProcessError): 35 | 36 | def __init__(self, returncode, cmd, output=None, stderr=None): 37 | super(CalledProcessError, self).__init__(returncode, cmd, output) 38 | self.stderr = stderr 39 | 40 | @property 41 | def stdout(self): 42 | return self.output 43 | 44 | @stdout.setter 45 | def stdout(self, value): # pragma: no cover 46 | self.output = value 47 | 48 | 49 | else: 50 | string_classes = (str,) 51 | text_type = str 52 | 53 | def iteritems(d): 54 | return iter(d.items()) 55 | 56 | def makedirs(name, mode=0o777, exist_ok=False): # allow os.makedirs mocking 57 | return os.makedirs(name, mode, exist_ok=exist_ok) 58 | 59 | def stderr_write_bytes(data, flush=False): 60 | """Encode data str and write to sys.stderr (flush if requested).""" 61 | encoding = sys.stderr.encoding or sys.getdefaultencoding() 62 | sys.stderr.write(data.decode(encoding)) 63 | if flush: 64 | sys.stderr.flush() 65 | 66 | def Popen_stderr_devnull(*args, **kwargs): # noqa: N802 67 | return subprocess.Popen(*args, stderr=subprocess.DEVNULL, **kwargs) 68 | 69 | CalledProcessError = subprocess.CalledProcessError 70 | -------------------------------------------------------------------------------- /utils/graphviz/backend.py: -------------------------------------------------------------------------------- 1 | # backend.py - execute rendering, open files in viewer 2 | 3 | import os 4 | import re 5 | import sys 6 | import errno 7 | import logging 8 | import platform 9 | import subprocess 10 | 11 | from . import _compat 12 | 13 | from . import tools 14 | 15 | __all__ = [ 16 | 'render', 'pipe', 'version', 'view', 17 | 'ENGINES', 'FORMATS', 'RENDERERS', 'FORMATTERS', 18 | 'ExecutableNotFound', 'RequiredArgumentError', 19 | ] 20 | 21 | ENGINES = { # http://www.graphviz.org/pdf/dot.1.pdf 22 | 'dot', 'neato', 'twopi', 'circo', 'fdp', 'sfdp', 'patchwork', 'osage', 23 | } 24 | 25 | FORMATS = { # http://www.graphviz.org/doc/info/output.html 26 | 'bmp', 27 | 'canon', 'dot', 'gv', 'xdot', 'xdot1.2', 'xdot1.4', 28 | 'cgimage', 29 | 'cmap', 30 | 'eps', 31 | 'exr', 32 | 'fig', 33 | 'gd', 'gd2', 34 | 'gif', 35 | 'gtk', 36 | 'ico', 37 | 'imap', 'cmapx', 38 | 'imap_np', 'cmapx_np', 39 | 'ismap', 40 | 'jp2', 41 | 'jpg', 'jpeg', 'jpe', 42 | 'json', 'json0', 'dot_json', 'xdot_json', # Graphviz 2.40 43 | 'pct', 'pict', 44 | 'pdf', 45 | 'pic', 46 | 'plain', 'plain-ext', 47 | 'png', 48 | 'pov', 49 | 'ps', 50 | 'ps2', 51 | 'psd', 52 | 'sgi', 53 | 'svg', 'svgz', 54 | 'tga', 55 | 'tif', 'tiff', 56 | 'tk', 57 | 'vml', 'vmlz', 58 | 'vrml', 59 | 'wbmp', 60 | 'webp', 61 | 'xlib', 62 | 'x11', 63 | } 64 | 65 | RENDERERS = { # $ dot -T: 66 | 'cairo', 67 | 'dot', 68 | 'fig', 69 | 'gd', 70 | 'gdiplus', 71 | 'map', 72 | 'pic', 73 | 'pov', 74 | 'ps', 75 | 'svg', 76 | 'tk', 77 | 'vml', 78 | 'vrml', 79 | 'xdot', 80 | } 81 | 82 | FORMATTERS = {'cairo', 'core', 'gd', 'gdiplus', 'gdwbmp', 'xlib'} 83 | 84 | PLATFORM = sys.platform.lower() 85 | if 'java' in PLATFORM: 86 | import java.lang 87 | PLATFORM = java.lang.System.getProperty('os.name').lower() 88 | 89 | log = logging.getLogger(__name__) 90 | 91 | 92 | class ExecutableNotFound(RuntimeError): 93 | """Exception raised if the Graphviz executable is not found.""" 94 | 95 | _msg = ('failed to execute %r, ' 96 | 'make sure the Graphviz executables are on your systems\' PATH') 97 | 98 | def __init__(self, args): 99 | super(ExecutableNotFound, self).__init__(self._msg % args) 100 | 101 | 102 | class RequiredArgumentError(Exception): 103 | """Exception raised if a required argument is missing.""" 104 | 105 | 106 | class CalledProcessError(_compat.CalledProcessError): 107 | 108 | def __str__(self): 109 | s = super(CalledProcessError, self).__str__() 110 | return '%s [stderr: %r]' % (s, self.stderr) 111 | 112 | 113 | def command(engine, format_, filepath=None, renderer=None, formatter=None): 114 | """Return args list for ``subprocess.Popen`` and name of the rendered file.""" 115 | if formatter is not None and renderer is None: 116 | raise RequiredArgumentError('formatter given without renderer') 117 | 118 | if engine not in ENGINES: 119 | raise ValueError('unknown engine: %r' % engine) 120 | if format_ not in FORMATS: 121 | raise ValueError('unknown format: %r' % format_) 122 | if renderer is not None and renderer not in RENDERERS: 123 | raise ValueError('unknown renderer: %r' % renderer) 124 | if formatter is not None and formatter not in FORMATTERS: 125 | raise ValueError('unknown formatter: %r' % formatter) 126 | 127 | output_format = [f for f in (format_, renderer, formatter) if f is not None] 128 | cmd = [engine, '-T%s' % ':'.join(output_format)] 129 | rendered = None 130 | 131 | if filepath is not None: 132 | cmd.extend(['-O', filepath]) 133 | suffix = '.'.join(reversed(output_format)) 134 | rendered = '%s.%s' % (filepath, suffix) 135 | 136 | return cmd, rendered 137 | 138 | 139 | if PLATFORM == 'windows': # pragma: no cover 140 | def get_startupinfo(): 141 | """Return subprocess.STARTUPINFO instance hiding the console window.""" 142 | startupinfo = subprocess.STARTUPINFO() 143 | startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW 144 | startupinfo.wShowWindow = subprocess.SW_HIDE 145 | return startupinfo 146 | else: 147 | def get_startupinfo(): 148 | """Return None for startupinfo argument of ``subprocess.Popen``.""" 149 | return None 150 | 151 | 152 | def run(cmd, input=None, capture_output=False, check=False, quiet=False, **kwargs): 153 | """Run the command described by cmd and return its (stdout, stderr) tuple.""" 154 | log.debug('run %r', cmd) 155 | if input is not None: 156 | kwargs['stdin'] = subprocess.PIPE 157 | if capture_output: 158 | kwargs['stdout'] = kwargs['stderr'] = subprocess.PIPE 159 | 160 | try: 161 | proc = subprocess.Popen(cmd, startupinfo=get_startupinfo(), **kwargs) 162 | except OSError as e: 163 | if e.errno == errno.ENOENT: 164 | raise ExecutableNotFound(cmd) 165 | else: 166 | raise 167 | 168 | out, err = proc.communicate(input) 169 | 170 | if not quiet and err: 171 | _compat.stderr_write_bytes(err, flush=True) 172 | if check and proc.returncode: 173 | raise CalledProcessError(proc.returncode, cmd, 174 | output=out, stderr=err) 175 | 176 | return out, err 177 | 178 | 179 | def render(engine, format, filepath, renderer=None, formatter=None, quiet=False): 180 | """Render file with Graphviz ``engine`` into ``format``, return result filename. 181 | 182 | Args: 183 | engine: The layout commmand used for rendering (``'dot'``, ``'neato'``, ...). 184 | format: The output format used for rendering (``'pdf'``, ``'png'``, ...). 185 | filepath: Path to the DOT source file to render. 186 | renderer: The output renderer used for rendering (``'cairo'``, ``'gd'``, ...). 187 | formatter: The output formatter used for rendering (``'cairo'``, ``'gd'``, ...). 188 | quiet (bool): Suppress ``stderr`` output from the layout subprocess. 189 | Returns: 190 | The (possibly relative) path of the rendered file. 191 | Raises: 192 | ValueError: If ``engine``, ``format``, ``renderer``, or ``formatter`` are not known. 193 | graphviz.RequiredArgumentError: If ``formatter`` is given but ``renderer`` is None. 194 | graphviz.ExecutableNotFound: If the Graphviz executable is not found. 195 | subprocess.CalledProcessError: If the exit status is non-zero. 196 | 197 | The layout command is started from the directory of ``filepath``, so that 198 | references to external files (e.g. ``[image=...]``) can be given as paths 199 | relative to the DOT source file. 200 | """ 201 | dirname, filename = os.path.split(filepath) 202 | cmd, rendered = command(engine, format, filename, renderer, formatter) 203 | if dirname: 204 | cwd = dirname 205 | rendered = os.path.join(dirname, rendered) 206 | else: 207 | cwd = None 208 | run(cmd, capture_output=True, cwd=cwd, check=True, quiet=quiet) 209 | return rendered 210 | 211 | 212 | def pipe(engine, format, data, renderer=None, formatter=None, quiet=False): 213 | """Return ``data`` piped through Graphviz ``engine`` into ``format``. 214 | 215 | Args: 216 | engine: The layout commmand used for rendering (``'dot'``, ``'neato'``, ...). 217 | format: The output format used for rendering (``'pdf'``, ``'png'``, ...). 218 | data: The binary (encoded) DOT source string to render. 219 | renderer: The output renderer used for rendering (``'cairo'``, ``'gd'``, ...). 220 | formatter: The output formatter used for rendering (``'cairo'``, ``'gd'``, ...). 221 | quiet (bool): Suppress ``stderr`` output from the layout subprocess. 222 | Returns: 223 | Binary (encoded) stdout of the layout command. 224 | Raises: 225 | ValueError: If ``engine``, ``format``, ``renderer``, or ``formatter`` are not known. 226 | graphviz.RequiredArgumentError: If ``formatter`` is given but ``renderer`` is None. 227 | graphviz.ExecutableNotFound: If the Graphviz executable is not found. 228 | subprocess.CalledProcessError: If the exit status is non-zero. 229 | """ 230 | cmd, _ = command(engine, format, None, renderer, formatter) 231 | out, _ = run(cmd, input=data, capture_output=True, check=True, quiet=quiet) 232 | return out 233 | 234 | 235 | def version(): 236 | """Return the version number tuple from the ``stderr`` output of ``dot -V``. 237 | 238 | Returns: 239 | Two or three ``int`` version ``tuple``. 240 | Raises: 241 | graphviz.ExecutableNotFound: If the Graphviz executable is not found. 242 | subprocess.CalledProcessError: If the exit status is non-zero. 243 | RuntimmeError: If the output cannot be parsed into a version number. 244 | """ 245 | cmd = ['dot', '-V'] 246 | out, _ = run(cmd, check=True, 247 | stdout=subprocess.PIPE, 248 | stderr=subprocess.STDOUT) 249 | 250 | info = out.decode('ascii') 251 | ma = re.search(r'graphviz version (\d+\.\d+(?:\.\d+)?) ', info) 252 | if ma is None: 253 | raise RuntimeError('cannot parse %r output: %r' % (cmd, info)) 254 | return tuple(int(d) for d in ma.group(1).split('.')) 255 | 256 | 257 | def view(filepath, quiet=False): 258 | """Open filepath with its default viewing application (platform-specific). 259 | 260 | Args: 261 | filepath: Path to the file to open in viewer. 262 | quiet (bool): Suppress ``stderr`` output from the viewer process 263 | (ineffective on Windows). 264 | Raises: 265 | RuntimeError: If the current platform is not supported. 266 | """ 267 | try: 268 | view_func = getattr(view, PLATFORM) 269 | except AttributeError: 270 | raise RuntimeError('platform %r not supported' % PLATFORM) 271 | view_func(filepath, quiet) 272 | 273 | 274 | @tools.attach(view, 'darwin') 275 | def view_darwin(filepath, quiet): 276 | """Open filepath with its default application (mac).""" 277 | cmd = ['open', filepath] 278 | log.debug('view: %r', cmd) 279 | popen_func = _compat.Popen_stderr_devnull if quiet else subprocess.Popen 280 | popen_func(cmd) 281 | 282 | 283 | @tools.attach(view, 'linux') 284 | @tools.attach(view, 'freebsd') 285 | def view_unixoid(filepath, quiet): 286 | """Open filepath in the user's preferred application (linux, freebsd).""" 287 | cmd = ['xdg-open', filepath] 288 | log.debug('view: %r', cmd) 289 | popen_func = _compat.Popen_stderr_devnull if quiet else subprocess.Popen 290 | popen_func(cmd) 291 | 292 | 293 | @tools.attach(view, 'windows') 294 | def view_windows(filepath, quiet): 295 | """Start filepath with its associated application (windows).""" 296 | # TODO: implement quiet=True 297 | filepath = os.path.normpath(filepath) 298 | log.debug('view: %r', filepath) 299 | os.startfile(filepath) 300 | -------------------------------------------------------------------------------- /utils/graphviz/dot.py: -------------------------------------------------------------------------------- 1 | # dot.py - create dot code 2 | 3 | r"""Assemble DOT source code objects. 4 | 5 | >>> dot = Graph(comment=u'M\xf8nti Pyth\xf8n ik den H\xf8lie Grailen') 6 | 7 | >>> dot.node(u'M\xf8\xf8se') 8 | >>> dot.node('trained_by', u'trained by') 9 | >>> dot.node('tutte', u'TUTTE HERMSGERVORDENBROTBORDA') 10 | 11 | >>> dot.edge(u'M\xf8\xf8se', 'trained_by') 12 | >>> dot.edge('trained_by', 'tutte') 13 | 14 | >>> dot.node_attr['shape'] = 'rectangle' 15 | 16 | >>> print(dot.source.replace(u'\xf8', '0')) #doctest: +NORMALIZE_WHITESPACE 17 | // M0nti Pyth0n ik den H0lie Grailen 18 | graph { 19 | node [shape=rectangle] 20 | "M00se" 21 | trained_by [label="trained by"] 22 | tutte [label="TUTTE HERMSGERVORDENBROTBORDA"] 23 | "M00se" -- trained_by 24 | trained_by -- tutte 25 | } 26 | 27 | >>> dot.view('test-output/m00se.gv') # doctest: +SKIP 28 | 'test-output/m00se.gv.pdf' 29 | """ 30 | 31 | from . import lang 32 | from . import files 33 | 34 | __all__ = ['Graph', 'Digraph'] 35 | 36 | 37 | class Dot(files.File): 38 | """Assemble, save, and render DOT source code, open result in viewer.""" 39 | 40 | _comment = '// %s' 41 | _subgraph = 'subgraph %s{' 42 | _subgraph_plain = '%s{' 43 | _node = _attr = '\t%s%s' 44 | _attr_plain = _attr % ('%s', '') 45 | _tail = '}' 46 | 47 | _quote = staticmethod(lang.quote) 48 | _quote_edge = staticmethod(lang.quote_edge) 49 | 50 | _a_list = staticmethod(lang.a_list) 51 | _attr_list = staticmethod(lang.attr_list) 52 | 53 | def __init__(self, name=None, comment=None, 54 | filename=None, directory=None, 55 | format=None, engine=None, encoding=files.ENCODING, 56 | graph_attr=None, node_attr=None, edge_attr=None, body=None, 57 | strict=False): 58 | self.name = name 59 | self.comment = comment 60 | 61 | super(Dot, self).__init__(filename, directory, format, engine, encoding) 62 | 63 | self.graph_attr = dict(graph_attr) if graph_attr is not None else {} 64 | self.node_attr = dict(node_attr) if node_attr is not None else {} 65 | self.edge_attr = dict(edge_attr) if edge_attr is not None else {} 66 | 67 | self.body = list(body) if body is not None else [] 68 | 69 | self.strict = strict 70 | 71 | def _kwargs(self): 72 | result = super(Dot, self)._kwargs() 73 | result.update(name=self.name, 74 | comment=self.comment, 75 | graph_attr=dict(self.graph_attr), 76 | node_attr=dict(self.node_attr), 77 | edge_attr=dict(self.edge_attr), 78 | body=list(self.body), 79 | strict=self.strict) 80 | return result 81 | 82 | def clear(self, keep_attrs=False): 83 | """Reset content to an empty body, clear graph/node/egde_attr mappings. 84 | 85 | Args: 86 | keep_attrs (bool): preserve graph/node/egde_attr mappings 87 | """ 88 | if not keep_attrs: 89 | for a in (self.graph_attr, self.node_attr, self.edge_attr): 90 | a.clear() 91 | del self.body[:] 92 | 93 | def __iter__(self, subgraph=False): 94 | """Yield the DOT source code line by line (as graph or subgraph).""" 95 | if self.comment: 96 | yield self._comment % self.comment 97 | 98 | if subgraph: 99 | if self.strict: 100 | raise ValueError('subgraphs cannot be strict') 101 | head = self._subgraph if self.name else self._subgraph_plain 102 | else: 103 | head = self._head_strict if self.strict else self._head 104 | yield head % (self._quote(self.name) + ' ' if self.name else '') 105 | 106 | for kw in ('graph', 'node', 'edge'): 107 | attrs = getattr(self, '%s_attr' % kw) 108 | if attrs: 109 | yield self._attr % (kw, self._attr_list(None, attrs)) 110 | 111 | for line in self.body: 112 | yield line 113 | 114 | yield self._tail 115 | 116 | def __str__(self): 117 | """The DOT source code as string.""" 118 | return '\n'.join(self) 119 | 120 | source = property(__str__, doc=__str__.__doc__) 121 | 122 | def node(self, name, label=None, _attributes=None, **attrs): 123 | """Create a node. 124 | 125 | Args: 126 | name: Unique identifier for the node inside the source. 127 | label: Caption to be displayed (defaults to the node ``name``). 128 | attrs: Any additional node attributes (must be strings). 129 | """ 130 | name = self._quote(name) 131 | attr_list = self._attr_list(label, attrs, _attributes) 132 | line = self._node % (name, attr_list) 133 | self.body.append(line) 134 | 135 | def edge(self, tail_name, head_name, label=None, _attributes=None, **attrs): 136 | """Create an edge between two nodes. 137 | 138 | Args: 139 | tail_name: Start node identifier. 140 | head_name: End node identifier. 141 | label: Caption to be displayed near the edge. 142 | attrs: Any additional edge attributes (must be strings). 143 | """ 144 | tail_name = self._quote_edge(tail_name) 145 | head_name = self._quote_edge(head_name) 146 | attr_list = self._attr_list(label, attrs, _attributes) 147 | line = self._edge % (tail_name, head_name, attr_list) 148 | self.body.append(line) 149 | 150 | def edges(self, tail_head_iter): 151 | """Create a bunch of edges. 152 | 153 | Args: 154 | tail_head_iter: Iterable of ``(tail_name, head_name)`` pairs. 155 | """ 156 | edge = self._edge_plain 157 | quote = self._quote_edge 158 | lines = (edge % (quote(t), quote(h)) for t, h in tail_head_iter) 159 | self.body.extend(lines) 160 | 161 | def attr(self, kw=None, _attributes=None, **attrs): 162 | """Add a general or graph/node/edge attribute statement. 163 | 164 | Args: 165 | kw: Attributes target (``None`` or ``'graph'``, ``'node'``, ``'edge'``). 166 | attrs: Attributes to be set (must be strings, may be empty). 167 | 168 | See the :ref:`usage examples in the User Guide `. 169 | """ 170 | if kw is not None and kw.lower() not in ('graph', 'node', 'edge'): 171 | raise ValueError('attr statement must target graph, node, or edge: ' 172 | '%r' % kw) 173 | if attrs or _attributes: 174 | if kw is None: 175 | a_list = self._a_list(None, attrs, _attributes) 176 | line = self._attr_plain % a_list 177 | else: 178 | attr_list = self._attr_list(None, attrs, _attributes) 179 | line = self._attr % (kw, attr_list) 180 | self.body.append(line) 181 | 182 | def subgraph(self, graph=None, name=None, comment=None, 183 | graph_attr=None, node_attr=None, edge_attr=None, body=None): 184 | """Add the current content of the given sole ``graph`` argument as subgraph \ 185 | or return a context manager returning a new graph instance created \ 186 | with the given (``name``, ``comment``, etc.) arguments whose content is \ 187 | added as subgraph when leaving the context manager's ``with``-block. 188 | 189 | Args: 190 | graph: An instance of the same kind (:class:`.Graph`, :class:`.Digraph`) 191 | as the current graph (sole argument in non-with-block use). 192 | name: Subgraph name (``with``-block use). 193 | comment: Subgraph comment (``with``-block use). 194 | graph_attr: Subgraph-level attribute-value mapping (``with``-block use). 195 | node_attr: Node-level attribute-value mapping (``with``-block use). 196 | edge_attr: Edge-level attribute-value mapping (``with``-block use). 197 | body: Verbatim lines to add to the subgraph ``body`` (``with``-block use). 198 | 199 | See the :ref:`usage examples in the User Guide `. 200 | 201 | .. note:: 202 | If the ``name`` of the subgraph begins with ``'cluster'`` (all lowercase) 203 | the layout engine will treat it as a special cluster subgraph. 204 | """ 205 | if graph is None: 206 | return SubgraphContext(self, {'name': name, 207 | 'comment': comment, 208 | 'graph_attr': graph_attr, 209 | 'node_attr': node_attr, 210 | 'edge_attr': edge_attr, 211 | 'body': body}) 212 | 213 | args = [name, comment, graph_attr, node_attr, edge_attr, body] 214 | if not all(a is None for a in args): 215 | raise ValueError('graph must be sole argument of subgraph()') 216 | 217 | if graph.directed != self.directed: 218 | raise ValueError('%r cannot add subgraph of different kind:' 219 | ' %r' % (self, graph)) 220 | 221 | lines = ['\t' + line for line in graph.__iter__(subgraph=True)] 222 | self.body.extend(lines) 223 | 224 | 225 | class SubgraphContext(object): 226 | """Return a blank instance of the parent and add as subgraph on exit.""" 227 | 228 | def __init__(self, parent, kwargs): 229 | self.parent = parent 230 | self.graph = parent.__class__(**kwargs) 231 | 232 | def __enter__(self): 233 | return self.graph 234 | 235 | def __exit__(self, type_, value, traceback): 236 | if type_ is None: 237 | self.parent.subgraph(self.graph) 238 | 239 | 240 | class Graph(Dot): 241 | """Graph source code in the DOT language. 242 | 243 | Args: 244 | name: Graph name used in the source code. 245 | comment: Comment added to the first line of the source. 246 | filename: Filename for saving the source (defaults to ``name`` + ``'.gv'``). 247 | directory: (Sub)directory for source saving and rendering. 248 | format: Rendering output format (``'pdf'``, ``'png'``, ...). 249 | engine: Layout command used (``'dot'``, ``'neato'``, ...). 250 | encoding: Encoding for saving the source. 251 | graph_attr: Mapping of ``(attribute, value)`` pairs for the graph. 252 | node_attr: Mapping of ``(attribute, value)`` pairs set for all nodes. 253 | edge_attr: Mapping of ``(attribute, value)`` pairs set for all edges. 254 | body: Iterable of verbatim lines to add to the graph ``body``. 255 | strict (bool): Rendering should merge multi-edges. 256 | 257 | Note: 258 | All parameters are optional and can be changed under their 259 | corresponding attribute name after instance creation. 260 | """ 261 | 262 | _head = 'graph %s{' 263 | _head_strict = 'strict %s' % _head 264 | _edge = '\t%s -- %s%s' 265 | _edge_plain = _edge % ('%s', '%s', '') 266 | 267 | @property 268 | def directed(self): 269 | """``False``""" 270 | return False 271 | 272 | 273 | class Digraph(Dot): 274 | """Directed graph source code in the DOT language.""" 275 | 276 | if Graph.__doc__ is not None: 277 | __doc__ += Graph.__doc__.partition('.')[2] 278 | 279 | _head = 'digraph %s{' 280 | _head_strict = 'strict %s' % _head 281 | _edge = '\t%s -> %s%s' 282 | _edge_plain = _edge % ('%s', '%s', '') 283 | 284 | @property 285 | def directed(self): 286 | """``True``""" 287 | return True 288 | -------------------------------------------------------------------------------- /utils/graphviz/files.py: -------------------------------------------------------------------------------- 1 | # files.py - save, render, view 2 | 3 | """Save DOT code objects, render with Graphviz dot, and open in viewer.""" 4 | 5 | import os 6 | import io 7 | import codecs 8 | import locale 9 | import logging 10 | 11 | from ._compat import text_type 12 | 13 | from . import backend 14 | from . import tools 15 | 16 | __all__ = ['File', 'Source'] 17 | 18 | ENCODING = 'utf-8' 19 | 20 | 21 | log = logging.getLogger(__name__) 22 | 23 | 24 | class Base(object): 25 | 26 | _format = 'pdf' 27 | _engine = 'dot' 28 | _encoding = ENCODING 29 | 30 | @property 31 | def format(self): 32 | """The output format used for rendering (``'pdf'``, ``'png'``, ...).""" 33 | return self._format 34 | 35 | @format.setter 36 | def format(self, format): 37 | format = format.lower() 38 | if format not in backend.FORMATS: 39 | raise ValueError('unknown format: %r' % format) 40 | self._format = format 41 | 42 | @property 43 | def engine(self): 44 | """The layout commmand used for rendering (``'dot'``, ``'neato'``, ...).""" 45 | return self._engine 46 | 47 | @engine.setter 48 | def engine(self, engine): 49 | engine = engine.lower() 50 | if engine not in backend.ENGINES: 51 | raise ValueError('unknown engine: %r' % engine) 52 | self._engine = engine 53 | 54 | @property 55 | def encoding(self): 56 | """The encoding for the saved source file.""" 57 | return self._encoding 58 | 59 | @encoding.setter 60 | def encoding(self, encoding): 61 | if encoding is None: 62 | encoding = locale.getpreferredencoding() 63 | codecs.lookup(encoding) # raise early 64 | self._encoding = encoding 65 | 66 | def copy(self): 67 | """Return a copied instance of the object. 68 | 69 | Returns: 70 | An independent copy of the current object. 71 | """ 72 | kwargs = self._kwargs() 73 | return self.__class__(**kwargs) 74 | 75 | def _kwargs(self): 76 | ns = self.__dict__ 77 | return {a[1:]: ns[a] for a in ('_format', '_engine', '_encoding') 78 | if a in ns} 79 | 80 | 81 | class File(Base): 82 | 83 | directory = '' 84 | 85 | _default_extension = 'gv' 86 | 87 | def __init__(self, filename=None, directory=None, 88 | format=None, engine=None, encoding=ENCODING): 89 | if filename is None: 90 | name = getattr(self, 'name', None) or self.__class__.__name__ 91 | filename = '%s.%s' % (name, self._default_extension) 92 | self.filename = filename 93 | 94 | if directory is not None: 95 | self.directory = directory 96 | 97 | if format is not None: 98 | self.format = format 99 | 100 | if engine is not None: 101 | self.engine = engine 102 | 103 | self.encoding = encoding 104 | 105 | def _kwargs(self): 106 | result = super(File, self)._kwargs() 107 | result['filename'] = self.filename 108 | if 'directory' in self.__dict__: 109 | result['directory'] = self.directory 110 | return result 111 | 112 | def _repr_svg_(self): 113 | return self.pipe(format='svg').decode(self._encoding) 114 | 115 | def pipe(self, format=None, renderer=None, formatter=None, quiet=False): 116 | """Return the source piped through the Graphviz layout command. 117 | 118 | Args: 119 | format: The output format used for rendering (``'pdf'``, ``'png'``, etc.). 120 | renderer: The output renderer used for rendering (``'cairo'``, ``'gd'``, ...). 121 | formatter: The output formatter used for rendering (``'cairo'``, ``'gd'``, ...). 122 | quiet (bool): Suppress ``stderr`` output from the layout subprocess. 123 | Returns: 124 | Binary (encoded) stdout of the layout command. 125 | Raises: 126 | ValueError: If ``format``, ``renderer``, or ``formatter`` are not known. 127 | graphviz.RequiredArgumentError: If ``formatter`` is given but ``renderer`` is None. 128 | graphviz.ExecutableNotFound: If the Graphviz executable is not found. 129 | subprocess.CalledProcessError: If the exit status is non-zero. 130 | """ 131 | if format is None: 132 | format = self._format 133 | 134 | data = text_type(self.source).encode(self._encoding) 135 | 136 | out = backend.pipe(self._engine, format, data, 137 | renderer=renderer, formatter=formatter, 138 | quiet=quiet) 139 | 140 | return out 141 | 142 | @property 143 | def filepath(self): 144 | return os.path.join(self.directory, self.filename) 145 | 146 | def save(self, filename=None, directory=None): 147 | """Save the DOT source to file. Ensure the file ends with a newline. 148 | 149 | Args: 150 | filename: Filename for saving the source (defaults to ``name`` + ``'.gv'``) 151 | directory: (Sub)directory for source saving and rendering. 152 | Returns: 153 | The (possibly relative) path of the saved source file. 154 | """ 155 | if filename is not None: 156 | self.filename = filename 157 | if directory is not None: 158 | self.directory = directory 159 | 160 | filepath = self.filepath 161 | tools.mkdirs(filepath) 162 | 163 | data = text_type(self.source) 164 | 165 | log.debug('write %d bytes to %r', len(data), filepath) 166 | with io.open(filepath, 'w', encoding=self.encoding) as fd: 167 | fd.write(data) 168 | if not data.endswith(u'\n'): 169 | fd.write(u'\n') 170 | 171 | return filepath 172 | 173 | def render(self, filename=None, directory=None, view=False, cleanup=False, 174 | format=None, renderer=None, formatter=None, 175 | quiet=False, quiet_view=False): 176 | """Save the source to file and render with the Graphviz engine. 177 | 178 | Args: 179 | filename: Filename for saving the source (defaults to ``name`` + ``'.gv'``) 180 | directory: (Sub)directory for source saving and rendering. 181 | view (bool): Open the rendered result with the default application. 182 | cleanup (bool): Delete the source file after rendering. 183 | format: The output format used for rendering (``'pdf'``, ``'png'``, etc.). 184 | renderer: The output renderer used for rendering (``'cairo'``, ``'gd'``, ...). 185 | formatter: The output formatter used for rendering (``'cairo'``, ``'gd'``, ...). 186 | quiet (bool): Suppress ``stderr`` output from the layout subprocess. 187 | quiet_view (bool): Suppress ``stderr`` output from the viewer process 188 | (implies ``view=True``, ineffective on Windows). 189 | Returns: 190 | The (possibly relative) path of the rendered file. 191 | Raises: 192 | ValueError: If ``format``, ``renderer``, or ``formatter`` are not known. 193 | graphviz.RequiredArgumentError: If ``formatter`` is given but ``renderer`` is None. 194 | graphviz.ExecutableNotFound: If the Graphviz executable is not found. 195 | subprocess.CalledProcessError: If the exit status is non-zero. 196 | RuntimeError: If viewer opening is requested but not supported. 197 | 198 | The layout command is started from the directory of ``filepath``, so that 199 | references to external files (e.g. ``[image=...]``) can be given as paths 200 | relative to the DOT source file. 201 | """ 202 | filepath = self.save(filename, directory) 203 | 204 | if format is None: 205 | format = self._format 206 | 207 | rendered = backend.render(self._engine, format, filepath, 208 | renderer=renderer, formatter=formatter, 209 | quiet=quiet) 210 | 211 | if cleanup: 212 | log.debug('delete %r', filepath) 213 | os.remove(filepath) 214 | 215 | if quiet_view or view: 216 | self._view(rendered, self._format, quiet_view) 217 | 218 | return rendered 219 | 220 | def view(self, filename=None, directory=None, cleanup=False, 221 | quiet=False, quiet_view=False): 222 | """Save the source to file, open the rendered result in a viewer. 223 | 224 | Args: 225 | filename: Filename for saving the source (defaults to ``name`` + ``'.gv'``) 226 | directory: (Sub)directory for source saving and rendering. 227 | cleanup (bool): Delete the source file after rendering. 228 | quiet (bool): Suppress ``stderr`` output from the layout subprocess. 229 | quiet_view (bool): Suppress ``stderr`` output from the viewer process 230 | (ineffective on Windows). 231 | Returns: 232 | The (possibly relative) path of the rendered file. 233 | Raises: 234 | graphviz.ExecutableNotFound: If the Graphviz executable is not found. 235 | subprocess.CalledProcessError: If the exit status is non-zero. 236 | RuntimeError: If opening the viewer is not supported. 237 | 238 | Short-cut method for calling :meth:`.render` with ``view=True``. 239 | """ 240 | return self.render(filename=filename, directory=directory, 241 | view=True, cleanup=cleanup, 242 | quiet=quiet, quiet_view=quiet_view) 243 | 244 | def _view(self, filepath, format, quiet): 245 | """Start the right viewer based on file format and platform.""" 246 | methodnames = [ 247 | '_view_%s_%s' % (format, backend.PLATFORM), 248 | '_view_%s' % backend.PLATFORM, 249 | ] 250 | for name in methodnames: 251 | view_method = getattr(self, name, None) 252 | if view_method is not None: 253 | break 254 | else: 255 | raise RuntimeError('%r has no built-in viewer support for %r' 256 | ' on %r platform' % (self.__class__, format, 257 | backend.PLATFORM)) 258 | view_method(filepath, quiet) 259 | 260 | _view_darwin = staticmethod(backend.view.darwin) 261 | _view_freebsd = staticmethod(backend.view.freebsd) 262 | _view_linux = staticmethod(backend.view.linux) 263 | _view_windows = staticmethod(backend.view.windows) 264 | 265 | 266 | class Source(File): 267 | """Verbatim DOT source code string to be rendered by Graphviz. 268 | 269 | Args: 270 | source: The verbatim DOT source code string. 271 | filename: Filename for saving the source (defaults to ``'Source.gv'``). 272 | directory: (Sub)directory for source saving and rendering. 273 | format: Rendering output format (``'pdf'``, ``'png'``, ...). 274 | engine: Layout command used (``'dot'``, ``'neato'``, ...). 275 | encoding: Encoding for saving the source. 276 | 277 | Note: 278 | All parameters except ``source`` are optional. All of them can be changed 279 | under their corresponding attribute name after instance creation. 280 | """ 281 | 282 | @classmethod 283 | def from_file(cls, filename, directory=None, 284 | format=None, engine=None, encoding=ENCODING): 285 | """Return an instance with the source string read from the given file. 286 | 287 | Args: 288 | filename: Filename for loading/saving the source. 289 | directory: (Sub)directory for source loading/saving and rendering. 290 | format: Rendering output format (``'pdf'``, ``'png'``, ...). 291 | engine: Layout command used (``'dot'``, ``'neato'``, ...). 292 | encoding: Encoding for loading/saving the source. 293 | """ 294 | filepath = os.path.join(directory or '', filename) 295 | if encoding is None: 296 | encoding = locale.getpreferredencoding() 297 | log.debug('read %r with encoding %r', filepath, encoding) 298 | with io.open(filepath, encoding=encoding) as fd: 299 | source = fd.read() 300 | return cls(source, filename, directory, format, engine, encoding) 301 | 302 | def __init__(self, source, filename=None, directory=None, 303 | format=None, engine=None, encoding=ENCODING): 304 | super(Source, self).__init__(filename, directory, 305 | format, engine, encoding) 306 | self.source = source #: The verbatim DOT source code string. 307 | 308 | def _kwargs(self): 309 | result = super(Source, self)._kwargs() 310 | result['source'] = self.source 311 | return result 312 | -------------------------------------------------------------------------------- /utils/graphviz/lang.py: -------------------------------------------------------------------------------- 1 | # lang.py - dot language creation helpers 2 | 3 | """Quote strings to be valid DOT identifiers, assemble attribute lists.""" 4 | 5 | import re 6 | import collections 7 | 8 | from . import _compat 9 | 10 | from . import tools 11 | 12 | __all__ = ['quote', 'quote_edge', 'a_list', 'attr_list'] 13 | 14 | # http://www.graphviz.org/doc/info/lang.html 15 | 16 | HTML_STRING = re.compile(r'<.*>$', re.DOTALL) 17 | 18 | ID = re.compile(r'([a-zA-Z_][a-zA-Z0-9_]*|-?(\.\d+|\d+(\.\d*)?))$') 19 | 20 | KEYWORDS = {'node', 'edge', 'graph', 'digraph', 'subgraph', 'strict'} 21 | 22 | COMPASS = {'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw', 'c', '_'} # TODO 23 | 24 | 25 | def quote(identifier, 26 | html=HTML_STRING.match, valid_id=ID.match, dot_keywords=KEYWORDS): 27 | r"""Return DOT identifier from string, quote if needed. 28 | 29 | >>> quote('') 30 | '""' 31 | 32 | >>> quote('spam') 33 | 'spam' 34 | 35 | >>> quote('spam spam') 36 | '"spam spam"' 37 | 38 | >>> quote('-4.2') 39 | '-4.2' 40 | 41 | >>> quote('.42') 42 | '.42' 43 | 44 | >>> quote('<spam>') 45 | '<spam>' 46 | 47 | >>> quote(nohtml('<>')) 48 | '"<>"' 49 | 50 | >>> print(quote('"')) 51 | "\"" 52 | 53 | >>> print(quote('\\"')) 54 | "\\\"" 55 | """ 56 | if html(identifier) and not isinstance(identifier, NoHtml): 57 | pass 58 | elif not valid_id(identifier) or identifier.lower() in dot_keywords: 59 | return '"%s"' % identifier.replace('\\', '\\\\').replace('"', '\\"') 60 | return identifier 61 | 62 | 63 | def quote_edge(identifier): 64 | """Return DOT edge statement node_id from string, quote if needed. 65 | 66 | >>> quote_edge('spam') 67 | 'spam' 68 | 69 | >>> quote_edge('spam spam:eggs eggs') 70 | '"spam spam":"eggs eggs"' 71 | 72 | >>> quote_edge('spam:eggs:s') 73 | 'spam:eggs:s' 74 | """ 75 | node, _, rest = identifier.partition(':') 76 | parts = [quote(node)] 77 | if rest: 78 | port, _, compass = rest.partition(':') 79 | parts.append(quote(port)) 80 | if compass: 81 | parts.append(compass) 82 | return ':'.join(parts) 83 | 84 | 85 | def a_list(label=None, kwargs=None, attributes=None): 86 | """Return assembled DOT a_list string. 87 | 88 | >>> a_list('spam', {'spam': None, 'ham': 'ham ham', 'eggs': ''}) 89 | 'label=spam eggs="" ham="ham ham"' 90 | """ 91 | result = ['label=%s' % quote(label)] if label is not None else [] 92 | if kwargs: 93 | items = ['%s=%s' % (quote(k), quote(v)) 94 | for k, v in tools.mapping_items(kwargs) if v is not None] 95 | result.extend(items) 96 | if attributes: 97 | if hasattr(attributes, 'items'): 98 | attributes = tools.mapping_items(attributes) 99 | items = ['%s=%s' % (quote(k), quote(v)) 100 | for k, v in attributes if v is not None] 101 | result.extend(items) 102 | return ' '.join(result) 103 | 104 | 105 | def attr_list(label=None, kwargs=None, attributes=None): 106 | """Return assembled DOT attribute list string. 107 | 108 | Sorts ``kwargs`` and ``attributes`` if they are plain dicts (to avoid 109 | unpredictable order from hash randomization in Python 3 versions). 110 | 111 | >>> attr_list() 112 | '' 113 | 114 | >>> attr_list('spam spam', kwargs={'eggs': 'eggs', 'ham': 'ham ham'}) 115 | ' [label="spam spam" eggs=eggs ham="ham ham"]' 116 | 117 | >>> attr_list(kwargs={'spam': None, 'eggs': ''}) 118 | ' [eggs=""]' 119 | """ 120 | content = a_list(label, kwargs, attributes) 121 | if not content: 122 | return '' 123 | return ' [%s]' % content 124 | 125 | 126 | class NoHtml(object): 127 | """Mixin for string subclasses disabling fall-through of ``'<...>'``.""" 128 | 129 | __slots__ = () 130 | 131 | _doc = "%s subclass that does not treat ``'<...>'`` as DOT HTML string." 132 | 133 | @classmethod 134 | def _subcls(cls, other): 135 | name = '%s_%s' % (cls.__name__, other.__name__) 136 | bases = (other, cls) 137 | ns = {'__doc__': cls._doc % other.__name__} 138 | return type(name, bases, ns) 139 | 140 | 141 | NOHTML = collections.OrderedDict((c, NoHtml._subcls(c)) for c in _compat.string_classes) 142 | 143 | 144 | def nohtml(s): 145 | """Return copy of ``s`` that will not treat ``'<...>'`` as DOT HTML string in quoting. 146 | 147 | Args: 148 | s: String in which leading ``'<'`` and trailing ``'>'`` should be treated as literal. 149 | Raises: 150 | TypeError: If ``s`` is not a ``str`` on Python 3, or a ``str``/``unicode`` on Python 2. 151 | 152 | >>> quote('<>-*-<>') 153 | '<>-*-<>' 154 | 155 | >>> quote(nohtml('<>-*-<>')) 156 | '"<>-*-<>"' 157 | """ 158 | try: 159 | subcls = NOHTML[type(s)] 160 | except KeyError: 161 | raise TypeError('%r does not have one of the required types:' 162 | ' %r' % (s, list(NOHTML))) 163 | return subcls(s) 164 | -------------------------------------------------------------------------------- /utils/graphviz/tools.py: -------------------------------------------------------------------------------- 1 | # tools.py - generic helpers 2 | 3 | import os 4 | 5 | from . import _compat 6 | 7 | __all__ = ['attach', 'mkdirs', 'mapping_items'] 8 | 9 | 10 | def attach(object, name): 11 | """Return a decorator doing ``setattr(object, name)`` with its argument. 12 | 13 | >>> spam = type('Spam', (object,), {})() 14 | >>> @attach(spam, 'eggs') 15 | ... def func(): 16 | ... pass 17 | >>> spam.eggs # doctest: +ELLIPSIS 18 | 19 | """ 20 | def decorator(func): 21 | setattr(object, name, func) 22 | return func 23 | return decorator 24 | 25 | 26 | def mkdirs(filename, mode=0o777): 27 | """Recursively create directories up to the path of ``filename`` as needed.""" 28 | dirname = os.path.dirname(filename) 29 | if not dirname: 30 | return 31 | _compat.makedirs(dirname, mode=mode, exist_ok=True) 32 | 33 | 34 | def mapping_items(mapping): 35 | """Return an iterator over the ``mapping`` items, sort if it's a plain dict. 36 | 37 | >>> list(mapping_items({'spam': 0, 'ham': 1, 'eggs': 2})) 38 | [('eggs', 2), ('ham', 1), ('spam', 0)] 39 | 40 | >>> from collections import OrderedDict 41 | >>> list(mapping_items(OrderedDict(enumerate(['spam', 'ham', 'eggs'])))) 42 | [(0, 'spam'), (1, 'ham'), (2, 'eggs')] 43 | """ 44 | result = _compat.iteritems(mapping) 45 | if type(mapping) is dict: 46 | result = iter(sorted(result)) 47 | return result 48 | -------------------------------------------------------------------------------- /utils/leafblower.py: -------------------------------------------------------------------------------- 1 | from . import utils 2 | 3 | from ghidra.program.flatapi import FlatProgramAPI 4 | from ghidra.program.model.symbol import RefType 5 | from ghidra.program.model.block import BasicBlockModel 6 | 7 | 8 | def get_argument_registers(current_program): 9 | """ 10 | Get argument registers based on processor type. 11 | 12 | :param current_program: Ghidra program object. 13 | :type current_program: ghidra.program.model.listing.Program 14 | 15 | :returns: List of argument registers. 16 | :rtype: list(str) 17 | """ 18 | arch = utils.get_processor(current_program) 19 | 20 | if arch == 'MIPS': 21 | return ['a0', 'a1', 'a2', 'a3'] 22 | elif arch == 'ARM': 23 | return ['r0', 'r1', 'r2', 'r3'] 24 | return [] 25 | 26 | 27 | class Function(object): 28 | CANDIDATES = {} 29 | 30 | def __init__(self, function, candidate_attr, has_loop=None, 31 | argument_count=-1, format_arg_index=-1): 32 | 33 | self.name = function.getName() 34 | self.xref_count = function.getSymbol().getReferenceCount() 35 | self.has_loop = has_loop 36 | self.argument_count = argument_count 37 | self.format_arg_index = format_arg_index 38 | self.candidates = [] 39 | if candidate_attr in self.CANDIDATES: 40 | self.candidates = self.CANDIDATES[candidate_attr] 41 | 42 | 43 | class LeafFunction(Function): 44 | """ 45 | Class to hold leaf function candidates. 46 | """ 47 | 48 | CANDIDATES = { 49 | 1: ['atoi', 'atol', 'strlen'], 50 | 2: ['strcpy', 'strcat', 'strcmp', 'strstr', 'strchr', 'strrchr', 51 | 'bzero'], 52 | 3: ['strtol', 'strncpy', 'strncat', 'strncmp', 'memcpy', 'memmove', 53 | 'bcopy', 'memcmp', 'memset'] 54 | } 55 | 56 | def __init__(self, function, has_loop, argument_count): 57 | super(LeafFunction, self).__init__( 58 | function, argument_count, has_loop, argument_count) 59 | 60 | def to_list(self): 61 | return [self.name, str(self.xref_count), str(self.argument_count), 62 | ','.join(self.candidates)] 63 | 64 | @classmethod 65 | def is_candidate(cls, function, has_loop, argument_count): 66 | """ 67 | Determine is a function is a candidate for a leaf function. Leaf 68 | functions must have loops, make no external calls, require 1-3 69 | arguments, and have a reference count greater than 25. 70 | """ 71 | if not has_loop: 72 | return False 73 | 74 | if argument_count > 3 or argument_count == 0: 75 | return False 76 | 77 | if function.getSymbol().getReferenceCount() < 25: 78 | return False 79 | 80 | return True 81 | 82 | 83 | class FormatFunction(Function): 84 | """ 85 | Class to hold format string function candidates. 86 | """ 87 | 88 | CANDIDATES = { 89 | 0: ['printf'], 90 | 1: ['sprintf', 'fprintf', 'fscanf', 'sscanf'], 91 | 2: ['snprintf'] 92 | } 93 | 94 | def __init__(self, function, format_arg_index): 95 | super(FormatFunction, self).__init__( 96 | function, format_arg_index, format_arg_index=format_arg_index) 97 | 98 | def to_list(self): 99 | return [self.name, str(self.xref_count), str(self.format_arg_index), 100 | ','.join(self.candidates)] 101 | 102 | 103 | class FinderBase(object): 104 | def __init__(self, program): 105 | self._program = program 106 | self._flat_api = FlatProgramAPI(program) 107 | self._monitor = self._flat_api.getMonitor() 108 | self._basic_blocks = BasicBlockModel(self._program) 109 | 110 | def _display(self, title, entries): 111 | """ 112 | Print a simple table to the terminal. 113 | 114 | :param title: Title of the table. 115 | :type title: list 116 | 117 | :param entries: Entries to print in the table. 118 | :type entries: list(list(str)) 119 | """ 120 | lines = [title] + entries 121 | 122 | # Find the largest entry in each column so it can be used later 123 | # for the format string. 124 | max_line_len = [] 125 | for i in range(0, len(title)): 126 | column_lengths = [len(line[i]) for line in lines] 127 | max_line_len.append(max(column_lengths)) 128 | 129 | # Account for largest entry, spaces, and '|' characters on each line. 130 | separator = '=' * (sum(max_line_len) + 131 | (len(title) * (len(title) - 1)) 132 | + 1) 133 | spacer = '|' 134 | format_specifier = '{:<{width}}' 135 | 136 | # First block prints the title and '=' characters to make a title 137 | # border 138 | print separator 139 | print spacer, 140 | for width, column in zip(max_line_len, title): 141 | print format_specifier.format(column, width=width), 142 | print spacer, 143 | print '' 144 | print separator 145 | 146 | # Print the actual entries. 147 | for entry in entries: 148 | print spacer, 149 | for width, column in zip(max_line_len, entry): 150 | print format_specifier.format(column, width=width), 151 | print spacer, 152 | print '' 153 | print separator 154 | 155 | 156 | class LeafFunctionFinder(FinderBase): 157 | """ 158 | Leaf function finder class. 159 | """ 160 | 161 | def __init__(self, program): 162 | super(LeafFunctionFinder, self).__init__(program) 163 | self.leaf_functions = [] 164 | 165 | def find_leaves(self): 166 | """ 167 | Find leaf functions. Leaf functions are functions that have loops, 168 | make no external calls, require 1-3 arguments, and have a reference 169 | count greater than 25. 170 | """ 171 | function_manager = self._program.getFunctionManager() 172 | 173 | for function in function_manager.getFunctions(True): 174 | if not self._function_makes_call(function): 175 | loops = self._function_has_loops(function) 176 | argc = self._get_argument_count(function) 177 | 178 | if LeafFunction.is_candidate(function, loops, argc): 179 | self.leaf_functions.append(LeafFunction(function, 180 | loops, 181 | argc)) 182 | 183 | self.leaf_functions.sort(key=lambda x: x.xref_count, reverse=True) 184 | 185 | def display(self): 186 | """ 187 | Print leaf function candidates to the terminal. 188 | """ 189 | title = ['Function', 'XRefs', 'Args', 'Potential Function'] 190 | leaf_list = [leaf.to_list() for leaf in self.leaf_functions] 191 | 192 | self._display(title, leaf_list) 193 | 194 | def _function_makes_call(self, function): 195 | """ 196 | Determine if a function makes external calls. 197 | 198 | :param function: Function to inspect. 199 | :type function: ghidra.program.model.listing.Function 200 | 201 | :returns: True if the function makes external calls, False otherwise. 202 | :rtype: bool 203 | """ 204 | function_body = function.getBody() 205 | min_addr = function_body.minAddress 206 | max_addr = function_body.maxAddress 207 | 208 | curr_addr = min_addr 209 | while curr_addr <= max_addr: 210 | instruction = self._flat_api.getInstructionAt(curr_addr) 211 | if utils.is_call_instruction(instruction): 212 | return True 213 | curr_addr = curr_addr.next() 214 | return False 215 | 216 | def _function_has_loops(self, function): 217 | """ 218 | Determine if a function has internal loops. 219 | 220 | :param function: Function to inspect. 221 | :type function: ghidra.program.model.listing.Function 222 | 223 | :returns: True if the function has loops, False otherwise. 224 | :rtype: bool 225 | """ 226 | 227 | function_blocks = self._basic_blocks.getCodeBlocksContaining( 228 | function.body, self._monitor) 229 | 230 | while function_blocks.hasNext(): 231 | block = function_blocks.next() 232 | destinations = block.getDestinations(self._monitor) 233 | 234 | # Determine if the current block can result in jumping to a block 235 | # above the end address and in the same function. This indicates 236 | # an internal loop. 237 | while destinations.hasNext(): 238 | destination = destinations.next() 239 | dest_addr = destination.getDestinationAddress() 240 | destination_function = self._flat_api.getFunctionContaining( 241 | dest_addr) 242 | if destination_function == function and \ 243 | dest_addr <= block.minAddress: 244 | return True 245 | return False 246 | 247 | def _get_argument_count(self, function): 248 | """ 249 | Determine the argument count to the function. This is determined by 250 | inspecting argument registers to see if they are read from prior to 251 | being written to. 252 | 253 | :param function: Function to inspect. 254 | :type function: ghidra.program.model.listing.Function 255 | 256 | :returns: Argument count. 257 | :rtype: int 258 | """ 259 | used_args = [] 260 | arch_args = get_argument_registers(self._program) 261 | 262 | min_addr = function.body.minAddress 263 | max_addr = function.body.maxAddress 264 | 265 | curr_ins = self._flat_api.getInstructionAt(min_addr) 266 | 267 | while curr_ins and curr_ins.getAddress() < max_addr: 268 | for op_index in range(0, curr_ins.getNumOperands()): 269 | ref_type = curr_ins.getOperandRefType(op_index) 270 | # We only care about looking at reads and writes. Reads that 271 | # include and index into a register show as 'data' so look 272 | # for those as well. 273 | if ref_type not in [RefType.WRITE, RefType.READ, RefType.DATA]: 274 | continue 275 | 276 | # Check to see if the argument is an argument register. Remove 277 | # that register from the arch_args list so it can be ignored 278 | # from now on. If reading from the register add it to the 279 | # used_args list so we know its a used parameter. 280 | operands = curr_ins.getOpObjects(op_index) 281 | for operand in operands: 282 | op_string = operand.toString() 283 | if op_string in arch_args: 284 | arch_args.remove(op_string) 285 | if ref_type in [RefType.READ, RefType.DATA]: 286 | used_args.append(op_string) 287 | curr_ins = curr_ins.next 288 | 289 | return len(used_args) 290 | 291 | 292 | class FormatStringFunctionFinder(FinderBase): 293 | def __init__(self, program): 294 | super(FormatStringFunctionFinder, self).__init__(program) 295 | self._memory_map = self._program.getMemory() 296 | self.format_strings = self._find_format_strings() 297 | self.format_functions = [] 298 | 299 | def _find_format_strings(self): 300 | """ 301 | Find strings that contain format parameters. 302 | 303 | :returns: List of addresses that represent format string parameters. 304 | :rtype: list(ghidra.program.model.listing.Address 305 | """ 306 | format_strings = [] 307 | memory = self._memory_map.getAllInitializedAddressSet() 308 | strings = self._flat_api.findStrings(memory, 2, 1, True, True) 309 | 310 | for string in strings: 311 | curr_string = string.getString(self._memory_map) 312 | if '%' in curr_string: 313 | format_strings.append(string.getAddress()) 314 | return format_strings 315 | 316 | def _find_function_by_instruction(self, instruction): 317 | """ 318 | Find function associated with the instruction provided. Used to find 319 | function calls associated with parameters. Only searches in the 320 | code block of the provided instruction. 321 | 322 | :param instruction: Instruction to begin search from. 323 | :type instruction: ghidra.program.model.listing.Instruction 324 | 325 | :returns: Function if found, None otherwise. 326 | :rtype: ghidra.program.model.listing.Function 327 | """ 328 | containing_block = self._basic_blocks.getCodeBlocksContaining( 329 | instruction.getAddress(), self._monitor)[0] 330 | 331 | # Check if the current instruction is in the delay slot, back up one 332 | # instruction if true in case its a call. 333 | if instruction.isInDelaySlot(): 334 | curr_ins = instruction.previous 335 | else: 336 | curr_ins = instruction 337 | 338 | while curr_ins and curr_ins.getAddress() < containing_block.maxAddress: 339 | if utils.is_call_instruction(curr_ins): 340 | function_flow = curr_ins.getFlows() 341 | if function_flow: 342 | # The call instruction should only have one flow so 343 | # grabbing the first flow is fine. 344 | return self._flat_api.getFunctionAt(function_flow[0]) 345 | curr_ins = curr_ins.next 346 | 347 | return None 348 | 349 | def display(self): 350 | """ 351 | Print format function candidates to the terminal. 352 | """ 353 | title = ['Function', 'XRefs', 'Fmt Index', 'Potential Function'] 354 | format_list = [format.to_list() for format in self.format_functions] 355 | 356 | self._display(title, format_list) 357 | 358 | def find_functions(self): 359 | """ 360 | Find functions that take format strings as an argument. 361 | """ 362 | processed_functions = [] 363 | registers = get_argument_registers(self._program) 364 | 365 | for string in self.format_strings: 366 | references = self._flat_api.getReferencesTo(string) 367 | for reference in references: 368 | if not reference.getReferenceType() == RefType.PARAM: 369 | continue 370 | 371 | # Get the instruction that references the format string. 372 | instruction = self._flat_api.getInstructionAt( 373 | reference.fromAddress) 374 | 375 | function = self._find_function_by_instruction(instruction) 376 | if not function: 377 | continue 378 | 379 | function_name = function.getName() 380 | 381 | if function_name in processed_functions: 382 | continue 383 | 384 | register_used = instruction.getOpObjects( 385 | reference.getOperandIndex()) 386 | register_index = registers.index(register_used[0].toString()) 387 | 388 | self.format_functions.append(FormatFunction(function, 389 | register_index)) 390 | 391 | processed_functions.append(function_name) 392 | 393 | # Sort the functions by cross reference count 394 | self.format_functions.sort(key=lambda fmt_fn: fmt_fn.xref_count, 395 | reverse=True) 396 | -------------------------------------------------------------------------------- /utils/mipsropchain.py: -------------------------------------------------------------------------------- 1 | from . import mipsrop, utils 2 | import time 3 | 4 | REGISTERS = ['s0', 's1', 's2', 's3', 's4', 's5', 's6', 's7', 's8'] 5 | 6 | 7 | def update_available_registers(registers, gadget): 8 | """ 9 | Update available registers list based on what gadget does. 10 | 11 | :param registers: List of currently controlled registers. 12 | :type registers: list(str) 13 | 14 | :param gadget: Current gadget. 15 | :type gadget: GadgetChain 16 | 17 | :returns: New list of available registers. 18 | :rtype: list(str) 19 | """ 20 | new_registers = registers[:] 21 | 22 | try: 23 | for jump in gadget.jump_register: 24 | if 'sp' not in jump: 25 | new_registers.remove(jump) 26 | except ValueError: 27 | return [] 28 | 29 | for reg in gadget.overwritten: 30 | try: 31 | new_registers.remove(reg) 32 | except ValueError: 33 | pass 34 | 35 | for reg in gadget.control_gained: 36 | if reg not in registers: 37 | new_registers.append(reg) 38 | return new_registers 39 | 40 | 41 | def get_chain_length(chain): 42 | """ 43 | Get instruction length of a chain. 44 | 45 | :param chain: Gadget chain. 46 | :type chain: list(GadgetLinks) 47 | """ 48 | return sum(map(len, chain)) 49 | 50 | 51 | def default_gadget_search(link, controlled_registers, current_chain): 52 | """ 53 | Default search for finding gadgets in a chain. Chooses based on jump 54 | being controlled by registers that are controlled. 55 | """ 56 | for jump in link.jump_register: 57 | if 'sp' in jump: 58 | continue 59 | 60 | if jump not in controlled_registers: 61 | return False 62 | return True 63 | 64 | 65 | class ChainLink(object): 66 | def __init__(self, name, gadget, control_gadget=None): 67 | self.name = name 68 | self._gadget = gadget 69 | self._control_gadget = control_gadget 70 | self.chain = [] 71 | self.control_gained = {} 72 | self.overwritten = {} 73 | self.jump_register = {} 74 | self.action_register = {} 75 | 76 | # Double jumps need to be handled different from single jump gadgets. 77 | if isinstance(gadget, mipsrop.DoubleGadget): 78 | # Ignoring control gadgets for double jumps. Makes everything 79 | # more complicated. Might come back to this. 80 | if control_gadget: 81 | pass 82 | else: 83 | jump_one = gadget.first.get_source_register() 84 | jump_two = gadget.second.get_source_register() 85 | action_one = gadget.first.get_source_register() 86 | action_two = gadget.second.get_source_register() 87 | 88 | self.jump_register[jump_one] = \ 89 | gadget.first.call.getAddress() 90 | self.jump_register[jump_two] = \ 91 | gadget.second.call.getAddress() 92 | self.action_register[action_one] = \ 93 | gadget.first.control_instruction.getAddress() 94 | self.action_register[action_two] = \ 95 | gadget.second.control_instruction.getAddress() 96 | else: 97 | if control_gadget: 98 | control_reg = control_gadget.get_action_source_register()[0] 99 | action_reg = gadget.get_action_destination_register()[0] 100 | control_jump = control_gadget.get_action_source_register()[0] 101 | gadget_jump = control_gadget.jump.get_control_item() 102 | 103 | self.action_register[control_reg] = \ 104 | control_gadget.action.getAddress() 105 | 106 | self.action_register[action_reg] = gadget.action.getAddress() 107 | 108 | self.jump_register[control_jump] = \ 109 | control_gadget.action.getAddress() 110 | 111 | self.jump_register[gadget_jump] = \ 112 | control_gadget.jump.control_instruction.getAddress() 113 | 114 | self.chain.append(control_gadget) 115 | else: 116 | gadget_jump = gadget.jump.get_control_item() 117 | action = gadget.get_action_source_register()[0] 118 | self.jump_register[gadget_jump] = \ 119 | gadget.jump.control_instruction.getAddress() 120 | self.action_register[action] = gadget.action.getAddress() 121 | 122 | self.chain.append(gadget) 123 | self._get_registers_overwritten(gadget) 124 | self._validate_jumps() 125 | 126 | def __len__(self): 127 | length = len(self._gadget) 128 | if self._control_gadget: 129 | length += len(self._control_gadget) 130 | return length 131 | 132 | def __eq__(self, gadget): 133 | if type(gadget._gadget) is not type(self._gadget): 134 | return False 135 | elif isinstance(self._gadget, mipsrop.DoubleGadget): 136 | pass 137 | else: 138 | return self._gadget.action.getAddress() == gadget._gadget.action.getAddress() 139 | 140 | def _validate_jumps(self): 141 | """ 142 | Validate the gadget doesn't overwrite registers it needs to jump. 143 | 144 | :raises: ValueError if gadget is invalid. 145 | """ 146 | for register in self.overwritten.keys(): 147 | if register in self.jump_register.keys(): 148 | for overwritten in self.overwritten[register]: 149 | if self.jump_register > overwritten: 150 | raise ValueError 151 | 152 | # Make sure the action isn't overwritten in the gadget. 153 | action_dest = self.get_action_destination() 154 | if register in action_dest: 155 | for overwritten in self.overwritten[register]: 156 | action_addr = self.action_register.values() 157 | for addr in action_addr: 158 | if addr != overwritten: 159 | raise ValueError 160 | 161 | def _get_registers_overwritten(self, gadget): 162 | """ 163 | Record registers that are overwritten and which ones are controlled. 164 | """ 165 | instructions = gadget.get_instructions() 166 | for instruction in instructions: 167 | reg = mipsrop.get_overwritten_register(instruction) 168 | if reg and reg in REGISTERS + ['a0', 'a1', 'a2', 'a3', 'sp', 'v0', 'v1']: 169 | address = instruction.getAddress() 170 | if reg not in self.overwritten: 171 | self.overwritten[reg] = [address] 172 | else: 173 | self.overwritten[reg].append(address) 174 | if reg in self.control_gained: 175 | self.control_gained.pop(reg) 176 | if 'lw' in str(instruction) and 'sp' in str(instruction): 177 | self.control_gained[reg] = instruction.getAddress() 178 | 179 | def get_action_destination(self): 180 | """ 181 | Return the gadgets action destination register. Uses the gadget and not 182 | the control gadget because the whole purpose of the control gadget is 183 | calling the actual gadget. 184 | 185 | :returns: Action destination register. 186 | """ 187 | return self._gadget.get_action_destination_register() 188 | 189 | def get_action_source(self): 190 | """ 191 | Get the action source register. 192 | """ 193 | if self._control_gadget: 194 | return self._control_gadget.get_action_source_register() 195 | return self._gadget.get_action_source_register() 196 | 197 | def print_gadget(self, extended=False): 198 | """ 199 | Print the gadget 200 | """ 201 | title = self.name 202 | if self._control_gadget: 203 | title += ' (Control Gadget Required)' 204 | print title 205 | print '-' * len(title) 206 | print 'Control Gadget:' 207 | self._control_gadget.print_instructions() 208 | print '\n' 209 | print 'Gadget:' 210 | else: 211 | print title 212 | print '-' * len(title) 213 | 214 | self._gadget.print_instructions() 215 | if extended: 216 | print '\nControl Gained\n-------------' 217 | print self.control_gained 218 | print '\nOvewritten Registers\n-----------' 219 | print self.overwritten 220 | print '\nJump Register\n-------------' 221 | print self.jump_register 222 | print '\nAction Source\n-------------' 223 | print self.action_register 224 | try: 225 | print '\nAction Destination\n-------------' 226 | print self.get_action_destination() 227 | except: 228 | pass 229 | print '\n' 230 | 231 | 232 | class GadgetLinks(object): 233 | def __init__(self, name, rop_finder, gadgets, check_control=True, 234 | find_fn=default_gadget_search): 235 | self.name = name 236 | self._rop_finder = rop_finder 237 | self._links = gadgets 238 | self._find_fn = find_fn 239 | self.chains = [] 240 | 241 | for gadget in self._links: 242 | try: 243 | gadget_chain = ChainLink(name, gadget) 244 | except ValueError: 245 | continue 246 | 247 | jump_reg = gadget_chain.jump_register.keys()[0] 248 | if not isinstance(gadget, mipsrop.DoubleGadget) and \ 249 | check_control and jump_reg not in REGISTERS and \ 250 | 'sp' not in jump_reg: 251 | control_links = self._find_control_jump(jump_reg[0]) 252 | for control in control_links: 253 | try: 254 | gadget_chain = ChainLink(name, gadget, control) 255 | self.chains.append(gadget_chain) 256 | except ValueError: 257 | continue 258 | else: 259 | self.chains.append(gadget_chain) 260 | 261 | def _find_control_jump(self, jump_register): 262 | """ 263 | Find gadget that, when called, grants control of the current gadget. 264 | 265 | :param jump_register: Register control required to use this gadget. 266 | :type jump_register: str 267 | 268 | :returns: Gadgets that grant control of the jump register 269 | """ 270 | ins = mipsrop.MipsInstruction('.*mov', jump_register, '.*s') 271 | control = self._rop_finder.find_instructions([ins]) 272 | return control.gadgets 273 | 274 | def find_gadget(self, controlled_registers, current_chain): 275 | """ 276 | Find usable gadgets based on defined find function. 277 | 278 | :param controlled_registers: List of currently controlled registers. 279 | :type controlled_registers: list(str) 280 | 281 | :returns: List of gadgets that can be used based on the controlled 282 | registers. 283 | """ 284 | gadgets = [] 285 | for gadget in self.chains: 286 | if self._find_fn(gadget, controlled_registers, current_chain): 287 | gadgets.append(gadget) 288 | return gadgets 289 | 290 | 291 | class ChainBuilder(object): 292 | def __init__(self, rop_finder, registers_controlled, chain_limit, 293 | allow_reuse, verbose): 294 | self._rop_finder = rop_finder 295 | self._registers = registers_controlled 296 | self._allow_reuse = allow_reuse 297 | self._verbose = verbose 298 | self.gadgets = [] 299 | self.chains = [] 300 | self.chain_len = 0 301 | self.chain_limit = chain_limit 302 | self.max_chain = 0 303 | 304 | def add_gadgets(self, name, gadget, check_control=True, 305 | find_fn=default_gadget_search, index=None): 306 | """ 307 | Add new gadget to the chain builder. 308 | 309 | :param name: Name of the gadget. Only used for printing purposes. 310 | :type name: str 311 | 312 | :param gadget: List of available gadgets. 313 | :type gadget: list(mipsrop.RopGadget or mipsrop.DoubleGadget) 314 | 315 | :param check_control: If the gadget jump is not controllable by a saved 316 | register then search for a gadget to gain control. 317 | :type check_control: bool 318 | 319 | :param find_fn: Custom find function to use in place of searching by 320 | currently controlled registers. Function must have 321 | prototype 322 | `def function_name(link, controlled_regs, current_chain)` 323 | and return True is the gadget is usable, False if not. 324 | See MipsRopSystemChain for example use. 325 | :type find_fn: function 326 | 327 | :param index: Index to insert gadget. Index dictates it position in the 328 | generated chain. 329 | :type index: int 330 | """ 331 | gadget_links = GadgetLinks( 332 | name, self._rop_finder, gadget, check_control, find_fn) 333 | if index is not None: 334 | self.gadgets.insert(index, gadget_links) 335 | else: 336 | self.gadgets.append(gadget_links) 337 | 338 | def replace_gadgets(self, name, gadget, index, check_control=True, 339 | find_fn=default_gadget_search): 340 | """ 341 | Replace a previously added gadget in the chain builder. 342 | 343 | :param name: Name of the gadget. Only used for printing purposes. 344 | :type name: str 345 | 346 | :param gadget: List of available gadgets. 347 | :type gadget: list(mipsrop.RopGadget or mipsrop.DoubleGadget) 348 | 349 | :param check_control: If the gadget jump is not controllable by a saved 350 | register then search for a gadget to gain control. 351 | :type check_control: bool 352 | 353 | :param find_fn: Custom find function to use in place of searching by 354 | currently controlled registers. Function must have 355 | prototype 356 | `def function_name(link, controlled_regs, current_chain)` 357 | and return True is the gadget is usable, False if not. 358 | See MipsRopSystemChain for example use. 359 | :type find_fn: function 360 | 361 | :param index: Index to insert gadget. Index dictates it position in the 362 | generated chain. 363 | :type index: int 364 | """ 365 | gadget_links = GadgetLinks( 366 | name, self._rop_finder, gadget, check_control, find_fn) 367 | self.gadgets[index] = gadget_links 368 | 369 | def generate_chain(self): 370 | """ 371 | Generate a ROP chain based on the provided gadgets. 372 | """ 373 | self._process_links( 374 | self.gadgets[0], self._registers, self.gadgets[1:], []) 375 | if not self.chains and self._verbose: 376 | print 'ERROR: Looks like no chains were found. Failed to find ' + \ 377 | 'working gadget for "%s"' % self.gadgets[self.max_chain].name 378 | 379 | def display_chains(self, verbose): 380 | """ 381 | Pretty print the discovered chains. 382 | """ 383 | for i in range(len(self.chains)): 384 | title = 'Chain %d of %d (%d instructions)' % \ 385 | (i + 1, len(self.chains), get_chain_length(self.chains[i])) 386 | print '\n' 387 | print '*' * len(title) 388 | print title 389 | print '*' * len(title) 390 | for gadget in self.chains[i]: 391 | gadget.print_gadget(verbose) 392 | 393 | def _add_new_chain(self, new_chain): 394 | """ 395 | Add a new chain to the saved list and drop longer chains that are above 396 | the search limit. 397 | 398 | :param new_chain: Chain to add to the list. 399 | :type new_chain: list(GadgetChain) 400 | """ 401 | chain_length = get_chain_length(new_chain) 402 | if self._verbose: 403 | print 'Found new chain of length %d' % chain_length 404 | self.chains.append(new_chain) 405 | self.chains.sort(key=lambda chain: get_chain_length(chain)) 406 | self.chains = self.chains[:self.chain_limit] 407 | 408 | def _above_max_len(self, length): 409 | """ 410 | Check if length is above the longest chain saved. Automatically passes 411 | if the chain limit has not been reached. 412 | 413 | :return: True if above the limit, else False. 414 | :rtype: bool 415 | """ 416 | if len(self.chains) < self.chain_limit: 417 | return False 418 | if length < get_chain_length(self.chains[-1]): 419 | return False 420 | return True 421 | 422 | def _gadget_is_used(self, gadget): 423 | """ 424 | Check if gadget is used in a saved chain. 425 | 426 | :param gadget: Gadget to search for. 427 | :type gadget: GadgetChain 428 | 429 | :returns: True if gadget is used in a saved chain, False otherwise. 430 | :rtype: bool 431 | """ 432 | for chain in self.chains: 433 | for used_gadget in chain: 434 | if gadget == used_gadget: 435 | return True 436 | return False 437 | 438 | def _process_links(self, links, registers, remaining_links, chain): 439 | """ 440 | Recursively search through gadgets to build a chain. 441 | 442 | :param links: Current links to search for next gadget. 443 | :type links: GadgetLinks 444 | 445 | :param registers: Currently controlled registers. 446 | :type registers: list(str) 447 | 448 | :param remaining_links: Links left to use. 449 | :type remaining_links: list(GadgetLinks) 450 | 451 | :param chain: Current chain. 452 | :type chain: list(GadgetLink) 453 | """ 454 | if self._above_max_len(get_chain_length(chain)): 455 | return 456 | 457 | self.max_chain = max([self.max_chain, len(chain)]) 458 | 459 | available_links = links.find_gadget(registers, chain) 460 | 461 | current_chain = chain[:] 462 | for gadget in available_links: 463 | # Not a perfect solution, but provides a little variety to chains 464 | # if multiple are requested. Might not find the shortest because of 465 | # that. 466 | if not self._allow_reuse and self._gadget_is_used(gadget): 467 | continue 468 | 469 | current_chain.append(gadget) 470 | if remaining_links: 471 | register_control = update_available_registers(registers, 472 | gadget) 473 | self._process_links( 474 | remaining_links[0], register_control, remaining_links[1:], 475 | current_chain) 476 | else: # No remaining links mean the chain is complete. 477 | self._add_new_chain(current_chain[:]) 478 | if not self._allow_reuse: 479 | return 480 | current_chain.pop() 481 | -------------------------------------------------------------------------------- /utils/utils.py: -------------------------------------------------------------------------------- 1 | def get_instruction_list(code_manager, function): 2 | """ 3 | Get list of instructions in the function. 4 | 5 | :param function: Function to parse for instruction list. 6 | 7 | :returns: List of instructions. 8 | """ 9 | if function is None: 10 | return [] 11 | function_bounds = function.getBody() 12 | function_instructions = code_manager.getInstructions(function_bounds, True) 13 | return function_instructions 14 | 15 | 16 | def get_function(function_manager, address): 17 | """ 18 | Return the function that contains the address. 19 | 20 | :param address: Address within function. 21 | 22 | :returns: Function that contains the provided address. 23 | """ 24 | return function_manager.getFunctionContaining(address) 25 | 26 | 27 | def is_call_instruction(instruction): 28 | """ 29 | Determine if an instruction calls a function. 30 | 31 | :param instruction: Instruction to inspect. 32 | :type instruction: ghidra.program.model.listing.Instruction 33 | 34 | :returns: True if the instruction is a call, false otherwise. 35 | :rtype: bool 36 | """ 37 | if not instruction: 38 | return False 39 | 40 | flow_type = instruction.getFlowType() 41 | return flow_type.isCall() 42 | 43 | 44 | def is_jump_instruction(instruction): 45 | """ 46 | Determine if instruction is a jump. 47 | 48 | :param instruction: Instruction to inspect. 49 | :type instruction: ghidra.program.model.listing.Instruction 50 | 51 | :returns: True if the instruction is a jump, false otherwise. 52 | :rtype: bool 53 | """ 54 | if not instruction: 55 | return False 56 | 57 | flow_type = instruction.getFlowType() 58 | return flow_type.isJump() 59 | 60 | 61 | def get_processor(current_program): 62 | """ 63 | Get string representing the current programs processor. 64 | 65 | :param current_program: Current program loaded in Ghidra. 66 | :type current_program: ghidra.program.model.listing.Program. 67 | """ 68 | language = current_program.getLanguage() 69 | return language.getProcessor().toString() 70 | 71 | 72 | def find_function(current_program, function_name): 73 | """ 74 | Find a function, by name, in the current program. 75 | 76 | :param current_program: Current program loaded in Ghidra. 77 | :type current_program: ghidra.program.model.listing.Program 78 | 79 | :param function_name: Function to search for. 80 | :type function_name: str 81 | """ 82 | listing = current_program.getListing() 83 | if listing: 84 | return listing.getGlobalFunctions(function_name) 85 | return [] 86 | 87 | 88 | def address_to_int(address): 89 | """ 90 | Convert Ghidra address to integer. 91 | 92 | :param address: Address to convert to integer. 93 | :type address: ghidra.program.model.address.Address 94 | 95 | :returns: Integer representation of the address. 96 | :rtype: int 97 | """ 98 | return int(address.toString(), 16) 99 | 100 | 101 | def allowed_processors(current_program, processor_list): 102 | """ 103 | Function to prevent scripts from running against unsupported processors. 104 | 105 | :param current_program: Current program loaded in Ghidra. 106 | :type current_program: ghidra.program.model.listing.Program 107 | 108 | :param processor_list: List of supported processors. 109 | :type processor_list: list(str) 110 | """ 111 | curr_processor = get_processor(current_program) 112 | 113 | if curr_processor not in processor_list: 114 | print '%s is not a valid processor for this script. Supported ' \ 115 | 'processors are: %s' % (curr_processor, processor_list) 116 | exit(1) 117 | 118 | 119 | def table_pretty_print(title, entries): 120 | """ 121 | Print a simple table to the terminal. 122 | 123 | :param title: Title of the table. 124 | :type title: list 125 | 126 | :param entries: Entries to print in the table. 127 | :type entries: list(list(str)) 128 | """ 129 | # Pad entries to be the same length 130 | entries = [entry + ([''] * (len(title) - len(entry))) for entry in entries] 131 | lines = [title] + entries 132 | 133 | # Find the largest entry in each column so it can be used later 134 | # for the format string. Drop title entries if an entire column is empty. 135 | max_line_len = [] 136 | for i in range(0, len(title)): 137 | column_lengths = [len(line[i]) for line in lines] 138 | if sum(column_lengths[1:]) == 0: 139 | title = title[:i] 140 | break 141 | max_line_len.append(max(column_lengths)) 142 | 143 | # Account for largest entry, spaces, and '|' characters on each line. 144 | separator = '=' * (sum(max_line_len) + (len(title) * 3) + 1) 145 | spacer = '|' 146 | format_specifier = '{:<{width}}' 147 | 148 | # First block prints the title and '=' characters to make a title 149 | # border 150 | print separator 151 | print spacer, 152 | for width, column in zip(max_line_len, title): 153 | print format_specifier.format(column, width=width), 154 | print spacer, 155 | print '' 156 | print separator 157 | 158 | # Print the actual entries. 159 | for entry in entries: 160 | print spacer, 161 | for width, column in zip(max_line_len, entry): 162 | print format_specifier.format(column, width=width), 163 | print spacer, 164 | print '' 165 | print separator 166 | --------------------------------------------------------------------------------