├── .gitignore ├── Analyze_Shellcode.py ├── Annotate_Stack_Strings_In_Selection.py ├── Annotate_Yara_Matches.py ├── Copy_Selection_As_Python.py ├── Copy_Selection_As_Yara.py ├── File_Offset_Here.py ├── Fix_Imports_By_Ordinal.py ├── GNUmakefile ├── LICENSE.md ├── README.md ├── Save_Bytes_From_Here.py ├── Save_Selection_As_Bytes.py └── hopper_api └── __init__.py /.gitignore: -------------------------------------------------------------------------------- 1 | Scripts 2 | -------------------------------------------------------------------------------- /Analyze_Shellcode.py: -------------------------------------------------------------------------------- 1 | # vim: set fileencoding=utf-8 : 2 | 3 | # «Analyze Shellcode» for Hopper 4 4 | # Copyright (c) 2018, Daniel Roethlisberger 5 | # https://github.com/droe/hopper-scripts 6 | # 7 | # This aims at detecting and annotating typical shellcode patterns in Hopper: 8 | # - Known code blocks 9 | # - Popping the return address from the stack as a way to reference data 10 | # - Calling well-known imports by their name hash 11 | # It is not very useful with fully handcrafted shellcode, unless one or more of 12 | # these techniques was used. 13 | # 14 | # For best results: 15 | # 1) Load shellcode at any base address, disabling automatic analysis 16 | # 2) Modify -> Disassemble whole segment 17 | # 3) Run this script 18 | # 19 | # The script asks if it should mark everything unknown and disassemble before 20 | # walking all the disassembled instructions, looking for the above patterns. 21 | # Except for this optional initial disassembling pass, the script does not 22 | # attempt to change code to data and vice-versa. For more tricky shellcode, 23 | # typical workflow is to fix disassembly manually where needed and let the 24 | # script run again to do the annotations. 25 | 26 | 27 | import hopper_api as api 28 | 29 | 30 | IMPORTS = ( 31 | "kernel32.dll!LoadLibraryA", 32 | "kernel32.dll!GetVersion", 33 | "kernel32.dll!GetLastError", 34 | "kernel32.dll!SetUnhandledExceptionFilter", 35 | "kernel32.dll!CreateFileA", 36 | "kernel32.dll!DeleteFileA", 37 | "kernel32.dll!ReadFile", 38 | "kernel32.dll!ReadFileEx", 39 | "kernel32.dll!WriteFile", 40 | "kernel32.dll!WriteFileEx", 41 | "kernel32.dll!SetEvent", 42 | "kernel32.dll!GetTempPathA", 43 | "kernel32.dll!CloseHandle", 44 | "kernel32.dll!VirtualAlloc", 45 | "kernel32.dll!VirtualAllocEx", 46 | "kernel32.dll!VirtualFree", 47 | "kernel32.dll!CreateProcessA", 48 | "kernel32.dll!WriteProcessMemory", 49 | "kernel32.dll!CreateRemoteThread", 50 | "kernel32.dll!GetProcAddress", 51 | "kernel32.dll!WaitForSingleObject", 52 | "kernel32.dll!Sleep", 53 | "kernel32.dll!WinExec", 54 | "kernel32.dll!ExitProcess", 55 | "kernel32.dll!CreateThread", 56 | "kernel32.dll!ExitThread", 57 | "kernel32.dll!CreateNamedPipeA", 58 | "kernel32.dll!CreateNamedPipeW", 59 | "kernel32.dll!ConnectNamedPipe", 60 | "kernel32.dll!DisconnectNamedPipe", 61 | "kernel32.dll!lstrlenA", 62 | "ntdll.dll!RtlCreateUserThread", 63 | "ntdll.dll!RtlExitUserThread", 64 | "advapi32.dll!RevertToSelf", 65 | "advapi32.dll!StartServiceCtrlDispatcherA", 66 | "advapi32.dll!RegisterServiceCtrlHandlerExA", 67 | "advapi32.dll!SetServiceStatus", 68 | "advapi32.dll!OpenSCManagerA", 69 | "advapi32.dll!OpenServiceA", 70 | "advapi32.dll!ChangeServiceConfig2A", 71 | "advapi32.dll!CloseServiceHandle", 72 | "user32.dll!GetDesktopWindow", 73 | "ws2_32.dll!WSAStartup", 74 | "ws2_32.dll!WSASocketA", 75 | "ws2_32.dll!WSAAccept", 76 | "ws2_32.dll!bind", 77 | "ws2_32.dll!listen", 78 | "ws2_32.dll!accept", 79 | "ws2_32.dll!closesocket", 80 | "ws2_32.dll!connect", 81 | "ws2_32.dll!recv", 82 | "ws2_32.dll!send", 83 | "ws2_32.dll!setsockopt", 84 | "ws2_32.dll!gethostbyname", 85 | "wininet.dll!InternetOpenA", 86 | "wininet.dll!InternetConnectA", 87 | "wininet.dll!HttpOpenRequestA", 88 | "wininet.dll!HttpSendRequestA", 89 | "wininet.dll!InternetErrorDlg", 90 | "wininet.dll!InternetReadFile", 91 | "wininet.dll!InternetSetOptionA", 92 | "winhttp.dll!WinHttpOpen", 93 | "winhttp.dll!WinHttpConnect", 94 | "winhttp.dll!WinHttpOpenRequest", 95 | "winhttp.dll!WinHttpSendRequest", 96 | "winhttp.dll!WinHttpReceiveResponse", 97 | "winhttp.dll!WinHttpReadData", 98 | "dnsapi.dll!DnsQuery_A", 99 | "pstorec.dll!PStoreCreateInstance", 100 | ) 101 | 102 | 103 | # First match per start address wins. 104 | KNOWN_BLOCKS = ( 105 | { 106 | 'name': 'dll_call_by_hash', 107 | 'proc': True, 108 | 'comment': """ 109 | Metasploit x86 call-by-hash by Stephen Fewer 110 | https://github.com/rapid7/metasploit-framework/blob/master/external/source/shellcode/windows/x86/src/block/block_api.asm 111 | """, 112 | 'start': ( 113 | 0x60, # pushal 114 | 0x89, 0xE5, # mov ebp, esp 115 | 0x31, 0xD2, # xor edx, edx 116 | 0x64, 0x8B, 0x52, 0x30, # mov edx, dword [fs:edx+0x30] 117 | 0x8B, 0x52, 0x0C, # mov edx, dword [edx+0xc] 118 | 0x8B, 0x52, 0x14, # mov edx, dword [edx+0x14] 119 | ), 120 | 'end': ( 121 | 0x61, # popal 122 | 0x59, # pop ecx 123 | 0x5A, # pop edx 124 | 0x51, # push ecx 125 | 0xFF, 0xE0, # jmp eax 126 | 0x58, # pop eax 127 | 0x5F, # pop edi 128 | 0x5A, # pop edx 129 | 0x8B, 0x12, # mov edx, dword [edx] 130 | 0xEB, 0x86, # jmp 131 | ), 132 | }, { 133 | 'name': 'dll_call_by_hash', 134 | 'proc': True, 135 | 'comment': """ 136 | Metasploit x64 call-by-hash by Stephen Fewer 137 | https://github.com/rapid7/metasploit-framework/blob/master/external/source/shellcode/windows/x64/src/block/block_api.asm 138 | """, 139 | 'start': ( 140 | 0x41, 0x51, # push r9 141 | 0x41, 0x50, # push r8 142 | 0x52, # push rdx 143 | 0x51, # push rcx 144 | 0x56, # push rsi 145 | 0x48, 0x31, 0xD2, # xor rdx, rdx 146 | 0x65, 0x48, 0x8B, 0x52, 0x60, # mov rdx, qword [gs:rdx+0x60] 147 | 0x48, 0x8B, 0x52, 0x18, # mov rdx, qword [rdx+0x18] 148 | 0x48, 0x8B, 0x52, 0x20, # mov rdx, qword [rdx+0x20] 149 | ), 150 | 'end': ( 151 | 0x5E, # pop rsi 152 | 0x59, # pop rcx 153 | 0x5A, # pop rdx 154 | 0x41, 0x58, # pop r8 155 | 0x41, 0x59, # pop r9 156 | 0x41, 0x5A, # pop r10 157 | 0x48, 0x83, 0xEC, 0x20, # sub rsp, 0x20 158 | 0x41, 0x52, # push r10 159 | 0xFF, 0xE0, # jmp rax 160 | 0x58, # pop rax 161 | 0x41, 0x59, # pop r9 162 | 0x5A, # pop rdx 163 | 0x48, 0x8B, 0x12, # mov rdx, qword [rdx] 164 | 0xE9, 0x4F, 0xFF, 0xFF, 0xFF, # jmp 165 | ), 166 | }, 167 | ) 168 | 169 | 170 | class ImportHashes: 171 | CHECK = 'kernel32.dll!LoadLibraryA' 172 | METHODS = ('msf', ) 173 | CHECKSUMS = { 174 | 'msf': 0x0726774C, 175 | } 176 | 177 | def __init__(self): 178 | self._hashmaps = {} 179 | for which in self.METHODS: 180 | #print('Method %s' % which) 181 | self._hashmaps[which] = {} 182 | for spec in IMPORTS: 183 | m, f = spec.split('!') 184 | h = self._hash(which, m, f) 185 | if not h in self._hashmaps[which]: 186 | #print('Adding 0x%08x %s' % (h, spec)) 187 | self._hashmaps[which][h] = spec 188 | assert(self.CHECKSUMS[which] in self._hashmaps[which]) 189 | assert(self._hashmaps[which][self.CHECKSUMS[which]] == self.CHECK) 190 | 191 | def __contains__(self, x): 192 | return any([x in self._hashmaps[t] for t in self._hashmaps]) 193 | 194 | def __getitem__(self, k): 195 | # Currently returns the first match; should improve this to handle 196 | # collisions between different hashing methods in a more useful way 197 | for t in self._hashmaps: 198 | if k in self._hashmaps[t]: 199 | return self._hashmaps[t][k] 200 | raise KeyError(k) 201 | 202 | def _ror32(self, x, bits): 203 | return (x >> bits | x << (32 - bits)) & 0xFFFFFFFF 204 | 205 | def _wide(self, s): 206 | out = [] 207 | for c in s: 208 | out.append(c) 209 | out.append("\x00") 210 | return ''.join(out) 211 | 212 | def _hash_ror(self, module, function, bits): 213 | mhash = 0 214 | fhash = 0 215 | for c in self._wide(module + "\x00"): 216 | mhash = self._ror32(mhash, bits) + ord(c) 217 | for c in function + "\x00": 218 | fhash = self._ror32(fhash, bits) + ord(c) 219 | return (mhash + fhash) & 0xFFFFFFFF 220 | 221 | def _hash(self, which, module, function): 222 | if which == 'msf': 223 | return self._hash_ror(module.upper(), function, 13) 224 | raise NotImplementedError(which) 225 | 226 | 227 | class KnownBlocksHelper: 228 | # XXX this works, but badly needs a rewrite 229 | 230 | def __init__(self, seg, addr, size): 231 | self._seg = seg 232 | self._addr = addr 233 | self._size = size 234 | self._buf = seg.readBytes(addr, size) 235 | 236 | def compare_bytes(self, pos, literals): 237 | if pos < self._addr or pos >= self._addr + self._size: 238 | raise ValueError("pos out of bounds") 239 | if pos + len(literals) > self._addr + self._size: 240 | return False 241 | for i in range(len(literals)): 242 | if self._buf[pos + i - self._addr] != chr(literals[i]): 243 | return False 244 | return True 245 | 246 | def find_bytes(self, pos, literals): 247 | if pos < self._addr or pos >= self._addr + self._size: 248 | raise ValueError("pos out of bounds") 249 | if pos + len(literals) > self._addr + self._size: 250 | return -1 251 | remaining_size = self._size - (pos - self._addr) 252 | for i in range(remaining_size - len(literals)): 253 | if self.compare_bytes(pos + i, literals): 254 | return pos + i 255 | return -1 256 | 257 | def yield_matches(self, literals): 258 | match_addr = self.find_bytes(self._addr, literals) 259 | while match_addr != -1: 260 | yield match_addr 261 | match_addr = self.find_bytes(match_addr + 1, literals) 262 | 263 | def yield_known_blocks(self): 264 | matched_addrs = set() 265 | for block in KNOWN_BLOCKS: 266 | for start_addr in self.yield_matches(block['start']): 267 | if start_addr in matched_addrs: 268 | continue 269 | if 'end' in block: 270 | end_addr = self.find_bytes(start_addr + 1, block['end']) 271 | if not end_addr > start_addr: 272 | # if end cannot be found here, it will never be found 273 | break 274 | end_addr += len(block['end']) 275 | else: # block['size']? 276 | end_addr = start_addr + len(block['start']) 277 | yield block, start_addr, end_addr 278 | matched_addrs.add(start_addr) 279 | 280 | 281 | def first_stack_instruction(where, pos, n=16): 282 | for ins in where.instructions(pos): 283 | if ins.raw == None: 284 | break 285 | if ins.raw.isAConditionalJump() or ins.raw.isAnInconditionalJump(): 286 | break 287 | op = ins.op 288 | if op in ('hlt', 'int', 'enter', 'leave'): 289 | break 290 | if op.startswith('ret') or op.startswith('iret') or \ 291 | op.startswith('sys'): 292 | break 293 | if op.startswith('push') or op.startswith('pop'): 294 | return ins 295 | if ins.addr > pos + n: 296 | break 297 | return None 298 | 299 | 300 | def main(): 301 | print("Arch: %s" % api.executable.arch) 302 | 303 | seg = api.segments.current() 304 | sel = api.selection() 305 | 306 | if sel.is_range(): 307 | print("operating on current selection") 308 | shellcode = sel 309 | else: 310 | ans = api.message("Mark segment as undefined and disassemble?", 311 | ['Cancel', 'No', 'Yes']) 312 | if ans == 0: 313 | return 314 | elif ans == 2: 315 | seg.mark_as_undefined() 316 | seg.disassemble() 317 | 318 | print("operating on current segment") 319 | shellcode = seg 320 | 321 | print("analyzing range %x:%x" % (shellcode.start, shellcode.end)) 322 | 323 | # identify and mark known blocks 324 | kbhelper = KnownBlocksHelper(seg.raw, 325 | shellcode.start, len(shellcode)) 326 | for block, start_addr, end_addr in kbhelper.yield_known_blocks(): 327 | print("---> found known block '%s' at %x" % (block['name'], 328 | start_addr)) 329 | name = "%s_%x" % (block['name'], start_addr) 330 | api.set_label(start_addr, name) 331 | if 'proc' in block and block['proc']: 332 | api.mark_as_procedure(start_addr) 333 | if 'comment' in block and block['comment']: 334 | api.set_comment(start_addr, block['comment']) 335 | if 'inline_comment' in block and block['inline_comment']: 336 | api.set_icomment(start_addr, block['inline_comment']) 337 | if 'offsets' in block: 338 | for offset, offset_name in block['offsets']: 339 | offset_addr = start_addr + offset 340 | offset_name = "%s_%x" % (offset_name, offset_addr) 341 | api.set_label(offset_addr, offset_name) 342 | 343 | # xref or annotate call, pop reg combo 344 | for ins in shellcode.instructions(): 345 | if ins.op != 'call': 346 | continue 347 | arg = ins.arg(0) 348 | if not arg.startswith('0x'): 349 | continue 350 | target_addr = int(arg, 16) 351 | stack_ins = first_stack_instruction(shellcode, target_addr) 352 | if stack_ins == None or stack_ins.op != 'pop': 353 | continue 354 | 355 | if api.get_label(target_addr) == None: 356 | api.set_label(target_addr, "pop_retaddr_%x" % target_addr) 357 | 358 | #reg = stack_ins.arg(0) 359 | print("---> found call + pop retaddr combo at %x -> %x" % ( 360 | ins.addr, target_addr)) 361 | 362 | loaded_addr = ins.addr + len(ins) 363 | if loaded_addr == shellcode.end: 364 | # Hopper silently ignores xrefs to EOF 365 | api.set_icomment(stack_ins.addr, "end of shellcode") 366 | else: 367 | api.add_reference(stack_ins.addr, loaded_addr) 368 | if api.get_label(loaded_addr) == None: 369 | api.set_label(loaded_addr, "retaddr_%x" % loaded_addr) 370 | 371 | 372 | # annotate known import hashes 373 | hashes = ImportHashes() 374 | hash_ops = { 375 | # op, arg index 376 | 'push': 0, 377 | 'mov': 1, 378 | 'movabs': 1, 379 | } 380 | for ins in shellcode.instructions(): 381 | op = ins.op 382 | if not op in hash_ops: 383 | continue 384 | arg = ins.arg(hash_ops[op]) 385 | if not arg.startswith('0x'): 386 | continue 387 | cand_hash = int(arg, 16) 388 | if cand_hash in hashes: 389 | name = hashes[cand_hash] 390 | api.set_icomment(ins.addr, name) 391 | 392 | 393 | if __name__ == '__main__': 394 | api.run(main) 395 | 396 | -------------------------------------------------------------------------------- /Annotate_Stack_Strings_In_Selection.py: -------------------------------------------------------------------------------- 1 | # vim: set fileencoding=utf-8 : 2 | 3 | # «Annotate Stack Strings In Selection» for Hopper 4 4 | # Copyright (c) 2018, Daniel Roethlisberger 5 | # https://github.com/droe/hopper-scripts 6 | # 7 | # Annotate stack strings in the current selection with their decoded string 8 | # form. Supports XOR decryption using a single or multi-byte key and no 9 | # feedback. 10 | 11 | 12 | import hopper_api as api 13 | import binascii 14 | from itertools import cycle 15 | 16 | 17 | def unhexlify(s): 18 | if len(s) % 2 != 0: 19 | s = '0' + s 20 | return binascii.unhexlify(s) 21 | 22 | 23 | def xorcrypt(buf, key): 24 | if len(key) == 0: 25 | return buf 26 | k = cycle(key) 27 | return bytes(b ^ next(k) for b in buf) 28 | 29 | 30 | def main(): 31 | ans = api.ask_hex("XOR key in hex (optional)") 32 | if ans == None: 33 | return 34 | if len(ans) == 0: 35 | key = '\x00' 36 | else: 37 | key = unhexlify(ans) 38 | 39 | for ins in api.selection().instructions(): 40 | if ins.op in ('mov', 'movabs'): 41 | data = ins.arg(1) 42 | else: 43 | continue 44 | if not data.startswith('0x'): 45 | continue 46 | data = unhexlify(data[2:]) 47 | data = reversed(data) 48 | data = xorcrypt(data, key) 49 | api.set_icomment(ins.addr, repr(data)) 50 | 51 | 52 | if __name__ == '__main__': 53 | api.run(main) 54 | 55 | -------------------------------------------------------------------------------- /Annotate_Yara_Matches.py: -------------------------------------------------------------------------------- 1 | # vim: set fileencoding=utf-8 : 2 | 3 | # «Annotate Yara Matches» for Hopper 4 4 | # Copyright (c) 2018, Daniel Roethlisberger 5 | # https://github.com/droe/hopper-scripts 6 | # 7 | # Annotate all occurences of strings in one or multiple matching yara rules. 8 | # Adds a summary at the beginning of the executable and for each string 9 | # occurence. 10 | # 11 | # Uses yara via command line in order to avoid python module installation 12 | # hassles; at least on macOS, Hopper uses system python2, not Brew or MacPorts. 13 | 14 | 15 | import hopper_api as api 16 | import os 17 | import re 18 | import subprocess 19 | import tempfile 20 | 21 | 22 | def parse_yara_meta(s): 23 | meta = {} 24 | while len(s) > 0: 25 | m = re.match('^([a-zA-Z0-9_]+)="', s) 26 | if not m: 27 | raise ValueError("yara meta syntax error: %s" % s[:10]) 28 | k = m.group(1) 29 | v = None 30 | for i in range(len(k) + 2, len(s)): 31 | if s[i:i+1] == '"' and s[i-1:i] != '\\': 32 | v = s[len(k)+2:i].replace('\\"', '"') 33 | s = s[i+2:] 34 | break 35 | if v == None: 36 | raise ValueError("yara meta syntax error: %s" % s[:10]) 37 | meta[k] = v 38 | return meta 39 | 40 | 41 | def parse_yara_out(s): 42 | matches = [] 43 | for line in s.splitlines(): 44 | m = re.match('^([^ ]+) \\[(.*)\\] [^ ]+$', line) 45 | if m: 46 | rule = m.group(1) 47 | meta = m.group(2) 48 | meta = parse_yara_meta(meta) 49 | matches.append((rule, meta, [])) 50 | continue 51 | m = re.match('^(0x[0-9a-fA-F]+):(\\$[^ ]+):.*$', line) 52 | if m: 53 | offset = int(m.group(1), 16) 54 | string = m.group(2) 55 | matches[-1][2].append((offset, string)) 56 | continue 57 | raise ValueError("Syntax error in yara output: %s" % line) 58 | return matches 59 | 60 | 61 | def yara(rulefile, data): 62 | tmpdir = tempfile.mkdtemp() 63 | with open(tmpdir + '/executable', 'w') as f: 64 | f.write(data) 65 | 66 | proc = subprocess.Popen(["@@yara@@", "-s", "-m", "-r", rulefile, tmpdir], 67 | env={'LANG': 'en_US.UTF-8'}, 68 | stdout=subprocess.PIPE, 69 | stderr=subprocess.PIPE, 70 | shell=False) 71 | out, err = proc.communicate() 72 | if proc.returncode != 0: 73 | raise RuntimeError("yara returned %i\n%s" % (proc.returncode, err)) 74 | 75 | os.remove(tmpdir + '/executable') 76 | os.rmdir(tmpdir) 77 | return parse_yara_out(out) 78 | 79 | 80 | def main(): 81 | rulefile = api.ask_file("Yara rule", None, False) 82 | if not rulefile: 83 | return 84 | info = ["Matching yara rules from %s:" % rulefile] 85 | for rule, meta, strings in yara(rulefile, api.executable.bytes()): 86 | info.append(" rule %s" % rule) 87 | for k, v in meta.iteritems(): 88 | info.append(" %s: %s" % (k, v)) 89 | for fileoffset, name in strings: 90 | addr = api.otoa(fileoffset) 91 | info.append(" 0x%x %x: %s" % (fileoffset, addr, name)) 92 | comment = "yara: %s:%s" % (rule, name) 93 | api.add_comment(addr, comment) 94 | if len(info) == 1: 95 | info.append(' None') 96 | info = '\n'.join(info) 97 | api.add_comment(api.otoa(0), info) 98 | print(info) 99 | 100 | 101 | if __name__ == '__main__': 102 | api.run(main) 103 | 104 | -------------------------------------------------------------------------------- /Copy_Selection_As_Python.py: -------------------------------------------------------------------------------- 1 | # vim: set fileencoding=utf-8 : 2 | 3 | # «Copy Selection As Python» for Hopper 4 4 | # Copyright (c) 2018, Daniel Roethlisberger 5 | # https://github.com/droe/hopper-scripts 6 | # 7 | # Copy the current selection to clipboard in python syntax 8 | 9 | 10 | import hopper_api as api 11 | import re 12 | 13 | 14 | def render(binary, assembly): 15 | pattern = ", 0x".join(binary[i:i+2] for i in range(0, len(binary), 2)) 16 | pattern = "0x%s," % pattern 17 | return " %-36s# %s" % (pattern, assembly) 18 | 19 | 20 | def main(): 21 | out = [] 22 | for ins in api.selection().instructions(): 23 | out.append(render(hex(ins), str(ins))) 24 | api.clipboard.copy('\n'.join(out + [''])) 25 | 26 | 27 | if __name__ == '__main__': 28 | api.run(main) 29 | 30 | -------------------------------------------------------------------------------- /Copy_Selection_As_Yara.py: -------------------------------------------------------------------------------- 1 | # vim: set fileencoding=utf-8 : 2 | 3 | # «Copy Selection As Yara» for Hopper 4 4 | # Copyright (c) 2018, Daniel Roethlisberger 5 | # https://github.com/droe/hopper-scripts 6 | # 7 | # Copy the current selection to clipboard in yara syntax 8 | 9 | 10 | import hopper_api as api 11 | import re 12 | 13 | 14 | def render(binary, assembly): 15 | pattern = " ".join(binary[i:i+2] for i in range(0, len(binary), 2)) 16 | return " %-34s// %s" % (pattern, assembly) 17 | 18 | 19 | def main(): 20 | out = [] 21 | for ins in api.selection().instructions(): 22 | out.append(render(hex(ins), str(ins))) 23 | api.clipboard.copy('\n'.join(out + [''])) 24 | 25 | 26 | if __name__ == '__main__': 27 | api.run(main) 28 | 29 | -------------------------------------------------------------------------------- /File_Offset_Here.py: -------------------------------------------------------------------------------- 1 | # vim: set fileencoding=utf-8 : 2 | 3 | # «File Offset Here» for Hopper 4 4 | # Copyright (c) 2018, Daniel Roethlisberger 5 | # https://github.com/droe/hopper-scripts 6 | # 7 | # Write the file offset at the cursor position into a prefix comment 8 | 9 | 10 | import hopper_api as api 11 | 12 | 13 | def main(): 14 | addr = api.selection().start 15 | offset = api.atoo(addr) 16 | comment = "File offset here: %x (%i)" % (offset, offset) 17 | api.add_comment(addr, comment) 18 | 19 | 20 | if __name__ == '__main__': 21 | api.run(main) 22 | 23 | -------------------------------------------------------------------------------- /Fix_Imports_By_Ordinal.py: -------------------------------------------------------------------------------- 1 | # vim: set fileencoding=utf-8 : 2 | 3 | # «Fix Imports By Ordinal» for Hopper 4 4 | # Copyright (c) 2018, Daniel Roethlisberger 5 | # https://github.com/droe/hopper-scripts 6 | # 7 | # Rename imports by ordinal to their actual names. Operates on the selection, 8 | # or on all segments if no selection was made. 9 | # 10 | # Unfortunately, Hopper does not currently allow full-featured access to 11 | # external definitions from scripts. Best we can do is rename the labels, but 12 | # that will result in a different user experience than if the import would have 13 | # been handled by Hopper by name. 14 | 15 | 16 | import hopper_api as api 17 | import re 18 | 19 | 20 | IMPORTS = { 21 | 'ws2_32.dll': { 22 | 1: 'accept', 23 | 2: 'bind', 24 | 3: 'closesocket', 25 | 4: 'connect', 26 | 5: 'getpeername', 27 | 6: 'getsockname', 28 | 7: 'getsockopt', 29 | 8: 'htonl', 30 | 9: 'htons', 31 | 10: 'ioctlsocket', 32 | 11: 'inet_addr', 33 | 12: 'inet_ntoa', 34 | 13: 'listen', 35 | 14: 'ntohl', 36 | 15: 'ntohs', 37 | 16: 'recv', 38 | 17: 'recvfrom', 39 | 18: 'select', 40 | 19: 'send', 41 | 20: 'sendto', 42 | 21: 'setsockopt', 43 | 22: 'shutdown', 44 | 23: 'socket', 45 | 24: 'WSApSetPostRoutine', 46 | 25: 'FreeAddrInfoEx', 47 | 26: 'FreeAddrInfoExW', 48 | 27: 'FreeAddrInfoW', 49 | 28: 'GetAddrInfoExA', 50 | 29: 'GetAddrInfoExCancel', 51 | 30: 'GetAddrInfoExOverlappedResult', 52 | 31: 'GetAddrInfoExW', 53 | 32: 'GetAddrInfoW', 54 | 33: 'GetHostNameW', 55 | 34: 'GetNameInfoW', 56 | 35: 'InetNtopW', 57 | 36: 'InetPtonW', 58 | 37: 'SetAddrInfoExA', 59 | 38: 'SetAddrInfoExW', 60 | 39: 'WPUCompleteOverlappedRequest', 61 | 40: 'WPUGetProviderPathEx', 62 | 41: 'WSAAccept', 63 | 42: 'WSAAddressToStringA', 64 | 43: 'WSAAddressToStringW', 65 | 44: 'WSAAdvertiseProvider', 66 | 45: 'WSACloseEvent', 67 | 46: 'WSAConnect', 68 | 47: 'WSAConnectByList', 69 | 48: 'WSAConnectByNameA', 70 | 49: 'WSAConnectByNameW', 71 | 50: 'WSACreateEvent', 72 | 51: 'gethostbyaddr', 73 | 52: 'gethostbyname', 74 | 53: 'getprotobyname', 75 | 54: 'getprotobynumber', 76 | 55: 'getservbyname', 77 | 56: 'getservbyport', 78 | 57: 'gethostname', 79 | 58: 'WSADuplicateSocketA', 80 | 59: 'WSADuplicateSocketW', 81 | 60: 'WSAEnumNameSpaceProvidersA', 82 | 61: 'WSAEnumNameSpaceProvidersExA', 83 | 62: 'WSAEnumNameSpaceProvidersExW', 84 | 63: 'WSAEnumNameSpaceProvidersW', 85 | 64: 'WSAEnumNetworkEvents', 86 | 65: 'WSAEnumProtocolsA', 87 | 66: 'WSAEnumProtocolsW', 88 | 67: 'WSAEventSelect', 89 | 68: 'WSAGetOverlappedResult', 90 | 69: 'WSAGetQOSByName', 91 | 70: 'WSAGetServiceClassInfoA', 92 | 71: 'WSAGetServiceClassInfoW', 93 | 72: 'WSAGetServiceClassNameByClassIdA', 94 | 73: 'WSAGetServiceClassNameByClassIdW', 95 | 74: 'WSAHtonl', 96 | 75: 'WSAHtons', 97 | 76: 'WSAInstallServiceClassA', 98 | 77: 'WSAInstallServiceClassW', 99 | 78: 'WSAIoctl', 100 | 79: 'WSAJoinLeaf', 101 | 80: 'WSALookupServiceBeginA', 102 | 81: 'WSALookupServiceBeginW', 103 | 82: 'WSALookupServiceEnd', 104 | 83: 'WSALookupServiceNextA', 105 | 84: 'WSALookupServiceNextW', 106 | 85: 'WSANSPIoctl', 107 | 86: 'WSANtohl', 108 | 87: 'WSANtohs', 109 | 88: 'WSAPoll', 110 | 89: 'WSAProviderCompleteAsyncCall', 111 | 90: 'WSAProviderConfigChange', 112 | 91: 'WSARecv', 113 | 92: 'WSARecvDisconnect', 114 | 93: 'WSARecvFrom', 115 | 94: 'WSARemoveServiceClass', 116 | 95: 'WSAResetEvent', 117 | 96: 'WSASend', 118 | 97: 'WSASendDisconnect', 119 | 98: 'WSASendMsg', 120 | 99: 'WSASendTo', 121 | 100: 'WSASetEvent', 122 | 101: 'WSAAsyncSelect', 123 | 102: 'WSAAsyncGetHostByAddr', 124 | 103: 'WSAAsyncGetHostByName', 125 | 104: 'WSAAsyncGetProtoByNumber', 126 | 105: 'WSAAsyncGetProtoByName', 127 | 106: 'WSAAsyncGetServByPort', 128 | 107: 'WSAAsyncGetServByName', 129 | 108: 'WSACancelAsyncRequest', 130 | 109: 'WSASetBlockingHook', 131 | 110: 'WSAUnhookBlockingHook', 132 | 111: 'WSAGetLastError', 133 | 112: 'WSASetLastError', 134 | 113: 'WSACancelBlockingCall', 135 | 114: 'WSAIsBlocking', 136 | 115: 'WSAStartup', 137 | 116: 'WSACleanup', 138 | 117: 'WSASetServiceA', 139 | 118: 'WSASetServiceW', 140 | 119: 'WSASocketA', 141 | 120: 'WSASocketW', 142 | 121: 'WSAStringToAddressA', 143 | 122: 'WSAStringToAddressW', 144 | 123: 'WSAUnadvertiseProvider', 145 | 124: 'WSAWaitForMultipleEvents', 146 | 125: 'WSCDeinstallProvider', 147 | 126: 'WSCDeinstallProvider32', 148 | 127: 'WSCDeinstallProviderEx', 149 | 128: 'WSCEnableNSProvider', 150 | 129: 'WSCEnableNSProvider32', 151 | 130: 'WSCEnumNameSpaceProviders32', 152 | 131: 'WSCEnumNameSpaceProvidersEx32', 153 | 132: 'WSCEnumProtocols', 154 | 133: 'WSCEnumProtocols32', 155 | 134: 'WSCEnumProtocolsEx', 156 | 135: 'WSCGetApplicationCategory', 157 | 136: 'WSCGetApplicationCategoryEx', 158 | 137: 'WSCGetProviderInfo', 159 | 138: 'WSCGetProviderInfo32', 160 | 139: 'WSCGetProviderPath', 161 | 140: 'WSCGetProviderPath32', 162 | 141: 'WSCInstallNameSpace', 163 | 142: 'WSCInstallNameSpace32', 164 | 143: 'WSCInstallNameSpaceEx', 165 | 144: 'WSCInstallNameSpaceEx2', 166 | 145: 'WSCInstallNameSpaceEx32', 167 | 146: 'WSCInstallProvider', 168 | 147: 'WSCInstallProvider64_32', 169 | 148: 'WSCInstallProviderAndChains64_32', 170 | 149: 'WSCInstallProviderEx', 171 | 150: 'WSCSetApplicationCategory', 172 | 151: '__WSAFDIsSet', 173 | 152: 'WSCSetApplicationCategoryEx', 174 | 153: 'WSCSetProviderInfo', 175 | 154: 'WSCSetProviderInfo32', 176 | 155: 'WSCUnInstallNameSpace', 177 | 156: 'WSCUnInstallNameSpace32', 178 | 157: 'WSCUnInstallNameSpaceEx2', 179 | 158: 'WSCUpdateProvider', 180 | 159: 'WSCUpdateProvider32', 181 | 160: 'WSCUpdateProviderEx', 182 | 161: 'WSCWriteNameSpaceOrder', 183 | 162: 'WSCWriteNameSpaceOrder32', 184 | 163: 'WSCWriteProviderOrder', 185 | 164: 'WSCWriteProviderOrder32', 186 | 165: 'WSCWriteProviderOrderEx', 187 | 166: 'WahCloseApcHelper', 188 | 167: 'WahCloseHandleHelper', 189 | 168: 'WahCloseNotificationHandleHelper', 190 | 169: 'WahCloseSocketHandle', 191 | 170: 'WahCloseThread', 192 | 171: 'WahCompleteRequest', 193 | 172: 'WahCreateHandleContextTable', 194 | 173: 'WahCreateNotificationHandle', 195 | 174: 'WahCreateSocketHandle', 196 | 175: 'WahDestroyHandleContextTable', 197 | 176: 'WahDisableNonIFSHandleSupport', 198 | 177: 'WahEnableNonIFSHandleSupport', 199 | 178: 'WahEnumerateHandleContexts', 200 | 179: 'WahInsertHandleContext', 201 | 180: 'WahNotifyAllProcesses', 202 | 181: 'WahOpenApcHelper', 203 | 182: 'WahOpenCurrentThread', 204 | 183: 'WahOpenHandleHelper', 205 | 184: 'WahOpenNotificationHandleHelper', 206 | 185: 'WahQueueUserApc', 207 | 186: 'WahReferenceContextByHandle', 208 | 187: 'WahRemoveHandleContext', 209 | 188: 'WahWaitForNotification', 210 | 189: 'WahWriteLSPEvent', 211 | 190: 'freeaddrinfo', 212 | 191: 'getaddrinfo', 213 | 192: 'getnameinfo', 214 | 193: 'inet_ntop', 215 | 194: 'inet_pton', 216 | 500: 'WEP', 217 | }, 218 | 'oleaut32.dll': { 219 | 2: 'SysAllocString', 220 | 3: 'SysReAllocString', 221 | 4: 'SysAllocStringLen', 222 | 5: 'SysReAllocStringLen', 223 | 6: 'SysFreeString', 224 | 7: 'SysStringLen', 225 | 8: 'VariantInit', 226 | 9: 'VariantClear', 227 | 10: 'VariantCopy', 228 | 11: 'VariantCopyInd', 229 | 12: 'VariantChangeType', 230 | 13: 'VariantTimeToDosDateTime', 231 | 14: 'DosDateTimeToVariantTime', 232 | 15: 'SafeArrayCreate', 233 | 16: 'SafeArrayDestroy', 234 | 17: 'SafeArrayGetDim', 235 | 18: 'SafeArrayGetElemsize', 236 | 19: 'SafeArrayGetUBound', 237 | 20: 'SafeArrayGetLBound', 238 | 21: 'SafeArrayLock', 239 | 22: 'SafeArrayUnlock', 240 | 23: 'SafeArrayAccessData', 241 | 24: 'SafeArrayUnaccessData', 242 | 25: 'SafeArrayGetElement', 243 | 26: 'SafeArrayPutElement', 244 | 27: 'SafeArrayCopy', 245 | 28: 'DispGetParam', 246 | 29: 'DispGetIDsOfNames', 247 | 30: 'DispInvoke', 248 | 31: 'CreateDispTypeInfo', 249 | 32: 'CreateStdDispatch', 250 | 33: 'RegisterActiveObject', 251 | 34: 'RevokeActiveObject', 252 | 35: 'GetActiveObject', 253 | 36: 'SafeArrayAllocDescriptor', 254 | 37: 'SafeArrayAllocData', 255 | 38: 'SafeArrayDestroyDescriptor', 256 | 39: 'SafeArrayDestroyData', 257 | 40: 'SafeArrayRedim', 258 | 41: 'SafeArrayAllocDescriptorEx', 259 | 42: 'SafeArrayCreateEx', 260 | 43: 'SafeArrayCreateVectorEx', 261 | 44: 'SafeArraySetRecordInfo', 262 | 45: 'SafeArrayGetRecordInfo', 263 | 46: 'VarParseNumFromStr', 264 | 47: 'VarNumFromParseNum', 265 | 48: 'VarI2FromUI1', 266 | 49: 'VarI2FromI4', 267 | 50: 'VarI2FromR4', 268 | 51: 'VarI2FromR8', 269 | 52: 'VarI2FromCy', 270 | 53: 'VarI2FromDate', 271 | 54: 'VarI2FromStr', 272 | 55: 'VarI2FromDisp', 273 | 56: 'VarI2FromBool', 274 | 57: 'SafeArraySetIID', 275 | 58: 'VarI4FromUI1', 276 | 59: 'VarI4FromI2', 277 | 60: 'VarI4FromR4', 278 | 61: 'VarI4FromR8', 279 | 62: 'VarI4FromCy', 280 | 63: 'VarI4FromDate', 281 | 64: 'VarI4FromStr', 282 | 65: 'VarI4FromDisp', 283 | 66: 'VarI4FromBool', 284 | 67: 'SafeArrayGetIID', 285 | 68: 'VarR4FromUI1', 286 | 69: 'VarR4FromI2', 287 | 70: 'VarR4FromI4', 288 | 71: 'VarR4FromR8', 289 | 72: 'VarR4FromCy', 290 | 73: 'VarR4FromDate', 291 | 74: 'VarR4FromStr', 292 | 75: 'VarR4FromDisp', 293 | 76: 'VarR4FromBool', 294 | 77: 'SafeArrayGetVartype', 295 | 78: 'VarR8FromUI1', 296 | 79: 'VarR8FromI2', 297 | 80: 'VarR8FromI4', 298 | 81: 'VarR8FromR4', 299 | 82: 'VarR8FromCy', 300 | 83: 'VarR8FromDate', 301 | 84: 'VarR8FromStr', 302 | 85: 'VarR8FromDisp', 303 | 86: 'VarR8FromBool', 304 | 87: 'VarFormat', 305 | 88: 'VarDateFromUI1', 306 | 89: 'VarDateFromI2', 307 | 90: 'VarDateFromI4', 308 | 91: 'VarDateFromR4', 309 | 92: 'VarDateFromR8', 310 | 93: 'VarDateFromCy', 311 | 94: 'VarDateFromStr', 312 | 95: 'VarDateFromDisp', 313 | 96: 'VarDateFromBool', 314 | 97: 'VarFormatDateTime', 315 | 98: 'VarCyFromUI1', 316 | 99: 'VarCyFromI2', 317 | 100: 'VarCyFromI4', 318 | 101: 'VarCyFromR4', 319 | 102: 'VarCyFromR8', 320 | 103: 'VarCyFromDate', 321 | 104: 'VarCyFromStr', 322 | 105: 'VarCyFromDisp', 323 | 106: 'VarCyFromBool', 324 | 107: 'VarFormatNumber', 325 | 108: 'VarBstrFromUI1', 326 | 109: 'VarBstrFromI2', 327 | 110: 'VarBstrFromI4', 328 | 111: 'VarBstrFromR4', 329 | 112: 'VarBstrFromR8', 330 | 113: 'VarBstrFromCy', 331 | 114: 'VarBstrFromDate', 332 | 115: 'VarBstrFromDisp', 333 | 116: 'VarBstrFromBool', 334 | 117: 'VarFormatPercent', 335 | 118: 'VarBoolFromUI1', 336 | 119: 'VarBoolFromI2', 337 | 120: 'VarBoolFromI4', 338 | 121: 'VarBoolFromR4', 339 | 122: 'VarBoolFromR8', 340 | 123: 'VarBoolFromDate', 341 | 124: 'VarBoolFromCy', 342 | 125: 'VarBoolFromStr', 343 | 126: 'VarBoolFromDisp', 344 | 127: 'VarFormatCurrency', 345 | 128: 'VarWeekdayName', 346 | 129: 'VarMonthName', 347 | 130: 'VarUI1FromI2', 348 | 131: 'VarUI1FromI4', 349 | 132: 'VarUI1FromR4', 350 | 133: 'VarUI1FromR8', 351 | 134: 'VarUI1FromCy', 352 | 135: 'VarUI1FromDate', 353 | 136: 'VarUI1FromStr', 354 | 137: 'VarUI1FromDisp', 355 | 138: 'VarUI1FromBool', 356 | 139: 'VarFormatFromTokens', 357 | 140: 'VarTokenizeFormatString', 358 | 141: 'VarAdd', 359 | 142: 'VarAnd', 360 | 143: 'VarDiv', 361 | 144: 'BSTR_UserFree64', 362 | 145: 'BSTR_UserMarshal64', 363 | 146: 'DispCallFunc', 364 | 147: 'VariantChangeTypeEx', 365 | 148: 'SafeArrayPtrOfIndex', 366 | 149: 'SysStringByteLen', 367 | 150: 'SysAllocStringByteLen', 368 | 151: 'BSTR_UserSize64', 369 | 152: 'VarEqv', 370 | 153: 'VarIdiv', 371 | 154: 'VarImp', 372 | 155: 'VarMod', 373 | 156: 'VarMul', 374 | 157: 'VarOr', 375 | 158: 'VarPow', 376 | 159: 'VarSub', 377 | 160: 'CreateTypeLib', 378 | 161: 'LoadTypeLib', 379 | 162: 'LoadRegTypeLib', 380 | 163: 'RegisterTypeLib', 381 | 164: 'QueryPathOfRegTypeLib', 382 | 165: 'LHashValOfNameSys', 383 | 166: 'LHashValOfNameSysA', 384 | 167: 'VarXor', 385 | 168: 'VarAbs', 386 | 169: 'VarFix', 387 | 170: 'OaBuildVersion', 388 | 171: 'ClearCustData', 389 | 172: 'VarInt', 390 | 173: 'VarNeg', 391 | 174: 'VarNot', 392 | 175: 'VarRound', 393 | 176: 'VarCmp', 394 | 177: 'VarDecAdd', 395 | 178: 'VarDecDiv', 396 | 179: 'VarDecMul', 397 | 180: 'CreateTypeLib2', 398 | 181: 'VarDecSub', 399 | 182: 'VarDecAbs', 400 | 183: 'LoadTypeLibEx', 401 | 184: 'SystemTimeToVariantTime', 402 | 185: 'VariantTimeToSystemTime', 403 | 186: 'UnRegisterTypeLib', 404 | 187: 'VarDecFix', 405 | 188: 'VarDecInt', 406 | 189: 'VarDecNeg', 407 | 190: 'VarDecFromUI1', 408 | 191: 'VarDecFromI2', 409 | 192: 'VarDecFromI4', 410 | 193: 'VarDecFromR4', 411 | 194: 'VarDecFromR8', 412 | 195: 'VarDecFromDate', 413 | 196: 'VarDecFromCy', 414 | 197: 'VarDecFromStr', 415 | 198: 'VarDecFromDisp', 416 | 199: 'VarDecFromBool', 417 | 200: 'GetErrorInfo', 418 | 201: 'SetErrorInfo', 419 | 202: 'CreateErrorInfo', 420 | 203: 'VarDecRound', 421 | 204: 'VarDecCmp', 422 | 205: 'VarI2FromI1', 423 | 206: 'VarI2FromUI2', 424 | 207: 'VarI2FromUI4', 425 | 208: 'VarI2FromDec', 426 | 209: 'VarI4FromI1', 427 | 210: 'VarI4FromUI2', 428 | 211: 'VarI4FromUI4', 429 | 212: 'VarI4FromDec', 430 | 213: 'VarR4FromI1', 431 | 214: 'VarR4FromUI2', 432 | 215: 'VarR4FromUI4', 433 | 216: 'VarR4FromDec', 434 | 217: 'VarR8FromI1', 435 | 218: 'VarR8FromUI2', 436 | 219: 'VarR8FromUI4', 437 | 220: 'VarR8FromDec', 438 | 221: 'VarDateFromI1', 439 | 222: 'VarDateFromUI2', 440 | 223: 'VarDateFromUI4', 441 | 224: 'VarDateFromDec', 442 | 225: 'VarCyFromI1', 443 | 226: 'VarCyFromUI2', 444 | 227: 'VarCyFromUI4', 445 | 228: 'VarCyFromDec', 446 | 229: 'VarBstrFromI1', 447 | 230: 'VarBstrFromUI2', 448 | 231: 'VarBstrFromUI4', 449 | 232: 'VarBstrFromDec', 450 | 233: 'VarBoolFromI1', 451 | 234: 'VarBoolFromUI2', 452 | 235: 'VarBoolFromUI4', 453 | 236: 'VarBoolFromDec', 454 | 237: 'VarUI1FromI1', 455 | 238: 'VarUI1FromUI2', 456 | 239: 'VarUI1FromUI4', 457 | 240: 'VarUI1FromDec', 458 | 241: 'VarDecFromI1', 459 | 242: 'VarDecFromUI2', 460 | 243: 'VarDecFromUI4', 461 | 244: 'VarI1FromUI1', 462 | 245: 'VarI1FromI2', 463 | 246: 'VarI1FromI4', 464 | 247: 'VarI1FromR4', 465 | 248: 'VarI1FromR8', 466 | 249: 'VarI1FromDate', 467 | 250: 'VarI1FromCy', 468 | 251: 'VarI1FromStr', 469 | 252: 'VarI1FromDisp', 470 | 253: 'VarI1FromBool', 471 | 254: 'VarI1FromUI2', 472 | 255: 'VarI1FromUI4', 473 | 256: 'VarI1FromDec', 474 | 257: 'VarUI2FromUI1', 475 | 258: 'VarUI2FromI2', 476 | 259: 'VarUI2FromI4', 477 | 260: 'VarUI2FromR4', 478 | 261: 'VarUI2FromR8', 479 | 262: 'VarUI2FromDate', 480 | 263: 'VarUI2FromCy', 481 | 264: 'VarUI2FromStr', 482 | 265: 'VarUI2FromDisp', 483 | 266: 'VarUI2FromBool', 484 | 267: 'VarUI2FromI1', 485 | 268: 'VarUI2FromUI4', 486 | 269: 'VarUI2FromDec', 487 | 270: 'VarUI4FromUI1', 488 | 271: 'VarUI4FromI2', 489 | 272: 'VarUI4FromI4', 490 | 273: 'VarUI4FromR4', 491 | 274: 'VarUI4FromR8', 492 | 275: 'VarUI4FromDate', 493 | 276: 'VarUI4FromCy', 494 | 277: 'VarUI4FromStr', 495 | 278: 'VarUI4FromDisp', 496 | 279: 'VarUI4FromBool', 497 | 280: 'VarUI4FromI1', 498 | 281: 'VarUI4FromUI2', 499 | 282: 'VarUI4FromDec', 500 | 283: 'BSTR_UserSize', 501 | 284: 'BSTR_UserMarshal', 502 | 285: 'BSTR_UserUnmarshal', 503 | 286: 'BSTR_UserFree', 504 | 287: 'VARIANT_UserSize', 505 | 288: 'VARIANT_UserMarshal', 506 | 289: 'VARIANT_UserUnmarshal', 507 | 290: 'VARIANT_UserFree', 508 | 291: 'LPSAFEARRAY_UserSize', 509 | 292: 'LPSAFEARRAY_UserMarshal', 510 | 293: 'LPSAFEARRAY_UserUnmarshal', 511 | 294: 'LPSAFEARRAY_UserFree', 512 | 295: 'LPSAFEARRAY_Size', 513 | 296: 'LPSAFEARRAY_Marshal', 514 | 297: 'LPSAFEARRAY_Unmarshal', 515 | 298: 'VarDecCmpR8', 516 | 299: 'VarCyAdd', 517 | 300: 'BSTR_UserUnmarshal64', 518 | 301: 'DllCanUnloadNow', 519 | 302: 'DllGetClassObject', 520 | 303: 'VarCyMul', 521 | 304: 'VarCyMulI4', 522 | 305: 'VarCySub', 523 | 306: 'VarCyAbs', 524 | 307: 'VarCyFix', 525 | 308: 'VarCyInt', 526 | 309: 'VarCyNeg', 527 | 310: 'VarCyRound', 528 | 311: 'VarCyCmp', 529 | 312: 'VarCyCmpR8', 530 | 313: 'VarBstrCat', 531 | 314: 'VarBstrCmp', 532 | 315: 'VarR8Pow', 533 | 316: 'VarR4CmpR8', 534 | 317: 'VarR8Round', 535 | 318: 'VarCat', 536 | 319: 'VarDateFromUdateEx', 537 | 320: 'DllRegisterServer', 538 | 321: 'DllUnregisterServer', 539 | 322: 'GetRecordInfoFromGuids', 540 | 323: 'GetRecordInfoFromTypeInfo', 541 | 324: 'LPSAFEARRAY_UserFree64', 542 | 325: 'SetVarConversionLocaleSetting', 543 | 326: 'GetVarConversionLocaleSetting', 544 | 327: 'SetOaNoCache', 545 | 328: 'LPSAFEARRAY_UserMarshal64', 546 | 329: 'VarCyMulI8', 547 | 330: 'VarDateFromUdate', 548 | 331: 'VarUdateFromDate', 549 | 332: 'GetAltMonthNames', 550 | 333: 'VarI8FromUI1', 551 | 334: 'VarI8FromI2', 552 | 335: 'VarI8FromR4', 553 | 336: 'VarI8FromR8', 554 | 337: 'VarI8FromCy', 555 | 338: 'VarI8FromDate', 556 | 339: 'VarI8FromStr', 557 | 340: 'VarI8FromDisp', 558 | 341: 'VarI8FromBool', 559 | 342: 'VarI8FromI1', 560 | 343: 'VarI8FromUI2', 561 | 344: 'VarI8FromUI4', 562 | 345: 'VarI8FromDec', 563 | 346: 'VarI2FromI8', 564 | 347: 'VarI2FromUI8', 565 | 348: 'VarI4FromI8', 566 | 349: 'VarI4FromUI8', 567 | 350: 'LPSAFEARRAY_UserSize64', 568 | 351: 'LPSAFEARRAY_UserUnmarshal64', 569 | 352: 'OACreateTypeLib2', 570 | 353: 'SafeArrayAddRef', 571 | 354: 'SafeArrayReleaseData', 572 | 355: 'SafeArrayReleaseDescriptor', 573 | 356: 'SysAddRefString', 574 | 357: 'SysReleaseString', 575 | 358: 'VARIANT_UserFree64', 576 | 359: 'VARIANT_UserMarshal64', 577 | 360: 'VarR4FromI8', 578 | 361: 'VarR4FromUI8', 579 | 362: 'VarR8FromI8', 580 | 363: 'VarR8FromUI8', 581 | 364: 'VarDateFromI8', 582 | 365: 'VarDateFromUI8', 583 | 366: 'VarCyFromI8', 584 | 367: 'VarCyFromUI8', 585 | 368: 'VarBstrFromI8', 586 | 369: 'VarBstrFromUI8', 587 | 370: 'VarBoolFromI8', 588 | 371: 'VarBoolFromUI8', 589 | 372: 'VarUI1FromI8', 590 | 373: 'VarUI1FromUI8', 591 | 374: 'VarDecFromI8', 592 | 375: 'VarDecFromUI8', 593 | 376: 'VarI1FromI8', 594 | 377: 'VarI1FromUI8', 595 | 378: 'VarUI2FromI8', 596 | 379: 'VarUI2FromUI8', 597 | 380: 'VARIANT_UserSize64', 598 | 381: 'VARIANT_UserUnmarshal64', 599 | 401: 'OleLoadPictureEx', 600 | 402: 'OleLoadPictureFileEx', 601 | 411: 'SafeArrayCreateVector', 602 | 412: 'SafeArrayCopyData', 603 | 413: 'VectorFromBstr', 604 | 414: 'BstrFromVector', 605 | 415: 'OleIconToCursor', 606 | 416: 'OleCreatePropertyFrameIndirect', 607 | 417: 'OleCreatePropertyFrame', 608 | 418: 'OleLoadPicture', 609 | 419: 'OleCreatePictureIndirect', 610 | 420: 'OleCreateFontIndirect', 611 | 421: 'OleTranslateColor', 612 | 422: 'OleLoadPictureFile', 613 | 423: 'OleSavePictureFile', 614 | 424: 'OleLoadPicturePath', 615 | 425: 'VarUI4FromI8', 616 | 426: 'VarUI4FromUI8', 617 | 427: 'VarI8FromUI8', 618 | 428: 'VarUI8FromI8', 619 | 429: 'VarUI8FromUI1', 620 | 430: 'VarUI8FromI2', 621 | 431: 'VarUI8FromR4', 622 | 432: 'VarUI8FromR8', 623 | 433: 'VarUI8FromCy', 624 | 434: 'VarUI8FromDate', 625 | 435: 'VarUI8FromStr', 626 | 436: 'VarUI8FromDisp', 627 | 437: 'VarUI8FromBool', 628 | 438: 'VarUI8FromI1', 629 | 439: 'VarUI8FromUI2', 630 | 440: 'VarUI8FromUI4', 631 | 441: 'VarUI8FromDec', 632 | 442: 'RegisterTypeLibForUser', 633 | 443: 'UnRegisterTypeLibForUser', 634 | 444: 'OaEnablePerUserTLibRegistration', 635 | 445: 'HWND_UserFree', 636 | 446: 'HWND_UserMarshal', 637 | 447: 'HWND_UserSize', 638 | 448: 'HWND_UserUnmarshal', 639 | 449: 'HWND_UserFree64', 640 | 450: 'HWND_UserMarshal64', 641 | 451: 'HWND_UserSize64', 642 | 452: 'HWND_UserUnmarshal64', 643 | 500: 'OACleanup', 644 | }, 645 | } 646 | 647 | 648 | def main(): 649 | sel = api.selection() 650 | if sel.is_range(): 651 | ranges = (sel, ) 652 | else: 653 | ranges = api.segments 654 | 655 | pat = re.compile(r'(imp|j)_ordinal_([A-Za-z0-9_]+).[Dd][Ll][Ll]_([0-9]+)') 656 | for r in ranges: 657 | for addr in range(r.start, r.end): 658 | label = api.get_label(addr) 659 | if label == None: 660 | continue 661 | m = pat.match(label) 662 | if not m: 663 | continue 664 | prefix = m.group(1) + '_' 665 | libname = m.group(2).lower() + '.dll' 666 | ordinal = int(m.group(3)) 667 | if libname in IMPORTS and ordinal in IMPORTS[libname]: 668 | new_label = prefix + IMPORTS[libname][ordinal] 669 | print("renaming %x %s to %s" % (addr, label, new_label)) 670 | api.set_label(addr, new_label) 671 | 672 | 673 | if __name__ == '__main__': 674 | api.run(main) 675 | 676 | -------------------------------------------------------------------------------- /GNUmakefile: -------------------------------------------------------------------------------- 1 | YARA= $(shell which yara) 2 | ifeq ($(YARA),) 3 | $(error yara not found in PATH) 4 | endif 5 | 6 | ifeq ($(shell uname),Darwin) 7 | SCRIPTS_DIR= Library/Application Support/Hopper/Scripts 8 | REINPLACE= sed -i '' 9 | endif 10 | ifeq ($(shell uname),Linux) 11 | SCRIPTS_DIR= GNUstep/Library/ApplicationSupport/Hopper/Scripts 12 | REINPLACE= sed -i'' 13 | endif 14 | ifeq ($(SCRIPTS_DIR),) 15 | $(error $(shell uname) unsupported) 16 | endif 17 | 18 | SCRIPTS= $(wildcard *.py) 19 | LIBS= $(wildcard hopper_api/*.py) 20 | SYMLINK= Scripts 21 | 22 | 23 | all: 24 | 25 | install: install-api install-scripts 26 | 27 | install-api: $(LIBS) 28 | test -e $(SYMLINK) || ln -sf "$(HOME)/$(SCRIPTS_DIR)" $(SYMLINK) 29 | mkdir -p $(SYMLINK)/hopper_api 30 | cp $^ $(SYMLINK)/hopper_api/ 31 | rm -f $(SYMLINK)/hopper_api/*.pyc 32 | 33 | install-scripts: $(SCRIPTS) 34 | test -e $(SYMLINK) || ln -sf "$(HOME)/$(SCRIPTS_DIR)" $(SYMLINK) 35 | cp $^ $(SYMLINK)/ 36 | $(REINPLACE) -e s,@@yara@@,$(YARA),g $(SYMLINK)/*Yara*.py 37 | 38 | diff: $(SCRIPTS) $(LIBS) 39 | test -e $(SYMLINK) || ln -sf "$(HOME)/$(SCRIPTS_DIR)" $(SYMLINK) 40 | @for f in $^; do \ 41 | out=`diff -u $$f $(SYMLINK)/$$f`; \ 42 | size=`echo "$$out"|wc -l`; \ 43 | echo "$$out"|{ test $$size -gt `tput lines` && less || cat; }; \ 44 | done 45 | 46 | .PHONY: all install install-api install-scripts diff 47 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # License and copyright 2 | 3 | ## Copyright 4 | 5 | Copyright (c) 2018, Daniel Roethlisberger and contributors. 6 | All rights reserved. 7 | Licensed under the 2-clause BSD license contained herein. 8 | 9 | 10 | ## Contributions 11 | 12 | By contributing to the software, the contributor releases their 13 | contribution under the license and copyright terms herein. While 14 | contributors retain copyright to their contributions, they grant the 15 | main copyright holder of the software the irrevocable, transferable 16 | right to relicense the software as a whole or in part, including their 17 | contributions, under different open source licenses than the one 18 | contained herein. 19 | 20 | 21 | ## License 22 | 23 | Redistribution and use in source and binary forms, with or without 24 | modification, are permitted provided that the following conditions 25 | are met: 26 | 27 | 1. Redistributions of source code must retain the above copyright 28 | notice, this list of conditions, and the following disclaimer. 29 | 30 | 2. Redistributions in binary form must reproduce the above copyright 31 | notice, this list of conditions and the following disclaimer in the 32 | documentation and/or other materials provided with the distribution. 33 | 34 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 35 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 36 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 37 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 38 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 39 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 40 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 41 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 42 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 43 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 44 | 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scripts for Hopper Disassembler 2 | 3 | Copyright (C) 2018-2019, 2023-2024, [Daniel Roethlisberger](//daniel.roe.ch/). 4 | 5 | ## Synopsis 6 | 7 | make install 8 | # check Scripts menu in Hopper 9 | 10 | ## Description 11 | 12 | Some of my Hopper scripts that are polished and general enough to be 13 | potentially useful to others. They used to run on Hopper 4 on macOS and 14 | Linux and are slowly being fixed for Hopper 5 and Python 3. 15 | 16 | The scripts use a nicer wrapper API around the strictly procedural Hopper 17 | python API, otherwise the scripts are self-contained. 18 | 19 | ## Scripts 20 | 21 | - **Analyze Shellcode** - detect and annotate typical shellcode patterns: 22 | known code blocks, call import by hash, and call/pop reg 23 | - **Annotate Stack Strings in Selection** - annotate plaintext and 24 | XOR-encrypted stack strings 25 | - **Annotate Yara Matches** - apply a set of yara rules to the currently 26 | loaded document and annotate a summary of matching rules as well as each 27 | string occurence for matching rules 28 | - **Copy Selection As Python** - copy bytes in current selection to the 29 | clipboard, in python syntax, with assembly code in comments 30 | - **Copy Selection As Yara** - copy bytes in current selection to the 31 | clipboard, in yara syntax, with assembly code in comments 32 | - **File Offset Here** - add a prefix comment with the file offset at the 33 | current cursor position 34 | - **Fix Imports By Ordinal** - rename labels of imported functions by ordinal 35 | to their actual names 36 | - **Save Bytes From Here** - carve and save an arbitrarily-sized blob of 37 | optionally XOR-decrypted bytes from the current cursor position to a file 38 | - **Save Selection As Bytes** - carve and save an arbitrarily-sized blob of 39 | optionally XOR-decrypted bytes based on the current selection to a file 40 | 41 | ## Support 42 | 43 | There is no support whatsoever. No communication except in the form of pull 44 | requests fixing bugs or adding features. You are on your own. 45 | 46 | ## License 47 | 48 | Source code provided under a 2-clause BSD license. 49 | -------------------------------------------------------------------------------- /Save_Bytes_From_Here.py: -------------------------------------------------------------------------------- 1 | # vim: set fileencoding=utf-8 : 2 | 3 | # «Save Bytes From Here» for Hopper 5 4 | # Copyright (c) 2018, 2023, Daniel Roethlisberger 5 | # https://github.com/droe/hopper-scripts 6 | # 7 | # Save n bytes from current position to a file, optionally XOR decoded. 8 | 9 | 10 | import hopper_api as api 11 | import binascii 12 | from itertools import cycle 13 | 14 | 15 | def unhexlify(s): 16 | if len(s) % 2 != 0: 17 | s = '0' + s 18 | return binascii.unhexlify(s) 19 | 20 | 21 | def xorcrypt(buf, key): 22 | if len(key) == 0: 23 | return buf 24 | k = cycle(key) 25 | return bytes(b ^ next(k) for b in buf) 26 | 27 | 28 | def main(): 29 | addr = api.selection().start 30 | size = api.ask_int("Number of bytes") 31 | if size == None: 32 | return 33 | blob = api.document.read(addr, size) 34 | 35 | ans = api.ask_hex("XOR key in hex (optional)") 36 | if ans == None: 37 | return 38 | if len(ans) == 0: 39 | key = b'\x00' 40 | else: 41 | key = unhexlify(ans) 42 | blob = xorcrypt(blob, key) 43 | 44 | filename = api.ask_file("Save bytes to", None, True) 45 | if filename == None: 46 | return 47 | 48 | with open(filename, 'wb') as f: 49 | f.write(blob) 50 | 51 | 52 | if __name__ == '__main__': 53 | api.run(main) 54 | -------------------------------------------------------------------------------- /Save_Selection_As_Bytes.py: -------------------------------------------------------------------------------- 1 | # vim: set fileencoding=utf-8 : 2 | 3 | # «Save Selection As Bytes» for Hopper 5 4 | # Copyright (c) 2018, 2023-2024, Daniel Roethlisberger 5 | # https://github.com/droe/hopper-scripts 6 | # 7 | # Save selection to a file, optionally XOR decoded. 8 | 9 | 10 | import hopper_api as api 11 | import binascii 12 | from itertools import cycle 13 | 14 | 15 | def unhexlify(s): 16 | if len(s) % 2 != 0: 17 | s = '0' + s 18 | return binascii.unhexlify(s) 19 | 20 | 21 | def xorcrypt(buf, key): 22 | if len(key) == 0: 23 | return buf 24 | k = cycle(key) 25 | return bytes(b ^ next(k) for b in buf) 26 | 27 | 28 | def main(): 29 | addr = api.selection().start 30 | size = api.selection().end - addr 31 | if size == None: 32 | return 33 | blob = api.document.read(addr, size) 34 | 35 | ans = api.ask_hex("XOR key in hex (optional)") 36 | if ans == None: 37 | return 38 | if len(ans) == 0: 39 | key = b'\x00' 40 | else: 41 | key = unhexlify(ans) 42 | blob = xorcrypt(blob, key) 43 | 44 | filename = api.ask_file("Save bytes to", None, True) 45 | if filename == None: 46 | return 47 | 48 | with open(filename, 'wb') as f: 49 | f.write(blob) 50 | 51 | 52 | if __name__ == '__main__': 53 | api.run(main) 54 | -------------------------------------------------------------------------------- /hopper_api/__init__.py: -------------------------------------------------------------------------------- 1 | # vim: set fileencoding=utf-8 : 2 | 3 | # Extended API for Hopper 4 4 | # Copyright (c) 2018, Daniel Roethlisberger 5 | # https://github.com/droe/hopper-scripts 6 | # 7 | # Wraps the strictly procedural script API provided by Hopper 4 into an easier, 8 | # more convenient, pythonic, expressive and object-oriented script API, 9 | # resulting in less verbose and more readable scripts. 10 | 11 | 12 | import os 13 | import subprocess 14 | import sys 15 | import traceback 16 | import __main__ as main 17 | 18 | 19 | class APIClipboard: 20 | CMDMAP = { 21 | 'Darwin': ('pbcopy', 'pbpaste'), 22 | 'Linux': ('xsel -b -i', 'xsel -b -o'), 23 | } 24 | def __init__(self): 25 | uname = os.uname()[0] 26 | if uname in self.CMDMAP: 27 | self._cmd_copy, self._cmd_paste = self.CMDMAP[uname] 28 | else: 29 | raise NotImplementedError("%s not supported" % uname) 30 | 31 | def copy(self, s): 32 | proc = subprocess.Popen(self._cmd_copy, 33 | env={'LANG': 'en_US.UTF-8'}, 34 | stdin=subprocess.PIPE, 35 | shell=True) 36 | proc.communicate(s.encode('utf-8')) 37 | if proc.returncode != 0: 38 | msg = "%s failed with exit status %i" % (self._cmd_copy, 39 | proc.returncode) 40 | raise RuntimeError(msg) 41 | 42 | def paste(self): 43 | proc = subprocess.Popen(self._cmd_paste, 44 | env={'LANG': 'en_US.UTF-8'}, 45 | stdout=subprocess.PIPE, 46 | shell=True) 47 | out, err = proc.communicate() 48 | if proc.returncode != 0: 49 | msg = "%s failed with exit status %i" % (self._cmd_paste, 50 | proc.returncode) 51 | raise RuntimeError(msg) 52 | return out.decode('utf-8', errors='ignore') 53 | 54 | 55 | class APIInstruction: 56 | def __init__(self, hseg, addr): 57 | self._hseg = hseg 58 | self.addr = addr 59 | if hseg.getTypeAtAddress(addr) in (main.Segment.TYPE_CODE, 60 | main.Segment.TYPE_PROCEDURE): 61 | self._hins = hseg.getInstructionAtAddress(addr) 62 | self._bytes = hseg.readBytes(addr, 63 | self._hins.getInstructionLength()) 64 | else: 65 | self._hins = None 66 | self._bytes = hseg.readBytes(addr, 1) 67 | self._op = 'db' 68 | self._args = "0x%s" % hex(self) 69 | 70 | @property 71 | def raw(self): 72 | return self._hins 73 | 74 | def __len__(self): 75 | return len(self._bytes) 76 | 77 | def __hex__(self): 78 | return ''.join(x.encode('hex') for x in self._bytes) 79 | 80 | def __str__(self): 81 | return "%-8s %s" % (self.op, self.args) 82 | 83 | @property 84 | def op(self): 85 | if self._hins != None: 86 | return self._hins.getInstructionString() 87 | else: 88 | return self._op 89 | 90 | @property 91 | def args(self): 92 | if self._hins != None: 93 | insargs = [] 94 | for i in range(self._hins.getArgumentCount()): 95 | insargs.append(self._hins.getRawArgument(i)) 96 | return ', '.join(insargs) 97 | else: 98 | return self._args 99 | 100 | def arg(self, i): 101 | if self._hins != None: 102 | if i < 0 or i >= self._hins.getArgumentCount(): 103 | raise IndexError("arg index out of range") 104 | return self._hins.getRawArgument(i) 105 | else: 106 | return None 107 | 108 | 109 | class APIDocument: 110 | def __init__(self, hdoc): 111 | self._hdoc = hdoc 112 | 113 | @property 114 | def raw(self): 115 | return self._hdoc 116 | 117 | def read(self, addr, size): 118 | return self._hdoc.readBytes(addr, size) 119 | 120 | 121 | class APISelection: 122 | def __init__(self, hdoc): 123 | self._hsel = hdoc.getSelectionAddressRange() 124 | self._raw_lines = hdoc.getRawSelectedLines() 125 | self._segments = segments.in_range(self.start, self.end) 126 | 127 | @property 128 | def start(self): 129 | return self._hsel[0] 130 | 131 | @property 132 | def end(self): 133 | return self._hsel[1] 134 | 135 | def __len__(self): 136 | return int(self.end - self.start) 137 | 138 | def is_range(self): 139 | # Note: Raw lines contains the whole line if there was no selection, 140 | # so we cannot differentiate a one-line selection from no selection. 141 | return len(self._raw_lines) > 1 142 | 143 | def instructions(self, start=None, end=None): 144 | if start == None: 145 | start = self.start 146 | if end == None: 147 | end = self.end 148 | for seg in self._segments: 149 | seg_start = max(seg.start, start) 150 | seg_end = min(seg.end, end) 151 | for ins in seg.instructions(seg_start, seg_end): 152 | yield ins 153 | 154 | 155 | class APIExecutable: 156 | def __init__(self, hdoc): 157 | self._hdoc = hdoc 158 | 159 | def bytes(self): 160 | blob = self._hdoc.produceNewExecutable() 161 | if blob != None: 162 | return blob 163 | # fall back to direct file access 164 | with open(self.path, 'rb') as f: 165 | return f.read() 166 | 167 | @property 168 | def path(self): 169 | return self._hdoc.getExecutableFilePath() 170 | 171 | @property 172 | def arch_bits(self): 173 | # FIXME Hopper API limitation; no access to actual bits 174 | if self._hdoc.is64Bits(): 175 | return 64 176 | else: 177 | return 32 178 | 179 | @property 180 | def arch(self): 181 | # FIXME Hopper API limitation; no access to actual arch 182 | if self._hdoc.is64Bits(): 183 | return 'x64' 184 | else: 185 | return 'x86' 186 | 187 | 188 | class APISegment: 189 | def __init__(self, hseg): 190 | self._hseg = hseg 191 | 192 | @property 193 | def raw(self): 194 | return self._hseg 195 | 196 | @property 197 | def start(self): 198 | return self._hseg.getStartingAddress() 199 | 200 | @property 201 | def end(self): 202 | return self._hseg.getStartingAddress() + self._hseg.getLength() 203 | 204 | def __len__(self): 205 | length = self._hseg.getLength() 206 | if length > sys.maxint: 207 | raise ValueError("segment length > sys.maxint") 208 | return int(length) 209 | 210 | def __contains__(self, x): 211 | if isinstance(x, (int, long)): 212 | return x >= self.start and x < self.end 213 | raise NotImplementedError("__contains__ not implemented for: %r" % x) 214 | 215 | def bytes(self): 216 | return self._hseg.readBytes(self.start, len(self)) 217 | 218 | def mark_as_undefined(self): 219 | self._hseg.markRangeAsUndefined(self.start, len(self)) 220 | 221 | def disassemble(self): 222 | self._hseg.disassembleWholeSegment() 223 | 224 | def instructions(self, start=None, end=None): 225 | if start == None: 226 | start = self.start 227 | if end == None: 228 | end = self.end 229 | size = end - start 230 | buf = self._hseg.readBytes(start, size) 231 | pos = start 232 | while pos < end: 233 | ins = APIInstruction(self._hseg, pos) 234 | yield ins 235 | pos += len(ins) 236 | 237 | 238 | class APISegments: 239 | def __init__(self, hdoc): 240 | self._hdoc = hdoc 241 | 242 | def __iter__(self): 243 | segs = [] 244 | for i in range(self._hdoc.getSegmentCount()): 245 | segs.append(APISegment(self._hdoc.getSegment(i))) 246 | return iter(segs) 247 | 248 | def current(self): 249 | return self.by_addr(self._hdoc.getCurrentSegment().getStartingAddress()) 250 | 251 | def by_addr(self, addr): 252 | for seg in self: 253 | if not addr in seg: 254 | continue 255 | return seg 256 | raise ValueError("Address %x not in any segment" % addr) 257 | 258 | def in_range(self, start, end): 259 | segs = [] 260 | for seg in self: 261 | if start > seg.end or end < seg.start: 262 | continue 263 | segs.append(seg) 264 | return segs 265 | 266 | 267 | def message(*args, **kwargs): 268 | return document.raw.message(*args, **kwargs) 269 | 270 | 271 | def ask(*args, **kwargs): 272 | return document.raw.ask(*args, **kwargs) 273 | 274 | def ask_int(*args, **kwargs): 275 | ans = ask(*args, **kwargs) 276 | if ans == None: 277 | return None 278 | ans = ans.strip() 279 | is_hex = False 280 | if ans.startswith('0x'): 281 | ans = ans[2:] 282 | is_hex = True 283 | if ans.endswith('h'): 284 | ans = ans[:-1] 285 | is_hex = True 286 | if is_hex: 287 | ans = int(ans, 16) 288 | else: 289 | ans = int(ans) 290 | return ans 291 | 292 | def ask_hex(*args, **kwargs): 293 | ans = ask(*args, **kwargs) 294 | if ans == None: 295 | return None 296 | ans = ans.strip() 297 | if ans.startswith('0x'): 298 | ans = ans[2:] 299 | if ans.endswith('h'): 300 | ans = ans[:-1] 301 | return ans 302 | 303 | def ask_file(*args, **kwargs): 304 | return document.raw.askFile(*args, **kwargs) 305 | 306 | 307 | def ask_directory(*args, **kwargs): 308 | return document.raw.askDirectory(*args, **kwargs) 309 | 310 | 311 | def otoa(offset): 312 | return document.raw.getAddressFromFileOffset(offset) 313 | 314 | 315 | def atoo(addr): 316 | return document.raw.getFileOffsetFromAddress(addr) 317 | 318 | 319 | def get_comment(addr): 320 | return segments.by_addr(addr).raw.getCommentAtAddress(addr) 321 | 322 | 323 | def set_comment(addr, comment): 324 | return segments.by_addr(addr).raw.setCommentAtAddress(addr, comment) 325 | 326 | 327 | def add_comment(addr, comment): 328 | have = get_comment(addr) 329 | if have != None and have != '': 330 | comment = "%s\n%s" % (have, comment) 331 | return set_comment(addr, comment) 332 | 333 | 334 | def get_icomment(addr): 335 | return segments.by_addr(addr).raw.getInlineCommentAtAddress(addr) 336 | 337 | 338 | def set_icomment(addr, comment): 339 | return segments.by_addr(addr).raw.setInlineCommentAtAddress(addr, comment) 340 | 341 | 342 | def add_icomment(addr, comment): 343 | have = get_icomment(addr) 344 | if have != None and have != '': 345 | comment = "%s; %s" % (have, comment) 346 | return set_icomment(addr, comment) 347 | 348 | 349 | def add_reference(addr, to_addr): 350 | segments.by_addr(addr).raw.addReference(addr, to_addr) 351 | 352 | 353 | def get_label(addr): 354 | return document.raw.getNameAtAddress(addr) 355 | 356 | 357 | def set_label(addr, label): 358 | return document.raw.setNameAtAddress(addr, label) 359 | 360 | 361 | def mark_as_procedure(addr): 362 | return segments.by_addr(addr).raw.markAsProcedure(addr) 363 | 364 | 365 | def selection(): 366 | return APISelection(document.raw) 367 | 368 | 369 | def run(script_main): 370 | script_name = os.path.basename(main.__file__) 371 | 372 | # assumption: current document does not change during script runtime 373 | hdoc = main.Document.getCurrentDocument() 374 | global document 375 | document = APIDocument(hdoc) 376 | global executable 377 | executable = APIExecutable(hdoc) 378 | global segments 379 | segments = APISegments(hdoc) 380 | global clipboard 381 | clipboard = APIClipboard() 382 | 383 | try: 384 | print("Executing %s" % script_name) 385 | script_main() 386 | print("Completed %s" % script_name) 387 | except Exception as e: 388 | hdoc.message(str(e), ['Ok']) 389 | traceback.print_exc() 390 | 391 | --------------------------------------------------------------------------------