libc6-dev-i386
library:$ sudo apt install libc6-dev-i386
$ git clone https://github.com/longld/peda.git ~/peda (...) $ echo "source ~/peda/peda.py" >> ~/.gdbinit (...)
To check if everything is OK, run the command gdb
with no arguments. The prompt should be similar to this:
$ gdb GNU gdb (Ubuntu 8.1-0ubuntu3.2) 8.1.0.20180409-git .... gdb-peda$
Type q
to exit gdb. We are using gdb peda instead of the classic gdb because it is much more user friendly. We hope you'll like it ;)
A buffer overflow occurs when data written to a buffer overruns its boundary and overwrites adjacent memory locations, due to insufficient bounds checking.
In order to start debugging using GDB, you need to specify the program to be inspected. There are two options for doing this:
student@host$ gdb prog
student@host$ gdb (...) gdb-peda$ file prog Reading symbols from prog...done.
Once the debugging symbols from the executable were loaded, you can start executing your program using the run
command.
gdb-peda$ run
r
, ru
and run
are equivalent; c
, co
, continue
are equivalent.
In order to specify arguments for the debugged program, you can either:
gdb-peda$ set args arg1 arg2
gdb-peda$ run arg1 arg2
run
with no arguments uses the same arguments used by the previous run
, or those set by the set args
command.
Breakpoints represent places in your program where the execution should be stopped. They are added using the break command and its variants. Here are the most common usages:
break function
- Set a breakpoint at entry to function function. When using source languages that permit overloading of symbols, such as C++, function may refer to more than one possible place to break. See section Breakpoint menus, for a discussion of that situation.break linenum
- Set a breakpoint at line linenum in the current source file. The current source file is the last file whose source text was printed. The breakpoint will stop your program just before it executes any of the code on that line.break filename:linenum
- Set a breakpoint at line linenum in source file filename.break filename:function
- Set a breakpoint at entry to function function found in file filename. Specifying a file name as well as a function name is superfluous except when multiple files contain similarly named functions.break *address
- Set a breakpoint at address address. You can use this to set breakpoints in parts of your program which do not have debugging information or source files.
You can see an overview of the current breakpoints using the info break
command.
gdb-peda$ info break Num Type Disp Enb Address What 1 breakpoint keep y 0x0804856d in main at buggy.c:33 2 breakpoint keep y 0x080484d1 in print_message at buggy.c:12 3 breakpoint keep y 0x080484d1 in print_message at buggy.c:12
info break
is i b
.
In order to remove breakpoints, you can use the clear
or the delete
(d
) command. With the clear
command you can delete breakpoints according to where they are in your program. With the delete
command you can delete individual breakpoints by specifying their breakpoint numbers.
gdb-peda$ delete 2 gdb-peda$ clear buggy.c:33 Deleted breakpoint 1
Once you want to resume execution, you can use the continue
command.
gdb-peda$ continue Continuing. [Inferior 1 (process 5809) exited normally]
The start
command is very similar to run
, but instead of running the program until it ends (or until it crashes), it sets a breakpoint at the beginning of the main function.
[----------------------------------registers-----------------------------------] EAX: 0x56556fd4 --> 0x1edc EBX: 0x0 ECX: 0xffffd5c0 --> 0x1 EDX: 0xffffd5e4 --> 0x0 ESI: 0xffffd5c0 --> 0x1 EDI: 0x0 EBP: 0xffffd5a8 --> 0x0 ESP: 0xffffd580 --> 0xf7fbe3fc --> 0xf7fbf200 --> 0x0 EIP: 0x565555d0 (<main+31>: mov DWORD PTR [ebp-0x1f],0x6c6c6548) EFLAGS: 0x216 (carry PARITY ADJUST zero sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x565555c4 <main+19>: call 0x56555616 <__x86.get_pc_thunk.ax> 0x565555c9 <main+24>: add eax,0x1a0b 0x565555ce <main+29>: mov esi,ecx => 0x565555d0 <main+31>: mov DWORD PTR [ebp-0x1f],0x6c6c6548 0x565555d7 <main+38>: mov WORD PTR [ebp-0x1b],0x216f 0x565555dd <main+44>: mov BYTE PTR [ebp-0x19],0x0 0x565555e1 <main+48>: sub esp,0xc 0x565555e4 <main+51>: lea edx,[ebp-0x1f] [------------------------------------stack-------------------------------------] 0000| 0xffffd580 --> 0xf7fbe3fc --> 0xf7fbf200 --> 0x0 0004| 0xffffd584 --> 0x56556fd4 --> 0x1edc 0008| 0xffffd588 --> 0xffffd65c --> 0xffffd798 ("LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc"...) 0012| 0xffffd58c ("kVUV\001") 0016| 0xffffd590 --> 0x1 0020| 0xffffd594 --> 0xffffd654 --> 0xffffd782 ("/home/student/buffovf") 0024| 0xffffd598 --> 0xffffd65c --> 0xffffd798 ("LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc"...) 0028| 0xffffd59c --> 0xffffd5c0 --> 0x1 [------------------------------------------------------------------------------] Legend: code, data, rodata, value Temporary breakpoint 1, main (argc=0x1, argv=0xffffd654) at buffovf.c:16 16 char buf[] = "Hello!";
Let's take a look at the previous output that gdb peda prints. You can see it is seprated into 3 sections: registers, code and stack. With the original gdb you would have to manually print registers, disassemble code and inspect the stack. Thanks, God, for peda!!
There might be situations when you only want to execute one line of source code, or one machine instruction from your program. This action is called a step and can be categorized as follows:
step
or s
: Continue running your program until control reaches a different source line, then stop it and return control to GDB. If the line you are stepping over represents a function call, this command will step inside it.next
or n
: Continue to the next source line in the current stack frame. This is similar to step, but function calls that appear within the line of code are executed without stopping.There are also equivalent functions fo the machine instructions: stepi, nexti.
If you stepped into a function and you want to continue the execution until the function returns, you can use the finish
command.
No need to manually print registers anymore, but you still might need to print the content of a variable:
gdb-peda$ print input $6 = 0
The print
command allows you to specify the format of the output like this (you can find a full list of possible format specifiers here):
gdb-peda$ p/x $esp $3 = 0xffffcf00 gdb-peda$ x/2x 0xffffcf00 0xffffcf00: 0xffffcf4c 0xf7fcf0f0 gdb-peda$ p/d $esp $4 = 4294954752 gdb-peda$ p/s buf $9 = "OK. Bye!\n" gdb-peds$ p/x buf $10 = {0x4f, 0x4b, 0x2e, 0x20, 0x42, 0x79, 0x65, 0x21, 0xa, 0x0}
You can use the command x
(for “examine”) to examine memory in any of several formats, independently of your program's data types.
x/nfu addr
n
, f
, and u
are all optional parameters that specify how much memory to display and how to format it; addr
is an expression giving the address where you want to start displaying memory.
b
(bytes), h
(halfwords), w
(words)E.g.: Print 10 words in hexadecimal format, starting from the address of the current stack pointer.
gdb-peda$ x/10xw $esp 0xffffcf00: 0xffffcf58 0x4b4fdf10 0x7942202e 0x000a2165 0xffffcf10: 0xffffcf32 0xf7ffd918 0xffffcf58 0x080485b8 0xffffcf20: 0x00000000 0x080486b8
In order to change the value of a variable or of a specific memory area, you can use the set
command:
gdb-peda$ set g=4 gdb-peda$ set {int}0x83040 = 4
A backtrace is a summary of how your program got where it is. It shows one line per frame, for many frames, starting with the currently executing frame (frame zero), followed by its caller (frame one), and on up the stack.
backtrace
: Print a backtrace of the entire stack: one line per frame for all frames in the stack.
E.g.:
gdb-peda$ bt #0 print_message (input=0) at buggy.c:16 #1 0x080485b8 in main () at buggy.c:38
It is also possible to move up or down the stack using the following commands:
up n
: Move n frames up the stack. For positive numbers n, this advances toward the outermost frame, to higher frame numbers, to frames that have existed longer. n defaults to one.down n
: Move n frames down the stack. For positive numbers n, this advances toward the innermost frame, to lower frame numbers, to frames that were created more recently. n defaults to one.
Another useful command for printing information related to the current stack frame is info frame
. This command prints a verbose description of the selected stack frame, including:
Compile the following code:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> void wanted(int a) { if (a == 0xcafebabe) { puts("well done, you're cool!"); } else { puts("at least you tried"); } } void copy() { char name[12]; printf("what's ur last name?\n"); gets(name); printf("bye\n"); } int main(int argc, char **argv) { if (argc == 1) { puts("Usage: %s <name>\n"); return 1; } char buf[] = "hey"; printf("%s, %s\n", buf, argv[1]); copy(); exit(0); }
like this:
gcc buffovf.c -o buffovf -fno-stack-protector -m32 -g
You may need to install libc6-dev-i386
for 64-bit systems.
Run the program using GDB, setting the argument “AAA”. Set a breakpoint at the beginning of the main
function. Continue execution until you hit the breakpoint. Try to reach the beginning of the copy
function without setting another breakpoint.
After solving this exercise, don't close gdb.
Remove the existing breakpoint and set a new one at the beginning of the copy
function. Run again the program and continue execution until you hit the breakpoint. Print the value and the address of name
. Print the value of it after gets(name)
is executed.
Start the execution again (do not exit GDB) and print the address of buf
from the main function. What do you notice? Check in another tab if ASLR is enabled on your PC. What happens and how can you fix it?
Restart gdb and run until the beginning of the copy
function using next
and step
accordingly. Display stack info (bt
, info frame
). At what address is name
located? At what address is the saved return address located? How many bytes of input do you need in order to overwrite the return address?
We want to overflow the buffer name
from the copy() function. Run the program and provide an input so that the program crashes.
gdb$ run <program args> < <(python -c 'print("A" * 30)')
for stdin redirection directly within GDB! ;)
WARNING: DokuWiki replaces ”
with quote-like UTF-8 character unrecognized by bash!
We want to create an attack which invokes the wanted
function. What is the address of this function? Adjust the input so that the return address is overwritten with the address of the wanted
function.
wanted
function.
You can see that when using objdump
the addresses look weird (short):
000005fd <wanted>: 5fd: 55 push ebp 5fe: 89 e5 mov ebp,esp 600: 53 push ebx
They aren't actually real addresses, they are offsets counting the number of bytes from the beginning of the file. This happens because the program was compiled as PIC (position independent code). More details can be found here. Recompile the program without PIC and PIE using -fno-pic -no-pie
options for gcc.
080484b6 <wanted>: 80484b6: 55 push ebp 80484b7: 89 e5 mov ebp,esp 80484b9: 83 ec 08 sub esp,0x8
The wanted
function takes an argument. Adjust the previous payload so that when calling wanted
, the message well done, you're cool!
is displayed.
We can see that even if we call wanted
with the correct arguments, the program still crashes. Let's remove any trace that we've been there. Adjust the previous payload so that the program exits without a segmentation fault.
wanted
? What would be a great function to call? Where can you get its address from? After finding out the function, look for its address using objdump, you might find something there.
Please take a minute to fill in the feedback form for this lab.