这篇文章的研究,一般来说是没有可能碰到的,所以也就只是记录一下。
需要解决的问题是:假如我的手上有两份最终波形完全相同的hca,但是其中一个是ciph 0(无加密),另一个是ciph 56(密钥加密),如何处理这个ciph 56的版本呢?
首先,这个研究的起源就是chunithm。21年的大型leak之后,chuni new最终还是给自己的cri套件加上了密码。毕竟exe加了壳,短时间是没办法从源码入手了。不过这就有了一个从未遇到的情况——手上同时有了同一个波形的加密与未加密版本。
先说结论:搞出decrypt table很好办,从table到key也完全可行,只不过第二步的逻辑比较绕,对于我这么个算法苦手来说,试错了很多次才终于出来了结果。
文中的逻辑都对照于libcgss CHcaCipher部分的代码,可以对照着key到table的正向逻辑来阅读。
1. 提取decrypt table
虽然解hca音频已经用了很久,但是其中具体逻辑从来没有仔细去研究过。在读代码外加问了hozuki之下,在初期大概搞懂了原理:输入的cri key经过一系列变换之后会得到一个table,而hca加密的过程就是按照这个table进行字节替换。至于未加密的ciph 0则是直接使用线性对应的替换表,即 [0, 1, 2, … , 0xff] 。额外的,即使是ciph56的表,其中的0和0xff也是不进行替换直接线性对应的。因此,打开两个hca,如果看到header、0x00和0xff在两个文件中都是一样的位置的话,基本就可以确定这两个是相同波形的不同加密表了。
搞明白hca加密的逻辑,提出一个解码表也就不难了。不过如果暴力直接对hca波形部分匹配替换表的话,有可能会出小错误导致某几位的表是错误值(我确实碰到了)。因为hca本身是按照chunk分割的,chunk大小在hca header的comp块里面写着,如果没写默认是0x200,不过也有其他的值,比如0x155之类的。每个chunk最后两字节是这个chunk的crc,计算的过程和hca header的crc是一样的,找替换表的时候把每个chunk结尾两个字节跳过就可以了。
到这一步就已经可以自己写一个解码器了,自己进行ciph 56的最后一步,把hca波形全部替换成ciph 0的未加密数值,然后就可以用来解出wav用了。
2. 细读算法
从下往上读 CHcaCipher::Init56 函数,先看最后一个块:
// Generate CIPH table t = &_decryptTable[1]; for (uint32_t i = 0, v = 0; i < TableSize; i++) { v = (v + 0x11u) & 0xFFu; uint8_t a = t3[v]; if (a != 0 && a != 0xFFu) { *(t++) = a; } } _decryptTable[0] = 0; _decryptTable[0xFF] = 0xFF;
最终的解密表的数值是从t3中“乱序”挑选出来的,索引v每次加0x11。这个部分目前没有太大的价值,继续往上看
// Generate table #3 uint8_t t3[0x100], t31[0x10], t32[0x10], *t = t3; Init56_CreateTable(t31, t1[0]); for (uint8_t i = 0; i < 0x10; i++) { Init56_CreateTable(t32, t2[i]); uint8_t v = t31[i] << 4u; for (uint8_t j = 0; j < 0x10u; j++) { *(t++) = v | t32[j]; } }
这部分是t3表的生成,每个值分了两个部分,其中高4位是来自t31,低4位来自t32。这里可以发现,对于同样的高4位索引来说,最终t3值的高4位也是相同的,即0x0~0xf都是0xa_,0x30~0x3f都是0xb_。
这时候我们再转过头去观察一下解密表
观察一下hex表示,可以发现右上左下的斜线上,除了偶尔的错位外,每个值的高位基本是一致的。这就是因为第一段里面0x11的差值了。排掉开头的0x00,一般来说dec表的索引就是0x11, 0x22, 0x33, … , 0xff, 0x10,所以按照顺序记录下高4位数值的出现顺序,基本上就拿到了t31表的内容。
但是不要急着进行下一步,首先要注意一个点,在t31 t32计算t3的时候,如果遇到了0x00和0xff是需要跳过的,这样就会造成不连续。而观察第一行结尾的顺序是0xe 0x5 0xf,而第二行则是0xe 0x5 0x0 0xf,所以这里我们正好在开头就碰到了0x00跳过的情况。按照数值首次出现的顺序直接后续计算的话结果会严重对不上。那么这个要如何处理呢?
回到第二段,看一下 Init56_CreateTable的逻辑:
void CHcaCipher::Init56_CreateTable(uint8_t *r, uint8_t key) { uint32_t mul = ((key & 1u) << 3u) | 5u; uint32_t add = (key & 0xEu) | 1u; key >>= 4u; for (uint32_t i = 0; i < 0x10; i++) { key = (uint8_t)((key * mul + add) & 0xFu); *(r++) = key; } }
这个算法是输入一个key,输出16个“随机”数列,并且他们刚好不重复。数学上为什么会这样我就不研究了,反正讲不通。这里mul取值只能是5或13,而add值则是1 3 5 7 9 11 13 15,假如有数列中的一部分,就完全可以直接穷举验证,推出mul和add的值了。
先前按照出现顺序推出的t31是 [9, 4, 3, 6, 13, 8, 7, 10, 1, 12, 11, 14, 5, 15, 0, 2],取这个数列的0~3,3~6,6~9分别推算出三组mul和add,可以发现这组数列的mul和add是13和15。
CreateTable算法的另一个神奇之处就是,给定mul和add,数列的顺序是固定的,随便给一个值循环16次计算就能得到完整的顺序。这样最终算出来的数列是 [9, 4, 3, 6, 13, 8, 7, 10, 1, 12, 11, 14, 5, 0, 15, 2] ,可以看到0x0和0xf正好反了,因为第一次遇到0x00被跳过了。
得到的这个数列开头的所以实际上是0x11,所以需要将结尾的2提到最开头,这样才是真正的t31。
这里需要注意,移动了一个数字之后,这里结尾是0xf了,所以其实有一个可能,就是一开头的v=0x11时遇到0xff,实际从v=0x22开始。不过这次并不是这样的情况,如果后续计算出来出错了的话,可以再多移动一次。
得到了t31的正确顺序,首先可以推算出用来计算t31表的 t1[0] 值了。CreateTable输入的key低4位可以从mul和add得到,高4位则是数列最后一个值。
接下来,参照t31的顺序,从dec table开头开始模拟挑选t3,如果遇到一个地方索引高4位和t31不匹配,并且t31对应位置是0x0或者0xf,就是遇到了0x00和0xff。如此遍历完就能找齐16组t32。
到这一步可以进行一次验算,从这个t31和16个t32计算出t3和最终的dec table,确认和之前是不是相同,如果相同就说明得出的数列没有问题。
16个t32数列倒推出mul和add,然后计算出key值,就能得出t2的值,t2则是由t1计算出来的:
// Generate table #2 uint8_t t2[0x10] = { t1[1], (uint8_t)(t1[1] ^ t1[6]), (uint8_t)(t1[2] ^ t1[3]), t1[2], (uint8_t)(t1[2] ^ t1[1]), (uint8_t)(t1[3] ^ t1[4]), t1[3], (uint8_t)(t1[3] ^ t1[2]), (uint8_t)(t1[4] ^ t1[5]), t1[4], (uint8_t)(t1[4] ^ t1[3]), (uint8_t)(t1[5] ^ t1[6]), t1[5], (uint8_t)(t1[5] ^ t1[4]), (uint8_t)(t1[6] ^ t1[1]), t1[6], };
t2的0 3 6 9 12 15分别是t1的1 2 3 4 5 6,这里可以再验算一下,从t1计算完整的t2是否一致。
// Generate table #1 uint8_t t1[8]; if (!key1) { key2--; } key1--; for (uint8_t i = 0; i < 7; i++) { t1[i] = (uint8_t)key1; key1 = (key1 >> 8u) | (key2 << 24u); key2 >>= 8u; }
而后便是最终一步,从t1计算原key。这个逻辑看起来有些吓人,但实际上正向的逻辑就是输入的uint64减去1之后,按照小端顺序存成uint8。另外,平常常用的hca key格式是拆成两个uint32,key1是低32位,key2是高32位。
非常感谢,使用key成功的decode了想要的chuni音频
您好,在BiliPlus的帮助栏尾页有您的相关链接,请问您有没有站长的联系方式?目前在tucao网上的投稿视频已经无法下载了,是站点不支持下载了吗?
能否借地问一下蔚蓝档案的检测是直接对比文件MD5还是别的什么东西?
目前测试虽然so和metadata并没有加密而且ida能正常跑,但是如果你修改任何东西放回去都会造成还没进游戏就提示检测到app异常强制退出了。我把原文件放回去就没事了,已确定不是root问题
公主連結
六星的吉塔貌似不能讀取