导入地址表钩取技术解析

2024-06-05 20:36

本文主要是介绍导入地址表钩取技术解析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前置知识

导入表

在一个可执行文件需要用到其余DLL文件中的函数时,就需要用到导入表,用于记录需要引用的函数。例如我们编写的可执行文件需要用到CreateProcess函数,就需要用到kernel32.dll文件并且将其中的CreateProcess函数的信息导入到我们的可执行文件中,然后再调用。

为了管理这些导入函数,就构建了一个导入表进行统一的管理,简单来说,当我们编写的可执行文件中使用到导入函数就会去导入表中去搜索找到指定的导入函数,获取该导入函数的地址并调用。

image-20240424192254287

因此加载器在调用导入函数之前需要先找到导入表的所在处。在可执行文件映射到内存空间时,都是以Dos Header开始的,在该头部存在elfanew的字段,用于记录PE文件头的偏移,在PE文件头存在可选头的结构体,该结构体中存储数据目录项,其中就包括了导入表。因此在内存中我们需要通过Dos Header -> Nt Header -> Option Header -> Import Table的顺序获取导入表。

image-20240424193422922

这里使用《加密与解密》的图来看一下导入表的结构体,如下图。

image-20240424195938393

可以看到导入表涉及的变量非常多,这里重点关注OriginalFirstThunkFistThunk以及Name

  • Name:指向导入库的名称。
  • OriginalFistThunk:指向输入名称表,里面存储了导入函数的信息。
  • FirstThunk:指向输入地址表,可以看到在初始化的时候OriginalFistThunkFirstThunk指向的是同一块区域,即导入函数的信息。

输入名称表的结构体如下图,这里重点关注OrdinalAddressOfData

  • Ordinal:记录函数的序号,即导入函数以序号存储
  • AdressOfData:以函数命的形式记录导入函数

image-20240424201610702

那么INTIAT的区别在于,加载器会在从导入表中获取了导入函数名称后,会搜索该函数的名称并获取该函数的地址并填入到IAT中,因此在经历了加载器后,IAT中存储了实际地址。如下图。

image-20240424202850423

帮助网安学习,全套资料S信免费领取:
① 网安学习成长路径思维导图
② 60+网安经典常用工具包
③ 100+SRC分析报告
④ 150+网安攻防实战技术电子书
⑤ 最权威CISSP 认证考试指南+题库
⑥ 超1800页CTF实战技巧手册
⑦ 最新网安大厂面试题合集(含答案)
⑧ APP客户端安全检测指南(安卓+IOS)

导入地址表钩取技术

输入地址表钩取技术就是通过修改输入地址表的地址值,因此当调用该导入函数时会跳转到被篡改的地址上。

在钩取之前的状态如下图

image-20240424204535373

在钩取之后的状态如下图

image-20240424204853835

因此总结一下输入地址表钩取技术的流程

  • 确定需要钩取的导入函数
  • 获取输入地址表的地址
  • 在输入地址表中搜索需要钩取的导入函数地址并且将导入函数地址修改为自定义的函数
  • 在处理完之后需要在自定义函数中重新调用被钩取的函数

确定需要钩取的导入函数

首先确定可执行文件中存在什么导入函数,可以发现目标的可执行文件中导入了kernel32.dll的系统库,并且导入的CreateProcessW

image-20240424205932029

那么采用输入地址表钩取方法钩取CreateProcessW函数。

获取导入地址表的地址

根据DOS Header -> Nt Header -> Option Header ->Import Table的顺序进行搜索,即可获取导入地址表的地址。

代码如下

...//获取当前进程的基地址hMod = GetModuleHandle(NULL);pBase = (PBYTE)hMod;//进程的基地址是从DOS头开始的pImageDosHeader = (PIMAGE_DOS_HEADER)hMod;//通过e_lfanew变量获取NT头的偏移,然后加上基地址及NT头的位置pImageNtHeaders = (PIMAGE_NT_HEADERS)(pBase + pImageDosHeader->e_lfanew);//数据目录项下标为1的项是导入表pImageImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)(pImageNtHeaders->OptionalHeader.DataDirectory[1].VirtualAddress + pBase)
...

