╔════════════════════════════════════════════════════════════╗
 ___      __
___  __  ______         \_ |__  |  |    ___      ____  
\  \/ / /  _  /  ______  | __ \ |  |   / _ \   / __  \ 
 \   / (  (_| | /_____/  | \_\ \|  |__( (_) ) / /_/  / 
  \_/   \__   |          |___  /|____/ \___/  \___  /  
           |__|              \/              /_____/   
║   ╔══════╗                                     ╔═══════╗   ║
╚═══╣ HOME ╠═════════════════════════════════════╣ ABOUT ╠═══╝
    ╚══════╝                                     ╚═══════╝    
[CTF] Cyber Apocalypse - QuickScan   2024-03-16

[+] Preface

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.

[+] QuickScan

This challenge did not provide any binary throught 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.

disassembly.png

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()