CRC32碰撞的實現

看雪資料發表於2004-12-22

昨天晚上開始學習CRC32,發現這個HASH實際上應該很容易得出碰撞,下面給出一種生成碰撞的演算法

用CRC32對長度為N的資料效驗,初始效驗值為0xFFFFFFFF,經過N輪以後得到的值取反作為效驗值

生成碰撞的關鍵就是能夠找到4個位元組使得效驗值經過他們後得到一個已知的數

設:
在經過很多輪後效驗值為ABCD,接著要效驗的資料是abcd,效驗後的結果為WXYZ,其中4輪的查表索引值為mnop
(單個字母都表示一個位元組)
因此關鍵就是由ABCD+WXYZ推出abcd

定義4個函式F(x),G(x),H(x),I(x)分別表示以x為索引查表,取出來的DWORD的從高位到低位的4個位元組

CRC32效驗abcd的過程可以表示為:

R0:A,B,C,D <m=D^d>
R1:F(m),A^G(m),B^H(m),C^I(m) <n=c^C^I(m)>
R2:F(n),F(m)^G(n),A^G(m)^H(n),B^H(m)^I(n) <o=b^B^H(m)^I(n)>
R3:F(o),F(n)^G(o),F(m)^G(n)^H(o),A^G(m)^H(n)^I(o) <p=a^A^G(m)^H(n)^I(o)>
R4:F(p),F(o)^G(p),F(n)^G(o)^H(p),F(m)^G(n)^H(o)^I(p)

到R4這個得到的4個值就是效驗和WXYZ,總結一下這個過程:

-------------------------
<1>
W=F(p);
X=F(o)^G(p);
Y=F(n)^G(o)^H(p);
Z=F(m)^G(n)^H(o)^I(p);

<2>
m=d^D;
n=c^C^I(m);
o=b^B^H(m)^I(n);
p=a^A^G(m)^H(n)^I(o);
-------------------------

以上兩個方程表示了ABCD,abcd,WXYZ之間的關係,而mnop是中間變數

要解出abcd並不困難,設F(x)反函式為RF(x),得:
-----------------------
~<1>
p=RF(W);
o=RF(X^G(p));
n=RF(Y^G(o)^H(p));
m=RF(Z^G(n)^H(o)^I(p));

~<2>
d=m^D;
c=n^C^I(m);
b=o^B^H(m)^I(n);
a=p^A^G(m)^H(n)^I(o);
-----------------------

這裡假設要驗證的資料在記憶體中的分佈是:d,c,b,a  (這是為了方便處理小端模式)

這樣就可以得到一組資料值,不過這裡關鍵的問題是RF(x)是否存在,下面這個函式可以證明y=F(x)是個一一對映:

void TestRF()
{
  BYTE i,j;
  DWORD flag;
  for(i=0;i<=0xFF;i++)
  {
    flag=0;
    for(j=0;j<=0xFF;j++)
    {
      if(HIBYTE(HIWORD(CRC32_tab[j])) == i)
      {
        flag=1;
        printf("%X gets!\r\n",i);
        break;
      }
      if(j==0xFF)break;
    }
    if(!flag)printf("%X can't get RF\r\n",i);
    if(i==0xFF)break;
  }
}

檢測的結果是所有的0~255都是RF(x)的定義域,這說明RF(x)是完全存在的,上述演算法完全可行!

程式設計實現~<1> ~<2>的結果:

BYTE RF(BYTE x)
{
  BYTE j;
  for(j=0;j<=0xFF;j++)
  {
    if(HIBYTE(HIWORD(CRC32_tab[j])) == x)break;
  }
  return j;
}

BYTE F(BYTE x)
{
  return HIBYTE(HIWORD(CRC32_tab[x]));
}
BYTE G(BYTE x)
{
  return LOBYTE(HIWORD(CRC32_tab[x]));
}
BYTE H(BYTE x)
{
  return HIBYTE(LOWORD(CRC32_tab[x]));
}
BYTE I(BYTE x)
{
  return LOBYTE(LOWORD(CRC32_tab[x]));
}

#define MakeLong(a,b) MAKELONG(b,a)
#define MakeWord(a,b) MAKEWORD(b,a)

DWORD rCRC32(DWORD WXYZ,DWORD ABCD)
{
  BYTE p,o,n,m,a,b,c,d,W,X,Y,Z,A,B,C,D;

  W=HIBYTE(HIWORD(WXYZ));
  X=LOBYTE(HIWORD(WXYZ));
  Y=HIBYTE(LOWORD(WXYZ));
  Z=LOBYTE(LOWORD(WXYZ));

  A=HIBYTE(HIWORD(ABCD));
  B=LOBYTE(HIWORD(ABCD));
  C=HIBYTE(LOWORD(ABCD));
  D=LOBYTE(LOWORD(ABCD));

  p=RF(W);
  o=RF(X^G(p));
  n=RF(Y^G(o)^H(p));
  m=RF(Z^G(n)^H(o)^I(p));

  d=m^D;
  c=n^C^I(m);
  b=o^B^H(m)^I(n);
  a=p^A^G(m)^H(n)^I(o);

  return MakeLong(MakeWord(a,b),MakeWord(c,d));
}