获取导入函数地址并修改

在获取导入地址表的地址后,首先通过遍历导入表的结构体,提取其中的Name字段,判断是否为我们需要钩取的导入库名。在匹配完成后则选择继续遍历IAT中的函数地址,找到需要钩取的函数地址,找到后则修改为自定义函数的地址。

image-20240424212559365

代码如下

    ...//遍历导入表项for (; pImageImportDescriptor->Name; pImageImportDescriptor++){//获取导入库的名称szLibName = (LPCSTR)(pImageImportDescriptor->Name + pBase);//比较导入库的名称,判断是否为kernel32.dllif (!_stricmp(szLibName, szDllName)){//获取IATPIMAGE_THUNK_DATA pImageThunkData = (PIMAGE_THUNK_DATA)(pImageImportDescriptor->FirstThunk + pBase);//获取导入函数地址for (; pImageThunkData->u1.Function; pImageThunkData++){//判断函数地址是否是需要钩取的函数地址,这里需要注意的是64位与32位地址的区别if (pImageThunkData->u1.Function == (ULONGLONG)pfnOrg){//修改IAT的权限为可写VirtualProtect(&pImageThunkData->u1.Function, 4, PAGE_EXECUTE_READWRITE, &dwOldProtect);//将原始的地址修改为自定义函数地址pImageThunkData->u1.Function = (ULONGLONG)pfnNew;//将权限恢复VirtualProtect(&pImageThunkData->u1.Function, 4, dwOldProtect, &dwOldProtect);return TRUE;}}}...

在自定义函数中重新调用被钩取的函数

这里需要注意的是,我们需要构建一个自定函数,该函数的返回类型与参数需要与钩取的函数一模一样,这样我们就可以获取所有参数的信息,然后篡改后重新传递给原始的导入函数,即可完成钩取。

image-20240424213549505

代码如下,这里篡改原始CreateaProcessW函数的第一个参数,使计算器

...LPCWSTR applicationName = L"C:\\Windows\\System32\\calc.exe";return ((LPFN_CreateProcessW)g_pOrgFunc)(applicationName,lpCommandLine,lpProcessAttributes,lpThreadAttributes,bInheritHandles,dwCreationFlags,lpEnvironment,lpCurrentDirectory,lpStartupInfo,lpProcessInformation);
...

完整代码:https://github.com/h0pe-ay/HookTechnology/blob/main/Hook-IAT/iat.cpp

调试

刚开始使用的是xdbg调试,但是用的不太习惯,后面改用WinDbg还可以源码调试,这里记录一下需要用到的操作与指令。

符号表与源码加载

在设置中可以选择源码默认的目录以及符号表默认的目录,符号文件则是利用Visutal Studio编译生成的pdb文件。

其中srv*c:\Symbols*https://msdl.microsoft.com/download/symbols是下载官方的符号表文件,这里可以选择删掉只调试我们设置的文件。不然每次都需要下载一遍影响时间。

源码文件也可以在侧边栏选择Open source file选项打开。

image-20240425103000930

DLL加载调试

由于钩取时需要先使用DLL注入技术将自定义的DLL文件注入进去,因此想要调试钩取过程则需要在DLL附着的时候打下断点。

利用sxe ld:xxx.dll即可在加载xxx.dll的时候打下断点。

利用sxe ud:xxx.dll即在卸载xxx.dll的时候打下断点。

image-20240425103802758

关闭优化调试

防止自定义函数中的变量被优化导致不方便单步调试,在Visual Studio中可以选择关闭优化进行编译。

image-20240425104053895

这篇关于导入地址表钩取技术解析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1034105

相关文章

