├── .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 | 
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 | 
20 |
21 | ### After
22 | Disassembly after the conversion.
23 |
24 | 
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 | 
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 | 
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 | 
45 |
46 | ## Register Control
47 | Find ARM ROP gadgets that give control of registers by popping them off the stack.
48 |
49 | 
50 |
51 | ## Register Move
52 | Find ARM ROP gadgets that move values between registers.
53 |
54 | 
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 | 
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 | 
67 |
68 | 
69 |
70 | ## System
71 | Find ARM ROP gadgets for calling system with a user controlled argument.
72 |
73 | 
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 | 
6 |
7 | 
--------------------------------------------------------------------------------
/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 | 
10 |
11 | ### After
12 |
13 | 
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 | 
25 |
26 | **Cross Reference**
27 |
28 | 
29 |
30 | ### After
31 |
32 | **Data Section**
33 |
34 | 
35 |
36 | **Cross Reference**
37 |
38 | 
39 |
--------------------------------------------------------------------------------
/readmes/fluorescence.md:
--------------------------------------------------------------------------------
1 | # Fluorescence
2 | Highlight or un-highlight all function calls in the current binary.
3 |
4 | 
--------------------------------------------------------------------------------
/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 | 
--------------------------------------------------------------------------------
/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 | 
8 |
9 |
10 | ## Identify Format Parameter Functions
11 | Identify funtions that accept format parameters to identify sprintf, printf, fscanf, etc.
12 |
13 |
14 | 
--------------------------------------------------------------------------------
/readmes/local_cross_ref.md:
--------------------------------------------------------------------------------
1 | # Local Cross References
2 | Find references to the selected item in the current function.
3 |
4 | 
--------------------------------------------------------------------------------
/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 | 
8 |
9 | ## Epilogue
10 | Find gadgets that give control of saved registers.
11 |
12 | 
13 |
14 | 
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 | 
22 |
23 | 
24 |
25 | ## Indirect Return
26 | Find indirect return gadgets. Call t9 and then return to ra.
27 |
28 | 
29 |
30 | ## Li a0
31 | Find gadgets that load a small value into a0. Useful for calling sleep.
32 |
33 | 
34 |
35 | ## Prologue
36 | Find controllable gadgets at the beginning of functions that provide stack pointer movement.
37 |
38 | 
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 | 
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 | 
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 | 
70 |
71 |
72 | ## Stack Finder
73 | Find gadgets that place a stack address in a register.
74 |
75 | 
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 | 
83 |
84 | 
85 |
86 | ## System Gadgets
87 | Find gadgets suitable for calling system with user controlled arguments.
88 |
89 | 
--------------------------------------------------------------------------------
/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 | 
--------------------------------------------------------------------------------
/readmes/rename_variables.md:
--------------------------------------------------------------------------------
1 | # Rename Variables
2 | Rename saved stack variables for easier tracking. Only valid in MIPS.
3 |
4 | 
--------------------------------------------------------------------------------
/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 | 
24 |
25 | ## Save
26 | Save Rizzo signatures from the current project.
27 |
28 | 
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 | 
42 |
43 | ## Save
44 | Save Rizzo signatures from the current project.
45 |
46 | 
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 |
--------------------------------------------------------------------------------