老话题,程序自校验。不过放在 VS2008 下,情况又发生了变化。
作为软件开发者,我们一般不希望程序出来之后被人非法修改,比如破解或冠以 xxx 版等称谓,所以都会跟反调试反跟踪作斗争,当然,提到反跟踪作斗争,就像矛与盾,这个话题永远是无止境的,较量也是无止境的。再怎么想方设法保护程序,总有被 cracker 攻破的时候,只是时间的问题。当然我们也不能觉得既然这样那保护不保护也就无所谓了,就像我们明知道人生总有尽头,但不是会立即去报到一样:)
呵呵,婆婆妈妈这么多,意思只有一个,条件允许的话,还是适当的加上一些保护未必就是坏事。至于软件保护,可能稍微懂点的人立马会想到一个字,壳。是的,好的壳能给 cracker 增加不少的难度,那也是很大的话题,一般壳都具有自校验的功能,那我们可以尝试一下自己来实现这个小功能,而常用的自校验算法莫过于 CRC32 了,因为它加密之后的长度正好符合某些特定结构的长度。
之所以单独记下来作为备忘,是因为不同于以往 ASM 或者 VC6.0 等编译的程序,情况发生了些不痛不痒的变化,影响到最终方案的实施了。
读这篇文章,假设您已经知道了 CRC32 的基本概念,正在尝试实现程序自校验功能的开发者。我分成了下面几个部分:
一、不得不提的 CRC32 码表的生成:
查表法计算 CRC32 值是最快的。通常,您也可以保留一大段的 0xXXXXXXXX 等静态码表在程序里面,这并没有错,也完全可以。但是,PEid 等,具有一些插件,可以依据特征码来判定程序所使用的算法,所以在程序里面保留大段的静态码表会提前暴露身份,因此最好采用动态生成的方法。一个经典的动态码表的生成代码如下所示:
void CXCrc32::GenerateCrc32Table()
{
::SecureZeroMemory(&arCrc32Table, 256 * sizeof(DWORD));
for (int i = 0; i < 256; i++)
{
DWORD dwTempCrc = i;
for (int j = 0; j < 8; j++)
{
dwTempCrc = (dwTempCrc & 1) ? (dwTempCrc >> 1) ^ 0xEDB88320 : dwTempCrc >> 1;
}
arCrc32Table[i] = dwTempCrc;
}
}
这里的 arCrc32Table 就是我们要使用的 CRC32 码表,您最好放在类里面,在构造函数里面调用,这样后续的加密等就有了依据的表了。
二、加密字符串和文件,计算过程很简单,按 BYTE 进行,代码也很少:
DWORD CXCrc32::CalculateBufferCrc(const LPBYTE pbtBuffer, DWORD& dwSize)
{
ASSERT(dwSize >= 0);
LPBYTE pbtTempBuffer = pbtBuffer;
DWORD dwTempCrc = 0xFFFFFFFF;
while (dwSize--)
{
dwTempCrc = ((dwTempCrc >> 8) & 0x00FFFFFF) ^ arCrc32Table[(dwTempCrc ^ (*pbtTempBuffer)) & 0xFF];
pbtTempBuffer++;
}
return dwTempCrc ^ 0xFFFFFFFF;
}
三、了解一下 PE 文件结构是很有必要的:
四、生成文件的 CRC32 值后,写入文件,这是最头疼的问题,这就是前面所说的变化:
以往通常都是保存在 IMAGE_OPTIONAL_HEADER.Win32VersionValue 中,因为这是一个保留值,没有使用,其长度 DWORD 正好符合 CRC32 加密之后的长度, 4 个字节。所以他是一个理想的存储位置。
但是,在 VS2008 + XP Sp3 下(Vista 下我没有测试),情况很不同了:似乎这个所谓的保留值不再是保留值了,虽然编译器编译出来默认仍然是 0,我们仍然可以修改,但是对大小已经有了严格的限制,DWORD 值大小不能超过 5 !!
这一点就浪费了我不少的时间,一开始以为老办法仍然可行,没想到双击修改 Win32VersionValue 后的程序根本没反应,就跟没运行一样,而且,这样的东西根本没办法调试,所以只好一点一点试,然后拿 PE 工具查看 PE 头信息。最后测试发现不能超过 5,那实在是一件很沮丧的事,因为一旦 cracker 知道你是使用 CRC 来做自校验(比如我在第一点里面提到的静态码表),这个地方他肯定会关注,枚举不超过 5 次程序就被破了,那也太让他们扫兴了。
在网上搜索了一下,发现有人写的博客有:http://hi.baidu.com/zuikee/blog/item/738e7fd1286d9ad4562c8452.html,但是很可惜,这个值在 VS2008 下根本不管用,9.0 的 VC 运行库似乎就卯定了 0-5 了,哎,愁啊!
于是另想办法
查找 MSDN 关于 PE 的说明
http://msdn.microsoft.com/en-us/library/ms680339(VS.85).aspx
发现有个成员被标记为 obsolete,大喜,That's it! 就是他了,你不要了我正好需要,呵呵
于是编译、写入文件,测试,一切 OK:
五、单纯的 FileSize 是不可靠的,不是说不行,因为 cracker 也可以计算现在文件的 CRC32 值,回写过去,那就又挂了,所以计算最终需要写入的 CRC32 值的时候,可以加上一些特殊信息,比如,你的生日,女朋友的手机号等等,当然你在程序里面校验文件是够被修改的时候,也是需要加入同样的特殊信息的。
六、也可以把这个 CRC32 值也在自身 exe 文件末尾或者另外挂个 dll,但,似乎都没与这种方案妥当。
参考:老罗的经典文章,做 CRC 自校验不得不看的:
矛与盾的较量(2)——CRC原理篇
矛与盾的较量(3)——CRC实践篇