使用Java将DOCX文档解析为Markdown文档的代码实现

《使用Java将DOCX文档解析为Markdown文档的代码实现》在现代文档处理中,Markdown(MD)因其简洁的语法和良好的可读性,逐渐成为开发者、技术写作者和内容创作者的首选格式,然而,许多文... 目录引言1. 工具和库介绍2. 安装依赖库3. 使用Apache POI解析DOCX文档4. 将解析

Java字符串处理全解析(String、StringBuilder与StringBuffer)

《Java字符串处理全解析(String、StringBuilder与StringBuffer)》:本文主要介绍Java字符串处理全解析(String、StringBuilder与StringBu... 目录Java字符串处理全解析:String、StringBuilder与StringBuffer一、St

Spring Boot循环依赖原理、解决方案与最佳实践(全解析)

《SpringBoot循环依赖原理、解决方案与最佳实践(全解析)》循环依赖指两个或多个Bean相互直接或间接引用,形成闭环依赖关系,:本文主要介绍SpringBoot循环依赖原理、解决方案与最... 目录一、循环依赖的本质与危害1.1 什么是循环依赖?1.2 核心危害二、Spring的三级缓存机制2.1 三

C#中async await异步关键字用法和异步的底层原理全解析

《C#中asyncawait异步关键字用法和异步的底层原理全解析》:本文主要介绍C#中asyncawait异步关键字用法和异步的底层原理全解析,本文给大家介绍的非常详细,对大家的学习或工作具有一... 目录C#异步编程一、异步编程基础二、异步方法的工作原理三、代码示例四、编译后的底层实现五、总结C#异步编程

Python中使用正则表达式精准匹配IP地址的案例

《Python中使用正则表达式精准匹配IP地址的案例》Python的正则表达式(re模块)是完成这个任务的利器,但你知道怎么写才能准确匹配各种合法的IP地址吗,今天我们就来详细探讨这个问题,感兴趣的朋... 目录为什么需要IP正则表达式?IP地址的基本结构基础正则表达式写法精确匹配0-255的数字验证IP地

MySQL中FIND_IN_SET函数与INSTR函数用法解析

《MySQL中FIND_IN_SET函数与INSTR函数用法解析》:本文主要介绍MySQL中FIND_IN_SET函数与INSTR函数用法解析,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友一... 目录一、功能定义与语法1、FIND_IN_SET函数2、INSTR函数二、本质区别对比三、实际场景案例分

Java图片压缩三种高效压缩方案详细解析

《Java图片压缩三种高效压缩方案详细解析》图片压缩通常涉及减少图片的尺寸缩放、调整图片的质量(针对JPEG、PNG等)、使用特定的算法来减少图片的数据量等,:本文主要介绍Java图片压缩三种高效... 目录一、基于OpenCV的智能尺寸压缩技术亮点:适用场景:二、JPEG质量参数压缩关键技术:压缩效果对比

关于WebSocket协议状态码解析

《关于WebSocket协议状态码解析》:本文主要介绍关于WebSocket协议状态码的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录WebSocket协议状态码解析1. 引言2. WebSocket协议状态码概述3. WebSocket协议状态码详解3

CSS Padding 和 Margin 区别全解析

《CSSPadding和Margin区别全解析》CSS中的padding和margin是两个非常基础且重要的属性,它们用于控制元素周围的空白区域,本文将详细介绍padding和... 目录css Padding 和 Margin 全解析1. Padding: 内边距2. Margin: 外边距3. Padd

Oracle数据库常见字段类型大全以及超详细解析

《Oracle数据库常见字段类型大全以及超详细解析》在Oracle数据库中查询特定表的字段个数通常需要使用SQL语句来完成,:本文主要介绍Oracle数据库常见字段类型大全以及超详细解析,文中通过... 目录前言一、字符类型(Character)1、CHAR:定长字符数据类型2、VARCHAR2:变长字符数