补充声明:请勿用于非法用途,未经本人许可不能转载。
最近在某音刷到了一款叫XX冬日的手游,大致套路要升级建筑、升级英雄、做各种任务积攒资源从而提升实力,参与各种实力排行,下载之后玩了两天,各种任务小红点点到停不下来,属实上头。游戏中大多数操作都是无脑点击,于是想着通过逆向工程来看看能不能自动做任务。
主要想达成两个目的:第一分析下这个客户端apk和服务端的通信协议,看看能不能实现封包改包,如果服务端少了校验,甚至可以在pk时一键秒杀对方;第二看看游戏逻辑能否实现任务自动化,实现脱机养号。
于是有了此文,一方面记录一下一些apk逆向的惯用套路方便之后查阅,二来这些年在看雪、52破解等论坛混迹多年,常常感概于各路大神的骚操作,也常常觉得自己“曹操媳妇进菜园——甄姬拔菜”。
首先通过Reqable抓包,这个工具以前叫httpcanary,也叫小黄鸟,是安卓上一款抓包神器,现在可以配合PC端来使用,手机端抓到的包,可以直接在电脑上查看,还可以对url进行下断点改包,针对一些http/https交互的apk,还是比较好使的。

进入游戏后,点击一下领取资源、做任务的按钮,观察数据包,发现抓包面板没有响应,游戏也没有出现网络无连接的现象,根据以往的逆向经验,交互逻辑没有采用常规的http/https协议,可能是rpc或者自定义报文格式的tcp/udp协议。
如果apk是在java层面实现的通信协议,那么常规的思路就是从Activity出发,至于Activity的定位,可以通过MT管理器来查看定位,结合UI通过Frida来进行hook,手动抛出异常并打印调用栈,结合jadx反编译class.dex进而追溯到网络协议请求相关的代码逻辑。这里给出一种思路,本文未使用此法。
如果通信协议是rpc或者自定义格式的tcp/udp协议,通过socket层面的发送和接收来进行抓包,脚本如下
不过这游戏有反调试,需要先过掉。
配置好Frida的基础环境,电脑上需要通过python安装frida-tools和frida,frida-tools就是让我们可以在命令行里使用frida -U -f com.target.app -l hook.js --no-pause这样的命令,frida则是python的package,可以在python脚本中去引入使用。手机上则需要安装对应版本和CPU架构的frida-server,配置好基础环境之后附加到进程启动,启动之后游戏还没加载完就退出了,有针对frida的反调试。
将.apk拖到电脑上,后缀改名.zip之后,直接解压,查看lib目录下有如下.so文件

拿着.so名称做为关键字搜索反调试,找到了一篇文章,无壳app的libmsaoaidsec.so frida反调试绕过姿势 (qq.com),文章中已经详细说明了原理并给出了过反调试的脚本,直接拿过来使用。笑嘻嘻的复制完脚本,启动之后,过了一会儿还是退出了,搜索了几篇文章之后,猜测大概率是因为有一些frida的特征检测,针对此类特征码的检测,看到有文章通过hook诸如strcmp这类的字符串操作类函数来规避特征检测,不过也比较麻烦,没有去深究,我这里采用魔改frida的方式来规避。
下载地址,Release rusda16.2.1 · taisuii/rusda (github.com),仓库中有教程怎么修改编译,完全抹除了frida的字符串特征,作者也给出了编译好的版本。

模拟器选择x86,我是红米K40真机,架构是arm64,因此下载
rusda-server-android-arm64,不知道cpu架构可以通过如下命令查看
输出arm64-v8a
将魔改Frida推送到手机上并且启动监听
以上,手机端配置完成,接下来windows上需要安装对应版本的也就是16.2.1的frida,我的python版本为3.13.3,通过如下命令安装frida 16.2.1,对应frida-tools为12.3.0,由于我的电脑上之前安装的没有魔改的frida版本和这个android_server不对应,得先卸载掉,或者新建一套虚拟环境配置也行。
以上便完成了基础的环境搭建,尝试通过电脑端frida连接手机端,结合脚本如下直接就绕过了,笑容又回来了!
anti_frida.js脚本如下,来源于无壳app的libmsaoaidsec.so frida反调试绕过姿势 (qq.com)
接下来便可以的开心的进行hook了,我进入游戏之后,通过MT管理器,查到页面的Activity是com.unity3d.player.DDUnityLaunchActivity,切换了下面的几个页签之后,这个Activity都没有变,因此UI资源和游戏逻辑不在java层面,那么反编译class.dex就没必要了。只能知道是一个unity游戏。
结合Activity名称,lib目录下的.so文件,例如libunity.so和libil2cpp.so,以及\assets\bin\Data\Managed\Metadata\global-metadata.dat这些特征,也搜索到了几篇比较好的文章,如下
[原创]什么?IL2CPP APP分析这一篇就够啦!-茶余饭后-看雪-安全社区|安全招聘|kanxue.com
针对魔改unity引擎的il2cpp dump方案il2cpp_class_dumper-Android安全-看雪-安全社区|安全招聘|kanxue.com
[原创]IL2CPP 逆向初探-软件逆向-看雪-安全社区|安全招聘|kanxue.com
global-metadata.dat,libil2cpp.so解密修复
这几篇文章都值得细细品味,根据文章中的思路,首先尝试通过Prefare大佬的github.com/Perfare/Il2CppDumper,根据libil2cpp.so和global-metadata.dat去反编译得到C#符号表,当然是失败了的。根据文章中的思路,.so和global-metadata.data大概率经过了混淆和魔改,那么我们就需要尝试从内存中dump出来这两个文件,脚本如下,记得配合之前过反调试使用,进入游戏后,frida没有被异常终止之后在frida控制台中输入dump_so("libil2cpp.so")来使用
这样我们就把libil2cpp.so导出到了手机Download目录下了。
接下来是针对global-metadata.dat的导出,其中一种方式通过魔术头来漫游内存进行dump,这种方式后来发现其实dump出来的文件不对,所以我后面采用的是另一种方式来进行dump。
之后将这两个文件送入Il2CppDumper,报错“[VerifyElfHeader:145]"libil2cpp.so_0x7a4a345000_0x5c9e000.so" has bad ELF magic”。
通过010Editor打开文件libil2cpp.so_0x7a4a345000_0x5c9e000.so,尝试运行ELF模板也没有成功,查看字节如下

