在很多情況下,為了測試代碼或擴展操作系統的功能,軟件開發人員或測試人員必須截取系統函數調用。有一些軟件包能夠提供該功能,如微軟公司的 Detours* 庫,或 OK Thinking Software 的 Syringe*。但是從另一個角度而言,開發人員可能希望不需借助第三方軟件,自己就能實現該功能。 本文描述了函數截取的幾種不同方式,并詳細介紹了無需使用商業軟件包,也不需受 GNU*(通用公共許可證)許可的約束,就能夠實現該功能的一種通用方法。本文所有材料由英特爾公司開發,或根據 MSDN* 樣本代碼修改而來。 截取系統函數調用的兩項基本技術 大部分截取任意函數調用的方法都是準備一個 DLL,用來替代將被截取的目標函數,然后將 DLL 注入至目標進程;在與目標進程連接的基礎上, DLL 將自己與目標函數相連。這種技術之所以適合此任務,是因為在大多數情況下我們無法獲得目標應用程序的源代碼,而這種技術只需相對簡單地編寫一個包含代換函數的 DLL,就可將其與軟件的其它部分分離開來。 兩種截取方法已經過研究和分析。Syringe 通過修改函數輸入條目( thunking 表)運行。而Detours 庫則直接修改目標函數( 在目標進程空間內),并無條件地跳轉至代換函數。此外,它還提供能夠調用原始函數的 trampoline 函數。 Detours 技術之所以采用后一種方法,是因為在許多情況下,Syringe 無法找到 thunk,并且它不能提供 trampoline功能來調用原始函數。在這兩種方法下,注入 DLL 的工作方式相同。 截取系統函數調用的全部工作流程如下所示:  | DLL 注入 — 首先,主軟件打開目標進程,并使其加載包含代換函數的 DLL。
|  | 目標函數修改 — 當 DLL 連接至進程時,它在目標進程空間內修改目標函數,從而直接跳轉至 DLL 中的代換函數。Trampoline 函數能夠隨意調用原始函數。
|  | 目標函數截取 — 當調用目標函數時,它直接跳轉至 DLL 中的代換函數。如果開發人員希望調用原始的功能,則他或她就可以調用 trampoline 函數。
|
DLL 注入 本節內容完全以 MSDN 文章“ 定制調試診斷工具和實用程序 — 擺脫 DLL“地獄”?  ?(DLL Hell)*”為基礎,該文章還包括可下載的源代碼。在本文附錄中可獲得 Inject.cpp 和 Inject.h 。已對它們進行了定制以便于集成——僅需將其包括在項目中然后調用 InjectLib 即可。使目標進程加載 DLL 的算法按如下步驟工作:  | 通過調用 OpenProcess 打開目標進程。
|  | 通過調用 VirtualAllocEx 在目標進程中分配內存。利用 WriteProcessMemory 將要被注入的 DLL 名稱寫入分配的內存。
|  | 通過調用 GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW") 來獲取 LoadLibrary 的地址;
|  | 調用 CreateRemoteThread,指定 LoadLibrary 的入口點,并將 DLL (第 2 步中) 的名稱作為其自變量。目標進程將加載 DLL。
|  | 利用 VirtualFreeEx 釋放分配的內存。已不再需要該內存。
|
Inject.cpp 融合了包括穩固的安全特性等大量其它功能,但上述步驟已足夠闡明其核心概念。 目標函數修改 目標函數修改為自我修改代碼,盡管在將 jmp 注入進程內存的過程中存在一些缺陷,但它在? MSDN*?  上具有完善的文件證明。為避免混淆,本節列出了幾乎全部的樣本代碼。 目標函數修改的兩個主要方面為代換函數和 trampoline 函數。下面的代碼片斷為截取 GetSystemPowerStatus API 的 DLL 示例: 這段代碼為連接所做的第一件事就是調用 InterceptAPI。需要使用包含目標函數的模塊名稱、目標函數的名稱以及代換函數的地址。 GetSystemPowerStatus 位于 kernel32.dll 中。其它基本的 Win32* API,如 MessageBox 和 PeekMessage,都能夠在 user32.dll 中獲得。MSDN 指定每個 API 所屬的模塊;未來的增強版中,將自動為給定的 API 找到正確的模塊。 InterceptAPI 將目標函數的前五個字節覆蓋為無條件跳轉( opcode 0xE9),后面為四個字節的帶符號整數(向代換函數的位移)。位移從下一個指令開始;因此需要使用 pbReplaced - (pbTargetCode +4)。進行該代碼操作時,需要注意以下兩點:  | 將區域覆蓋的保護模式改為 VirtualProtect。否則,將發生非法訪問錯誤。
|  | 必須使用 FlushInstructionCache 來支持指令已存在于高速緩存中的情況。否則,即使內存中的指令已經有所變化,舊代碼仍將在高速緩存中運行。
|
現在,當調用 GetSystemPowerStatus 函數時,它跳轉至代換函數,然后直接返回調用方,成功截取調用。 Trampoline 函數 在很多情況下,代換函數除使用自身代碼外,還需調用原始目標函數,這樣就能夠擴展 API 的功能,而不是替換整個 API。Trampoline 函數可以提供該功能。Trampoline 函數的原理如下所示:  | 編寫一個具有相同聲明的啞元函數(dummy function),將作為 trampoline 使用。確保啞元函數的長度超過 10 個字節。
|  | 在覆蓋目標函數的前五個字節之前,將它們復制到 trampoline 函數的起始處。
|  | 利用無條件跳轉,將trampoline的第六個字節覆蓋為目標函數的第六個字節。
|  | 與之前一樣覆蓋目標函數。
|  | 當從代換函數或其它地方調用 trampoline 函數時,它執行復制出的原始代碼的前五個字節,然后跳轉至實際原始代碼的第六個字節。控制返回至 trampoline 的調用方。當完成其它任務時,控制返回至 API 的控制方。
|
可能存在一種復雜的情況,即原始代碼的第六個字節可能是先前指令的一部分。在這種情況下,函數會覆蓋部分先前指令,然后崩潰。在 GetSystemPowerStatus 的情況中,前五個字節后的新指令開始于第七個字節。因此,對于這種工作機制,需要將六個字節復制到 trampoline,并且代碼必須相應地調整這個偏移量。 代碼需要復制的字節數取決于 API。查看原始目標代碼( 利用調試器或反匯編器)并計算需要復制的字節數是非常必要的。未來的增強版將自動檢測正確的偏移量。假設我們已經知道正確的偏移量,下面的代碼則顯示出可建立 trampoline 函數的可擴展 InterceptAPI 函數: ?1?BOOL?InterceptAPI(HMODULE?hLocalModule,?const?char*?c_szDllName,?const?char*?c_szApiName, ?2?????????DWORD?dwReplaced,?DWORD?dwTrampoline,?int?offset) ?3?{ ?4?????int?i; ?5?????DWORD?dwOldProtect; ?6?????????DWORD?dwAddressToIntercept?=?(DWORD)GetProcAddress( ?7?????????????GetModuleHandle((char*)c_szDllName),?(char*)c_szApiName); ?8? ?9?????BYTE?*pbTargetCode?=?(BYTE?*)?dwAddressToIntercept; 10?????BYTE?*pbReplaced?=?(BYTE?*)?dwReplaced; 11?????BYTE?*pbTrampoline?=?(BYTE?*)?dwTrampoline; 12? 13?????//?Change?the?protection?of?the?trampoline?region 14?????//?so?that?we?can?overwrite?the?first?5?+?offset?bytes. 15?????VirtualProtect((void?*)?dwTrampoline,?5+offset,?PAGE_WRITECOPY,?&dwOldProtect); 16?????for?(i=0;i<offset;i++) 17?????????*pbTrampoline++?=?*pbTargetCode++; 18?????pbTargetCode?=?(BYTE?*)?dwAddressToIntercept; 19? 20?????//?Insert?unconditional?jump?in?the?trampoline. 21?????*pbTrampoline++?=?0xE9;????????//?jump?rel32 22?????*((signed?int?*)(pbTrampoline))?=?(pbTargetCode+offset)?-?(pbTrampoline?+?4); 23?????VirtualProtect((void?*)?dwTrampoline,?5+offset,?PAGE_EXECUTE,?&dwOldProtect); 24????? 25?????//?Overwrite?the?first?5?bytes?of?the?target?function 26?????VirtualProtect((void?*)?dwAddressToIntercept,?5,?PAGE_WRITECOPY,?&dwOldProtect); 27?????*pbTargetCode++?=?0xE9;????????//?jump?rel32 28?????*((signed?int?*)(pbTargetCode))?=?pbReplaced?-?(pbTargetCode?+4); 29?????VirtualProtect((void?*)?dwAddressToIntercept,?5,?PAGE_EXECUTE,?&dwOldProtect); 30????? 31?????//?Flush?the?instruction?cache?to?make?sure? 32?????//?the?modified?code?is?executed. 33?????FlushInstructionCache(GetCurrentProcess(),?NULL,?NULL); 34?????return?TRUE; 35?} 36? 結論 本文描述了截取系統函數調用的一種通用方法,同時還提供了 trampoline 函數,從而保留了原始功能。本文僅對方法進行簡要描述,并未對完整的軟件包作出說明,因此如下一些細節并沒有實現:  | 自動檢測包含目標 API 的模塊。
|  | 自動檢測 trampoline 函數的偏移量。
|  | 刪除代換函數,并注入 DLL。(到目前為止,清空代換函數的唯一方法是關閉應用程序。)
|
然而,對于開發人員而言,無需依賴第三方軟件包,執行截取任意系統函數調用的軟件,本文中涉及的技術、說明及源代碼已經足夠。 ??1? #include?"stdafx.h" ??2? #include?"Inject.h" ??3? ??4? #include?<tchar.h> ??5? #include?<malloc.h>????//?For?alloca ??6? #include?<pi.h> ??7? ??8? #ifdef?UNICODE ??9? #define?InjectLib?InjectLibW ?10? #else ?11? #define?InjectLib?InjectLibA ?12? #endif???//?!UNICODE ?13? ?14? BOOL?AdjustDacl(HANDLE?h,?DWORD?DesiredAccess) ?15? { ?16? ????//?the?WORLD?Sid?is?trivial?to?form?programmatically?(S-1-1-0) ?17? ????SID?world?=?{?SID_REVISION,?1,?SECURITY_WORLD_SID_AUTHORITY,?0?}; ?18? ???? ?19? ????EXPLICIT_ACCESS?ea?= ?20? ????{ ?21? ????????DesiredAccess, ?22? ????????????SET_ACCESS, ?23? ????????????NO_INHERITANCE, ?24? ????????{ ?25? ????????????0,?NO_MULTIPLE_TRUSTEE, ?26? ????????????????TRUSTEE_IS_SID, ?27? ????????????????TRUSTEE_IS_USER, ?28? ????????????????reinterpret_cast<LPTSTR>(&world) ?29? ????????} ?30? ????}; ?31? ????ACL*?pdacl?=?0; ?32? ????DWORD?err?=?SetEntriesInAcl(1,?&ea,?0,?&pdacl); ?33? ????if?(err?==?ERROR_SUCCESS) ?34? ????{ ?35? ????????err?=?SetSecurityInfo(h,?SE_KERNEL_OBJECT,?DACL_SECURITY_INFORMATION,?0,?0,?pdacl,?0); ?36? ????????LocalFree(pdacl); ?37? ????????return(err?==?ERROR_SUCCESS); ?38? ????} ?39? ????else ?40? ????????return(FALSE); ?41? } ?42? ?43? //?Useful?helper?function?for?enabling?a?single?privilege ?44? BOOL?EnableTokenPrivilege(HANDLE?htok,?LPCTSTR?szPrivilege,?TOKEN_PRIVILEGES&?tpOld) ?45? { ?46? ????TOKEN_PRIVILEGES?tp; ?47? ????tp.PrivilegeCount?=?1; ?48? ????tp.Privileges[0].Attributes?=?SE_PRIVILEGE_ENABLED; ?49? ????if?(LookupPrivilegeValue(0,?szPrivilege,?&tp.Privileges[0].Luid)) ?50? ????{ ?51? ????????//?htok?must?have?been?opened?with?the?following?permissions: ?52? ????????//?TOKEN_QUERY?(to?get?the?old?priv?setting) ?53? ????????//?TOKEN_ADJUST_PRIVILEGES?(to?adjust?the?priv) ?54? ????????DWORD?cbOld?=?sizeof?tpOld; ?55? ????????if?(AdjustTokenPrivileges(htok,?FALSE,?&tp,?cbOld,?&tpOld,?&cbOld)) ?56? ????????????//?Note?that?AdjustTokenPrivileges?may?succeed,?and?yet ?57? ????????????//?some?privileges?weren't?actually?adjusted. ?58? ????????????//?You've?got?to?check?GetLastError()?to?be?sure! ?59? ????????????return(ERROR_NOT_ALL_ASSIGNED?!=?GetLastError()); ?60? ????????else ?61? ????????????return(FALSE); ?62? ????} ?63? ????else ?64? ????????return(FALSE); ?65? } ?66? ?67? ?68? //?Corresponding?restoration?helper?function ?69? BOOL?RestoreTokenPrivilege(HANDLE?htok,?const?TOKEN_PRIVILEGES&?tpOld) ?70? { ?71? ????return(AdjustTokenPrivileges(htok,?FALSE,?const_cast<TOKEN_PRIVILEGES*>(&tpOld),?0,?0,?0)); ?72? } ?73? ?74? HANDLE?GetProcessHandleWithEnoughRights(DWORD?PID,?DWORD?AccessRights) ?75? { ?76? ????HANDLE?hProcess?=?::OpenProcess(AccessRights,?FALSE,?PID); ?77? ????if?(hProcess?==?NULL) ?78? ????{ ?79? ????????HANDLE?hpWriteDAC?=?OpenProcess(WRITE_DAC,?FALSE,?PID); ?80? ????????if?(hpWriteDAC?==?NULL) ?81? ????????{ ?82? ????????????//?hmm,?we?don't?have?permissions?to?modify?the?DACL ?83? ????????????//?time?to?take?ownership ?84? ????????????HANDLE?htok; ?85? ????????????if?(!OpenProcessToken(GetCurrentProcess(),?TOKEN_QUERY?|?TOKEN_ADJUST_PRIVILEGES,?&htok)) ?86? ????????????????return(FALSE); ?87? ???????????? ?88? ????????????TOKEN_PRIVILEGES?tpOld; ?89? ????????????if?(EnableTokenPrivilege(htok,?SE_TAKE_OWNERSHIP_NAME,?tpOld)) ?90? ????????????{ ?91? ????????????????//?SeTakeOwnershipPrivilege?allows?us?to?open?objects?with ?92? ????????????????//?WRITE_OWNER,?but?that's?about?it,?so?we'll?update?the?owner, ?93? ????????????????//?and?dup?the?handle?so?we?can?get?WRITE_DAC?permissions. ?94? ????????????????HANDLE?hpWriteOwner?=?OpenProcess(WRITE_OWNER,?FALSE,?PID); ?95? ????????????????if?(hpWriteOwner?!=?NULL) ?96? ????????????????{ ?97? ????????????????????BYTE?buf[512];?//?this?should?always?be?big?enough ?98? ????????????????????DWORD?cb?=?sizeof?buf; ?99? ????????????????????if?(GetTokenInformation(htok,?TokenUser,?buf,?cb,?&cb)) 100? ????????????????????{ 101? ????????????????????????DWORD?err?=? 102? ????????????????????????????SetSecurityInfo(? 103? ????????????????????????????hpWriteOwner,? 104? ????????????????????????????SE_KERNEL_OBJECT, 105? ????????????????????????????OWNER_SECURITY_INFORMATION, 106? ????????????????????????????reinterpret_cast<TOKEN_USER*>(buf)->User.Sid, 107? ????????????????????????????0,?0,?0? 108? ????????????????????????????); 109? ????????????????????????if?(err?==?ERROR_SUCCESS) 110? ????????????????????????{ 111? ????????????????????????????//?now?that?we're?the?owner,?we've?implicitly?got?WRITE_DAC 112? ????????????????????????????//?permissions,?so?ask?the?system?to?reevaluate?our?request, 113? ????????????????????????????//?giving?us?a?handle?with?WRITE_DAC?permissions 114? ????????????????????????????if?( 115? ????????????????????????????????!DuplicateHandle(? 116? ????????????????????????????????GetCurrentProcess(),? 117? ????????????????????????????????hpWriteOwner, 118? ????????????????????????????????GetCurrentProcess(),? 119? ????????????????????????????????&hpWriteDAC, 120? ????????????????????????????????WRITE_DAC,?FALSE,?0? 121? ????????????????????????????????)? 122? ????????????????????????????????) 123? ????????????????????????????????hpWriteDAC?=?NULL; 124? ????????????????????????} 125? ????????????????????} 126? ???????????????????? 127? ????????????????????//?don't?forget?to?close?handle 128? ????????????????????::CloseHandle(hpWriteOwner); 129? ????????????????} 130? ???????????????? 131? ????????????????//?not?truly?necessary?in?this?app, 132? ????????????????//?but?included?for?completeness 133? ????????????????RestoreTokenPrivilege(htok,?tpOld); 134? ????????????} 135? ???????????? 136? ????????????//?don't?forget?to?close?the?token?handle 137? ????????????::CloseHandle(htok); 138? ????????} 139? ???????? 140? ????????if?(hpWriteDAC) 141? ????????{ 142? ????????????//?we've?now?got?a?handle?that?allows?us?WRITE_DAC?permission 143? ????????????AdjustDacl(hpWriteDAC,?AccessRights); 144? ???????????? 145? ????????????//?now?that?we've?granted?ourselves?permission?to?access? 146? ????????????//?the?process,?ask?the?system?to?reevaluate?our?request, 147? ????????????//?giving?us?a?handle?with?right?permissions 148? ????????????if?( 149? ????????????????!DuplicateHandle(? 150? ????????????????GetCurrentProcess(),? 151? ????????????????hpWriteDAC, 152? ????????????????GetCurrentProcess(),? 153? ????????????????&hProcess, 154? ????????????????AccessRights,? 155? ????????????????FALSE,? 156? ????????????????0? 157? ????????????????)? 158? ????????????????) 159? ????????????????hProcess?=?NULL; 160? ???????????? 161? ????????????CloseHandle(hpWriteDAC); 162? ????????} 163? ????} 164? ???? 165? ????return(hProcess); 166? } 167? 168? BOOL?WINAPI?InjectLibW(DWORD?dwProcessId,?PCWSTR?pszLibFile)? 169? { 170? ????BOOL?fOk?=?FALSE;?//?Assume?that?the?function?fails 171? ????HANDLE?hProcess?=?NULL,?hThread?=?NULL; 172? ????PWSTR?pszLibFileRemote?=?NULL; 173? ???? 174? ????//?Get?a?handle?for?the?target?process. 175? ????hProcess?=? 176? ????????GetProcessHandleWithEnoughRights( 177? ????????dwProcessId, 178? ????????PROCESS_QUERY_INFORMATION?|???//?Required?by?Alpha 179? ????????PROCESS_CREATE_THREAD?????|???//?For?CreateRemoteThread 180? ????????PROCESS_VM_OPERATION??????|???//?For?VirtualAllocEx/VirtualFreeEx 181? ????????PROCESS_VM_WRITE??????????????//?For?WriteProcessMemory 182? ????????); 183? ????if?(hProcess?==?NULL) 184? ????????return(FALSE); 185? ???? 186? ????//?Calculate?the?number?of?bytes?needed?for?the?DLL's?pathname 187? ????int?cch?=?1?+?lstrlenW(pszLibFile); 188? ????int?cb??=?cch?*?sizeof(WCHAR); 189? ???? 190? ????//?Allocate?space?in?the?remote?process?for?the?pathname 191? ????pszLibFileRemote?=? 192? ????????(PWSTR)?VirtualAllocEx(hProcess,?NULL,?cb,?MEM_COMMIT,?PAGE_READWRITE); 193? ???? 194? ????if?(pszLibFileRemote?!=?NULL) 195? ????{ 196? ????????//?Copy?the?DLL's?pathname?to?the?remote?process's?address?space 197? ????????if?(WriteProcessMemory(hProcess,?pszLibFileRemote,? 198? ????????????(PVOID)?pszLibFile,?cb,?NULL)) 199? ????????{ 200? ????????????//?Get?the?real?address?of?LoadLibraryW?in?Kernel32.dll 201? ????????????PTHREAD_START_ROUTINE?pfnThreadRtn?=?(PTHREAD_START_ROUTINE) 202? ????????????????GetProcAddress(GetModuleHandle(TEXT("Kernel32")),?"LoadLibraryW"); 203? ????????????if?(pfnThreadRtn?!=?NULL) 204? ????????????{ 205? ????????????????//?Create?a?remote?thread?that?calls?LoadLibraryW(DLLPathname) 206? ????????????????hThread?=?CreateRemoteThread(hProcess,?NULL,?0,? 207? ????????????????????pfnThreadRtn,?pszLibFileRemote,?0,?NULL); 208? ????????????????if?(hThread?!=?NULL) 209? ????????????????{ 210? ????????????????????//?Wait?for?the?remote?thread?to?terminate 211? ????????????????????WaitForSingleObject(hThread,?INFINITE); 212? ???????????????????? 213? ????????????????????fOk?=?TRUE;?//?Everything?executed?successfully 214? ???????????????????? 215? ????????????????????CloseHandle(hThread); 216? ????????????????} 217? ????????????} 218? ????????} 219? ????????//?Free?the?remote?memory?that?contained?the?DLL's?pathname 220? ????????VirtualFreeEx(hProcess,?pszLibFileRemote,?0,?MEM_RELEASE); 221? ????} 222? ???? 223? ????CloseHandle(hProcess); 224? ???? 225? ????return(fOk); 226? } 227? 228? 229? BOOL?WINAPI?InjectLibA(DWORD?dwProcessId,?PCSTR?pszLibFile)?{ 230? ???? 231? ????//?Allocate?a?(stack)?buffer?for?the?Unicode?version?of?the?pathname 232? ????PWSTR?pszLibFileW?=?(PWSTR)? 233? ????????_alloca((lstrlenA(pszLibFile)?+?1)?*?sizeof(WCHAR)); 234? ???? 235? ????//?Convert?the?ANSI?pathname?to?its?Unicode?equivalent 236? ????wsprintfW(pszLibFileW,?L"%S",?pszLibFile); 237? ???? 238? ????//?Call?the?Unicode?version?of?the?function?to?actually?do?the?work. 239? ????return(InjectLibW(dwProcessId,?pszLibFileW)); 240? } 241?
|