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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值