病毒基礎系列

看雪資料發表於2002-03-10

前言:
   病毒沒有什麼可怕的,也並不象想像中的複雜,玩彙編的人如果沒有看過病毒?簡直是白活一遭...病毒就象是雙刃劍,惡意使用就會帶來惡果,我本人對於此類行為深惡痛絕!我們研究不是為了破壞而是為了知己知彼,另外病毒中確實也有很多高超的技巧值得我們學習,這才是我們的目的所在,我絕沒有教唆人犯罪的意圖而且就我的水平來講也遠達不到.
   在研究病毒之前有幾項基礎知識要了解:
       1)ring0的獲取,可參見我翻譯的一篇ring0的文章,另外這些資料internet上也很多.
       2)Seh的知識,可參見我寫的<<seh in asm研究>>
       3)PE結構的知識,ZouDan大俠的論文,IczeLion的PE教學和載 LUEVELSMEYER的《PE檔案格式》,最為重要!
       4)檔案讀寫的基本知識,主要是CreateFileA,ReadFile,WriteFile,CreateFileMapping...等檔案讀寫知識,如果你還不瞭解,最好先學學win32ASM的基礎知識.
       5)PolyEngine...MetaMorphism等基本概念
       6)anti-debug的一些概念
       7)有關MBR,FAT等知識,對了解一些老Virus有用.
       

       下面我有的一些tips,可能會對理解病毒有所幫助.另外這些技術在加殼類軟體中也很常見.


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


                               tip1: api函式地址的獲取

這是一個老題目了,如果我們不用任何引入庫,能否在程式中呼叫api函式?當然可以!方法有很多,你可能早就知道了,如果你已經瞭解了,就此打住,這是為還不瞭解這一技術而寫.另外這也是病毒必用的技巧之一,如果你對病毒技術感興趣,接著看下去.

這裡假設你瞭解PE的基本結構,如果還不懂,找點資料來看看,到處都是呦.

在幾乎每個病毒的開頭都用下面的語句:
       call    delta
delta:  
       pop     ebp                            
       sub     ebp,offset delta                
       mov     dword ptr [ebp+offset appBase],ebp
       
讓我們考慮一下程式的執行情況,如果下面的程式碼由編譯器自動編譯連線,那麼程式執行的基址一般是400000h,如果是在Nt下執行,那麼基址可能不同,比如從100000h開始,不用擔心,作業系統的Loader會自動為你重定位.但是這裡停下來讓我們看一下,如果你想要把這段程式碼附加到其他程式的後面並想讓其正確執行的話,就不是那麼簡單了,因為你的程式碼可能要從555588h處開始執行,而在沒有得到宿主程式許可的情況下期望作業系統自動為你修正偏移錯誤是不可能的,既然有非常的目的,就得費點力氣,自己搞定重定位.而上面的程式碼就是首先得到eip指標,也是delta在程式執行時的實際偏移,然後減掉程式碼頭到delta的偏移從而得到你的程式碼的真正基址,後面對於偏移的操作都應以這個真正的偏移為準.這就是你上面看到的.如果不明白,就仔細想一下,nothing difficult!下面的例子演示了這一點,並沒有全部重定位,因為這只是技術演示.

現在回到本文的正式內容,要想獲得api的地址,得首先獲得諸如kernel32.dll,user32.dll的基址,然後再找到真正的函式地址.如何獲得基址和函式地址呢呢?有幾種方法
       1)搜尋宿主的引入表獲得GetModuleHandleA函式和GetProcAddress的地址,然後透過他返回系統dll的基址.因為很多程式都要使用這兩個函式,因此在某些情況下是可行的,如果宿主沒有使用GetProcAddress,那你就不得不搜尋Export表了.
       2)直接獲得kernel32.dll的基址,然後再搜尋Export表獲得GetProcAddress和LoadLibraryA的地址,然後我們就能得到任何想呼叫的函式地址.

       3)硬編碼呼叫函式,比如在9X下GetModuleHandleA的地址一般是BFF7****.

第一種和第三種方法存在相容性的問題,假如宿主沒有呼叫GetModuleHandleA,那麼你就不能獲得基址,別的就更別想了...硬編碼問題更大,作業系統不同則不能執行了,比如9X下可能在有些計算機上正常,但肯定不能在Nt/2K下執行...

