The GNU Debugger (GDB)

Getting help with GDB

Whenever you want to find out more information about GDB commands feel free to search for it inside the documentation or by using the help command followed by your area of interest. For example searching for help for the disassemble command can be obtained by running the following command in GDB:

#print info about all help areas available
#identify the area of your question
(gdb) help
#print info about available data commands
#identify the command you want to learn more about
(gdb) help data
#print info about a specific command
#find out more about the command you are searching for
(gdb) help disassemble

Opening a program with GDB

A program can be opened for debugging in a number of ways. We can run GDB directly attaching it to a program:

$ gdb [executable-file]

Or we can open up GDB and then specify the program we are trying to attach to using the file or file-exec command:

$ gdb
(gdb) file [executable-file]

Furthermore we can attach GDB to a running service if we know it's process id:

$ gdb --pid [pid_number]

Disassembling

GDB allows disassembling of binary code using the disassemble command (it may be shortened to disas). The command can be issued either on a memory address or using labels.

(gdb) disas *main
Dump of assembler code for function main:
=> 0x080484c4 <+0>:,  push   ebp
   0x080484c5 <+1>:,  mov    ebp,esp
   0x080484c7 <+3>:,  and    esp,0xfffffff0
   0x080484ca <+6>:,  sub    esp,0x30
   0x080484cd <+9>:,  mov    DWORD PTR [esp+0x12],0x24243470
   0x080484d5 <+17>:,mov    DWORD PTR [esp+0x16],0x64723077
   0x080484dd <+25>:,mov    WORD PTR [esp+0x1a],0x21
....Output ommited.....
 
(gdb) disas 0x080484c4
Dump of assembler code for function main:
=> 0x080484c4 <+0>:,  push   ebp
   0x080484c5 <+1>:,  mov    ebp,esp
   0x080484c7 <+3>:,  and    esp,0xfffffff0
   0x080484ca <+6>:,  sub    esp,0x30
   0x080484cd <+9>:,  mov    DWORD PTR [esp+0x12],0x24243470
   0x080484d5 <+17>:,mov    DWORD PTR [esp+0x16],0x64723077
   0x080484dd <+25>:,mov    WORD PTR [esp+0x1a],0x21

Adding Breakpoints

Breakpoints are important to suspend the execution of the program being debugged in a certain place. Adding breakpoints is done with the break command. A good idea is to place a breakpoint at the main function of the program you are trying to exploit. Given the fact that you have already run objdump and disassembled the program you know the address for the start of the main function. This means that we can set a breakpoint for the start of our program in two ways:

(gdb) break *main
(gdb) break *0x[main_address_obtained_with_objdump]

The general format for setting breakpoints in GDB is as follows:

(gdb) break [LOCATION] [thread THREADNUM] [if CONDITION]

Issuing the break command with no parameters will place a breakpoint at the current address.

GDB allows using abbreviated forms for all the commands it supports. Learning these abbreviations comes with time and will greatly improve you work output. Always be on the lookout for using abbreviated commands.

The abbreviated command for setting breakpoints is simply b.

Listing Breakpoints

At any given time all the breakpoints in the program can be displayed using the info breakpoints command:

(gdb) info breakpoints

You can also issue the abbreviated form of the command:

(gdb) i b

Deleting Breakpoints

Breakpoints can be removed by issuing the delete breakpoints command followed by the breakpoints number, as it is listed in the output of the info breakpoints command.

(gdb) delete breakpoints [breakpoint_number]

You can also delete all active breakpoints by issuing the following the delete breakpoints command with no parameters:

(gdb) delete breakpoints

Once a breakpoint is set you would normally want to launch the program into execution. You can do this by issuing the run command. The program will start executing and stop at the first breakpoint you have set.

(gdb) run

Execution flow

Execution flow can be controlled in GDB using the continue, stepi, nexti as follows:

