Skip to content

Commit 35e7a8d

Browse files
committed
Add fix + tests to handle debugger memory-bp triggering twice on same instruction (when instruction write on 2 != pages)
1 parent c46d76a commit 35e7a8d

File tree

2 files changed

+65
-2
lines changed

2 files changed

+65
-2
lines changed

tests/test_debugger.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,54 @@ def trigger(self, dbg, exc):
367367
for i in range(NB_NOP_IN_PAGE + 1):
368368
assert TSTBP.DATA[i] == addr + i
369369

370+
def test_memory_breakpoint_trigger_multipage(proc32_64_debug):
371+
"""Check that a memory breakpoint triggering on multiple page on the same instruction restore are correctly restored on all pages"""
372+
373+
class MultiPageMemBP(windows.debug.MemoryBreakpoint):
374+
ALL_TRIGGER_ADDR = []
375+
376+
def trigger(self, dbg, exc):
377+
pc_fault_addr = dbg.current_thread.context.pc
378+
self.ALL_TRIGGER_ADDR.append(pc_fault_addr)
379+
print(hex(pc_fault_addr))
380+
# Stop when we have a breakpoint in the 2nd page of the alloc
381+
if shellcodeaddr + 0x1000 <= pc_fault_addr <= shellcodeaddr + 0x2000:
382+
dbg.current_process.exit()
383+
384+
# Trigger a write that will write on both page at once,
385+
# Triggering both pages mem-bp on the same instruction.
386+
# Then call an instruction at the end of page to see if both page of mem-bp still trigger the BP
387+
388+
shellcodeaddr = proc32_64_debug.virtual_alloc(0x2000)
389+
390+
if proc32_64_debug.bitness == 64:
391+
shellcode = x64.MultipleInstr()
392+
shellcode += x64.Mov("RAX", shellcodeaddr + 0xffe)
393+
shellcode += x64.Mov("RCX", 0xc3909090) # Nopnopnopret
394+
shellcode += x64.Mov(x64.mem("[RAX]"), "ECX") # Will write on both page at once
395+
shellcode += x64.Push("RAX")
396+
shellcode += x64.Ret() # Jump on the nop + ret
397+
else:
398+
shellcode = x86.MultipleInstr()
399+
shellcode += x86.Mov("EAX", shellcodeaddr + 0xffe)
400+
shellcode += x86.Mov("ECX", 0xc3909090) # Nopnopnopret
401+
shellcode += x86.Mov(x86.mem("[EAX]"), "ECX") # Will write on both page at once
402+
shellcode += x86.Push("EAX")
403+
shellcode += x86.Ret() # Jump on the nop + ret
404+
405+
d = windows.debug.Debugger(proc32_64_debug)
406+
bp = MultiPageMemBP(addr=shellcodeaddr, size=0x2000, events="XW")
407+
proc32_64_debug.write_memory(shellcodeaddr, shellcode.get_code())
408+
d.add_bp(bp)
409+
proc32_64_debug.create_thread(shellcodeaddr, 0)
410+
411+
d.loop()
412+
# Check that the 2 nop at the end of the first page of the membp trigger
413+
# If access right where not correctly restored: this would not trigger on the first page
414+
assert shellcodeaddr + 0xffe in bp.ALL_TRIGGER_ADDR
415+
assert shellcodeaddr + 0xfff in bp.ALL_TRIGGER_ADDR
416+
assert shellcodeaddr + 0x1000 in bp.ALL_TRIGGER_ADDR
417+
370418

371419
# breakpoint remove
372420
import threading
@@ -700,4 +748,6 @@ def WaitForDebugEvent_KeyboardInterrupt(debug_event):
700748
assert proc32_64_debug.read_memory(addr, len(TEST_CODE)) == TEST_CODE
701749
assert bad_thread.context.pc == addr
702750
else:
703-
raise ValueError("Should have raised")
751+
raise ValueError("Should have raised")
752+
753+

windows/debug/debugger.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -685,11 +685,24 @@ def _handle_exception_access_violation(self, exception, excp_addr):
685685
self._pass_memory_breakpoint(bp, original_prot, fault_page)
686686
return DBG_CONTINUE
687687

688+
# We may have setup the "EEFlags.TF" ourself if the membreakpoint triggered twice on the same instruction
689+
# Ex: write on two pages handled by our breakpoint (unaligned write on 0xfff-0x1000)
690+
691+
originalctx = self.current_thread.context
692+
original_tf = originalctx.EEFlags.TF
693+
# Temporary disable EEFlags.TF to see if user callback explicit ask for it
694+
originalctx.EEFlags.TF = 0
695+
self.current_thread.set_context(originalctx)
696+
688697
with self.DisabledMemoryBreakpoint():
689698
continue_flag = mem_bp.trigger(self, exception)
690699
if self._killed_in_action():
691700
return continue_flag
692-
self._explicit_single_step[self.current_thread.tid] = self.current_thread.context.EEFlags.TF
701+
# Update explicit trigger based on new value of EEFlags.TF
702+
self._explicit_single_step[self.current_thread.tid] |= self.current_thread.context.EEFlags.TF
703+
# Reupdate the real EEFlags.TF based on its current value and the original one
704+
if original_tf != 0 and not self.current_thread.context.EEFlags.TF:
705+
self.single_step()
693706
if self._explicit_single_step[self.current_thread.tid]:
694707
dbgprint("Someone ask for an explicit Single step - 5", "DBG")
695708
# If BP has not been removed in trigger, pas it

0 commit comments

Comments
 (0)