老掉牙的東西--Api地址的“手動”獲取 (8千字)

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

api函式地址的獲取

                                by Hume/冷雨飄心

這是一個老題目了,如果我們不用任何引入庫,能否在程式中呼叫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的地址然後顯示一個訊息框,目的在於演示,重要部分加了註釋,很好明白.

.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:
        invoke    InitCommonControls
       
        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]
        call    GetApiA                        ;獲取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
        invoke    K32_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

相關文章