(gdb) help continue
#Continue program being debugged, after signal or breakpoint.
#If proceeding from breakpoint, a number N may be used as an argument,
#which means to set the ignore count of that breakpoint to N - 1 (so that
#the breakpoint won't break until the Nth time it is reached).
(gdb) help stepi
#Step one instruction exactly.
#Argument N means do this N times (or till program stops for another reason).
(gdb) help nexti
#Step one instruction, but proceed through subroutine calls.
#Argument N means do this N times (or till program stops for another reason).

You can also use the abbreviated format of the commands: c (continue), si (stepi), ni (nexti).

If at any point you want to start the program execution from the beginning you can always reissue the run command.

Another technique that can be used for setting breakpoints is using offsets.

As you already know, each assembly instruction takes a certain number of bytes inside the executable file. This means that whenever you are setting breakpoints using offsets you must always set them at instruction boundaries.

(gdb) break *main
Breakpoint 1 at 0x80484c4
(gdb) run
Starting program: bash_login
 
Breakpoint 1, 0x080484c4 in main ()
(gdb) disas main
Dump of assembler code for function main:
=> 0x080484c4 <+0>:,  push   ebp
   0x080484c5 <+1>: ,mov    ebp,esp
   0x080484c7 <+3>: ,and    esp,0xfffffff0
   0x080484ca <+6>: ,sub    esp,0x30
   0x080484cd <+9>: ,mov    DWORD PTR [esp+0x12],0x24243470
   0x080484d5 <+17>:,mov    DWORD PTR [esp+0x16],0x64723077
   0x080484dd <+25>:,mov    WORD PTR [esp+0x1a],0x21
 
.....Output ommited.....
 
(gdb) break *main+6
Breakpoint 2 at 0x80484ca

Examine and Print, your most powerful tools

GDB allows examining of memory locations be them specified as addresses or stored in registers. The x command (for examine) is arguably one of the most powerful tool in your arsenal and the most common command you are going to run when exploiting.

The format for the examine command is as follows:

(gdb) x/nfu [address]
        n:  How many units to print
        f:  Format character
              a Pointer
              c Read as integer, print as character
              d Integer, signed decimal
              f Floating point number
              o Integer, print as octal
              s Treat as C string (read all successive memory addresses until null character and print as characters)
              t Integer, print as binary (t="two")
              u Integer, unsigned decimal
              x Integer, print as hexadecimal
        u:  Unit
              b: Byte
              h: Half-word (2 bytes)
              w: Word (4 bytes)
              g: Giant word (8 bytes)
              i: Instruction (read n assembly instructions from the specified memory address)

In contrast with the examine command, which reads data at a memory location the print command (shorthand p) prints out values stored in registers and variables.

The format for the print command is as follows:

(gdb) p/f [what]
        f:  Format character
              a Pointer
              c Read as integer, print as character
              d Integer, signed decimal
              f Floating point number
              o Integer, print as octal
              s Treat as C string (read all successive memory addresses until null character and print as characters)
              t Integer, print as binary (t="two")
              u Integer, unsigned decimal
              x Integer, print as hexadecimal
              i Instruction (read n assembly instructions from the specified memory address)

For a better explanation please follow through with the following example:

#a breakpoint has been set inside the program and the program has been run with the appropriate commands to reach the breakpoint
#at this point we want to see which are the following 10 instructions
(gdb) x/10i 0x080484c7
   0x80484c7 <main+3>:,and    esp,0xfffffff0
   0x80484ca <main+6>:,sub    esp,0x30
   0x80484cd <main+9>:,mov    DWORD PTR [esp+0x12],0x24243470
   0x80484d5 <main+17>:,mov    DWORD PTR [esp+0x16],0x64723077
   0x80484dd <main+25>:,mov    WORD PTR [esp+0x1a],0x21
   0x80484e4 <main+32>:,mov    eax,0x8048630
   0x80484e9 <main+37>:,mov    DWORD PTR [esp],eax
   0x80484ec <main+40>:,call   0x80483b0 <printf@plt>
   0x80484f1 <main+45>:,mov    eax,0x804864a
   0x80484f6 <main+50>:,lea    edx,[esp+0x1c]
 
