← Back to Writeups
HTBN/AReversing

FlappyFlopper

XESXOR8/23/20266 min read
#reversing#htb#n/a

FlappyFlopper

Platform: HackTheBox | Category: Reversing | Type: Challenge | Difficulty: Medium | OS: NA | Author: D3v0o0Nu11 | Date: 2026-03-24 | Status: Solved Techniques: arm64_disassembly, elf_relocation_parsing, got_table_analysis, il2cpp_metadata_extraction, static_data_extraction

Summary

Reconstructing in order: H T B { F l 4 p y _ F l 0 p p 3 r _ 0 n _ M 0 b 1 l 3 }

Recon

Port scan

nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
PortServiceVersionNotes
<PORT><SVC><VER><notes>

Enumeration highlights

  • Event: HackTheBox | ID: 20260324_hackthebox_flappyflopper
  • Tags: game_hacking, android, apk, unity, il2cpp, arm64, metadata_extraction, got_relocation
  • Indicators: Unity IL2CPP Android APK, libil2cpp.so in lib/arm64-v8a/, global-metadata.dat in assets/bin/Data/Managed/Metadata/, static string array field in dumped class, GOT entries with zero values requiring relocation parsing
  • Source: 20260324_hackthebox_flappyflopper.md

Foothold

Vulnerability / Misconfiguration

  1. Arm64_disassembly
  2. Elf_relocation_parsing
  3. Got_table_analysis
  4. Il2cpp_metadata_extraction
  5. Static_data_extraction
<command>

Exploitation

  • See original writeup content for detailed exploitation.

Privilege Escalation

Enumeration

sudo -l
find / -perm -4000 2>/dev/null
getcap -r / 2>/dev/null
cat /etc/crontab
ps aux

Exploitation

  1. N/A for challenge-type writeup; see exploitation above.
  2. Flag obtained via challenge solve.
<command>

Flags

FlagLocationValue
flagREDACTED

Key Takeaways / Lessons

  • arm64_disassembly
  • elf_relocation_parsing
  • got_table_analysis
  • il2cpp_metadata_extraction
  • static_data_extraction
  • Tags: game_hacking, android, apk, unity, il2cpp, arm64, metadata_extraction, got_relocation

Original Writeup

<details><summary>Click to expand original content</summary>

Description

No one has got 10000 score yet! Are you able to do so?

English summary: An Android APK (Flappyflopper.arm64-v8a.apk) is provided, which is a Flappy Bird clone built with Unity IL2CPP. The goal is to achieve a score of 10000 to get the flag, but the intended solution is to reverse engineer the IL2CPP binary to extract the flag directly from the static data.

Analysis

The APK is a Unity game compiled with IL2CPP (Intermediate Language to C++), which compiles C# code to native ARM64 code. Key files extracted from the APK:

  • lib/arm64-v8a/libil2cpp.so - The compiled native code
  • assets/bin/Data/Managed/Metadata/global-metadata.dat - IL2CPP metadata (version 29)

Using Il2CppDumper to recover class and method signatures revealed a Score class with a suspicious static flag field:

public class Score : MonoBehaviour // TypeDefIndex: 7209
{
    // Fields
    private static string[] flag; // 0x0 - the flag stored as char array!
    public Font myFontAaset;      // 0x20
    public Text scoreText;        // 0x28
    private int score;            // 0x30

    // Methods
    private void Start() { }           // RVA: 0x9AD55C
    public void ScoreUp() { }          // RVA: 0x9ACDD4
    private void UpdateScoreText() { } // RVA: 0x9AD568
    public void .ctor() { }            // RVA: 0x9AD6E0
    private static void .cctor() { }   // RVA: 0x9AD6EC - initializes flag[]
}

The static constructor .cctor() at RVA 0x9AD6EC is responsible for initializing the flag array. This is where the flag characters are assigned.

Solution

Step 1: APK Extraction

unzip Flappyflopper.arm64-v8a.apk -d apk_extracted/

Step 2: IL2CPP Metadata Recovery

DOTNET_ROLL_FORWARD=LatestMajor dotnet Il2CppDumper.dll \
    apk_extracted/lib/arm64-v8a/libil2cpp.so \
    apk_extracted/assets/bin/Data/Managed/Metadata/global-metadata.dat \
    il2cpp_output/

This generates:

  • dump.cs - Reconstructed C# class definitions with RVAs
  • script.json - Method addresses and ScriptString mappings
  • stringliteral.json - String literals with addresses

Step 3: Disassembly of Score.cctor()

Using radare2 to disassemble the static constructor at offset 0x9AD6EC:

r2 -A libil2cpp.so
[0x00000000]> s 0x9AD6EC
[0x009ad6ec]> pdf

