Reverse Engineering a Cocos2dx-JS Game
Cocos2dx-JS 게임 리버스 엔지니어링
Reverse engineering is often seen as a dark art, reserved for hackers and security researchers. But for me, it’s a hobby — a way to satisfy my curiosity, learn new techniques, and improve my own software by understanding how others protect theirs. Recently, I embarked on a quest to reverse engineer a Cocos2d-x JS game. This article will walk you through my process, the tools I used, and the lessons I learned along the way.
리버스 엔지니어링은 종종 해커와 보안 연구원들을 위한 어두운 기술로 여겨집니다. 하지만 저에게는 호기심을 충족시키고, 새로운 기술을 배우며, 다른 사람들이 자신의 소프트웨어를 어떻게 보호하는지 이해함으로써 제 소프트웨어를 개선하는 취미입니다. 최근 저는 Cocos2d-x JS 게임을 리버스 엔지니어링하는 여정을 시작했습니다. 이 글에서는 제 과정, 사용한 도구들, 그리고 그 과정에서 배운 교훈들을 안내할 것입니다.
The Challenge: A Non-Conventional Cocos2d-x JS Game
도전 과제: 비전통적인 Cocos2d-x JS 게임
The game in question, which I will not name not to expose it, was built using a custom version of Cocos2d-x JS, a popular framework for mobile game development. Unlike standard builds, this game didn’t facilitate discovery of the encryption key, several articles pointed out that the key could be found by either looking for a certain string in the libcocos2d.so lib using a hex editor, or disassembling it and then looking for the xxtea_decrypt method:
문제의 게임은 노출을 피하기 위해 이름을 밝히지 않겠지만, 모바일 게임 개발에 인기 있는 프레임워크인 Cocos2d-x JS의 커스텀 버전을 사용해 만들어졌습니다. 일반적인 빌드와 달리, 이 게임은 암호화 키를 찾기 쉽게 만들지 않았습니다. 여러 기사에서는 hex 에디터를 사용해 libcocos2d.so 라이브러리에서 특정 문자열을 찾거나, 이를 디스어셈블한 후 xxtea_decrypt 메서드를 찾아 키를 발견할 수 있다고 지적했습니다.
In this case however, nor the key was to be found near the string pointed by the tutorials, nor we could look for this method by name as names were mangled. So what I knew at that point:
그러나 이 경우에는 튜토리얼에서 가리키는 문자열 근처에서 키를 찾을 수 없었고, 이름이 난독화되어 있어 이 메서드를 이름으로 찾을 수도 없었습니다. 그래서 그 시점에서 제가 알았던 것은:
- Stripped or moved Decryption Key: The decryption key for the JS files was removed from the code or moved to an unknown location, making it super hard to extract using conventional methods.
제거되거나 이동된 복호화 키: JS 파일의 복호화 키가 코드에서 제거되었거나 알 수 없는 위치로 이동되어 기존 방법으로 추출하기 매우 어려웠습니다. - Mangled Method Names: All method names were obfuscated, rendering online guides and tools useless.
난독화된 메서드 이름: 모든 메서드 이름이 난독화되어 온라인 가이드와 도구가 무용지물이 되었습니다.
This made the game a perfect candidate for reverse engineering, as it presented unique challenges that required a combination of static and dynamic analysis.
이는 게임을 정적 및 동적 분석을 결합해야 하는 독특한 도전 과제를 제시하여 리버스 엔지니어링에 완벽한 후보로 만들었습니다.
Tools of the Trade 도구 소개
To tackle this challenge, I used a variety of tools:
이 문제를 해결하기 위해 다양한 도구를 사용했습니다:
- Frida: For runtime hooking and dynamic analysis.
Frida: 런타임 후킹 및 동적 분석을 위해 사용합니다. - IDA Pro: For disassembling the binary and understanding the code flow. Ghidra can also be used as a free open source alternative.
IDA Pro: 바이너리를 역분석하고 코드 흐름을 이해하기 위해 사용합니다. Ghidra는 무료 오픈 소스 대안으로도 사용할 수 있습니다. - APKTool: To unpack and repackage the game’s APK.
APKTool: 게임의 APK를 풀고 다시 패키징하기 위해 사용. - Android Emulator: For testing and debugging. I eventually switched to a iOS due to anti-debugging protections on Android.
Android Emulator: 테스트 및 디버깅용. 안드로이드의 안티 디버깅 보호 때문에 결국 iOS로 전환함. - Jailbroken iPad M1: For dynamic analysis as well. I’ve used Domanine as the JB method. For people with ARM (m1/2/3..) Macs there’s also a flashy method to run iOS apps on it that can eliminate the need for a jailbroken device, but describing the steps would require an article of its own: reference 1, reference 2.
Jailbroken iPad M1: 동적 분석용으로도 사용. JB 방법으로 Domanine을 사용함. ARM(M1/2/3 등) 맥 사용자들을 위해 탈옥된 기기 없이도 iOS 앱을 실행할 수 있는 화려한 방법이 있지만, 그 절차를 설명하려면 별도의 글이 필요함: 참고 1, 참고 2. - Custom Scripts: I used cocos2d-dec to decrypt the JS files and wrote custom Python scripts to handle image decryption.
Custom Scripts: cocos2d-dec를 사용해 JS 파일을 복호화하고 이미지 복호화를 처리하기 위해 맞춤형 파이썬 스크립트를 작성함.
My setup was a Mac with an M3 processor, but the process is platform-agnostic and can be replicated on Windows or Linux.
제 환경은 M3 프로세서를 탑재한 Mac이었지만, 이 과정은 플랫폼에 구애받지 않으며 Windows나 Linux에서도 동일하게 수행할 수 있습니다.
The Process: From Static Analysis to Runtime Hooking
과정: 정적 분석에서 런타임 후킹까지
Step 1: Understanding the Encryption
1단계: 암호화 이해하기
Cocos2d-x games typically compress and encrypt JS files using the XXTEA algorithm. However, in this case, the decryption key was either not stored in the binary, or moved to an unknown location, and all method names were mangled. This meant I had to find the key dynamically.
Cocos2d-x 게임은 일반적으로 XXTEA 알고리즘을 사용하여 JS 파일을 압축하고 암호화합니다. 그러나 이번 경우에는 복호화 키가 바이너리에 저장되어 있지 않거나 알 수 없는 위치로 이동되었으며, 모든 메서드 이름이 난독화되어 있었습니다. 이는 키를 동적으로 찾아야 한다는 것을 의미했습니다.
Step 2: Static Analysis with IDA Pro
2단계: IDA Pro를 이용한 정적 분석
I started by loading the cocos2d generated binary (libcocos2djs.so if Android, or app binary if iOS) into IDA Pro and searching for obvious strings like “encrypt” and “decrypt.” One string stood out: “can’t decrypt code ...”
cocos2d에서 생성된 바이너리(libcocos2djs.so는 Android용, iOS용은 앱 바이너리)를 IDA Pro에 로드한 후 “encrypt”와 “decrypt” 같은 명확한 문자열을 검색하는 것부터 시작했습니다. 그중 한 문자열이 눈에 띄었습니다: “코드를 복호화할 수 없습니다 ...”