魔术头不是7F 45 4C 46,对比正常的ELF文件,通过010Editor模板解析之后应该如下

关于ELF文件头格式的解析,有这些文章参考
[原创] ELF文件结构浅析-解析器和加载器实现-软件逆向-看雪-安全社区|安全招聘|kanxue.com
[原创] SoFixer导入表问题修复及ELF解析简记-软件逆向-看雪-安全社区|安全招聘|kanxue.com
正常的so的elf_header大小为0x40,例如我们观察lib目录下的libil2cpp.so的头部如下

我这里dump出来的so的前面0x40字节被替换了,这样做一是可以阻止一些根据ELF魔术头进行的dump脚本,二是可以阻断我们dump出so之后直接拖入IDA进行反编译。
当然这里我们也能确定一些固定的信息,例如0x10大小的e_ident固定为7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00,e_type固定为3,e_machine固定为183,e_version固定为1,程序入口entry暂时不能确定,e_phoff固定为64,也就是0x40,因为elf_program_header_table是紧跟着elf_header的,而elf_header的大小固定为0x40,所以程序头表elf_program_header_table的偏移也是固定的,至于节头表section_header_table相关信息我们现在没法确定,根据这些固定的信息,通过脚本替换前面0x40的elf_header。
替换后对比结果如下,其实也就是前面0x14字节有差异。

好了,elf_header修复之后用Releases · F8LEFT/SoFixer (github.com)进行修复。

修复之后的再拖到010Editor观察,运行ELF模板后如下

可以看到,SoFixer帮我们恢复了程序头表和节头表,但是还是少了一个动态符号表dynamic_symbol_table,我们仔细观察elf_header发现里面的
Elf64_Half e_ehsize_ELF_HEADER_SIZE固定的64
Elf64_Half e_phentsize_PROGRAM_HEADER_ENTRY_SIZE_IN_FILE对比原版so是56
Elf64_Half e_shentsize_SECTION_HEADER_ENTRY_SIZE固定为64

手动编辑一下相关字节之后,重新运行ELF.bt,底部多出来了动态符号表dynamic_symbol_table了
其实我回头看的时候,发现我们添加头部的脚本里面还可以确定上面手动改的3个参数,调整后如下,这样填充完头部之后,通过SoFixer可以一步到位。
再次尝试通过Il2CppDumper去反编译,尝试得到dump.cs,还是失败,心灰意冷。
不过修复之后,拖入IDA之后可以反编译。至此,我们终于踏出了艰难的一步,拿到了修复之后的libil2cpp.so,由于文件比较大,重建符号表时间比较长。遗留:修复之后的so有92.6M多,apk目录下的原版so仅有53.4M,里面填充了很多空字节,不过不影响IDA反编译。
既然静态dump不行,于是我又尝试去试了一下针对魔改unity引擎的il2cpp dump方案il2cpp_class_dumper-Android安全-看雪-安全社区|安全招聘|kanxue.com这篇文章里的,通过Zygisk-Il2CppDumper来直接从内存里面导出dump.cs,启动目标apk之后,程序崩溃,没有成功导出dump.cs,我查看日志之后也没有运行到导出的位置。
其实到了这里,根据上面那篇文章的提示,大概率猜到了隐藏了il2cpp的部分导出函数,进而让Zygisk-Il2CppDumper这样的工具没办法找到函数偏移。
于是我又去尝试如下工具:
AndroidReverser-Test/frida-find-il2cpp-api: 使用frida spawn启动目标il2cpp手游注入脚本获得需要的il2cpp api的所在偏移 (github.com)
AndroidReverser-Test/il2cpp_class_dumper: 一个用于动态dump 魔改il2cpp框架的unity游戏符号的frida脚本 (github.com)
这两个工具的原理差不多,既然你混淆了il2cpp的导出函数名,就通过汇编特征码去定位,根据已经给的字符串特征码交叉引用,只找到了一个函数,il2cpp_class_get_methods。在导出表中名字如下,好家伙,混淆成了HTPcDxoiHnMqKZdyUYO9uXzP

