Windows優化大師 v2.9+ (11千字)

看雪資料發表於2000-08-22

Windows優化大師v2.9+的註冊碼加密演算法

                              written by dr0, 2000/08/22

本文將以彙編程式碼為例,解釋一下“Windows優化大師” v2.9+中用到的RSA演算法。
先簡單介紹一下RSA演算法,便於和彙編程式碼對照。具體的數學理論我們在此並不太關心,有興趣的可以去查參考資料。

RSA演算法簡述:
1、取兩個素數p和q。
2、計算n=pq,f=(p-1)(q-1)。
3、隨機選取整數e,滿足條件gcd(e, f)=1,其中gcd為最大公約數。
4、計算d,使得乘積de對f求餘的結果為1,即de和1對f同餘。
上述只有e和n對外公開,用於加密。

加密過程(符號^表示乘冪,mod表示求餘):
    e為加密金鑰,假定明文為m,則密文c = (m ^ e) mod n。

解密過程:
    d為解密金鑰,則解密得到的明文m'= (c ^ d) mod n。

RSA被攻破的一個充分條件是n被因子分解,即得到p和q。因為得到p和q之後便可以計算出f,從而根據“de和1對f同餘”這個條件計算出解密金鑰d來。

根據上面的加密和解密演算法可以看出:
1、RSA的加密和解密是對稱的,即加密和解密可以使用同一個函式;
2、RSA加密主要依靠模冪運算,因此這部分運算的複雜度對演算法的效率影響最大。關於模冪運算,在《計算機密碼學》(清華出版社,盧開澄)一書中講了一種演算法,該演算法很容易理解,其指導思想就是不停地降階,從而降低時間複雜度。用類C語言的偽碼描述如下:

//下面這個函式計算(m ^ e) mod n
ReturnValueType  encrypt_decrypt(m, e, n)
{
  LocalVariables a, b, c;

  a = m;
  b = e;
  c = 1;

  while(b)
  {
    if ((b mod 2) == 0)
        {
        b = b / 2;        //降階
                a = (a * a) mod n;
        }
        else
        {
                b = b - 1;
                c = (a * c) mod n;
        }
  }       
   
  return c;
}

Windows優化大師的破解過程已有很多文章講過,本文重點分析其對我們輸入的註冊碼進行加密運算的部分,因為只有這一部分求逆的難度比較大些,其它部分可以直接拷貝或者很容易求逆(例如環形移位運算)。我們將會看到,其使用的模冪演算法就是上述的演算法。

用IDA反彙編OctoDll.dll,可以看到一個名為Registed( )的函式,這顯然是在判斷註冊碼。這個函式將對我們輸入的兩部分註冊碼分別進行RSA加密,再移位,把移位的結果和一個數(這個數是根據“You are big pig.”、“1234567”以及註冊申請碼計算出來的,這個計算過程無需求逆)進行比較,然後返回另一個值(它的主程式會判斷該值是否為0x14)。

Registed    proc near

    var_10      = dword    ptr -10h
    var_A        = word    ptr -0Ah
    var_8        = dword    ptr -8
    var_4        = dword    ptr -4
    arg_0        = dword    ptr  8
    arg_8        = dword    ptr  10h

        push  ebp
        mov    ebp, esp
        add    esp, 0FFFFFFF0h
        push  ebx
        xor    edx, edx
        mov    [ebp+var_10], edx
        mov    ebx, eax
        xor    eax, eax
        push  ebp
        push  offset loc_4043FE
        push  dword ptr fs:[eax]
        mov    fs:[eax], esp
        mov    eax, [ebp+arg_8]
        mov    [ebp+var_8], eax
        mov    eax, [ebp+arg_0]
        mov    [ebp+var_4], eax
        lea    eax, [ebp+var_10]
        mov    edx, ebx
        call  unknown_libname_17
        mov    eax, [ebp+var_10]
        lea    edx, [ebp+var_A]
        lea    ecx, [ebp+var_8]
        call  sub_4041D8      //核心判斷
        test  eax, eax
        jnz    short loc_4043E4
        xor    ebx, ebx        //bad guy
        jmp    short loc_4043E8

loc_4043E4:
        movzx  ebx, [ebp+var_A] //return value

loc_4043E8:
        xor    eax, eax
        pop    edx
        pop    ecx
        pop    ecx
        mov    fs:[eax], edx
        push  offset loc_404405

loc_4043F5:
        lea    eax, [ebp+var_10]
        call  sub_402E5C
        retn

很顯然,核心判斷在call  sub_4041D8中。跟進去,看見函式體如下。這個函式的前半部分是在根據“You are big pig.”、“1234567”以及註冊申請碼通過雜湊運算計算出幾個數來。後半部分才是最值得關心的。註釋如下:

sub_4041D8    proc near

var_24        = dword    ptr -24h
var_20        = dword    ptr -20h
var_1C        = dword    ptr -1Ch
var_18        = dword    ptr -18h
var_14        = dword    ptr -14h
var_10        = dword    ptr -10h
var_C        = dword    ptr -0Ch
var_8        = dword    ptr -8
var_4        = dword    ptr -4

        push  ebp
        mov    ebp, esp
        add    esp, 0FFFFFFDCh
        push  ebx
        push  esi
        push  edi
        xor    ebx, ebx
        mov    [ebp+var_14], ebx
        mov    esi, ecx
        lea    edi, [ebp+var_10]
        movsd
        movsd
        mov    [ebp+var_8], edx
        mov    [ebp+var_4], eax
        mov    eax, [ebp+var_4]
        call  sub_402FC0
        xor    eax, eax
        push  ebp
        push  offset loc_40435F
        push  dword ptr fs:[eax]
        mov    fs:[eax], esp
        lea    eax, [ebp+var_14]
        mov    edx, offset _str_You_are_big_pig.Text
        call  sub_402EC4
        mov    eax, [ebp+var_4]
        call  unknown_libname_18
        and    eax, 80000007h
        jns    short loc_40422A
        dec    eax
        or    eax, 0FFFFFFF8h
        inc    eax

loc_40422A:
        test    eax, eax
        jz    short loc_40425A
        lea    eax, [ebp+var_4]
        mov    edx, offset _str_1234567.Text
        call    @System@@LStrCat$qqrv ;    System __linkproc__ LStrCat(void)
        mov    eax, [ebp+var_4]
        call    unknown_libname_18
        test    eax, eax
        jns    short loc_40424A
        add    eax, 7

loc_40424A:
        sar    eax, 3
        mov    edx, eax
        shl    edx, 3
        lea    eax, [ebp+var_4]
        call    @System@@LStrSetLength$qqrv ; System __linkproc__ LStrSetLength(void)

loc_40425A:
        xor    esi, esi
        lea    eax, [ebp+var_4]
        call    sub_402FD0
        mov    edi, eax
        lea    eax, [ebp+var_14]
        call    sub_402FD0
        mov    ebx, eax
        jmp    short loc_4042A2

loc_404272:
        mov    eax, [edi+esi*4]
        mov    [ebp+var_1C], eax
        mov    eax, [edi+esi*4+4]
        mov    [ebp+var_18], eax
        mov    edx, ebx
        lea    eax, [ebp+var_1C]
        call    sub_404170
        mov    eax, [ebx]
        mov    [ebx+8], eax
        mov    eax, [ebx+4]
        mov    [ebx+0Ch], eax
        mov    eax, [ebp+var_1C]
        mov    [ebx], eax
        mov    eax, [ebp+var_18]
        mov    [ebx+4], eax
        add    esi, 2

loc_4042A2:
        mov    eax, [ebp+var_4]
        call    unknown_libname_18
        test    eax, eax
        jns    short loc_4042B1
        add    eax, 3

loc_4042B1:
        sar    eax, 2
        cmp    esi, eax
        jb    short loc_404272  //以上為前半部分,不用求逆

        mov    eax, [ebp+var_10]
        xor    edx, edx
        push    edx            //註冊碼的第一部分m1的高位(恆為0)
        push    eax            //註冊碼的第一部分m1的低位
        push    ds:dword_4050D0 //加密金鑰e的高位(0)
        push    ds:dword_4050CC //加密金鑰e的低位(3B442AF9)
        push    ds:dword_4050D8 //n的高位(0)
        push    ds:dword_4050D4 //n的低位(69AAA0E3)
        call    sub_4040B8      //encrypt_decrypt(m1, e, n)
        sub    eax, 2
        mov    [ebp+var_24], eax
        mov    eax, [ebp+var_C]
        xor    edx, edx
        push    edx              //註冊碼的第二部分m2的高位(恆為0)
        push    eax              //註冊碼的第二部分m2的低位
        push    ds:dword_4050D0  //加密金鑰e的高位(0)
        push    ds:dword_4050CC  //加密金鑰e的低位(3B442AF9)
        push    ds:dword_4050D8  //n的高位(0)
        push    ds:dword_4050D4  //n的低位(69AAA0E3)
        call    sub_4040B8      //encrypt_decrypt(m2, e, n)
        sub    eax, 2
        mov    [ebp+var_20], eax
        shl    [ebp+var_24], 2  //移位
        lea    ecx, [ebp+var_24]
        mov    eax, [ecx]
        mov    edx, [ecx+4]
        shrd  eax, edx, 2    //這條和下面一條完成64 bit環形移位
        shr    edx, 2
        mov    [ecx], eax
        mov    [ecx+4], edx
        mov    eax, [ebp+var_24]
        cmp    eax, [ebp+var_1C] //比較
        jz    short loc_404330
        xor    ebx, ebx
        jmp    short loc_404341

