首页
社区
课程
招聘
[原创]记录一款Unity il2cpp lua手游的逆向全过程
发表于: 2025-8-10 17:24 10267

[原创]记录一款Unity il2cpp lua手游的逆向全过程

2025-8-10 17:24
10267

补充声明:请勿用于非法用途,未经本人许可不能转载。

最近在某音刷到了一款叫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-toolsfridafrida-tools就是让我们可以在命令行里使用frida -U -f com.target.app -l hook.js --no-pause这样的命令,frida则是pythonpackage,可以在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.1frida,我的python版本为3.13.3,通过如下命令安装frida 16.2.1,对应frida-tools12.3.0,由于我的电脑上之前安装的没有魔改的frida版本和这个android_server不对应,得先卸载掉,或者新建一套虚拟环境配置也行。

以上便完成了基础的环境搭建,尝试通过电脑端frida连接手机端,结合脚本如下直接就绕过了,笑容又回来了!

anti_frida.js脚本如下,来源于无壳app的libmsaoaidsec.so frida反调试绕过姿势 (qq.com)

接下来便可以的开心的进行hook了,我进入游戏之后,通过MT管理器,查到页面的Activitycom.unity3d.player.DDUnityLaunchActivity,切换了下面的几个页签之后,这个Activity都没有变,因此UI资源和游戏逻辑不在java层面,那么反编译class.dex就没必要了。只能知道是一个unity游戏。

结合Activity名称,lib目录下的.so文件,例如libunity.solibil2cpp.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.soglobal-metadata.dat去反编译得到C#符号表,当然是失败了的。根据文章中的思路,.soglobal-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

正常的soelf_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 00e_type固定为3e_machine固定为183e_version固定为1,程序入口entry暂时不能确定,e_phoff固定为64,也就是0x40,因为elf_program_header_table是紧跟着elf_header的,而elf_header的大小固定为0x40,所以程序头表elf_program_header_table的偏移也是固定的,至于节头表section_header_table相关信息我们现在没法确定,根据这些固定的信息,通过脚本替换前面0x40elf_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对比原版so56

Elf64_Half e_shentsize_SECTION_HEADER_ENTRY_SIZE固定为64

图片描述

手动编辑一下相关字节之后,重新运行ELF.bt,底部多出来了动态符号表dynamic_symbol_table

其实我回头看的时候,发现我们添加头部的脚本里面还可以确定上面手动改的3个参数,调整后如下,这样填充完头部之后,通过SoFixer可以一步到位。

再次尝试通过Il2CppDumper去反编译,尝试得到dump.cs,还是失败,心灰意冷。

不过修复之后,拖入IDA之后可以反编译。至此,我们终于踏出了艰难的一步,拿到了修复之后的libil2cpp.so,由于文件比较大,重建符号表时间比较长。遗留:修复之后的so92.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文件了,和源文件对比如下。

图片描述

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

图片描述

dump.cs里面翻一下,尝试搜索一些heroattack,这类可能的关键字,又是毛也没有,怀疑人生,游戏逻辑藏哪去了?至于DummyDll目录下的就是Csharp写的了,可以通过dnSpy这类工具去反编译,不过游戏逻辑不在里面,就没去看了。

lib目录下还有一个solibtolua.so,之前一直没关注它,后来我去搜了一下Unity游戏开发,尝试从开发者的视角来看,找到了文章如下:[讨论][原创]安卓lua解密——opcode修改后dump反编译-Android安全-看雪-安全社区|安全招聘|kanxue.com

原来如此,网游为了方便实现热更,游戏逻辑写到了Lua里面。这套娃给我套得已经一愣一愣的,那就是又要去dump这个lua脚本呗,通过上面的文章知道lual_loadbuffer用来加载lua的脚本,那直接hook它就可以了。到这里其实上面反编译Csharpdump.cs也起到了作用,我们在dump.cs里面去搜索lual_loadbuffer,找到了两个函数。这个可能在IDA里面也能搜到,因为我是把反编译之后的字符串通过ida.pyida_with_struct.py重建过后去搜的,不知道如果不重建能不能搜到。

根据这个偏移去IDA里面按G跳转到指定偏移位置,tolua_loadbuffer如下,第一个参数是lua虚拟机句柄,第二个参数是文件字节流,第三个参数是文件大小,第四个参数是文件名,第五个参数是方法名。

图片描述

luaL_loadbuffer如下,参数和tolua_loadbuffer一致。

图片描述

这里hook函数tolua_loadbufferluaL_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);
     
    // Hook目标方法(处理重载方法)
    var overloads = cls[targetMethod].overloads;
    for (var i = 0; i < overloads.length; i++) {
        overloads[i].implementation = function () {
            try {
                // 1. 打印方法调用信息
                console.log(`\n[+] Called ${targetClass}.${targetMethod}`);
                console.log("Arguments:", JSON.stringify(arguments));
                 
                // 2. 创建异常对象获取调用栈
                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));
                 
                // 3. 主动抛出异常
                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);
     
    // Hook目标方法(处理重载方法)
    var overloads = cls[targetMethod].overloads;
    for (var i = 0; i < overloads.length; i++) {
        overloads[i].implementation = function () {
            try {
                // 1. 打印方法调用信息
                console.log(`\n[+] Called ${targetClass}.${targetMethod}`);
                console.log("Arguments:", JSON.stringify(arguments));
                 
                // 2. 创建异常对象获取调用栈
                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));
                 
                // 3. 主动抛出异常
                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 #这里自定义端口号,防止反Frida检测特征端口 -l指定监听地址 -D表示后台运行
ps -A | grep rusda #查看后台进程
 
# 输出如下
haydn:/data/local/tmp # ps -A | grep rusda
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 #这里自定义端口号,防止反Frida检测特征端口 -l指定监听地址 -D表示后台运行
ps -A | grep rusda #查看后台进程
 
# 输出如下
haydn:/data/local/tmp # ps -A | grep rusda
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 #-H指定远程Host的ip和端口 因为我是无线调式 usb调试的用-U -f指定包名
frida -H 192.168.0.101:54321 -f com.example.package -l anti_frida.js #-H指定远程Host的ip和端口 因为我是无线调式 usb调试的用-U -f指定包名
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"]);
        }
    }
    // console.log(call_constructors_addr)
    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"]);
        }
    }
    // console.log(call_constructors_addr)
    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";//global-metadata.dat头部特征
        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
                            }
                        ));
                        //0x108,0x10C如果不行,换 0x100,0x104
                        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));
                        }
                        //根据两个偏移得出global-metadata大小
                        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 ()
                    {
                        //console.log("搜索完毕")
                    }
                }
            );
        }
    }
    );
}
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";//global-metadata.dat头部特征
        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
                            }
                        ));
                        //0x108,0x10C如果不行,换 0x100,0x104
                        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));
                        }
                        //根据两个偏移得出global-metadata大小
                        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 ()
                    {
                        //console.log("搜索完毕")
                    }
                }
            );
        }
    }
    );
}
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          # ET_DYN (共享对象文件)
    e_machine = 183     # EM_AARCH64 (arm64)
    e_version = 1
    e_entry = 0         # 程序入口
    e_phoff = 64        # 程序头表起始位置
    e_shoff = 0         # 节头表起始位置
    e_flags = 0
    e_ehsize = 64       # ELF头大小
    e_phentsize = 0    # 程序头大小
    e_phnum = 7        # 程序头数量
    e_shentsize = 0    # 节头大小
    e_shnum = 0        # 节头数量
    e_shstrndx = 0     # 节头字符串表索引
 
    # 构建ELF头结构 (小端序)
    elf_header = struct.pack(
        '<16s'    # e_ident
        'H'        # e_type
        'H'        # e_machine
        'I'        # e_version
        'Q'        # e_entry
        'Q'        # e_phoff
        'Q'        # e_shoff
        'I'        # e_flags
        'H'        # e_ehsize
        'H'        # e_phentsize
        'H'        # e_phnum
        'H'        # e_shentsize
        'H'        # e_shnum
        'H'        # e_shstrndx
        ,
         
        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
    )
     
    # 读取原始dump文件
    with open(input_file, 'rb') as f:
        data = f.read()
     
    # 检查文件长度是否足够
    if len(data) < e_ehsize:
        raise ValueError("File is smaller than ELF header size")
     
    # 将新ELF头与原始内容组合 (替换前64字节)
    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          # ET_DYN (共享对象文件)
    e_machine = 183     # EM_AARCH64 (arm64)
    e_version = 1
    e_entry = 0         # 程序入口
    e_phoff = 64        # 程序头表起始位置
    e_shoff = 0         # 节头表起始位置
    e_flags = 0
    e_ehsize = 64       # ELF头大小
    e_phentsize = 0    # 程序头大小
    e_phnum = 7        # 程序头数量
    e_shentsize = 0    # 节头大小
    e_shnum = 0        # 节头数量
    e_shstrndx = 0     # 节头字符串表索引
 
    # 构建ELF头结构 (小端序)
    elf_header = struct.pack(
        '<16s'    # e_ident
        'H'        # e_type
        'H'        # e_machine
        'I'        # e_version
        'Q'        # e_entry
        'Q'        # e_phoff
        'Q'        # e_shoff
        'I'        # e_flags
        'H'        # e_ehsize
        'H'        # e_phentsize
        'H'        # e_phnum
        'H'        # e_shentsize
        'H'        # e_shnum
        'H'        # e_shstrndx
        ,
         
        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
    )
     
    # 读取原始dump文件
    with open(input_file, 'rb') as f:
        data = f.read()
     
    # 检查文件长度是否足够
    if len(data) < e_ehsize:
        raise ValueError("File is smaller than ELF header size")
     
    # 将新ELF头与原始内容组合 (替换前64字节)
    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          # ET_DYN (共享对象文件)
    e_machine = 183     # EM_AARCH64 (arm64)
    e_version = 1
    e_entry = 0         # 程序入口
    e_phoff = 64        # 程序头表起始位置
    e_shoff = 0         # 节头表起始位置
    e_flags = 0
    e_ehsize = 64       # ELF头大小
    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          # ET_DYN (共享对象文件)
    e_machine = 183     # EM_AARCH64 (arm64)
    e_version = 1
    e_entry = 0         # 程序入口
    e_phoff = 64        # 程序头表起始位置
    e_shoff = 0         # 节头表起始位置
    e_flags = 0
    e_ehsize = 64       # ELF头大小
    e_phentsize = 56    # 程序头大小
    e_phnum = 7        # 程序头数量
    e_shentsize = 64    # 节头大小
    e_shnum = 0        # 节头数量
    e_shstrndx = 0     # 节头字符串表索引
# -*- coding: utf-8 -*-
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)
 
    # 遍历函数前20条指令
    ea = func_ea
    for _ in range(20):
        # 获取指令字节
        insn_bytes = idc.get_bytes(ea, idc.get_item_size(ea))
         
        # 使用 binascii 将字节串转换为十六进制字符串
        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:  # Removed encoding parameter
        json.dump(signatures, f, ensure_ascii=False, indent=2)
     
    print("[+] Saved signatures for {} functions to {}".format(len(signatures), output_path))
 
if __name__ == "__main__":
    main()
# -*- coding: utf-8 -*-
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)
 
    # 遍历函数前20条指令
    ea = func_ea
    for _ in range(20):
        # 获取指令字节
        insn_bytes = idc.get_bytes(ea, idc.get_item_size(ea))
         
        # 使用 binascii 将字节串转换为十六进制字符串
        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编辑 ,原因: 补充声明
收藏
免费 37
支持
分享
最新回复 (47)
雪    币: 9152
活跃值: (5157)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2

导出函数最终都会被unity调用来着,所以直接找一个原版的unity对照字符串看一下就知道了

当然我也只遇到过一次,用这种方法可以解决

2025-8-10 18:34
2
雪    币: 494
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
breaklink 导出函数最终都会被unity调用来着,所以直接找一个原版的unity对照字符串看一下就知道了当然我也只遇到过一次,用这种方法可以解决

没错,我去看了下这个游戏,将libunity.so dump下并修复找到sub_6D3494函数就能看到所有的il2cpp api

最后于 2025-8-10 19:40 被安卓逆向test编辑 ,原因:
2025-8-10 19:40
1
雪    币: 734
活跃值: (811)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
4
安卓逆向test breaklink 导出函数最终都会被unity调用来着,所以直接找一个原版的unity对照字符串看一下就知道了当然我也只遇到过一次,用这 ...
大佬来了 荣幸 回去看了一下确实 如果顺序和原版一致的话 直接就对上了 想起来一个个找就肝疼
2025-8-10 21:26
0
雪    币: 1568
活跃值: (2026)
能力值: ( LV12,RANK:229 )
在线值:
发帖
回帖
粉丝
5

signature 应该还需要再处理一下重定位的问题才有机会匹配成功。

拿着结果去手动对比验证了一下结果,发现不太准,取特征码和匹配的方式也有点讲究,大家感兴趣可以改下脚本,这个脚本还是有点呆了。


关于 sproto 协议的设计部分:

云风的 BLOG

设计一种简化的 protocol buffer 协议
c64K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1L8r3!0Y4i4K6u0W2j5$3!0V1K9h3&6Y4L8X3!0%4i4K6u0W2j5$3!0E0i4K6u0r3x3U0l9I4y4q4)9J5c8U0l9%4i4K6u0r3k6h3A6G2P5i4m8J5L8%4c8G2i4K6u0W2K9s2c8E0L8l9`.`.

最后于 2025-8-10 21:57 被zZhouQing编辑 ,原因:
2025-8-10 21:53
1
雪    币: 200
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
哈哈,lua他没自定义字节码,不然你至少还得整三天,顺便告诉你LuaJIT Decompiler v2可以直接拖文件夹到exe就行,自动批量跑
2025-8-10 23:07
1
雪    币: 144
活跃值: (1993)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
666666666
2025-8-10 23:10
0
雪    币: 734
活跃值: (811)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
8
Zepp7289 哈哈,lua他没自定义字节码,不然你至少还得整三天,顺便告诉你LuaJIT Decompiler v2可以直接拖文件夹到exe就行,自动批量跑
哈哈 原来如此 我以为他工具不支持嘞
2025-8-11 01:10
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
9
66666
2025-8-11 05:46
0
雪    币: 545
活跃值: (4304)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
tql,学习一下
2025-8-11 09:45
0
雪    币: 309
活跃值: (1331)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
mark
2025-8-11 09:56
0
雪    币: 19
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
12
mark
2025-8-11 10:12
0
雪    币: 5902
活跃值: (6628)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
13
tql,学习一下
2025-8-11 11:14
0
雪    币: 734
活跃值: (811)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
14
2beNo2 tql,学习一下
看了佬的文章也很强啊
2025-8-11 12:38
0
雪    币: 1353
活跃值: (6047)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
15
用lua dump还原打乱的opcode是一种方法,有些网游如果不提供lua dump函数接口,可以用triton识别出lua opcode
大概原理是
cf0K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6S2M7X3y4Z5k6i4u0U0M7X3g2S2N6q4)9J5c8Y4c8A6N6r3q4F1
这种,过一阵我写篇文章来详细说明
2025-8-11 19:16
3
雪    币: 734
活跃值: (811)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
16
qj111111 用lua dump还原打乱的opcode是一种方法,有些网游如果不提供lua dump函数接口,可以用triton识别出lua opcode 大概原理是 077K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6S2M7R3`.`. ...
期待你的文章 到时候@我 学习一下
2025-8-11 20:51
0
雪    币: 575
活跃值: (1395)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
qj111111 用lua dump还原打乱的opcode是一种方法,有些网游如果不提供lua dump函数接口,可以用triton识别出lua opcode 大概原理是 b19K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6S2M7R3`.`. ...
同期待,更新求踢
2025-8-11 22:41
0
雪    币: 137
活跃值: (1626)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
18
无尽冬日这个游戏可以换个方式,从微信小程序入手,解密wasm然后提取dll。再修复下偏移,拿到完整的解密。
2025-8-12 11:00
2
雪    币: 289
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
19
看爽了
2025-8-12 13:36
0
雪    币: 751
活跃值: (1797)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
大佬太厉害了
2025-8-12 16:56
0
雪    币: 734
活跃值: (811)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
21
Light紫星 大佬太厉害了
谦卑求学
2025-8-12 18:39
0
雪    币: 734
活跃值: (811)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
22
阿杨` 看爽了
你能看得懂也不简单
2025-8-12 18:39
1
雪    币: 734
活跃值: (811)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
23
胡家二少 无尽冬日这个游戏可以换个方式,从微信小程序入手,解密wasm然后提取dll。再修复下偏移,拿到完整的解密。
有空研究下 
2025-8-12 18:40
0
雪    币: 104
活跃值: (7189)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
24
6666
2025-8-13 13:37
0
雪    币: 289
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
25
G33k3r 你能看得懂也不简单
看得懂,不会写。拿来用的话没啥问题。哈哈哈哈  之前接了个单子,都没接触过,找帖子,拼在一块,硬生生是把lua解密出来,然后自己修改一份符合要求的lua,然后替换资源,实现的功能 大佬确实np
2025-8-14 12:44
1
游客
登录 | 注册 方可回帖
返回