逆向工程核心原理——IAT_HOOK

  1. 前言
  2. 选定目标API
  3. OllyDbg验证
  4. IAT钩取原理
  5. 练习实例
    1. DLLMain()
    2. MySetWindowTextW()
    3. hook_iat()
  6. 调试被注入的DLL文件
  7. 总结

前言

API hook中有一种是通过注入DLL文件来钩取API的,DLL注入目标进程后,修改IAT来更改进程中调用的特定API的功能。

这一节我们以Windows下的计算器为例。向计算器进程中插入用户的DLL文件,钩取IAT的user32.SetWindowTextW()API地址。

选定目标API

我们在选择使用的API钩取技术后,接下来重要的一步就是定目标API,也就是我们要钩取的API。由于我们没有丰富的开发经验,所以大部分情况下我们是不知道要钩取哪个API,在操作系统中,某项功能最终都是由某个或者某些API提供的,我们可以通过检索的方法,推测+验证来确认需要钩取的API。

我们发现有两个API比较引人注目,分别是SetWindowTextW()、SetDlgItemTextW(),他们负责向计算器的文本框中显示文本。由于SetDlgItemTextW其实内部又调用了SetWindowTextW,所以我们就假设钩取SetWindowTextW这一个API就够了

BOOL SetWindowTextW(
  [in]           HWND    hWnd,
  [in, optional] LPCWSTR lpString
);

我们注意到第二个参数是一个字符串指针,我们将这个指针指向的位置的数字改为中文,应该就能实现目的,现在进行验证

OllyDbg验证

我们先给API下个断点,这样子在程序运行并且显示字符时就会断下来

程序运行时,我们发现会给个初始值0。此时我们随便输入一个数字

程序断了下来,查看字符串指针,缓冲区也是7,我们将这个值改为中文7试试,注意中文7对应的Unicode为4e03,由于x86是小端存储,我们需要改为03 4e ,F9运行,修改成功

IAT钩取原理

这是正常的调用user32.SetWindowTextW(),地址1001110存储的是user32.SetWindowTextW()的地址,当call [1001110],其实就是call 77D0960E,程序调用SetWindowTextW函数,执行完后正常返回。

hookiat.dll中提供了mySetWindowTextW()的钩取函数,在1002628首先执行的call指令与正常执行是一样的,只不过这次先进去mySetWindowTextW函数,也就是说在保持代码不变的前提下,将IAT中保存的API起始地址改成了用户函数的起始地址。执行完call指令后,程序进入mySetWindowTextW函数内部,经过一些操作后,调用真正的SetWindowTextW,1000B6B8存放着user32.SetWindowTextW的起始地址,执行完毕后,通过ret指令又回到hookiat中执行,再经过ret指令,返回到calc原来代码段,这就是整个hook iat的过程。

练习实例

DLLMain()

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
	switch( fdwReason )
	{
		case DLL_PROCESS_ATTACH : 
            // 保存原始API的地址
           	g_pOrgFunc = GetProcAddress(GetModuleHandle(L"user32.dll"), 
                                        "SetWindowTextW");
            // # hook
            //  用hookiat.MySetWindowTextW钩取user32.SetWindowTextW
			hook_iat("user32.dll", g_pOrgFunc, (PROC)MySetWindowTextW);
			break;
		case DLL_PROCESS_DETACH :
            // # unhook
            //  将calc.exe恢复原值
            hook_iat("user32.dll", (PROC)MySetWindowTextW, g_pOrgFunc);
			break;
	}
	return TRUE;
}

通过g_pOrgFunc保存SetWindowTextW的地址,这个会用在后面脱钩的过程中,另外由于已经加载了user32.dll,所以可以直接调用GetProcAddress,否则需要先调用LoadLibrary。

在DLL加载时,通过hook_iat函数,钩取IAT,将user32.SetWindowTextW的地址更换成hookiat.MySetWindowTextW地址。

在DLL卸载时,触发hook_iat函数,钩取IAT脱钩,将hookiat.MySetWindowTextW的地址更换成user32.SetWindowTextW地址。

MySetWindowTextW()

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);
}

计算器的IAT被钩取后,每当代码中调用user32.SetWindowTextW()函数时,都会先调用hookiat. MySetWindowTextW函数。g_pOrgFunc存放着原始API,指向user32.SetWindowTextW()

hook_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;

    //读取PE文件头信息
	hMod = GetModuleHandle(NULL);//hMod=ImageBase
	pAddr = (PBYTE)hMod;		 //pAddr=ImageBase
	pAddr += *((DWORD*)&pAddr[0x3C]);//PE标识
	dwRVA = *((DWORD*)&pAddr[0x80]);//dwRVA=RVA of IID

	pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod+dwRVA);

	for( ; pImportDesc->Name; pImportDesc++ )
	{
        // dllname的rva
		szLibName = (LPCSTR)((DWORD)hMod + pImportDesc->Name);
		//比较 pImportDesc->Name与“user32.dll”是否一样
		if( !_stricmp(szLibName, szDllName) )
		{
            // pThunk = IMAGE_IMPORT_DESCRIPTOR.FirstThunk
            // pThunk是导入地址表(IAT)的rva
			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 )
				{
                    // 更改内存属性为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;
}

调试被注入的DLL文件

由于Ollydbg1.0在调试Dll的时候会出现一些bug,所以我们使用Ollydbg2.0

先将calc.exe附加到Ollydbg上,然后F9运行。设置在加载新DLL时暂停

然后注入我们的DLL,od就会停在我们新加载的DLL的入口点,再把刚才设置的pause on new module取消掉

我们可以通过单步跟或者查找DllMain中使用的字符串来找到DllMain()函数,我们知道DllMain中使用了“user32.dll”和”SetWindowTextW“字符串

可以结合之前的C语言代码进行调试

总结

IAT钩取主要就是对IAT进行修改,将IAT中原本存放SetWindowTextW的地方设置成我们自己的函数,当我们的函数执行完后再跳回到真正的SetWindowText执行接下来的指令。


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 767778848@qq.com