I am using Python to write a rudimentary debugger that will use Windows APIs to debug C source files. I am stuck on creating the software breakpoints. I did some research and found this article that explains setting software breakpoints involves setting 0xCC to a location.
original = ctypes.c_ubyte()
bytes_read = SIZE_T()
handle = OpenProcess(PROCESS_ALL_ACCESS, False, self.process_id)
# Read original byte
ReadProcessMemory(handle, address, ctypes.byref(original), 1, ctypes.byref(bytes_read))
# Write INT3
int3 = ctypes.c_ubyte(0xCC)
bytes_written = SIZE_T()
WriteProcessMemory(handle, address, ctypes.byref(int3), 1, ctypes.byref(bytes_written))
self.breakpoints[address] = original.value
CloseHandle(handle)
Getting the address using comtypes that uses the MSDIA SDK to parse a PDB file:
source_files = self.session.findFile(None, r"/path/to/source/file.c", 0x2 | 0x4)
source_file = source_files.Item(0)
line_numbers = self.session.findLinesByLinenum(source_file.compilands[0], source_file, line, 0)
line_number = line_numbers.Next(1)[0]
address = self.base_address + line_number.addressOffset # + line_number.virtualAddress
Logic to handle software breakpoints:
def handle_software_breakpoint(self, thread_id, exception_address):
"""Handle a software breakpoint hit (INT3)"""
if exception_address not in self.breakpoints:
print("[!] Breakpoint hit at unknown address 0x%X" % exception_address)
return
# Fix instruction pointer to re-execute original instruction
context = CONTEXT32()
context.ContextFlags = CONTEXT_ALL
thread_handle = OpenThread(THREAD_ALL_ACCESS, False, thread_id)
GetThreadContext(thread_handle, ctypes.byref(context))
context.Eip -= 1 # Rewind past INT3
SetThreadContext(thread_handle, ctypes.byref(context))
CloseHandle(thread_handle)
original_byte = self.breakpoints[exception_address]
handle = OpenProcess(PROCESS_ALL_ACCESS, False, self.process_id)
# Restore original instruction byte
orig = ctypes.c_ubyte(original_byte)
size = SIZE_T()
WriteProcessMemory(handle, exception_address, ctypes.byref(orig), 1, ctypes.byref(size))
CloseHandle(handle)
print("[*] Software breakpoint handled at 0x%X" % exception_address)
However the only breakpoint that is handled is at address 0x77BF1B52. How can I ensure that the breakpoints are reached? Does one have to account for comments when reading memory addresses from a line in source code?
Python/C/C++ solutions are acceptable.
Below is the debug loop:
def run_debug_loop(self):
print("[+] Starting debug loop... waiting for events.")
debug_event = DEBUG_EVENT()
while WaitForDebugEvent(ctypes.byref(debug_event), INFINITE):
code = debug_event.dwDebugEventCode
pid = debug_event.dwProcessId
tid = debug_event.dwThreadId
if code == CREATE_PROCESS_DEBUG_EVENT:
print("[+] Process created, setting breakpoints...")
# Example: set a breakpoint at main()
self.set_software_breakpoint(self.io_addresses["B"])
ResumeThread(self.thread_handle)
elif code == EXCEPTION_DEBUG_EVENT:
record = debug_event.u.Exception.ExceptionRecord
exc_code = record.ExceptionCode
addr = record.ExceptionAddress
if exc_code == EXCEPTION_BREAKPOINT:
self.handle_software_breakpoint(tid, addr)
elif exc_code == EXCEPTION_SINGLE_STEP:
print("[!] Unexpected single-step (from restored breakpoint)")
elif code == 5: # EXIT_PROCESS_DEBUG_EVENT
print("[+] Process exited.")
break
ContinueDebugEvent(pid, tid, DBG_CONTINUE)