文章目錄
- 一:動态庫技術
- 1.DLL靜态調用
- 2.DLL動态調用
- 二:動态加載技術
- 1.導出函數起始位址
- 2.在程式設計中使用動态加載技術
- 1.查找kernel32.dll的基位址
- 2.擷取GetProcAddress位址
- 3.在代碼中使用擷取的函數位址程式設計
動态加載技術可以讓程式設計者脫離複雜的導入表結構,在程式空間中構造類似于導入表的調用引入函數機制。
在了解這一知識前最好先了解windows虛拟記憶體管理,詳情見我部落格
一:動态庫技術
1.DLL靜态調用
又稱隐式調用。隐式加載就是在程式編譯的時候就将dll編譯到可執行檔案中。調用一個動态連結庫的函數通常采取的方式是:把産生動态連結庫時産生的".lib"庫檔案和".inc"包含的檔案加入到應用程式的工程中,想使用DLL中的函數時,直接使用函數的名字即可,例如,加入user32.lib user32.inc調用MessageBox函數
2.DLL動态調用
又稱顯式調用。通過API函數加載和解除安裝DLL來達到調用DLL函數的目的。與動态庫調用有關的函數主要包括:
- LoadLibrary(或MFC的AfxLoadLibrary),裝載動态連結庫
- GetProcAddress,擷取要引入函數的VA(虛拟記憶體位址),将符号名或辨別号轉換為DLL内部位址
- FreeLibrary(或MFC的AfxFreeLibrary),釋放動态連結庫
//1.加載動态庫
HINSTANCE m_hDll = LoadLibrary(_T("MFCDLL1.dll"));
//2.根據函數名擷取函數位址
typedef IHpDllWin* (*hpDllFun)();
hpDllFun pShowDlg = (hpDllFun)GetProcAddress(m_hDll, "ShowDialog");
//3.擷取導出類對象指針,調用導出函數
IHpDllWin* m_hpwin = pShowDlg();
//4.解除安裝dll
FreeLibrary(hDll);
二:動态加載技術
1.導出函數起始位址
程式引進動态連結庫的最終目的是要調用動态連結庫裡的函數代碼,是以,擷取動态連接配接庫裡的導出函數起始位址是動态加載技術的關鍵。
現在假設user32.dll被動态裝載到記憶體的0x77DF0000處,那麼MessageBoxA的入口位址VA就是:
0x77DF000 + 0x00026544 = 0x7E16544
如果一個函數在程序空間中的VA 确定以後,最簡單拿的調用方式就是通過一下寫死方式來調用
push xx ;顯示往棧裡壓入該函數的參數,個數由調用的函數決定
......
mov eax,77E16544;
call eax
2.在程式設計中使用動态加載技術
1.查找kernel32.dll的基位址
.386
.model flat,stdcall
option casemap:none
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
;資料段
.data
szText db 'kernel32.dll在本程式位址空間的基位址為:%08x',0dh,0ah,0
kernel32Base dd ?
szBuffer db 256 dup(0)
;代碼段
.code
_getKernelBase proc _dwKernelRetAddress
local @dwRet
pushad
mov @dwRet,0
mov edi,_dwKernelRetAddress
and edi,0ffff0000h ;查找指令所在頁的邊界,以1000h對齊
.repeat
.if word ptr [edi]==IMAGE_DOS_SIGNATURE ;找到kernel32.dll的dos頭
mov esi,edi
add esi,[esi+003ch]
.if word ptr [esi]==IMAGE_NT_SIGNATURE ;找到kernel32.dll的PE頭辨別
mov @dwRet,edi
.break
.endif
.endif
sub edi,010000h
.break .if edi<070000000h
.until FALSE
popad
mov eax,@dwRet
ret
_getKernelBase endp
start:
mov eax,dword ptr [esp]
invoke _getKernelBase,eax
invoke wsprintf,addr szBuffer,addr szText,eax
invoke MessageBox,NULL,addr szBuffer,NULL,MB_OK
ret
end start
2.擷取GetProcAddress位址
;------------------------------------------------
; 從記憶體中子產品的導出表中擷取某個 API 的入口位址
;------------------------------------------------
_getApi proc _hModule,_lpszApi
local @dwReturn,@dwStringLen
pushad
mov @dwReturn,0
call @F
@@:
pop ebx
sub ebx,offset @B
;建立用于錯誤處理的SEH結構
assume fs:nothing
push ebp
lea eax,[ebx+offset _ret]
push eax
lea eax,[ebx+offset _SEHHandler]
push eax
push fs:[0]
mov fs:[0],esp
;計算API字元串的長度(注意帶尾部的0)
mov edi,_lpszApi
mov ecx,-1
xor al,al
cld
repnz scasb
mov ecx,edi
sub ecx,_lpszApi
mov @dwStringLen,ecx
;從DLL檔案頭的資料目錄中擷取導出表的位置
mov esi,_hModule
add esi,[esi+3ch]
assume esi:ptr IMAGE_NT_HEADERS
mov esi,[esi].OptionalHeader.DataDirectory.VirtualAddress
add esi,_hModule
assume esi:ptr IMAGE_EXPORT_DIRECTORY
mov ebx,[esi].AddressOfNames
add ebx,_hModule
xor edx,edx
.repeat
push esi
mov edi,[ebx]
add edi,_hModule
mov esi,_lpszApi
mov ecx,@dwStringLen
repz cmpsb
.if ZERO?
pop esi
jmp @F
.endif
pop esi
add ebx,4
inc edx
.until edx>=[esi].NumberOfNames
jmp _ret
@@:
;API名稱索引->序号索引->位址索引
sub ebx,[esi].AddressOfNames
sub ebx,_hModule
shr ebx,1
add ebx,[esi].AddressOfNameOrdinals
add ebx,_hModule
movzx eax,word ptr [ebx]
shl eax,2
add eax,[esi].AddressOfFunctions
add eax,_hModule
;從位址表得到導出函數位址
mov eax,[eax]
add eax,_hModule
mov @dwReturn,eax
_ret:
pop fs:[0]
add esp,0ch
assume esi:nothing
popad
mov eax,@dwReturn
ret
_getApi endp
start:
invoke _getApi,hDllKernel32,addr szGetProcAddress ;擷取GetProcAddress函數的記憶體位址
mov _GetProcAddress,eax
...
ret
end start
3.在代碼中使用擷取的函數位址程式設計
;聲明函數
_QLMessageBoxA typedef proto :dword,:dword,:dword,:dword
;聲明函數引用
_ApiMessageBoxA typedef ptr _QLMessageBoxA
...
;定義函數
_messageBox _ApiMessageBoxA ?
;動态擷取_messageBox的位址
...
;調用函數
invoke _messageBox,NULL,offset szText,NULL,MB_OK