Gdb python
Jump to navigation
Jump to search
This page collects information on Python integration in gdb.
Python script
Using Capstone to trace program
import re
import gdb
from capstone import *
from capstone.arm import *
import binascii
# Documentation : https://sourceware.org/gdb/onlinedocs/gdb/Python-API.html
# Server-side : gdbserver --multi CLIENT_ADDR:REMOTE_PORT
# Client-side : gdb-multiarch ./my_exec
# Load the script in GDB CLI : source /path/to/gdb_script_st.py
REMOTE_ADDR = "192.168.20.150"
REMOTE_PORT = "6666"
REMOTE_FILE_PATH = "/path/on/remote/to/my_exec"
PAGINATION = "off"
LOG = "off"
LOG_REDIRECT = "on"
LOG_OVERWRITE = "on"
LOG_FILE = "trace.log"
TRACE_START_ADDR = 0x112fc
TRACE_END_ADDR = 0
exited = False
def read_register(register_name):
return int(gdb.selected_frame().read_register(register_name)) & 0xffffffff
def process_register(register_name, access, occurences, readen_registers, written_registers, operand_string):
if register_name not in occurences:
occurences[register_name] = (match.span() for match in re.finditer(register_name, operand_string))
start, end = next(occurences[register_name])
if access & CS_AC_WRITE:
written_registers.append(register_name)
else:
register_value = read_register(register_name)
readen_registers.append((register_name, register_value, start + occurences["offset"], end + occurences["offset"]))
occurences["offset"] += len(f"0x{register_value:x}") - len(register_name)
def concrete_operand_string(operand_string, readen_registers):
for register_name, register_value, start, end in readen_registers:
operand_string = operand_string[:start] + f"0x{register_value:x}" + operand_string[end:]
return operand_string
def exit_handler(event):
global exited
exited = True
if hasattr(event, "exit_code"):
print(f"[!] Program has exited returning {event.exit_code}")
else:
print(f"[!] Program has exited (couldn't read exit code)")
class TraceRun (gdb.Command):
def __init__(self):
super(TraceRun, self).__init__("trace-run", gdb.COMMAND_RUNNING)
def invoke(self, arg, from_tty):
global exited
exited = False
inferior = gdb.selected_inferior()
pc = TRACE_START_ADDR
breakpoint = gdb.Breakpoint(f"*0x{pc:x}", gdb.BP_BREAKPOINT, temporary=True)
cs_engine = Cs(CS_ARCH_ARM, CS_MODE_ARM)
cs_engine.detail = True
gdb.execute(f"r {arg}")
if not exited:
gdb.execute(f"set logging {LOG}")
while(pc != TRACE_END_ADDR):
readen_registers = []
written_registers = []
occurences = {"offset": 0}
memory_view = inferior.read_memory(pc, 0x4)
instruction = next(cs_engine.disasm(memory_view.tobytes(), pc))
for operand in instruction.operands:
if operand.type == ARM_OP_REG:
process_register(instruction.reg_name(operand.value.reg), operand.access, occurences, readen_registers, written_registers, instruction.op_str)
if operand.type == ARM_OP_MEM:
if operand.value.mem.base:
process_register(instruction.reg_name(operand.value.mem.base), operand.access, occurences, readen_registers, written_registers, instruction.op_str)
if operand.value.mem.index:
process_register(instruction.reg_name(operand.value.mem.index), operand.access, occurences, readen_registers, written_registers, instruction.op_str)
gdb.execute("ni")
print(f"0x{instruction.address:x}: {instruction.mnemonic} {instruction.op_str}")
concrete_op_str = concrete_operand_string(instruction.op_str, readen_registers)
if instruction.op_str != concrete_op_str:
print(f"0x{instruction.address:x}: {instruction.mnemonic} {concrete_op_str}")
for register_name in written_registers:
register_value = read_register(register_name)
print(f"0x{instruction.address:x}: {register_name} = 0x{register_value:x}")
pc = read_register("pc")
gdb.execute("set logging off")
gdb.execute("c")
def main():
gdb.execute(f"target extended-remote {REMOTE_ADDR}:{REMOTE_PORT}")
gdb.execute(f"set remote exec-file {REMOTE_FILE_PATH}")
gdb.execute(f"set pagination {PAGINATION}")
gdb.execute(f"set logging file {LOG_FILE}")
gdb.execute(f"set logging redirect {LOG_REDIRECT}")
gdb.execute(f"set logging overwrite {LOG_OVERWRITE}")
gdb.execute(f"set logging off")
gdb.execute(f"set confirm off")
gdb.events.exited.connect(exit_handler)
if __name__ == "__main__":
# Commands registration
TraceRun()
# Main
main()
Disable ptrace detection in a program
Some programs calls ptrace
to detect whether they are debugged or traced (with strace
).
We can set breakpoints to disable this detection:
- On first call,
ptrace
must return 0 to indicate success. - On second call,
ptrace
must return -1 to indicate a process was attached already (the program itself did it in first call above).
We use Python variables to store this state, and reset on program exit:
import gdb
PTRACE_ADDR = 0x000153d0
unptrace_breakpoint = None
unptrace_breakpoint_value = 0
def exit_handler(event):
global unptrace_breakpoint_value
exited = True
if hasattr(event, "exit_code"):
print(f"[!] Program has exited returning {event.exit_code}")
else:
print(f"[!] Program has exited (couldn't read exit code)")
unptrace_breakpoint_value=0
class PtraceBreakpoint (gdb.Breakpoint):
def stop(self):
global unptrace_breakpoint_value
print(f"set $r0={unptrace_breakpoint_value}")
gdb.execute(f"set $r0={unptrace_breakpoint_value}")
gdb.execute("set $pc=$lr")
print(f"unptrace: returned {unptrace_breakpoint_value}!")
unptrace_breakpoint_value=0xffffffff
return False
class UnPtrace (gdb.Command):
def __init__(self):
global unptrace_breakpoint
global unptrace_breakpoint_value
super(UnPtrace, self).__init__("unptrace", gdb.COMMAND_USER)
unptrace_breakpoint = None
unptrace_breakpoint_value = 0
def invoke(self, arg, from_tty):
global unptrace_breakpoint
if unptrace_breakpoint == None:
unptrace_breakpoint = PtraceBreakpoint(f"*0x{PTRACE_ADDR:x}", gdb.BP_BREAKPOINT)
unptrace_breakpoint_value = 0
def main():
gdb.events.exited.connect(exit_handler)
if __name__ == "__main__":
# Commands registration
UnPtrace()
# Main
main()
Trace and inspect some functions calls
Here we set up some breakpoint hook to inspect calls to read(2)
and write(2)
:
import gdb
import binascii
READ_ADDR = 0x000152f0
WRITE_ADDR = 0x00015300
exited = False
def exit_handler(event):
global exited
exited = True
if hasattr(event, "exit_code"):
print(f"[!] Program has exited returning {event.exit_code}")
else:
print(f"[!] Program has exited (couldn't read exit code)")
read_r0 = None
read_r1 = None
read_r2 = None
class ReadBackBreakpoint (gdb.Breakpoint):
def stop(self):
global read_r0, read_r1, read_r2
if read_r0 != None:
lr = read_register("pc") # PC is LR when we created the bkp
r0 = read_r0
r1 = read_r1
r2 = read_r2
r_bytes = gdb.selected_inferior().read_memory(r1,r2).tobytes()
print(f"0x{lr-4:08x}: read_({r0},0x{r1:08x},{r2:3}) <-- {binascii.hexlify(r_bytes).decode()}")
(read_r0,read_r1,read_r2) = (None,None,None)
return False
class ReadBreakpoint (gdb.Breakpoint):
def stop(self):
global read_r0, read_r1, read_r2
r0 = read_register("r0")
r1 = read_register("r1")
r2 = read_register("r2")
lr = read_register("lr")
# print(f"0x{lr-4:08x}: read_({r0},0x{r1:08x},{r2:3})")
# Set up temporary bkp to collect read bytes
(read_r0,read_r1,read_r2) = (r0,r1,r2)
breakpoint = ReadBackBreakpoint(f"*0x{lr:x}", gdb.BP_BREAKPOINT, temporary=True)
return False
class WriteBreakpoint (gdb.Breakpoint):
def stop(self):
lr = read_register("lr")
r0 = read_register("r0")
r1 = read_register("r1")
r2 = read_register("r2")
w_bytes = gdb.selected_inferior().read_memory(r1, r2).tobytes()
print(f"0x{lr-4:08x}: write({r0},0x{r1:08x},{r2:3}) ==> {binascii.hexlify(w_bytes).decode()}")
print()
return False
class HookRW (gdb.Command):
def __init__(self):
super(HookRW, self).__init__("hookrw", gdb.COMMAND_USER)
self.read_bkp = None
self.write_bkp = None
def invoke(self, arg, from_tty):
if self.read_bkp == None:
self.read_bkp = ReadBreakpoint(f"*0x{READ_ADDR:x}", gdb.BP_BREAKPOINT)
if self.write_bkp == None:
self.write_bkp = WriteBreakpoint(f"*0x{WRITE_ADDR:x}", gdb.BP_BREAKPOINT)
def main():
gdb.execute(f"set pagination {PAGINATION}")
gdb.execute(f"set logging file {LOG_FILE}")
gdb.execute(f"set logging redirect {LOG_REDIRECT}")
gdb.execute(f"set logging overwrite {LOG_OVERWRITE}")
gdb.execute(f"set logging off")
gdb.execute(f"set confirm off")
gdb.events.exited.connect(exit_handler)
if __name__ == "__main__":
# Commands registration
HookRW()
# Main
main()