我們截獲函數(shù)執(zhí)行最直接的目的就是為函數(shù)增添功能,修改返回值,或者為調試以及性能測試加入附加的代碼,或者截獲函數(shù)的輸入輸出作研究,破解使用。通過訪 問源代碼,我們可以輕而易舉的使用重建(Rebuilding)操作系統(tǒng)或者應用程序的方法在它們中間插入新的功能或者做功能擴展。然而,在今天這個商業(yè) 化的開發(fā)世界里,以及在只有二進制代碼發(fā)布的系統(tǒng)中,研究人員幾乎沒有機會可以得到源代碼。本文主要討論Detour在Windows二進制PE文件基礎 上的API截獲技術。對于Linux平臺,作這件事情將會非常的簡單,由于最初的操作系統(tǒng)設計者引入了LD_PRELOAD。如果你設置 LD_PRELOAD=mylib.so ,那么應用程序在載入 dll時,會先查看mylib.so的符號表,在relocation 的時候會優(yōu)先 使用mylib.so 里的 symbol 。假如你在mylib.so里有個printf() ,那么這個printf就會替代libc的 printf。 而在mylib.so里的這個printf可以直接訪問 libc.so里的printf函數(shù)指針來獲得真正的 printf的入口地 址。 這樣,所有的dll的API HOOK在loader加載dll的時候就已經(jīng)完成,非常自然,和平臺相關的部分全部交給loader去處理。
一、 Detour開發(fā)庫:
? 簡介
Detours是一個在x86平臺上截獲任意Win32函數(shù)調用的工具庫。中斷代碼可以在運行時動態(tài)加載。Detours使用一個無條件轉移指令來替換目 標函數(shù)的最初幾條指令,將控制流轉移到一個用戶提供的截獲函數(shù)。而目標函數(shù)中的一些指令被保存在一個被稱為“trampoline” (譯注:英文意為蹦 床,雜技)的函數(shù)中,在這里我覺得翻譯成目標函數(shù)的部分克隆/拷貝比較貼切。這些指令包括目標函數(shù)中被替換的代碼以及一個重新跳轉到目標函數(shù)的無條件分 支。而截獲函數(shù)可以替換目標函數(shù),或者通過執(zhí)行“trampoline”函數(shù)的時候將目標函數(shù)作為子程序來調用的辦法來擴展功能。
Detours是執(zhí)行時被插入的。內存中的目標函數(shù)的代碼不是在硬盤上被修改的,因而可以在一個很好的粒度上使得截獲二進制函數(shù)的執(zhí)行變得更容易。例如, 一個應用程序執(zhí)行時加載的DLL中的函數(shù)過程可以被插入一段截獲代碼(detoured),與此同時,這個DLL還可以被其他應用程序按正常情況執(zhí)行(譯 注:也就是按照不被截獲的方式執(zhí)行,因為DLL二進制文件沒有被修改,所以發(fā)生截獲時不會影響其他進程空間加載這個DLL)。不同于DLL的重新鏈接或者 靜態(tài)重定向,Detours庫中使用的這種中斷技術確保不會影響到應用程序中的方法或者系統(tǒng)代碼對目標函數(shù)的定位。
如果其他人為了調試或者在內部使用其他系統(tǒng)檢測手段而試圖修改二進制代碼,Detours將是一個可以普遍使用的開發(fā)包。據(jù)我所知,Detours是第一 個可以在任意平臺上將未修改的目標代碼作為一個可以通過“trampoline”調用的子程序來保留的開發(fā)包。而以前的系統(tǒng)在邏輯上預先將截獲代碼放到目 標代碼中,而不是將原始的目標代碼做為一個普通的子程序來調用。我們獨特的“trampoline”設計對于擴展現(xiàn)有的軟件的二進制代碼是至關重要的。
出于使用基本的函數(shù)截獲功能的目的,Detours同樣提供了編輯任何DLL導入表的功能,達到向存在的二進制代碼中添加任意數(shù)據(jù)節(jié)表的目的,向一個新進 程或者一個已經(jīng)運行著的進程中注入一個DLL。一旦向一個進程注入了DLL,這個動態(tài)庫就可以截獲任何Win32函數(shù),不論它是在應用程序中或者在系統(tǒng)庫 中。
? 基本原理
1. WIN32進程的內存管理
眾所周知,WINDOWS NT實現(xiàn)了虛擬存儲器,每一WIN32進程擁有4GB的虛存空間, 關于WIN32進程的虛存結構及其操作的具體細節(jié)請參閱WIN32 API手冊, 以下僅指出與Detours相關的幾點:
(1) 進程要執(zhí)行的指令也放在虛存空間中
(2) 可以使用QueryProtectEx函數(shù)把存放指令的頁面的權限更改為可讀可寫可執(zhí)行,再改寫其內容,從而修改正在運行的程序
(3) 可以使用VirtualAllocEx從一個進程為另一正運行的進程分配虛存,再使用 QueryProtectEx函數(shù)把頁面的權限更改為可讀可寫可執(zhí)行,并把要執(zhí)行的指令以二進制機器碼的形式寫入,從而為一個正在運行的進程注入任意的代碼 。
2. 攔截WIN32 API的原理
Detours定義了三個概念:
(1) Target函數(shù):要攔截的函數(shù),通常為Windows的API。
(2) Trampoline函數(shù):Target函數(shù)的部分復制品。因為Detours將會改寫Target函數(shù),所以先把Target函數(shù)的前5個字節(jié)復制保存好,一方面仍然保存Target函數(shù)的過程調用語義,另一方面便于以后的恢復。
(3) Detour 函數(shù):用來替代Target函數(shù)的函數(shù)。
Detours在Target函數(shù)的開頭加入JMP Address_of_ Detour_ Function指令(共5個字節(jié))把對Target函數(shù) 的調用引導到自己的Detour函數(shù), 把Target函數(shù)的開頭的5個字節(jié)加上JMP Address_of_ Target _ Function+ 5共10個字節(jié)作為Trampoline函數(shù)。請參考下面的圖1和圖2。
(圖1:Detour函數(shù)的過程)

