CRC计算(续)
来源:百度文库 编辑:神马文学网 时间:2024/03/29 01:59:22
[ xiwang 发表于 2005-12-6 9:58:00 ]
主题 :CRC原理及其逆向分析方法.
作者 : Anarchriz/DREAD.
发布日期 :29 april 1999 (最后更改 30 april 1999).
目的 :CRC 算法.
使用工具 :QEdit 2.1 (The best!) &Wordpad & some CRC progs.
figure :CRC教程与c001的CRC逆向分析方法.
翻译:arbin
CRC原理及其逆向破解方法:
介绍:
这篇短文包含CRC原理介绍和其逆向分析方法,很多程序员和破解者不是很清楚了解
CRC的工作原理,而且几乎没人知道如何逆向分析它的方法,事实上它是非常有用的.
首先,这篇教程教你一般如何计算CRC,你可以将它用在数据代码保护中.第二,主要是
介绍如何逆向分析CRC-32,你可以以此来分析程序中的CRC保护(象反病毒编码).当然
有很多有效的工具用来对付CRC,但我怀疑它是否会说明原理.
我要告诉你,这篇短文里中应用了很多数学知识,这不会影响一些人,而且会被一般的
程序员与逆向分析者很好理解.为什么?那么如果你不知道数学是如何被应用在CRC中,
我建议你可以停止继续学习了.所以我假定你们(读者)都是具备二进制算术知识的.
第一部分:CRC 介绍,CRC是什么和计算CRC的方法.
循环冗余码 CRC
我们都知道CRC.甚至你没有印象,但当你想到那些来自诸如RAR,ZIP等压缩软件发给你
由于错误连接和其他一些意外原因导致的文件错误的恼人的消息时,你就会知道.CRC是块
数据的计算值,比如对每一个文件进行压缩.在一个解压缩过程中,程序会从新计算解压文件
的CRC值,并且将之与从文件中读取的CRC值进行比对,如果值相同,那么正确.在CRC-32中,
会有1/2^32的可能性发生对确认数据更改的校验错误.
很多人认为CRC就是循环冗余校验,假如CRC真的就是循环冗余校验,那么很多人都错用了
这个术语.你不能说"这个程序的CRC是12345678".人们也常说某一个程序有CRC校验,而不
说是 "循环冗余校验" 校验.结论:CRC 代表循环冗余码,而不是循环冗余校验.
计算是如何完成的呢?好,主要的想法就是将一个文件看成一个被一些数字分割的很长的
位字串,这里会有一个余数---CRC!你总会有一个余数(可以是0),它至多比除数小一.
(9/3=3 余数=0 ; (9+2)/3=3 余数=2)
(或者它本身就包含一个除数在其中).
在这里CRC计算方法与除法有一点点区别,除法就是将被减数重复的减去除数X次,然后留下
余数.如果你希望得到原值,那么你就要把除数乘上X次,然后加上余数.
CRC计算使用特殊的减法与加法完成的.也就是一种新的"算法".计算中每一位计算的进位值
被"遗忘"了.
看如下两个例子,1是普通减法,2和3是特殊的.
-+
(1) 1101 (2) 1010 1010 (3) 0+0=0 0-0=0
1010- 1111+ 1111- 0+1=1 *0-1=1
---- ---- ---- 1+0=1 1-0=1
0011 0101 0101 *1+1=0 1-1=0
在(1)中,右数第二列可以看成是0-1=-1,因此要从高位借1,就变成(10+0)-1=1.(这就象普通
的‘by-paper‘十进制减法).特例(2,3)中,1+1会有正常的结果10,‘1‘是计算后的进位.这个值
被忽略了.特殊情况0-1应该有正常结果‘-1‘就要退到下一位.这个值也被忽略了.假如你对编程
有一定了解,这就象,XOR 操作或者更好.
现在来看一个除法的例子:
在普通算法中:
1001/1111000\1101 13 9/120\13
1001 - 09 -|
---- -- |
1100 30 |
1001 - 27 -
---- --
0110 3 -> 余数
0000 -
----
1100
1001 -
----
011 -> 3, 余数
在CRC算法中:
1001/1111000\1110 9/120\14 余数为 6
1001 -
----
1100
1001 -
----
1010
1001 -
----
0110
0000 -
----
110 -> 余数
(例 3)
这个除法的商并不重要,也没必要去记住,因为他们仅仅是一组无关紧要的位串.真正
重要的是余数!它就是这个值,可以说比原文件还重要的值,他就是基本的CRC.
过度到真正的CRC码计算.
进行一个CRC计算我们需要选则一个除数,从现在起我们称之为"poly".宽度W就是最高位
的位置,所以这个poly 1001的W 是3,而不是4.注意最高位总是1,当你选定一个宽度,那么你只
需要选择低W各位的值.
假如我们想计算一个位串的CRC码,我们想确定每一个位都被处理过,因此,我们要在目标
位串后面加上W个0位.在此例中,我们假设位串为1111.请仔细分析下面一个例子:
Poly = 10011, 宽度 W=4
位串 Bitstring
Bitstring + W zeros = 110101101 + 0000
10011/1101011010000\110000101 (我们不关心此运算的商)
10011|||||||| -
-----||||||||
10011|||||||
10011||||||| -
-----|||||||
00001||||||
00000|||||| -
-----||||||
00010|||||
00000||||| -
-----|||||
00101||||
00000|||| -
-----||||
01010|||
00000||| -
-----|||
10100||
10011|| -
-----||
01110|
00000| -
-----|
11100
10011 -
-----
1111 -> 余数 -> the CRC!
(例 4)
重要两点声明如下:
1.只有当Bitstring的最高位为1,我们才将它与poly做XOR运算,否则我们只是将
Bitstring左移一位.
2.XOR运算的结果就是被操作位串bitstring与低W位进行XOR运算,因为最高位总为0.
算法设计:
你们都应知道基于位运算的算法是非常慢的而且效率低下.但如果将计算放在每一字节上
进行,那么效率将大大提高.不过我们只能接受poly的宽度是8的倍数(一个字节;).可以形
象的看成这样一个宽度为32的poly(W=32):
3 2 1 0 byte
+---+---+---+---+
Pop! <--| | | | |<-- bitstring with W zero bits added, in this case 32
+---+---+---+---+
1<--- 32 bits ---> this is the poly, 4*8 bits
(figure 1)
这是一个你用来存放暂时CRC结果的记存器,现在我称它为CRC记存器或者记存器.你从右
至左移动位串,当从左边移出的位是1,则整个记存器被与poly的低W位进行XOR运算.(此例
中为32).事实上,我们精确的完成了上面除法所做的事情.
移动前记存器值为:10110100
当从右边移入4位时,左边的高4位将被移出,此例中1011将被移出,而1101被移入.
情况如下:
当前8位CRC记存器 : 01001101
刚刚被移出的高4位 : 1011
我们用此poly : 101011100, 宽度 W=8
现在我们用如前介绍的方法来计算记存器的新值.
顶部 记存器
---- --------
1011 01001101 高四位和当前记存器值
1010 11100 + (*1) Poly 放在顶部最高位进行XOR运算 (因为那里是1)
-------------
0001 10101101 运算结果
现在我们仍有一位1在高4位:
0001 10101101 上一步结果
1 01011100+ (*2) Poly 放在顶部的最低位进行XOR运算 (因为那里是1)
-------------
0000 11110001 第二步运算结果
^^^^
现在顶部所有位均为0,所以我们不需要在与poly进行XOR运算
你可以得到相同的结果如果你先将(*1)与(*2)做XOR然后将结果与记存器值做XOR.
这就是标准XOR运算的特性:
(a XOR b) XOR c = a XOR (b XOR c) 由此,推出如下的运算顺序也是正确的.
1010 11100 poly (*1) 放在顶部最高位
1 01011100+ polys (*2) 放在顶部最低位
-------------
1011 10111100 (*3) XOR运算结果
The result (*3) 将(*3)与记存器的值做XOR运算
1011 10111100
1011 01001101+ 如右:
-------------
0000 11110001
你看到了吗?得到一样的结果!现在(*3)变的重要了,因为顶部为1010则(3)的值总是等于
10111100(当然是在一定的条件之下)这意味着你可以预先计算出任意顶部位结合的XOR值.
注意,顶部结果总是0,这就是组合XOR操作导致的结果.(翻译不准确,保留原文)
现在我们回到figure 1,对每一个顶部字节的值都做移出操作,我们可以预先计算出一个值.
此例中,它将是一个包含256个double word(32 bit)双字的表.(附录中CRC-32的表).
(翻译不准确,保留原文)
用伪语言表示我们的算法如下:
While (byte string is not exhausted)
Begin
Top = top_byte of register ;
Register = Register shifted 8 bits left ORred with a new byte from string ;
Register = Register XORred by value from precomputedTable at position Top ;
End
direct table算法:
上面提到的算法可以被优化.字节串中的字节在被用到之前没有必要经过整个记村器.用
这个新的算法,我们可以直接用一个字节去XOR一个字节串通过将此字节移出记存器.结果
指向预先计算的表中的一个值,这个值是用来被记存器的值做XOR运算的.
我不十分确切的知道为什么这会得到同样的结果(这需要了解XOR运算的特性),但是这又
极为便利,因为你无须在你的字节串后填充0字节/位.(如果你知道原理,请告诉我:)
让我们来实现这个算法:
+----< byte string (or file) 字节串,(或是文件)
|
v 3 2 1 0 byte 字节
| +---+---+---+---+
XOR---<| | | | | Register 记存器
| +---+---+---+---+
| |
| XOR
| ^
v +---+---|---+---+
| | | | | | Precomputed table 值表(用来进行操作)
| +---+---+---+---+
+--->-: : : : :
+---+---+---+---+
| | | | |
+---+---+---+---+
(figure 2)
‘reflected‘ direct Table 算法:
由于这里有这样一个与之相对应的‘反射‘算法,事情显得复杂了.一个反射的值/记存器
就是将它的每一位以此串的中心位为标准对调形成的.例如:0111011001就是1001101110
的反射串.
他们提出‘反射‘是因为UART(一种操作IO的芯片)发送每一个字节时是先发最没用的0位,
最后再发最有意义的第七位.这与正常的位置是相逆的.
除了信息串不做反射以外,在进行下一步操作前,要将其于的数据都做反射处理.所以在
计算值表时,位向右移,且poly也是作过反射处理的.当然,在计算CRC时,记存器也要向右
移,而且值表也必须是反射过的.
byte string (or file) -->---+
| 1. 表中每一个入口都是反射的.
byte 3 2 1 0 V 2. 初始化记存器也是反射的.
+---+---+---+---+ | 3. 但是byte string中的数据不是反射的,
| | | | |>---XOR 因为其他的都做过反射处理了.
+---+---+---+---+ |
| |
XOR V
^ |
+---+---|---+---+ |
| | | | | | 值表
+---+---+---+---+ |
: : : : : <---+
+---+---+---+---+
| | | | |
+---+---+---+---+
(figure 3)
我们的算法如下:
1. 将记存器向右移动一个字节.
2. 将刚移出的哪个字节与byte string中的新字节做XOR运算,
得出一个指向值表table[0..255]的索引
3. 将索引所指的表值与记存器做XOR运算.
4. 如数据没有全部处理完,则跳到步骤1.
下面是这个算法的简单的可执行汇编源码:
完整的CRC-32标准所包含的内容:
Name : "CRC-32"
Width : 32
Poly : 04C11DB7
Initial value : FFFFFFFF
Reflected : True
XOR out with : FFFFFFFF
作为对你好奇心的奖励, 这里是CRC-16标准: :)
Name : "CRC-16"
Width : 16
Poly : 8005
Initial value : 0000
Reflected : True
XOR out with : 0000
‘XOR out with‘ 是为了最终得到CRC而用来与记存器最后结果做XOR运算的值.
假如你想了解一些关于‘reversed‘逆向CRC poly的话,请看我的参考文章.
我是在16位DOS模式下用的32位编码,因此你会在这个程序中看到很多32位与16位混合
的编码...当然这是很容易转换成纯32位编码的.注意这个程序是经过完整测试并且能够
正常运行的.下面的Java 和 C 代码都是由这个汇编代码而来的.
底下的这段程序就是用来计算CRC-32 table的:
xor ebx, ebx ;ebx=0, 将被用做一个指针.
InitTableLoop:
xor eax, eax ;eax=0 为计算新的entry.
mov al, bl ;al<-bl
;生成入口.
xor cx, cx
entryLoop:
test eax, 1
jz no_topbit
shr eax, 1
xor eax, poly
jmp entrygoon
no_topbit:
shr eax, 1
entrygoon:
inc cx
test cx, 8
jz entryLoop
mov dword ptr[ebx*4 + crctable], eax
inc bx
test bx, 256
jz InitTableLoop
注释: - crctable 是一个包含256个dword的数组.
- 由于使用反射算法,EAX被向右移.
- 因此最低的8位被处理了.
用Java和C写的代码如下(int is 32 bit):
for (int bx=0; bx<256; bx++){
int eax=0;
eax=eax&0xFFFFFF00+bx&0xFF; // 就是 ‘mov al,bl‘ 指令
for (int cx=0; cx<8; cx++){
if (eax&&0x1) {
eax>>=1;
eax^=poly;
}
else eax>>=1;
}
crctable[bx]=eax;
}
下面的汇编代码是用来计算CRC-32的:
computeLoop:
xor ebx, ebx
xor al, [si]
mov bl, al
shr eax, 8
xor eax, dword ptr[4*ebx+crctable]
inc si
loop computeLoop
xor eax, 0FFFFFFFFh
注释: - ds:si 指向将要被处理的byte string信息流.
- cx 信息流的长度.
- eax 是当前的CRC.
- crctable是用来计算CRC的值表.
- 此例中记存器的初始值为: FFFFFFFF.
- 要将中间值与FFFFFFFFh做XOR才能得到CRC
面是Java和C写的代码:
for (int cx=0; cx>=8;
eax^=crcTable[ebx];
}
eax^=0xFFFFFFFF;
现在我们已经完成了本文的第一部分:CRC原理部分,所以如果你希望能够对CRC做更深
的研究,那么我建议你去读在本文最后给出连接上的资料,我读了.好了,终于到了本文最
有意思的部分,CRC的逆向分析!
------------------------------------------------------------------------------------
第二部分 CRC的逆向分析:
我遇到了很多障碍,当我思考如何破解CRC时.我试图使用一些特殊顺序的字节使CRC无效.
但我没有做到...后来我意识到这种方法是行不同的,因为CRC内建了一些处理过程,无论你
改变任何位它都不会出问题,真正的CRC就是在不断变化的,总是在变化的.找一些CRC程序,
你可以自己尝试一下.
现在我知道我只能‘纠正‘在CRC后面的那些我想改变的字节.所以我要构造一个字节序列,
它可以将CRC转化成任何我想要的样子!
具体实现这个想法
一个字节串? 01234567890123456789012345678901234567890123456789012
You want to change from ^ this byte to ^ this one.
就是位置9->26.
同时我们需要额外的4个字节用来在最后恢复原始字节串.
当你计算CRC-32时,从0-8都没有问题,直到第9位,修补过的字节串会使CRC发生根本的改变.
即使当走过了第26位,以后的字节都没有改变,你也不可能在得到原始的CRC了,不可能了!你读
过后面的段落时就会明白为什么.间而言之,当你修改一个字节串时,要保证CRC不变.
1. 计算并保存从1~9位的CRC.
2. 继续计算直到第27位还有额外的4字节并保存结果.
3. 用1的值来计算新的字节串和额外4字节的CRC(对应patch后的新的CRC值),并将之保存.
4. 现在我们得到了一个新的CRC,但是我们希望将它还原成原先的CRC,所以我们用逆向算法
来计算那额外的4字节.
1~3就是实际的情况,下面你将学到最关键的部分4.
‘反转‘CRC-16
我想,先来介绍计算逆CRC-16对于你来说会简单些.好的,我们现在处在一个恰当的位置,
在以修改代码后面,就是你想将CRC还原的地方.我们知道原始的CRC(是在patch代码之前计
算出来的)还有这个当前的记存器值.现在我们的目的就是计算可以改变当前记存器值到原
始记存器值的两个字节.首先,我们用正常的方法计算这两个未知字节的CRC.我们设他们为
X,Y.设记存器为a1,a0,只有0不能用来作为变量(00).:)在来看一下我们的CRC算法,figure
3,更好的理解下面我要做的.
好,我们开始:
用这两字节串‘X Y‘ 字节是从左边开始被处理的.
记存器现在是a1 a0.
用‘+‘来表示XOR运算(和第一部分中用的一样)
处理第一个字节, X:
a0+X 这是顶部字节的计算结果 (1)
b1 b0 这是(1)在表中索引对象.
00 a1 向右移动记存器.
00+b1 a1+b0 上面两行对应位做XOR运算.
现在记存器为: (b1) (a1+b0)
处理第二个字, Y:
(a1+b0)+Y 此轮顶部字节的计算结果(2)
c1 c0 这是(2)在表中的索引对象.
00 b1 向右移动记存器.
00+c1 b1+c0 上面两行对应位做XOR运算.
最后记存器就是: (c1) (b1+c0)
我用一点不同的方法来表示:
a0 + X =(1) 在表中指向b1 b0.
a1 + b0 + Y =(2) 在表中指向c1 c0.
b1 + c0=d0 记存器中新的低位字节.
c1=d1 记存器中新的高位字节.
(1) (2)
Wow! 请大家暂时记住上面的信息:)
别着急, 下面给出一个有具体值的例子.
如果你想要的记存器的值是d1 d0(是原始的CRC),而且你知道在变换之前的记存器的值
(a1 a0)...那么你将要送如什么样的2个字节进记存器来做CRC计算呢?
好了,现在我们的工作应该从幕后走到台前来了.d0一定是bi+c0,并且d1一定是c1...
但是这到底是怎么回事,我听到你这样问了,你能知道b1和c0的值吗???你还记得哪个值表
吗?你只需要在表中查找c0 c1这个字的值就可以了因为你知道c1.所以你需要编写一个查
找程序.假如你找到了这个值,一定要记住这个值的索引,因为这就是找出未知的两个顶部
字节,举例来说:(1)和(2)!
所以,现在你找到了c1 c0,那么如何来得到b1 b0呢?如果b1+c0=d0那么b1=d0+c0!如前所
述,现在你用哪个查找程序在表中查b1 b0的值.现在我们得到了所有计算X和Y所需要的值.
Cool huh?
a1+b0+Y=(2) so Y=a1+b0+(2)
a0+X=(1) so X=a0+(1)
实例.
让我们来看看这个具体值的例子:
-register before: (a1=)DE (a0=)AD
-wanted register: (d1=)12 (d0=)34
在附录的CRC-16的表中查找以12开头值的入口.这里入口38h的值为12C0.试这找一找是否还
有以12开头的值的入口.你不可能在找到的,因为我们计算每一中顶部字节组合而得的值的
入口,一共是256个值,记住!
现在我们知道(2)=38,c1=12,c0=C0,所以b1=C0+34=F4,现在查找以F4开头的b1的入口.这里
入口4Fh的值是F441.
我们还知道 (1)=4F,b1=F4,b0=41,现在所有我们需要的都已经清楚了,接下来我们计算X,Y.
Y=a1+b0+(2)=DE+41+38=A7
X=a0+(1) =AD+4F =E2
结论:将CRC 记存器的值从 DEAD 变为 1234 我们需要这两个字节 E2 A7 (以此顺序).
你看,破解CRC校验你需要反向计算,还有要记住的就是计算过程中的值.当你在用汇编编写
查找表程序时,要注意intel在小模式中是反向存储值的.现在你可能已经明白如何去破解这个
CRC-16了...下面介绍如何在CRC-32中实现.
破解 CRC-32
现在我们来看CRC-32,和CRC-16是一样容易的(可能一样的不容易你认为).这里你操作的对象
是4个字节的而不是2字节的.继续向下看,将它与上面CRC-16版本做对比.
设4字节串 X Y Z W , 从左边开始处理.
设记存器为 a3 a2 a1 a0
注意a3是MSB,而a0是LSB
处理第一个字节, X:
a0+X 这是顶部字节的计算结果(1).
b3 b2 b1 b0 这是(1)在表中索引对象序列.
00 a3 a2 a1 右移记存器.
00+b3 a3+b2 a2+b1 a1+b0 上面两行对应位做XOR运算.
现在记存器是: (b3) (a3+b2) (a2+b1) (a1+b0)
Processing second byte, Y:
(a1+b0)+Y 这是顶部字节的计算结果(2).
c3 c2 c1 c0 这是(2)在表中索引对象序列.
00 b3 a3+b2 a2+b1 右移记存器.
00+c3 b3+c2 a3+b2+c1 a2+b1+c0 上面两行对应位做XOR运算.
现在记存器是: (c3) (b3+c2) (a3+b2+c1) (a2+b1+c0)
Processing third byte, Z:
(a2+b1+c0)+Z 这是顶部字节的计算结果(3).
d3 d2 d1 d0 这是(3)在表中索引对象序列.
00 c3 b3+c2 a3+b2+c1 右移记存器.
00+d3 c3+d2 b3+c2+d1 a3+b2+c1+d0 上面两行对应位做XOR运算.
现在记存器是: (d3) (c3+d2) (b3+c2+d1) (a3+b2+c1+d0)
Processing fourth byte, W:
(a3+b2+c1+d0)+W 这是顶部字节的计算结果(4).
e3 e2 e1 e0 这是(4)在表中索引对象序列.
00 d3 c3+d2 b3+c2+d1 右移记存器.
00+e3 d3+e2 c3+d2+e1 b3+c2+d1+e0 上面两行对应位做XOR运算.
最后,记存器为: (e3) (d3+e2) (c3+d2+e1) (b3+c2+d1+e0)
我用一个不同一点的方法来表示:
a0 + X =(1) 在表中指向 b3 b2 b1 b0
a1 + b0 + Y =(2) 在表中指向 c3 c2 c1 c0
a2 + b1 + c0 + Z =(3) 在表中指向 d3 d2 d1 d0
a3 + b2 + c1 + d0 + W =(4) 在表中指向 e4 e3 e2 e1
b3 + c2 + d1 + e0 =f0
c3 + d2 + e1 =f1
d3 + e2 =f2
e3 =f3
(1) (2) (3) (4)
(figure 4)
这里是用的与CRC-16同样的方法来实现的,我会给出一个具体值的例子.查找用附录中
CRC-32的值表.
Take for CRC register before, a3 a2 a1 a0 -> AB CD EF 66
Take for CRC register after, f3 f2 f1 f0 -> 56 33 14 78 (wanted value)
我们开始:
First byte of entries entry value
e3=f3 =56 -> 35h=(4) 56B3C423 for e3 e2 e1 e0
d3=f2+e2 =33+B3 =E6 -> 4Fh=(3) E6635C01 for d3 d2 d1 d0
c3=f1+e1+d2 =14+C4+63 =B3 -> F8h=(2) B3667A2E for c3 c2 c1 c0
b3=f0+e0+d1+c2=78+23+5C+66=61 -> DEh=(1) 616BFFD3 for b3 b2 b1 b0
Now we have all needed values, then
X=(1)+ a0= DE+66=B8
Y=(2)+ b0+a1= F8+D3+EF=C4
Z=(3)+ c0+b1+a2= 4F+2E+FF+CD=53
W=(4)+d0+c1+b2+a3=35+01+7A+6B+AB=8E
(final computation)
结论:要将 CRC-32 的记存器的值从 ABCDEF66 改变到 56331478 我们需要这样一个字节
序列: B8 C4 53 8E
主题 :CRC原理及其逆向分析方法.
作者 : Anarchriz/DREAD.
发布日期 :29 april 1999 (最后更改 30 april 1999).
目的 :CRC 算法.
使用工具 :QEdit 2.1 (The best!) &Wordpad & some CRC progs.
figure :CRC教程与c001的CRC逆向分析方法.
翻译:arbin
CRC原理及其逆向破解方法:
介绍:
这篇短文包含CRC原理介绍和其逆向分析方法,很多程序员和破解者不是很清楚了解
CRC的工作原理,而且几乎没人知道如何逆向分析它的方法,事实上它是非常有用的.
首先,这篇教程教你一般如何计算CRC,你可以将它用在数据代码保护中.第二,主要是
介绍如何逆向分析CRC-32,你可以以此来分析程序中的CRC保护(象反病毒编码).当然
有很多有效的工具用来对付CRC,但我怀疑它是否会说明原理.
我要告诉你,这篇短文里中应用了很多数学知识,这不会影响一些人,而且会被一般的
程序员与逆向分析者很好理解.为什么?那么如果你不知道数学是如何被应用在CRC中,
我建议你可以停止继续学习了.所以我假定你们(读者)都是具备二进制算术知识的.
第一部分:CRC 介绍,CRC是什么和计算CRC的方法.
循环冗余码 CRC
我们都知道CRC.甚至你没有印象,但当你想到那些来自诸如RAR,ZIP等压缩软件发给你
由于错误连接和其他一些意外原因导致的文件错误的恼人的消息时,你就会知道.CRC是块
数据的计算值,比如对每一个文件进行压缩.在一个解压缩过程中,程序会从新计算解压文件
的CRC值,并且将之与从文件中读取的CRC值进行比对,如果值相同,那么正确.在CRC-32中,
会有1/2^32的可能性发生对确认数据更改的校验错误.
很多人认为CRC就是循环冗余校验,假如CRC真的就是循环冗余校验,那么很多人都错用了
这个术语.你不能说"这个程序的CRC是12345678".人们也常说某一个程序有CRC校验,而不
说是 "循环冗余校验" 校验.结论:CRC 代表循环冗余码,而不是循环冗余校验.
计算是如何完成的呢?好,主要的想法就是将一个文件看成一个被一些数字分割的很长的
位字串,这里会有一个余数---CRC!你总会有一个余数(可以是0),它至多比除数小一.
(9/3=3 余数=0 ; (9+2)/3=3 余数=2)
(或者它本身就包含一个除数在其中).
在这里CRC计算方法与除法有一点点区别,除法就是将被减数重复的减去除数X次,然后留下
余数.如果你希望得到原值,那么你就要把除数乘上X次,然后加上余数.
CRC计算使用特殊的减法与加法完成的.也就是一种新的"算法".计算中每一位计算的进位值
被"遗忘"了.
看如下两个例子,1是普通减法,2和3是特殊的.
-+
(1) 1101 (2) 1010 1010 (3) 0+0=0 0-0=0
1010- 1111+ 1111- 0+1=1 *0-1=1
---- ---- ---- 1+0=1 1-0=1
0011 0101 0101 *1+1=0 1-1=0
在(1)中,右数第二列可以看成是0-1=-1,因此要从高位借1,就变成(10+0)-1=1.(这就象普通
的‘by-paper‘十进制减法).特例(2,3)中,1+1会有正常的结果10,‘1‘是计算后的进位.这个值
被忽略了.特殊情况0-1应该有正常结果‘-1‘就要退到下一位.这个值也被忽略了.假如你对编程
有一定了解,这就象,XOR 操作或者更好.
现在来看一个除法的例子:
在普通算法中:
1001/1111000\1101 13 9/120\13
1001 - 09 -|
---- -- |
1100 30 |
1001 - 27 -
---- --
0110 3 -> 余数
0000 -
----
1100
1001 -
----
011 -> 3, 余数
在CRC算法中:
1001/1111000\1110 9/120\14 余数为 6
1001 -
----
1100
1001 -
----
1010
1001 -
----
0110
0000 -
----
110 -> 余数
(例 3)
这个除法的商并不重要,也没必要去记住,因为他们仅仅是一组无关紧要的位串.真正
重要的是余数!它就是这个值,可以说比原文件还重要的值,他就是基本的CRC.
过度到真正的CRC码计算.
进行一个CRC计算我们需要选则一个除数,从现在起我们称之为"poly".宽度W就是最高位
的位置,所以这个poly 1001的W 是3,而不是4.注意最高位总是1,当你选定一个宽度,那么你只
需要选择低W各位的值.
假如我们想计算一个位串的CRC码,我们想确定每一个位都被处理过,因此,我们要在目标
位串后面加上W个0位.在此例中,我们假设位串为1111.请仔细分析下面一个例子:
Poly = 10011, 宽度 W=4
位串 Bitstring
Bitstring + W zeros = 110101101 + 0000
10011/1101011010000\110000101 (我们不关心此运算的商)
10011|||||||| -
-----||||||||
10011|||||||
10011||||||| -
-----|||||||
00001||||||
00000|||||| -
-----||||||
00010|||||
00000||||| -
-----|||||
00101||||
00000|||| -
-----||||
01010|||
00000||| -
-----|||
10100||
10011|| -
-----||
01110|
00000| -
-----|
11100
10011 -
-----
1111 -> 余数 -> the CRC!
(例 4)
重要两点声明如下:
1.只有当Bitstring的最高位为1,我们才将它与poly做XOR运算,否则我们只是将
Bitstring左移一位.
2.XOR运算的结果就是被操作位串bitstring与低W位进行XOR运算,因为最高位总为0.
算法设计:
你们都应知道基于位运算的算法是非常慢的而且效率低下.但如果将计算放在每一字节上
进行,那么效率将大大提高.不过我们只能接受poly的宽度是8的倍数(一个字节;).可以形
象的看成这样一个宽度为32的poly(W=32):
3 2 1 0 byte
+---+---+---+---+
Pop! <--| | | | |<-- bitstring with W zero bits added, in this case 32
+---+---+---+---+
1<--- 32 bits ---> this is the poly, 4*8 bits
(figure 1)
这是一个你用来存放暂时CRC结果的记存器,现在我称它为CRC记存器或者记存器.你从右
至左移动位串,当从左边移出的位是1,则整个记存器被与poly的低W位进行XOR运算.(此例
中为32).事实上,我们精确的完成了上面除法所做的事情.
移动前记存器值为:10110100
当从右边移入4位时,左边的高4位将被移出,此例中1011将被移出,而1101被移入.
情况如下:
当前8位CRC记存器 : 01001101
刚刚被移出的高4位 : 1011
我们用此poly : 101011100, 宽度 W=8
现在我们用如前介绍的方法来计算记存器的新值.
顶部 记存器
---- --------
1011 01001101 高四位和当前记存器值
1010 11100 + (*1) Poly 放在顶部最高位进行XOR运算 (因为那里是1)
-------------
0001 10101101 运算结果
现在我们仍有一位1在高4位:
0001 10101101 上一步结果
1 01011100+ (*2) Poly 放在顶部的最低位进行XOR运算 (因为那里是1)
-------------
0000 11110001 第二步运算结果
^^^^
现在顶部所有位均为0,所以我们不需要在与poly进行XOR运算
你可以得到相同的结果如果你先将(*1)与(*2)做XOR然后将结果与记存器值做XOR.
这就是标准XOR运算的特性:
(a XOR b) XOR c = a XOR (b XOR c) 由此,推出如下的运算顺序也是正确的.
1010 11100 poly (*1) 放在顶部最高位
1 01011100+ polys (*2) 放在顶部最低位
-------------
1011 10111100 (*3) XOR运算结果
The result (*3) 将(*3)与记存器的值做XOR运算
1011 10111100
1011 01001101+ 如右:
-------------
0000 11110001
你看到了吗?得到一样的结果!现在(*3)变的重要了,因为顶部为1010则(3)的值总是等于
10111100(当然是在一定的条件之下)这意味着你可以预先计算出任意顶部位结合的XOR值.
注意,顶部结果总是0,这就是组合XOR操作导致的结果.(翻译不准确,保留原文)
现在我们回到figure 1,对每一个顶部字节的值都做移出操作,我们可以预先计算出一个值.
此例中,它将是一个包含256个double word(32 bit)双字的表.(附录中CRC-32的表).
(翻译不准确,保留原文)
用伪语言表示我们的算法如下:
While (byte string is not exhausted)
Begin
Top = top_byte of register ;
Register = Register shifted 8 bits left ORred with a new byte from string ;
Register = Register XORred by value from precomputedTable at position Top ;
End
direct table算法:
上面提到的算法可以被优化.字节串中的字节在被用到之前没有必要经过整个记村器.用
这个新的算法,我们可以直接用一个字节去XOR一个字节串通过将此字节移出记存器.结果
指向预先计算的表中的一个值,这个值是用来被记存器的值做XOR运算的.
我不十分确切的知道为什么这会得到同样的结果(这需要了解XOR运算的特性),但是这又
极为便利,因为你无须在你的字节串后填充0字节/位.(如果你知道原理,请告诉我:)
让我们来实现这个算法:
+----< byte string (or file) 字节串,(或是文件)
|
v 3 2 1 0 byte 字节
| +---+---+---+---+
XOR---<| | | | | Register 记存器
| +---+---+---+---+
| |
| XOR
| ^
v +---+---|---+---+
| | | | | | Precomputed table 值表(用来进行操作)
| +---+---+---+---+
+--->-: : : : :
+---+---+---+---+
| | | | |
+---+---+---+---+
(figure 2)
‘reflected‘ direct Table 算法:
由于这里有这样一个与之相对应的‘反射‘算法,事情显得复杂了.一个反射的值/记存器
就是将它的每一位以此串的中心位为标准对调形成的.例如:0111011001就是1001101110
的反射串.
他们提出‘反射‘是因为UART(一种操作IO的芯片)发送每一个字节时是先发最没用的0位,
最后再发最有意义的第七位.这与正常的位置是相逆的.
除了信息串不做反射以外,在进行下一步操作前,要将其于的数据都做反射处理.所以在
计算值表时,位向右移,且poly也是作过反射处理的.当然,在计算CRC时,记存器也要向右
移,而且值表也必须是反射过的.
byte string (or file) -->---+
| 1. 表中每一个入口都是反射的.
byte 3 2 1 0 V 2. 初始化记存器也是反射的.
+---+---+---+---+ | 3. 但是byte string中的数据不是反射的,
| | | | |>---XOR 因为其他的都做过反射处理了.
+---+---+---+---+ |
| |
XOR V
^ |
+---+---|---+---+ |
| | | | | | 值表
+---+---+---+---+ |
: : : : : <---+
+---+---+---+---+
| | | | |
+---+---+---+---+
(figure 3)
我们的算法如下:
1. 将记存器向右移动一个字节.
2. 将刚移出的哪个字节与byte string中的新字节做XOR运算,
得出一个指向值表table[0..255]的索引
3. 将索引所指的表值与记存器做XOR运算.
4. 如数据没有全部处理完,则跳到步骤1.
下面是这个算法的简单的可执行汇编源码:
完整的CRC-32标准所包含的内容:
Name : "CRC-32"
Width : 32
Poly : 04C11DB7
Initial value : FFFFFFFF
Reflected : True
XOR out with : FFFFFFFF
作为对你好奇心的奖励, 这里是CRC-16标准: :)
Name : "CRC-16"
Width : 16
Poly : 8005
Initial value : 0000
Reflected : True
XOR out with : 0000
‘XOR out with‘ 是为了最终得到CRC而用来与记存器最后结果做XOR运算的值.
假如你想了解一些关于‘reversed‘逆向CRC poly的话,请看我的参考文章.
我是在16位DOS模式下用的32位编码,因此你会在这个程序中看到很多32位与16位混合
的编码...当然这是很容易转换成纯32位编码的.注意这个程序是经过完整测试并且能够
正常运行的.下面的Java 和 C 代码都是由这个汇编代码而来的.
底下的这段程序就是用来计算CRC-32 table的:
xor ebx, ebx ;ebx=0, 将被用做一个指针.
InitTableLoop:
xor eax, eax ;eax=0 为计算新的entry.
mov al, bl ;al<-bl
;生成入口.
xor cx, cx
entryLoop:
test eax, 1
jz no_topbit
shr eax, 1
xor eax, poly
jmp entrygoon
no_topbit:
shr eax, 1
entrygoon:
inc cx
test cx, 8
jz entryLoop
mov dword ptr[ebx*4 + crctable], eax
inc bx
test bx, 256
jz InitTableLoop
注释: - crctable 是一个包含256个dword的数组.
- 由于使用反射算法,EAX被向右移.
- 因此最低的8位被处理了.
用Java和C写的代码如下(int is 32 bit):
for (int bx=0; bx<256; bx++){
int eax=0;
eax=eax&0xFFFFFF00+bx&0xFF; // 就是 ‘mov al,bl‘ 指令
for (int cx=0; cx<8; cx++){
if (eax&&0x1) {
eax>>=1;
eax^=poly;
}
else eax>>=1;
}
crctable[bx]=eax;
}
下面的汇编代码是用来计算CRC-32的:
computeLoop:
xor ebx, ebx
xor al, [si]
mov bl, al
shr eax, 8
xor eax, dword ptr[4*ebx+crctable]
inc si
loop computeLoop
xor eax, 0FFFFFFFFh
注释: - ds:si 指向将要被处理的byte string信息流.
- cx 信息流的长度.
- eax 是当前的CRC.
- crctable是用来计算CRC的值表.
- 此例中记存器的初始值为: FFFFFFFF.
- 要将中间值与FFFFFFFFh做XOR才能得到CRC
面是Java和C写的代码:
for (int cx=0; cx>=8;
eax^=crcTable[ebx];
}
eax^=0xFFFFFFFF;
现在我们已经完成了本文的第一部分:CRC原理部分,所以如果你希望能够对CRC做更深
的研究,那么我建议你去读在本文最后给出连接上的资料,我读了.好了,终于到了本文最
有意思的部分,CRC的逆向分析!
------------------------------------------------------------------------------------
第二部分 CRC的逆向分析:
我遇到了很多障碍,当我思考如何破解CRC时.我试图使用一些特殊顺序的字节使CRC无效.
但我没有做到...后来我意识到这种方法是行不同的,因为CRC内建了一些处理过程,无论你
改变任何位它都不会出问题,真正的CRC就是在不断变化的,总是在变化的.找一些CRC程序,
你可以自己尝试一下.
现在我知道我只能‘纠正‘在CRC后面的那些我想改变的字节.所以我要构造一个字节序列,
它可以将CRC转化成任何我想要的样子!
具体实现这个想法
一个字节串? 01234567890123456789012345678901234567890123456789012
You want to change from ^ this byte to ^ this one.
就是位置9->26.
同时我们需要额外的4个字节用来在最后恢复原始字节串.
当你计算CRC-32时,从0-8都没有问题,直到第9位,修补过的字节串会使CRC发生根本的改变.
即使当走过了第26位,以后的字节都没有改变,你也不可能在得到原始的CRC了,不可能了!你读
过后面的段落时就会明白为什么.间而言之,当你修改一个字节串时,要保证CRC不变.
1. 计算并保存从1~9位的CRC.
2. 继续计算直到第27位还有额外的4字节并保存结果.
3. 用1的值来计算新的字节串和额外4字节的CRC(对应patch后的新的CRC值),并将之保存.
4. 现在我们得到了一个新的CRC,但是我们希望将它还原成原先的CRC,所以我们用逆向算法
来计算那额外的4字节.
1~3就是实际的情况,下面你将学到最关键的部分4.
‘反转‘CRC-16
我想,先来介绍计算逆CRC-16对于你来说会简单些.好的,我们现在处在一个恰当的位置,
在以修改代码后面,就是你想将CRC还原的地方.我们知道原始的CRC(是在patch代码之前计
算出来的)还有这个当前的记存器值.现在我们的目的就是计算可以改变当前记存器值到原
始记存器值的两个字节.首先,我们用正常的方法计算这两个未知字节的CRC.我们设他们为
X,Y.设记存器为a1,a0,只有0不能用来作为变量(00).:)在来看一下我们的CRC算法,figure
3,更好的理解下面我要做的.
好,我们开始:
用这两字节串‘X Y‘ 字节是从左边开始被处理的.
记存器现在是a1 a0.
用‘+‘来表示XOR运算(和第一部分中用的一样)
处理第一个字节, X:
a0+X 这是顶部字节的计算结果 (1)
b1 b0 这是(1)在表中索引对象.
00 a1 向右移动记存器.
00+b1 a1+b0 上面两行对应位做XOR运算.
现在记存器为: (b1) (a1+b0)
处理第二个字, Y:
(a1+b0)+Y 此轮顶部字节的计算结果(2)
c1 c0 这是(2)在表中的索引对象.
00 b1 向右移动记存器.
00+c1 b1+c0 上面两行对应位做XOR运算.
最后记存器就是: (c1) (b1+c0)
我用一点不同的方法来表示:
a0 + X =(1) 在表中指向b1 b0.
a1 + b0 + Y =(2) 在表中指向c1 c0.
b1 + c0=d0 记存器中新的低位字节.
c1=d1 记存器中新的高位字节.
(1) (2)
Wow! 请大家暂时记住上面的信息:)
别着急, 下面给出一个有具体值的例子.
如果你想要的记存器的值是d1 d0(是原始的CRC),而且你知道在变换之前的记存器的值
(a1 a0)...那么你将要送如什么样的2个字节进记存器来做CRC计算呢?
好了,现在我们的工作应该从幕后走到台前来了.d0一定是bi+c0,并且d1一定是c1...
但是这到底是怎么回事,我听到你这样问了,你能知道b1和c0的值吗???你还记得哪个值表
吗?你只需要在表中查找c0 c1这个字的值就可以了因为你知道c1.所以你需要编写一个查
找程序.假如你找到了这个值,一定要记住这个值的索引,因为这就是找出未知的两个顶部
字节,举例来说:(1)和(2)!
所以,现在你找到了c1 c0,那么如何来得到b1 b0呢?如果b1+c0=d0那么b1=d0+c0!如前所
述,现在你用哪个查找程序在表中查b1 b0的值.现在我们得到了所有计算X和Y所需要的值.
Cool huh?
a1+b0+Y=(2) so Y=a1+b0+(2)
a0+X=(1) so X=a0+(1)
实例.
让我们来看看这个具体值的例子:
-register before: (a1=)DE (a0=)AD
-wanted register: (d1=)12 (d0=)34
在附录的CRC-16的表中查找以12开头值的入口.这里入口38h的值为12C0.试这找一找是否还
有以12开头的值的入口.你不可能在找到的,因为我们计算每一中顶部字节组合而得的值的
入口,一共是256个值,记住!
现在我们知道(2)=38,c1=12,c0=C0,所以b1=C0+34=F4,现在查找以F4开头的b1的入口.这里
入口4Fh的值是F441.
我们还知道 (1)=4F,b1=F4,b0=41,现在所有我们需要的都已经清楚了,接下来我们计算X,Y.
Y=a1+b0+(2)=DE+41+38=A7
X=a0+(1) =AD+4F =E2
结论:将CRC 记存器的值从 DEAD 变为 1234 我们需要这两个字节 E2 A7 (以此顺序).
你看,破解CRC校验你需要反向计算,还有要记住的就是计算过程中的值.当你在用汇编编写
查找表程序时,要注意intel在小模式中是反向存储值的.现在你可能已经明白如何去破解这个
CRC-16了...下面介绍如何在CRC-32中实现.
破解 CRC-32
现在我们来看CRC-32,和CRC-16是一样容易的(可能一样的不容易你认为).这里你操作的对象
是4个字节的而不是2字节的.继续向下看,将它与上面CRC-16版本做对比.
设4字节串 X Y Z W , 从左边开始处理.
设记存器为 a3 a2 a1 a0
注意a3是MSB,而a0是LSB
处理第一个字节, X:
a0+X 这是顶部字节的计算结果(1).
b3 b2 b1 b0 这是(1)在表中索引对象序列.
00 a3 a2 a1 右移记存器.
00+b3 a3+b2 a2+b1 a1+b0 上面两行对应位做XOR运算.
现在记存器是: (b3) (a3+b2) (a2+b1) (a1+b0)
Processing second byte, Y:
(a1+b0)+Y 这是顶部字节的计算结果(2).
c3 c2 c1 c0 这是(2)在表中索引对象序列.
00 b3 a3+b2 a2+b1 右移记存器.
00+c3 b3+c2 a3+b2+c1 a2+b1+c0 上面两行对应位做XOR运算.
现在记存器是: (c3) (b3+c2) (a3+b2+c1) (a2+b1+c0)
Processing third byte, Z:
(a2+b1+c0)+Z 这是顶部字节的计算结果(3).
d3 d2 d1 d0 这是(3)在表中索引对象序列.
00 c3 b3+c2 a3+b2+c1 右移记存器.
00+d3 c3+d2 b3+c2+d1 a3+b2+c1+d0 上面两行对应位做XOR运算.
现在记存器是: (d3) (c3+d2) (b3+c2+d1) (a3+b2+c1+d0)
Processing fourth byte, W:
(a3+b2+c1+d0)+W 这是顶部字节的计算结果(4).
e3 e2 e1 e0 这是(4)在表中索引对象序列.
00 d3 c3+d2 b3+c2+d1 右移记存器.
00+e3 d3+e2 c3+d2+e1 b3+c2+d1+e0 上面两行对应位做XOR运算.
最后,记存器为: (e3) (d3+e2) (c3+d2+e1) (b3+c2+d1+e0)
我用一个不同一点的方法来表示:
a0 + X =(1) 在表中指向 b3 b2 b1 b0
a1 + b0 + Y =(2) 在表中指向 c3 c2 c1 c0
a2 + b1 + c0 + Z =(3) 在表中指向 d3 d2 d1 d0
a3 + b2 + c1 + d0 + W =(4) 在表中指向 e4 e3 e2 e1
b3 + c2 + d1 + e0 =f0
c3 + d2 + e1 =f1
d3 + e2 =f2
e3 =f3
(1) (2) (3) (4)
(figure 4)
这里是用的与CRC-16同样的方法来实现的,我会给出一个具体值的例子.查找用附录中
CRC-32的值表.
Take for CRC register before, a3 a2 a1 a0 -> AB CD EF 66
Take for CRC register after, f3 f2 f1 f0 -> 56 33 14 78 (wanted value)
我们开始:
First byte of entries entry value
e3=f3 =56 -> 35h=(4) 56B3C423 for e3 e2 e1 e0
d3=f2+e2 =33+B3 =E6 -> 4Fh=(3) E6635C01 for d3 d2 d1 d0
c3=f1+e1+d2 =14+C4+63 =B3 -> F8h=(2) B3667A2E for c3 c2 c1 c0
b3=f0+e0+d1+c2=78+23+5C+66=61 -> DEh=(1) 616BFFD3 for b3 b2 b1 b0
Now we have all needed values, then
X=(1)+ a0= DE+66=B8
Y=(2)+ b0+a1= F8+D3+EF=C4
Z=(3)+ c0+b1+a2= 4F+2E+FF+CD=53
W=(4)+d0+c1+b2+a3=35+01+7A+6B+AB=8E
(final computation)
结论:要将 CRC-32 的记存器的值从 ABCDEF66 改变到 56331478 我们需要这样一个字节
序列: B8 C4 53 8E
CRC计算(续)
CRC计算实例
如何计算CRC校验码
CRC原理
CRC校验计算方法1
CRC校验原理
循环冗余校验CRC的算法分析
循环冗余校验CRC的算法
CRC - 蚩尤的日志 - 网易博客
装修费用的一些计算公式(续)
祖传计算生命(转载)
DB2中的存储(空间)计算
《锅炉计算手册》(3册)
钢筋工程量计算方法总结(柱)
钢筋工程量计算方法总结(板)
祖传计算生命密法(转载)
磁针石: 可信计算(Good)
祖传计算生命密法(转载)
祖传计算生命 密法(转载)
磁针石: 可信计算(new)
计算生命密法(转载)
祖传计算生命密法(转载)
祖传计算生命密法(转载).
猪的计算公式(让人无语)