查看原文
其他

Unity的et热更新分析和补丁

wuaiwu 看雪学苑 2024-04-20



前言


unity项目,市面上多数都是c#通过il2cpp转换的代码,核心逻辑都在libil2cpp.so(安卓)或者主程序中(ios)。et框架的核心代码并没有经过il2cpp,而是跑在ilruntime上,主包并不包含核心逻辑。核心逻辑是首次运行下发,然后动态加载的。这里想介绍并记录下ilruntime上的反射来获取游戏数据,并调用热更新中的代码的方式。





热更新代码保存方式


1、以安卓为例,在sdcard上找到热更新后的目录,然后找到code.untiy3d,这个便是核心模块。头部0x120长度的内容删掉,然后就能用assetbundle打开了。

内容如下:



2、将这些asset直接到处,就能看到hotfix.dll的c#代码,也是游戏的核心代码。核心代码不在Assembly-CSharp中。





patch方案和反射


1、替换二进制文件,直接打patch:使用dnspy直接修改hotfix.dll,然后保存。在游戏loadasseembly的时候,将内容替换成修改后的hotfix.dll,这种方式实践后一加载就挂,根本不能用。

加载loadassembly的代码:


2、替换二进制,首先生成一个自己的c#的dll,使用ilrepack合并到hotfix.dll中,在hotfix的c#代码中,将特定函数的代码进行patch,并将这个特定函数的代码,写到自己的c#的dll中,这样就能无限扩充逻辑了,不受限原来函数的字节码长度。这样就能保证原来的特定函数,不会应该dnspy添加il字节码,超过特定函数的原本的长度。不过,这个仅仅是我的思路。实际操作,ilrepack合并完。loadassembly加载并替换的时候,就直接挂壁了。

3、ilruntime的反射。由于通过il2cpp的api,去获取assembly,再去获取image,这种方式压根不能获取热更新的模块,所以,也无法通过il2cpp的api去反射调用热更新中的代码。ilruntime中执行的assembly。根据ilruntime的官方文档,要先找到ilruntime的appdomain,然后找到loadedtypes。这个loadedtypes中存放了热更新的所有类的类型,然后才能获取到原本的relectiontype。

ilruntime部分官方文档内容如下:


3.1 翻译:appdomain.LoadedTypes["TypeName"]这句的反射获取方式。首先获取ilruntime的appdomain,经过研究et框架的,找到hotfix这个类,它对象的0x10偏移处就是appdomain。




得到appdomain后,然后它获取它的成员LoadedTypes(是个字典,反射的话,是在不好解析,直接解析内存数据,也不会解析。翻看ilruntime源码,准备放弃的时候。在ida函数中到一个函数ILRuntime_Runtime_Enviorment_AppDomain__GetType,frida试了下,传递类型,返回的正好是itype,也就是上边这句appdomain.LoadedTypes["TypeName"]效果一致)。


Type type = t.ReflectionType(官方文档未更新,写成了ReflectedType)这句比较好翻译,直接在ida中找到了对应的函数ILRuntime_CLR_TypeSystem_ILType__get_ReflectionType。同样的,找到t.GetField("xxx"),这句,也能找到到对应的ida函数ILRuntime_Reflection_ILRuntimeFieldInfo__GetValue,还有静态成员(ILRuntime_Reflection_ILRuntimeFieldInfo__GetValue),property(System_Type__GetProperty_40653988)成员,方法(System_Type__GetMethod_40652536),都能找到一个函数与c#的语句对应。

最后,就是调用函数。官方文档给出了两种反射调用热更新模块中的函数的方法。我使用的的是appdomain.invoke(同样的在ida中找到invoke的实现函数ILRuntime_Runtime_Enviorment_AppDomain__Invoke)的方法。



上图中的this,就是我们之前获取的appdomain,type就是c#的string类型(可以用过il2cpp_string_new来创建),method就是方法名,instance就是对象被反射调用的对象指针(静态函数的话,就穿null,其它的话,就通过反射constructor来构造,多数都是另一个类的静态成员)。

System_object_array就是函数参数。这个构造方法,经过我不断的尝试,可以通过il2cpp_new_array_special来创建,并设置。最后一个参数传null。创建并构造system_object_array的代码如下:


经过上边的处理,调用ilruntime中的指定类的指定方法就可以了。剩下的就是选择,你要调用哪些c#函数。





hook抓包


1、安卓,这个比较好处理,直接用dobby的hook就可以。et框架的代码是下沉到il2cpp中了。我想截取游戏所有的收发包,经过分析学习et框架,找到四个函数能够抓取http和tcp的所有代码。

下图依次是抓取tcp收包、tcp发包、http发包和http收包。选择这四个hook点的原因是,现有市面框架,无法hook到热更新中的c#函数。只能被迫找il2cpp中的函数,恰好被我找到这四个函数了。


2、ios。ios上的非越狱,是不太好hook的,新申请的内存设置无法可执行。越狱下跑hook正常,非越狱直接挂壁。但是,经过我的搜索,dobby作者,五年前还在更新一个machostaticpatcher的代码(现在删了不知何故)。我试了下,能打静态patch,patch也能在非越狱手机上,加签名来跑了。




总结


主要就是反射采集游戏数据,抓包分析游戏数据。我想记录下做过的事情,也希望对分析unity热更新的后来者有所帮助。



看雪ID:wuaiwu

https://bbs.kanxue.com/user-home-213417.htm

*本文为看雪论坛优秀文章,由 wuaiwu 原创,转载请注明来自看雪社区



# 往期推荐

1、安卓逆向基础知识之动态调试及安卓逆向常规手段

2、iOS越狱检测app及frida过检测

3、Large Bin Attack学习(_int_malloc源码细读 )

4、CVE-2022-2588 Dirty Cred漏洞分析与复现

5、开发常识 | 彻底理清 CreateFile 读写权限与共享模式的关系

6、XAntiDenbug的检测逻辑与基本反调试




球分享

球点赞

球在看



点击阅读原文查看更多

继续滑动看下一个
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存