(圖2: Detour函數(shù)的調用過程)

說明:
? 目標函數(shù):
目標函數(shù)的函數(shù)體(二進制)至少有5個字節(jié)以上。按照微軟的說明文檔Trampoline函數(shù)的函數(shù)體是拷貝前5個字節(jié)加一個無條件跳轉指令的話(如果沒 有特殊處理不可分割指令的話),那么前5個字節(jié)必須是完整指令,也就是不能第5個字節(jié)和第6個字節(jié)是一條不可分割的指令,否則會造成Trampoline 函數(shù)執(zhí)行錯誤,一條完整的指令被硬性分割開來,造成程序崩潰。對于第5字節(jié)和第6個字節(jié)是不可分割指令需要調整拷貝到雜技函數(shù)(Trampoline)的 字節(jié)個數(shù),這個值可以查看目標函數(shù)的匯編代碼得到。此函數(shù)是目標函數(shù)的修改版本,不能在Detour函數(shù)中直接調用,需要通過對Trampoline函數(shù) 的調用來達到間接調用。
? Trampoline函數(shù):
此函數(shù)默認分配了32個字節(jié),函數(shù)的內容就是拷貝的目標函數(shù)的前5個字節(jié),加上一個JMP Address_of_ Target _ Function+5指令,共10個字節(jié)。
此函數(shù)僅供您的Detour函數(shù)調用,執(zhí)行完前5個字節(jié)的指令后再絕對跳轉到目標函數(shù)的第6個字節(jié)繼續(xù)執(zhí)行原功能函數(shù)。
? Detour函數(shù):
此函數(shù)是用戶需要的截獲API的一個模擬版本,調用方式,參數(shù)個數(shù)必須和目標函數(shù)相一致。如目標函數(shù)是__stdcall,則Detour函數(shù)聲明也必須 是__stdcall,參數(shù)個數(shù)和類型也必須相同,否則會造成程序崩潰。此函數(shù)在程序調用目標函數(shù)的第一條指令的時候就會被調用(無條件跳轉過來的),如 果在此函數(shù)中想繼續(xù)調用目標函數(shù),必須調用Trampoline函數(shù)(Trampoline函數(shù)在執(zhí)行完目標函數(shù)的前5個字節(jié)的指令后會無條件跳轉到目標 函數(shù)的5個字節(jié)后繼續(xù)執(zhí)行),不能再直接調用目標函數(shù),否則將進入無窮遞歸(目標函數(shù)跳轉到Detour函數(shù),Detour函數(shù)又跳轉到目標函數(shù)的遞歸, 因為目標函數(shù)在內存中的前5個字節(jié)已經(jīng)被修改成絕對跳轉)。通過對Trampoline函數(shù)的調用后可以獲取目標函數(shù)的執(zhí)行結果,此特性對分析目標函數(shù)非 常有用,而且可以將目標函數(shù)的輸出結果進行修改后再傳回給應用程序。
Detour提供了向運行中的應用程序注入Detour函數(shù)和在二進制文件基礎上注入Detour函數(shù)兩種方式。本章主要討論第二種工作方式。通過 Detours提供的開發(fā)包可以在二進制EXE文件中添加一個名稱為Detour的節(jié)表,如下圖3所示,主要目的是實現(xiàn)PE加載器加載應用程序的時候會自 動加載您編寫的Detours DLL,在Detours Dll中的DLLMain中完成對目標函數(shù)的Detour。
(圖3)