第二種方法相容性比較好,因此作以介紹.

       一點背景:在PE Loader裝入我們的程式啟動後堆疊頂的地址是是程式的返回地址,肯定在Kernel中!

       因此我們可以得到這個地址,然後向低地址縮減驗證一直到找到模組的起始地址,驗證條件為PE頭不能大於4096bytes,PE header的ImageBase值應該和當前指標相等,嘿嘿,簡單吧,而且相容性還不錯.

       要獲得Api的地址首先要獲得GetModuleHandle,LoadLibraryA,GetProcAddress的地址,這是透過搜尋Export表來實現的,具體原理就是PE Export表的結構,如果瞭解了PE結構就很簡單了.下面我加了點註釋,沒有最佳化程式碼,是為了便於理解.

       好,這一部分結束了!

這是一個例子,沒有用任何預引入函式,加了一條invoke InitCommonControls是為了在2K下也能正常執行,否則不能在2K下不載入!
程式得到MessageBoxA的地址然後顯示一個訊息框,目的在於演示,重要部分加了註釋,很好明白.
注意連線時加入/section:.text,RWE選項。
.586
.model flat, stdcall
option casemap :none   ; case sensitive
include c:\hd\hd.h
include c:\hd\mac.h

;;--------------

GetApiA         proto    :DWORD,:DWORD

;;--------------
.CODE
appBase         dd ?
k32Base         dd ?

lpApiAddrs      label   near
               dd      offset sGetModuleHandle
               dd      offset sGetProcAddress
               dd      offset sExitProcess
               dd      offset sLoadLibrary
               dd      0

sGetModuleHandle       db "GetModuleHandleA",0
sGetProcAddress        db "GetProcAddress",0
sExitProcess           db "ExitProcess",0
sLoadLibrary           db "LoadLibraryA",0

sMessageBoxA           db "MessageBoxA",0

 
aGetModuleHandle                dd 0
aGetProcAddress                 dd 0
aExitProcess                    dd 0
aLoadLibrary                    dd 0
aMessageBoxA                    dd 0

u32                     db "User32.dll",0
k32                     db "Kernel32.dll",0

sztit                   db "By Hume,2002",0
szMsg0                  db "Hey,Hope U enjoy it!",0
;;-----------------------------------------

__Start:
       invokeInitCommonControls
       
       call    delta
delta:  
       pop     ebp                             ;得到delta地址
       sub     ebp,offset delta                ;因為在其他程式中基址可能不是預設的所以需要重定位
       mov     dword ptr [ebp+offset appBase],ebp     ;呵呵仔細想想
       
mov     ecx,[esp]                       ;返回地址
       xor     edx,edx
getK32Base:
       dec     ecx                             ;逐位元組比較驗證
       mov     dx,word  ptr [ecx+IMAGE_DOS_HEADER.e_lfanew]   ;就是ecx+3ch
       test    dx,0f000h                       ;Dos Header+stub不可能太大,超過4096byte
       jnz     getK32Base                      ;加速檢驗
       cmp     ecx,dword ptr [ecx+edx+IMAGE_NT_HEADERS.OptionalHeader.ImageBase]
       jnz     getK32Base                      ;看Image_Base值是否等於ecx即模組起始值,
       mov     [ebp+offset k32Base],ecx        ;如果是,就認為找到kernel32的Base值
       
       lea     edi,[ebp+offset aGetModuleHandle]
       lea     esi,[ebp+offset lpApiAddrs]
lop_get:
       lodsd
       cmp     eax,0
       jz      End_Get
       push    eax
       push    dword ptr [ebp+offset k32Base]
       callGetApiA                         ;獲取API地址        
       stosd
       jmp     lop_get
End_Get:
       push    offset u32
       call    dword ptr [ebp+offset aLoadLibrary]     ;在程式空間載入User32.dll
       
       lea     EDX,[EBP+OFFSET sMessageBoxA]
       push    edx
       push    eax
       mov     eax,dword ptr [ebp+aGetProcAddress]     ;用GetProcAddress獲得MessageBoxA的地址
       call    eax                                     ;呼叫GetProcAddress

       push    40h+1000h                               ;style
       push    offset sztit                            ;title
       push    offset  szMsg0                          ;訊息內容
       push    0
       call    eax                                     ;一個訊息框產生了...嘿嘿
                                                       ;有理由為此高興吧,因為我們沒有預先引入
