微軟研究院Detour開發包之API攔截技術【轉】

 

我們截獲函數執行最直接的目的就是爲函數增添功能,修改返回值,或者爲調試以及性能測試加入附加的代碼,或者截獲函數的輸入輸出作研究,破解使用。通過訪 問源代碼,我們可以輕而易舉的使用重建(Rebuilding)操作系統或者應用程序的方法在它們中間插入新的功能或者做功能擴展。然而,在今天這個商業 化的開發世界裏,以及在只有二進制代碼發佈的系統中,研究人員幾乎沒有機會可以得到源代碼。本文主要討論Detour在Windows二進制PE文件基礎 上的API截獲技術。對於Linux平臺,作這件事情將會非常的簡單,由於最初的操作系統設計者引入了LD_PRELOAD。如果你設置  LD_PRELOAD=mylib.so ,那麼應用程序在載入 dll時,會先查看mylib.so的符號表,在relocation 的時候會優先 使用mylib.so 裏的 symbol 。假如你在mylib.so裏有個printf() ,那麼這個printf就會替代libc的 printf。 而在mylib.so裏的這個printf可以直接訪問 libc.so裏的printf函數指針來獲得真正的 printf的入口地 址。 這樣,所有的dll的API HOOK在loader加載dll的時候就已經完成,非常自然,和平臺相關的部分全部交給loader去處理。
一、  Detour開發庫:
  簡介
Detours是一個在x86平臺上截獲任意Win32函數調用的工具庫。中斷代碼可以在運行時動態加載。Detours使用一個無條件轉移指令來替換目 標函數的最初幾條指令,將控制流轉移到一個用戶提供的截獲函數。而目標函數中的一些指令被保存在一個被稱爲“trampoline” (譯註:英文意爲蹦 牀,雜技)的函數中,在這裏我覺得翻譯成目標函數的部分克隆/拷貝比較貼切。這些指令包括目標函數中被替換的代碼以及一個重新跳轉到目標函數的無條件分 支。而截獲函數可以替換目標函數,或者通過執行“trampoline”函數的時候將目標函數作爲子程序來調用的辦法來擴展功能。
Detours是執行時被插入的。內存中的目標函數的代碼不是在硬盤上被修改的,因而可以在一個很好的粒度上使得截獲二進制函數的執行變得更容易。例如, 一個應用程序執行時加載的DLL中的函數過程可以被插入一段截獲代碼(detoured),與此同時,這個DLL還可以被其他應用程序按正常情況執行(譯 注:也就是按照不被截獲的方式執行,因爲DLL二進制文件沒有被修改,所以發生截獲時不會影響其他進程空間加載這個DLL)。不同於DLL的重新鏈接或者 靜態重定向,Detours庫中使用的這種中斷技術確保不會影響到應用程序中的方法或者系統代碼對目標函數的定位。
如果其他人爲了調試或者在內部使用其他系統檢測手段而試圖修改二進制代碼,Detours將是一個可以普遍使用的開發包。據我所知,Detours是第一 個可以在任意平臺上將未修改的目標代碼作爲一個可以通過“trampoline”調用的子程序來保留的開發包。而以前的系統在邏輯上預先將截獲代碼放到目 標代碼中,而不是將原始的目標代碼做爲一個普通的子程序來調用。我們獨特的“trampoline”設計對於擴展現有的軟件的二進制代碼是至關重要的。
出於使用基本的函數截獲功能的目的,Detours同樣提供了編輯任何DLL導入表的功能,達到向存在的二進制代碼中添加任意數據節表的目的,向一個新進 程或者一個已經運行着的進程中注入一個DLL。一旦向一個進程注入了DLL,這個動態庫就可以截獲任何Win32函數,不論它是在應用程序中或者在系統庫 中。
  基本原理
1.  WIN32進程的內存管理
衆所周知,WINDOWS NT實現了虛擬存儲器,每一WIN32進程擁有4GB的虛存空間, 關於WIN32進程的虛存結構及其操作的具體細節請參閱WIN32 API手冊, 以下僅指出與Detours相關的幾點:
(1) 進程要執行的指令也放在虛存空間中
(2) 可以使用QueryProtectEx函數把存放指令的頁面的權限更改爲可讀可寫可執行,再改寫其內容,從而修改正在運行的程序
(3) 可以使用VirtualAllocEx從一個進程爲另一正運行的進程分配虛存,再使用 QueryProtectEx函數把頁面的權限更改爲可讀可寫可執行,並把要執行的指令以二進制機器碼的形式寫入,從而爲一個正在運行的進程注入任意的代碼 。
2. 攔截WIN32 API的原理
Detours定義了三個概念:
    (1) Target函數:要攔截的函數,通常爲Windows的API。
