├── dobby
├── __init__.py
├── dobby_remote.py
├── dobby_const.py
├── reversetaint.py
├── dobby_types.py
├── x86const.py
├── interface.py
├── dobby_unicorn.py
├── dobby_triton.py
├── winsys.py
└── dobby.py
├── .gitignore
├── tester
├── Makefile
├── Test2
│ ├── asm_code.asm
│ ├── Test2.vcxproj.filters
│ ├── Test2.sln
│ ├── Test2.c
│ ├── .gitignore
│ └── Test2.vcxproj
├── tester.S
└── tester.c
├── LICENCE.txt
├── beepexample.py
├── README.md
└── other_tools
├── side_utils.py
└── uni_ident_test.py
/dobby/__init__.py:
--------------------------------------------------------------------------------
1 | from .dobby import *
2 |
3 | __all__ = ["dobby", "dobby_const", "dobby_types"]
4 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/tester/Makefile:
--------------------------------------------------------------------------------
1 | CC=x86_64-w64-mingw32-gcc-win32
2 | CFLAGS=-Wall -Werror -Wextra -Og
3 |
4 | all: tester.exe
5 |
6 | tester.exe: tester.c tester.S
7 | $(CC) $(CFLAGS) -o $@ $^
8 |
9 | clean:
10 | rm -f tester.exe
11 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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_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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 = "
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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/other_tools/uni_ident_test.py:
--------------------------------------------------------------------------------
1 | from unicorn import *
2 | from unicorn.x86_const import *
3 | import struct
4 |
5 |
6 | uc = Uc(UC_ARCH_X86, UC_MODE_64)
7 |
8 | def insHook(emu, addr, sz, user_data):
9 | print(f"HOOK: Code @ {addr:x} ({sz:x})")
10 |
11 | def rwHook(emu, access, addr, sz, val, user_data):
12 | print(f"HOOK: mem {access} @ {addr:x} ({sz:x})")
13 |
14 | def invalMemHook(emu, access, addr, sz, val, user_data):
15 | print(f"HOOK: invalid mem {access} @ {addr:x} ({sz:x})")
16 | return False
17 |
18 | def invalInsHook(emu, user_data):
19 | print(f"HOOK: invalid instruction")
20 | return False
21 |
22 | def intrHook(emu, intno, user_data):
23 | print(f"HOOK: interrupt {intno:x}")
24 |
25 | uc.hook_add(UC_HOOK_CODE, insHook, None, 0, 0xffffffffffffffff)
26 | uc.hook_add(UC_HOOK_MEM_READ | UC_HOOK_MEM_WRITE, rwHook, None)
27 | uc.hook_add(UC_HOOK_MEM_INVALID, invalMemHook, None)
28 | uc.hook_add(UC_HOOK_INSN_INVALID, invalInsHook, None)
29 | uc.hook_add(UC_HOOK_INTR, intrHook, None)
30 | # set up page table for ident mapping
31 |
32 | tableaddr = 0x00008f0000000000
33 | nexttable = tableaddr
34 | pgshft = 12
35 | entryaddrmask = 0x0000fffffffff000
36 |
37 | uc.reg_write(UC_X86_REG_CR3, tableaddr)
38 | uc.mem_map(tableaddr, 1 << pgshft, UC_PROT_READ)
39 | nexttable += 1 << pgshft
40 |
41 | cr0val = 0
42 | cr0val |= 1 << 0 # Protected Mode
43 | cr0val |= 0 << 1 # Monitor Coprocessor
44 | cr0val |= 0 << 2 # Emulation Mode
45 | cr0val |= 1 << 3 # Task Switched ?
46 | cr0val |= 1 << 4 # Extension Type ?
47 | cr0val |= 1 << 5 # Numeric Error
48 | cr0val |= 1 << 16 # Write Protect
49 | cr0val |= 0 << 18 # Alignment Mask
50 | cr0val |= 0 << 29 # Not Write-through
51 | cr0val |= 0 << 30 # Cache Disable
52 | cr0val |= 1 << 31 # Paging Enabled
53 | uc.reg_write(UC_X86_REG_CR0, cr0val)
54 |
55 | cr4val = 0
56 | cr4val |= 0 << 0 # VME
57 | cr4val |= 0 << 1 # PVI
58 | cr4val |= 0 << 2 # TSD
59 | cr4val |= 0 << 3 # DE
60 | cr4val |= 0 << 4 # PSE
61 | cr4val |= 1 << 5 # PAE
62 | cr4val |= 0 << 6 # MCE
63 | cr4val |= 1 << 7 # PGE
64 | cr4val |= 1 << 8 # PCE
65 | cr4val |= 1 << 9 # OSFXSR
66 | cr4val |= 0 << 10 # OSXMMEXCPT
67 | cr4val |= 1 << 11 # UMIP
68 | cr4val |= 1 << 12 # LA57
69 | cr4val |= 0 << 13 # VMXE
70 | cr4val |= 0 << 14 # SMXE
71 | cr4val |= 1 << 17 # PCIDE
72 | cr4val |= 0 << 18 # OSXSAVE
73 | cr4val |= 1 << 20 # SMEP
74 | cr4val |= 1 << 21 # SMAP
75 | cr4val |= 0 << 22 # PKE
76 | cr4val |= 0 << 23 # CET (gross)
77 | cr4val |= 0 << 24 # PKS
78 | uc.reg_write(UC_X86_REG_CR4, cr4val)
79 |
80 | def virt2phys(virt):
81 | return virt & 0x0000ffffffffffff
82 |
83 | def phys2virt(phys):
84 | if phys & (1<<47):
85 | return phys | 0xffff000000000000
86 | else:
87 | return phys
88 |
89 | def walkentry(eaddr, sp, prot, isend=False, doalloc=True):
90 | eaddr = virt2phys(eaddr)
91 | entry = struct.unpack("> 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 |
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 |
--------------------------------------------------------------------------------
/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/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/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/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(" 1 or (add_nul == 1 and numbytes != 0):
203 | srcval += b"\x00\x00"
204 |
205 | if len(srcval) == 0:
206 | # null buffer, 0 len
207 | ctx.setu16(dst + 0x0, 0)
208 | ctx.setu16(dst + 0x2, 0)
209 | ctx.setu64(dst + 0x8, 0)
210 | else:
211 | dstbuf = ctx.alloc(len(srcval))
212 | ctx.setMemVal(dstbuf, srcval)
213 | ctx.setu16(dst + 0x0, numbytes)
214 | ctx.setu16(dst + 0x2, numbytes)
215 | ctx.setu64(dst + 0x8, dstbuf)
216 |
217 | s = str(srcval, "UTF_16_LE")
218 |
219 | ctx.doRet(0)
220 |
221 | print(f"RtlDuplicateUnicodeString : \"{s}\"")
222 | return HookRet.OP_DONE_INS
223 |
224 | def IoCreateFileEx_hook(hook, ctx, addr, sz, op, provider):
225 | h = ctx.active.globstate["nexthandle"]
226 | ctx.active.globstate["nexthandle"] += 1
227 |
228 | phandle = ctx.getRegVal(DB_X86_R_RCX)
229 | oa = ctx.getRegVal(DB_X86_R_R8)
230 | iosb = ctx.getRegVal(DB_X86_R_R9)
231 | sp = ctx.getRegVal(DB_X86_R_RSP)
232 | disp = ctx.getu32(sp + 0x28 + (3 * 8))
233 | driverctx = ctx.getu64(sp + 0x28 + (10 * 8))
234 |
235 | if ctx.issym and ctx.isSymbolizedMemory(oa+0x10, 8):
236 | print("Unicode string in object attributes is symbolized")
237 | return HookRet.FORCE_STOP_INS
238 | namep = ctx.getu64(oa+0x10)
239 | name = readUnicodeStr(ctx, namep)
240 |
241 | ctx.setu64(phandle, h)
242 |
243 | # set up iosb
244 | info = 0
245 | disp_str = ""
246 | if disp == 0:
247 | disp_str = "FILE_SUPERSEDE"
248 | info = 0 # FILE_SUPERSEDED
249 | elif disp == 1:
250 | disp_str = "FILE_OPEN"
251 | info = 1 # FILE_OPENED
252 | elif disp == 2:
253 | disp_str = "FILE_CREATE"
254 | info = 2 # FILE_CREATED
255 | elif disp == 3:
256 | disp_str = "FILE_OPEN_IF"
257 | info = 2 # FILE_CREATED
258 | elif disp == 4:
259 | disp_str = "FILE_OVERWRITE_IF"
260 | info = 3 # FILE_OVERWRITTEN
261 | elif disp == 5:
262 | disp_str = "FILE_OVERWRITE_IF"
263 | info = 2 # FILE_CREATED
264 | ctx.setu64(iosb, 0)
265 | ctx.setu64(iosb+8, info)
266 |
267 | objinfo = (h, name, disp, driverctx, provider)
268 | ctx.active.globstate["handles"][h] = objinfo
269 |
270 | ctx.doRet(0)
271 |
272 | print(f"IoCreateFileEx: \"{name}\" {disp_str} = {h}")
273 |
274 | return HookRet.STOP_INS
275 |
276 | def IoCreateDevice_hook(hook, ctx, addr, sz, op, provider):
277 | drvobj = ctx.getRegVal(DB_X86_R_RCX)
278 | easz = ctx.getRegVal(DB_X86_R_RDX)
279 | dname = ctx.getRegVal(DB_X86_R_R8)
280 | dtype = ctx.getRegVal(DB_X86_R_R9)
281 | sp = ctx.getRegVal(DB_X86_R_RSP)
282 | char = ctx.getu32(sp + 0x28 + (0 * 8))
283 | exclusive = ctx.getu64(sp + 0x28 + (1 * 8))
284 | outdev = ctx.getu64(sp + 0x28 + (2 * 8))
285 |
286 | name = readUnicodeStr(ctx, dname)
287 | print(f"Driver is trying to create device {name}")
288 |
289 | return HookRet.FORCE_STOP_INS
290 |
291 | def ZwClose_hook(hook, ctx, addr, sz, op, provider):
292 | h = ctx.getRegVal(DB_X86_R_RCX)
293 | name = ctx.active.globstate["handles"][h][1]
294 | del ctx.active.globstate["handles"][h]
295 | print(f"Closed File {h} ({name})")
296 | ctx.doRet(0)
297 | return HookRet.OP_DONE_INS
298 |
299 | def ZwWriteFile_hook(hook, ctx, addr, sz, op, provider):
300 | h = ctx.getRegVal(DB_X86_R_RCX)
301 | evt = ctx.getRegVal(DB_X86_R_RDX)
302 | apcrou = ctx.getRegVal(DB_X86_R_R8)
303 | apcctx = ctx.getRegVal(DB_X86_R_R9)
304 | sp = ctx.getRegVal(DB_X86_R_RSP)
305 | iosb = ctx.getu64(sp + 0x28 + (0 * 8))
306 | buf = ctx.getu64(sp + 0x28 + (1 * 8))
307 | blen = ctx.getu32(sp + 0x28 + (2 * 8))
308 | poff = ctx.getu64(sp + 0x28 + (3 * 8))
309 |
310 | if apcrou != 0:
311 | print("ZwWriteFile with apcroutine!")
312 | return HookRet.FORCE_STOP_INS
313 |
314 | name = ctx.active.globstate["handles"][h][1]
315 |
316 | off = 0
317 | if poff != 0:
318 | off = ctx.getu64(poff)
319 |
320 | ctx.setu64(iosb, 0)
321 | ctx.setu64(iosb+8, blen)
322 | ctx.doRet(0)
323 |
324 | print(f"ZwWriteFile: {h}({name})) {hex(blen)} bytes{(' at offset ' + hex(off)) if poff != 0 else ''}")
325 | ctx.printMem(buf, blen)
326 |
327 | return HookRet.OP_DONE_INS
328 |
329 | def ZwReadFile_hook(hook, ctx, addr, sz, op, provider):
330 | h = ctx.getRegVal(DB_X86_R_RCX)
331 | sp = ctx.getRegVal(DB_X86_R_RSP)
332 | iosb = ctx.getu64(sp + 0x28 + (0 * 8))
333 | buf = ctx.getu64(sp + 0x28 + (1 * 8))
334 | blen = ctx.getu32(sp + 0x28 + (2 * 8))
335 | poff = ctx.getu64(sp + 0x28 + (3 * 8))
336 | print(f"ZwReadFile: {h}({name}) {hex(blen)} into {hex(buf)}")
337 | if poff:
338 | offval = ctx.getu64(poff)
339 | print(f"Read is at offset {hex(offval)}")
340 |
341 | ctx.doRet(0)
342 | return HookRet.FORCE_STOP_INS
343 |
344 | def ZwFlushBuffersFile_hook(hook, ctx, addr, sz, op, provider):
345 | h = ctx.getRegVal(DB_X86_R_RCX)
346 | iosb = ctx.getRegVal(DB_X86_R_RDX)
347 | ctx.setu64(iosb, 0)
348 | ctx.setu64(iosb+8, 0)
349 |
350 | print(f"ZwFlushBuffersFile {h}")
351 | ctx.doRet(0)
352 |
353 | return HookRet.DONE_INS
354 |
355 | def KeAreAllApcsDisabled_hook(hook, ctx, addr, sz, op, provider):
356 | # checks:
357 | # currentthread.SpecialAcpDisable
358 | # KeAreInterruptsEnabled (IF in rflags)
359 | # cr8 == 0
360 | #TODO do all the above checks
361 | cr8val = ctx.getRegVal(DB_X86_R_CR8)
362 | ie = ((ctx.getRegVal(DB_X86_R_EFLAGS) >> 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/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 |
--------------------------------------------------------------------------------