前一篇介紹了API Hook的幾種方法,本文則直接以CopyFile爲例採用DLL注入的方法來展示編碼流程。
一個完整的HOOK過程包括四個部分:
1)待注入的DLL
2)DLL注入器
3)目標進程
4)主程序
DLL注入器的實現
注入器需要實現的功能就是將用戶DLL注入到目標進程中。可設計爲一個獨立DLL,方便複用。DLL可規劃兩個接口,類似內存分配的malloc/free的配對使用。接口如下:
BOOL WINAPI Inject(LPCTSTR lpszUsrApiDLL, LPCTSTR lpszHookProcess);
BOOL WINAPI Eject(LPCTSTR lpszUsrApiDLL, LPCTSTR lpszHookProcess);
具體實現上代碼:
尋找目標進程PID
採用CreateToolhelp32Snapshot、Process32First、Process32Next、Module32First、Module32Next
一系列函數即可獲得進程列表及模塊列表。不是本文重點,就不上代碼了。
執行注入
直接上代碼
/* 注入,將dll注入到指定進程中,只給出核心代碼 */
int InjectLib(DWORD dwProcessId, LPCTSTR lpszLib)
{
PTHREAD_START_ROUTINE pfnRemote =(PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle("Kernel32"), "LoadLibraryA");
/*打開目標進程*/
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
/*在目標進程上分配可讀寫空間*/
int nMemSize =strlen(lpszLib) + 1;
void *pRemoteMem = VirtualAllocEx(hProcess, NULL, nMemSize, MEM_COMMIT, PAGE_READWRITE);
/*寫目標進程,將用戶DLL加入目標進程空間(未加載)*/
int ret = WriteProcessMemory(hProcess, pRemoteMem, (void*)lpszLib, nMemSize, NULL);
/*在目標進程中建立遠線程*/
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, pfnRemote, pRemoteMem, 0, NULL);
/*目標進程中執行LoadLibrary即將用戶DLL真正加載起來生效*/
WaitForSingleObject(hThread, INFINITE);
VirtualFreeEx(hProcess, pRemoteMem, 0, MEM_RELEASE);
CloseHandle(hProcess);
CloseHandle(hThread);
return 0;
}
/* 反注入,將用戶dll從目標進程中剝離出來 */
int EjectLib(DWORD dwProcessId, LPCTSTR lpszLib)
{
PTHREAD_START_ROUTINE pfnRemote =(PTHREAD_START_ROUTINE)
GetProcAddress(GetModuleHandle("Kernel32"), "FreeLibrary");
/*打開目標進程*/
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
/*獲取目標進程的模塊列表,檢查用戶dll是否存在;不存在則直接返回*/
/*代碼省略*/
/*在目標進程中建立遠線程*/
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, pfnRemote, hModule, 0, NULL);
/*目標進程中執行FreeLibrary即將用戶DLL卸載掉*/
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hProcess);
CloseHandle(hThread);
return 0;
}
至此,注入器DLL的核心功能已實現。需要注意的是,注入前必須用戶足夠的權限,即必須先提權限,代碼如下:
BOOL ObtainSeDebugPrivilege()
{
HANDLE hToken;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
{
return FALSE;
}
LUID luid;
if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid))
{
return FALSE;
}
TOKEN_PRIVILEGES TokenPrivileges;
TOKEN_PRIVILEGES PreviousTokenPrivileges;
DWORD dwPreviousTokenPrivilegesSize = sizeof(TOKEN_PRIVILEGES);
TokenPrivileges.PrivilegeCount = 1;
TokenPrivileges.Privileges[0].Luid = luid;
TokenPrivileges.Privileges[0].Attributes = 0;
if(!AdjustTokenPrivileges(hToken, FALSE, &TokenPrivileges, sizeof(TOKEN_PRIVILEGES),
&PreviousTokenPrivileges, &dwPreviousTokenPrivilegesSize))
{
return FALSE;
}
PreviousTokenPrivileges.PrivilegeCount = 1;
PreviousTokenPrivileges.Privileges[0].Luid = luid;
PreviousTokenPrivileges.Privileges[0].Attributes |= SE_PRIVILEGE_ENABLED;
if(!AdjustTokenPrivileges(hToken, FALSE, &PreviousTokenPrivileges,
dwPreviousTokenPrivilegesSize, NULL, NULL))
{
return FALSE;
}
return TRUE;
}
待注入的DLL
此DLL主要實現用戶功能以及系統API的替換,本質上就是將系統(或其他第三方)的API地址改寫爲自己實現的函數的地址。這裏包括兩個方面:
實現自己的API
這個比較簡單,即自己實現和目標api完全一致的函數(返回值和參數列表必須一致),CopyFile的實例如下,因爲有ANSI和Wide兩個版本,所以都需實現(貌似系統最終都是調用Wide版本)。如果hook是攔截原api的行爲,則執行完自己的代碼即可返回;否則還需繼續執行原api行爲。示例代碼如下:
/*獲取進程中指定DLL的函數地址*/
PROC GetProcAddr(LPCTSTR pszApiName, LPCTSTR pszOwnerModule)
{
HMODULE hOwner = GetModuleHandle(pszOwnerModule);
PROC pfn = GetProcAddress(hOwner, pszApiName);
return pfn;
}
typedef BOOL (WINAPI *PFN_CopyFileA)(LPCSTR, LPCSTR, BOOL);
BOOL WINAPI HOOK_CopyFileA(LPCSTR lpExistingFileName, LPCSTR lpNewFileName, BOOL bFailIfExists)
{
/*TODO::Add code here ...*/
/*非攔截的情況下會調用原始的api*/
PFN_CopyFileA pfnAPI = (PFN_CopyFileA) GetProcAddr("CopyFileA", "kernel32.dll");
return pfnAPI(lpExistingFileName, lpNewFileName, bFailIfExists);
}
註冊API
之前注入器已經在目標進程中開了遠線程並執行了LoadLibrary / FreeLibrary,它會首先執行到DllMain函數,原型如下:
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch(ul_reason_for_call)
{
/*LoadLibrary*/
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
/*註冊用戶API*/
return TRUE;
/*FreeLibray*/
case DLL_PROCESS_DETACH:
case DLL_THREAD_DETACH:
/*解除用戶API,恢復原API*/
return TRUE;
default:
break;
}
return TRUE;
}
typedef struct _APIHOOK32_ENTRY
{
CHAR pszApiName[64];
CHAR pszOwnerModule[64];
PROC pfnOriginAddress;
PROC pfnDummyAddress;
HMODULE hCallerModule;
}APIHOOK32_ENTRY, *PAPIHOOK32_ENTRY;
BOOL HOOKUPAPI(APIHOOK32_ENTRY* api)
{
ULONG uSize = 0;
/*獲取指向PE文件中的Import中IMAGE_DIRECTORY_DESCRIPTOR數組的指針*/
PIMAGE_IMPORT_DESCRIPTOR pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)
ImageDirectoryEntryToData(api->hCallerModule, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &uSize);
/*查找記錄,看看有沒有我們想要的DLL*/
for (; pImportDesc->Name; pImportDesc++)
{
LPSTR pszDLL = (LPSTR)((PBYTE)api->hCallerModule + pImportDesc->Name);
if (lstrcmpiA(pszDLL, api->pszOwnerModule) == 0)
break;
}
/*尋找我們想要的函數*/
PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA) ((PBYTE)api->hCallerModule + pImportDesc->FirstThunk);//IAT
for (; pThunk->u1.Function; pThunk++)
{
//ppfn記錄了與IAT表項相應的函數的地址
PROC* pfn= (PROC *)&pThunk->u1.Function;
if (*pfn == api->pfnOriginAddress)
{
/*如果地址相同,也就是找到了我們想要的函數,進行改寫,將其指向我們所定義的函數*/
WriteProcessMemory(GetCurrentProcess(), pfn, &(api->pfnDummyAddress), sizeof(api->pfnDummyAddress), NULL);
return TRUE;
}
}
return FALSE;
}
BOOL HookWindowsAPI(APIHOOK32_ENTRY* api)
{
if (api->hCallerModule == NULL)
{
MEMORY_BASIC_INFORMATION memInfo;
VirtualQuery(HOOKUPAPI, &memInfo, sizeof(memInfo));
HMODULE hHookModule = (HMODULE)memInfo.AllocationBase;
MODULEENTRY32 me = {sizeof(MODULEENTRY32)};
CHAR szExeFile[MAX_PATH] = { 0 };
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0);
BOOL bOK = Module32First(hSnapshot, &me);
while (bOK)
{
if (me.hModule != hHookModule)
{
ZeroMemory(szExeFile, sizeof(szExeFile));
strcpy(szExeFile, me.szExePath);
strlwr(szExeFile);
api->hCallerModule = me.hModule;
if (HOOKUPAPI(api))
{}
}
bOK = Module32Next(hSnapshot, &me);
}
return TRUE;
}
return HOOKUPAPI(api);
}
Hook與Unhook都調用HookWindowsAPI函數,區別在於將自定義結構體APIHOOK32_ENTRY中的兩個PROC字段互換即可。
目標進程
當然,目標進程不是我們寫的,否則就不用hook這麼麻煩了,( ̄▽ ̄)”
主程序
主程序就比較簡單了,只需要調用注入器,在需要的時候調用Inject接口,用完再Eject即可,這裏就不上代碼了。