(2) Trampoline函數:Target函數的部分複製品。因爲Detours將會改寫Target函數,所以先把Target函數的前5個字節複製保存好,一方面仍然保存Target函數的過程調用語義,另一方面便於以後的恢復。
(3) Detour 函數:用來替代Target函數的函數。
Detours在Target函數的開頭加入JMP Address_of_ Detour_ Function指令(共5個字節)把對Target函數 的調用引導到自己的Detour函數, 把Target函數的開頭的5個字節加上JMP Address_of_ Target _ Function+ 5共10個字節作爲Trampoline函數。請參考下面的圖1和圖2。
(圖1:Detour函數的過程)
crack_01
(圖2: Detour函數的調用過程)
crack_02
說明:
  目標函數:
目標函數的函數體(二進制)至少有5個字節以上。按照微軟的說明文檔Trampoline函數的函數體是拷貝前5個字節加一個無條件跳轉指令的話(如果沒 有特殊處理不可分割指令的話),那麼前5個字節必須是完整指令,也就是不能第5個字節和第6個字節是一條不可分割的指令,否則會造成Trampoline 函數執行錯誤,一條完整的指令被硬性分割開來,造成程序崩潰。對於第5字節和第6個字節是不可分割指令需要調整拷貝到雜技函數(Trampoline)的 字節個數,這個值可以查看目標函數的彙編代碼得到。此函數是目標函數的修改版本,不能在Detour函數中直接調用,需要通過對Trampoline函數 的調用來達到間接調用。
  Trampoline函數:
此函數默認分配了32個字節,函數的內容就是拷貝的目標函數的前5個字節,加上一個JMP Address_of_ Target _ Function+5指令,共10個字節。
此函數僅供您的Detour函數調用,執行完前5個字節的指令後再絕對跳轉到目標函數的第6個字節繼續執行原功能函數。
  Detour函數:
此函數是用戶需要的截獲API的一個模擬版本,調用方式,參數個數必須和目標函數相一致。如目標函數是__stdcall,則Detour函數聲明也必須 是__stdcall,參數個數和類型也必須相同,否則會造成程序崩潰。此函數在程序調用目標函數的第一條指令的時候就會被調用(無條件跳轉過來的),如 果在此函數中想繼續調用目標函數,必須調用Trampoline函數(Trampoline函數在執行完目標函數的前5個字節的指令後會無條件跳轉到目標 函數的5個字節後繼續執行),不能再直接調用目標函數,否則將進入無窮遞歸(目標函數跳轉到Detour函數,Detour函數又跳轉到目標函數的遞歸, 因爲目標函數在內存中的前5個字節已經被修改成絕對跳轉)。通過對Trampoline函數的調用後可以獲取目標函數的執行結果,此特性對分析目標函數非 常有用,而且可以將目標函數的輸出結果進行修改後再傳回給應用程序。
Detour提供了向運行中的應用程序注入Detour函數和在二進制文件基礎上注入Detour函數兩種方式。本章主要討論第二種工作方式。通過 Detours提供的開發包可以在二進制EXE文件中添加一個名稱爲Detour的節表,如下圖3所示,主要目的是實現PE加載器加載應用程序的時候會自 動加載您編寫的Detours DLL,在Detours Dll中的DLLMain中完成對目標函數的Detour。
(圖3)
crack_03
二、  Detours提供的截獲API的相關接口
Detours的提供的API 接口可以作爲一個共享DLL給外部程序調用,也可以作爲一個靜態Lib鏈接到您的程序內部。
Trampoline函數可以動態或者靜態的創建,如果目標函數本身是一個鏈接符號,使用靜態的trampoline函數將非常簡單。如果目標函數不能在鏈接時可見,那麼可以使用動態trampoline函數。
  要使用靜態的trampoline函數來截獲目標函數,應用程序生成trampoline的時候必須使用
DETOUR_TRAMPOLINE宏。DETOUR_TRAMPOLINE有兩個輸入參數:trampoline的原型和目標函數的名字。
注意,對於正確的截獲模型,包括目標函數,trampoline函數,以及截獲函數都必須是完全一致的調用形式,包括參數格式和調用約定。當通過 trampoline函數調用目標函數的時候拷貝正確參數是截獲函數的責任。由於目標函數僅僅是截獲函數的一個可調用分支(截獲函數可以調用 trampoline函數也可以不調用),這種責任幾乎就是一種下意識的行爲。
使用相同的調用約定可以確保寄存器中的值被正確的保存,並且保證調用堆棧在截獲函數調用目標函數的時候能正確的建立和銷燬。
可以使用DetourFunctionWithTrampoline函數來截獲目標函數。這個函數有兩個參數:trampoline函數以及截獲函數的指針。因爲目標函數已經被加到trampoline函數中,所有不需要在參數中特別指定。
  我們可以使用DetourFunction函數來創建一個動態的trampoline函數,它包括兩個參數:一個指向目標函數的指針和一個截獲函數的指針。DetourFunction分配一個新的trampoline函數並將適當的截獲代碼插入到目標函數中去。
