First of all, we need to install GDB:
sudo apt install gdb
Next we will be installing an extension to GDB called pwndbg
that will make our life easier. Instructions for installation are here.
You can verify that your installation works by running gdb /bin/true
in your command line, then running start
when the prompt shows.
You should see something similar to the screenshot above.
To start off, you may use the below template for your exploits
from pwn import *
p = process("path to bin")
pause()
# actual exploit here
p.interactive()
On execution of the above script, it will pause and print out the target binary's process id:
We can then attach to the target process using gdb in another terminal (make sure to NOT kill our paused python script):
This extremely long output that is printed is the context
of your program (a minimal view into the state of the program).
Now, you can go to the original terminal where the script is running, then press enter to unpause your script. The below commands are used inside the gdb terminal to interact with the program. Right now, the program is still in a paused state.
continue
/c
You can unpause the execution of the program. Execution continues until the program exits or until a breakpoint is reached
breakpoint <point>
/b <point>
Place a breakpoint at <point>
e.g. main
(directly at the first instruction of the main
function) or
main+55
(the instruction 55 bytes after the main
function) or *0x404123
(the instruction at 0x404123).
A breakpoint is a place where execution is paused when the $rip (instruction pointer) reaches there.
si
Execute exactly 1 instruction (step into). If the instruction is a call
instruction, we will be stepping INSIDE the target function.
For example, if you are at paused at the call puts
instruction and you enter this into gdb, you will arrive at the call stub for puts
.
ni
Execute exactly 1 instruction (step over), without stepping into a function. If the instruction call
instruction, the entire target function
is executed, then we are the instruction directly after the call
instruction.
tele <address> <count>
Print out data from <address>
, a total of <count>
rows. For example, to print relative to the rbp
register, at a offset of 0x20, you can use tele rbp+0x20
.
disass <func>
Print out the assembly for this <func>
function.
Worflow is similar to the section above Commands
. Right after this, you will need to put a breakpoint right after the first 'input-based'
call. For example, consider this function:
The first 'input-based' call would be the call to read_long
, which reads in a integer. Thus we should put a breakpoint using b main+44
.
Arguments to functions are stored in registers. In sequence the arguments are stored in (rdi, rsi, rdx, rcx, r8, r9, then the stack). Return value of a function is stored in the rax register.
If you see eax
instead of rax
, do not fret, they are in fact the same register:
If you are inside the function a
, local variables are between the rsp
and rbp
registers. The saved rbp
is at rbp
, and the return address is at rbp+8
.
For example, saved rbp
is 0 and return address is libc_start_main+243
in the above image. Which local variable is which, depends on the assembly.
e.g. for based on the above image and the image at Typical Workflow
, the output of read_long
is saved into a variable at rbp-0x8
.