By tracing references to this string, I found an if
block that I assumed was working with decryption result. The method preceding this block was likely the function I was looking for, with arguments for the encrypted content and the key.
이 문자열에 대한 참조를 추적하면서, 복호화 결과를 다루는 것으로 추정되는 if
블록을 발견했습니다. 이 블록 앞에 있는 메서드가 아마도 제가 찾고 있던 함수였으며, 암호화된 내용과 키에 대한 인자를 가지고 있었습니다.

복호화 실패 문자열을 참조한 메서드, IDA Pro로 디컴파일됨.
If you take a look at the first image in the article again, you’ll see that this function call seems to match exactly the signature of xxtea_decrypt method that we found online, this plus the fact that we see that error as a possible result of its return made me very confident that this was the function I was looking for, and if I were right, the key would be passed as its third argument.
기사의 첫 번째 이미지를 다시 보면, 이 함수 호출이 온라인에서 찾은 xxtea_decrypt 메서드의 시그니처와 정확히 일치하는 것처럼 보입니다. 이와 함께 반환값의 가능한 결과로서 오류가 나타나는 점을 고려할 때, 이 함수가 내가 찾고 있던 함수라는 확신이 들었고, 만약 내가 맞다면 키는 세 번째 인수로 전달될 것입니다.
Step 3: Dynamic Analysis with Frida
3단계: Frida를 이용한 동적 분석
With this hypothesis, I used Frida to hook the suspected decryption method at runtime.
이 가설을 바탕으로, 실행 시간에 의심되는 복호화 메서드를 후킹하기 위해 Frida를 사용했습니다.
The process to hook the function with Frida was the following, in IDA the offset of the function, relative to the module’s (libcocos2djs.so) base was 0x06D84AC
Frida로 함수를 후킹하는 과정은 다음과 같았으며, IDA에서 모듈(libcocos2djs.so) 기준 함수의 오프셋은 0x06D84AC였습니다.

