Gdb: Difference between revisions
Line 91: | Line 91: | ||
== Front-ends == |
== Front-ends == |
||
=== Gef === |
|||
⚫ | |||
⚫ | |||
Simple install script [https://github.com/hugsy/gef]: |
|||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
=== GDB dashboard === |
=== GDB dashboard === |
||
Line 141: | Line 164: | ||
See [[Voltron]]. |
See [[Voltron]]. |
||
=== |
=== GDB TUI === |
||
Some example inspired from [https://www.youtube.com/watch?v=PorfLSr3DDI Greg Law video on Youtube]: |
|||
⚫ | |||
* Start TUI mode with {{kb|Ctrl-X A}}, or <code>gdb -tui</code>, or <code>tui enable</code>. |
|||
⚫ | |||
* {{kb|Ctrl-L}} repaint the screen. |
|||
* {{kb|Ctrl-X 2}} to split the layout, or <code>layout split</code>. |
|||
* <code>tui reg float</code> for instance to change the register view. |
|||
Note that while in TUI mode, up and down keys will scroll the source code: |
|||
⚫ | |||
* {{kb|Up}} / {{kb|Down}} scroll the source code. |
|||
⚫ | |||
* {{kb|Ctrl-P}} / {{kb|Ctrl-N}} Previous or Next command in command history. |
|||
⚫ | |||
* {{kb|Ctrl-O}} switch focus ({{red|does not work for me}}). |
|||
=== CGDB === |
|||
⚫ | |||
{{todo| Look at CGDB as simple alternative to GDB TUI}} |
|||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
== Prepare debug session == |
== Prepare debug session == |
Revision as of 08:20, 21 November 2019
References
- 7.2 Example Debugging Session: Segmentation Fault Example (unkownroad.com)
- Debugging with GDB (delorie.com)
- GDB commands (tutorialspoint.com)
- An Interactive Guide to Faster, Less Frustrating Debugging
- Norm Matloff's Debugging Tutorial (also guide to fast editing and gui debuggers DDD, GVD)
- 8 gdb tricks you should know (oracle.com)
GDB front-ends
There are several types of front-end for GDB:
- GDB built-in front-end (using TUI, Text User Interface)
- Front-end that customizes heavily .gdbinit file.
- Front-end using the newer MI2 interface.
- Front-end using the older MI interface.
More front-ends:
- GDB built-in
- GDB TUI — GDB own Text User Interface (C-x C-a: http://davis.lbl.gov/Manuals/GDB/gdb_21.html)
- gdbinit front-end
- gdbinit hacks
- Using colout one may color almost any gdb output.
- MI2 front-ends
- (Neovim) lldb.nvim
- Only for LLDB.
- (Vim) Conque-GDB
- Use old shell implementation, not the new neovim terminal.
- (Vim) pyclewn
- Read some comment it was not convenient, and would require extra plugin like https://github.com/notEvil/vim-debug.
- MI front-end (old interface)
Tools and libraries
- Capstone, the ultimate disassembly engine.
Tutorials
- GDB TUI mode, python, reverse debugging (with
record
).
GDB configuration
GDB reads file ~/.gdbinit at start.
Some references:
Bare minimum configuration
From StackOverflow [1]:
set history save on
set print pretty
set output-radix 16
set height 0
Install Python modules for GDB
From pwndbg [2]:
# Find the Python version used by GDB.
PYVER=$(gdb -batch -q --nx -ex 'pi import platform; print(".".join(platform.python_version_tuple()[:2]))')
PYTHON=$(gdb -batch -q --nx -ex 'pi import sys; print(sys.executable)')
PYTHON="${PYTHON}${PYVER}"
# Find the Python site-packages that we need to use so that
# GDB can find the files once we've installed them.
SITE_PACKAGES=$(gdb -batch -q --nx -ex 'pi import site; print(site.getsitepackages()[0])')
# or to install in user mode:
SITE_PACKAGES=$(gdb -batch -q --nx -ex 'pi import site; print(site.getusersitepackages())')
# Install Python dependencies
sudo ${PYTHON} -m pip install --target ${SITE_PACKAGES} -Ur requirements.txt
Example of file requirements.txt
pip pycparser psutil>=3.1.0
Front-ends
Gef
Simple install script [3]:
# via the install script
$ wget -q -O- https://github.com/hugsy/gef/raw/master/scripts/gef.sh | sh
# manually
$ wget -O ~/.gdbinit-gef.py -q https://github.com/hugsy/gef/raw/master/gef.py
$ echo source ~/.gdbinit-gef.py >> ~/.gdbinit
Dependencies:
- Install keystone-engine, NOT keystone (fix error
AttributeError: 'module' object has no attribute 'KS_ARCH_X86'
):
pip3 install --no-binary keystone-engine keystone-engine
GDB dashboard
GDB dashboard is a modular visual interface for GDB in Python.
To install simply copy .gdbinit as ~/.gdbinit
cp gdb-dashboard/.gdbinit ~/.gdbinit
Alternatively, source it from ~/.gdbinit:
source ~/.gdbinit-dashboard
- Install pygments
Install pygments to get source highlighting
sudo pip install Pygments # Globally pip install Pygments # Locally
If GDB uses python3 (ldd $(which gdb))
), you'll need to install with pip3
:
sudo pip3 install Pygments # Globally pip3 install Pygments # Locally
To get the list of available styles:
python from pygments.styles import get_all_styles as styles python for s in styles(): print(s)
Alternative styles:
- Issues
Cannot write the dashboard: [Errno 10] No child process
issue #56
- This is a bug in the gdb vendor. Fix file platform.py:
@@ -1009,7 +1009,10 @@
except (AttributeError,os.error):
return default
output = string.strip(f.read())
- rc = f.close()
+ try:
+ rc = f.close()
+ except:
+ rc = 0
if not output or rc:
return default
else:
Cannot write the dashboard: Invalid character '?' in expression.
- Likely due to incorrect character in
info reg
. The register is evaluated viaparse_and_eval
Voltron
See Voltron.
GDB TUI
Some example inspired from Greg Law video on Youtube:
- Start TUI mode with Ctrl-X A, or
gdb -tui
, ortui enable
. - Ctrl-L repaint the screen.
- Ctrl-X 2 to split the layout, or
layout split
. tui reg float
for instance to change the register view.
Note that while in TUI mode, up and down keys will scroll the source code:
- Up / Down scroll the source code.
- Ctrl-P / Ctrl-N Previous or Next command in command history.
- Ctrl-O switch focus (does not work for me).
CGDB
☒ | TODO: Look at CGDB as simple alternative to GDB TUI |
Prepare debug session
- Compile with debug symbols, use option -g:
gcc -g program.c # -g : debug symbols
gcc -g -O0 program.c # ... -O0: disable optimization
- Force core dumps (see bash help ulimit):
ulimit -c unlimited
./a.out
# Segmentation fault (core dumped)
GDB invocation
gdb a.out
gdb a.out core.1234 # If coredump available
GDB commands
Reference
- GDB manual
- https://beej.us/guide/bggdb/
Break points and watch points | |
---|---|
|
Set a breakpoint at current line, at given line NUMBER or NUMBER lines after/before current line. |
|
Set breakpoint at LOCATION.
|
|
Stop execution when EXPR changes |
|
Stop execution when EXPR is accessed |
|
list breakpoints |
|
Clear breakpoint by LOCATION |
|
Delete all breakpoints |
|
Clear breakpoint by NUMBER (as listed by i b )
|
|
Disable breakpoint by NUMBER (as listed by i b )
|
|
Save current breakpoints as script FILE. Use source to reload.
|
Registers | |
|
Display registers |
Execute program | |
---|---|
|
Start (or restart) program. Arguments may include wildcards (*) and redirections (<, <<...) |
|
Start and break on main
|
|
Kill current program. |
|
Continue and interrupted program. |
|
Step (into) current line, or NUMBER lines. |
|
Step one (assembly) instruction exactly (N times) |
|
Run to next line (over current line) |
|
Step one (assembly) instruction exactly (N times), but over subroutine calls. |
|
Execute till returning from current selected frame. |
|
Run until temporary breakpoint set at LOCATION. |
|
Execute until the program reaches a source line greater than current (very handy). |
View stack | |
---|---|
|
Print backtrace of all stack frames, or innermost (outermost) COUNT frames if COUNT>0 (COUNT<0) |
|
Select frame FRAME and print stack frame |
|
Go up a level in the stack (frame calling current frame). |
|
Go down a level in the stack (frame called by current frame). |
View memory | |
---|---|
|
Display EXPR at each prompt (if within scope). |
|
Examine n memory locations at ADDR, format as n (
|
|
Print information on local variables / function arguments in the current frame |
|
print EXPR. |
|
Undisplay expression by NUMBER. |
View code | |
---|---|
|
List (10 by default) lines of current frame |
|
Disassemble a specified section of memory |
Miscellaneous | |
---|---|
|
Quit gdb. |
|
Get help on COMMAND, or search commands related to WORD. |
|
Source script FILE. |
RETURN | Repeat last command. |
|
Load symbols from FILE at given ADDR. |
Change memory
set VARIABLE = VALUE
For instance, given a program
int main(void){
char[] person = "Bob";
char[] p2 = "Alice";
printf("Hello %s\n");
}
set main::person = { 'S', 'a', 'm', 0x00 }
set main::person = "Sam"
# To change memory directly:
set {char [4]}0x43f800 = {0x01, 0x02, 0x03, 0x04}
Alternatively we can use strcpy
[4]:
(gdb) p malloc(20) $3 = (void *) 0x6ce81808 (gdb) p strcpy($3, "my string") $4 = 1827149832 (gdb) x/s $3 0x6ce81808: "my string"
Dump / restore
For instance, to load an Intel hex file:
restore firmware.hex
To generate the same file:
dump ihex memory firmware.hex 0x00400000 0x0040FFFF
Note that dump
can also save the result of an expression:
dump [format] memory filename start_addr end_addr
dump [format] value filename expr
Python extension
See Python in GDB manual.
Troubleshooting
Tips
Use RETURN to repeat last command
Pressing RETURN repeats all last command:
- Commands like
s
(step) orn
(next). Very handy to step in the code. - Command like
x/16w 0x10100000
orx/16i 0x10100000
, each time printing 16 new words.
Define a custom label for breakpoint in C/C++
Say we want to set a breakpoint at a specified location in source file, but this position may move over time. The easiest is to use an asm
statement to define the label [5]:
#include <stdio.h>
int main () {
void *ret_p = &&ret;
printf("ret: %p\n", ret_p);
goto *ret_p;
return 1;
ret:
asm("RET:")
return 0;
}
This will add a symbol table entry as follows.
gcc -Wl,--export-dynamic t.c -ldl
readelf -s a.out | grep RET
# 41: 0804858a 0 NOTYPE LOCAL DEFAULT 13 RET
Use help
to test a command abbreviation
Don't know if f
stands for finish
or frame
? Just use help f
:
help f
# Select and print a stack frame.
# ...
8 gdb tricks you should know
From https://blogs.oracle.com/ksplice/entry/8_gdb_tricks_you_should:
- Use
break WHERE if COND
- For instance
break context_switch if next == init_task
.
- Use
command
- This sets commands to be executed when a breakpoint is hit. For instance
b do_mmap_pgoff
# Breakpoint 1 at 0xffffffff8111a441: file mm/mmap.c, line 940.
command 1
# Type commands for when breakpoint 1 is hit, one per line.
# End with a line saying just "end".
>print addr
>print len
>print prot
>end
- Use
gdb --args
to specify runtime arguments
gdb --args pizzamaker --deep-dish --toppings=pepperoni
# ...
show args
# Argument list to give program being debugged when it is started is
# " --deep-dish --toppings=pepperoni".
b main
# ...
- Finding source files
- Use
directory
to add directory to search for source files.
list main
# 1192 ls.c: No such file or directory.
# in ls.c
directory ~/src/coreutils-7.4/src/
# Source directories searched: /home/nelhage/src/coreutils-7.4:$cdir:$cwd
list main
- Use
set substitute-path
to fix absolute paths
list schedule
# 5519 /build/buildd/linux-2.6.32/kernel/sched.c: No such file or directory.
# in /build/buildd/linux-2.6.32/kernel/sched.c
set substitute-path /build/buildd/linux-2.6.32 /home/nelhage/src/linux-2.6.32/
list schedule
- Use gcc
-ggdb3
to add MACRO's symbol as gdb macro
- Use gdb's variable like
$1
but also define your own
set $foo = 4
p $foo
# $3 = 4
- Use register variables
- All CPU registers are available as variables like
$R0
,$R1
, but gdb also defines cross-architectures ones like$sp
and$pc
. For instance, if$rsi
register is used to pass the 1st parameter,
break write if $rsi == 2
- The
x
command
x/s 0xffffffff81946000
# ffffffff81946000 <>: "root=/dev/sda1 quiet"
# Use x/i as a quick way to disassemble memory
x/5i schedule
# 0xffffffff8154804a <schedule>: push %rbp
# 0xffffffff8154804b <schedule+1>: mov $0x11ac0,%rdx
# 0xffffffff81548052 <schedule+8>: mov %gs:0xb588,%rax
# 0xffffffff8154805b <schedule+17>: mov %rsp,%rbp
# 0xffffffff8154805e <schedule+20>: push %r15
# Print code surround current pc
x/20i $ip-40
- Use the
@
symbol
- The following works if source code is something like int a[ 10 ] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };:
p a
# $1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
- But if the array is much bigger, or array is a C++ vector, we can use
@
:
p *&a[0]@10
# $1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
p *&a[550]@4
# $1 = {551, 552, 553, 554}
Use value history variables $
See summary list below. For more details, check GDB manual on Value History.
$1 # History value 1
$2 # History value 2
$ # MOST RECENT value in the history
$$ # ... and value before that
$$2 # ... and value before that
$$0 # Same as $
$$1 # Same as $$
For instance:
p *$ # Dereference last value
p *$.next # ... follow the chain
<RETURN> # ... again
<RETURN> # ... again
show values # Print the last 10 values.
Disassemble at an arbitrary address
disas FUNCTION
command only works for defined FUNCTION
. To disassemble at an arbitrary address:
x/i 0x10100000
one instruction at address0x10100000
,x/16i 0x10100000
(16 instruction) at addressOx10100000
,x/16i *0x10100000
(16 instruction) at address contained atOx10100000
,x/16i $PC
disassemble 16 instructions at PC.
Redirect STDIN
Redirect STDIN with the run
command:
gdb ./myprog
run <input.txt # Run with stdin redirected
One can also pass parameters with the run
command (or use gdb --args
command-line parameter).
Exit gdb if run fails
Use python script
def tryRun():
try:
gdb.execute("run")
except:
print("Exception occured during gdb command: run")
gdb.execute("quit 1")
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()
Load symbols from another ELF file
This is useful in embedded systems where some part of the code are already loaded or stored in a ROM. When the debugger stops in a function for which it doesn't have the symbols, it cannot decode the stack and provide a correct backtrace. The fix is to load the symbols for that function using the command add-symbol-file
.
add-symbol-file a.out 0 # Add symbol from file a.out, at offset 0
Typically the offset to give is the lowest address in the ELF file.
Modify memory content
Use set
[6]. Simplest is to modify a variable:
set variable i = 10
But GDB can write in any memory location:
set {int}0x83040 = 4
set *((int *)0x83040) = 4
Remote debug with gdbserver
Start the gdb server on the remote machine:
gdbserver --multi 192.168.20.1:6666 # CLIENT_ADDR:REMOTE_PORT
This will listen on port 6666
, waiting for connection from client 192.168.20.1
On the client, start the GDB client:
gdb-multiarch ./my_exec # multiarch necessary if client/server are different arch
Then, in GDB:
target extended-remote {REMOTE_ADDR}:{REMOTE_PORT}
set remote exec-file /path/on/remote/to/my_exec
If the remote executable uses dynamic libraries, these must be sent to the client. To accelerate the loading, copy first the libraries locally on the client, then tell GDB where to find them [7]:
set sysroot /d/st/courses/training_obfu/formation_reverse/dynamic/tp/qmessage/pi_root
set solib-absolute-prefix /d/st/courses/training_obfu/formation_reverse/dynamic/tp/qmessage/pi_root
set solib-search-path /d/st/courses/training_obfu/formation_reverse/dynamic/tp/qmessage/pi_root
Examples
Simple Segmentation Fault Example
(From [8])
Example program segfault.c:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
char *buf;
buf = malloc(1<<31);
fgets(buf, 1024, stdin);
printf("%s\n", buf);
return 1;
}
|
Compile and launch gdb:
gcc -g segfault.c
gdb a.out
The debug session run
backtrace
frame 3
print buf
kill
break segfault.c:8
run
print buf
next
print buf Fix the bug, then start again, watching now watch buf
# Start again, answer 'y' when asked to start from beginning
run
# Break at watch point, let's _c_ontinue
c
|
More GDB stuff
display i # Display i at each stop
display/4i $pc # Display the 4 next instruction at each stop
u # Continue until we reach an higher pc address
Using gdb-dashboard
da # Print dashboard again
da reg # Remove register
da reg # Add it back
da m w 0x1000 0x10 # Watch memory zone at 0x1000 for 16 bytes
da m w 0x2000 0x10 # Add a second zone at 0x2000
Frequently used commands
info reg
x/i $pc
GDB script
Breakpoint command lists
See help commands
(or here)
Example script:
break foo if x>0
commands
silent
printf "x is %d\n",x
cont
end
- Use
silent
to make the breakpoint silent. - Use condition (here
if x>0
) on breakpoint to break conditionally.
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()