#let's examine the memory at 0x80486a0 because we have a hint that the eax register holds a parameter
#as it is then placed on the stack (we'll explain later how we have reached this conclusion)
(gdb) x/s 0x80486a0
0x80486a0:, "\nPlease provide password:"
 
# we now set a breakpoint for main+49
(gdb) break *0x80484e9
Breakpoint 3 at 0x80484e9
 
(gdb) continue
Continuing.
 
Breakpoint 3, 0x080484e9 in main ()
 
#let's examine the eax register (it should hold the address for the beginning of the string so let's interpret it as appropriately)
#take note that in GDB registers are preceded by the "$" character very much like variables
(gdb) x/s $eax
0x8048630:, "\nPlease provide password:"
#now let's print the contents of the eax register as hexadecimal
 
(gdb) p/x $eax
$1 = 0x8048630
# as you can see the eax register hold the memory for the beginning of the string
# this shows you how "x" interprets data from memory while "p" merely prints out the contents in the required format
# you can think of it as "x" dereferencing while "p" not dereferencing

GDB command file

When exploiting, there are a couple of commands that you will issue periodically and doing that by hand will get cumbersome. GDB commands files will allow you to run a specific set of commands automatically after each command you issue manually. This comes in especially handy when you're stepping through a program and want to see what happens with the registers and stack after each instruction is ran, which is the main target when exploiting.

The examine command online has sense when code is already running on the machine so inside the file we are going to use the display command which translates to the same output.

In order to use this option you must first create your commands file. This file can include any GDB commands you like but a good start would be printing out the content of all the register values, the next ten instructions that are going to be executed, and some portion from the top of the stack.

The reason for examining all of the above after each instruction is ran will become more clear once the we go through the second section of the session.

Command file template:

display/10i $eip
display/x $eax
display/x $ebx
display/x $ecx
display/x $edx
display/x $edi
display/x $esi
display/x $ebp
display/32xw $esp

In order to view all register values you could use the x command. However the values of all registers can be obtained by running the info all-registers command:

(gdb) info all-registers
eax            0x8048630,134514224
ecx            0xbffff404,-1073744892
edx            0xbffff394,-1073745004
ebx            0xb7fc6ff4,-1208193036
esp            0xbffff330,0xbffff330
ebp            0xbffff368,0xbffff368
esi            0x0,0
edi            0x0,0
eip            0x80484e9,0x80484e9 <main+37>
eflags         0x286,[ PF SF IF ]
cs             0x73,115
ss             0x7b,123
ds             0x7b,123
es             0x7b,123
fs             0x0,0
gs             0x33,51
st0            *value not available*
st1            *value not available*
st2            *value not available*
st3            *value not available*
st4            *value not available*
st5            *value not available*
st6            *value not available*
st7            *value not available*
fctrl          0x37f,895
fstat          0x0,0
ftag           0xffff,65535
fiseg          0x0,0
fioff          0x0,0
foseg          0x0,0
---Type <return> to continue, or q <return> to quit---
fooff          0x0,0
fop            0x0,0
mxcsr          0x1f80,[ IM DM ZM OM UM PM ]
ymm0           *value not available*
ymm1           *value not available*
ymm2           *value not available*
ymm3           *value not available*
ymm4           *value not available*
ymm5           *value not available*
ymm6           *value not available*
ymm7           *value not available*
mm0            *value not available*
mm1            *value not available*
mm2            *value not available*
mm3            *value not available*
mm4            *value not available*
mm5            *value not available*
mm6            *value not available*
mm7            *value not available*

One thing you might notice while using GDB is that addresses seem to be pretty similar between runs. Although with experience you will gain a better feel for where an address points to, one thing to remember at this point would be that stack addresses usually have the 0xbffff…. format.

