某cocos2djs游戏jsc以及png文件解密

[复制链接]
查看847 | 回复0 | 2024-12-14 19:35:38 | 显示全部楼层 |阅读模式

立即注册,突破游客限制,与技术大牛交流切磋,畅享海量资源,让你的技术之路不再孤单!

您需要 登录 才可以下载或查看,没有账号?立即注册

×
本帖最后由 逆向君 于 2024-12-14 19:37 编辑



资源文件解密
首先解包APP后打开assets\resources文件夹后发现png文件,但是直接打开提示图片已破损,我勒个豆,顿感不妙,遂换一个工具试试,使用010打开后有了新发现。如下图:
截图202412141926507712.png
这小子竟然偷偷换了PNG的魔术头,那么他有木有可能偷懒只是改了魔术头呢?于是乎我就准备把头在给他安装回去,让他不在那么不正常!!!填充头部8字节的魔术头(89 50 4E 47 0D 0A 1A 0A),保存,重新打开,发现还是不行。这小子是一点懒也不偷啊!年度最佳员工奖得给他!
那没办法了,轻易得到的总不是最珍惜的!干他!!!
首先观察几张加密的PNG文件后发现头部7字节仿佛是flag,是固定的,7字节后可能就是加密数据。
加载图片的话,一般会调用open、fopen之类的函数,我们使用frida hook 这些API,看看是否会有所收获。经过一堆输出然后检索信息后得到了有效信息如下:
  1. [Libc::fopen] fopen filename /data/user/0/com.xxxx.xxx/files/gamecaches/mnpanda/17340646274751859.png
  2. [INFO][12/13/2024, 00:37:11 PM][PID:6478][Thread-25][6654][showNativeStacks]: Backtrace:
  3. 0x7493b6edfc libcocos2djs.so!0x766dfc
  4. 0x7493b6edfc libcocos2djs.so!0x766dfc
  5. 0x7493b58664 libcocos2djs.so!0x750664
  6. 0x7493b6ed0c libcocos2djs.so!0x766d0c
  7. 0x7493c792dc libcocos2djs.so!0x8712dc
  8. 0x7493befe48 libcocos2djs.so!0x7e7e48
  9. 0x7493c81d4c libcocos2djs.so!0x879d4c
  10. 0x7493c82808 libcocos2djs.so!0x87a808
  11. 0x75c6b0c5cc libc.so!_ZL15__pthread_startPv+0xd4
  12. 0x75c6aa5fc0 libc.so!__start_thread+0x48
  13. 0x75c6aa5fc0 libc.so!__start_thread+0x48
