学习:
- 什么是il2cpp,发挥了什么作用
- il2cpp生成的C++代码长什么样子,并通过一个简单的函数看看和原生C#相比 il ,C++ 的二进制反汇编。
- 如何配置环境生成C++源码和C#对应的IL2CPP二进制文件,方便你用来对比分析
- 没有unity ,在命令行使用il2cpp
il2cpp 简介
在2015年引入的部署方案,显著提升unity的游戏性能。
标准的Unity游戏由.NET程序集组成,这些程序集交由各平台的解释器(CLR)执行。il2cpp 就需要先获取这些程序集 ,解析il ,生成对应的C++代码,最后转换成机器码。
想要深入il2cpp可以看看下面的两篇文章:
https://blogs.unity3d.com/2015/05/06/an-introduction-to-ilcpp-internals/
https://jacksondunstan.com/articles/tag/il2cpp
il2cpp改变了传统Unity的逆向过程,我们从.net 程序集分析深入到更底层的反汇编。为了让分析更容易, 需要对IL2CPP有较深的理解,也是这个系列的目的。
追踪路径: Hello World
以helloworld为例
using System;
namespace HelloWorld
{
class Program
{
static void Main(string[] args) {
var a = 1;
var b = 2;
Console.WriteLine("Hello World: {0}", a + b);
}
}
}
你可能觉得IL2CPP 会将代码转换成这样
#include <stdio.h>
int main(int argc, char **argv) {
int a = 1;
int b = 2;
printf("Hello world: %d\r\n", a + b);
}
实际
// System.Void HelloWorld.Program::Main(System.String[])
IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR void Program_Main_m7A2CC8035362C204637A882EDBDD0999B3D31776 (StringU5BU5D_t933FB07893230EA91C40FF900D5400665E87B14E* ___args0, const RuntimeMethod* method)
{
static bool s_Il2CppMethodInitialized;
if (!s_Il2CppMethodInitialized)
{
il2cpp_codegen_initialize_method (Program_Main_m7A2CC8035362C204637A882EDBDD0999B3D31776_MetadataUsageId);
s_Il2CppMethodInitialized = true;
}
int32_t V_0 = 0;
int32_t V_1 = 0;
{
V_0 = 2;
int32_t L_0 = V_0;
V_1 = ((int32_t)il2cpp_codegen_add((int32_t)1, (int32_t)L_0));
int32_t L_1 = V_1;
int32_t L_2 = L_1;
RuntimeObject * L_3 = Box(Int32_t585191389E07734F19F3156FF88FB3EF4800D102_il2cpp_TypeInfo_var, &L_2);
IL2CPP_RUNTIME_CLASS_INIT(Console_t5C8E87BA271B0DECA837A3BF9093AC3560DB3D5D_il2cpp_TypeInfo_var);
Console_WriteLine_m22F0C6199F705AB340B551EA46D3DB63EE4C6C56(_stringLiteral331919585E3D6FC59F6389F88AE91D15E4D22DD4, L_3, /*hidden argument*/NULL);
return;
}
}
IL@CPP 是以程序集的IL代码作为输入,而不是C#源码 , 所以第一步:
ldc.i4.1
stloc.0
ldc.i4.2
stloc.1
ldstr "Hello World: {0}"
ldloc.0
ldloc.1
add
box System.Int32
call System.Void System.Console::WriteLine(System.String,System.Object)
ret
IL是基于栈的伪代码 , IL2CPP 会按行扫描 IL 字节码 并将它转换成不是基于栈的C++代码,所以我们会在C++代码钟看到冗余的变量和赋值语句。
il2cpp_codegen_add 处理表达式”a+b” 中 ”+“的操作符重载 ,IL2CPP_RUNTIME_CLASS_INIT 确保在被赋值前已经被它的静态初始化函数创建。例如在Console.WriteLine。函数头部,对于每个静态方法都有布尔值的判断,来确保正常的初始化,但是为什么要这么做呢?
很明显,如果你在逆向游戏,你没有Untiy项目看不到产生的C++代码,但是你如果了解从C#转换成C++代码的流程,能对你游戏分析过程产生帮助。
先看x64 C++版本的反汇编代码
; int __cdecl main(int argc, const char **argv, const char **envp)
main proc near
sub rsp, 28h
mov edx, 3
lea rcx, _Format ; "Hello world: %d\n"
call printf
xor eax, eax
add rsp, 28h
retn
main endp
IL2CPP版的长这样
; void __fastcall Program_Main_m2325437134(Il2CppObject *__this, StringU5BU5D_t1642385972 *___args0, MethodInfo *method)
Program_Main_m2325437134 proc near
push rbx
sub rsp, 20h
cmp cs:s_Il2CppMethodInitialized_8016, 0
jnz short loc_14038BFF1
mov ecx, cs:?Program_Main_m2325437134_MetadataUsageId@@3IB
call ?InitializeMethodMetadata@MetadataCache@vm@il2cpp@@SAXI@Z
mov cs:s_Il2CppMethodInitialized_8016, 1
loc_14038BFF1:
mov rcx, cs:?Int32_t2071877448_il2cpp_TypeInfo_var@@3PEAUIl2CppClass@@EA
lea rdx, [rsp+48h]
mov dword ptr [rsp+48h], 3
call ?Box@Object@vm@il2cpp@@SAPEAUIl2CppObject@@PEAUIl2CppClass@@PEAX@Z
mov rcx, cs:?Console_t2311202731_il2cpp_TypeInfo_var@@3PEAUIl2CppClass@@EA
mov rbx, rax
test byte ptr [rcx+10Ah], 1
jz short loc_14038C02B
cmp dword ptr [rcx+0BCh], 0
jnz short loc_14038C02B
call ?ClassInit@Runtime@vm@il2cpp@@SAXPEAUIl2CppClass@@@Z
loc_14038C02B:
mov rdx, cs:?_stringLiteral3443654334@@3PEAUString_t2029220233@@EA
xor r9d, r9d
mov r8, rbx
xor ecx, ecx
call Console_WriteLine_m3776981455
add rsp, 20h
pop rbx
retn
Program_Main_m2325437134 endp
可以看出要理解IL2CPP一个方法的反汇编,需要明白IL2CPP提供关键数据结构和内部API,我们用一个简单的例子来挖掘il2cpp的流程。
用il2cpp来生成代码
- 创建空的Unity项目,设置scripting backend 为il2cpp,添加代码,build。
- 直接命令行使用il2cpp.exe 的命令行工具。
Il2CppInspector 提供了精简的方式来生成代码,后面会讲。(我就不讲了)
Unity使用il2cpp生成代码
- 用UnityHub创建项目
2. File -> Build Setting 选择PC , MAC ,linux IOS 或者android的任何一种
3. Player Setting Player -> Other Setting -> Configuration , 将Scripting Backend 修改为il2cpp , 确保C++ compiler 设置成release。
4. 把你要用的源代码放到Project标签下Assets文件夹。
5. File -> Build Setting ,点击Build 选择存放的文件夹
可以注意的生成文件:
Mono编译:
Test_BackUpThisFolder_ButDontShipItWithYourGame\Managed\Assembly-CSharp.dll
.
IL2CPP生成的C++代码
Test_BackUpThisFolder_ButDontShipItWithYourGame\il2cppOutput
.
C++编译的二进制文件: GameAssembly.dll
il2cpp产生的metadata文件Test_Data\il2cpp_data\Metadata\global-metadata.dat
.
(Unity 2019.3.1f1版本的输出文件)
il2cpp命令行
il2cpp 是一套独立的工具链 , 我们可以在Unity安装路径的Editor\Data下看到:
il2cpp
Mono
or MonoBleedingEdge
(depending on version; new versions of Unity use the latter)PlaybackEngines\AndroidPlayer
(if building for Android)PlaybackEngines\windowsstandalonesupport
(if building for Win32; not needed for UWP)
如果平台是android 那还需要下载android NDK。
复制这些目录到新的目录,这就是你的IL2CPP工具链了 , 然后编写Hello World 实例就有一个测试用的编译流程。
il2cpp.exe 有不同的版本,和很多的参数,不同的参数会产生及其不同结果,所有具体游戏的反汇编取决于工程师使用的编译选项,
--convert-to-cpp
converts the input assemblies to C++--compile-cpp
compiles the C++ to executable machine code--libil2cpp-static
bundles libil2cpp in with the executable. You should always specify this option as it is how software is shipped--platform
is the target build platform, eg.WindowsDesktop
,Android
etc.--architecture
is the target architecture, eg.x86
,x64
,ARMv7
--configuration
is the build configuration to use. Normally you will want to useRelease
to produce code most similar to that shipped with games--dotnetprofile="unityaot"
sets the .NET profile (later versions of IL2CPP require this to avoid errors)--forcerebuild
will force the C++ to be re-generated even if it already exists--assembly
is a comma-separated list of assemblies to compile
OR--directory
is a comma-separated list of directories containing assemblies to compile--outputpath
specifies where to save the compiled executable binary--generatedcppdir
specifies where to save the generated C++ code--verbose
enables verbose output
编译Android项目时 , 增加
--additional-include-directories=<path to your AndroidPlayer folder>/Tools/bdwgc/include
--additional-include-directories=<path to your AndroidPlayer folder>/Tools/libil2cpp/include
--tool-chain-path=<path to the android NDK>
实例
il2cpp.exe ^
--assembly=HelloWorld.exe ^
--outputpath=HelloIl2Cpp.exe ^
--libil2cpp-static ^
--convert-to-cpp ^
--compile-cpp ^
--generatedcppdir=Cpp ^
--verbose
要编译windows32位
il2cpp.exe ^
--convert-to-cpp ^
--emit-null-checks ^
--enable-array-bounds-check ^
--dotnetprofile="unityaot" ^
--compile-cpp ^
--libil2cpp-static ^
--platform="WindowsDesktop" ^
--architecture="x86" ^
--configuration="Release" ^
--outputpath=Output ^
--map-file-parser="il2cpp\MapFileParser\MapFileParser.exe" ^
--directory=InputAssemblies ^
--generatedcppdir=Cpp ^
--verbose ^
编译android
il2cpp.exe ^
--convert-to-cpp ^
--emit-null-checks ^
--enable-array-bounds-check ^
--dotnetprofile="unityaot" ^
--compile-cpp ^
--libil2cpp-static ^
--platform="Android" ^
--architecture="ARMv7" ^
--configuration="Release" ^
--outputpath=Output ^
--additional-include-directories="PlaybackEngines/AndroidPlayer/Tools\bdwgc/include" ^
--additional-include-directories="PlaybackEngines/AndroidPlayer/Tools\libil2cpp/include" ^
--tool-chain-path="PlaybackEngines/AndroidPlayer/NDK" ^
--map-file-parser="il2cpp\MapFileParser\MapFileParser.exe" ^
--directory=InputAssemblies ^
--generatedcppdir=Cpp ^
--verbose ^
IL2CPP Reverse Engineering Part 1: Hello World and the IL2CPP Toolchain
原文地址
0 条评论