From 125888cd602a28207eac1cbf0f501abc4fabd54b Mon Sep 17 00:00:00 2001 From: Clement Rouault Date: Wed, 13 Jul 2016 10:10:12 +0200 Subject: [PATCH] Add sample / test for memory breakpoint and single step --- README.md | 102 +++++++++++++- samples/debugger_membp_singlestep.py | 8 +- samples/local_debugger.py | 49 +++---- windows/test/test_debugger.py | 192 ++++++++++++++++++++++++++- 4 files changed, 312 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 01e5b99..1be6ff1 100644 --- a/README.md +++ b/README.md @@ -156,14 +156,112 @@ KeyValue(name='MYQWORD', value=123456789987654321L, type=11) # Explore Values >>> tstkey.values [KeyValue(name='MYQWORD', value=123456789987654321L, type=11), KeyValue(name='VALUE', value=u'a_value_for_my_key', type=1)] +``` + +### Debugger + +PythonForWindows provides a standard debugger to debug other processes. + +```python +import windows +import windows.debug +import windows.test +import windows.native_exec.simple_x86 as x86 + +from windows.test import pop_calc_32 +from windows.generated_def import EXCEPTION_ACCESS_VIOLATION + +class MyDebugger(windows.debug.Debugger): + def on_exception(self, exception): + code = exception.ExceptionRecord.ExceptionCode + addr = exception.ExceptionRecord.ExceptionAddress + print("Got exception {0} at 0x{1:x}".format(code, addr)) + if code == EXCEPTION_ACCESS_VIOLATION: + print("Access Violation: kill target process") + self.current_process.exit() + +calc = windows.test.pop_calc_32(dwCreationFlags=DEBUG_PROCESS) +d = MyDebugger(calc) +calc.execute(x86.assemble("int3; mov [0x42424242], EAX; ret")) +d.loop() + +## Ouput ## +Got exception EXCEPTION_BREAKPOINT(0x80000003L) at 0x77e13c7d +Got exception EXCEPTION_BREAKPOINT(0x80000003L) at 0x230000 +Got exception EXCEPTION_ACCESS_VIOLATION(0xc0000005L) at 0x230001 +Access Violation: kill target process +``` + +The debugger handles + +* Standard breakpoint ``int3`` +* Hardware Execution breakpoint ``DrX`` +* Memory breakpoint ``virtual protect`` +#### LocalDebugger + +You can also debug your own process (or debug a process by injection) via the LocalDebugger. + +The LocalDebugger is an abstraction around Vectored Exception Handler (VEH) + +```python +import windows +from windows.generated_def.winstructs import * +import windows.native_exec.simple_x86 as x86 + +class SingleSteppingDebugger(windows.debug.LocalDebugger): + SINGLE_STEP_COUNT = 4 + def on_exception(self, exc): + code = self.get_exception_code() + context = self.get_exception_context() + print("EXCEPTION !!!! Got a {0} at 0x{1:x}".format(code, context.pc)) + self.SINGLE_STEP_COUNT -= 1 + if self.SINGLE_STEP_COUNT: + return self.single_step() + return EXCEPTION_CONTINUE_EXECUTION + +class RewriteBreakpoint(windows.debug.HXBreakpoint): + def trigger(self, dbg, exc): + context = dbg.get_exception_context() + print("GOT AN HXBP at 0x{0:x}".format(context.pc)) + # Rewrite the infinite loop with 2 nop + windows.current_process.write_memory(self.addr, "\x90\x90") + # Ask for a single stepping + return dbg.single_step() + + +d = SingleSteppingDebugger() +# Infinite loop + nop + ret +code = x86.assemble("label :begin; jmp :begin; nop; ret") +func = windows.native_exec.create_function(code, [PVOID]) +print("Code addr = 0x{0:x}".format(func.code_addr)) +# Create a thread that will infinite loop +t = windows.current_process.create_thread(func.code_addr, 0) +# Add a breakpoint on the infitine loop +d.add_bp(RewriteBreakpoint(func.code_addr)) +t.wait() +print("Done!") + +## Output ## + +Code addr = 0x6a0002 +GOT AN HXBP at 0x6a0002 +EXCEPTION !!!! Got a EXCEPTION_SINGLE_STEP(0x80000004L) at 0x6a0003 +EXCEPTION !!!! Got a EXCEPTION_SINGLE_STEP(0x80000004L) at 0x6a0004 +EXCEPTION !!!! Got a EXCEPTION_SINGLE_STEP(0x80000004L) at 0x6a0005 +EXCEPTION !!!! Got a EXCEPTION_SINGLE_STEP(0x80000004L) at 0x770c7c04 +Done! ``` + +The local debugger handles + +* Standard breakpoint ``int3`` +* Hardware Execution breakpoint ``DrX`` + ### Other stuff (see doc / samples) -- Debugger -- LocalDebugger (VEH based) - Network - Services - COM diff --git a/samples/debugger_membp_singlestep.py b/samples/debugger_membp_singlestep.py index 9c1e496..903fbc8 100644 --- a/samples/debugger_membp_singlestep.py +++ b/samples/debugger_membp_singlestep.py @@ -38,6 +38,10 @@ class SingleStepOnWrite(windows.debug.MemoryBreakpoint): eip = dbg.current_thread.context.pc print("Instruction at <{0:#x}> wrote at <{1:#x}>".format(eip, fault_addr)) dbg.single_step_counter = 4 + #import pdb;pdb.set_trace() + #if fault_addr == self.addr + 4: + # print("Delete self BP") + # dbg.del_bp(self) return dbg.single_step() @@ -52,12 +56,14 @@ injected += x86.Mov("EAX", 0) injected += x86.Mov(x86.deref(data), "EAX") injected += x86.Add("EAX", 4) injected += x86.Mov(x86.deref(data + 4), "EAX") +injected += x86.Add("EAX", 8) +injected += x86.Mov(x86.deref(data + 8), "EAX") injected += x86.Nop() injected += x86.Nop() injected += x86.Ret() calc.write_memory(code, injected.get_code()) -d.add_bp(SingleStepOnWrite(data, size=0x1000)) +d.add_bp(SingleStepOnWrite(data + 1, size=5)) calc.create_thread(code, 0) d.loop() diff --git a/samples/local_debugger.py b/samples/local_debugger.py index aa7f415..37ee4a6 100644 --- a/samples/local_debugger.py +++ b/samples/local_debugger.py @@ -3,54 +3,41 @@ import os.path import pprint sys.path.append(os.path.abspath(__file__ + "\..\..")) -import ctypes import windows -import windows.debug from windows.generated_def.winstructs import * +import windows.native_exec.simple_x86 as x86 -ct = windows.current_thread -t = [t for t in windows.current_process.threads if t.tid == ct.tid][0] - - - -class YoloDebugger(windows.debug.LocalDebugger): - def __init__(self, single_step_count): - super(YoloDebugger, self).__init__() - self.single_step_count = single_step_count - +class SingleSteppingDebugger(windows.debug.LocalDebugger): + SINGLE_STEP_COUNT = 4 def on_exception(self, exc): code = self.get_exception_code() context = self.get_exception_context() print("EXCEPTION !!!! Got a {0} at 0x{1:x}".format(code, context.pc)) - if self.single_step_count: - self.single_step_count -= 1 + self.SINGLE_STEP_COUNT -= 1 + if self.SINGLE_STEP_COUNT: return self.single_step() return EXCEPTION_CONTINUE_EXECUTION - -class YoloHXBP(windows.debug.HXBreakpoint): +class RewriteBreakpoint(windows.debug.HXBreakpoint): def trigger(self, dbg, exc): context = dbg.get_exception_context() - print("GOT AN HXBP <3 at 0x{0:x}".format(context.pc)) + print("GOT AN HXBP at 0x{0:x}".format(context.pc)) + # Rewrite the infinite loop with 2 nop windows.current_process.write_memory(self.addr, "\x90\x90") + # Ask for a single stepping return dbg.single_step() -print("Your main thread is {0}".format(windows.current_thread.tid)) - -d = YoloDebugger(5) +d = SingleSteppingDebugger() # Infinite loop + nop + ret - -addr = windows.native_exec.native_function.allocator.write_code("\xeb\xfe\x90\x90\x90\x90\xc3") -func_type = ctypes.CFUNCTYPE(PVOID) -func = func_type(addr) - -print("Code addr = 0x{0:x}".format(addr)) - -t = windows.current_process.create_thread(addr, 0) - -d.add_bp(YoloHXBP(addr)) - +code = x86.assemble("label :begin; jmp :begin; nop; ret") +func = windows.native_exec.create_function(code, [PVOID]) +print("Code addr = 0x{0:x}".format(func.code_addr)) +# Create a thread that will infinite loop +t = windows.current_process.create_thread(func.code_addr, 0) +# Add a breakpoint on the infitine loop +d.add_bp(RewriteBreakpoint(func.code_addr)) t.wait() +print("Done!") diff --git a/windows/test/test_debugger.py b/windows/test/test_debugger.py index 100e8e4..bc759e7 100644 --- a/windows/test/test_debugger.py +++ b/windows/test/test_debugger.py @@ -1,6 +1,8 @@ from test_utils import * from windows.generated_def.winstructs import * +import threading + class DebuggerTestCase(unittest.TestCase): def debuggable_calc_32(self): return windows.utils.create_process(r"C:\python27\python.exe", dwCreationFlags=DEBUG_PROCESS | CREATE_NEW_CONSOLE, show_windows=True) @@ -380,10 +382,11 @@ class DebuggerTestCase(unittest.TestCase): def trigger(self, dbg, exc): fault_addr = exc.ExceptionRecord.ExceptionInformation[1] eax = dbg.current_thread.context.Eax + if eax == 42: + dbg.current_process.exit() + return TEST_CASE.assertEqual(fault_addr, data + eax) store_data[0] += 1 - if store_data[0] == 2: - dbg.current_process.exit() return calc = pop_calc_32(dwCreationFlags=DEBUG_PROCESS) @@ -396,17 +399,22 @@ class DebuggerTestCase(unittest.TestCase): injected += x86.Mov(x86.deref(data), "EAX") injected += x86.Add("EAX", 4) injected += x86.Mov(x86.deref(data + 4), "EAX") + injected += x86.Add("EAX", 4) + # This one should NOT trigger the MemBP of size 8 + injected += x86.Mov(x86.deref(data + 8), "EAX") + injected += x86.Mov("EAX", 42) + injected += x86.Mov(x86.deref(data), "EAX") injected += x86.Ret() calc.write_memory(addr, injected.get_code()) - d.add_bp(TSTBP(data, size=0x1000)) + d.add_bp(TSTBP(data, size=0x8)) calc.create_thread(addr, 0) d.loop() - # Used to verif we actually called the Breakpoints + # Used to verif we actually called the Breakpoints for the good addresses TEST_CASE.assertEqual(store_data[0], 2) def test_memory_breakpoint_exec(self): - """Check that HXBPBP/dbg can trigger single step""" + """Check MemoryBP EXEC""" TEST_CASE = self NB_NOP_IN_PAGE = 3 data = [] @@ -432,6 +440,180 @@ class DebuggerTestCase(unittest.TestCase): for i in range(NB_NOP_IN_PAGE + 1): TEST_CASE.assertEqual(data[i], addr + i) + + def test_standard_breakpoint_self_remove(self): + TEST_CASE = self + data = [] + + def do_check(): + calc.execute_python_unsafe("open(u'FILENAME1')").wait() + calc.execute_python_unsafe("open(u'FILENAME2')").wait() + calc.execute_python_unsafe("open(u'FILENAME3')").wait() + calc.exit() + + class TSTBP(windows.debug.Breakpoint): + def trigger(self, dbg, exc): + addr = exc.ExceptionRecord.ExceptionAddress + ctx = dbg.current_thread.context + filename = dbg.current_process.read_wstring(dbg.current_process.read_ptr(ctx.sp + 0x4)) + data.append(filename) + if filename == u"FILENAME2": + dbg.del_bp(self) + + calc = pop_calc_32(dwCreationFlags=DEBUG_PROCESS) + d = windows.debug.Debugger(calc) + d.add_bp(TSTBP("kernel32.dll!CreateFileW")) + threading.Thread(target=do_check).start() + d.loop() + TEST_CASE.assertEqual(data, [u"FILENAME1", u"FILENAME2"]) + + def test_standard_breakpoint_remove(self): + TEST_CASE = self + data = [] + + def do_check(): + calc.execute_python_unsafe("open(u'FILENAME1')").wait() + calc.execute_python_unsafe("open(u'FILENAME2')").wait() + d.del_bp(the_bp) + calc.execute_python_unsafe("open(u'FILENAME3')").wait() + calc.exit() + + class TSTBP(windows.debug.Breakpoint): + def trigger(self, dbg, exc): + addr = exc.ExceptionRecord.ExceptionAddress + ctx = dbg.current_thread.context + filename = dbg.current_process.read_wstring(dbg.current_process.read_ptr(ctx.sp + 0x4)) + data.append(filename) + + calc = pop_calc_32(dwCreationFlags=DEBUG_PROCESS) + d = windows.debug.Debugger(calc) + the_bp = TSTBP("kernel32.dll!CreateFileW") + d.add_bp(the_bp) + threading.Thread(target=do_check).start() + d.loop() + TEST_CASE.assertEqual(data, [u"FILENAME1", u"FILENAME2"]) + + def test_hxbp_breakpoint_remove(self): + TEST_CASE = self + data = [] + + def do_check(): + calc.execute_python_unsafe("open(u'FILENAME1')").wait() + calc.execute_python_unsafe("open(u'FILENAME2')").wait() + d.del_bp(the_bp) + calc.execute_python_unsafe("open(u'FILENAME3')").wait() + calc.exit() + + class TSTBP(windows.debug.HXBreakpoint): + def trigger(self, dbg, exc): + addr = exc.ExceptionRecord.ExceptionAddress + ctx = dbg.current_thread.context + filename = dbg.current_process.read_wstring(dbg.current_process.read_ptr(ctx.sp + 0x4)) + data.append(filename) + + calc = pop_calc_32(dwCreationFlags=DEBUG_PROCESS) + d = windows.debug.Debugger(calc) + the_bp = TSTBP("kernel32.dll!CreateFileW") + d.add_bp(the_bp) + threading.Thread(target=do_check).start() + d.loop() + TEST_CASE.assertEqual(data, [u"FILENAME1", u"FILENAME2"]) + + def test_hxbp_breakpoint_self_remove(self): + TEST_CASE = self + data = [] + + def do_check(): + calc.execute_python_unsafe("open(u'FILENAME1')").wait() + calc.execute_python_unsafe("open(u'FILENAME2')").wait() + calc.execute_python_unsafe("open(u'FILENAME3')").wait() + calc.exit() + + class TSTBP(windows.debug.HXBreakpoint): + def trigger(self, dbg, exc): + addr = exc.ExceptionRecord.ExceptionAddress + ctx = dbg.current_thread.context + filename = dbg.current_process.read_wstring(dbg.current_process.read_ptr(ctx.sp + 0x4)) + data.append(filename) + if filename == u"FILENAME2": + #import pdb;pdb.set_trace() + dbg.del_bp(self) + + calc = pop_calc_32(dwCreationFlags=DEBUG_PROCESS) + d = windows.debug.Debugger(calc) + d.add_bp(TSTBP("kernel32.dll!CreateFileW")) + threading.Thread(target=do_check).start() + d.loop() + TEST_CASE.assertEqual(data, [u"FILENAME1", u"FILENAME2"]) + + + def test_mem_breakpoint_remove(self): + TEST_CASE = self + data = [] + + def generate_read_at(addr): + res = x86.MultipleInstr() + res += x86.Mov("EAX", x86.deref(addr)) + res += x86.Ret() + return res.get_code() + + def do_check(): + calc.execute(generate_read_at(data_addr)).wait() + calc.execute(generate_read_at(data_addr + 4)).wait() + d.del_bp(the_bp) + calc.execute(generate_read_at(data_addr + 8)).wait() + calc.exit() + + class TSTBP(windows.debug.MemoryBreakpoint): + DEFAULT_PROTECT = PAGE_NOACCESS + def trigger(self, dbg, exc): + addr = exc.ExceptionRecord.ExceptionAddress + fault_addr = exc.ExceptionRecord.ExceptionInformation[1] + data.append(fault_addr) + + calc = pop_calc_32(dwCreationFlags=DEBUG_PROCESS) + d = windows.debug.Debugger(calc) + data_addr = calc.virtual_alloc(0x1000) + the_bp = TSTBP(data_addr, size=0x1000) + d.add_bp(the_bp) + threading.Thread(target=do_check).start() + d.loop() + TEST_CASE.assertEqual(data, [data_addr, data_addr + 4]) + + def test_mem_breakpoint_self_remove(self): + TEST_CASE = self + data = [] + + def generate_read_at(addr): + res = x86.MultipleInstr() + res += x86.Mov("EAX", x86.deref(addr)) + res += x86.Ret() + return res.get_code() + + def do_check(): + calc.execute(generate_read_at(data_addr)).wait() + calc.execute(generate_read_at(data_addr + 4)).wait() + calc.execute(generate_read_at(data_addr + 8)).wait() + calc.exit() + + class TSTBP(windows.debug.MemoryBreakpoint): + DEFAULT_PROTECT = PAGE_NOACCESS + def trigger(self, dbg, exc): + addr = exc.ExceptionRecord.ExceptionAddress + fault_addr = exc.ExceptionRecord.ExceptionInformation[1] + data.append(fault_addr) + if fault_addr == data_addr + 4: + dbg.del_bp(self) + + calc = pop_calc_32(dwCreationFlags=DEBUG_PROCESS) + d = windows.debug.Debugger(calc) + data_addr = calc.virtual_alloc(0x1000) + the_bp = TSTBP(data_addr, size=0x1000) + d.add_bp(the_bp) + threading.Thread(target=do_check).start() + d.loop() + TEST_CASE.assertEqual(data, [data_addr, data_addr + 4]) + if __name__ == '__main__': alltests = unittest.TestSuite() alltests.addTest(unittest.makeSuite(DebuggerTestCase))