BlackHat MEA 2023 CTF Finals: Reverse Engineering Writeup

Detailed writeup of the Ground Hog Day reverse engineering challenge from the BlackHat MEA 2023 CTF final round in Riyadh - binary analysis, function recovery, and flag extraction.

Context

BlackHat MEA 2023 was held in Riyadh, Saudi Arabia. Our team competed in the CTF final round, placing 31st among 250+ international teams. The competition spanned three days with challenges released each day across categories including reverse engineering, pwn, cryptography, forensics, and web exploitation.

This writeup covers the Ground Hog Day challenge released on Day 3 - a reverse engineering problem that required understanding function behavior, identifying the flag generation logic, and extracting the flag either through static analysis or careful dynamic debugging.

Initial Reconnaissance

A zip file was provided containing a single binary called main. Let’s start with basic file analysis:

$ file main
main: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked,
      interpreter /lib64/ld-linux-x86-64.so.2, not stripped

$ checksec --file=main
RELRO           STACK CANARY      NX            PIE
Partial RELRO   No canary found   NX enabled    No PIE

Key observations:

  • ELF 64-bit - standard Linux binary
  • Dynamically linked - uses libc, we can identify standard functions
  • Not stripped - symbol names are preserved (function names visible)
  • NX enabled - stack is non-executable (not relevant for RE but good to note)
  • No PIE - addresses are fixed, simplifies debugging
  • No canary - no stack protection (again, more relevant for pwn)

Running the Binary

$ ./main
BLACKh4t_CTF{fl4g_p4rt_

Interesting - the binary prints part of the flag but then hangs without completing. It doesn’t crash; it just stops producing output. Something in the program’s logic is preventing the full flag from being printed.

Strings Analysis

$ strings main | grep -i flag
# Nothing obvious - the flag isn't stored as a plaintext string

The flag characters are being generated at runtime, not stored statically. This confirms we need to understand the program’s logic.

Static Analysis with Ghidra

Loading the binary in Ghidra and looking at the function list, we find two key functions: main and why.

The main Function

Decompiled, main contains a loop that:

  1. Initializes a character array
  2. Iterates through the array, performing transformations on each byte
  3. Prints each transformed character with putchar()
  4. Calls the why function at some point during iteration

The transformation involves XOR operations, bit rotations, and arithmetic on each byte index. The first portion of the flag (BLACKh4t_CTF{fl4g_p4rt_) is generated by the loop before it reaches the point where why is called.

The why Function

This function was the key to the challenge. It contains a deliberate stalling mechanism - either an infinite loop, a very long sleep, or a computation that takes impractically long to complete. This is the “Ground Hog Day” concept: the program gets stuck in a loop, repeating the same state over and over.

The function performs a computation that in theory completes, but the iteration count is astronomically high - effectively an infinite loop in practical terms.

The Solution

There were two approaches:

Approach 1: Static Recovery (Preferred)

Since the flag generation logic is entirely deterministic and based on the loop index, we can extract the transformation algorithm from Ghidra and reimplement it in Python, skipping the why function entirely:

# Extracted from Ghidra's decompilation
encoded = [0x42, 0x4c, 0x41, 0x43, 0x4b, ...]  # byte array from binary
key_schedule = [...]  # transformation parameters

flag = ""
for i in range(len(encoded)):
    b = encoded[i]
    b ^= key_schedule[i % len(key_schedule)]
    b = ((b >> 3) | (b << 5)) & 0xFF
    flag += chr(b)

print(flag)

This immediately prints the complete flag without needing to execute the binary at all.

Approach 2: Dynamic Patching

In GDB, let the binary run until it enters the why function, then force-return from it:

$ gdb ./main
(gdb) break why
(gdb) run
# Hits breakpoint at why
(gdb) return
# Force return from why without executing it
(gdb) continue
# Binary continues printing the rest of the flag

Alternatively, NOP out the call why instruction entirely and run the patched binary.

Lessons Learned

CTF Time Pressure

In a competition setting, the fastest approach was GDB patching - identify that why is the blocking function, skip it, get the flag. Static analysis gives a cleaner understanding but takes more time.

Triage Speed

The key to solving this quickly was recognizing the pattern within the first 2 minutes:

  1. Binary prints partial output → something is blocking
  2. Two functions: main and whywhy is suspicious
  3. why contains a loop → it’s the blocker
  4. Skip why → get the flag

In CTF finals, this kind of rapid triage separates top teams from mid-tier ones. You don’t need to fully understand every instruction - you need to identify the critical path and exploit it.

This challenge was part of our 31st place finish among 250+ international teams at BlackHat MEA 2023. The reverse engineering category tested speed and pattern recognition as much as technical depth.

← Home More Ctf writeups →