loc_404330:
        mov    ax, word ptr [ebp+var_20] //返回值
        and    ax, 0FFFFh
        mov    edx, [ebp+var_8]
        mov    [edx], ax
        or    ebx, 0FFFFFFFFh

loc_404341:
        xor    eax, eax
        pop    edx
        pop    ecx
        pop    ecx
        mov    fs:[eax], edx
        push    offset loc_404366

loc_40434E:
        lea    eax, [ebp+var_14]
        call    sub_402E5C
        lea    eax, [ebp+var_4]
        call    sub_402E5C
        retn

即上面的兩個call sub_4040B8分別等價於
      encrypt_decrypt(m1, e, n)
      encrypt_decrypt(m2, e, n)
只需要把sub_4040B8的函式體和上面的演算法偽碼描述對照一下就明白為什麼等價了(注意windows優化大師使用的是64 bit的運算,每個數要用兩個32 bit的數來表示,因此操作每個64 bit的數至少要兩條指令):

sub_4040B8    proc near

var_8        = dword    ptr -8    //區域性變數c的低位(c中將存放返回值)
var_4        = dword    ptr -4    //區域性變數c的高位
arg_0        = dword    ptr  8      //m的低32位
arg_4        = dword    ptr  0Ch    //m的高32位
arg_8        = dword    ptr  10h    //e的低32位
arg_C        = dword    ptr  14h    //e的高32位
arg_10        = dword    ptr  18h  //n的低32位
arg_14        = dword    ptr  1Ch  //n的高32位

        push    ebp
        mov    ebp, esp
        add    esp, 0FFFFFFF8h
        mov    [ebp+var_8], 1    // c = 1;
        mov    [ebp+var_4], 0
        jmp    short loc_40414A

loc_4040CE:

        push    0
        push    2
        mov    eax, [ebp+arg_8]
        mov    edx, [ebp+arg_C]
        call    __LLMOD          //b mod 2
        cmp    edx, 0
        jnz    short loc_40411B
        cmp    eax, 0
        jnz    short loc_40411B
        push    0
        push    2
        mov    eax, [ebp+arg_8]
        mov    edx, [ebp+arg_C]
        call    __LLDIV          // b = b / 2
        mov    [ebp+arg_8], eax
        mov    [ebp+arg_C], edx
        push    [ebp+arg_14]
        push    [ebp+arg_10]
        push    [ebp+arg_14]
        push    [ebp+arg_10]
        push    [ebp+arg_4]
        push    [ebp+arg_0]
        call    sub_404060      // a = (a * a) mod n
        mov    [ebp+arg_10], eax
        mov    [ebp+arg_14], edx
        jmp    short loc_40414A

loc_40411B:

        mov    eax, [ebp+arg_8]
        mov    edx, [ebp+arg_C]
        sub    eax, 1            // b = b - 1
        sbb    edx, 0
        mov    [ebp+arg_8], eax
        mov    [ebp+arg_C], edx
        push    [ebp+arg_14]
        push    [ebp+arg_10]
        push    [ebp+var_4]
        push    [ebp+var_8]
        push    [ebp+arg_4]
        push    [ebp+arg_0]
        call    sub_404060    //c = (a * c) mod n
        mov    [ebp+var_8], eax
        mov    [ebp+var_4], edx

loc_40414A:
        cmp    [ebp+arg_C], 0    //b等於0嗎?
        jnz    short loc_40415C
        cmp    [ebp+arg_8], 0
        ja    loc_4040CE        //繼續迴圈
        jmp    short loc_404162 

loc_40415C:
        jg    loc_4040CE        //繼續迴圈

loc_404162:
        mov    eax, [ebp+var_8]  //返回值c
        mov    edx, [ebp+var_4]
        pop    ecx
        pop    ecx
        pop    ebp
        retn    18h
sub_4040B8    endp

至此我們已經搞清其加密演算法,且得到如下結果:
e = 0x000000003B442AF9
n = 0x0000000069AAA0E3
剩下的事情就是對n進行因子分解,寫出序號產生器.

相關文章