导入地点表钩取技能解析

打印 上一主题 下一主题

主题 876|帖子 876|积分 2628

前置知识

导入表

在一个可实行文件需要用到其余DLL文件中的函数时,就需要用到导入表,用于记载需要引用的函数。例如我们编写的可实行文件需要用到CreateProcess函数,就需要用到kernel32.dll文件并且将其中的CreateProcess函数的信息导入到我们的可实行文件中,然后再调用。
为了管理这些导入函数,就构建了一个导入表举行统一的管理,简单来说,当我们编写的可实行文件中使用到导入函数就会去导入表中去搜索找到指定的导入函数,获取该导入函数的地点并调用。
[img=720,204.57564575645756]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202406051550142.png[/img]

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

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

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

  • Name:指领导入库的名称。
  • OriginalFistThunk:指向输入名称表,里面存储了导入函数的信息。
  • FirstThunk:指向输入地点表,可以看到在初始化的时候OriginalFistThunk与FirstThunk指向的是同一块区域,即导入函数的信息。
输入名称表的结构体如下图,这里重点关注Ordinal与AddressOfData

  • Ordinal:记载函数的序号,即导入函数以序号存储
  • AdressOfData:以函数命的形式记载导入函数
[img=720,215.74007220216606]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202406051550146.png[/img]

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

导入地点表钩取技能

输入地点表钩取技能就是通过修改输入地点表的地点值,因此当调用该导入函数时会跳转到被篡改的地点上。
在钩取之前的状态如下图
[img=720,305.2455089820359]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202406051550148.png[/img]

在钩取之后的状态如下图
[img=720,554.7394540942928]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202406051550149.png[/img]

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

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

首先确定可实行文件中存在什么导入函数,可以发现目的的可实行文件中导入了kernel32.dll的体系库,并且导入的CreateProcessW
[img=720,302.9888475836431]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202406051550150.png[/img]

那么采用输入地点表钩取方法钩取CreateProcessW函数。
【----帮助网安学习,以下全部学习资料免费领!加vx:dctintin,备注 “博客园” 获取!】
 ① 网安学习成长路径思维导图
 ② 60+网安经典常用工具包
 ③ 100+SRC漏洞分析陈诉
 ④ 150+网安攻防实战技能电子书
 ⑤ 最权威CISSP 认证考试指南+题库
 ⑥ 超1800页CTF实战技巧手册
 ⑦ 最新网安大厂口试题合集(含答案)
 ⑧ APP客户端安全检测指南(安卓+IOS)
获取导入地点表的地点

根据DOS Header -> Nt Header -> Option Header ->Import Table的顺序举行搜索,即可获取导入地点表的地点。
代码如下
  1. ...
  2.        //获取当前进程的基地址
  3.    hMod = GetModuleHandle(NULL);
  4.    pBase = (PBYTE)hMod;
  5.     //进程的基地址是从DOS头开始的
  6.    pImageDosHeader = (PIMAGE_DOS_HEADER)hMod;
  7.     //通过e_lfanew变量获取NT头的偏移,然后加上基地址及NT头的位置
  8.    pImageNtHeaders = (PIMAGE_NT_HEADERS)(pBase + pImageDosHeader->e_lfanew);
  9.     //数据目录项下标为1的项是导入表
  10.    pImageImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)(pImageNtHeaders->OptionalHeader.DataDirectory[1].VirtualAddress + pBase)
  11. ...
复制代码
获取导入函数地点并修改

在获取导入地点表的地点后,首先通过遍历导入表的结构体,提取其中的Name字段,判断是否为我们需要钩取的导入库名。在匹配完成后则选择继续遍历IAT中的函数地点,找到需要钩取的函数地点,找到后则修改为自定义函数的地点。
[img=720,582.7476038338658]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202406051550151.png[/img]

代码如下
  1.    ...
  2.    //遍历导入表项
  3.    for (; pImageImportDescriptor->Name; pImageImportDescriptor++)
  4.    {
  5.        //获取导入库的名称
  6.        szLibName = (LPCSTR)(pImageImportDescriptor->Name + pBase);
  7.        //比较导入库的名称,判断是否为kernel32.dll
  8.        if (!_stricmp(szLibName, szDllName))
  9.        {
  10.            //获取IAT
  11.            PIMAGE_THUNK_DATA pImageThunkData = (PIMAGE_THUNK_DATA)(pImageImportDescriptor->FirstThunk + pBase);
  12.            //获取导入函数地址
  13.            for (; pImageThunkData->u1.Function; pImageThunkData++)
  14.            {
  15.                //判断函数地址是否是需要钩取的函数地址,这里需要注意的是64位与32位地址的区别
  16.                if (pImageThunkData->u1.Function == (ULONGLONG)pfnOrg)
  17.                {
  18.                    //修改IAT的权限为可写
  19.                    VirtualProtect(&pImageThunkData->u1.Function, 4, PAGE_EXECUTE_READWRITE, &dwOldProtect);
  20.                    //将原始的地址修改为自定义函数地址
  21.                    pImageThunkData->u1.Function = (ULONGLONG)pfnNew;
  22.                    //将权限恢复
  23.                    VirtualProtect(&pImageThunkData->u1.Function, 4, dwOldProtect, &dwOldProtect);
  24.                    return TRUE;
  25.                }
  26.            }
  27.        }
  28.        ...
复制代码
在自定义函数中重新调用被钩取的函数

这里需要注意的是,我们需要构建一个自定函数,该函数的返回范例与参数需要与钩取的函数一模一样,这样我们就可以获取全部参数的信息,然后篡改后重新传递给原始的导入函数,即可完成钩取。
[img=720,160.5540166204986]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202406051550152.png[/img]

代码如下,这里篡改原始CreateaProcessW函数的第一个参数,使计算器
  1. ...
  2.    LPCWSTR applicationName = L"C:\\Windows\\System32\\calc.exe";
  3.    
  4.    return ((LPFN_CreateProcessW)g_pOrgFunc)(applicationName,
  5.        lpCommandLine,
  6.        lpProcessAttributes,
  7.        lpThreadAttributes,
  8.        bInheritHandles,
  9.        dwCreationFlags,
  10.        lpEnvironment,
  11.        lpCurrentDirectory,
  12.        lpStartupInfo,
  13.        lpProcessInformation);
  14. ...
复制代码
完整代码: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选项打开。
[img=720,400.2021222839818]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202406051550153.png[/img]

DLL加载调试

由于钩取时需要先使用DLL注入技能将自定义的DLL文件注入进去,因此想要调试钩取过程则需要在DLL附着的时候打下断点。
利用sxe ld:xxx.dll即可在加载xxx.dll的时候打下断点。
利用sxe ud:xxx.dll即在卸载xxx.dll的时候打下断点。
[img=720,446.63900414937757]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202406051550154.png[/img]

关闭优化调试

防止自定义函数中的变量被优化导致不方便单步调试,在Visual Studio中可以选择关闭优化举行编译。
[img=720,344.6445497630332]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202406051550155.png[/img]

更多网安技能的在线实操练习,请点击这里>>
  

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

九天猎人

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表