Gdb python
Jump to navigation
Jump to search
This page collects information on Python integration in gdb.
Links
Tutorial
From Greg Law video.
- Single line python: for instance
python print("Hello World")
- Multi-line python: simply enter
python
import os
print ("My pid is %d" % os.getpid())
- Integration with gdb:
python print(gdb.breakpoints()[0].location)
python gdb.Breakpoint('7')
, to insert breakpoint at line 7 in current file.
How-to
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()