2022-03-20 10,730 ℃
在两年前发布Riru版Il2CppDumper的时候,就打算发布这篇配套的文章,结果码了一半就被我丢到了一旁,后来每次想起来的时候总是以”既然代码都发布了大概也不需要写什么所谓的教程了吧”的理由停下了准备码字的手,就这样一转眼来到了2022年。两个月前Magisk更新了v24版本,新增了Zygisk替代了Riru,所以我也顺势更新了Zygisk的版本,然后从草稿箱中把这篇文章翻了出来准备修改成Zygisk的版本发布,结果改着改着又被我丢了。这段时间疫情形势严峻,我住的小区也被封禁,不能出门的我又从草稿箱把这篇文章翻了出来,不过这次总算是难产成功了。
Zygisk和Il2Cpp Api
先简单介绍下Zygisk和Il2Cpp Api,其实接下来要做的跟我之前的使用Riru修改手游这篇文章类似,Zygisk就是一个用来注入代码到游戏里的工具,只不过这次是通过调用Il2Cpp的Api来进行修改。那么为什么要使用Il2Cpp Api呢?首先就是通用性,api作为导出函数在任何平台上使用il2cpp编译的游戏里都是存在的,所以在修改里使用api部分的代码不用考虑是移动还是pc平台都可以使用。还有就是对于函数地址这种,需要在代码里硬编码的部分也可以通过调用api获取,这样即使游戏更新只要之前修改的逻辑还能用,比如让某某函数返回0这种,就不需要更新修改的代码。而且api还能非常轻松的直接修改运行时的数据,比如字段等等。最后因为是运行时修改,也能绕过大部分壳,目前大多数游戏厂商都没有对api做保护。
使用API前的准备工作
如果你比较懒不想从头开始写,你可以直接下载Zygisk-Il2CppDumper的源码,然后从这之后写你的代码就行,使用api前的准备工作都已经做好,也有Dobby这个hook框架。需要注意dumper是直接使用最新的api声明文件,里面有些函数在老版本里是不存在的,而且没有包含Unity结构的详细定义。如果你需要特定版本的Unity api和结构体头文件,可以从这里下载替换il2cpp-api-functions.h和il2cpp-class.h即可。要编译时只要运行gradle任务:module:assembleRelease就会在out文件夹生成zip包。
下面就是详细步骤了,因为已经提供源码了这里就不会再贴代码,不懂的地方请配合源码食用。
首先是Zygisk部分,从zygisk-module-sample下载源码拿到zygisk.hpp和example.cpp,修改example.cpp的部分就跟之前riru那篇类似了,在preAppSpecialize中判断当前程序包名,然后在postAppSpecialize中启动新线程进行之后的操作。
在新线程里要做的事就是初始化il2cpp的api了,不过首先我们需要从Unity的安装包里偷下api的声明和用到的结构,具体参考这小节的开头。至于初始化,在安卓和ios平台,可以直接使用dlopen + dlsym。不过安卓平台游戏可能加壳,dlopen就不管用了,所以源码里是使用hook do_dlopen的方式来取handle然后再调用dlsym。
顺带说下windows平台,可以通过GetModuleHandle + GetProcAddress来初始化api。
认识基础结构
在使用api之前需要认识一下两个基本结构,Il2CppClass和Il2CppObject,其中Il2CppClass就相当于是一个类的定义,Il2CppObject就是类的实例,可以通过il2cpp_object_new生成,也就是C#里的new操作,在使用api的时候任何类和类的实例都可以通过Il2CppClass和Il2CppObject表示而不需要关心类的具体结构,如果需要类的具体声明,可以考虑从Il2CppDumper生成的il2cpp.h中复制。
使用API
首先需要调用il2cpp_thread_attach,不然在后面进行一些操作可能会使游戏崩溃
auto domain = il2cpp_domain_get();
il2cpp_thread_attach(domain);
接下来就是基本操作,遍历il2cpp_domain_get_assemblies的返回值取出想要的dll,然后用il2cpp_assembly_get_image获取Il2CppImage
auto assemblies = il2cpp_domain_get_assemblies(domain, &size);
for (int i = 0; i < size; ++i) {
if (strcmp(assemblies[i]->aname.name, "Assembly-CSharp") == 0) {
auto image = il2cpp_assembly_get_image(assemblies[i]);
}
}
有了Il2CppImage后就可以调用il2cpp_class_from_name获取Il2CppClass,想要获取某个method,就可以继续调用il2cpp_class_get_method_from_name,最后一参数是method的参数数量
auto klass = il2cpp_class_from_name(image, "Namespace", "Classname");
auto method = il2cpp_class_get_method_from_name(klass, "MethodName", 1);
auto addr = method->methodPointer;
有些情况下il2cpp_class_get_method_from_name可能无法使用,比如刚好有同名同参数数量的多个method,这个时候就使用il2cpp_class_get_methods进行迭代直到找到自己需要的。
void *iter = nullptr;
while (auto method = il2cpp_class_get_methods(klass, &iter)) {
//TODO
}
如果想要调用函数,函数声明可以照着c#代码手抄一份,或者用Il2CppDumper生成script.json后从里面复制。需要注意的是非静态函数需要添加一个实例(Il2CppObject*)参数作为第一个参数,可以根据需求使用hook或者调用il2cpp_object_new生成,静态函数的话需要注意版本号,如果版本号大于等于2018.3,则不需要添加参数,小于2018.3一样要添加一个实例参数,只是传NULL。另外在函数声明最后还要添加一个”void* method”参数,这个参数在大多数函数里直接传NULL就行,这里就不展开讲了。
//c#函数
int Add(int a, int b)
//c声明
//是静态函数,版本大于等于2018.3
int32_t Add(int32_t a, int32_t b, void *method)
//是静态函数,版本小于2018.3
int32_t Add(Il2CppObject* _null, int32_t a, int32_t b, void *method)
//非静态函数
int32_t Add(Il2CppObject* _this, int32_t a, int32_t b, void *method)
在il2cpp中字符串都是Il2CppString结构,需要使用时,可以调用il2cpp_string_new生成,不过要从Il2CppString转回std::string或者char*就稍微有点麻烦,因为本身Il2CppString在内存中存储的格式是Utf16,所以这里就直接复制il2cpp里的代码来进行转换,使用时需要从Unity的安装路径“Editor\Data\il2cpp\libil2cpp\utils\utf8-cpp”偷utf8-cpp来使用。
//代码来自“Editor\Data\il2cpp\libil2cpp\utils\StringUtils.cpp”
std::string Utf16ToUtf8(const Il2CppChar *utf16String, int maximumSize) {
const Il2CppChar *ptr = utf16String;
size_t length = 0;
while (*ptr) {
ptr++;
length++;
if (maximumSize != -1 && length == maximumSize)
break;
}
std::string utf8String;
utf8String.reserve(length);
utf8::unchecked::utf16to8(utf16String, ptr, std::back_inserter(utf8String));
return utf8String;
}
std::string Utf16ToUtf8(const Il2CppChar *utf16String) {
return Utf16ToUtf8(utf16String, -1);
}
std::string Il2CppStringToStdString(Il2CppString *str) {
auto chars = il2cpp_string_chars(str);
return Utf16ToUtf8(chars);
}
最后说下字段(field)方面的api,首先是取需要的字段,这个跟前面的method类似,使用il2cpp_class_get_field_from_name直接通过名称获取或者用il2cpp_class_get_fields遍历。拿到字段后要对值进行操作的话,就需要分下情况,对于静态字段,读取和写入直接使用il2cpp_field_static_get_value和il2cpp_field_static_set_value就行,如果是需要修改实例类的字段值,大多数情况都需要使用hook获取到你需要修改的实例类,然后再通过il2cpp_field_get_value和il2cpp_field_set_value进行读取和写入。
api的介绍就到这里了,基本会使用到的api都介绍完了,其他的api就请自行查阅il2cpp-api-functions.h文件,里面已经给api分好了类,命名也非常清晰。
“””
可以通过il2cpp_object_new生成,也就是C#里的new操作
“””
大佬,这个il2cpp_object_new跟new还是有差异吧?il2cpp_object_new感觉没有执行ctor中的逻辑
可以再执行下il2cpp_runtime_object_init就会执行ctor了
大佬 Il2CppObject *instance里存着我想要的数据
struct Il2CppObject
{
Il2CppClass *klass;
void *monitor; //0
};
这个结构怎么分析出我想要的数据
转成具体的结构体类型
C#代码:
“`
{
this._collider = base.GetComponent();
}
“`
IDA反汇编得到的:
“`
{
Component_object = UnityEngine_Component__GetComponent_object_(
a1,
Method_UnityEngine_Component_GetComponent_Collider___);
*(_DWORD *)(a1 + 24) = Component_object;
}
“`
其中 `Method_UnityEngine_Component_GetComponent_Collider___` 是静态解析出来的一个变量,值是 `0xC0069E01` :
“`
.data:06B7882C 01 9E 06 C0 Method$UnityEngine.Component.GetComponent_Collider___ DCD 0xC0069E01
“`
怎么通过 il2cpp API 来动态地获取这个变量?
api应该没有办法获取,不过可能可以不用考虑这个参数,直接传null试试
Thanks for your tools and tutorials, is it possible to ask you if you can make a tutorial or an update for assetstudio to be able to deserialize text assets of il2cpp games? I’ve been trying to do that for a few months but got nothing (I don’t know anything about programming ) 谢谢!
大佬你好,非常感谢你的框架.
我在使用的时候发现个问题,并没有找到DobbyHook.
想问下文章里面是怎么Hook il2cpp的函数呢,需要自己添加Dobby吗?
不好意思新版本我已经删掉Dobby了,你自己去下载一份Dobby添加进去吧
解决了,非常感谢.
Perfare 大佬,可否请教一个问题,Unity的il2cpp为什么一定要导出那些对游戏安全有隐患的API呢?我搜遍资料都没有找到说明unity这么做的原理,可否解惑?感谢
这个我就真不知道了,只有Unity知道它为什么要这样做
il2cpp_thread_attach(domain);//闪退
可能是因为il2cpp还没有初始化完成,我在最新的代码里加上了判断的方法,等il2cpp初始化完成后再调用就不会闪退了
大佬,我调用il2cpp_domain_get就闪退
可能是游戏隐藏或者修改了api
Zygisk用不了,已经用其他方式实现了
最近刚好在一个游戏上遇到这个问题,检查之后发现是因为il2cpp还没初始化完成,我在最新的代码里加上了判断的方法,如果有需要你可以看看
怎么判断是否初始化完成
请去github上看代码
大佬,请问下cs里面得这种地址需要怎么获取 |-RVA: 0x156C800 Offset: 0x156C800 VA: 0x156C800 |-Action.Invoke ?zygisk得il2cppdumper dump不出来这些消息
如果你是指泛型的函数地址的话,zygisk-il2cppdumper不支持获取泛型函数
girhub actions 怎么操作
我认为 ios 的元数据已加密,有解决方案吗?..
是否有可能使此工具也适用于 ios?..
原理是支持的,现成的工具参见frida-il2cpp-bridge
大佬 可以发个修改字段的例子嘛
我在使用il2cpp_object_new生成类的实例时游戏会卡死
搞定了
请问大佬是怎么搞定的吗 想请教一下方法
大佬,我想问一下怎么获取下面这个方法的参数,从ida看,它的调用是没有参数的,不知道il2cpp是怎么实现的
public static Void MyAESDecrypted(ref Byte[] bytes) { }
这个我还真没研究过,不过你反编译下这段函数看代码用到bytes这个参数地方的汇编应该可以很容易知道是怎么来的
大佬你好,如何通过il2cpp_field_static_get_value获取Byte[]类型的参数呢?
没懂你这个问题是什么意思,如果静态字段是数组你调用这个api后取出来的不就是Il2CppArray *吗
大佬
auto assemblies = il2cpp_domain_get_assemblies(domain, &size);
for (int i = 0; i aname.name, “Assembly-CSharp”) == 0) {
image = il2cpp_assembly_get_image(assemblies[i]);
}
}
这段代码里的 -> 报了这个错 Member access into incomplete type ‘const Il2CppAssembly’
应该怎么修复 求教
如果你不需要修改代码推荐直接用github action编译,如果你自己添加修改了代码就得你自己检查
我在Zygisk-Il2CppDumper代码(commit:5ff73ad)基础上做修改时也碰到了这个问题,发现似乎是 il2cpp-class.h 里只有 typedef struct Il2CppAssembly Il2CppAssembly; 这样一句声明、但缺少 struct Il2CppAssembly 类型定义的的缘故。
去大佬提到的那个 Il2CppVersions 仓库,用适当unity版本的头文件(类型声明在仓库headers目录下,il2cpp function的声明在api目录下)对 il2cpp-class.h 内容做替换就能消除这个报错的样子。
要进行类型转换,C++代码如下:
image = const_cast(il2cpp_assembly_get_image(assemblies[i]));
用il2cppdumpper得到的dump.cs文件里面已经标注了各dll的索引,例如:
// Image 0: Assembly-CSharp.dll – 0
// Image 1: mscorlib.dll – 10434
// Image 2: ClientCommons.dll – 12215
…
所以可以省去for循环的判断,直接使用
image_AssemblyCSharp = const_cast(il2cpp_assembly_get_image(assemblies[0]));
image_mscorlib = const_cast(il2cpp_assembly_get_image(assemblies[1]));
image_ClientCommons = const_cast(il2cpp_assembly_get_image(assemblies[2]));
大佬你好 运行你的Zygisk-il2cppdumper脚本游戏会直接闪退 我通过frida 查看libil2cpp.so里的函数 发现没有il2cpp_assembly_get_image 这个函数 请问这种情况下我该怎么办
游戏名:奥比岛
Unity版本:2020.3.32f1
确实,其它api基本都在,就这个消失,应该是被魔改了。我也很想知道面对这种情况应该怎么做
我去github翻阅源码后,一句代码就搞定它了
可以传授下 如何做到的吗
可以尝试通过Il2CppAssembly结构直接取Il2CppImage* image;
谢谢大佬 问题已解决
没搞过IL2CPP_NEW实例化达到修改函数怎么操作,调试一次重启一次麻了,有没有示例
没太懂你想要做什么,如果你只是要拿到函数地址进行修改并不需要使用实例化
大佬你好强啊,爱了爱了
大佬,有Il2CppDumper的安卓版吗。有时候只想查个偏移用gg修改
你可以尝试在安卓上跑netcore版
Dictionary dicPostData是不是用Il2CppObject *dicPostData
如:
public void Post(string url, Dictionary dicPostData, Action funcResult) { }
Hook C代码:
void old_Post(Il2CppObject* _this,Il2CppString *url, Il2CppObject *dicPostData, Il2CppObject *funcResult);
void new_Post(Il2CppObject* _this,Il2CppString *url, Il2CppObject *dicPostData, Il2CppObject *funcResult); v
oid new_Post(
Il2CppObject* _this,Il2CppString *url, Il2CppObject *dicPostData, Il2CppObject *funcResult){
std::string purl = Il2CppStringToStdString(url);
LOGI(“dumping2 purl start %s “,purl.c_str());
saveFile(purl.c_str(), sizeof(purl.c_str()), “purl.txt”);
LOGI(“dumping2 purl end”);
return old_Post(_this,url,dicPostData,funcResult);
}
能成功HOOk。但是我如何获取dicPostData里面的键值?然后保存
因为泛型的操作过于复杂所以我正文里没写。
首先我最一般用的是hook Dictionary的Add函数,这样既可以抓到键值也可以修改。
具体操作通过反射获取到Dictionary泛型实例的Il2CppClass,之后就可以使用il2cpp_class_get_method_from_name获取到Add函数的地址了。我这里就稍微贴点代码,Zygisk-Il2CppDumper后面也有具体的反射代码。其中用到了静态函数,注意版本差异
auto corlib = il2cpp_get_corlib();
auto assemblyClass = il2cpp_class_from_name(corlib, “System.Reflection”, “Assembly”);
auto assemblyLoad = il2cpp_class_get_method_from_name(assemblyClass, “Load”, 1);
auto assemblyGetType = il2cpp_class_get_method_from_name(assemblyClass, “GetType”, 1);
typedef void *(*Assembly_Load_ftn)(void *, Il2CppString *, void *);
typedef Il2CppReflectionType *(*Assembly_GetType_ftn)(void *, Il2CppString *, void *);
auto reflectionAssembly = ((Assembly_Load_ftn) assemblyLoad->methodPointer)(nullptr,
il2cpp_string_new(“mscorlib”),
nullptr);
auto reflectionType = ((Assembly_GetType_ftn) assemblyGetType->methodPointer)(
reflectionAssembly, il2cpp_string_new(“System.Collections.Generic.Dictionary`2[System.String,System.String]”),
nullptr);
auto klass = il2cpp_class_from_system_type(reflectionType);
然后调用il2cpp_class_get_method_from_name就行了
大佬,调用assemblyGetType的时候出现错误,无法获取到System.Collections.Generic.Dictionary`2[System.String,System.String],
但是获取System.Collections.Generic.Dictionary`2是正常的,但是后续il2cpp_class_get_method_from_name无法获取到泛型函数的地址,请问要怎么处理
这问题感觉不太可能发生,除非游戏本身没有使用到Dictionary,那你自然就获取不到
大佬你好,请问下:byte[] 类型应该怎么写hook 参数呢
如果你不需要使用直接用void*代替就行,如果需要取值就定义成Il2CppArray *,遍历array->vector就行
如何通过il2cpp_field_static_get_value获取Byte[]类型的参数呢?
大佬可以给一段简单的demo代码吗,不太熟悉c语言,照着百度的std::vector遍历方法,改了一下,报fault addr了 (val 的C#类型是byte[])
Il2CppArray* bytes = (Il2CppArray*)val;
uint32_t count_1 = il2cpp_array_length(bytes);
LD(“length %d”, count_1);
char* chars = new char[count_1];
for(int i = 0; i vector[i]);
chars[i] = v;
}
回复后的后半段代码怎么变了..应该是这样的
for(int i = 0; i vector[i]);
chars[i] = v;
}
只能传图片了,回复之后的代码会被过滤掉一部分….
https://p.inari.site/guest/24-04/13/661a20c4e96ff.png
大佬,请问一下这个可以通过修改获取so的大小然后达到:Dump出解密好的so吗,因为相比于只能看到函数名,我更倾向于用IDA去分析他的一个函数的走向与处理的流程
用api的话其实也能获取调用堆栈来方便逆向,可以看看这个工具https://github.com/vfsfitvnm/frida-il2cpp-bridge
当然可以用来dump so,不过需要你自己处理map然后将数据读出来,不过既然已经root了建议直接使用gameguardian
大佬 请问一下 我想要frida hook 一个游戏的libil2cpp.so 但是列举了所有游戏加载的so 文件 发现游戏并没有加载libil2cpp.so 文件 但是已经确定这个文件是unity开发的了 游戏名叫无悔华夏
我并没有用过frida,所以没法解决你的问题,不过我测了一下Zygisk-Il2CppDumper,是没有任何问题的
大佬,你有试过用Zygisk-Il2CppDumper去dump月圆之夜的吗?我刚刚试了没dump出来(其它游戏试过了,成功),TapTap上面有这个游戏的下载
本来就不保证能适用于所有游戏,有游戏不能用是正常的
il2cpp_class_get_method_from_name一调用整个游戏就卡死。
这是什么原因呢?
IDA中,这个函数的附近有大量DCQ。这个采取了什么措施? 大佬给指路下
描述太模糊,可能是游戏做了保护
il2cpp_class_get_method_from_name 这个函数从libunity.so调用过来,我用frida stalker追踪了指令,可以正常运行(它搞了花指令 IDA里一堆DCQ ),但是如果直接hook这个函数来调用,就会卡死, 在il2cpp_class_get_method_from_name(libil2cpp.so中)之前有一堆地址计算,我怀疑是从libunity.so中除了函数参数外,通过额外寄存器给出了一些初始值,以便能计算出正确的代码地址。 您之前碰到这种保护方式吧? 我想坐下确认。
https://github.com/Perfare/Zygisk-Il2CppDumper/issues/16#issuecomment-701231190
以前有遇到类似的,不过可能不是你这个
感谢回复,跟您提到的问题比较不一致,该游戏有导出函数。 目前我通过hook该游戏libunity库中对il2cpp_系列函数调用过程的参数与返回值,被动获取到相关结构体。 但是发现该游戏应该是使用了Beebytes’ obfuscator(猜测该插件)进行了函数名称与字段的混淆(非全部代码都混淆,关键一些函数混淆了)。 这种情况下,请问有什么思路去梳理这些函数与字段的含义呢? 可否给几个思路。谢谢
大概只能猜测和通过跟踪调用堆栈来判断了,这个工具https://github.com/vfsfitvnm/frida-il2cpp-bridge可能对你有帮助
嗯;感谢提供思路.
可是现在大量游戏都会检测,一开游戏就显示 “检测到异常,违反安全策略 Code 00000XX“
不知道这个壳到底是啥,大部分新游戏都会加这个壳,比如少女前线,还有今天sega刚出的真·锁链战记 都是跳这个
实在不知道要怎么过这个壳啊。。我记得多年前的魔法少女小圆也是这个壳,然后有人发布了脱壳后的版本 直接就能运行。。
大大有办法麽
你只是单纯没隐藏root罢了。。。
隐藏了的,用了最新的magisk的zygisk和Shamiko,开了最强检测momo,momo显示顺利通过,今天发布的真锁链战记打开还是显示检测到异常,违反安全策略..
这个游戏我今天测试过了,并没有任何检测能影响到调用il2cpp api,建议你自己检查下是不是还开了其他东西,比如xposed之类的
我是在模拟器里开的,估计是检测到模拟器了,不知道到底检测了什么模拟器的参数。。大佬有思路吗,我去试试
没研究过,我几乎不用模拟器修改游戏
好的 谢谢大佬
不需要结构是挺舒服的
香爆了
你是不是被关起来了又不见人
么有啊 只是电报不能用了
老鸽你那QQ搜不到啊之前就跟你说了,这都tm几年了