@@:                                                     ;這些函式
       push    0
       call    [ebp+aExitProcess]
       
;-----------------------------------------
K32_api_retrieve        proc    Base:DWORD ,sApi:DWORD

       push    edx                     ;儲存edx    
       xor     eax,eax                 ;此時esi=sApi
Next_Api:                               ;edi=AddressOfNames
       mov     esi,sApi
       xor     edx,edx
       dec     edx
Match_Api_name:
       mov     bl,byte  ptr [esi]
       inc     esi
       cmp     bl,0
       jz      foundit

       inc     edx

       push    eax
       mov     eax,[edi+eax*4]         ;AddressOfNames的指標,遞增
       add     eax,Base                ;注意是RVA,一定要加Base值
       cmp     bl,byte  ptr [eax+edx]  ;逐字元比較  
       pop     eax
       jz      Match_Api_name          ;繼續搜尋
       inc     eax                     ;不匹配,下一個api
       loop    Next_Api
       jmp     no_exist                ;若全部搜完,即未存在
foundit:
       pop     edx                     ;edx=AddressOfNameOrdinals
       shl     eax,1                   ;*2得到AddressOfNameOrdinals的指標
       movzx   eax,word  ptr [edx+eax] ;eax返回指向AddressOfFunctions的指標
       ret
no_exist:
       pop     edx
       xor     eax,eax
       ret
K32_api_retrieve        endp
;-----------------------------------------

GetApiA         proc    Base:DWORD,sApi:DWORD
       local    ADDRofFun:DWORD
       pushad
       mov     edi,Base
       add     edi,IMAGE_DOS_HEADER.e_lfanew
       mov     edi,[edi]                       ;現在edi=off PE_HEADER
       add     edi,Base                        ;得到IMAGE_NT_HEADERS的偏移                        

       mov     ebx,edi
       mov     edi,[edi+IMAGE_NT_HEADERS.OptionalHeader.DataDirectory.VirtualAddress]
       add     edi,Base                        ;得到edi=IMAGE_EXPORT_DIRECTORY入口
       
       mov     eax,[edi+1ch]                   ;AddressOfFunctions的地址
       add     eax,Base
       mov     ADDRofFun,eax
                                               ;ecx=NumberOfNames
       mov     ecx,[edi+18h]                  
       mov     edx,[edi+24h]                  
       add     edx,Base                        ;edx=AddressOfNameOrdinals

       mov     edi,[edi+20h]
       add     edi,Base                        ;edi=AddressOfNames
       invokeK32_api_retrieve,Base,sApi
       mov     ebx,ADDRofFun
       shl     eax,2                           ;要*4才得到偏移
       add     eax,ebx
       mov     eax,[eax]
       add     eax,Base                        ;加上Base!
       mov     [esp+7*4],eax                   ;eax返回api地址
       popad
       ret
GetApiA         endp

;-----------------------------------------

