il2cpp情况下破解Addressable.cn加密的ab包

  在上一篇文章里,提到过如果在il2cpp情况下,UnityEngine的dll也不会被混淆,也可以按方法来改。后来自己实操的时候发现并不是这么简单,因为UnityEngine的代码虽然没有被混淆,但是它也和游戏逻辑代码一并归入到了global-metadata.dat文件下,也就是说我们定位不到代码段,也就无法按照上一篇文章所讲的方法去破解了。这篇文章主要是想说明在il2cpp打包的情况下,具体应该如何操作。
  首先,不管是mono还是il2cpp打包出来的项目,ab包的存放路径都是StreamingAssets,下图是il2cpp打包后的StreamingAssets路径。

接下来,我们进入StreamingAssets目录下,打开catalog.json文件。

该文件存放了自定义资源文件路径名。我们随便选取一条路径,这里我选用“Assets/AddressableAssets/Bgm/MainScene.mp3”(放心,没有侵权,这是我自己的游戏项目~)

  然后我们任意新建一个空项目工程,接着导入Addressable.cn包(这里我全网找了个遍也没找到Addressable.cn包,可能因为停止更新所以弃用或下架了,最新版是1.18.11,但是只能找到官方的包描述文档Document对Addressable.cn的功能介绍和使用,断断续续找了近两个月,最终找到了一条安装方式。)
打开包管理器,选择通过名字添加包

  然后包名填写 “com.unity.addressables.cn” ,版本填写1.18.11 ,(版本1.17.0疯狂报错,1.18后修复) ,接着点击Add,等待安装。这一步的主要目的是为了在出包时添加Unity.ResourceManager.dll,我们才能进行修改,(当然出包后手动添加该dll及相关依赖dll也应该ok,这样我们就不用添加Addressable.cn包了。)

  接着我们新建一个脚本,挂载到游戏的场景物体上,作为我们的函数入口。这里的资源路径名,我们填写上面获得的路径即可,如果ab包做了拆分,请手写遍历去加载所有的路径,也就会加载所有bundle,其中相同的ab包只会加载一次,不会重复加载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

public class Load : MonoBehaviour
{
private void Start()
{
Addressables.InitializeAsync();
LoadAsset<Object>("Assets/AddressableAssets/Bgm/MainScene.mp3");
}

/// <summary>
/// 同步加载(单个资源)
/// </summary>
/// <typeparam name="T">资源类型</typeparam>
/// <param name="path">资源路径,如果为空则不会加载</param>
public T LoadAsset<T>(string path) where T : Object
{
if (string.IsNullOrEmpty(path))
{
Debug.LogError("路径不能为空");
return null;
}

AsyncOperationHandle<T> handle = Addressables.LoadAssetAsync<T>(path);
T asset = handle.WaitForCompletion(); //挂起当前线程,直到操作完成为止

if (!asset)
{
Debug.LogError("加载失败" + path);
}

return asset;
}
}

然后我们就可以出包了。
出包后,我们将要破解的StreamingAssets文件夹复制到我们出包后的对应路径下,也就是“……_Data”目录下,如下图所示:

  接下来,请移步到我的上一篇文章,对Unity.ResourceManager.dll进行修改。破解addressables.cn加密AssetBundle并提取游戏资源的一条可行方法
  所有工作都准备就绪后,我们运行项目,等一会,再关闭项目进程(不然bundle文件锁为占用中)。我们就得到了解密后的bundle了。


  我又琢磨了几天,又发现了一道防线,并且似乎安全性还挺高。
  项目中,找个地方新建一个脚本,取名任意,类名就是待会要用到的,这里暂用AddressableEncode,请自定义秘钥string,但是需要是16字节的(注意是字节,UTF-8下,英文、符号、数字单个都是1B,中文不是)。脚本内容可以根据Addressable.cn官方文档去修改,这里做一个示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace UnityEngine.ResourceManagement.ResourceProviders
{
/// <summary>
/// 提供给Addressable.cn的自定义加密方式
/// </summary>
public class AddressableEncode : IDataConverter
{
private static byte[] Key => Encoding.UTF8.GetBytes("1234567890123456"); //修改此处密钥,需要16字节

private SymmetricAlgorithm _mAlgorithm;

private SymmetricAlgorithm Algorithm
{
get
{
if (_mAlgorithm != null) return _mAlgorithm;
_mAlgorithm = new AesManaged {Padding = PaddingMode.Zeros};
var initVector = new byte[_mAlgorithm.BlockSize / 8];
for (var i = 0; i < initVector.Length; i++)
initVector[i] = (byte) i;
_mAlgorithm.IV = initVector;
_mAlgorithm.Key = Key;
_mAlgorithm.Mode = CipherMode.ECB;

return _mAlgorithm;
}
}

public Stream CreateReadStream(Stream input, string id)
{
return new CryptoStream(input,
Algorithm.CreateDecryptor(Algorithm.Key, Algorithm.IV),
CryptoStreamMode.Read);
}

public Stream CreateWriteStream(Stream input, string id)
{
return new CryptoStream(input,
Algorithm.CreateEncryptor(Algorithm.Key, Algorithm.IV),
CryptoStreamMode.Write);
}
}
}

接着我们在ab包配置里选择我们自定义的加密方式。

  这样打出来包,目前来看就暂时安全了。
  原理上,可以看到我们自定义的加密方式下面还有三个自带的方式,都会被一同打包进Unity.ResouceManager.dll中,所以我们相当于知道了加密的方式,上面的破解方法其实也是就是对付自带的加密方法。但是如果我们用自定义加密方式,写的类通过il2cpp处理后,是存在于global-metadata.dat文件中,而不是Unity.ResouceManager.dll中,所以我们就得不到加密方式,Unity.ResouceManager拿不到加密方式,自然就连解密都解不了了,更不要说解密后的数据流被我们截获了。