The function:

  1. Allocates a string array of 28 elements
  2. Loads individual characters from GOT (Global Offset Table) entries
  3. Assigns them to flag[0] through flag[27] in a specific order

Step 4: Resolving GOT Entries via ELF Relocations

GOT entries are zero in the static file (filled at runtime by the dynamic linker). To resolve them statically, we parse the .rela.dyn ELF section to find the addend values which point to actual string data addresses.

#!/usr/bin/env python3
"""
FlappyFlopper IL2CPP Flag Extractor
Parses ELF relocations to resolve GOT entries and extract flag characters
"""

import struct
import json

def parse_elf_relocations(binary_path):
    """Parse .rela.dyn section to get GOT -> string address mappings"""
    with open(binary_path, 'rb') as f:
        data = f.read()
    
    # Find .rela.dyn section (ELF64 parsing)
    # Section header offset at 0x28, section count at 0x3C
    e_shoff = struct.unpack('<Q', data[0x28:0x30])[0]
    e_shnum = struct.unpack('<H', data[0x3C:0x3E])[0]
    e_shstrndx = struct.unpack('<H', data[0x3E:0x40])[0]
    
    relocations = {}
    # Parse sections to find .rela.dyn
    for i in range(e_shnum):
        sh_offset = e_shoff + i * 64
        sh_type = struct.unpack('<I', data[sh_offset+4:sh_offset+8])[0]
        
        # SHT_RELA = 4
        if sh_type == 4:
            sec_offset = struct.unpack('<Q', data[sh_offset+24:sh_offset+32])[0]
            sec_size = struct.unpack('<Q', data[sh_offset+32:sh_offset+40])[0]
            
            # Parse RELA entries (24 bytes each)
            for j in range(0, sec_size, 24):
                entry = data[sec_offset+j:sec_offset+j+24]
                r_offset = struct.unpack('<Q', entry[0:8])[0]
                r_addend = struct.unpack('<q', entry[16:24])[0]
                relocations[r_offset] = r_addend
    
    return relocations

def load_script_strings(script_json_path):
    """Load ScriptString mappings from Il2CppDumper output"""
    with open(script_json_path, 'r') as f:
        data = json.load(f)
    
    # Map address -> string value
    strings = {}
    for entry in data.get('ScriptString', []):
        addr = entry['Address']
        value = entry['Value']
        strings[addr] = value
    
    return strings

# GOT base address (from disassembly)
GOT_BASE = 0x1569000

# GOT offsets for each character (from ARM64 disassembly of .cctor)
# Format: (GOT_offset, array_index)
got_mappings = [
    (0x2d8, 0),   # H
    (0x308, 1),   # T
    (0x320, 2),   # B
    (0x2c0, 3),   # {
    (0x348, 4),   # F
    (0x328, 5),   # l
    (0x300, 6),   # 4
    (0x2c8, 7),   # p
    (0x310, 8),   # y
    (0x2f8, 9),   # _
    (0x348, 10),  # F (reused)
    (0x328, 11),  # l (reused)
    (0x340, 12),  # 0
    (0x2c8, 13),  # p (reused)
    (0x2c8, 14),  # p (reused)
    (0x2d0, 15),  # 3
    (0x318, 16),  # r
    (0x2f8, 17),  # _ (reused)
    (0x340, 18),  # 0 (reused)
    (0x2e8, 19),  # n
    (0x2f8, 20),  # _ (reused)
    (0x330, 21),  # M
    (0x340, 22),  # 0 (reused)
    (0x2e0, 23),  # b
    (0x338, 24),  # 1
    (0x328, 25),  # l (reused)
    (0x2d0, 26),  # 3 (reused)
    (0x2f0, 27),  # }
]

# Resolve and print flag
relocations = parse_elf_relocations('libil2cpp.so')
strings = load_script_strings('il2cpp_output/script.json')

flag = [''] * 28
for got_offset, idx in got_mappings:
    got_addr = GOT_BASE + got_offset
    if got_addr in relocations:
        string_addr = relocations[got_addr]
        if string_addr in strings:
            flag[idx] = strings[string_addr]

print(''.join(flag))

Step 5: Key Character Mappings

From the relocation analysis:

GOT OffsetCharacterUsed at indices
0x2d8H0
0x308T1
0x320B2
0x2c0{3
0x348F4, 10
0x328l5, 11, 25
0x30046
0x2c8p7, 13, 14
0x310y8
0x2f8_9, 17, 20
0x340012, 18, 22
0x2d0315, 26
0x318r16
0x2e8n19
0x330M21
0x2e0b23
0x338124
0x2f0}27

Reconstructing in order: H T B { F l 4 p y _ F l 0 p p 3 r _ 0 n _ M 0 b 1 l 3 }

</details>

Auto-tracked: saved to WriteUps; run /xesor-revise to fold lessons into XESXor_Methodology.md.

signed by XESXOR