HTB Cyber Apocalypse 2025: EndlessCycle

TL;DR

Dumping de-obfuscated code from a self-decrypting program.

Challenge Overview

Looks like a simple flag checker.

Under Ghidra decompilation, it doesn't look like the string "What is the flag?" is displayed.

Further analysis shows that the program attempts to decrypt itself using a known seed value of CFD2BC5B through rand(), and then finally turns it into execution by calling (*pcVar2)().

Debugging

To debug this, set a breakpoint at the instruction before the program jumps to the decrypted shellcode with breakrva 0x1214.

Upon hitting the breakpoint, the obfuscated code would have been decrypted completely by then. At this point, we can now dump the decrypted program withdump memory decrypted.bin $rax $rax+0x9e. The higher memory region is at an offset of 0x9e because we know that the original program allocated a buffer size of 0x9e via mmap().

Opening the dumped program in radare2 now shows the expected string of "What is the flag?".

Going back to GDB, performing a step-into with si should also give the same result.

We can then inspect the entire decrypted program in assembly with x/50i $rip or x/50i $pc.

There are two areas of interest here. Let's set a breakpoint at both addresses:

  • 0x7ffff7fc2059: xor DWORD PTR [rcx],0xbeefcafe

  • 0x7ffff7fc207a: repz cmps BYTE PTR ds:[rsi],BYTE PTR es:[rdi]

Upon breaking at 0x7ffff7fc2059, we learn that our user input is loaded into rcx, which is then XOR-ed with the key of 0xbeefcafe.

At the 2nd breakpoint, we learn that the XOR key is loaded into rdi, whereas rsi appears to be our ciphertext.

Solution

XORing the ciphertext in blocks of 4 bytes yields the flag.

Flag: HTB{l00k_b3y0nd_th3_w0rld}

Last updated