DWORD RCRC32(DWORD WXYZ,DWORD abcd)
{
  BYTE p,o,n,m,a,b,c,d,W,X,Y,Z,A,B,C,D;

  W=HIBYTE(HIWORD(WXYZ));
  X=LOBYTE(HIWORD(WXYZ));
  Y=HIBYTE(LOWORD(WXYZ));
  Z=LOBYTE(LOWORD(WXYZ));

  a=HIBYTE(HIWORD(abcd));
  b=LOBYTE(HIWORD(abcd));
  c=HIBYTE(LOWORD(abcd));
  d=LOBYTE(LOWORD(abcd));

  p=RF(W);
  o=RF(X^G(p));
  n=RF(Y^G(o)^H(p));
  m=RF(Z^G(n)^H(o)^I(p));

  D=m^d;
  C=n^c^I(m);
  B=o^b^H(m)^I(n);
  A=p^a^G(m)^H(n)^I(o);

  return MakeLong(MakeWord(A,B),MakeWord(C,D));
}

DWORD CRC32(DWORD ABCD,DWORD abcd)
{
  BYTE p,o,n,m,a,b,c,d,W,X,Y,Z,A,B,C,D;

  A=HIBYTE(HIWORD(ABCD));
  B=LOBYTE(HIWORD(ABCD));
  C=HIBYTE(LOWORD(ABCD));
  D=LOBYTE(LOWORD(ABCD));

  a=HIBYTE(HIWORD(abcd));
  b=LOBYTE(HIWORD(abcd));
  c=HIBYTE(LOWORD(abcd));
  d=LOBYTE(LOWORD(abcd));

  m=d^D;
  n=c^C^I(m);
  o=b^B^H(m)^I(n);
  p=a^A^G(m)^H(n)^I(o);

  W=F(p);
  X=F(o)^G(p);
  Y=F(n)^G(o)^H(p);
  Z=F(m)^G(n)^H(o)^I(p);

  return MakeLong(MakeWord(W,X),MakeWord(Y,Z));
}

這個尋找一個碰撞變得非常簡單,首先選取任意的一段資料,做CRC32效驗,結果就是ABCD
透過計算方程組<1><2>後得到abcd,將abcd和原來的資料連線就是碰撞的結果!

例如,"DonQuixote[CCG][iPB]"這個字串的CRC32是0x8A0C90C9,下面這段程式碼可以算出它的碰撞來:

int main(int argc, char* argv[])
{

  DWORD x=rCRC32(~0x8A0C90C9,~CRC((BYTE*)"ipb",3));

  char str[5];
  memcpy(str,&x,4);
  str[4]=0;
  printf("x=%X\r\nstring=%s\r\n",x,str);

  return 0;
}

這裡給出一些結果:

DonQuixote[CCG][iPB]
123Dp0
ccg_G
ipbkw

他們都得到相同的CRC32效驗和=0x8A0C90C9
(看雪有個工具可以驗證此結果,地址是http://www.pediy.com/tools/Cryptography/Hash/DAMN%20Hash%20Calculat

or%20.zip)

但是這麼做必須修改最後一個DWORD,有時我們可能在修改中間某個位置的DWORD,這也是可以實現的
只要根據WXYZ,abcd計算出ABCD就可以了:
-------------------------
(mnop的計算和~<1>~<2>一樣)
~<2'>
D=m^d;
C=n^c^I(m);
B=o^b^H(m)^I(n);
A=p^a^G(m)^H(n)^I(o);
-------------------------
這樣可以把資料反著"效驗"回去,到了中間某個位置再來放修改值

****************************************************************************************8

現在想到的一些應用:

這種尋找碰撞的演算法可以應用到anti-debug上,大多數效驗都是把驗證值放在效驗段外面
透過計算~<2'>和~<2>可以連著效驗值一起計算,構造出資料滿足:CRC(***A***)=A

另外windows的系統檔案也是CRC32效驗的,這樣就可以寫出一種病毒感染系統檔案後得到的效驗值和原來的一樣
更好的用處的做成RootKit這樣的後門,這樣會更加難以被發現

據說TCP/IP的資料包也是CRC32效驗,那麼也許可以寫出病毒混亂一個資料包後使它的效驗和不變

......




by DonQuixote[CCG][iPB]

2004/12/20

相關文章