So what I needed to do was to run the game with Frida with a hook file attached, that would intercept this function call and print the key. Which can be achieved with the following code:
그래서 제가 해야 했던 것은 후크 파일이 첨부된 상태로 Frida를 사용해 게임을 실행하는 것이었고, 이 파일이 해당 함수 호출을 가로채서 키를 출력하도록 하는 것이었습니다. 이는 다음 코드를 통해 달성할 수 있습니다:
Java.perform(function() {
// Function to find and hook the module
function hookModule() {
// Get the base address of libcocos2djs.so
var baseAddr = Module.findBaseAddress('libcocos2djs.so');
if (baseAddr === null) {
console.log('Module not loaded yet, retrying...');
return false;
}
// Calculate the target address by adding the offset to base address
var targetAddr = baseAddr.add(0x6D84AC);
Interceptor.attach(targetAddr, {
onEnter: function(args) {
// args[0] = a1
// args[1] = a2
// args[2] = a3 (string pointer)
// args[3] = a4
// args[4] = a5
// Read the string from the pointer (a3)
var length = args[3].toInt32();
var stringPtr = args[2];
// Read the bytes and create a string
var bytes = Memory.readByteArray(stringPtr, length);
// Convert ArrayBuffer to regular array before using map
var byteArray = Array.from(new Uint8Array(bytes));
console.log('String content (hex):', byteArray.map(b => b.toString(16).padStart(2, '0')).join(' '));
// Try to read as UTF-8 string
try {
var str = Memory.readUtf8String(stringPtr, length);
console.log('String content (UTF-8):', str);
} catch (e) {
console.log('Failed to read as UTF-8 string:', e);
}
}
});
console.log('Hook installed at offset 0x6D84AC');
return true;
}
// Try to hook immediately
if (!hookModule()) {
// If failed, set up an interval to keep trying
var intervalId = setInterval(function() {
if (hookModule()) {
clearInterval(intervalId);
}
}, 50);
}
});
Run the game with Frida, passing the hook file so it runs right when the app starts:
앱이 시작될 때 바로 실행되도록 후크 파일을 전달하여 Frida로 게임을 실행하세요:
frida -U -l hook.js -f com.the.game
And… bingo! 그리고… 빙고!

