Gdb python: Difference between revisions
(2 intermediate revisions by the same user not shown) | |||
Line 51: | Line 51: | ||
== How-to == |
== How-to == |
||
=== Define custom gdb command === |
=== Define custom gdb command === |
||
* https://sourceware.org/gdb/onlinedocs/gdb/Commands-In-Python.html#Commands-In-Python |
|||
<source lang="python"> |
<source lang="python"> |
||
class MyCommand( gdb.Command ): |
class MyCommand( gdb.Command ): |
||
Line 439: | Line 441: | ||
print int(str(gdb.parse_and_eval('$SP')),16) |
print int(str(gdb.parse_and_eval('$SP')),16) |
||
# 10032 |
# 10032 |
||
print "0x%08x" % gdb.parse_and_eval('$SP') |
|||
# 0x00002730 |
|||
print int(gdb.parse_and_eval('$SP')) |
print int(gdb.parse_and_eval('$SP')) |
||
# ERROR! |
# ERROR! |
||
Line 498: | Line 502: | ||
python print "0x%08x" % (gdb.parse_and_eval('__begin_text').address) |
python print "0x%08x" % (gdb.parse_and_eval('__begin_text').address) |
||
# 0x00040000 |
# 0x00040000 |
||
</source> |
|||
=== Read / Write / Search memory === |
|||
* https://www.zeuthen.desy.de/dv/documentation/unixguide/infohtml/gdb/Inferiors-In-Python.html |
|||
* <code>buffer</code> object: https://docs.python.org/2.7/library/functions.html#buffer |
|||
:* Python 3: Use <code>memoryview</code>. |
|||
* Examples: |
|||
:* https://stackoverflow.com/questions/46572440/gdb-python-module-read-memory-content |
|||
:* https://gist.github.com/ricksladkey/bdcd761a5b06e3d670728d8cc96458ba |
|||
<source lang="python"> |
|||
read_memory(address, length) |
|||
write_memory(address, buffer [, length]) |
|||
search_memory(address, length, pattern) |
|||
</source> |
|||
This is for Python 2: |
|||
<source lang="python"> |
|||
inf = gdb.selected_inferior() |
|||
b = inf.read_memory(0x40000,16) # Return a buffer object |
|||
print list(b) # ['u', '\x86' ....] |
|||
b.tobytes() # ERROR! Python2 |
|||
str(b) # ERROR! Python2 |
|||
</source> |
</source> |
Latest revision as of 20:52, 13 March 2021
This page collects information on Python integration in gdb.
Links
- Example for Dumping memory, colorizing listing, pretty printer, tracing and profiling.
- Nice examples for
read_memory
,write_memory
,parse_and_eval
, manipulating and casting GDBValue
objects.
# gdb$ x/dwx $esp
# 0xbffff7ac: 0xb7e4ee46gdb$
int_pointer_type = gdb.lookup_type(’int’).pointer()
stack_address = gdb.Value(0xbffff7ac)
stack_address_pointer = stack_address.cast(int_pointer_type)
content = long(stack_address_pointer.dereference())
print hex(content & 0xffffffff)
# 0xb7e4ee46L
def deref_long_from_addr(addr):
'''
Get the value pointed by addr
'''
p_long = gdb.lookup_type('long').pointer()
vale = gdb.Value(addr).cast(p_long).dereference()
return long(val) & 0xffffffff
Tutorial
From Greg Law video.
- Single line python: for instance
python print("Hello World")
- Multi-line python: simply enter
python
, then finish withend
python
import os
print ("My pid is %d" % os.getpid())
end
- Integration with gdb:
python print(gdb.breakpoints()[0].location)
python gdb.Breakpoint('7')
, to insert breakpoint at line 7 in current file.
- To get output of a gdb execution, use
to_string=True
:
x=gdb.execute("show architecture", to_string=True).strip()
How-to
Define custom gdb command
class MyCommand( gdb.Command ):
"""MyCommand"""
def __init__(self):
super(MyCommand, self).__init__( "mycommand", gdb.COMMAND_USER )
def invoke(self, arg, from_tty):
print("My great command is alive!")
print "R0=0x%08X" %( gdb.parse_and_eval("$R0") )
MyCommand()
Quit program gracefully with custom python breakpoint
Here for instance a command Quit
(note that gdb would accept abbreviation Q
), that set a temporary breakpoint that definitely quit when reaching a custom exit_function
, and jump to some cleanup_function
:
JUMP_TO_FUNCTION="cleanup_function"
QUIT_ON_FUNCTION="exit_function"
class QuitBreakpoint(gdb.Breakpoint):
def stop(self):
print("Quitting...")
gdb.execute("quit")
class Quit( gdb.Command ):
"""Quit"""
def __init__(self):
super(Quit, self).__init__("Quit", gdb.COMMAND_USER )
def invoke(self, arg, from_tty):
print("Setting bkp")
QuitBreakpoint(QUIT_ON_FUNCTION, gdb.BP_BREAKPOINT, temporary=True)
gdb.execute("set $eip=%s" % JUMP_TO_FUNCTION)
gdb.execute("continue")
Quit()
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()
Define stop hook from Python
def stop_handler (event):
gdb.execute('dd')
gdb.execute('bt')
gdb.events.stop.connect (stop_handler)
Quit gdb from Python
Simply call quit()
:
python quit()
Get architecture
Besides show architecture
:
def is_alive():
"""Check if GDB is running."""
try:
return gdb.selected_inferior().pid > 0
except Exception:
return False
return False
if is_alive():
arch = gdb.selected_frame().architecture()
return arch.name()
Get register values
# https://stackoverflow.com/questions/6103887/how-do-i-access-the-registers-with-python-in-gdb
# https://programtalk.com/python-examples/gdb.parse_and_eval/
# Using parse_and_eval
print type(gdb.parse_and_eval('$SP')), gdb.parse_and_eval('$SP')
# <type 'gdb.Value'> 0x2730
print str(gdb.parse_and_eval('$SP'))
# 0x2730
print int(str(gdb.parse_and_eval('$SP')),16)
# 10032
print "0x%08x" % gdb.parse_and_eval('$SP')
# 0x00002730
print int(gdb.parse_and_eval('$SP'))
# ERROR!
print long(gdb.parse_and_eval('$SP'))
# 10032
print int(gdb.parse_and_eval('(int)$SP'))
# 10032
print int(gdb.parse_and_eval('(long)$SP'))
# 10032
int(long(gdb.parse_and_eval('$SP')) & 0xffffffff)
# 10032
# Using read_register
print int(str(gdb.selected_frame().read_register('SP),16)
# 10032
Implement offsetof and sizeof
# https://programtalk.com/python-examples/gdb.parse_and_eval/
def offsetof(struct_name, member_name):
expr = '(size_t)&(((%s *)0)->%s) - (size_t)((%s *)0)' % \
(struct_name, member_name, struct_name)
return to_int(gdb.parse_and_eval(expr))
def sizeof(type_name):
return to_int(gdb.parse_and_eval('sizeof(%s)' % (type_name)))
Get description of any GDB object
One option is to use the pythonic dir(...)
, but in fact invoking help gives the best result
python help(gdb.selected_frame().read_register('SP'))
# Help on Value object:
#
# class Value(__builtin__.object)
# GDB value object
# ...
# cast(...)
# Cast the value to the supplied type.
# ...
# address
# The address of the value.
Get address of a symbol
For instance, say __begin_text
:
python print gdb.parse_and_eval('__begin_text').address
# 0x40000 <__start>
python print str(gdb.parse_and_eval('__begin_text').address)
# 0x40000 <__start>
python print int(gdb.parse_and_eval('__begin_text').address)
# ERROR
python print long(gdb.parse_and_eval('__begin_text').address)
# 262144
python print "0x%08x" % (gdb.parse_and_eval('__begin_text').address)
# 0x00040000
Read / Write / Search memory
- https://www.zeuthen.desy.de/dv/documentation/unixguide/infohtml/gdb/Inferiors-In-Python.html
buffer
object: https://docs.python.org/2.7/library/functions.html#buffer
- Python 3: Use
memoryview
.
- Python 3: Use
- Examples:
read_memory(address, length)
write_memory(address, buffer [, length])
search_memory(address, length, pattern)
This is for Python 2:
inf = gdb.selected_inferior()
b = inf.read_memory(0x40000,16) # Return a buffer object
print list(b) # ['u', '\x86' ....]
b.tobytes() # ERROR! Python2
str(b) # ERROR! Python2