IAT HOOK

iat hook,也就是修改导入表实现函数调用的hook(运行时hook本进程)。注:iat(import address table,导入函数地址表,在这里说明一下:导入表是image import table,导入函数地址表是 image import address table,这是两个不同的pe文件中的表)

要实现iat hook,首先得清楚iat和导入表的结构。pe文件中,不管是在硬盘上的文件,还是内存中的映像,导入表和iat都会存在,而且在硬盘上和在映像中的组织方式相同(但是整个pe文件在硬盘上和在内存中的组织方式有很大不同)。pe文件的结构在这篇文章中不会讨论太多,但是理解这篇文章需要pe文件结构的知识,请在了解pe文件结构的情况下阅读这篇文章,如果还不了解,可以参考这本书:windows pe权威指南,写的非常详尽,特别是pe文件头和各种表。

pe文件中的导入表的起始是一组叫做image_import_descriptor的结构体,每一个结构体20个字节,对应于一个模块,以一个全0的结构体结束。用汇编定义如下:
IMAGE_IMPORT_DESCRIPTOR STRUCT 
  union 
    Characteristics dd ? 
    OriginalFirstThunk dd ? 
  ends 
  TimeDateStamp dd ? 
  ForwarderChain dd ? 
  Name1 dd ? ; 我们需要关注这一个成员
  FirstThunk dd ? ;还有这一个成员

IMAGE_IMPORT_DESCRIPTOR ENDS

这里我们只关心在内存中他们会指向哪里,而不关心在硬盘上的文件,所以我们的hook是在运行时完成的。上面的结构体中,Name1是一个rva(relative virtual address,相对虚拟地址),也就是说它是一个相对于imagebase(模块基地址)的一个偏移,模块基地址可以用GetModuleHandle(ModuleName)来获取,如果ModuleName我们传入NULL,可以获取这个函数在被执行时所在的模块。Name1指向的是什么呢?

我们前面说过,每一个IMAGE_IMPORT_DESCRIPTOR对应于一个模块,这个Name1指向的就是这个模块的名字(以0结尾什么的就不需要我说明了)。FirstThunk也是一个rva,它指向Name1对应的动态链接库的所有的被当前程序导入了的函数,实际上它指向的地方就位于iat中,可以说导入表和iat是你中有我,我中有你。那么iat到底是怎么组织的呢?参考下面:
  链接库A导入函数1的地址
  链接库A导入函数2的地址
  00000000
  链接库B导入函数1的地址
  链接库B导入函数2的地址
  链接库B导入函数3的地址

  00000000

   ..................

iat是分成一块一块的,每一块对应于一个被程序导入的动态链接库,每一块都是一个接一个的地址,最后以双字的0表示这一个模块的函数地址已经全部存储好了。实际上,在硬盘上iat的组织方式不是这样的,但是我们只关心在内存中它是怎样组织的。了解了上面的知识后,我们就可以看看当我们在程序中,当调用一个函数的时候到底发生了什么。比如下面这句:int pid=GetCurrentProcessId();

我们要分解的就是GetCurrentProcessId到底是怎么完成的。GetCurrentProcessId()位于kernle32.dll中,如果我们在程序中调用了这个函数,编译链接器就知道,我们的程序在运行的时候需要知道GetCurrentProcessId在内存的哪个位置,于是就在pe文件中的导入表中加入一个image import descriptor,结构中的Name1指向一个字符串kernel32.dll,这样,pe加载器在将我们的程序加载到内存中时,它就知道,我们的程序需要调用kernel32.dll中的函数,它就把kernel32.dll映射到我们的进程的空间中(要深入说明这个很复杂,我们可以简单的理解为,我们的程序的地址空间中已经把整个的kerner32.dll映射了)。

虽然这个需要的dll已经映射好了,但是我们要的函数的地址放在哪里呢?这里就是iat起作用的地方了,仔细看上面的iat的组织方式,我们假设链接库A就是kernel32.dll,那pe加载器在加载的时候,就把GetCurrentProcessId的地址放到上面那个结构中“链接库A导入函数1的地址”那个地方。

