Analysis process
1. View game engine types
2. Install the game
3. Packet capture analysis
4.ida analysis so file
5. Obtain key decryption
6. Analyze the decrypted source code
7. Verify the analysis results
Download the game. This game is a cocos2d JS game. The encrypted file is jsc suffix.
Install the App to capture the package. When you open the game, you can request the package and obtain the game configuration information, but the data is encrypted.
>>>>>>>>>>>>>>>>>>>Then start working v_v
Find libcos2djs. In the \ lib\arm64-v8a directory So file, pull in ida analysis
Find} xxtea_ The ida position of decrypt is 0x6FAED4
I use Frida hook
frida -U com.yoyo.happyfish -l klby.js --no-pause
Package name: com yoyo. happyfish
Get jsc decryption key
Here we get the decryption key of jsc. The rest is to decrypt jsc into js. Analyze the js code to see how to encrypt and decrypt the data
hook code klby js
// Find base address var baseAddr = Module.findBaseAddress('libcocos2djs.so'); console.log('libcocos2djs.so baseAddr: ' + baseAddr); var xxtea_decrypt = resolveAddress(0x6FAED4);//Decrypt jsc Interceptor.attach(xxtea_decrypt, { onEnter: function (args) { console.log('[+] Called xxtea_decrypt ' + xxtea_decrypt); console.log('[+] args0,r0: ' + args[0]);//Data data console.log('[+] args1,r1: ' + args[1]);//data length console.log('[+] args2,r2: ' + args[2]);//secret key console.log('[+] args2,r3: ' + args[3]);//Key length dumpAddr('args0', args[0], 64); dumpAddr('args2', args[2], 64); }, onLeave: function (retval) { console.log('[+] Returned from xxtea_decrypt: ' + retval); } }); function dumpAddr(info, addr, size) { if (addr.isNull()) return; console.log('Data dump ' + info + ' :'+addr); var buf = addr.readByteArray(size); // If you want color magic, set ansi to true console.log(hexdump(buf, { offset: 0, length: size, header: true, ansi: false })); } function resolveAddress(addr) { var idaBase = 0x0; // Enter the base address of jvm.dll as seen in your favorite disassembler (here IDA) var offset = ptr(addr).sub(idaBase); // Calculate offset in memory from base address in IDA database var result = baseAddr.add(offset); // Add current memory base address to offset of function to monitor console.log('[+] New addr=' + result); // Write location of function in memory to console return result; }
Start decrypting jsc files. I use the fool decryption method. Just fill in the key and a software will do it automatically.
A decrypted compressed package. Open the compressed package and drag the file out.
Open the file and search for m-urlencoded;charset=utf - locate where the Http request is sent.
If aesEncryptString data encryption and HttpEncryptKey are found, we will print the HttpEncryptKey.
I'll be lazy and add it directly to the http request header. I can see the key from the packet capture.
Select the new encryption and put it The js file is encrypted back to the jsc file to replace the original file.
Rendering, get the aes encryption key.
I also printed the POST data, content params: {"channelid": "0"}
Then I'll test whether the key is correct. The test results are completely consistent
Then I'll see how to decrypt it.
Search aesEncryptString. The following is the decryption method aesDecryptString
Search for aesDecryptString, find the http data return, call aesDecryptString, and decrypt the data with t.config Httpencryptkey. The description is the same as that of encryption.
The test and decryption of the returned data are also successful, so our first step ① is completed.
Decrypt part of js code
var i = function() { function t() {} t.randomAesKeyBuffer = function() { return n(16); }; t.randomAesIvBuffer = function() { return n(16); }; t.stringToBuffer = function(t) { for (var e = [], o = t.length, n = 0; n < o; n++) e[n] = t.charCodeAt(n); return new Uint8Array(e); }; t.bufferToString = function(t) { for (var e = "", o = Array.from(t), n = 0, r = o.length; n < r; n++) e += String.fromCharCode(o[n]); return e; }; t.aesEncryptString = function(t, e) { if (e.length > 0) { var o = by.CryptoJS, n = o.enc.Utf8.parse(t), r = o.enc.Utf8.parse(e); return by.CryptoJS.AES.encrypt(r, n, { mode: o.mode.ECB, padding: o.pad.Pkcs7 }).toString(); } return ""; }; t.aesDecryptString = function(t, e) { var o = by.CryptoJS, n = o.enc.Utf8.parse(t), r = o.AES.decrypt(e, n, { mode: o.mode.ECB, padding: o.pad.Pkcs7 }); return o.enc.Utf8.stringify(r).toString(); }; t.aesEncryptBuffer = function(t, e, o) { var n = by.CryptoJS, i = n.lib.WordArray.create(t), a = n.lib.WordArray.create(e), s = n.lib.WordArray.create(o); return r(n.AES.encrypt(s, i, { iv: a, mode: n.mode.CBC, padding: n.pad.ZeroPadding }).ciphertext); }; t.aesDecryptBuffer = function(t, e, o) { var n = by.CryptoJS, i = n.lib.WordArray.create(t), a = n.lib.WordArray.create(e), s = n.lib.WordArray.create(o), c = n.enc.Base64.stringify(s); return r(n.AES.decrypt(c, i, { iv: a, mode: n.mode.CBC, padding: n.pad.NoPadding })); }; t.rsaEncryptWithPublicKey = function(t) { var e = by.forge.pki.publicKeyFromPem("-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEA3svz/3o/IBJGL3Lml5zH1jTpey+Z1fZraHF5dOj0FA1f8eJM7naE\ncO4tDuIgbICXfNW4Ln+hTi33EMzulpZpehigYFd9aPinJ6ynhNXE4BL/2NauYIRq\nvTy+72PtL4gswT0l0tWWbCX2yNco0G8J4fk92ZivuO+/osTtmYLMmNJ/hjPi4u86\njRtLkoSQ+6X55OJmLA1enWiu5hxDQQtue3Yw96Wo7fUJh8BnTBw7lTUwTHJhadoK\nt6mJqQfv0DnnHaOERkwxNgL/c6u04R2ruywyKrC5aaFRqZRrGA3zthUZS5K3/YLq\nU8tf+UBiBI1hEyroAWe8Q8/syBJ0cYOCOQIDAQAB\n-----END RSA PUBLIC KEY-----"), o = this.bufferToString(t), n = e.encrypt(o); return this.stringToBuffer(n); }; t.hex_md5 = function(t) { var e = by.CryptoJS; return e.MD5(t).toString(e.enc.Hex); }; t.LogTag = "[CryptoTool]"; t.strEncryptKey = "thndwdhbqdyydsy5"; return t; }();
>>>>Colored egg<<<<
Careful people may have found that the key is saved in t.Config Httpencryptkey, Config keyword, the description is saved to a certain place, so dig again.
Just find a gameconfig. In the assets directory Config file,
Open file found to be encrypted
Then look in the js file to see if there is a decryption key and search gameconfig config
Find the following key code: aesDecryptString
cc.loader.load({ url: e, type: "text" }, function(e, o) { //Key decryption key strEncryptKey found is thndwdhbqdyydsy5 var n = i.CryptoTool.aesDecryptString(i.CryptoTool.strEncryptKey, o); r = JSON.parse(n); by.extend("by", "Config", r); });
t.loadConfig = function(t) { if (void 0 == by.Config) { var e = ""; if (cc.sys.isNative) e = cc.path.join(r.default.pkgInsideRootPath, "GameConfig.config"); else { e = globalThis.location.origin + globalThis.location.pathname; var o = globalThis.location.search, n = "TestChannel"; if ("" != o) { var a = o.substr(1, o.length).split("&"); a && (n = a[0]); } e += n + ".config"; } cc.loader.load({ url: e, type: "text" }, function(e, o) { if (e) t(e); else { var n = i.CryptoTool.aesDecryptString(i.CryptoTool.strEncryptKey, o), r = JSON.parse(n); by.extend("by", "Config", r); t && t(e, r); } }); } else t(void 0, by.Config); };
The decrypted local configuration file contains the HttpEncryptKey field, which is the same as that obtained above.
This time only analyzes the encryption at the http level. This game also has a websocket long connection. The specific decryption analysis will be written next time.