CRC原理及其逆向破解方法


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的逆向分析!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM