├── .gitignore ├── README.rst ├── disp.py ├── runpe.py └── trace.py /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | !.gitignore 3 | *.DS* 4 | *.sh 5 | *.pyc 6 | *.exe 7 | *.sys 8 | *.h 9 | *.c 10 | *.s 11 | *.S 12 | *.asm 13 | 14 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | x86-64-pe-emu 2 | ============= 3 | 4 | .. image:: http://i.imgur.com/IRFe6Zd.png 5 | 6 | This is a simple x86-64 emulator for AMD64 PE files (Windows binaries). This was 7 | originally designed to run device drivers for analysis, but it will be extended to 8 | be much more. 9 | 10 | Notes 11 | ----- 12 | 13 | 1. This was mainly done for analysis of Windows kernel device drivers (packed ones mostly.) 14 | 2. This is very experiemental, and it relies heavily on the underlying libraries. 15 | 3. Some stuff are ultimately broken. 16 | 4. It can run under any system that unicorn/capstone engines support (Linux, Windows, Mac OS, etc.) 17 | 18 | Requirements 19 | ------------ 20 | 21 | - Unicorn engine 22 | - Capstone engine 23 | - pefile 24 | - numpy 25 | - Python 2.7 26 | 27 | This looks silly, why? 28 | ---------------------- 29 | 30 | Personal reasons, fun experience, etc. This can greatly aid somehow in reverse 31 | engineering tasks. 32 | 33 | This will definitely be extended to be much more, but for now, it's just a silly 34 | PE runner, it "fake-resolves" imports as dummy functions. 35 | As noted before, this was originally written for device driver analysis, so not 36 | much stuff is done, it's kept minimal (for now). 37 | 38 | Disclaimer 39 | ---------- 40 | 41 | Don't look at this yet, this is pretty much in an alpha stage, and will most 42 | likely take time to improve. 43 | 44 | -------------------------------------------------------------------------------- /disp.py: -------------------------------------------------------------------------------- 1 | X86_EFLAGS_CF = 1 << 0 2 | X86_EFLAGS_FIXED = 1 << 1 3 | X86_EFLAGS_PF = 1 << 2 4 | X86_EFLAGS_AF = 1 << 4 5 | X86_EFLAGS_ZF = 1 << 6 6 | X86_EFLAGS_SF = 1 << 7 7 | X86_EFLAGS_TF = 1 << 8 8 | X86_EFLAGS_IF = 1 << 9 9 | X86_EFLAGS_DF = 1 << 10 10 | X86_EFLAGS_OF = 1 << 11 11 | X86_EFLAGS_IOPL = 1 << 12 12 | X86_EFLAGS_IOPL_MASK = 3 << 12 13 | X86_EFLAGS_NT = 1 << 14 14 | X86_EFLAGS_RF = 1 << 16 15 | X86_EFLAGS_VM = 1 << 17 16 | X86_EFLAGS_AC = 1 << 18 17 | X86_EFLAGS_VIF = 1 << 19 18 | X86_EFLAGS_VIP = 1 << 20 19 | X86_EFLAGS_ID = 1 << 21 20 | 21 | eflag_list = [ 22 | ( "CF", X86_EFLAGS_CF ), 23 | ( "PF", X86_EFLAGS_PF ), 24 | ( "AF", X86_EFLAGS_AF ), 25 | ( "ZF", X86_EFLAGS_ZF ), 26 | ( "SF", X86_EFLAGS_SF ), 27 | ( "IF", X86_EFLAGS_IF ), 28 | ( "TF", X86_EFLAGS_TF ), 29 | ( "OF", X86_EFLAGS_OF ), 30 | ( "NT", X86_EFLAGS_NT ), 31 | ( "RF", X86_EFLAGS_RF ), 32 | ( "VM", X86_EFLAGS_VM ), 33 | ( "AC", X86_EFLAGS_AC ), 34 | ( "VIF", X86_EFLAGS_VIF ), 35 | ( "ID", X86_EFLAGS_ID ) 36 | ] 37 | 38 | def efl_iopl(efl): 39 | return (efl >> 12) & 3 40 | 41 | def dump_eflags(efl, iopl=True): 42 | e = "" 43 | if iopl: 44 | "iopl({:x}) ".format(efl_iopl(efl)) 45 | for flag in eflag_list: 46 | if efl & flag[1]: 47 | e += flag[0] + " " 48 | return e 49 | 50 | def dump_deflags(efl, prev_efl): 51 | eop = "" 52 | dif = efl ^ prev_efl 53 | if dif == 0: 54 | return eop 55 | 56 | for flag in eflag_list: 57 | if dif & flag[1]: 58 | if efl & flag[1]: 59 | eop += flag[0] + "=1 " 60 | else: 61 | eop += flag[0] + "=0 " 62 | return eop 63 | 64 | def read_str(uc, addr): 65 | tmp = "" 66 | r = addr 67 | while True: 68 | try: 69 | v = uc.mem_read(r, 1) 70 | except: 71 | break 72 | if v == '\0' or v < 0 or v > 128: 73 | break 74 | tmp += v 75 | r += 1 76 | return tmp 77 | 78 | def read_bytes(uc, addr, length): 79 | b = bytes() 80 | r = addr 81 | i = 0 82 | while i < length: 83 | try: 84 | v = uc.mem_read(r, 1) 85 | except: 86 | break 87 | 88 | b += v 89 | i += 1 90 | r += 1 91 | return b 92 | 93 | def disp_hex_ascii_line(buf, length, off): 94 | r = "{:05d} ".format(off) 95 | for i in range(length): 96 | r += "{:02X} ".format(buf[i]) 97 | if i == 7: 98 | r += ' ' 99 | if length < 8: 100 | r += ' ' 101 | 102 | if length < 16: 103 | gap = 16 - length 104 | for i in range(gap): 105 | r += " " 106 | r += " " 107 | 108 | for i in range(length): 109 | if ord(chr(buf[i])) < 128: 110 | r += chr(buf[i]) 111 | else: 112 | r += "." 113 | r += "\n" 114 | return r 115 | 116 | def disp_bytes(uc, addr, length): 117 | buf = read_bytes(uc, addr, length) 118 | if len(buf) == 0: 119 | return "" 120 | 121 | if len(buf) <= 16: 122 | return disp_hex_ascii_line(buf, len(buf), 0) 123 | 124 | r = "" 125 | off = 0 126 | boff = 0 127 | rem = len(buf) 128 | while True: 129 | line_len = 16 % rem 130 | r += disp_hex_ascii_line(buf[boff:boff+line_len], line_len, off) 131 | 132 | rem -= line_len 133 | boff += line_len 134 | off += 16 135 | if rem <= 16: 136 | r += disp_hex_ascii_line(buf[boff:boff+line_len], rem, off) 137 | break 138 | return r 139 | -------------------------------------------------------------------------------- /runpe.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import print_function 4 | from unicorn import * 5 | from unicorn.x86_const import * 6 | from capstone import * 7 | from capstone.x86 import * 8 | from trace import * 9 | from disp import * 10 | 11 | import struct 12 | import pefile 13 | import os 14 | import sys 15 | import ctypes 16 | import string 17 | import curses 18 | import argparse 19 | 20 | PAGE_SHIFT = 12 21 | PAGE_SIZE = 1 << PAGE_SHIFT 22 | PAGE_MASK = ~(PAGE_SIZE - 1) 23 | 24 | # All of the addresses below must be page aligned. 25 | IMAGE_BASE = 0xFFFFF88007bdb000 26 | image_size = 0 27 | 28 | # For IAT. 29 | MIN_FUNC_ADDR = 0xFFFFF40010111000 30 | MAX_FUNC_PAGES = 20 31 | MAX_FUNC_ADDR = MIN_FUNC_ADDR + MAX_FUNC_PAGES * PAGE_SIZE 32 | # nop 33 | # xorl %eax, %eax 34 | # ret 35 | EMPTY_FUNC = '\x90\x31\xc0\xc3' 36 | func_dict = dict() 37 | 38 | DRIVER_BASE = 0xFFFFFA8000001000 39 | DRIVER_SIZE = 0x1000 40 | 41 | REGISTRY_BASE = 0xFFFFFA8000002000 42 | REGISTRY_SIZE = 0x1000 43 | 44 | STACK_BASE = 0xFFFFF8600000000 45 | STACK_SIZE = 0x6000 46 | STACK_REDZONE = 0x1000 47 | 48 | GDT_BASE = 0xFFFFF880009FA000 49 | GDT_SIZE = 0x10000 50 | GDT_CS_IDX = 2 51 | GDT_TR_IDX = 8 52 | GDT_FS_IDX = 10 53 | GDT_GS_IDX = 5 54 | GDT_TR_BASE = 0xfffff0009f3ec000 55 | GDT_TR_LIMIT = 0x67 56 | GDT_FS_BASE = 0xfffffffffffb0000 57 | GDT_FS_LIMIT = 0x7c00 58 | GDT_GS_BASE = GDT_FS_BASE + GDT_FS_LIMIT + PAGE_SIZE 59 | GDT_GS_LIMIT = 0xFFFFFFFF 60 | 61 | cs = Cs(CS_ARCH_X86, CS_MODE_64) 62 | cs.detail = True 63 | trace = Trace() 64 | singlestep = True 65 | breakpoints = [] 66 | regs = [ 67 | ("rax", UC_X86_REG_RAX), 68 | ("rcx", UC_X86_REG_RCX), 69 | ("rdx", UC_X86_REG_RDX), 70 | ("rbx", UC_X86_REG_RBX), 71 | ("rsi", UC_X86_REG_RSI), 72 | ("rdi", UC_X86_REG_RDI), 73 | ("rsp", UC_X86_REG_RSP), 74 | ("rbp", UC_X86_REG_RBP), 75 | ("r8", UC_X86_REG_R8), 76 | ("r9", UC_X86_REG_R9), 77 | ("r10", UC_X86_REG_R10), 78 | ("r11", UC_X86_REG_R11), 79 | ("r12", UC_X86_REG_R12), 80 | ("r13", UC_X86_REG_R13), 81 | ("r14", UC_X86_REG_R14), 82 | ("r15", UC_X86_REG_R15), 83 | ("rip", UC_X86_REG_RIP) 84 | ] 85 | 86 | ACCESS_NONE = 0 87 | ACCESS_READ = 1 88 | ACCESS_WRITE = 2 89 | ACCESS_EXEC = 4 90 | 91 | # Curses 92 | stdscr = None 93 | ins_win = None 94 | reg_win = None 95 | stk_win = None 96 | inf_win = None 97 | 98 | class Breakpoint: 99 | def __init__(self, addr, size, access): 100 | self.addr = addr 101 | self.size = size 102 | self.access = access 103 | 104 | def uc_access_to_bits(access): 105 | if access == UC_MEM_READ: 106 | return ACCESS_READ 107 | elif access == UC_MEM_WRITE: 108 | return ACCESS_WRITE 109 | elif access == UC_MEM_FETCH: 110 | return ACCESS_EXEC 111 | return ACCESS_NONE 112 | 113 | def find_bp_addr(addr): 114 | for bp in breakpoints: 115 | if addr >= bp.addr and addr < bp.addr + bp.size: 116 | return bp 117 | return None 118 | 119 | def align(a, size): 120 | return a & ~(size - 1) 121 | 122 | def in_range(addr, start, size): 123 | return addr >= start and addr < start + size 124 | 125 | def is_stack_addr(addr): 126 | return in_range(addr, STACK_BASE, STACK_SIZE) 127 | 128 | def is_redzone_addr(addr): 129 | return in_range(addr, STACK_BASE + STACK_SIZE - STACK_REDZONE, 130 | STACK_REDZONE) 131 | 132 | def is_faked_func(addr): 133 | return in_range(addr, MIN_FUNC_ADDR, MAX_FUNC_ADDR) 134 | 135 | def probe_access(uc, addr, ac): 136 | try: 137 | c = uc.mem_read(addr, 1) 138 | if ac & ACCESS_WRITE: 139 | uc.mem_write(addr, c) 140 | except: 141 | return False 142 | 143 | def resolve_sym(addr): 144 | if in_range(addr, IMAGE_BASE, image_size): 145 | return "img+{:016X}".format(addr - IMAGE_BASE) 146 | if in_range(addr, DRIVER_BASE, DRIVER_SIZE): 147 | return "dri+{:X}".format(addr - DRIVER_BASE) 148 | if in_range(addr, REGISTRY_BASE, REGISTRY_SIZE): 149 | return "reg+{:X}".format(addr - REGISTRY_BASE) 150 | if is_redzone_addr(addr): 151 | return "red+{:X}".format(addr - STACK_BASE - (STACK_SIZE - 152 | STACK_REDZONE)) 153 | if is_stack_addr(addr): 154 | return "stk+{:X}".format(addr - STACK_BASE) 155 | if in_range(addr, GDT_FS_BASE, GDT_FS_LIMIT): 156 | return "fs:{:X}".format(addr - GDT_FS_BASE) 157 | if in_range(addr, GDT_GS_BASE, GDT_GS_LIMIT): 158 | return "gs:{:X}".format(addr - GDT_GS_BASE) 159 | if is_faked_func(addr): 160 | fstart = align(addr, PAGE_SIZE) 161 | if fstart in func_dict: 162 | return "{:s}+{:016X}".format(func_dict[fstart], addr - fstart) 163 | return "unmapped-imp:{:016X}+{:X}".format(addr, addr - fstart) 164 | return "unk:{:X}".format(addr) 165 | 166 | def dump_stack(uc, orig, frame, start, count): 167 | for i in range(count): 168 | cur = start + i * 8 169 | val = struct.unpack("> 16) & 0xFF) # base mid 210 | if limit > 0xFFFFF: # need granuality? 211 | limit >>= 12 212 | shi |= 1 << 23 213 | shi |= seg_type << 8 # segment type 214 | shi |= 1 << 12 # system 215 | shi |= (dpl & 3) << 13 # DPL 216 | shi |= 1 << 15 # Present 217 | shi |= ((limit >> 16) & 0xF) << 16 # limit 218 | shi |= 1 << 20 # AVL 219 | shi |= 1 << 21 # Long mode 220 | shi |= ((base >> 24) & 0xFF) << 24 # base 221 | return slo | shi << 32 222 | 223 | def build_gdt_seg(base, limit, dpl, seg_type): 224 | return struct.pack("> 32) 225 | 226 | def switch_to_ss(e): 227 | global singlestep 228 | if e: 229 | singlestep = True 230 | inf_win.addstr("Singlestep mode active\n") 231 | 232 | def hook_mem_unmapped(uc, access, addr, size, value, user_data): 233 | switch_to_ss(True) 234 | inf_win.addstr("{:s}: memory unmapped (r/w) at {:s} size = {:d} value = {:X}\n".format(resolve_sym(uc.reg_read(UC_X86_REG_RIP)), resolve_sym(addr), size, value)) 235 | inf_win.refresh() 236 | return True 237 | 238 | def hook_instr_unmapped(uc, access, addr, size, value, user_data): 239 | inf_win.addstr("{:s}: memory unmapped (exec) {:X}\n".format(resolve_sym(uc.reg_read(UC_X86_REG_RIP)), addr)) 240 | if is_faked_func(addr): 241 | inf_win.addstr("mapping fake function " + func_dict[align(addr, PAGE_SIZE)] + "\n") 242 | inf_win.refresh() 243 | uc.mem_map(align(addr, PAGE_SIZE), PAGE_SIZE) 244 | uc.mem_write(addr, EMPTY_FUNC) 245 | switch_to_ss(True) 246 | return True 247 | inf_win.refresh() 248 | return False 249 | 250 | def hook_mem_access(uc, access, addr, size, value, user_data): 251 | bpa = find_bp_addr(addr) 252 | if bpa is not None and (bpa.access & uc_access_to_bits(access)) != 0: 253 | switch_to_ss(True) 254 | inf_win.addstr("Breakpoint hit at {:s} val = {:X} access {:X}\n".format(resolve_sym(addr), value, access)) 255 | inf_win.refresh() 256 | return True 257 | 258 | def hook_instr(uc, address, size, user_data): 259 | global singlestep 260 | try: 261 | is_break = False 262 | rip = uc.reg_read(UC_X86_REG_RIP) 263 | mem = uc.mem_read(address, size) 264 | for insn in cs.disasm(mem, size): 265 | ins_win.addstr("{:s}: {:5s}\t{:s}\n".format(resolve_sym(rip), 266 | insn.mnemonic, insn.op_str), curses.color_pair(1)) 267 | trace.push_insn(uc, rip, insn) 268 | if insn.opcode[0] == 0xcc: 269 | is_break = True 270 | switch_to_ss(is_break) 271 | if is_break: 272 | inf_win.addstr("Breakpoint hit\n", curses.color_pair(3)) 273 | elif singlestep and inf_win.getch() == ord('g'): 274 | singlestep = False 275 | inf_win.addstr("Continue.\n", curses.color_pair(3)) 276 | ins_win.refresh() 277 | dump_context(uc) 278 | except KeyboardInterrupt: 279 | uc.emu_stop() 280 | 281 | def runpe(filename, run_length): 282 | uc = Uc(UC_ARCH_X86, UC_MODE_64) 283 | 284 | # rcx = driver object 285 | uc.mem_map(DRIVER_BASE, DRIVER_SIZE) 286 | uc.reg_write(UC_X86_REG_RCX, DRIVER_BASE) 287 | 288 | # rdx = registry path 289 | uc.mem_map(REGISTRY_BASE, REGISTRY_SIZE) 290 | uc.reg_write(UC_X86_REG_RDX, REGISTRY_BASE) 291 | 292 | # allocate the stack 293 | uc.mem_map(STACK_BASE, STACK_SIZE) 294 | uc.reg_write(UC_X86_REG_RSP, STACK_BASE + STACK_SIZE - STACK_REDZONE) 295 | 296 | # Map GDT 297 | gdt = [None] * (GDT_SIZE / 16) 298 | gdt[GDT_CS_IDX] = build_gdt_seg(0, 0xfffff, 0, 11) 299 | gdt[GDT_FS_IDX] = build_gdt_seg(GDT_FS_BASE, GDT_FS_LIMIT, 0, 3) 300 | gdt[GDT_GS_IDX] = build_gdt_seg(GDT_GS_BASE, GDT_GS_LIMIT, 0, 3) 301 | gdt[GDT_TR_IDX] = build_gdt_seg(GDT_TR_BASE, GDT_TR_LIMIT, 0, 3) 302 | # Set GDTR 303 | gdtr = (0, GDT_BASE, GDT_SIZE, 0) 304 | uc.reg_write(UC_X86_REG_GDTR, gdtr) 305 | uc.mem_map(GDT_BASE, GDT_SIZE) 306 | uc.mem_write(GDT_BASE, bytes(gdt)) 307 | 308 | # Map TR 309 | uc.mem_map(GDT_TR_BASE, PAGE_SIZE) 310 | 311 | # Set segments 312 | uc.reg_write(UC_X86_REG_TR, (GDT_TR_IDX << 3, GDT_TR_BASE, GDT_TR_LIMIT, 0x8b)) 313 | uc.reg_write(UC_X86_REG_FS, GDT_FS_IDX << 3) 314 | uc.reg_write(UC_X86_REG_GS, GDT_GS_IDX << 3) 315 | uc.reg_write(UC_X86_REG_CS, GDT_CS_IDX << 3) 316 | 317 | # Set EFL and TPR 318 | uc.reg_write(UC_X86_REG_EFLAGS, X86_EFLAGS_FIXED | X86_EFLAGS_ID | X86_EFLAGS_IF) 319 | uc.reg_write(UC_X86_REG_CR8, 0) 320 | 321 | img = pefile.PE(filename) 322 | nt_hdr = img.NT_HEADERS 323 | opt = nt_hdr.OPTIONAL_HEADER 324 | size = opt.SizeOfImage 325 | base = opt.ImageBase 326 | ep = opt.AddressOfEntryPoint 327 | 328 | global image_size 329 | image_size = size 330 | 331 | inf_win.addstr("Information\n", curses.color_pair(4)) 332 | inf_win.addstr("Image base = {:X} size = {:X}, ep = {:X} hdr_size {:X}\n".format(base, 333 | size, ep, opt.SizeOfHeaders)) 334 | tmp = img.get_memory_mapped_image(ImageBase=IMAGE_BASE) 335 | data = bytearray(tmp) 336 | inf_win.addstr("Image rebased at {:X} size {:X}\n".format(IMAGE_BASE, len(tmp))) 337 | 338 | # Resolve IAT... 339 | mod_index = 0 340 | for module in img.DIRECTORY_ENTRY_IMPORT: 341 | iat_offset = 0 342 | for sym in module.imports: 343 | name = "" 344 | if sym.import_by_ordinal: 345 | if sym.name is not None: 346 | name = "Ord: {:s}+{:s}+{:d}".format(module.dll.decode("utf-8"), 347 | sym.name.decode("utf-8"), 348 | sym.ordinal) 349 | else: 350 | name = "Ord: {:s}+{:d}".format(module.dll.decode("utf-8"), 351 | sym.ordinal) 352 | else: 353 | # Hint 354 | name = "{:s}+{:s}".format(module.dll.decode("utf-8"), 355 | sym.name.decode("utf-8")) 356 | 357 | offset = 0 358 | if module.struct.FirstThunk != 0: 359 | offset = module.struct.FirstThunk + iat_offset 360 | else: 361 | offset = sym.struct_table.AddressOfData 362 | 363 | fun_addr = MIN_FUNC_ADDR + ((iat_offset >> 3) + mod_index) * PAGE_SIZE 364 | func_dict[fun_addr] = name 365 | data[offset:offset+8] = struct.pack("> Emulation finished, CPU context:") 396 | dump_context(uc) 397 | 398 | def stop(): 399 | curses.echo() 400 | curses.nocbreak() 401 | stdscr.keypad(False) 402 | curses.endwin() 403 | trace.write("out_trace.txt") 404 | 405 | def start(screen): 406 | global stdscr 407 | global reg_win, ins_win, inf_win, stk_win 408 | 409 | stdscr = curses.initscr() 410 | stdscr.keypad(True) 411 | 412 | ins_win = curses.newwin(20, 140, 10, 10) 413 | ins_win.scrollok(True) 414 | 415 | reg_win = curses.newwin(20, 70, 10, 80) 416 | reg_win.idcok(True) 417 | 418 | stk_win = curses.newwin(60, 160, 10, 140) 419 | stk_win.idcok(True) 420 | stk_win.idlok(False) 421 | 422 | inf_win = curses.newwin(100, 100, 30, 10) 423 | #inf_win.scrollok(True) 424 | 425 | curses.echo() 426 | curses.cbreak() 427 | curses.start_color() 428 | curses.use_default_colors() 429 | 430 | curses.init_pair(1, curses.COLOR_GREEN, curses.COLOR_BLACK) 431 | curses.init_pair(2, curses.COLOR_YELLOW, curses.COLOR_BLACK) 432 | curses.init_pair(3, curses.COLOR_MAGENTA, curses.COLOR_BLACK) 433 | curses.init_pair(4, curses.COLOR_CYAN, curses.COLOR_BLACK) 434 | 435 | def main(): 436 | parser = argparse.ArgumentParser() 437 | parser.add_argument("--file", help="PE file to run", type=str) 438 | parser.add_argument("--ss", help="single step mode") 439 | parser.add_argument("--len", help="how many instructions to run", type=int) 440 | parser.add_argument("--att", help="AT&T disasm syntax") 441 | args = parser.parse_args() 442 | if args.file == None: 443 | print("File argument is required") 444 | parser.print_help() 445 | sys.exit(1) 446 | 447 | global singestep 448 | if args.ss != None: 449 | singlestep = True 450 | 451 | if args.att != None: 452 | cs.syntax = CS_OPT_SYNTAX_ATT 453 | 454 | length = -1 455 | if args.len != None: 456 | length = args.length 457 | 458 | curses.wrapper(start) 459 | runpe(args.file, length) 460 | stop() 461 | 462 | if __name__ == "__main__": 463 | main() 464 | -------------------------------------------------------------------------------- /trace.py: -------------------------------------------------------------------------------- 1 | from capstone import * 2 | from capstone.x86 import * 3 | from disp import * 4 | 5 | import numpy as np 6 | import os 7 | 8 | INSN_UNK = 0 9 | INSN_JMP_CONDITIONAL = 1 10 | INSN_JMP_UNCONDITIONAL = 2 11 | INSN_JMP_DYN = 3 12 | INSN_CALL = 4 13 | INSN_CALL_DYN = 5 14 | INSN_NOP = 6 15 | INSN_UD2 = 7 16 | INSN_FP_SAVE = 8 17 | INSN_FP_SETUP = 9 18 | INSN_RET = 10 19 | INSN_IRETF = 11 20 | INSN_LEA = 12 21 | INSN_SYSCALL = 13 22 | INSN_SYSEXIT = 14 23 | 24 | class Instruction: 25 | def __init__(self, insn, addr): 26 | self.insn = insn 27 | self.outs = "" 28 | self.outp = 0xdeadbeefdeadbeef 29 | self.addr = addr 30 | self.type = INSN_UNK 31 | self.call = False 32 | self.labl = False 33 | self.cache() 34 | 35 | def __repr__(self): 36 | i = self.insn 37 | return "{:5s}\t{:s}".format(i.mnemonic, i.op_str) 38 | 39 | def process(self, mu, prev_efl): 40 | insn = self.insn 41 | outp = None 42 | if len(insn.operands) != 0: 43 | out = insn.operands[0] 44 | mask = 0xffffffffffffffff 45 | if out.size == 1: 46 | mask = 0xff 47 | elif out.size == 2: 48 | mask = 0xffff 49 | elif out.size == 4: 50 | mask = 0xffffffff 51 | 52 | if "push" in insn.mnemonic or "pop" in insn.mnemonic: 53 | self.outs = "RSP = {:X}".format(mu.reg_read(X86_REG_RSP)) 54 | elif out.type == X86_OP_REG: 55 | outp = mu.reg_read(out.reg) & mask 56 | self.outs = "{:3s} = {:X}".format(insn.reg_name(out.reg).upper(), outp) 57 | elif out.type == X86_OP_IMM: 58 | outp = out.imm & mask 59 | 60 | efl = mu.reg_read(X86_REG_EFLAGS) 61 | if (efl ^ prev_efl) != 0: 62 | if len(self.outs) != 0: 63 | self.outs += " " 64 | self.outs += dump_deflags(efl, prev_efl) 65 | self.outp = outp 66 | 67 | if self.type >= INSN_JMP_CONDITIONAL and self.type <= INSN_JMP_DYN: 68 | return (outp, False) 69 | elif self.type == INSN_CALL or self.type == INSN_CALL_DYN: 70 | return (outp, True) 71 | return None 72 | 73 | def cache(self): 74 | insn = self.insn 75 | op1 = insn.opcode[0] 76 | op2 = insn.opcode[1] 77 | rex = insn.rex 78 | modrm = insn.modrm 79 | sib = insn.sib 80 | 81 | if (op1 >= 0x70 and op1 <= 0x7f) or op1 == 0xe3: 82 | self.type = INSN_JMP_CONDITIONAL 83 | elif op1 == 0x0f: 84 | if op2 >= 0x80 and op2 <= 0x8f: 85 | self.type = INSN_JMP_CONDITIONAL 86 | elif op2 == 0x0b or op2 == 0xb9: 87 | # UD1, UD2. 88 | self.type = INSN_UD2 89 | elif op2 == 0x0d or op2 == 0x1f: 90 | # nop qword ptr [rbp] (rex.w) 91 | # nop dword ptr [rbp] 92 | # nop word ptr [rbp] (66h) 93 | self.type = INSN_NOP 94 | elif op2 == 0x05 or op2 == 0x34: 95 | # sysenter, syscall 96 | self.type = INSN_SYSCALL 97 | elif op2 == 0x35 or op2 == 0x07: 98 | # no need to check rex.w 99 | self.type = INSN_SYSEXIT 100 | elif op1 == 0x89: 101 | # mov rbp, rsp 102 | if rex == 0x48 and modrm == 0xe5: 103 | self.type == INSN_FP_SAVE 104 | elif op1 == 0x8b: 105 | # mov rbp, [rsp + xx] 106 | if rex == 0x48 and modrm == 0x2c and sib == 0x24: 107 | self.type == INSN_FP_SETUP 108 | elif op1 == 0x90: 109 | self.type = INSN_NOP 110 | elif op1 == 0xe9 or op1 == 0xeb: 111 | self.type = INSN_JMP_UNCONDITIONAL 112 | elif op1 == 0xc2 or op1 == 0xc3: 113 | self.type = INSN_RET 114 | elif op1 == 0xca or op1 == 0xcb or op1 == 0xcf: 115 | self.type = INSN_IRETF 116 | elif op1 == 0xe8: 117 | self.type = INSN_CALL 118 | elif op1 == 0xff: 119 | r = (modrm >> 3) & 7 120 | if r == 2 or r == 3: 121 | self.type = INSN_CALL_DYN 122 | elif r == 4: 123 | self.type = INSN_JMP_DYN 124 | elif op1 == 0x8d: 125 | self.type = INSN_LEA 126 | 127 | class Trace: 128 | def __init__(self): 129 | self.insn_stack = [] 130 | self.prev_efl = 0 131 | 132 | def push_insn(self, mu, addr, insn): 133 | l = len(self.insn_stack) 134 | out = None 135 | if l != 0: 136 | top = self.insn_stack[l - 1] 137 | out = top.process(mu, self.prev_efl) 138 | self.prev_efl = mu.reg_read(X86_REG_EFLAGS) 139 | 140 | i = Instruction(insn, addr) 141 | self.insn_stack.append(i) 142 | if out is not None: 143 | # top is definitely a jmp or call, but it could be conditional so we need to 144 | # check the addr. 145 | if top.type == INSN_JMP_DYN or top.type == INSN_CALL_DYN: 146 | jmp_addr = out[0] 147 | else: # rip relative 148 | jmp_addr = np.uint64(top.addr) + np.uint64(out[0]) - np.uint64(top.insn.size) 149 | if addr == jmp_addr: 150 | if out[1]: 151 | i.call = True 152 | else: 153 | i.labl = True 154 | 155 | def write(self, path): 156 | self.wr_trace(path) 157 | self.wr_flow(path) 158 | 159 | def wr_trace(self, path): 160 | f = os.open(path, os.O_CREAT | os.O_RDWR) 161 | os.write(f, "Address\t\t\tInstruction\t\t\t\tResult\n") 162 | for insn in self.insn_stack: 163 | addr = "{:016x}".format(insn.addr) 164 | if insn.call: 165 | addr = "sub_" + addr 166 | elif insn.labl: 167 | addr = "loc_" + addr 168 | 169 | os.write(f, addr + "\t{:36s}\t{:50s}\n".format(insn, insn.outs)) 170 | os.close(f) 171 | 172 | def wr_flow(self, path): 173 | f = os.open("flow" + path, os.O_CREAT | os.O_RDWR) 174 | for insn in self.insn_stack: 175 | if insn.call: 176 | os.write(f, "sub_{:x}:\n".format(insn.addr)) 177 | elif insn.labl: 178 | os.write(f, "loc_{:x}:\n".format(insn.addr)) 179 | os.write(f, "\t{:s}\n".format(insn)) 180 | os.close(f) 181 | 182 | --------------------------------------------------------------------------------