将AddressablesCN加密保护的数据解密与修改

解密与修改方法

在dnSpy中搜索找到函数 UnityEngine.ResourceManagement.ResourceProviders.AssetBundleResource.LoadWithDataProc如下

private void LoadWithDataProc(string path, uint crc){
	FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read);
	Stream stream = m_dataProc.CreateReadStream(fileStream, m_ProvideHandle.Location.InternalId);
	if (stream.CanSeek)
	{
		m_RequestOperation = AssetBundle.LoadFromStreamAsync(stream, crc);
		return;
	}
	MemoryStream memoryStream = new MemoryStream();
	stream.CopyTo(memoryStream);
	stream.Flush();
	stream.Dispose();
	fileStream.Dispose();
	memoryStream.Position = 0L;
	m_RequestOperation = AssetBundle.LoadFromStreamAsync(memoryStream, crc);
}

将其修改为如下

private void LoadWithDataProc(string path, uint crc)
		{
			//Debug.Log(path);
			FileStream fileStream;
			Stream stream;
			if (File.Exists(path + "uproc"))
			{
				//Debug.Log("Load " + path + "uproc");
				fileStream = new FileStream(path + "uproc", FileMode.Open, FileAccess.Read);
				stream = new MemoryStream();
				fileStream.CopyTo(stream);
			}
			else
			{
				//Debug.Log("Load " + path);
				fileStream = new FileStream(path, FileMode.Open, FileAccess.Read);
				stream = this.m_dataProc.CreateReadStream(fileStream, this.m_ProvideHandle.Location.InternalId);
				FileStream fileStreamNu = new FileStream(path + "uproc", FileMode.CreateNew, FileAccess.Write);
				MemoryStream memoryStream2 = new MemoryStream();
				stream.CopyTo(memoryStream2);
				memoryStream2.Position = 0L;
				memoryStream2.CopyTo(fileStreamNu);
				memoryStream2.Position = 0L;
				stream.Flush();
				stream.Dispose();
				stream = memoryStream2;
				fileStreamNu.Flush();
				fileStreamNu.Dispose();
			}
			if (stream.CanSeek)
			{
				this.m_RequestOperation = AssetBundle.LoadFromStreamAsync(stream, crc);
				return;
			}
			MemoryStream memoryStream3 = new MemoryStream();
			stream.CopyTo(memoryStream3);
			stream.Flush();
			stream.Dispose();
			fileStream.Dispose();
			memoryStream3.Position = 0L;
			this.m_RequestOperation = AssetBundle.LoadFromStreamAsync(memoryStream3, crc);
		}

原.bundle文件在加载时将生成一个对应的已解密的.bundleuproc文件,为普通AB格式,可直接在AssetStudio查看,并且游戏受该已解密文件修改的影响,但原资源文件将失去作用

原理简介

上面代码中this.m_dataProc调用到的是在unity中设置的继承了IDataConverter的用于加密解密的流处理器类,可能是自定义的也可能不是,不过它是什么对于这种解密方法没有影响。这里看到FileStream可以确定是在最终步骤的直接对文件流进行的加解密,将解密后的文件流从新写到对应的解密后文件,直接增加后缀一一对应,并且如果存在的话直接读取解密后(可能进行修改)的资源进行加载。
不难看出,解密的功能是可以用独立程序完成的,只要可以调用到对应的解密函数,但是这种直接读取解密后的文件的方法可以节省大量繁琐步骤,更方便对资源的修改,事实上通过调用this.m_dataProc.CreateWriteStream即可将解密数据重新加密。

关于该流处理器类到底存放在什么地方,以及它是如何被引用的

可以追查到在函数UnityEngine.ResourceManagement.ResourceProviders.AssetBundleProvider.Initialize中有

SerializedType serializedType = JsonUtility.FromJson<SerializedType>(data);
if (serializedType.Value != null)
{
	DataStreamProcessor = Activator.CreateInstance(serializedType.Value) as IDataConverter;
}

是该处理器类引用的来源。对data的追查则到了UnityEngine.ResourceManagement.Util.ObjectInitializationData结构中的

[FormerlySerializedAs("m_data")]
[SerializeField]
private string m_Data;

<game>\<game>_Data\StreamingAssets\aa\catalog.json文件中的内容通过搜索可以确定是UnityEngine.AddressableAssets.ResourceLocators.ContentCatalogData的序列化,其中的m_ResourceProviderData中的第一项ObjectInitializationData中的m_Data的值则是与运行过程中Debug输出到文件的data一模一样,可以确定这就是它最源头的来源(其它地方也没找到)。

曲折经历与思路

本来打算用AssetStudio找到然后修改某个MonoBehaviour的值(已经提前在ILspy里找到代码中使用MonoBehaviour上的值的内容了),结果找不到,就简单看了看好辨识的资源,结果明显有缺少,才发现一股脑拖进去的这些AB压根没识别。然后对相关资源的代码进行排查追踪,正着找了好半天果然还是太抽象了。决定搜一搜Addressables(aa)这个和AB看起来不太一样的东西,一顿搜索发现了CN版的加密。跟着它的说明文档的思路直接搜AESStreamProcessor就在游戏代码的程序集里找到了一个,这个是八九不离十。此时突发奇想,向其中安插throw,直接抓住调用栈,找到这个修改的函数。当然过程中并不一帆风顺,有很多这查一查那追一追的过程,我甚至为了方便获取信息向其中加入了一个用网络进行通信的debugmsg到一个控制台程序打印出来(尤其是前期要大量重复打印各种信息的时候十分的好用),总共用时大约9h左右。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值