OK,我们假设需要的kernel32.dll已经映射好了,我们要用的函数的地址也找到了并且放到了前面说的那个地方,程序中调用这个函数的时候到底怎么跳转的呢?如果反汇编的话,它就变成下面这个模样:(地址我随意写的,大概这样)
0x00400078  call 0x00403456
      .
      .
0x00403456  jmp [0x00401234]
而在0x00401234里面放的是这个东西:0x7c8099b0,那上面的jmp就跳到x07c8099b0开始执行。再看看上面这个流程,在0x00400078这个地方,有一个call指令,这个call指令就是代替源程序中的GetCurrentProcessId()这一句。call到0x00403456后,发现是一个jmp指令,jmp到的地方是0x00401234这个地址中存放的一个地址,也就在是先到0x00401234中取出一个双字值X,然后跳转到X处开始执行。

这个流程有点拐弯抹角,可以仔细看看。重要的地方在0x00401234,这个地方存放的就是GetCurrentProcessId()的地址,而0x00401234自己在的地方就是iat中,就是前面的“链接库A导入函数1的地址”在的地方。 理解了上面的call流程,我们就可以想想,如果我们把0x00401234这个地址里面的东西换掉,它不是原来是0x7c8099b0吗,也就是GetCurrentProcessId()函数在内存中的位置吗,我们把它换成0x00400000,或者随便什么东西,0x00000000什么的,如果我们的程序中,在后面调用GetCurrentProcessId()的时候,它就跑到我们写的那个地方去了。

栗子:

// .H
#pragma once
#include "stdafx.h"

BOOL ImportAddressTableHook(IN HMODULE hModule, IN LPCSTR pImageName, IN LPCVOID pTargetFuncAddr, IN LPCVOID pReplaceFuncAddr);

DWORD WINAPI GetCurrentProcessId_Hook()
{
	// to do...
	MessageBoxA(NULL, "This is GetCurrentProcessId_Hook", "caption", MB_OK);
	return 0;
}

int WINAPI MyMessageBoxW(
	__in_opt HWND hWnd,
	__in_opt LPCWSTR lpText,
	__in_opt LPCWSTR lpCaption,
	__in UINT uType)
{
	//todo ...
	return MessageBoxA(NULL, "This Is MyMessageBoxW!", "caption", MB_OK);
}

BOOL Rookits(IN HMODULE hModule, IN LPCSTR pImageName, IN LPCSTR pTargetFuncName, IN LPVOID pReplaceFuncAddr)
{
	LPDWORD pTargetFuncAddr = NULL;
	HMODULE hLib = LoadLibraryA(pImageName);
	if (NULL != hLib)
	{
		pTargetFuncAddr = (LPDWORD)GetProcAddress(hLib, pTargetFuncName);
		return ImportAddressTableHook(hModule, pImageName, pTargetFuncAddr, pReplaceFuncAddr);
	}

	return FALSE;
}

BOOL ImportAddressTableHook
(
	IN HMODULE hModule,
	IN LPCSTR pImageName,
	IN LPCVOID pTargetFuncAddr,
	IN LPCVOID pReplaceFuncAddr
	)
{
	IMAGE_DOS_HEADER* pImageDosHearder = (IMAGE_DOS_HEADER*)hModule;
	IMAGE_OPTIONAL_HEADER* pImageOptionalHeader = (IMAGE_OPTIONAL_HEADER*)((DWORD)hModule + pImageDosHearder->e_lfanew + 24);
	IMAGE_IMPORT_DESCRIPTOR* pImageImportDescriptor = (IMAGE_IMPORT_DESCRIPTOR*)
		((DWORD)hModule + pImageOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

	while (pImageImportDescriptor != 0)
	{
		LPCSTR pszModName = (PSTR)((PBYTE)GetModuleHandle(NULL) + pImageImportDescriptor->Name);
		if (_stricmp(pszModName, pImageName) == 0)
			break;
		pImageImportDescriptor++;
	}

	if (pImageImportDescriptor == NULL)
		return FALSE;

	IMAGE_THUNK_DATA* pImageThunkData = (IMAGE_THUNK_DATA*)((DWORD)hModule + pImageImportDescriptor->FirstThunk);
	if (pImageThunkData == NULL)
		return FALSE;
	
	while (pImageThunkData->u1.Function)
	{
		LPDWORD lpFunctionAddress = (LPDWORD)&(pImageThunkData->u1.Function);
		if (*lpFunctionAddress == (DWORD)pTargetFuncAddr)
		{
			MEMORY_BASIC_INFORMATION mbi;
			VirtualQuery(mbi.BaseAddress, &mbi, sizeof(MEMORY_BASIC_INFORMATION));
			VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &mbi.Protect);

			if (!WriteProcessMemory((HANDLE)-1, lpFunctionAddress, &pReplaceFuncAddr, sizeof(lpFunctionAddress), NULL))
			{
				return FALSE;
			}

			DWORD dwProtectOld;
			VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, &dwProtectOld);
			return TRUE;
		}
		pImageThunkData++;
	}
	return FALSE;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma pack(1)