二、 Detours提供的截獲API的相關接口
Detours的提供的API 接口可以作為一個共享DLL給外部程序調用,也可以作為一個靜態(tài)Lib鏈接到您的程序內部。
Trampoline函數(shù)可以動態(tài)或者靜態(tài)的創(chuàng)建,如果目標函數(shù)本身是一個鏈接符號,使用靜態(tài)的trampoline函數(shù)將非常簡單。如果目標函數(shù)不能在鏈接時可見,那么可以使用動態(tài)trampoline函數(shù)。
? 要使用靜態(tài)的trampoline函數(shù)來截獲目標函數(shù),應用程序生成trampoline的時候必須使用
DETOUR_TRAMPOLINE宏。DETOUR_TRAMPOLINE有兩個輸入?yún)?shù):trampoline的原型和目標函數(shù)的名字。
注意,對于正確的截獲模型,包括目標函數(shù),trampoline函數(shù),以及截獲函數(shù)都必須是完全一致的調用形式,包括參數(shù)格式和調用約定。當通過 trampoline函數(shù)調用目標函數(shù)的時候拷貝正確參數(shù)是截獲函數(shù)的責任。由于目標函數(shù)僅僅是截獲函數(shù)的一個可調用分支(截獲函數(shù)可以調用 trampoline函數(shù)也可以不調用),這種責任幾乎就是一種下意識的行為。
使用相同的調用約定可以確保寄存器中的值被正確的保存,并且保證調用堆棧在截獲函數(shù)調用目標函數(shù)的時候能正確的建立和銷毀。
可以使用DetourFunctionWithTrampoline函數(shù)來截獲目標函數(shù)。這個函數(shù)有兩個參數(shù):trampoline函數(shù)以及截獲函數(shù)的指針。因為目標函數(shù)已經(jīng)被加到trampoline函數(shù)中,所有不需要在參數(shù)中特別指定。
? 我們可以使用DetourFunction函數(shù)來創(chuàng)建一個動態(tài)的trampoline函數(shù),它包括兩個參數(shù):一個指向目標函數(shù)的指針和一個截獲函數(shù)的指針。DetourFunction分配一個新的trampoline函數(shù)并將適當?shù)慕孬@代碼插入到目標函數(shù)中去。
當目標函數(shù)不是很容易使用的時候,DetourFindFunction函數(shù)可以找到那個函數(shù),不管它是DLL中導出的函數(shù),或者是可以通過二進制目標函數(shù)的調試符號找到。
DetourFindFunction接受兩個參數(shù):庫的名字和函數(shù)的名字。如果DetourFindFunction函數(shù)找到了指定的函數(shù),返回該函數(shù) 的指針,否則將返回一個NULL指針。DetourFindFunction會首先使用Win32函數(shù)LoadLibrary 和 GetProcAddress來定位函數(shù),如果函數(shù)沒有在DLL的導出表中找到,DetourFindFunction將使用ImageHlp庫來搜索有 效的調試符號(譯注:這里的調試符號是指Windows本身提供的調試符號,需要單獨安裝,具體信息請參考Windows的用戶診斷支持信息)。 DetourFindFunction返回的函數(shù)指針可以用來傳遞給DetourFunction以生成一個動態(tài)的trampoline函數(shù)。
我們可以調用DetourRemoveTrampoline來去掉對一個目標函數(shù)的截獲。
注意,因為Detours中的函數(shù)會修改應用程序的地址空間,請確保當加入截獲函數(shù)或者去掉截獲函數(shù)的時候沒有其他線程在進程空間中執(zhí)行,這是程序員的責任。一個簡單的方法保證這個時候是單線程執(zhí)行就是在加載Detours庫的時候在DllMain中呼叫函數(shù)。
三、 使用Detours實現(xiàn)對API的截獲的兩種方法
建立一個MFC對話框工程,在對話框的OK按鈕的單擊事件中加入對MessageBoxA函數(shù)的調用,編譯后的程序名稱MessageBoxApp,效果如圖。

