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
彙編api函數地址的獲取
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.