hitachimako 发表于 2024-2-21 12:48

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

# 前言
近日在逆向某安卓手游的数据时,发现其AssetBundle、网络请求均使用AES加密。
使用Frida Il2Cpp Bridge工具可在仅使用已知名称的情况下,方便地实现对某个可实例化类型的实例钩取,查看其内部函数的调用参数和结果。

## 工具
将il2cpp lib和metadata.dat dump为dll结构的工具:(https://github.com/Perfare/Il2CppDumper/releases)
Frida以及Frida server:(https://github.com/frida/frida/releases)
Frida Il2Cpp Bridge:(https://github.com/vfsfitvnm/frida-il2cpp-bridge/releases)
dnSpy:(https://github.com/dnSpy/dnSpy/releases)
IDA Pro 7.7:[百度网盘](https://pan.baidu.com/s/1_2vKPRmcd3nIzkItymbgoQ?pwd=qejc)
NodeJS:[官方网站](https://nodejs.org/en)
Python:[官方网站](https://www.python.org/)

## 使用环境
PC:Windows 11
手机:Mi 8
Root:Magisk 27.0

# 实战
## 对APK的预操作
1. 使用解压软件解压APK,并找到`/lib/arm64-v8a/libil2cpp.so`和`/assets/bin/Data/Managed/Metadata/metadata.dat`。
2. 启动`il2cpp-dumper`,依次选中`libil2cpp.so`和`metadata.dat`,等候其生成dump出的文件。我们在此仅关注DummyDll文件夹中的Dll结构。
3. 使用IDA(在此选用7.7版本,因为最新的8.3版本并不支持对arm64的反汇编)导入`libil2cpp.so`,并使用工具栏中的`File/Script file...`选取`il2cpp-dumper`目录下的`ida_with_struct_py3.py`,依次选择对应的文件即可将名称信息导入IDA。

## 使用IDA进行初步分析
经过一段时间的分析,发现目标程序对`Unity.ResourceManager`进行魔改,并加入了`SecurityStreams`类进行加密:

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

由此可知,IV通过文件名计算而来,Key通过ToArray函数而来,但此函数有5000多行,很难分析参数v307的结构和其偏移对应的值。
调用Decrypt函数的时候需要传入Key和IV,因此钩取该函数的调用参数值成为了最佳选择。

## 使用Frida和Frida Il2Cpp Bridge进行Hook
### 配置Frida环境
1. 使用带有Root手机开启adb,使用`adb push frida-server-xx.x.x-android-arm64 /data/local/tmp`将其置入手机
2. 使用`adb shell`进入命令行, `su`命令获取root权限,进入路径`cd /data/local/tmp`,为可执行文件添加权限`chmod +x ./frida-server-xx.x.x-android-arm64`然后启动可执行程序`./frida-server-xx.x.x-android-arm64`
3. 在电脑上安装Frida`pip install frida frida-tools`

### 配置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时,参数被打印于控制台:

由于读取多个bundle时的key一致,因此得出key,由于Unity asset bundle文件的前16字节是基本相同的,因此无需考虑iv造成的前16字节乱码,直接将其定义为可识别的头部即可通过AssetStudio等程序拆出资源文件。

## 举一反三
经过抓包,发现几乎所有网络请求都是被加密的。经过一段时间的搜寻,发现其网络请求函数`SendTgServerEncrypted`:

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

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

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

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

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

使用该密钥,空IV解密,发现前16byte为乱码,但16byte后明文完整。合理猜测response前16byte为IV,后面为密文,使用该逻辑成功解密出明文。

# 尾声
Il2Cpp Bridge还有很多其它实用功能,比如直接定位某函数并通过invoke()调用,获取返回值,若对其感兴趣可以阅读[官方wiki](https://github.com/vfsfitvnm/frida-il2cpp-bridge/wiki)。

本人第一次在论坛发表文章,也是为了记录自己的逆向学习过程,若有不足请大佬多多指出!

Smilience 发表于 2024-4-16 17:39

        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 发表于 2024-2-23 11:14

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 发表于 2024-2-22 23:05

这个厉害,很好的教程

yuqic987 发表于 2024-2-22 23:34

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

Junlee 发表于 2024-2-23 08:24

这个厉害,很好的教程

zouzhiqiang 发表于 2024-2-23 11:09

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

慵懒丶L先森 发表于 2024-2-23 13:11

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

zouzhiqiang 发表于 2024-2-23 17:01

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文件,但如果这些特征都没有呢??比如某神,它做的就比较好,你可以去试试

阿清 发表于 2024-2-23 17:35

多出一些这类教程 真不错

hitachimako 发表于 2024-2-23 22:50

zouzhiqiang 发表于 2024-2-23 17:01
我明白你的意思,metadata元数据文件确实是存放ill2cpp.so文件中的符号表和偏移,一般dump metadata.data ...

这个我其实很早就研究过(老mhy玩家了),在il2cpp中搜索有关metadata.dat的字符串就会发现猫腻在MetadataCache::Initialize里,确实可以规避靠文件头搜索的dumper,还有相当多硬壳il2cpp和metadata同时加密,mhy对metadata的魔改不算是很强的加密,通用模式处理不了的加壳确实就只能对着ida推了
页: [1] 2
查看完整版本: 使用Frida Il2Cpp Bridge 方便地进行Il2Cpp实例Hook