学习:

  • 什么是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来生成代码

  1. 创建空的Unity项目,设置scripting backend 为il2cpp,添加代码,build。
  2. 直接命令行使用il2cpp.exe 的命令行工具。

Il2CppInspector 提供了精简的方式来生成代码,后面会讲。(我就不讲了)

Unity使用il2cpp生成代码

  1. 用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. WindowsDesktopAndroid etc.
  • --architecture is the target architecture, eg. x86x64ARMv7
  • --configuration is the build configuration to use. Normally you will want to use Release 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

原文地址


pareto

未来什么方向不管,先做自己喜欢做的事情。

0 条评论

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注