股市風暴4.0的外殼分析與脫殼方法(一) (7千字)

看雪資料發表於2001-06-10

標題:股市風暴4.0的外殼分析與脫殼方法
所用工具:trw2000,Procdump
等級:手動脫殼入門
前言:股市風暴這個軟體本身對我沒有多大用處,但是它的外殼讓我花費了相當長的時間,現在終於基本明白了它的流程,寫下來,與那些對脫殼感興趣的同志共同交流。
正文:
對於一個加殼的程式,常見的暴力破解方法一般有兩種,一種是直接地脫殼,另外一種是利用SMC,即在外殼程式中加入一段額外的程式碼,當外殼對程式處理完畢後改變外殼的流程,跳轉到加入的額外程式碼中,在記憶體中對生成的原程式進行修改,達到暴破的目的。對付SMC的常見方法也是運用SMC,即外殼程式也是在執行中動態生成,使破解者無法在外殼執行結束後輕易改變流程。這點在這個外殼中可以說有很充分的表現。當程式載入後可以看到如下程式碼:
0167:005E3000  PUSHA              <-入口
0167:005E3001  CALL    005E40F3
0167:005E3006  RET   
0167:005E3007  AND      DWORD [EDI-22],BYTE +21
0167:005E300B  ADD      BH,[EDI-3E]
0167:005E300E  SCASB 
0167:005E300F  XCHG    BH,[EBX+54]
當程式執行到5E3006後會RET到另一處執行一段程式,執行完後又回到5E3007,此時程式碼已經變為:
0167:005E3000  PUSHA 
0167:005E3001  CALL    005E40F3
0167:005E3006  RET   
0167:005E3007  ADD      EBX,BYTE +07
0167:005E300A  REP JMP  SHORT 005E300C    
0167:005E300D  JECXZ    005E2F92
0167:005E300F  RET   
0167:005E3010  SBB      EDX,[EBX-18]
可以看到下面的程式碼已經完全改變,這就是SMC。如果你很好奇跟進那段SMC程式碼你會發現,那段程式碼執行完後會自行銷燬,看來外殼作者實在很小心謹慎。
SMC程式碼由此開始直到5E3197有近十重之多。當一個外殼運用SMC動態生成程式碼有3重以上時一般用SMC來暴破就很困難了。十重!天哪,還是放棄用SMC暴破的念頭吧。
當這些SMC工作全部完成後(事實上後面還有),是對外殼做一些初始(希望我沒有搞錯),包括對一些函式取他們的入口值,比如ExitProcess、LoadLibraryA等,等這些完成後就進入了外殼中真正重要的部分。
對於一個保護良好的外殼,antidebug是必須的,不在這方面留心一下的話恐怕連自己怎麼被幹掉都不知道了。下面就是他的antidebug程式碼。
:005E3468 8D8539040000            lea eax, dword ptr [ebp+00000439]
:005E346E 50                      push eax            <-EAX=5E3479,異常處理程式地址
:005E346F 33C0                    xor eax, eax
:005E3471 64FF30                  push dword ptr fs:[eax]
:005E3474 648920                  mov dword ptr fs:[eax], esp    <-建立SEH鏈
:005E3477 EB10                    jmp 005E3489
:005E3479 8B642408                mov esp, dword ptr [esp+08]    <-異常處理程式入口,在此下斷點
:005E347D 8B6C2408                mov ebp, dword ptr [esp+08]
:005E3481 8D854F040000            lea eax, dword ptr [ebp+0000044F]
:005E3487 50                      push eax
:005E3488 C3                      ret        <-到5E348F繼續執行

:005E3489 CC                      int 03    <-下faults off,經過時產生異常可中斷在上面入口,如不下faults off,經過時不產生異常就debugger detected了
:005E348A E911010000              jmp 005E35A0
:005E348F 8D8561040000            lea eax, dword ptr [ebp+00000461]
:005E3495 89442404                mov dword ptr [esp+04], eax
:005E3499 646789260000            mov fs:[0000], esp
:005E349F EB10                    jmp 005E34B1
:005E34A1 8B642408                mov esp, dword ptr [esp+08]
:005E34A5 8B6C2408                mov ebp, dword ptr [esp+08]
:005E34A9 8D85A8040000            lea eax, dword ptr [ebp+000004A8]
:005E34AF 50                      push eax
:005E34B0 C3                      ret

:005E34B1 0F018D8E120000          sidt [ebp+0000128E]            <-取IDTR的內容
:005E34B8 8B8590120000            mov eax, dword ptr [ebp+00001290]    <-取IDT表的基地址
:005E34BE 83C04E                  add eax, 0000004E
:005E34C1 668B18                  mov bx, word ptr [eax]
:005E34C4 C1E310                  shl ebx, 10
:005E34C7 668B5802                mov bx, word ptr [eax+02]        <-EBX為INT10的入口偏移
:005E34CB B8000C0000              mov eax, 00000C00
:005E34D0 813B45524548            cmp dword ptr [ebx], 48455245
:005E34D6 0F84C4000000            je 005E35A0        
:005E34DC 813B524F4753            cmp dword ptr [ebx], 53474F52    
:005E34E2 74F2                    je 005E34D6            
以INT10的入口為起點,在C00長度範圍內查詢字串"EREH"和"ROGS",如找到則判斷為有DEBUG程式駐留。why?
:005E34E4 43                      inc ebx
:005E34E5 48                      dec eax
:005E34E6 7FE8                    jg 005E34D0            <-沒有找到,繼續往下
:005E34E8 8D85BA040000            lea eax, dword ptr [ebp+000004BA]
:005E34EE 89442404                mov dword ptr [esp+04], eax
:005E34F2 646789260000            mov fs:[0000], esp
:005E34F8 EB10                    jmp 005E350A
:005E34FA 8B642408                mov esp, dword ptr [esp+08]    <-又是一個異常處理程式入口,可下斷點
:005E34FE 8B6C2408                mov ebp, dword ptr [esp+08]
:005E3502 8D85DE040000            lea eax, dword ptr [ebp+000004DE]
:005E3508 50                      push eax
:005E3509 C3                      ret        <-跳到5E351E

:005E350A 9C                      pushfd            \
:005E350B 810C2400010000          or dword ptr [esp], 00000100     \
:005E3512 7502                    jne 005E3516              >注意
:005E3514 CD20                    int 20             /
:005E3516 9D                      popfd                /
上面這幾行作用是將單步執行標誌TF置真,然後在執行下一句時產生單步異常,就會中斷在上面異常處理程式入口,如果在這還單步走或在下面兩句下斷點的話,破壞了單步標誌,不產生異常就GAME OVER了;
:005E3517 F8                      clc
:005E3518 0F8382000000            jnb 005E35A0
:005E351E 8D85F0040000            lea eax, dword ptr [ebp+000004F0]
:005E3524 89442404                mov dword ptr [esp+04], eax
:005E3528 646789260000            mov fs:[0000], esp
:005E352E EB10                    jmp 005E3540
:005E3530 8B642408                mov esp, dword ptr [esp+08]
:005E3534 8B6C2408                mov ebp, dword ptr [esp+08]
:005E3538 8D8517050000            lea eax, dword ptr [ebp+00000517]
:005E353E 50                      push eax
:005E353F C3                      ret

:005E3540 66B80043                mov ax, 4300             \
:005E3544 CD68                    int 68              \
:005E3546 80BDC410000000          cmp byte ptr [ebp+000010C4], 00  \檢測softice
:005E354D 7408                    je 005E3557              /
:005E354F 66057B0C                add ax, 0C7B              /
:005E3553 6648                    dec ax             /
:005E3555 7449                    je 005E35A0        <-檢測到就跳
:005E3557 8D8529050000            lea eax, dword ptr [ebp+00000529]
:005E355D 89442404                mov dword ptr [esp+04], eax
:005E3561 646789260000            mov fs:[0000], esp
:005E3567 EB10                    jmp 005E3579
:005E3569 8B642408                mov esp, dword ptr [esp+08]
:005E356D 8B6C2408                mov ebp, dword ptr [esp+08]
:005E3571 8D8554050000            lea eax, dword ptr [ebp+00000554]
:005E3577 50                      push eax
:005E3578 C3                      ret