所以我猜测其它几个关键API的字符串特征码也被抹除了。
到了这里,由于这两个工具需要的API不是很多,我的思路是去找一个原版没有混淆的版本来对比汇编代码,看看能不能再找出几个来,所以我又去下载了Unity Editor,由于Unity Editor版本较多,这里一定要下载对应版本的,至于下载什么版本,一种方式是查看\assets\data\下的.unity3d,可以看到版本号为2022.3.52f1

另一种方式,通过SamboyCoding/Cpp2IL: Work-in-progress tool to reverse unity's IL2CPP toolchain. (github.com)这个工具也能拿到,这个工具应该是整合了很多功能,可以直接对没有魔改的Unity游戏进行反编译,不过这里也没有去深究它了,拿到版本号和上面一致。

一顿下载操作现学Unity Editor开发安卓apk,最后编译打包得到了一个没有混淆的libil2cpp.so,并且在Unity\Hub\Editor\2022.3.52f1\Editor\Data\il2cpp\libil2cpp下面有源码,这样对比起来看其实就没那么困难了。此时我们一个IDA打开没有混淆的so,另一个打开被混淆的so,之后在未混淆的里面搜索il2cpp_method_get_name这些函数,再通过汇编特征码去对比定位,这里由于读汇编代码一个个去匹配实在太慢了,写了个脚本来进行汇编特征码匹配。
大致思路就是,在未混淆的so里面搜索函数,将函数名和汇编特征码和常量引用存到E:\python_project\il2cpp-project\original_signatures.json,这个脚本需要在IDA里通过ALT+F7来加载运行。
得到的结果如下
之后再去混淆函数名的IDA里去根据汇编特征码和字符串引用来匹配定位函数
最后输出的结果类似如下
拿着结果去手动对比验证了一下结果,发现不太准,取特征码和匹配的方式也有点讲究,大家感兴趣可以改下脚本,这个脚本还是有点呆了。
函数少,硬着头皮手动去对比吧,最后结合源码,未混淆的so,混淆的so,得到了如下对应关系,这里纯是体力活,给我看得两眼冒金星,差点原地飞升。
观察了一下,这里的函数名和混淆后的名称长度是一致的,而且前面都是HTP开头,后面的不知道采用了啥加密方式,其实有种思路就是去hook这个混淆的函数偏移,然后打印调用栈,看看会不会有符号解密的地方,然后看看能不能还原它的加密方式,这样我通过未混淆的API经过加密函数不就拿到了混淆的函数了。
备注:文章发表后,@breaklink和@安卓逆向test两位大佬在评论区中提示,il2cpp的导出函数最终都会被libunity.so调用,这里大家可以dump出来libunity.so,和上面同样的思路进行修复,对比自己编译的libunity.so,导出函数的对应关系便一目了然了。
到了这里其实已经可以用上面提到的find_il2cpp_api2.js这个脚本去结合frida-il2cpp-bridge来使用了,不过我实操之后发现这两脚本能导出东西,但是打开一看,毛也没有,差点当场吐血。

这时候我又想起来前面导出的glabal-metadata.dat比原本的小了很多,所以我怀疑dump出来的不完整甚至不对,导致Il2CppDumper解析不出来dump.cs。兜兜转转又给我干会原地。
我又回去看原本的glabal-metadata.dat,是这样的。也是HTP开头,和混淆函数名的开头一致,可能一样算法,也可能是加固厂商特有标识。

头部信息不是标准的AF 1B B1 FA。大概率魔改了结构或者加密了。
根据il2cpp的源码可以知道,最终会根据LoadMetaDataFile这个函数去加载glabal-metadata.dat。核心的逻辑如下,

拿着这个关键字global-metadata.dat直接去混淆后的so里来搜索字符串,IDA可以通过Shift+F12生成字符串表,进而查看这个字符串的交叉引用,很容易就找到了这个加载的位置,其实这里如果对抗的话可以把这个global-metadata.dat隐藏加密一下,这样定位起来就又只能通过汇编特征码了。

这段就是加载global-metadata.dat的逻辑了

其实到了这里,只需要去hook函数sub_1238164,就可以拿到加载之后的文件了,而且经过判断,后续直接对文件头进行解析了,也没有看到可疑的解密操作。所以可以确定这里的global-metadata.dat即使被加密了,也是解密过后的,否则无法进行函数头的解析。脚本如下,
这里Il2CppGlobalMetadataHeader我是看源码下定义的,如果遇到了头部被魔改的情况,那可能需要进一步跟踪sub_1238164的解析函数头部分,我对比了一下原版so和混淆的so之后,发现解析部分是没有差异的,所以确定头部没有被魔改。

之后就是拿到了导出之后的global-metadata-decrypted.dat文件了,和源文件对比如下。

此时,拿到了修复之后的.so和global-metadata.dat,送入Il2CppDumper,弯弯绕绕还是拿到了导出符号表,如下。stringliteral.json和script.json这个可以结合Il2CppDumper-win-v6.7.46目录下的ida.py和ida_with_struct.py来帮助IDA进行符号解析。

去dump.cs里面翻一下,尝试搜索一些hero,attack,这类可能的关键字,又是毛也没有,怀疑人生,游戏逻辑藏哪去了?至于DummyDll目录下的就是Csharp写的了,可以通过dnSpy这类工具去反编译,不过游戏逻辑不在里面,就没去看了。
lib目录下还有一个so是libtolua.so,之前一直没关注它,后来我去搜了一下Unity游戏开发,尝试从开发者的视角来看,找到了文章如下:[讨论][原创]安卓lua解密——opcode修改后dump反编译-Android安全-看雪-安全社区|安全招聘|kanxue.com
原来如此,网游为了方便实现热更,游戏逻辑写到了Lua里面。这套娃给我套得已经一愣一愣的,那就是又要去dump这个lua脚本呗,通过上面的文章知道lual_loadbuffer用来加载lua的脚本,那直接hook它就可以了。到这里其实上面反编译Csharp的dump.cs也起到了作用,我们在dump.cs里面去搜索lual_loadbuffer,找到了两个函数。这个可能在IDA里面也能搜到,因为我是把反编译之后的字符串通过ida.py和ida_with_struct.py重建过后去搜的,不知道如果不重建能不能搜到。
根据这个偏移去IDA里面按G跳转到指定偏移位置,tolua_loadbuffer如下,第一个参数是lua虚拟机句柄,第二个参数是文件字节流,第三个参数是文件大小,第四个参数是文件名,第五个参数是方法名。

luaL_loadbuffer如下,参数和tolua_loadbuffer一致。

这里hook函数tolua_loadbuffer和luaL_loadbuffer都行
这里留意第一个点,dataStart读取的位置偏移了0x20,如果不进行偏移,会发现dump出来头部多了0x20字节,会影响后面的lua反编译。对应如下

另外name是加密过的,解密逻辑是下面框起来的函数

v5是name,之后通过sub_123BE38处理得到v10,然后调用qword_3153C60,这里qword_3153C60其实是通过上面那个__fastcall来延迟加载的一个函数指针。也懒得去研究它怎么解密的了,直接根据这个函数签名自己构建一个NativeFunction函数在js里主动调用解密。也就是decrypt_name的逻辑。
到这里,其实所有的lua字节码已经拿到了,大致如下

不过到了这里还是没有结束,这玩意儿也是编译之后的bytecode,送入lua虚拟机后可以直接执行,但是你直接看肯定看不懂,这里还是看一下这个字节码的结构。

这里1B 4C 4A开头的是LuaJit编译的特征码

另外还有一个Luac编译器编译后的字节码是这样的,魔术为1B 4C 75 61

如果是Luajit的字节码,需要通过marsinator358/luajit-decompiler-v2: LuaJIT bytecode decompiler (github.com)进行反编译,如果是Luac的通过unluac_2023_12_24.jar反编译。我这里用的luajit-decompiler-v2。这玩意没法批量反编译,搞个脚本反编译,保持原有目录结构。这里如果是魔改的字节码,那么可以参照这篇文章:[原创]对一手游的自定义 luajit 字节码的研究-Android安全-看雪-安全社区|安全招聘|kanxue.com。
libtolua.so相关资料
unity tolua_mob64ca14173efa的技术博客_51CTO博客
topameng/tolua_runtime: tolua runtime library (github.com)
unity3d中交互对话系统 unity游戏交互_mob64ca1401464d的技术博客_51CTO博客
最后拿到Lua源码如下

其实已经能够看到sproto_crypt就是这游戏的数据协议加解密了,翻一翻这个目录,通过sproto这类关键字,搜索得到 cloudwu/sproto: Yet another protocol library like google protocol buffers , but simple and fast. (github.com),本质就是将rpc传输的数据进行序列化和反序列化。结合文章开头的hook网络sendto函数,不难解析出游戏协议,感兴趣的可以往下研究。
到了这里已经耗费了一周时间,至于一刀999,目前大部分网游都是服务器端数据为准,改了本地意义也不大。至于脱机,可以说是外挂的终极形态,相当于用其它语言自己实现一套客户端,然后根据资源数量或者事件做一些自动化的决策来和服务端通信,整体的复杂度和自己开发一套端游差不多了,肝疼。
本文主要介绍了当前Unity+Il2cpp+Lua实现热更新游戏的逆向思路,包含Frida反调试绕过,so的dump和修复,global-metadata.dat的dump和修复,Lua脚本的dump和反编译。逆向和安全的对抗始终是魔高一尺,道高一丈,文中很多思路和工具都是站在巨人的肩膀上,长路漫漫,还需上下求索。
Java.perform(function () {
var targetClass = "com.example.TargetClass";
var targetMethod = "methodToHook";
var cls = Java.use(targetClass);
var overloads = cls[targetMethod].overloads;
for (var i = 0; i < overloads.length; i++) {
overloads[i].implementation = function () {
try {
console.log(`\n[+] Called ${targetClass}.${targetMethod}`);
console.log("Arguments:", JSON.stringify(arguments));
var Exception = Java.use("java.lang.Exception");
var e = Exception.$new("Frida Hook Stack Trace");
console.log("Call Stack:");
console.log(Java.use("android.util.Log").getStackTraceString(e));
throw e;
} catch (err) {
console.error("Hook Error:", err);
}
};
}
console.log(`[+] Hooked ${targetClass}.${targetMethod}`);
});
Java.perform(function () {
var targetClass = "com.example.TargetClass";
var targetMethod = "methodToHook";
var cls = Java.use(targetClass);
var overloads = cls[targetMethod].overloads;
for (var i = 0; i < overloads.length; i++) {
overloads[i].implementation = function () {
try {
console.log(`\n[+] Called ${targetClass}.${targetMethod}`);
console.log("Arguments:", JSON.stringify(arguments));
var Exception = Java.use("java.lang.Exception");
var e = Exception.$new("Frida Hook Stack Trace");
console.log("Call Stack:");
console.log(Java.use("android.util.Log").getStackTraceString(e));
throw e;
} catch (err) {
console.error("Hook Error:", err);
}
};
}
console.log(`[+] Hooked ${targetClass}.${targetMethod}`);
});
function hook_sendto() {
const sendto = Module.findExportByName('libc.so', 'sendto');
Interceptor.attach(sendto, {
onEnter(args) {
this.buf = args[1];
this.size = args[2];
this.data = ptr(this.buf).readByteArray(this.size.toInt32());
console.log('Sendto:', hexdump(this.data));
}
});
}
function hook_sendto() {
const sendto = Module.findExportByName('libc.so', 'sendto');
Interceptor.attach(sendto, {
onEnter(args) {
this.buf = args[1];
this.size = args[2];
this.data = ptr(this.buf).readByteArray(this.size.toInt32());
console.log('Sendto:', hexdump(this.data));
}
});
}
adb shell getprop ro.product.cpu.abi
adb shell getprop ro.product.cpu.abi
adb push rusda-server-android-arm64 /data/local/tmp/
adb shell
su
chmod +x rusda-server-android-arm64
./rusda-server-android-arm64 -l 0.0.0.0:54321 -D
ps -A | grep rusda
haydn:/data/local/tmp
root 5469 1 2237164 50516 do_sys_poll 0 S rusda-server-android-arm64
kill -9 5469
adb push rusda-server-android-arm64 /data/local/tmp/
adb shell
su
chmod +x rusda-server-android-arm64
./rusda-server-android-arm64 -l 0.0.0.0:54321 -D
ps -A | grep rusda
haydn:/data/local/tmp
root 5469 1 2237164 50516 do_sys_poll 0 S rusda-server-android-arm64
kill -9 5469
pip uninstall frida frida-tools
pip install frida==16.2.1
pip install frida-tools=12.3.0
frida --version
16.2.1
pip uninstall frida frida-tools
pip install frida==16.2.1
pip install frida-tools=12.3.0
frida --version
16.2.1
frida -H 192.168.0.101:54321 -f com.example.package -l anti_frida.js
frida -H 192.168.0.101:54321 -f com.example.package -l anti_frida.js
function hook_dlopen(){
const android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
if(android_dlopen_ext) {
Interceptor.attach(android_dlopen_ext, {
onEnter: function(args) {
const pathptr = args[0];
if(pathptr != null && pathptr != undefined) {
const path = ptr(pathptr).readCString();
if(path != null && path.indexOf("libmsaoaidsec.so") != -1){
console.log(`android_dlopen_ext >>> load ${path}`);
hook_call_constructors_libmsaoaidsec();
}
}
},
onLeave: function() {
}
});
}
}
function hook_call_constructors_libmsaoaidsec() {
let linker = null;
if (Process.pointerSize === 4) {
linker = Process.findModuleByName("linker");
} else {
linker = Process.findModuleByName("linker64");
}
let call_constructors_addr, get_soname
let symbols = linker.enumerateSymbols();
for (let index = 0; index < symbols.length; index++) {
let symbol = symbols[index];
if (symbol.name === "__dl__ZN6soinfo17call_constructorsEv") {
call_constructors_addr = symbol.address;
} else if (symbol.name === "__dl__ZNK6soinfo10get_sonameEv") {
get_soname = new NativeFunction(symbol.address, "pointer", ["pointer"]);
}
}
var listener = Interceptor.attach(call_constructors_addr, {
onEnter: function (args) {
var module = Process.findModuleByName("libmsaoaidsec.so")
if (module != null) {
Interceptor.replace(module.base.add(0x1c544), new NativeCallback(function () {
console.log("0x1c544:替换成功")
}, "void", []))
Interceptor.replace(module.base.add(0x1b8d4), new NativeCallback(function () {
console.log("0x1b8d4:替换成功")
}, "void", []))
Interceptor.replace(module.base.add(0x26e5c), new NativeCallback(function () {
console.log("0x26e5c:替换成功")
}, "void", []))
listener.detach();
}
},
onLeave: function(retvel) {
}
})
}
function hook_dlopen(){
const android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
if(android_dlopen_ext) {
Interceptor.attach(android_dlopen_ext, {
onEnter: function(args) {
const pathptr = args[0];
if(pathptr != null && pathptr != undefined) {
const path = ptr(pathptr).readCString();
if(path != null && path.indexOf("libmsaoaidsec.so") != -1){
console.log(`android_dlopen_ext >>> load ${path}`);
hook_call_constructors_libmsaoaidsec();
}
}
},
onLeave: function() {
}
});
}
}
function hook_call_constructors_libmsaoaidsec() {
let linker = null;
if (Process.pointerSize === 4) {
linker = Process.findModuleByName("linker");
} else {
linker = Process.findModuleByName("linker64");
}
let call_constructors_addr, get_soname
let symbols = linker.enumerateSymbols();
for (let index = 0; index < symbols.length; index++) {
let symbol = symbols[index];
if (symbol.name === "__dl__ZN6soinfo17call_constructorsEv") {
call_constructors_addr = symbol.address;
} else if (symbol.name === "__dl__ZNK6soinfo10get_sonameEv") {
get_soname = new NativeFunction(symbol.address, "pointer", ["pointer"]);
}
}
var listener = Interceptor.attach(call_constructors_addr, {
onEnter: function (args) {
var module = Process.findModuleByName("libmsaoaidsec.so")
if (module != null) {
Interceptor.replace(module.base.add(0x1c544), new NativeCallback(function () {
console.log("0x1c544:替换成功")
}, "void", []))
Interceptor.replace(module.base.add(0x1b8d4), new NativeCallback(function () {
console.log("0x1b8d4:替换成功")
}, "void", []))
Interceptor.replace(module.base.add(0x26e5c), new NativeCallback(function () {
console.log("0x26e5c:替换成功")
}, "void", []))
listener.detach();
}
},
onLeave: function(retvel) {
}
})
}
function dump_so(so_name) {
Java.perform(function () {
var libso = Process.getModuleByName(so_name);
console.log("[name]:", libso.name);
console.log("[base]:", libso.base);
console.log("[size]:", ptr(libso.size));
console.log("[path]:", libso.path);
var file_path = "/sdcard/Download/" + libso.name + "_" + libso.base + "_" + ptr(libso.size) + ".so";
var file_handle = new File(file_path, "wb");
if (file_handle && file_handle != null) {
Memory.protect(ptr(libso.base), libso.size, 'r--');
var libso_buffer = ptr(libso.base).readByteArray(libso.size);
file_handle.write(libso_buffer);
file_handle.flush();
file_handle.close();
console.log("[dump]:", file_path);
}
});
}
function dump_so(so_name) {
Java.perform(function () {
var libso = Process.getModuleByName(so_name);
console.log("[name]:", libso.name);
console.log("[base]:", libso.base);
console.log("[size]:", ptr(libso.size));
console.log("[path]:", libso.path);
var file_path = "/sdcard/Download/" + libso.name + "_" + libso.base + "_" + ptr(libso.size) + ".so";
var file_handle = new File(file_path, "wb");
if (file_handle && file_handle != null) {
Memory.protect(ptr(libso.base), libso.size, 'r--');
var libso_buffer = ptr(libso.base).readByteArray(libso.size);
file_handle.write(libso_buffer);
file_handle.flush();
file_handle.close();
console.log("[dump]:", file_path);
}
});
}
function get_self_process_name()
{
var openPtr = Module.getExportByName('libc.so', 'open');
var open = new NativeFunction(openPtr, 'int', ['pointer', 'int']);
var readPtr = Module.getExportByName("libc.so", "read");
var read = new NativeFunction(readPtr, "int", ["int", "pointer", "int"]);
var closePtr = Module.getExportByName('libc.so', 'close');
var close = new NativeFunction(closePtr, 'int', ['int']);
var path = Memory.allocUtf8String("/proc/self/cmdline");
var fd = open(path, 0);
if (fd != -1)
{
var buffer = Memory.alloc(0x1000);
var result = read(fd, buffer, 0x1000);
close(fd);
result = ptr(buffer).readCString();
return result;
}
return "-1";
}
function frida_Memory()
{
Java.perform(function ()
{
var pattern = "AF 1B B1 FA 18";
console.log("头部标识:" + pattern);
var addrArray = Process.enumerateRanges("r--");
for (var i = 0; i < addrArray.length; i++)
{
var addr = addrArray[i];
Memory.scan(addr.base, addr.size, pattern,
{
onMatch: function (address, size)
{
console.log('搜索到 ' + pattern + " 地址是:" + address.toString());
console.log(hexdump(address,
{
offset: 0,
length: 0x110,
header: true,
ansi: true
}
));
var DefinitionsOffset = parseInt(address, 16) + 0x108;
var DefinitionsOffset_size = Memory.readInt(ptr(DefinitionsOffset));
var DefinitionsCount = parseInt(address, 16) + 0x10C;
var DefinitionsCount_size = Memory.readInt(ptr(DefinitionsCount));
if (DefinitionsCount_size < 10)
{
DefinitionsOffset = parseInt(address, 16) + 0x100;
DefinitionsOffset_size = Memory.readInt(ptr(DefinitionsOffset));
DefinitionsCount = parseInt(address, 16) + 0x104;
DefinitionsCount_size = Memory.readInt(ptr(DefinitionsCount));
}
var global_metadata_size = DefinitionsOffset_size + DefinitionsCount_size
console.log("大小:", global_metadata_size);
var file = new File("/data/data/" + get_self_process_name() + "/global-metadata.dat", "wb");
file.write(Memory.readByteArray(address, global_metadata_size));
file.flush();
file.close();
console.log('路径:' + "/data/data/" + get_self_process_name() + "/global-metadata.dat");
console.log('导出完毕...');
},
onComplete: function ()
{
}
}
);
}
}
);
}
function get_self_process_name()
{
var openPtr = Module.getExportByName('libc.so', 'open');
var open = new NativeFunction(openPtr, 'int', ['pointer', 'int']);
var readPtr = Module.getExportByName("libc.so", "read");
var read = new NativeFunction(readPtr, "int", ["int", "pointer", "int"]);
var closePtr = Module.getExportByName('libc.so', 'close');
var close = new NativeFunction(closePtr, 'int', ['int']);
var path = Memory.allocUtf8String("/proc/self/cmdline");
var fd = open(path, 0);
if (fd != -1)
{
var buffer = Memory.alloc(0x1000);
var result = read(fd, buffer, 0x1000);
close(fd);
result = ptr(buffer).readCString();
return result;
}
return "-1";
}
function frida_Memory()
{
Java.perform(function ()
{
var pattern = "AF 1B B1 FA 18";
console.log("头部标识:" + pattern);
var addrArray = Process.enumerateRanges("r--");
for (var i = 0; i < addrArray.length; i++)
{
var addr = addrArray[i];
Memory.scan(addr.base, addr.size, pattern,
{
onMatch: function (address, size)
{
console.log('搜索到 ' + pattern + " 地址是:" + address.toString());
console.log(hexdump(address,
{
offset: 0,
length: 0x110,
header: true,
ansi: true
}
));
var DefinitionsOffset = parseInt(address, 16) + 0x108;
var DefinitionsOffset_size = Memory.readInt(ptr(DefinitionsOffset));
var DefinitionsCount = parseInt(address, 16) + 0x10C;
var DefinitionsCount_size = Memory.readInt(ptr(DefinitionsCount));
if (DefinitionsCount_size < 10)
{
DefinitionsOffset = parseInt(address, 16) + 0x100;
DefinitionsOffset_size = Memory.readInt(ptr(DefinitionsOffset));
DefinitionsCount = parseInt(address, 16) + 0x104;
DefinitionsCount_size = Memory.readInt(ptr(DefinitionsCount));
}
var global_metadata_size = DefinitionsOffset_size + DefinitionsCount_size
console.log("大小:", global_metadata_size);
var file = new File("/data/data/" + get_self_process_name() + "/global-metadata.dat", "wb");
file.write(Memory.readByteArray(address, global_metadata_size));
file.flush();
file.close();
console.log('路径:' + "/data/data/" + get_self_process_name() + "/global-metadata.dat");
console.log('导出完毕...');
},
onComplete: function ()
{
}
}
);
}
}
);
}
import struct
def fix_elf_header(input_file, output_file):
e_ident = bytes.fromhex('7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00')
e_type = 3
e_machine = 183
e_version = 1
e_entry = 0
e_phoff = 64
e_shoff = 0
e_flags = 0
e_ehsize = 64
e_phentsize = 0
e_phnum = 7
e_shentsize = 0
e_shnum = 0
e_shstrndx = 0
elf_header = struct.pack(
'<16s'
'H'
'H'
'I'
'Q'
'Q'
'Q'
'I'
'H'
'H'
'H'
'H'
'H'
'H'
,
e_ident,
e_type,
e_machine,
e_version,
e_entry,
e_phoff,
e_shoff,
e_flags,
e_ehsize,
e_phentsize,
e_phnum,
e_shentsize,
e_shnum,
e_shstrndx
)
with open(input_file, 'rb') as f:
data = f.read()
if len(data) < e_ehsize:
raise ValueError("File is smaller than ELF header size")
new_data = elf_header + data[e_ehsize:]
with open(output_file, 'wb') as f:
f.write(new_data)
if __name__ == "__main__":
import sys
if len(sys.argv) != 3:
print("Usage: python fix_elf_header.py <input_dump.so> <output_fixed.so>")
sys.exit(1)
input_file = sys.argv[1]
output_file = sys.argv[2]
fix_elf_header(input_file, output_file)
print(f"ELF header repaired. Output saved to {output_file}")
import struct
def fix_elf_header(input_file, output_file):
e_ident = bytes.fromhex('7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00')
e_type = 3
e_machine = 183
e_version = 1
e_entry = 0
e_phoff = 64
e_shoff = 0
e_flags = 0
e_ehsize = 64
e_phentsize = 0
e_phnum = 7
e_shentsize = 0
e_shnum = 0
e_shstrndx = 0
elf_header = struct.pack(
'<16s'
'H'
'H'
'I'
'Q'
'Q'
'Q'
'I'
'H'
'H'
'H'
'H'
'H'
'H'
,
e_ident,
e_type,
e_machine,
e_version,
e_entry,
e_phoff,
e_shoff,
e_flags,
e_ehsize,
e_phentsize,
e_phnum,
e_shentsize,
e_shnum,
e_shstrndx
)
with open(input_file, 'rb') as f:
data = f.read()
if len(data) < e_ehsize:
raise ValueError("File is smaller than ELF header size")
new_data = elf_header + data[e_ehsize:]
with open(output_file, 'wb') as f:
f.write(new_data)
if __name__ == "__main__":
import sys
if len(sys.argv) != 3:
print("Usage: python fix_elf_header.py <input_dump.so> <output_fixed.so>")
sys.exit(1)
input_file = sys.argv[1]
output_file = sys.argv[2]
fix_elf_header(input_file, output_file)
print(f"ELF header repaired. Output saved to {output_file}")
def fix_elf_header(input_file, output_file):
e_ident = bytes.fromhex('7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00')
e_type = 3
e_machine = 183
e_version = 1
e_entry = 0
e_phoff = 64
e_shoff = 0
e_flags = 0
e_ehsize = 64
e_phentsize = 56
e_phnum = 7
e_shentsize = 64
e_shnum = 0
e_shstrndx = 0
def fix_elf_header(input_file, output_file):
e_ident = bytes.fromhex('7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00')
e_type = 3
e_machine = 183
e_version = 1
e_entry = 0
e_phoff = 64
e_shoff = 0
e_flags = 0
e_ehsize = 64
e_phentsize = 56
e_phnum = 7
e_shentsize = 64
e_shnum = 0
e_shstrndx = 0
import idautils
import idc
import json
import binascii
TARGET_FUNCTIONS = [
"il2cpp_method_get_name",
"il2cpp_class_get_name",
"il2cpp_class_get_type",
"il2cpp_class_get_namespace",
"il2cpp_method_get_param",
"il2cpp_method_get_param_count",
"il2cpp_class_from_type"
]
def extract_function_signature(func_ea):
signature = []
func_end = idc.get_func_attr(func_ea, idc.FUNCATTR_END)
ea = func_ea
for _ in range(20):
insn_bytes = idc.get_bytes(ea, idc.get_item_size(ea))
hex_bytes = binascii.hexlify(insn_bytes).decode('utf-8').upper()
hex_bytes = " ".join(hex_bytes[i:i+2] for i in range(0, len(hex_bytes), 2))
signature.append(hex_bytes)
ea = idc.next_head(ea, func_end)
if ea == idc.BADADDR:
break
return signature
def extract_constant_references(func_ea):
constants = set()
for ref in idautils.CodeRefsTo(func_ea, 0):
for head in idautils.Heads(ref, ref + 16):
if idc.print_insn_mnem(head) == "LDR":
op = idc.get_operand_value(head, 1)
if op != idc.BADADDR:
constant = idc.get_strlit_contents(op)
if constant:
constants.add(constant.decode('utf-8', errors='ignore'))
return list(constants)
def main():
signatures = {}
for func_name in TARGET_FUNCTIONS:
func_ea = idc.get_name_ea_simple(func_name)
if func_ea == idc.BADADDR:
print("[-] Function not found: {}".format(func_name))
continue
print("[*] Processing {} at {}".format(func_name, hex(func_ea)))
signature = extract_function_signature(func_ea)
constants = extract_constant_references(func_ea)
signatures[func_name] = {
"address": hex(func_ea),
"signature": signature,
"constants": constants,
"min_length": len(signature)
}
output_path = "E:\python_project\il2cpp-project\original_signatures.json"
with open(output_path, "w") as f:
json.dump(signatures, f, ensure_ascii=False, indent=2)
print("[+] Saved signatures for {} functions to {}".format(len(signatures), output_path))
if __name__ == "__main__":
main()
import idautils
import idc
import json
import binascii
TARGET_FUNCTIONS = [
"il2cpp_method_get_name",
"il2cpp_class_get_name",
"il2cpp_class_get_type",
"il2cpp_class_get_namespace",
"il2cpp_method_get_param",
"il2cpp_method_get_param_count",
"il2cpp_class_from_type"
]
def extract_function_signature(func_ea):
signature = []
func_end = idc.get_func_attr(func_ea, idc.FUNCATTR_END)
ea = func_ea
for _ in range(20):
insn_bytes = idc.get_bytes(ea, idc.get_item_size(ea))
hex_bytes = binascii.hexlify(insn_bytes).decode('utf-8').upper()
hex_bytes = " ".join(hex_bytes[i:i+2] for i in range(0, len(hex_bytes), 2))
signature.append(hex_bytes)
ea = idc.next_head(ea, func_end)
if ea == idc.BADADDR:
break
return signature
def extract_constant_references(func_ea):
constants = set()
for ref in idautils.CodeRefsTo(func_ea, 0):
for head in idautils.Heads(ref, ref + 16):
if idc.print_insn_mnem(head) == "LDR":
op = idc.get_operand_value(head, 1)
if op != idc.BADADDR:
[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!
最后于 2025-10-13 22:15
被G33k3r编辑
,原因: 补充声明