LigaCTF 2026
Detonate2
LigaCTF 2026 ligactf2026, reverse engineering, malware analysis writeup covering Detonate2 with analysis, solution steps, and final recovery notes.
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:
-
A fake standalone flag string.
-
A runtime-generated MD5 flag.
-
A misleading
.exeextension 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.