typedef struct _JMPCODE
{
	BYTE jmp;
	DWORD addr;
}JMPCODE, *PJMPCODE;

typedef FARPROC (WINAPI* GETPROCADDRESS)(
	_In_ HMODULE hModule,
	_In_ LPCSTR lpProcName);

typedef int (WINAPI* MESSAGEBOXA)(
		_In_opt_ HWND hWnd,
		_In_opt_ LPCSTR lpText,
		_In_opt_ LPCSTR lpCaption,
		_In_ UINT uType);


typedef int (WINAPI* MESSAGEBOXW)(
	_In_opt_ HWND hWnd,
	_In_opt_ LPCWSTR lpText,
	_In_opt_ LPCWSTR lpCaption,
	_In_ UINT uType);

GETPROCADDRESS realGetProcAddress = NULL;

_declspec(naked) FARPROC WINAPI MyGetProcAddress(
	_In_ HMODULE hModule,
	_In_ LPCSTR lpProcName)
{
	_asm
	{
		push ebp
		mov ebp, esp
	}

	// 如果是MessageBoxW, 把MyMessageBox返回
	if (hModule == ::GetModuleHandle(L"user32.dll") && _strcmpi(lpProcName, "MessageBoxW") == 0)
	{
		_asm
		{
			mov eax, MyMessageBoxW;
			mov esp, ebp
			pop ebp
			ret
		}
	}
	else{
		_asm
		{
			mov ebx, realGetProcAddress
			add ebx, 5
			jmp ebx
		}
	}
}

void Hook_GetProcAddr()
{
	realGetProcAddress = GetProcAddress;

	JMPCODE jumpCode;
	jumpCode.jmp = 0xe9;
	jumpCode.addr = (DWORD)MyGetProcAddress - ((DWORD)realGetProcAddress + 5);

	DWORD dwNewProtect = PAGE_EXECUTE_READWRITE;
	MEMORY_BASIC_INFORMATION mbi;
	VirtualQuery(realGetProcAddress, &mbi, sizeof(MEMORY_BASIC_INFORMATION));

	VirtualProtect(mbi.BaseAddress, mbi.RegionSize, dwNewProtect, &mbi.Protect);
	WriteProcessMemory(GetCurrentProcess(), realGetProcAddress, &jumpCode, sizeof(jumpCode), NULL);
	VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, &dwNewProtect);
}

void UnHook_GetProcAddr()
{
	BYTE backCode[5] = { 0 };
	backCode[0] = 0x8B;   // mov edi, edi
	backCode[1] = 0xFF;
	backCode[2] = 0x55;   // push ebp
	backCode[3] = 0x8B;   // mov ebp, esp
	backCode[4] = 0xEC;

	DWORD dwNewProtect = PAGE_EXECUTE_READWRITE;
	MEMORY_BASIC_INFORMATION mbi;
	VirtualQuery(GetProcAddress, &mbi, sizeof(MEMORY_BASIC_INFORMATION));

	VirtualProtect(mbi.BaseAddress, mbi.RegionSize, dwNewProtect, &mbi.Protect);
	WriteProcessMemory(GetCurrentProcess(), GetProcAddress, backCode, sizeof(backCode) / sizeof(backCode[0]), NULL);
	VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, &dwNewProtect);
}