:005E3579 F685C4100000FF          test byte ptr [ebp+000010C4], FF
:005E3580 7412                    je 005E3594
:005E3582 7501                    jne 005E3585

:005E3585 BD4B484342              MOV      EBP,4243484B    \
:005E358A 6A04                    PUSH    BYTE +04     \
:005E358C 58                      POP      EAX          >再次檢測softice
:005E358D CC                      INT3             /
:005E358E 663D0400                CMP      AX,04    /
:005E3592 750C                    jne 005E35A0        <-檢測到就跳
:005E3594 64678F060000            pop word ptr fs:[0000]
:005E359A 83C404                  add esp, 00000004
:005E359D 5D                      pop ebp
:005E359E EB16                    jmp 005E35B6        <-一切正常

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:005E348A(U), :005E34D6(C), :005E3518(C), :005E3555(C), :005E3592(C)
|
:005E35A0 64678F060000            pop word ptr fs:[0000]<-debugger detected,退出
:005E35A6 83C404                  add esp, 00000004
:005E35A9 5D                      pop ebp
:005E35AA 81ED06D24000            sub ebp, 0040D206
:005E35B0 E9D8070000              jmp 005E3D8D
:005E35B5 C3                      ret

:005E35B6 8BDD                    mov ebx, ebp        <-如果沒發現DEBUG程式就到這兒
:005E35B8 81ED06D24000            sub ebp, 0040D206
:005E35BE 8DBD3AE34000            lea edi, dword ptr [ebp+0040E33A]

既然沒發現有異常情況,那接下來就該對原程式解碼了。
0167:005E35D5  POP      ECX
0167:005E35D6  MOV      ESI,[EDI]
0167:005E35D8  ADD      EDI,BYTE +04
0167:005E35DB  LODSB 
0167:005E35DC  ADD      AL,34
0167:005E35DE  JZ      NEAR 005E3B67
0167:005E35E4  LOOP    005E35D6
0167:005E35E6  MOV      EBP,EBP
0167:005E35E8  OR      EAX,EAX
0167:005E35EA  JMP      SHORT 005E35EE
0167:005E35EC  INT      20
0167:005E35EE  CWDE   
0167:005E35EF  CALL    005E376C        <-注意
0167:005E35F4  BOUND    ESI,[EDX+38]
0167:005E35F7  JC      005E3660
0167:005E35F9  INT3   
在5E35D6到5E35E4裡面轉幾個圈後,用F10帶過5E35EF的CALL,你會發現下面的程式碼改變了,所以5E35EF的CALL應該又是一次利用SMC來恢復解碼程式。
下面就是真正解碼的過程:
:005E363D AC                      lodsb            <-讀入一個字元
:005E363E 02C1                    add al, cl
:005E3640 0AC9                    or cl, cl
:005E3642 D2C8                    ror al, cl
:005E3644 0453                    add al, 53
:005E3646 0ADB                    or bl, bl
……                            <-計算過程
:005E3745 AA                      stosb
:005E3746 69D2A7E7E4D4            imul edx, D4E4E7A7
:005E374C F9                      stc
:005E374D 7202                    jb 005E3751        <-第一次計算後放回
:005E374F CD20                    int 20
:005E3751 D1C2                    rol edx, 1
:005E3753 69DB711FEE6A            imul ebx, 6AEE1F71
:005E3759 03DA                    add ebx, edx
:005E375B 49                      dec ecx
:005E375C 0F8FDBFEFFFF            jg 005E363D        <-迴圈,準備計算下一位元組
:005E3762 8D85A6DA4000            lea eax, dword ptr [ebp+0040DAA6]    <-EAX=5E38DF
:005E3768 FFE0                    jmp eax        <-第一次計算全部結束後跳

