├── .gitignore ├── LICENCE.txt ├── README.md ├── beepexample.py ├── dobby ├── __init__.py ├── dobby.py ├── dobby_const.py ├── dobby_remote.py ├── dobby_triton.py ├── dobby_types.py ├── dobby_unicorn.py ├── interface.py ├── reversetaint.py ├── winsys.py └── x86const.py ├── other_tools ├── side_utils.py └── uni_ident_test.py └── tester ├── Makefile ├── Test2 ├── .gitignore ├── Test2.c ├── Test2.sln ├── Test2.vcxproj ├── Test2.vcxproj.filters └── asm_code.asm ├── tester.S └── tester.c /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | target* 3 | *.dat 4 | *.bin 5 | *.sys 6 | *.i64 7 | *.exe 8 | *.swp 9 | *.nam 10 | *.til 11 | *.id0 12 | *.id1 13 | *.id2 14 | *.trace 15 | *.bndb* 16 | *.snap 17 | tracecmp.py 18 | -------------------------------------------------------------------------------- /LICENCE.txt: -------------------------------------------------------------------------------- 1 | I haven't decided on a licence yet. 2 | If you want to use this project for personal use, that is fine. Use at your own risk. 3 | You shouldn't yet modify, redistribute, or use this code in a commercial produce. 4 | If you want to do any of that, contact me. 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dobby2 2 | --- 3 | #### What is Dobby? 4 | Dobby is a hobby project I have created to help me reverse engineer a few highly obfuscated binaries. 5 | 6 | Dobby is an emulator that provides interesting tools like a symbolic API, reverse taint analysis, multiple emulation engines, snapshots, and quick prototyping. 7 | 8 | The tool is built upon a set of "providers" that work as backends. Currently the two providers include [Triton](https://github.com/JonathanSalwan/Triton) and [Unicorn](https://github.com/unicorn-engine/unicorn). (See dobby_unicorn.py and dobby_triton.py) Having multiple different engines with different strengths share an environment is a big advantage. 9 | 10 | Other providers can be added simply by implementing the portions of an API that they can. (See interface.py) 11 | 12 | ## Features 13 | - Kernel-mode Emulation 14 | - x86_64 emulation 15 | - Concolic Execution 16 | - Reverse Taint Analysis 17 | - Snapshotting 18 | - Multiple Backends 19 | - Execution Hooks 20 | - Read/Write hooks 21 | - Memory Annotations 22 | - Instruction Trace Comparisons 23 | 24 | ## Q & A 25 | 26 | #### How does this tool help defeat strong obfuscation? 27 | One of the best ways to deobfuscate a binary is to ignore it's obfuscations and just watch for it's side effects. As long as a target binary does not know it is being emulated this can reveal a lot of information. However there is always the fear that the binary has detected it is being watched, and will act differently. Both differential execution and symbolic execution help Dobby find and mitigate the detections. 28 | 29 | Differential execution in this case just means that Dobby can run the same setup with multiple different engines, and report to the researcher where two engines have different execution paths or memory use. Combining this with reverse taint analysis allows us to narrow down a set of reasons why one engine acts different from another. 30 | 31 | Symbolic Execution (actually "concolic" execution) allows us to denote areas of memory or registers as symbolic variables. Dobby will alert us when these variables are used as memory addresses, or used to decide which path execution will take. This allows us to denote much of our environment as symbolic and not worry about setting it up correctly until we see that the target binary even cares about it. It also allows us to do things like backward slicing to produce equations that tell us exactly how our variables were used to produce a value. 32 | 33 | Many existing deobfuscation methods use some kind of lifting and optimization steps to try and produce equivalent code that is simpler. For a significantly complicated binary though, gathering the symbolic equations can explode in terms of memory and processing time. Dobby instead is not designed to be "fire and forget". Dobby works with the reverse engineer allowing symbolic expressions to be concretized into real values before the expressions grow too big for the machine to handle. 34 | 35 | #### How does this tool compare to other projects? 36 | 37 | This project is similar to a few other projects. For most use cases, you probably want a more mature tool. Below is a table comparing a few of them with Dobby. 38 | 39 | = | Dobby | Qiling | Speakeasy | PANDA | QEMU | Triton | Unicorn 40 | ---|-------|--------|-----------|-------|------|--------|--------- 41 | Usability | MediumLow - hobby project | High - Well designed API on Unicorn | High - Well designed API on Unicorn especially for Windows Kernel | Highish - Extension of QEmu | Medium - Not designed as a library | Highish - Requires a bit of work for this use case (hence the creation of Dobby) | Very High - The Best API, but requires a bunch of work to set up environment (hence the creation of Qiling, Speakeasy, and Dobby) 42 | Symbolic Support | Yes | No | No | Some with LLVM IR | No | Yes | No 43 | Snapshots | Yes | Yes | No? | Yes | Yes | No | No 44 | Multiple Backends | Yes | No | No | No | Yes | No | No 45 | Actively Maintained | Still in hobby phase | Yes | Yes | Yes | Yes | Yes | Yes 46 | Licence | ? | GPLv2 | MIT | GPLv2 | GPLv2 | Apache2 | GPLv2 47 | Fun to use | Yes | Yes | Yes | Yesish | Kinda | Yes | Very Yes 48 | Cool Name | No | Yes | Yes | No | Very No | Yes | Yes 49 | Stems from QEMU | Yes | Yes | Yes | Yes | Is | No? | Yes 50 | Publications | No | Yes | Yes | Yes | Yes | Yes | Yes 51 | 52 | [Qiling Framework](https://github.com/qilingframework/qiling) 53 | 54 | [Fireeye's Speakeasy](https://github.com/fireeye/speakeasy) 55 | 56 | [Panda](https://github.com/panda-re/panda) 57 | 58 | [QEMU](https://www.qemu.org/) 59 | 60 | [Triton](https://github.com/JonathanSalwan/Triton) 61 | 62 | [Unicorn](https://github.com/unicorn-engine/unicorn) 63 | 64 | ## Example 65 | See the file beepexample.py 66 | 67 | ## Installation Notes 68 | This project depends on the following python3 packages, which can be installed with pip: 69 | - triton 70 | - pyvex 71 | - lief 72 | - unicorn 73 | -------------------------------------------------------------------------------- /beepexample.py: -------------------------------------------------------------------------------- 1 | if __name__ == '__main__': 2 | print("Please import this file from the interpreter") 3 | exit(-1) 4 | 5 | from dobby import * 6 | from dobby.winsys import * 7 | from dobby import dobby_triton 8 | from dobby import dobby_unicorn 9 | import os 10 | import time 11 | 12 | # load the PE 13 | base = 0xfffff80154a00000 14 | entry = base + 0x602b 15 | ctx = Dobby() 16 | dobby_triton.DobbyTriton(ctx) 17 | #dobby_unicorn.DobbyUnicorn(ctx) 18 | 19 | # setup easy reg access 20 | rax = DB_X86_R_RAX 21 | rbx = DB_X86_R_RBX 22 | rcx = DB_X86_R_RCX 23 | rdx = DB_X86_R_RDX 24 | rdi = DB_X86_R_RDI 25 | rsi = DB_X86_R_RSI 26 | rbp = DB_X86_R_RBP 27 | rsp = DB_X86_R_RSP 28 | r8 = DB_X86_R_R8 29 | r9 = DB_X86_R_R9 30 | r10 = DB_X86_R_R10 31 | r11 = DB_X86_R_R11 32 | r12 = DB_X86_R_R12 33 | r13 = DB_X86_R_R13 34 | r14 = DB_X86_R_R14 35 | r15 = DB_X86_R_R15 36 | rip = DB_X86_R_RIP 37 | gs = DB_X86_R_GS 38 | 39 | save = "beepstartstate" 40 | savefile = save+".snap" 41 | starttime = time.time() 42 | if os.path.exists(savefile): 43 | # load from saved state 44 | print("Loading file") 45 | save = ctx.loadSnapFile(savefile) 46 | print("Restoring State") 47 | ctx.restoreSnap(save) 48 | if not ctx.active.getName() == "Triton": 49 | ctx.setRegVal(DB_X86_R_CR0, 0x10039) # turn off paging until we add that feature to our unicorn setup 50 | print("Restored") 51 | else: 52 | print("No state file found, restarting from start") 53 | print("Loading Beep...") 54 | pe = ctx.loadPE("./beep.sys", base) 55 | 56 | print("Loaded") 57 | ctx.initState(entry, entry+5) 58 | # add in predefined API hooks 59 | initSys(ctx) 60 | 61 | # setup args 62 | if ctx.issym: 63 | ctx.symbolizeRegister(rdx, "RegistryPath") 64 | else: 65 | ctx.setRegVal(rdx, 0x3031323340414243) 66 | 67 | drvobj = createDrvObj(ctx, base, pe.virtual_size, entry, "\\??\\C:\\Windows\\System32\\drivers\\beep.sys", name="beep") 68 | regpath = createUnicodeStr(ctx, "\\REGISTRY\\MACHINE\\SYSTEM\\CURRENTCONTROLSET\\SERVICES\\BEEP") 69 | ctx.setRegVal(rcx, drvobj) 70 | ctx.setRegVal(rdx, regpath) 71 | 72 | # save state for quick loading next time 73 | ctx.takeSnap(save) 74 | ctx.saveSnapFile(save, savefile) 75 | 76 | ctx.startTrace() 77 | 78 | ctx.printIns = False 79 | 80 | endtime = time.time() 81 | 82 | print(f"ctx prepped for beep driver entry in {endtime-starttime} seconds") 83 | -------------------------------------------------------------------------------- /dobby/__init__.py: -------------------------------------------------------------------------------- 1 | from .dobby import * 2 | 3 | __all__ = ["dobby", "dobby_const", "dobby_types"] 4 | -------------------------------------------------------------------------------- /dobby/dobby.py: -------------------------------------------------------------------------------- 1 | from .dobby_const import * 2 | from .dobby_types import * 3 | import os 4 | import lief 5 | import json 6 | import struct 7 | import string 8 | import pickle 9 | 10 | class Dobby: 11 | """ 12 | This Dobby class holds only global state 13 | All real state is held by the providers themselves 14 | And transfered in snapshots 15 | """ 16 | def __init__(self): 17 | print("🤘 Starting Dobby 🤘") 18 | self.systemtimestart = 0x1d68ce74d7e4519 19 | self.IPC = 16 # instructions / Cycle 20 | self.CPN = 3.2 # GigaCycles / Second == Cycles / Nanosecond 21 | self.IPN = self.IPC * self.CPN * 100 # instructions per 100nanosecond 22 | self.printIns = True 23 | 24 | # setup values for active providers 25 | self.isemu = False 26 | self.issym = False 27 | self.isreg = False 28 | self.ismem = False 29 | self.issnp = False 30 | self.active = None 31 | self.providers = [] 32 | 33 | self.snapshots = {} 34 | 35 | # for now just support x86 36 | self.spreg = DB_X86_R_RSP 37 | self.ipreg = DB_X86_R_RIP 38 | self.pgshft = DB_X86_PGSHFT 39 | self.pgsz = DB_X86_PGSZ 40 | self.name2reg = x86name2reg 41 | self.reg2name = x86reg2name 42 | 43 | # trace format (addr, disass, [[addrsread],[addrswritten]], inssz) 44 | self.trace_disass = True 45 | self.trace_dref = False 46 | self.trace_inssz = False 47 | self.trace_api = True 48 | 49 | #TODO automatically register providers here? 50 | 51 | def registerProvider(self, provider, name, activate): 52 | if provider in self.providers: 53 | print(f"Provider {name} is already registered") 54 | return 55 | print(f"Registering provider {name}") 56 | self.providers.append(provider) 57 | 58 | if activate: 59 | if self.active is not None: 60 | print("Warning, deactivating previous provider") 61 | self.deactivateProvider() 62 | self.activateProvider(provider) 63 | 64 | def activateProvider(self, provider): 65 | if self.active == provider: 66 | return 67 | if self.active is not None: 68 | self.deactivateProvider() 69 | self.active = provider 70 | self.isemu = provider.isEmuProvider 71 | self.issym = provider.isSymProvider 72 | self.isreg = provider.isRegContextProvider 73 | self.ismem = provider.isMemoryProvider 74 | self.issnp = provider.isSnapshotProvider 75 | self.isfuz = provider.isFuzzerProvider 76 | 77 | self.active.activated() 78 | 79 | def deactivateProvider(self): 80 | if self.active is not None: 81 | self.active.deactivated() 82 | 83 | self.active = None 84 | self.isemu = False 85 | self.issym = False 86 | self.isreg = False 87 | self.ismem = False 88 | self.issnp = False 89 | 90 | def removeProvider(self, provider): 91 | if self.active == provider: 92 | deactivateProvider() 93 | 94 | self.providers.remove(provider) 95 | 96 | provider.removed() 97 | 98 | def getProvider(self, nameprefix): 99 | found = None 100 | for p in self.providers: 101 | if p.getName().lower().startswith(nameprefix.lower()): 102 | if found is not None: 103 | raise KeyError(f"Multiple providers start with \"{nameprefix}\"") 104 | found = p 105 | if found is not None: 106 | return found 107 | raise KeyError(f"No active provider that starts with \"{nameprefix}\"") 108 | 109 | def setProvider(self, nameprefix): 110 | p = self.getProvider(nameprefix) 111 | self.activateProvider(p) 112 | 113 | def delProvider(self, nameprefix): 114 | p = self.getProvider(nameprefix) 115 | removeProvider(p) 116 | 117 | def perm2Str(self, p): 118 | s = "" 119 | if p & MEM_READ: 120 | s += "r" 121 | if p & MEM_WRITE: 122 | s += "w" 123 | if p & MEM_EXECUTE: 124 | s += "x" 125 | return s 126 | 127 | def printBounds(self): 128 | for b in self.getBoundsRegions(True): 129 | print(hex(b[0])[2:].zfill(16), '-', hex(b[1])[2:].zfill(16), self.perm2Str(b[2])) 130 | 131 | def printAst(self, ast): 132 | if not self.issym: 133 | raise RuntimeError("No symbolic providers are active") 134 | self.active.printAst(ast) 135 | 136 | def printReg(self, reg): 137 | print(self.getRegName(reg), end=" = ") 138 | 139 | if self.issym and self.isSymbolizedRegister(reg): 140 | ast = self.getRegisterAst(reg) 141 | self.printAst(ast) 142 | else: 143 | # concrete value 144 | print(hex(self.getRegVal(reg))) 145 | 146 | def ip(self): 147 | self.getRegVal(self.ipreg) 148 | 149 | def printSymMem(self, addr, amt, stride): 150 | if not self.issym: 151 | raise RuntimeError("No symbolic providers are active") 152 | if not self.inBounds(addr, amt, MEM_NONE): 153 | print("Warning, OOB memory") 154 | for i in range(0, amt, stride): 155 | memast = self.getMemoryAst(addr+i, stride) 156 | print(hex(addr+i)[2:].zfill(16), end=": ") 157 | self.printAst(memast) 158 | 159 | def printMem(self, addr, amt=0x60): 160 | if not self.inBounds(addr, amt, MEM_NONE): 161 | print("Warning, OOB memory") 162 | # read symbolic memory too 163 | if self.issym and self.isSymbolizedMemory(addr, amt): 164 | print("Warning, contains symbolized memory") 165 | self.printSymMem(addr, amt, 8, simp) 166 | else: 167 | mem = self.getMemVal(addr, amt) 168 | hexdmp(mem, addr) 169 | 170 | def printRegMem(self, reg, amt=0x60): 171 | # dref register, if not symbolic and call printMem 172 | if self.issym and self.isSymbolizedRegister(reg): 173 | print("Symbolic Register") 174 | self.printReg(reg) 175 | else: 176 | addr = self.getRegVal(reg) 177 | self.printMem(addr, amt) 178 | 179 | def printStack(self, amt=0x60): 180 | self.printRegMem(self.spreg, amt) 181 | 182 | def printQMem(self, addr, amt=12): 183 | if not self.inBounds(addr, amt*8, MEM_NONE): 184 | print("Warning, OOB memory") 185 | for i in range(amt): 186 | a = addr + (8*i) 187 | v = self.getu64(a) 188 | print(hex(a)[2:]+':', hex(v)[2:]) 189 | 190 | def printMap(self): 191 | mp = [ x for x in self.active.ann if (x.end - x.start) != 0 ] 192 | mp.sort(key = lambda x: x.start) 193 | 194 | # add bounds areas not covered by ann 195 | for p in self.active.bounds: 196 | covered = False 197 | # if b is not contained by any annotation save it 198 | s = p << self.pgshft 199 | e = s + self.pgsz 200 | for m in mp: 201 | if m.end <= s: 202 | continue 203 | if m.start >= e: 204 | break 205 | # take out a chunk 206 | if m.start <= s < m.end: 207 | s = m.end 208 | if m.start < e <= m.end: 209 | e = m.start 210 | 211 | if e <= s: 212 | covered = True 213 | break 214 | 215 | if not covered: 216 | mp.append(Annotation(s, e, "UNK", "IN BOUNDS, NO ANN")) 217 | mp.sort(key = lambda x: x.start) 218 | 219 | print("\n".join([str(x) for x in mp])) 220 | 221 | def getfmt(self, addr, fmt, sz): 222 | return struct.unpack(fmt, self.getMemVal(addr, sz))[0] 223 | 224 | def getu64(self, addr): 225 | return self.getfmt(addr, " end: 463 | end = e 464 | 465 | if self.inBounds(base, end - base, MEM_NONE): 466 | raise MemoryError(f"Could not load pe {pe.name} at {hex(base)}, because it would clobber existing memory") 467 | 468 | self.active.modules.append(pe) 469 | 470 | dif = base - pe.optional_header.imagebase 471 | 472 | # load concrete mem vals from image 473 | # we need to load in header as well 474 | rawhdr = b"" 475 | with open(path, "rb") as fp: 476 | rawhdr = fp.read(pe.sizeof_headers) 477 | roundedlen = (len(rawhdr) + (self.pgsz-1)) & (~(self.pgsz-1)) 478 | 479 | self.updateBounds(base, base+roundedlen, MEM_READ, False) 480 | self.setMemVal(base, rawhdr) 481 | 482 | self.addAnn(base, base+len(rawhdr), "MAPPED_PE_HDR", pe.name) 483 | 484 | for phdr in pe.sections: 485 | start = base + phdr.virtual_address 486 | end = start + len(phdr.content) 487 | 488 | if (end - start) < phdr.virtual_size: 489 | end = start + phdr.virtual_size 490 | 491 | # round end up to page size 492 | end = (end + 0xfff) & (~0xfff) 493 | 494 | #annotate the memory region 495 | self.addAnn(start, end, "MAPPED_PE", pe.name + '(' + phdr.name + ')') 496 | perm = MEM_NONE 497 | if phdr.has_characteristic(lief.PE.SECTION_CHARACTERISTICS.MEM_EXECUTE): 498 | perm |= MEM_EXECUTE 499 | if phdr.has_characteristic(lief.PE.SECTION_CHARACTERISTICS.MEM_READ): 500 | perm |= MEM_READ 501 | if phdr.has_characteristic(lief.PE.SECTION_CHARACTERISTICS.MEM_WRITE): 502 | perm |= MEM_WRITE 503 | 504 | self.updateBounds(start, end, perm, False) 505 | self.setMemVal(start, phdr.content) 506 | 507 | # do reloactions 508 | for r in pe.relocations: 509 | lastabs = False 510 | for re in r.entries: 511 | if lastabs: 512 | # huh, it wasn't the last one? 513 | print(f"Warning, got a ABS relocation that wasn't the last one") 514 | if failwarn: 515 | raise ReferenceError("Bad relocation") 516 | if re.type == lief.PE.RELOCATIONS_BASE_TYPES.DIR64: 517 | a = re.address 518 | val = self.getu64(base + a) 519 | 520 | slid = val + dif 521 | 522 | self.setu64(base + a, slid) 523 | elif re.type == lief.PE.RELOCATIONS_BASE_TYPES.ABSOLUTE: 524 | # last one is one of these as a stop point 525 | lastabs = True 526 | else: 527 | print(f"Warning: PE Loading: Unhandled relocation type {re.type}") 528 | if failwarn: 529 | raise ReferenceError("Bad relocation") 530 | 531 | # setup exception handlers 532 | # actually, we check exception handlers at runtime, in case they change under us 533 | 534 | # symbolize imports 535 | for i in pe.imports: 536 | for ie in i.entries: 537 | # extend the API HOOKS execution hook 538 | hookaddr = self.active.apihooks.end 539 | self.active.apihooks.end += 8 540 | self.setu64(base + ie.iat_address, hookaddr) 541 | 542 | name = i.name + "::" + ie.name 543 | # create symbolic entry in the, if the address is used strangly 544 | # really this hook should be in the IAT, if the entry is a pointer to something bigger than 8 bytes 545 | #TODO 546 | # but for now, we just assume most of these are functions or pointers to something 8 or less bytes large? 547 | if self.issym: 548 | self.symbolizeMemory(hookaddr, 8, "IAT val from " + pe.name + " for " + name) 549 | 550 | # create execution hook in hook are 551 | h = self.addHook(hookaddr, hookaddr+8, MEM_EXECUTE, None, "IAT entry from " + pe.name + " for " + name) 552 | h.isApiHook = True 553 | 554 | self.updateBounds(self.active.apihooks.start, self.active.apihooks.end, MEM_ALL, False) 555 | 556 | # annotate symbols from image 557 | for sym in pe.exported_functions: 558 | if not sym.name: 559 | continue 560 | self.addAnn(sym.address + base, sym.address + base, "SYMBOL", pe.name + "::" + sym.name) 561 | 562 | return pe 563 | 564 | def getExceptionHandlers(self, addr): 565 | # should return a generator that will walk back over exception handlers 566 | # generator each time returns (filteraddr, handleraddr) 567 | #TODO 568 | raise NotImplementedError("Lot to do here") 569 | 570 | # also create an API to setup args for filter/handler, do state save, etc 571 | #TODO 572 | 573 | def addHook(self, start, end, htype, handler=None, label=""): 574 | # handler takes 4 args, (hook, addr, sz, op, provider) 575 | # handler returns a HookRet code that determines how to procede 576 | if (htype & MEM_ALL) == 0: 577 | raise ValueError("Hook didn't specify a proper type") 578 | 579 | h = Hook(start, end, htype, label, handler) 580 | 581 | added = False 582 | if (htype & MEM_ALL) == 0: 583 | raise ValueError(f"Unknown Hook Type {htype}") 584 | 585 | if (htype & MEM_EXECUTE) != 0: 586 | added = True 587 | self.active.hooks[0].append(h) 588 | if (htype & MEM_READ) != 0: 589 | added = True 590 | self.active.hooks[1].append(h) 591 | if (htype & MEM_WRITE) != 0: 592 | added = True 593 | self.active.hooks[2].append(h) 594 | 595 | if self.isemu: 596 | self.active.insertHook(h) 597 | else: 598 | print("Warning, added a hook without a emulation provider active") 599 | 600 | return h 601 | 602 | def bp(self, addr): 603 | self.addHook(addr, addr+1, MEM_EXECUTE, None, "breakpoint") 604 | 605 | def delHook(self, hook, htype=MEM_ALL): 606 | if self.isemu: 607 | self.active.removeHook(hook) 608 | 609 | if (htype & MEM_EXECUTE) != 0 and hook in self.active.hooks[0]: 610 | self.active.hooks[0].remove(hook) 611 | if (htype & MEM_READ) != 0 and hook in self.active.hooks[1]: 612 | self.active.hooks[1].remove(hook) 613 | if (htype & MEM_WRITE) != 0 and hook in self.active.hooks[2]: 614 | self.active.hooks[2].remove(hook) 615 | 616 | def doRet(self, retval=0): 617 | self.setRegVal(DB_X86_R_RAX, retval) 618 | sp = self.getRegVal(self.spreg) 619 | retaddr = self.getu64(sp) 620 | self.setRegVal(self.ipreg, retaddr) 621 | self.setRegVal(self.spreg, sp+8) 622 | 623 | @staticmethod 624 | def noopemuhook(hook, ctx, addr, sz, op, provider): 625 | return HookRet.CONT_INS 626 | 627 | @staticmethod 628 | def retzerohook(hook, ctx, addr, sz, op, provider): 629 | ctx.doRet(0) 630 | return HookRet.DONE_INS 631 | 632 | @staticmethod 633 | def stack_guard_hook(hk, ctx, addr, sz, op, provider): 634 | if ctx.active.stackann is None: 635 | raise RuntimeError("No stack was initalized") 636 | # grow the stack, if we can 637 | newstart = ctx.stackann.start - 0x1000 638 | if ctx.inBounds(newstart, 0x1000, MEM_NONE): 639 | # error, stack ran into something else 640 | print(f"Stack overflow! Stack with top at {stackann.start} could not grow") 641 | return True 642 | 643 | # grow annotation 644 | ctx.stackann.start = newstart 645 | # grow bounds 646 | ctx.updateBounds(newstart, ctx.stackann[1], MEM_READ | MEM_WRITE, provider) 647 | # move the hook 648 | hk.start = newstart - 0x1000 649 | hk.end = newstart 650 | return False 651 | 652 | 653 | def addVolatileSymHook(name, addr, sz, op, stops=False): 654 | if op != MEM_READ: 655 | raise TypeError("addVolatileSymHook only works with read hooks") 656 | 657 | if not self.issym: 658 | raise RuntimeError("No symbolic providers are active") 659 | 660 | hit_count = 0 661 | def vshook(hook, ctx, addr, sz, op, provider): 662 | nonlocal hit_count 663 | # create a new symbol for every hit 664 | ctx.symbolizeMemory(addr, sz, name+hex(hit_count)) 665 | hit_count += 1 666 | return HookRet.OP_CONT_INS 667 | 668 | self.addHook(addr, addr+sz, op, vshook, name + "_VolHook") 669 | 670 | def createThunkHook(self, symname, pename="", dostop=False): 671 | symaddr = self.getImageSymbol(symname, pename) 672 | def dothunk(hook, ctx, addr, sz, op, provider): 673 | ctx.setRegVal(ctx.ipreg, symaddr) 674 | return HookRet.OP_DONE_INS 675 | return dothunk 676 | 677 | def stopNextHook(self, hook, count=1): 678 | oldhandler = hook.handler 679 | def stoponce(hook, ctx, addr, sz, op, provider): 680 | nonlocal count 681 | if count <= 1: 682 | hook.handler = oldhandler 683 | return HookRet.FORCE_STOP_INS 684 | else: 685 | count -= 1 686 | return oldhandler(hook, ctx, addr, sz, op, provider) 687 | hook.handler = stoponce 688 | 689 | def setApiHandler(self, name, handler, overwrite=False): 690 | if not self.isemu: 691 | raise RuntimeError("No emulation providers are active") 692 | 693 | found = [x for x in self.active.hooks[0] if x.isApiHook and x.label.endswith("::"+name) and 0 != (x.htype & MEM_EXECUTE)] 694 | 695 | if overwrite=="ignore" and len(found) == 0: 696 | # no need 697 | return 698 | if len(found) != 1: 699 | raise KeyError(f"Found {len(found)} api hooks that match the name {name}, unable to set handler") 700 | 701 | hk = found[0] 702 | 703 | doh = True 704 | if hk.handler is not None and not overwrite: 705 | raise KeyError(f"Tried to set a handler for a API hook that already has a set handler") 706 | 707 | hk.handler = handler 708 | 709 | @staticmethod 710 | def rdtscHook(ctx, addr, provider): 711 | cycles = ctx.getCycles() 712 | newrip = ctx.getRegVal(ctx.ipreg) + 2 713 | ctx.setRegVal(ctx.ipreg, newrip) 714 | aval = cycles & 0xffffffff 715 | dval = (cycles >> 32) & 0xffffffff 716 | ctx.setRegVal(DB_X86_R_RAX, aval) 717 | ctx.setRegVal(DB_X86_R_RDX, dval) 718 | return HookRet.DONE_INS 719 | 720 | def updateBounds(self, start, end, permissions, overrule=False): 721 | if end <= start: 722 | raise ValueError("Tried to UpdateBounds with end <= start") 723 | 724 | startshft = start >> self.pgshft 725 | startcountshft = startshft 726 | endshft = (end + (self.pgsz-1)) >> self.pgshft 727 | while startshft < endshft: 728 | doupdate = True 729 | if startshft in self.active.bounds: 730 | existingperm = self.active.bounds[startshft] 731 | if permissions == existingperm: 732 | if startcountshft == startshft: 733 | startcountshft += 1 734 | doupdate = False 735 | elif not overrule: 736 | raise MemoryError(f"Tried to update bounds with permissions {permissions} when they were already {self.active.bounds[start]}") 737 | 738 | if doupdate: 739 | self.active.bounds[startshft] = permissions 740 | # here we could update a page table, if we did there 741 | 742 | startshft += 1 743 | 744 | # call the providers 745 | if self.ismem and startcountshft != endshft: 746 | rndstart = startcountshft << self.pgshft 747 | rndend = endshft << self.pgshft 748 | self.active.updateBounds(rndstart, rndend, permissions) 749 | 750 | def createPageTables(self): 751 | raise NotImplementedError("Our current providers don't work well with page tables") 752 | 753 | def inBounds(self, addr, sz, access): 754 | start = addr >> self.pgshft 755 | sz = (sz + (self.pgsz-1)) >> self.pgshft 756 | end = (start + sz) 757 | 758 | while start < end: 759 | if start not in self.active.bounds: 760 | return False 761 | if access != (access & self.active.bounds[start]): 762 | print("DEBUG Violated Memory Permissions?") 763 | return False 764 | start += 1 765 | return True 766 | 767 | def getBoundsRegions(self, withPerm=False): 768 | out = [] 769 | curp = MEM_NONE 770 | start = 0 771 | last = -1 772 | for p in sorted(self.active.bounds): 773 | if last == -1: 774 | start = p 775 | curp = self.active.bounds[p] 776 | elif p > (last+1) or (withPerm and curp != self.active.bounds[p]): 777 | if withPerm: 778 | out.append((start << self.pgshft, (last+1) << self.pgshft, curp)) 779 | else: 780 | out.append((start << self.pgshft, (last+1) << self.pgshft)) 781 | start = p 782 | curp = self.active.bounds[p] 783 | last = p 784 | 785 | if start <= last: 786 | if withPerm: 787 | out.append((start << self.pgshft, (last+1) << self.pgshft, curp)) 788 | else: 789 | out.append((start << self.pgshft, (last+1) << self.pgshft)) 790 | 791 | return out 792 | 793 | def getNextFreePage(self, addr): 794 | start = addr >> self.pgshft 795 | while start in self.active.bounds: 796 | start += 1 797 | 798 | return start << self.pgshft 799 | 800 | def addAnn(self, start, end, mtype, label=""): 801 | ann = Annotation(start, end, mtype, label) 802 | self.active.ann.append(ann) 803 | return ann 804 | 805 | def getImageSymbol(self, symname, pename=""): 806 | # not to be confused with getSymbol, which works on symbolic symbols 807 | # this works on annotations of type SYMBOL 808 | symname = pename + "::" + symname 809 | match = [ x for x in self.active.ann if x.mtype == "SYMBOL" and x.label.endswith(symname) ] 810 | 811 | if len(match) == 0: 812 | raise KeyError(f"Unable to find Symbol {symname}") 813 | if len(match) > 1: 814 | raise KeyError(f"Found multiple Symbols matching {symname}") 815 | 816 | return match[0].start 817 | 818 | def alloc(self, amt): 819 | if self.active.nextalloc == 0: 820 | self.active.nextalloc = 0xffff765400000000 if self.active.priv else 0x660000 821 | 822 | start = self.active.nextalloc 823 | 824 | # round amt up to 0x10 boundry 825 | amt = (amt+0xf) & (~0xf) 826 | 827 | end = start + amt 828 | self.active.nextalloc = end 829 | 830 | # if there is already an "ALLOC" annotation, extend it 831 | allocann = None 832 | for a in self.active.ann: 833 | if a.end == start and a.mtype == "ALLOC": 834 | allocann = a 835 | allocann.end = end 836 | break; 837 | if allocann is None: 838 | allocann = Annotation(start, end, "ALLOC", "allocations") 839 | self.active.ann.append(allocann) 840 | 841 | self.updateBounds(start, end, MEM_READ | MEM_WRITE) 842 | 843 | return start 844 | 845 | def initState(self, start, end, stackbase=0, ring=0): 846 | #TODO be able to initalize/track multiple contexts 847 | #TODO work in emu mode 848 | 849 | self.active.priv = (ring == 0) 850 | if stackbase == 0: 851 | stackbase = 0xffffb98760000000 if self.active.priv else 0x64f000 852 | 853 | # zero or symbolize all registers 854 | for r in self.getAllReg(): 855 | n = self.getRegName(r) 856 | sym = False 857 | if n in ["cr8", "cr0", "cr3", "cr4"]: 858 | sym=False 859 | elif n.startswith("cr") or n in ["gs", "fs"]: 860 | sym = True 861 | 862 | self.setRegVal(r, 0) 863 | if self.issym and sym: 864 | self.symbolizeRegister(r, "Inital " + n) 865 | 866 | # setup rflags to be sane 867 | self.setRegVal( 868 | DB_X86_R_EFLAGS, 869 | (1 << 9) | # interrupts enabled 870 | (ring << 12) | # IOPL 871 | (1 << 21) # support cpuid 872 | ) 873 | 874 | # setup sane control registers 875 | self.setRegVal(DB_X86_R_CR8, 0) # IRQL of 0 (PASSIVE_LEVEL) 876 | 877 | cr0val = 0 878 | cr0val |= 1 << 0 # Protected Mode 879 | cr0val |= 0 << 1 # Monitor Coprocessor 880 | cr0val |= 0 << 2 # Emulation Mode 881 | cr0val |= 1 << 3 # Task Switched ? 882 | cr0val |= 1 << 4 # Extension Type ? 883 | cr0val |= 1 << 5 # Numeric Error 884 | cr0val |= 1 << 16 # Write Protect 885 | cr0val |= 0 << 18 # Alignment Mask 886 | cr0val |= 0 << 29 # Not Write-through 887 | cr0val |= 0 << 30 # Cache Disable 888 | if self.active.getName() == 'unicorn': 889 | # unicorn does not work well for how we want 890 | # even if we generate good page tables 891 | # so we just have to not enable it 892 | # and use snapshotting back and forth to get past CR0 detection 893 | cr0val |= 0 << 31 # Paging Enabled 894 | else: 895 | cr0val |= 1 << 31 # Paging Enabled 896 | 897 | self.setRegVal(DB_X86_R_CR0, cr0val) 898 | 899 | # set cr4 900 | cr4val = 0 901 | cr4val |= 0 << 0 # VME 902 | cr4val |= 0 << 1 # PVI 903 | cr4val |= 0 << 2 # TSD 904 | cr4val |= 0 << 3 # DE 905 | cr4val |= 0 << 4 # PSE 906 | cr4val |= 1 << 5 # PAE 907 | cr4val |= 0 << 6 # MCE 908 | cr4val |= 1 << 7 # PGE 909 | cr4val |= 1 << 8 # PCE 910 | cr4val |= 1 << 9 # OSFXSR 911 | cr4val |= 0 << 10 # OSXMMEXCPT 912 | cr4val |= 1 << 11 # UMIP 913 | cr4val |= 1 << 12 # LA57 914 | cr4val |= 0 << 13 # VMXE 915 | cr4val |= 0 << 14 # SMXE 916 | cr4val |= 1 << 17 # PCIDE 917 | cr4val |= 0 << 18 # OSXSAVE 918 | cr4val |= 1 << 20 # SMEP 919 | cr4val |= 1 << 21 # SMAP 920 | cr4val |= 0 << 22 # PKE 921 | cr4val |= 0 << 23 # CET (gross) 922 | cr4val |= 0 << 24 # PKS 923 | 924 | self.setRegVal(DB_X86_R_CR4, cr4val) 925 | 926 | # set up cr3 and paging 927 | #TODO 928 | #self.createPageTables() 929 | 930 | # create stack 931 | if self.active.stackann is None: 932 | stackstart = stackbase - (0x1000 * 16) 933 | self.active.stackann = self.addAnn(stackstart, stackbase, "STACK", "Inital Stack") 934 | self.updateBounds(stackstart, stackbase, MEM_READ | MEM_WRITE, False) 935 | self.addHook(stackstart - (0x1000), stackstart, MEM_WRITE, self.stack_guard_hook, "Stack Guard") 936 | 937 | # create end hook 938 | self.addHook(end, end+1, MEM_EXECUTE, None, "End Hit") 939 | 940 | # set initial rip and rsp 941 | self.setRegVal(self.ipreg, start) 942 | self.setRegVal(self.spreg, stackbase - 0x100) 943 | 944 | return True 945 | 946 | def startTrace(self): 947 | if not self.isemu: 948 | raise RuntimeError("No emulation providers are active") 949 | 950 | return self.active.startTrace() 951 | 952 | def getTrace(self): 953 | if not self.isemu: 954 | raise RuntimeError("No emulation providers are active") 955 | 956 | return self.active.getTrace() 957 | 958 | def stopTrace(self): 959 | if not self.isemu: 960 | raise RuntimeError("No emulation providers are active") 961 | 962 | return self.active.stopTrace() 963 | 964 | def printTracePiece(self, trace, prev=42, end=-1, printind=False): 965 | if end < 0: 966 | end = len(trace) + end + 1 967 | 968 | start = end - prev 969 | 970 | for i in range(start,end): 971 | if i < 0 or i >= len(trace): 972 | continue 973 | e = trace[i] 974 | out = hex(e[0])[2:] 975 | if len(e) >= 2: 976 | out += ", " + e[1] 977 | if printind: 978 | out = f"{i:x} " + out 979 | print(out) 980 | 981 | def printTrace(self, prev=42): 982 | self.printTracePiece(self.getTrace(), prev, -1) 983 | 984 | def cmpTraceAddrs(self, t1, t2, cmpdref=False): 985 | # looks like trying to stop execution with ^C can make the trace skip? 986 | if len(t1) != len(t2): 987 | print("Traces len differ ", len(t1), len(t2)) 988 | 989 | l = min(len(t1), len(t2)) 990 | 991 | differ = False 992 | for i in range(l): 993 | t1i = t1[i] 994 | t2i = t2[i] 995 | if (t1i[0] != t2i[0]): 996 | differ = True 997 | print(f"Traces diverge after {i:x} instructions ( @ {t1i[0]:x}, @ {t2i[0]:x})") 998 | break 999 | if cmpdref and len(t1i) >=3 and len(t2i) >=3: 1000 | if t1i[2] != t2i[2]: 1001 | print(f"Traces's memory dereferences differ after {i:x} instructions @ {t1i[0]:x}") 1002 | break 1003 | if not differ: 1004 | print("Traces match") 1005 | 1006 | def saveTrace(self, trace, filepath): 1007 | with open(filepath, "w") as fp: 1008 | for te in trace: 1009 | fp.write(json.dumps(te) + '\n') 1010 | 1011 | def saveCurrentTrace(self, filepath): 1012 | self.saveTrace(self.getTrace(), filepath) 1013 | 1014 | def loadTrace(self, filepath): 1015 | trace = [] 1016 | with open(filepath, "r") as fp: 1017 | while True: 1018 | l = fp.readline() 1019 | if l == "": 1020 | break 1021 | trace.append(json.loads(l)) 1022 | return trace 1023 | 1024 | def getApiTraceIndex(self, trace, apiname): 1025 | out = [] 1026 | for i in range(len(trace)): 1027 | if trace[i][0] != TRACE_API_ADDR: 1028 | continue 1029 | dis = trace[i][1] 1030 | if dis.endswith(apiname): 1031 | out.append(i) 1032 | return out 1033 | 1034 | #TODO move to EMU interface? 1035 | def handle_hook(self, hk, addr, sz, op, ignorehook): 1036 | self.active.lasthook = hk 1037 | 1038 | handler = hk.handler 1039 | 1040 | stopret = StepRet.HOOK_EXEC 1041 | if op == MEM_READ: 1042 | stopret = StepRet.HOOK_READ 1043 | elif op == MEM_WRITE: 1044 | stopret = StepRet.HOOK_WRITE 1045 | elif op != MEM_EXECUTE: 1046 | raise TypeError(f"Unknown op to handler hook \"{op}\"") 1047 | 1048 | if handler is not None: 1049 | # optionally leave entry in trace 1050 | if self.active.isTracing() and self.trace_api and (self.active.apihooks.start <= addr < self.active.apihooks.end): 1051 | dis = hk.label 1052 | self.active.traceAPI(dis) 1053 | 1054 | hret = handler(hk, self, addr, sz, op, self.active) 1055 | 1056 | if hret == HookRet.FORCE_STOP_INS: 1057 | return (True, stopret) 1058 | elif hret == HookRet.OP_CONT_INS: 1059 | if self.active.opstop: 1060 | hret = HookRet.STOP_INS 1061 | else: 1062 | hret = HookRet.CONT_INS 1063 | elif hret == HookRet.OP_DONE_INS: 1064 | if self.active.opstop: 1065 | hret = HookRet.STOP_INS 1066 | else: 1067 | hret = HookRet.DONE_INS 1068 | 1069 | if hret == HookRet.STOP_INS: 1070 | if not ignorehook: 1071 | return (True, stopret) 1072 | else: 1073 | return (False, stopret) 1074 | elif hret == HookRet.CONT_INS: 1075 | return (False, StepRet.OK) 1076 | elif hret == HookRet.DONE_INS: 1077 | # only applies to exec type hooks 1078 | if op != MEM_EXECUTE: 1079 | raise TypeError(f"Hook \"{str(hk)}\" returned done, even though the op type is \"{op}\"") 1080 | return (True, StepRet.OK) 1081 | elif hret == HookRet.ERR: 1082 | return (True, StepRet.HOOK_ERR) 1083 | else: 1084 | raise TypeError(f"Unknown return from hook handler for hook {eh}") 1085 | else: 1086 | # no ignoring API hooks with no handler 1087 | if (self.active.apihooks.start <= addr < self.active.apihooks.end) or (not ignorehook): 1088 | return (True, stopret) 1089 | else: 1090 | return (False, StepRet.OK) 1091 | 1092 | def step(self, ignorehook=True): 1093 | if not self.isemu: 1094 | raise RuntimeError("No emulation providers are active") 1095 | 1096 | return self.active.step(ignorehook, self.printIns) 1097 | 1098 | def cont(self, ignorehook=True, n=0): 1099 | if not self.isemu: 1100 | raise RuntimeError("No emulation providers are active") 1101 | 1102 | return self.active.cont(ignorehook, self.printIns) 1103 | 1104 | def until(self, addr, ignorehook=True): 1105 | if not self.isemu: 1106 | raise RuntimeError("No emulation providers are active") 1107 | 1108 | return self.active.until(addr, ignorehook, self.printIns) 1109 | 1110 | def next(self, ignorehook=True): 1111 | if not self.isemu: 1112 | raise RuntimeError("No emulation providers are active") 1113 | 1114 | return self.active.next(ignorehook, self.printIns) 1115 | 1116 | def takeSnap(self, name): 1117 | if not self.issnp: 1118 | raise RuntimeError("No emulation providers are active") 1119 | 1120 | if name in self.snapshots: 1121 | raise KeyError(f"Snapshot named {name} already exists") 1122 | 1123 | snap = Snapshot(name) 1124 | 1125 | snap.take(self) 1126 | 1127 | self.active.takeSnapshot(snap) 1128 | 1129 | self.snapshots[name] = snap 1130 | 1131 | def restoreSnap(self, name, trackDiff=False): 1132 | if not self.issnp: 1133 | raise RuntimeError("No emulation providers are active") 1134 | 1135 | if name not in self.snapshots: 1136 | raise KeyError(f"No snapshot named {name}") 1137 | 1138 | snap = self.snapshots[name] 1139 | 1140 | snap.restore(self) 1141 | 1142 | self.active.restoreSnapshot(snap, trackDiff) 1143 | 1144 | def removeSnap(self, name): 1145 | if name not in self.snapshots: 1146 | raise KeyError(f"No snapshot named {name}") 1147 | 1148 | del self.snapshots[name] 1149 | 1150 | def saveSnapFile(self, name, filename=None): 1151 | 1152 | if name not in self.snapshots: 1153 | raise KeyError(f"No snapshot named {name}") 1154 | 1155 | if filename is None: 1156 | filename = "./"+name+".snap" 1157 | 1158 | snap = self.snapshots[name] 1159 | try: 1160 | with open(filename, "wb") as fp: 1161 | pickle.dump(snap, fp) 1162 | except Exception as e: 1163 | os.unlink(filename) 1164 | raise e 1165 | 1166 | def loadSnapFile(self, filename): 1167 | snap = None 1168 | with open(filename, "rb") as fp: 1169 | snap = pickle.load(fp) 1170 | 1171 | name = snap.name 1172 | 1173 | if name in self.snapshots: 1174 | print(f"Snapshot named {name} already exists, appending a number") 1175 | i = 0 1176 | while name+str(i) in self.snapshots: 1177 | i += 1 1178 | name = name+str(i) 1179 | 1180 | self.snapshots[name] = snap 1181 | 1182 | return name 1183 | 1184 | def takeSnapToFile(self, name="_quicksave", filename=None): 1185 | takeSnap(name) 1186 | saveSnapFile(name, filename) 1187 | removeSnap(name) 1188 | 1189 | def hexdmp(stuff, start=0): 1190 | printable = string.digits + string.ascii_letters + string.punctuation + ' ' 1191 | rowlen = 0x10 1192 | mid = (rowlen//2)-1 1193 | for i in range(0, len(stuff), rowlen): 1194 | # start of line 1195 | print(hex(start + i)[2:].zfill(16), end=": ") 1196 | 1197 | # bytes 1198 | rowend = min(i+rowlen, len(stuff)) 1199 | for ci in range(i, rowend): 1200 | print(stuff[ci:ci+1].hex(), end=(" " if ((ci & (rowlen-1)) != mid) else '-')) 1201 | 1202 | # padding 1203 | empty = rowlen - (rowend - i) 1204 | if empty != 0: 1205 | # pad out 1206 | print(" " * empty, end="") 1207 | 1208 | print(' ', end="") 1209 | 1210 | # ascii 1211 | for c in stuff[i:rowend]: 1212 | cs = chr(c) 1213 | if cs in printable: 1214 | print(cs, end="") 1215 | else: 1216 | print(".", end="") 1217 | print() 1218 | -------------------------------------------------------------------------------- /dobby/dobby_const.py: -------------------------------------------------------------------------------- 1 | from .x86const import * 2 | from enum import Enum 3 | 4 | class HookRet(Enum): 5 | ERR = -1 6 | CONT_INS = 0 7 | DONE_INS = 1 8 | STOP_INS = 2 9 | FORCE_STOP_INS = 3 # unlike STOP_INS this one can not be ignored 10 | OP_CONT_INS = 4 # This one can optionally be a stop or a continue, depending on ctx.opstop 11 | OP_DONE_INS = 5 # This one can optionally be a stop or a done, depending on ctx.opstop 12 | 13 | class StepRet(Enum): 14 | ERR_STACK_OOB = -3 15 | ERR_IP_OOB = -2 16 | ERR = -1 17 | OK = 0 18 | HOOK_EXEC = 1 19 | HOOK_WRITE = 2 20 | HOOK_READ = 3 21 | HOOK_INS = 4 22 | HOOK_CB = 5 23 | HOOK_ERR = 6 24 | PATH_FORKED = 7 25 | STACK_FORKED = 8 26 | BAD_INS = 9 27 | DREF_SYMBOLIC = 10 28 | DREF_OOB = 11 29 | INTR = 12 30 | 31 | # Matches Unicorn's permissions values 32 | MEM_NONE = 0 33 | MEM_READ = 1 34 | MEM_WRITE = 2 35 | MEM_EXECUTE = 4 36 | MEM_ALL = 7 37 | 38 | # Trace consts 39 | TRACE_API_ADDR = -1 40 | -------------------------------------------------------------------------------- /dobby/dobby_remote.py: -------------------------------------------------------------------------------- 1 | from triton import * 2 | 3 | from .interface import * 4 | from .dobby import * 5 | 6 | 7 | #TODO this will be a interface for any remote providers 8 | # I plan to use this with my hypervisor as a really fast provider 9 | class DobbyRemote(DobbyProvider, DobbyEmu, DobbySym, DobbyRegContext, DobbyMem, DobbySnapshot, DobbyFuzzer): 10 | """ 11 | Dobby provider using Triton DSE 12 | """ 13 | 14 | def __init__(self, ctx, remotename): 15 | super().__init__(ctx, remotename) 16 | 17 | #TODO 18 | raise NotImplementedError(f"TODO") 19 | -------------------------------------------------------------------------------- /dobby/dobby_triton.py: -------------------------------------------------------------------------------- 1 | from triton import * 2 | 3 | from .interface import * 4 | from .dobby import * 5 | 6 | class DobbyTriton(DobbyProvider, DobbyEmu, DobbySym, DobbyRegContext, DobbyMem, DobbySnapshot): 7 | """ 8 | Dobby provider using Triton DSE 9 | """ 10 | 11 | def __init__(self, ctx): 12 | super().__init__(ctx, "Triton") 13 | self.ctx = ctx 14 | self.ctx.triton = self 15 | 16 | # setup Triton API 17 | self.api = TritonContext(ARCH.X86_64) 18 | self.api.enableSymbolicEngine(True) 19 | #TODO add a dobby interface for taint and hook up triton's 20 | 21 | # set modes appropriately 22 | self.api.setMode(MODE.ALIGNED_MEMORY, False) 23 | self.api.setMode(MODE.AST_OPTIMIZATIONS, True) 24 | self.api.setMode(MODE.CONCRETIZE_UNDEFINED_REGISTERS, False) 25 | self.api.setMode(MODE.CONSTANT_FOLDING, True) 26 | # remove this if you want to backslice 27 | self.api.setMode(MODE.ONLY_ON_SYMBOLIZED, True) 28 | self.api.setMode(MODE.ONLY_ON_TAINTED, False) 29 | self.api.setMode(MODE.PC_TRACKING_SYMBOLIC, False) 30 | self.api.setMode(MODE.SYMBOLIZE_INDEX_ROTATION, False) 31 | self.api.setMode(MODE.TAINT_THROUGH_POINTERS, False) 32 | 33 | # register callbacks 34 | self.addrswritten = [] 35 | self.addrsread = [] 36 | self.callbackson = False 37 | try: 38 | self.api.addCallback(CALLBACK.GET_CONCRETE_MEMORY_VALUE, self.getMemCallback) 39 | self.api.addCallback(CALLBACK.SET_CONCRETE_MEMORY_VALUE, self.setMemCallback) 40 | except TypeError: 41 | # older verisons of Triton wanted it flipped? 42 | self.api.addCallback(self.getMemCallback, CALLBACK.GET_CONCRETE_MEMORY_VALUE) 43 | self.api.addCallback(self.setMemCallback, CALLBACK.SET_CONCRETE_MEMORY_VALUE) 44 | 45 | 46 | # save off types for checking later 47 | self.type_MemoryAccess = type(MemoryAccess(0,1)) 48 | self.type_Register = type(self.api.registers.rax) 49 | 50 | self.inscount = 0 51 | self.trace = None 52 | self.defsyms = set() 53 | self.lastins = None 54 | 55 | self.triton_inshooks = { 56 | "smsw": self.smswHook, 57 | } 58 | 59 | self.db2tri = {} 60 | self.tri2db = {} 61 | 62 | for dbreg in x86allreg: 63 | regname = x86reg2name[dbreg] 64 | try: 65 | trireg = getattr(self.api.registers, regname) 66 | self.db2tri[dbreg] = trireg 67 | self.tri2db[trireg] = dbreg 68 | except AttributeError: 69 | pass 70 | 71 | def removed(self): 72 | self.ctx.triton = None 73 | 74 | #EMU INTERFACE 75 | 76 | def getInsCount(self): 77 | return self.inscount 78 | 79 | def insertHook(self, hook): 80 | pass 81 | 82 | def removeHook(self, hook): 83 | pass 84 | 85 | def insertInstructionHook(self, insname, handler): 86 | pass 87 | 88 | def removeInstructionHook(self, insname, handler): 89 | pass 90 | 91 | def startTrace(self): 92 | if self.trace is not None: 93 | raise ValueError("Tried to start trace when there is already a trace being collected") 94 | self.trace = [] 95 | 96 | def getTrace(self): 97 | return self.trace 98 | 99 | def stopTrace(self): 100 | t = self.trace 101 | self.trace = None 102 | return t 103 | 104 | def isTracing(self): 105 | return self.trace is not None 106 | 107 | def traceAPI(self, label): 108 | self.trace.append((TRACE_API_ADDR, label)) 109 | 110 | def step(self, ignorehook, printIns): 111 | ins = self.getNextIns() 112 | if printIns: 113 | if (self.apihooks.start <= ins.getAddress() < self.apihooks.end): 114 | #TODO print API hook label 115 | print("API hook") 116 | else: 117 | print(ins) 118 | return self.stepi(ins, ignorehook) 119 | 120 | def cont(self, ignorehook, printInst): 121 | if ignorehook: 122 | ret = self.step(True, printInst) 123 | if ret != StepRet.OK: 124 | return ret 125 | while True: 126 | ret = self.step(False, printInst) 127 | if ret != StepRet.OK: 128 | return ret 129 | 130 | def contn(self, ignorehook, printIns, n): 131 | if ignorehook: 132 | ret = self.step(True, printInst) 133 | n -= 1 134 | if ret != StepRet.OK: 135 | return ret 136 | while n > 0: 137 | ret = self.step(False, printInst) 138 | if ret != StepRet.OK: 139 | return ret 140 | n -= 1 141 | return ret 142 | 143 | def until(self, addr, ignorehook, printInst): 144 | ret = StepRet.OK 145 | if ignorehook: 146 | ret = self.step(True) 147 | if ret != StepRet.OK: 148 | return ret 149 | 150 | while True: 151 | ripreg = self.api.registers.rip 152 | rip = self.api.getConcreteRegisterValue(ripreg) 153 | if rip == addr: 154 | break 155 | 156 | ret = self.step(False) 157 | if ret != StepRet.OK: 158 | break 159 | 160 | return ret 161 | 162 | def next(self, ignorehook, printInst): 163 | # this is just an until next ins if cur ins is call 164 | i = self.getNextIns() 165 | if i.getDisassembly().startswith("call"): 166 | na = i.getNextAddress() 167 | return self.until(na, ignorehook) 168 | 169 | return self.step(ignorehook) 170 | 171 | # EMU HELPERS 172 | 173 | def getNextIns(self): 174 | # rip should never be symbolic when this function is called 175 | # Is this check worth it? Slows us down, when we do it after each step anyways 176 | if self.api.isRegisterSymbolized(self.api.registers.rip): 177 | #TODO use hasUnsetSym to see if the symbols are already concretized 178 | # if so, evalReg rip 179 | raise ValueError("Tried to get instruction with symbolized rip") 180 | rip = self.api.getConcreteRegisterValue(self.api.registers.rip) 181 | insbytes = self.api.getConcreteMemoryAreaValue(rip, 15) 182 | inst = Instruction(rip, insbytes) 183 | self.api.disassembly(inst) 184 | return inst 185 | 186 | def setMemCallback(self, trictx, mem, val): 187 | if not self.callbackson: 188 | return 189 | 190 | addr = mem.getAddress() 191 | size = mem.getSize() 192 | 193 | self.addrswritten.append((mem.getAddress(), mem.getSize())) 194 | 195 | def getMemCallback(self, trictx, mem): 196 | if not self.callbackson: 197 | return 198 | 199 | addr = mem.getAddress() 200 | size = mem.getSize() 201 | 202 | self.addrsread.append((mem.getAddress(), mem.getSize())) 203 | 204 | def stepi(self, ins, ignorehook=False): 205 | # do pre-step stuff 206 | self.addrswritten = [] 207 | self.addrsread = [] 208 | self.lastins = ins 209 | 210 | # rip and rsp should always be a concrete value at the beginning of this function 211 | rspreg = self.api.registers.rsp 212 | ripreg = self.api.registers.rip 213 | rsp = self.api.getConcreteRegisterValue(rspreg) 214 | rip = self.api.getConcreteRegisterValue(ripreg) 215 | 216 | #TODO add exception raising after possible first_chance stop? 217 | 218 | # enforce page permissions 219 | if not self.ctx.inBounds(rip, ins.getSize(), MEM_EXECUTE): 220 | return StepRet.ERR_IP_OOB 221 | 222 | # check if rip is at a hooked execution location 223 | for eh in self.hooks[0]: #TODO be able to search quicker here 224 | if eh.start <= rip < eh.end: 225 | # hooked 226 | #TODO multiple hooks at the same location? 227 | stop, sret = self.ctx.handle_hook(eh, rip, 1, MEM_EXECUTE, ignorehook) 228 | if not stop: 229 | break 230 | return sret 231 | 232 | # check if we are about to do a memory deref of: 233 | # a symbolic value 234 | # a hooked location (if not ignorehook) 235 | # an out of bounds location 236 | # we can't know beforehand if it is a write or not, so verify after the instruction 237 | #TODO automatically detect symbolic expressions that are evaluable based on variables we have set 238 | for o in ins.getOperands(): 239 | if isinstance(o, self.type_MemoryAccess): 240 | lea = ins.getDisassembly().find("lea") 241 | nop = ins.getDisassembly().find("nop") 242 | if lea != -1 or nop != -1: 243 | # get that fake crap out of here 244 | continue 245 | # check base register isn't symbolic 246 | basereg = o.getBaseRegister() 247 | baseregid = basereg.getId() 248 | if baseregid != 0 and self.api.isRegisterSymbolized(basereg): 249 | #TODO check if the register can be concretized here 250 | return StepRet.DREF_SYMBOLIC 251 | 252 | # check index register isn't symbolic 253 | indexreg = o.getIndexRegister() 254 | indexregid = indexreg.getId() 255 | if indexregid != 0 and self.api.isRegisterSymbolized(indexreg): 256 | #TODO check if the register can be concretized here 257 | return StepRet.DREF_SYMBOLIC 258 | 259 | # check segment isn't symbolic 260 | segreg = o.getSegmentRegister() 261 | segregis = segreg.getId() 262 | if segreg.getId() != 0 and self.api.isRegisterSymbolized(segreg): 263 | #TODO check if the register can be concretized here 264 | return StepRet.DREF_SYMBOLIC 265 | 266 | #TODO check ins.isSymbolized? 267 | 268 | self.inscount += 1 269 | 270 | insaddr = ins.getAddress() 271 | if self.trace is not None: 272 | if len(self.trace) == 0 or self.trace[-1][0] != insaddr: 273 | dis = None 274 | drefs = None 275 | inssz = None 276 | if self.ctx.trace_disass: 277 | dis = ins.getDisassembly() 278 | if self.ctx.trace_dref: 279 | drefs = [[],[]] 280 | if self.ctx.trace_inssz: 281 | inssz = ins.getSize() 282 | 283 | item = (insaddr, dis, drefs, inssz) 284 | self.trace.append(item) 285 | 286 | # every X thousand instructions, check for inf looping ? 287 | #if (self.inscount & 0xffff) == 0: 288 | #TODO 289 | # check for current rip addr in past X instructions 290 | # for each of those, backward, check if same loop reaches start of lopp 291 | # enforce some minimum loop count required? Over 9000? 292 | 293 | # check inshooks 294 | ins_name = ins.getDisassembly().split()[0] 295 | ihret = None 296 | if ins_name in self.triton_inshooks: 297 | ihret = self.triton_inshooks[ins_name](self.ctx, insaddr, ins, self) 298 | elif ins_name in self.inshooks: 299 | ihret = self.inshooks[ins_name](self.ctx, insaddr, self) 300 | 301 | if ihret is not None: 302 | if ihret == HookRet.OP_CONT_INS: 303 | if self.opstop: 304 | ihret = HookRet.STOP_INS 305 | else: 306 | ihret = HookRet.CONT_INS 307 | if ihret == HookRet.OP_DONE_INS: 308 | if self.opstop: 309 | ihret = HookRet.STOP_INS 310 | else: 311 | ihret = HookRet.DONE_INS 312 | 313 | if ihret == HookRet.ERR: 314 | return StepRet.HOOK_ERR 315 | elif ihret == HookRet.CONT_INS: 316 | pass 317 | elif ihret == HookRet.DONE_INS: 318 | return StepRet.OK 319 | elif ihret == HookRet.STOP_INS and not ignorehook: 320 | return StepRet.HOOK_INS 321 | elif ihret == HookRet.FORCE_STOP_INS: 322 | return StepRet.HOOK_INS 323 | else: 324 | raise ValueError("Unknown return from instruction hook") 325 | 326 | self.callbackson = True 327 | 328 | # actually do a step 329 | #TODO how do we detect exceptions like divide by zero? 330 | # triton just lets divide by zero through 331 | if not self.api.processing(ins): 332 | return StepRet.BAD_INS 333 | 334 | self.callbackson = False 335 | 336 | if self.trace is not None and self.ctx.trace_dref: 337 | self.trace[-1][2][0] += self.addrsread 338 | self.trace[-1][2][1] += self.addrswritten 339 | 340 | #TODO we are doing bounds checks after they already happen 341 | # this is a problem if we want to be able to handle exceptions properly 342 | 343 | # Read and Write hooks 344 | for addr, size in self.addrsread: 345 | # check access is in bounds 346 | if not self.ctx.inBounds(addr, size, MEM_READ): 347 | print("DEBUG: oob read at", hex(addr)) 348 | return StepRet.DREF_OOB 349 | 350 | # check if access is hooked 351 | for rh in self.hooks[1]: 352 | if (rh.start - size) < addr < rh.end: 353 | # hooked 354 | #TODO multiple hooks at the same location? 355 | stop, sret = self.ctx.handle_hook(rh, addr, size, MEM_READ, ignorehook) 356 | if not stop: 357 | break 358 | return sret 359 | 360 | for addr, size in self.addrswritten: 361 | # check access is in bounds 362 | if not self.ctx.inBounds(addr, size, MEM_READ): 363 | print("DEBUG: oob write at", hex(addr)) 364 | return StepRet.DREF_OOB 365 | 366 | # check if access is hooked 367 | for wh in self.hooks[2]: 368 | if (wh.start - size) < addr < wh.end: 369 | # hooked 370 | #TODO multiple hooks at the same location? 371 | stop, sret = self.ctx.handle_hook(rh, addr, size, MEM_WRITE, ignorehook) 372 | if not stop: 373 | break 374 | return sret 375 | 376 | # check if we forked rip 377 | if self.api.isRegisterSymbolized(ripreg): 378 | return StepRet.PATH_FORKED 379 | # find what symbols it depends on 380 | # and use setSymbol to give a concrete value for the var 381 | # then use evalReg(rip) to evaluate rip 382 | 383 | # check if we forked rsp 384 | if self.api.isRegisterSymbolized(ripreg): 385 | return StepRet.STACK_FORKED 386 | 387 | return StepRet.OK 388 | 389 | @staticmethod 390 | def smswHook(ctx, addr, ins, trictx): 391 | cr0val = ctx.getRegVal(DB_X86_R_CR0) 392 | 393 | newrip = ctx.getRegVal(DB_X86_R_RIP) + ins.getSize() 394 | ctx.setRegVal(DB_X86_R_RIP, newrip) 395 | 396 | op = ins.getOperands()[0] 397 | if isinstance(op, trictx.type_Register): 398 | trictx.api.setConcreteRegisterValue(op, cr0val) 399 | else: 400 | raise NotImplementedError("TODO") 401 | 402 | return HookRet.DONE_INS 403 | 404 | # SYMBOLIC INTERFACE 405 | 406 | def isSymbolizedRegister(self, reg): 407 | trireg = self.db2tri[reg] 408 | return self.api.isRegisterSymbolized(trireg) 409 | 410 | def isSymbolizedMemory(self, addr, size): 411 | #TODO do smart sizing to not do a ton of memory access things here 412 | for i in range(size): 413 | if self.api.isMemorySymbolized(addr+i): 414 | return True 415 | return False 416 | 417 | def symbolizeRegister(self, reg, name): 418 | trireg = self.db2tri[reg] 419 | return self.api.symbolizeRegister(trireg, name) 420 | 421 | def symbolizeMemory(self, addr, size, name): 422 | #TODO be smart about this with as large of aligned memory access as we can get away with 423 | for i in range(size): 424 | self.api.symbolizeMemory(MemoryAccess(addr + i, 1), name +"+"+hex(i)[2:]) 425 | 426 | def getSymbol(self, name): 427 | # use name to find symbol with that alias 428 | syms = self.api.getSymbolicVariables() 429 | for s in syms: 430 | if symname == syms[s].getAlias(): 431 | return s 432 | raise KeyError(f"Unknown symbol {symname}") 433 | 434 | def setSymbolVal(self, sym, value, overwrite=False): 435 | # use setConcreteVariableValue to set the value 436 | # store the variable id in our list of set variables 437 | if sym in self.defsyms: 438 | if overwrite: 439 | print("Warning overwriting previously concretized symbol") 440 | else: 441 | raise KeyError(f"Attempted to concretize symbol {sym} which has already been set before") 442 | 443 | self.defsyms.add(sym) 444 | 445 | svar = self.api.getSymbolicVariable(sym) 446 | 447 | self.api.setConcreteVariableValue(svar, value) 448 | 449 | def getRegisterAst(self, reg): 450 | trireg = self.db2tri[reg] 451 | return self.api.getRegisterAst(trireg) 452 | 453 | def getMemoryAst(self, addr, size): 454 | #TODO do a smart memory access. This will fail on unaligned size/addr 455 | return self.api.getMemoryAst(MemoryAccess(addr, size)) 456 | 457 | def printAst(self, ast): 458 | ast = self.api.simplify(ast, True) 459 | print(self.taboutast(str(ast))) 460 | 461 | def getUnsetSym(self, ast, single=True, allSym=False, followRef=True): 462 | # walk the ast and see if any of the symbols are not in our list 463 | # depth first search, stop when we hit the first one 464 | symlist = set() 465 | path = [ast] 466 | while len(path) > 0: 467 | cur, path = path[-1], path[:-1] 468 | nt = cur.getType() 469 | if nt == AST_NODE.VARIABLE: 470 | # Found one! 471 | symvar = cur.getSymbolicVariable() 472 | sym = symvar.getId() 473 | if not allSym and sym in self.defsyms: 474 | continue 475 | 476 | if single: 477 | return sym 478 | else: 479 | symlist.add(symvar.getId()) 480 | elif nt == AST_NODE.REFERENCE: 481 | # get symexp and continue 482 | if followRef: 483 | path.append(cur.getSymbolicExpression().getAst()) 484 | else: 485 | path += cur.getChildren() 486 | 487 | 488 | if single: 489 | return None 490 | else: 491 | return list(symlist) 492 | 493 | def getUnsetCount(self): 494 | varcount = {} 495 | ses = self.api.getSymbolicExpressions() 496 | for k in ses: 497 | ast = ses[k].getAst() 498 | 499 | # note, this will only count each variable in this se once 500 | unsetvars = self.getUnsetSym(ast, single=False, followRef=False) 501 | for uv in unsetvars: 502 | if uv not in varcount: 503 | varcount[uv] = 1 504 | else: 505 | varcount[uv] += 1 506 | 507 | return varcount 508 | 509 | def evalReg(self, reg, checkUnset=True): 510 | trireg = self.db2tri[reg] 511 | # Use after using setSymbol 512 | if self.api.isRegisterSymbolized(trireg): 513 | if checkUnset: 514 | ast = self.api.getRegisterAst(trireg) 515 | unsetsym = self.getUnsetSym(ast, True, False) 516 | if unsetsym is not None: 517 | print(f"Unable to eval register, relies on unset symbol {unsetsym}") 518 | return False 519 | 520 | val = self.api.getSymbolicRegisterValue(trireg) 521 | self.api.setConcreteRegisterValue(trireg, val) 522 | return True 523 | else: 524 | print("Unable to eval register, is not symbolized") 525 | return False 526 | 527 | def evalMem(self, addr, size, checkUnset=True): 528 | # Use after using setSymbol 529 | for i in range(size): 530 | if checkUnset: 531 | # doing this for every byte seems like a lot 532 | # should probably use bigger getMemoryAst, but that has to be aligned 533 | ast = self.api.getMemoryAst(MemoryAccess(addr+i, 1)) 534 | unsetsym = self.getUnsetSym(ast, True, False) 535 | if unsetsym is not None: 536 | print(f"Unable to eval memory at {hex(addr+i)[2:]}, relies on unset symbol {unsetsym}") 537 | return False 538 | mem = self.api.getSymbolicMemoryValue(MemoryAccess(addr+i, 1)) 539 | self.api.setConcreteMemoryValue(addr+i, mem) 540 | return True 541 | 542 | # SYMBOLIC HELPERS 543 | 544 | def taboutast(self, h, ta=2, hexify=True, maxline=90): 545 | if hexify: 546 | h = ' '.join(['bv'+hex(int(x[2:])) if x.startswith('bv') else x for x in h.split()]) 547 | 548 | out = "" 549 | tc = ' ' 550 | tb = tc * ta 551 | tl = 0 552 | i = 0 553 | while i < len(h): 554 | assert tl >= 0 555 | c = h[i] 556 | if c == '(': 557 | tl += 1 558 | assert i+1 < len(h) 559 | cn = h[i+1] 560 | end = -1 561 | # first check if the () of this one will fit in this line 562 | 563 | depth = 0 564 | ii = i+1 565 | didline = False 566 | while True: 567 | cl = h.find(')', ii) 568 | op = h.find('(', ii) 569 | 570 | assert cl != -1 571 | 572 | if (cl - i) > maxline: 573 | break 574 | 575 | if op == -1 or cl < op: 576 | if depth <= 0: 577 | # at end 578 | end = cl 579 | break 580 | else: 581 | depth -= 1 582 | ii = cl+1 583 | else: 584 | depth += 1 585 | ii = op+1 586 | 587 | if end != -1: 588 | tl -= 1 589 | 590 | # otherwise if it starts with a (_ grab that as the op 591 | elif cn == '(': 592 | assert h[i+2] == '_' 593 | # print all of this (group) before newline 594 | end = h.find(')', i) 595 | assert end != -1 596 | check = h.find('(', i+2) 597 | assert check == -1 or check > end 598 | # otherwise grab the op 599 | else: 600 | end = h.find(' ', i) 601 | assert end != -1 602 | end = end-1 603 | 604 | out += h[i:end+1] 605 | i = end 606 | 607 | out += '\n' + (tb * tl) 608 | while (i+1) < len(h) and h[i+1] == ' ': 609 | i += 1 610 | elif c == ')': 611 | tl -= 1 612 | if len(out) > 0 and out[-1] != tc: 613 | out += '\n' + (tb * tl) 614 | else: 615 | # they tabbed us too much 616 | out = out[:0 - len(tb)] 617 | out += c 618 | out += '\n' + (tb * tl) 619 | while (i+1) < len(h) and h[i+1] == ' ': 620 | i += 1 621 | elif c == ' ': 622 | out += '\n' + (tb * tl) 623 | while (i+1) < len(h) and h[i+1] == ' ': 624 | i += 1 625 | else: 626 | out += c 627 | 628 | i += 1 629 | 630 | return out 631 | 632 | # REG INTERFACE 633 | 634 | def getRegVal(self, reg): 635 | trireg = self.db2tri[reg] 636 | return self.api.getConcreteRegisterValue(trireg) 637 | 638 | def setRegVal(self, reg, val): 639 | trireg = self.db2tri[reg] 640 | self.api.setConcreteRegisterValue(trireg, val) 641 | 642 | def getAllRegisters(self): 643 | return list(self.db2tri.keys()) 644 | 645 | # REG HELPERS 646 | 647 | def db2Tri(self, reg): 648 | return self.db2tri[reg] 649 | 650 | def tri2Db(self, trireg): 651 | return self.tri2db[trireg] 652 | 653 | # MEM INTERFACE 654 | 655 | def disass(self, addr=-1, count=16): 656 | if addr == -1: 657 | addr = self.getConcreteRegisterValue(self.api.registers.rip) 658 | lines = [] 659 | for i in range(count): 660 | insbytes = self.api.getConcreteMemoryAreaValue(addr, 15) 661 | inst = Instruction(addr, insbytes) 662 | self.api.disassembly(inst) 663 | lines.append((addr, inst.getDisassembly())) 664 | addr = inst.getNextAddress() 665 | return lines 666 | 667 | def getInsLen(self, addr=-1): 668 | if addr == -1: 669 | addr = self.getConcreteRegisterValue(self.api.registers.rip) 670 | insbytes = self.api.getConcreteMemoryAreaValue(addr, 15) 671 | inst = Instruction(addr, insbytes) 672 | self.api.disassembly(inst) 673 | return inst.getNextAddress() - addr 674 | 675 | def getMemVal(self, addr, amt): 676 | return self.api.getConcreteMemoryAreaValue(addr, amt) 677 | 678 | def setMemVal(self, addr, val): 679 | self.api.setConcreteMemoryAreaValue(addr, val) 680 | 681 | def updateBounds(self, start, end, permissions): 682 | pass # not needed in this provider 683 | 684 | # SNAPSHOT INTERFACE 685 | 686 | def takeSnapshot(self, snapshot): 687 | pass 688 | 689 | def restoreSnapshot(self, snapshot, trackDiff=False): 690 | pass 691 | -------------------------------------------------------------------------------- /dobby/dobby_types.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import zlib 3 | 4 | class Hook: 5 | """ 6 | Hook handlers should have the signature (hook, addr, sz, op, provider) -> None 7 | """ 8 | def __init__(self, start, end, htype, label="", handler=None): 9 | self.start = start 10 | self.end = end 11 | self.label = label 12 | self.handler = handler 13 | self.htype = htype 14 | self.isApiHook=False 15 | 16 | def __repr__(self): 17 | return f"Hook @ {hex(self.start)}:\"{self.label}\"{' (no handler)' if self.handler is None else ''}" 18 | 19 | class Annotation: 20 | def __init__(self, start, end, mtype="UNK", label=""): 21 | self.start = start 22 | self.end = end 23 | self.mtype = mtype 24 | self.label = label 25 | 26 | def __repr__(self): 27 | return f"{hex(self.start)}-{hex(self.end)}=>\"{self.mtype}:{self.label}\"" 28 | 29 | class Snapshot: 30 | COMP_NONE = 0 31 | COMP_ZLIB = 1 32 | 33 | def __init__(self, name): 34 | self.name = name 35 | self.setup = False 36 | self.extradata = None 37 | self.hasemu = False 38 | self.hassym = False 39 | self.hasreg = False 40 | self.hasmem = False 41 | 42 | def take(self, ctx): 43 | if ctx.active is None: 44 | raise RuntimeError("Tried to take snapshot with no active provider") 45 | 46 | # interface independant stuff 47 | 48 | self.priv = ctx.active.priv 49 | #TODO self.modules = copy.deepcopy(ctx.active.modules) 50 | self.nextalloc = ctx.active.nextalloc 51 | self.pagetablebase = ctx.active.pagetablebase 52 | self.hooks = copy.deepcopy(ctx.active.hooks) 53 | self.inshooks = copy.deepcopy(ctx.active.inshooks) 54 | self.ann = copy.deepcopy(ctx.active.ann) 55 | self.bounds = copy.deepcopy(ctx.active.bounds) 56 | self.apihooks = copy.deepcopy(ctx.active.apihooks) 57 | self.stackann = copy.deepcopy(ctx.active.stackann) 58 | self.globstate = copy.deepcopy(ctx.active.globstate) 59 | 60 | if ctx.isemu: 61 | self.hasemu = True 62 | # nothing else to save here 63 | 64 | if ctx.issym: 65 | print("WARNING, saving symbolic state is not yet implemented") 66 | 67 | if ctx.isreg: 68 | self.hasreg = True 69 | # save off register state 70 | self.rstate = {} 71 | for r in ctx.active.getAllRegisters(): 72 | rv = ctx.active.getRegVal(r) 73 | self.rstate[r] = rv 74 | 75 | if ctx.ismem: 76 | self.hasmem = True 77 | # save off memory state 78 | self.mem = [] 79 | for start, end in ctx.getBoundsRegions(False): 80 | sz = end - start 81 | val = ctx.getMemVal(start, sz, allowsymb=True) 82 | cval = zlib.compress(val, 6) 83 | self.mem.append((start, sz, self.COMP_ZLIB, cval)) 84 | 85 | self.setup = True 86 | 87 | def restore(self, ctx): 88 | if ctx.active is None: 89 | raise RuntimeError("Tried to take snapshot with no active provider") 90 | 91 | if not self.setup: 92 | raise RuntimeError("Tried to restore a snapshot that isn't set up") 93 | 94 | # interface independant stuff 95 | 96 | ctx.active.priv = self.priv 97 | #TODO ctx.active.modules = copy.deepcopy(self.modules) 98 | ctx.active.nextalloc = self.nextalloc 99 | ctx.active.pagetablebase = self.pagetablebase 100 | ctx.active.hooks = copy.deepcopy(self.hooks) 101 | ctx.active.inshooks = copy.deepcopy(self.inshooks) 102 | ctx.active.ann = copy.deepcopy(self.ann) 103 | ctx.active.bounds = copy.deepcopy(self.bounds) 104 | ctx.active.apihooks = copy.deepcopy(self.apihooks) 105 | ctx.active.stackann = copy.deepcopy(self.stackann) 106 | ctx.active.globstate = copy.deepcopy(self.globstate) 107 | 108 | if ctx.isemu: 109 | if not self.hasemu: 110 | print("Warning: loading state from a provider without Emulation") 111 | else: 112 | # nothing to load here 113 | pass 114 | 115 | if ctx.issym: 116 | if not self.hassym: 117 | print("Warning: loading state from a provider without Symbolism") 118 | else: 119 | print("Warning: symbolic loading not implemented") 120 | #TODO 121 | 122 | if ctx.isreg: 123 | if not self.hasreg: 124 | print("Warning: loading state from a provider without Emulation") 125 | else: 126 | # restore register state 127 | allreg = ctx.active.getAllRegisters() 128 | for r,rv in self.rstate.items(): 129 | if r not in allreg: 130 | if rv != 0: 131 | print(f"Warning: Did not load unsupported register {ctx.getRegName(r)}!") 132 | else: 133 | ctx.active.setRegVal(r, rv) 134 | 135 | if ctx.ismem: 136 | if not self.hasmem: 137 | print("Warning: loading state from a provider without Emulation") 138 | else: 139 | #TODO Copy On Write memory mapped files? 140 | # update bounds 141 | #TODO do this in groups, not once per page 142 | for start, end, perm in ctx.getBoundsRegions(True): 143 | ctx.active.updateBounds(start, end, perm) 144 | 145 | # update memory contents 146 | for m in self.mem: 147 | addr, sz, comptype, cval = m 148 | 149 | val = None 150 | if comptype == self.COMP_ZLIB: 151 | val = zlib.decompress(cval, bufsize=sz) 152 | elif comptype == self.COMP_NONE: 153 | val = cval 154 | else: 155 | raise TypeError("Unknown compression type") 156 | 157 | ctx.setMemVal(addr, val) 158 | 159 | def __repr__(self): 160 | return f"SaveState({self.name})" 161 | -------------------------------------------------------------------------------- /dobby/dobby_unicorn.py: -------------------------------------------------------------------------------- 1 | from unicorn import * 2 | from unicorn.x86_const import * 3 | 4 | from .interface import * 5 | from .dobby import * 6 | 7 | class DobbyUnicorn(DobbyProvider, DobbyEmu, DobbyRegContext, DobbyMem, DobbySnapshot): 8 | """ 9 | Dobby provider using Triton DSE 10 | """ 11 | 12 | def __init__(self, ctx): 13 | self.ctx = ctx 14 | super().__init__(ctx, "Unicorn") 15 | 16 | self.emu = Uc(UC_ARCH_X86, UC_MODE_64) 17 | self.ctx.unicorn = self 18 | self.trace = None 19 | self.inscount = 0 20 | self.stepret = StepRet.OK 21 | self.intrnum = -1 22 | self.ignorehookaddr = -1 23 | self.trystop = False 24 | self.regtrans = {} 25 | self.lasterr = None 26 | self.printIns = False 27 | 28 | # hook every instruction 29 | self.emu.hook_add(UC_HOOK_CODE, self.insHook, None, 0, 0xffffffffffffffff) 30 | # hook read/write 31 | self.emu.hook_add(UC_HOOK_MEM_READ | UC_HOOK_MEM_WRITE, self.rwHook, None) 32 | # hook invalid stuff 33 | self.emu.hook_add(UC_HOOK_MEM_INVALID, self.invalMemHook, None) 34 | 35 | #older versions of unicorn don't have this 36 | try: 37 | self.emu.hook_add(UC_HOOK_INSN_INVALID, self.invalInsHook, None) 38 | except: 39 | print("Unable to install emulation invalid instruction hook") 40 | 41 | self.emu.hook_add(UC_HOOK_INTR, self.intrHook, None) 42 | 43 | self.db2uc = {} 44 | self.uc2db = {} 45 | 46 | for dbreg in x86allreg: 47 | regname = x86reg2name[dbreg] 48 | ucregname = "UC_X86_REG_" + regname.upper() 49 | #TODO support Floating point and tr stuff 50 | if regname.startswith("fp") or regname.endswith("tr") or regname in ["msr"]: 51 | continue 52 | try: 53 | ucreg = getattr(unicorn.x86_const, ucregname) 54 | self.db2uc[dbreg] = ucreg 55 | self.uc2db[ucreg] = dbreg 56 | except AttributeError: 57 | pass 58 | 59 | def removed(self): 60 | self.ctx.unicorn = None 61 | 62 | # EMU INTERFACE 63 | 64 | def getInsCount(self): 65 | return self.inscount 66 | 67 | def insertHook(self, hook): 68 | if hook.htype == MEM_EXECUTE: 69 | #TODO 70 | # write in breakpoint instructions for execution hooks 71 | pass 72 | 73 | def removeHook(self, hook): 74 | #TODO handle removing any saved breakpoint instructions 75 | pass 76 | 77 | def insertInstructionHook(self, insname, handler): 78 | pass 79 | 80 | def removeInstructionHook(self, insname, handler): 81 | pass 82 | 83 | def startTrace(self): 84 | if self.trace is not None: 85 | raise ValueError("Tried to start trace when there is already a trace being collected") 86 | self.trace = [] 87 | 88 | def getTrace(self): 89 | return self.trace 90 | 91 | def stopTrace(self): 92 | t = self.trace 93 | self.trace = None 94 | return t 95 | 96 | def isTracing(self): 97 | return self.trace is not None 98 | 99 | def traceAPI(self, label): 100 | self.trace.append((TRACE_API_ADDR, label)) 101 | 102 | def step(self, ignorehook, printIns): 103 | self.printIns = printIns 104 | self.stepret = StepRet.OK 105 | self.lasterr = None 106 | self.trystop = False 107 | 108 | addr = self.emu.reg_read(UC_X86_REG_RIP) 109 | 110 | self.ignorehookaddr = -1 111 | if ignorehook: 112 | self.ignorehookaddr = addr 113 | 114 | try: 115 | self.emu.emu_start(addr, 0xffffffffffffffff, 0, 1) 116 | except UcError as e: 117 | self.lasterr = e 118 | 119 | # we go over because the ins hook is called even when we don't execute the final instruction 120 | self.inscount -= 1 121 | 122 | return self.stepret 123 | 124 | def cont(self, ignorehook, printIns, n=0): 125 | self.printIns = printIns 126 | self.stepret = StepRet.OK 127 | addr = self.emu.reg_read(UC_X86_REG_RIP) 128 | self.lasterr = None 129 | self.trystop = False 130 | 131 | self.ignorehookaddr = -1 132 | if ignorehook: 133 | self.ignorehookaddr = addr 134 | 135 | try: 136 | self.emu.emu_start(addr, 0xffffffffffffffff, 0, n) 137 | except UcError as e: 138 | self.lasterr = e 139 | 140 | # we go over because the ins hook is called even when we don't execute the final instruction 141 | self.inscount -= 1 142 | 143 | return self.stepret 144 | 145 | def contn(self, ignorehook, printIns, n): 146 | return self.cont(ignorehook, printInt, n) 147 | 148 | def until(self, addr, ignorehook, printIns): 149 | self.printIns = printIns 150 | self.stepret = StepRet.OK 151 | self.lasterr = None 152 | self.trystop = False 153 | 154 | addr = self.emu.reg_read(UC_X86_REG_RIP) 155 | 156 | self.ignorehookaddr = -1 157 | if ignorehook: 158 | self.ignorehookaddr = addr 159 | 160 | try: 161 | self.emu.emu_start(addr, until, 0, 0) 162 | except UcError as e: 163 | self.lasterr = e 164 | 165 | # we go over because the ins hook is called even when we don't execute the final instruction 166 | self.inscount -= 1 167 | 168 | return self.stepret 169 | 170 | def next(self, ignorehook, printIns): 171 | raise NotImplementedError(f"TODO") 172 | 173 | # EMU HELPERS 174 | 175 | def insHook(self, emu, addr, sz, user_data): 176 | if self.printIns: 177 | print('@', hex(addr)) 178 | 179 | if self.trystop: 180 | # insHook just happens on the instruction that doesn't happen sometimes 181 | #TODO check to make sure this doesn't happen twice in a row? 182 | #print("In instruction hook when we wanted to stop!") 183 | emu.emu_stop() 184 | return 185 | 186 | # this hook could happen even if we are not about to execute this instruction 187 | # it happens before we are stopped 188 | ignorehook = (addr == self.ignorehookaddr) 189 | try: 190 | for eh in self.hooks[0]: 191 | if eh.start <= addr < eh.end: 192 | # hooked 193 | stop, sret = self.ctx.handle_hook(eh, addr, 1, MEM_EXECUTE, ignorehook) 194 | if not stop: 195 | break 196 | if sret == StepRet.OK: 197 | return 198 | self.stepret = sret 199 | emu.emu_stop() 200 | self.trystop = True 201 | return 202 | except Exception as e: 203 | print("Stopping emulation, exception occured during insHook:", e) 204 | self.stepret = StepRet.ERR 205 | emu.emu_stop() 206 | self.trystop = True 207 | raise e 208 | 209 | if self.trace is not None: 210 | if len(self.trace) == 0 or self.trace[-1][0] != addr: 211 | dis = None 212 | dref = None 213 | inssz = None 214 | if self.ctx.trace_dref: 215 | dref = [[],[]] 216 | #if self.ctx.trace_disass: 217 | #TODO 218 | if self.ctx.trace_inssz: 219 | inssz = sz 220 | item = (addr, dis, dref, inssz) 221 | self.trace.append(item) 222 | 223 | #TODO this will go up too much because we get called to much, can we fix that? 224 | self.inscount += 1 225 | 226 | #TODO do inshooks here 227 | 228 | def rwHook(self, emu, access, addr, sz, val, user_data): 229 | if self.trystop: 230 | #TODO better way to stop? 231 | print("In memory hook when we wanted to stop!") 232 | emu.emu_stop() 233 | 234 | if not self.trystop and self.ctx.trace_dref and self.trace is not None: 235 | if access == UC_MEM_WRITE: 236 | self.trace[-1][2][1].append((addr, sz)) 237 | else: 238 | self.trace[-1][2][0].append((addr, sz)) 239 | 240 | #TODO why can't I read registers from here? 241 | try: 242 | # handle read / write hooks 243 | if access == UC_MEM_WRITE: 244 | for wh in self.hooks[2]: 245 | if wh.start <= addr < wh.end: 246 | # hooked 247 | #TODO should ignorehook if on that address? 248 | stop, sret = self.ctx.handle_hook(wh, addr, sz, MEM_WRITE, False) 249 | if not stop: 250 | break 251 | self.stepret = sret 252 | emu.emu_stop() 253 | self.trystop = True 254 | return 255 | else: 256 | for rh in self.hooks[1]: 257 | if rh.start <= addr < rh.end: 258 | # hooked 259 | #TODO should ignorehook if on that address? 260 | stop, sret = self.ctx.handle_hook(rh, addr, sz, MEM_READ, False) 261 | if not stop: 262 | break 263 | self.stepret = sret 264 | emu.emu_stop() 265 | self.trystop = True 266 | return 267 | except Exception as e: 268 | print("Stopping emulation, exception occured during rwHook:", e) 269 | self.stepret = StepRet.ERR 270 | emu.emu_stop() 271 | self.trystop = True 272 | raise e 273 | 274 | def invalMemHook(self, emu, access, addr, sz, val, user_data): 275 | if self.trystop: 276 | print("In invalid memory hook when we wanted to stop!") 277 | emu.emu_stop() 278 | ret = False 279 | #TODO why can't I read registers from here? 280 | try: 281 | # handle read / write hooks 282 | if access == UC_MEM_WRITE: 283 | for wh in self.hooks[2]: 284 | if wh.start <= addr < wh.end: 285 | # hooked 286 | #TODO return True/False? 287 | #TODO should ignorehook if on that address? 288 | stop, sret = self.ctx.handle_hook(wh, addr, sz, MEM_WRITE, False) 289 | if not stop: 290 | break 291 | self.stepret = sret 292 | emu.emu_stop() 293 | self.trystop = True 294 | return ret 295 | else: 296 | for rh in self.hooks[1]: 297 | if rh.start <= addr < rh.end: 298 | # hooked 299 | #TODO return True/False? 300 | #TODO should ignorehook if on that address? 301 | stop, sret = self.ctx.handle_hook(rh, addr, sz, MEM_READ, False) 302 | if not stop: 303 | break 304 | self.stepret = sret 305 | emu.emu_stop() 306 | self.trystop = True 307 | return ret 308 | # if no hooks handle it 309 | self.stepret = StepRet.DREF_OOB 310 | except Exception as e: 311 | print("Stopping emulation, exception occured during invalMemHook:", e) 312 | self.stepret = StepRet.ERR 313 | ret = False 314 | return ret 315 | 316 | def invalInsHook(self, emu, user_data): 317 | print(f"DEBUG Invalid ins") 318 | return False 319 | 320 | def intrHook(self, emu, intno, user_data): 321 | self.stepret = StepRet.INTR 322 | self.intrnum = intno 323 | emu.emu_stop() 324 | self.trystop = True 325 | 326 | # REG INTERFACE 327 | 328 | def getRegVal(self, reg): 329 | ucreg = self.db2uc[reg] 330 | return self.emu.reg_read(ucreg) 331 | 332 | def setRegVal(self, reg, val): 333 | ucreg = self.db2uc[reg] 334 | self.emu.reg_write(ucreg, val) 335 | 336 | def getAllRegisters(self): 337 | return list(self.db2uc.keys()) 338 | 339 | # MEM INTERFACE 340 | 341 | def disass(self, addr=-1, count=16): 342 | #TODO use capstone I guess 343 | raise NotImplementedError(f"Unicorn disassembly not implemented yet") 344 | 345 | def getInsLen(self, addr=-1): 346 | raise NotImplementedError(f"Unicorn length disassembly not implemented yet") 347 | 348 | def getMemVal(self, addr, amt): 349 | return self.emu.mem_read(addr, amt) 350 | 351 | def setMemVal(self, addr, val): 352 | self.emu.mem_write(addr, bytes(val)) 353 | 354 | def updateBounds(self, start, end, permissions): 355 | #TODO update page table 356 | try: 357 | self.emu.mem_map(start, end - start, permissions) 358 | except UcError as e: 359 | # probably already mapped, find pages to map 360 | #TODO 361 | raise e 362 | 363 | # MEM HELPER 364 | 365 | def printUcMap(self): 366 | reg_i = self.emu.mem_regions() 367 | for r_beg, r_end, _ in reg_i: 368 | print(hex(r_beg) +'-'+ hex(r_end)) 369 | 370 | # SNAPSHOT INTERFACE 371 | 372 | def takeSnapshot(self, snapshot): 373 | pass 374 | 375 | def restoreSnapshot(self, snapshot, trackDiff=False): 376 | pass 377 | -------------------------------------------------------------------------------- /dobby/interface.py: -------------------------------------------------------------------------------- 1 | from .dobby_const import * 2 | from .dobby_types import * 3 | 4 | """ 5 | This is the class that all providers must inherit from 6 | """ 7 | class DobbyProvider: 8 | """ 9 | This should be the first class inherited from, with the interfaces following in order after 10 | """ 11 | def __init__(self, ctx, name, apihookarea=0xffff414100000000): 12 | self.ctx = ctx 13 | self.providerName = name 14 | 15 | #TODO maybe some of these items should actually be created in the interface's inits? 16 | # because some of them are specific to emulation, memory, etc 17 | 18 | self.priv = True 19 | self.modules = [] 20 | 21 | # heap stuff 22 | self.nextalloc = 0 23 | 24 | # Stop for OP_STOP_INS 25 | self.opstop = False 26 | 27 | # hooks that are installed 28 | self.hooks = [[], [], []] # Execute, read, write 29 | self.lasthook = None 30 | 31 | # inshooks are handlers of the form func(ctx, addr, provider) 32 | self.inshooks = { 33 | "rdtsc" : self.ctx.rdtscHook, 34 | } 35 | 36 | # setup annotation stuff 37 | # annotations are for noting things in memory that we track 38 | self.ann = [] 39 | 40 | # setup bounds 41 | # bounds is for sandboxing areas we haven't setup yet and tracking permissions 42 | self.bounds = {} 43 | self.pagetablebase = 0xffff800000000000 44 | 45 | # add annotation for the API_FUNC area 46 | self.apihooks = Annotation(apihookarea, apihookarea, "API_HOOKS", "API HOOKS") 47 | self.ann.append(self.apihooks) 48 | 49 | self.stackann = None 50 | 51 | # other global state that gets saved with the sate 52 | self.globstate = {} 53 | 54 | # Dobby provider id stuff 55 | self.isEmuProvider = issubclass(type(self), DobbyEmu) 56 | self.isSymProvider = issubclass(type(self), DobbySym) 57 | self.isRegContextProvider = issubclass(type(self), DobbyRegContext) 58 | self.isMemoryProvider = issubclass(type(self), DobbyMem) 59 | self.isSnapshotProvider = issubclass(type(self), DobbySnapshot) 60 | self.isFuzzerProvider = issubclass(type(self), DobbyFuzzer) 61 | 62 | ctx.registerProvider(self, name, True) 63 | 64 | def getName(self): 65 | return self.providerName 66 | 67 | def activated(self): 68 | """ 69 | Called when provider is activated 70 | """ 71 | pass 72 | 73 | def deactivated(self): 74 | """ 75 | Called when provider is activated 76 | """ 77 | pass 78 | 79 | def removed(self): 80 | """ 81 | Called when provider is removed 82 | """ 83 | pass 84 | 85 | def __repr__(self): 86 | return f"Dobby Provider {self.providerName}" 87 | 88 | """ 89 | These are the interfaces that providers can fill out to work with the Dobby system 90 | """ 91 | 92 | class DobbyEmu: 93 | """ 94 | Emulation interface for providers to fill out 95 | Maybe Execution interface is a better term 96 | Could be implemented by a debugger 97 | """ 98 | 99 | def getInsCount(self): 100 | """ 101 | Get current instruction count 102 | """ 103 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 104 | 105 | def insertHook(self, hook): 106 | """ 107 | Called when a hook is added 108 | Hooks are stored in ctx.hooks 109 | But if the provider has to do something on insertion, do it here 110 | If the provider is not active when the hook is added, it will not get this call 111 | """ 112 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 113 | 114 | def removeHook(self, hook): 115 | """ 116 | Called when a hook is removed 117 | Hooks are stored in ctx.hooks and will be removed after this callback 118 | But if the provider has to do something on removal, do it here 119 | If the provider is not active when the hook is added, it will not get this call 120 | """ 121 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 122 | 123 | def insertInstructionHook(self, insname, handler): 124 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 125 | 126 | def removeInstructionHook(self, insname, handler): 127 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 128 | 129 | def startTrace(self, getdrefs=False): 130 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 131 | 132 | def getTrace(self): 133 | """ 134 | Trace should be a list of entries 135 | Each entry being (address, disassembly, [list of read addresses, list of written addresses]) 136 | the disassembly and read/written addresses are optional if startTrace was given with getdrefs 137 | then the user wants the read and written addresses to be included, if possible 138 | """ 139 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 140 | 141 | def stopTrace(self): 142 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 143 | 144 | def isTracing(self): 145 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 146 | 147 | def traceAPI(self, label): 148 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 149 | 150 | def step(self, ignoreCurrentHook=True, printIns=True): 151 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 152 | 153 | def cont(self, ignoreCurrentHook=True, printIns=True): 154 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 155 | 156 | def contn(self, ignorehook, printIns, n): 157 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 158 | 159 | def until(self, addr, ignoreCurrentHook=True, printIns=True): 160 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 161 | 162 | def next(self, ignoreCurrentHook=True, printIns=True): 163 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 164 | 165 | class DobbySym: 166 | """ 167 | Symbolic interface for providers to fill out 168 | """ 169 | 170 | def isSymbolizedRegister(self, reg): 171 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 172 | 173 | def isSymbolizedMemory(self, addr, size): 174 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 175 | 176 | def symbolizeRegister(self, reg, name): 177 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 178 | 179 | def symbolizeMemory(self, addr, size, name): 180 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 181 | 182 | def getSymbol(self, name): 183 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 184 | 185 | def setSymbolVal(self, sym, value, overwrite=False): 186 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 187 | 188 | def getRegisterAst(self, reg): 189 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 190 | 191 | def getMemoryAst(self, addr, size): 192 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 193 | 194 | def printAst(self, ast): 195 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 196 | 197 | def getUnsetSym(self, ast, single=True, allSym=False, followRef=True): 198 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 199 | 200 | def getUnsetCount(self): 201 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 202 | 203 | def evalReg(self, reg, checkUnset=True): 204 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 205 | 206 | def evalMem(self, addr, size, checkUnset=True): 207 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 208 | 209 | class DobbyRegContext: 210 | """ 211 | Register Read interface for providers to fill out 212 | """ 213 | 214 | def getRegVal(self, reg): 215 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 216 | 217 | def setRegVal(self, reg, val): 218 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 219 | 220 | def getAllRegisters(self): 221 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 222 | 223 | class DobbyMem: 224 | """ 225 | Memory Read interface for providers to fill out 226 | """ 227 | 228 | def disass(self, addr=-1, count=16): 229 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 230 | 231 | def getInsLen(self, addr=-1): 232 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 233 | 234 | def getMemVal(self, addr, amt): 235 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 236 | 237 | def setMemVal(self, addr, val): 238 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 239 | 240 | def updateBounds(self, start, end, permissions): 241 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 242 | 243 | class DobbySnapshot: 244 | """ 245 | State saving and restoring for providers to fill out 246 | """ 247 | 248 | def takeSnapshot(self, snapshot): 249 | """ 250 | A chance to save off things that the normal snapshot state 251 | does not know about for this provider 252 | """ 253 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 254 | 255 | def restoreSnapshot(self, snapshot, trackDiff=False): 256 | """ 257 | A chance to restore things that the normal snapshot state 258 | does not know about for this provider 259 | """ 260 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 261 | 262 | class DobbyFuzzer: 263 | """ 264 | Utilities needed by the fuzzing tooling 265 | """ 266 | 267 | def getRunCoverage(self): 268 | """ 269 | Gets coverage information for current run as a unique value/hash 270 | """ 271 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 272 | 273 | def resetCoverage(self): 274 | """ 275 | Resets coverage info, starting a new coverage run 276 | """ 277 | raise NotImplementedError(f"{str(type(self))} does not implement this function") 278 | 279 | 280 | -------------------------------------------------------------------------------- /dobby/reversetaint.py: -------------------------------------------------------------------------------- 1 | import pyvex 2 | import archinfo 3 | from .dobby_const import * 4 | 5 | def getRegInOut(ctx, address, inslen=15, arch=None, oneins=True, ignorereg=[]): 6 | b = ctx.getMemVal(address, inslen) 7 | if arch is None: 8 | arch = archinfo.ArchAMD64() 9 | irsb = pyvex.lift(b, address, arch) 10 | 11 | out = [[],[]] # [[regin][regout]] 12 | 13 | for s in irsb.statements[1:]: 14 | if isinstance(s, pyvex.IRStmt.IMark): 15 | if oneins: 16 | #bad inslen, grab again, otherwise we miss things 17 | #unless we really have the full block 18 | return getRegInOut(ctx, address, s.addr - address, arch, oneins, ignorereg) 19 | elif isinstance(s, pyvex.IRStmt.Put): 20 | roff = s.offset 21 | rsz = s.data.result_size(irsb.tyenv)//8 22 | for i in range(rsz): 23 | r = roff + i 24 | if r not in ignorereg: 25 | out[1].append(r) 26 | 27 | for e in s.expressions: 28 | if isinstance(e, pyvex.IRStmt.Get): 29 | roff = e.offset 30 | rsz = e.result_size(irsb.tyenv)//8 31 | for i in range(rsz): 32 | r = roff + i 33 | if r not in ignorereg: 34 | out[0].append(r) 35 | 36 | # if there is an option for changing RIP here, we need to report RIP as an output 37 | #if not isinstance(irsb.next, pyvex.expr.Const): 38 | # out[1].append("rip") 39 | 40 | return out 41 | 42 | def revtainttrace(ctx, trace, intaintedaddrs, intaintedregs, outputtrace=None, printinfo=False): 43 | # tainted addrs is a set of addresses, for each byte tainted 44 | # tainted regs is a set of register, currently string names 45 | # convert registers to offsets in pyvex arch style 46 | taintedregs = set() 47 | taintedaddrs = set() 48 | 49 | arch = archinfo.ArchAMD64() 50 | for r in intaintedregs: 51 | rname = r 52 | if not isinstance(r, str): 53 | rname = r.getName() 54 | roff, rsz = arch.registers[rname] 55 | for i in range(rsz): 56 | taintedregs.add(roff + i) 57 | 58 | for addr, sz in intaintedaddrs: 59 | for i in range(sz): 60 | taintedaddrs.append(addr+i) 61 | 62 | ignorereg = [] 63 | for r in arch.registers: 64 | # cc is too broad of a register, taints too much 65 | # sp is also misused by VEX in some of the instructions? 66 | # so this isn't 100% accurate, but still gives me some good answers 67 | if r.startswith("cc_") or r.endswith("sp"): 68 | roff, rsz = arch.registers[r] 69 | for i in range(rsz): 70 | ignorereg.append(roff+i) 71 | 72 | raxreg = arch.registers['rax'] 73 | taintedins = 1 74 | 75 | for i in range(len(trace)-1, -1, -1): 76 | if not printinfo and (i & 0xfff == 0): 77 | print(f"{i:x}\t{taintedins/len(trace):.2%} tainted\tTaintedRegs: {strVexRegSet(arch, taintedregs)}, num adders = {len(taintedaddrs)}") 78 | te = trace[i] 79 | if te[0] == TRACE_API_ADDR: 80 | # Got an api area! 81 | # if rax is tainted, we issue a stop here 82 | # if any volatile register is tainted, issue a stop here 83 | for raxi in range(raxreg[0], raxreg[0]+raxreg[1]): 84 | if raxi in taintedregs: 85 | taintedregs.remove(raxi) 86 | print(f"\n\nDepends on ret from API {te[1]}\n\n") 87 | # stop? 88 | 89 | 90 | #TODO check if the api has in/out addresses recorded too 91 | if len(te) < 3: 92 | raise ValueError("Need drefs in trace for reverse taint analysis") 93 | l = 15 if len(te) < 4 else te[3] 94 | regs = getRegInOut(ctx, te[0], l, arch, True, ignorereg) 95 | # if one of the output registers or addresses was in our tainted list: 96 | # remove registers output this last instruction 97 | # add registers input 98 | # remove memory written 99 | # add memory read 100 | spreads = False 101 | for r in regs[1]: 102 | if r in taintedregs: 103 | spreads = True 104 | break 105 | # check written addresses 106 | for addr, sz in te[2][1]: 107 | for i in range(sz): 108 | if addr+i in taintedaddrs: 109 | spreads = True 110 | 111 | if printinfo: 112 | print(f"@{te[0]:x} : {te[1]} : {strVexRegSet(arch, regs[0])} : {strVexRegSet(arch, regs[1])}") 113 | 114 | if spreads: 115 | taintedins += 1 116 | # remove outputs we were tracking that got written out 117 | for r in regs[1]: 118 | if r in taintedregs: 119 | taintedregs.remove(r) 120 | for addr, sz in te[2][1]: 121 | for i in range(sz): 122 | if addr+i in taintedaddrs: 123 | taintedaddrs.remove(addr+i) 124 | # add inputs 125 | for r in regs[0]: 126 | taintedregs.add(r) 127 | for addr, sz in te[2][0]: 128 | for i in range(sz): 129 | taintedaddrs.add(addr+i) 130 | if printinfo: 131 | print(f"\tTaintedRegs: {strVexRegSet(arch, taintedregs)}, num adders = {len(taintedaddrs)}") 132 | 133 | if outputtrace is not None: 134 | # will be in reverse order 135 | outputtrace.append(trace[i]) 136 | 137 | print(f"{taintedins}/{len(trace)} = {taintedins/len(trace):.2%} tainted") 138 | 139 | #change vex offsets back to real registers 140 | return (taintedaddrs, strVexRegSet(arch,taintedregs)) 141 | 142 | def strVexRegSet(arch, regs): 143 | offs = arch.register_names 144 | out = set() 145 | for r in regs: 146 | if r in offs: 147 | out.add(arch.register_names[r]) 148 | return ','.join(out) 149 | -------------------------------------------------------------------------------- /dobby/winsys.py: -------------------------------------------------------------------------------- 1 | if __name__ == '__main__': 2 | print("Please import this file from a dobby script") 3 | exit(-1) 4 | 5 | import struct 6 | from .dobby import * 7 | from .dobby_const import * 8 | 9 | # windows kernel helper functions 10 | def createIrq(ctx, irqtype, inbuf): 11 | raise NotImplementedError("TODO") 12 | 13 | def createDrvObj(ctx, start, size, entry, path, name="DriverObj"): 14 | dobjsz = 0x150 15 | d = ctx.alloc(dobjsz) 16 | dex = ctx.alloc(0x50) 17 | dte = ctx.alloc(0x120) 18 | 19 | # initialize driver object 20 | # type = 0x4 21 | ctx.setu16(d + 0x00, 0x4) 22 | # size = 0x150 23 | ctx.setu16(d + 0x02, dobjsz) 24 | # DeviceObject = 0 25 | ctx.setu64(d + 0x08, 0x0) 26 | 27 | # flags = ?? 28 | #TODO 29 | ctx.trySymbolizeMemory(d+0x10, 8, name+".Flags") 30 | 31 | # DriverStart = start 32 | ctx.setu64(d + 0x18, start) 33 | # DriverSize = size 34 | ctx.setu32(d + 0x20, size) 35 | 36 | # DriverSection = LDR_DATA_TABLE_ENTRY 37 | # not sure what most of these fields are, so we will see what is used 38 | # set up DriverSection 39 | ctx.trySymbolizeMemory(dte+0x0, 0x10, name + ".DriverSection.InLoadOrderLinks") 40 | ctx.trySymbolizeMemory(dte+0x10, 0x10, name + ".DriverSection.InMemoryOrderLinks") 41 | ctx.trySymbolizeMemory(dte+0x20, 0x10, name + ".DriverSection.InInitializationOrderLinks") 42 | ctx.setu64(dte+0x30, start) 43 | ctx.setu64(dte+0x38, entry) 44 | ctx.setu64(dte+0x40, size) 45 | initUnicodeStr(ctx, dte+0x48, path) 46 | initUnicodeStr(ctx, dte+0x58, path.split('\\')[-1]) 47 | ctx.trySymbolizeMemory(dte+0x68, 0x8, name + ".DriverSection.Flags") 48 | ctx.trySymbolizeMemory(dte+0x70, 0x10, name + ".DriverSection.HashLinks") 49 | ctx.setu64(dte+0x80, 0) # TimeDateStamp 50 | ctx.trySymbolizeMemory(dte+0x88, 0x8, name + ".DriverSection.EntryPointActivationContext") 51 | ctx.setu64(dte+0x90, 0) # Lock 52 | ctx.trySymbolizeMemory(dte+0x98, 0x8, name + ".DriverSection.DdagNode") 53 | ctx.trySymbolizeMemory(dte+0xa0, 0x10, name + ".DriverSection.NodeModuleLink") 54 | ctx.trySymbolizeMemory(dte+0xb0, 0x8, name + ".DriverSection.LoadContext") 55 | ctx.trySymbolizeMemory(dte+0xb8, 0x8, name + ".DriverSection.ParentDllBase") 56 | ctx.trySymbolizeMemory(dte+0xc0, 0x8, name + ".DriverSection.SwitchBackContext") 57 | ctx.trySymbolizeMemory(dte+0xc8, 0x20, name + ".DriverSection.IndexNodeStuff") 58 | ctx.trySymbolizeMemory(dte+0xf8, 0x8, name + ".DriverSection.OriginalBase") 59 | ctx.trySymbolizeMemory(dte+0x100, 0x8, name + ".DriverSection.LoadTime") 60 | ctx.setu32(dte+0x108, 0) # BaseNameHashValue 61 | ctx.setu32(dte+0x10c, 0) # LoadReasonStaticDependency 62 | ctx.trySymbolizeMemory(dte+0x110, 4, name + ".DriverSection.ImplicitPathOptions") 63 | ctx.setu32(dte+0x118, 0) # DependentLoadFlags 64 | ctx.setu32(dte+0x11c, 0) # SigningLevel 65 | 66 | #ctx.trySymbolizeMemory(d+0x28, 8, name+".DriverSection") 67 | ctx.setu64(d+0x28, dte) 68 | 69 | # DriverExtension = dex 70 | ctx.setu64(d + 0x30, dex) 71 | # DriverName 72 | initUnicodeStr(ctx, d+0x38, "\\Driver\\" + name) 73 | 74 | # HardwareDatabase = ptr str 75 | hd = createUnicodeStr(ctx, "\\REGISTRY\\MACHINE\\HARDWARE\\DESCRIPTION\\SYSTEM") 76 | ctx.setu64(d + 0x48, hd) 77 | 78 | # FastIoDispatch = 0 79 | ctx.setu64(d + 0x50, 0x0) 80 | # DriverInit = DriverEntry 81 | ctx.setu64(d + 0x58, entry) 82 | # DriverStartIO = 0 83 | ctx.setu64(d + 0x60, 0x0) 84 | # DriverUnload = 0 85 | ctx.setu64(d + 0x68, 0x0) 86 | # MajorFunctions = 0 87 | ctx.setMemVal(d + 0x70, b"\x00" * 8 * 28) 88 | 89 | # initialize driver extension 90 | # ext.DriverObject = d 91 | ctx.setu64(dex + 0x00, d) 92 | # ext.AddDevice = 0 93 | ctx.setu64(dex + 0x08, 0) 94 | # ext.Count = 0 95 | ctx.setu64(dex + 0x10, 0) 96 | # ext.ServiceKeyName 97 | initUnicodeStr(ctx, dex+0x18, name) 98 | # ext.ClientDriverExtension = 0 99 | ctx.setu64(dex + 0x28, 0) 100 | # ext.FsFilterCallbacks = 0 101 | ctx.setu64(dex + 0x30, 0) 102 | # ext.KseCallbacks = 0 103 | ctx.setu64(dex + 0x38, 0) 104 | # ext.DvCallbacks = 0 105 | ctx.setu64(dex + 0x40, 0) 106 | # ext.VerifierContext = 0 107 | ctx.setu64(dex + 0x48, 0) 108 | 109 | return d 110 | 111 | def createUnicodeStr(ctx, s): 112 | ustr = ctx.alloc(0x10) 113 | initUnicodeStr(ctx, ustr, s) 114 | return ustr 115 | 116 | def initUnicodeStr(ctx, addr, s): 117 | us = s.encode("UTF-16-LE") 118 | buf = ctx.alloc(len(us)) 119 | ctx.setMemVal(buf, us) 120 | 121 | ctx.setu16(addr + 0, len(us)) 122 | ctx.setu16(addr + 2, len(us)) 123 | ctx.setu64(addr + 0x8, buf) 124 | 125 | def readUnicodeStr(ctx, addr): 126 | l = ctx.getu16(addr) 127 | ptr = ctx.getu64(addr+0x8) 128 | if ctx.issym and ctx.isSymbolizedMemory(addr+8, 8): 129 | print("Tried to read from a symbolized buffer in a unicode string") 130 | return "" 131 | b = ctx.getMemVal(ptr, l) 132 | return str(b, "UTF_16_LE") 133 | 134 | def setIRQL(ctx, newlevel): 135 | oldirql = ctx.getRegVal(DB_X86_R_CR8) 136 | #TODO save old one at offset from gs see KeRaiseIrqlToDpcLevel 137 | ctx.setRegVal(DB_X86_R_CR8, newlevel) 138 | return oldirql 139 | 140 | def KeBugCheckEx_hook(hook, ctx, addr, sz, op, provider): 141 | code = ctx.getRegVal(DB_X86_R_RCX) 142 | print(f"Bug Check! Code: {code:x}. See other 4 params for more info") 143 | return HookRet.FORCE_STOP_INS 144 | 145 | def ExAllocatePoolWithTag_hook(hook, ctx, addr, sz, op, provider): 146 | #TODO actually have an allocator? Hope they don't do this a lot 147 | #TODO memory permissions based on pool 148 | pool = ctx.getRegVal(DB_X86_R_RCX) 149 | amt = ctx.getRegVal(DB_X86_R_RDX) 150 | tag = struct.pack("> 9) & 1) 363 | 364 | ret = 0 if cr8val == 0 and ie == 1 else 1 365 | print(f"KeAreAllApcsDisabled : {ret}") 366 | ctx.doRet(ret) 367 | return HookRet.DONE_INS 368 | 369 | def KeIpiGenericCall_hook(hook, ctx, addr, sz, op, provider): 370 | fcn = ctx.getRegVal(DB_X86_R_RCX) 371 | arg = ctx.getRegVal(DB_X86_R_RDX) 372 | # set IRQL to IPI_LEVEL 373 | old_level = setIRQL(ctx, 0xe) 374 | # do IpiGeneric Call 375 | ctx.setRegVal(DB_X86_R_RCX, arg) 376 | ctx.setRegVal(DB_X86_R_RIP, fcn) 377 | 378 | # set hook for when we finish 379 | def finish_KeIpiGenericCall_hook(hook, ctx, addr, sz, op, provider): 380 | # remove self 381 | ctx.delHook(hook) 382 | 383 | setIRQL(ctx, old_level) 384 | 385 | rval = ctx.getRegVal(DB_X86_R_RAX) 386 | print(f"KeIpiGenericCall returned {hex(rval)}") 387 | 388 | return HookRet.OP_CONT_INS 389 | 390 | curstack = ctx.getRegVal(DB_X86_R_RSP) 391 | retaddr = ctx.getu64(curstack) 392 | 393 | ctx.addHook(retaddr, retaddr+1, MEM_EXECUTE, handler=finish_KeIpiGenericCall_hook, label="") 394 | print(f"KeIpiGenericCall {hex(fcn)} ({hex(arg)})") 395 | return HookRet.OP_DONE_INS 396 | 397 | def ZwQuerySystemInformation_hook(hook, ctx, addr, sz, op, provider): 398 | infoclass = ctx.getRegVal(DB_X86_R_RCX) 399 | buf = ctx.getRegVal(DB_X86_R_RDX) 400 | buflen = ctx.getRegVal(DB_X86_R_R8) 401 | retlenptr = ctx.getRegVal(DB_X86_R_R9) 402 | 403 | if infoclass == 0x0b: #SystemModuleInformation 404 | # buffer should contain RTL_PROCESS_MODULES structure 405 | raise NotImplementedError(f"Unimplemented infoclass SystemModuleInformation in ZwQuerySystemInformation") 406 | elif infoclass == 0x4d: #SystemModuleInformationEx 407 | # buffer should contain RTL_PROCESS_MODULE_INFORMATION_EX 408 | # has to include the module we are emulating 409 | # just copy over a good buffer from the computer? 410 | # if they actually use the info we are in trouble 411 | # actually load in a bunch of modules? :( 412 | # might have to support paging in/out if that needs to happen 413 | # for now just try a good value 414 | # see side_utils for doing this from python to get example output 415 | # TODO provide a good output, but symbolize any real addresses 416 | raise NotImplementedError(f"Unimplemented infoclass SystemModuleInformationEx in ZwQuerySystemInformation") 417 | else: 418 | raise NotImplementedError(f"Unimplemented infoclass in ZwQuerySystemInformation : {hex(infoclass)}") 419 | 420 | def ExSystemTimeToLocalTime_hook(hook, ctx, addr, sz, op, provider): 421 | ctx.setRegVal(DB_X86_R_RIP, ctx.active.globstate["_thunk_symaddr0"]) 422 | print("ExSystemTimeToLocalTime") 423 | return HookRet.DONE_INS 424 | 425 | def RtlTimeToTimeFields_hook(hook, ctx, addr, sz, op, provider): 426 | ctx.setRegVal(DB_X86_R_RIP, ctx.active.globstate["_thunk_symaddr1"]) 427 | print("RtlTimeToTimeFields") 428 | return HookRet.DONE_INS 429 | 430 | def _stricmp_hook(hook, ctx, addr, sz, op, provider): 431 | ctx.setRegVal(DB_X86_R_RIP, ctx.active.globstate["_thunk_symaddr2"]) 432 | s1addr = ctx.getRegVal(DB_X86_R_RCX) 433 | s2addr = ctx.getRegVal(DB_X86_R_RDX) 434 | s1 = ctx.getCStr(s1addr) 435 | s2 = ctx.getCStr(s2addr) 436 | print(f"_stricmp \"{s1}\" vs \"{s2}\"") 437 | return HookRet.OP_DONE_INS 438 | 439 | def wcscat_s_hook(hook, ctx, addr, sz, op, provider): 440 | ctx.setRegVal(DB_X86_R_RIP, ctx.active.globstate["_thunk_symaddr3"]) 441 | s1addr = ctx.getRegVal(DB_X86_R_RCX) 442 | s2addr = ctx.getRegVal(DB_X86_R_R8) 443 | num = ctx.getRegVal(DB_X86_R_RDX) 444 | s1 = ctx.getCWStr(s1addr) 445 | s2 = ctx.getCWStr(s2addr) 446 | print(f"wcscat_s ({num}) \"{s1}\" += \"{s2}\"") 447 | return HookRet.OP_DONE_INS 448 | 449 | def wcscpy_s_hook(hook, ctx, addr, sz, op, provider): 450 | ctx.setRegVal(DB_X86_R_RIP, ctx.active.globstate["_thunk_symaddr4"]) 451 | dst = ctx.getRegVal(DB_X86_R_RCX) 452 | src = ctx.getRegVal(DB_X86_R_R8) 453 | num = ctx.getRegVal(DB_X86_R_RDX) 454 | s = ctx.getCWStr(src) 455 | print(f"wcscpy_s {hex(dst)[2:]}({num}) <= \"{s}\"") 456 | return HookRet.OP_DONE_INS 457 | 458 | def RtlInitUnicodeString_hook(hook, ctx, addr, sz, op, provider): 459 | ctx.setRegVal(DB_X86_R_RIP, ctx.active.globstate["_thunk_symaddr5"]) 460 | src = ctx.getRegVal(DB_X86_R_RDX) 461 | s = ctx.getCWStr(src) 462 | print(f"RtlInitUnicodeString \"{s}\"") 463 | return HookRet.OP_DONE_INS 464 | 465 | def swprintf_s_hook(hook, ctx, addr, sz, op, provider): 466 | ctx.setRegVal(DB_X86_R_RIP, ctx.active.globstate["_thunk_symaddr6"]) 467 | buf = ctx.getRegVal(DB_X86_R_RCX) 468 | fmt = ctx.getRegVal(DB_X86_R_R8) 469 | fmts = ctx.getCWStr(fmt) 470 | # set hook for after return 471 | sp = ctx.getRegVal(DB_X86_R_RSP) 472 | retaddr = ctx.getu64(sp) 473 | def finish_swprintf_s_hook(hook, ctx, addr, sz, op, provider): 474 | # remove self 475 | ctx.delHook(hook) 476 | s = ctx.getCWStr(buf) 477 | print(f"Finished swprintf_s: \"{s}\" from \"{fmts}\"") 478 | return HookRet.OP_CONT_INS 479 | ctx.addHook(retaddr, retaddr+1, MEM_EXECUTE, handler=finish_swprintf_s_hook, label="") 480 | return HookRet.OP_DONE_INS 481 | 482 | def vswprintf_s_hook(hook, ctx, addr, sz, op, provider): 483 | ctx.setRegVal(DB_X86_R_RIP, ctx.active.globstate["_thunk_symaddr7"]) 484 | buf = ctx.getRegVal(DB_X86_R_RCX) 485 | fmt = ctx.getRegVal(DB_X86_R_R8) 486 | fmts = ctx.getCWStr(fmt) 487 | # set hook for after return 488 | sp = ctx.getRegVal(DB_X86_R_RSP) 489 | retaddr = ctx.getu64(sp) 490 | def finish_vswprintf_s_hook(hook, ctx, addr, sz, op, provider): 491 | # remove self 492 | ctx.delHook(hook) 493 | s = ctx.getCWStr(buf) 494 | print(f"Finished vswprintf_s: \"{s}\" from \"{fmts}\"") 495 | return HookRet.OP_CONT_INS 496 | ctx.addHook(retaddr, retaddr+1, MEM_EXECUTE, handler=finish_vswprintf_s_hook, label="") 497 | return HookRet.OP_DONE_INS 498 | 499 | def _vsnwprintf_hook(hook, ctx, addr, sz, op, provider): 500 | ctx.setRegVal(DB_X86_R_RIP, ctx.active.globstate["_thunk_symaddr8"]) 501 | buf = ctx.getRegVal(DB_X86_R_RCX) 502 | fmt = ctx.getRegVal(DB_X86_R_R8) 503 | fmts = ctx.getCWStr(fmt) 504 | # set hook for after return 505 | sp = ctx.getRegVal(DB_X86_R_RSP) 506 | retaddr = ctx.getu64(sp) 507 | def finish__vsnwprintf_s_hook(hook, ctx, addr, sz, op, provider): 508 | # remove self 509 | ctx.delHook(hook) 510 | s = ctx.getCWStr(buf) 511 | print(f"Finished _vsnwprintf_s: \"{s}\" from \"{fmts}\"") 512 | return HookRet.OP_CONT_INS 513 | ctx.addHook(retaddr, retaddr+1, MEM_EXECUTE, handler=finish__vsnwprintf_s_hook, label="") 514 | return HookRet.OP_DONE_INS 515 | 516 | def createThunkHooks(ctx): 517 | # have to be in higher scope for pickling the hooks 518 | name = "ExSystemTimeToLocalTime" 519 | ctx.active.globstate["_thunk_symaddr0"] = ctx.getImageSymbol(name, "ntoskrnl.exe") 520 | ctx.setApiHandler(name, ExSystemTimeToLocalTime_hook, "ignore") 521 | 522 | name = "RtlTimeToTimeFields" 523 | ctx.active.globstate["_thunk_symaddr1"] = ctx.getImageSymbol(name, "ntoskrnl.exe") 524 | ctx.setApiHandler(name, RtlTimeToTimeFields_hook, "ignore") 525 | 526 | name = "_stricmp" 527 | ctx.active.globstate["_thunk_symaddr2"] = ctx.getImageSymbol(name, "ntoskrnl.exe") 528 | ctx.setApiHandler(name, _stricmp_hook, "ignore") 529 | 530 | name = "wcscat_s" 531 | ctx.active.globstate["_thunk_symaddr3"] = ctx.getImageSymbol(name, "ntoskrnl.exe") 532 | ctx.setApiHandler(name, wcscat_s_hook, "ignore") 533 | 534 | name = "wcscpy_s" 535 | ctx.active.globstate["_thunk_symaddr4"] = ctx.getImageSymbol(name, "ntoskrnl.exe") 536 | ctx.setApiHandler(name, wcscpy_s_hook, "ignore") 537 | 538 | name = "RtlInitUnicodeString" 539 | ctx.active.globstate["_thunk_symaddr5"] = ctx.getImageSymbol(name, "ntoskrnl.exe") 540 | ctx.setApiHandler(name, RtlInitUnicodeString_hook, "ignore") 541 | 542 | name = "swprintf_s" 543 | ctx.active.globstate["_thunk_symaddr6"] = ctx.getImageSymbol(name, "ntoskrnl.exe") 544 | ctx.setApiHandler(name, swprintf_s_hook, "ignore") 545 | 546 | name = "vswprintf_s" 547 | ctx.active.globstate["_thunk_symaddr7"] = ctx.getImageSymbol(name, "ntoskrnl.exe") 548 | ctx.setApiHandler(name, vswprintf_s_hook, "ignore") 549 | 550 | name = "_vsnwprintf" 551 | ctx.active.globstate["_thunk_symaddr8"] = ctx.getImageSymbol(name, "ntoskrnl.exe") 552 | ctx.setApiHandler(name, _vsnwprintf_hook, "ignore") 553 | 554 | 555 | def setNtosThunkHook(ctx, name, dostop): 556 | ctx.setApiHandler(name, ctx.createThunkHook(name, "ntoskrnl.exe", dostop), "ignore") 557 | 558 | def registerWinHooks(ctx): 559 | ctx.setApiHandler("RtlDuplicateUnicodeString", RtlDuplicateUnicodeString_hook, "ignore") 560 | ctx.setApiHandler("KeBugCheckEx", KeBugCheckEx_hook, "ignore") 561 | ctx.setApiHandler("ExAllocatePoolWithTag", ExAllocatePoolWithTag_hook, "ignore") 562 | ctx.setApiHandler("ExFreePoolWithTag", ExFreePoolWithTag_hook, "ignore") 563 | ctx.setApiHandler("IoCreateFileEx", IoCreateFileEx_hook, "ignore") 564 | ctx.setApiHandler("ZwClose", ZwClose_hook, "ignore") 565 | ctx.setApiHandler("ZwWriteFile", ZwWriteFile_hook, "ignore") 566 | ctx.setApiHandler("ZwReadFile", ZwReadFile_hook, "ignore") 567 | ctx.setApiHandler("ZwFlushBuffersFile", ZwFlushBuffersFile_hook, "ignore") 568 | ctx.setApiHandler("KeAreAllApcsDisabled", KeAreAllApcsDisabled_hook, "ignore") 569 | ctx.setApiHandler("KeIpiGenericCall", KeIpiGenericCall_hook, "ignore") 570 | ctx.setApiHandler("IoCreateDevice", IoCreateDevice_hook, "ignore") 571 | 572 | createThunkHooks(ctx) 573 | 574 | def loadNtos(ctx, base=0xfffff8026be00000): 575 | # NOTE just because we load ntos doesn't mean it is initialized at all 576 | # Make sure you initalize the components you intend to use 577 | print("Loading nt...") 578 | ctx.loadPE("ntoskrnl.exe", base) 579 | print("Loaded!") 580 | 581 | def kuser_time_hook(hk, ctx, addr, sz, op, provider): 582 | # InterruptTime is 100ns scale time since start 583 | it = ctx.getTicks() 584 | # SystemTime is 100ns scale, as timestamp 585 | st = ctx.getTime() 586 | # TickCount is 1ms scale, as ticks update as if interrupts have maximum period? 587 | # TODO adjust this? 588 | tc = int(it // 10000) 589 | 590 | shared_data_addr = 0xfffff78000000000 591 | # write the values back 592 | bts = struct.pack(">32) 593 | ctx.setMemVal(shared_data_addr + 0x320, bts) 594 | bts = struct.pack(">32, st, st>>32) 595 | ctx.setMemVal(shared_data_addr + 0x8, bts) 596 | 597 | if shared_data_addr + 0x8 <= addr < shared_data_addr + 0x14: 598 | print("Read from InterruptTime") 599 | if shared_data_addr + 0x14 <= addr < shared_data_addr + 0x20: 600 | print("Read from SystemTime") 601 | if shared_data_addr + 0x320 <= addr < shared_data_addr + 0x330: 602 | print("Read from TickCount") 603 | return HookRet.CONT_INS 604 | 605 | def initSys(ctx): 606 | # setup global state we track 607 | ctx.active.globstate["poolAllocations"] = [] # see ExAllocatePoolWithTag 608 | ctx.active.globstate["handles"] = {} # number : (object,) 609 | ctx.active.globstate["nexthandle"] = 1 610 | 611 | loadNtos(ctx) 612 | registerWinHooks(ctx) 613 | 614 | # setup KUSER_SHARED_DATA at 0xFFFFF78000000000 615 | shared_data_addr = 0xfffff78000000000 616 | shared_data_sz = 0x720 617 | ctx.addAnn(shared_data_addr, shared_data_addr + shared_data_sz, "GLOBAL", "_KUSER_SHARED_DATA") 618 | ctx.updateBounds(shared_data_addr, shared_data_addr + shared_data_sz, MEM_READ, False) 619 | 620 | #TODO verify tick count/time works how you think 621 | # time is # of 100-nanosecond intervals 622 | # these numbers aren't actually any good because we hook out a looot of functionality? 623 | # but eh, if things don't work then use a volatile symbol hook here 624 | 625 | ctx.addHook(shared_data_addr + 0x8, shared_data_addr+0x20, MEM_READ, kuser_time_hook, "Interrupt and System Time hook") 626 | ctx.addHook(shared_data_addr + 0x320, shared_data_addr+0x32c, MEM_READ, kuser_time_hook, "Tick Time hook") 627 | 628 | ctx.setMemVal( 629 | shared_data_addr + 0x0, 630 | b'\x00\x00\x00\x00' + # +0x0 .TickCountLowDeprecated 631 | b'\x00\x00\xa0\x0f' + # +0x4 .TickCountMultiplier 632 | # HOOK THIS and use instruction count to add to it 633 | b'O\xcaW[\xd8\x05\x00\x00\xd8\x05\x00\x00' + # +0x8 .InterruptTime 634 | # HOOK THIS and use instruction count to add to it 635 | b'\x19E~M\xe7\x8c\xd6\x01\xe7\x8c\xd6\x01' + # +0x14 .SystemTime 636 | b'\x00\xa0\x11\x87!\x00\x00\x00!\x00\x00\x00' + # +0x20 .TimeZoneBias 637 | b'd\x86' + # +0x2c .ImageNumberLow 638 | b'd\x86' + # +0x2e .ImageNumberHigh 639 | b'C\x00:\x00\\\x00W\x00I\x00N\x00D\x00O\x00' + # +0x30 .NtSystemRoot 640 | b'W\x00S\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 641 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 642 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 643 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 644 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 645 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 646 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 647 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 648 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 649 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 650 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 651 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 652 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 653 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 654 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 655 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 656 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 657 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 658 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 659 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 660 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 661 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 662 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 663 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 664 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 665 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 666 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 667 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 668 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 669 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 670 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 671 | b'\x00\x00\x00\x00\x00\x00\x00\x00' + 672 | b'\x00\x00\x00\x00' + # +0x238 .MaxStackTraceDepth 673 | b'\x00\x00\x00\x00' + # +0x23c .CryptoExponent 674 | b'\x02\x00\x00\x00' + # +0x240 .TimeZoneId 675 | b'\x00\x00 \x00' + # +0x244 .LargePageMinimum 676 | b'\x00\x00\x00\x00' + # +0x248 .AitSamplingValue 677 | b'\x00\x00\x00\x00' + # +0x24c .AppCompatFlag 678 | b'I\x00\x00\x00\x00\x00\x00\x00' + # +0x250 .RNGSeedVersion 679 | b'\x00\x00\x00\x00' + # +0x258 .GlobalValidationRunlevel 680 | b'\x1c\x00\x00\x00' + # +0x25c .TimeZoneBiasStamp 681 | b'aJ\x00\x00' + # +0x260 .NtBuildNumber 682 | b'\x01\x00\x00\x00' + # +0x264 .NtProductType 683 | b'\x01' + # +0x268 .ProductTypeIsValid 684 | b'\x00' + # +0x269 .Reserved0 685 | b'\t\x00' + # +0x26a .NativeProcessorArchitecture 686 | b'\n\x00\x00\x00' + # +0x26c .NtMajorVersion 687 | b'\x00\x00\x00\x00' + # +0x270 .NtMinorVersion 688 | #ctx.symbolizeMemory(MemoryAccess(shared_data_addr + 0x274, 0x4), "kuser_shared_data.ProcessorFeature[0:4]") 689 | #ctx.symbolizeMemory(MemoryAccess(shared_data_addr + 0x278, 0x8), "kuser_shared_data.ProcessorFeature[4:c]") 690 | #ctx.symbolizeMemory(MemoryAccess(shared_data_addr + 0x280, 0x20), "kuser_shared_data.ProcessorFeature[c:2c]") 691 | #ctx.symbolizeMemory(MemoryAccess(shared_data_addr + 0x2a0, 0x10), "kuser_shared_data.ProcessorFeature[2c:3c]") 692 | #ctx.symbolizeMemory(MemoryAccess(shared_data_addr + 0x2b0, 0x8), "kuser_shared_data.ProcessorFeature[3c:44]") 693 | #ctx.symbolizeMemory(MemoryAccess(shared_data_addr + 0x2b8, 0x4), "kuser_shared_data.reserved3") 694 | b'\x00\x00\x01\x01\x00\x00\x01\x00\x01\x01\x01\x00\x01\x01\x01\x00' + # +0x274 .ProcessorFeatures 695 | b'\x00\x01\x00\x00\x00\x01\x01\x01\x00\x00\x00\x00\x01\x00\x00\x00' + 696 | b'\x01\x01\x00\x00\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00' + 697 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 698 | b'\xff\xff\xfe\x7f' + # +0x2b4 .Reserved1 699 | b'\x00\x00\x00\x80' + # +0x2b8 .Reserved3 700 | b'\x00\x00\x00\x00' + # +0x2bc .TimeSlip 701 | b'\x00\x00\x00\x00' + # +0x2c0 .AlternativeArchitecture 702 | b' \x00\x00\x00' + # +0x2c4 .BootId 703 | b'\x00\x00\x00\x00\x00\x00\x00\x00' + # +0x2c8 .SystemExpirationDate 704 | b'\x10\x03\x00\x00' + # +0x2d0 .SuiteMask 705 | # Yeah, go ahead and keep this one a 0 706 | b'\x00' + # +0x2d4 .KdDebuggerEnabled # Yeah, go ahead and keep this one a 0 707 | b'\n' + # +0x2d5 .Reserved 708 | b'<\x00' + # +0x2d6 .CyclesPerYield 709 | b'\x01\x00\x00\x00' + # +0x2d8 .ActiveConsoleId 710 | b'\x04\x00\x00\x00' + # +0x2dc .DismountCount 711 | b'\x01\x00\x00\x00' # +0x2e0 .ComPlusPackage 712 | ) 713 | #TODO hook this properly 714 | ctx.trySymbolizeMemory(shared_data_addr + 0x2e4, 0x4, "kuser_shared_data.LastSystemRITEventTickCount") 715 | #b'\xc9\x85N&' + # +0x2e4 .LastSystemRITEventTickCount 716 | 717 | ctx.setMemVal( 718 | shared_data_addr + 0x2e8, 719 | b'\x94\xbb?\x00' + # +0x2e8 .NumberOfPhysicalPages 720 | b'\x00' + # +0x2ec .SafeBootMode 721 | b'\x01' + # +0x2ed .VirtualizationFlags #TODO worth symbolizing? 722 | b'\x00\x00' + # +0x2ee .Reserved12 723 | #TODO should any of these be changed? 724 | # ULONG DbgErrorPortPresent : 1; 725 | # ULONG DbgElevationEnabled : 1; // second bit 726 | # ULONG DbgVirtEnabled : 1; // third bit 727 | # ULONG DbgInstallerDetectEnabled : 1; // fourth bit 728 | # ULONG DbgSystemDllRelocated : 1; 729 | # ULONG DbgDynProcessorEnabled : 1; 730 | # ULONG DbgSEHValidationEnabled : 1; 731 | # ULONG SpareBits : 25; 732 | b'\x0e\x01\x00\x00' + # +0x2f0 .SpareBits 733 | b'\x00\x00\x00\x00' + # +0x2f4 .DataFlagsPad 734 | b'\xc3\x00\x00\x00\x00\x00\x00\x00' + # +0x2f8 .TestRetInstruction 735 | b'\x80\x96\x98\x00\x00\x00\x00\x00' + # +0x300 .QpcFrequency 736 | b'\x00\x00\x00\x00' + # +0x308 .SystemCall 737 | b'\x00\x00\x00\x00' + # +0x30c .UserCetAvailableEnvironments 738 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + # +0x310 .SystemCallPad 739 | # HOOK THIS and use instruction count to add to it 740 | b'\x17\x9es\x02\x00\x00\x00\x00\x00\x00\x00\x00' + # +0x320 .ReservedTickCountOverlay 741 | b'\x00\x00\x00\x00' + # +0x32c .TickCountPad 742 | b'\xd3PB\x1b' + # +0x330 .Cookie 743 | b'\x00\x00\x00\x00' + # +0x334 .CookiePad 744 | b'\xbc\x1d\x00\x00\x00\x00\x00\x00' + # +0x338 .ConsoleSessionForegroundProcessId 745 | #TODO hook this? 746 | b'\xa2{H\x1a\x00\x00\x00\x00' + # +0x340 .TimeUpdateLock 747 | b'-\x83\x87[\xd8\x05\x00\x00' + # +0x348 .BaselineSystemTimeQpc 748 | b'-\x83\x87[\xd8\x05\x00\x00' + # +0x350 .BaselineInterruptTimeQpc 749 | b'\x00\x00\x00\x00\x00\x00\x00\x80' + # +0x358 .QpcSystemTimeIncrement 750 | b'\x00\x00\x00\x00\x00\x00\x00\x80' + # +0x360 .QpcInterruptTimeIncrement 751 | b'\x01' + # +0x368 .QpcSystemTimeIncrementShift 752 | b'\x01' + # +0x369 .QpcInterruptTimeIncrementShift 753 | b'\x18\x00' + # +0x36a .UnparkedProcessorCount 754 | b'\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + # +0x36c .EnclaveFeatureMask 755 | b'\x03\x00\x00\x00' + # +0x37c .TelemetryCoverageRound 756 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + # +0x380 .UserModeGlobalLogger 757 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 758 | b'\x00\x00\x00\x00' + # +0x3a0 .ImageFileExecutionOptions 759 | b'\x01\x00\x00\x00' + # +0x3a4 .LangGenerationCount 760 | b'\x00\x00\x00\x00\x00\x00\x00\x00' + # +0x3a8 .Reserved4 761 | b'\x17\xfc\x9eU\xd8\x03\x00\x00' + # +0x3b0 .InterruptTimeBias 762 | b'\xcd"\x15G\xd8\x03\x00\x00' + # +0x3b8 .QpcBias 763 | b'\x18\x00\x00\x00' + # +0x3c0 .ActiveProcessorCount 764 | b'\x01' + # +0x3c4 .ActiveGroupCount 765 | b'\x00' + # +0x3c5 .Reserved9 766 | b'\x83' + # +0x3c6 .QpcBypassEnabled 767 | b'\x00' + # +0x3c7 .QpcShift 768 | b'\x9a,\x17\xcdq\x8c\xd6\x01' + # +0x3c8 .TimeZoneBiasEffectiveStart 769 | b'\x000\x9d;\x14\xb0\xd6\x01' + # +0x3d0 .TimeZoneBiasEffectiveEnd 770 | b'\x07\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00' + # +0x3d8 .XState 771 | b'@\x03\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\xa0\x00\x00\x00' + 772 | b'\xa0\x00\x00\x00\x00\x01\x00\x00@\x02\x00\x00\x00\x01\x00\x00' + 773 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 774 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 775 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 776 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 777 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 778 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 779 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 780 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 781 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 782 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 783 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 784 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 785 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 786 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 787 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 788 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 789 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 790 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 791 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 792 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 793 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 794 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 795 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 796 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 797 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 798 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 799 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 800 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 801 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 802 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 803 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 804 | b'\x00\x00\x00\x00\x00\x00\x00\x00@\x03\x00\x00\xa0\x00\x00\x00' + 805 | b'\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 806 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 807 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 808 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 809 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 810 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 811 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 812 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 813 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 814 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 815 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 816 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 817 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 818 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 819 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 820 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 821 | b'\x00\x00\x00\x00\x00\x00\x00\x00' + 822 | b'\x12\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + # +0x710 .FeatureConfigurationChangeStamp 823 | b'\x00\x00\x00\x00' # +0x71c .Spare 824 | ) 825 | 826 | # setup KPCR and KPRCB 827 | #TODO 828 | 829 | 830 | 831 | -------------------------------------------------------------------------------- /dobby/x86const.py: -------------------------------------------------------------------------------- 1 | # page size 2 | DB_X86_PGSHFT = 12 3 | DB_X86_PGSZ = (1 << DB_X86_PGSHFT) 4 | 5 | # register values 6 | DB_X86_R_AC = 0x1 7 | DB_X86_R_AF = 0x2 8 | DB_X86_R_AH = 0x3 9 | DB_X86_R_AL = 0x4 10 | DB_X86_R_AX = 0x5 11 | DB_X86_R_BH = 0x6 12 | DB_X86_R_BL = 0x7 13 | DB_X86_R_BP = 0x8 14 | DB_X86_R_BPL = 0x9 15 | DB_X86_R_BX = 0xa 16 | DB_X86_R_CF = 0xb 17 | DB_X86_R_CH = 0xc 18 | DB_X86_R_CL = 0xd 19 | DB_X86_R_CR0 = 0xe 20 | DB_X86_R_CR1 = 0xf 21 | DB_X86_R_CR10 = 0x10 22 | DB_X86_R_CR11 = 0x11 23 | DB_X86_R_CR12 = 0x12 24 | DB_X86_R_CR13 = 0x13 25 | DB_X86_R_CR14 = 0x14 26 | DB_X86_R_CR15 = 0x15 27 | DB_X86_R_CR2 = 0x16 28 | DB_X86_R_CR3 = 0x17 29 | DB_X86_R_CR4 = 0x18 30 | DB_X86_R_CR5 = 0x19 31 | DB_X86_R_CR6 = 0x1a 32 | DB_X86_R_CR7 = 0x1b 33 | DB_X86_R_CR8 = 0x1c 34 | DB_X86_R_CR9 = 0x1d 35 | DB_X86_R_CS = 0x1e 36 | DB_X86_R_CX = 0x1f 37 | DB_X86_R_DAZ = 0x20 38 | DB_X86_R_DE = 0x21 39 | DB_X86_R_DF = 0x22 40 | DB_X86_R_DH = 0x23 41 | DB_X86_R_DI = 0x24 42 | DB_X86_R_DIL = 0x25 43 | DB_X86_R_DL = 0x26 44 | DB_X86_R_DM = 0x27 45 | DB_X86_R_DR0 = 0x28 46 | DB_X86_R_DR1 = 0x29 47 | DB_X86_R_DR10 = 0x2a 48 | DB_X86_R_DR11 = 0x2b 49 | DB_X86_R_DR12 = 0x2c 50 | DB_X86_R_DR13 = 0x2d 51 | DB_X86_R_DR14 = 0x2e 52 | DB_X86_R_DR15 = 0x2f 53 | DB_X86_R_DR2 = 0x30 54 | DB_X86_R_DR3 = 0x31 55 | DB_X86_R_DR4 = 0x32 56 | DB_X86_R_DR5 = 0x33 57 | DB_X86_R_DR6 = 0x34 58 | DB_X86_R_DR7 = 0x35 59 | DB_X86_R_DR8 = 0x36 60 | DB_X86_R_DR9 = 0x37 61 | DB_X86_R_DS = 0x38 62 | DB_X86_R_DX = 0x39 63 | DB_X86_R_EAX = 0x3a 64 | DB_X86_R_EBP = 0x3b 65 | DB_X86_R_EBX = 0x3c 66 | DB_X86_R_ECX = 0x3d 67 | DB_X86_R_EDI = 0x3e 68 | DB_X86_R_EDX = 0x3f 69 | DB_X86_R_EFLAGS = 0x40 70 | DB_X86_R_EIP = 0x41 71 | DB_X86_R_EIZ = 0x42 72 | DB_X86_R_ES = 0x43 73 | DB_X86_R_ESI = 0x44 74 | DB_X86_R_ESP = 0x45 75 | DB_X86_R_FP0 = 0x46 76 | DB_X86_R_FP1 = 0x47 77 | DB_X86_R_FP2 = 0x48 78 | DB_X86_R_FP3 = 0x49 79 | DB_X86_R_FP4 = 0x4a 80 | DB_X86_R_FP5 = 0x4b 81 | DB_X86_R_FP6 = 0x4c 82 | DB_X86_R_FP7 = 0x4d 83 | DB_X86_R_FPCW = 0x4e 84 | DB_X86_R_FPSW = 0x4f 85 | DB_X86_R_FPTAG = 0x50 86 | DB_X86_R_FS = 0x51 87 | DB_X86_R_FZ = 0x52 88 | DB_X86_R_GDTR = 0x53 89 | DB_X86_R_GS = 0x54 90 | DB_X86_R_ID = 0x55 91 | DB_X86_R_IDTR = 0x56 92 | DB_X86_R_IE = 0x57 93 | DB_X86_R_IF = 0x58 94 | DB_X86_R_IM = 0x59 95 | DB_X86_R_IP = 0x5a 96 | DB_X86_R_K0 = 0x5b 97 | DB_X86_R_K1 = 0x5c 98 | DB_X86_R_K2 = 0x5d 99 | DB_X86_R_K3 = 0x5e 100 | DB_X86_R_K4 = 0x5f 101 | DB_X86_R_K5 = 0x60 102 | DB_X86_R_K6 = 0x61 103 | DB_X86_R_K7 = 0x62 104 | DB_X86_R_LDTR = 0x63 105 | DB_X86_R_MM0 = 0x64 106 | DB_X86_R_MM1 = 0x65 107 | DB_X86_R_MM2 = 0x66 108 | DB_X86_R_MM3 = 0x67 109 | DB_X86_R_MM4 = 0x68 110 | DB_X86_R_MM5 = 0x69 111 | DB_X86_R_MM6 = 0x6a 112 | DB_X86_R_MM7 = 0x6b 113 | DB_X86_R_MSR = 0x6c 114 | DB_X86_R_MXCSR = 0x6d 115 | DB_X86_R_NT = 0x6e 116 | DB_X86_R_OE = 0x6f 117 | DB_X86_R_OF = 0x70 118 | DB_X86_R_OM = 0x71 119 | DB_X86_R_PE = 0x72 120 | DB_X86_R_PF = 0x73 121 | DB_X86_R_PM = 0x74 122 | DB_X86_R_R10 = 0x75 123 | DB_X86_R_R10B = 0x76 124 | DB_X86_R_R10D = 0x77 125 | DB_X86_R_R10W = 0x78 126 | DB_X86_R_R11 = 0x79 127 | DB_X86_R_R11B = 0x7a 128 | DB_X86_R_R11D = 0x7b 129 | DB_X86_R_R11W = 0x7c 130 | DB_X86_R_R12 = 0x7d 131 | DB_X86_R_R12B = 0x7e 132 | DB_X86_R_R12D = 0x7f 133 | DB_X86_R_R12W = 0x80 134 | DB_X86_R_R13 = 0x81 135 | DB_X86_R_R13B = 0x82 136 | DB_X86_R_R13D = 0x83 137 | DB_X86_R_R13W = 0x84 138 | DB_X86_R_R14 = 0x85 139 | DB_X86_R_R14B = 0x86 140 | DB_X86_R_R14D = 0x87 141 | DB_X86_R_R14W = 0x88 142 | DB_X86_R_R15 = 0x89 143 | DB_X86_R_R15B = 0x8a 144 | DB_X86_R_R15D = 0x8b 145 | DB_X86_R_R15W = 0x8c 146 | DB_X86_R_R8 = 0x8d 147 | DB_X86_R_R8B = 0x8e 148 | DB_X86_R_R8D = 0x8f 149 | DB_X86_R_R8W = 0x90 150 | DB_X86_R_R9 = 0x91 151 | DB_X86_R_R9B = 0x92 152 | DB_X86_R_R9D = 0x93 153 | DB_X86_R_R9W = 0x94 154 | DB_X86_R_RAX = 0x95 155 | DB_X86_R_RBP = 0x96 156 | DB_X86_R_RBX = 0x97 157 | DB_X86_R_RCX = 0x98 158 | DB_X86_R_RDI = 0x99 159 | DB_X86_R_RDX = 0x9a 160 | DB_X86_R_RF = 0x9b 161 | DB_X86_R_RH = 0x9c 162 | DB_X86_R_RIP = 0x9d 163 | DB_X86_R_RIZ = 0x9e 164 | DB_X86_R_RL = 0x9f 165 | DB_X86_R_RSI = 0xa0 166 | DB_X86_R_RSP = 0xa1 167 | DB_X86_R_SF = 0xa2 168 | DB_X86_R_SI = 0xa3 169 | DB_X86_R_SIL = 0xa4 170 | DB_X86_R_SP = 0xa5 171 | DB_X86_R_SPL = 0xa6 172 | DB_X86_R_SS = 0xa7 173 | DB_X86_R_ST0 = 0xa8 174 | DB_X86_R_ST1 = 0xa9 175 | DB_X86_R_ST2 = 0xaa 176 | DB_X86_R_ST3 = 0xab 177 | DB_X86_R_ST4 = 0xac 178 | DB_X86_R_ST5 = 0xad 179 | DB_X86_R_ST6 = 0xae 180 | DB_X86_R_ST7 = 0xaf 181 | DB_X86_R_TF = 0xb0 182 | DB_X86_R_TR = 0xb1 183 | DB_X86_R_UE = 0xb2 184 | DB_X86_R_UM = 0xb3 185 | DB_X86_R_VIF = 0xb4 186 | DB_X86_R_VIP = 0xb5 187 | DB_X86_R_VM = 0xb6 188 | DB_X86_R_XMM0 = 0xb7 189 | DB_X86_R_XMM1 = 0xb8 190 | DB_X86_R_XMM10 = 0xb9 191 | DB_X86_R_XMM11 = 0xba 192 | DB_X86_R_XMM12 = 0xbb 193 | DB_X86_R_XMM13 = 0xbc 194 | DB_X86_R_XMM14 = 0xbd 195 | DB_X86_R_XMM15 = 0xbe 196 | DB_X86_R_XMM16 = 0xbf 197 | DB_X86_R_XMM17 = 0xc0 198 | DB_X86_R_XMM18 = 0xc1 199 | DB_X86_R_XMM19 = 0xc2 200 | DB_X86_R_XMM2 = 0xc3 201 | DB_X86_R_XMM20 = 0xc4 202 | DB_X86_R_XMM21 = 0xc5 203 | DB_X86_R_XMM22 = 0xc6 204 | DB_X86_R_XMM23 = 0xc7 205 | DB_X86_R_XMM24 = 0xc8 206 | DB_X86_R_XMM25 = 0xc9 207 | DB_X86_R_XMM26 = 0xca 208 | DB_X86_R_XMM27 = 0xcb 209 | DB_X86_R_XMM28 = 0xcc 210 | DB_X86_R_XMM29 = 0xcd 211 | DB_X86_R_XMM3 = 0xce 212 | DB_X86_R_XMM30 = 0xcf 213 | DB_X86_R_XMM31 = 0xd0 214 | DB_X86_R_XMM4 = 0xd1 215 | DB_X86_R_XMM5 = 0xd2 216 | DB_X86_R_XMM6 = 0xd3 217 | DB_X86_R_XMM7 = 0xd4 218 | DB_X86_R_XMM8 = 0xd5 219 | DB_X86_R_XMM9 = 0xd6 220 | DB_X86_R_YMM0 = 0xd7 221 | DB_X86_R_YMM1 = 0xd8 222 | DB_X86_R_YMM10 = 0xd9 223 | DB_X86_R_YMM11 = 0xda 224 | DB_X86_R_YMM12 = 0xdb 225 | DB_X86_R_YMM13 = 0xdc 226 | DB_X86_R_YMM14 = 0xdd 227 | DB_X86_R_YMM15 = 0xde 228 | DB_X86_R_YMM16 = 0xdf 229 | DB_X86_R_YMM17 = 0xe0 230 | DB_X86_R_YMM18 = 0xe1 231 | DB_X86_R_YMM19 = 0xe2 232 | DB_X86_R_YMM2 = 0xe3 233 | DB_X86_R_YMM20 = 0xe4 234 | DB_X86_R_YMM21 = 0xe5 235 | DB_X86_R_YMM22 = 0xe6 236 | DB_X86_R_YMM23 = 0xe7 237 | DB_X86_R_YMM24 = 0xe8 238 | DB_X86_R_YMM25 = 0xe9 239 | DB_X86_R_YMM26 = 0xea 240 | DB_X86_R_YMM27 = 0xeb 241 | DB_X86_R_YMM28 = 0xec 242 | DB_X86_R_YMM29 = 0xed 243 | DB_X86_R_YMM3 = 0xee 244 | DB_X86_R_YMM30 = 0xef 245 | DB_X86_R_YMM31 = 0xf0 246 | DB_X86_R_YMM4 = 0xf1 247 | DB_X86_R_YMM5 = 0xf2 248 | DB_X86_R_YMM6 = 0xf3 249 | DB_X86_R_YMM7 = 0xf4 250 | DB_X86_R_YMM8 = 0xf5 251 | DB_X86_R_YMM9 = 0xf6 252 | DB_X86_R_ZE = 0xf7 253 | DB_X86_R_ZF = 0xf8 254 | DB_X86_R_ZM = 0xf9 255 | DB_X86_R_ZMM0 = 0xfa 256 | DB_X86_R_ZMM1 = 0xfb 257 | DB_X86_R_ZMM10 = 0xfc 258 | DB_X86_R_ZMM11 = 0xfd 259 | DB_X86_R_ZMM12 = 0xfe 260 | DB_X86_R_ZMM13 = 0xff 261 | DB_X86_R_ZMM14 = 0x100 262 | DB_X86_R_ZMM15 = 0x101 263 | DB_X86_R_ZMM16 = 0x102 264 | DB_X86_R_ZMM17 = 0x103 265 | DB_X86_R_ZMM18 = 0x104 266 | DB_X86_R_ZMM19 = 0x105 267 | DB_X86_R_ZMM2 = 0x106 268 | DB_X86_R_ZMM20 = 0x107 269 | DB_X86_R_ZMM21 = 0x108 270 | DB_X86_R_ZMM22 = 0x109 271 | DB_X86_R_ZMM23 = 0x10a 272 | DB_X86_R_ZMM24 = 0x10b 273 | DB_X86_R_ZMM25 = 0x10c 274 | DB_X86_R_ZMM26 = 0x10d 275 | DB_X86_R_ZMM27 = 0x10e 276 | DB_X86_R_ZMM28 = 0x10f 277 | DB_X86_R_ZMM29 = 0x110 278 | DB_X86_R_ZMM3 = 0x111 279 | DB_X86_R_ZMM30 = 0x112 280 | DB_X86_R_ZMM31 = 0x113 281 | DB_X86_R_ZMM4 = 0x114 282 | DB_X86_R_ZMM5 = 0x115 283 | DB_X86_R_ZMM6 = 0x116 284 | DB_X86_R_ZMM7 = 0x117 285 | DB_X86_R_ZMM8 = 0x118 286 | DB_X86_R_ZMM9 = 0x119 287 | 288 | 289 | # create Register to Name translation 290 | x86regkey = "DB_X86_R_" 291 | x86allreg = [ globals()[x] for x in globals() if x.startswith(x86regkey) ] 292 | x86name2reg = { x.lower()[len(x86regkey):] : globals()[x] for x in globals() if x.startswith(x86regkey)} 293 | x86reg2name = { x86name2reg[x] : x for x in x86name2reg } 294 | assert(len(x86reg2name) == len(x86name2reg)) 295 | 296 | -------------------------------------------------------------------------------- /other_tools/side_utils.py: -------------------------------------------------------------------------------- 1 | 2 | # windbg helper scripts 3 | 4 | # needs to handle unions and multi level pieces 5 | def parse_dt(dtstr): 6 | out = [] 7 | last_lvl = [0] 8 | #TODO handle unions when we handle nested types 9 | for l in dtstr.split('\n'): 10 | if not l.strip().startswith("+0x"): 11 | #TODO 12 | continue 13 | if not l.startswith(' '): 14 | print("line =", l) 15 | #TODO 16 | raise NotImplementedError("Need to implement nested type dt parsing") 17 | ll = l.split() 18 | if ll[0].startswith('+0x'): 19 | out.append((int(ll[0][3:],16), ll[1])) 20 | 21 | if out[0][0] != 0: 22 | raise TypeError("dtstr does not have a type that starts at zero?") 23 | return out 24 | 25 | def parse_db(dbstr): 26 | out = b"" 27 | for l in dbstr.split('\n'): 28 | out += bytes.fromhex(l.split(" ")[1].replace('-', '')) 29 | return out 30 | 31 | def gen_sym_type(dtstr, typesize, addr, name): 32 | #TODO allow addr to be a variable name 33 | dt = parse_dt(dtstr) 34 | out = "" 35 | for i in range(len(dt)): 36 | e = typesize 37 | if i != (len(dt)-1): 38 | e = dt[i+1][0] 39 | sz = e - dt[i][0] 40 | # hopefully it fits in a MemoryAccess size 41 | out += f"ctx.symbolizeMemory(MemoryAccess( {hex(addr + dt[i][0])} , {hex(sz)} ), \"{name + '.' + dt[i][1]}\")\n" 42 | return out 43 | 44 | def gen_mem_init(dtstr, dbstr, addr, name=""): 45 | #TODO allow addr to be a variable name 46 | dt = parse_dt(dtstr) 47 | db = parse_db(dbstr) 48 | typesize = len(db) 49 | 50 | out = "" 51 | for i in range(len(dt)): 52 | e = typesize 53 | if i != (len(dt)-1): 54 | e = dt[i+1][0] 55 | s = dt[i][0] 56 | 57 | out += "ctx.api.setConcreteMemoryAreaValue(\n" 58 | out += f" {hex(addr + dt[i][0])}, # {name + '.' + dt[i][1]}\n" 59 | out += f" bytes.fromhex(\"{db[s:e].hex()}\")\n" 60 | out += ")\n" 61 | 62 | return out 63 | 64 | def gen_commented_memdmp(dtstr, dbstr): 65 | dt = parse_dt(dtstr) 66 | db = parse_db(dbstr) 67 | 68 | # cut up where we have 69 | out = "" 70 | for d in dt[::-1]: 71 | b = db[d[0]:] 72 | bl = [ b[i:i+0x10] for i in range(0x0, len(b), 0x10) ] 73 | first = True 74 | line = "" 75 | for bi in bl: 76 | line += str(bi) 77 | line += " + " 78 | if first: 79 | line += " # +" + hex(d[0]) + " ." + d[1] 80 | line += "\n" 81 | first = False 82 | 83 | db = db[:d[0]] 84 | out = line + out 85 | 86 | out += "b\"\"\n" 87 | return out 88 | 89 | 90 | import ctypes 91 | import struct 92 | 93 | def QuerySysInfo(infoclass=0x4d): 94 | retsz = ctypes.c_ulong(0) 95 | retszptr = ctypes.pointer(retsz) 96 | ctypes.windll.ntdll.NtQuerySystemInformation(infoclass, 0, 0, retszptr) 97 | buf = (ctypes.c_byte * retsz.value)() 98 | ctypes.windll.ntdll.NtQuerySystemInformation(infoclass, buf, len(buf), retszptr) 99 | return bytes(buf) 100 | 101 | def quickhex(chunk): 102 | spced = ' '.join([chunk[i:i+1].hex() for i in range(len(chunk))]) 103 | fourd = ' '.join([spced[i:i+(4*3)] for i in range(0, len(spced), (4*3))]) 104 | sxtnd = '\n'.join([fourd[i:i+(((4*3)+2)*4)] for i in range(0, len(fourd), (((4*3)+2)*4))]) 105 | print(sxtnd) 106 | 107 | def parseModInfoEx(infobuf): 108 | #TODO 109 | fmt = "> pgshft 136 | ep = (stop-1) >> pgshft 137 | 138 | while sp <= ep: 139 | 140 | # 9 bits of PML4 index 141 | pml4 = (sp >> (39 - pgshft)) & 0x1ff 142 | # 9 bits of PDPTE 143 | pdpte = (sp >> (30 - pgshft)) & 0x1ff 144 | pde = (sp >> (21 - pgshft)) & 0x1ff 145 | pte = (sp >> (12 - pgshft)) & 0x1ff 146 | 147 | pdpte_table = walkentry(tableaddr + (8 * pml4), sp, prot) 148 | pde_table = walkentry(pdpte_table + (8 * pdpte), sp, prot) 149 | pte_table = walkentry(pde_table + (8 * pde), sp, prot) 150 | walkentry(pte_table + (8 * pte), sp, prot, True) 151 | 152 | sp += 1 153 | 154 | def printpagetable(tab, depth=0): 155 | if depth == 0: 156 | print(f"pml4 table @ {tab:x}") 157 | 158 | depth += 1 159 | 160 | t = uc.mem_read(tab, 0x1000) 161 | for i in range(0, 0x1000, 8): 162 | e = struct.unpack(" 2 | #define WIN32_LEAN_AND_MEAN 3 | #include 4 | #include 5 | 6 | 7 | void aa(int v) 8 | { 9 | (void)v; 10 | int* null = 0; 11 | 12 | __try 13 | { 14 | puts("a0"); 15 | 16 | __try 17 | { 18 | puts("a1"); 19 | *null = 1; 20 | puts("NEVER"); 21 | } 22 | __finally 23 | { 24 | puts("a3"); 25 | } 26 | puts("NEVER"); 27 | } 28 | __except ((puts("a2"),EXCEPTION_EXECUTE_HANDLER)) 29 | { 30 | puts("a4"); 31 | } 32 | puts("a5/5"); 33 | } 34 | 35 | void bb_1(int v) 36 | { 37 | int* null = 0; 38 | 39 | __try 40 | { 41 | if (v == 13) { 42 | puts("NEVER"); 43 | *null = v; 44 | } 45 | } 46 | __except (EXCEPTION_EXECUTE_HANDLER) 47 | { 48 | puts("NEVER"); 49 | } 50 | 51 | 52 | if (v == 12) { 53 | puts("b2"); 54 | *null = v; 55 | } 56 | 57 | __try 58 | { 59 | bb_1(v + 1); 60 | puts("NEVER"); 61 | } 62 | __except (((v > 12) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)) 63 | { 64 | puts("NEVER"); 65 | } 66 | } 67 | 68 | void bb_0(int v) 69 | { 70 | __try 71 | { 72 | puts("b1"); 73 | bb_1(v + 1); 74 | puts("NEVER"); 75 | } 76 | __finally 77 | { 78 | puts("b5"); 79 | } 80 | } 81 | 82 | int bb_filt(unsigned int code, struct _EXCEPTION_POINTERS* ep) 83 | { 84 | puts("b3"); 85 | if (code == EXCEPTION_ACCESS_VIOLATION) { 86 | puts("b4"); 87 | return EXCEPTION_EXECUTE_HANDLER; 88 | } 89 | return EXCEPTION_CONTINUE_EXECUTION; 90 | } 91 | 92 | void bb(int v) 93 | { 94 | puts("b0"); 95 | __try 96 | { 97 | bb_0(v + 1); 98 | } 99 | __except (bb_filt(GetExceptionCode(), GetExceptionInformation())) 100 | { 101 | puts("b6"); 102 | } 103 | puts("b7/7"); 104 | } 105 | 106 | int cc_filt(unsigned int code, struct _EXCEPTION_POINTERS* ep) 107 | { 108 | switch (code) { 109 | case EXCEPTION_ACCESS_VIOLATION: 110 | switch (ep->ExceptionRecord->ExceptionInformation[0]) { 111 | case 0: // read 112 | puts("c0"); 113 | break; 114 | case 1: // write 115 | puts("c1"); 116 | break; 117 | case 8: // execute 118 | puts("c2"); 119 | break; 120 | } 121 | break; 122 | case EXCEPTION_ILLEGAL_INSTRUCTION: 123 | puts("c3"); 124 | break; 125 | case EXCEPTION_INT_DIVIDE_BY_ZERO: 126 | puts("c4"); 127 | break; 128 | case EXCEPTION_BREAKPOINT: 129 | puts("c5"); 130 | break; 131 | case EXCEPTION_SINGLE_STEP: 132 | puts("c6"); 133 | break; 134 | case EXCEPTION_STACK_OVERFLOW: 135 | puts("c7"); 136 | break; 137 | default: 138 | return EXCEPTION_CONTINUE_SEARCH; 139 | } 140 | return EXCEPTION_EXECUTE_HANDLER; 141 | } 142 | 143 | extern char ROSTR[]; 144 | extern void cc_asm_ii(void); 145 | extern void cc_asm_ss(void); 146 | 147 | void cc(int v) 148 | { 149 | int* addr; 150 | int t; 151 | if (v < 0) { 152 | cc(v); 153 | } 154 | v = 0; 155 | __try { 156 | while (1) { 157 | __try 158 | { 159 | switch (v) { 160 | case 0: // read violation 161 | addr = 0x30; 162 | v = *addr; 163 | break; 164 | case 1: // write violation 165 | addr = &ROSTR; 166 | *addr = 'T'; 167 | 168 | break; 169 | case 2: // execute violation 170 | addr = (int*)"ABCDEFG"; 171 | ((void(__stdcall*)(void))addr)(); 172 | break; 173 | case 3: // illegal inst 174 | cc_asm_ii(); 175 | break; 176 | case 4: // div 0 177 | t = v - 4; 178 | v = v / t; 179 | break; 180 | case 5: // breakpoint 181 | __debugbreak(); 182 | break; 183 | case 6: // singlestep 184 | cc_asm_ss(); 185 | break; 186 | case 7: // stack overflow 187 | cc(-1); 188 | break; 189 | default: 190 | puts("NEVER"); 191 | __leave; 192 | case 8: 193 | puts("c8"); 194 | return; 195 | } 196 | } 197 | __except (cc_filt(GetExceptionCode(), GetExceptionInformation())) 198 | { 199 | v++; 200 | continue; 201 | } 202 | puts("NEVER"); 203 | printf("Failed on v = %d\n", v); 204 | v++; 205 | } 206 | } 207 | __finally 208 | { 209 | puts("c9/9"); 210 | } 211 | puts("NEVER"); 212 | } 213 | 214 | int dd_filt(unsigned int code, struct _EXCEPTION_POINTERS* ep) 215 | { 216 | PCONTEXT ctx; 217 | if (code == EXCEPTION_BREAKPOINT) { 218 | ctx = ep->ContextRecord; 219 | ctx->Rip += 1; // step past 0xCC 220 | return EXCEPTION_CONTINUE_EXECUTION; 221 | } 222 | 223 | return EXCEPTION_EXECUTE_HANDLER; 224 | } 225 | 226 | void dd(int v) 227 | { 228 | int* addr = 0; 229 | 230 | __try 231 | { 232 | v = *addr; 233 | } 234 | __except ((puts("d0"), EXCEPTION_EXECUTE_HANDLER)) 235 | { 236 | puts("d1"); 237 | } 238 | 239 | for (v = 0; v < 6; v++) { 240 | __try 241 | { 242 | addr = (int*)_alloca(sizeof(int)); 243 | *addr = v; 244 | } 245 | __except (EXCEPTION_EXECUTE_HANDLER) 246 | { 247 | puts("NEVER"); 248 | } 249 | } 250 | 251 | __try 252 | { 253 | __debugbreak(); 254 | 255 | puts("d2"); 256 | 257 | **((int**)addr) = v; 258 | } 259 | __except (dd_filt(GetExceptionCode(), GetExceptionInformation())) 260 | { 261 | puts("d3"); 262 | } 263 | 264 | puts("d4/4"); 265 | } 266 | 267 | // include chained calls with chained execption handlers 268 | // include stack allocation later in the function 269 | // turn off inlineing 270 | // funaly, except, filters that continue, search, and handle 271 | // access violation invalid mem read/write, access violation write to ro, access violation execute nx, data type misalignments, illegal instructions, int / 0, int ovf, float / 0, flowt ovf und, brk, single step 272 | 273 | int main(int argc, char* argv[]) 274 | { 275 | puts("START"); 276 | aa(0); // simple nested except and finally 277 | bb(1); // nested across multiple functions 278 | cc(2); // a bunch of different exception types 279 | dd(3); // strange function with mid function stack allocations 280 | puts("DONE"); 281 | } -------------------------------------------------------------------------------- /tester/Test2/Test2.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30204.135 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Test2", "Test2.vcxproj", "{5561B6CA-3982-4676-B267-090A6ED9FA6E}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {5561B6CA-3982-4676-B267-090A6ED9FA6E}.Debug|x64.ActiveCfg = Debug|x64 17 | {5561B6CA-3982-4676-B267-090A6ED9FA6E}.Debug|x64.Build.0 = Debug|x64 18 | {5561B6CA-3982-4676-B267-090A6ED9FA6E}.Debug|x86.ActiveCfg = Debug|Win32 19 | {5561B6CA-3982-4676-B267-090A6ED9FA6E}.Debug|x86.Build.0 = Debug|Win32 20 | {5561B6CA-3982-4676-B267-090A6ED9FA6E}.Release|x64.ActiveCfg = Release|x64 21 | {5561B6CA-3982-4676-B267-090A6ED9FA6E}.Release|x64.Build.0 = Release|x64 22 | {5561B6CA-3982-4676-B267-090A6ED9FA6E}.Release|x86.ActiveCfg = Release|Win32 23 | {5561B6CA-3982-4676-B267-090A6ED9FA6E}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {AD39CB29-9A6B-4B44-8358-64777FF21482} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /tester/Test2/Test2.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | Win32Proj 24 | {5561b6ca-3982-4676-b267-090a6ed9fa6e} 25 | Test2 26 | 10.0 27 | 28 | 29 | 30 | Application 31 | true 32 | v142 33 | Unicode 34 | 35 | 36 | Application 37 | false 38 | v142 39 | true 40 | Unicode 41 | 42 | 43 | Application 44 | true 45 | v142 46 | Unicode 47 | 48 | 49 | Application 50 | false 51 | v142 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | true 76 | 77 | 78 | false 79 | 80 | 81 | true 82 | 83 | 84 | false 85 | 86 | 87 | 88 | Level3 89 | true 90 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 91 | true 92 | 93 | 94 | Console 95 | true 96 | 97 | 98 | 99 | 100 | Level3 101 | true 102 | true 103 | true 104 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 105 | true 106 | 107 | 108 | Console 109 | true 110 | true 111 | true 112 | 113 | 114 | 115 | 116 | Level3 117 | true 118 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 119 | true 120 | OnlyExplicitInline 121 | true 122 | CompileAsC 123 | 124 | 125 | Console 126 | true 127 | 128 | 129 | 130 | 131 | Level3 132 | true 133 | true 134 | true 135 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 136 | true 137 | OnlyExplicitInline 138 | Disabled 139 | false 140 | CompileAsC 141 | 142 | 143 | Console 144 | true 145 | true 146 | true 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /tester/Test2/Test2.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | 23 | 24 | Source Files 25 | 26 | 27 | -------------------------------------------------------------------------------- /tester/Test2/asm_code.asm: -------------------------------------------------------------------------------- 1 | 2 | .CONST 3 | PUBLIC ROSTR 4 | ROSTR: 5 | DB "THIS IS READ ONLY", 0 6 | 7 | .CODE 8 | 9 | cc_asm_ii PROC PUBLIC FRAME 10 | .ENDPROLOG 11 | ud2 12 | ret 13 | cc_asm_ii ENDP 14 | 15 | cc_asm_ss PROC PUBLIC FRAME 16 | .ENDPROLOG 17 | DB 0F1h 18 | ret 19 | cc_asm_ss ENDP 20 | 21 | END -------------------------------------------------------------------------------- /tester/tester.S: -------------------------------------------------------------------------------- 1 | .intel_syntax noprefix 2 | .global test_asm 3 | .global get_realpid 4 | 5 | dat_1: 6 | .quad 0x4141414141414141, 0, 0, 0x4040404040404040 7 | 8 | test_asm: 9 | lea rcx, [dat_1] 10 | mov rdx, 2 11 | mov rax, [rcx + (rdx * 8) + 8] 12 | 13 | mov rax, dat_1 14 | 15 | ret 16 | 17 | get_realpid: 18 | 19 | xor rax, rax 20 | xor rcx, rcx 21 | mov rax, gs:[rax + 0x850 + (rcx * 2)] 22 | ret 23 | 24 | 25 | -------------------------------------------------------------------------------- /tester/tester.c: -------------------------------------------------------------------------------- 1 | #define WIN32_LEAN_AND_MEAN 2 | #include 3 | #include 4 | #include 5 | 6 | extern void test_asm(void); 7 | extern uint32_t get_realpid(void); 8 | 9 | int test_rec(int v) 10 | { 11 | static int stat = 42; 12 | 13 | v = v + v + stat; 14 | stat += 0x1; 15 | if (v & 1) { 16 | v = test_rec(v); 17 | } else if (v < 0) { 18 | v -= test_rec(v); 19 | } 20 | 21 | return v + stat; 22 | } 23 | 24 | int main(int argc, char** argv) 25 | { 26 | uint32_t pid = 0; 27 | uint32_t rpid = 0; 28 | 29 | if (argc > 1) { 30 | pid = GetCurrentProcessId(); 31 | printf("PID = %d\n", pid); 32 | printf("argv1 = %s\n", argv[1]); 33 | } 34 | printf("GLE = %ld\n", GetLastError()); 35 | 36 | // test complex memory access based off gs 37 | rpid = get_realpid(); 38 | if (pid != rpid) { 39 | 40 | printf("PIDs don't match!"); 41 | } 42 | //TODO test memory access with no registers 43 | // test call chain 44 | printf("Rec got us %d\n", test_rec(argc)); 45 | //TODO 46 | // shows up as immediate? if so, how to handle this case? 47 | test_asm(); 48 | 49 | return 0; 50 | } 51 | --------------------------------------------------------------------------------