標籤:c++函數打樁windows api攔截 dll記憶體注入
windows 下實現函數打樁:攔截API方式 最近因為工作需要,開始研究函數打樁的方法。由於不想對工程做過多的修改,於是放棄了使用Google gmock的想法。但是也足足困擾另外我一天一宿。經過奮戰,終於有所收穫。閑話少說,開始看看有什麼方法。
一、基礎準備1.
函數調用的原理:通過函數名(函數的入口地址)對函數進行訪問,假設我們能夠改變函數首地址指向的記憶體的話,使其跳轉到另一個函數去執行的話,那麼就可以實現函數打樁了。2.
方法:對函數首地址出寫入一條組合語言 jmp xxx (其中xxx是要跳轉的相對位址)。3. 令原函數為oldFun,新函數為newFun,那麼打樁時函數跳轉的相對位址 offset = newFun - oldFun - (我們制定的這條指令的大小),此處為絕對跳轉指令的長度=5。 jmp xxx一共6位元組。
函數:
1. VirtualQuery
WINBASEAPISIZE_TWINAPIVirtualQuery( __in_opt LPCVOID lpAddress, //所查記憶體位址 __out_bcount_part(dwLength, return) PMEMORY_BASIC_INFORMATION lpBuffer, //儲存記憶體地區的buffer __in SIZE_T dwLength //資訊長度 );
該函數用於查詢某一段記憶體地區的記憶體資訊,事實VirtualQueryEx也可以使用。2. VirtualProtect
WINBASEAPIBOOLWINAPIVirtualProtect( __in LPVOID lpAddress, __in SIZE_T dwSize, __in DWORD flNewProtect, __out PDWORD lpflOldProtect );
該函數用於修改指定記憶體區dwSize個位元組的保護模式。
3. VirtualProtectEx
WINBASEAPIBOOLWINAPIVirtualProtectEx( __in HANDLE hProcess, //進程控制代碼 __in LPVOID lpAddress, //需要修改的記憶體首地址 __in SIZE_T dwSize, //修改的位元組數 __in DWORD flNewProtect, //新的保護屬性 __out PDWORD lpflOldProtect //舊的保護屬性 );
VirtualProtectEx 用於改變指定進程記憶體段的保護模式,預設情況下函數的記憶體空間不可寫,這就是為什麼要用改變保護屬性的函數。
4. ReadProcessMemory
WINBASEAPIBOOLWINAPIReadProcessMemory( __in HANDLE hProcess, __in LPCVOID lpBaseAddress, __out_bcount_part(nSize, *lpNumberOfBytesRead) LPVOID lpBuffer, __in SIZE_T nSize, __out_opt SIZE_T * lpNumberOfBytesRead );
讀取進程記憶體,lpProcess是首地址,而lpBuffer用於儲存讀出的資料,nSize是需要讀出的位元組數。
5. WriteProcessMemory
WINBASEAPIBOOLWINAPIWriteProcessMemory( __in HANDLE hProcess, __in LPVOID lpBaseAddress, __in_bcount(nSize) LPCVOID lpBuffer, __in SIZE_T nSize, __out_opt SIZE_T * lpNumberOfBytesWritten );
該函數用於寫進程的記憶體空間,可以向進程記憶體注入想要注入的資料,例如函數等。
6. GetCurrentProcess
WINBASEAPI__outHANDLEWINAPIGetCurrentProcess( VOID );
該函數返回一個偽進程控制代碼0xffffffff,任何需要進程控制代碼的記憶體都可以使用它。
二、對庫中API打樁
方案一:
打樁:
#define FLATJMPCODE_LENGTH 5 //x86 平坦記憶體模式下,絕對跳轉指令長度#define FLATJMPCMD_LENGTH 1 //機械碼0xe9長度#define FLATJMPCMD 0xe9 //對應彙編的jmp指令// 記錄被打樁函數的內容,以便恢複BYTE g_apiBackup[FLATJMPCODE_LENGTH+FLATJMPCMD_LENGTH];BOOL setStub(LPVOID ApiFun,LPVOID HookFun){ BOOL IsSuccess = FALSE; DWORD TempProtectVar; //臨時保護屬性變數 MEMORY_BASIC_INFORMATION MemInfo; //記憶體分頁屬性資訊 VirtualQuery(ApiFun,&MemInfo,sizeof(MEMORY_BASIC_INFORMATION)); if(VirtualProtect(MemInfo.BaseAddress,MemInfo.RegionSize, PAGE_READWRITE,&MemInfo.Protect)) //修改頁面為可寫 { memcpy((void*)g_apiBackup,(const void*)ApiFun, sizeof(g_apiBackup)); *(BYTE*)ApiFun = FLATJMPCMD; //攔截API,在函數程式碼片段前面注入jmp xxx *(DWORD*)((BYTE*)ApiFun + FLATJMPCMD_LENGTH) = (DWORD)HookFun - (DWORD)ApiFun - FLATJMPCODE_LENGTH; VirtualProtect(MemInfo.BaseAddress,MemInfo.RegionSize, MemInfo.Protect,&TempProtectVar); //改回原屬性 IsSuccess = TRUE; } return IsSuccess;}
清樁:
BOOL clearStub(LPVOID ApiFun){ BOOL IsSuccess = FALSE; DWORD TempProtectVar; //臨時保護屬性變數 MEMORY_BASIC_INFORMATION MemInfo; //記憶體分頁屬性資訊 VirtualQuery(ApiFun,&MemInfo,sizeof(MEMORY_BASIC_INFORMATION)); if(VirtualProtect(MemInfo.BaseAddress,MemInfo.RegionSize, PAGE_READWRITE,&MemInfo.Protect)) //修改頁面為可寫 { memcpy((void*)ApiFun, (const void*)g_apiBackup, sizeof(g_apiBackup)); //恢複程式碼片段 VirtualProtect(MemInfo.BaseAddress,MemInfo.RegionSize, MemInfo.Protect,&TempProtectVar); //改回原屬性 IsSuccess = TRUE; } return IsSuccess;}
方案二:
打樁:
bool setStub(LPVOID ApiFun,LPVOID HookFun){HANDLE file_handler = GetCurrentProcess(); //擷取進程偽控制代碼DWORD oldProtect,TempProtectVar;char newCode[6]; //用於讀取函數原有記憶體資訊int SIZE = FLATJMPCODE_LENGTH+FLATJMPCMD_LENGTH; //需要修改的記憶體大小if(!VirtualProtectEx(file_handler,ApiFun,SIZE,PAGE_READWRITE,&oldProtect)) //修改記憶體為可讀寫{return false;}if(!ReadProcessMemory(file_handler,ApiFun,newCode,SIZE,NULL)) //讀取記憶體{return false;}memcpy((void*)g_apiBackup,(const void*)newCode, sizeof(g_apiBackup)); //儲存被打樁函數資訊*(BYTE*)ApiFun = FLATJMPCMD; *(DWORD*)((BYTE*)ApiFun + FLATJMPCMD_LENGTH) = (DWORD)HookFun - (DWORD)ApiFun - FLATJMPCODE_LENGTH; //樁函數注入 VirtualProtectEx(file_handler,ApiFun,SIZE,oldProtect,&TempProtectVar); //恢複保護屬性}
清樁:
bool clearStub(LPVOID ApiFun){ BOOL IsSuccess = FALSE; HANDLE file_handler = GetCurrentProcess(); DWORD oldProtect,TempProtectVar; int SIZE = FLATJMPCODE_LENGTH+FLATJMPCMD_LENGTH;if(VirtualProtectEx(file_handler,ApiFun,SIZE,PAGE_READWRITE,&oldProtect)) { memcpy((void*)ApiFun, (const void*)g_apiBackup, sizeof(g_apiBackup)); //恢複被打樁函數記憶體 VirtualProtectEx(file_handler,ApiFun,SIZE,oldProtect,&TempProtectVar); IsSuccess = TRUE; } return IsSuccess;}
方案三:
打樁:
bool setStub(LPVOID ApiFun,LPVOID HookFun){HANDLE file_handler = GetCurrentProcess();DWORD oldProtect,TempProtectVar;char newCode[6];int SIZE = FLATJMPCODE_LENGTH+FLATJMPCMD_LENGTH;if(!ReadProcessMemory(file_handler,ApiFun,newCode,SIZE,NULL)){return false;}memcpy((void*)g_apiBackup,(const void*)newCode, sizeof(g_apiBackup));*(BYTE*)newCode = FLATJMPCMD; *(DWORD*)((BYTE*)newCode + FLATJMPCMD_LENGTH) = (DWORD)HookFun - (DWORD)ApiFun - FLATJMPCODE_LENGTH; if(!WriteProcessMemory(file_handler,ApiFun,newCode,FLATJMPCODE_LENGTH,NULL)){return false;}}
說來也怪,這個方案沒有改變讀取許可權,居然也可以,這裡寫入的方式是用WriteProcessMemory來實現,與直接用指標同理。清樁同上。但是如果直接用指標來寫就會出錯,暫時不知道原因。
至此我們實現了函數的打樁,但是有個小小的問題,若僅僅是如此,對類函數中成員函數打樁有點小問題,指標無法轉換,這是因為類成員函數的指標不僅僅是一個普通的指標,他還包括其他資訊。所有這裡需要解決這個問題,網上找到了兩個方法:
1. 類的普通函數成員地址轉換
LPVOID GetClassFnAddress(...){ LPVOID FnAddress; __asm { lea eax,FnAddress mov edx,[ebp+8] // ebp+8 為第一個形參的地址,ebp+C 為第二個形參的地址,以此類推 mov [eax],edx } return FnAddress;}
2. 類的虛成員函數地址轉換
LPVOID GetClassVirtualFnAddress(LPVOID pthis,int Index) //Add 2010.8.6{ LPVOID FnAddress; *(int*)&FnAddress = *(int*)pthis; //lpvtable *(int*)&FnAddress = *(int*)((int*)FnAddress + Index); return FnAddress;}
至此函數打樁的介紹告一段落。
windows 下實現函數打樁:攔截API方式