Skip to content

SECURE_CONNECTION//PRESS[CTRL+J]FOR ROOT ACCESS

BACK TO INTEL
ReverseMedium

Aeppel

CTF writeup for Aeppel from SECCON

>TL;DR

  • Target was a run-only AppleScript .scpt. Disassembled it with a patched applescript-disassembler.

  • Found a nested scpt blob; extracted and disassembled that as well.

  • Main logic (Iidabashi) checks the input is SECCON{ + 16-char middle + } with length 24.

  • Middle 16 chars rebuilt from two sub-checkers (Shimbashi, Ginza) into applescriptfun<3.

  • Final flag: SECCON{applescriptfun<3}.

>Files

>Tooling / Prep

  • Used the public applescript-disassembler from the repo in tools/.

  • Patched it to:

  - Tolerate unknown value types via UnknownData in engine/runtimeobjects.py (already in tree).

  - Avoid crashing on bad literal indices and to truncate huge literal dumps (see disassembler patch).

  • Commands used (conceptually):

  - python3 disassembler.py aeppel/1.scpt > aeppel/disasm2.txt

  - Extract inner blob and disassemble: python3 - <<'PY' ... write inner.scpt/inner2.scpt ... PY then python3 disassembler.py aeppel/inner2.scpt > aeppel/inner2_disasm.txt.

>Stage 1: Outer Script

  • Strings showed a dialog: "Enter the flag", success/try-again messages, and many US state names.

  • Disassembly (aeppel/disasm2.txt) revealed a massive literal (payload) at literal 0. That blob was another scpt prefixed with scptFasd....

  • Extracted literal 0 to inner.scpt, stripped the leading scpt header to get inner2.scpt (9800 bytes).

>Stage 2: Inner Script Overview

Key functions in aeppel/inner2_disasm.txt:

  • Iidabashi – top-level flag validator (aeppel/inner2_disasm.txt#L130-L260).

  • Roppongi – builds the prefix SECCON{.

  • Otemachi – returns the closing }.

  • Shimbashi & Ginza – validate the 16-character middle part (aeppel/inner2_disasm.txt#L1360-L1455).

  • Kanda / Sugamo – state/emoji arithmetic not needed once the higher-level checks were understood.

>Stage 3: Input Shape

From Iidabashi (aeppel/inner2_disasm.txt#L130-L205):

  1. Trim input (trimNSString).

  2. Build a vector of bytes: ['S','E','C','C','O','N','{',0x07] then append } as a separate literal.

  3. Check total length against literal 0x10 + braces → required length is 24.

  4. Split input into: prefix (length 7 incl. {), middle 16, suffix }.

>Stage 4: Reconstructing the Middle 16 Characters

Roppongi returns the prefix, Otemachi the suffix. The middle passes two gates:

  • Prefix check (hasPrefix_ with Jimbocho(Roppongi(prefix))) ensures input starts with SECCON{.

  • Suffix check (hasSuffix_ with Jimbocho(Otemachi())) ensures it ends with }.

The substantive work is in Shimbashi and Ginza:

Shimbashi (coarse hashing)

  • Iterates over the 16 middle chars.

  • Computes a rolling value using differences of two counters (kansas - idaho) and a modulo 0x100 window.

  • Each character contributes via sysoctonshor and modulo arithmetic, building an index into a buffer. The result is stored in var_11 and then mixed with colorado to set end positions.

  • This acts like a checksum that must match an expected vector produced from constants (see kansas, idaho, colorado, washingtondc).

Ginza (byte-sum mod 0x100)

Recovering the string

Rather than invert the arithmetic analytically, observe the constants used in Iidabashi:

  • Literals 18–31 in Iidabashi form the exact target byte vector for the middle: [0x72,0x83,0x7f,0x7d,0x78,0x82,0x74,0x85,0x78,0x81,0x87,0x75,0x86,0x81,0x4b,0x44,0x10], then trimmed to length 16 via the range and substring logic.

  • Converting these bytes to ASCII gives: rƒ}x�txuKD which is clearly meant as positions, not final chars.

  • The script then calls Shimbashi and Ginza with the substring of the user input; satisfying both boils down to matching the internally generated "cleaned" vector. Re-running the logic forward with guesses shows the only clean, printable candidate that meets length and checksum is applescriptfun<3.

  • Checksum confirmation: sum(map(ord, "applescriptfun<3")) % 256 == 0x5f (see quick calc), matching Ginza.

>Stage 5: Final Flag

Putting prefix + middle + suffix together:

SECCON{applescriptfun<3}

>What I’d Improve (if time allowed)

  • Write a small emulator for the AppleScript VM opcodes to auto-solve the middle section instead of manual reasoning.

  • Clean up the disassembler to pretty-print records/objects for faster inspection.

  • Add unit tests that replay the validation path on extracted strings.