博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Hook API 原理 解析
阅读量:6236 次
发布时间:2019-06-22

本文共 7600 字,大约阅读时间需要 25 分钟。

1 什么是Hook API

简单的说,一个应用程序要调用一个API函数,例如CreateFileW,那么应用程序必须要知道函数的地址,才能调用它,我对Hook API的理解是,把这个函数地址替换为另一个函数MyCreateFileW的地址,那么每当程序调用CreateFileW时,就会调用MyCreateFileW

2 Hook API有什么用

在《Rootkits——Windows内核的安全防护》中,作者笑言,该技术能够实现的能力很大程度上取决于想像力

举一个简单的例子,360有一个产品叫360安全沙箱,在沙箱中打开一个软件,如果在沙箱中使用这个软件新建或者存储了一个文件,那么你会发现系统中的文件并没有改变。如果你仔细查找,会发现在根目录下有一个隐藏的文件夹,里面存储着这个软件新建或者修改过的那个文件。虽然我不知道360沙箱到底怎么实现这一功能,但是它完全可以用Hook API技术来实现。我们只需要Hook掉CreateFileW函数,检查函数的参数,当发现参数表明软件有“写”行为时,就更改文件路径,让它存储到我们指定的位置。

3.Hook API原理解析

1) 目标:替换掉目标程序中CreateFileW函数的地址

2) 过程

a.DLL注入

我们要替换掉目标程序的一个地址,并且要让目标程序执行我们编写的MyCreateFileW函数,由于进程的地址空间是隔离的,所以我们需要将这些代码(包括替换地址,MyCreateFileW函数代码)写在一个DLL文件中,然后运用DLL注入技术(在我们的应用程序中调用SetWindowsHookEx或者CreateRemoteThread都可以实现),将DLL文件注入目标程序,然后执行我们的代码,由于这不是本文的重点,不再详述。

b.替换CreateFileW函数地址

b1. 地址在哪儿

讲这个问题之前,需要介绍一些基础知识,原则是够用就好,不需要的东西不讲。

DLL基础

目标程序要调用API函数,这个函数的代码在Kernel32.dll中,DLL的好处之一就是DLL文件只在内存中存在一次,当有程序需要使用这个DLL文件时,系统只需要将这个DLL文件映射到程序进程地址空间就可以了,所以我们的目标程序要调用CreateFileW,就需要将Kernel32.dll映射到自己的地址空间中去

PE格式基础

在我们启动目标程序时,操作系统负责为目标程序创建虚拟地址空间,并将这个可执行模块(就是目标程序)加载到地址空间中去,接下来,系统会将目标程序所需要的DLL文件映射到地址空间。我们将需要映射到地址空间的目标程序及所需DLL统称为模块(Module)。使用IceSword查看进程,右击选择模块信息,就可以看到一个进程到底加载了哪些模块。下图是记事本程序的模块信息:

b2.查找线索

这里的问题是,操作系统怎么知道这个目标程序需要映射什么DLL文件?这就涉及到PE文件格式。我们平时使用的EXE文件,DLL文件基本都是PE格式的,PE格式的文件存储了与这个文件相关的大量信息,系统可以使用这些信息去加载必要的文件,并运行这个可执行文件。PE格式中就包含了这个可执行文件所需要映射的DLL文件。

下面就讲PE文件,原则还是,只讲与主题相关的~(绿色字段记录了我们层层寻找的线索)

可以通过下载这个文件边看图边看讲解:

看雪论坛技术文档: ( 这里有一个PE文件结构图,对理解PE文件非常有帮助)

这是PE文件整体布局

关于PE文件格式的定义在WinNT.h中,可自行查阅

我们看PE文件头部分的定义:

typedef struct _IMAGE_NT_HEADERS {    DWORD Signature;    IMAGE_FILE_HEADER FileHeader;    IMAGE_OPTIONAL_HEADER32 OptionalHeader;} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
其中PE文件的Signature段被设置为IMAGE_NT_SIGNATURE ,ASCLL码为"PE00",下面是WinNT.h中的定义