//.CPP
#include "stdafx.h"
#include "Demo1.h"
#include <iostream>
using namespace std;

int main()
{
	Rookits(::GetModuleHandle(NULL), "User32.dll", "MessageBoxW", MyMessageBoxW);

	Hook_GetProcAddr();
	MessageBoxW(NULL, L"hello world2", L"caption", MB_OK);

	MESSAGEBOXW MBW = (MESSAGEBOXW)GetProcAddress(GetModuleHandleA("User32.dll"), "MessageBoxW");
	if(MBW)
		MBW(NULL, L"hello world3", L"caption", MB_OK);

	MESSAGEBOXA MBA = (MESSAGEBOXA)GetProcAddress(GetModuleHandleA("User32.dll"), "MessageBoxA");
	if (MBA)
		MBA(NULL, "hello world4", "caption", MB_OK);

	UnHook_GetProcAddr();

	//Rookits(::GetModuleHandle(NULL), "KERNEL32.dll", "GetCurrentProcessId", GetCu
	//cout << "current pid :  " << GetCurrentProcessId() << endl;

	system("pause");
    return 0;
}

此例子中:只修改了EXE的IAT和实现了使用GetProcAddresss的方式如何HOOK,栗子中的Hook_GetProcAddr采用的API HOOK

优化点:

1.枚举所有模块实现用IAT HOOK

2.API HOOK LoadLibrary相关API,当新的模块被加载后,为新的模块实现HOOK

检测被IAT HOOK的方法:

通过函数地址是否在某一个范围内(用户区 or 内核区,是否在原模块的地址空间范围内)。

参考如下:

http://bbs.pediy.com/showthread.php?p=1161499

http://bbs.pediy.com/showthread.php?p=1287440

IAT(Import Address Table,导入地址表)Hook 是一种常见的函数拦截技术,主要用于在 Windows 应用程序中拦截对特定 API 函数的调用,它通过修改目标程序的导入地址表,实现函数调用的劫持[^3]。 ### 原理 每个 Windows 应用程序或 DLL 通常会调用其他 DLL(如 kernel32.dll 或 user32.dll)中的 API 函数。为了方便程序使用,这些外部函数的实际地址会被存储在 IAT 中。当程序调用某个外部函数(例如 send 函数)时,实际上会先从 IAT 中获取该函数的地址。IAT Hook 的核心原理就是通过替换 IAT 表中函数的原始地址从而实现 Hook,不过这需要充分理解 PE 文件的结构才能完成[^1][^3]。 ### 实现方法 IAT Hook 的实现过程通常如下: 1. **实验环境搭建**:准备好相应的开发环境和目标程序。 2. **采用创建远程线程的方式注入 DLL**:将编写好的 DLL 注入到目标进程中。 3. **编写 hookiat.dll 实现 IAT Hook**: - **DllMain() 主函数**:作为 DLL 的入口点,进行一些初始化操作。 - **自定义钩取函数**:例如 MyWriteFile(),用于替换原始函数的功能。 - **核心:hook_iat() 函数实现 IAT 修改**: - 寻找 IID 结构体表。 - 寻找 ITD 结构体实现 IAT 表修改。 ### 应用场景 - **安全防护**:可以拦截恶意软件对系统 API 的调用,从而阻止其进行恶意操作。 - **调试和监控**:在调试过程中,可以通过 IAT Hook 来监控程序对某些 API 的调用情况,帮助分析程序的行为。 - **功能扩展**:可以通过替换某些 API 函数的实现,为程序添加额外的功能。 ### 示例代码 以下是一个简单的伪代码示例,展示了 IAT Hook 的基本实现思路: ```python # 伪代码,仅作示意 def hook_iat(target_process, target_api, new_function): # 寻找 IID 结构体表 iid_table = find_iid_table(target_process) # 寻找 ITD 结构体 itd_struct = find_itd_struct(iid_table, target_api) # 修改 IAT 表中的函数地址 itd_struct.address = new_function.address return True ```
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值