END__Start
;------------------------------------------End all

  • 鏈 接:http://bbs.pediy.com
  • 看看我的程式碼

  • 作 者:wowocock  
  • 時 間:2002/08/22 08:55pm
  • 鏈 接:http://bbs.pediy.com
  • 看看我的程式碼
    ;適用系統Win9x/me/2k/xp/nt
    .586p
    .model flat, stdcall
    option casemap :none   ; case sensitive
    include \masm32\include\windows.inc
    include \masm32\include\kernel32.inc
    includelib \masm32\lib\kernel32.lib
    include \masm32\include\user32.inc
    includelib \masm32\lib\user32.lib

    GetApiAddress PROTO :DWORD,:BYTE

    .data
     Kernel32Addrdd ?
     ExportKernel dd ?

     GetProcAddr dd ?
     GetModuleHandleAddr dd ?
     LoadLibraryAddr dd ?
     ExitProcessAddr dd ?

     aGetProcAddr db "GetProcAddress",0
     GetProcAddLen equ $-aGetProcAddr-1

     aGetModuleHandle db "GetModuleHandle",0
     GetModuleHandleLen equ $-aGetModuleHandle-1

     aLoadLibrary db "LoadLibrary",0
     LoadLibraryLen equ $-aLoadLibrary-1

     aExitProcess db "ExitProcess",0
     ExitProcessLen equ $-aExitProcess-1

     szTitle db "Test",0
     temp1 db "   Kernel32.dll Address is:%8x:",0dh,0ah
           db " GetProcAddress Address is:%8x:",0dh,0ah
    db "GetModuleHandle Address is:%8x",0dh,0ah
    db "    LoadLibrary Address is:%8x",0dh,0ah
    db "    ExitProcess Address is:%8x",0
     temp2 db 256 dup(?)
    .code

    Start:
     mov   eax,[esp] ;//取Kernel32返回地址
     and   ax,0f000h
     mov   esi,eax   ;//得到Kernel.PELoader程式碼位置(不精確)
    LoopFindKernel32:
     sub   esi,1000h
     cmp   word ptr[esi],'ZM' ;//搜尋EXE檔案頭
     jnz   short LoopFindKernel32
    GetPeHeader:
     movzx edi,word ptr[esi+3ch]
     add   edi,esi
     cmp   word ptr[edi],'EP' ;//確認是否PE檔案頭
     jnz   short LoopFindKernel32      ;esi->kernel32,edi->kernel32 PE HEADER
     ;//////////////////////////////////////////////////任務:查詢GetProcAddress函式地址
     mov Kernel32Addr,esi

    GetPeExportTable:
     mov   ebp,[edi+78h];4+14h+60h
     add   ebp,Kernel32Addr      ;//得到輸出函式表
     mov   ExportKernel,ebp
     
     push GetProcAddLen
     push offset aGetProcAddr
     call GetApiAddress
     mov  GetProcAddr,eax
     
     push GetModuleHandleLen
     push offset aGetModuleHandle
     call GetApiAddress
     mov  GetModuleHandleAddr,eax

     push LoadLibraryLen
     push offset aLoadLibrary
     call GetApiAddress
     mov  LoadLibraryAddr,eax

     push ExitProcessLen
     push offset aExitProcess
     call GetApiAddress
     mov  ExitProcessAddr,eax
     invoke wsprintf,addr temp2,addr temp1,Kernel32Addr,GetProcAddr,GetModuleHandleAddr,LoadLibraryAddr,ExitProcessAddr
     invoke MessageBoxA,0,addr temp2,addr szTitle,0
     push 0
     call dword ptr[ExitProcessAddr]

    GetApiAddress proc AddressOfName:dword,ApiLength:byte
     push ebx
     push esi
     push edi
     mov  edi,ExportKernel
    assume edi:ptr IMAGE_EXPORT_DIRECTORY
    GetExportNameList:  
     mov   ebx,[edi].AddressOfNames ;//得到輸出函式名錶
     add   ebx,Kernel32Addr     ;ebx->AddressOfNames(函式名字的指標地址).
     xor   eax,eax      ;//函式序號計數
     mov   edx,Kernel32Addr      ;//暫存Kernel32模組控制程式碼;edx->kernel32
     push edi   ;儲存EDI

    LoopFindApiStr:
     add   ebx,04      
     inc   eax          ;//增加函式計數
     mov   edi,[ebx]
     add   edi,edx      ;//得到一個Api函式名字串.edi->函式名
     StrGetProcAddress:
     mov esi,AddressOfName          ;//得到Api名字字串
     cmpsd                   ;比較前4個字元是否相等
     jnz   short LoopFindApiStr  ;eax=函式名的INDEX  
     xor   ecx,ecx
     mov   cl, ApiLength
     sub   cl,4        ;//比較剩餘的GetProcAddress串
     cld
    Goon:
     cmpsb
     jnz   short LoopFindApiStr  ;eax=函式名的INDEX
     loop Goon

     pop edi ;恢復EDI
     mov   esi,edx
     movebx,[edi].AddressOfNameOrdinals
     addebx,esi     ;//取函式序號地址列表,ebx->AddresssOfNameOrdinals
     movzx ecx,word ptr [ebx+eax*2]
     mov   ebx,[edi].AddressOfFunctions
     add   ebx,esi      ;//得到Kernel32函式地址列表
     mov   ebx,[ebx+ecx*4]
     add   ebx,esi      ;//計算GetProcAddress函式地址
     mov   eax,ebx      ;eax=API函式地址,esi=Kernel32.dll hModule
     pop edi
     pop esi
     pop ebx
     ret
    GetApiAddress endp
    end Start

    相關文章