Challenge details :
|FCSC2020||Why not a sandbox||PWN||474||50|
Your goal is to call the print_flag function in order to show the flag
Service : nc challenges1.france-cybersecurity-challenge.fr 4005
That challenge was an ELF binary running a python jail, using
Audit Hook to prevent us from escaping.
print_flag function was part of the
lib_flag.so included in the jail.
We had to leak the binary so we could examine the GOT.PLT section and find an entry for the
welcome was also in the
lib_flag.so library, we could find our way to the
Let’s get a shell ;)
I started this challenge with some basic fuzzing, trying to import some modules or to call the
print_flag function :
I noticed something very interesting to me in that output :
Why ? Because I didn’t know anything about the so called
Audit Hook, so I searched for some documentation and here is was I found :
Why not a Sandbox section caught my eyes, since the challenge had exactly the same name, so I digged deeper :
If you read these articles, you will find mentions to
CPython which is basically the default python interpreter.
To keep it simple, actions taken by the python runtime are visible to auditing tools thanks to
Cpython and thanks to the python API.
Audit Hook is the feature that makes it possible, and it also makes it possible to trigger actions based on events.
But that feature is not meant to be used for sandboxing, as you can read in the first link I shared :
This proposal does not attempt to restrict functionality, but simply exposes the fact that the functionality is being used.
Since audit hooks have the ability to safely prevent an operation occurring, this feature does enable the ability to provide some level of sandboxing. In most cases, however, the intention is to enable logging rather than creating a sandbox.
Well, it looked like the author of the challenge decided to create a sandbox using
Audit Hook anyway :)
I searched for documentation again, and I saw that
ctypes was often used to bypass that kind of sandbox.
import os was forbidden due to a hook on the
os import event, I decided to find
os somewhere in order to get a shell.
I ended up with 2 options :
The second one provided me an interactive bash, and tadaaa :
Finding the way to the flag
As you could see on the previous screenshot, there was 2 files in the directory :
At this point, I noticed that
spython was the jail and that a shared object library was here, but I wasn’t able to read it, damn :/
ldd spython told me that the library was loaded in
spython (note that
spython was actually an ELF embedding python code)
Based on the name of the library (
lib_flag), I thought that the
print_flag function was probably part of the lib.
ctypes provide C data type implementation and the ability to load DLL and shared object library, here is what I tried :
But unfortunatly, as you can see, that was also forbidden due to a hook…
After some hours reading documentation and searching into
ctypes using the
dir command, I finally decided to get the binary so then I could analyse it using
radare2 (I encoded it using base64 so i could just copy past it on my machine and decode it).
Because the binary was loading some shared objects, I took a look at the GOT/PLT sections, and noticed that there was an entry in the GOT.PLT section for a function named
welcome… That function wasn’t common and seemed to be a part of the
As you can see, the address of the
welcome entry in the GOT section was
0x40a8. But that’s a relative address, an offset, since the binary wasn’t mapped into memory yet. That would be a good start to call the
welcome function, uh ?
Here is the idea i had in mind :
- Find the base address of the
spythonbinary mapped into binary
- Create a
base_addr + welcome_GOTentry_offsetusing
- Dereference that pointer to get the
- Create a c-function object using the address and call it.
In order to understand, we have to know what is the
GOT/PLT, and how to wrap C code with python using
So here is a reminder :
PLT stands for
Procedure Linkage Table which is, put simply, used to call external functions whose address isn’t known in the time of linking, and will be resolved by the dynamic linker at runtime.
GOT stands for
Global Offsets Table and is also used to resolve addresses. When an address as already been resolved, it’s stored directly in the GOT.PLT section
ctypes provide the
ctypes.c_int64 that can be used with the
ctypes.POINTER constructor. It also provide the
ctypes.CFUNCTYPE function that can be used to wrap c function.
Last trick : the basee address can be found in
/proc/self/maps… BUT the
open function was caught by a hook… Hopefully,
ctypes struck again !
Here is the way I opened
/proc/self/maps and got the
base_addr + welcome_GOTentry_offset :
gotplt = int(ctypes.__loader__.get_data("../../../../proc/self/maps").decode().split("-"), 16) + 0x40a8
Then, it was possible to cast that address into a
p = ctypes.cast(gotplt, ctypes.POINTER(ctypes.c_int64))
And bam, I was ready to call the function using :
I tried… and… It worked just as expected :D
OK, OK, but the function I wanted to call at the very beginning was
The hard part was done at this point, I decided to bruteforce the offset between the two functions, and finally got the precious flag !
The offset was
0x14, here is the final payload (oneliner) :
ctypes.CFUNCTYPE(ctypes.c_void_p)(ctypes.cast(int(ctypes.__loader__.get_data("../../../../proc/self/maps").decode().split("-"), 16) + 0x40a8, ctypes.POINTER(ctypes.c_int64)).contents.value+0x14)()
Note : It was also possible to find the flag using memory leak with
I hope you enjoyed !