Квиз (Quiz)
Квиз (Quiz)
Platform: duckerz | Category: Reversing | Type: Challenge | Difficulty: Medium | OS: NA | Author: XESXOR | Date: 2026-02-11 | Status: Solved Techniques: bytecode_reverse_engineering, custom_disassembler_development, hermes_v96_disassembly, jwt_api_interaction, sha256_hash_chain_reconstruction, string_table_extraction
Summary
Task: React Native APK with Hermes v96 bytecode containing hidden token generation logic. Solution: Custom disassembler to extract string table, reconstruct 5-step SHA256 hash chain algorithm, generate final token for API flag retrieval.
Квиз (Quiz) — DUCKERZ CTF
Description
Хотим выкатить с друзьями безобидное приложение, повышающее кругозор. Мне пообещали, что никаких секретов там нет.
One file provided:
quiz.apk(~83MB) — Android APK, React Native (Expo) application with Hermes JavaScript bytecode
Category: Reverse Engineering Points: 300 | Difficulty: Medium
Analysis
Step 1: Initial Reconnaissance
The APK is a React Native (Expo) application. The JavaScript bundle is compiled into Hermes bytecode version 96 — this is not regular JS, but a binary format for Meta's Hermes virtual machine.
Standard tools (hbctool) do not support Hermes v96, so a fully custom approach was required.
Step 2: Extracting Strings from Hermes Bytecode
From 16,005 strings in the string table, key ones were found:
- API endpoints:
/api/riddle/next,/api/riddle/answer,/get_flag - Functions:
activateStep1-activateStep5,generateFinalToken,isFlagReady,generateHash,createToken - Suffixes:
_activated,_final,_secure - 11 hex strings (salts):
a7f3e9c2b8d4f1a6,9e2c8b5f3a7d1e4c, ...
Step 3: Custom Hermes v96 Disassembler
A disassembler was written from scratch (~192 opcodes), parsing 14,681 functions. Targeted analysis of key token generation functions.
Step 4: Algorithm Reconstruction
5-step SHA256 hash chain with salts, double hashing for the final token.
Solution
python read-only • copy allowed#!/usr/bin/env python3 import hashlib def sha256(s: str) -> str: return hashlib.sha256(s.encode()).hexdigest() salts = [ "a7f3e9c2b8d4f1a6", "9e2c8b5f3a7d1e4c", "6f8a2e5c9b3d7f1a", "4c9e7a2f5b8d1e3c", "2f6a9c5e8b1d4f7a", ] step1 = sha256(salts[0] + "_activated") step2 = sha256(salts[1] + "_" + step1) step3 = sha256(salts[2] + "_" + step2) step4 = sha256(salts[3] + "_" + step3) step5 = sha256(salts[4] + "_" + step4) combined = f"{step1}_{step2}_{step3}_{step4}_{step5}_final" hash1 = sha256(combined) final_token = sha256(hash1 + "_secure") # -> 02833cfe8924def0704a474fbb5f56c6510be338c94b38c7def44155e8f0ae1b
POST /get_flag with the token and JWT authorization -> flag.