【游戏开发进阶】教你使用IL2CppDumper从Unity il2cpp的二进制文件中获取类型、方法、字段等(反编译) |
您所在的位置:网站首页 › gg修改器在哪个目录文件夹 › 【游戏开发进阶】教你使用IL2CppDumper从Unity il2cpp的二进制文件中获取类型、方法、字段等(反编译) |
文章目录
一、前言二、下载IL2CppDumper三、Unity Demo工程四、IL2CPP打包五、拿到libil2cpp.so与global-metadata.dat六、执行Il2CppDumper.exe七、查看反编译后的文件1、dump.cs2、il2cpp.h3、script.json4、stringliteral.json5、DummyDll/Assembly-CSharp.dll
八、拓展:使用IDA逆向il2cpp.so,得到函数体内部逻辑九、拓展补充:IL2CppDumper原理十、结束语
一、前言
点关注不迷路,持续输出Unity干货文章。 嗨,大家好,我是新发。 Unity使用Mono方式打出来的apk,我们可以直接从包内拿到Assembly-CSharp.dll,如果开发者没有对Assembly-CSharp.dll进行加密处理,那么我们可以很方便地使用ILSpy.exe对其进行反编译。 如果使用IL2CPP方式出包,则没有Assembly-CSharp.dll,不过,有一个IL2CppDumper工具,通过它,我们可以逆向得到Assembly-CSharp.dll,下面就教大家如何使用这个IL2CppDumper吧。 二、下载IL2CppDumperIL2CppDumper是一个开源工具,在GitHub上可以直接下载到。 地址:https://github.com/Perfare/Il2CppDumper 点击右边的Releases下面的版本进行下载即可。 下载后解压,文件如下,可以看到Il2CppDumper.exe。 三、Unity Demo工程为了进行测试,我们创建个Unity Demo工程,创建两个脚本,如下: Main.cs脚本: using System.Collections; using System.Collections.Generic; using UnityEngine; public class Main : MonoBehaviour { enum TEST_ENUM { E1, E2, E3 } public int test_int = 0; protected float test_float = 0.0f; private string test_string = "hello world"; public int[] test_int_array; protected List test_string_list; private TEST_ENUM test_enum; private Hello hello = new Hello(); private void Awake() { Init(); } private void Init() { test_int = 1; test_float = 2.0f; test_int_array = new int[3] { 4, 5, 6 }; test_string_list = new List() { "il2cpp", "test" }; test_enum = TEST_ENUM.E2; } private void Start() { hello.SayHello(); test_string = "Hi, I am linxinfa"; hello.Say(test_string); StartCoroutine(TestCoroutine()); } private IEnumerator TestCoroutine() { for (int i = 0; i public void SayHello() { Debug.Log("Hello, IL2CPP"); } public void Say(string text) { Debug.Log(text); } } 四、IL2CPP打包使用IL2CPP方式,打出apk包。 打出的apk如下。 五、拿到libil2cpp.so与global-metadata.dat奖test.apk改为test.zip,解压进入目录中,拿到libil2cpp.so与global-metadata.dat。 \lib\armeabi-v7a\libil2cpp.so \assets\bin\Data\Managed\Metadata\global-metadata.dat 六、执行Il2CppDumper.exe回到Il2CppDumper.exe所在的目录,创建input目录和output目录。 将libil2cpp.so与global-metadata.dat拷贝到input目录中。 创建一个il2cpp_decompilation.bat文件。 il2cpp_decompilation.bat文件内容如下: ..\Il2CppDumper.exe libil2cpp.so global-metadata.dat ..\output双击执行il2cpp_decompilation.bat,如下: 进入output目录,可以看到生成了如下文件: 七、查看反编译后的文件 1、dump.cs这个文件会把C#的dll代码的类、方法、字段列出来。比如我们写的Main.cs和Hello.cs。 // Namespace: public class Hello // TypeDefIndex: 1414 { // Methods // RVA: 0x39CB58 Offset: 0x39CB58 VA: 0x39CB58 public void .ctor() { } // RVA: 0x39CB60 Offset: 0x39CB60 VA: 0x39CB60 public void SayHello() { } // RVA: 0x39CBF0 Offset: 0x39CBF0 VA: 0x39CBF0 public void Say(string text) { } } // Namespace: public class Main : MonoBehaviour // TypeDefIndex: 1415 { // Fields public int test_int; // 0xC protected float test_float; // 0x10 private string test_string; // 0x14 public int[] test_int_array; // 0x18 protected List test_string_list; // 0x1C private Main.TEST_ENUM test_enum; // 0x20 private Hello hello; // 0x24 // Methods // RVA: 0x39CC78 Offset: 0x39CC78 VA: 0x39CC78 public void .ctor() { } // RVA: 0x39CD08 Offset: 0x39CD08 VA: 0x39CD08 private void Awake() { } // RVA: 0x39CD0C Offset: 0x39CD0C VA: 0x39CD0C private void Init() { } // RVA: 0x39CE7C Offset: 0x39CE7C VA: 0x39CE7C private void Start() { } [DebuggerHiddenAttribute] // RVA: 0x348B00 Offset: 0x348B00 VA: 0x348B00 // RVA: 0x39CF20 Offset: 0x39CF20 VA: 0x39CF20 private IEnumerator TestCoroutine() { } } 2、il2cpp.h生成的cpp的头文件,从头文件里我们也可以看到相关的数据结构。 struct Hello_Fields { }; struct Main_Fields : UnityEngine_MonoBehaviour_Fields { int32_t test_int; float test_float; struct System_String_o* test_string; struct System_Int32_array* test_int_array; struct System_Collections_Generic_List_string__o* test_string_list; int32_t test_enum; struct Hello_o* hello; }; 3、script.json以json格式显示类的方法信息: { "Address": 3787608, "Name": "Hello$$.ctor", "Signature": "void Hello___ctor (Hello_o* __this, const MethodInfo* method);" }, { "Address": 3787616, "Name": "Hello$$SayHello", "Signature": "void Hello__SayHello (Hello_o* __this, const MethodInfo* method);" }, { "Address": 3787760, "Name": "Hello$$Say", "Signature": "void Hello__Say (Hello_o* __this, System_String_o* text, const MethodInfo* method);" }, { "Address": 3787896, "Name": "Main$$.ctor", "Signature": "void Main___ctor (Main_o* __this, const MethodInfo* method);" }, { "Address": 3788040, "Name": "Main$$Awake", "Signature": "void Main__Awake (Main_o* __this, const MethodInfo* method);" }, { "Address": 3788044, "Name": "Main$$Init", "Signature": "void Main__Init (Main_o* __this, const MethodInfo* method);" }, { "Address": 3788412, "Name": "Main$$Start", "Signature": "void Main__Start (Main_o* __this, const MethodInfo* method);" }, { "Address": 3788576, "Name": "Main$$TestCoroutine", "Signature": "System_Collections_IEnumerator_o* Main__TestCoroutine (Main_o* __this, const MethodInfo* method);" }, 4、stringliteral.json以json的格式显示所有的字符串信息: { "value": "Hello, IL2CPP", "address": "0x52A0AC" }, { "value": "hello world", "address": "0x52A0B0" }, { "value": "il2cpp", "address": "0x52A0B4" }, { "value": "test", "address": "0x52A0B8" }, { "value": "Hi, I am linxinfa", "address": "0x52A0BC" }, { "value": "TestCoroutine, test_int: ", "address": "0x52A0C0" }, 5、DummyDll/Assembly-CSharp.dll进入DummyDll目录,可以看到很多dll,其中就有Assembly-CSharp.dll,我们可以使用ILSpy.exe对其进行反编译。 可以看到,与刚刚的dump.cs文件内容是一致的。 八、拓展:使用IDA逆向il2cpp.so,得到函数体内部逻辑从上面IL2CppDumper我们可以发现,逆向得到的函数体都是空的,看不了内部逻辑。有没有办法可以逆向得到函数体内部逻辑呢? 有,需要借助另一个反编译神器:IDA,全称交互式反汇编器(Interactive Disassembler)。 关于IDA的使用,我之前写过一篇文章:《新发的日常学习——IDA的入门使用,反编译so/dll文件(反编译神器)》,感兴趣的同学可以看看。 IDA下载链接:https://pan.baidu.com/s/1NATDYzomBYiwrwdH6qBjUA 提取码:2dmy IDA官网:https://www.hex-rays.com/ IDA有32位的和64位两个exe,要根据你反编译的文件运行对应的exe。 Unity使用IL2CPP打包时,选择的CPU架构可以选择ARMv7和ARM64,由于我上面拿的libil2cpp.so是armeabi-v7a的,也就是32位的,所以我运行的32位的IDA。 在IDA中载入libil2cpp.so,耐心等待它的解析。 假设我们要查看Main脚本的Awake方法的函数体,我们从刚刚的dump.cs可以看到对应的函数地址: // RVA: 0x39CD08 Offset: 0x39CD08 VA: 0x39CD08 private void Awake() { }地址是:0x39CD08,我们在IDA中按G键,输入地址:0x39CD08,点击OK, 可以看到跳过来是这样的,这里对应的是汇编代码,这个sub_39CD08就是我们的Awake函数。 源代码中,Awake函数中调用了Init函数: private void Awake() { Init(); }而在dump.cs中我们可以看到Init的函数地址是0x39CD0C: // RVA: 0x39CD0C Offset: 0x39CD0C VA: 0x39CD0C private void Init() { }我们再回过头看IDA,我们就可以对应起来了。 我们可以直接按F5将汇编转成对应的c代码,这个sub_39CD08就是Awake函数,可以看到,这里代码被优化了,Awake函数内部实际上就是Init中的内容,不过看起来也是挺晦涩难懂的,感兴趣的同学可以深入研究研究。 signed int __fastcall sub_39CD08(_DWORD *a1) { _DWORD *v1; // r4 int v2; // r5 int v3; // r5 int v4; // r5 int v5; // r0 int v6; // r0 signed int result; // r0 v1 = a1; if ( !byte_52A52B ) { sub_39688C(&elf_hash_chain[89]); byte_52A52B = 1; } v1[3] = 1; v1[4] = 0x40000000; v2 = dword_524724; sub_38CFC4(dword_524724); v3 = il2cpp_array_new_specific_0(v2, 3); sub_773C4(0, v3, dword_5277E8, 0); v1[6] = v3; v4 = sub_3CCA3C(dword_524EF4); v5 = sub_303278(v4, dword_5262D8); if ( v4 ) { sub_304144(v4, dword_52A0B4, dword_5262E0); } else { ((void (__fastcall *)(int))loc_3BF764)(v5); v6 = sub_304144(0, dword_52A0B4, dword_5262E0); ((void (__fastcall *)(int))loc_3BF764)(v6); } sub_304144(v4, dword_52A0B8, dword_5262E0); result = 1; v1[7] = v4; v1[8] = 1; return result; } 九、拓展补充:IL2CppDumper原理il2cpp将游戏 C#代码转换为C++代码,然后编译为各平台Native代码。 虽然游戏逻辑是以Native代码运行, 但依然要实现 C#某些语言特性(如GC、反射),il2cpp将所有的 C#中的类名、方法名、属性名、字符串等地址信息记录在 global-metadata.dat文件。il2cpp启动时会从这个文件读取所需要的类名、方法名、属性名、字符串等地址信息。 我们可以在Unity安装目录的Editor目录中找到il2cpp虚拟机的源码:vm目录。 在里面的GlobalMetadata.cpp中,就可以看到加载global-metadata.dat文件的逻辑。 注:如果开发者对global-metadata.dat文件做了加密,那么在GlobalMetadata.cpp中加载global-metadata.dat前需要实现对应的解密逻辑。 IL2CppDumper正是利用global-metadata.dat文件进行逆向的。 global-metadata.dat文件是一个二进制文件,是按照一定的数据结构进行写入的。 我们可以下载个010Editor查看global-metadata.dat文件。 010Editor下载地址:https://www.sweetscape.com/download/010editor/ 运行010Editor,将global-metadata.dat文件拖入010Editor中,我们可以看到前四个字节是AF 1B B1 FA。 AF 1B B1 FA是global-metadata.dat文件的标识,如果你去看IL2CppDumper源码,你就会看到这个标识的判断: 注意,IL2CppDumper是使用C#写的,C#在windows平台上是小端字节序,即数据的高字节保存在内存的高地址中,我们上面使用010Editor看二进制文件的时候,从左到右地址是升高的,所以上面的AF 1B B1 FA对应到C#代码中就是0xFAB11BAF,不要搞反了哦。 我们这样看global-metadata.dat文件是看不出啥东西的,我们需要告诉010Editor如何解析这个global-metadata.dat文件。 这个时候,就需要下载UnityMetadata.bt模板文件了。 UnityMetadata.bt模板文件下载:https://www.sweetscape.com/010editor/repository/files/UnityMetadata.bt 下载完之后,在010Editor中点击菜单Templates / Open Template...,选择刚刚下载的UnityMetadata.bt。 然后点击这个三角形按钮运行模板。 运行成功,可以看到global-metadata.dat解析出数据来了。 不过我们光从global-metadata.dat是看不出具体的字符串的,需要依赖libil2cpp.so进行寻址才行。 我们可以在IL2CppDumper源码中看到执行Dump方法需要传入metadata和il2Cpp。 因为libil2cpp.so是UNIX派系的二进制文件,所以它的前4个字节是7F 45 4C 46,对应到C#小端的十六进制表达就是0x464c457f。 我们可以在Init方法中看到读取libil2cpp.so的逻辑, 再往里走,就是Elf格式文件的解析了,这里就不展开了。执行完Dump即可生成逆向文件了。 感兴趣的同学可以继续深入研究下。 十、结束语完毕。 喜欢Unity的同学,不要忘记点击关注,如果有什么Unity相关的技术难题,也欢迎留言或私信~ |
今日新闻 |
点击排行 |
|
推荐新闻 |
图片新闻 |
|
专题文章 |
CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭 |