中級例項教程―之瘋狂單詞v1.70

看雪資料發表於2015-11-15

前言:

俺學破解其實也只有不到半個月的時間,之所以敢到這裡班門弄斧寫“中級”教程,主要是發現各種教程大多在重複入門知識,廣大菜鳥缺乏進一步的指導,抱著“俺不挨臭雞蛋誰挨臭雞蛋”的信念,希望能夠給菜鳥同志們深入學習帶來一點點啟發,如此足矣。

一、研究物件簡介:

《瘋狂單詞》是俺收集到的最好的背單詞共享軟體,我想任何想鑽研技術的人都會發現具備讀懂 e文資料的能力是相當重要的,而學好 e文必須過詞彙關,這也是俺要拿《瘋狂單詞》開刀的初衷,目前最新的版本是v1.7x,用google 可以找到很多提供下載的地方,建議下載語音壓縮版,20兆,當然如果頻寬不是問題,110 兆的語音清晰版發音會更好一些。

《瘋狂單詞》需要重新啟動程式來驗證註冊碼,好像是從“utraledit”學來的,這種方式對“爆破一族”有一定的防範能力,因為程式不再侷限於在“請輸入註冊碼”和“恭喜註冊成功”之間對註冊碼進行驗證,它可以在任何地方對註冊碼進行突擊檢查,一旦發現註冊碼非法,隨時取消已註冊狀態。所以即使爆破註冊成功,爆破者總是無法判斷程式中是否還有地方埋有不知道什麼時候會引爆的“地雷”。

《瘋狂單詞》不是用 註冊碼 = f(使用者名稱) 的方式將正確的註冊碼算出來,再與輸入的註冊碼做比較,而是構造了一個隱函式演算法,程式總是判斷 F(註冊碼,使用者名稱)的值是否合法,這樣,正確的註冊碼永遠不會在程式的計算過程中出現,對於掌握了在暫存器和記憶體中找註冊碼這種初級技術的菜鳥有很好的防範作用。

《瘋狂單詞》運用了一些反跟蹤的手段,特別是針對 bpx +靜態地址的斷點和 bpm斷點,比如你跟蹤到了驗證註冊碼的函式的地址:0040c660,下 bpx 0040c660, 會發現下一次中斷時0040c660處的程式碼變成了“INVALID”; 再比如你跟蹤到存放假碼的地址:024ab100,下 bpm 024ab100, 會發現程式會用各種方法反覆將該地址裡的值轉移到其他地址去,甚至會分批幾個位元組幾個位元組地偷運,或者寫進登錄檔然後再讀進另一個地址,等等。

二、總體破解思路:

不管程式如何驗證註冊碼,在驗證之前它總要知道你輸入的假碼是什麼,所以在驗證之前它必然會完成兩個動作:

a) 從註冊介面讀入假碼
b) 對假碼進行預處理,可能只是簡單的格式驗證也可能是做一個變換:變碼 = f(假碼),然後將處理過後的假碼或變碼放到一個記憶體地址,註冊碼驗證函式一定會訪問這個地址。

所以跟蹤假碼就成為了找到驗證函式的一把鑰匙:

bpx getwindowtexta (或 bpx getdlgitemtexta) do "d esp->8"
中斷後esp存放的是函式執行完畢後的返回地址,因為32位系統中每個地址佔4個位元組空間,所以此時esp->4是傳遞給函式的第 1個引數,也就是呼叫函式前最後一個壓棧的引數的地址,同理esp->8是第二個引數,如此類推。函式getwindowtexta的第二個引數就是函式取得的字串的存放地址,也就是我們要跟蹤的假碼的地址,d esp->8可以得到這一地址,這個地址不是固定的,假設為xxxxxxxx。

bpmd xxxxxxxx rw
密切關注程式對這一地址的任何讀寫操作,如果程式將該地址中的假碼複製到另一地址,馬上 bpmd 另一地址,如果發現程式呼叫regsetvalueexa函式將該地址中的假碼寫入登錄檔,記下呼叫之前倒數第二個壓棧的值(第二個引數),假設為aaaa(dword,四位元組),馬上下 bpx regqueryvalueex if *(esp->8)==aaaa do "d esp->14",如果程式從登錄檔將假碼讀入另一地址,則馬上 bpmd 另一地址。這樣你肯定會在假碼的帶領下找到程式的註冊碼驗證函式!

由於程式往往會將 F(假碼,使用者名稱)分解成不止一個二元方程,即:
f1(假碼) = f(使用者名稱)
f2(假碼) = f(使用者名稱)
......
fn(假碼) = f(使用者名稱)

所以第一次跟蹤到的驗證函式一般只包含大F 演算法的一部分,所以只能初步瞭解該演算法。程式在完成第一次驗證之後可能會告訴你“註冊碼正確,請重新啟動程式完成註冊”,請千萬不要被它迷惑,你必須繼續讓“鑰匙”帶領你去找其他的驗證函式,防止程式“偷偷地”做第二次、第三次甚至第n 次驗證。即使關閉程式也要堅決搞清楚它將鑰匙放到哪裡儲存起來了,防止程式重新執行後再次驗證。在跟蹤假碼時程式可能會反覆對假碼進行變換,每一次變換後都要繼續跟蹤變碼,即使程式將變碼寫入登錄檔,也要看看它會不會再讀出來再做驗證,即使程式什麼事都不做了等著你關閉程式,你也要看看在你點選了“X” 按鈕之後程式有沒有處理OnDestroy 訊息響應函式再次變換或驗證變碼,總之要對假碼及假碼的一切變碼 bpmd 到底!

程式重啟時要採用前面提到的 bpx regqueryvalyeexa 加 bpm 手段繼續跟蹤“鑰匙”,直到你對大F 演算法有了足夠的瞭解,能夠推算出正確的註冊碼甚至寫出序號產生器為止。

三、驗證演算法分析:

跟蹤到第一次驗證函式為0040b880,程式碼分析如下:
:u 0040b880 L 271
001B:0040B880  PUSH      FF-----------------------------------------------------
001B:0040B882  PUSH      00467DFB
001B:0040B887  MOV       EAX,FS:[00000000]
001B:0040B88D  PUSH      EAX
001B:0040B88E  MOV       FS:[00000000],ESP     這一段是函式對呼叫環境的
                                              儲存,不用深究
001B:0040B895  SUB       ESP,000001A4
001B:0040B89B  PUSH      EBX
001B:0040B89C  PUSH      EBP
001B:0040B89D  PUSH      ESI
001B:0040B89E  PUSH      EDI----------------------------------------------------

001B:0040B89F  MOV       ESI,00000008<---------迴圈控制變數,從假碼的第9位開始
                                              驗證      
001B:0040B8A4  MOV       DWORD PTR [ESP+1C],00000000
001B:0040B8AC  MOV       EBX,00000001<---------迴圈控制變數,驗證到第20位時回
                                              到第2位繼續驗證

001B:0040B8B1  PUSH      0049141C-----------------------------------------------
001B:0040B8B6  LEA       ECX,[ESP+14]
001B:0040B8BA  CALL      00449D4A
001B:0040B8BF  MOV       ECX,00000064          將[ESP+24]之後的100個dword地址
001B:0040B8C4  MOV       EAX,EBX               賦值為“1”
001B:0040B8C6  LEA       EDI,[ESP+24]          將[ESP+24]這一地址記入EDI,這
001B:0040B8CA  XOR       EBP,EBP               一地址存放輸入的註冊碼(假碼)
001B:0040B8CC  REPZ STOSD                      的各位數字值,是程式在驗證函式
                                              之前做的
001B:0040B8CE  MOV       EDI,[ESP+000001C8]    再將從[ESP+28]到[ESP+64]這16
001B:0040B8D5  MOV       EAX,00000008          個dword地址依次賦值為
001B:0040B8DA  MOV       ECX,00000005          “1975121885116918”,估計與
001B:0040B8DF  MOV       [ESP+40],EAX          作者生日有關,將用來作為加密
001B:0040B8E3  MOV       [ESP+44],EAX          演算法的一個密碼錶
001B:0040B8E7  MOV       [ESP+60],EAX
001B:0040B8EB  LEA       EAX,[EDI+04]
001B:0040B8EE  MOV       EDX,00000009
001B:0040B8F3  MOV       [ESP+30],ECX
001B:0040B8F7  MOV       [ESP+48],ECX
001B:0040B8FB  PUSH      EAX                   注意這期間進行過一次壓棧操作,所
                                              以ESP的值發生過變化,對後面的
001B:0040B8FC  LEA       ECX,[ESP+14]          [ESP+xx]的地址有所影響
001B:0040B900  MOV       [ESP+000001C0],EBP
001B:0040B907  MOV       [ESP+28],EBX
001B:0040B90B  MOV       [ESP+2C],EDX
001B:0040B90F  MOV       DWORD PTR [ESP+30],00000007
001B:0040B917  MOV       [ESP+38],EBX
001B:0040B91B  MOV       DWORD PTR [ESP+3C],00000002
001B:0040B923  MOV       [ESP+40],EBX
001B:0040B927  MOV       [ESP+50],EBX
001B:0040B92B  MOV       [ESP+54],EBX
001B:0040B92F  MOV       DWORD PTR [ESP+58],00000006----------------------------

001B:0040B937  MOV       [ESP+5C],EDX-------------------------------------------
001B:0040B93B  MOV       [ESP+60],EBX
001B:0040B93F  CALL      0044A0F4
001B:0040B944  LEA       EAX,[EDI+08]
001B:0040B947  LEA       ECX,[ESP+10]
001B:0040B94B  PUSH      EAX
001B:0040B94C  CALL      0044A0F4
001B:0040B951  LEA       EAX,[EDI+14]
001B:0040B954  LEA       ECX,[ESP+10]
001B:0040B958  PUSH      EAX
001B:0040B959  CALL      0044A0F4
001B:0040B95E  LEA       EAX,[EDI+0C]
001B:0040B961  LEA       ECX,[ESP+10]          這一段是將你輸入的使用者名稱、信箱
001B:0040B965  PUSH      EAX                   地址與軟體的名稱、版本號合併成
001B:0040B966  CALL      0044A0F4              為一個目標串,放入[ESP+10],
001B:0040B96B  LEA       EAX,[EDI+10]          程式實際上驗證 F(註冊碼,目標串)
001B:0040B96E  LEA       ECX,[ESP+10]          目標串形如
001B:0040B972  PUSH      EAX                   yourmail+wordslover+yourname+1.60
001B:0040B973  CALL      0044A0F4              看來作者對這一演算法非常滿意,從
001B:0040B978  LEA       EAX,[EDI+1C]          1.6版沿用至今
001B:0040B97B  LEA       ECX,[ESP+10]
001B:0040B97F  PUSH      EAX
001B:0040B980  CALL      0044A0F4
001B:0040B985  LEA       EAX,[EDI+20]
001B:0040B988  LEA       ECX,[ESP+10]
001B:0040B98C  PUSH      EAX
001B:0040B98D  CALL      0044A0F4
001B:0040B992  LEA       EAX,[EDI+24]
001B:0040B995  LEA       ECX,[ESP+10]
001B:0040B999  PUSH      EAX
001B:0040B99A  CALL      0044A0F4-----------------------------------------------

001B:0040B99F  MOV       ECX,[ESP+10]<---------將目標串地址寫入ECX
001B:0040B9A3  XOR       EAX,EAX<--------------EAX清零
001B:0040B9A5  MOV       ECX,[ECX-08]<---------取目標串的長度
001B:0040B9A8  CMP       ECX,EAX<--------------長度為零則跳轉(目標串包含
                                              wordslover1.60,長度怎麼可
                                              能是零?有病!)
001B:0040B9AA  JLE       0040B9F1
001B:0040B9AC  MOV       EDX,EDI<--------------將假碼數字表地址寫入EDX
001B:0040B9AE  MOV       [ESP+18],EAX<---------將[ESP+18]作為從零開始的一個
                                              臨時變數
001B:0040B9B2  MOV       EDX,[ESI*4+EDX+30]<---將假碼當前位(ESI)的數字值
                                              寫入EDX
001B:0040B9B6  MOV       [ESP+20],EDX<---------將該值寫入[ESP+20]儲存

--------------------------------------------------------------------------------

001B:0040B9BA  MOV       EDX,[ESP+10]<---------將目標串地址寫入EDX
001B:0040B9BE  MOV       EDI,[ESP+18]<---------將變數[ESP+18]的值寫入EDI
001B:0040B9C2  AND       EDI,8000000F<---------變數[ESP+18] 除以16的餘數
001B:0040B9C8  MOV       DL,[EDX+EAX]<---------將目標串的當前位的ASC碼寫入DL
001B:0040B9CB  JNS       0040B9D2<-------------若餘數不為負值則跳轉?[ESP+18]
                                              的所有賦值動作都在掌握之內,怎
                                              麼可能大於80000000?有病!
001B:0040B9CD  DEC       EDI
001B:0040B9CE  OR        EDI,-10
001B:0040B9D1  INC       EDI
001B:0040B9D2  MOV       EDI,[EDI*4+ESP+24]<---根據變數[ESP+18]的值讀入密碼錶
                                              (19751218...)的相應位的數值寫
                                              入EDI
001B:0040B9D6  MOVSX     EDX,DL<---------------將DL中的目標串當前位的ASC碼值寫
                                              入EDX
001B:0040B9D9  IMUL      EDI,EDX<--------------當前密碼值與目標串當前ASC碼值的
                                              乘積
001B:0040B9DC  ADD       EDI,[ESP+20]<---------再加上當前假碼數值
001B:0040B9E0  ADD       EBP,EDI<--------------在EBP中累計這一結果
001B:0040B9E2  MOV       EDI,[ESP+18]<---------將變數[ESP+18]的值寫入EDI
001B:0040B9E6  INC       EAX<------------------目標串的當前位+1
001B:0040B9E7  ADD       EDI,ESI<--------------變數[ESP+18]+假碼的當前位
001B:0040B9E9  CMP       EAX,ECX<--------------目標串的當前位是否等於目標串長度
001B:0040B9EB  MOV       [ESP+18],EDI<---------更新變數[ESP+18]
001B:0040B9EF  JL        0040B9BA<-------------迴圈直至目標串結束


這一段迴圈是針對假碼的每一位p[i]計算一個值A[i] = f(p[i],目標串),(其中i=ESI
設目標串為d,密碼錶為c,則 A[i] = {
                                    int num = 0;
                                    for(int j=0; j<d.lenth(); j++)
                                        num = num + d[j]*c[j*i%16]+p[i];
                                    return num;
                                 }

--------------------------------------------------------------------------------

001B:0040B9F1  MOV       EAX,ESI
001B:0040B9F3  AND       EAX,80000007<---------EAX等於假碼當前位除以8的餘數
001B:0040B9F8  JNS       0040B9FF
001B:0040B9FA  DEC       EAX
001B:0040B9FB  OR        EAX,-08
001B:0040B9FE  INC       EAX
001B:0040B9FF  JNZ       0040BA61<-------------餘數不為零則跳轉
001B:0040BA01  CMP       ESI,13
001B:0040BA04  JNZ       0040BA10<-------------不為19則跳轉,除以8餘0怎麼可能
                                              等於19?有病!
001B:0040BA06  MOV       DWORD PTR [ESP+18],00000000
001B:0040BA0E  JMP       0040BA17

--------------------------------------------------------------------------------
001B:0040BA10  LEA       ECX,[ESI+01]<---------ECX等於假碼當前位+1
001B:0040BA13  MOV       [ESP+18],ECX<---------存到臨時變數[ESP+18]
001B:0040BA17  MOV       EDX,[ESP+000001C4]<---字串形式的假碼的地址的指標寫
                                              入EDX
001B:0040BA1E  MOV       EAX,ESI<--------------EAX等於假碼當前位
001B:0040BA20  MOV       EDI,[EDX]<------------字串形式的假碼的地址寫入EDI
001B:0040BA22  CDQ<----------------------------EDX清零
001B:0040BA23  MOV       ECX,[EDI-08]<---------假碼長度寫入ECX
001B:0040BA26  IDIV      ECX<------------------在EDX裡得到假碼當前位(EAX)除
                                              以假碼長度(ECX)的餘數,還不就
                                              是假碼的當前位?毛病!
001B:0040BA28  MOV       AL,[EDI+EDX]<---------將假碼當前位的ASC碼值寫入AL
001B:0040BA2B  MOV       EDI,0000000A<---------EDI等於10
001B:0040BA30  MOV       [ESP+17],AL<----------將假碼當前位的ASC碼值寫入臨時變
                                              量[ESP+17]
001B:0040BA34  MOV       EAX,EBP<--------------當前A值,參見前述
                                              A[i]=f(p[i],目標串)
001B:0040BA36  CDQ<----------------------------EDX清零
001B:0040BA37  IDIV      EDI<------------------在EDX裡得到當前A值(EAX)除以10
                                              的餘數
001B:0040BA39  MOV       EAX,[ESP+18]<---------EAX=假碼當前位+1
001B:0040BA3D  MOV       EDI,[ESP+000001C8]<---ESP+1c8+30是數值形式的假碼的地址
001B:0040BA44  SUB       DL,[EAX*4+EDI+30]<----將DL裡的餘數減去假碼下一位的ASC
                                              碼值
001B:0040BA48  MOV       AL,[ESP+17]<----------AL=假碼當前位的ASC碼值
001B:0040BA4C  ADD       DL,AL<----------------餘數-假碼下一位在加上當前位後的
                                              結果
001B:0040BA4E  MOV       EAX,ESI
001B:0040BA50  PUSH      EDX
001B:0040BA51  CDQ                              
001B:0040BA52  IDIV      ECX                   無用功,老毛病!
001B:0040BA54  MOV       ECX,[ESP+000001C8]    
001B:0040BA5B  PUSH      EDX
001B:0040BA5C  CALL      0044A234<-------------將結果替換字串形式假碼的當前位

這一段是將假碼的第9、17位做變換:
p[8] = p[9] + A[8]%10
p[16] = p[17] + A[16]%10

--------------------------------------------------------------------------------

001B:0040BA61  MOV       EAX,ESI
001B:0040BA63  MOV       ECX,00000007
001B:0040BA68  CDQ
001B:0040BA69  IDIV      ECX
001B:0040BA6B  TEST      EDX,EDX
001B:0040BA6D  JNZ       0040BA96
001B:0040BA6F  CMP       ESI,13
001B:0040BA72  JNZ       0040BA78
001B:0040BA74  XOR       ECX,ECX
001B:0040BA76  JMP       0040BA7B
001B:0040BA78  LEA       ECX,[ESI+01]
001B:0040BA7B  MOV       EAX,EBP
001B:0040BA7D  MOV       EDI,0000000A
001B:0040BA82  CDQ
001B:0040BA83  IDIV      EDI
001B:0040BA85  MOV       EAX,[ESP+000001C8]
001B:0040BA8C  SUB       EDX,[ECX*4+EAX+30]
001B:0040BA90  JNZ       0040BADA

累死了!這一段就不祥加註釋了,反正就是驗證第8、15位(i%7==0)的A值必須為零,
否則就JNZ 0040bada,驗證失敗!
--------------------------------------------------------------------------------

001B:0040BA92  INC       DWORD PTR [ESP+1C]<---若A值為零則標誌變數[ESP+14]+1
001B:0040BA96  INC       ESI<------------------假碼當前位+1
001B:0040BA97  CMP       ESI,13
001B:0040BA9A  JLE       0040BA9E
001B:0040BA9C  MOV       ESI,EBX<--------------若當前位為19(從0開始計算,19是
                                              第20位),則當前位回到1
001B:0040BA9E  LEA       ECX,[ESP+10]
001B:0040BAA2  MOV       DWORD PTR [ESP+000001BC],FFFFFFFF
001B:0040BAAD  CALL      00449CDC
001B:0040BAB2  CMP       DWORD PTR [ESP+1C],14
001B:0040BAB7  JL        0040B8B1<-------------迴圈直至標誌為等於20,相當於把
                                              所有工序重複10遍,因為ESI每從1
                                              到19一次,僅有兩次機會修改標誌位
001B:0040BABD  MOV       AL,BL<----------------函式返回值置1,表示驗證成功  
001B:0040BABF  MOV       ECX,[ESP+000001B4]
001B:0040BAC6  POP       EDI
001B:0040BAC7  POP       ESI
001B:0040BAC8  POP       EBP
001B:0040BAC9  POP       EBX
001B:0040BACA  MOV       FS:[00000000],ECX
001B:0040BAD1  ADD       ESP,000001B0
001B:0040BAD7  RET       0008
001B:0040BADA  LEA       ECX,[ESP+10]
001B:0040BADE  MOV       DWORD PTR [ESP+000001BC],FFFFFFFF
001B:0040BAE9  CALL      00449CDC
001B:0040BAEE  XOR       AL,AL<---------------函式返回值置0,表示驗證失敗
001B:0040BAF0  JMP       0040BABF

這一驗證函式僅僅驗證了假碼的兩位,然後又將假碼的另兩位做了10次變換,然後就恭喜你註冊成功!所以必須繼續跟蹤變碼,發現果然在你下達了關閉程式的指令後它在程式視窗從螢幕上消失的同時偷偷又呼叫了一個類似的驗證函式,這次它用同樣的方法驗證變碼的其中一位而將變碼的另六位做了20次變換!重新啟動程式後你會發現它在很多各地方再次進行驗證和變換,經過推理可知如果你輸入的註冊碼的每一位的A值都等於0,而且每一位在變換後都可以保持原值,則不管程式如何變換,該註冊碼永遠都能透過驗證!

所以。。。俺的序號產生器可以誕生了!


結語:

瘋狂單詞構造了一個巧妙的演算法,要求註冊碼滿足:
註冊碼 = F(註冊碼,使用者名稱)
然後他每次驗證註冊碼之後,只有正確的註冊碼可以保持不變,並透過下一次的驗證。
但不足的是該作者的離散數學功底太差,他將大F分解成多個小f時僅僅採用了分段函
數的形勢,函式表示式本身是一樣的,僅僅是作用域不同而已(驗證不同的位),假如能
夠將大F分解成多個函式表示式不同的小f,透過跟蹤小f分析其大F的演算法將是非常困難的!
當然,衷心希望此文不要被瘋狂單詞作者看見,即使看見,也衷心希望他不會馬上瘋狂:)
預告:俺的下一篇教程將是《楚漢棋緣》,據說是很不錯的象棋共享軟體,敬請關注!

相關文章