Unity IL2CPP 游戏分析入门

这篇具有很好参考价值的文章主要介绍了Unity IL2CPP 游戏分析入门。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、目标

很多时候App加密本身并不难,难得是他用了一套新玩意,天生自带加密光环。例如PC时代的VB,直接ida的话,汇编代码能把你看懵。

但是要是搞明白了他的玩法,VB Decompiler一上,那妥妥的就是源码。

Unity 和 Flutter 也是如此。

最近迷上了一个小游戏 Dream Blast,今天就拿他解剖吧。

com.rovio.dream

二、步骤

侦测敌情

从apk包里面发现libil2cpp.so,就足以证明是Unity写的游戏了。

在Android下Unity有两种玩法,一种是Mono方式打包,我们可以从包内拿到Assembly-CSharp.dll,如果开发者没有对Assembly-CSharp.dll进行加密处理,那么我们可以很方便地使用ILSpy.exe对其进行反编译。这样看到的就是妥妥的C#源码了。

由于总所周知的原因,这种玩法肯定会被公司开除的。现在工作这么难找,所以大家都采取第二种玩法了,使用IL2CPP方式打包,就没有Assembly-CSharp.dll。这样就不会让人轻易攻破了。

这时候就需要召唤出IL2CPP界的Decompiler了。

Il2CppDumper

github.com/Perfare/Il2…

Il2CppDumper 通过 assets/bin/Data/Managed/Metadata/global-metadata.dat 字符串文件 和 lib/armeabi-v7a/libil2cpp.so 游戏二进制文件来还原C#写的代码逻辑。

目前只有编译好的windows可执行文件,所以目前只能在win下使用。(本例演示的是Arm32)

1、先把global-metadata.dat 和 libil2cpp.so 这两个文件拷贝到同一个目录。

2、运行 Il2CppDumper-x86.exe,在弹出的文件选择框里面,先选择 libil2cpp.so,然后再选择 global-metadata.dat。

Initializing metadata...
Metadata Version: 27
Initializing il2cpp file...
Applying relocations...
WARNING: find JNI_OnLoad
ERROR: This file may be protected.
Il2Cpp Version: 27
Searching...
Change il2cpp version to: 27.1
CodeRegistration : 205f9c8
MetadataRegistration : 205ff3c
Dumping...
Done!
Generate struct...
Done!
Generate dummy dll...
Done!
Press any key to exit... 

这就算反编译成功了。

一共会生成 DummyDll 目录, script.json,stringliteral.json,dump.cs,il2cpp.h 等文件。

script.json和stringliteral.json是辅助ida 和ghidra 分析的,可以用 ida.py 这个脚本导入到ida里面去。

这会我们只关心 dump.cs。

存盘文件

为了 好好 玩一个游戏,除了改内存,还一个重要的方案就是改配置文件甚至改存盘文件了。

遥想当年帝国时代非得搞个200的人口上限,直接hook一下,把200改成2000他不香吗? (电脑拖崩溃了)

细心 分析了一下,这个游戏的存盘文件在

/sdcard/Android/data/com.rovio.dream/files/usesr/XXX-XXX-XXX/prefs.json

改它,改它,可是它加密了

分析

这时候显示出 dump.cs 的用处了,这可是活地图呀。

在里面搜一下 “prefs.json”

