结果:在计算器窗口中把本来应该显示的数字替换为中文
须知:计算器显示数据的时候需要调用user32.dll里面的SetWindowText() API
过程:在程序运行的时候会加载user32.dll到内存,这个时候会把IAT里面写入成函数的真正地址(具体见文章中图),这里自己写一个dll当dll被加载会修改user32.dll!SetWindowTextW对应的IAT值达到劫持程序执行流的目的。
注意:32位程序要编译成32位dll,64位同样
#include"windows.h"
LPVOID g_porgFunc = NULL;
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
g_porgFunc = GetProcAddress(GetModuleHandle(L"user32.dll"), "SetWindowTextW");
//用hookiad.MySetWindowsText()钩取user32.SetWindowTextW()
hook_iat("user32.dll", g_porgFunc, (PROC)MySetWindowTextW);
break;
case DLL_PROCESS_DETACH:
//将calc.exe的IAT恢复原位
hook_iat("user32.dll", (PROC)MySetWindowTextW, g_porgFunc);
break;
}
return True;
}
首先获取要挂钩函数的地址,
然后把地址替换实现钩取和脱钩
然后需要一个转化函数,函数完成的过程:
1.把传入的参数进行转化
2.调用原本的参数
typedef BOOL(WINAPI *PFSETWINDOWTEXTW)(HWND hWnd, LPWSTR lpString);
//cal.exe的IAT被钩取之后当代码中调用user32.SetWindowText函数时会首先调用hookiat.MyWindowTextW函数
BOOL WINAPI MySetWindowTextW(HWND hWnd,LPWSTR lpString)
{
wchar_t* pNum = L"零一二三四五六七八九";
wchar_t temp[2] = { 0 };
int i = 0, nLen = 0, nIndex = 0;
nLen = wcslen(lpString);
for (i = 0; i < nLen; i++)
{
//将数字转中文
//lpString 是宽字符版本的字符串
if (L'0' <= lpString[i] && lpString[i] <= L'9')
{
temp[0] = lpString[i];
nIndex = _wtoi(temp);//读入的字符是宽字符
lpString[i] = pNum[nIndex];
}
}//转化
//调用user32.SetWindowTextW()API
//修改lpString缓冲区的内容
return ((PFSETWINDOWTEXTW)g_porgFunc)(hWnd, lpString);
}
当程序正常执行SetWindowsTextW()API的时候函数会被hook_iat函数劫持为调用MySetWindowTextW函数,SetWindowsTextW的2个参数会被传递过来作为MySetWindowTextW的参数,这是对第二个参数(要显示的字符)进行修改完成后再次调用SetWindowTextW函数(不过这个时候的第二个参数已经被修改了)
那么hook_iat是怎么实现的呢?在看之前先补充一些关于IAT的知识
IAT的结构如图
BOOL hook_iat(LPCSTR szDllName,PROC pfnorg, PROC pfnNew)
{
HMODULE hMod;
LPCSTR szLibName;
PIMAGE_IMPORT_DESCRIPTOR pImportDesc;
PIMAGE_THUNK_DATA pThunk;
DWORD dwOldProtect, dwRVA;
PBYTE pAddr;
//hMod,pAddr=ImageBase of cal.exe , VA to IMAGE_DOS_HEADER
hMod = GetModuleHandle(NULL);//获取最近运行exe的句柄
pAddr = (PBYTE)hMod;
//pAddr=VA to IMAGE_NT_HEADERS
pAddr += *((DWORD*)&pAddr[0x3c]);
//dwRVA=RVA to IMAGE_IMPORT_DESCRIPTOR Table
dwRVA = *((DWORD*)&pAddr[0x80]);
//pImportDesc=Va to Image_Import_DESCRIPTOR Table
pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod + dwRVA);
for (; pImportDesc->Name; pImportDesc++)
{
//szLibName=VA to IMAGE_IMPORT_DESCRIPTOR.Name
szLibName = (LPCSTR)(DWORD)hMod + pImportDesc->Name;
if (!_stricmp(szLibName, szDllName))//找到对应dll
{
//pThunk=IMAGE_IMPORT_DESCRIPTOR.FirstThunk
// =VA to IAT(Import Address Table)
pThunk = (PIMAGE_THUNK_DATA)((DWORD)hMod + pImportDesc->FirstThunk);
//pThunk->u1.Function=VA to API
for (; pThunk->u1.Function; pThunk++)
{
if (pThunk->u1.Function == (DWORD)pfnorg)
{
//更改内存属性
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;
}
//替换IAT中szDllName动态链接库的pfnorg函数地址为pfnNew函数的地址
过程分为这几步
- 找到exe文件的加载地址(GetModuleHandle(NULL)函数)
- 找到user32.dll的加载地址,和SetWindowTextW的加载地址
- 修改IAT内存的属性用VirtualProtect函数
- 修改IAT->Function值(这个值是函数的真正运行时地址)(dll被主调exe加载到自己的虚拟内存空间,所以直接赋值)
- 把内存权限修改回去
dll注入参考https://blog.csdn.net/qq_38204481/article/details/82939494
调试:用OD的附加选项
右击,查找,所有模块中的名称
敲入字符即可搜索到相应的API或者dll
64位程序可以用windbg
总结:包含3个函数,主函数负责被加载的时候调用,hook_iat函数负责修改IAT,MySetWindowTextW函数对原函数的具体修改
和调试方式的比较:调试方式对原本的exe影响较大,容易被检测,修改IAT的方式仅仅是增加一个库或者直接代码注入,简洁。