Buffer Overflows
What is a Buffer Overflow
A buffer overflow attack is one that exploits the location of a variable in memory to overwrite data. Some deprecated C functions are vulnerable to overflow attacks because they do not enforce sizes. In this writeup, I will demonstrate this vulnerability with the gets
function from the stdio
header.
man gets
gives the following description:
1 2 3 4 5 6 DESCRIPTION Never use this function. gets() reads a line from stdin into the buffer pointed to by s until either a terminating newline or EOF, which it replaces with a null byte ('\0'). No check for buffer overrun is performed (see BUGS below).
The last line describes the critical error we can exploit. Because there is no size check (unlike fgets
), we can read in as many bytes as we want from a file as long as there is no newline character [0x0d].
The Stack
In the C memory model, the stack is the section of memory that stores local variables, preserves the value of certain registers, and contains the return address. This can be seen when looking at a disassembled binary (with objdump -d
).
Note that the stack grows downard, so subtraction and negative offsets are growing the stack
1213: 55 push %rbp
1214: 48 89 e5 mov %rsp,%rbp
1217: 48 83 ec 10 sub $0x10,%rsp
121b: 89 7d fc mov %edi,-0x4(%rbp)
...
1270: c9 leave
1271: c3 ret
In the above example, the address of the previous stack frame (%rbp
) is pushed onto the stack. Afterwards, the current address of the stack from (in %rsp
) is copied into %rbp
. Next, the stack pointer %rsp
is subtracted by 0x10 to make space for local variables. The register %edi
stores the first argument to the function, so it gets copied to 4 bytes from the address of the stack base -0x4(%rbp)
.
This probably means that the variable that was stored in
%edi
i was a 4-byte variable.
Finally, the leave
instruction copies the base pointer %rbp
into the stack pointer %rsp
and pops the return address that was pushed onto the stack into %rbp
, while ret
pops the return address to the instruction pointer register %rip
. This allows the program to continue where it left off.
GDB
Using GDB, we can examine the value of items in the stack. I have attached a binary of a program to demonstrate this.
You can download the source code and the makefile from the same directory and build by running
make clean
and thenmake
.
First, we want to start a gdb session for the file with gdb program
For this example, try only using the binary (i.e., don’t use layout src or look at the source code).
You can use
layout asm
to see the assembly, which you would have access to throughobjdump -d
Now let’s set a breakpoint where gets
is called (b *main+41
). We can run the program with r
and hit the breakpoint. Looking at the next instructions, we can see that a local variable is being compared with a constant.
0x555555555200 <main+87> cmpl $0xdeadbeef,-0x14(%rbp)
0x555555555207 <main+94> je 0x555555555224 <main+123>
This is followed by a je
instruction, meaning that we are checking if the variable that is 20 (0x14) bytes from %rbp is equal to $0xdeadbeef, then going to a certain code block. Let’s examine the value of the local variable!
(gdb) x $rbp-0x14
0x7fffffffdebc: 0xffffffff
We can clearly see that the value of the variable is not equal to 0xdeadbeef, so we can conclude that the default behaviour is to not jump to main+82.
Sidenote: hex2raw
To make it easy to enter raw values, you can redirect a file into the provided hex2raw utility. If it does not work for you, you can find an online hex2raw converter and copy the outputs into a file, then redirect it into the program.
Use
./hex2raw < input.txt | ./program
to pipe the output of hex2raw into the program.