In order to run GDB with the commands file you have just generated, when launching GDB specify the -x [command_file] parameter.

GDB PEDA

As you can see using GDB can be cumbersome, this is why we recommend using the PEDA (Python Exploit Development Assistance for GDB) plugin presented in the previous session.

Give the fact that PEDA is just a wrapper, all the functionality of GDB will be available when running gdb-peda.

Some of the advantages of using PEDA include:

  1. Automatic preview of registers, code and stack after each instruction (you no longer need to create your own commands file)
  2. Automatic dereferencing and following through of memory locations
  3. Color coding

Installation

You can download peda using:

git clone https://github.com/longld/peda.git ~/peda

To set it up add the following to your ~/.gdbinit file and then run gdb as usual:

.gdbinit
# Source all settings from the peda dir
source ~/peda/peda.py
 
# These are other settings I have found useful
 
# When inspecting large portions of code the scrollbar works better than 'less'
set pagination off
 
 
# Keep a history of all the commands typed. Search is possible using ctrl-r
set history save on
set history filename ~/.gdb_history
set history size 32768
set history expansion on

PEDA Commands

Click to display ⇲

Click to hide ⇱

pdis command gives a pretty output that is similar to what the disas command in GDB prints:

Usage:  pdis main

If pdis is used with an address as a parameter, the output will be similar to what x/Ni prints out (where N is the number of instructions you want to disassemble) Usage: -pdis [address]/N - where N is the number of instructions you want to be printed

The stepi command has the same effect as in GDB however, if you are running PEDA you will notice that after each step PEDA will automatically print register values, several lines of code from eip register and a portion of the stack:

gdb-peda$ stepi
[----------------------------------registers-----------------------------------]
EAX: 0x1
EBX: 0xb7fc6ff4 --> 0x1a0d7c
ECX: 0xbffff404 --> 0xbffff569 ("/home/dgioga/sss/bash_login")
EDX: 0xbffff394 --> 0xb7fc6ff4 --> 0x1a0d7c
ESI: 0x0
EDI: 0x0
EBP: 0xbffff368 --> 0x0
ESP: 0xbffff360 --> 0x8048560 (<__libc_csu_init>:,push   ebp)
EIP: 0x80484ca (<main+6>:,sub    esp,0x30)
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x80484c4 <main>:,push   ebp
   0x80484c5 <main+1>:,mov    ebp,esp
   0x80484c7 <main+3>:,and    esp,0xfffffff0
=> 0x80484ca <main+6>:,sub    esp,0x30
   0x80484cd <main+9>:,mov    DWORD PTR [esp+0x12],0x24243470
   0x80484d5 <main+17>:,mov    DWORD PTR [esp+0x16],0x64723077
   0x80484dd <main+25>:,mov    WORD PTR [esp+0x1a],0x21
   0x80484e4 <main+32>:,mov    eax,0x8048630
[------------------------------------stack-------------------------------------]
0000| 0xbffff360 --> 0x8048560 (<__libc_csu_init>:,push   ebp)
0004| 0xbffff364 --> 0x0
0008| 0xbffff368 --> 0x0
0012| 0xbffff36c --> 0xb7e3f4d3 (<__libc_start_main+243>:,mov    DWORD PTR [esp],eax)
0016| 0xbffff370 --> 0x1
0020| 0xbffff374 --> 0xbffff404 --> 0xbffff569 ("/home/dgioga/sss/bash_login")
0024| 0xbffff378 --> 0xbffff40c --> 0xbffff585 ("SSH_AGENT_PID=1948")
0028| 0xbffff37c --> 0xb7fdc858 --> 0xb7e26000 --> 0x464c457f
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x080484ca in main ()

You can always use the following commands to obtain context at any given moment inside the debug process:

  1. context reg
  2. context code
  3. context stack
  4. context all