复制代码
通过上面的信息我们可以发现解密函数可能来自libcocos2djs.so,IDA加载该so后跳转到
0x766dfc 这个地址看一下。如下图:
截图202412141928021741.png
这里只是文件操作相关,说明加密函数还在上一层,继续追,找到其调用位置,如下图:
截图202412141928353933.png
该函数主要是尝试从 OBB(扩展)文件或 Android 资产系统加载文件内容,在往上追......
截图202412141929005113.png
直到这里,一切就变得明了了。我们跟进cocos2d::Image::initWithImageData函数看看。
截图202412141929258811.png
cocos2d::Image::deEncryptPng函数中,密钥固定为1f8fd1612362fdd6f753f2ee55107d2b,跟进函数看看
截图202412141930007086.png
hook看一下参数
截图202412141930435354.png
果然跟上面的猜测一样,原始数据偏移7字节后即为加密数据,然后key和每个字节做异或运算得到明文数据。按照原始程序将此还原为C。主要逻辑代码如下:
  1. int deEncryptPng(
  2.         const unsigned char *inputData,  // 本地读取的加密PNG数据(不含PNG头和IEND)
  3.         size_t inputLen,                 // inputData长度
  4.         const char *key,                 // 异或密钥字符串
  5.         unsigned char *outputData        // 输出的完整解密PNG数据
  6. )
  7. {
  8.     // 计算密钥长度
  9.     size_t keyLen = strlen(key);
  10.     // 总输出长度 = 输入长度 + 8字节PNG头 + 12字节IEND = inputLen + 20
  11.     size_t outputLen = inputLen + 20;
  12.     // 写入PNG标准头部:89 50 4E 47 0D 0A 1A 0A
  13.     // (标准PNG文件头)
  14.     outputData[0] = 0x89;
  15.     outputData[1] = 0x50;
  16.     outputData[2] = 0x4E;
  17.     outputData[3] = 0x47;
  18.     outputData[4] = 0x0D;
  19.     outputData[5] = 0x0A;
  20.     outputData[6] = 0x1A;
  21.     outputData[7] = 0x0A;
  22.     // 将输入数据拷贝到outputData的第8字节开始的位置
  23.     memcpy(outputData + 8, inputData, inputLen);
  24.     // 写入IEND块到尾部
  25.     // IEND块共12字节:00 00 00 00 49 45 4E 44 AE 42 60 82
  26.     size_t endPos = outputLen - 12;
  27.     outputData[endPos + 0]  = 0x00;
  28.     outputData[endPos + 1]  = 0x00;
  29.     outputData[endPos + 2]  = 0x00;
  30.     outputData[endPos + 3]  = 0x00;
  31.     outputData[endPos + 4]  = 0x49; // 'I'
  32.     outputData[endPos + 5]  = 0x45; // 'E'
  33.     outputData[endPos + 6]  = 0x4E; // 'N'
  34.     outputData[endPos + 7]  = 0x44; // 'D'
  35.     outputData[endPos + 8]  = 0xAE;
  36.     outputData[endPos + 9]  = 0x42;
  37.     outputData[endPos + 10] = 0x60;
  38.     outputData[endPos + 11] = 0x82;
  39.     // 异或处理区域:从offset=8一直到(outputLen - 13)字节位置
  40.     // 这对应原代码中v8 = a4 - 13的逻辑
  41.     if (outputLen > 20) {
  42.         size_t start = 8;
  43.         size_t stop = outputLen - 13; // 包含此位置
  44.         size_t idx = 0;
  45.         for (size_t i = start; i <= stop; i++) {
  46.             if (idx >= keyLen) idx = 0;
  47.             outputData[i] ^= (unsigned char)key[idx++];
  48.         }
  49.     }
  50.     // 返回密钥长度(根据原函数返回值逻辑)
  51.     return (int)keyLen;
复制代码
尝试解密,发现姿势正确!!!
截图202412141931324126.png
jsc文件解密
在查找PNG文件的途中,发现了jsc文件,那就顺便研究一下姿势吧!
通过询问GPT得到了一定的思路,GPT回答如下:
截图202412141931567925.png
按照这个思路,我们去IDA里面搜一下xxtea。
截图202412141932197117.png
发现有个设置key的地方,跟进去看一下。
截图202412141932556689.png
代码逻辑比较简单,就是根据传入指针 result 的内容,对一个全局字符串变量 byte_1BD9AC8进行设置,记住这个全局变量,一会要考!!!!
那我们就交叉引用看一下调用它的地方。
截图202412141933189546.png
这时候我们就发现key固定为bc337194-20c1-45。既然找到密钥了,再回去看xxtea的解密函数。
截图202412141933404551.png
对于参数对应关系不太清楚,这时候需要查阅一下源码xxtea_decrypt 通过源码可知,参数一为加密数据,参数二为加密数据长度,参数三为解密key,参数四为key的长度,参数为解密数据长度。
验证是否和源码描述一样,我们可以通过查看静态代码和hook的方式去确认。首先我们查看调用解密方法的地方。主要逻辑如下图:
截图202412141934023423.png
这时候我们基本上确定和分析的一样,我们可以hook看一下数据。在打开加密的jsc文件,在hook 结果中搜索一下加密数据的前几位,就可以确定是否是我们想要的数据。
截图202412141934317930.png
那么接下来就可以写代码验证了,解密方法为xxtea_decrypt, 密钥为bc337194-20c1-45。主要代码如下:
  1. def export_data(
  2.     jsc_file_path: str, encryption_key_str: str, output_file_path: str
  3. ) -> Optional[tuple]:
  4.     """
  5.     Function for decrypting JSC files
  6.     """
  7.     logger.info("Starting export_data...")
  8.     code, status = check_file(jsc_file_path, ".JSC", True)
  9.     if code != "OK":
  10.         return code, status
  11.     jsc_file = open(jsc_file_path, "rb")
  12.     jsc_file_data = jsc_file.read()
  13.     jsc_file.close()
  14.     logger.info(f"Decrypting with key = {encryption_key_str}")
  15.     output_data = xxtea.decrypt(jsc_file_data, encryption_key_str, padding=False)
  16.     if len(output_data) == 0:
  17.         return "WRONG_KEY", "Invalid encryption key!"
  18.     is_gzip_file = True
  19.     try:
  20.         output_data = zlib.decompress(output_data, 32 + 15)
  21.         logger.info("IT IS a GZIP archive.")
  22.     except zlib.error as error:
  23.         logger.info("It's NOT a GZIP archive.")
  24.         is_gzip_file = False
  25.     if not is_gzip_file:
  26.         try:
  27.             zip_file = io.BytesIO(output_data)
  28.             ZipFile(zip_file)
  29.             output_file_path += ".zip"
  30.             logger.info("IT IS a ZIP archive.")
  31.         except BadZipFile as error:
  32.             logger.info("It is NOT a ZIP archive.")
  33.     js_file = open(output_file_path, "wb")
  34.     js_file.write(output_data)
  35.     js_file.close()
  36.     logger.info(f"File exported: {output_file_path}")
  37.     return "OK", ""
复制代码
运行后查看,解密成功~~~~~~
截图202412141935303356.png



这个人还没有签名
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则