[CreateAssetMenuAttribute] // RVA: 0x3979B8 Offset: 0x3979B8 VA: 0x3979B8
public class UserPrefs : UserPrefsBase, IInitializable, IInitializableInit // TypeDefIndex: 7278
{// Fieldsprivate const string EK = "8CSstq6cz1Gp9YSQpr2l";private const string PrefsFileName = "prefs.json"; .... // RVA: 0xAAE690 Offset: 0xAAE690 VA: 0xAAE690 Slot: 42public void Init() { }.... 

从这里得到两个有用的信息,一个是存盘文件在UserPrefs类里面处理,再一个EK可能就是密钥或者密钥的一部分。

可以上ida了,打开libil2cpp.so细嚼慢咽一下。

首先运行 Il2CppDumper-v6\ida_py3.py (低版本的ida请跑ida.py)

然后 在弹出的文件选择框里面 ,选择刚才反编译出来的script.json,最后再跑一次ida_py3.py 把stringliteral.json 也加进来。

万事俱备了,我们去分析一下 UserPrefs_Init() ,地图告诉我们它在 0xAAE690,

ida里面去到 0xAAE690, 然后Create Function, 再F5以下,代码就出来了。

代码看上去还是有点懵,它似乎 System_Guid__NewGuid(v47, 0); 生成了个guid,然后再加上了EK

v43 = System_String__Concat_23810904(*(_DWORD *)(a1 + 28), StringLiteral_1313, 0); 

StringLiteral_1313就是 EK。

不过好消息是 最后 它要初始化一个 CryptoUtility___ctor

int __fastcall CryptoUtility___ctor(int a1)
{int v2; // r6_DWORD *UTF8; // r0if ( !byte_2173DF8 ){sub_48CE2C(&System_Security_Cryptography_AesManaged_TypeInfo);sub_48CE2C(&System_Security_Cryptography_Rfc2898DeriveBytes_TypeInfo);sub_48CE2C(&StringLiteral_1149);byte_2173DF8 = 1;}v2 = sub_48CF00(System_Security_Cryptography_AesManaged_TypeInfo);System_Security_Cryptography_AesManaged___ctor(v2, 0);*(_DWORD *)(a1 + 16) = v2;System_Object___ctor(a1, 0);UTF8 = (_DWORD *)System_Text_Encoding__get_UTF8(0);if ( !UTF8 )sub_48CF08();return sub_9DB34C(*UTF8, &StringLiteral_1149, *(_DWORD *)(*UTF8 + 344), *(_DWORD *)(*UTF8 + 340));
} 

很明显,算法是 AES, 那么key是啥呢? aes还有cbc和ecb,又应该是哪一个呢?

Rfc2898DeriveBytes

幸亏咱还是懂点C#的,一个优秀的C#程序员,看到AesManaged和Rfc2898DeriveBytes,就知道套路了。

Rfc2898DeriveBytes的入参是一个password和salt,然后生成一组key和iv,后面就是aes做AES-128-CBC了。

目标很明确了,搞到pwd和salt。

ida双击进到 sub_9DB34C

void __fastcall sub_9DB34C(int a1,_DWORD *a2,int a3,int (__fastcall *a4)(int, _DWORD),int a5,int a6,int a7,int a8,int a9,int a10)
{int v10; // r4int v11; // r5int v12; // r6int v13; // r7int v14; // r6int v15; // r0v13 = a4(v12, *a2);v14 = sub_48CF00(System_Security_Cryptography_Rfc2898DeriveBytes_TypeInfo);v15 = System_Security_Cryptography_Rfc2898DeriveBytes___ctor(v14, v11, v13, 0);if ( !v14 )sub_48CF08(v15);... 

真相只有一个,hook 这个 System_Security_Cryptography_Rfc2898DeriveBytes___ctor 就可以拿到 pwd和salt了。 a2是pwd,a3是 salt。

Tip:

github.com/microsoft/r…

int __fastcall System_Security_Cryptography_Rfc2898DeriveBytes___ctor_17396484(int a1, int a2, int a3, int a4)
{int v8; // r6if ( !byte_2176D99 ){sub_48CE2C((int)&System_Security_Cryptography_HMACSHA1_TypeInfo);byte_2176D99 = 1;}System_Security_Cryptography_DeriveBytes___ctor(a1, 0);System_Security_Cryptography_Rfc2898DeriveBytes__set_Salt(a1, a3);System_Security_Cryptography_Rfc2898DeriveBytes__set_IterationCount(a1, a4);*(_DWORD *)(a1 + 20) = a2;v8 = sub_48CF00(System_Security_Cryptography_HMACSHA1_TypeInfo);System_Security_Cryptography_HMACSHA1___ctor_22256684(v8, a2, 0);*(_DWORD *)(a1 + 16) = v8;return System_Security_Cryptography_Rfc2898DeriveBytes__Initialize(a1);
} 

说干就干

var libxx = Process.getModuleByName("libil2cpp.so");
console.log("*****************************************************");
console.log("name: " +libxx.name);
console.log("base: " +libxx.base);
console.log("size: " +ptr(libxx.size));

Interceptor.attach(ptr(libxx.base).add(0x1097304),{onEnter: function(args){console.log("=== pwd");console.log(TAG + hexdump(ptr(this.context.r1), { offset: 0, length: 128, header: true, ansi: true }) );console.log("=== salt ");console.log(TAG + hexdump(ptr(this.context.r2), { offset: 0, length: 64, header: true, ansi: true }) );},onLeave:function(retval){}
}); 

这就尴尬了

Error: unable to find module 'libil2cpp.so' 

libil2cpp.so 大概率是动态载入的,所以刚启动app的时候木有libil2cpp.so。

如果我们要hook的函数之后会被多次调用,那么可以延迟几秒钟来载入 setTimeout(main, 1000*3);

不过这里我们要hook的都是init和ctor之类的初始化函数,几秒钟之后可能都初始化完成了。

hook_constructor

要第一时间hook 动态载入的so,就需要从so的加载开始搞

function hook_constructor0() {if (Process.pointerSize == 4) {var linker = Process.findModuleByName("linker");} else {var linker = Process.findModuleByName("linker64");}var addr_call_function =null;var addr_g_ld_debug_verbosity = null;var addr_async_safe_format_log = null;if (linker) {var symbols = linker.enumerateSymbols();for (var i = 0; i < symbols.length; i++) {var name = symbols[i].name;if (name.indexOf("call_function") >= 0){addr_call_function = symbols[i].address;}else if(name.indexOf("g_ld_debug_verbosity") >=0){addr_g_ld_debug_verbosity = symbols[i].address;ptr(addr_g_ld_debug_verbosity).writeInt(2);} else if(name.indexOf("async_safe_format_log") >=0 && name.indexOf('va_list') < 0){addr_async_safe_format_log = symbols[i].address;}}}if(addr_async_safe_format_log){Interceptor.attach(addr_async_safe_format_log,{onEnter: function(args){this.log_level= args[0];this.tag = ptr(args[1]).readCString()this.fmt = ptr(args[2]).readCString()if(this.fmt.indexOf("c-tor") >= 0 && this.fmt.indexOf('Done') < 0){this.function_type = ptr(args[3]).readCString(), // func_typethis.so_path = ptr(args[5]).readCString();var strs = new Array(); //定义一数组strs = this.so_path.split("/"); //字符分割this.so_name = strs.pop();this.func_offset= ptr(args[4]).sub(Module.findBaseAddress(this.so_name))if(this.so_name == "libil2cpp.so") {var targetSo = Module.findBaseAddress(this.so_name);console.log(TAG +' so_name:',this.so_name);console.log(TAG +' ptr:',ptr(targetSo));hookDbg(targetSo);}}},onLeave: function(retval){}})}
}

function hookDbg(targetSo){Interceptor.attach(targetSo.add(0xAAE690),{onEnter: function(args){console.log(" UserPrefs_ctor *****************************************************");},onLeave:function(retval){}});Interceptor.attach(ptr(targetSo).add(0x1097304),{onEnter: function(args){console.log("=== pwd");console.log(TAG + hexdump(ptr(this.context.r1), { offset: 0, length: 128, header: true, ansi: true }) );console.log("=== salt ");console.log(TAG + hexdump(ptr(this.context.r2), { offset: 0, length: 64, header: true, ansi: true }) );},onLeave:function(retval){}});

} 

这次的结果就比较完美了

global-metadata.dat,unity,游戏,游戏引擎,spring,开发语言

1:rc

Rfc2898DeriveBytes的入参是String,可以看到String在内存中的布局, 0x0C 开始的4个字节是 字符串长度,0x10开始才是真正的字符串。

password 是存档的文件夹名称+EK

salt 是个固定的字符串

带着这个结果我们再回过头去看 UserPrefs__Init的F5的代码,重点关注那几个 System_String_Concat 就更有心得了。

三、总结

为了抵抗Il2CppDumper,敌人变狡猾了,所以作者推出了更帅的 Zygisk-Il2CppDumper

现在套路这么多,技能得不断更新才能跟的上,又要掉头发了。

变来变去的都是外围,万变不离其宗的还是arm汇编,最后的定位还是需要你的汇编功底。

网络游戏改存盘是没用的,一联服务器就把你覆盖了。

global-metadata.dat,unity,游戏,游戏引擎,spring,开发语言

1:ffshow

富贵故如此,营营何所求global-metadata.dat,unity,游戏,游戏引擎,spring,开发语言文章来源地址https://www.toymoban.com/news/detail-800029.html

到了这里,关于Unity IL2CPP 游戏分析入门的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 一次Unity3D IL2CPP 打包错误

    目录 一、错误描述 二、问题分析 三、解决方法 四、效果验证 五、后记 采用IL2CPP生成的时候,出现了4个错误: (1)Building LibraryBeeartifactsWinPlayerBuildProgramei6vjku08_i_vm6.lump.obj failed with output: (2)BuildFailedException: Incremental Player build failed! (3)Build completed with a result of ‘

    2024年02月03日
    浏览(35)
  • unity导入Android报错Unity.IL2CPP.Building.BuilderFailedException

    把从unity导出的Android包作为一个模块嵌入至Android程序 在解决了sdk31损坏等一系列问题,终于将程序成功安装到了手机上,以为就要通向光明未来了,结果在手机端调试时发现本人手机硬件(高通778,v8)不支持unity程序。 经过百度发现我导出的unity项目只支持处理器架构v7的手

    2024年02月05日
    浏览(27)
  • Unity 安卓构建错误:IL2cpp 需要提取的资源提取失败

    Unity 安卓构建错误:IL2cpp 需要提取的资源提取失败 问题描述: 在 Unity 中进行安卓构建时,可能会遇到一个报错信息:“failed to extract resources needed by IL2cpp”。这个错误通常发生在将 Unity 项目构建为安卓应用程序时,而 IL2cpp 则是 Unity 用于将 C# 代码编译为本地代码的工具。

    2024年02月04日
    浏览(30)
  • 解决Unity安卓编译错误: IL2CPP需要的资源提取失败

    解决Unity安卓编译错误: IL2CPP需要的资源提取失败 在开发Unity游戏时,我们经常会遇到各种各样的问题。其中一个常见的问题是,当我们尝试将游戏导出为Android应用程序时,可能会遇到一个名为\\\"Failed to extract resources needed by IL2CPP\\\"的错误。本文将介绍如何解决这个问题,并提供

    2024年02月03日
    浏览(28)
  • 2023年Unity Il2CPP/MONO FPS逆向工程

    实战引擎 : Unity Il2CPP/Mono 学完可做 : 森林之子,后室,逃离塔科夫,BattleBit,Rust等 几乎通杀全部Unity引擎游戏 简介: 实战编程代码:C/C++ B站空间:https://space.bilibili.com/2134677790 课程详细目录 :2023年Unity Il2CPP/MONO FPS逆向工程 · 语雀 联系方式 :点击课程详细目录查看 效果图

    2024年02月13日
    浏览(33)
  • 反编译Unity IL2CPP APK:深入探索逆向工程技术

    反编译Unity IL2CPP APK:深入探索逆向工程技术 在移动应用开发领域中,Unity引擎被广泛使用以创建令人惊叹的游戏和应用程序。然而,有时候我们可能需要研究某些应用程序的内部机制或者了解其实现细节。本文将介绍如何反编译基于Unity引擎的IL2CPP APK,并提供相应的源代码和

    2024年02月06日
    浏览(34)
  • Unity2020 打包报错:windowUnity.IL2CPP.Building.BuilderFailedException

    在打包的时候报错:windowUnity.IL2CPP.Building.BuilderFailedException 解决方法:把IL2CPP换成mono,重新打包即可 依次点击:Edit-Player-Configuration-Mono 再次重新打包即可

    2024年02月16日
    浏览(32)
  • 初识IL2CPP

    在Unity中进行打包时,有两种打包方式选择: Mono 和 IL2CPP Mono和IL2Cpp是Unity的脚本后处理方式,通过脚本后处理实现Unity的跨平台 (1). Mono组成组件: C#编辑器,CLI虚拟机,以及核心类别程序库 (2).跨平台过程 Mono通过C#编辑器把脚本打包成中间语言(IL)IL所在的文件就是.dll后缀的

    2024年02月14日
    浏览(37)
  • Unity WebGL 打包il2cpp.exe did not run properly!

    是中文问题,WebGL的illcpp对执行过程中一点点的中文都不允许存在。包括 硬盘的中文名称 参考 如何更改磁盘盘符的名字 我的文档中的用户名有中文 系统用户名有中文 win+L封锁之后解锁会出现当前系统用户是否有中文 打包路径中有中文 这个很好解决 环境变量中有中文 由于

    2024年02月16日
    浏览(38)
  • Unity IL2CPP包Crash闪退提示 Cause: null pointer dereference 解决办法

    最近打包到安卓测试时进logo后一直闪退,打开Android Logcat后发现抛出crash 看样子是空引用崩溃的 在网上找了几个方法来试试,到最后才发现是场景里有Missing物体 方法1.关闭StripEngineCode 在Project Settings选择Player页,在里面的Other Settings取消勾选Strip Engine Code 我试了这个对我没用

    2024年03月17日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包