Decrypting (and Re-encrypting) Cocos2d Lua Game Scripts
About the author. I’m Colin Xiao — an independent reverse engineer, mostly working solo on contract jobs. I dig into compiled code of every kind, including but not limited to: Android and iOS apps, x86/x64/ARM native binaries, packers and obfuscators, DRM and license checks, network protocols and APIs, crypto and signature algorithms, malware, game binaries, and embedded/IoT firmware. I also handle large-scale data collection and vulnerability research. If you have any project in mind, I’d be glad to take it on — whatever it is, as long as it’s within my wheelhouse. Just reach out. You can reach me on X at @colinxiao_lab or on Telegram at https://t.me/ColinXiao666.
This research is published for educational and defensive purposes. The target sample is anonymized throughout.
Decrypting (and Re-encrypting) Cocos2d Lua Game Scripts
Background
A bounty post went up offering 1200 CNY to decrypt some Lua scripts. I know Lua reasonably well, and in a cocos2d game the Lua scripts are usually already decrypted at the point where the engine loads them — so off the top of my head this didn’t look hard. My real motivation was curiosity about how people actually turn this kind of work into money, not the money itself. (Having spent my career on the pure-technical side, I’ve noticed the monetization muscle is usually the weak one.)

The decryption path
The poster pasted a script_encrypt function. Honestly, the poster seemed to know what they were doing. Here’s the function:

GetMCKernel is a singleton, and result+32 calls a member function. I hunted for a likely candidate and guessed this was the one:

Taking a “just call it and see” approach, I built a small program with the NDK that calls the function directly:

And that was it. Here’s part of the recovered Lua source:

The whole thing took under half an hour. But the story doesn’t end there.
The encryption side
The catch
The poster’s response: sure, you decrypted it, but the script you gave me can’t be used directly — you have to encrypt it back, or no payout. Even after I pointed out that you can just modify script_decrypt to return the plaintext directly (they were afraid of it being reused), they wouldn’t budge. Then they promised to add 800 CNY on top if I also produced the re-encryption. Fine — I decided to put in the work.
Going back over the binary: this .so only contains the Lua decryption code, no encryption. The global g_table is referenced exactly once:

Huffman compression
Huffman compression — I’d studied it once, long forgotten. Back to the textbooks for the definition:

To understand the source, I searched GitHub for a few Huffman implementations and worked out how Huffman file compression actually operates. This is pulled from one of those projects:

The gist: read the source file, build a frequency table _infos from the observed characters, then use that table to construct the Huffman tree. Reading back over the IDA decompilation, I realized g_table is a substitution table used to encrypt the _infos table.
Classical substitution (encrypt/decrypt)
Since g_table is a substitution table — index → character — the inverse direction is just character → index.
The substitution decryption in the .so looks like this:

Inverting the substitution for encryption is straightforward:

At this point we can encrypt the frequency table.
Building the Huffman tree

The tree code in the .so is messy, and since it’s C++ it’s painful to reconstruct by hand, so I chose the “just call it” route again:

One important detail: the Huffman tree is not unique (at least based on what I learned from the GitHub projects — I’m not sure if this conflicts with the formal theory, but it seems the traversal order — preorder, inorder, postorder — can differ). Different implementations can produce different trees, which is exactly why I chose to call the .so’s own function to generate this tree:

So now I have the Huffman tree. The last missing piece for encrypting a file is how the paths are stored.
Huffman encoding
Huffman compression stores characters at the leaf nodes and uses the paths to preserve character order. “0” picks the left subtree, “1” picks the right. Here’s the classic diagram of the idea:

With the basics clear, the decompression side walks the paths, so the compression side writes the paths. Each 0 or 1 is one bit. The catch: in the .so’s decryption, I found it writes 7 bits per byte — meaning the last bit is always 0. I arrived at this through trial and error; the original author may not have done it intentionally, since everyone copies each other’s code and someone, somewhere, wrote it slightly differently.

The left is the GitHub code (count==8); the right is what I analyzed (perCount==7). The GitHub version is clearly correct — the 7-bit version that leaves a spare 0-bit was a real trap for me, because the encrypted output came out completely inconsistent with the original.
Wrapping up
Once I had the _infos frequency-table initialization, the Huffman-tree construction, and the 7-bit path analysis, the whole Huffman encrypt/decrypt cycle made sense, and reconstructing the encryption algorithm was easy. A Lua file actually consists of three parts, parsed back to front: the last dword is the count of unique characters in the Lua source (×4 = the size of the _infos table), the middle is the infos frequency table, and the front is the Lua character-path information:

Afterward
Making money online is harder than it looks. Along the way the poster asked me for a GUI and insisted I finish the encryption before they’d pay for the decryption — which felt like a bait-and-switch. But stubbornness kicked in, and once I was in, I saw it through. The 1200 wasn’t the point; I never did learn how the poster monetized it. I’ve been meaning to figure out how to earn on my own terms rather than being locked into a day job — and I hadn’t written anything up in a while, so this is the record.
(The images were fine while editing but broke on publish; for the full version see the PDF.)
Colin Xiao — I take on contract reverse-engineering work, and writeups like this are how I show what I can do. If you need help with anything in this space — including but not limited to the areas I cover above — I’m available. No project is too big or too small — if it’s reverse engineering and I can do it, I’ll take it. DM me on X (@colinxiao_lab) or Telegram (@ColinXiao666).
Get in touch