One additional PEDA command which can be used to show values in registers is the telescope command. The command dereferentiates pointer values until it gets to a value and prints out the entire trace.

The command can be used with both registers and memory addresses:

gdb-peda$ telescope $eax
0000| 0x8048630 ("\nPlease provide password:")
0004| 0x8048634 ("ase provide password:")
0008| 0x8048638 ("provide password:")
0012| 0x804863c ("ide password:")
0016| 0x8048640 ("password:")
0020| 0x8048644 ("word:")
0024| 0x8048648 --> 0x7325003a (':')
0028| 0x804864c --> 0x0
gdb-peda$ telescope 0x8048630
0000| 0x8048630 ("\nPlease provide password:")
0004| 0x8048634 ("ase provide password:")
0008| 0x8048638 ("provide password:")
0012| 0x804863c ("ide password:")
0016| 0x8048640 ("password:")
0020| 0x8048644 ("word:")
0024| 0x8048648 --> 0x7325003a (':')
0028| 0x804864c --> 0x0

In the example above, the memory address 0x8048630 was loaded into EAX. That is why examining the register or the memory location gives the same output.

For more information on various PEDA commands you can always visit the PEDA help through the help peda command It is always a better idea to use PEDA commands when available. However you should also know the basics of using GDB as well.

Altering variables and memory with PEDA and GDB

In addition to basic registers, GDB has a two extra variables which map onto some of the existing registers, as follows: * $pc – $eip * $sp – $esp * $fp – $ebp

In addition to these there are also two registers which can be used to view the processor state $ps – processor status

Values of memory addresses and registers can be altered at execution time. Because altering memory is a lot easier using PEDA we are going to use it throughout today's session.

If you want to do it the hard way (no PEDA) you can always look into the set GDB command.

The easiest way of altering the execution flow of a program is editing the $eflags register just before jump instructions.

Using PEDA the $eflags register can be easily modified:

gdb-peda$ eflags
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
gdb-peda$ help eflags
Display/set/clear value of eflags register
Usage:
    eflags
    eflags [set|clear] flagname

Notice that the flags that are sent are printed in all-caps when the eflags command is issued.

The patch command can be used to modify values that reside inside memory.

gdb-peda$ telescope 0x8048630
0000| 0x8048630 ("\nPlease provide password:")
0004| 0x8048634 ("ase provide password:")
0008| 0x8048638 ("provide password:")
0012| 0x804863c ("ide password:")
0016| 0x8048640 ("password:")
0020| 0x8048644 ("word:")
0024| 0x8048648 --> 0x7325003a (':')
0028| 0x804864c --> 0x0
 
gdb-peda$ help patch
Patch memory start at an address with string/hexstring/int
Usage:
    patch address (multiple lines input)
    patch address "string"
    patch from_address to_address "string"
    patch (will patch at current $pc)
 
gdb-peda$ patch 0x8048630 "Modified valu of the string\x00"
Written 28 bytes to 0x8048630
 
gdb-peda$ telescope 0x8048630
0000| 0x8048630 ("Modified valu of the string")
0004| 0x8048634 ("fied valu of the string")
0008| 0x8048638 (" valu of the string")
0012| 0x804863c ("u of the string")
0016| 0x8048640 (" the string")
0020| 0x8048644 (" string")
0024| 0x8048648 --> 0x676e69 ('ing')
0028| 0x804864c --> 0x0

As you can see the string residing in memory at address 0x8048630 has been modified using the patch command.

PEDA does not offer enhancements in modifying registry values. For modifying registry values you can use the GDB set command.

gdb-peda$ p/x $eax
$10 = 0x1
gdb-peda$ set $eax=0x80
gdb-peda$ p/x $eax
$11 = 0x80

Basic stuff

