├── .gitignore ├── DebugAdapter.py ├── LICENSE ├── ProcessView.py ├── QueuedAdapter.py ├── README.md ├── __init__.py ├── binjaplug.py ├── cli.py ├── dbgeng.py ├── dbgengadapt ├── Makefile ├── dbgengadapt.cpp └── dbgengadapt.dll ├── dockwidgets ├── AdapterSettingsDialog.py ├── BreakpointsWidget.py ├── ConsoleWidget.py ├── ControlsWidget.py ├── DebugView.py ├── ModulesWidget.py ├── RegistersWidget.py ├── StackWidget.py ├── ThreadsWidget.py └── widget.py ├── documentation ├── Manual.html ├── Manual.md └── resources │ ├── adapter_class_diagram.graphml │ ├── adapter_class_diagram.png │ ├── backend_arch.graphml │ ├── backend_arch.png │ ├── gui1.png │ ├── gui2.png │ ├── gui3.png │ └── gui4.png ├── examples ├── dacman_live_hilbert.py └── md5_opc_freq.py ├── gdb.py ├── gdblike.py ├── lldb.py ├── mame_coleco.py ├── media ├── cli.png ├── gui.png └── icons │ ├── add.svg │ ├── cancel.svg │ ├── connect.svg │ ├── disconnect.svg │ ├── pause.svg │ ├── remove.svg │ ├── restart.svg │ ├── resume.svg │ ├── run.svg │ ├── stepinto.svg │ ├── stepout.svg │ ├── stepover.svg │ └── stop.svg ├── plugin.json ├── requirements.txt ├── rsp.py ├── test.py ├── testbins ├── Makefile-android ├── Makefile-linux ├── Makefile-macos ├── Makefile-win32 ├── Makefile-win64 ├── analysis_propagation_x64.asm ├── asmtest_aarch64-android.s ├── asmtest_armv7-android.s ├── asmtest_x64.asm ├── asmtest_x86.asm ├── cat.c ├── clear.py ├── commandline_test.c ├── do_exception.c ├── exitcode.c ├── hello_x64.asm ├── helloworld.c ├── helloworld_func.c ├── helloworld_loop.c ├── helloworld_objc.m ├── helloworld_recursion.c ├── helloworld_thread.c ├── helloworld_virtual.cpp ├── hh1.asm ├── indirect_calls_x64.asm ├── many_stdlib_calls.c ├── md5 │ ├── Makefile-linux │ ├── Makefile-macos │ ├── global.h │ ├── md5.h │ ├── md5c.c │ └── md5driver.c ├── missing_switch_case_x64.asm ├── nopspeed.c ├── rm_func_starts.py ├── undiscovered_func2_x64.asm └── undiscovered_func_x64.asm ├── titan_engine ├── Makefile-scyllawrapper ├── Makefile-titanengine └── README.md ├── tools ├── sniffrsp.py └── xmltest.py ├── ui.py └── utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | __pycache__/ 3 | *.idea 4 | 5 | -------------------------------------------------------------------------------- /DebugAdapter.py: -------------------------------------------------------------------------------- 1 | import os 2 | import platform 3 | from enum import Enum, auto, unique 4 | 5 | class GeneralError(Exception): 6 | pass 7 | class BreakpointClearError(Exception): 8 | pass 9 | class BreakpointSetError(Exception): 10 | pass 11 | class NotInstalledError(Exception): 12 | pass 13 | class NotExecutableError(Exception): 14 | pass 15 | class PermissionDeniedError(Exception): 16 | pass 17 | class ProcessStartError(Exception): 18 | pass 19 | 20 | @unique 21 | class ADAPTER_TYPE(Enum): 22 | DEFAULT = 0 23 | LOCAL_DBGENG = auto() 24 | LOCAL_GDB = auto() 25 | LOCAL_LLDB = auto() 26 | REMOTE_GDB = auto() 27 | REMOTE_LLDB = auto() 28 | REMOTE_SENSE = auto() 29 | 30 | @staticmethod 31 | def use_exec(adapter_type): 32 | return adapter_type in [ 33 | ADAPTER_TYPE.DEFAULT, 34 | ADAPTER_TYPE.LOCAL_DBGENG, 35 | ADAPTER_TYPE.LOCAL_GDB, 36 | ADAPTER_TYPE.LOCAL_LLDB 37 | ] 38 | 39 | @staticmethod 40 | def use_connect(adapter_type): 41 | return adapter_type in [ 42 | ADAPTER_TYPE.REMOTE_GDB, 43 | ADAPTER_TYPE.REMOTE_LLDB, 44 | ADAPTER_TYPE.REMOTE_SENSE 45 | ] 46 | 47 | @staticmethod 48 | def can_use(adapter_type): 49 | system = platform.system() 50 | 51 | if system == 'Windows': 52 | return adapter_type in [ 53 | ADAPTER_TYPE.DEFAULT, 54 | ADAPTER_TYPE.LOCAL_DBGENG, 55 | ADAPTER_TYPE.REMOTE_GDB, 56 | ADAPTER_TYPE.REMOTE_LLDB, 57 | ADAPTER_TYPE.REMOTE_SENSE 58 | ] 59 | elif system == 'Linux': 60 | return adapter_type in [ 61 | ADAPTER_TYPE.DEFAULT, 62 | ADAPTER_TYPE.LOCAL_GDB, 63 | ADAPTER_TYPE.REMOTE_GDB, 64 | ADAPTER_TYPE.REMOTE_LLDB, 65 | ADAPTER_TYPE.REMOTE_SENSE 66 | ] 67 | elif system == 'Darwin': 68 | return adapter_type in [ 69 | ADAPTER_TYPE.DEFAULT, 70 | ADAPTER_TYPE.LOCAL_LLDB, 71 | ADAPTER_TYPE.REMOTE_GDB, 72 | ADAPTER_TYPE.REMOTE_LLDB, 73 | ADAPTER_TYPE.REMOTE_SENSE 74 | ] 75 | else: 76 | return False 77 | 78 | def get_adapter_for_current_system(**kwargs): 79 | from . import dbgeng, gdb, lldb 80 | 81 | system = platform.system() 82 | 83 | if system == 'Windows': 84 | return dbgeng.DebugAdapterDbgeng(**kwargs) 85 | elif system == 'Linux': 86 | return gdb.DebugAdapterGdb(**kwargs) 87 | elif system == 'Darwin': 88 | return lldb.DebugAdapterLLDB(**kwargs) 89 | else: 90 | raise Exception('unsupported system: %s' % system) 91 | 92 | def get_new_adapter(adapter_type = ADAPTER_TYPE.DEFAULT, **kwargs): 93 | from . import dbgeng, gdblike, gdb, lldb 94 | 95 | if adapter_type == ADAPTER_TYPE.LOCAL_DBGENG: 96 | return dbgeng.DebugAdapterDbgeng(**kwargs) 97 | elif adapter_type == ADAPTER_TYPE.LOCAL_GDB or adapter_type == ADAPTER_TYPE.REMOTE_GDB: 98 | return gdb.DebugAdapterGdb(**kwargs) 99 | elif adapter_type == ADAPTER_TYPE.LOCAL_LLDB or adapter_type == ADAPTER_TYPE.REMOTE_LLDB: 100 | return lldb.DebugAdapterLLDB(**kwargs) 101 | elif adapter_type == ADAPTER_TYPE.REMOTE_SENSE: 102 | return gdblike.DebugAdapterGdbLike(**kwargs) 103 | elif adapter_type == ADAPTER_TYPE.DEFAULT: 104 | return get_adapter_for_current_system(**kwargs) 105 | else: 106 | raise Exception('unsupported adapter type: %s' % adapter_type) 107 | 108 | def new_terminal(cmdline): 109 | cmd = '' 110 | plat_sys = platform.system() 111 | if plat_sys == 'Darwin': 112 | cmd += 'osascript -e \'tell app "Terminal" to do script "%s"\'' % cmdline 113 | cmd += ' -e \'activate application "Terminal"\'' 114 | os.system(cmd) 115 | elif plat_sys == 'Linux': 116 | #cmd += 'exo-open --launch TerminalEmulator \'%s\'' % cmdline 117 | cmd += 'x-terminal-emulator -e %s' % cmdline 118 | os.system(cmd) 119 | else: 120 | raise Exception('unable to start new terminal window in system: %s' % plat_sys) 121 | 122 | @unique 123 | class STOP_REASON(Enum): 124 | UNKNOWN = 0 125 | STDOUT_MESSAGE = auto() 126 | PROCESS_EXITED = auto() 127 | BACKEND_DISCONNECTED = auto() 128 | 129 | # macos's EXC_BAD_ACCESS 130 | # linux's SIGNAL_SEGV 131 | ACCESS_VIOLATION = auto() 132 | 133 | # macos's EXC_BREAKPOINT 134 | # linux's SIGNAL_TRAP 135 | SINGLE_STEP = auto() 136 | 137 | # macos's EXC_ARITHMETIC 138 | # linux's SIGNAL_FPE (floating point exception) 139 | CALCULATION = auto() 140 | 141 | # 142 | BREAKPOINT = auto() 143 | 144 | # 145 | ILLEGAL_INSTRUCTION = auto() 146 | 147 | SIGNAL_HUP = auto() 148 | SIGNAL_INT = auto() 149 | SIGNAL_QUIT = auto() 150 | SIGNAL_ILL = auto() 151 | SIGNAL_ABRT = auto() 152 | SIGNAL_EMT = auto() 153 | SIGNAL_FPE = auto() 154 | SIGNAL_KILL = auto() 155 | SIGNAL_BUS = auto() 156 | SIGNAL_SEGV = auto() 157 | SIGNAL_SYS = auto() 158 | SIGNAL_PIPE = auto() 159 | SIGNAL_ALRM = auto() 160 | SIGNAL_TERM = auto() 161 | SIGNAL_URG = auto() 162 | SIGNAL_STOP = auto() 163 | SIGNAL_TSTP = auto() 164 | SIGNAL_CONT = auto() 165 | SIGNAL_CHLD = auto() 166 | SIGNAL_TTIN = auto() 167 | SIGNAL_TTOU = auto() 168 | SIGNAL_IO = auto() 169 | SIGNAL_XCPU = auto() 170 | SIGNAL_XFSZ = auto() 171 | SIGNAL_VTALRM = auto() 172 | SIGNAL_PROF = auto() 173 | SIGNAL_WINCH = auto() 174 | SIGNAL_INFO = auto() 175 | SIGNAL_USR1 = auto() 176 | SIGNAL_USR2 = auto() 177 | SIGNAL_STKFLT = auto() 178 | SIGNAL_BUX = auto() 179 | SIGNAL_POLL = auto() 180 | # TODO: get away from macos specific value 181 | EXC_EMULATION = auto() 182 | EXC_SOFTWARE = auto() 183 | EXC_SYSCALL = auto() 184 | EXC_MACH_SYSCALL = auto() 185 | EXC_RPC_ALERT = auto() 186 | EXC_CRASH = auto() 187 | 188 | class DebugAdapter: 189 | def __init__(self, **kwargs): 190 | # stdout handling callback 191 | self.cb_stdout = kwargs.get('stdout', None) 192 | 193 | # session start/stop 194 | def exec(self, path, args=[], **kwargs): 195 | raise NotImplementedError('') 196 | def attach(self, pid): 197 | raise NotImplementedError('') 198 | def connect(self, server, port): 199 | raise NotImplementedError('') 200 | def detach(self): 201 | ''' quit debug session, debuggee left running ''' 202 | raise NotImplementedError('') 203 | def quit(self): 204 | ''' quit debug session, debuggee terminated ''' 205 | raise NotImplementedError('') 206 | 207 | # target info 208 | def target_arch(self): 209 | raise NotImplementedError('') 210 | def target_path(self): 211 | raise NotImplementedError('') 212 | def target_pid(self): 213 | raise NotImplementedError('') 214 | def target_base(self): 215 | raise NotImplementedError('') 216 | 217 | # threads 218 | # 'index' is our abstracted thread identifier [0, 1, ..., n-1] 219 | # 'tid' or OS's assigned numeric thread identifier 220 | # 'selected' thread means subsequent actions (read registers, step) happen on this thread 221 | # 'active' thread means the latest execution-halting action (threw exception, hit breakpoint) occurred in this thread 222 | def thread_list(self): 223 | ''' return a list of thread information ''' 224 | raise NotImplementedError('') 225 | def thread_selected(self): 226 | ''' return thread id that is active ''' 227 | raise NotImplementedError('') 228 | def thread_select(self, tidx): 229 | ''' make a given thread id active ''' 230 | raise NotImplementedError('') 231 | 232 | # breakpoints 233 | def breakpoint_set(self, address): 234 | ''' set software breakpoint at address ''' 235 | raise NotImplementedError('') 236 | def breakpoint_clear(self, address): 237 | ''' delete breakpoint by address ''' 238 | raise NotImplementedError('') 239 | def breakpoint_list(self): 240 | ''' return list of addresses ''' 241 | raise NotImplementedError('') 242 | 243 | # register 244 | def reg_read(self, reg): 245 | raise NotImplementedError('') 246 | def reg_write(self, reg, value): 247 | raise NotImplementedError('') 248 | def reg_list(self): 249 | raise NotImplementedError('') 250 | def reg_bits(self, reg): 251 | raise NotImplementedError('') 252 | 253 | # mem 254 | def mem_read(self, address, length): 255 | raise NotImplementedError('') 256 | def mem_write(self, address, data): 257 | raise NotImplementedError('') 258 | def mem_modules(self, cache_ok=True): 259 | raise NotImplementedError('') 260 | 261 | # break 262 | def break_into(self): 263 | raise NotImplementedError('') 264 | 265 | # execution control 266 | def go(self): 267 | raise NotImplementedError('') 268 | def step_into(self): 269 | raise NotImplementedError('') 270 | def step_over(self): 271 | raise NotImplementedError('') 272 | 273 | # lower level adapter specific stuff 274 | def raw(self, data): 275 | raise NotImplementedError('') 276 | def set_timeout(self): 277 | raise NotImplementedError('') 278 | def kill_comms(self): 279 | raise NotImplementedError('') 280 | 281 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Vector35 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /ProcessView.py: -------------------------------------------------------------------------------- 1 | from binaryninja import BinaryView, SegmentFlag 2 | 3 | from . import binjaplug 4 | from . import DebugAdapter 5 | 6 | """ 7 | The debug memory BinaryView layout is in a few pieces: 8 | - DebugProcessView represents the entire debugged process, containing segments for mapped memory 9 | - DebugMemoryView represents the raw memory of the process (eg like a raw BinaryView) 10 | """ 11 | 12 | class DebugProcessView(BinaryView): 13 | name = "Python Debugged Process" 14 | def __init__(self, parent): 15 | self.memory = DebugMemoryView(parent) 16 | self.local_view = parent 17 | BinaryView.__init__(self, parent_view=self.memory, file_metadata=self.memory.file) 18 | self.arch = parent.arch 19 | self.platform = parent.platform 20 | 21 | self.saved_bases = {} 22 | 23 | # TODO: Read segments from debugger 24 | length = self.memory.perform_get_length() 25 | self.add_auto_segment(0, length, 0, length, SegmentFlag.SegmentReadable | SegmentFlag.SegmentWritable | SegmentFlag.SegmentExecutable) 26 | self.add_auto_section("Memory", 0, length) 27 | 28 | def perform_get_address_size(self): 29 | return self.parent_view.arch.address_size 30 | 31 | @classmethod 32 | def is_valid_for_data(self, data): 33 | return False 34 | 35 | def perform_get_length(self): 36 | return self.memory.perform_get_length() 37 | 38 | def perform_is_executable(self): 39 | return True 40 | 41 | def perform_is_valid_offset(self, addr): 42 | return True 43 | 44 | def mark_dirty(self): 45 | self.memory.mark_dirty() 46 | 47 | """ 48 | Update cached base address for the remote process 49 | """ 50 | def update_base(self): 51 | self.saved_bases = {} 52 | 53 | """ 54 | Get the base address of the binary in the debugged process 55 | """ 56 | def get_remote_base(self, relative_view=None): 57 | if relative_view is None: 58 | relative_view = self.local_view 59 | module = relative_view.file.original_filename 60 | if module not in self.saved_bases: 61 | debug_state = binjaplug.get_state(self.local_view) 62 | base = debug_state.modules[module] 63 | self.saved_bases[module] = base 64 | return self.saved_bases[module] 65 | 66 | """ 67 | Determine if the debugged process is using ASLR for its code segment 68 | (eg in a PIE binary) 69 | """ 70 | def is_code_aslr(self, relative_view=None): 71 | if relative_view is None: 72 | relative_view = self.local_view 73 | return self.get_remote_base(relative_view) != relative_view.start 74 | 75 | """ 76 | Given a local address (relative to the analysis binaryview), 77 | find its remote address (relative to the debugged process) after ASLR 78 | If the address is not within our view, it will be unchanged 79 | """ 80 | def local_addr_to_remote(self, local_addr, relative_view=None): 81 | if relative_view is None: 82 | relative_view = self.local_view 83 | local_base = relative_view.start 84 | remote_base = self.get_remote_base(relative_view) 85 | if local_addr < local_base or local_addr >= local_base + len(relative_view): 86 | # Not within our local binary, return original 87 | return local_addr 88 | return local_addr - local_base + remote_base 89 | 90 | """ 91 | Given a remote address (relative to the debugged process) after ASLR, 92 | find its local address (relative to the analysis binaryview) 93 | If the address is not within our view, it will be unchanged 94 | """ 95 | def remote_addr_to_local(self, remote_addr, relative_view=None): 96 | if relative_view is None: 97 | relative_view = self.local_view 98 | # TODO: Make sure the addr is within the loaded segments for our binary 99 | # Else return the original 100 | local_base = relative_view.start 101 | remote_base = self.get_remote_base(relative_view) 102 | local_addr = remote_addr - remote_base + local_base 103 | if local_addr < local_base or local_addr >= local_base + len(relative_view): 104 | # Not within our local binary, return original 105 | return remote_addr 106 | return local_addr 107 | 108 | """ 109 | Determine if a remote address is within the loaded BinaryView 110 | """ 111 | def is_local_addr(self, remote_addr, relative_view=None): 112 | if relative_view is None: 113 | relative_view = self.local_view 114 | local_base = relative_view.start 115 | remote_base = self.get_remote_base(relative_view) 116 | local_addr = remote_addr - remote_base + local_base 117 | return local_addr > local_base and local_addr < local_base + len(relative_view) 118 | 119 | class DebugMemoryView(BinaryView): 120 | name = "Python Debugged Process Memory" 121 | def __init__(self, parent): 122 | BinaryView.__init__(self, parent_view=parent, file_metadata=parent.file) 123 | self.value_cache = {} 124 | self.error_cache = {} 125 | self.arch = parent.arch 126 | self.platform = parent.platform 127 | 128 | def perform_get_address_size(self): 129 | if self.parent_view is None or self.parent_view.arch is None: 130 | return 8 # Something sane 131 | return self.parent_view.arch.address_size 132 | 133 | @classmethod 134 | def is_valid_for_data(self, data): 135 | return False 136 | 137 | def perform_get_length(self): 138 | # Assume 8 bit bytes (hopefully a safe assumption) 139 | return (2 ** (self.perform_get_address_size() * 8)) - 1 140 | 141 | def perform_read(self, addr, length): 142 | if self.parent_view is None: 143 | return None 144 | adapter = binjaplug.get_state(self.parent_view).adapter 145 | if adapter is None: 146 | return None 147 | 148 | # ProcessView implements read caching in a manner inspired by CPU cache: 149 | # Reads are aligned on 256-byte boundaries and 256 bytes long 150 | 151 | # Cache read start: round down addr to nearest 256 byte boundary 152 | cache_start = addr & ~0xFF 153 | # Cache read end: round up addr+length to nearest 256 byte boundary 154 | cache_end = (addr + length + 0xFF) & ~0xFF 155 | # Cache read length: accounting for rounding down start and rounding up end 156 | cache_len = cache_end - cache_start 157 | # List of 256-byte block addresses to read into the cache to fully cover this region 158 | cache_blocks = range(cache_start, cache_start + cache_len, 0x100) 159 | 160 | result = b'' 161 | 162 | for block in cache_blocks: 163 | if block in self.error_cache: 164 | return None 165 | if block not in self.value_cache: 166 | try: 167 | batch = adapter.mem_read(block, 0x100) 168 | # Cache storage is block => data for every block 169 | self.value_cache[block] = batch 170 | except DebugAdapter.GeneralError as e: 171 | # Probably disconnected; can't read 172 | self.error_cache[block] = True 173 | return None 174 | 175 | # Get data out of cache 176 | cached = self.value_cache[block] 177 | if addr + length < block + len(cached): 178 | # Last block 179 | cached = cached[:((addr + length) - block)] 180 | if addr > block: 181 | # First block 182 | cached = cached[addr-block:] 183 | result += cached 184 | 185 | return result 186 | 187 | def perform_write(self, addr, data): 188 | if self.parent_view is None: 189 | return 0 190 | adapter = binjaplug.get_state(self.parent_view).adapter 191 | if adapter is None: 192 | return 0 193 | # Assume any memory change invalidates all of memory (suboptimal, may not be necessary) 194 | self.mark_dirty() 195 | try: 196 | if adapter.mem_write(addr, data) == 0: 197 | return len(data) 198 | else: 199 | return 0 200 | except DebugAdapter.GeneralError as e: 201 | # Probably disconnected 202 | return 0 203 | 204 | def perform_is_executable(self): 205 | return True 206 | 207 | def perform_is_valid_offset(self, addr): 208 | return True 209 | 210 | # def perform_insert(self, addr, data): 211 | # def perform_remove(self, addr, length): 212 | # def perform_get_modification(self, addr): 213 | # def perform_is_offset_readable(self, offset): 214 | # def perform_is_offset_writable(self, addr): 215 | # def perform_is_offset_executable(self, addr): 216 | # def perform_get_next_valid_offset(self, addr): 217 | # def perform_get_start(self): 218 | # def perform_get_entry_point(self): 219 | # def perform_get_default_endianness(self): 220 | # def perform_is_relocatable(self): 221 | 222 | def mark_dirty(self): 223 | self.value_cache = {} 224 | self.error_cache = {} 225 | 226 | DebugProcessView.register() 227 | DebugMemoryView.register() 228 | -------------------------------------------------------------------------------- /QueuedAdapter.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import traceback 3 | from queue import Queue, Empty 4 | from . import DebugAdapter 5 | 6 | ''' 7 | Adapter shim that provides a thread-safe, blocking way to access an Adapter. 8 | All adapter calls are done on one thread to not have race conditions on the socket. 9 | ''' 10 | class QueuedAdapter(DebugAdapter.DebugAdapter): 11 | RECORD_STATS = False 12 | 13 | def __init__(self, adapter, **kwargs): 14 | DebugAdapter.DebugAdapter.__init__(self, **kwargs) 15 | self.adapter = adapter 16 | 17 | self.queue = Queue() 18 | self.results = {} 19 | self.next_index = 0 20 | self.lock = threading.Lock() 21 | self.worker_thread = threading.Thread(target=lambda: self.worker()) 22 | self.worker_thread.start() 23 | 24 | self.function_stats = {} 25 | 26 | def __del__(self): 27 | pass 28 | 29 | # ------------------------------------------------------------------------- 30 | # Thread-safe work queue for the adapter. Results and exceptions 31 | # are returned via the map self.results. Submitted jobs block for 32 | # completion with condition variables. 33 | # ------------------------------------------------------------------------- 34 | 35 | def worker(self): 36 | while True: 37 | # Get next job 38 | index, job = self.queue.get() 39 | if job == 'break': 40 | break 41 | 42 | # Get condition variable 43 | cond = self.results[index] 44 | 45 | # 46 | try: 47 | self.queue.task_done() 48 | self.results[index] = (True, job()) 49 | except Exception as e: 50 | #print('worker thread got exception: ', e) 51 | self.results[index] = (False, e) 52 | 53 | # Signal completion 54 | cond.acquire() 55 | cond.notify() 56 | cond.release() 57 | 58 | def submit(self, job): 59 | # Submissions to queue fail if thread isn't present 60 | if not (self.worker_thread and self.worker_thread.is_alive()): 61 | return False 62 | 63 | # Be sure to atomically increment the index counter 64 | with self.lock: 65 | index = self.next_index 66 | self.next_index += 1 67 | 68 | # Condition variable will be notified when the job is done 69 | cond = threading.Condition() 70 | # Acquire *before* the job is submitted so we don't lose a notify 71 | cond.acquire() 72 | self.results[index] = cond 73 | 74 | # Don't block on put() but instead on the condition variable 75 | self.queue.put((index, job), False) 76 | cond.wait() 77 | 78 | # Condition signalled, collect results 79 | cond.release() 80 | suceeded, result = self.results[index] 81 | del self.results[index] 82 | # False indicates an exception was thrown 83 | if not suceeded: 84 | raise result 85 | return result 86 | 87 | # ------------------------------------------------------------------------- 88 | # Track statistics for which adapter functions are called the most, and 89 | # which stack traces call them (slow!) 90 | # ------------------------------------------------------------------------- 91 | 92 | def record_stat(self, stat): 93 | if not QueuedAdapter.RECORD_STATS: 94 | return 95 | if stat in self.function_stats: 96 | self.function_stats[stat].append(traceback.extract_stack()) 97 | else: 98 | self.function_stats[stat] = [traceback.extract_stack()] 99 | 100 | def dump_stats(self): 101 | for (stat, items) in sorted(self.function_stats.items(), key=lambda a: len(a[1]), reverse=True): 102 | print("{}: {}".format(stat, len(items))) 103 | 104 | # ------------------------------------------------------------------------- 105 | # Stub functions for the adapter interface 106 | # All of these are routed through the work queue via submit() and are 107 | # thread-safe as a result 108 | # ------------------------------------------------------------------------- 109 | 110 | def exec(self, path, args=[], **kwargs): 111 | self.record_stat("exec") 112 | return self.submit(lambda: self.adapter.exec(path, args, **kwargs)) 113 | def attach(self, pid): 114 | self.record_stat("attach") 115 | return self.submit(lambda: self.adapter.attach(pid)) 116 | def connect(self, server, port): 117 | self.record_stat("connect") 118 | return self.submit(lambda: self.adapter.connect(server, port)) 119 | def detach(self): 120 | self.record_stat("detach") 121 | return self.submit(lambda: self.adapter.detach()) 122 | def quit(self): 123 | self.record_stat("quit") 124 | 125 | # set loop break out signal (but thread could be blocking on previous call) 126 | self.queue.put((-1, 'break'), False) 127 | 128 | # bypass queue, we rely on underlying adapter's quit() to unblock our thread 129 | # (perhaps by closing the socket that our worker thread is recv() on) 130 | self.adapter.quit() 131 | 132 | def target_arch(self): 133 | self.record_stat("target_arch") 134 | return self.submit(lambda: self.adapter.target_arch()) 135 | def target_path(self): 136 | self.record_stat("target_path") 137 | return self.submit(lambda: self.adapter.target_path()) 138 | def target_pid(self): 139 | self.record_stat("target_pid") 140 | return self.submit(lambda: self.adapter.target_pid()) 141 | def target_base(self): 142 | self.record_stat("target_base") 143 | return self.submit(lambda: self.adapter.target_base()) 144 | 145 | def thread_list(self): 146 | self.record_stat("thread_list") 147 | return self.submit(lambda: self.adapter.thread_list()) 148 | def thread_selected(self): 149 | self.record_stat("thread_selected") 150 | return self.submit(lambda: self.adapter.thread_selected()) 151 | def thread_select(self, tidx): 152 | self.record_stat("thread_select") 153 | return self.submit(lambda: self.adapter.thread_select(tidx)) 154 | 155 | def breakpoint_set(self, address): 156 | self.record_stat("breakpoint_set") 157 | return self.submit(lambda: self.adapter.breakpoint_set(address)) 158 | def breakpoint_clear(self, address): 159 | self.record_stat("breakpoint_clear") 160 | return self.submit(lambda: self.adapter.breakpoint_clear(address)) 161 | def breakpoint_list(self): 162 | self.record_stat("breakpoint_list") 163 | return self.submit(lambda: self.adapter.breakpoint_list()) 164 | 165 | def reg_read(self, reg): 166 | self.record_stat("reg_read") 167 | return self.submit(lambda: self.adapter.reg_read(reg)) 168 | def reg_write(self, reg, value): 169 | self.record_stat("reg_write") 170 | return self.submit(lambda: self.adapter.reg_write(reg, value)) 171 | def reg_list(self): 172 | self.record_stat("reg_list") 173 | return self.submit(lambda: self.adapter.reg_list()) 174 | def reg_bits(self, reg): 175 | self.record_stat("reg_bits") 176 | return self.submit(lambda: self.adapter.reg_bits(reg)) 177 | 178 | def mem_read(self, address, length): 179 | self.record_stat("mem_read") 180 | return self.submit(lambda: self.adapter.mem_read(address, length)) 181 | def mem_write(self, address, data): 182 | self.record_stat("mem_write") 183 | return self.submit(lambda: self.adapter.mem_write(address, data)) 184 | def mem_modules(self, cache_ok=True): 185 | self.record_stat("mem_modules") 186 | return self.submit(lambda: self.adapter.mem_modules(cache_ok)) 187 | 188 | def break_into(self): 189 | self.record_stat("break_into") 190 | # skip job queue (which is possibly waiting in go/step_into/step_over) 191 | threading.Thread(target=lambda: self.adapter.break_into()).start() 192 | 193 | def go(self): 194 | self.record_stat("go") 195 | return self.submit(lambda: self.adapter.go()) 196 | def step_into(self): 197 | self.record_stat("step_into") 198 | return self.submit(lambda: self.adapter.step_into()) 199 | def step_over(self): 200 | self.record_stat("step_over") 201 | return self.submit(lambda: self.adapter.step_over()) 202 | 203 | def raw(self, data): 204 | self.record_stat("raw") 205 | return self.submit(lambda: self.adapter.raw(data)) 206 | 207 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Debugger (v1.3.8) 2 | Author: **Vector35** 3 | 4 | ## Notice of deprecation 5 | 6 | This repository is deprecated and no longer actively maintained. We have ported the debugger to C++ and it will be shipped with Binary Ninja. 7 | The new debugger is open-source at https://github.com/Vector35/debugger. Please feel free to test it and report issues. 8 | 9 | The C++ debugger already has all of the Python debugger's functionality (and more). If you have installed the python debugger, you are advised to either remove it or -- in case you still need it -- update it to the latest version to avoid conflict with the C++ debugger. 10 | 11 | ## Description: 12 | 13 | Debug Windows, Linux, and MacOS targets from within Binary Ninja. 14 | 15 | ### Screenshots 16 | 17 | ![](https://github.com/Vector35/deprecated_python_debugger/blob/master/media/gui.png?raw=true) 18 | ![](https://github.com/Vector35/deprecated_python_debugger/blob/master/media/cli.png?raw=true) 19 | 20 | 21 | 22 | 23 | ## Installation Instructions 24 | 25 | ### Darwin 26 | 27 | To install this manually, please see the "Using Plugins" section of the [Getting Started Guide](https://docs.binary.ninja/getting-started.html#using-plugins). Or use Binary Ninja's built in plugin manager. 28 | 29 | ### Windows 30 | 31 | To install this manually, please see the "Using Plugins" section of the [Getting Started Guide](https://docs.binary.ninja/getting-started.html#using-plugins). Or use Binary Ninja's built in plugin manager. 32 | 33 | ### Linux 34 | 35 | To install this manually, please see the "Using Plugins" section of the [Getting Started Guide](https://docs.binary.ninja/getting-started.html#using-plugins). Or use Binary Ninja's built in plugin manager. 36 | 37 | ## Minimum Version 38 | 39 | This plugin requires the following minimum version of Binary Ninja: 40 | 41 | * 2085 42 | 43 | 44 | ## License 45 | 46 | This plugin is released under a MIT license. 47 | ## Metadata Version 48 | 49 | 2 50 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | # This plugin supports scripting both in the ui and in headless plugins 2 | # Start scripts with the following: 3 | # import debugger 4 | # dbg = debugger.get(bv) 5 | 6 | import os 7 | import re 8 | import sys 9 | 10 | # if binja license fails, assume standalone mode, with minimal debugger functionality 11 | standalone = False 12 | try: 13 | import binaryninja 14 | # guard against namespace package trap 15 | if not sys.modules['binaryninja'].__file__: 16 | raise Exception 17 | except Exception: 18 | standalone = True 19 | 20 | # warn if minimum version not met 21 | try: 22 | if standalone: 23 | raise Exception('no version check in standalone mode') 24 | 25 | from binaryninja import core_version, log_error 26 | 27 | (major, minor, buildid) = re.match(r'^(\d+)\.(\d+)\.?(\d+)?', core_version()).groups() 28 | major = int(major) 29 | minor = int(minor) 30 | buildid = int(buildid) if buildid is not None else 0xffffffff 31 | 32 | import json 33 | fpath = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'plugin.json') 34 | with open(fpath) as fp: 35 | data = json.load(fp) 36 | min_version = data['minimumbinaryninjaversion'] 37 | 38 | # git builds end with ' development' 39 | if not (core_version().endswith('development') or core_version().endswith('test')): 40 | if buildid < min_version: 41 | log_error("Debugger relies on features and fixes present in Binary Ninja >= {}. Errors may follow, please update.".format(min_version)) 42 | except: 43 | pass 44 | 45 | if sys.version_info < (3, 6, 0): 46 | # Stop executing this file, we will just throw on later imports 47 | raise Exception("Debugger requires Python 3.6 or greater") 48 | 49 | if not standalone: 50 | from . import binjaplug 51 | 52 | """ 53 | Retrieve the debugger state instance for a given BinaryView 54 | """ 55 | def get(bv): 56 | return binjaplug.get_state(bv) 57 | 58 | -------------------------------------------------------------------------------- /dbgengadapt/Makefile: -------------------------------------------------------------------------------- 1 | all: dbgengadapt.dll 2 | 3 | dbgengadapt.dll: dbgengadapt.obj 4 | link dbgengadapt.obj dbgeng.lib /DLL /OUT:dbgengadapt.dll 5 | 6 | dbgengadapt.obj: dbgengadapt.cpp 7 | cl dbgengadapt.cpp /c /O1 /EHsc /Fo:dbgengadapt.obj 8 | 9 | clean: 10 | rm -rf *.dll *.obj *.lib *.exp *.pdb 11 | -------------------------------------------------------------------------------- /dbgengadapt/dbgengadapt.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vector35/deprecated_python_debugger/71d72f874a39787ff9a1a001cd417beb19ca08f1/dbgengadapt/dbgengadapt.dll -------------------------------------------------------------------------------- /dockwidgets/AdapterSettingsDialog.py: -------------------------------------------------------------------------------- 1 | import binaryninjaui 2 | if "qt_major_version" in binaryninjaui.__dict__ and binaryninjaui.qt_major_version == 6: 3 | from PySide6 import QtCore 4 | from PySide6.QtCore import Qt, QAbstractItemModel, QModelIndex, QSize 5 | from PySide6.QtGui import QPalette, QFontMetricsF 6 | from PySide6.QtWidgets import QApplication, QHBoxLayout, QVBoxLayout, QWidget, QDialog, QPushButton, QFormLayout, QLineEdit, QLabel, QMenu, QCheckBox 7 | else: 8 | from PySide2 import QtCore 9 | from PySide2.QtCore import Qt, QAbstractItemModel, QModelIndex, QSize 10 | from PySide2.QtGui import QPalette, QFontMetricsF 11 | from PySide2.QtWidgets import QApplication, QHBoxLayout, QVBoxLayout, QWidget, QDialog, QPushButton, QFormLayout, QLineEdit, QLabel, QMenu, QCheckBox 12 | 13 | from binaryninja import BinaryView, Settings, SettingsScope 14 | from binaryninjaui import DockContextHandler, UIActionHandler, LinearView, ViewFrame, UIContext 15 | 16 | import shlex 17 | 18 | from .. import binjaplug, DebugAdapter 19 | 20 | class AdapterSettingsDialog(QDialog): 21 | def __init__(self, parent, data): 22 | assert type(data) == BinaryView 23 | self.bv = data 24 | QDialog.__init__(self, parent) 25 | 26 | debug_state = binjaplug.get_state(self.bv) 27 | 28 | self.setWindowTitle("Debug Adapter Settings") 29 | self.setMinimumSize(UIContext.getScaledWindowSize(400, 130)) 30 | self.setAttribute(Qt.WA_DeleteOnClose) 31 | 32 | layout = QVBoxLayout() 33 | layout.setSpacing(0) 34 | 35 | titleLabel = QLabel("Adapter Settings") 36 | titleLayout = QHBoxLayout() 37 | titleLayout.setContentsMargins(0, 0, 0, 0) 38 | titleLayout.addWidget(titleLabel) 39 | 40 | self.adapterEntry = QPushButton(self) 41 | self.adapterMenu = QMenu(self) 42 | for adapter in DebugAdapter.ADAPTER_TYPE: 43 | if not DebugAdapter.ADAPTER_TYPE.can_use(adapter): 44 | continue 45 | def select_adapter(adapter): 46 | return lambda: self.selectAdapter(adapter) 47 | self.adapterMenu.addAction(adapter.name, select_adapter(adapter)) 48 | if adapter == debug_state.adapter_type: 49 | self.adapterEntry.setText(adapter.name) 50 | 51 | self.adapterEntry.setMenu(self.adapterMenu) 52 | 53 | self.argumentsEntry = QLineEdit(self) 54 | self.addressEntry = QLineEdit(self) 55 | self.portEntry = QLineEdit(self) 56 | self.requestTerminalEmulator = QCheckBox(self) 57 | 58 | self.formLayout = QFormLayout() 59 | self.formLayout.addRow("Adapter Type", self.adapterEntry) 60 | self.formLayout.addRow("Command Line Arguments", self.argumentsEntry) 61 | self.formLayout.addRow("Address", self.addressEntry) 62 | self.formLayout.addRow("Port", self.portEntry) 63 | self.formLayout.addRow("Request Terminal Emulator", self.requestTerminalEmulator) 64 | 65 | buttonLayout = QHBoxLayout() 66 | buttonLayout.setContentsMargins(0, 0, 0, 0) 67 | 68 | self.cancelButton = QPushButton("Cancel") 69 | self.cancelButton.clicked.connect(lambda: self.reject()) 70 | self.acceptButton = QPushButton("Accept") 71 | self.acceptButton.clicked.connect(lambda: self.accept()) 72 | self.acceptButton.setDefault(True) 73 | buttonLayout.addStretch(1) 74 | buttonLayout.addWidget(self.cancelButton) 75 | buttonLayout.addWidget(self.acceptButton) 76 | 77 | layout.addLayout(titleLayout) 78 | layout.addSpacing(10) 79 | layout.addLayout(self.formLayout) 80 | layout.addStretch(1) 81 | layout.addSpacing(10) 82 | layout.addLayout(buttonLayout) 83 | 84 | self.setLayout(layout) 85 | 86 | self.addressEntry.setText(debug_state.remote_host) 87 | self.portEntry.setText(str(debug_state.remote_port)) 88 | 89 | self.addressEntry.textEdited.connect(lambda: self.apply()) 90 | self.portEntry.textEdited.connect(lambda: self.apply()) 91 | 92 | self.argumentsEntry.setText(' ' .join(shlex.quote(arg) for arg in debug_state.command_line_args)) 93 | self.argumentsEntry.textEdited.connect(lambda: self.updateArguments()) 94 | 95 | self.requestTerminalEmulator.setChecked(debug_state.request_terminal_emulator) 96 | self.requestTerminalEmulator.stateChanged.connect(lambda: self.apply()) 97 | 98 | self.accepted.connect(lambda: self.apply()) 99 | 100 | def selectAdapter(self, adapter): 101 | self.bv.store_metadata('python_debugger.adapter_type', adapter.value) 102 | debug_state = binjaplug.get_state(self.bv) 103 | debug_state.adapter_type = adapter 104 | self.adapterEntry.setText(adapter.name) 105 | 106 | if DebugAdapter.ADAPTER_TYPE.use_exec(adapter): 107 | self.argumentsEntry.setEnabled(True) 108 | self.addressEntry.setEnabled(False) 109 | self.portEntry.setEnabled(False) 110 | elif DebugAdapter.ADAPTER_TYPE.use_connect(adapter): 111 | self.argumentsEntry.setEnabled(False) 112 | self.addressEntry.setEnabled(True) 113 | self.portEntry.setEnabled(True) 114 | 115 | def apply(self): 116 | debug_state = binjaplug.get_state(self.bv) 117 | arguments = shlex.split(self.argumentsEntry.text()) 118 | debug_state.command_line_args = arguments 119 | self.bv.store_metadata('python_debugger.command_line_args', arguments) 120 | 121 | address = self.addressEntry.text() 122 | port = int(self.portEntry.text()) 123 | 124 | debug_state.remote_host = address 125 | debug_state.remote_port = port 126 | 127 | self.bv.store_metadata('python_debugger.remote_host', address) 128 | self.bv.store_metadata('python_debugger.remote_port', port) 129 | 130 | request_terminal_emulator = self.requestTerminalEmulator.isChecked() 131 | debug_state.request_terminal_emulator = request_terminal_emulator 132 | self.bv.store_metadata('python_debugger.request_terminal_emulator', request_terminal_emulator) 133 | 134 | def updateArguments(self): 135 | try: 136 | arguments = shlex.split(self.argumentsEntry.text()) 137 | self.acceptButton.setEnabled(True) 138 | except: 139 | self.acceptButton.setEnabled(False) 140 | 141 | -------------------------------------------------------------------------------- /dockwidgets/BreakpointsWidget.py: -------------------------------------------------------------------------------- 1 | import binaryninjaui 2 | if "qt_major_version" in binaryninjaui.__dict__ and binaryninjaui.qt_major_version == 6: 3 | from PySide6 import QtCore 4 | from PySide6.QtCore import Qt, QAbstractItemModel, QModelIndex, QSize 5 | from PySide6.QtGui import QPalette, QFontMetricsF 6 | from PySide6.QtWidgets import QApplication, QHBoxLayout, QVBoxLayout, QWidget, QTableView, QItemDelegate, QStyle, QHeaderView, QAbstractItemView 7 | else: 8 | from PySide2 import QtCore 9 | from PySide2.QtCore import Qt, QAbstractItemModel, QModelIndex, QSize 10 | from PySide2.QtGui import QPalette, QFontMetricsF 11 | from PySide2.QtWidgets import QApplication, QHBoxLayout, QVBoxLayout, QWidget, QTableView, QItemDelegate, QStyle, QHeaderView, QAbstractItemView 12 | 13 | from binaryninjaui import DockContextHandler, UIActionHandler, ThemeColor 14 | from binaryninja import BinaryView 15 | 16 | from . import widget 17 | from .. import binjaplug 18 | 19 | breakpoints = {} 20 | 21 | class DebugBreakpointsListModel(QAbstractItemModel): 22 | def __init__(self, parent, bv): 23 | QAbstractItemModel.__init__(self, parent) 24 | self.bv = bv 25 | self.columns = ["Enabled", "Location", "Remote Address"] 26 | self.update_rows(None) 27 | 28 | def update_rows(self, new_rows): 29 | self.beginResetModel() 30 | 31 | if new_rows is None: 32 | self.rows = [] 33 | else: 34 | self.rows = new_rows 35 | 36 | self.endResetModel() 37 | 38 | """ 39 | General outline for QAbstractItemModel. We have to implement any pure virtual functions (fn = 0) 40 | 41 | virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const = 0; 42 | virtual QModelIndex parent(const QModelIndex &child) const = 0; 43 | virtual bool hasChildren(const QModelIndex &parent = QModelIndex()) const; 44 | 45 | virtual int rowCount(const QModelIndex &parent = QModelIndex()) const = 0; 46 | virtual int columnCount(const QModelIndex &parent = QModelIndex()) const = 0; 47 | 48 | virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const = 0; 49 | virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); 50 | 51 | virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; 52 | virtual bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole); 53 | 54 | virtual void sort(int column, Qt::SortOrder order) override; 55 | virtual Qt::ItemFlags flags(const QModelIndex& i) const override; 56 | """ 57 | 58 | def index(self, row, column, parent): 59 | if parent.isValid() or column > len(self.columns) or row >= len(self.rows): 60 | return QModelIndex() 61 | return self.createIndex(row, column) 62 | 63 | def parent(self, child): 64 | return QModelIndex() 65 | 66 | def hasChildren(self, parent): 67 | return False 68 | 69 | def rowCount(self, parent): 70 | if parent.isValid(): 71 | return 0 72 | return len(self.rows) 73 | 74 | def columnCount(self, parent): 75 | return len(self.columns) 76 | 77 | def headerData(self, section, orientation, role): 78 | if role != Qt.DisplayRole: 79 | return None 80 | if orientation == Qt.Vertical: 81 | return None 82 | return self.columns[section] 83 | 84 | def data(self, index, role): 85 | if not index.isValid(): 86 | return None 87 | if index.row() < 0 or index.row() >= len(self.rows): 88 | return None 89 | if role != Qt.DisplayRole: 90 | return None 91 | 92 | conts = self.rows[index.row()] 93 | 94 | # Format data into displayable text 95 | if self.columns[index.column()] == 'Location': 96 | text = '%s+0x%x' % (conts['module'], conts['offset']) 97 | elif self.columns[index.column()] == 'Remote Address': 98 | text = '%x' % conts['address'] 99 | elif self.columns[index.column()] == 'Enabled': 100 | text = str(conts['enabled']) 101 | return text 102 | 103 | 104 | class DebugBreakpointsItemDelegate(QItemDelegate): 105 | def __init__(self, parent): 106 | QItemDelegate.__init__(self, parent) 107 | 108 | self.font = binaryninjaui.getMonospaceFont(parent) 109 | self.font.setKerning(False) 110 | self.baseline = QFontMetricsF(self.font).ascent() 111 | self.char_width = binaryninjaui.getFontWidthAndAdjustSpacing(self.font)[0] 112 | self.char_height = QFontMetricsF(self.font).height() 113 | self.char_offset = binaryninjaui.getFontVerticalOffset() 114 | 115 | self.expected_char_widths = [10, 20, 20] 116 | 117 | """ 118 | virtual QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& idx) const override; 119 | """ 120 | def sizeHint(self, option, idx): 121 | return QSize(self.char_width * self.expected_char_widths[idx.column()] + 4, self.char_height) 122 | 123 | """ 124 | virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& idx) const override; 125 | """ 126 | def paint(self, painter, option, idx): 127 | # Draw background highlight in theme style 128 | selected = option.state & QStyle.State_Selected != 0 129 | if selected: 130 | painter.setBrush(binaryninjaui.getThemeColor(binaryninjaui.ThemeColor.SelectionColor)) 131 | else: 132 | painter.setBrush(option.backgroundBrush) 133 | painter.setPen(Qt.NoPen) 134 | painter.drawRect(option.rect) 135 | 136 | text = idx.data() 137 | max_width = option.rect.width() // self.char_width 138 | if len(text) > max_width: 139 | text = text[:max_width - 1] + '…' 140 | 141 | # Draw text 142 | painter.setFont(self.font) 143 | painter.setPen(option.palette.color(QPalette.WindowText).rgba()) 144 | painter.drawText(2 + option.rect.left(), self.char_offset + self.baseline + option.rect.top(), str(text)) 145 | 146 | 147 | class DebugBreakpointsWidget(QWidget, DockContextHandler): 148 | def __init__(self, parent, name, data): 149 | if not type(data) == BinaryView: 150 | raise Exception('expected widget data to be a BinaryView') 151 | 152 | self.bv = data 153 | 154 | QWidget.__init__(self, parent) 155 | DockContextHandler.__init__(self, self, name) 156 | self.actionHandler = UIActionHandler() 157 | self.actionHandler.setupActionHandler(self) 158 | 159 | self.table = QTableView(self) 160 | self.model = DebugBreakpointsListModel(self.table, data) 161 | self.table.setModel(self.model) 162 | 163 | self.item_delegate = DebugBreakpointsItemDelegate(self) 164 | self.table.setItemDelegate(self.item_delegate) 165 | 166 | # self.table.setSortingEnabled(True) 167 | self.table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) 168 | self.table.setSelectionMode(QAbstractItemView.ExtendedSelection) 169 | 170 | self.table.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents) 171 | self.table.verticalHeader().setVisible(False) 172 | 173 | self.table.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) 174 | self.table.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel) 175 | 176 | self.table.resizeColumnsToContents() 177 | self.table.resizeRowsToContents() 178 | self.table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch) 179 | 180 | layout = QVBoxLayout() 181 | layout.setContentsMargins(0, 0, 0, 0) 182 | layout.setSpacing(0) 183 | layout.addWidget(self.table) 184 | self.setLayout(layout) 185 | 186 | def notifyOffsetChanged(self, offset): 187 | pass 188 | 189 | def notifyBreakpointsChanged(self, new_rows): 190 | self.model.update_rows(new_rows) 191 | 192 | def contextMenuEvent(self, event): 193 | self.m_contextMenuManager.show(self.m_menu, self.actionHandler) 194 | 195 | def shouldBeVisible(self, view_frame): 196 | if view_frame is None: 197 | return False 198 | else: 199 | return True 200 | 201 | -------------------------------------------------------------------------------- /dockwidgets/ConsoleWidget.py: -------------------------------------------------------------------------------- 1 | import binaryninjaui 2 | if "qt_major_version" in binaryninjaui.__dict__ and binaryninjaui.qt_major_version == 6: 3 | from PySide6 import QtCore 4 | from PySide6.QtCore import Qt, QSize 5 | from PySide6.QtWidgets import QApplication, QHBoxLayout, QVBoxLayout, QLabel, QWidget, QPushButton, QLineEdit, QTextEdit 6 | from PySide6.QtGui import QTextCursor 7 | else: 8 | from PySide2 import QtCore 9 | from PySide2.QtCore import Qt, QSize 10 | from PySide2.QtWidgets import QApplication, QHBoxLayout, QVBoxLayout, QLabel, QWidget, QPushButton, QLineEdit, QTextEdit 11 | from PySide2.QtGui import QTextCursor 12 | 13 | import binaryninja 14 | from binaryninjaui import DockHandler, DockContextHandler, UIActionHandler, getMonospaceFont 15 | 16 | from .. import binjaplug 17 | 18 | class DebugConsoleWidget(QWidget, DockContextHandler): 19 | def __init__(self, parent, name, data): 20 | if not type(data) == binaryninja.binaryview.BinaryView: 21 | raise Exception('expected widget data to be a BinaryView') 22 | 23 | self.bv = data 24 | 25 | QWidget.__init__(self, parent) 26 | DockContextHandler.__init__(self, self, name) 27 | self.actionHandler = UIActionHandler() 28 | self.actionHandler.setupActionHandler(self) 29 | 30 | layout = QVBoxLayout() 31 | self.consoleText = QTextEdit(self) 32 | self.consoleText.setReadOnly(True) 33 | self.consoleText.setFont(getMonospaceFont(self)) 34 | layout.addWidget(self.consoleText, 1) 35 | 36 | inputLayout = QHBoxLayout() 37 | inputLayout.setContentsMargins(4, 4, 4, 4) 38 | 39 | promptLayout = QVBoxLayout() 40 | promptLayout.setContentsMargins(0, 5, 0, 5) 41 | 42 | inputLayout.addLayout(promptLayout) 43 | 44 | self.consoleEntry = QLineEdit(self) 45 | inputLayout.addWidget(self.consoleEntry, 1) 46 | 47 | self.entryLabel = QLabel("pydbg>>> ", self) 48 | self.entryLabel.setFont(getMonospaceFont(self)) 49 | promptLayout.addWidget(self.entryLabel) 50 | promptLayout.addStretch(1) 51 | 52 | self.consoleEntry.returnPressed.connect(lambda: self.sendLine()) 53 | 54 | layout.addLayout(inputLayout) 55 | layout.setContentsMargins(0, 0, 0, 0) 56 | layout.setSpacing(0) 57 | self.setLayout(layout) 58 | 59 | def sizeHint(self): 60 | return QSize(300, 100) 61 | 62 | def canWrite(self): 63 | debug_state = binjaplug.get_state(self.bv) 64 | try: 65 | return debug_state.adapter.stdin_is_writable() 66 | except: 67 | return False 68 | 69 | def sendLine(self): 70 | if not self.canWrite(): 71 | return 72 | 73 | line = self.consoleEntry.text() 74 | self.consoleEntry.setText("") 75 | 76 | debug_state = binjaplug.get_state(self.bv) 77 | try: 78 | debug_state.send_console_input(line) 79 | except Exception as e: 80 | self.notifyStdout("Error sending input: {} {}\n".format(type(e).__name__, ' '.join(e.args))) 81 | 82 | def notifyStdout(self, line): 83 | self.consoleText.insertPlainText(line) 84 | 85 | # Scroll down 86 | cursor = self.consoleText.textCursor() 87 | cursor.clearSelection() 88 | cursor.movePosition(QTextCursor.End) 89 | self.consoleText.setTextCursor(cursor) 90 | 91 | self.updateEnabled() 92 | 93 | def updateEnabled(self): 94 | enabled = self.canWrite() 95 | self.consoleEntry.setEnabled(enabled) 96 | self.entryLabel.setText("stdin>>> " if enabled else "stdin (unavailable) ") 97 | 98 | #-------------------------------------------------------------------------- 99 | # callbacks to us api/ui/dockhandler.h 100 | #-------------------------------------------------------------------------- 101 | def notifyOffsetChanged(self, offset): 102 | self.updateEnabled() 103 | 104 | def notifyViewChanged(self, view_frame): 105 | self.updateEnabled() 106 | 107 | def contextMenuEvent(self, event): 108 | self.m_contextMenuManager.show(self.m_menu, self.actionHandler) 109 | 110 | def shouldBeVisible(self, view_frame): 111 | if view_frame is None: 112 | return False 113 | else: 114 | return True 115 | -------------------------------------------------------------------------------- /dockwidgets/ModulesWidget.py: -------------------------------------------------------------------------------- 1 | import binaryninjaui 2 | if "qt_major_version" in binaryninjaui.__dict__ and binaryninjaui.qt_major_version == 6: 3 | from PySide6 import QtCore 4 | from PySide6.QtCore import Qt, QAbstractItemModel, QModelIndex, QSize 5 | from PySide6.QtGui import QPalette, QFontMetricsF 6 | from PySide6.QtWidgets import QApplication, QHBoxLayout, QVBoxLayout, QWidget, QTableView, QItemDelegate, QStyle, QHeaderView, QAbstractItemView, QLabel, QPushButton 7 | else: 8 | from PySide2 import QtCore 9 | from PySide2.QtCore import Qt, QAbstractItemModel, QModelIndex, QSize 10 | from PySide2.QtGui import QPalette, QFontMetricsF 11 | from PySide2.QtWidgets import QApplication, QHBoxLayout, QVBoxLayout, QWidget, QTableView, QItemDelegate, QStyle, QHeaderView, QAbstractItemView, QLabel, QPushButton 12 | 13 | import binaryninja 14 | from binaryninjaui import DockContextHandler, UIActionHandler, ThemeColor 15 | 16 | from . import widget 17 | from .. import binjaplug 18 | 19 | class DebugModulesListModel(QAbstractItemModel): 20 | def __init__(self, parent, bv): 21 | QAbstractItemModel.__init__(self, parent) 22 | self.bv = bv 23 | self.columns = ["Address", "Name", "Full Path"] 24 | self.update_rows(None) 25 | 26 | def update_rows(self, new_rows): 27 | self.beginResetModel() 28 | 29 | if new_rows is None: 30 | self.rows = [] 31 | else: 32 | self.rows = new_rows 33 | 34 | self.endResetModel() 35 | 36 | def index(self, row, column, parent): 37 | if parent.isValid() or column > len(self.columns) or row >= len(self.rows): 38 | return QModelIndex() 39 | return self.createIndex(row, column) 40 | 41 | def parent(self, child): 42 | return QModelIndex() 43 | 44 | def hasChildren(self, parent): 45 | return False 46 | 47 | def rowCount(self, parent): 48 | if parent.isValid(): 49 | return 0 50 | return len(self.rows) 51 | 52 | def columnCount(self, parent): 53 | return len(self.columns) 54 | 55 | def headerData(self, section, orientation, role): 56 | if role != Qt.DisplayRole: 57 | return None 58 | if orientation == Qt.Vertical: 59 | return None 60 | return self.columns[section] 61 | 62 | def data(self, index, role): 63 | if not index.isValid(): 64 | return None 65 | if index.row() < 0 or index.row() >= len(self.rows): 66 | return None 67 | 68 | info = self.rows[index.row()] 69 | 70 | if role == Qt.DisplayRole: 71 | # Format data into displayable text 72 | if self.columns[index.column()] == "Address": 73 | text = '0x%x' % info['address'] 74 | elif self.columns[index.column()] == "Name": 75 | text = info['modpath'] 76 | if '/' in text: 77 | text = text[text.rfind('/')+1:] 78 | elif self.columns[index.column()] == "Full Path": 79 | text = info['modpath'] 80 | else: 81 | raise NotImplementedError('Unknown column') 82 | return text 83 | 84 | return None 85 | 86 | class DebugModulesItemDelegate(QItemDelegate): 87 | def __init__(self, parent): 88 | QItemDelegate.__init__(self, parent) 89 | 90 | self.font = binaryninjaui.getMonospaceFont(parent) 91 | self.font.setKerning(False) 92 | self.baseline = QFontMetricsF(self.font).ascent() 93 | self.char_width = binaryninjaui.getFontWidthAndAdjustSpacing(self.font)[0] 94 | self.char_height = QFontMetricsF(self.font).height() 95 | self.char_offset = binaryninjaui.getFontVerticalOffset() 96 | 97 | self.expected_char_widths = [20, 20, 30] 98 | 99 | def sizeHint(self, option, idx): 100 | width = self.expected_char_widths[idx.column()] 101 | data = idx.data() 102 | if data is not None: 103 | width = max(width, len(data)) 104 | return QSize(self.char_width * width + 4, self.char_height) 105 | 106 | def paint(self, painter, option, idx): 107 | # Draw background highlight in theme style 108 | selected = option.state & QStyle.State_Selected != 0 109 | if selected: 110 | painter.setBrush(binaryninjaui.getThemeColor(binaryninjaui.ThemeColor.SelectionColor)) 111 | else: 112 | painter.setBrush(option.backgroundBrush) 113 | painter.setPen(Qt.NoPen) 114 | painter.drawRect(option.rect) 115 | 116 | text = idx.data() 117 | painter.setFont(self.font) 118 | painter.setPen(option.palette.color(QPalette.WindowText).rgba()) 119 | painter.drawText(2 + option.rect.left(), self.char_offset + self.baseline + option.rect.top(), str(text)) 120 | 121 | 122 | class DebugModulesWidget(QWidget, DockContextHandler): 123 | def __init__(self, parent, name, data): 124 | if not type(data) == binaryninja.binaryview.BinaryView: 125 | raise Exception('expected widget data to be a BinaryView') 126 | 127 | self.bv = data 128 | 129 | QWidget.__init__(self, parent) 130 | DockContextHandler.__init__(self, self, name) 131 | self.actionHandler = UIActionHandler() 132 | self.actionHandler.setupActionHandler(self) 133 | 134 | self.table = QTableView(self) 135 | self.model = DebugModulesListModel(self.table, data) 136 | self.table.setModel(self.model) 137 | 138 | self.item_delegate = DebugModulesItemDelegate(self) 139 | self.table.setItemDelegate(self.item_delegate) 140 | 141 | # self.table.setSortingEnabled(True) 142 | self.table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) 143 | self.table.setSelectionMode(QAbstractItemView.ExtendedSelection) 144 | 145 | self.table.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents) 146 | self.table.verticalHeader().setVisible(False) 147 | 148 | self.table.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) 149 | self.table.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel) 150 | 151 | self.table.resizeColumnsToContents() 152 | self.table.resizeRowsToContents() 153 | 154 | update_layout = QHBoxLayout() 155 | update_layout.setContentsMargins(0, 0, 0, 0) 156 | 157 | update_label = QLabel("Data is Stale") 158 | update_button = QPushButton("Refresh") 159 | update_button.clicked.connect(lambda: self.refresh()) 160 | 161 | update_layout.addWidget(update_label) 162 | update_layout.addStretch(1) 163 | update_layout.addWidget(update_button) 164 | 165 | self.update_box = QWidget() 166 | self.update_box.setLayout(update_layout) 167 | 168 | self.layout = QVBoxLayout() 169 | self.layout.setContentsMargins(0, 0, 0, 0) 170 | self.layout.setSpacing(0) 171 | self.layout.addWidget(self.table) 172 | self.setLayout(self.layout) 173 | 174 | def notifyOffsetChanged(self, offset): 175 | pass 176 | 177 | def refresh(self): 178 | debug_state = binjaplug.get_state(self.bv) 179 | debug_state.ui.update_modules() 180 | 181 | def notifyModulesChanged(self, new_modules): 182 | self.model.update_rows(new_modules) 183 | self.table.resizeColumnsToContents() 184 | self.layout.removeWidget(self.update_box) 185 | self.update_box.setVisible(False) 186 | 187 | def mark_dirty(self): 188 | self.layout.addWidget(self.update_box) 189 | self.update_box.setVisible(True) 190 | 191 | def contextMenuEvent(self, event): 192 | self.m_contextMenuManager.show(self.m_menu, self.actionHandler) 193 | 194 | def shouldBeVisible(self, view_frame): 195 | if view_frame is None: 196 | return False 197 | else: 198 | return True 199 | 200 | -------------------------------------------------------------------------------- /dockwidgets/RegistersWidget.py: -------------------------------------------------------------------------------- 1 | import binaryninjaui 2 | if "qt_major_version" in binaryninjaui.__dict__ and binaryninjaui.qt_major_version == 6: 3 | from PySide6 import QtCore 4 | from PySide6.QtCore import Qt, QAbstractItemModel, QModelIndex, QSize 5 | from PySide6.QtGui import QPalette, QFontMetricsF 6 | from PySide6.QtWidgets import QApplication, QHBoxLayout, QVBoxLayout, QWidget, QTableView, QItemDelegate, QStyle, QHeaderView, QAbstractItemView 7 | else: 8 | from PySide2 import QtCore 9 | from PySide2.QtCore import Qt, QAbstractItemModel, QModelIndex, QSize 10 | from PySide2.QtGui import QPalette, QFontMetricsF 11 | from PySide2.QtWidgets import QApplication, QHBoxLayout, QVBoxLayout, QWidget, QTableView, QItemDelegate, QStyle, QHeaderView, QAbstractItemView 12 | 13 | from binaryninjaui import DockContextHandler, UIActionHandler, ThemeColor 14 | from binaryninja import BinaryView 15 | 16 | from . import widget 17 | from .. import binjaplug 18 | 19 | class DebugRegistersListModel(QAbstractItemModel): 20 | def __init__(self, parent, bv): 21 | QAbstractItemModel.__init__(self, parent) 22 | self.bv = bv 23 | self.columns = ["Name", "Value"] 24 | self.rows = [] 25 | self.update_rows(None) 26 | 27 | def update_rows(self, new_rows): 28 | self.beginResetModel() 29 | 30 | old_regs = {} 31 | for (reg, value) in self.rows: 32 | old_regs[reg] = value 33 | 34 | self.rows = [] 35 | self.row_info = [] 36 | if new_rows is None: 37 | self.endResetModel() 38 | return 39 | 40 | # Fill self.rows 41 | for info in new_rows: 42 | self.rows.append((info['name'], info['value'])) 43 | info['state'] = 'unchanged' if old_regs.get(info['name'], -1) == info['value'] else 'updated' 44 | self.row_info.append(info) 45 | 46 | self.endResetModel() 47 | 48 | def index(self, row, column, parent): 49 | if parent.isValid() or column > len(self.columns) or row >= len(self.rows): 50 | return QModelIndex() 51 | return self.createIndex(row, column) 52 | 53 | def parent(self, child): 54 | return QModelIndex() 55 | 56 | def hasChildren(self, parent): 57 | return False 58 | 59 | def rowCount(self, parent): 60 | if parent.isValid(): 61 | return 0 62 | return len(self.rows) 63 | 64 | def columnCount(self, parent): 65 | return len(self.columns) 66 | 67 | def flags(self, index): 68 | f = super().flags(index) 69 | if index.column() == 1: 70 | f |= Qt.ItemIsEditable 71 | return f 72 | 73 | def headerData(self, section, orientation, role): 74 | if role != Qt.DisplayRole: 75 | return None 76 | if orientation == Qt.Vertical: 77 | return None 78 | return self.columns[section] 79 | 80 | def data(self, index, role): 81 | if not index.isValid(): 82 | return None 83 | if index.row() < 0 or index.row() >= len(self.rows): 84 | return None 85 | 86 | conts = self.rows[index.row()][index.column()] 87 | info = self.row_info[index.row()] 88 | 89 | if role == Qt.DisplayRole: 90 | # Format data into displayable text 91 | if index.column() == 1: 92 | # Pad out to ceil(bitlength/4) nibbles 93 | text = ('%x' % conts).rjust((info['bits'] + 3) // 4, "0") 94 | else: 95 | text = str(conts) 96 | return text 97 | elif role == Qt.UserRole: 98 | return info['state'] 99 | 100 | return None 101 | 102 | def setData(self, index, value, role): 103 | # Verify that we can edit this value 104 | if (self.flags(index) & Qt.EditRole) != Qt.EditRole: 105 | return False 106 | if len(value) == 0: 107 | return False 108 | 109 | info = self.row_info[index.row()] 110 | old_val = self.rows[index.row()][1] 111 | try: 112 | new_val = int(value, 16) 113 | except: 114 | return False 115 | register = info['name'] 116 | 117 | if new_val == old_val: 118 | return False 119 | 120 | # Tell the debugger to update 121 | debug_state = binjaplug.get_state(self.bv) 122 | debug_state.registers[register] = new_val 123 | 124 | # Update internal copy to show modification 125 | updated_val = debug_state.registers[register] 126 | 127 | # Make sure the debugger actually let us set the register 128 | self.rows[index.row()] = (register, updated_val) 129 | self.row_info[index.row()]['state'] = 'modified' if updated_val == new_val else info['state'] 130 | 131 | self.dataChanged.emit(index, index, [role]) 132 | self.layoutChanged.emit() 133 | return True 134 | 135 | class DebugRegistersItemDelegate(QItemDelegate): 136 | def __init__(self, parent): 137 | QItemDelegate.__init__(self, parent) 138 | 139 | self.font = binaryninjaui.getMonospaceFont(parent) 140 | self.font.setKerning(False) 141 | self.baseline = QFontMetricsF(self.font).ascent() 142 | self.char_width = binaryninjaui.getFontWidthAndAdjustSpacing(self.font)[0] 143 | self.char_height = QFontMetricsF(self.font).height() 144 | self.char_offset = binaryninjaui.getFontVerticalOffset() 145 | 146 | self.expected_char_widths = [10, 32] 147 | 148 | def sizeHint(self, option, idx): 149 | width = self.expected_char_widths[idx.column()] 150 | data = idx.data() 151 | if data is not None: 152 | width = max(width, len(data)) 153 | return QSize(self.char_width * width + 4, self.char_height) 154 | 155 | def paint(self, painter, option, idx): 156 | # Draw background highlight in theme style 157 | selected = option.state & QStyle.State_Selected != 0 158 | if selected: 159 | painter.setBrush(binaryninjaui.getThemeColor(binaryninjaui.ThemeColor.SelectionColor)) 160 | else: 161 | painter.setBrush(option.backgroundBrush) 162 | painter.setPen(Qt.NoPen) 163 | painter.drawRect(option.rect) 164 | 165 | text = idx.data() 166 | state = idx.data(Qt.UserRole) 167 | 168 | # Draw text depending on state 169 | painter.setFont(self.font) 170 | if state == 'updated': 171 | painter.setPen(option.palette.color(QPalette.Highlight).rgba()) 172 | elif state == 'modified': 173 | painter.setPen(binaryninjaui.getThemeColor(ThemeColor.OrangeStandardHighlightColor).rgba()) 174 | else: 175 | painter.setPen(option.palette.color(QPalette.WindowText).rgba()) 176 | painter.drawText(2 + option.rect.left(), self.char_offset + self.baseline + option.rect.top(), str(text)) 177 | 178 | def setEditorData(self, editor, idx): 179 | if idx.column() == 1: 180 | data = idx.data() 181 | editor.setText(data) 182 | 183 | 184 | class DebugRegistersWidget(QWidget, DockContextHandler): 185 | def __init__(self, parent, name, data): 186 | if not type(data) == BinaryView: 187 | raise Exception('expected widget data to be a BinaryView') 188 | 189 | self.bv = data 190 | 191 | QWidget.__init__(self, parent) 192 | DockContextHandler.__init__(self, self, name) 193 | self.actionHandler = UIActionHandler() 194 | self.actionHandler.setupActionHandler(self) 195 | 196 | self.table = QTableView(self) 197 | self.model = DebugRegistersListModel(self.table, data) 198 | self.table.setModel(self.model) 199 | 200 | self.item_delegate = DebugRegistersItemDelegate(self) 201 | self.table.setItemDelegate(self.item_delegate) 202 | 203 | # self.table.setSortingEnabled(True) 204 | self.table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) 205 | self.table.setSelectionMode(QAbstractItemView.ExtendedSelection) 206 | 207 | self.table.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents) 208 | self.table.verticalHeader().setVisible(False) 209 | 210 | self.table.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) 211 | self.table.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel) 212 | 213 | self.table.resizeColumnsToContents() 214 | self.table.resizeRowsToContents() 215 | 216 | layout = QVBoxLayout() 217 | layout.setContentsMargins(0, 0, 0, 0) 218 | layout.setSpacing(0) 219 | layout.addWidget(self.table) 220 | self.setLayout(layout) 221 | 222 | def notifyOffsetChanged(self, offset): 223 | pass 224 | 225 | def notifyRegistersChanged(self, new_regs): 226 | self.model.update_rows(new_regs) 227 | self.table.resizeColumnsToContents() 228 | 229 | def contextMenuEvent(self, event): 230 | self.m_contextMenuManager.show(self.m_menu, self.actionHandler) 231 | 232 | def shouldBeVisible(self, view_frame): 233 | if view_frame is None: 234 | return False 235 | else: 236 | return True 237 | 238 | -------------------------------------------------------------------------------- /dockwidgets/StackWidget.py: -------------------------------------------------------------------------------- 1 | import binaryninjaui 2 | if "qt_major_version" in binaryninjaui.__dict__ and binaryninjaui.qt_major_version == 6: 3 | from PySide6 import QtCore 4 | from PySide6.QtCore import Qt, QAbstractItemModel, QModelIndex, QSize 5 | from PySide6.QtGui import QPalette, QFontMetricsF 6 | from PySide6.QtWidgets import QApplication, QHBoxLayout, QVBoxLayout, QWidget, QTableView, QItemDelegate, QStyle, QHeaderView, QAbstractItemView 7 | else: 8 | from PySide2 import QtCore 9 | from PySide2.QtCore import Qt, QAbstractItemModel, QModelIndex, QSize 10 | from PySide2.QtGui import QPalette, QFontMetricsF 11 | from PySide2.QtWidgets import QApplication, QHBoxLayout, QVBoxLayout, QWidget, QTableView, QItemDelegate, QStyle, QHeaderView, QAbstractItemView 12 | 13 | from binaryninja import Endianness, BinaryView 14 | from binaryninjaui import DockContextHandler, UIActionHandler, ThemeColor 15 | 16 | from . import widget 17 | from .. import binjaplug 18 | 19 | class DebugStackModel(QAbstractItemModel): 20 | def __init__(self, parent, bv): 21 | QAbstractItemModel.__init__(self, parent) 22 | self.bv = bv 23 | self.columns = ["Offset", "Value", "References", "Address"] 24 | self.rows = [] 25 | self.update_rows(None) 26 | 27 | def update_rows(self, new_rows): 28 | self.beginResetModel() 29 | 30 | old_values = {} 31 | for info in self.rows: 32 | old_values[info['offset']] = info['value'] 33 | 34 | self.rows = [] 35 | if new_rows is None: 36 | self.endResetModel() 37 | return 38 | 39 | # Fill self.rows 40 | for info in new_rows: 41 | info['state'] = 'unchanged' if old_values.get(info['offset'], -1) == info['value'] else 'updated' 42 | self.rows.append(info) 43 | 44 | self.endResetModel() 45 | 46 | def index(self, row, column, parent): 47 | if parent.isValid() or column > len(self.columns) or row >= len(self.rows): 48 | return QModelIndex() 49 | return self.createIndex(row, column) 50 | 51 | def parent(self, child): 52 | return QModelIndex() 53 | 54 | def hasChildren(self, parent): 55 | return False 56 | 57 | def rowCount(self, parent): 58 | if parent.isValid(): 59 | return 0 60 | return len(self.rows) 61 | 62 | def columnCount(self, parent): 63 | return len(self.columns) 64 | 65 | def flags(self, index): 66 | f = super().flags(index) 67 | if self.columns[index.column()] == "Value": 68 | f |= Qt.ItemIsEditable 69 | return f 70 | 71 | def headerData(self, section, orientation, role): 72 | if role != Qt.DisplayRole: 73 | return None 74 | if orientation == Qt.Vertical: 75 | return None 76 | return self.columns[section] 77 | 78 | def data(self, index, role): 79 | if not index.isValid(): 80 | return None 81 | if index.row() < 0 or index.row() >= len(self.rows): 82 | return None 83 | 84 | info = self.rows[index.row()] 85 | debug_state = binjaplug.get_state(self.bv) 86 | 87 | if role == Qt.DisplayRole: 88 | # Format data into displayable text 89 | column = self.columns[index.column()] 90 | if column == "Value": 91 | conts = info['value'] 92 | if debug_state.remote_arch.endianness == Endianness.LittleEndian: 93 | conts = conts[::-1] 94 | text = conts.hex() 95 | elif column == "Offset": 96 | text = str(hex(info['offset'])) 97 | elif column == "Address": 98 | text = str(hex(info['address'])) 99 | elif column == "References": 100 | texts = [] 101 | for ref in info['refs']: 102 | if ref['source'] == 'register': 103 | register = ref['register'] 104 | if ref['dest'] == 'address': 105 | texts.append(register) 106 | elif ref['dest'] == 'value': 107 | texts.append('&' + register) 108 | text = ", ".join(texts) 109 | else: 110 | text = "???" 111 | return text 112 | elif role == Qt.UserRole: 113 | return info['state'] 114 | 115 | return None 116 | 117 | def setData(self, index, value, role): 118 | # Verify that we can edit this value 119 | if (self.flags(index) & Qt.EditRole) != Qt.EditRole: 120 | return False 121 | if len(value) == 0: 122 | return False 123 | 124 | info = self.rows[index.row()] 125 | debug_state = binjaplug.get_state(self.bv) 126 | 127 | old_val = info['value'] 128 | # Need to take string be hex and turn into bytes in the correct endianness 129 | if len(value) % 2 == 1: 130 | value = '0' + value 131 | try: 132 | new_val = bytes.fromhex(value) 133 | except: 134 | return False 135 | if debug_state.remote_arch.endianness == Endianness.LittleEndian: 136 | new_val = new_val[::-1] 137 | new_val = new_val.ljust(len(old_val), b'\x00') 138 | address = info['address'] 139 | 140 | if new_val == old_val: 141 | return False 142 | 143 | # Tell the debugger to update 144 | memory_view = debug_state.memory_view 145 | memory_view.write(address, new_val) 146 | 147 | # Update internal copy to show modification 148 | updated_val = memory_view.read(address, len(old_val)) 149 | 150 | # Make sure the debugger actually let us set the value 151 | self.rows[index.row()]['value'] = updated_val 152 | self.rows[index.row()]['state'] = 'modified' if updated_val == new_val else info['state'] 153 | 154 | self.dataChanged.emit(index, index, [role]) 155 | self.layoutChanged.emit() 156 | return True 157 | 158 | class DebugStackItemDelegate(QItemDelegate): 159 | def __init__(self, parent): 160 | QItemDelegate.__init__(self, parent) 161 | 162 | self.font = binaryninjaui.getMonospaceFont(parent) 163 | self.font.setKerning(False) 164 | self.baseline = QFontMetricsF(self.font).ascent() 165 | self.char_width = binaryninjaui.getFontWidthAndAdjustSpacing(self.font)[0] 166 | self.char_height = QFontMetricsF(self.font).height() 167 | self.char_offset = binaryninjaui.getFontVerticalOffset() 168 | 169 | self.expected_char_widths = [10, 20, 30, 20] 170 | 171 | def sizeHint(self, option, idx): 172 | return QSize(self.char_width * self.expected_char_widths[idx.column()] + 4, self.char_height) 173 | 174 | def paint(self, painter, option, idx): 175 | # Draw background highlight in theme style 176 | selected = option.state & QStyle.State_Selected != 0 177 | if selected: 178 | painter.setBrush(binaryninjaui.getThemeColor(binaryninjaui.ThemeColor.SelectionColor)) 179 | else: 180 | painter.setBrush(option.backgroundBrush) 181 | painter.setPen(Qt.NoPen) 182 | painter.drawRect(option.rect) 183 | 184 | text = idx.data() 185 | state = idx.data(Qt.UserRole) 186 | 187 | # Draw text depending on state 188 | painter.setFont(self.font) 189 | if state == 'updated': 190 | painter.setPen(option.palette.color(QPalette.Highlight).rgba()) 191 | elif state == 'modified': 192 | painter.setPen(binaryninjaui.getThemeColor(ThemeColor.OrangeStandardHighlightColor).rgba()) 193 | else: 194 | painter.setPen(option.palette.color(QPalette.WindowText).rgba()) 195 | painter.drawText(2 + option.rect.left(), self.char_offset + self.baseline + option.rect.top(), str(text)) 196 | 197 | def setEditorData(self, editor, idx): 198 | if idx.column() == 1: 199 | data = idx.data() 200 | editor.setText(data) 201 | 202 | 203 | class DebugStackWidget(QWidget, DockContextHandler): 204 | def __init__(self, parent, name, data): 205 | if not type(data) == BinaryView: 206 | raise Exception('expected widget data to be a BinaryView') 207 | 208 | self.bv = data 209 | 210 | QWidget.__init__(self, parent) 211 | DockContextHandler.__init__(self, self, name) 212 | self.actionHandler = UIActionHandler() 213 | self.actionHandler.setupActionHandler(self) 214 | 215 | self.table = QTableView(self) 216 | self.model = DebugStackModel(self.table, data) 217 | self.table.setModel(self.model) 218 | 219 | self.item_delegate = DebugStackItemDelegate(self) 220 | self.table.setItemDelegate(self.item_delegate) 221 | 222 | # self.table.setSortingEnabled(True) 223 | self.table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) 224 | self.table.setSelectionMode(QAbstractItemView.ExtendedSelection) 225 | 226 | self.table.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents) 227 | self.table.verticalHeader().setVisible(False) 228 | 229 | self.table.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) 230 | self.table.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel) 231 | 232 | self.table.resizeColumnsToContents() 233 | self.table.resizeRowsToContents() 234 | 235 | layout = QVBoxLayout() 236 | layout.setContentsMargins(0, 0, 0, 0) 237 | layout.setSpacing(0) 238 | layout.addWidget(self.table) 239 | self.setLayout(layout) 240 | 241 | def notifyOffsetChanged(self, offset): 242 | pass 243 | 244 | def notifyStackChanged(self, new_stack): 245 | self.model.update_rows(new_stack) 246 | 247 | def contextMenuEvent(self, event): 248 | self.m_contextMenuManager.show(self.m_menu, self.actionHandler) 249 | 250 | def shouldBeVisible(self, view_frame): 251 | if view_frame is None: 252 | return False 253 | else: 254 | return True 255 | 256 | -------------------------------------------------------------------------------- /dockwidgets/ThreadsWidget.py: -------------------------------------------------------------------------------- 1 | import binaryninjaui 2 | if "qt_major_version" in binaryninjaui.__dict__ and binaryninjaui.qt_major_version == 6: 3 | from PySide6 import QtCore 4 | from PySide6.QtCore import Qt, QAbstractItemModel, QModelIndex, QSize 5 | from PySide6.QtGui import QPalette, QFontMetricsF 6 | from PySide6.QtWidgets import QApplication, QHBoxLayout, QVBoxLayout, QWidget, QTableView, QItemDelegate, QStyle, QHeaderView, QAbstractItemView 7 | else: 8 | from PySide2 import QtCore 9 | from PySide2.QtCore import Qt, QAbstractItemModel, QModelIndex, QSize 10 | from PySide2.QtGui import QPalette, QFontMetricsF 11 | from PySide2.QtWidgets import QApplication, QHBoxLayout, QVBoxLayout, QWidget, QTableView, QItemDelegate, QStyle, QHeaderView, QAbstractItemView 12 | 13 | from binaryninja import BinaryView 14 | from binaryninjaui import DockContextHandler, UIActionHandler, ThemeColor 15 | 16 | from . import widget 17 | from .. import binjaplug 18 | 19 | class DebugThreadsListModel(QAbstractItemModel): 20 | def __init__(self, parent): 21 | QAbstractItemModel.__init__(self, parent) 22 | self.columns = ["TID", "Location"] 23 | self.rows = [] 24 | self.update_rows(None) 25 | 26 | # called from widget's notifyThreadsChanged() function 27 | # new_rows is list of {'tid':, 'rip':, 'selected':} 28 | def update_rows(self, new_rows): 29 | self.beginResetModel() 30 | 31 | old_threads = {} 32 | for (tid, ip) in self.rows: 33 | old_threads[tid] = ip 34 | 35 | # clear old data 36 | self.rows = [] 37 | self.row_info = [] 38 | if new_rows is None: 39 | self.endResetModel() 40 | return 41 | 42 | # set new data 43 | sel_row = None 44 | for info in new_rows: 45 | (tid, rip) = (info['tid'], info['ip']) 46 | # actual values for the table rows 47 | self.rows.append((tid, rip)) 48 | # parallel list of the incoming dict, augmented 49 | # (keys 'selected', 'bits', 'state' used in display) 50 | if info.get('selected', False): 51 | sel_row = len(self.rows)-1 52 | info['state'] = ['updated', 'unchanged'][old_threads.get(tid,-1) == rip] 53 | self.row_info.append(info) 54 | 55 | self.endResetModel() 56 | 57 | # return index to selected (row, col=0) 58 | if sel_row != None: 59 | return self.createIndex(sel_row, 0) 60 | 61 | def index(self, row, column, parent): 62 | if parent.isValid() or column > len(self.columns) or row >= len(self.rows): 63 | return QModelIndex() 64 | return self.createIndex(row, column) 65 | 66 | def parent(self, child): 67 | return QModelIndex() 68 | 69 | def hasChildren(self, parent): 70 | return False 71 | 72 | def rowCount(self, parent): 73 | if parent.isValid(): 74 | return 0 75 | return len(self.rows) 76 | 77 | def columnCount(self, parent): 78 | return len(self.columns) 79 | 80 | def flags(self, index): 81 | f = super().flags(index) 82 | 83 | if index.column() == 1: 84 | f |= Qt.ItemIsEditable 85 | return f 86 | 87 | def headerData(self, section, orientation, role): 88 | if role != Qt.DisplayRole: 89 | return None 90 | if orientation == Qt.Vertical: 91 | return None 92 | return self.columns[section] 93 | 94 | def data(self, index, role): 95 | if not index.isValid(): 96 | return None 97 | if index.row() < 0 or index.row() >= len(self.rows): 98 | return None 99 | 100 | contents = self.rows[index.row()][index.column()] 101 | info = self.row_info[index.row()] 102 | 103 | if role == Qt.DisplayRole: 104 | # Format data into displayable text 105 | if index.column() == 1: 106 | # Address is like a pointer 107 | text = '0x%x' % contents 108 | else: 109 | # TID should just be integer 110 | text = '%x' % contents 111 | return text 112 | elif role == Qt.UserRole: 113 | return info['state'] # 'updated', 'modified', 'unchanged' 114 | # TODO: look into Qt::CheckStateRole for whether thread selected or not 115 | 116 | return None 117 | 118 | # called back after user edits 119 | def setData(self, index, value, role): 120 | pass 121 | 122 | class DebugThreadsItemDelegate(QItemDelegate): 123 | def __init__(self, parent): 124 | QItemDelegate.__init__(self, parent) 125 | 126 | self.font = binaryninjaui.getMonospaceFont(parent) 127 | self.font.setKerning(False) 128 | self.baseline = QFontMetricsF(self.font).ascent() 129 | self.char_width = binaryninjaui.getFontWidthAndAdjustSpacing(self.font)[0] 130 | self.char_height = QFontMetricsF(self.font).height() 131 | self.char_offset = binaryninjaui.getFontVerticalOffset() 132 | 133 | self.expected_char_widths = [10, 32] 134 | 135 | def sizeHint(self, option, idx): 136 | return QSize(self.char_width * self.expected_char_widths[idx.column()] + 4, self.char_height) 137 | 138 | def paint(self, painter, option, idx): 139 | # Draw background highlight in theme style 140 | selected = option.state & QStyle.State_Selected != 0 141 | if selected: 142 | painter.setBrush(binaryninjaui.getThemeColor(binaryninjaui.ThemeColor.SelectionColor)) 143 | else: 144 | painter.setBrush(option.backgroundBrush) 145 | painter.setPen(Qt.NoPen) 146 | painter.drawRect(option.rect) 147 | 148 | text = idx.data() 149 | state = idx.data(Qt.UserRole) 150 | 151 | # Draw text depending on state 152 | painter.setFont(self.font) 153 | if state == 'updated': 154 | painter.setPen(option.palette.color(QPalette.Highlight).rgba()) 155 | elif state == 'modified': 156 | painter.setPen(binaryninjaui.getThemeColor(ThemeColor.OrangeStandardHighlightColor).rgba()) 157 | else: 158 | painter.setPen(option.palette.color(QPalette.WindowText).rgba()) 159 | painter.drawText(2 + option.rect.left(), self.char_offset + self.baseline + option.rect.top(), str(text)) 160 | 161 | def setEditorData(self, editor, idx): 162 | return None 163 | # TODO: add checkbox to select thread 164 | #if idx.column() == 1: 165 | # data = idx.data() 166 | # editor.setText(data) 167 | 168 | class DebugThreadsWidget(QWidget, DockContextHandler): 169 | def __init__(self, parent, name, data): 170 | if not type(data) == BinaryView: 171 | raise Exception('expected widget data to be a BinaryView') 172 | 173 | self.bv = data 174 | 175 | QWidget.__init__(self, parent) 176 | DockContextHandler.__init__(self, self, name) 177 | self.actionHandler = UIActionHandler() 178 | self.actionHandler.setupActionHandler(self) 179 | 180 | self.table = QTableView(self) 181 | self.model = DebugThreadsListModel(self.table) 182 | self.table.setModel(self.model) 183 | self.table.clicked.connect(self.threadRowClicked) 184 | 185 | self.item_delegate = DebugThreadsItemDelegate(self) 186 | self.table.setItemDelegate(self.item_delegate) 187 | 188 | # self.table.setSortingEnabled(True) 189 | self.table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) 190 | self.table.setSelectionMode(QAbstractItemView.ExtendedSelection) 191 | 192 | self.table.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents) 193 | self.table.verticalHeader().setVisible(False) 194 | 195 | self.table.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) 196 | self.table.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel) 197 | 198 | self.table.resizeColumnsToContents() 199 | self.table.resizeRowsToContents() 200 | self.table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch) 201 | 202 | layout = QVBoxLayout() 203 | layout.setContentsMargins(0, 0, 0, 0) 204 | layout.setSpacing(0) 205 | layout.addWidget(self.table) 206 | self.setLayout(layout) 207 | 208 | def notifyOffsetChanged(self, offset): 209 | pass 210 | 211 | # called from QTableView's clicked signal 212 | # index: QModelIndex 213 | def threadRowClicked(self, index): 214 | index = self.model.createIndex(index.row(), 0) 215 | tid_str = self.model.data(index, Qt.DisplayRole) 216 | #print('clicked to change to thread %s' % tid_str) 217 | stateObj = binjaplug.get_state(self.bv) 218 | if stateObj.connected and not stateObj.running: 219 | tid = int(tid_str, 16) 220 | stateObj.threads.current = tid 221 | stateObj.ui.context_display() 222 | stateObj.ui.on_step() 223 | else: 224 | print('cannot set thread in current state') 225 | 226 | # called from plugin's context_display() function 227 | def notifyThreadsChanged(self, new_threads): 228 | idx_selected = self.model.update_rows(new_threads) 229 | if idx_selected: 230 | self.table.setCurrentIndex(idx_selected) 231 | 232 | def contextMenuEvent(self, event): 233 | self.m_contextMenuManager.show(self.m_menu, self.actionHandler) 234 | 235 | def shouldBeVisible(self, view_frame): 236 | if view_frame is None: 237 | return False 238 | else: 239 | return True 240 | 241 | -------------------------------------------------------------------------------- /dockwidgets/widget.py: -------------------------------------------------------------------------------- 1 | import binaryninjaui 2 | if "qt_major_version" in binaryninjaui.__dict__ and binaryninjaui.qt_major_version == 6: 3 | from PySide6.QtWidgets import QApplication, QWidget 4 | else: 5 | from PySide2.QtWidgets import QApplication, QWidget 6 | 7 | from binaryninjaui import DockHandler 8 | import sys 9 | import traceback 10 | 11 | debug_dockwidgets = [] 12 | 13 | def create_widget(widget_class, name, parent, data, *args): 14 | # It is imperative this function return *some* value because Shiboken will try to deref what we return 15 | # If we return nothing (or throw) there will be a null pointer deref (and we won't even get to see why) 16 | # So in the event of an error or a nothing, return an empty widget that at least stops the crash 17 | try: 18 | widget = widget_class(parent, name, data, *args) 19 | 20 | if not widget: 21 | raise Exception('expected widget, got None') 22 | 23 | global debug_dockwidgets 24 | 25 | found = False 26 | for (bv, widgets) in debug_dockwidgets: 27 | if bv == data: 28 | widgets[name] = widget 29 | found = True 30 | 31 | if not found: 32 | debug_dockwidgets.append((data, { 33 | name: widget 34 | })) 35 | 36 | widget.destroyed.connect(lambda destroyed: destroy_widget(destroyed, widget, data, name)) 37 | 38 | return widget 39 | except Exception as e: 40 | traceback.print_exc(file=sys.stderr) 41 | return QWidget(parent) 42 | 43 | def destroy_widget(destroyed, old, data, name): 44 | # Gotta be careful to delete the correct widget here 45 | for (bv, widgets) in debug_dockwidgets: 46 | if bv == data: 47 | for (name, widget) in widgets.items(): 48 | if widget == old: 49 | # If there are no other references to it, this will be the only one and the call 50 | # will delete it and invoke __del__. 51 | widgets.pop(name) 52 | return 53 | 54 | 55 | def register_dockwidget(widget_class, name, area, orientation, default_visibility, *args): 56 | dock_handler = DockHandler.getActiveDockHandler() 57 | 58 | # create main debugger controls 59 | dock_handler.addDockWidget(name, lambda n,p,d: create_widget(widget_class, n, p, d, *args), area, orientation, default_visibility) 60 | 61 | def get_dockwidget(data, name): 62 | for (bv, widgets) in debug_dockwidgets: 63 | if bv == data: 64 | return widgets.get(name) 65 | 66 | return None 67 | 68 | -------------------------------------------------------------------------------- /documentation/resources/adapter_class_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vector35/deprecated_python_debugger/71d72f874a39787ff9a1a001cd417beb19ca08f1/documentation/resources/adapter_class_diagram.png -------------------------------------------------------------------------------- /documentation/resources/backend_arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vector35/deprecated_python_debugger/71d72f874a39787ff9a1a001cd417beb19ca08f1/documentation/resources/backend_arch.png -------------------------------------------------------------------------------- /documentation/resources/gui1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vector35/deprecated_python_debugger/71d72f874a39787ff9a1a001cd417beb19ca08f1/documentation/resources/gui1.png -------------------------------------------------------------------------------- /documentation/resources/gui2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vector35/deprecated_python_debugger/71d72f874a39787ff9a1a001cd417beb19ca08f1/documentation/resources/gui2.png -------------------------------------------------------------------------------- /documentation/resources/gui3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vector35/deprecated_python_debugger/71d72f874a39787ff9a1a001cd417beb19ca08f1/documentation/resources/gui3.png -------------------------------------------------------------------------------- /documentation/resources/gui4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vector35/deprecated_python_debugger/71d72f874a39787ff9a1a001cd417beb19ca08f1/documentation/resources/gui4.png -------------------------------------------------------------------------------- /examples/dacman_live_hilbert.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # display live code coverage of DACMAN Colecovision game running in MAME 4 | # drawn as Hilbert curve 5 | # 6 | # $ mame -v coleco -video soft -cart /path/to/DACMAN.ROM -window -nomax -resolution 560x432 -debugger gdbstub -debug 7 | # $ ./dacman_live_hilbert /path/to/DACMAN.ROM 8 | # 9 | 10 | import os 11 | import sys 12 | import math 13 | import time 14 | import struct 15 | import platform 16 | from collections import defaultdict 17 | 18 | import binaryninja 19 | from binaryninja.binaryview import BinaryViewType 20 | 21 | from PIL import Image, ImageDraw 22 | 23 | # globals 24 | n = None 25 | draw = None 26 | 27 | #------------------------------------------------------------------------------ 28 | # Hilbert curve mapping algorithms from: 29 | # https://en.wikipedia.org/wiki/Hilbert_curve 30 | #------------------------------------------------------------------------------ 31 | 32 | def rot(n, x, y, rx, ry): 33 | if ry == 0: 34 | if rx == 1: 35 | x = n-1 - x; 36 | y = n-1 - y; 37 | 38 | (y,x) = (x,y) 39 | 40 | return (x,y) 41 | 42 | def d2xy(n, d): 43 | (x,y,t) = (0,0,d) 44 | 45 | level = 1 46 | while level 0: 62 | rx = int((x & s) > 0) 63 | ry = int((y & s) > 0) 64 | d += s * s * ((3 * rx) ^ ry) 65 | (x, y) = rot(n, x, y, rx, ry) 66 | s //= 2 67 | 68 | return d 69 | 70 | #------------------------------------------------------------------------------ 71 | # Hilbert curve drawing helpers 72 | #------------------------------------------------------------------------------ 73 | 74 | # trace a Hilbert region by "wall following" 75 | def wall_follower(d0, d1): 76 | global n 77 | 78 | def ok(x, y): 79 | if x<0 or y<0: return False 80 | d = xy2d(n**2, x, y) 81 | #print('is %d within %d,%d' % (d, d0, d1)) 82 | return d>=0 and d>=d0 and d (%d,%d)' % (x1,y1,x2,y2)) 143 | draw.line((x1,y1,x2,y2), width=1, fill=color) 144 | 145 | def draw_region(start, stop, color1='#00ff00', color2=None): 146 | global draw 147 | trace = wall_follower(start, stop) 148 | draw.polygon(trace, outline=color1, fill=color2) 149 | 150 | #------------------------------------------------------------------------------ 151 | # main() 152 | #------------------------------------------------------------------------------ 153 | 154 | if __name__ == '__main__': 155 | # analyze functions 156 | fpath = sys.argv[1] 157 | bv = BinaryViewType.get_view_of_file(fpath) 158 | bv.update_analysis_and_wait() 159 | lowest = None 160 | highest = None 161 | addr2func = {} 162 | for f in bv.functions: 163 | addr_start = f.start 164 | addr_end = f.start + f.total_bytes 165 | 166 | if lowest==None or addr_start < lowest: 167 | lowest = addr_start 168 | if highest==None or addr_end >= highest: 169 | highest = addr_end 170 | 171 | addr2func[addr_start] = f 172 | 173 | print('lowest address: 0x%04X' % lowest) 174 | print('highest address: 0x%04X' % highest) 175 | 176 | # launch debugger, set breakpoints 177 | from debugger import DebugAdapter, gdblike 178 | adapter = gdblike.connect_sense('localhost', 23946) 179 | for addr in addr2func: 180 | print('setting breakpoint at %04X: %s' % (addr, addr2func[addr].symbol.full_name)) 181 | adapter.breakpoint_set(addr) 182 | 183 | # calculate image size 184 | pixels = 1 185 | while pixels < (highest-lowest): 186 | pixels *= 4 187 | n = int(math.sqrt(pixels)) 188 | print('n:', n) 189 | img = Image.new('RGB', (n,n)) 190 | draw = ImageDraw.Draw(img) 191 | 192 | # intialize pygame 193 | import pygame 194 | from pygame.locals import * 195 | pygame.init() 196 | surface = pygame.display.set_mode((4*n, 4*n), RESIZABLE) 197 | pygame.display.set_caption('DACMAN code coverage') 198 | 199 | # palette is "tab20" from matplotlib 200 | palette_i = 0 201 | palette = [ 202 | '#1F77B4', '#AEC7E8', '#FF7F0E', '#FFBB78', '#2CA02C', '#98DF8A', '#D62728', '#FF9896', 203 | '#9467BD', '#C5B0D5', '#8C564B', '#C49C94', '#E377C2', '#F7B6D2', '#7F7F7F', '#C7C7C7', 204 | '#BCBD22', '#DBDB8D', '#17BECF', '#9EDAE5' 205 | ] 206 | 207 | print('reading to rock, press any key!') 208 | input() 209 | 210 | while 1: 211 | # process pygame events 212 | for event in pygame.event.get(): 213 | if event.type == QUIT: 214 | pygame.quit() 215 | sys.exit() 216 | 217 | # wait for breakpoint, clear it 218 | (reason, data) = adapter.go() 219 | assert reason in [DebugAdapter.STOP_REASON.BREAKPOINT, DebugAdapter.STOP_REASON.SINGLE_STEP] 220 | pc = adapter.reg_read('pc') 221 | f = addr2func[pc] 222 | print('%s()' % f.symbol.full_name) 223 | adapter.breakpoint_clear(pc) 224 | 225 | # draw function 226 | addr_start = f.start 227 | addr_end = f.start + f.total_bytes 228 | if addr_end - addr_start < 4: 229 | continue 230 | print('drawing %s [0x%04X, 0x%04X)' % (f.symbol.full_name, addr_start, addr_end)) 231 | draw_region(addr_start - lowest, addr_end - lowest, None, palette[palette_i]) 232 | palette_i = (palette_i+1) % len(palette) 233 | 234 | # drawing to pygame 235 | raw_str = img.tobytes('raw', 'RGB') 236 | img_surface = pygame.image.fromstring(raw_str, (n, n), 'RGB') 237 | img_surface = pygame.transform.scale(img_surface, (4*n, 4*n)) 238 | surface.blit(img_surface, (0,0)) 239 | pygame.display.update() 240 | 241 | #time.sleep(.1) 242 | -------------------------------------------------------------------------------- /examples/md5_opc_freq.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # demonstrate use of headless debugger to count opcode frequency for an md5 calculation 4 | # 5 | # you probably want to run sometimes like: 6 | # $ ./md5_opc_freq.py ../testbins/md5/md5_x64-macos 7 | 8 | import os 9 | import sys 10 | import struct 11 | import platform 12 | from collections import defaultdict 13 | import binaryninja 14 | from binaryninja.binaryview import BinaryViewType 15 | 16 | RED = '\x1B[31m' 17 | GREEN = '\x1B[32m' 18 | YELLOW = '\x1B[93m' 19 | NORMAL = '\x1B[0m' 20 | 21 | # globals 22 | tally = defaultdict(int) 23 | 24 | # open for analysis 25 | fpath = sys.argv[1] 26 | bv = BinaryViewType.get_view_of_file(fpath) 27 | bv.update_analysis_and_wait() 28 | 29 | from debugger import DebugAdapter, lldb, gdb 30 | 31 | def show_stats(): 32 | global tally 33 | 34 | n_instrs = sum(tally.values()) 35 | print('\x1B[H', end='') # cursor to top left 36 | print('\x1B[J', end='') # clear screen from cursor down 37 | for instr in sorted(tally, key=lambda x: tally[x], reverse=True): 38 | print('instr:%s%s%s total:%s%d%s percentage:%s%.02f%%%s' % (\ 39 | YELLOW,instr.rjust(8),NORMAL, 40 | GREEN,tally[instr],NORMAL, 41 | RED,tally[instr]/n_instrs*100,NORMAL) 42 | ) 43 | # create debug adapter 44 | if platform.system() == 'Linux': 45 | adapt = gdb.DebugAdapterGdb() 46 | elif platform.system() == 'Darwin': 47 | adapt = lldb.DebugAdapterLLDB() 48 | else: 49 | raise Exception('unknown system!') 50 | adapt.exec(fpath, ['-sabcdefghijklmnopqrstuvwxyz']) 51 | 52 | # sense aslr situation, resolve symbols 53 | base = adapt.target_base() 54 | base_bv = bv.start 55 | delta = base - base_bv 56 | print('analysis rooted at %s0x%X%s, target in memory at %s0x%X%s, delta %s0x%X%s' % (\ 57 | GREEN,base_bv,NORMAL, 58 | GREEN,base,NORMAL, 59 | RED,delta,NORMAL)) 60 | 61 | for f in bv.functions: 62 | if f.symbol.full_name in ['MD5Init', '_MD5Init']: MD5Init = f.start + delta 63 | if f.symbol.full_name in ['MD5Update', '_MD5Update']: MD5Update = f.start + delta 64 | if f.symbol.full_name in ['MD5Final', '_MD5Final']: MD5Final = f.start + delta 65 | print(' MD5Init: %s0x%X%s' % (GREEN,MD5Init,NORMAL)) 66 | print('MD5Update: %s0x%X%s' % (GREEN,MD5Update,NORMAL)) 67 | print(' MD5Final: %s0x%X%s' % (GREEN,MD5Final,NORMAL)) 68 | 69 | print('press any key to continue') 70 | input() 71 | 72 | # go until MD5 starts 73 | adapt.breakpoint_set(MD5Init) 74 | (reason, data) = adapt.go() 75 | assert reason == DebugAdapter.STOP_REASON.BREAKPOINT 76 | assert adapt.reg_read('rip') == MD5Init 77 | adapt.breakpoint_clear(MD5Init) 78 | 79 | print('at MD5Init()') 80 | 81 | # step until MD5Final 82 | while 1: 83 | (reason, data) = adapt.step_into() 84 | assert reason in [DebugAdapter.STOP_REASON.BREAKPOINT, DebugAdapter.STOP_REASON.SINGLE_STEP] 85 | rip = adapt.reg_read('rip') 86 | opc = bv.get_disassembly(rip - delta).split()[0] 87 | tally[opc] += 1 88 | show_stats() 89 | #print('0x%X %s' % (rip, opc)) 90 | if adapt.reg_read('rip') == MD5Final: 91 | break 92 | 93 | n_instrs = sum(tally.values()) 94 | print('at MD5Final(), %d instructions so far' % n_instrs) 95 | 96 | # step until return to caller 97 | rsp = adapt.reg_read('rsp') 98 | stack = adapt.mem_read(rsp, 8) 99 | caller = struct.unpack(' 2 | 3 | 4 | 5 | Layer 1 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /media/icons/cancel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Layer 1 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /media/icons/connect.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Layer 1 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /media/icons/disconnect.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Layer 1 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /media/icons/pause.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Layer 1 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /media/icons/remove.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Layer 1 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /media/icons/restart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Layer 1 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /media/icons/resume.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Layer 1 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /media/icons/run.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Layer 1 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /media/icons/stepinto.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Layer 1 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /media/icons/stepout.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Layer 1 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /media/icons/stepover.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Layer 1 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /media/icons/stop.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Layer 1 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginmetadataversion": 2, 3 | "name": "Debugger", 4 | "author": "Vector35", 5 | "type": [ 6 | "ui", 7 | "helper" 8 | ], 9 | "api": [ 10 | "python3" 11 | ], 12 | "description": "Debugger support", 13 | "longdescription": "Debug Windows, Linux, and MacOS targets from within Binary Ninja.\n\n### Screenshots\n\n![](https://github.com/Vector35/deprecated_python_debugger/blob/master/media/gui.png?raw=true)\n![](https://github.com/Vector35/deprecated_python_debugger/blob/master/media/cli.png?raw=true)\n\n", 14 | "license": { 15 | "name": "MIT", 16 | "text": "Copyright 2020 Vector35\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." 17 | }, 18 | "platforms": [ 19 | "Darwin", 20 | "Windows", 21 | "Linux" 22 | ], 23 | "installinstructions": { 24 | "Darwin": "To install this manually, please see the \"Using Plugins\" section of the [Getting Started Guide](https://docs.binary.ninja/getting-started.html#using-plugins). Or use Binary Ninja's built in plugin manager.", 25 | "Windows": "To install this manually, please see the \"Using Plugins\" section of the [Getting Started Guide](https://docs.binary.ninja/getting-started.html#using-plugins). Or use Binary Ninja's built in plugin manager.", 26 | "Linux": "To install this manually, please see the \"Using Plugins\" section of the [Getting Started Guide](https://docs.binary.ninja/getting-started.html#using-plugins). Or use Binary Ninja's built in plugin manager." 27 | }, 28 | "version": "1.3.8", 29 | "minimumbinaryninjaversion": 2085 30 | } -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | colorama 2 | -------------------------------------------------------------------------------- /rsp.py: -------------------------------------------------------------------------------- 1 | import re 2 | import socket 3 | 4 | # custom exceptions 5 | class RspDisconnected(Exception): 6 | pass 7 | class RspAckMissing(Exception): 8 | pass 9 | class RspExpectedStartOfPacket(Exception): 10 | pass 11 | class RspGeneralError(Exception): 12 | pass 13 | 14 | # rsp connection class 15 | class RspConnection(): 16 | def __init__(self, sock): 17 | self.sock = sock 18 | self.acks_enabled = True 19 | self.server_capabilities = {} 20 | self.pktlen = 0xfff 21 | 22 | def acks_enable(self): 23 | self.acks_enabled = True 24 | 25 | def acks_disable(self): 26 | self.acks_enabled = False 27 | 28 | def ack_expect(self): 29 | if not self.acks_enabled: return 30 | 31 | resp = self.sock.recv(1) 32 | if resp == b'': 33 | raise RspDisconnected('disconnection while waiting for ack') 34 | if resp != b'+': 35 | raise RspAckMissing('got instead: %s' % str(resp)) 36 | return b'+' 37 | 38 | def ack_send(self): 39 | if not self.acks_enabled: return 40 | self.sock.send(b'+') 41 | 42 | def negotiate(self, client_capabilities): 43 | # collect all server capabilities as a reply to our client abilities 44 | reply = self.tx_rx('qSupported:' + client_capabilities) 45 | for line in reply.split(';'): 46 | if '=' in line: 47 | (name, val) = line.split('=') 48 | self.server_capabilities[name] = val 49 | else: 50 | self.server_capabilities[line] = None 51 | #for (name,val) in self.server_capabilities.items(): 52 | # print('%s = %s' % (name,val)) 53 | 54 | # store the maximum packet length 55 | self.pktlen = int(self.server_capabilities.get('PacketSize', '0xfff'), 16) 56 | 57 | # turn off acks if supported 58 | # (lldb appears to support this without advertising it in their capabilities list) 59 | #if 'QStartNoAckMode+' in self.server_capabilities: 60 | reply = self.tx_rx('QStartNoAckMode') 61 | if reply == 'OK': 62 | self.acks_enabled = False 63 | 64 | def send_raw(self, data: bytes): 65 | self.sock.send(data.encode('utf-8')) 66 | 67 | def send_payload(self, data: str): 68 | # packet is "$#" 69 | checksum = sum(map(ord, data)) % 256 70 | packet = '$' + data + '#' + ("%02x" % checksum) 71 | self.send_raw(packet) 72 | 73 | def recv_packet_data(self, decode=True): 74 | hexes = b'abcdefABCDEF0123456789' 75 | 76 | # start packet 77 | pkt = self.sock.recv(1) 78 | if pkt != b'$': 79 | raise RspExpectedStartOfPacket('got instead: %s' % str(pkt)) 80 | 81 | # consume until '#' and checksum bytes 82 | while not (len(pkt)>=3 and pkt[-3] == ord('#') and pkt[-2] in hexes and pkt[-1] in hexes): 83 | tmp = self.sock.recv(1) 84 | if tmp == b'': 85 | raise RspDisconnected('disconnection while receiving packet') 86 | pkt = pkt + tmp 87 | 88 | # acknowledge 89 | self.ack_send() 90 | 91 | result = pkt[1:-3].decode('utf-8') if decode else pkt[1:-3] 92 | return result 93 | 94 | def tx_rx(self, data, expect='ack_then_reply', handler_async_pkt=None): 95 | try: 96 | self.send_payload(data) 97 | 98 | reply = None 99 | 100 | if expect == 'nothing': 101 | reply = '' 102 | elif expect == 'ack_then_reply': 103 | self.ack_expect() 104 | reply = self.recv_packet_data() 105 | elif expect == 'host_io': 106 | self.ack_expect() 107 | reply = self.recv_packet_data(False) 108 | if reply[0:1] != b'F': 109 | raise RspGeneralError('host i/o packet did not start with F: ' + str(reply)) 110 | (result_errno, result, errno, attachment) = (None, None, None, None) 111 | # split off attachment 112 | if b';' in reply: 113 | (result_errno, attachment) = reply.split(b';', 1) 114 | attachment = binary_decode(attachment) 115 | else: 116 | result_errno = reply 117 | # split off errno 118 | result_errno = result_errno[1:].decode('utf-8') 119 | if ',' in result_errno: 120 | (result, errno) = result_errno.split(',') 121 | errno = int(errno, 16) 122 | else: 123 | result = result_errno 124 | # return result 125 | result = int(result, 16) 126 | return(result, errno, attachment) 127 | elif expect == 'mixed_output_ack_then_reply': 128 | ack_received = False 129 | while 1: 130 | peek1 = self.sock.recv(1, socket.MSG_PEEK) 131 | 132 | if peek1 == b'': 133 | # this happens in lldb/debugserver if target is killed (eg: ctrl+c) 134 | raise RspDisconnected('recv() returned empty, backend disconnected') 135 | 136 | if peek1 == b'+': 137 | if ack_received: 138 | raise RspGeneralError('received two acks, somethings wrong') 139 | self.sock.recv(1) 140 | ack_received = True 141 | continue 142 | 143 | if peek1 != b'$': 144 | raise RspExpectedStartOfPacket('got: %s' % self.sock.recv(16)) 145 | reply = self.recv_packet_data() 146 | if reply[0] == 'O': 147 | if handler_async_pkt: 148 | handler_async_pkt(reply) 149 | else: 150 | # return first non-output packet 151 | break 152 | if not ack_received and self.acks_enabled: 153 | raise RspGeneralError('expected ack, none received') 154 | result = reply 155 | elif expect == 'ack_then_ok': 156 | self.ack_expect() 157 | reply = self.recv_packet_data() 158 | if reply != 'OK': 159 | raise RspGeneralError('expected OK, got: %s' % reply) 160 | elif expect == 'ack_then_empty': 161 | self.ack_expect() 162 | reply = self.recv_packet_data() 163 | if reply != '': 164 | raise RspGeneralError('expected empty, got: %s' % reply) 165 | else: 166 | print('dunno how to expect %s' % expect) 167 | 168 | if '*' in reply: 169 | reply = un_rle(reply) 170 | 171 | return reply 172 | 173 | except OSError: 174 | raise RspDisconnected('disconnection while transmitting') 175 | 176 | def get_xml(self, fname): 177 | # https://sourceware.org/gdb/current/onlinedocs/gdb/General-Query-Packets.html#qXfer-target-description-read 178 | #print('downloading %s' % fname) 179 | xml = '' 180 | offs = 0 181 | pktsize = int(self.server_capabilities.get('PacketSize', '1000'), 16) 182 | while 1: 183 | data = self.tx_rx('qXfer:features:read:%s:%X,%X' % (fname, offs, pktsize), 'ack_then_reply') 184 | if not data[0] in ['l', 'm']: 185 | raise RspGeneralError('acquiring xml') 186 | if data[1:]: 187 | #print('read 0x%X bytes' % len(tmp)) 188 | tmp = un_rle(data[1:]) 189 | xml += tmp 190 | offs += len(tmp) 191 | if data[0] == 'l': 192 | break 193 | 194 | return xml 195 | 196 | #-------------------------------------------------------------------------- 197 | # GDB RSP FUNCTIONS (HIGHER LEVEL) 198 | #-------------------------------------------------------------------------- 199 | 200 | # https://sourceware.org/gdb/current/onlinedocs/gdb/Overview.html#Binary-Data 201 | # see "The binary data representation uses..." 202 | def binary_decode(data): 203 | result = b'' 204 | skip = 0 205 | for (i,val) in enumerate(data): 206 | if skip: 207 | skip = False 208 | elif val == 0x7d: # '}' 209 | result += bytes([data[i+1] ^ 0x20]) 210 | skip = True 211 | elif val == 0x2a: # '*' 212 | repeat = data[i+1] - 29 213 | result = result + bytes([result[-1]] * repeat) 214 | skip = True 215 | else: 216 | result += bytes([val]) 217 | return result 218 | 219 | def un_rle(data): 220 | if not '*' in data: 221 | return data 222 | 223 | skip = 0 224 | result = '' 225 | for (i,char) in enumerate(data): 226 | if skip: 227 | skip = False 228 | elif char == '*': 229 | repeat = ord(data[i+1])-29 230 | result = result + result[-1]*repeat 231 | skip = True 232 | else: 233 | result += char 234 | 235 | return result 236 | 237 | def packet_T_to_dict(data, lookup_reg={}): 238 | # map the info to a context dictionary 239 | context = {} 240 | context['signal'] = int(data[1:3], 16) 241 | 242 | for key_vals in data[3:].split(';'): 243 | if not key_vals: 244 | continue 245 | 246 | if not ':' in key_vals: 247 | raise RspGeneralError('expected \':\' in packet T reply: %s' % key_vals) 248 | 249 | (key, val) = key_vals.split(':') 250 | val = un_rle(val) 251 | 252 | if key == 'thread': 253 | tid = None 254 | if val.startswith('p'): 255 | if not '.' in val: 256 | raise RspGeneralError('expected \'.\' in thread value of packet T reply: %s' % reply) 257 | (core_id, thread_id) = val[1:].split('.') 258 | # TODO: deal with cores 259 | context['thread'] = int(thread_id, 16) 260 | else: 261 | context['thread'] = int(val, 16) 262 | 263 | elif re.match(r'^[0-9a-fA-F]+$', key): 264 | rid = int(key, 16) 265 | reg_name = lookup_reg.get(rid, 'r%d' % rid) 266 | val = int(''.join(reversed([val[i:i+2] for i in range(0,len(val),2)])), 16) 267 | context[reg_name] = val 268 | else: 269 | # 'metype', 'mecount', 'medata', 'memory', etc. 270 | context[key] = val 271 | 272 | return context 273 | -------------------------------------------------------------------------------- /testbins/Makefile-android: -------------------------------------------------------------------------------- 1 | # make -f Makefile-android to cross-compile on linux/macos 2 | 3 | # you can override this with environment variable, depending on where your NDK is installed 4 | 5 | # the naming convention here is _ 6 | # where is standard -- 7 | # and vendor is omitted 8 | 9 | # Android 5.0 and later only support position-independent executables (-fPIE) 10 | # see also: -fPIC 11 | 12 | NDK ?= $(HOME)/android-ndk-r15c 13 | 14 | SYSR = $(NDK)/platforms/android-23/arch-arm 15 | GCC = $(NDK)/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-gcc 16 | AS = $(NDK)/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-as 17 | LD = $(NDK)/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-ld 18 | 19 | SYSR_64 = $(NDK)/platforms/android-23/arch-arm64 20 | GCC_64 = $(NDK)/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-gcc 21 | AS_64 = $(NDK)/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-as 22 | LD_64 = $(NDK)/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-ld 23 | 24 | TARGETS_ARMV7 = helloworld_pie_armv7-android helloworld_loop_pie_armv7-android helloworld_thread_pie_armv7-android do_exception_pie_armv7-android asmtest_armv7-android 25 | TARGETS_AARCH64 = helloworld_pie_aarch64-android helloworld_loop_pie_aarch64-android helloworld_thread_pie_aarch64-android do_exception_pie_aarch64-android asmtest_aarch64-android 26 | TARGETS_ALL = $(TARGETS_ARMV7) $(TARGETS_AARCH64) 27 | 28 | # first target is default target when no target specified 29 | all: $(TARGETS_ALL) 30 | 31 | armv7: $(TARGETS_ARMV7) 32 | 33 | aarch64: $(TARGETS_AARCH64) 34 | 35 | push: 36 | adb push $(TARGETS_ALL) /data/local/tmp 37 | 38 | clean: 39 | rm -rf $(TARGETS_ALL) *.o 40 | 41 | #------------------------------------------------------------------------------ 42 | # armv7 43 | #------------------------------------------------------------------------------ 44 | helloworld_pie_armv7-android: helloworld.c 45 | $(GCC) -march=armv7-a --sysroot=$(SYSR) -pie -DOS_IS_ANDROID -DARCH_IS_ARMV7 helloworld.c -o helloworld_pie_armv7-android 46 | 47 | helloworld_loop_pie_armv7-android: helloworld_loop.c 48 | $(GCC) -march=armv7-a --sysroot=$(SYSR) -pie -DOS_IS_ANDROID -DARCH_IS_ARMV7 helloworld_loop.c -o helloworld_loop_pie_armv7-android 49 | 50 | helloworld_thread_pie_armv7-android: helloworld_thread.c 51 | $(GCC) -march=armv7-a --sysroot=$(SYSR) -pie -DOS_IS_ANDROID -DARCH_IS_ARMV7 -std=c11 helloworld_thread.c -o helloworld_thread_pie_armv7-android 52 | 53 | do_exception_pie_armv7-android: do_exception.c 54 | $(GCC) -march=armv7-a --sysroot=$(SYSR) -pie -DOS_IS_ANDROID -DARCH_IS_ARMV7 -std=c11 do_exception.c -o do_exception_pie_armv7-android 55 | 56 | asmtest_armv7-android: asmtest_armv7-android.o 57 | $(LD) -m elf32-littlearm asmtest_armv7-android.o -o asmtest_armv7-android 58 | 59 | asmtest_armv7-android.o: asmtest_armv7-android.s 60 | $(AS) asmtest_armv7-android.s -o asmtest_armv7-android.o 61 | 62 | #------------------------------------------------------------------------------ 63 | # aarch64 64 | #------------------------------------------------------------------------------ 65 | helloworld_pie_aarch64-android: helloworld.c 66 | $(GCC_64) --sysroot=$(SYSR_64) -pie -DOS_IS_ANDROID -DARCH_IS_AARCH64 helloworld.c -o helloworld_pie_aarch64-android 67 | 68 | helloworld_loop_pie_aarch64-android: helloworld_loop.c 69 | $(GCC_64) --sysroot=$(SYSR_64) -pie -DOS_IS_ANDROID -DARCH_IS_AARCH64 helloworld_loop.c -o helloworld_loop_pie_aarch64-android 70 | 71 | helloworld_thread_pie_aarch64-android: helloworld_thread.c 72 | $(GCC_64) --sysroot=$(SYSR_64) -pie -DOS_IS_ANDROID -DARCH_IS_AARCH64 -std=c11 helloworld_thread.c -o helloworld_thread_pie_aarch64-android 73 | 74 | do_exception_pie_aarch64-android: do_exception.c 75 | $(GCC_64) --sysroot=$(SYSR_64) -pie -DOS_IS_ANDROID -DARCH_IS_AARCH64 -std=c11 do_exception.c -o do_exception_pie_aarch64-android 76 | 77 | asmtest_aarch64-android: asmtest_aarch64-android.o 78 | $(LD_64) -m aarch64elf asmtest_aarch64-android.o -o asmtest_aarch64-android 79 | 80 | asmtest_aarch64-android.o: asmtest_aarch64-android.s 81 | $(AS_64) asmtest_aarch64-android.s -o asmtest_aarch64-android.o 82 | -------------------------------------------------------------------------------- /testbins/Makefile-linux: -------------------------------------------------------------------------------- 1 | # make -f Makefile-linux 2 | 3 | TARGETS_X86 = helloworld_x86-linux helloworld_loop_x86-linux helloworld_thread_x86-linux helloworld_func_x86-linux helloworld_pie_x86-linux helloworld_thread_pie_x86-linux helloworld_loop_pie_x86-linux helloworld_func_pie_x86-linux asmtest_x86-linux do_exception_pie_x86-linux many_stdlib_calls_x86-linux cat_x86-linux 4 | TARGETS_X64 = helloworld_x64-linux helloworld_loop_x64-linux helloworld_thread_x64-linux helloworld_func_x64-linux helloworld_pie_x64-linux helloworld_thread_pie_x64-linux helloworld_loop_pie_x64-linux helloworld_func_pie_x64-linux asmtest_x64-linux do_exception_pie_x64-linux many_stdlib_calls_x64-linux hello_x64-linux indirect_calls_x64-linux missing_switch_case_x64-linux cat_x64-linux 5 | TARGETS_ALL = $(TARGETS_X86) $(TARGETS_X64) 6 | 7 | all: $(TARGETS_ALL) 8 | 9 | clean: 10 | rm -rf $(TARGETS_ALL) *.o 11 | 12 | #------------------------------------------------------------------------------ 13 | # 32-bit 14 | #------------------------------------------------------------------------------ 15 | 16 | helloworld_x86-linux: helloworld.c 17 | gcc -W -m32 -no-pie helloworld.c -o helloworld_x86-linux 18 | 19 | helloworld_thread_x86-linux: helloworld_thread.c 20 | gcc -W -m32 -no-pie helloworld_thread.c -lpthread -o helloworld_thread_x86-linux 21 | 22 | helloworld_loop_x86-linux: helloworld_loop.c 23 | gcc -W -m32 -no-pie helloworld_loop.c -o helloworld_loop_x86-linux 24 | 25 | helloworld_func_x86-linux: helloworld_func.c 26 | gcc -W -m32 -no-pie helloworld_func.c -o helloworld_func_x86-linux 27 | 28 | helloworld_pie_x86-linux: helloworld.c 29 | gcc -W -m32 -pie helloworld.c -o helloworld_pie_x86-linux 30 | 31 | helloworld_thread_pie_x86-linux: helloworld_thread.c 32 | gcc -W -m32 -pie helloworld_thread.c -lpthread -o helloworld_thread_pie_x86-linux 33 | 34 | helloworld_loop_pie_x86-linux: helloworld_loop.c 35 | gcc -W -m32 -pie helloworld_loop.c -o helloworld_loop_pie_x86-linux 36 | 37 | helloworld_func_pie_x86-linux: helloworld_func.c 38 | gcc -W -m32 -pie helloworld_func.c -o helloworld_func_pie_x86-linux 39 | 40 | many_stdlib_calls_x86-linux: many_stdlib_calls.c 41 | gcc -W -m32 -pie many_stdlib_calls.c -o many_stdlib_calls_x86-linux 42 | 43 | cat_x86-linux: cat.c 44 | gcc -W -m32 -pie cat.c -o cat_x86-linux 45 | 46 | do_exception_pie_x86-linux: do_exception.c 47 | gcc -W -m32 -pie -z execstack -g -DARCH_IS_X86 do_exception.c -o do_exception_pie_x86-linux 48 | 49 | asmtest_x86-linux: asmtest_x86.asm 50 | nasm asmtest_x86.asm -f elf32 -DOS_IS_LINUX -o asmtest_x86-linux.o 51 | ld -m elf_i386 asmtest_x86-linux.o -o asmtest_x86-linux 52 | 53 | #------------------------------------------------------------------------------ 54 | # 64-bit 55 | #------------------------------------------------------------------------------ 56 | 57 | helloworld_x64-linux: helloworld.c 58 | gcc -W -no-pie helloworld.c -o helloworld_x64-linux 59 | 60 | helloworld_thread_x64-linux: helloworld_thread.c 61 | gcc -W -no-pie helloworld_thread.c -lpthread -o helloworld_thread_x64-linux 62 | 63 | helloworld_loop_x64-linux: helloworld_loop.c 64 | gcc -W -no-pie helloworld_loop.c -o helloworld_loop_x64-linux 65 | 66 | helloworld_func_x64-linux: helloworld_func.c 67 | gcc -W -no-pie helloworld_func.c -o helloworld_func_x64-linux 68 | 69 | helloworld_pie_x64-linux: helloworld.c 70 | gcc -W -pie helloworld.c -o helloworld_pie_x64-linux 71 | 72 | helloworld_thread_pie_x64-linux: helloworld_thread.c 73 | gcc -W -pie helloworld_thread.c -lpthread -o helloworld_thread_pie_x64-linux 74 | 75 | helloworld_loop_pie_x64-linux: helloworld_loop.c 76 | gcc -W -pie helloworld_loop.c -o helloworld_loop_pie_x64-linux 77 | 78 | helloworld_func_pie_x64-linux: helloworld_func.c 79 | gcc -W -pie helloworld_func.c -o helloworld_func_pie_x64-linux 80 | 81 | many_stdlib_calls_x64-linux: many_stdlib_calls.c 82 | gcc -W -pie many_stdlib_calls.c -o many_stdlib_calls_x64-linux 83 | 84 | cat_x64-linux: cat.c 85 | gcc -W -pie cat.c -o cat_x64-linux 86 | 87 | do_exception_pie_x64-linux: do_exception.c 88 | gcc -W -pie -z execstack -DARCH_IS_X64 do_exception.c -o do_exception_pie_x64-linux 89 | 90 | asmtest_x64-linux: asmtest_x64.asm 91 | nasm asmtest_x64.asm -f elf64 -DOS_IS_LINUX -o asmtest_x64-linux.o 92 | ld -m elf_x86_64 asmtest_x64-linux.o -o asmtest_x64-linux 93 | 94 | hello_x64-linux: hello_x64.asm 95 | nasm hello_x64.asm -f elf64 -DOS_IS_LINUX -o hello_x64-linux.o 96 | ld -m elf_x86_64 hello_x64-linux.o -o hello_x64-linux 97 | 98 | indirect_calls_x64-linux: indirect_calls_x64.asm 99 | nasm indirect_calls_x64.asm -f elf64 -DOS_IS_LINUX -o indirect_calls_x64-linux.o 100 | ld -m elf_x86_64 indirect_calls_x64-linux.o -o indirect_calls_x64-linux 101 | 102 | missing_switch_case_x64-linux: missing_switch_case_x64.asm 103 | nasm missing_switch_case_x64.asm -f elf64 -DOS_IS_LINUX -o missing_switch_case_x64-linux.o 104 | ld -m elf_x86_64 missing_switch_case_x64-linux.o -o missing_switch_case_x64-linux 105 | 106 | -------------------------------------------------------------------------------- /testbins/Makefile-macos: -------------------------------------------------------------------------------- 1 | # make -f Makefile-macosos 2 | 3 | TARGETS = helloworld_x64-macos exitcode_x64-macos helloworld_loop_x64-macos helloworld_thread_x64-macos helloworld_func_x64-macos helloworld_recursion_x64-macos helloworld_pie_x64-macos helloworld_thread_pie_x64-macos helloworld_loop_pie_x64-macos helloworld_func_pie_x64-macos helloworld_recursion_pie_x64-macos asmtest_x64-macos hello_x64-macos nopspeed_x64-macos cat_x64-macos commandline_test_x64-macos helloworld_objc_x64-macos helloworld_virtual_x64-macos do_exception_x64-macos undiscovered_func_x64-macos undiscovered_func2_x64-macos indirect_calls_x64-macos many_stdlib_calls_x64-macos analysis_propagation_x64-macos missing_switch_case_x64-macos 4 | 5 | all: $(TARGETS) 6 | 7 | clean: 8 | rm -rf $(TARGETS) *.o 9 | 10 | helloworld_x64-macos: helloworld.c 11 | gcc -Wl,-no_pie helloworld.c -o helloworld_x64-macos 12 | 13 | exitcode_x64-macos: exitcode.c 14 | gcc -Wl,-no_pie exitcode.c -o exitcode_x64-macos 15 | 16 | helloworld_thread_x64-macos: helloworld_thread.c 17 | gcc -Wl,-no_pie helloworld_thread.c -o helloworld_thread_x64-macos 18 | 19 | helloworld_loop_x64-macos: helloworld_loop.c 20 | gcc -Wl,-no_pie helloworld_loop.c -o helloworld_loop_x64-macos 21 | 22 | helloworld_func_x64-macos: helloworld_func.c 23 | gcc -Wl,-no_pie helloworld_func.c -o helloworld_func_x64-macos 24 | 25 | helloworld_recursion_x64-macos: helloworld_recursion.c 26 | gcc -Wl,-no_pie helloworld_recursion.c -o helloworld_recursion_x64-macos 27 | 28 | helloworld_pie_x64-macos: helloworld.c 29 | gcc -Wl,-pie helloworld.c -o helloworld_pie_x64-macos 30 | 31 | helloworld_thread_pie_x64-macos: helloworld_thread.c 32 | gcc -Wl,-pie helloworld_thread.c -o helloworld_thread_pie_x64-macos 33 | 34 | helloworld_loop_pie_x64-macos: helloworld_loop.c 35 | gcc -Wl,-pie helloworld_loop.c -o helloworld_loop_pie_x64-macos 36 | 37 | helloworld_func_pie_x64-macos: helloworld_func.c 38 | gcc -Wl,-pie helloworld_func.c -o helloworld_func_pie_x64-macos 39 | 40 | helloworld_recursion_pie_x64-macos: helloworld_recursion.c 41 | gcc -Wl,-pie helloworld_recursion.c -o helloworld_recursion_pie_x64-macos 42 | 43 | helloworld_objc_x64-macos: helloworld_objc.m 44 | gcc -Wl,-no_pie helloworld_objc.m -o helloworld_objc_x64-macos -framework AppKit -framework Carbon -framework Foundation 45 | 46 | helloworld_virtual_x64-macos: helloworld_virtual.cpp 47 | g++ -Wl,-no_pie helloworld_virtual.cpp -o helloworld_virtual_x64-macos 48 | 49 | many_stdlib_calls_x64-macos: many_stdlib_calls.c 50 | gcc -Wl,-no_pie many_stdlib_calls.c -o many_stdlib_calls_x64-macos 51 | 52 | asmtest_x64-macos: asmtest_x64.asm 53 | nasm -f macho64 -DOS_IS_MACOS asmtest_x64.asm -o asmtest_x64-macos.o 54 | ld -macosx_version_min 10.7.0 -lSystem asmtest_x64-macos.o -o asmtest_x64-macos 55 | 56 | hello_x64-macos: hello_x64.asm 57 | nasm -f macho64 -DOS_IS_MACOS hello_x64.asm -o hello_x64-macos.o 58 | ld -macosx_version_min 10.7.0 -lSystem hello_x64-macos.o -o hello_x64-macos 59 | 60 | analysis_propagation_x64-macos: analysis_propagation_x64.asm 61 | nasm -f macho64 -DOS_IS_MACOS analysis_propagation_x64.asm -o analysis_propagation_x64-macos.o -l analysis_propagation_x64-macos.lst 62 | ld -macosx_version_min 10.7.0 -lSystem analysis_propagation_x64-macos.o -o analysis_propagation_x64-macos 63 | strip analysis_propagation_x64-macos 64 | ./rm_func_starts.py ./analysis_propagation_x64-macos 65 | 66 | missing_switch_case_x64-macos: missing_switch_case_x64.asm 67 | nasm -f macho64 -DOS_IS_MACOS missing_switch_case_x64.asm -o missing_switch_case_x64-macos.o -l missing_switch_case_x64-macos.lst 68 | ld -macosx_version_min 10.7.0 -lSystem missing_switch_case_x64-macos.o -o missing_switch_case_x64-macos 69 | strip missing_switch_case_x64-macos 70 | ./rm_func_starts.py ./missing_switch_case_x64-macos 71 | 72 | undiscovered_func_x64-macos: undiscovered_func_x64.asm 73 | nasm -f macho64 -DOS_IS_MACOS undiscovered_func_x64.asm -o undiscovered_func_x64-macos.o 74 | ld -macosx_version_min 10.7.0 -lSystem undiscovered_func_x64-macos.o -o undiscovered_func_x64-macos 75 | 76 | undiscovered_func2_x64-macos: undiscovered_func2_x64.asm 77 | nasm -f macho64 -DOS_IS_MACOS undiscovered_func2_x64.asm -o undiscovered_func2_x64-macos.o 78 | ld -macosx_version_min 10.7.0 -lSystem undiscovered_func2_x64-macos.o -o undiscovered_func2_x64-macos 79 | strip undiscovered_func2_x64-macos 80 | ./rm_func_starts.py ./undiscovered_func2_x64-macos 81 | 82 | indirect_calls_x64-macos: indirect_calls_x64.asm 83 | nasm -f macho64 -DOS_IS_MACOS indirect_calls_x64.asm -o indirect_calls_x64-macos.o 84 | ld -macosx_version_min 10.7.0 -lSystem indirect_calls_x64-macos.o -o indirect_calls_x64-macos 85 | strip indirect_calls_x64-macos 86 | ./rm_func_starts.py ./indirect_calls_x64-macos 87 | 88 | do_exception_x64-macos: do_exception.c 89 | gcc -Wl,-no_pie -DARCH_IS_X64 do_exception.c -o do_exception_x64-macos 90 | 91 | # 32-bit macos application (need to modify MacOSX Architectures.xcspec on Xcode 10+) 92 | sysroot := $(shell xcode-select -p) 93 | helloworld_i386: helloworld.c 94 | gcc -Wl,-pie -arch i386 -isysroot "$(sysroot)/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.13.sdk" helloworld.c -o helloworld_i386-macos 95 | 96 | # Sample app with mmap and shellcode 97 | nopspeed_x64-macos: nopspeed.c 98 | gcc -Wl,-pie -O3 nopspeed.c -o nopspeed_x64-macos 99 | 100 | cat_x64-macos: cat.c 101 | gcc -Wl,-pie -O3 cat.c -o cat_x64-macos 102 | 103 | commandline_test_x64-macos: commandline_test.c 104 | gcc -Wl,-pie -O3 commandline_test.c -o commandline_test_x64-macos 105 | 106 | -------------------------------------------------------------------------------- /testbins/Makefile-win32: -------------------------------------------------------------------------------- 1 | # nmake -f Makefile-win32 from within "x86 Native Tools Command Prompt" 2 | 3 | TARGETS_ALL = helloworld_x86-windows.exe exitcode_x86-windows.exe helloworld_loop_x86-windows.exe helloworld_thread_x86-windows.exe helloworld_func_x86-windows.exe helloworld_pie_x86-windows.exe helloworld_loop_pie_x86-windows.exe helloworld_thread_pie_x86-windows.exe helloworld_func_pie_x86-windows.exe asmtest_x86-windows.exe do_exception_pie_x86-windows.exe 4 | 5 | all: $(TARGETS_ALL) 6 | 7 | clean: 8 | del /Q $(TARGETS_ALL) *.obj *.pdb 9 | 10 | helloworld_x86-windows.exe: helloworld.c 11 | cl helloworld.c /Fe:helloworld_x86-windows.exe /link /DYNAMICBASE:NO 12 | 13 | exitcode_x86-windows.exe: exitcode.c 14 | cl exitcode.c /Fe:exitcode_x86-windows.exe /link /DYNAMICBASE:NO 15 | 16 | helloworld_thread_x86-windows.exe: helloworld_thread.c 17 | cl helloworld_thread.c /Fe:helloworld_thread_x86-windows.exe /link /DYNAMICBASE:NO 18 | 19 | helloworld_loop_x86-windows.exe: helloworld_loop.c 20 | cl helloworld_loop.c /Fe:helloworld_loop_x86-windows.exe /link /DYNAMICBASE:NO 21 | 22 | helloworld_func_x86-windows.exe: helloworld_func.c 23 | cl helloworld_func.c /Fe:helloworld_func_x86-windows.exe /Zi /link /DYNAMICBASE:NO 24 | 25 | helloworld_pie_x86-windows.exe: helloworld.c 26 | cl helloworld.c /Fe:helloworld_pie_x86-windows.exe /link 27 | 28 | helloworld_thread_pie_x86-windows.exe: helloworld_thread.c 29 | cl helloworld_thread.c /Fe:helloworld_thread_pie_x86-windows.exe /link 30 | 31 | helloworld_loop_pie_x86-windows.exe: helloworld_loop.c 32 | cl helloworld_loop.c /Fe:helloworld_loop_pie_x86-windows.exe /link 33 | 34 | helloworld_func_pie_x86-windows.exe: helloworld_func.c 35 | cl helloworld_func.c /Fe:helloworld_func_pie_x86-windows.exe /link 36 | 37 | do_exception_pie_x86-windows.exe: do_exception.c 38 | cl do_exception.c /Fe:do_exception_pie_x86-windows.exe /link 39 | 40 | asmtest_x86-windows.exe: asmtest_x86-windows.obj 41 | link asmtest_x86-windows.obj kernel32.lib /ENTRY:WinMain /SUBSYSTEM:CONSOLE /DYNAMICBASE:NO /OUT:asmtest_x86-windows.exe 42 | 43 | asmtest_x86-windows.obj: asmtest_x86.asm 44 | nasm -f win32 -DOS_IS_WINDOWS asmtest_x86.asm -o asmtest_x86-windows.obj 45 | -------------------------------------------------------------------------------- /testbins/Makefile-win64: -------------------------------------------------------------------------------- 1 | # nmake -f Makefile-win64 from within "x64 Native Tools Command Prompt" 2 | 3 | TARGETS_ALL = helloworld_x64-windows.exe exitcode_x64-windows.exe helloworld_loop_x64-windows.exe helloworld_thread_x64-windows.exe helloworld_func_x64-windows.exe helloworld_pie_x64-windows.exe helloworld_loop_pie_x64-windows.exe helloworld_thread_pie_x64-windows.exe helloworld_func_pie_x64-windows.exe asmtest_x64-windows.exe hh1.exe do_exception_pie_x64-windows.exe hello_x64-windows.exe indirect_calls_x64-windows.exe missing_switch_case_x64-windows.exe cat_x64-windows.exe 4 | 5 | all: $(TARGETS_ALL) 6 | 7 | clean: 8 | del /Q $(TARGETS_ALL) *.obj *.pdb 9 | 10 | helloworld_x64-windows.exe: helloworld.c 11 | cl helloworld.c /Fe:helloworld_x64-windows.exe /link /DYNAMICBASE:NO 12 | 13 | cat_x64-windows.exe: cat.c 14 | cl cat.c /Fe:cat_x64-windows.exe /link /DYNAMICBASE:NO 15 | 16 | exitcode_x64-windows.exe: exitcode.c 17 | cl exitcode.c /Fe:exitcode_x64-windows.exe /link /DYNAMICBASE:NO 18 | 19 | helloworld_thread_x64-windows.exe: helloworld_thread.c 20 | cl helloworld_thread.c /Fe:helloworld_thread_x64-windows.exe /link /DYNAMICBASE:NO 21 | 22 | helloworld_loop_x64-windows.exe: helloworld_loop.c 23 | cl helloworld_loop.c /Fe:helloworld_loop_x64-windows.exe /link /DYNAMICBASE:NO 24 | 25 | helloworld_func_x64-windows.exe: helloworld_func.c 26 | cl helloworld_func.c /Fe:helloworld_func_x64-windows.exe /link /DYNAMICBASE:NO 27 | 28 | helloworld_pie_x64-windows.exe: helloworld.c 29 | cl helloworld.c /Fe:helloworld_pie_x64-windows.exe /link 30 | 31 | helloworld_thread_pie_x64-windows.exe: helloworld_thread.c 32 | cl helloworld_thread.c /Fe:helloworld_thread_pie_x64-windows.exe /link 33 | 34 | helloworld_loop_pie_x64-windows.exe: helloworld_loop.c 35 | cl helloworld_loop.c /Fe:helloworld_loop_pie_x64-windows.exe /link 36 | 37 | helloworld_func_pie_x64-windows.exe: helloworld_func.c 38 | cl helloworld_func.c /Fe:helloworld_func_pie_x64-windows.exe /link 39 | 40 | do_exception_pie_x64-windows.exe: do_exception.c 41 | cl do_exception.c -DARCH_IS_X64 /Fe:do_exception_pie_x64-windows.exe /link 42 | 43 | asmtest_x64-windows.exe: asmtest_x64-windows.obj 44 | link asmtest_x64-windows.obj /ENTRY:WinMain /SUBSYSTEM:CONSOLE /LARGEADDRESSAWARE:NO /DYNAMICBASE:NO /OUT:asmtest_x64-windows.exe kernel32.lib 45 | 46 | asmtest_x64-windows.obj: asmtest_x64.asm 47 | nasm -f win64 -DOS_IS_WINDOWS asmtest_x64.asm -o asmtest_x64-windows.obj 48 | 49 | hh1.exe: hh1.asm 50 | nasm -f bin -DOS_IS_WINDOWS hh1.asm -o hh1.exe 51 | 52 | hello_x64-windows.exe: hello_x64-windows.obj 53 | link hello_x64-windows.obj /ENTRY:WinMain /SUBSYSTEM:CONSOLE /LARGEADDRESSAWARE:NO /DYNAMICBASE:NO /OUT:hello_x64-windows.exe kernel32.lib 54 | 55 | hello_x64-windows.obj: hello_x64.asm 56 | nasm -f win64 -DOS_IS_WINDOWS hello_x64.asm -o hello_x64-windows.obj 57 | 58 | indirect_calls_x64-windows.exe: indirect_calls_x64-windows.obj 59 | link indirect_calls_x64-windows.obj /ENTRY:WinMain /SUBSYSTEM:CONSOLE /LARGEADDRESSAWARE:NO /DYNAMICBASE:NO /OUT:indirect_calls_x64-windows.exe kernel32.lib 60 | 61 | indirect_calls_x64-windows.obj: indirect_calls_x64.asm 62 | nasm -f win64 -DOS_IS_WINDOWS indirect_calls_x64.asm -o indirect_calls_x64-windows.obj 63 | 64 | missing_switch_case_x64-windows.exe: missing_switch_case_x64-windows.obj 65 | link missing_switch_case_x64-windows.obj /ENTRY:WinMain /SUBSYSTEM:CONSOLE /LARGEADDRESSAWARE:NO /DYNAMICBASE:NO /OUT:missing_switch_case_x64-windows.exe kernel32.lib 66 | 67 | missing_switch_case_x64-windows.obj: missing_switch_case_x64.asm 68 | nasm -f win64 -DOS_IS_WINDOWS missing_switch_case_x64.asm -o missing_switch_case_x64-windows.obj 69 | -------------------------------------------------------------------------------- /testbins/analysis_propagation_x64.asm: -------------------------------------------------------------------------------- 1 | ; The point here is to test the ability to pass runtime information to analysis. 2 | ; 3 | ; The simplest case is when a call is encountered in the debugger where the 4 | ; destination is not yet an identified function. Just make a function at this 5 | ; address. 6 | ; 7 | ; The second case is when a branch is encountered whose destination is not 8 | ; in the set of indirect branches picked up by analysis. This is exercised here 9 | ; by having a jump table's check bypassed from another function. Binja picks up 10 | ; the legal values of the jump table, but doesn't see that a side flow of 11 | ; execution can hop over the constraint for the value that indexes the table. 12 | 13 | default rel 14 | 15 | global start 16 | global function_with_indirect_call 17 | global function_with_switch 18 | section .text 19 | 20 | start: 21 | ; call case 4 (illegal) of switch by jumping passed check 22 | lea rbx, [function_with_switch] 23 | mov edi, 431 24 | call mapper ; returns 7 25 | add rbx, rax 26 | mov rcx, 4 27 | call rbx 28 | 29 | ; call case0, case1 of switch 30 | mov rdi, 0 31 | call function_with_switch 32 | mov rdi, 1 33 | call function_with_switch 34 | mov rdi, 2 35 | call function_with_switch 36 | mov rdi, 3 37 | call function_with_switch 38 | 39 | ; call case 5 (illegal) of switch by jumping passed check 40 | lea rbx, [function_with_switch] 41 | mov edi, 431 42 | call mapper ; returns 7 43 | add rbx, rax 44 | mov rcx, 5 45 | call rbx 46 | 47 | ; make some indirect calls 48 | call function_with_indirect_call 49 | 50 | ; done 51 | mov rax, 0x2000001 ; exit 52 | mov rdi, 0 53 | syscall 54 | ret 55 | 56 | function_with_switch: 57 | ; 00000000: 0x48, 0x89, 0xf9 58 | mov rcx, rdi ; arg0: 0,1,2,3 59 | ; 00000003: 0x48, 0x83, 0xe1, 0x03 60 | and rcx, 0x3 61 | ; 00000007: <--- jumping here bypasses the constraint 62 | 63 | lea rax, [.jump_table] 64 | movsx rdx, dword[rax+rcx*4] 65 | add rdx, rax 66 | jmp rdx 67 | 68 | .case0: 69 | call print_00 70 | jmp .switch_end 71 | 72 | .case1: 73 | call print_01 74 | jmp .switch_end 75 | 76 | .case2: 77 | call print_02 78 | jmp .switch_end 79 | 80 | .case3: 81 | call print_03 82 | jmp .switch_end 83 | 84 | .switch_end: 85 | ret 86 | 87 | .jump_table: 88 | dd function_with_switch.case0 - .jump_table 89 | dd function_with_switch.case1 - .jump_table 90 | dd function_with_switch.case2 - .jump_table 91 | dd function_with_switch.case3 - .jump_table 92 | ; these entries should be invisible/illegal to binja because of the "and 3" constraint 93 | dd junk + 0x30 - .jump_table 94 | dd junk + 0x8e - .jump_table 95 | 96 | function_with_indirect_call: 97 | mov rcx, 4 98 | 99 | .next: 100 | push rcx 101 | 102 | .test4: 103 | cmp rcx, 4 104 | jne .test3 105 | lea rbx, [print_00] 106 | jmp .dispatch 107 | 108 | .test3: 109 | cmp rcx, 3 110 | jne .test2 111 | lea rbx, [print_01] 112 | jmp .dispatch 113 | 114 | .test2: 115 | cmp rcx, 2 116 | jne .test1 117 | lea rbx, [junk] 118 | mov rdi, 453 ; -> 48 119 | call mapper 120 | add rbx, rax 121 | jmp .dispatch 122 | 123 | .test1: 124 | cmp rcx, 1 125 | lea rbx, [junk] 126 | mov rdi, 163 ; -> 142 127 | call mapper 128 | add rbx, rax 129 | 130 | .dispatch: 131 | call rbx 132 | 133 | .check: 134 | pop rcx 135 | loop .next 136 | ret 137 | 138 | ; evade data flow 139 | ; maps {1,2,3,4,5,6,7,8,9,10,...} -> {1,3,9,27,81,243,220,151,453,341,...} 140 | ; forward with pow(3,x,509) 141 | ; reverse with brute force [x for x in range(508) if pow(3,x,509) == y] 142 | mapper: 143 | mov rcx, rdi ; arg0: number to map 144 | mov rax, 1 145 | jrcxz .done 146 | .step: 147 | imul rax, 3 148 | .reduce: 149 | cmp rax, 509 150 | jl .next 151 | sub rax, 509 152 | jmp .reduce 153 | .next: 154 | loop .step 155 | .done: 156 | ret 157 | 158 | print_00: 159 | lea rsi, [.msg_start] 160 | lea rdx, [.done] 161 | sub rdx, rsi 162 | mov rdi, 1 ; stdout 163 | mov rax, 0x2000004 ; write 164 | syscall 165 | jmp .done 166 | .msg_start: 167 | db "I'm print_00!", 0x0a 168 | .done: 169 | ret 170 | 171 | print_01: 172 | mov rsi, .msg_start 173 | mov rdx, .done 174 | sub rdx, rsi 175 | mov rdi, 1 ; stdout 176 | mov rax, 0x2000004 ; write 177 | syscall 178 | jmp .done 179 | .msg_start: 180 | db "I'm print_01!", 0x0a 181 | .done: 182 | ret 183 | 184 | print_02: 185 | mov rsi, .msg_start 186 | mov rdx, .done 187 | sub rdx, rsi 188 | mov rdi, 1 ; stdout 189 | mov rax, 0x2000004 ; write 190 | syscall 191 | jmp .done 192 | .msg_start: 193 | db "I'm print_02!", 0x0a 194 | .done: 195 | ret 196 | 197 | print_03: 198 | mov rsi, .msg_start 199 | mov rdx, .done 200 | sub rdx, rsi 201 | mov rdi, 1 ; stdout 202 | mov rax, 0x2000004 ; write 203 | syscall 204 | jmp .done 205 | .msg_start: 206 | db "I'm print_03!", 0x0a 207 | .done: 208 | ret 209 | 210 | junk: 211 | ; junk 212 | db 0xEF, 0x3D, 0x53, 0x7C, 0xFB, 0x80, 0x3B, 0x28, 213 | db 0x15, 0xD1, 0xA2, 0xCD, 0x5E, 0x7E, 0xBC, 0xE1, 214 | db 0xC6, 0x1B, 0x63, 0x05, 0xB7, 0xD3, 0xBA, 0x3B, 215 | db 0x39, 0xCA, 0x46, 0xA1, 0x32, 0xD9, 0x8A, 0xB5, 216 | db 0x8F, 0xD6, 0xFA, 0xAE, 0x08, 0x2D, 0xD5, 0x6F, 217 | db 0x1E, 0xD6, 0xB8, 0x72, 0xA9, 0x8D, 0x86, 0xE8 218 | 219 | ; junk + 0x30 220 | ; hidden function 221 | db 0x48, 0x8D, 0x35, 0x18, 0x00, 0x00, 0x00, ; lea rsi, [.msg_start] 222 | db 0x48, 0x8D, 0x15, 0x1F, 0x00, 0x00, 0x00, ; lea rdx, [.done] 223 | db 0x48, 0x29, 0xF2 ; sub rdx, rsi 224 | db 0xBF, 0x01, 0x00, 0x00, 0x00 ; mov rdi, 1 ; stdout 225 | db 0xB8, 0x04, 0x00, 0x00, 0x02 ; mov rax, 0x2000004 ; write 226 | db 0x0F, 0x05 ; syscall 227 | db 0xEB, 0x0E ; jmp .done 228 | ; .msg_start: "YOU FOUND ME1" 229 | db 0x59, 0x4F, 0x55, 0x20, 0x46, 0x4F, 0x55, 0x4E, 0x44, 0x20, 0x4D, 0x45, 0x31, 0x0a 230 | ; .done: 231 | db 0xC3 ; ret 232 | 233 | ; junk + 0x5e 234 | db 0xB4, 0xDE, 0xF0, 0x6B, 0x54, 0x40, 0x08, 0x46, 235 | db 0xF6, 0xAC, 0xDD, 0x82, 0x8C, 0x74, 0x2C, 0x7F, 236 | db 0xBD, 0x0B, 0xC1, 0xBA, 0x12, 0x1F, 0xD0, 0x7C, 237 | db 0x44, 0xFF, 0x43, 0x5F, 0xC6, 0x85, 0xF3, 0x23, 238 | db 0x6B, 0x65, 0x41, 0x2C, 0xB4, 0x4A, 0x5E, 0x24, 239 | db 0x35, 0xBA, 0x57, 0x76, 0x18, 0xAB, 0xE0, 0x51 240 | 241 | ; junk + 0x8e 242 | ; hidden function 243 | db 0x48, 0x8D, 0x35, 0x18, 0x00, 0x00, 0x00, ; lea rsi, [.msg_start] 244 | db 0x48, 0x8D, 0x15, 0x1F, 0x00, 0x00, 0x00, ; lea rdx, [.done] 245 | db 0x48, 0x29, 0xF2 ; sub rdx, rsi 246 | db 0xBF, 0x01, 0x00, 0x00, 0x00 ; mov rdi, 1 ; stdout 247 | db 0xB8, 0x04, 0x00, 0x00, 0x02 ; mov rax, 0x2000004 ; write 248 | db 0x0F, 0x05 ; syscall 249 | db 0xEB, 0x0E ; jmp .done 250 | ; .msg_start: "YOU FOUND ME2" 251 | db 0x59, 0x4F, 0x55, 0x20, 0x46, 0x4F, 0x55, 0x4E, 0x44, 0x20, 0x4D, 0x45, 0x32, 0x0a 252 | ; .done: 253 | db 0xC3 ; ret 254 | 255 | section .data 256 | db "Here's some data.", 0x0a 257 | -------------------------------------------------------------------------------- /testbins/asmtest_aarch64-android.s: -------------------------------------------------------------------------------- 1 | // NOTES: 2 | // kernel call: syscall number in x8, args in x0, x1, x2, ... 3 | // syscall number lookup /include/uapi/asm-generic/unistd.h 4 | // 5 | .global _start 6 | 7 | .text 8 | 9 | _start: 10 | nop 11 | bl bounce 12 | nop 13 | bl bounce 14 | nop 15 | bl bounce 16 | nop 17 | bl bounce 18 | nop 19 | bl bounce 20 | nop 21 | bl bounce 22 | nop 23 | bl bounce 24 | nop 25 | bl bounce 26 | 27 | mov x2, msglen // arg2: message length 28 | 29 | // TODO: explore in detail why this won't work 30 | //mov x1, msg // arg1: message 31 | adrp x1, msg 32 | add x1, x1, :lo12:msg 33 | 34 | mov x0, #1 // arg0: stdout 35 | 36 | mov x8, #64 // __NR_write 37 | svc #0 38 | 39 | mov x0, #0 // arg0: status 40 | mov x8, #94 // __NR_exit 41 | svc #0 42 | 43 | bounce: 44 | ret 45 | 46 | .data 47 | 48 | msg: 49 | .asciz "Hello, world!\n" 50 | 51 | msglen = . - msg 52 | 53 | -------------------------------------------------------------------------------- /testbins/asmtest_armv7-android.s: -------------------------------------------------------------------------------- 1 | .data 2 | 3 | /* Data segment: define our message string and calculate its length. */ 4 | msg: 5 | .ascii "Hello, ARM!\n" 6 | len = . - msg 7 | 8 | .text 9 | 10 | /* Our application's entry point. */ 11 | .globl _start 12 | _start: 13 | nop 14 | bl bounce 15 | nop 16 | bl bounce 17 | nop 18 | bl bounce 19 | nop 20 | bl bounce 21 | nop 22 | bl bounce 23 | nop 24 | bl bounce 25 | nop 26 | bl bounce 27 | nop 28 | bl bounce 29 | 30 | /* syscall write(int fd, const void *buf, size_t count) */ 31 | mov %r0, $1 /* fd := STDOUT_FILENO */ 32 | ldr %r1, =msg /* buf := msg */ 33 | ldr %r2, =len /* count := len */ 34 | mov %r7, $4 /* write is syscall #4 */ 35 | swi $0 /* invoke syscall */ 36 | 37 | /* syscall exit(int status) */ 38 | mov %r0, $0 /* status := 0 */ 39 | mov %r7, $1 /* exit is syscall #1 */ 40 | swi $0 /* invoke syscall */ 41 | 42 | bounce: 43 | bx %lr 44 | -------------------------------------------------------------------------------- /testbins/asmtest_x64.asm: -------------------------------------------------------------------------------- 1 | default rel 2 | 3 | %ifdef OS_IS_WINDOWS 4 | global WinMain 5 | extern ExitProcess, GetStdHandle, WriteConsoleA 6 | 7 | section .bss 8 | numCharsWritten resd 1 9 | 10 | section .text 11 | WinMain: 12 | %endif 13 | 14 | %ifdef OS_IS_LINUX 15 | global _start 16 | section .text 17 | _start: 18 | %endif 19 | 20 | %ifdef OS_IS_MACOS 21 | global start 22 | section .text 23 | start: 24 | %endif 25 | 26 | nop 27 | call bounce 28 | nop 29 | call bounce 30 | nop 31 | call bounce 32 | nop 33 | call bounce 34 | nop 35 | call bounce 36 | nop 37 | call bounce 38 | nop 39 | call bounce 40 | nop 41 | call bounce 42 | 43 | %ifdef OS_IS_WINDOWS 44 | mov ecx, -11 ; STD_OUTPUT_HANDLE 45 | call GetStdHandle 46 | 47 | push 0 48 | mov r9, numCharsWritten 49 | mov r8, msg.len 50 | mov rdx, msg 51 | mov rcx, rax 52 | call WriteConsoleA 53 | add rsp, 0x8 54 | 55 | mov rcx, 0 56 | call ExitProcess 57 | 58 | %else 59 | mov rsi, msg 60 | mov rdx, msg.len 61 | mov rdi, 1 ; stdout 62 | %endif 63 | 64 | %ifdef OS_IS_LINUX 65 | mov rax, 1 ; write 66 | syscall 67 | mov rdi, 0 ; arg0: status 68 | mov rax, 60 ; __NR_exit 69 | syscall 70 | %endif 71 | 72 | %ifdef OS_IS_MACOS 73 | mov rax, 0x2000004 ; write 74 | syscall 75 | mov rax, 0x2000001 ; exit 76 | mov rdi, 0 77 | syscall 78 | %endif 79 | 80 | bounce: 81 | retn 82 | 83 | section .data 84 | msg: 85 | db "Hello, world!", 0x0a 86 | .len: equ $ - msg 87 | -------------------------------------------------------------------------------- /testbins/asmtest_x86.asm: -------------------------------------------------------------------------------- 1 | default rel 2 | 3 | %ifdef OS_IS_WINDOWS 4 | global WinMain 5 | 6 | extern _GetStdHandle@4 7 | extern _WriteConsoleA@20 8 | extern _ExitProcess@4 9 | 10 | section .bss 11 | numCharsWritten resd 1 12 | 13 | section .text 14 | WinMain: 15 | %endif 16 | 17 | %ifdef OS_IS_LINUX 18 | global _start 19 | section .text 20 | _start: 21 | %endif 22 | 23 | nop 24 | call bounce 25 | nop 26 | call bounce 27 | nop 28 | call bounce 29 | nop 30 | call bounce 31 | nop 32 | call bounce 33 | nop 34 | call bounce 35 | nop 36 | call bounce 37 | nop 38 | call bounce 39 | 40 | %ifdef OS_IS_WINDOWS 41 | push -11 ; STD_OUTPUT_HANDLE 42 | call _GetStdHandle@4 43 | 44 | push 0 45 | push numCharsWritten 46 | push msglen 47 | push msg 48 | push eax 49 | call _WriteConsoleA@20 50 | add esp, 0x8 51 | 52 | push 0 53 | call _ExitProcess@4 54 | %endif 55 | 56 | %ifdef OS_IS_LINUX 57 | mov edx, msglen ; arg2: message length 58 | mov ecx, msg ; arg1: message 59 | mov ebx, 1 ; arg0: stdout 60 | mov eax, 4 ; __NR_write 61 | int 0x80 62 | 63 | mov ebx, 0 ; arg0: status 64 | mov eax, 1 ; __NR_exit 65 | int 0x80 66 | %endif 67 | 68 | bounce: 69 | ret 70 | 71 | section .data 72 | msg: db "Hello, world!", 0x0a, 0 73 | msglen: equ $ - msg 74 | -------------------------------------------------------------------------------- /testbins/cat.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int ac, char **av) 4 | { 5 | while (1) { 6 | int ch = fgetc(stdin); 7 | if (ch == EOF && (feof(stdin) || ferror(stdin))) { 8 | break; 9 | } 10 | fputc(ch, stdout); 11 | } 12 | 13 | return 0; 14 | } 15 | -------------------------------------------------------------------------------- /testbins/clear.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import platform 5 | 6 | exceptions = ['clear.py'] 7 | 8 | print('clearing all executable tests, regardless of OS') 9 | 10 | executables = [] 11 | for fname in os.listdir('.'): 12 | if fname in exceptions: 13 | continue 14 | if fname.endswith('.exe'): 15 | executables.append(fname) 16 | elif fname.endswith('.obj'): 17 | executables.append(fname) 18 | elif fname.endswith('.pdb'): 19 | executables.append(fname) 20 | elif fname.endswith('.o'): 21 | executables.append(fname) 22 | elif platform.system() != 'Windows' and os.access(os.path.join(fname), os.X_OK): 23 | executables.append(fname) 24 | 25 | print('plan to delete:\n%s\n\nhit any key to continue, ctrl+c to cancel' % '\n'.join(executables)) 26 | input() 27 | 28 | for exe in executables: 29 | print('deleting %s' % exe) 30 | os.remove(exe) 31 | -------------------------------------------------------------------------------- /testbins/commandline_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int ac, char **av) 4 | { 5 | printf("Arguments: \n"); 6 | for (int i = 0; i < ac; i ++) { 7 | printf("%s\n", av[i]); 8 | } 9 | 10 | return 0; 11 | } 12 | -------------------------------------------------------------------------------- /testbins/do_exception.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | typedef int (*PFOO)(void); 6 | 7 | int main(int ac, char **av) 8 | { 9 | printf("start\n"); 10 | 11 | if(!strcmp(av[1], "segfault")) { 12 | printf("accessing from 0xDEADBEEF\n"); 13 | return *(int *)0xDEADBEEF; 14 | } 15 | 16 | if(!strcmp(av[1], "illegalinstr")) { 17 | #if defined(ARCH_IS_X64) 18 | printf("X64 bad instruction\n"); 19 | unsigned char buf[] = { 20 | 0x66, 0x06, // push es on x86, invalid in x64 21 | 0x0f, 0xb9, // ud2b 22 | 0x0f, 0x0b, // ud2 23 | 0xfe, 0xf0, 24 | 0x90, 25 | 0x90 26 | #elif defined(ARCH_IS_X86) 27 | printf("X86 bad instruction\n"); 28 | unsigned char buf[] = { 29 | 0x0f, 0x0b // ud2 30 | #elif defined(ARCH_IS_ARMV7) 31 | printf("ARMV7 bad instruction\n"); 32 | unsigned char buf[] = { 33 | 0xf0, 0xde, 0xf0, 0xe7, // little endian 0xe7f0def0 34 | 0xe7, 0xf0, 0xde, 0xf0 // big endian 35 | #elif defined(ARCH_IS_AARCH64) 36 | printf("AARCH64 bad instruction\n"); 37 | unsigned char buf[] = { 38 | // https://developer.arm.com/docs/ddi0596/a/a64-base-instructions-alphabetic-order/udf-permanently-undefined 39 | 0x00, 0x00, 0x00, 0x00 40 | #endif 41 | }; 42 | 43 | PFOO bar = (PFOO)buf; 44 | return bar(); 45 | } 46 | 47 | if(!strcmp(av[1], "divzero")) { 48 | printf("dividing by zero\n"); 49 | #if defined(ARCH_IS_X86) 50 | unsigned char buf[] = { 51 | 0x31, 0xdb, // xor ebx, ebx 52 | 0xf7, 0xf3, // div ebx 53 | 0xc3 54 | }; 55 | PFOO bar = (PFOO)buf; 56 | bar(); 57 | #endif 58 | float foo = 31337; 59 | int i = 9; 60 | while(i >= 0) { 61 | printf("dividing by %d\n", i); 62 | foo = foo/i; 63 | i -= 1; 64 | printf("result is: %f\n", foo); 65 | } 66 | } 67 | 68 | printf("end\n"); 69 | return 0; 70 | } 71 | -------------------------------------------------------------------------------- /testbins/exitcode.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int ac, char **av) 5 | { 6 | int rc = atoi(av[1]); 7 | printf("returning %d\n", rc); 8 | return rc; 9 | } 10 | -------------------------------------------------------------------------------- /testbins/hello_x64.asm: -------------------------------------------------------------------------------- 1 | default rel 2 | 3 | %ifdef OS_IS_WINDOWS 4 | global WinMain 5 | extern ExitProcess, GetStdHandle, WriteConsoleA 6 | 7 | section .bss 8 | numCharsWritten resd 1 9 | 10 | section .text 11 | WinMain: 12 | %endif 13 | 14 | %ifdef OS_IS_LINUX 15 | global _start 16 | section .text 17 | _start: 18 | %endif 19 | 20 | %ifdef OS_IS_MACOS 21 | global start 22 | section .text 23 | start: 24 | %endif 25 | 26 | %ifdef OS_IS_WINDOWS 27 | mov ecx, -11 ; STD_OUTPUT_HANDLE 28 | call GetStdHandle 29 | 30 | push 0 31 | mov r9, numCharsWritten 32 | mov r8, msg.len 33 | mov rdx, msg 34 | mov rcx, rax 35 | call WriteConsoleA 36 | add rsp, 0x8 37 | 38 | mov rcx, 0 39 | call ExitProcess 40 | 41 | %else 42 | lea rsi, [msg] 43 | mov rdx, msg.len 44 | mov rdi, 1 ; stdout 45 | %endif 46 | 47 | %ifdef OS_IS_LINUX 48 | mov rax, 1 ; write 49 | syscall 50 | mov rdi, 0 ; arg0: status 51 | mov rax, 60 ; __NR_exit 52 | syscall 53 | %endif 54 | 55 | %ifdef OS_IS_MACOS 56 | mov rax, 0x2000004 ; write 57 | syscall 58 | mov rax, 0x2000001 ; exit 59 | mov rdi, 0 60 | syscall 61 | %endif 62 | 63 | msg: 64 | db "Hello, world!", 0x0a 65 | .len: equ $ - msg 66 | -------------------------------------------------------------------------------- /testbins/helloworld.c: -------------------------------------------------------------------------------- 1 | // tests: cmdline arguments, process return value, stdout 2 | 3 | #include 4 | 5 | int main(int argc, char **argv) 6 | { 7 | int i; 8 | 9 | printf("Hello, world!\n"); 10 | 11 | printf("argc: %d\n", argc); 12 | for(i=0; i 2 | 3 | int hello(int a) 4 | { 5 | printf("Hello, world! %d\n", a); 6 | return a; 7 | } 8 | 9 | int main(int ac, char **av) 10 | { 11 | hello(0); 12 | hello(1); 13 | hello(2); 14 | hello(3); 15 | return 0; 16 | } 17 | -------------------------------------------------------------------------------- /testbins/helloworld_loop.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #if defined(_WIN32) || defined(_WIN64) 4 | #include 5 | #define PIDFUNC _getpid 6 | #else 7 | #include 8 | #define PIDFUNC getpid 9 | #endif 10 | 11 | int main(int ac, char **av) 12 | { 13 | int i; 14 | for(i=0; 1; i++) { 15 | int process_id = PIDFUNC(); 16 | 17 | printf("Hello, world! pid:%d i:%d\n", process_id, i); 18 | int j; 19 | for(j=0; j<100000000; ++j) 20 | i = i*7; 21 | } 22 | return 11; 23 | } 24 | -------------------------------------------------------------------------------- /testbins/helloworld_objc.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | int main() { 4 | NSLog(@"Hello, world!"); 5 | return 0; 6 | } 7 | -------------------------------------------------------------------------------- /testbins/helloworld_recursion.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int fib(int a) 5 | { 6 | if (a == 0 || a == 1) { 7 | return 1; 8 | } 9 | return fib(a - 1) + fib(a - 2); 10 | } 11 | 12 | int main(int ac, char **av) 13 | { 14 | if (ac == 1) { 15 | for (int i = 0; i < 50; i ++) { 16 | printf("The %dth fibonacci number is %d\n", i, fib(i)); 17 | } 18 | } else { 19 | printf("The %dth fibonacci number is %d\n", atoi(av[1]), fib(atoi(av[1]))); 20 | } 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /testbins/helloworld_thread.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #if defined(_WIN32) || defined(_WIN64) 5 | #define OS_IS_WINDOWS 6 | #endif 7 | 8 | #if defined(OS_IS_WINDOWS) 9 | #include 10 | #else 11 | #include 12 | #include 13 | #include 14 | #endif 15 | 16 | #if defined(OS_IS_WINDOWS) 17 | DWORD WINAPI ThreadFunc(void* vargp) 18 | //#define SLEEP1SEC Sleep(1000) 19 | #else 20 | void *thread_func(void *vargp) 21 | //#define SLEEP1SEC sleep(1) 22 | #endif 23 | 24 | #define SLEEP1SEC for(int i=0; i<99999999; ++i) { foo *= 3; } 25 | 26 | { 27 | int i; 28 | int myid = *(int *)vargp; 29 | srand(myid); 30 | for(i=0; i<1000; ++i) { 31 | printf("I'm thread %d.\n", myid); 32 | int foo = 7; 33 | /* stop at random rip, visible in debugger */ 34 | switch(rand()%10) { 35 | case 0: printf("rolled 0\n"); SLEEP1SEC; break; 36 | case 1: printf("rolled 1\n"); SLEEP1SEC; break; 37 | case 2: printf("rolled 2\n"); SLEEP1SEC; break; 38 | case 3: printf("rolled 3\n"); SLEEP1SEC; break; 39 | case 4: printf("rolled 4\n"); SLEEP1SEC; break; 40 | case 5: printf("rolled 5\n"); SLEEP1SEC; break; 41 | case 6: printf("rolled 6\n"); SLEEP1SEC; break; 42 | case 7: printf("rolled 7\n"); SLEEP1SEC; break; 43 | case 8: printf("rolled 8\n"); SLEEP1SEC; break; 44 | case 9: printf("rolled 9\n"); SLEEP1SEC; break; 45 | } 46 | } 47 | 48 | #if defined(OS_IS_WINDOWS) 49 | return 0; 50 | #else 51 | return NULL; 52 | #endif 53 | } 54 | 55 | int main(int ac, char **av) 56 | { 57 | printf("Before Thread\n"); 58 | 59 | #if defined(OS_IS_WINDOWS) 60 | DWORD ids[4] = {0, 1, 2, 3}; 61 | HANDLE hThreadArray[4]; 62 | hThreadArray[0] = CreateThread(NULL, 0, ThreadFunc, (void *)(ids+0), 0, NULL); 63 | hThreadArray[1] = CreateThread(NULL, 0, ThreadFunc, (void *)(ids+1), 0, NULL); 64 | hThreadArray[2] = CreateThread(NULL, 0, ThreadFunc, (void *)(ids+2), 0, NULL); 65 | hThreadArray[3] = CreateThread(NULL, 0, ThreadFunc, (void *)(ids+3), 0, NULL); 66 | WaitForMultipleObjects(4, hThreadArray, TRUE, INFINITE); 67 | #else 68 | int ids[4] = {0, 1, 2, 3}; 69 | pthread_t thread_id[4]; 70 | pthread_create(&thread_id[0], NULL, thread_func, (void *)(ids+0)); 71 | pthread_create(&thread_id[1], NULL, thread_func, (void *)(ids+1)); 72 | pthread_create(&thread_id[2], NULL, thread_func, (void *)(ids+2)); 73 | pthread_create(&thread_id[3], NULL, thread_func, (void *)(ids+3)); 74 | pthread_join(thread_id[0], NULL); 75 | pthread_join(thread_id[1], NULL); 76 | pthread_join(thread_id[2], NULL); 77 | pthread_join(thread_id[3], NULL); 78 | #endif 79 | 80 | return 12; 81 | } 82 | -------------------------------------------------------------------------------- /testbins/helloworld_virtual.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | class Foo { 5 | public: 6 | virtual ~Foo() {} 7 | virtual void bar() = 0; 8 | }; 9 | 10 | class Bar : public Foo { 11 | public: 12 | virtual void bar() { 13 | printf("Bar!\n"); 14 | } 15 | }; 16 | 17 | class Baz : public Bar { 18 | public: 19 | virtual void bar() { 20 | printf("Baz!\n"); 21 | } 22 | }; 23 | 24 | int main(int ac, char **av) 25 | { 26 | Foo* foo = new Baz(); 27 | foo->bar(); 28 | delete foo; 29 | return 0; 30 | } 31 | -------------------------------------------------------------------------------- /testbins/indirect_calls_x64.asm: -------------------------------------------------------------------------------- 1 | ; The point here is to test the ability to pass runtime information to analysis. 2 | ; 3 | ; The same `call ebx` is executed 4 times, see label ".dispatch". 4 | ; The first two times are with functions identified by analysis. 5 | ; The second two times evade analysis and require runtime information. 6 | ; Binja should make functions in the second two cases and add comments of runtime 7 | ; annotation option is enabled. 8 | 9 | default rel 10 | 11 | %ifdef OS_IS_LINUX 12 | global _start 13 | section .text 14 | _start: 15 | %endif 16 | 17 | %ifdef OS_IS_MACOS 18 | global start, junk, mapper 19 | section .text 20 | start: 21 | %endif 22 | 23 | %ifdef OS_IS_WINDOWS 24 | global WinMain 25 | extern ExitProcess, GetStdHandle, WriteConsoleA 26 | section .text 27 | WinMain: 28 | %endif 29 | 30 | mov rcx, 4 ; <------- LOOP VAR 31 | 32 | .next: 33 | push rcx 34 | 35 | .test4: 36 | cmp rcx, 4 37 | jne .test3 38 | lea rbx, [sub_00] 39 | jmp .dispatch 40 | 41 | .test3: 42 | cmp rcx, 3 43 | jne .test2 44 | lea rbx, [sub_01] 45 | jmp .dispatch 46 | 47 | .test2: 48 | cmp rcx, 2 49 | jne .test1 50 | lea rbx, [junk] 51 | mov rdi, 0x60 ; -> 0x30 52 | call mapper 53 | add rbx, rax 54 | jmp .dispatch 55 | 56 | .test1: 57 | cmp rcx, 1 58 | lea rbx, [junk] 59 | mov rdi, 0xC6 ; -> 0x63 60 | call mapper 61 | add rbx, rax 62 | 63 | .dispatch: 64 | call rbx ; <-------- HERE 65 | 66 | .check: 67 | pop rcx 68 | loop .next 69 | 70 | ; exit 71 | 72 | %ifdef OS_IS_LINUX 73 | mov rdi, 0 ; arg0: status 74 | mov rax, 60 ; __NR_exit 75 | syscall 76 | %endif 77 | 78 | %ifdef OS_IS_MACOS 79 | mov rax, 0x2000001 ; exit 80 | mov rdi, 0 81 | syscall 82 | %endif 83 | 84 | %ifdef OS_IS_WINDOWS 85 | mov rcx, 0 86 | call ExitProcess 87 | %endif 88 | 89 | ret 90 | 91 | sub_00: 92 | ret 93 | 94 | sub_01: 95 | nop 96 | ret 97 | 98 | junk: 99 | ; junk 100 | db 0xEF, 0x3D, 0x53, 0x7C, 0xFB, 0x80, 0x3B, 0x28 101 | db 0x15, 0xD1, 0xA2, 0xCD, 0x5E, 0x7E, 0xBC, 0xE1 102 | db 0xC6, 0x1B, 0x63, 0x05, 0xB7, 0xD3, 0xBA, 0x3B 103 | db 0x39, 0xCA, 0x46, 0xA1, 0x32, 0xD9, 0x8A, 0xB5 104 | db 0x8F, 0xD6, 0xFA, 0xAE, 0x08, 0x2D, 0xD5, 0x6F 105 | db 0x1E, 0xD6, 0xB8, 0x72, 0xA9, 0x8D, 0x86, 0xE8 106 | 107 | ; junk + 0x30 108 | ; hidden function ; sub_02() 109 | db 0x90 ; nop 110 | db 0x90 ; nop 111 | db 0xC3 ; ret 112 | 113 | ; junk + 0x33 114 | db 0xB4, 0xDE, 0xF0, 0x6B, 0x54, 0x40, 0x08, 0x46 115 | db 0xF6, 0xAC, 0xDD, 0x82, 0x8C, 0x74, 0x2C, 0x7F 116 | db 0xBD, 0x0B, 0xC1, 0xBA, 0x12, 0x1F, 0xD0, 0x7C 117 | db 0x44, 0xFF, 0x43, 0x5F, 0xC6, 0x85, 0xF3, 0x23 118 | db 0x6B, 0x65, 0x41, 0x2C, 0xB4, 0x4A, 0x5E, 0x24 119 | db 0x35, 0xBA, 0x57, 0x76, 0x18, 0xAB, 0xE0, 0x51 120 | 121 | ; junk + 0x63 122 | ; hidden function ; sub_03() 123 | db 0x90 ; nop 124 | db 0x90 ; nop 125 | db 0x90 ; nop 126 | db 0xC3 ; ret 127 | 128 | db 0xB9, 0x57, 0x18, 0x13, 0x61, 0xB0, 0x86, 0xBF 129 | db 0x1F, 0x6B, 0xB7, 0x72, 0x07, 0x35, 0xB1, 0x02 130 | db 0x7E, 0x6A, 0x2E, 0x1B, 0x7A, 0x0B, 0xEB, 0xDA 131 | db 0x05, 0xCF, 0xC9, 0xBD, 0x9E, 0xB6, 0x07, 0xBF 132 | 133 | mapper: 134 | mov rax, rdi ; arg0: number to map 135 | shr rax, 1 136 | ret 137 | -------------------------------------------------------------------------------- /testbins/many_stdlib_calls.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int ac, char **av) { 4 | for (int i = 0; i < 10; i ++) { 5 | printf("\n"); 6 | printf("\n"); 7 | printf("\n"); 8 | printf("\n"); 9 | printf("\n"); 10 | printf("\n"); 11 | printf("\n"); 12 | printf("\n"); 13 | printf("\n"); 14 | printf("\n"); 15 | printf("\n"); 16 | printf("\n"); 17 | printf("\n"); 18 | printf("\n"); 19 | printf("\n"); 20 | printf("\n"); 21 | printf("\n"); 22 | printf("\n"); 23 | printf("\n"); 24 | printf("\n"); 25 | printf("\n"); 26 | printf("\n"); 27 | printf("\n"); 28 | printf("\n"); 29 | printf("\n"); 30 | printf("\n"); 31 | printf("\n"); 32 | printf("\n"); 33 | printf("\n"); 34 | printf("\n"); 35 | printf("\n"); 36 | printf("\n"); 37 | } 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /testbins/md5/Makefile-linux: -------------------------------------------------------------------------------- 1 | all: md5_x64-linux 2 | 3 | md5_x64-linux: md5driver.c md5c.c global.h md5.h 4 | gcc -g -DMD=5 md5driver.c md5c.c -o md5_x64-linux 5 | -------------------------------------------------------------------------------- /testbins/md5/Makefile-macos: -------------------------------------------------------------------------------- 1 | all: md5_x64-macos 2 | 3 | md5_x64-macos: md5driver.c md5c.c global.h md5.h 4 | gcc -DMD=5 md5driver.c md5c.c -o md5_x64-macos 5 | -------------------------------------------------------------------------------- /testbins/md5/global.h: -------------------------------------------------------------------------------- 1 | /* GLOBAL.H - RSAREF types and constants 2 | */ 3 | 4 | #ifndef PROTOTYPES 5 | #define PROTOTYPES 0 6 | #endif 7 | 8 | #include 9 | 10 | /* POINTER defines a generic pointer type */ 11 | typedef uint8_t *POINTER; 12 | 13 | /* UINT2 defines a two byte word */ 14 | typedef uint16_t UINT2; 15 | 16 | /* UINT4 defines a four byte word */ 17 | typedef uint32_t UINT4; 18 | 19 | /* PROTO_LIST is defined depending on how PROTOTYPES is defined above. 20 | If using PROTOTYPES, then PROTO_LIST returns the list, otherwise it 21 | returns an empty list. 22 | */ 23 | #if PROTOTYPES 24 | #define PROTO_LIST(list) list 25 | #else 26 | #define PROTO_LIST(list) () 27 | #endif 28 | 29 | -------------------------------------------------------------------------------- /testbins/md5/md5.h: -------------------------------------------------------------------------------- 1 | /* MD5.H - header file for MD5C.C 2 | */ 3 | 4 | /* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All 5 | rights reserved. 6 | 7 | License to copy and use this software is granted provided that it 8 | is identified as the "RSA Data Security, Inc. MD5 Message-Digest 9 | Algorithm" in all material mentioning or referencing this software 10 | or this function. 11 | 12 | License is also granted to make and use derivative works provided 13 | that such works are identified as "derived from the RSA Data 14 | Security, Inc. MD5 Message-Digest Algorithm" in all material 15 | mentioning or referencing the derived work. 16 | 17 | RSA Data Security, Inc. makes no representations concerning either 18 | the merchantability of this software or the suitability of this 19 | software for any particular purpose. It is provided "as is" 20 | without express or implied warranty of any kind. 21 | 22 | These notices must be retained in any copies of any part of this 23 | documentation and/or software. 24 | */ 25 | 26 | /* MD5 context. */ 27 | typedef struct { 28 | UINT4 state[4]; /* state (ABCD) */ 29 | UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */ 30 | unsigned char buffer[64]; /* input buffer */ 31 | } MD5_CTX; 32 | 33 | void MD5Init PROTO_LIST ((MD5_CTX *)); 34 | void MD5Update PROTO_LIST 35 | ((MD5_CTX *, unsigned char *, unsigned int)); 36 | void MD5Final PROTO_LIST ((unsigned char [16], MD5_CTX *)); 37 | 38 | -------------------------------------------------------------------------------- /testbins/md5/md5driver.c: -------------------------------------------------------------------------------- 1 | /* MDDRIVER.C - test driver for MD2, MD4 and MD5 2 | */ 3 | 4 | /* Copyright (C) 1990-2, RSA Data Security, Inc. Created 1990. All 5 | rights reserved. 6 | 7 | RSA Data Security, Inc. makes no representations concerning either 8 | the merchantability of this software or the suitability of this 9 | software for any particular purpose. It is provided "as is" 10 | without express or implied warranty of any kind. 11 | 12 | These notices must be retained in any copies of any part of this 13 | documentation and/or software. 14 | */ 15 | 16 | /* The following makes MD default to MD5 if it has not already been 17 | defined with C compiler flags. 18 | */ 19 | #ifndef MD 20 | #define MD MD5 21 | #endif 22 | 23 | #include 24 | #include 25 | #include 26 | #include "global.h" 27 | 28 | #if MD == 2 29 | #include "md2.h" 30 | #endif 31 | #if MD == 4 32 | #include "md4.h" 33 | #endif 34 | #if MD == 5 35 | #include "md5.h" 36 | #endif 37 | 38 | /* Length of test block, number of test blocks. 39 | */ 40 | #define TEST_BLOCK_LEN 1000 41 | #define TEST_BLOCK_COUNT 1000 42 | 43 | static void MDString PROTO_LIST ((char *)); 44 | static void MDTimeTrial PROTO_LIST ((void)); 45 | static void MDTestSuite PROTO_LIST ((void)); 46 | static void MDFile PROTO_LIST ((char *)); 47 | static void MDFilter PROTO_LIST ((void)); 48 | static void MDPrint PROTO_LIST ((unsigned char [16])); 49 | 50 | #if MD == 2 51 | #define MD_CTX MD2_CTX 52 | #define MDInit MD2Init 53 | #define MDUpdate MD2Update 54 | #define MDFinal MD2Final 55 | #endif 56 | #if MD == 4 57 | #define MD_CTX MD4_CTX 58 | #define MDInit MD4Init 59 | #define MDUpdate MD4Update 60 | #define MDFinal MD4Final 61 | #endif 62 | #if MD == 5 63 | #define MD_CTX MD5_CTX 64 | #define MDInit MD5Init 65 | #define MDUpdate MD5Update 66 | #define MDFinal MD5Final 67 | #endif 68 | 69 | /* Main driver. 70 | 71 | Arguments (may be any combination): 72 | -sstring - digests string 73 | -t - runs time trial 74 | -x - runs test script 75 | filename - digests file 76 | (none) - digests standard input 77 | */ 78 | int main (argc, argv) 79 | int argc; 80 | char *argv[]; 81 | { 82 | int i; 83 | 84 | if (argc > 1) 85 | for (i = 1; i < argc; i++) 86 | if (argv[i][0] == '-' && argv[i][1] == 's') 87 | MDString (argv[i] + 2); 88 | else if (strcmp (argv[i], "-t") == 0) 89 | MDTimeTrial (); 90 | else if (strcmp (argv[i], "-x") == 0) 91 | MDTestSuite (); 92 | else 93 | MDFile (argv[i]); 94 | else 95 | MDFilter (); 96 | 97 | return (0); 98 | } 99 | 100 | /* Digests a string and prints the result. 101 | */ 102 | static void MDString (string) 103 | char *string; 104 | { 105 | MD_CTX context; 106 | unsigned char digest[16]; 107 | unsigned int len = strlen (string); 108 | 109 | MDInit (&context); 110 | MDUpdate (&context, string, len); 111 | MDFinal (digest, &context); 112 | 113 | printf ("MD%d (\"%s\") = ", MD, string); 114 | MDPrint (digest); 115 | printf ("\n"); 116 | } 117 | 118 | /* Measures the time to digest TEST_BLOCK_COUNT TEST_BLOCK_LEN-byte 119 | blocks. 120 | */ 121 | static void MDTimeTrial () 122 | { 123 | MD_CTX context; 124 | time_t endTime, startTime; 125 | unsigned char block[TEST_BLOCK_LEN], digest[16]; 126 | unsigned int i; 127 | printf 128 | ("MD%d time trial. Digesting %d %d-byte blocks ...", MD, 129 | TEST_BLOCK_LEN, TEST_BLOCK_COUNT); 130 | 131 | /* Initialize block */ 132 | for (i = 0; i < TEST_BLOCK_LEN; i++) 133 | block[i] = (unsigned char)(i & 0xff); 134 | 135 | /* Start timer */ 136 | time (&startTime); 137 | 138 | /* Digest blocks */ 139 | MDInit (&context); 140 | for (i = 0; i < TEST_BLOCK_COUNT; i++) 141 | MDUpdate (&context, block, TEST_BLOCK_LEN); 142 | MDFinal (digest, &context); 143 | 144 | /* Stop timer */ 145 | time (&endTime); 146 | 147 | printf (" done\n"); 148 | printf ("Digest = "); 149 | MDPrint (digest); 150 | printf ("\nTime = %ld seconds\n", (long)(endTime-startTime)); 151 | printf 152 | ("Speed = %ld bytes/second\n", 153 | (long)TEST_BLOCK_LEN * (long)TEST_BLOCK_COUNT/(endTime-startTime)); 154 | } 155 | 156 | /* Digests a reference suite of strings and prints the results. 157 | */ 158 | static void MDTestSuite () 159 | { 160 | printf ("MD%d test suite:\n", MD); 161 | 162 | MDString (""); 163 | MDString ("a"); 164 | MDString ("abc"); 165 | MDString ("message digest"); 166 | MDString ("abcdefghijklmnopqrstuvwxyz"); 167 | MDString 168 | ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); 169 | MDString 170 | ("1234567890123456789012345678901234567890\ 171 | 1234567890123456789012345678901234567890"); 172 | } 173 | 174 | /* Digests a file and prints the result. */ 175 | static void MDFile (filename) 176 | char *filename; 177 | { 178 | FILE *file; 179 | MD_CTX context; 180 | int len; 181 | unsigned char buffer[1024], digest[16]; 182 | 183 | if ((file = fopen (filename, "rb")) == NULL) 184 | printf ("%s can't be opened\n", filename); 185 | 186 | else { 187 | MDInit (&context); 188 | while ((len = fread (buffer, 1, 1024, file))) 189 | MDUpdate (&context, buffer, len); 190 | MDFinal (digest, &context); 191 | 192 | fclose (file); 193 | 194 | printf ("MD%d (%s) = ", MD, filename); 195 | MDPrint (digest); 196 | printf ("\n"); 197 | } 198 | } 199 | 200 | /* Digests the standard input and prints the result. 201 | */ 202 | static void MDFilter () 203 | { 204 | MD_CTX context; 205 | int len; 206 | unsigned char buffer[16], digest[16]; 207 | 208 | MDInit (&context); 209 | while ((len = fread (buffer, 1, 16, stdin))) 210 | MDUpdate (&context, buffer, len); 211 | MDFinal (digest, &context); 212 | 213 | MDPrint (digest); 214 | printf ("\n"); 215 | } 216 | 217 | /* Prints a message digest in hexadecimal. 218 | */ 219 | static void MDPrint (digest) 220 | unsigned char digest[16]; 221 | { 222 | unsigned int i; 223 | for (i = 0; i < 16; i++) 224 | printf ("%02x", digest[i]); 225 | } 226 | 227 | -------------------------------------------------------------------------------- /testbins/missing_switch_case_x64.asm: -------------------------------------------------------------------------------- 1 | ; Demonstrate runtime information informing analysis. 2 | ; 3 | ; The switch statement has 4 legitimate cases and 4 secret cases. 4 | ; Analysis will statically find the 4 legitimate. 5 | ; Analysis will learn the other 4 while stepping through the table dispatch at runtime. 6 | 7 | default rel 8 | 9 | %ifdef OS_IS_LINUX 10 | global _start 11 | section .text 12 | _start: 13 | %endif 14 | 15 | %ifdef OS_IS_MACOS 16 | global start 17 | section .text 18 | start: 19 | %endif 20 | 21 | %ifdef OS_IS_WINDOWS 22 | global WinMain 23 | extern ExitProcess, GetStdHandle, WriteConsoleA 24 | section .text 25 | WinMain: 26 | %endif 27 | 28 | start: 29 | ; get pointer past switch constraint (which binja static analyzed) 30 | lea rbx, [function_with_switch] 31 | mov edi, 14 32 | call mapper ; returns 7 33 | add rbx, rax ; skip over switch constraint 34 | 35 | ; call secret cases 36 | mov rcx, 4 37 | call rbx 38 | mov rcx, 5 39 | call rbx 40 | mov rcx, 6 41 | call rbx 42 | mov rcx, 7 43 | call rbx 44 | 45 | ; call legit cases 46 | mov rdi, 0 47 | call function_with_switch 48 | mov rdi, 1 49 | call function_with_switch 50 | mov rdi, 2 51 | call function_with_switch 52 | mov rdi, 3 53 | call function_with_switch 54 | 55 | %ifdef OS_IS_LINUX 56 | mov rdi, 0 ; arg0: status 57 | mov rax, 60 ; __NR_exit 58 | syscall 59 | %endif 60 | 61 | %ifdef OS_IS_MACOS 62 | mov rax, 0x2000001 ; exit 63 | mov rdi, 0 64 | syscall 65 | %endif 66 | 67 | %ifdef OS_IS_WINDOWS 68 | mov rcx, 0 69 | call ExitProcess 70 | %endif 71 | 72 | ; exit (so Binja knows end-of-function) 73 | ret 74 | 75 | function_with_switch: 76 | ; 00000000: 0x48, 0x89, 0xf9 77 | mov rcx, rdi ; arg0: 0,1,2,3 78 | ; 00000003: 0x48, 0x83, 0xe1, 0x03 79 | and rcx, 0x3 80 | ; 00000007: <--- jumping here bypasses the constraint 81 | 82 | lea rax, [.jump_table] 83 | movsx rdx, dword[rax+rcx*4] 84 | add rdx, rax 85 | jmp rdx 86 | 87 | .case0: 88 | mov rax, 0 89 | jmp .switch_end 90 | 91 | .case1: 92 | mov rax, 1 93 | jmp .switch_end 94 | 95 | .case2: 96 | mov rax, 2 97 | jmp .switch_end 98 | 99 | .case3: 100 | mov rax, 3 101 | jmp .switch_end 102 | 103 | .case4: 104 | mov rax, 4 105 | jmp .switch_end 106 | 107 | .case5: 108 | mov rax, 5 109 | jmp .switch_end 110 | 111 | .case6: 112 | mov rax, 6 113 | jmp .switch_end 114 | 115 | .case7: 116 | mov rax, 7 117 | jmp .switch_end 118 | 119 | .switch_end: 120 | ret 121 | 122 | .jump_table: 123 | dd function_with_switch.case0 - .jump_table 124 | dd function_with_switch.case1 - .jump_table 125 | dd function_with_switch.case2 - .jump_table 126 | dd function_with_switch.case3 - .jump_table 127 | ; these entries should be invisible/illegal to binja because of the "and 3" constraint 128 | dd function_with_switch.case4 - .jump_table 129 | dd function_with_switch.case5 - .jump_table 130 | dd function_with_switch.case6 - .jump_table 131 | dd function_with_switch.case7 - .jump_table 132 | 133 | ; evade data flow: return given number integer divided by 2 134 | mapper: 135 | mov rax, rdi 136 | shr rax, 1 137 | ret 138 | 139 | -------------------------------------------------------------------------------- /testbins/nopspeed.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | typedef uint64_t (*sc_fn)(); 8 | 9 | sc_fn gen_sc_fn(unsigned char *nops, uint64_t nop_len, uint64_t total_nop_bytes) { 10 | unsigned char *sc = malloc(total_nop_bytes + 0x100); 11 | uint64_t size = 0; 12 | 13 | // mfence 14 | sc[size++] = 0x0f; 15 | sc[size++] = 0xae; 16 | sc[size++] = 0xf0; 17 | 18 | // lfence 19 | sc[size++] = 0x0f; 20 | sc[size++] = 0xae; 21 | sc[size++] = 0xe8; 22 | 23 | // rdtsc 24 | sc[size++] = 0x0f; 25 | sc[size++] = 0x31; 26 | 27 | // push rax 28 | sc[size++] = 0x50; 29 | // push rdx 30 | sc[size++] = 0x52; 31 | 32 | while (size < total_nop_bytes) { 33 | for (int i = 0; i < nop_len; i ++) { 34 | sc[size++] = nops[i]; 35 | } 36 | } 37 | 38 | // rdtsc 39 | sc[size++] = 0x0f; 40 | sc[size++] = 0x31; 41 | 42 | // shl rdx, 0x20 43 | sc[size++] = 0x48; 44 | sc[size++] = 0xc1; 45 | sc[size++] = 0xe2; 46 | sc[size++] = 0x20; 47 | 48 | // xor rdx, rax 49 | sc[size++] = 0x48; 50 | sc[size++] = 0x31; 51 | sc[size++] = 0xc2; 52 | 53 | // pop rcx 54 | sc[size++] = 0x59; 55 | // pop rax 56 | sc[size++] = 0x58; 57 | 58 | // shl rcx, 0x20 59 | sc[size++] = 0x48; 60 | sc[size++] = 0xc1; 61 | sc[size++] = 0xe1; 62 | sc[size++] = 0x20; 63 | 64 | // xor rcx, rax 65 | sc[size++] = 0x48; 66 | sc[size++] = 0x31; 67 | sc[size++] = 0xc1; 68 | 69 | // sub rdx, rcx 70 | sc[size++] = 0x48; 71 | sc[size++] = 0x29; 72 | sc[size++] = 0xca; 73 | 74 | // mov rax, rcx 75 | sc[size++] = 0x48; 76 | sc[size++] = 0x89; 77 | sc[size++] = 0xd0; 78 | 79 | // ret 80 | sc[size++] = 0xc3; 81 | 82 | void *ptr = mmap(0, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE, 0, 0); 83 | 84 | if (ptr == MAP_FAILED) { 85 | perror("mmap"); 86 | exit(1); 87 | } 88 | 89 | memcpy(ptr, sc, size); 90 | 91 | free(sc); 92 | 93 | return ptr; 94 | } 95 | 96 | int main() { 97 | unsigned char nops[16][15] = { 98 | {0x90} 99 | , {0x66,0x90} 100 | , {0x0f,0x1f,0x00} 101 | , {0x0f,0x1f,0x40,0x00} 102 | , {0x0f,0x1f,0x44,0x00,0x00} 103 | , {0x66,0x0f,0x1f,0x44,0x00,0x00} 104 | , {0x0f,0x1f,0x80,0x00,0x00,0x00,0x00} 105 | , {0x0f,0x1f,0x84,0x00,0x00,0x00,0x00,0x00} 106 | , {0x66,0x0f,0x1f,0x84,0x00,0x00,0x00,0x00,0x00} 107 | , {0x66,0x2e,0x0f,0x1f,0x84,0x00,0x00,0x00,0x00,0x00} 108 | , {0x66,0x66,0x2e,0x0f,0x1f,0x84,0x00,0x00,0x00,0x00,0x00} 109 | , {0x66,0x66,0x66,0x2e,0x0f,0x1f,0x84,0x00,0x00,0x00,0x00,0x00} 110 | , {0x66,0x66,0x66,0x66,0x2e,0x0f,0x1f,0x84,0x00,0x00,0x00,0x00,0x00} 111 | , {0x66,0x66,0x66,0x66,0x66,0x2e,0x0f,0x1f,0x84,0x00,0x00,0x00,0x00,0x00} 112 | , {0x66,0x66,0x66,0x66,0x66,0x66,0x2e,0x0f,0x1f,0x84,0x00,0x00,0x00,0x00,0x00} 113 | }; 114 | 115 | int total_nop_bytes = 0x800000; 116 | 117 | for (int i = 0; i < 15; i++) { 118 | sc_fn ptr = gen_sc_fn(nops[i], i + 1, total_nop_bytes); 119 | 120 | uint64_t sum = 0; 121 | uint64_t runs = 0; 122 | 123 | for (int j = 0; j < 1000; j ++) { 124 | uint64_t time = ptr(); 125 | sum += time; 126 | runs ++; 127 | } 128 | double avg = (double)sum / (double)runs; 129 | printf("0x%x bytes of nops of length %d took %f cycles on avg\n", total_nop_bytes, i + 1, avg); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /testbins/rm_func_starts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # remove mach-o function starts by setting the size of the LUT in the load command to size 0 4 | # offs+0: cmd (0x26 == LC_FUNCTION_STARTS) 5 | # offs+4: cmdsize (0x10) 6 | # offs+8: LUT offset 7 | # offs+C: LUT size <---- HERE 8 | 9 | import sys 10 | from struct import unpack 11 | 12 | data = None 13 | with open(sys.argv[1], 'rb') as fp: 14 | data = fp.read() 15 | 16 | assert data[0:4] == b'\xCF\xFA\xED\xFE' 17 | assert data[4:8] == b'\x07\x00\x00\x01' # CPU_TYPE_X86_X64 18 | ncmds = unpack(' 48 25 | call mapper 26 | add rbx, rax 27 | call rbx 28 | 29 | ; calls ecret function #2 30 | lea rbx, [junk] 31 | mov rdi, 284 ; -> 142 32 | call mapper 33 | add rbx, rax 34 | call rbx 35 | 36 | ; done, exit 37 | mov rax, 0x2000001 ; exit 38 | mov rdi, 0 39 | syscall 40 | ret 41 | 42 | print_00: 43 | lea rsi, [.msg_start] 44 | lea rdx, [.done] 45 | sub rdx, rsi 46 | mov rdi, 1 ; stdout 47 | mov rax, 0x2000004 ; write 48 | syscall 49 | jmp .done 50 | .msg_start: 51 | db "I'm print_00!", 0x0a 52 | .done: 53 | ret 54 | 55 | print_01: 56 | mov rsi, .msg_start 57 | mov rdx, .done 58 | sub rdx, rsi 59 | mov rdi, 1 ; stdout 60 | mov rax, 0x2000004 ; write 61 | syscall 62 | jmp .done 63 | .msg_start: 64 | db "I'm print_01!", 0x0a 65 | .done: 66 | ret 67 | 68 | junk: 69 | ; junk 70 | db 0xEF, 0x3D, 0x53, 0x7C, 0xFB, 0x80, 0x3B, 0x28, 71 | db 0x15, 0xD1, 0xA2, 0xCD, 0x5E, 0x7E, 0xBC, 0xE1, 72 | db 0xC6, 0x1B, 0x63, 0x05, 0xB7, 0xD3, 0xBA, 0x3B, 73 | db 0x39, 0xCA, 0x46, 0xA1, 0x32, 0xD9, 0x8A, 0xB5, 74 | db 0x8F, 0xD6, 0xFA, 0xAE, 0x08, 0x2D, 0xD5, 0x6F, 75 | db 0x1E, 0xD6, 0xB8, 0x72, 0xA9, 0x8D, 0x86, 0xE8 76 | 77 | ; junk + 0x30 78 | ; hidden function 79 | db 0x48, 0x8D, 0x35, 0x18, 0x00, 0x00, 0x00, ; lea rsi, [.msg_start] 80 | db 0x48, 0x8D, 0x15, 0x1F, 0x00, 0x00, 0x00, ; lea rdx, [.done] 81 | db 0x48, 0x29, 0xF2 ; sub rdx, rsi 82 | db 0xBF, 0x01, 0x00, 0x00, 0x00 ; mov rdi, 1 ; stdout 83 | db 0xB8, 0x04, 0x00, 0x00, 0x02 ; mov rax, 0x2000004 ; write 84 | db 0x0F, 0x05 ; syscall 85 | db 0xEB, 0x0E ; jmp .done 86 | ; .msg_start: "YOU FOUND ME1" 87 | db 0x59, 0x4F, 0x55, 0x20, 0x46, 0x4F, 0x55, 0x4E, 0x44, 0x20, 0x4D, 0x45, 0x31, 0x0a 88 | ; .done: 89 | db 0xC3 ; ret 90 | 91 | ; junk + 0x5e 92 | db 0xB4, 0xDE, 0xF0, 0x6B, 0x54, 0x40, 0x08, 0x46, 93 | db 0xF6, 0xAC, 0xDD, 0x82, 0x8C, 0x74, 0x2C, 0x7F, 94 | db 0xBD, 0x0B, 0xC1, 0xBA, 0x12, 0x1F, 0xD0, 0x7C, 95 | db 0x44, 0xFF, 0x43, 0x5F, 0xC6, 0x85, 0xF3, 0x23, 96 | db 0x6B, 0x65, 0x41, 0x2C, 0xB4, 0x4A, 0x5E, 0x24, 97 | db 0x35, 0xBA, 0x57, 0x76, 0x18, 0xAB, 0xE0, 0x51 98 | 99 | ; junk + 0x8e 100 | ; hidden function 101 | db 0x48, 0x8D, 0x35, 0x18, 0x00, 0x00, 0x00, ; lea rsi, [.msg_start] 102 | db 0x48, 0x8D, 0x15, 0x1F, 0x00, 0x00, 0x00, ; lea rdx, [.done] 103 | db 0x48, 0x29, 0xF2 ; sub rdx, rsi 104 | db 0xBF, 0x01, 0x00, 0x00, 0x00 ; mov rdi, 1 ; stdout 105 | db 0xB8, 0x04, 0x00, 0x00, 0x02 ; mov rax, 0x2000004 ; write 106 | db 0x0F, 0x05 ; syscall 107 | db 0xEB, 0x0E ; jmp .done 108 | ; .msg_start: "YOU FOUND ME2" 109 | db 0x59, 0x4F, 0x55, 0x20, 0x46, 0x4F, 0x55, 0x4E, 0x44, 0x20, 0x4D, 0x45, 0x32, 0x0a 110 | ; .done: 111 | db 0xC3 ; ret 112 | 113 | ; evade data flow: return given number integer divided by 2 114 | mapper: 115 | mov rax, rdi ; arg0: number to map 116 | shr rax, 1 117 | ret 118 | -------------------------------------------------------------------------------- /testbins/undiscovered_func_x64.asm: -------------------------------------------------------------------------------- 1 | default rel 2 | 3 | %ifdef OS_IS_WINDOWS 4 | global WinMain 5 | extern ExitProcess 6 | 7 | section .text 8 | WinMain: 9 | %endif 10 | 11 | %ifdef OS_IS_LINUX 12 | global _start 13 | section .text 14 | _start: 15 | %endif 16 | 17 | %ifdef OS_IS_MACOS 18 | global start 19 | section .text 20 | start: 21 | %endif 22 | 23 | call undiscovered 24 | 25 | %ifdef OS_IS_WINDOWS 26 | mov rcx, 0 27 | call ExitProcess 28 | %endif 29 | 30 | %ifdef OS_IS_LINUX 31 | mov rdi, 0 ; arg0: status 32 | mov rax, 60 ; __NR_exit 33 | syscall 34 | %endif 35 | 36 | %ifdef OS_IS_MACOS 37 | mov rax, 0x2000001 ; exit 38 | mov rdi, 0 39 | syscall 40 | %endif 41 | 42 | retn 43 | 44 | undiscovered: 45 | ; lea rax, [rip] 46 | db 0x48, 0x8d, 0x05, 0x00, 0x00, 0x00, 0x00 47 | add rax, 6 48 | ; Fake call to rax 49 | push rax 50 | retn 51 | ; Unlabelled code that binja does not discover automatically 52 | mov rax, 0x1234 53 | mov rbx, 0x5678 54 | retn 55 | 56 | section .data 57 | -------------------------------------------------------------------------------- /titan_engine/Makefile-scyllawrapper: -------------------------------------------------------------------------------- 1 | SWITCHES = /O1 -DWIN32 -D_WINDOWS -D_USRDLL -DSCYLLA_WRAPPER_EXPORTS -DUNICODE -D_UNICODE /EHsc 2 | 3 | all: scylla_wrapper_x64.dll 4 | 5 | scylla_wrapper_x64.dll: ApiReader.o Architecture.o DeviceNameResolver.o dllmain.o IATReferenceScan.o IATSearch.o ImportRebuilder.o NativeWinApi.o PeParser.o ProcessAccessHelp.o ProcessLister.o scylla_wrapper.o stdafx.o StringConversion.o SystemInformation.o Thunks.o 6 | link *.o advapi32.lib distorm_x64.lib /DLL /OUT:scylla_wrapper_x64.dll 7 | 8 | ApiReader.o: ApiReader.cpp 9 | cl /c $(SWITCHES) ApiReader.cpp /Fo:ApiReader.o 10 | 11 | Architecture.o: Architecture.cpp 12 | cl /c $(SWITCHES) Architecture.cpp /Fo:Architecture.o 13 | 14 | DeviceNameResolver.o: DeviceNameResolver.cpp 15 | cl /c $(SWITCHES) DeviceNameResolver.cpp /Fo:DeviceNameResolver.o 16 | 17 | dllmain.o: dllmain.cpp 18 | cl /c $(SWITCHES) dllmain.cpp /Fo:dllmain.o 19 | 20 | IATReferenceScan.o: IATReferenceScan.cpp 21 | cl /c $(SWITCHES) IATReferenceScan.cpp /Fo:IATReferenceScan.o 22 | 23 | IATSearch.o: IATSearch.cpp 24 | cl /c $(SWITCHES) IATSearch.cpp /Fo:IATSearch.o 25 | 26 | ImportRebuilder.o: ImportRebuilder.cpp 27 | cl /c $(SWITCHES) ImportRebuilder.cpp /Fo:ImportRebuilder.o 28 | 29 | NativeWinApi.o: NativeWinApi.cpp 30 | cl /c $(SWITCHES) NativeWinApi.cpp /Fo:NativeWinApi.o 31 | 32 | PeParser.o: PeParser.cpp 33 | cl /c $(SWITCHES) PeParser.cpp /Fo:PeParser.o 34 | 35 | ProcessAccessHelp.o: ProcessAccessHelp.cpp 36 | cl /c $(SWITCHES) ProcessAccessHelp.cpp /Fo:ProcessAccessHelp.o 37 | 38 | ProcessLister.o: ProcessLister.cpp 39 | cl /c $(SWITCHES) ProcessLister.cpp /Fo:ProcessLister.o 40 | 41 | scylla_wrapper.o: scylla_wrapper.cpp 42 | cl /c $(SWITCHES) scylla_wrapper.cpp /Fo:scylla_wrapper.o 43 | 44 | stdafx.o: stdafx.cpp 45 | cl /c $(SWITCHES) stdafx.cpp /Fo:stdafx.o 46 | 47 | StringConversion.o: StringConversion.cpp 48 | cl /c $(SWITCHES) StringConversion.cpp /Fo:StringConversion.o 49 | 50 | SystemInformation.o: SystemInformation.cpp 51 | cl /c $(SWITCHES) SystemInformation.cpp /Fo:SystemInformation.o 52 | 53 | Thunks.o: Thunks.cpp 54 | cl /c $(SWITCHES) Thunks.cpp /Fo:Thunks.o 55 | 56 | -------------------------------------------------------------------------------- /titan_engine/README.md: -------------------------------------------------------------------------------- 1 | (TODO: instructions for compiling titanengine) 2 | 3 | -------------------------------------------------------------------------------- /tools/sniffrsp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # usage: 4 | # sudo ./sniffrsp.py 5 | 6 | import sys 7 | import pyshark 8 | 9 | sys.path.append('.') 10 | sys.path.append('..') 11 | import rsp 12 | 13 | RED = '\x1B[31m' 14 | GREEN = '\x1B[32m' 15 | NORMAL = '\x1B[0m' 16 | 17 | if sys.argv[1:]: 18 | port = int(sys.argv[1]) 19 | filter_expression = 'port %d' % port 20 | ports = [port] 21 | else: 22 | filter_expression = '(port 31337) or (port 31338) or (port 31339) or (port 31340)' 23 | ports = [31337, 31338, 31339, 31340] 24 | 25 | cap = pyshark.LiveCapture(interface='lo', bpf_filter=filter_expression) 26 | 27 | for pkt in cap.sniff_continuously(): 28 | if not hasattr(pkt.tcp, 'payload'): 29 | continue 30 | 31 | srcport = int(str(pkt.tcp.srcport), 10) 32 | if srcport in ports: 33 | print(RED + '<- ', end='') 34 | else: 35 | print(GREEN + '-> ', end='') 36 | 37 | result = '' 38 | for hex2 in str(pkt.tcp.payload).split(':'): 39 | byte = int(hex2, 16) 40 | if byte >= 32 and byte <= 126: 41 | result += chr(byte) 42 | else: 43 | result += '\\x%02X' % byte 44 | 45 | print(result, end='') 46 | if '*' in result: 47 | result = rsp.un_rle(result) 48 | print('\nrle-decoded: ' + result, end='') 49 | 50 | print(NORMAL) 51 | 52 | cap.close() 53 | -------------------------------------------------------------------------------- /tools/xmltest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # POC download/parse description xml's from gdb 4 | 5 | import sys 6 | import socket 7 | import xml.parsers.expat 8 | 9 | sys.path.append('..') 10 | import rsp 11 | 12 | def get_xml(sock, fname): 13 | print('downloading %s' % fname) 14 | data = rsp.tx_rx(sock, 'qXfer:features:read:%s:0,fff' % fname, 'ack_then_reply') 15 | assert data[0] == 'l' 16 | data = rsp.un_rle(data[1:]) 17 | return data 18 | 19 | def download_xml(sock, fname): 20 | with open(fname, 'w') as fp: 21 | xmltxt = get_xml(sock, fname) 22 | print('writing %s' % fname) 23 | fp.write(xmltxt) 24 | 25 | def parse_target_xml(sock): 26 | # 27 | # collect subfiles included from target.xml 28 | # 29 | subfiles = [] 30 | def search_include(name, attrs): 31 | nonlocal subfiles 32 | if 'include' in name: 33 | if name != 'xi:include': 34 | raise Exception('unknown include tag: %s' % name) 35 | if not 'href' in attrs: 36 | raise Exception('include tag attributes contain no href') 37 | fname = attrs['href'] 38 | print('found include: %s' % fname) 39 | subfiles.append(fname) 40 | 41 | p = xml.parsers.expat.ParserCreate() 42 | p.StartElementHandler = search_include 43 | xmltxt = get_xml(sock, 'target.xml') 44 | print(xmltxt) 45 | p.Parse(xmltxt) 46 | 47 | # 48 | # collect registers referenced in all subfiles 49 | # 50 | regnum = 0 51 | regname2num = {} 52 | def search_reg(name, attrs): 53 | nonlocal regnum, regname2num 54 | if name == 'reg': 55 | regname = attrs['name'] 56 | if 'regnum' in attrs: 57 | regnum = int(attrs['regnum']) 58 | print('-------- fast-forwarding regnum to %d' % regnum) 59 | if 'bitsize' in attrs: 60 | bitsize = int(attrs['bitsize']) 61 | print('has bitsize %d' % bitsize) 62 | print('assigning reg %s num %d' % (regname, regnum)) 63 | regname2num[regname] = regnum 64 | regnum += 1 65 | 66 | for fname in subfiles: 67 | print('acquiring %s' % fname) 68 | xmltxt = get_xml(sock, fname) 69 | p = xml.parsers.expat.ParserCreate() 70 | p.StartElementHandler = search_reg 71 | p.Parse(xmltxt) 72 | 73 | # 74 | # done 75 | # 76 | for (name,num) in sorted(regname2num.items(), key=lambda x: x[1]): 77 | print('%d: %s' % (num, name)) 78 | 79 | 80 | if __name__ == '__main__': 81 | port = 31337 82 | if sys.argv[1:]: 83 | port = int(sys.argv[1]) 84 | 85 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 86 | sock.connect(('localhost', port)) 87 | reply = rsp.tx_rx(sock, 'Hgp0.0') 88 | parse_target_xml(sock) 89 | sock.close() 90 | 91 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import struct 2 | import binascii 3 | 4 | RED = '\x1B[31m' 5 | GREEN = '\x1B[32m' 6 | BROWN = '\x1B[33m' 7 | NORMAL = '\x1B[0m' 8 | 9 | def green(msg): 10 | print(GREEN + msg + NORMAL) 11 | 12 | def red(msg): 13 | print(RED + msg + NORMAL) 14 | 15 | def hex_dump(data, addr=0, grouping=1, endian='little'): 16 | result = '' 17 | 18 | while(data): 19 | ascii = '' 20 | buff16 = data[0:16] 21 | data = data[16:] 22 | result += "%s%016X%s: " % (GREEN, addr, NORMAL) 23 | 24 | i = 0 25 | while i < 16: 26 | if(i < len(buff16)): 27 | f0 = { \ 28 | 'big': {1:'>B', 2:'>H', 4:'>I', 8:'>Q'}, \ 29 | 'little': {1:'= ord(' ') and u8 <= ord('~')): 44 | ascii += chr(u8) 45 | else: 46 | ascii += '.' 47 | else: 48 | if grouping == 1: 49 | result += ' '*3 50 | elif grouping == 2: 51 | result += ' '*5 52 | elif grouping == 4: 53 | result += ' '*9 54 | elif grouping == 8: 55 | result += ' '*17 56 | 57 | i += grouping 58 | 59 | result += ' %s\n' % ascii 60 | 61 | addr += 16 62 | 63 | return result 64 | 65 | def disasm1(data, addr, arch='x86_64'): 66 | from binaryninja import Architecture 67 | arch = Architecture[arch] 68 | toksAndLen = arch.get_instruction_text(data, addr) 69 | if not toksAndLen or toksAndLen[1]==0: 70 | return (None, 0) 71 | toks = toksAndLen[0] 72 | strs = ''.join(list(map(lambda x: x.text, toks))) 73 | return [strs, toksAndLen[1]] 74 | 75 | def disasm(data, addr, arch='x86_64'): 76 | if not data: 77 | return 78 | lines = [] 79 | offs = 0 80 | while offs < len(data): 81 | addrstr = '%016X' % (addr+offs) 82 | (asmstr, length) = disasm1(data[offs:], addr+offs, arch) 83 | if length == 0: break 84 | bytestr = binascii.hexlify(data[offs:offs+length]).decode('utf-8').ljust(16) 85 | lines.append('%s%s%s: %s %s' % (GREEN, addrstr, NORMAL, bytestr, asmstr)) 86 | offs += length 87 | return '\n'.join(lines) 88 | 89 | --------------------------------------------------------------------------------