首页
社区
课程
招聘
[原创]unity的et热更新分析和补丁
2024-3-21 15:35 5004

[原创]unity的et热更新分析和补丁

2024-3-21 15:35
5004

一、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热更新的后来者有所帮助。


[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

收藏
点赞1
打赏
分享
最新回复 (4)
雪    币: 6
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_ldbucrik 2024-3-21 20:21
2
0
感谢分享
雪    币: 19085
活跃值: (28674)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
秋狝 2024-3-23 20:49
3
1
感谢分享
雪    币: 4991
活跃值: (3731)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mb_rjdrqvpa 2024-4-2 11:38
4
0
有点好奇,为什么patch方案,前2种理论上可行,但一跑就挂的原因?
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_bahjqanq 4天前
5
0
大佬,感谢分享。目前我也是逆向小白一个,刚入门。目前也是在研究 iOS 侧,也是发现非越狱情况下,必须给 Mach-O 进行修改,才能实现 inline hook C 的函数。
然后就研究到 MachOStaticPatcher 这个库了(通过 Dobby 摸到这个5年前的库)。但是不知道为什么,这个库在使用过程中碰到一些问题。恰好这里看到您也使用过,所以想请教一下~
希望得到您的赐教
游客
登录 | 注册 方可回帖
返回