(圖4)
? 靜態(tài)方法
建立一個Dll工程,名稱為ApiHook,這里以Visual C++6.0開發(fā)環(huán)境,以截獲ASCII版本的MessageBoxA函數(shù)來說明。在Dll的工程加入:
DETOUR_TRAMPOLINE(int WINAPI Real_Messagebox(HWND hWnd ,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType), ::MessageBoxA);
生成一個靜態(tài)的MessageBoxA的Trampoline函數(shù),在Dll工程中加入目標函數(shù)的Detour函數(shù):
int WINAPI MessageBox_Mine( HWND hWnd ,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType)
{
CString tmp= lpText;
tmp+=” 被Detour截獲”;
return Real_Messagebox(hWnd,tmp,lpCaption,uType);
// return ::MessageBoxA(hWnd,tmp,lpCaption,uType); //Error
}
在Dll入口函數(shù)中的加載Dll事件中加入:
DetourFunctionWithTrampoline((PBYTE)Real_Messagebox, (PBYTE)MessageBox_Mine);
在Dll入口函數(shù)中的卸載Dll事件中加入:
DetourRemove((PBYTE)Real_Messagebox, (PBYTE)MessageBox_Mine);
? 動態(tài)方法
建立一個Dll工程,名稱為ApiHook,這里以Visual C++6.0開發(fā)環(huán)境,以截獲ASCII版本的MessageBoxA函數(shù)來說明。在Dll的工程加入:
//聲明MessageBoxA一樣的函數(shù)原型
typedef int (WINAPI * MessageBoxSys)( HWND hWnd ,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType);
//目標函數(shù)指針
MessageBoxSys SystemMessageBox=NULL;
//Trampoline函數(shù)指針
MessageBoxSys Real_MessageBox=NULL;
在Dll工程中加入目標函數(shù)的Detour函數(shù):
int WINAPI MessageBox_Mine( HWND hWnd ,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType)
{
CString tmp= lpText;
tmp+=” 被Detour截獲”;
return Real_Messagebox(hWnd,tmp,lpCaption,uType);
// return ::MessageBoxA(hWnd,tmp,lpCaption,uType); //Error
}
在Dll入口函數(shù)中的加載Dll事件中加入:
SystemMessageBox=(MessageBoxSys)DetourFindFunction("user32.dll","MessageBoxA");
if(SystemMessageBox==NULL)
{
return FASLE;
}
Real_MessageBox=(MessageBoxSys)DetourFunction((PBYTE)SystemMessageBox, (PBYTE)MessageBox_Mine);
在Dll入口函數(shù)中的卸載Dll事件中加入:
DetourRemove((PBYTE)Real_Messagebox, (PBYTE)MessageBox_Mine);
? 重寫二進制可執(zhí)行文件
使用Detours自帶的SetDll.exe重寫二進制可執(zhí)行文件,可以在需要截獲的程序中加入一個新的Detours的PE節(jié)表。對于本文就是新建一個批處理文件調用SetDll.exe。
@echo off
if not exist MessageBoxApp.exe (
echo 請將文件解壓到MessageBoxApp.exe的安裝目錄, 然后執(zhí)行補丁程序
) else (
setdll /d:ApiHook.dll MessageBoxApp.exe
)
Pause
調用后使用depends.exe(微軟VC6.0開發(fā)包的工具之一)觀察MessageBoxApp.exe前后變化, 可以看到Setdll已經(jīng)重寫MessageBoxApp.exe
成功,加入了對ApiHook.dll的依賴關系。

(執(zhí)行SetDll.exe前) (執(zhí)行SetDll.exe后)
執(zhí)行SetDll.exe重寫后的MessageBoxApp.exe,點擊確定后可以看到結果如下:
至此,MessageBoxApp.exe對MessageBoxA函數(shù)的調用已經(jīng)被截獲,彈出的對話框內容已經(jīng)明顯說明這一點。

本文轉自:http://www.cnblogs.com/flying_bat/archive/2008/04/18/1159996.html
posted on 2012-11-02 14:32
王海光 閱讀(673)
評論(0) 編輯 收藏 引用 所屬分類:
其他