原理介绍
上一节学习了通过调试API进行Hook,调试API 改变的是某一个点的时候的线程上下文相关内容,比如函数参数,缓冲区,函数返回地址等
这一节讲手动修改PE结构中的IAT来进行 Hook,通过修改IAT进行Hook实际上是修改进程的IAT地址,然后让目标函数执行的时候,执行我们自己的函数
因为要操作目标进程的内容,所以注入一个dll会比较方便,注入的dll会有目标进程的全部权限,而且也能很好的把Hook函数放到目标进程中去,当然,代码注入也是可以,就是比较麻烦,这里用dll注入来实现
这里还是以64位的记事本为例,目标是Hook WriteFile API,来让每次执行的时候弹个框(因为只是练习Hook,所以不用太复杂的功能)
首先我们要先了解一下64位下的IAT与32位有什么区别
遍历64位IAT函数名称和地址
在定位64位IAT的时候,遇到了点坑,那就是忽视了32位和64位区别所导致的,后来我专门来遍历一下64位进程的IAT了解
首先要了解一个API:GetModuleHandle,参数只有1个,是目标模块名,这里填NULL表示当前进程,返回的是一个模块句柄,也就是当前进程的首地址
以此为基础,我们就可以开始定位IAT了,下面源代码是定位64位当前进程IAT函数名称和地址(具体关于IAT相关理论在我之前的笔记里有详细分析,这里不啰嗦了)
要注意的区别
-
64位程序的基地址是Unsigned Long Long类型,用DWORD64表示就行
-
运行中的程序,IAT里记录的是VA而不是RVA
-
运行中的程序,INT里记录的是RVA而不是VA
源代码:
#include<stdio.h>
#include<Windows.h>
int main() {
//获取当前exe的首地址
HANDLE hMod = GetModuleHandle(NULL);//可以访问到当前线程在内存中的映像首地址,也就是基地址
//获取当前exe的NT结构
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)hMod;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)((DWORD64)hMod + pDos->e_lfanew);
//获取当前exe的数据目录表导入表信息
PIMAGE_OPTIONAL_HEADER pOptional = &pNt->OptionalHeader;
PIMAGE_DATA_DIRECTORY pDir = &pOptional->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
PIMAGE_IMPORT_DESCRIPTOR pImpDes = PIMAGE_IMPORT_DESCRIPTOR(pDir->VirtualAddress + (DWORD64)hMod);
//遍历导入表
for (; pImpDes->Name; pImpDes++) {
DWORD64 dwDllName = pImpDes->Name + (DWORD64)hMod;
printf("DLL名称:%s\r\n", dwDllName);
PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)(pImpDes->FirstThunk + (DWORD64)hMod);//导入地址表
PIMAGE_THUNK_DATA pINT = (PIMAGE_THUNK_DATA)(pImpDes->OriginalFirstThunk + (DWORD64)hMod);//INT
if (pThunk->u1.Ordinal != 0) {
for (; pThunk->u1.Function; pThunk++,pINT++) {
PIMAGE_IMPORT_BY_NAME dwFunName = (PIMAGE_IMPORT_BY_NAME)(pINT->u1.AddressOfData + (DWORD64)hMod);
DWORD64* dwFunAddr = &(pThunk->u1.Function);
printf("函数名称:%-30s", dwFunName->Name);
printf("函数地址:%p\r\n", *dwFunAddr);
}
}
printf("\r\n------------------\r\n\r\n");
}
CloseHandle(hProcess);
return 0;
}
效果演示
通过修改IAT进行Hook
这里还是以64位的记事本为例,目标是Hook WriteFile API,来让每次执行的时候弹个框(因为只是练习Hook,所以不用太复杂的功能)
这里的难点在于如何判断自己是否成功定位到了目标的IAT,关于修改IAT只需要改一下访问权限直接修改即可
这里对遍历IAT不熟悉的话,最好去写个64位程序来遍历自身的IAT来练习练习
其他想说的,都在代码里:
dllmain.dll
#include<windows.h>
#include<stdio.h>
SIZE_T g_pOrgFuncAddr = 0;
BOOL IAT_Hook(LPCSTR DLLname, PROC szOrgFuncAddr, PROC szMyFuncAddr);
//我们自己的Hook函数
BOOL WINAPI MyWriteFile(
_In_ HANDLE hFile,
_In_reads_bytes_opt_(nNumberOfBytesToWrite) LPCVOID lpBuffer,
_In_ DWORD nNumberOfBytesToWrite,
_Out_opt_ LPDWORD lpNumberOfBytesWritten,
_Inout_opt_ LPOVERLAPPED lpOverlapped
) {
MessageBox(NULL,L"警告",L"ERROR",MB_OK);
return WriteFile(hFile,lpBuffer,nNumberOfBytesToWrite,lpNumberOfBytesWritten,lpOverlapped);
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
//保存原始API地址
g_pOrgFuncAddr = (SIZE_T)GetProcAddress(GetModuleHandleA("kernel32.dll"),"WriteFile");//WriteFile在kernel32.dll里!!
IAT_Hook("kernel32.dll",(PROC)g_pOrgFuncAddr,(PROC)MyWriteFile);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
IAT_Hook("kernel32.dll", (PROC)MyWriteFile, (PROC)g_pOrgFuncAddr);
break;
}
return TRUE;
}
BOOL IAT_Hook(LPCSTR DLLname, PROC szOrgFuncAddr, PROC szMyFuncAddr) {
HMODULE hModule = GetModuleHandle(NULL);//NULL表示返回进程本身的首地址
//定位到NT头
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((SIZE_T)hModule + pDosHeader->e_lfanew);
//定位到映像地址和导入地址表
DWORD64 szImageBase = pNtHeader->OptionalHeader.ImageBase;
DWORD64 szImpRva = (DWORD64)pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
//导入表VA
PIMAGE_IMPORT_DESCRIPTOR pImpDes = (PIMAGE_IMPORT_DESCRIPTOR)(szImageBase + szImpRva);
//查找欲HOOK的dll
while (pImpDes->Name) {
DWORD64 szNameAddr = szImageBase + pImpDes->Name;//获取dll名称VA
char szName[MAXBYTE] = { 0 };
strcpy_s(szName, (char*)szNameAddr);//提取字符串
//MessageBoxA(0, szName, 0, 0);
//判断dll是否是我们要Hook的dll
if (strcmp(_strlwr(szName), DLLname) == 0) {
//此时选中的就是指定的dll
//遍历指定函数
PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)(pImpDes->FirstThunk + (DWORD64)hModule);
while (pThunk->u1.Function) {
if (pThunk->u1.Function == (DWORD64)szOrgFuncAddr) {
//修改访问权限
DWORD dwOldProtect;
VirtualProtectEx(GetCurrentProcess(),(LPVOID)&pThunk->u1.Function, 8, PAGE_EXECUTE_READWRITE, &dwOldProtect);//&dwOldProtect这个参数必须有,不然就会失败!!
//修改IAT
pThunk->u1.Function = (DWORD64)szMyFuncAddr;
break;
}
pThunk++;
}
break;
}
pImpDes++;
}
if (pImpDes->Name == NULL){
return FALSE;
}
return TRUE;
}
IATHook.cpp
这里就是简单的远线程dll注入:
#include<stdio.h>
#include<windows.h>
//这里写个dll注入,来加载Hook DLL
BOOL DllInject(LPCSTR WndClassName,LPCSTR szDllPath) {
//获取进程句柄
HWND hWnd = FindWindowA(WndClassName,NULL);
DWORD dwPid = 0;
GetWindowThreadProcessId(hWnd, &dwPid);
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwPid);
//获取LoadLibraryA地址
LPVOID pFunAddr = GetProcAddress(LoadLibraryA("kernel32.dll"), "LoadLibraryA");
//向目标写入dll全路径
SIZE_T sSize = strlen(szDllPath);
LPVOID pDllPath = VirtualAllocEx(hProcess,NULL,sSize,MEM_COMMIT,PAGE_READWRITE);
WriteProcessMemory(hProcess,pDllPath,szDllPath,sSize,NULL);
//创建远程线程进行注入
HANDLE hThread = CreateRemoteThread(hProcess,NULL,NULL,(LPTHREAD_START_ROUTINE)pFunAddr,pDllPath,0,NULL);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
return TRUE;
}
int main() {
DllInject("notepad", "C:\\Users\\helloworld\\source\\ReversecoreBook\\IATHook\\x64\\Debug\\IATHookdll.dll");
return 0;
}
效果演示
总结
这个练习,我之前做过一次IAT Hook一个32位的记事本的,当时很快就做出来了,这次花了很长很长时间,再此做个自我反思:这次我存在的问题是:遇到问题的时候,没能冷静下来逐步分析反而在那不停的调试,下次要想办法提醒自己先静下来!
这次花时间长主要是因为注入的DLL,我不好确认问题出在哪里,对64位IAT不熟悉就是应该先动手练一次再去写DLL!
然后就是对使用的API,比如那个VirtualProtectEx不熟悉,最后一个参数不能写NULL!
还有一个问题就是,我要找WriteFile这个API,我从user32.dll里找了半天,其实是在kernel32.dll中,太不冷静了唉
冷静很重要,清醒的头脑更具有创造力