Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
strategy:
fail-fast: false
matrix:
runs-on: [windows-2019, windows-latest]
runs-on: [windows-2022, windows-latest]
python-version: [2.7, 3.6, 3.11]
python-architecture: [x86, x64]
include:
Expand Down
52 changes: 51 additions & 1 deletion tests/test_debugger.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,54 @@ def trigger(self, dbg, exc):
for i in range(NB_NOP_IN_PAGE + 1):
assert TSTBP.DATA[i] == addr + i

def test_memory_breakpoint_trigger_multipage(proc32_64_debug):
"""Check that a memory breakpoint triggering on multiple page on the same instruction restore are correctly restored on all pages"""

class MultiPageMemBP(windows.debug.MemoryBreakpoint):
ALL_TRIGGER_ADDR = []

def trigger(self, dbg, exc):
pc_fault_addr = dbg.current_thread.context.pc
self.ALL_TRIGGER_ADDR.append(pc_fault_addr)
print(hex(pc_fault_addr))
# Stop when we have a breakpoint in the 2nd page of the alloc
if shellcodeaddr + 0x1000 <= pc_fault_addr <= shellcodeaddr + 0x2000:
dbg.current_process.exit()

# Trigger a write that will write on both page at once,
# Triggering both pages mem-bp on the same instruction.
# Then call an instruction at the end of page to see if both page of mem-bp still trigger the BP

shellcodeaddr = proc32_64_debug.virtual_alloc(0x2000)

if proc32_64_debug.bitness == 64:
shellcode = x64.MultipleInstr()
shellcode += x64.Mov("RAX", shellcodeaddr + 0xffe)
shellcode += x64.Mov("RCX", 0xc3909090) # Nopnopnopret
shellcode += x64.Mov(x64.mem("[RAX]"), "ECX") # Will write on both page at once
shellcode += x64.Push("RAX")
shellcode += x64.Ret() # Jump on the nop + ret
else:
shellcode = x86.MultipleInstr()
shellcode += x86.Mov("EAX", shellcodeaddr + 0xffe)
shellcode += x86.Mov("ECX", 0xc3909090) # Nopnopnopret
shellcode += x86.Mov(x86.mem("[EAX]"), "ECX") # Will write on both page at once
shellcode += x86.Push("EAX")
shellcode += x86.Ret() # Jump on the nop + ret

d = windows.debug.Debugger(proc32_64_debug)
bp = MultiPageMemBP(addr=shellcodeaddr, size=0x2000, events="XW")
proc32_64_debug.write_memory(shellcodeaddr, shellcode.get_code())
d.add_bp(bp)
proc32_64_debug.create_thread(shellcodeaddr, 0)

d.loop()
# Check that the 2 nop at the end of the first page of the membp trigger
# If access right where not correctly restored: this would not trigger on the first page
assert shellcodeaddr + 0xffe in bp.ALL_TRIGGER_ADDR
assert shellcodeaddr + 0xfff in bp.ALL_TRIGGER_ADDR
assert shellcodeaddr + 0x1000 in bp.ALL_TRIGGER_ADDR


# breakpoint remove
import threading
Expand Down Expand Up @@ -700,4 +748,6 @@ def WaitForDebugEvent_KeyboardInterrupt(debug_event):
assert proc32_64_debug.read_memory(addr, len(TEST_CODE)) == TEST_CODE
assert bad_thread.context.pc == addr
else:
raise ValueError("Should have raised")
raise ValueError("Should have raised")


9 changes: 6 additions & 3 deletions windows/debug/breakpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ def __init__(self, addr, size=None, events=None):
self.size = size if size is not None else self.DEFAULT_SIZE
events = events if events is not None else self.DEFAULT_EVENTS
self.events = set(events)
self._reput_pages = [] # The current memory BP page that is passed