By printing the method’s arguments, I was able to capture the decryption key. This was a breakthrough moment — I now had the key to decrypt the JS files.
메서드의 인수를 출력함으로써 복호화 키를 캡처할 수 있었습니다. 이것은 획기적인 순간이었고 — 이제 JS 파일을 복호화할 수 있는 키를 손에 넣은 셈이었습니다.
Step 4: Decrypting the JS Files
4단계: JS 파일 복호화
Using the cocos2d-dec tool, I decrypted the JS files and dumped their plaintext contents. This gave me access to the game’s logic and allowed me to understand how it worked under the hood.
cocos2d-dec 도구를 사용하여 JS 파일을 복호화하고 평문 내용을 덤프했습니다. 이를 통해 게임의 로직에 접근할 수 있었고, 내부 작동 방식을 이해할 수 있었습니다.
Usage was pretty straight forward, in the case of this game, xxtea had an additional xor step, so I also needed to use -xs and -xk arguments in the tool, passing to it the magic header of the xxtea-encrypted files, which you can identify by opening them. It will be basically the initial content and the only readable thing in the file.
사용법은 꽤 직관적이었으며, 이 게임의 경우 xxtea에 추가적인 xor 단계가 있어서 도구에 -xs 및 -xk 인수를 사용해야 했습니다. 이 인수에는 xxtea로 암호화된 파일의 매직 헤더를 전달해야 하는데, 파일을 열어 확인할 수 있습니다. 기본적으로 파일 내에서 처음 나오는 내용이자 유일하게 읽을 수 있는 부분입니다.
I pointed the tool at the path in which apktool decompressed the apk file, it will recurse finding .jsc files and decrypting them, creating the .js file in the same folder.
apktool이 apk 파일을 분해한 경로를 도구에 지정했으며, 도구는 해당 경로를 재귀적으로 탐색하여 .jsc 파일을 찾아 복호화하고, 같은 폴더에 .js 파일을 생성합니다.
python3 js_xxtea_decrypt.py -k xxxxxxxx-8161-xx -xs "$(echo -en 'xxxxxx' | xxd -p)" -xk xxxxxx ~/dev/the-game/game-root-folder

복호화 전후의 파일
Step 5: Decrypting the PNG Assets
5단계: PNG 자산 복호화
As an additional step, I also wanted to dump all of the game’s images, and they were also encrypted using XXTEA, but with a twist: the key was dynamically generated based on the first 16 bytes of the file name. The process to discover that was the following, first I tried to find methods with obvious names containing “png” or “image” in it like “decrypt_image”, “decrypt_png” etc, but didn’t find any, they were also probably mangled such as the other ones. I then tried an approach similar to the one that helped with finding the method that decrypted files, but couldn’t find suggestive strings also, so I changed strategy and decided to work backwards…
추가 단계로, 게임의 모든 이미지를 덤프하고 싶었는데, 이 이미지들도 XXTEA로 암호화되어 있었지만 한 가지 변형이 있었습니다: 키가 파일 이름의 처음 16바이트를 기반으로 동적으로 생성되었습니다. 이를 발견하는 과정은 다음과 같았습니다. 먼저 “decrypt_image”, “decrypt_png” 등 “png”나 “image”가 포함된 명확한 이름의 메서드를 찾으려 했으나 찾지 못했고, 아마도 다른 메서드들처럼 난독화되어 있었던 것 같습니다. 그 다음 파일을 복호화하는 메서드를 찾는 데 도움을 준 방법과 유사한 접근법을 시도했지만, 유의미한 문자열도 찾지 못해 전략을 바꾸어 역방향으로 작업하기로 결정했습니다…
I knew that for the images to be rendered, they first need to be decrypted, and for this to happen, they first need to be loaded from the disk in their encrypted format, so I’ve put a breakpoint in fopen
function using IDA Pro and traced the call stack to find the decryption routine. This strategy is very common in RE, we work with the assumption that an interesting flow will need to use a standard library API and put a breakpoint there to find caller method. This was the method that called fopen and my findings:
이미지가 렌더링되기 위해서는 먼저 복호화되어야 한다는 것을 알고 있었고, 이를 위해서는 먼저 암호화된 형식으로 디스크에서 로드되어야 하므로 IDA Pro를 사용하여 fopen
함수에 중단점을 설정하고 호출 스택을 추적하여 복호화 루틴을 찾았습니다. 이 전략은 리버스 엔지니어링(RE)에서 매우 흔한 방법으로, 흥미로운 흐름이 표준 라이브러리 API를 사용해야 한다는 가정 하에 중단점을 설정하여 호출자 메서드를 찾습니다. 이것이 fopen을 호출한 메서드였고 제 발견 내용은 다음과 같습니다:

호출을 추적하여 찾은 fopen 호출을 포함한 루틴
Ok, there’s some interesting things there, first the fopen call that I definitely only found because I traced back from my break point given its not referenced directly, then on the bottom we can see a call to sub_6D84AC, bingo!
좋아요, 여기에는 흥미로운 점들이 있습니다. 먼저 fopen 호출은 직접 참조되지 않기 때문에 중단점에서 역추적하여야만 확실히 찾을 수 있었습니다. 그리고 아래쪽에는 sub_6D84AC 호출이 보이네요, 빙고!
This is our xxtea_decryt function, same as we found while looking how to decrypt js files, however, we can see there’s some logic around defining the key (3rd) argument in the call, v16. The logic is simple, it’s basically checking if the length of the name of the file being decrypted is < 17, if it is, it uses the default key which is in plain text there for us to see, otherwise, it uses another value, at that point I basically copy pasted the whole method in Cursor and asked my friend Claude to tell me what this value is and it was spot on: it’s the first 16 chars of the name of the file.
이것은 우리의 xxtea_decrypt 함수로, js 파일을 복호화하는 방법을 찾으면서 발견한 것과 동일합니다. 그러나 호출 시 세 번째 인자인 키를 정의하는 로직(v16)이 있는 것을 볼 수 있습니다. 로직은 간단하며, 복호화 중인 파일 이름의 길이가 17 미만인지 확인합니다. 만약 그렇다면, 평문으로 볼 수 있는 기본 키를 사용하고, 그렇지 않으면 다른 값을 사용합니다. 그 시점에서 저는 전체 메서드를 Cursor에 복사하여 친구 Claude에게 이 값이 무엇인지 물었고, 정확히 맞았습니다: 파일 이름의 처음 16자입니다.
With the help of good old Claude again, I wrote a Python script to replicate the same decryption logic and successfully decrypted all game’s PNG files.
다시 한 번 좋은 친구 Claude의 도움으로, 동일한 복호화 로직을 재현하는 파이썬 스크립트를 작성했고, 게임의 모든 PNG 파일을 성공적으로 복호화했습니다.
Challenges and Breakthroughs
도전과 돌파구
Setting up the RE environment itself is a challenge. Jailbreaking an iOS device is no easy task these days, one needs to have some specific device on some specific range of iOS versions. Also, anti-debugging protections can made it difficult to attach a debugger, I couldn’t do it in a rooted Android environment, so I switched to a jailbroken M1 iPad, which worked seamlessly. Another challenge is understanding the assembly code, but this part has come a long way since when I started reverse engineering games to hack them around 2007, tools like IDA Pro and LLMs made this process much easier.
RE 환경 자체를 설정하는 것만으로도 도전 과제입니다. 요즘 iOS 기기를 탈옥하는 것은 쉽지 않은 일이며, 특정 iOS 버전 범위 내의 특정 기기가 필요합니다. 또한, 안티 디버깅 보호 기능 때문에 디버거를 연결하기 어려울 수 있는데, 루팅된 안드로이드 환경에서는 할 수 없어서 탈옥된 M1 아이패드로 전환했더니 원활하게 작동했습니다. 또 다른 도전 과제는 어셈블리 코드를 이해하는 것이지만, 이 부분은 2007년경 게임을 해킹하기 위해 리버스 엔지니어링을 시작했을 때에 비해 많이 발전했으며, IDA Pro와 LLMs 같은 도구들이 이 과정을 훨씬 쉽게 만들어 주었습니다.
The breakthrough came when I successfully hooked the decryption method with Frida and captured the key. This opened the door to decrypting both the JS files and the PNG assets.
돌파구는 Frida로 복호화 메서드를 성공적으로 후킹하고 키를 캡처했을 때 찾아왔습니다. 이것은 JS 파일과 PNG 자산 모두를 복호화할 수 있는 문을 열어주었습니다.
Results: A Fully Decrypted Game
결과: 완전히 복호화된 게임
By the end of the process, I had access to all the game’s decrypted JS files and PNG assets. In a second moment, I also wrote a Frida hook to the “evalString” function in libcocos2djs.so that allowed me to modify the game’s JS code in my local computer and replace what was loaded in runtime, making it easy to debug the non-native part of the game.
과정이 끝날 무렵, 저는 게임의 모든 복호화된 JS 파일과 PNG 자산에 접근할 수 있었습니다. 그 다음으로는 libcocos2djs.so의 “evalString” 함수에 Frida 후킹을 작성하여 로컬 컴퓨터에서 게임의 JS 코드를 수정하고 런타임에 로드된 내용을 교체할 수 있게 했으며, 이를 통해 게임의 비네이티브 부분을 쉽게 디버깅할 수 있었습니다.
Ethical Considerations 윤리적 고려사항
It’s important to note that this project was solely for educational purposes. Reverse engineering can be a powerful tool for learning, but it should never be used to steal or copy someone else’s work. Instead, it’s an opportunity to learn from others and improve your own software.
이 프로젝트는 오로지 교육 목적으로만 진행되었음을 명심하는 것이 중요합니다. 리버스 엔지니어링은 학습에 강력한 도구가 될 수 있지만, 다른 사람의 작업을 훔치거나 복제하는 데 사용되어서는 안 됩니다. 대신, 이는 다른 사람들로부터 배우고 자신의 소프트웨어를 개선할 수 있는 기회입니다.
Lessons Learned 배운 교훈
- Combining Static and Dynamic Analysis: Static analysis alone wasn’t enough — I needed runtime hooking to capture the decryption key.
정적 분석과 동적 분석의 결합: 정적 분석만으로는 충분하지 않았습니다 — 복호화 키를 포착하기 위해 런타임 후킹이 필요했습니다. - The Power of Frida: Frida is an incredibly versatile tool for dynamic analysis and runtime manipulation.
Frida의 힘: Frida는 동적 분석과 런타임 조작을 위한 매우 다재다능한 도구입니다. - LLMs as Reverse Engineering Assistants: Tools like Claude 3.5 Sonnet and DeepSeek R1 made it easier to understand assembly code and write custom hook scripts.
역공학 보조 도구로서의 LLMs: Claude 3.5 Sonnet 및 DeepSeek R1과 같은 도구들은 어셈블리 코드를 이해하고 맞춤형 후크 스크립트를 작성하는 것을 더 쉽게 만들어 주었습니다. - Anti-Debugging Protections: Be prepared to find these, overcoming them can be a BIG challenge sometimes, but if you’re lucky, something as simple as switch platforms or devices can help you with that.
안티 디버깅 보호: 이러한 보호 기능을 발견할 준비를 하세요. 이를 극복하는 것은 때때로 큰 도전이 될 수 있지만, 운이 좋다면 플랫폼이나 장치를 단순히 전환하는 것만으로도 도움이 될 수 있습니다.
Empowering Others 다른 사람들에게 힘을 실어주기
My goal with this article is to empower you to tackle similar challenges. Whether you’re a reverse engineering enthusiast, a game developer, or a security researcher, the techniques and tools I’ve shared can help you achieve your goals. Remember, the key to success is persistence, curiosity, and a willingness to learn.
이 글의 목표는 여러분이 유사한 도전에 맞설 수 있도록 힘을 실어주는 것입니다. 리버스 엔지니어링 애호가이든, 게임 개발자이든, 보안 연구원이든, 제가 공유한 기술과 도구들은 여러분이 목표를 달성하는 데 도움이 될 수 있습니다. 성공의 열쇠는 끈기, 호기심, 그리고 배우려는 의지임을 기억하세요.
If you’re interested in reverse engineering, I encourage you to dive in and start experimenting. Use the tools and techniques I’ve shared, and look for more resources online, a tip is that there’s a LOT of good information about this subject in Chinese websites, so Baidu + Google translator can be a big ally. And most importantly, always approach reverse engineering with respect for others’ work and a commitment to ethical practices.
리버스 엔지니어링에 관심이 있다면, 직접 뛰어들어 실험해 보시길 권장합니다. 제가 공유한 도구와 기술을 사용하고, 온라인에서 더 많은 자료를 찾아보세요. 팁을 드리자면, 이 주제에 관한 좋은 정보가 중국어 웹사이트에 많이 있으니, 바이두와 구글 번역기를 활용하는 것이 큰 도움이 될 수 있습니다. 그리고 가장 중요한 것은, 항상 타인의 작업에 대한 존중과 윤리적 실천에 대한 의지를 가지고 리버스 엔지니어링에 접근하는 것입니다.