WindowsAPI查缺补漏-PE文件格式解析
WindowsAPI查缺补漏-PE文件格式解析
碎碎念
PE格式为Win32可执行文件采用的文件格式,Win64下称为PE32+。虚拟地址VA表示数据在进程地址空间中内存地址,相对虚拟地址RVA表示数据相对模块基地址偏移,文件偏移地址FOA为文件中数据相对于文件头的偏移。
本节不讲PE文件结构,只讲代码实现。
DOS头
DOS MZ头是个IMAGE_DOS_HEADER结构:
1 | typedef struct _IMAGE_DOS_HEADER { |
除了e_magic和e_lfanew,其他字段全为0也不影响程序运行。DOS Stub也不重要。
PE头
PE头结构:
1 | typedef struct _IMAGE_NT_HEADERS64 { |
其中IMAGE_FILE_HEADER:
1 | typedef struct _IMAGE_FILE_HEADER { |
Machine可以是IMAGE_FILE_MACHINE_I386、IMAGE_FILE_MACHINE_IA64、IMAGE_FILE_MACHINE_AMD64。Characteristics,可以是组合:
枚举值 | 含义 |
---|---|
IMAGE_FILE_RELOCS_STRIPPED | 无重定位信息 |
IMAGE_FILE_EXECUTABLE_IMAGE | 为可执行文件 |
IMAGE_FILE_LINE_NUMS_STRIPPED | 不存在COFF行号 |
IMAGE_FILE_LOCAL_SYMS_STRIPPED | 不存在COFF符号表 |
IMAGE_FILE_LARGE_ADDRESS_AWARE | 该程序可处理大于2GB内存地址 |
IMAGE_FILE_32BIT_MACHINE | 只能在32位平台上运行 |
IMAGE_FILE_DEBUG_STRIPPED | 不含调试信息 |
IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP | 位于可移动媒体上时复制到交换文件并运行 |
IMAGE_FILE_NET_RUN_FROM_SWAP | 位于网络上时复制到交换文件并运行 |
IMAGE_FILE_SYSTEM | 系统文件 |
IMAGE_FILE_DLL | DLL文件 |
IMAGE_FILE_UP_SYSTEM_ONLY | 只能在单处理器计算机上运行 |
扩展PE头结构有:
1 | typedef struct _IMAGE_OPTIONAL_HEADER64 { |
Magic可以是:
枚举值 | 含义 |
---|---|
IMAGE_NT_OPTIONAL_HDR32_MAGIC | 32位可执行映像PE |
IMAGE_NT_OPTIONAL_HDR64_MAGIC | 64位可执行映像PE32+ |
IMAGE_ROM_OPTIONAL_HDR_MAGIC | ROM映像 |
Subsystem字段可以是:
枚举值 | 含义 |
---|---|
IMAGE_SUBSYSTEM_UNKNOWN | 未知 |
IMAGE_SUBSYSTEM_NATIVE | 无需子系统 |
IMAGE_SUBSYSTEM_WINDOWS_GUI | Windows GUI子系统 |
IMAGE_SUBSYSTEM_WINDOWS_CUI | Windows CUI子系统 |
IMAGE_SUBSYSTEM_OS2_CUI | OS/2 CUI子系统 |
IMAGE_SUBSYSTEM_POSIX_CUI | POSIX CUI子系统 |
IMAGE_SUBSYSTEM_WINDOWS_CE_GUI | Windows CE系统 |
IMAGE_SUBSYSTEM_EFI_APPLICATION | 可扩展固件接口EFI应用程序 |
IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER | 具有启动服务的EFI驱动程序 |
IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER | 具有运行时服务的EFI驱动程序 |
IMAGE_SUBSYSTEM_EFI_ROM | EFI ROM映像 |
IMAGE_SUBSYSTEM_XBOX | Xbox系统 |
IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION | 引导程序 |
DllCharacteristics字段实际上针对任何PE文件,不只是DLL,可以是组合:
枚举值 | 含义 |
---|---|
IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA | 可处理64位虚拟地址,即PE32+ |
IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE | 取消ASLR |
IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY | 强制执行代码完整性检查 |
IMAGE_DLLCHARACTERISTICS_NX_COMPAT | 该PE映像与DEP兼容 |
IMAGE_DLLCHARACTERISTICS_NO_ISOLATION | 可识别但不应隔离 |
IMAGE_DLLCHARACTERISTICS_NO_SEH | 不用SEH |
IMAGE_DLLCHARACTERISTICS_NO_BIND | 不要绑定PE映像 |
IMAGE_DLLCHARACTERISTICS_WDM_DRIVER | WDM驱动程序 |
IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE | 可识别WTS |
数据块结构:
1 | typedef struct _IMAGE_DATA_DIRECTORY { |
数据块顺序依次为:
枚举值 | 含义 | 节区名 |
---|---|---|
IMAGE_DIRECTORY_ENTRY_EXPORT | 导出表 | .edata |
IMAGE_DIRECTORY_ENTRY_IMPORT | 导入表 | .idata |
IMAGE_DIRECTORY_ENTRY_RESOURCE | 资源表 | .rsrc |
IMAGE_DIRECTORY_ENTRY_EXCEPTION | 异常表 | .pdata |
IMAGE_DIRECTORY_ENTRY_SECURITY | 属性证书表 | |
IMAGE_DIRECTORY_ENTRY_BASERELOC | 重定位表 | .reloc |
IMAGE_DIRECTORY_ENTRY_DEBUG | 调试信息 | .debug |
IMAGE_DIRECTORY_ENTRY_ARCHITECTURE | 平台相关数据 | |
IMAGE_DIRECTORY_ENTRY_GLOBALPTR | 指向全局指针寄存器值 | |
IMAGE_DIRECTORY_ENTRY_TLS | TLS | .tls |
IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG | 加载配置信息表 | |
IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT | 绑定导入表 | |
IMAGE_DIRECTORY_ENTRY_IAT | 导入函数地址表IAT | |
IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT | 延迟加载导入表 | |
IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR | CLR运行时头部数据 | .cormeta |
节表
在IMAGE_NT_HEADERS后紧接着就是IMAGE_SECTION_HEADER用于描述节区信息:
1 | typedef struct _IMAGE_SECTION_HEADER { |
Characteristics常用组合有:
枚举值 | 含义 |
---|---|
IMAGE_SCN_CNT_CODE | 包含可执行代码 |
IMAGE_SCN_CNT_INITIALIZED_DATA | 包含已初始化数据 |
IMAGE_SCN_CNT_UNINITIALIZED_DATA | 包含未初始化数据 |
IMAGE_SCN_GPREL | 包含通过全局指针引用的数据 |
IMAGE_SCN_LNK_NRELOC_OVFL | 包含扩展重定位 |
IMAGE_SCN_MEM_DISCARDABLE | 可根据需要丢弃 |
IMAGE_SCN_MEM_NOT_CACHED | 无法缓存 |
IMAGE_SCN_MEM_NOT_PAGED | 无法分页 |
IMAGE_SCN_MEM_SHARED | 可在内存中共享 |
IMAGE_SCN_MEM_EXECUTE | 包含可执行属性 |
IMAGE_SCN_MEM_READ | 包含可读属性 |
IMAGE_SCN_MEM_WRITE | 包含可写属性 |
小结1
RVA转FOA:
1 |
|
导入表
导入表是一个导入表描述符结构IID数组,即IMAGE_IMPORT_DESCRIPTOR结构,最后以一个内容全为0的该结构结束。
1 | typedef struct _IMAGE_IMPORT_DESCRIPTOR { |
其中IMAGE_THUNK_DATA64定义如下,这结构就是个ULONGLONG,但不同时刻有不同含义。当ULONGLONG第一位为1时,该值低位为Oridianl,用IMAGE_ORDINAL_FLAG64测试。当第一位为0时,该值为AddressOfData,以函数名字符串形式导入。
1 | typedef struct _IMAGE_THUNK_DATA64 { |
其中IMAGE_IMPORT_BY_NAME:
1 | typedef struct _IMAGE_IMPORT_BY_NAME { |
上述OrigninalFirstThunk与FirstThunk内容完全一样,称为双桥结构。但PE文件被映射到内存中后,2桥FirstThunk数组其中元素由记录导入函数名变为函数入口地址。OriginalFirstThunk用于通过函数地址反查导入函数名,有些编译器不用而全填0,称为单桥结构。
FirstThunk指向导入函数内存地址,所有DLL导入函数内存地址数组顺序排列在一起,形成导入函数地址表IAT。OriginalFirstThunk最终指向函数名称形成一个导入函数名称数组,所有DLL导入函数名称数组顺序排列在一起,形成导入函数名称表INT。
导出表
导出表起始位置只有一个导出表目录IMAGE_EXPORT_DIRECOTRY结构。
1 | typedef struct _IMAGE_EXPORT_DIRECTORY { |
AddressOfFunctions指向全部导出函数入口地址的DWORD数组,每个DWORD表示一个导出函数的RVA内存地址,数组元素个数等于NumberOfFunctions,这些函数既可通过函数名导出,也可通过函数序数导出。AddressOfNames指向函数名称地址DWORD数组,数组中每个DWORD表示一个函数名称RVA,数组元素个数等于NumberOfNames。AddressOfNameOrdinals指向一个WORD数组,每个WORD表示导出函数地址表EAT索引,与AddressOfNames指向的ENT表一一对应。
通过函数名地址表ENT的索引找到函数序数表对应WORD值,该WORD值即为导出函数地址表EAT的索引。
重定位表
重定位算法为:操作数绝对地址+(模块实际载入地址-模块建议装载地址)。一个32位内存地址需要4字节,每个直接记录太占空间了。绝对地址相邻重定位项高位地址相同,当以4KB页为单位在页面中寻址时,只需12位内粗泥底质。
重定位表是一个重定位块结构IMAGE_BASE_RELOCATION数组:
1 | typedef struct _IMAGE_BASE_RELOCATION { |
该结构后紧跟一个WORD数组表示每个重定位项,WORD的高4位表示重定位项类型,低12位为相对页起始地址的相对地址。即操作数绝对地址的RVA等于VirtualAddress+WORD低12位。最后以一个全0的该结构为结束。WORD高4位类型可以是:
枚举值 | 含义 |
---|---|
IMAGE_REL_BASED_ABSOLUTE | 对齐用,有时用 |
IMAGE_REL_BASED_HIGH | 操作数绝对地址高16位需被修正,x86常用 |
IMAGE_REL_BASED_LOW | 操作数绝对地址低16位需被修正 |
IMAGE_REL_BASED_HIGHLOW | 操作数绝对地址32位都要被修正 |
IMAGE_REL_BASED_HIGHADJ | 当前项为高16位,下一个重定位项位低16位,即该重定向要占俩重定向位 |
IMAGE_REL_BASED_MACHINE_SPECIFIC_5 | MIPS平台跳转指令用 |
IMAGE_REL_BASED_MACHINE_SPECIFIC_9 | MIPS16平台跳转指令用 |
IMAGE_REL_BASED_DIR64 | 64位程序的64位操作操作数绝对地址,x64常用 |
PEInfo
1 |
|
模拟PE加载器
1 |
|
线程局部存储表
线程局部存储表是个TLS目录IMAGE_TLS_DIRECTORY64结构,TLS模板时存放所有TLS变量初始化值得数据块。
1 | typedef struct _IMAGE_TLS_DIRECTORY64 { |
AddressOfCallBacks最后以NULL指针结尾。TLS回调函数定义格式如下:
1 | VOID NTAPI TlsCallback( |
例如添加TLS回调函数:
1 |
|
其中节区名叫“.CRT$XLB”,CRT表示使用C运行时机制,X为随机标识,L表示TLS Callback Section,B可以是除A和Z外任意一个字母。当编译为Release时,需要项目属性->C/C++->优化->全程序优化设为否,否则TLS回调函数不会被调用。
加载配置信息表
加载配置信息表是一个加载配置目录IMAGE_LOAD_CONFIG_DIRECTORY64。该表用作异常处理,存放了SEH各种异常句柄。程序发生异常时,系统根据异常类别进行分发处理,并根据句柄实施程序流程转向。
1 | typedef struct _IMAGE_LOAD_CONFIG_DIRECTORY64 { |
资源表
资源组织方式类似文件系统的目录组织方式。共有3层目录结构,每层下有若干个目录,每个目录下有若干个目录入口,每个入口结构指向下一层的目录。第1层下只有一个目录,该目录按照资源类型划分,如光标、图标和菜单等入口结构。第2层按照资源ID划分,每个目录下有多个该资源类型的不同ID的资源。第3层按照代码页划分,如简体中文、繁体中文和英语等,该层目录下的个目录入口指向相对应资源数据入口。各资源数据入口指向资源数据。
每个资源目录为IMAGE_RESOURCE_DIRECTORY结构,每个目录入口为IMAGE_RESOURCE_DIRECTORY_ENTRY结构,每个资源数据入口为IMAGE_RESOURCE_DATA_ENTRY结构。
资源目录IMAGE_RESOURCE_DIRECTORY结构如下。用于第1层时,标准资源类型被解释为255一下的一个ID数字,自定义资源结构可以是个字符串或255~65535中ID数字,即NumberOfNamedEntries+NumberOfIdEntries为DirectoryEntries数组元素个数。用于第2层时同理,资源ID可以是一个字符串或1~65525之间的ID数字。用于第3层时,每种标准语言都有预定义ID,如简体中文为0x0804、英语为0x0409等。
对于OffsetToData字段,当在第1或2层时,该字段最高位为1,剩余低位值为一个指向资源目录表的相对资源表的偏移量;当在第3层时,该字段最高位为0,指向资源数据入口结构的相对资源表的偏移量。
1 | typedef struct _IMAGE_RESOURCE_DIRECTORY { |
资源目录入口IMAGE_RESOURCE_DIRECTORY_ENTRY结构如下。用于第1层时,若为标准资源类型,Name最高位为0,剩余低位表示255以下ID数字;若为自定义资源类型,Name最高位为1,剩余低位为指向相对资源表的IMAGE_RESOURCE_DIR_STRING_U结构偏移量,也可以为255~65535中一个ID数字。用于第二层时,Name表示资源ID,或指向相对资源表的IMAGE_RESOURCE_DIRECTORY_ENTRY结构偏移量。用于第3层时表示代码页ID。
1 | typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY { |
当为自定义资源类型时,NameString表示资源类型Unicode字符串:
1 | typedef struct _IMAGE_RESOURCE_DIR_STRING_U { |
资源数据入口IMAGE_RESOURCE_DATA_ENTRY结构如下。
1 | typedef struct _IMAGE_RESOURCE_DATA_ENTRY { |
预定义资源类型有:
枚举值 | 含义 |
---|---|
RT_CURSOR | 光标 |
RT_BITMAP | 位图 |
RT_ICON | 图标 |
RT_MENU | 菜单 |
RT_DIALOG | 对话框 |
RT_STRING | 字符串表 |
RT_FONTDIR | 字体目录 |
RT_FONT | 字体 |
RT_ACCELERATOR | 加速键 |
RT_RCDATA | 应用程序定义的资源 |
RT_MESSAGETABLE | 消息表 |
RT_GROUP_CURSOR | 光标组 |
RT_GROUP_ICON | 图标组 |
RT_VERSION | 程序版本 |
RT_DLGINCLUDE | 提供符号名称的头文件 |
RT_PLUGPLAY | 即插即用资源 |
RT_VXD | VXD |
RT_ANICURSOR | 动态光标 |
RT_ANIICON | 动态图标 |
RT_HTML | HTML |
RT_MANIFEST | 清单文件 |
常见语言代码页ID有:
ID | 含义 |
---|---|
0x0000 | 中性语言 |
0x0400 | 程序默认语言 |
0x0404 | 中文 中国台湾 |
0x0804 | 中文 中国 |
0x0C04 | 中文 中国香港 |
0x1004 | 中文 新加坡 |
0x0409 | 英语 美国 |
0x0809 | 英语 英国 |
0x0411 | 日语 |
0x412 | 韩语 |
0x0412 | 俄语 |
获取方式:
1 |
|
延迟加载导入表
延迟加载指的是通过隐式链接的DLL。可执行模块开始运行时并不加载延时加载的DLL,也不会检查该DLL是否存在,只有当代码中调用延迟加载DLL中函数时,系统才实际载入该DLL。设置延迟加载DLL后,编译器在PE文件中创建一个延迟加载导入表,记录可执行模块要导入的DLL以及相关函数信息。
延迟加载导入表是一个延迟加载描述IMAGE_DELAYLOAD_DESCRIPTOR结构数组,结构个数取决要延迟加载的DLL数,每个结构对应一个DLL,以全0该结构结束。
1 | typedef struct _IMAGE_DELAYLOAD_DESCRIPTOR { |
每个IMAGE_THUNK_DATA表示一个导入函数信息,最后以一个全0该结构结束。
1 | typedef struct _IMAGE_THUNK_DATA64 { |
校验和与CRC
校验和
计算校验和的过程为:将IMAGE_NT_HEADERS.OptionalHeader.CheckSum字段清零,以WORD为单位对数据块进行带进位的累加,大于WORD部分自动溢出,将累加和与文件长度相加得PE文件校验和。ImageHlp.dll用于操作PE文件,
MapFileAndCheckSum
计算指定文件校验和:
1 | DWORD MapFileAndCheckSum( |
例如:
1 |
|
CheckSumMappedFile
计算指定PE文件校验和:
1 | PIMAGE_NT_HEADERS CheckSumMappedFile( |
调用者可自行通过返回的指针修改IMAGE_NT_HEADERS.OptionalHeader.CheckSum。
循环冗余校验CRC
常用版本有CRC-8、CRC-12、CRC-16、CRC-CCITT、CRC-32、CRC-32C等。WinRAR、NERO、ARJ、LHA等压缩文件采用CRC-32,磁盘驱动器读写采用CRC-16,通用图像存储格式如GIF、TIFF等也采用CRC。这里实现一个文件或一段数据的CRC-32,可以实现一个自定义函数,也可用NtDll.dll中RtlComputeCrc32
。
1 |
|
x64下书写汇编
x64不支持内联汇编,但intrin.h中提供了一系列intrinsic函数,这里就介绍俩。
__cpuid/__cpuidex
获取CPU信息,如CPU星号和家族等;获取CPU支持的功能,如是否支持MMX、SSE、FPU指令等。
1 | VOID __cpuuid( |
该指令原用法:
1 | mov eax, 功能号 |
返回后通过eax、ebx、ecx和ebx返回所需信息,这里分别放入cpuInfo数组。获取基本信息时,功能号最高位为0;获取扩展信息时,最高位为1。
法一
不让内联汇编就外联,添加Test.asm汇编源文件。该文件右键打开属性,配置自定义生成工具,命令行选项写以下,表示用ml64.exe编译.asm为.obj到生成配置目录下,用/Fo指定输出.obj文件名,用/c仅编译不自动链接。生成配置目录即“\项目名\x64\Release”等。
1 | ml64 /Fo $(IntDir)%(fileName).obj /c %(fileName).asm |
输出选项写以下,表示告诉链接器到哪找.obj文件以完成链接。
1 | $(IntDir)%(fileName).obj |
这里汇编文件为:
1 | .code |
源代码这样写:
1 |
|
法二
写成ShellCode。
1 |
|
Detour
Detour库用于拦截API函数,原理就是将目标函数前几条指令替换为无条件跳转到用户提供的自定义函数的jmp指令。自定义函数完成拦截处理后,恢复执行目标函数被覆盖的前几条指令,并继续执行目标函数后续部分。目标函数执行完后自定义函数返回。
自定义函数在内部实现层次上分为两个函数。执行拦截处理的为Detour绕行函数,其函数参数、函数调用约定和返回值类型必须与目标函数完全一致。Trampoline跳板函数执行目标函数被覆盖的前几条指令并跳转到目标函数后续部分的功能。Trampoline在Detour函数中被调用。
Detour库除了可以对API函数拦截,还可修改可执行文件导入表、项可执行文件添加数据、将DLL加载到目标进程等。该库托管在Github上,自己下载是源码,用x64 Native Tools Command Prompt for VS 2022命令行在Makefile所在目录下用命令:
1 | nmake |
运行后目录下生成include、lib.X64、bin.X64目录。
DLL注入
DetourIsHelperProcess
检查被注入DLL所属当前线程是辅助进程还是目标进程:
1 | BOOL DetourIsHelperProcess(VOID); |
是辅助进程返回TRUE,是目标进程返回FALSE。
DetourRestoreAfterWith
恢复目标进程导入表:
1 | BOOL DetourRestoreAfterWith(VOID); |
DetourTransactionBegin
开启事务:
1 | BOOL DetourTransactionBegin(VOID); |
DetourUpdateThread
指定更新线程:
1 | LONG DetourAttach( |
DetourDetach
执行Unhook处理:
1 | LONG DetourDetach( |
DetourTransactionCommit
提交事务:
1 | LONG DetourTransactionCommit(VOID); |
例子
可执行模块用DetourCreateProcessWithDllEx
或DetourCreateProcessWithDlls
将被注入DLL加载到目标进程中。内部实现方法是修改目标进程导入表,将被注入DLL对应导入表描述符结构放在导入表最前部,即可在目标进程气都后及执行代码前加载被注入DLL,在被注入DLL中用DetourAttach
执行Hook处理。
Detour支持x86与x64之间跨架构创建目标进程,但此时DetourCreateProcessWithDllEx
或DetourCreateProcessWithDlls
必须创建临时辅助进程,方法是将被注入DLL加载到rundll32.exe进程并用DetourFinishHelperProcess
创建一个辅助进程,辅助进程加载被注入DLL副本,保证使用正确的架构代码来修改目标进程IAT。此时要注意:被注入DLL必须导出DetourFinishHelperProcess
且导出函数序数为1,否则目标进程无法启动;被注入DLL应在其DllMain
中用DetourIsHelperProcess
检查其所属当前进程是辅助进程还是目标进程,辅助进程则不应Hook。
执行DetourAttack
后DetourExtTextOutW中OriginalExtTextOutW被改为指向Trampoline函数。
本节中,事务是一系列原子性、独占性的操作。操作前要开启事务,最后提交事务,提交后所做操作才会生效。用DetourAttach
执行Hook和用DetourDetach
执行Unhook前要先用DetourTransactionBegin
开启事务,之后用DetourTransactionCommit
提交事务,有时用DetourUpdateThread
指定受事务影响的需更新的线程。
1 |
|
将DLL加载到目标进程
DetourCreateProcessWithDllEx
创建一个新进程并将指定DLL加载到该进程中:
1 | BOOL DetourCreateProcessWithDllEx( |
使用标准CreateProcessW
时,pfnCreateProcessW可设为NULL。该函数在内部用CreateProcessW
,且dwCreationFlags设为CREATE_SUSPENDED以挂起模式创建目标进程。该函数修改目标进程导入表,将指定DLL对应导入表描述符结构放在导入表最前部,然后恢复目标进程执行,系统先加载指定DLL。
DetourCreateProcessWithDlls
创建一个新线程并将指定的若干个DLL加载到该线程中:
1 | BOOL DetourCreateProcessWithDlls( |
描述同DetourCreateProcessWithDllEx
。
DetourAttachEx
同DetourAttach
,还可获取Detour函数、Trampoline函数和目标函数地址:
1 | LONG DetourAttachEx( |
DetourFindFunction
从指定模块中查找指定函数地址:
1 | PVOID DetourFindFunction( |
DetourCodeFromPointer
获取指定函数的代码实现地址:
1 | PVOID DetourCodeFromPointer( |
与DetourFindFunction
不同的是,可能指定的函数只有个jmp,DetourCodeFromPointer
会继续跟进并找到代码实现地址。如:
1 | LPVOID lpGetCurrentProcess = NULL, lpRealGetCurrentProcess = NULL, lpGlobals = NULL; |
DetourEnumerateModules
枚举进程中模块:
1 | HMODULE DetourEnumerateModules( |
第一次hModuelLast为NULL,函数返回下一个模块句柄,以返回的模块句柄循环调用该函数,直到返回NULL。
DetourGetEntryPoint
获取模块入口点:
1 | PVOID DetourGetEntryPoint( |
hModule为NULL则返回调用进程可执行模块入口点。
DetourEnumerateExports
枚举模块导出函数:
1 | BOOL DetourEnumerateExports( |
每当枚举到一个导出函数,都调用一次回调函数,格式为:
1 | BOOL CALLBACK ExportFunc( |
需要继续枚举时,回调函数返回TRUE,终止则返回FALSE。
DetourEnumerateImports
枚举模块导入表:
1 | BOOL DetourEnumerateImports( |
还有个DetourEnumerateImportsEx
自己去学。
例子
为了保证DLL中导出函数DetourFinishHelperProcess
的导出函数为1,应添加模块定义文件xxx.def:
1 | EXPORTS |
然后分别编译x86和x64两个版本为xxx32.dll和xxx64.dll。DetourCreateProcessWithDllEx
和DetourCreateProcessWithDlls
发现选错架构的DLL时,会自动改为正确的DLL,但不建议依赖该纠错特性。
注入器:
1 |
|
编辑可执行文件
DetourBinaryOpen
将可执行文件内容读入内存:
1 | PDETOUR_BINARY DetourBinaryOpen( |
DetourBinarySetPayload
项Detour二进制文件对象中添加数据:
1 | PVOID DetourBinarySetPayload( |
DetourBinaryFindPayload
从Detour二进制文件对象中查找指定GUID的数据:
1 | PVOID DetourBinaryFindPayload( |
DetourBinaryDeletePayload/DetourBinaryPurgePayloads
从Detour二进制文件对象中删除指定/所有数据:
1 | BOOL DetourBinaryDeletePayload( |
DetourBinaryEditImports
修改Detour二进制文件对象导入表:
1 | BOOL DetourBinaryEditImports( |
DetourBinaryResetImports
重置Detour二进制文件对象导入表:
1 | BOOL DetourBinaryResetImports( |
DetourBinaryWrite
将更新写入文件:
1 | BOOL DetourBinaryWrite( |
DetourBinaryClose
关闭 打开的Detour二进制文件对象:
1 | BOOL DetourBinaryClose( |
IAT Hook
实现方式为修改API对应IAT项内存地址为自定义函数内存地址。为了栈平衡,自定义函数的参数、调用约定和返回值类型等必须与目标函数一致。
除了修改进程所有模块IAT中目标函数对应IAT项,为了能够在加载模块时加载其他依赖模块,还需Hook掉LoadLibrary*
和GetProcAddress
等函数。
ImageDirectoryEntryToDataEx
从普通PE文件或PE内存映像中查找指定数据目录内存地址:
1 | PVOID IMAGEAPI ImageDirectoryEntryToDataEx( |
nDirectoryEntry如IMAGE_DIRECTORY_ENTRY_EXPORT为导出表等。
例子
1 |
|
对于延迟加载DLL的情况,录入延迟加载GetMd5.dll中的GetMd5函数,则需要:
1 |
|
此外因延迟加载使用GetProcAddress
获取函数地址,所以只Hook掉该函数也可以:
1 |
|