Skip to content

fix: decode Fire Kirin obfuscated native assets - #35

Open
babyRess wants to merge 2 commits into
Crain99:mainfrom
babyRess:upstream/fire-kirin-deobfuscator
Open

fix: decode Fire Kirin obfuscated native assets#35
babyRess wants to merge 2 commits into
Crain99:mainfrom
babyRess:upstream/fire-kirin-deobfuscator

Conversation

@babyRess

@babyRess babyRess commented Aug 7, 2026

Copy link
Copy Markdown

Problem

Cocos Creator 2.4 builds served by the Fire Kirin platform prepend an obfuscation
header to native binary assets. cc-reverse copies those bytes verbatim, so recovered
PNG/JPEG/MP3 files are unusable — file(1) reports data, and neither an image
decoder nor Cocos Creator will open them.

The prefix is present in the bytes the server delivers; it is not introduced by
cc-reverse. cc-reverse simply has no notion of it.

Observed on a real capture: 57 of 59 images and 129 of 129 audio files were
unreadable after recovery.

Algorithm

Taken from the game's own loader (CommLoading/downLoadPlugin.js), not reverse-guessed.
Two markers, two code paths:

MARKER_A = "@@@ADW@@@%%"           (11 bytes)
MARKER_B = "Gj&e5/S%*z]~)i!O`r"    (18 bytes)
XOR_KEY  = [75,108,38,101,5,47,82,35,41,93,126,43,105,79,96,118]
Marker Loader path Transform
A blob (image) strip 11
B blob (image) strip 18 + 74
A arraybuffer (audio/binary) strip 11, then XOR every byte with 1
B arraybuffer (audio/binary) strip 18, then XOR with the 16-byte rotating key

The +74 pad on the image path is empirically derived — it is what the loader does,
and it holds across all 167 marker-B images in the sample, but I do not know why it
is 74. Flagging that honestly rather than implying it is understood.

Unmarked input is returned byte-identical, so non-obfuscated projects are unaffected.

Scope of this PR

This PR adds the decoder module and its tests only. The module is inert: it
exports a pure byte transform and is not wired into any pipeline here. Nothing in
cc-reverse's behaviour changes until a caller invokes it deliberately.

The obfuscation is Fire Kirin-specific, not a general Cocos feature. Integration
into the recovery pipeline is deliberately kept out of this PR; in my downstream branch
it sits behind an explicit --deobfuscate fire-kirin flag, with detection reported but
never silently applied. Happy to follow up with that wiring if you want it in-tree.

Verification

  • 238/238 images and 129/129 audio in a real capture decode to valid magic bytes.
  • Round-trip tests build a real minimal PNG in-code (zlib deflate + hand-rolled CRC32),
    obfuscate it under each scheme, decode it back, and assert byte-exact equality plus
    independent re-parse via image-size. So the image branches are proven to produce a
    decodable artifact, not merely correct prefix arithmetic.
  • The rotating-key fixture exceeds 16 bytes so key wraparound is exercised.
  • Full suite green.

Notes for review

  • Zero new dependencies (zlib is a Node built-in; image-size was already a dep).
  • All fixtures are synthetic — no game assets are vendored into the repo.
  • deobfuscateAsset(buffer, ext) returns { data, scheme } so callers can report
    what happened rather than guess.
  • Files kept under the project's 200-line convention.

Cocos 2.4 builds served by the Fire Kirin platform prepend an obfuscation
header to native binary assets. cc-reverse copied those bytes verbatim, so
recovered PNG/JPEG/MP3 files were unusable (file(1) reports "data").

The prefix is present in the server-delivered bytes, not introduced by
cc-reverse. The algorithm is taken from the game's own loader
(CommLoading/downLoadPlugin.js), which uses two markers and two code paths:

  "@@@adw@@@%%"        (11 bytes)
  "Gj&e5/S%*z]~)i!O`r" (18 bytes)

Images travel the blob path and are header-stripped only; the 18-byte marker
carries an additional 74-byte pad. Audio and other binaries travel the
arraybuffer path and are additionally XOR-decrypted -- constant 1 for the
first marker, a 16-byte rotating key for the second.

Unmarked input is returned byte-identical, so non-obfuscated projects are
unaffected.
Covers the full scheme x loader-path matrix:

  11-byte marker, image/blob   -> strip 11
  11-byte marker, arraybuffer  -> strip 11 + XOR 1
  18-byte marker, image/blob   -> strip 18 + 74
  18-byte marker, arraybuffer  -> strip 18 + rotating-key XOR

The roundtrip suite builds a real, minimal, valid PNG in-code (zlib deflate
plus a hand-rolled CRC32), obfuscates it under each scheme, decodes it back,
and asserts byte-exact equality plus independent re-parse via image-size --
so the image branches are proven to yield a decodable artifact, not merely
correct prefix arithmetic. The rotating-key fixture exceeds 16 bytes so key
wraparound is exercised.

All fixtures are synthetic. No game assets are vendored into the repo.
@babyRess
babyRess marked this pull request as ready for review August 7, 2026 11:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant