使用Frida Il2Cpp Bridge 方便地进行Il2Cpp实例Hook

查看 128|回复 10
作者:hitachimako   
前言
近日在逆向某安卓手游的数据时,发现其AssetBundle、网络请求均使用AES加密。
使用Frida Il2Cpp Bridge工具可在仅使用已知名称的情况下,方便地实现对某个可实例化类型的实例钩取,查看其内部函数的调用参数和结果。
工具
将il2cpp lib和metadata.dat dump为dll结构的工具:il2cpp-dumper
Frida以及Frida server:Github
Frida Il2Cpp Bridge:Github
dnSpy:Github
IDA Pro 7.7:百度网盘
NodeJS:官方网站
Python:官方网站
使用环境
PC:Windows 11
手机:Mi 8
Root:Magisk 27.0
实战
对APK的预操作
[ol]
  • 使用解压软件解压APK,并找到/lib/arm64-v8a/libil2cpp.so和/assets/bin/Data/Managed/Metadata/metadata.dat。
  • 启动il2cpp-dumper,依次选中libil2cpp.so和metadata.dat,等候其生成dump出的文件。我们在此仅关注DummyDll文件夹中的Dll结构。
  • 使用IDA(在此选用7.7版本,因为最新的8.3版本并不支持对arm64的反汇编)导入libil2cpp.so,并使用工具栏中的File/Script file...选取il2cpp-dumper目录下的ida_with_struct_py3.py,依次选择对应的文件即可将名称信息导入IDA。
    [/ol]
    使用IDA进行初步分析
    经过一段时间的分析,发现目标程序对Unity.ResourceManager进行魔改,并加入了SecurityStreams类进行加密:


    图片1.jpg (62.2 KB, 下载次数: 0)
    下载附件
    2024-2-21 12:45 上传

    接着,打开IDA定位到此函数,并寻找其函数的调用,发现如下调用:


    图片2.jpg (60.55 KB, 下载次数: 0)
    下载附件
    2024-2-21 12:46 上传

    由此可知,IV通过文件名计算而来,Key通过ToArray函数而来,但此函数有5000多行,很难分析参数v307的结构和其偏移对应的值。
    调用Decrypt函数的时候需要传入Key和IV,因此钩取该函数的调用参数值成为了最佳选择。
    使用Frida和Frida Il2Cpp Bridge进行Hook
    配置Frida环境
    [ol]
  • 使用带有Root手机开启adb,使用adb push frida-server-xx.x.x-android-arm64 /data/local/tmp将其置入手机
  • 使用adb shell进入命令行, su命令获取root权限,进入路径cd /data/local/tmp,为可执行文件添加权限chmod +x ./frida-server-xx.x.x-android-arm64然后启动可执行程序./frida-server-xx.x.x-android-arm64
  • 在电脑上安装Fridapip install frida frida-tools
    [/ol]
    配置Il2Cpp Bridge环境
    创建文件夹并创建文件index.ts编写package.json,使其识别index.ts为源代码,编译出frida可用的js。
    {
      "main": "index.ts",
      "scripts": {
        "prepare": "npm run build",
        "loop": "frida-compile index.ts -w -o frida.js"
      },
      "dependencies": {
        "frida-il2cpp-bridge": "^0.8.8"
      }
    }
    在此目录下打开命令行并运行npm run loop会自动安装所需的库,该命令行会不断检测index.ts是否被修改,然后重新编译。
    接着编辑index.ts,在首行加入import "frida-il2cpp-bridge",可成功编译,环境配置完毕。
    编写Hook代码
    编写一个Hook代码,使其定位Unity.ResourceManager,并Hook默认命名空间中的SecurityStreams类,对其进行Attach。
    Il2Cpp.perform(() => {
            const SecurityStreams = Il2Cpp.domain.assembly("Unity.ResourceManager").image.class("SecurityStreams");
            Il2Cpp.trace(true).classes(SecurityStreams).and().attach();
    });
    进行Attach后,在该类被初始化、实例方法被调用时,能够自动在控制台输出详细参数。
    进行Hook
    当index.ts被自动编译为frida.js完成后,使用frida注入程序。
    frida -U -f app.package.name -l .\frida.js
    进入游戏,函数被自动Hook,在调用Decrypted时,参数被打印于控制台:


    图片3.jpg (127.55 KB, 下载次数: 0)
    下载附件
    2024-2-21 12:46 上传

    由于读取多个bundle时的key一致,因此得出key,由于Unity asset bundle文件的前16字节是基本相同的,因此无需考虑iv造成的前16字节乱码,直接将其定义为可识别的头部即可通过AssetStudio等程序拆出资源文件。
    举一反三
    经过抓包,发现几乎所有网络请求都是被加密的。经过一段时间的搜寻,发现其网络请求函数SendTgServerEncrypted:


    图片4.jpg (46.5 KB, 下载次数: 0)
    下载附件
    2024-2-21 12:46 上传

    但此函数并没有传入key等,由此可见key应该在函数内部进行计算。
    此时打开IDA,对此函数进行分析:


    图片5.jpg (71.16 KB, 下载次数: 0)
    下载附件
    2024-2-21 12:46 上传

    可见NetworkUtils被初始化后直接获取该类的key和otp字段,然后进行密钥计算。由此可知key应该是固定值并在cctor中被赋值。
    因此,对初始化函数进行分析:


    图片6.jpg (225.08 KB, 下载次数: 0)
    下载附件
    2024-2-21 12:46 上传

    由此可见,一些编译器生成的PrivateImplementationDetails偏移值被sub_EDC5C8初始化后才能够变成有效偏移值,因此通过静态分析得出真正的key值极为困难。
    不过我们知道,想要通过AES加密必须初始化一个AES加密类,我们在此发现了游戏函数对该类型的引用:


    图片7.jpg (29.23 KB, 下载次数: 0)
    下载附件
    2024-2-21 12:47 上传

    mscorlib中的System.Security.Cryptography.Aes看起来并没有什么有用字段,通过翻阅一些文档,发现其实际上使用System.Core中的System.Security.Cryptography.AesCryptoServiceProvider.CreateDecryptor创建解密器,而该类型是这样的:


    图片8.jpg (73.79 KB, 下载次数: 0)
    下载附件
    2024-2-21 12:47 上传

    由此可见,我们仅需钩取System.Security.Cryptography.AesCryptoServiceProvider类即可得知CreateDecryptor函数被调用时的参数。
    尝试钩取,特意发送几个网络请求,同时获得以下信息:


    图片9.jpg (290.81 KB, 下载次数: 0)
    下载附件
    2024-2-21 12:47 上传

    使用该密钥,空IV解密,发现前16byte为乱码,但16byte后明文完整。合理猜测response前16byte为IV,后面为密文,使用该逻辑成功解密出明文。
    尾声
    Il2Cpp Bridge还有很多其它实用功能,比如直接定位某函数并通过invoke()调用,获取返回值,若对其感兴趣可以阅读官方wiki。
    本人第一次在论坛发表文章,也是为了记录自己的逆向学习过程,若有不足请大佬多多指出!

    函数, 下载次数

  • Smilience   

            public static string xxxx(string input)
                    {
                            return null;
                    } 对于这种该如何hook呢?这是我写的xxxxt.implementation = function (value: Il2Cpp.String): Il2Cpp.String
    vs报错:Type '(this: Class | Object | ValueType, value: String) => String' is not assignable to type '(this: Class | Object | ValueType, ...parameters: Type[]) => ReturnType'.
      Types of parameters 'value' and 'parameters' are incompatible.
        Type 'Type' is not assignable to type 'String'.
          Type 'number' is not assignable to type 'String'.
    hitachimako
    OP
      


    zouzhiqiang 发表于 2024-2-23 11:09
    frida-bridge就是根据ill2cpp源文件中一些特定的方法,计算方法加载时的偏移地址得到,如果ill2cpp.so文件 ...

    符号表存在于metadata.dat,如果metadata加密,可以先不处理Apk,直接先注入il2cpp-bridge,然后会在默认目录生成无加密的metadata.dat。so混淆也可以通过类似Zygisk-Il2CppDumper这样的工具获取正常so文件。il2cpp-bridge默认通过从内存中获取符号表钩取,一般来说为了方便Bug寻找和维护,不会把原metadata直接进行obf
    xixicoco   

    这个厉害,很好的教程
    yuqic987   

    学习了,现在刚好在追个加密,头都追大了
    Junlee   

    这个厉害,很好的教程
    zouzhiqiang   

    frida-bridge就是根据ill2cpp源文件中一些特定的方法,计算方法加载时的偏移地址得到,如果ill2cpp.so文件中不存在符号表,就难搞咯
    慵懒丶L先森   

    Frida Il2Cpp Bridge工具之前就看到有了,但是找了很久都没有发现使用的教程,感谢分享
    zouzhiqiang   


    hitachimako 发表于 2024-2-23 11:14
    符号表存在于metadata.dat,如果metadata加密,可以先不处理Apk,直接先注入il2cpp-bridge,然后会在默认 ...

    我明白你的意思,metadata元数据文件确实是存放ill2cpp.so文件中的符号表和偏移,一般dump metadata.data文件,都是文件固定头部(AF 1B B1 FA)进行内存检索+ 偏移104(100或者108)个字节得到metadata文件大小,得到原始metadata.dat文件,但如果这些特征都没有呢??比如某神,它做的就比较好,你可以去试试
    阿清   

    多出一些这类教程 真不错
    您需要登录后才可以回帖 登录 | 立即注册

    返回顶部