安全编程技术-API hook实验
API Hook实验
API Hook技术是对API函数进行Hook(挂钩)的技术。API Hook的基本方法就是通过hook“接触”到需要修改的API函数入口点,改变它的地址指向新的自定义的函数。
API Hook和消息Hook的区别是,它们Hook的对象不同:消息Hook Windows消息,API Hook 特定函数
API Hook方法多种:IAT Hook、代码Hook、EAT Hook
EAT: export address table,导出地址表
本次实验是一个IAT Hook示例。
实验环境:Win xp sp3, VC 6.0
利用API Hook技术,模拟对lstrcpy函数进行Hook,捕获输入输出。
步骤:
[1] 编写自定义函数:实现检测等需要的功能;
[2] Hook实现:根据PE文件结构寻找IAT,并将IAT中的目标函数的地址更换为自定义的函数地址;
[3] Dll注入:将包含IAT Hook代码及自定义的Hook函数的Dll注入到目标文件中。
- strcpy_exe应用程序的代码(注入的目标文件):
#include <stdio.h>
#include <string.h>
#include <windows.h>
int main(){
char dest[50] = {0};
char src[50] = {0};
char flag = '0';
while(true)
{
printf("push q to quit, c to continue!\n");
flag=getchar();
getchar();
if(flag == 'q') break;
printf("Input Src String!\n");
printf("Src: ");
gets(src);
lstrcpyW((LPWSTR)dest, (LPWSTR)src);
printf("Dest:%s\n\n",dest);
}
return 0;
}
-
strcpy_hookiat.dll编写(自定义函数功能)
#include <stdio.h> #include <stdlib.h> #include <wchar.h> #include <windows.h> #include <time.h> typedef LPTSTR(WINAPI* PFLSTRCPYW)(LPWSTR lpString1, LPWSTR lpString2); FARPROC g_pOrgFunc = NULL; char* wideCharToMultiByte(wchar_t* pwcStrKey) { // 第一次调用确认转换后单字节字符串的长度,用于开辟空间 int pSize = WideCharToMultiByte(CP_OEMCP, 0, pwcStrKey, wcslen(pwcStrKey), NULL, 0, NULL, NULL); char* pcStrKey = new char[pSize + 1]; // 第二次调用将双字节字符串转换成单字节字符串 WideCharToMultiByte(CP_OEMCP, 0, pwcStrKey, wcslen(pwcStrKey), pcStrKey, pSize, NULL, NULL); pcStrKey[pSize] = '\0'; return pcStrKey; //如果想要转换成String,直接赋值即可 //String pKey = pcStrKey; } void writelog(const char *log) { time_t tDate; struct tm *eventTime; time(&tDate); eventTime = localtime(&tDate); int iYear = eventTime->tm_year + 1900; int iMon = eventTime->tm_mon + 1; int iDay = eventTime->tm_mday; int iHour = eventTime->tm_hour; int iMin = eventTime->tm_min; int iSec = eventTime->tm_sec; char sDate[16]; sprintf(sDate, "%04d-%02d-%02d", iYear, iMon, iDay); char sTime[16]; sprintf(sTime, "%02d:%02d:%02d", iHour, iMin, iSec); char s[1024]; sprintf(s, "%s %s %s\n", sDate, sTime, log); FILE *fd = fopen("my.log", "a+"); if (fd != NULL) { fputs(s, fd); fclose(fd); } } // MylstrcpyW LPTSTR WINAPI MylstrcpyW(LPWSTR lpString1, LPWSTR lpString2) { char log[100]; //char* dst = wideCharToMultiByte(lpString1); //char* src = wideCharToMultiByte(lpString2); sprintf(log, "lstrcpyW被调用:\tDST参数为%s\tSRC参数为%s", (char*)(lpString1), (char*)(lpString2)); writelog(log); // 执行kernel32!lstrcpyW() API LPTSTR Result = ((PFLSTRCPYW)g_pOrgFunc)(lpString1, lpString2); if (Result != NULL) { sprintf(log, "本次Cpy成功:\tDST参数为%s\tSRC参数为%s", (char*)(lpString1), (char*)(lpString2)); writelog(log); } else { sprintf(log, "本次cpy失败"); writelog(log); } //delete[] dst; //delete[] src; return Result; } // IAT HOOK函数 BOOL hook_iat(LPCSTR szDllName, PROC pfnOrg, PROC pfnNew) { // szDllName指目标API所在系统Dll名称,即"kernel32.dll" // pfnOrg指原始API地址,即lstrcpyW()的地址 // pfnNew指用于替换lstrcpyW()的自定义函数的地址,即MylstrcpyW()的地址 HMODULE hMod; LPCSTR szLibName; PIMAGE_IMPORT_DESCRIPTOR pImportDesc; PIMAGE_THUNK_DATA pThunk; DWORD dwOldProtect, dwRVA; PBYTE pAddr; // hMod, pAddr = 可执行文件的ImageBase // = VA of MZ signature (IMAGE_DOS_HEADER) hMod = GetModuleHandle(NULL); pAddr = (PBYTE)hMod; // pAddr = VA of PE signature (IMAGE_NT_HEADERS) pAddr += *((DWORD*)&pAddr[0x3C]); // dwRVA = RVA of IMAGE_IMPORT_DESCRIPTOR Table dwRVA = *((DWORD*)&pAddr[0x80]); // pImportDesc = VA of IMAGE_IMPORT_DESCRIPTOR Table pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod + dwRVA); for (; pImportDesc->Name; pImportDesc++) { // szLibName = VA of IMAGE_IMPORT_DESCRIPTOR.Name szLibName = (LPCSTR)((DWORD)hMod + pImportDesc->Name); if (!_stricmp(szLibName, szDllName)) { // pThunk = IMAGE_IMPORT_DESCRIPTOR.FirstThunk // = VA to IAT(Import Address Table) pThunk = (PIMAGE_THUNK_DATA)((DWORD)hMod + pImportDesc->FirstThunk); // pThunk->u1.Function = VA of API for (; pThunk->u1.Function; pThunk++) { if (pThunk->u1.Function == (DWORD*)pfnOrg) { // 更改内存属性为E/R/W VirtualProtect((LPVOID)&pThunk->u1.Function, 4, PAGE_EXECUTE_READWRITE, &dwOldProtect); // 修改IAT值(钩取) pThunk->u1.Function = (DWORD*)pfnNew; // 恢复内存属性 VirtualProtect((LPVOID)&pThunk->u1.Function, 4, dwOldProtect, &dwOldProtect); return TRUE; } } } } return FALSE; } BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { switch (fdwReason) { case DLL_PROCESS_ATTACH: // 保存原始API地址 g_pOrgFunc = GetProcAddress(GetModuleHandle((LPCTSTR)"kernel32.dll"), "lstrcpyW"); // 用hookiat!MysetWindowText()钩取kernel32!SetWindowTextW() hook_iat("kernel32.dll", g_pOrgFunc, (PROC)MylstrcpyW); break; case DLL_PROCESS_DETACH: // 将..exe的IAT恢复原值 hook_iat("kernel32.dll", (PROC)MylstrcpyW, g_pOrgFunc); break; } return TRUE; }
- InjectDll.exe编写(通过PID注入dll)
#include <stdio.h> #include <windows.h> #include <tlhelp32.h> #include <winbase.h> #include <tchar.h> // 定义缺少的常量 #ifndef SE_PRIVILEGE_ENABLED #define SE_PRIVILEGE_ENABLED 0x00000002 #endif #ifndef SE_DEBUG_NAME #define SE_DEBUG_NAME TEXT("SeDebugPrivilege") #endif void usage() { printf("\nInjectDll.exe\n" "- USAGE : InjectDll.exe <i|e> <PID> <dll_path>\n\n"); } // dwPID指待注入目标进程的PID值 // szDllName指带注入DLL的path BOOL InjectDll(DWORD dwPID, LPCTSTR szDllName) { HANDLE hProcess, hThread; LPVOID pRemoteBuf; DWORD dwBufSize = (DWORD)(_tcslen(szDllName) + 1) * sizeof(TCHAR); LPTHREAD_START_ROUTINE pThreadProc; // #1.使用dwPID获取目标进程(notepad.exe)句柄 if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID))) { DWORD dwErr = GetLastError(); _tprintf(_T("OpenProcess failed: %lu\n"), dwErr); return FALSE; } // #2.在目标进程(notepad.exe)中分配szDllName大小的内存 pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE); if (pRemoteBuf == NULL) { _tprintf(_T("VirtualAllocEx failed\n")); CloseHandle(hProcess); return FALSE; } // #3.将szDll路径写入分配的内存 if (!WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllName, dwBufSize, NULL)) { _tprintf(_T("WriteProcessMemory failed\n")); VirtualFreeEx(hProcess, pRemoteBuf, 0, MEM_RELEASE); CloseHandle(hProcess); return FALSE; } // #4.获取LoadLibraryA() API的地址 pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandleA("kernel32.dll"), "LoadLibraryA"); if (pThreadProc == NULL) { _tprintf(_T("GetProcAddress failed\n")); VirtualFreeEx(hProcess, pRemoteBuf, 0, MEM_RELEASE); CloseHandle(hProcess); return FALSE; } // #5.在exe进程中运行线程 hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL); if (hThread == NULL) { _tprintf(_T("CreateRemoteThread failed\n")); VirtualFreeEx(hProcess, pRemoteBuf, 0, MEM_RELEASE); CloseHandle(hProcess); return FALSE; } WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); VirtualFreeEx(hProcess, pRemoteBuf, 0, MEM_RELEASE); CloseHandle(hProcess); return TRUE; } BOOL EjectDll(DWORD dwPID, LPCTSTR szDllName) { BOOL bMore = FALSE, bFound = FALSE; HANDLE hSnapshot, hProcess, hThread; MODULEENTRY32 me = { sizeof(me) }; LPTHREAD_START_ROUTINE pThreadProc; //使用TH32CS_SNAPMODULE参数,获取eXe加载的DLL名称 if (INVALID_HANDLE_VALUE == (hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID))) { _tprintf(_T("CreateToolhelp32Snapshot failed\n")); return FALSE; } //从DLL中查找注入的DLL bMore = Module32First(hSnapshot, &me); for (; bMore; bMore = Module32Next(hSnapshot, &me)) { if (!_tcsicmp(me.szModule, szDllName) || !_tcsicmp(me.szExePath, szDllName)) { bFound = TRUE; break; } } if (!bFound) { _tprintf(_T("DLL not found in target process\n")); CloseHandle(hSnapshot); return FALSE; } //OpenProcess函数用来打开一个己存在的进程对象,并返回进程的句柄 if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID))) { _tprintf(_T("OpenProcess failed\n")); CloseHandle(hSnapshot); return FALSE; } //获取FreeLibrary() API的地址 pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandleA("kernel32.dll"), "FreeLibrary"); if (pThreadProc == NULL) { _tprintf(_T("GetProcAddress failed\n")); CloseHandle(hProcess); CloseHandle(hSnapshot); return FALSE; } //在exe进程中运行线程 hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, me.modBaseAddr, 0, NULL); if (hThread == NULL) { _tprintf(_T("CreateRemoteThread failed\n")); CloseHandle(hProcess); CloseHandle(hSnapshot); return FALSE; } WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); CloseHandle(hProcess); CloseHandle(hSnapshot); return TRUE; } DWORD _EnableNTPrivilege(LPCTSTR szPrivilege, DWORD dwState) { DWORD dwRtn = 0; HANDLE hToken; //获得进程访问令牌的句柄 if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) { LUID luid; //查看系统权限的特权值,返向信息到一个LUID结构体里 if (LookupPrivilegeValue(NULL, szPrivilege, &luid)) { BYTE t1[sizeof(TOKEN_PRIVILEGES) + sizeof(LUID_AND_ATTRIBUTES)]; BYTE t2[sizeof(TOKEN_PRIVILEGES) + sizeof(LUID_AND_ATTRIBUTES)]; DWORD cbTP = sizeof(TOKEN_PRIVILEGES) + sizeof(LUID_AND_ATTRIBUTES); PTOKEN_PRIVILEGES pTP = (PTOKEN_PRIVILEGES)t1; PTOKEN_PRIVILEGES pPrevTP = (PTOKEN_PRIVILEGES)t2; pTP->PrivilegeCount = 1; pTP->Privileges[0].Luid = luid; //SE_DEBUG_NAME特权 pTP->Privileges[0].Attributes = dwState; //SE_PRIVILEGE_ENABLED表示属性为特权启动 //启用或禁止指定访问令牌的特权 if (AdjustTokenPrivileges(hToken, FALSE, pTP, cbTP, pPrevTP, &cbTP)) { dwRtn = pPrevTP->Privileges[0].Attributes; } } CloseHandle(hToken); } return dwRtn; } int _tmain(int argc, TCHAR *argv[]) { if (argc != 4) { usage(); return 1; } //adjust privilege _EnableNTPrivilege(SE_DEBUG_NAME, SE_PRIVILEGE_ENABLED); //InjectDll.exe <i|e> <PID> <dll_path> BOOL result = FALSE; if (!_tcsicmp(argv[1], _T("i"))) { result = InjectDll((DWORD)_ttoi(argv[2]), argv[3]); } else if (!_tcsicmp(argv[1], _T("e"))) { result = EjectDll((DWORD)_ttoi(argv[2]), argv[3]); } else { _tprintf(_T("Invalid option: %s\n"), argv[1]); usage(); return 1; } _tprintf(_T("Operation %s\n"), result ? _T("succeeded") : _T("failed")); return result ? 0 : 1; }
-
结果演示
-
将文件放置到同一路径下
-
运行strcpy_exe,在资源管理器中查看PID 这里是1844
-
将InjectDll.exe拖入cmd命令窗口,按下回车可以看见用法
-
将InjectDll.exe拖入cmd后,输入
i 1844
,再把dll也拖入cmd
-
按下回车,提示Operation succeeded
-
再使用strcpy_exe进行复制字符串,会在目录下生成一个my.txt,里面记录了复制的日志
-
至于这里为什么要双写前两个字符,据说这里固定丢失前两个字符,具体原因不是本次实验的重点。