def trigger(self, dbg, exception):
"""Called when breakpoint is hit"""
Expand Down Expand Up @@ -126,14 +127,16 @@ def extract_arguments_32bits(self, cproc, cthread):
def extract_arguments_64bits(self, cproc, cthread):
x = windows.debug.X64ArgumentRetriever()
res = OrderedDict()
for i, (name, type) in enumerate(zip(self.target_params, self.target_args)):
for i, (name, atype) in enumerate(zip(self.target_params, self.target_args)):
value = x.get_arg(i, cproc, cthread)
rt = windows.remotectypes.transform_type_to_remote64bits(type)
rt = windows.remotectypes.transform_type_to_remote64bits(atype)
if issubclass(rt, windows.remotectypes.RemoteValue):
t = rt(value, cproc)
else:
t = rt(value)
if not hasattr(t, "contents"):

if (not isinstance(t, (windows.remotectypes.RemotePtr64, windows.remotectypes.RemotePtr32)) or
isinstance(t, (ctypes.c_char_p, ctypes.c_wchar_p))):
try:
t = t.value
except AttributeError:
Expand Down
23 changes: 19 additions & 4 deletions windows/debug/debugger.py
Original file line number Diff line number Diff line change
Expand Up @@ -422,8 +422,9 @@ def _setup_breakpoint_MEMBP(self, bp, target):
return True

def _restore_breakpoint_MEMBP(self, bp, target):
(page_addr, page_prot) = bp._reput_page
return target.virtual_protect(page_addr, PAGE_SIZE, page_prot, None)
for (page_addr, page_prot) in bp._reput_pages:
target.virtual_protect(page_addr, PAGE_SIZE, page_prot, None)
del bp._reput_pages[:]


def _remove_breakpoint_MEMBP(self, bp, target):
Expand Down Expand Up @@ -542,7 +543,7 @@ def _pass_memory_breakpoint(self, bp, page_protect, fault_page):
ctx = thread.context
ctx.EEFlags.TF = 1
thread.set_context(ctx)
bp._reput_page = (fault_page, page_prot.value)
bp._reput_pages.append((fault_page, page_prot.value))
self._breakpoint_to_reput[cp.pid].add(bp)

# debug event handlers
Expand Down Expand Up @@ -665,6 +666,7 @@ def _handle_exception_access_violation(self, exception, excp_addr):
fault_type = exception.ExceptionRecord.ExceptionInformation[0]
fault_addr = exception.ExceptionRecord.ExceptionInformation[1]
pc_addr = self.current_thread.context.pc
dbgprint("Handling access_violation at pc={0:#x} addr={1:#x}".format(pc_addr, fault_addr), "DBG")
if fault_addr == pc_addr:
fault_type = EXEC
event = EVENT_STR[fault_type]
Expand All @@ -683,11 +685,24 @@ def _handle_exception_access_violation(self, exception, excp_addr):
self._pass_memory_breakpoint(bp, original_prot, fault_page)
return DBG_CONTINUE

# We may have setup the "EEFlags.TF" ourself if the membreakpoint triggered twice on the same instruction
# Ex: write on two pages handled by our breakpoint (unaligned write on 0xfff-0x1000)

originalctx = self.current_thread.context
original_tf = originalctx.EEFlags.TF
# Temporary disable EEFlags.TF to see if user callback explicit ask for it
originalctx.EEFlags.TF = 0
self.current_thread.set_context(originalctx)

with self.DisabledMemoryBreakpoint():
continue_flag = mem_bp.trigger(self, exception)
if self._killed_in_action():
return continue_flag
self._explicit_single_step[self.current_thread.tid] = self.current_thread.context.EEFlags.TF
# Update explicit trigger based on new value of EEFlags.TF
self._explicit_single_step[self.current_thread.tid] |= self.current_thread.context.EEFlags.TF
# Reupdate the real EEFlags.TF based on its current value and the original one
if original_tf != 0 and not self.current_thread.context.EEFlags.TF:
self.single_step()
if self._explicit_single_step[self.current_thread.tid]:
dbgprint("Someone ask for an explicit Single step - 5", "DBG")
# If BP has not been removed in trigger, pas it
Expand Down
Loading