:005E38DF 93                      xchg eax,ebx        <-準備第二次計算
:005E38E0 61                      popad
:005E38E1 56                      push esi
:005E38E2 57                      push edi
:005E38E3 8BFB                    mov edi, ebx
:005E38E5 8BF7                    mov esi, edi
:005E38E7 8B9DF0E34000            mov ebx, dword ptr [ebp+0040E3F0]
:005E38ED AC                      lodsb            <-再次讀入
:005E38EE EB02                    jmp 005E38F2
:005E38F0 CD20                    int 20
:005E38F2 34B5                    xor al, B5
:005E38F4 2C63                    sub al, 63
……
:005E3916 AA                      stosb            <-第二次計算後放回
:005E3917 49                      dec ecx
:005E3918 7FD3                    jg 005E38ED        <-迴圈,準備計算下一位元組
:005E391A 5F                      pop edi
:005E391B 5E                      pop esi
:005E391C 8B8F00E44000            mov ecx, dword ptr [edi+0040E400]
:005E3922 8B87FCE34000            mov eax, dword ptr [edi+0040E3FC]
:005E3928 F7C100000080            test ecx, 80000000
:005E392E 741C                    je 005E394C
:005E3930 81E1FFFFFF7F            and ecx, 7FFFFFFF
:005E3936 0385DCE34000            add eax, dword ptr [ebp+0040E3DC]
:005E393C 50                      push eax
:005E393D 51                      push ecx
:005E393E E81A000000              call 005E395D        <-又一次計算
:005E3943 83F8FF                  cmp eax, FFFFFFFF
:005E3946 0F84F3010000            je 005E3B3F
:005E394C 83C708                  add edi, 00000008
:005E394F 4E                      dec esi        <-每解碼一個段,ESI減一
:005E3950 7406                    je 005E3958        <-全部解碼完畢後跳
:005E3952 FFA5ACD74000            jmp dword ptr [ebp+0040D7AC]    <-返回上面對下一個段解碼
整個過程迴圈3次,每次迴圈中對同一段程式碼進行3次計算解碼,第一次迴圈是對程式碼段解碼,解碼位元組由401000到523E00,而且只有這一次是隻有2次計算(跳過了5E383E處的CALL),第二次迴圈是對524000到527000的段解碼,第三次迴圈是對528000到52B000的段解碼,這個段可是非常重要的,等跳到5E3598時你可以看一下這個段的內容,往下翻幾頁你就會看到諸如kernel32.dll、DeleteCriticalSection等文字,如果你對PE檔案有些瞭解的話,你應該知道這兒是輸入表,而且,很多的加殼軟體都會對它進行加密,防止軟體被DUMP,既然現在已經對輸入表解碼完畢,那接下來自然是馬上對它再加密。加密程式碼是從5E3B0F到5E3D86,由於比較長而且也不宜說清楚,所以我就不詳細寫了。加密以後的結果是這樣:原來在正常執行時[528xxx]中存放的應該是某個函式的入口地址,比如我機器上[5281A4]中存放的是"BFF8AF06",它是函式"Kernel32!DeleteCriticalSection"的入口地址,而現在被加密後存放的是"850000",而850000處的程式碼為:
0167:00850000  JMP      NEAR [00850120]
[850120]中存放的才是"BFF8AF06"。如果我們DUMP程式的話,由於850000處的空間是程式外殼動態申請的,DUMP時不會被儲存下來,[5281A4]中的內容就指向了無效的地址,執行到那時就會產生非法操作。所以我們想DUMP程式的話當然不希望它被加密,否則脫殼後還要再重建輸入表,而且它在加密時在輸入表裡加入了很多的垃圾,用implist根本不能工作,用Import REConstructor恐怕也得有不少的手工工作要做。既然如此,那索性跳過這一段加密程式碼,讓它不加密不就可以了嗎(注1)?那在哪跳呢?上面我們說了,加密程式碼是從5E3B0F開始的,從那往上找,一看就是:
:005E3AFF 8BB5CCE34000            mov esi, dword ptr [ebp+0040E3CC]
:005E3B05 85F6                    test esi, esi
:005E3B07 0F84BF020000            je 005E3DCC
:005E3B0D 03F2                    add esi, edx
不有個跳轉,跳到5E3DCC,剛好跳過了加密程式碼(事實上當加密完成時是從5E3B1F跳到5E3DCC),既然如此那就改變一下流程,直接跳到5E3DCC。接下來程式又幹了些什麼呢?你可以用Exescope試著開啟原檔案,開啟後看裡面的RC資料,有一些能看到內容,而另外一些則提示無法識別結構,可能被壓縮。而我們根據前面解碼的地址知道已經解碼的段裡並不包括.rsrc段,所以接下來還要幹什麼基本就可以猜到了,應該是對.rsrc段中被加密部分解碼。從5E3DCC往下走幾步後你就會看到這行程式碼:
:005E3DFF E859FBFFFF              call 005E395D
是不是很眼熟?對,上面的解碼部分中第三次解碼也是用的同一個CALL,如果你跟進去的話你會找到它解碼的起始地址是5418CC,剛好在.rsrc段中。
好了,經過上面的CALL後,外殼對於程式的解碼就全部完成了。解碼完成後又該幹什麼呢?既然程式完全解碼了,它當然不希望你把它DUMP出來,接下來就是防DUMP,程式並不複雜,但很有效,它會讓Procdump在DUMP時產生非法操作而關閉,這是如何實現的呢?請看:
先是準備工作,防DUMP一般是對檔案頭進行破壞,而檔案頭一般是防寫的,那它是破壞的呢?它連續呼叫了3個函式,分別是:
0167:005E3E2C  call getcurrentprocessID    <-檢取當前呼叫程式的標示符
……
0167:005E3E55  call openprocess        <-返回一個以存在的過程物件的控制程式碼
……
0167:005E3E8B  call VirtualProtectEx     <-修改呼叫執行緒虛擬地址空間中被提交的頁的訪問保護許可權
透過連續呼叫這三個函式,就去掉了當前程式檔案頭的防寫。如果你很感興趣,你還可以仔細看一下,在每個函式呼叫之前還有一個CALL,它其實是JMP的變形,你再仔細看一下的話,你會發現它其實是一個很精巧的設計,除了做為JMP的變形,可以迷惑人以外,還有一個用處哦。8-)
既然檔案頭已經可寫了,接下來就是破壞:
0167:005E3EAF  MOV      EDI,[EBP+0040E3DC]    <-取程式基址(400000)
0167:005E3EB5  ADD      EDI,[EDI+3C]        <-取程式頭地址(400100)
0167:005E3EB8  OR      WORD [EDI+06],BYTE -01    <-將程式頭偏移06處改為FFFF,那兒原來放的是什麼東西呢?瞭解檔案頭的朋友應該知道那放的是檔案有幾個段。天啊!什麼怪物會有65535個段!怪不得Procdump會 被搞死掉。馬上去掉這句。現在再dump就安全了。
好了,外殼的任務基本完成了,再接下來該幹什麼呢?應該去執行程式了。且慢,還有一項工作沒幹,作案完畢後該破壞現場,毀滅罪證,請看:
0167:005E3EBD  MOV      EBX,[EBP+0040E3F8]
0167:005E3EC3  NOT      EBX
0167:005E3EC5  ADD      EBX,[EBP+0040E3DC]
0167:005E3ECB  MOV      [ESP-04],EBX        <-此時EBX=509EAB,入口地址?
0167:005E3ECF  LEA      EDI,[EBP+0040E2D6]
0167:005E3ED5  XOR      EAX,EAX
0167:005E3ED7  MOV      ECX,020B
0167:005E3EDC  REP STOSB             <-這段是將5E4110~5E431B清零

