动态获取API函数地址
cvc論壇里好久沒人寫基礎文章了,我就大膽地來個大家寫個有關API函數地址獲取的文章,希望對初學病毒的你有所幫助?
要想動態地獲得一個API函數的地址,我們通常都是調用系統的LoadLibraryA()函數和GetProcAddress()函數來動態地獲得。LoadLibraryA()用來加載API函數對應的動態鏈接庫
(dll),GetProcAddress()函數用來獲得API函數對應的入口地址。
那如何獲得LoadLibraryA()和GetProcAddress()的地址呢?
我們知道LoadLibraryA()和GetProcAddress()都是由kernel32.dll引出的函數,所以LoadLibraryA()的地址也可以由GetProcAddress()得到。那么GetProcAddress()的地址如何得
到呢?
上面說過GetProcAddress()的地址在kernel32.dll中,具體地說是在kernel32的引出表中,所以我們的問題變為如何找到kernel32的引出表地址,或者說如何找到kernel32的基地
址。
找kernel32基地址的方法一般有三種:暴力搜索法、異常處理鏈表搜索法、PEB法。
暴力搜索法是最早的動態查找kernel32基地址的方法。它的原理是幾乎所有的win32可執行文件(pe格式文件)運行的時候都加載kernel32.dll,可執行文件進入入口點執行后esp
存放的一般是Kernel32.DLL 中的某個地址,所以沿著這個地址向上查找就可以找到kernel32的基地址。
那么如何知道我們找到的地址是kernel32的基地址呢?
因為kernel32.dll也是標準的pe結構文件,pe結構文件的開始是IMAGE_DOS_HEADER結構,IMAGE_DOS_HEADER結構的第一個字段是e_magic,它的值為’MZ’用于證明這是DOS兼容的
文件類型,所以如果我們找到的地址所指向的字符串為’MZ’,那么我們可以確信這是kernel32的基地址,具體代碼如下:
Find_kernel32:
??? mov eax,[esp]
??? and eax,00000fffh;因為kernel32的基地址在內存中頁的開始處,這代碼的作用是對齊
??? ;頁的開始
compare:
??? cmp eax,40000h;比較是否低于應用程序的邊界
??? jb find_kernel32_fail;低于則查找失敗
??? cmp word ptr[eax],’ZM’;比較e_magic,因為Intel CPU是小端模式所以是’ZM’不是’MZ’
??? je kernel32_found
??? sub eax.100h;以頁為單位加速查找
??? jmp compare
kernel32_found:
補充:and eax,00000ffffh也可以換成or eax,00000fffh xor eax,00000fffh 我更喜歡后一種:)
暴力搜索是有缺陷的,如果搜索的內存為不可讀時,電腦就會藍屏,所以毒客們發明了更安全的異常處理鏈表搜索法。所謂異常處理鏈表就是系統提供的處理異常的機制,當系統
遇到一個不知道如何處理的異常時就會查找異常處理鏈表,找到對應的異常處理程序,把保存的處理程序地址賦給eip,并執行處理程序,避免系統崩潰,異常處理鏈表的最后一項
是默認異常處理函數UnhandledExceptionFilter,因為UnhandledExceptionFilter在kernel32中,所以從UNhandledExceptionFilter地址向上搜索即可找到kernel32的基地址,具體
代碼如下:
Load_SEH:
??? xor edx, edx;edx=0
??? push dword ptr fs:{edx];注冊SEH異常處理函數
??? mov fs:[edx],esp
??? mov eax,[esp+(12*4)];指向SEH末尾
??? xor ax,ax; 對齊于64k邊界開始
find_kernel32:
??? cmp eax,40000h;比較是否低于應用程序的邊界
??? jb find_kernel32_fail;低于則查找失敗
??? cmp word ptr[eax],’ZM’;比較MZ標志
??? je kernel32_found
??? sub eax,65536;以64k為邊界加速查找
??? jmp find_kernel32
kernel32_found:
補充:找UnhandledExceptionFilter函數地址還可以用如下方法UnhandledExceptionFilter指針是在異常鏈表的最后,它的上一個值是指向下一個處理點的地址,因為后面沒有異常
處理點了,所以是0xffffffff,所以獲得UnhandledExceptionFilter地址的代碼為:
GetExceptionFilter:
??? cmp [eax],0xffffffff
??? je GetedExceptionFilter
??? mov eax,[eax]
jmp GetExceptionFilter
GetedExceptionFilter:
??? mov eax,[eax+4]
隨著黑客技術的發展,后來出現了更為簡單的方法:PEB法。(結構示意圖來自 http://bbs.nyasama.com/forum.php?mod=viewthread&tid=585 )
-----------------------------------------PEB--結構--------------------------------------------------------------
View Code-----------------------------------------------------------------------------------------------------------------
原理如下:在NT內核系統中fs寄存器指向TEB結構,TEB+0x30處指向PEB結構,PEB+0x0c處指向PEB_LDR_DATA結構,
PEB_LDR_DATA+0x1c處存放一些動態鏈接庫地址,第一個指向ntdl.dll,第二個就是kernel32.dll的基地址了。對于非NT內核的9x系統也有類似的鏈接結構,但是我沒有找到相關的
文檔,無法做出解釋,記下來就好了,就像考前臨時抱佛腳那樣:) PEB法的代碼如下:
find_kernel32:
push esi
xor eax, eax
assume fs:nothing
mov eax, fs:[eax+030h]; eax指向PEB結構
test eax, eax;是否為9x
js find_kernel32_9x
find_kernel32_nt:
mov eax, [eax + 0ch];eax指向PEB_LDR_DATA結構
mov esi, [eax + 01ch];?
lodsd
mov eax, [eax + 08h];eax中為kernel基地址
jmp find_kernel32_finished
find_kernel32_9x:
mov eax, [eax + 034h]
lea eax, [eax + 07ch]
mov eax, [eax + 03ch]
kernel32_found:
pop esi
補充:只用cmp word ptr[eax],’ZM’來驗證kernel32的基地址有點太單薄,我們可以加點東西,在IMAGE_DOS_HEADER+0x3ch處是e_lfanew存放IMAGE_NT_HEAGER結構的文件地址,
IMAGE_NT_HEAGER結構的第一個字段是signature值為’PE’用于證明是PE文件頭,我們還可以檢查文件的重分配表的文件地址值是否為40h,子系統是否為win32系統,是否是DLL文
件,具體代碼為:
assume esi :ptr IMAGE_DOS_HEADER
cmp [esi].e_magic,IMAGE_DOS_SIGNATURE ?
jne find_kernel32
cmp [esi]. e_lfarlc,040h;比較重分配表的文件地址
jne find_kernel32
add esi,[esi].e_lfanew ;此時edx指向PE文件頭
assume esi:ptr IMAGE_NT_HEADERS
cmp [esi].Signatur,IMAGE_NT_SIGNATURE ;是PE文件嗎?
jne find_kernel32
cmp word ptr [esi].OptionalHeader.Subsystem,2比較子系統
jne find_kernel32
cmp word ptr [esi]. OptionalHeader.DllCharacterstics,00100000b;比較DLL狀態
得到了kernel32的基地址,下一步就是定位到引出表找到GetProcAddress的地址。PE頭+120處就是引出表的文件地址,引出表中有三個我們需要的字段:
AddressOfFunction字段:指向模塊中所有函數地址的數組
AddressOfNames字段:指向模塊中所有函數名稱的數組
AddressOfNameOrdinals字段:指向AddressOfNames數組中函數對應序數的數組
我們可以這樣差找函數地址,用函數的名稱在AddressOfName指向的數組中找對應的序號,序號乘以2后在AddressOfNameOrdinals指向的數組中找對應的序數,序數乘以4后在
AddressOfFunction指向的數組中找對應的函數地址,具體代碼如下:
輸入;
esi=要查找的函數名
eax=函數所在動態鏈接庫地址
輸出:
eax=函數的地址
GetFunctionAddress PROC
mov ebx, [eax + 3Ch];指向PE頭
add ebx, eax
add ebx, 120
mov ebx, [ebx]
add ebx, eax ;ebx=引出表地址
xor edx, edx
mov ecx, [ebx + 32];AddressOfNames
add ecx, eax
push ??? esi
push edx
CompareNext: 在函數名數組中查找下一個函數名
pop edx
pop esi
inc edx
mov edi, [ecx]
add edi, eax
add ecx, 4
push ??? esi
push ??? edx?
CompareName:?? ;比較要查找的函數名是否于函數名數組中的一致
mov dl, [edi]
mov dh, [esi]
cmp dl, dh
jne CompareNext
inc edi
inc esi
cmp byte ptr [esi], 0
je GetAddress
jmp CompareName
GetAddress:
pop edx
pop esi
dec edx
shl edx, 1;乘2
mov ecx, [ebx + 36];AddressOfNameOrdinals
add ecx, eax
add ecx, edx
xor edx, edx
mov dx, [ecx]
shl edx, 2;乘4
mov ecx, [ebx + 28];AddressOfFunction
add ecx, eax
add ecx, edx
add eax, [ecx]?
ret?
GetFunctionAddress ENDP
通過以上討論GetProcAddress地址可以用如下代碼獲得
mov eax ,[ebp+Kernel32]
lea esi,[ebp+API_GetProcAddress]
call GetFuntionAddress
mov [ebp+ADDR_GetProcAddress],eax
對于kernel32引出的函數都可以用如上的方法獲得地址
對于非kernel32引出的函數如MessageBoxA()可以這樣獲取:
Lea ebx,[ebp+offset_DLL_User32]
push ebx
call [ebp+ADDR_LoadLibraryA];裝載 user32.dll??
push eax ;eax = user32的基地址
lea esi,[ebp+API_MessageBoxA]
push esi
call [ebp+ADDR_GetProcAddressA]
mov [ebp+ADDR_MessageBoxA],eax
補充:現在的殺毒軟件都對GetProcAddress函數掛鉤,會對調用GetProcAddress函數的程序進行檢查,所以為了繞過這保護機制我們不用GetProcAddress函數,像獲取kernel32引
出函數那樣,直接在動態鏈接庫中找相應的函數地址,還是以MessageBoxA為例,代碼如下:
lea esi, [ebp + API_LoadLibraryA]
call GetFunctionAddress
lea ebx, [ebp + offset_DLL_User32]
push ??? ebx
call eax ; 裝載 user32.dll?? eax = user32的基地址
lea esi, [ebp + API_MessageBoxA]
call GetFunctionAddress
mov [ebp + ADDR_MessageBoxA], eax
就寫這么多了有錯誤還希望大家指出來,下一次寫有關簡單病毒優化的文章希望大家支持:)
總結
以上是生活随笔為你收集整理的动态获取API函数地址的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机专业英语词汇大全
- 下一篇: 机器人学导论——笔记(1)