The most common actions done in gdb are: setting breakpoints, stepping through program execution and examining memory. The following are commands you need to know:

  • run [args] ⇒ restart the program with [args] as args
  • stepi ⇒ execute the current instruction and go to the next one - if it's a call instruction go to that subroutine (step into)
  • nexti ⇒ execute the current instruction and go to the next one - if it's a call instruction execute the whole subroutine in the background (step over)
  • break ⇒ set a permanent breakpoint on an address or function
  • info break ⇒ display all current breakpoints set
  • delete 2 ⇒ delete the breakpoint with index 2 (from the list of current breakpoints)
  • continue ⇒ continue execution after hitting a breakpoint (or receiving a signal)
  • hexdump <addr> [/NR] ⇒ dump NR lines of memory starting from <addr>. (by default NR is 1)
  • x /s <addr> ⇒ dump a string starting from <addr> (/100s would dump 100 strings)
  • x /wx <addr> ⇒ dump a dword starting from <addr> (/100wx would dump 100 dwords)

Dynamic analysis shortcuts

In peda you have quick access to information that you would otherwise have to obtain using other tools as presented before:

gdb-peda$ vmmap
Start      End        Perm	Name
0x08048000 0x08049000 r-xp	/tmp/black/crackmes/crackme3
0x08049000 0x0804a000 r--p	/tmp/black/crackmes/crackme3
0x0804a000 0x0804b000 rw-p	/tmp/black/crackmes/crackme3
0xf7ded000 0xf7dee000 rw-p	mapped
0xf7dee000 0xf7f93000 r-xp	/lib32/libc-2.17.so
0xf7f93000 0xf7f95000 r--p	/lib32/libc-2.17.so
0xf7f95000 0xf7f96000 rw-p	/lib32/libc-2.17.so
0xf7f96000 0xf7f99000 rw-p	mapped
0xf7fda000 0xf7fdb000 rw-p	mapped
0xf7fdb000 0xf7fdc000 r-xp	[vdso]
0xf7fdc000 0xf7ffc000 r-xp	/lib32/ld-2.17.so
0xf7ffc000 0xf7ffd000 r--p	/lib32/ld-2.17.so
0xf7ffd000 0xf7ffe000 rw-p	/lib32/ld-2.17.so
0xfffdd000 0xffffe000 rw-p	[stack]
 
gdb-peda$ elfheader
.interp = 0x8048174
.note.ABI-tag = 0x8048188
.hash = 0x80481a8
.gnu.hash = 0x80481e0
.dynsym = 0x8048204
.dynstr = 0x8048294
.gnu.version = 0x80482f6
.gnu.version_r = 0x8048308
.rel.dyn = 0x8048328
.rel.plt = 0x8048338
.init = 0x8048368
.plt = 0x8048390
.text = 0x8048400
.fini = 0x80486c4
.rodata = 0x80486d8
.eh_frame_hdr = 0x80486fc
.eh_frame = 0x8048738
.init_array = 0x8049f00
.fini_array = 0x8049f04
.jcr = 0x8049f08
.dynamic = 0x8049f0c
.got = 0x8049ffc
.got.plt = 0x804a000
.data = 0x804a024
.bss = 0x804a044
gdb-peda$ elfsymbol
Found 6 symbols
fgets@plt = 0x80483a0
puts@plt = 0x80483b0
__gmon_start__@plt = 0x80483c0
exit@plt = 0x80483d0
strlen@plt = 0x80483e0
__libc_start_main@plt = 0x80483f0

You can also search for strings in the mapped regions:

gdb-peda$ find "Correct"
Searching for 'Correct' in: None ranges
Found 2 results, display max 2 items:
crackme3 : 0x80486ea ("Correct!")
crackme3 : 0x80496ea ("Correct!")
 
gdb-peda$ find "/bin/sh"
Searching for '/bin/sh' in: None ranges
Found 1 results, display max 1 items:
libc : 0xf7f53be6 ("/bin/sh")
cns/extra/gdb.txt · Last modified: 2020/10/11 15:11 by mihai.dumitru2201
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0