Post

深入理解CRC

0x01 引言

第一次逆向碰到CRC的算法,一开始看见不知道是在干嘛 还好有大佬指点 知道这是CRC 此处@这是大佬的博客 而且 我看了网上讲关于CRC的帖子 对镜像算法的实现都是引入逆转比特函数 显然不像我这道逆向题的代码 picture 0

0x02 算法分析

本文不是面向CRC小白,你需要先了解CRC基本信息,可以看这些帖子:

我学习CRC32、CRC16、CRC原理和算法的总结(与WINRAR结果一致)

【脑冻结】CRC我就拿下了

如果你已经理解了CRC的工作原理,那么你应该知道CRC的计算方法:直接计算法,驱动表法,镜像驱动表(还有两种变体 不重要)

那么你也理解CRC的驱动表法的工作原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<stdio.h>

#define LUT_LENGTH 8
#define CRC_LENGTH 8
#define CRC_POLY 0x04C11DB7

int main(){
     int CRC_TBALE[1<<LUT_LENGTH];
    for(int index=0;index<(1 << LUT_LENGTH);index++){
        int temp = 0;
        for(int j=LUT_LENGTH;j>0;j--){
            if((index >> (j-1) ^ temp >> (CRC_LENGTH-1)) & 0x1){ //判断首位是不是1
                temp = (temp << 1) ^ CRC_POLY;    //是 左移 异或
            }
            else 
                temp = temp << 1; // 不是 直接左移
        }
        CRC_TBALE[i] = temp;
    }
    for(int i=0;i<(1 << LUT_LENGTH);i++){
        printf("%x\n",CRC_TBALE[i]);
    }
}

通过这种方法生成的表,叫做直接查询表。通过镜像算法生成的表,叫做‘正规查询表’,也是网上查到的表。
借用大佬的图,解释这两个图的关系: picture 1

可以看出要想得到正规查询表 我们需要将输入镜像,然后将表的index镜像,然后将表的内容逆转 网上的镜像算法be like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//“颠倒的直驱表法”的程序: 
 
//同样要先做一个颠倒比特的子程序: 
unsigned long int Reflect(unsigned long int ref, char ch)  
{ 
unsigned long int value=0;  
  // 交换bit0和bit7,bit1和bit6,类推  
for(int i = 1; i < (ch + 1); i++)  
{  
  if(ref & 1)  
    value |= 1 << (ch - i);  
  ref >>= 1;  
}  
return value;  
} 
unsigned long int crc32_table[256];  
unsigned long int ulPolynomial = 0x04c11db7;  
unsigned long int crc,temp;  
 
for(int i = 0; i <= 0xFF; i++)   // 生成CRC32“正规查询表” 
{  
  temp=Reflect(i, 8);  
  crc32_table[i]= temp<< 24;  
  for (int j = 0; j < 8; j++) 
  {  
    unsigned long int t1,t2;  
    unsigned long int flag=crc32_table[i]&0x80000000;  
    t1=(crc32_table[i] << 1);  
    if(flag==0)  
      t2=0;  
    else  
      t2=ulPolynomial;  
    crc32_table[i] =t1^t2 ;  
  }  
  crc=crc32_table[i];  
  crc32_table[i] = Reflect(crc32_table[i], 32);  
}  

可以看到该实现算法引入逆转bit函数,和我们逆的这个不能说差别很大,只能说一点都不像。

那我们看这道逆向题的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include<stdio.h>
int main(){
  unsigned int *puVar1;
  unsigned long uVar2;
  long lVar3;
  unsigned int uVar4;
  unsigned int arr[256];

  unsigned int DAT_0079ac68 = 0xedb88320;
  puVar1 = arr;
  for (uVar2 = 0; 
  uVar4 = -((unsigned int)uVar2 & 1) & DAT_0079ac68 ^ (unsigned int)((uVar2 & 0xffffff) >> 1),
      uVar4 = -(uVar4 & 1) & DAT_0079ac68 ^ uVar4 >> 1, 
      uVar4 = -(uVar4 & 1) & DAT_0079ac68 ^ uVar4 >> 1, 
      uVar4 = -(uVar4 & 1) & DAT_0079ac68 ^ uVar4 >> 1, 
      uVar4 = -(uVar4 & 1) & DAT_0079ac68 ^ uVar4 >> 1, 
      uVar4 = -(uVar4 & 1) & DAT_0079ac68 ^ uVar4 >> 1, 
      uVar4 = -(uVar4 & 1) & DAT_0079ac68 ^ uVar4 >> 1, 
      *puVar1 = -(uVar4 & 1) & DAT_0079ac68 ^ uVar4 >> 1 
      , uVar2 != 0xff; uVar2 = uVar2 + 1) {
    puVar1 = puVar1 + 1;
  }
  for(int i=0;i<256;i++){
    printf("%d %x\n",i,arr[i]);
  }
}

该算法,首先将POLY逆转(0x04c11db7 -> 0xedb88320),但是没有明显的index逆转和表内容逆转 初步推测该算法直接实现了镜像算法,所以没有逆转比特的操作

