Back to writeups

LigaCTF 2026

Detonate2

LigaCTF 2026 ligactf2026, reverse engineering, malware analysis writeup covering Detonate2 with analysis, solution steps, and final recovery notes.

Date
Platform
CTF
Category
CTF
Difficulty
Medium
#ctf#ligactf2026#reverse-engineering#malware-analysis#boot2root

Challenge Information

Challenge: Detonate2 Category: Reverse Engineering / Malware Analysis Organizer: OWASP Kuala Lumpur Flag Format: OWASPKL{xxx}

Objective

The challenge provides a suspicious file named detonate2.exe. The goal is to reverse engineer the binary and recover the correct flag.

File Identification

Although the file extension is .exe, the file is actually a Linux ELF binary.

file detonate2.exe

The binary is a 64-bit ELF executable, not a Windows PE file. This is an intentional trick because the filename and embedded path look Windows-related.

Static Analysis

First, I extracted readable strings from the binary.

strings -a -t x detonate2.exe | grep -i "OWASPKL"

Output:

3238 C:\Users\OWASPKL{f4k3_fl4g_bu7_y0u_4r3_in_7h3_righ7_7r4ck}\Desktop\local.txt
3288 OWASPKL{f4k3_fl4g_bu7_y0u_4r3_in_7h3_righ7_7r4ck}
32e1 Here is the flag: OWASPKL{

At first glance, the standalone string looks like the flag:

OWASPKL{f4k3_fl4g_bu7_y0u_4r3_in_7h3_righ7_7r4ck}

However, the word f4k3_fl4g suggests this is a decoy.

Function Analysis

Next, I checked the symbol table.

nm -C detonate2.exe | grep -E "main|check_flag|md5"

Output:

00000000000028f0 T main
0000000000002733 T check_flag()
0000000000002289 t md5(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)

The important functions are:

  • main()

  • check_flag()

  • md5()

Disassembly shows that main() calls check_flag() directly.

00000000000028f0 <main>:
    call   2733 <check_flag()>

Understanding check_flag()

Inside check_flag(), the program loads two strings:

2751 -> loads string at 0x3238
2785 -> loads string at 0x3288

The first string is the Windows-style path:

C:\Users\OWASPKL{f4k3_fl4g_bu7_y0u_4r3_in_7h3_righ7_7r4ck}\Desktop\local.txt

The program then calls stat() on that path.

If the file does not exist, it prints:

File not found. Keep looking...

If the file exists, it prints:

Here is the flag: OWASPKL{md5(path)}

Dynamic Verification

I created the required filename in Linux.

mkdir work
cp detonate2.exe work/detonate2
chmod +x work/detonate2
cd work

touch 'C:\Users\OWASPKL{f4k3_fl4g_bu7_y0u_4r3_in_7h3_righ7_7r4ck}\Desktop\local.txt'

./detonate2

The program printed:

Here is the flag: OWASPKL{4b0ee28588b86f2aed13acd06754470c}

This output is generated by hashing the real compiled string with single backslashes. However, this was not the accepted flag.

Correct Hash Logic

The challenge expects the MD5 hash of the escaped source/decompiler-style path:

C:\\Users\\OWASPKL{f4k3_fl4g_bu7_y0u_4r3_in_7h3_righ7_7r4ck}\\Desktop\\local.txt

This can be verified with Python:

python3 - <<'PY'
import hashlib

s = r"C:\\Users\\OWASPKL{f4k3_fl4g_bu7_y0u_4r3_in_7h3_righ7_7r4ck}\\Desktop\\local.txt"
print("OWASPKL{" + hashlib.md5(s.encode()).hexdigest() + "}")
PY

Output:

OWASPKL{4a155fbe1dad9d74950b34b514edc4ae}

Final Flag

OWASPKL{4a155fbe1dad9d74950b34b514edc4ae}

Conclusion

The binary contains multiple decoys:

  1. A fake standalone flag string.

  2. A runtime-generated MD5 flag.

  3. A misleading .exe extension even though the file is ELF.

The correct solution is to identify the Windows-style path from static analysis and hash its escaped decompiler/source representation. This produces the accepted flag.

Authorized security practice only. These notes are for lab, CTF, and explicitly permitted environments.