原文地址:https://www.win32k.cn/d/123-unityu3dni-xiang-yin-qing-ji-chu
最近看了一些号称u3d的逆向视频,标题什么u3d什么引擎结构高大上,结果开始进去用ce一顿乱搜,而我认为的依靠引擎结构逆向应该绝不使用这种不靠谱的方式,应该一切都是确定且理所当然的….只好自己去其他地方逛逛…略有收获送给大家。
首先u3d分为两个部分,底层部分是用c++写的引擎实现(不开源)用来实现引擎的基础功能,比如游戏对象的管理和物理效果;第二部分是使用脚本语言编写的逻辑层(一般使用C#),游戏逻辑由此实现,u3d游戏开发也是在这一层写代码,而因为C#跨平台的策略分为两种Mono和li2cpp,所以u3d上层也分为Mono和li2cpp,不过都是大同小异通过工具可轻松解决。
由于游戏逻辑使用C#实现,所以破解十分简单,使用开源的dnSpy-net反编译工具可以轻松阅读游戏源码,使用CE的Mono功能获取dump从而获取偏移,无需任何搜索和查看汇编,一切轻松自在,所以这方面资料较多,我就不浪费口水了。
而底层引擎实现由于是C++有不开源(只有商业合作的公司可以获得源码),因此资料稀少,但是它又实际管理着所有的游戏对象,因此无法避开它。
因此许多文章和帖子都是草草告你某某模块的某某偏移是对象管理器,然后给出一大堆偏移,告诉你这是链表,去遍历它就可以得到所有游戏对象…. 所以今天我们就来好好说说。
其实我们想得到的就一样东西那就是 “GameObjectManager”,望文生义它是U3d引擎中管理所有游戏对象的结构体,得到它就得到了一切!它的结构其实比较简单,下面主要讲讲如何依靠IDA逆向..不..应该是获取它的结构。
通常你会在u3d程序下发现一个名叫“UnityPlayer.dll”的共享库,这就是u3d引擎的本体,虽然它不开源但是官方提供了符号下载,基本也差不多了,在Github上下载一个 Releases · just-ero/Unity-PDB-Downloader (github.com),将你的UnityPlayer.dll拖拽到上面就可以下载到PDB文件。
使用IDA加载UnityPlayer.dll并加载PDB符号文件,等个十几分钟让IDA分析完毕,然后修改image base 为0,方便我们获取偏移。
然后搜索 GameObject_CUSTOM_FindGameObjectsWithTag 这个函数,或者搜索 FindGameObjectsWithTag 这个字符串按x可以看到引擎注册“FindGameObjectsWithTag”功能回调函数为GameObject_CUSTOM_FindGameObjectsWithTag,然后点进去可以清楚的看到里面使用了 “GameObjectManager = GetGameObjectManager();” 点进去->

轻松获取GameObjectManager这个全局变量的偏移,再通过Local types窗口搜索“GameObjectManager”点进去,轻松获取GameObjectManager结构成员和偏移,以及GameObject的结构和偏移:

图片中IDA的结构体有点不直观,在后面我会给出遍历GameObject的代码参考,我们可以看到GameObject+0x60的地方是对象名称,可以通过这里来区分对象,+0x30的位置是组件列表,GameObject并没有实际功能,功能都是组件实现的,因此这个列表里面可能包含C#写的组件;也有可能包含u3d原生组件比如transform(每个GameObject默认第一个组件,包含GameObject的位置信息)或者相机组件,目前从IDA来看这些原生组件都是C++编写的,由于不太懂C#目前这块了解得比较模糊,不过显然所有组件都有共同的父类Unity::Component,在一些论坛经常可以看到 GameObject+0x30]+0xXX] 就是在取组件。
猜测C++的结构要暴露给C#使用,C#应该要提供对应的封装,比如C#中也有Transform类但是你无法在dnSpy-net中看到它的具体实现,但是在IDA中可以明确看到它的结构。并且应该有互相绑定的关系,也就是说C#对象通过某种方式可以访问到对应的C++对象,C++对象也可以访问到对应的C#对象,目前感觉它们的关系是 C++对象 + 0x28] = C# 对象,C#对象 + 0x10] = C++对象。
而GameObjectManager其中每个链表都是简单的双向循环链表,+0,+8是上下节点,+0x10是保存的GameObject指针,其中m_ActiveNodes保存了所有活动对象感觉是 有坐标在游戏里面有实体的东西,比如人物、箱子之类的,而m_TaggedNodes则可能存在游戏相机之类的对象,而 m_MainCameraTaggedNodes 在过去并不存在,现在看名字貌似单独管理游戏相机,大家可以自行遍历。
一般来说可以通过遍历所有对象来获取游戏世界对象“GameWorld”, 然后获取它的组件 本地游戏世界“LoadGameWorld”这个类一般是游戏开发者使用C#编写的,里面还会有一些唾手可得的人物列表、物品列表 具体视程序而定,本文章仅点明基础和指明方向,具体细节读者可通过IDA和dnSpy自行查看想逆向的程序。
遍历代码:
struct UString
{
char Buffer[256];
};
class GameObjectManager
{
public:
char pad_0x0000[0x8]; //0x0000
uintptr_t m_pLastTaggedObject; //0x0000
uintptr_t m_pFirstTaggedObject; //0x0008
char pad_0x0018[0x8]; //0x0018
uintptr_t m_pLastActiveObject; //0x0010
uintptr_t m_pFirstActiveObject; //0x0018
}; //Size=0x0020
uintptr_t GetObjectByName(GameObjectManager ObjManager, const char* Name)
{
uintptr_t CurrentObject = ObjManager.m_pFirstActiveObject;
while (CurrentObject && (CurrentObject != ObjManager.m_pLastActiveObject))
{
if (CurrentObject == 0)
continue;
uintptr_t Object = Driver.Read<uintptr_t>(CurrentObject + 0x10);
if (Object == 0)
continue;
uintptr_t NamePtr = Driver.Read<uintptr_t>(Object + 0x60);
if (NamePtr == 0)
continue;
if (strcmp(Name, Driver.Read<UString>(NamePtr).Buffer) == 0)
return Object;
CurrentObject = Driver.Read<uintptr_t>(CurrentObject + 0x8);
}
return 0;
}
Comments