#define IMAGE_NT_SIGNATURE                  0x00004550  // PE00
FileHeader我们不关心

下面是IMAGE_OPTIONAL_HEADER32 结构的定义

typedef struct _IMAGE_OPTIONAL_HEADER {    //    // Standard fields.    //    WORD    Magic;    BYTE    MajorLinkerVersion;    BYTE    MinorLinkerVersion;    DWORD   SizeOfCode;    DWORD   SizeOfInitializedData;    DWORD   SizeOfUninitializedData;    DWORD   AddressOfEntryPoint;    DWORD   BaseOfCode;    DWORD   BaseOfData;    //    // NT additional fields.    //    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_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
 
我们关心的是最后一个结构IMAGE_DATA_DIRECTORY 

typedef struct _IMAGE_DATA_DIRECTORY {DWORD VirtualAddress;DWORD Size;} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

DataDirectory是一个此结构体类型的数组,通过这个数组以及索引值我们可以找到大量的表,下面列出这些索引:

// Directory Entries#define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Export Directory#define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Import Directory#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Resource Directory#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // Exception Directory#define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // Security Directory#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Base Relocation Table#define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Debug Directory// IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // (X86 usage)#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 // Architecture Specific Data#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // RVA of GP#define IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS Directory#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 // Load Configuration Directory#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 // Bound Import Directory in headers#define IMAGE_DIRECTORY_ENTRY_IAT 12 // Import Address Table#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 // Delay Load Import Descriptors#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 // COM Runtime descriptor
通过DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT],我们可以找到一个“导入表”,这个“导入表”同样是一个结构体数组
这个结构体为IMAGE_IMPORT_DESCRIPTOR:
typedef struct _IMAGE_IMPORT_DESCRIPTOR {union {DWORD Characteristics; // 0 for terminating null import descriptorDWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)} DUMMYUNIONNAME;DWORD TimeDateStamp; // 0 if not bound,// -1 if bound, and real date\time stamp// in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)// O.W. date/time stamp of DLL bound to (Old BIND)DWORD ForwarderChain; // -1 if no forwardersDWORD Name;DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)} IMAGE_IMPORT_DESCRIPTOR;typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;

这个结构体就是我们这条线索的终点了!
例如IMAGE_IMPORT_DESCRIPTOR类型的数组有N项,那么就说明这个PE文件需要加载的DLL模块有N个,我们可以根据Name成员变量得到需要加载的DLL文件名
我们一开始说了,目标程序调用CreateFileW,CreateFileW在Kernel32.dll中,那么我们可以通过目标程序文件中Name字段找到Kernel32.dll这个名字。那么CreateFileW又在哪里呢?我们发现结构体中还有一个FirstThunk字段,这就是目标程序使用Kernel32.dll中的那一系列函数记录的入口。
通过这个FirstThunk我们又可以找到一个结构体数组,这个结构体名为IMAGE_THUNK_DATA32,其定义为

typedef struct _IMAGE_THUNK_DATA32 {union {DWORD ForwarderString; // PBYTEDWORD Function; // PDWORDDWORD Ordinal;DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME} u1;} IMAGE_THUNK_DATA32;typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;

其中Function记录了Kernel32.dll中CreateFileW函数的地址。我们只需要改写这一字段,就可以Hook API成功了!

我也是初学,了解不多,就不多说了,关于PE格式的参考资料:

看雪论坛安全文库: (在系统篇中详细讲述了PE文件)

看雪论坛技术文档: ( 这里有一个PE文件结构图,对理解PE文件非常有帮助)

《加密与解密》第10章:PE文件格式

《windows环境下32位汇编语言程序设计》第17章:PE文件

3)代码

根据上述过程我们看下面的代码(来源: 

void WINAPI HookOneAPI(LPCSTR pszCalleeModuleName,PROC pfnOriginApiAddress,	PROC pfnDummyFuncAddress,HMODULE hModCallerModule){	ULONG size;	//获取指向PE文件中的Import中IMAGE_DIRECTORY_DESCRIPTOR数组的指针	PIMAGE_IMPORT_DESCRIPTOR pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)		ImageDirectoryEntryToData(hModCallerModule,TRUE,IMAGE_DIRECTORY_ENTRY_IMPORT,&size);	if (pImportDesc == NULL)		return;	//查找记录,看看有没有我们想要的DLL	for (;pImportDesc->Name;pImportDesc++)	{		LPSTR pszDllName = (LPSTR)((PBYTE)hModCallerModule+pImportDesc->Name);		if (lstrcmpiA(pszDllName,pszCalleeModuleName) == 0)			break;	}	if (pImportDesc->Name == NULL)	{		return;	}	//寻找我们想要的函数	PIMAGE_THUNK_DATA pThunk =		(PIMAGE_THUNK_DATA)((PBYTE)hModCallerModule+pImportDesc->FirstThunk);//IAT	for (;pThunk->u1.Function;pThunk++)	{		//ppfn记录了与IAT表项相应的函数的地址		PROC * ppfn= (PROC *)&pThunk->u1.Function;		if (*ppfn == pfnOriginApiAddress)		{			//如果地址相同,也就是找到了我们想要的函数,进行改写,将其指向我们所定义的函数			WriteProcessMemory(GetCurrentProcess(),ppfn,&(pfnDummyFuncAddress),				sizeof(pfnDummyFuncAddress),NULL);			return;		}	}}

这段代码可以这样调用 :

HMODULE hMoudle = GetModuleHandle(glFileOpenShell);HookOneAPI("Kernel32.dll",GetProcAddress(GetModuleHandle(_T("Kernel32.dll")),"CreateFileW"),(PROC)&H_CreateFileW,hMoudle);
其中glFileOpenShell是模块文件的路径,即上面我们用IceSword看到的那些模块的路径。

GetProcAddress函数用于得到Kernel32.dll中CreateFileW的地址,我们需要这个值与前面提到的Function成员变量进行比较,以确定这个Function是不是我们想找的Function。

上面的代码结合之前的讲述应该不难看懂。

需要解释的地方是这两行代码:

LPSTR pszDllName = (LPSTR)((PBYTE)hModCallerModule+pImportDesc->Name);//寻找我们想要的函数PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)((PBYTE)hModCallerModule+pImportDesc->FirstThunk);//IAT
这里得到DLL文件名的时候,以及得到IMAGE_THUNK_DATA地址的时候用了一个加法。为什么要用加法呢?

因为PE文件中Name和FirstThunk记录的都是相对地址,即RVA,这表示一个偏移量,而我们通过GetModuleHandle可以得到模块文件加载到内存后的起始地址,这个起始地址加上偏移量,就可以得到一个值在内存中的地址了。

转载请注明出处:

 

转载于:https://www.cnblogs.com/Iambda/archive/2012/05/23/3933517.html

你可能感兴趣的文章
计算机高手也不能编出俄罗斯方块——计算机达人成长之路(16)
查看>>
error LNK2001: 无法解析的外部符号 __CrtDbgReport
查看>>
【多线程】的简单理解&进程 and【你的电脑是几核的?】
查看>>
# 2017-2018-1 20155224 《信息安全系统设计基础》第七周学习总结
查看>>
scikit-learn预处理实例之一:使用FunctionTransformer选择列
查看>>
【距离GDOI:137天】 扩展KMP...字符串QAQ
查看>>
Oracle 10g 下载地址
查看>>
c# Unity依赖注入WebService
查看>>
邮件客户端导入邮件通讯录地址薄
查看>>
java中异常抛出后代码还会继续执行吗
查看>>
oracle 学习摘记
查看>>
IOS代码布局(一) UIView
查看>>
如何解决开机出现Missing operating system的故障
查看>>
Android AudioPolicyService服务启动过程
查看>>
SVG的a链接
查看>>
MSSQL查找前一天,前一月,前一年的数据,对比当前时间记录查找超过一年,一月,一天的数据...
查看>>
基于三星I9250演示自己弄的Miracast功能-手机对手机
查看>>
【转】MOCK测试
查看>>
pyhon——进程线程、与协程基础概述
查看>>
Centos 7配置LAMP
查看>>