0167:005E3EDE  LEA      EDI,[EBP+0040D1C6]
0167:005E3EE4  MOV      ECX,0EEB
0167:005E3EE9  REP STOSB
0167:005E3EEB  STOSB              <-這段是將從5E3000到這句的所有外殼程式碼清零。

0167:005E3EEC  LEA      EDI,[EBP+0040D1C6]
0167:005E3EF2  MOV      BYTE [EDI],E9
0167:005E3EF5  INC      EDI
0167:005E3EF6  SUB      EBX,EDI
0167:005E3EF8  SUB      EBX,BYTE +04
0167:005E3EFB  MOV      [EDI],EBX        <-這是將5E3000處程式碼改為JMP 00509EAB,why?

0167:005E3EFD  LEA      EDI,[EBP+0040E0B1]
0167:005E3F03  MOV      ECX,1F
0167:005E3F08  REP STOSB
0167:005E3F0A  STOSB              <-再將這段與上面一段清零。

0167:005E3F0B  JMP      SHORT 005E3F0F
0167:005E3F0D  INT      20
0167:005E3F0F  POPA   
0167:005E3F10  JMP      NEAR [ESP-24]        <-跳到509EAB處,應該就是程式入口吧。
現在你可以用Procdump來dump了,不過你會發現,dump出的程式即使能反彙編,得到的也是亂七八糟的東西,原來從509EAB到522541這一段程式碼還會對程式做解碼工作,所以你可以一直執行到522541,然後等跳到509E35處時再開始dump,此時得到的程式就完全暴露在你眼前,你想對她做什麼都可以了(xixi.不要想壞主意哦)如果你在這之前跳過了對輸入表加密的那一段的話,dump出的程式連輸入表都無須修復,這樣的dump是不是很完美?(缺點就是這次的執行得以非法操作告終8-()
當然必要的修復總是要的,你得將入口偏移改為109E35,輸入表偏移改為128000,然後就可以執行了。
最後還有一個小問題,就是每次執行它都會在當前目錄下生成一個stockstorm.EXE,而且會不給你任何警告就重啟。太可惡了!難道想透過不斷的重啟來搞垮我的電腦嗎?幹掉它!用W32DASM反彙編,查詢EXITWINDOWS,共找到3個,其中一個是這兒:
* Reference To: user32.ExitWindowsEx, Ord:0000h
:005097FA E875D5EFFF              Call 00406D74        <-罪魁禍首就是它

由此往上看有:
* Reference To: kernel32.CopyFileA, Ord:0000h
:00509786 E889CEEFFF              Call 00406614
:0050978B 8D55EC                  lea edx, dword ptr [ebp-14]
:0050978E A1606C5200              mov eax, dword ptr [00526C60]
……

複製檔案?好象我們找到地方了。怎樣才能避過這些呢?再往上找有:
:00509731 648920                  mov dword ptr fs:[eax], esp
:00509734 84C9                    test cl, cl
:00509736 0F84D3000000            je 0050980F        <-這個
:0050973C 6A00                    push 00000000
將上面這個je改為jmp再執行,一切OK!
別急,一般執行已經沒問題了,不過它還會對一些程式過敏,比如filemon、softice等,如果記憶體中剛好有filemon在執行的話同樣也會導致重啟,
* Reference To: kernel32.CreateFileA, Ord:0000h
                                  |
:004A2FA8 E87F36F6FF              Call 0040662C        <-利用CreateFileA開啟檔案
:004A2FAD 83F8FF                  cmp eax, FFFFFFFF
:004A2FB0 744D                    je 004A2FFF        <-開啟不成功則跳
:004A2FB2 50                      push eax
…………
* Reference To: user32.ExitWindowsEx, Ord:0000h
                                  |
:004A2FE2 E88D3DF6FF              Call 00406D74
:004A2FE7 EB16                    jmp 004A2FFF
程式試圖開啟的檔案包括TRW、TRW2000、SOFTICE、SOFTICE FOR NT、FILEMON、REGMON、WKPE,開啟成功則重啟,對付的方法是將上面的je改為jmp就可以了。
這樣改應該不犯法吧?我只是讓它在我的電腦上跑得更順暢。脫殼後程式的啟動時間比原先大約減少了一半,
脫殼前後的程式如果都進行壓縮的話,脫殼後的程式體積大約也減小了一半。還有由於外殼的原因,原來的程式在NT下據說無法正常進入,這樣改動後應該可以了吧(沒有驗證)。減小體積、加快啟動,改進相容性,這算不算對原程式的最佳化?
注1:事實上這樣做是有問題的,正常程式的輸入表在程式被載入時由系統根據函式名進行初始化,在不同的系統下會初始化為不同的入口值,以便跨平臺執行,現在程式被加殼,載入時真正的輸入表並沒有被初始化,這個工作得由外殼程式來完成,所以外殼對輸入表的加密同時也是初始化,如果我們跳過加密程式碼的話,輸入表沒有被初始化,下面執行時會非法操作,不過這對於我們脫殼沒有影響。
感謝:在脫殼過程中得到了Ljttt等的幫助,沒有他們的指點,我也寫不出這篇文章,所以在此表示感謝。

                 hying[CCG]
                2001年5月28日

相關文章