下面我们来拆解一下这个算法:

1
2
3
for(uVar2=0;....;uVar2!=0xff;uVar=uVar + 1){
    //整个外层循环控制index的范围
}

接着看内存循环,如果在for循环里看这段代码会很奇怪,但是如果你还记得驱动表的生成算法里的内层for循环的话,那我相信这也不难理解 大概率内层for循环被优化掉了,ghidr反编译,直接内联到外层for循环了(这里吐槽ida,ida识别出来真的灾难现场,代码那叫一个惨不忍睹。。。)

1
2
3
4
5
6
7
8
9
10
11
uVar4 = -((unsigned int)uVar2 & 1) & DAT_0079ac68 ^ (unsigned int)((uVar2 & 0xffffff) >> 1),
      uVar4 = -(uVar4 & 1) & DAT_0079ac68 ^ uVar4 >> 1, 
      uVar4 = -(uVar4 & 1) & DAT_0079ac68 ^ uVar4 >> 1, 
      uVar4 = -(uVar4 & 1) & DAT_0079ac68 ^ uVar4 >> 1, 
      uVar4 = -(uVar4 & 1) & DAT_0079ac68 ^ uVar4 >> 1, 
      uVar4 = -(uVar4 & 1) & DAT_0079ac68 ^ uVar4 >> 1, 
      uVar4 = -(uVar4 & 1) & DAT_0079ac68 ^ uVar4 >> 1, 
      *puVar1 = -(uVar4 & 1) & DAT_0079ac68 ^ uVar4 >> 1 
//这段我也是找了好久 没有找到相关直接镜像算法的例子 那只好我自己分析了
// 对比驱动表的算法还是有章可循的

先回忆一下驱动表的算法

1
2
3
4
5
6
7
for(int j=LUT_LENGTH;j>0;j--){
    if((index >> (j-1) ^ temp >> (CRC_LENGTH-1)) & 0x1){ //判断首位是不是1
        temp = (temp << 1) ^ CRC_POLY;    //是 左移 异或
    }
    else 
        temp = temp << 1; // 不是 直接左移
}

镜像算法的原理是相似的,temp根据高位到低位的顺序一步步生成,镜像算法那就根据temp根据地位到高位的顺序生成呗。 下面解释内层循环第一层

1
2
3
4
5
6
7
8
9
10
11
uVar4 = -((unsigned int)uVar2 & 1) & DAT_0079ac68 ^ (unsigned int)((uVar2 & 0xffffff) >> 1),
// -((unsigned int)uVar2 & 1) 用来判断uVar2(相当于驱动表算法的index)的最低位是否为1(想不通的可以手动模拟一下)
// -((unsigned int)uVar2 & 1) & DAT_0079ac68
//     -((unsigned int)uVar2 & 1) == 0 => -((unsigned int)uVar2 & 1) & DAT_0079ac68 = 0
//     -((unsigned int)uVar2 & 1) == 1 => -((unsigned int)uVar2 & 1) & DAT_0079ac68 = DAT_0079ac68
//     -((unsigned int)uVar2 & 1) & DAT_0079ac68 ^ (unsigned int)((uVar2 & 0xffffff) >> 1) 相当于 temp = (temp << 1) ^ CRC_POLY的镜像
  uVar4 = -(uVar4 & 1) & DAT_0079ac68 ^ uVar4 >> 1
// -(uVar4 & 1) 同理
// -(uVar4 & 1) & DAT_0079ac68 ^ uVar4 >> 1 同理
// 到这里可以发现 uVar4 就是驱动表算法里的temp
// -(uVar4 & 1) & DAT_0079ac68 这个位运算我只能说妙啊 省一个if

0x03 算法总结

总结一下 镜像算法是如何实现表index镜像和表内容镜像的: 表index镜像和表内容镜像是一个东西,因为表的内容是由index产生的,index处理方式不同,表的内容就会不同 镜像算法先处理index低位,产生表内容的低位一次类推 写表操作通过*puVar1和puVar1 = puVar1 + 1递推往后写(举个例子 当前index是1,按驱动表法看就是80h,但是写的就是table[1],emmm,you know m3)

0x4 未完待续

picture 2

这里其实也是CRC的一部分,并且是镜像版的”DIRECT TABLE ALGORITHM“,就是在驱动表上的数学优化 算法步骤: Shift the register left by one byte, reading in a new message byte. XOR the top byte just rotated out of the register with the next message byte to yield an index into the table ([0,255]). XOR the table value into the register. Goto 1 iff more augmented message bytes. 具体原理可以看我上面推荐第二篇,讲的很清楚

这里可以看到CRC对整个代码段做了计算,最后的取反也可以理解^0xFFFFFFFF,这是CRC32模型参数(XorOut=FFFFFFFF,表示还需要将结果值与0xffffffff进行XOR)

算法分析完毕!

This post is licensed under CC BY 4.0 by the author.