This year, I had the honour to write some challenges for the BSidesTLV conference :^) This is my third time in a row, nice.
Interested in JIT Compilers and funky allocator implementations?
— faulty *ptrrr (@0x_shaq) June 26, 2023
Try my challenges at @BSidesTLV_CTF
gl hf :^) pic.twitter.com/fpedEv2iUA
All the challenge files can be found here.
Challenge Description
You are getting a tar archive with:
- .patch files: pwn1.patch and pwn2.patch
- An entire setup and compiles PHP with the patches applied(Dockerfile)
- a php ini configuration file that basically disables every possible function in PHP: conf.ini
- A server.py file that gets your input and run arbitrary PHP code(+with the .ini configs applied).
The .patch files introduce a new function for the PHP8 interpreter called jit_optimize()
, it accepts two parameters: a function name and an offset.
The new function takes the offset you provide(2nd parameter) and adds that to the JIT’ed trace function pointer: pwn1.patch:59
Before we dive into the solution, and the explanation of what this primitive gives us: we need to talk briefly about what ‘JIT-Spray’ is.
JIT-Spray for Dummies
In many interpreters, the JIT Compiler produces assembly instructions with immediate values. Usually, those immediate values can be derived from constants in your script. To make it more clear, here’s an example from LuaJIT[1].
Those index specifiers(tbl[some_constant]
) turns into immediate values when the JIT Engine compiles them to native assembly:
This is the same snippet but in a hexdump view:
Now I just want to remind you that:
- This(in red) is a user-controlled data
- We’re looking at an executable page
In other words, we can:
- Spray constants that will be used as an arbitrary shellcode(tiny shellcodes with jumps in between, because every constant is a 7-8 bytes long with garbage instructions between them)
- Chain this with another memory corruption bug to make the JIT’ed function pointer point to the middle of the function and not to the beginning(it should point where our
0x414141...
starts).
The spraying technique can be different for each interpreter. In LuaJIT(above) we are using the index specifier syntax(tbl[some_index]
). And in PHP8, we can use the ==
operator, as we will see below.
Solution
You’re getting a very powerful primitive to modify a function pointer of a JIT’ed trace. To leverage that, you spray constants that will later be part of an immediate asm instruction/immediate value(after compilation). Those constants, in conjunction with the primitive the challenge gives you, can turn into arbitrary shellcode execution(tiny shellcodes with jumps in between, because every constant is a 7-8 bytes long with garbage instructions between them). You just need to provide an offset that will point to the middle of the generated assembly and not to the beginning(middle==where your constants begin). You provide this offset to the jit_optimize()
function
output:
[+] Opening connection to zend-master.ctf.bsidestlv.com on port 1337: Done
[*] Switching to interactive mode
[+] Found!
arg_func_name=hot
func_name=hot
addr=0x496f49e0
[~] Optimizing JIT'ed func/trace...
[+] Done! new addr @ 0x496f4a01
$ ls
conf.ini flag.txt sanity-tests.py server.py
$ cat flag.txt
BSidesTLV2023{only-the-dragon-warrior-can-jit-spray-like-this}