Hook技术入门
0x00 前言
要说在Hook技术里面最基础的,那就是IAT Hook,它的原理就是通过修改PE结构中的IAT表,将其替换成我们自己定义的函数,最终实现Hook,所以在进行Hook之前,我们得很清楚的PE结构,接下来我们先讲解一下怎么索引到IAT表。
0x01 PE文件格式解析
随便拖一个程序进入Uedit进行分析
这一部分就是PE文件的DOS头,先看看DOS头的结构体,咱边看结构体边分析:
typedef struct IMAGE_DOS_HEADER { WORD e_magic; WORD e_cblp; WORD e_cp; WORD e_crlc; WORD e_cparhdr; WORD e_minalloc; WORD e_maxalloc; WORD e_ss; WORD e_sp; WORD e_csum; WORD e_ip; WORD e_cs; WORD e_lfarlc; WORD e_ovno; WORD e_res[4]; WORD e_oemid; WORD e_oeminfo; WORD e_res2[10]; DWORD e_lfanew; }IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
在DOS头里面最关键是两个成员就是e_magic,e_lfanew(其他的可以忽略),e_magic就是DOS头的标识,也就是MZ,为什么是MZ呢,其实就是Mark Zbikowski,他是MS-DOS主要开发者之一,为了纪念老人家,e_lfanew就是下一个头的偏移(基于文件基址),可能有同学要有疑问了,为啥现在还要有这个DOS头呢,因为微软为了向下兼容,所以才没有删除DOS头,那么加上偏移我们来看看:
PE头也有它自己的结构体,Signature和e_magic是一个道理,这里的Signature是PE。
typedef struct IMAGE_NT_HEADERS { DWORD Signature; IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER32 OptionalHeader; } IMAGE_NT_HEADERS,*PIMAGE_NT_HEADERS;
选择的区域是上面结构体中的FileHeader
它也有自己的结构体,感兴趣的小伙伴可以自己去查查每个字段的具体含义,这里就不展开了:
typedef struct _IMAGE_FILE_HEADER { WORD Machine; //运行平台 WORD NumberOfSections; //节表数目 DWORD TimeDateStamp; //时间戳 DWORD PointerToSymbolTable; DWORD NumberOfSymbols; //符号数 WORD SizeOfOptionalHeader; //可选部首长度 WORD Characteristics; //文件属性 }
说完FileHeader,重点就来了,那就是OptionalHeade,除去Signature,FileHeader剩下的就是OptionalHeade:
结构体如下,我们关注其最重要的一个成员DataDirectory(数据目录),它包含了我们要替换的导入表。
typedef struct _IMAGE_OPTIONAL_HEADER { WORD Magic; BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode; DWORD SizeOfInitializedData; DWORD SizeOfUninitializedData; DWORD AddressOfEntryPoint; DWORD BaseOfCode; DWORD BaseOfData; DWORD ImageBase; DWORD SectionAlignment; DWORD FileAlignment; WORD MajorOperatingSystemVersion; WORD MinorOperatingSystemVersion; WORD MajorImageVersion; WORD MinorImageVersion; WORD MajorSubsystemVersion; WORD MinorSubsystemVersion; DWORD Win32VersionValue; DWORD SizeOfImage; DWORD SizeOfHeaders; DWORD CheckSum; WORD Subsystem; WORD DllCharacteristics; DWORD SizeOfStackReserve; DWORD SizeOfStackCommit; DWORD SizeOfHeapReserve; DWORD SizeOfHeapCommit; DWORD LoaderFlags; DWORD NumberOfRvaAndSizes; IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; } IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;
它妥妥的有十六张表,我们只关注导入表:
typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; DWORD OriginalFirstThunk; } DUMMYUNIONNAME; DWORD TimeDateStamp; DWORD ForwarderChain; DWORD Name; //指向DLL名字的RVA DWORD FirstThunk; } IMAGE_IMPORT_DESCRIPTOR;
这里面最重要的就是OriginalFirstThunk,Name,FirstThunk
其中OriginalFirstThunk,FirstThunk分别指向INT和IAT表,心心念念的IAT表终于登场了,这两个成员指向的都是同一个表,为什么这样做等下说
当我们的函数加载完成后,我们IAT指向了函数真正的地址
在IAT在加载完真正的函数地址之后,如果没有INT表来标识具体的函数名称的话,操作系统就完全不知道它加载的这个函数名称是啥,只有一个函数的实现偏移和实现,并不知道它是啥,所以才会有INT表的存在。
0x02 IAT Hook 代码编写
到此PE结构也就讲解完了,也就可以开始写IAT Hook
我们的代码应该包含以下功能:
1.定义我们自己的Hook后的函数
int WNAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) { return OldMessageBoxA(hWnd, "Hello NSDA", lpCaption, uType); }
我们自己的MessageBox必须和原来的MessageBox的参数一模一样,这样才能保证在调用自己的函数的时候不会报错
2.接下来就是找到IAT表
HMODULE ImageBase = GetModuleHandle(NULL); //获得模块基地址 PIMAGE_DOS_HEADER pDosHead = (PIMAGE_DOS_HEADER)(DWORD)ImageBase; DWORD dwTemp = (DWORD)pDosHead + (DWORD)pDosHead->e_lfanew; PIMAGE_NT_HEADERS pNtHead = (PIMAGE_NT_HEADERS)dwTemp; PIMAGE_OPTIONAL_HEADER pOpHead = (PIMAGE_OPTIONAL_HEADER)&pNtHead->OptionalHeader; DWORD dwInputTable = pOpHead->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; DWORD dwTemp = (DWORD)GetModuleHandle(NULL) + dwInputTable; PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)dwTemp; PIMAGE_IMPORT_DESCRIPTOR pCurrent = pImport;
通过GetModuleHandle获得模块基地址之后,我们就可以拿到程序的DOS头,PE头,文件头,可选头
3.接下来就是遍历IAT将其替换成我们自己函数的地址
while (*(DWORD*)pFirstThunk != NULL) { if (*(DWORD*)pFirstThunk == (DWORD)OldMessageBoxA) { DWORD oldProtected; VirtualProtect(pFirstThunk, 0x1000, PAGE_EXECUTE_READWRITE, &oldProtected); memcpy(pFirstThunk, (DWORD *)&dwTemp, 4); VirtualProtect(pFirstThunk, 0x1000, oldProtected, &oldProtected); } pFirstThunk++; }
通过遍历IAT表找到MessageBox的地址之后进行替换,有一点需要注意的,需要先修改文件的页属性才能进行替换(在XP系统上不用)
完整代码就不贴了,大伙们可以自己去尝试把完整的代码写出来,下面看看Hook完的效果:
文章作者: 广软NSDA安全团队
