写在前面的话: 因为一些兴趣近期对一款网游的资源文件进行的分析提取,历经10天挑灯夜战, 最终成功解开了PKG压缩包, 还原了 骨骼3D文件, 动作3D文件, 重要的是借由逆向,间接学到了不少初级3D数学知识,过程思路为主,代码为辅,将在本文中一一介绍。 (解开了也没钱没用途 写个帖子以防自己忘记)
-
文中用到的主要工具: C32asm,Ollydbg,x64_dbg,WinDbg,IDA Pro,PCHunter,3DMAX
-
参考资料:
①百度百科COLLADA介绍
②collada快速入门
③DAE模型与骨骼动画解析渲染
(可跳过剩下内容直接看下一章节),如果要强行逆向也是可以的,旋转X,Y,Z在转换成所谓的64字节时,观察排列可得知X,Y,Z进行矩阵相乘时有关系可循, 比如X转换64字节后某一个位置固定是0或1,那后一个64字节与X相乘时,某一些位置结果不变, 这样可以固定得到一个原数据, 然后一步一步推算回去,不过脑子代价太大,我个人选择另寻他路,实在不行时再强行逆向算法。 在后期得到正在的算法后,可以知道这里的5次"关键函数"累加正是旋转X,Y,Z,的矩阵信息(3X3) 平移信息4字节, 缩放信息 4字节, 各5个信息组合起来, 而X,Y,Z转换计算可以在后面的算法代码看到。
参考资料:④:【3D计算机图形学】变换矩阵、欧拉角、四元数
参考:⑤ 四元数,欧拉角及姿态矩阵的相互转换
参考:⑥ 矩阵乘法的本质是什么?
参考:⑦理解矩阵乘法
参考:⑧矩阵的加、减、乘、除、求逆运算的实现
参考:⑨为什么matrix(矩阵)不可以相除?
如有转载希望标明出处
①百度百科COLLADA介绍
②collada快速入门
③DAE模型与骨骼动画解析渲染
④:【3D计算机图形学】变换矩阵、欧拉角、四元数
⑤ 四元数,欧拉角及姿态矩阵的相互转换
⑥ 矩阵乘法的本质是什么?
⑦理解矩阵乘法
⑧矩阵的加、减、乘、除、求逆运算的实现
⑨为什么matrix(矩阵)不可以相除?
游戏文件夹内有许多.pkg为后缀的文件,且文件大小不俗,一般都是存放着压缩过的游戏图片,音乐,建模等数据,过去热门的游戏有各种提取/解压工具, 甚至有的游戏可以用winrar直接打开, 但很不幸,此次的目标两者皆否。
打开C32Asm,16进制选取游戏文件夹内的某pkg文件后可以看到。
一眼能认出的就是 文件名字还有 下面的 被压缩过的数据, 由经验判断, 78 01 极可能是 zip算法的标头, 于是从420偏移处开始选择,一直到结尾空白区域把压缩数据复制出来保存。
选择了 6694 字节进行解压, 解压成功,的确是zip算法, 但是,文本文件各种乱码, 图片文件各种花屏, 却又能看到部分明文, 数据的确是正确的,是什么原因呢? 经过对比以及几次失败的尝试后发现。 我选取的是 6694 个字节, 但回到图1,压缩数据前的那4个字节数值却是 F6190000 反转(变量储存在内存的反转关系不用多说了吧) 后为 000019F6 = 6646, 这多出来的48字节怎么回事呢? 填充字节? 偷字节? 还是其中的某些字节有过处理? 如此的话静态分析就不会有结果了, 到了这里不得不动态调试游戏一探究竟。
由于是TP保护的网游,使用OD附加前需要搞定几处保护,简单讲 path nop掉gamebase.dll的沙盘创建,提前占用R0调试端口,然后运行网游后,使用PCHunter 恢复几处R3HOOK ,调试器能附加后,使用WinDbg附加找到主动抛异常的几个线程记下, 并重新运行游戏继续使用PCHunter搞定几个异常线程,就可以用OD附加了。
通过PCHunter可以发现,游戏运行后打开了所有的PKG文件句柄, 所以我们要对ReadFile以及SetFilePointer下断,返回用户代码进行多次来回运行后, 观察定位了几个关键函数, (由于时间过去10多天就不给出详细的跟踪过程了)
首先,ReadFile 读取都是1016字节一次,读一次后,SetFilePointer就会把偏移+8 然后再读下一段1016字节, 也就是说,每1024个字节会舍弃掉最后8字节, 这样就解释得通了, 为什么前面手动选取到6694个字节, 但数据前的标头却写的6646字节。 6646/1016*8取整=6 6*8刚好就是多出来的字节了。 多出来的这些就是不需要的,读取的时候跳过。测试后能成功解压出正常的数据,这个点算是解决了。
第二个点,文件名对应数据的偏移怎么拿到, 在游戏里找好第一次需要读取PKG包的点,对SetFilePointer下断后,第一次的返回就是临近获得偏移的地方,后发现偏移量每次都是由一个数*0x400后得到的, 而这个数,在文件名的前面28个字节处,代表这个文件名的数据从第几个0x400偏移开始..
//定义此结构用于读取包内文件信息
PkgFileInfo =record
FileDataGroup:Cardinal; //数据偏移位于第几组 FileDataGroup*0x400=偏移
unknow:Cardinal;
unknow2:Cardinal;
unknow3:Cardinal;
unknow4:Cardinal;
unknow5:Cardinal;
UnCompfileSize:Cardinal; //解压后的大小
FileName:array[0..255] of Char; //文件名
end;
知道文件名列表的规则,知道了数据读取规则,剩下的就是解压缩了。
写到这里才发现写得敷衍了些, 因为过去时间太久加上本文的重点在后面,解压这里就简单带过吧。搞定PKG文件花了半天时间, 而接下来的9天都是与3D信息文件的战斗...
解开了pkg包内的数据后,有图片,有脚本,有字符串表,都是明文可以直接打开的, 唯独3D数据,skel,anim,col等等 骨骼,蒙皮,动作等文件有过处理,尝试用各种3D软件都无法读取, C32asm打开也看不出什么名堂, 因为我从没接触过3D方面, 不管是游戏还是建模等等方面, 所以一时语塞了。这时候拖关系得到一个重要的信息,这款游戏的3D文件都是通过一个编码器从Collada数据格式转换过来的, 而这个编码器!你猜怎么着,它居然就在我们公司楼下的美术的电脑上, 今晚就潜伏进去偷过来(开玩笑)。
拿到了这个编码器,就有了前进的方向,目前需要进行测试收集信息,那么就离不开所谓的Collada数据格式。
通过搜索得知,collada是一种数据规范,意为让不同的3D软件都能读同一个格式取到相同的信息,各工具间导出导入方便交换3D数据。 一般这样的文件在3DMAX里导出为*.dae文件.
简而言之,目前需要逆向编码器对dae文件的编码过程,从而通过游戏文件逆向还原成dae文件就对了。打开3DMAX, 创建3个简单的骨骼,设置自动关键帧,选择其中一个节点做平移动作和旋转动作,并导出为dae格式,导出时不需要三角算法和单一矩阵。
DAE文件为XML格式,打开后看到
观察得知<library_animations>节点下存储了动画信息,<library_visual_scenes>节点下存储了骨骼信息,目前只关注这两个节点就可以了。由于骨骼文件的数据较少,决定先从简单的开始分析,多次观察统计后不难发现, node 节点包含一块骨骼(点)的信息, 如果有有子骨骼的话,就会在node节点下再包含一个node节点,刚才创建的3个连着的骨骼,其实上就是父子关系, 不过现阶段骨骼里面的各种数值代表什么意思完全不清楚。
对编码器动手之前,还是需要静态分析一下的,尝试用编码器编码刚才导出的Test.Dae文件, 编码器是一个控制台程序,直接运行就退出了,推测是通过参数调用, 载入OD,设置命名行参数为"1111111111111",并单步运行, 直到结束都没在堆栈或寄存器等看到类似11111111的字符,连GetCommandLine都得不到执行, 转换思路搜索字符串来到可疑的地方。
013E99E2 |. 68 A4684701 push converte.014768A4 ; collada\
013E99E7 |. C785 D4FEFFFF>mov [local.75],0xF
013E99F1 |. C785 D0FEFFFF>mov [local.76],0x0
013E99FB |. C685 C0FEFFFF>mov byte ptr ss:[ebp-0x140],0x0
013E9A02 |. E8 49C0FFFF call converte.013E5A50
013E9A07 |. 8D85 68FDFFFF lea eax,[local.166]
013E9A0D |. 50 push eax ; /pFindFileData = 0014FA18
013E9A0E |. 68 B0684701 push converte.014768B0 ; |collada\*.dae
013E9A13 |. C645 FC 02 mov byte ptr ss:[ebp-0x4],0x2 ; |
013E9A17 |. FF15 10604701 call dword ptr ds:[<&KERNEL32.FindFirstF>; \FindFirstFileA
013E9A1D |. 8BF8 mov edi,eax
013E9A1F |. 83FF FF cmp edi,-0x1
013E9A22 |. 75 2D jnz short converte.013E9A51
013E9A24 |. E8 67B10200 call converte.01414B90
013E9A29 |. 8B10 mov edx,dword ptr ds:[eax]
013E9A2B |. 68 C0684701 push converte.014768C0 ; No Collada files found
013E9A30 |. 8BC8 mov ecx,eax
013E9A32 |. FF52 04 call dword ptr ds:[edx+0x4]
013E9A35 |. 68 E4684701 push converte.014768E4 ; \n
013E9A3A |. E8 075F0600 call converte.0144F946
013E9A3F |. 68 C0684701 push converte.014768C0 ; No Collada files found
013E9A44 |. E8 FD5E0600 call converte.0144F946
013E9A49 |. 83C4 08 add esp,0x8
013E9A4C |. E9 A9010000 jmp converte.013E9BFA
013E9A51 |> 6A 00 push 0x0 ; /pSecurity = NULL
013E9A53 |. 68 D8684701 push converte.014768D8 ; |Converted
013E9A58 |. FF15 14604701 call dword ptr ds:[<&KERNEL32.CreateDire>; \CreateDirectoryA
原来编码器会判断有无collada文件夹,有的话才对里面的*.dae文件进行处理, 配置好后运行, 得到 Test.skel 和Test.anim 两个文件, 因为刚才的DAE文件里只有骨骼和动画,刚好就得到这两个文件。
用C32Asm打开Test.skel 可以看到。
通过多次生成不同的骨骼文件静态对比后 可以看到 骨骼名字结束后, 空了4个字节,然后是64个字节的骨骼信息, 然后4个字节表示下方有几个子节点,然后才是下一个骨骼的数据,现在要做的就是理解这64个字节是怎么转换得来的了,由经验得知, 3F800000是 浮点数1.000000 的十六进制数据, Skel文件内多次看到, 那么这64个字节其实是16个浮点数? 回去看一下dae文件,是不是都是浮点数? 难道!!! 所谓的编码器,就是简单的把DAE文件中的浮点数转换成16进制??????
然而!!! 并不是... skel中的浮点数和dae中的肉眼看没有任何关系。进行到这里时,本来让我继续我是拒绝的.又没人给钱.
过了一天,灵光一闪,3DMAX在导出DAE文件时,collada选项那里,有一个三角算法和一个单一矩阵可供选择,于是,导出单一矩阵后,可以看到Testjuzheng.dae内,bone001骨骼信息node节点那里有了的变化
<node name="Bone001" id="Bone001" sid="Bone001" type="JOINT">
<matrix sid="matrix">
0.679118 -0.734029 -0.000000 9.930389 0.000000 0.000000 1.000000 0.000000 -0.734029 -0.679118 0.000000 -8.927395 0.000000 0.000000 0.000000 1.000000
</matrix>
在看编码后的Testjuzheng.skel
这 16 个浮点数!!! 刚好对应到了 Skel文件的 Bone001下的16个浮点数, 虽然16个数排列顺序不一样, 但这是一个重大突破。 像饿了好几天的求生者发现食物一样,我立马去验证Bone002的数据……………… 很遗憾, 对不上...... 这感觉就像女神和你的告白下一秒说是愚人节一样, 但无风不起浪, 第一个数据完全相同这一点足以给我动力继续下去了。到了这一步, 不得不开始动态调试, 分析Bone002的浮点数据到了骨骼文件怎么就变了。
用OD载入编码器,搜索字符串"ibrary_visual_scenes",来到调用函数,下方紧接着就是 "node" 的字符串,从node 下方进入处理过程后,单步跟踪,定位到了骨骼节点小数字符串转换成 16 进制的过程,下面不远就是 这64个字节进行转换的位置了, 由于太简单就不上图了, 直接上关键的算法。
由于OD不支持查看XMM寄存器的数值,这里改用x64_dbg调试
这个函数,就是Bone002摇身一变成为Skel文件里数据的关键,函数一共三个参数,一个为上一个节点(Bone001)的 16个浮点数, 一个为本次节点(Bone002 dae中)的16个浮点数, 还有一个为输出地址, 那么搞定这个算法骨骼就可以通过Skel文件还原成DAE文件中的数值了, 通过跟踪理解, 尝试做了这样一张图
IDA Pro中的伪代码为:
讲真,IDA的伪代码我并看不懂。口头描述代码如下
float a[16]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}; //DAE中的排序
float b[16]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
a={1,5,9,13,2,6,10,14,3,7,11,15,4,8,12,16};
b={1,5,9,13,2,6,10,14,3,7,11,15,4,8,12,16}; //转换成编码器内存中的排序
a和b 进入函数过程的话.a为上一个节点信息, b为本次
用第二次循环举例
将 本次节点(b)信息的 第二组 2 6 10 14 传给寄存器XMM0 并进行洗牌操作后,各寄存器状态如下
XMM0 2 2 2 2
XMM1 6 6 6 6
XMM2 10 10 10 10
XMM3 14 14 14 14
xmm4-xmm7的数据为:
XMM4 13 9 5 1 //这里是 上个节点a的所有数据
XMM5 14 10 6 2
XMM6 15 11 7 3
XMM7 16 12 8 4
然后和 XMM0-XMM3 和 XMM4-XMM7 分别相乘后数据如下:
XMM0 26 18 10 2
XMM1 84 60 36 12
XMM2 150 110 70 30
XMM3 224 168 112 56
ADD 484 356 228 100 (累加操作)
这其中的一次循环 484 356 228 100 就是保存到skel中的数据了。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)