mirror of
https://github.com/hakril/PythonForWindows
synced 2026-06-08 14:31:45 +00:00
Add sample / test for memory breakpoint and single step
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
+18
-31
@@ -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!")
|
||||
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user