當目標函數不是很容易使用的時候,DetourFindFunction函數可以找到那個函數,不管它是DLL中導出的函數,或者是可以通過二進制目標函數的調試符號找到。
DetourFindFunction接受兩個參數:庫的名字和函數的名字。如果DetourFindFunction函數找到了指定的函數,返回該函數 的指針,否則將返回一個NULL指針。DetourFindFunction會首先使用Win32函數LoadLibrary 和 GetProcAddress來定位函數,如果函數沒有在DLL的導出表中找到,DetourFindFunction將使用ImageHlp庫來搜索有 效的調試符號(譯註:這裏的調試符號是指Windows本身提供的調試符號,需要單獨安裝,具體信息請參考Windows的用戶診斷支持信息)。 DetourFindFunction返回的函數指針可以用來傳遞給DetourFunction以生成一個動態的trampoline函數。
我們可以調用DetourRemoveTrampoline來去掉對一個目標函數的截獲。
注意,因爲Detours中的函數會修改應用程序的地址空間,請確保當加入截獲函數或者去掉截獲函數的時候沒有其他線程在進程空間中執行,這是程序員的責任。一個簡單的方法保證這個時候是單線程執行就是在加載Detours庫的時候在DllMain中呼叫函數。
三、  使用Detours實現對API的截獲的兩種方法
建立一個MFC對話框工程,在對話框的OK按鈕的單擊事件中加入對MessageBoxA函數的調用,編譯後的程序名稱MessageBoxApp,效果如圖。
crack_04
(圖4)
  靜態方法
建立一個Dll工程,名稱爲ApiHook,這裏以Visual C++6.0開發環境,以截獲ASCII版本的MessageBoxA函數來說明。在Dll的工程加入:
DETOUR_TRAMPOLINE(int WINAPI Real_Messagebox(HWND hWnd ,
    LPCSTR lpText,
    LPCSTR lpCaption,
UINT uType), ::MessageBoxA);
生成一個靜態的MessageBoxA的Trampoline函數,在Dll工程中加入目標函數的Detour函數:
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入口函數中的加載Dll事件中加入:
DetourFunctionWithTrampoline((PBYTE)Real_Messagebox, (PBYTE)MessageBox_Mine);
在Dll入口函數中的卸載Dll事件中加入:
DetourRemove((PBYTE)Real_Messagebox, (PBYTE)MessageBox_Mine);
  動態方法
建立一個Dll工程,名稱爲ApiHook,這裏以Visual C++6.0開發環境,以截獲ASCII版本的MessageBoxA函數來說明。在Dll的工程加入:
//聲明MessageBoxA一樣的函數原型
typedef int  (WINAPI * MessageBoxSys)( HWND hWnd ,
    LPCSTR lpText,
    LPCSTR lpCaption,
    UINT uType);
//目標函數指針
MessageBoxSys SystemMessageBox=NULL;
//Trampoline函數指針
MessageBoxSys Real_MessageBox=NULL;
在Dll工程中加入目標函數的Detour函數:
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入口函數中的加載Dll事件中加入:
  SystemMessageBox=(MessageBoxSys)DetourFindFunction("user32.dll","MessageBoxA");
  if(SystemMessageBox==NULL)
  {
    return FASLE;
  }
  Real_MessageBox=(MessageBoxSys)DetourFunction((PBYTE)SystemMessageBox, (PBYTE)MessageBox_Mine);
在Dll入口函數中的卸載Dll事件中加入:
DetourRemove((PBYTE)Real_Messagebox, (PBYTE)MessageBox_Mine);
  重寫二進制可執行文件
使用Detours自帶的SetDll.exe重寫二進制可執行文件,可以在需要截獲的程序中加入一個新的Detours的PE節表。對於本文就是新建一個批處理文件調用SetDll.exe。
@echo off
if not exist MessageBoxApp.exe (
echo 請將文件解壓到MessageBoxApp.exe的安裝目錄, 然後執行補丁程序
) else (
setdll /d:ApiHook.dll MessageBoxApp.exe
)
Pause
調用後使用depends.exe(微軟VC6.0開發包的工具之一)觀察MessageBoxApp.exe前後變化, 可以看到Setdll已經重寫MessageBoxApp.exe
成功,加入了對ApiHook.dll的依賴關係。
crack_05
      (執行SetDll.exe前)                                                       (執行SetDll.exe後)
執行SetDll.exe重寫後的MessageBoxApp.exe,點擊確定後可以看到結果如下:
至此,MessageBoxApp.exe對MessageBoxA函數的調用已經被截獲,彈出的對話框內容已經明顯說明這一點。
crack_06

 

 

轉自:http://www.cnblogs.com/flying_bat/archive/2008/04/18/1159996.html

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章