[原创]关于.pbb加密文件的算法解析及破解
最近刚好有用到一个.pbb格式的视频,在网上找破解工具也没有找到,只有自己动手破解了,下面简单介绍一下破解过程, 由于是国产商业软件,很多东西就点到为止,其中涉及到具体的实现我就不再谈及了,希望大家理解。
先上成果图,视屏采用加密软件自带的:示例视频.mp4,用加密软件加密后:示例视频.mp4.pbb,鉴于是自己用,也就没想着做界面:
获取文件路径及获取key关联值

图1
根据key关联值获取key

图2
根据key解密视频

图3
还原成功,可以正常播放

图4
下面详细说一下破解的过程。
我刚开始准备破解方案是从加密软件的读取软件入手,如果可以直接分析加密文件解密过程,就不难把文件还原,结果我下了这个阅读器并拖到OD中单步了几下后,我发现这个阅读器加了强壳,要想分析的话要先脱壳,先不说最后能不能脱掉,就算能脱掉时间成本也太大,故放弃这个方案。
接下来我想既然不能从解密过程入手,那就从加密过程入手,所以就又下了加密器,令人欣喜的是,用PEid查看这个软件,发现没有加壳,拖到OD中查看,单步几步发现没有混淆,也没有反调试,故下定决心就从这里入手,打开加密软件,发现加密要点击“外发控制”这个按钮,在OD中搜索字符串,如图5所示,发现很多有关字符串:

图5
我们可以看到,其中有两个部分有“外放文件”,一个是“不小于512”,一个是“不小于256”,但在“不小于256”处后面有“pdf”、“mpg”等明显代表文件格式的字符串,所以选择这处点击跳转到调试窗口中,大致浏览一下这个函数我们可以发现很多有用的函数,比如CreateFile,CopyFile,ReadFile等,可以确定这就是我们要找的函数。找到函数头部分下一个软件断点,重新运行,按F5,在软件中选择示例视频.mp4,点击外发控制,一直下一步,然后会断在我们所下断点处,这部分操作我就不上图了,只说一下具体操作流程,毕竟我们的主要任务是分析它的加密算法。
单步运行并分析上文所下断点的函数,我们可以发现,在ReadFile读取处,程序读取了源文件,程序中共有7个WriteFile,在数据窗口中跟随ReadFile的第二个参数即读入源文件数据的部分,继续单步执行,发现第1个WriteFile不会被执行,在执行到第2个WriteFile处时,程序写入随机字节: 0x200000,而数据窗口中跟随的数据段已经发生改变,在010editor中打开加密后的文件:示例视频.mp4.pbb,发现其数据与在OD中数据窗口中的数据一致,所以可以确定这段数据就是加密后的数据,而在ReadFile与WriteFile之间只有一个函数:call PycFileO.00E45190(将函数PycFileO.00E45190重命名为核心加密函数,下文用到的部分也以核心加密函数指代),所以可以确定这个函数即使我们所要找的加密函数。先不管加密函数的具体内容,继续执行其所处函数,当我们执行到第3个WriteFile处时,程序写入了随机n个字节(n=0~16),在第4个WriteFile处,发现程序把上文中数据窗口中跟随的密文写入了文件,在第5个WriteFile处,程序将其上面一个随机产生的0x20大小的内容进行了写入,第6个WriteFile处,程序将了0x2A8内容进行了简单填充,第7个WriteFile处,写入了固定大小 0x10C的内容,经分析与密钥有关。
经过分析,发现加密只与核心加密函数有关,所以我们的重点放到核心加密函数上来,其它的部分在这里不做讨论。为了节省时间,我用了IDA的F5功能查看整个核心加密函数的具体结构,如图6所示:

图6
其中每个函数大致的功能如图中所述,具体里面每一步的步骤在这里不做详细描述,大家可以自己去跟进。下面我直接给出经过整理、还原后的C++形式的加密代码,并进行简要的说明(图6中所示三个未知函数在下文中以函数1、函数2、函数3相称):
一直跟进函数2,可以找到置换表和置换方法:
// 置换表
unsigned char xTab[256] = {
0xD6, 0x90, 0xE9, 0xFE, 0xCC, 0xE1, 0x3D, 0xB7, 0x16, 0xB6, 0x14, 0xC2, 0x28, 0xFB, 0x2C, 0x05,
0x2B, 0x67, 0x9A, 0x76, 0x2A, 0xBE, 0x04, 0xC3, 0xAA, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99,
0x9C, 0x42, 0x50, 0xF4, 0x91, 0xEF, 0x98, 0x7A, 0x33, 0x54, 0x0B, 0x43, 0xED, 0xCF, 0xAC, 0x62,
0xE4, 0xB3, 0x1C, 0xA9, 0xC9, 0x08, 0xE8, 0x95, 0x80, 0xDF, 0x94, 0xFA, 0x75, 0x8F, 0x3F, 0xA6,
0x47, 0x07, 0xA7, 0xFC, 0xF3, 0x73, 0x17, 0xBA, 0x83, 0x59, 0x3C, 0x19, 0xE6, 0x85, 0x4F, 0xA8,
0x68, 0x6B, 0x81, 0xB2, 0x71, 0x64, 0xDA, 0x8B, 0xF8, 0xEB, 0x0F, 0x4B, 0x70, 0x56, 0x9D, 0x35,
0x1E, 0x24, 0x0E, 0x5E, 0x63, 0x58, 0xD1, 0xA2, 0x25, 0x22, 0x7C, 0x3B, 0x01, 0x21, 0x78, 0x87,
0xD4, 0x00, 0x46, 0x57, 0x9F, 0xD3, 0x27, 0x52, 0x4C, 0x36, 0x02, 0xE7, 0xA0, 0xC4, 0xC8, 0x9E,
0xEA, 0xBF, 0x8A, 0xD2, 0x40, 0xC7, 0x38, 0xB5, 0xA3, 0xF7, 0xF2, 0xCE, 0xF9, 0x61, 0x15, 0xA1,
0xE0, 0xAE, 0x5D, 0xA4, 0x9B, 0x34, 0x1A, 0x55, 0xAD, 0x93, 0x32, 0x30, 0xF5, 0x8C, 0xB1, 0xE3,
0x1D, 0xF6, 0xE2, 0x2E, 0x82, 0x66, 0xCA, 0x60, 0xC0, 0x29, 0x23, 0xAB, 0x0D, 0x53, 0x4E, 0x6F,
0xD5, 0xDB, 0x37, 0x45, 0xDE, 0xFD, 0x8E, 0x2F, 0x03, 0xFF, 0x6A, 0x72, 0x6D, 0x6C, 0x5B, 0x51,
0x8D, 0x1B, 0xAF, 0x92, 0xBB, 0xDD, 0xBC, 0x7F, 0x11, 0xD9, 0x5C, 0x41, 0x1F, 0x10, 0x5A, 0xD8,
0x0A, 0xC1, 0x31, 0x88, 0xA5, 0xCD, 0x7B, 0xBD, 0x2D, 0x74, 0xD0, 0x12, 0xB8, 0xE5, 0xB4, 0xB0,
0x89, 0x69, 0x97, 0x4A, 0x0C, 0x96, 0x77, 0x7E, 0x65, 0xB9, 0xF1, 0x09, 0xC5, 0x6E, 0xC6, 0x84,
0x18, 0xF0, 0x7D, 0xEC, 0x3A, 0xDC, 0x4D, 0x20, 0x79, 0xEE, 0x5F, 0x3E, 0xD7, 0xCB, 0x39, 0x48
};
// 置换
unsigned int doXTab(unsigned int val)
{
unsigned int rc = 0;
for (int i = 0; i < 4; i++) {
unsigned char kv = val & 0xff;
rc |= xTab[kv] << 8 * i;
val >>= 8;
}
return ((rc >> 9) | (rc << 23)) ^ rc ^ ((rc >> 19) | (rc << 13));
}
unsigned int doYTab(unsigned int val)
{
unsigned int rc = 0;
for (int i = 0; i < 4; i++) {
unsigned char kv = val & 0xff;
rc |= xTab[kv] << 8 * i;
val >>= 8;
}
return ((rc >> 8) | (rc << 24)) ^ ((rc >> 14) | (rc << 18))
^ ((rc >> 22) | (rc << 10)) ^ rc ^ ((rc >> 30) | (rc) << 2);
}函数2的具体实现如下:
// 固定
unsigned char sKey[16] = {
0xC6, 0xBA, 0xB1, 0xA3, 0x50, 0x33, 0xAA, 0x56, 0x97, 0x91, 0x7D, 0x67, 0xDC, 0x22, 0x70, 0xB2
};
// 固定异或表
unsigned char sTab[128] = {
0x15, 0x0E, 0x07, 0x00, 0x31, 0x2A, 0x23, 0x1C, 0x4D, 0x46, 0x3F, 0x38, 0x69, 0x62, 0x5B, 0x54,
0x85, 0x7E, 0x77, 0x70, 0xA1, 0x9A, 0x93, 0x8C, 0xBD, 0xB6, 0xAF, 0xA8, 0xD9, 0xD2, 0xCB, 0xC4,
0xF5, 0xEE, 0xE7, 0xE0, 0x11, 0x0A, 0x03, 0xFC, 0x2D, 0x26, 0x1F, 0x18, 0x49, 0x42, 0x3B, 0x34,
0x65, 0x5E, 0x57, 0x50, 0x81, 0x7A, 0x73, 0x6C, 0x9D, 0x96, 0x8F, 0x88, 0xB9, 0xB2, 0xAB, 0xA4,
0xD5, 0xCE, 0xC7, 0xC0, 0xF1, 0xEA, 0xE3, 0xDC, 0x0D, 0x06, 0xFF, 0xF8, 0x29, 0x22, 0x1B, 0x14,
0x45, 0x3E, 0x37, 0x30, 0x61, 0x5A, 0x53, 0x4C, 0x7D, 0x76, 0x6F, 0x68, 0x99, 0x92, 0x8B, 0x84,
0xB5, 0xAE, 0xA7, 0xA0, 0xD1, 0xCA, 0xC3, 0xBC, 0xED, 0xE6, 0xDF, 0xD8, 0x09, 0x02, 0xFB, 0xF4,
0x25, 0x1E, 0x17, 0x10, 0x41, 0x3A, 0x33, 0x2C, 0x5D, 0x56, 0x4F, 0x48, 0x79, 0x72, 0x6B, 0x64
};
unsigned int *dsTab = (unsigned int *)&sTab[0];
pvTable doBuild(pvKey cKey,bool enc)
{
pvTable vt = boost::make_shared<vTable>();
unsigned int dwKey[32 + 4] = { 0 };
unsigned int *psKey = (unsigned int *)sKey;
unsigned int *pcKey = (unsigned int *)cKey->v;
for (int i = 0; i < 4; i++)
dwKey[i] = psKey[i] ^ pcKey[i];
for (int i = 0; i < 0x20; i++) {
unsigned int v = dwKey[i + 1] ^ dwKey[i + 2]
^ dwKey[i + 3] ^ dsTab[i];
v = doXTab(v);
dwKey[i + 4] = v ^ dwKey[i];
if (enc) {
vt->v[i] = dwKey[i + 4];
}
else {
vt->v[31 - i] = dwKey[i + 4];
}
}
return vt;
}函数3的具体实现如下:
void doEnc(pvData src, pvKey key)
{
pvTable vt = doBuild(key);
doEnc(src->v, 0x10, vt);
}
void doEnc(void *src, pvTable vt)
{
unsigned int tTab[0x24] = { 0 };
memcpy(tTab, src, 0x10);
for (int i = 0; i < 0x20; i++) {
tTab[i + 4] = tTab[i] ^ doYTab(tTab[i + 1] ^ tTab[i + 2] ^ tTab[i + 3] ^ vt->v[i]);
}
unsigned int *value = (unsigned int *)src;
for (int i = 0; i < 4; i++) {
value[i] = tTab[0x23 - i];
}
}
void doEnc(void *src,int len, pvTable vt)
{
int ll = len / 0x10;
for (int i = 0; i < ll; i++) {
doEnc((unsigned char *)src + 0x10 * i, vt);
}
}经过验证(测试函数在附件里),确定我们所还原出的加密算法与原加密算法功能基本相同,据此我们也就不难写出解密函数来,解密函数这里就不给出了,程序分析到这里差不多也要结束了,接下来最重要的就是找到上面代码中所提的key,由于是国产商业软件,所以具体获取key的过程就不能在这里说明了,这里只提供一个我的解决办法:如果你详细分析过核心加密函数后就会发现,该程序没有将key保存到本地,而是上传到了网络,并且key的计算与当前时间有关,所以我们可以利用时间关系来计算出key,上文中所提第7个WriteFile处写入的0x10c处即为与key关联的数据,下面可以给出获取此处数据的函数:
// 解密并获取key
pvKey getKey(const unsigned char *mem, int size)
{
pvTable vt = doBuild(buildKey(), false);
doDec((void *)mem, size, vt);
char buf[17] = { 0 };
for ( int i = 0; i < 16; i++ ) {
buf[i] = mem[i * 16 + 8];
}
return buildKey(buf);
}
void runGetInfo(fInfo file)
{
char buf[0x10c + 0x20];
memcpy(buf, file.addr + file.size - 0x10c, 0x10c);
memcpy(&buf[0x10c], file.addr + file.size - 0x10C - 0x2A8 - 0x20, 0x20);
pvKey oldKey = getKey((const unsigned char*)buf, 0x10c);
// 打印
printf("key: ");
for ( int i = 0; i < 16; i++ ) {
printf("%.2x", oldKey->v[i]);
}
}此处数据经过计算,就可以得出我们需要的key值,具体方法在此处不便给出,有兴趣的可以私下交流,还请大家见谅。附件里有测试版的加密与解密流程,供大家参考。
