本篇文章仅为学习交流所用,涉及的数据已做脱敏处理,请勿用于不当途径,侵权请联系
首先解包APP后打开assets\resources文件夹后发现png文件,但是直接打开提示图片已破损,我勒个豆,顿感不妙,遂换一个工具试试,使用010打开后有了新发现。如下图:

这小子竟然偷偷换了PNG的魔术头,那么他有木有可能偷懒只是改了魔术头呢?于是乎我就准备把头在给他安装回去,让他不在那么不正常!!!填充头部8字节的魔术头(89 50 4E 47 0D 0A 1A 0A),保存,重新打开,发现还是不行。这小子是一点懒也不偷啊!年度最佳员工奖得给他!
那没办法了,轻易得到的总不是最珍惜的!干他!!!
首先观察几张加密的PNG文件后发现头部7字节仿佛是flag,是固定的,7字节后可能就是加密数据。
加载图片的话,一般会调用open、fopen之类的函数,我们使用frida hook 这些API,看看是否会有所收获。经过一堆输出然后检索信息后得到了有效信息如下:
[Libc::fopen] fopen filename /data/user/0/com.xxxx.xxx/files/gamecaches/mnpanda/17340646274751859.png
[INFO][12/13/2024, 00:37:11 PM][PID:6478][Thread-25][6654][showNativeStacks]: Backtrace:
0x7493b6edfc libcocos2djs.so!0x766dfc
0x7493b6edfc libcocos2djs.so!0x766dfc
0x7493b58664 libcocos2djs.so!0x750664
0x7493b6ed0c libcocos2djs.so!0x766d0c
0x7493c792dc libcocos2djs.so!0x8712dc
0x7493befe48 libcocos2djs.so!0x7e7e48
0x7493c81d4c libcocos2djs.so!0x879d4c
0x7493c82808 libcocos2djs.so!0x87a808
0x75c6b0c5cc libc.so!_ZL15__pthread_startPv+0xd4
0x75c6aa5fc0 libc.so!__start_thread+0x48
0x75c6aa5fc0 libc.so!__start_thread+0x48
通过上面的信息我们可以发现解密函数可能来自libcocos2djs.so,IDA加载该so后跳转到
0x766dfc 这个地址看一下。如下图:

这里只是文件操作相关,说明加密函数还在上一层,继续追,找到其调用位置,如下图:

该函数主要是尝试从 OBB(扩展)文件或 Android 资产系统加载文件内容,在往上追......

直到这里,一切就变得明了了。我们跟进cocos2d::Image::initWithImageData函数看看。

cocos2d::Image::deEncryptPng函数中,密钥固定为1f8fd1612362fdd6f753f2ee55107d2b,跟进函数看看

hook看一下参数

果然跟上面的猜测一样,原始数据偏移7字节后即为加密数据,然后key和每个字节做异或运算得到明文数据。按照原始程序将此还原为C。主要逻辑代码如下:
尝试解密,发现姿势正确!!!

在查找PNG文件的途中,发现了jsc文件,那就顺便研究一下姿势吧!
通过询问GPT得到了一定的思路,GPT回答如下:

按照这个思路,我们去IDA里面搜一下xxtea。

发现有个设置key的地方,跟进去看一下。

代码逻辑比较简单,就是根据传入指针 result 的内容,对一个全局字符串变量 byte_1BD9AC8进行设置,记住这个全局变量,一会要考!!!!
那我们就交叉引用看一下调用它的地方。

这时候我们就发现key固定为bc337194-20c1-45。既然找到密钥了,再回去看xxtea的解密函数。

对于参数对应关系不太清楚,这时候需要查阅一下源码xxtea_decrypt 通过源码可知,参数一为加密数据,参数二为加密数据长度,参数三为解密key,参数四为key的长度,参数为解密数据长度。
验证是否和源码描述一样,我们可以通过查看静态代码和hook的方式去确认。首先我们查看调用解密方法的地方。主要逻辑如下图:

这时候我们基本上确定和分析的一样,我们可以hook看一下数据。在打开加密的jsc文件,在hook 结果中搜索一下加密数据的前几位,就可以确定是否是我们想要的数据。

那么接下来就可以写代码验证了,解密方法为xxtea_decrypt, 密钥为bc337194-20c1-45。主要代码如下:
运行后查看,解密成功~~~~~~

至此,涉及jsc和资源文件的解密就结束了!!!那位同学的最佳员工可能暂时没了,不过新的KPI又有了!!!你就说吧,我对你好不好!哈哈哈哈哈~~~
项目完整代码:cocos2dx_decryption
int
deEncryptPng(
const
unsigned
char
*inputData,
size_t
inputLen,
const
char
*key,
unsigned
char
*outputData
)
{
size_t
keyLen =
strlen
(key);
size_t
outputLen = inputLen + 20;
outputData[0] = 0x89;
outputData[1] = 0x50;
outputData[2] = 0x4E;
outputData[3] = 0x47;
outputData[4] = 0x0D;
outputData[5] = 0x0A;
outputData[6] = 0x1A;
outputData[7] = 0x0A;
memcpy
(outputData + 8, inputData, inputLen);
size_t
endPos = outputLen - 12;
outputData[endPos + 0] = 0x00;
outputData[endPos + 1] = 0x00;
outputData[endPos + 2] = 0x00;
outputData[endPos + 3] = 0x00;
outputData[endPos + 4] = 0x49;
outputData[endPos + 5] = 0x45;
outputData[endPos + 6] = 0x4E;
outputData[endPos + 7] = 0x44;
outputData[endPos + 8] = 0xAE;
outputData[endPos + 9] = 0x42;
outputData[endPos + 10] = 0x60;
outputData[endPos + 11] = 0x82;
if
(outputLen > 20) {
size_t
start = 8;
size_t
stop = outputLen - 13;
size_t
idx = 0;
for
(
size_t
i = start; i <= stop; i++) {
if
(idx >= keyLen) idx = 0;
outputData[i] ^= (unsigned
char
)key[idx++];
}
}
return
(
int
)keyLen;
}
int
deEncryptPng(
const
unsigned
char
*inputData,
size_t
inputLen,
const
char
*key,
unsigned
char
*outputData
)
{
size_t
keyLen =
strlen
(key);
size_t
outputLen = inputLen + 20;
outputData[0] = 0x89;
outputData[1] = 0x50;
outputData[2] = 0x4E;
outputData[3] = 0x47;
outputData[4] = 0x0D;
outputData[5] = 0x0A;
outputData[6] = 0x1A;
outputData[7] = 0x0A;
memcpy
(outputData + 8, inputData, inputLen);
size_t
endPos = outputLen - 12;
outputData[endPos + 0] = 0x00;
outputData[endPos + 1] = 0x00;
outputData[endPos + 2] = 0x00;
outputData[endPos + 3] = 0x00;
outputData[endPos + 4] = 0x49;
outputData[endPos + 5] = 0x45;
outputData[endPos + 6] = 0x4E;
outputData[endPos + 7] = 0x44;
outputData[endPos + 8] = 0xAE;
outputData[endPos + 9] = 0x42;
outputData[endPos + 10] = 0x60;
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2024-12-14 15:36
被REerr编辑
,原因: