GDB Guide Part 1 - Basics
GDB Guides Series:
1. Introduction
Part 1 of this series is meant as an introduction to GDB for beginners. As I progress through this series, I will be sharing more advanced tips and even provide a nice template in writing your own custom GDB commands using python!
2. Overview
Today’s guide will show you how to:
- Attach to a Process
- Set useful GDB Configuration
- Inspect Registers
- Disassemble Functions
I will be providing an example C file here for you to compile so that you may follow the walkthrough and deepen your understanding.
To compile test.c:
$ gcc test.c -o test
3. Attaching to a Process
There are two ways to go about doing this. The first which is the simplest, is to run GDB with the binary path as its parameter. The second way to do this is to attach to an already running process via its PID.
3.1 Via Binary
If you have chosen this method, it means that you know the binary full path and also the parameters to run the binary.
$ gdb $BINARY_PATH
Afterwards, run the binary with its parameters.
(gdb) r $param1 $param2 ....
3.2 Via PID
Alternatively, you can also attach to a running process via its PID in several ways as well.
-
You can start either GDB first, then do
attach $PID.Attaching via PID - Method 1$ gdb (gdb) attach $pid
-
OR, you can start GDB with the
-pparameter.Attaching via PID - Method 2$ gdb -p `pidof $PROC_NAME`
3.3 Test Example
I have created a video for those who are more visual learners, using a compiled test.c as an example for GDB to attach to.
Attaching to Test Program - Short Demo
4. Useful GDB Configurations
I would recommend the following configurations right from the get-go when doing dynamic analysis.
-
set pagination off- By default, this is set to “on” and it is really annoying when you are dealing with large outputs, prompting you to press
Enterin order to continue.
- By default, this is set to “on” and it is really annoying when you are dealing with large outputs, prompting you to press
-
set logging enabled on- By default, this is set to “off”. This is very useful especially when you want to look back at the commands you ran and the outputs you had. If the file path is not stated, it will be written to gdb.txt. See here for more information on GDB logging.
-
set confirm off- By default, this is set to “on” as GDB is very cautious. When automating dynamic analysis with a GDB script, this will break your automation. I will recommend turning this off only when you need to automate things.
-
set print pretty on- By default, this is set to “off”. This is more of a personal preference for me. This is more for you to decide whether you want your structure to be printed in one line or in a more readable form.
Pretty Print On/Off
# set print pretty on $1 = { next = 0x0, flags = { sweet = 1, sour = 1 }, meat = 0x54 "Pork" } # set print pretty off $1 = {next = 0x0, flags = {sweet = 1, sour = 1}, meat = 0x54 "Pork"}
- By default, this is set to “off”. This is more of a personal preference for me. This is more for you to decide whether you want your structure to be printed in one line or in a more readable form.
-
set output-radix 16- By default, output-radix is set to 10. This would print out numbers in GDB as hexadecimal. I’m hard-wired to read hexadecimals when debugging stuff, so this is a must for me.
-
set disassembly-flavor intel- By default disassembly-flavor is set to “att”. However, I prefer the “intel” style disassembly despite specializing in Linux exploitation 🤡.
-
set print elements 0- By default, the limit is set to 200 characters. I will usually set this to 0 which means that printing is unlimited. This is especially useful whenever you are trying to print a very large string from memory.
5. Inspecting Registers
There are actually several ways to print the values of the registers.
5.1 Via info registers
# Prints all registers' values
(gdb) info registers
# Print only rax value
(gdb) info registers rax
# Print an array of registers
(gdb) info registers rdi rsi rdx rcx
5.2 Via print / printf
# Print rax value
(gdb) print $rax
# Printf rax value
(gdb) printf "rax = %d\n", $rax
# Printf rdi, rsi, rdx, rcx
(gdb) printf "rdi = %d\nrsi = %d\nrdx = %d\nrcx = %d\n", $rdi, $rsi, $rdx, $rcx
5.3 Test Example
Showing Registers’ Values - Short Demo
6. Looking at Disassembly
Looking at disassembly provides us more information such as offsets and instruction sets. Of course, you need not use GDB’s disassembly feature for static analysis and can always use Ghidra or IDA (Free Version or Pro) which is a lot more user-friendly.
Assuming we can only use GDB, then the way to go about doing this is to make use of the disassemble command.
# If no address/function is stated, disassemble function at current instruction.
(gdb) disassemble
# Disassemble the entire function
(gdb) disassemble [Function]
# Disassemble the entire function that contain the given address
(gdb) disassemble [Address]
# Disassemble instruction between start and end address.
(gdb) disassemble [Start],[End]
# Disassemble from start of function to address of function + offset.
(gdb) disassemble [Function],+[Length]
# Disassmble from address to address + offset
(gdb) disassemble [Address],+[Length]
# Show the current instruction (rip)
(gdb) disassemble /m [...]
# Show the byte values of all instructions
(gdb) disassemble /r [...]
6.1 Test Example
Viewing Disassembly - Short Demo
7. Conclusion
I figured this is a good stopping point and of course, we are not yet done! I barely scratched the surface just for the basics! Stay tuned for part 2, where I will teach you more about Linux calling conventions, setting breakpoints etc.