Machine problem 1: Buffer overflow to exit
Consider the following code for a nonterminating program:
// vuln.c
#include <stdio.h>
void vuln() {
char buffer[8];
gets(buffer);
}
int main() {
vuln();
while (1) {
}
}
This program will accept an unbounded number of non-null byte characters from standard input until you hit \<Enter> and then proceed to perform an infinite loop.
The goal is to construct "shellcode" to cause the program to terminate
with a desired exit code of 1
using a stack smash attack.
Note that crashes due to malformed "shellcode" will not result in an
exit code of 1
and therefore will not count.
Compiling
Compile the vuln.c
source code with the following incantation.
$ gcc -m32 -fno-stack-protector -mpreferred-stack-boundary=2
-fno-pie -ggdb -z execstack vuln.c -o vuln
Otherwise, your exploit will not work due to memory safety mitigations.
Finding addresses
Using gdb
we can find addresses that we’re interested in.
Attach gdb
to our vuln
program.
$ gdb vuln
Try to set a breakpoint at line 1.
(gdb) break 1
Breakpoint 1 at 0x1193: file vuln.c, line 5.
(gdb) run
Breakpoint 1, vuln () at vuln.c:5
5 gets(buffer);
Print addresses.
(gdb) print &buffer
$1 = (char (*)[8]) 0xffffd6c8
Show registers.
(gdb) info registers
eax 0x565561a2 1448436130
ecx 0x90761f74 -1871306892
edx 0xffffd700 -10496
ebx 0xf7fa9e34 -134570444
...
Show stack frame info.
(gdb) info frame
Stack level 0, frame at 0xffffd6d8:
eip = 0x56556193 in vuln (vuln.c:5); saved eip = 0x565561aa
called by frame at 0xffffd6e0
source language c.
...
Obtaining machine code
Various ways to obtain machine code.
One way is to write assembly in C and disassemble it.
// asm.c
int main() {
__asm__("xor %eax, %eax;"
"inc %eax;"
"mov %eab, %eax;"
"leave;"
"ret;"
);
}
Compile using the following incantation:
$ gcc -m32 -fno-stack-protector -fno-pie -masm=intel asm.c -o asm
And obtain machine code with the objdump
tool:
$ objdump -d asm > asmdump
Machine code will be found in the file called asmdump
.
...
0000117d <main>:
117d: 55 push %ebp
117e: 89 e5 mov %esp,%ebp
1180: e8 13 00 00 00 call 1198 <__x86.get_pc_thunk.ax>
1185: 05 6f 2e 00 00 add $0x2e6f,%eax
118a: 31 c0 xor %eax,%eax
118c: 40 inc %eax
118d: 89 c3 mov %eax,%ebx
118f: cd 80 int $0x80
1191: b8 00 00 00 00 mov $0x0,%eax
1196: 5d pop %ebp
1197: c3 ret
...
Here, we see that xor %eax, %eax
is 0x31 0xc0
(look at line
118a
) in machine code.
Writing down the shellcode
Suppose we want to put a 1
in the %eax
register, the assembly for that is:
xor %eax, %eax // set %eax to 0
inc %eax // increment %eax by 1
The machine code from the disassembly shows these two instructions
are: 0x31 0xc0
and 0x40
.
We can write down actual bytes to a file called egg
using echo
:
$ echo -ne "\x31\xc0\x40" > egg
Running the program in gdb
In gdb
, we can run vuln
and send in "shellcode" from egg
as the input:
(gdb) run < egg
When the program asks for input, the contents of egg
will be passed
in automatically.