在 VS2008 下用 CRC32 算法实现程序自校验

 

老话题,程序自校验。不过放在 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 了,哎,愁啊!

使用 PE Explorer 查看 PE 头

 

于是另想办法

查找 MSDN 关于 PE 的说明

http://msdn.microsoft.com/en-us/library/ms680339(VS.85).aspx

发现有个成员被标记为 obsolete,大喜,That's it! 就是他了,你不要了我正好需要,呵呵

于是编译、写入文件,测试,一切 OK:

测试 CRC32 自校验

 

五、单纯的 FileSize 是不可靠的,不是说不行,因为 cracker 也可以计算现在文件的 CRC32 值,回写过去,那就又挂了,所以计算最终需要写入的 CRC32 值的时候,可以加上一些特殊信息,比如,你的生日,女朋友的手机号等等,当然你在程序里面校验文件是够被修改的时候,也是需要加入同样的特殊信息的。

 

六、也可以把这个 CRC32 值也在自身 exe 文件末尾或者另外挂个 dll,但,似乎都没与这种方案妥当。

 

 

参考:老罗的经典文章,做 CRC 自校验不得不看的:

         矛与盾的较量(2)——CRC原理篇 
         矛与盾的较量(3)——CRC实践篇

 

 

 

 

 

易语言 CRC32.ec校验模块 林子深作品 比其他的都便宜 CRC32.ec校验模块 林子深作品 下载 CRC32.ec校验模块下载 我们很有必要给自己的软件加上更好的保护。CRC校验就是其中的一种不错的方法。 CRC是什么东西呢?其实我们大家都不应该会对它陌生,回忆一下?你用过RAR和ZIP等压缩软件吗?它们是不是常常会给你一个恼人的“CRC校验错误”信息呢?我想你应该明白了吧,CRC就是块数据的计算值,它的全称是“Cyclic Redundancy Check”,中文名是“循环冗余码”,“CRC校验”就是“循环冗余校验”。 CRC有什么用呢?它的应用范围很广泛,最常见的就是在网络传输中进行信息的校对。其实我们大可以把它应用到软件保护中去,因为它的计算是非常非常非常严格的。严格到什么程度呢?你的程序只要被改动了一个字节(甚至只是大小写的改动),它的值就会跟原来的不同。呵呵,是不是很厉害呢?所以只要给你的“原”程序计算好CRC值,储存在某个地方,然后在程序中随机地再对文件进行CRC校验,接着跟第一次生成并保存好的CRC值进行比较,如果相等的话就说明你的程序没有被修改/破解过,如果不等的话,那么很可能你的程序遭到了病毒的感染,或者被奸人用16进制工具暴力破解过了。 CRC的本质是进行XOR运算,运算的过程我们不用管它,因为运算过程对最后的结果没有意义;我们真正感兴趣的只是最终得到的余数,这个余数就是CRC值。 易语言用户林子深提供了一个CRC32.EC的易模块,这个模块可以在论坛上或资源网上搜索到。大家可以直接拿过来用,导入易语言后如下图所示: 调用方式可为如下代码: 判断(取循环风俗冗余校验和(读入文件(取执行文件名()))= 18293823767) 其中上述的数字为主程序CRC码值。 大家可以利用上述特性,即结果是数值的特性,不用进行比较,而是用加减法的方法得出前后两个CRC验的差值,如果为0,那说明两个数字是相等的,主程序没有被改动过。否则就是被改过的。 例如下述代码: 程序被改 = 1 计次循环首(取绝对值(取循环风俗冗余校验和(读入文件(取执行文件名()))- 18293823767),) 程序被改 = 0- 跳出循环() 计次循环尾() 返回(程序被改) 上述代码中,“取绝对值()”是将负数也转换为正数,以方便进行计次循环。 上述代码中,数值型变量“程序被改”为1表示没有被改动过,如果为0表示被改动过。返回的数值就可以进行一些陷阱的计算了。 我的方案的缺点分析: CRC-32的值其实可以由奸人自行计算得出后,重新写入到程序验证处。这样的话,我们做的工作岂不是没有意义了? 其实解决的方法还是有的,我们可以在计算CRC-32值之前,对参与转换的字符串加点手脚,例如对这个字符串进行移位、xor等操作,或者把自己的生日等信息加入到字符串中,随你的便什么都行,总之不是单纯的文件的内容就行了,然后在最后比较的时候,也用同样的方法反计算出CRC-32值。这样得到的CRC-32就不是由文件的内容计算出来的,相信对破解者的阻力也会加大不少。 总结: 以上的方法大家都可以在软件中采用,最好写到一个DLL文件中,然后多放几个需要验证的子程序,随时以备调用即可。
程序载入OD   下断bp CreateFileA,F9运行   OD中断,ALT+F9执行到用户代码      下面就是F8单步跟踪了      程序在0048EE23处出现对话框,很明显,在0048EDF9处的跳转就是关键跳了   直接将0048EDF9  /7E 38        jle short dumped_.0048EE33   改成0048EDF9  /7E 38         jmp dumped_.0048EE33   保存下,运行成功   把脱好的程序载入ResScope,发现有非标准资源结构,那就用Fix Resource修正下资源,再次再入ResScope,这次可以修改资源了,但是问题又来了。。。   打开修正好的程序程序只是一闪而过就自动关闭了,看来还有校验,继续操起OD,再次载入程序   下断bp CreateFileA,F9运行   OD中断,ALT+F9执行到用户代码,继续F8单步走      0048EE5F出OD就会跑飞了,显然0048EE5D处的跳转又是关键跳啦,   把0048EE5D  /74 05        je short 1.0048EE64   改成0048EE5D  /74 05        jmp 1.0048EE64   就OK了,保存,运行一切正常   现在程序太大,我们再用CxLrb大侠汉化的Resource Binder V2.6处理一下!   挖塞,程序一下从154M缩小到1.13 MB,这样的结果还是让人满意的   但是一运行,程序又是一闪而过,还有校验,OK,继续   OD载入程序,这次可能会比较卡   下断bp CreateFileA,F9运行   OD中断,ALT+F9执行到用户代码,继续F8单步走      0048EE3F处OD跑飞,也很明显了0048EE3D处就是关键跳啦,   把0048EE3D  . /74 05       je short 2.0048EE44   改成0048EE3D  . /74 05       jmp 2.0048EE44   保存,运行成功,感觉048EE4A处的跳转应该也是个校验吧,只是没用到,程序处理到这里就差不多了,有是不妥之处还望大侠们指点!
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值