Hack.lu CTF 2018: Petite Prison (Pwn 500)
nc arcade.fluxfingers.net 1812
Writeup
For TL;DR see below.
The service
When connecting to the service, you are greeted with a banner and a welcome message, telling you that you are at the Petite Prison bar, where you can mix your drinks yourself. You can choose a drink, there are also subcategories of drinks (cocktails, …). After selecting one, you are asked whether you want to mix it yourself or let the bartender do it. If you choose to do it, you get base64-encoded data, which turns out to be an ELF binary. If you ask the bartender to do it for you, you can also tell some special whishes before the drink is made, but there does not seem to happen anything based on that. Finally you get a little ASCII art drink, like this beer:
1 | _.._..,_,_ |
After trying to download all drinks (which are all ELF binaries), you notice that there is one drink they deny to serve: Fruity Light-Absorbing Gin. Seems like you found your target.
While reversing the accessible drink-binaries, you will soon notice that all they do is output ASCII art, with some minor obfuscation in place. These are all dead ends.
After returning to the menu to search for anything else, you notice that when selecting a drink and entering a subcategory, you get a ..
menu item to go back to the parent category. Looks suspicious, eh? And it works as expected, you can traverse (almost) the whole file system by navigating up from the main category. And you can also download most files, including the app’s sauce, by selecting it as a drink and “mixing it yourself”.
This reveals the following app structure:
1 | . |
banner.txt
is just the ASCII art banner of the servicebinpatch.py
looks like a lib that provides binary byte-level patching functionalityminijail-recipe-executor.sh
is a small bash script that executes its first argument with a tool calledminijail0
(more on that later)minijail-seccomp.policy
is a seccomp whitelist configuration that allows very few syscalls (basicallywrite
andexit
)petite_prison.py
is the main script of the service- the files in
recipes/
are the binaries that you can select as drinks, but we cannot read theF.L.A.G.
drink :( rfc31337.txt
is a fake RFC describing the format accepted bybinpatch.py
Taking a closer look at petite_prison.py
, you can see that you can modify the selected drink-binary by entering binary patch instructions (as defined in the RFC) to add, modify or delete up to 21 bytes. The file then gets executed as prison:prison
with only the few allowed syscalls (mentioned above) via minijail0
. After a quick web search, you know what minijail is:
Minijail is a sandboxing and containment tool used in Chrome OS and Android. It provides an executable that can be used to launch and sandbox other programs, […].
So all that is left to do is crafting a binary that escapes the minijail! To do this, we have to understand how minijail enforces the restrictions.
Before doing anything, minijail checks if the target executable is linked statically or dynamically. In the first case, the restrictions are applied beforehand. In the second case, minijail LD_PRELOAD
s __libc_start_main
and applies the restrictions after libc has been loaded (because this apparently crashes when the restrictions are too tight).
Solution 1: The Expected
The teams that solved the challenge used an expected solution: They injected shellcode at the executable’s entrypoint, before the jump to the preloaded __libc_start_main
, because the restrictions have not been enforced at that point. Some of them just called the execve
syscall with /bin/sh
, but one team used a more elegant way (in my opinion): They overwrote the __libc_start_main
string with execve
in the .dynstr
section, so the address of execve
gets loaded when __libc_start_main@PLT
is called. Then they overwrote the beginning of the main
function with /bin/sh
, which gets pushed as the first argument to __libc_start_main@PLT
.
Unfortunately there is a bug in the service which allowed non-printable ASCII chars in the binary patch. This made the challenge a little easier, but I guess it’s ok because still only 5 teams solved it :D
Solution 0: The Intended
While reading about how minijail works, I stumbled across the following comment in the source code that determines whether the file that should be executed is statically or dynamically linked:
1 | if (is_elf_magic(pHeader)) { |
So we need to patch the first bytes of the file to be a shebang that defines a statically linked binary as the interpreter. To find a suitable interpreter, we can dump all files via the path traversal vulnerability, which leads us to /bin/busybox
. The final exploit is:
1 | #!/usr/bin/env python2 |
TL;DR
Leak the source code via path traversal, then patch the beginning of a binary to
1 |
|
to confuse minijail0
to assume the interpreter is dynamically linked (when it is in fact statically linked), making its LD_PRELOAD
trick useless.