【游戏开发进阶】教你使用IL2CppDumper从Unity il2cpp的二进制文件中获取类型、方法、字段等(反编译)
文章目录
- 一、前言
- 二、下载IL2CppDumper
- 三、Unity Demo工程
- 四、IL2CPP打包
- 五、拿到libil2cpp.so与global-metadata.dat
- 六、执行Il2CppDumper.exe
- 七、查看反编译后的文件
- 1、dump.cs
- 2、il2cpp.h
- 3、script.json
- 4、stringliteral.json
- 5、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
吧。
二、下载IL2CppDumper
IL2CppDumper
是一个开源工具,在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<string> 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<string>() { "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 < 100; ++i){++test_int;Debug.Log("TestCoroutine, test_int: " + test_int);yield return new WaitForSeconds(1);}}
}
Hello.cs
脚本
using UnityEngine;public class Hello
{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: 0x39CB58public void .ctor() { }// RVA: 0x39CB60 Offset: 0x39CB60 VA: 0x39CB60public void SayHello() { }// RVA: 0x39CBF0 Offset: 0x39CBF0 VA: 0x39CBF0public void Say(string text) { }
}// Namespace:
public class Main : MonoBehaviour // TypeDefIndex: 1415
{// Fieldspublic int test_int; // 0xCprotected float test_float; // 0x10private string test_string; // 0x14public int[] test_int_array; // 0x18protected List<string> test_string_list; // 0x1Cprivate Main.TEST_ENUM test_enum; // 0x20private Hello hello; // 0x24// Methods// RVA: 0x39CC78 Offset: 0x39CC78 VA: 0x39CC78public void .ctor() { }// RVA: 0x39CD08 Offset: 0x39CD08 VA: 0x39CD08private void Awake() { }// RVA: 0x39CD0C Offset: 0x39CD0C VA: 0x39CD0Cprivate void Init() { }// RVA: 0x39CE7C Offset: 0x39CE7C VA: 0x39CE7Cprivate void Start() { }[DebuggerHiddenAttribute] // RVA: 0x348B00 Offset: 0x348B00 VA: 0x348B00// RVA: 0x39CF20 Offset: 0x39CF20 VA: 0x39CF20private 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; // r4int v2; // r5int v3; // r5int v4; // r5int v5; // r0int v6; // r0signed int result; // r0v1 = 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
相关的技术难题,也欢迎留言或私信~
【游戏开发进阶】教你使用IL2CppDumper从Unity il2cpp的二进制文件中获取类型、方法、字段等(反编译)相关推荐
- HTML5游戏开发进阶指南(亚马逊5星畅销书,教你用HTML5和JavaScript构建游戏!)
HTML5游戏开发进阶指南(亚马逊5星畅销书,教你用HTML5和JavaScript构建游戏!) [印]香卡(Shankar,A.R.) 著 谢光磊 译 ISBN 978-7-121-21226-0 ...
- 【游戏开发进阶】教你Unity通过Jenkins实现自动化打包,打包这种事情就交给策划了(保姆级教程 | 命令行打包 | 自动构建)
文章目录 一.前言 二.Jenkins简介 三.Jenkins的下载与安装 1.JDK下载与安装 2.Jenkins下载 3.Jenkins安装 4.Jenkins初始化 四.Jenkins的基本操作 ...
- HTML5游戏开发进阶指南
<HTML5游戏开发进阶指南> 基本信息 作者: (印)香卡(Shankar,A.R.) 译者: 谢光磊 出版社:电子工业出版社 ISBN:9787121212260 上架时间:2013- ...
- 世嘉MD游戏开发进阶篇【三】:向量归一化的实现及应用
向量归一化是非常有用的,游戏中经常能用到,就说大家都见过的,FC魂斗罗的敌人发射子弹就能用到了,敌人向玩家发射子弹首先要获取到向量,这个向量不能直接作为方向去用,必须要经过归一化处理才行,经过归一化处 ...
- 【SQL开发实战技巧】系列(十八):数据仓库中时间类型操作(进阶)INTERVAL、EXTRACT以及如何确定一年是否为闰年及周的计算
系列文章目录 [SQL开发实战技巧]系列(一):关于SQL不得不说的那些事 [SQL开发实战技巧]系列(二):简单单表查询 [SQL开发实战技巧]系列(三):SQL排序的那些事 [SQL开发实战技巧] ...
- 游戏开发计算机图形学杂项知识系列:OpenGL红宝书中第一个渲染程序Triangles常见问题归总
游戏开发计算机图形学杂项知识系列:OpenGL红宝书中第一个渲染程序Triangles常见问题归总 声明:未经作者允许,严禁商用,转载请标明出处和来源,谢谢 转载自:https://www.cnblo ...
- 【SQL开发实战技巧】系列(十九):数据仓库中时间类型操作(进阶)如何一个SQL打印当月或一年的日历?如何确定某月内第一个和最后—个周内某天的日期?
系列文章目录 [SQL开发实战技巧]系列(一):关于SQL不得不说的那些事 [SQL开发实战技巧]系列(二):简单单表查询 [SQL开发实战技巧]系列(三):SQL排序的那些事 [SQL开发实战技巧] ...
- 【SQL开发实战技巧】系列(二十一):数据仓库中时间类型操作(进阶)识别重叠的日期范围,按指定10分钟时间间隔汇总数据
系列文章目录 [SQL开发实战技巧]系列(一):关于SQL不得不说的那些事 [SQL开发实战技巧]系列(二):简单单表查询 [SQL开发实战技巧]系列(三):SQL排序的那些事 [SQL开发实战技巧] ...
- 【SQL开发实战技巧】系列(十六):数据仓库中时间类型操作(初级)日、月、年、时、分、秒之差及时间间隔计算
系列文章目录 [SQL开发实战技巧]系列(一):关于SQL不得不说的那些事 [SQL开发实战技巧]系列(二):简单单表查询 [SQL开发实战技巧]系列(三):SQL排序的那些事 [SQL开发实战技巧] ...
- 学习3D游戏开发进阶之路
笔者从事IT行业15年了,一直奋斗在一线编程,从普通程序员逐步成长到上市公司技术总监,目前在创业公司担任技术合伙人,主要负责公司整个项目团队的技术管理.在网上或者论坛上很多同学请教过我关于如何学习3D ...
最新文章
- 怎么看b树是几阶_看我在B站上怎么学习的
- ARP命令详解--网络命令详解二
- LeNet训练MNIST
- c语言程序二级考试题,2016年计算机二级考试C语言程序设计真题
- Java HashMap原理及内部存储结构
- boost::hana::sfinae用法的测试程序
- 平行四边形的特殊性质
- bzoj2595 [Wc2008]游览计划
- 远程开发工作具备因素有哪些?
- python3解析纯真ip数据库
- 怎样在中国消灭IE6浏览器
- 无线网检查服务器在那,无线网络服务器地址在哪里找
- EasyDarwin开源流媒体云平台支持EasyCamera摄像机、EasyCamera手机直播监控、EasyNVR等多终端接入
- 防冲撞协议原理实验报告
- 工业级手持式扫描仪3d扫描首选迪万科技抄数服务
- 计算机管理要继续请输入,Windows 10上提示UAC错误:如要继续请输入管理员密码!...
- 云视频会议已成未来发展必然趋势
- Petalinux和SDK安装
- 未来的量子计算机模型,量子计算机上量子人工生命模型
- spring 项目启动完成执行_凝聚合力形成攻坚之势——21天顺利完成启动炉烟道改造施工项目...