Since HTB published official writeups for all of this years challenges1 i initially dismissed the idea of doing writeups. However since the authors approaches to solving the challenges often differed from my solutions, an additional perspective might be beneficial to some people.
This challenge did not provide any binary through the web interface, only a remote docker container could be spawned. Upon making a connection to the docker we are greeted by this message:
I am about to send you 128 base64-encoded ELF files, which load a value onto the stack. You must send back the loaded value as a hex string
You must analyze them all in under 20 seconds
Let's start with a warmup
ELF: f0VMRgIBAQAAAAA[...]AADzpLg8AAAADwU=
Expected bytes: 9ef5b7a5055a0cbccd850ecd321ebb4e0fa31cb2d1d1aec9
So we are given a binary in base64 encoded format and have find out which bytes are pushed (or written to the stack). Note that I shortened the base64 encoded part for brevity. The disassembly of the one and only function in the given ELF is shown below.
The binary copies 0x18 bytes of data from a file offset onto the stack. I expected this pattern to slightly differ between the binaries, thus I did not want to rely on basic pattern matching to solve this challenge. But recently I stumbled upon an emulattion framework called qiling2 that seemed to be well be a perfect match for the job.
So after setting up some auxiliary logic for handling the file and remote connection in python and started to look at qiling.
The basics are outlined and demonstrated very well in the examples provided in qilings documentation.
I just added a hook to dump all memory write operations in the range from 0x0 to 0xffffffffffffffff. This was done under the assumption that no memory write outside of the desired bytes was happening (an assumption that proved to be correct). Running the script shown below yields the flag after all 128 binaries are processed:
HTB{y0u_4n4lyz3d_th3_p4tt3ns!}
(no i did not, and yes there is a typo in the original flag)
from pwn import *
from qiling import *
import base64
def get_file(io):
io.recvuntil(b"ELF: ")
b64_bin = io.recvuntil(b"\n")
f = open("binary", "wb")
f.write(base64.b64decode(b64_bin))
def run_binary():
rootfs = r'/CTFs/2024/cyberapocalypse/rev_quickscan/'
ql = Qiling(["/CTFs/2024/cyberapocalypse/rev_quickscan/binary"], rootfs)
ql.hook_mem_write(mem_write, begin=0,end=0xffffffffffffffff)
ql.run()
def mem_write(ql: Qiling, access: int, address: int, size: int, value: int) -> None:
ql.log.debug(f'intercepted a memory write to {address:#x} (value = {value:#x})')
global response
response+=hex(value)[2:].rjust(2, "0")
if __name__ == "__main__":
io = remote("94.237.63.46",31053)
global response
response = ""
# first file
get_file(io)
run_binary()
io.sendline(response)
response = ""
for i in range(0,128):
get_file(io)
run_binary()
io.sendline(response)
response = ""
io.interactive()