2.9 PE结构:重建导入表结构

脱壳修复是指在进行加壳保护后的二进制程序脱壳操作后,由于加壳操作的不同,有些程序的导入表可能会受到影响,导致脱壳后程序无法正常运行。因此,需要进行修复操作,将脱壳前的导入表覆盖到脱壳后的程序中,以使程序恢复正常运行。一般情况下,导入表被分为IAT(Import Address Table,导入地址表)和INT(Import Name Table,导入名称表)两个部分,其中IAT存储着导入函数的地址,而INT存储着导入函数的名称。在脱壳修复中,一般是通过将脱壳前和脱壳后的输入表进行对比,找出IAT和INT表中不一致的地方,然后将脱壳前的输入表覆盖到脱壳后的程序中,以完成修复操作。

数据目录表的第二个成员指向导入表,该指针在PE开头位置向下偏移0x80h处,此处PE开始位置为0xF0h也就是说导入表偏移地址应该在0xf0+0x80h=170h如下图中,导入表相对偏移为0x21d4h

这个地址的读取同样可以使用PeView工具得到,通过输入DataDirectory读者可看到如下图所示的输出信息,其中第二行则是导入表的地址。

这里的0x21d4是一个RVA地址,需要将其转换为磁盘文件FOA偏移才能定位到导入表在文件中的位置,使用RvaToFoa命令可快速完成计算,转换后的文件偏移为0x11d4

此处我们也可以通过使用虚拟偏移地址减去实际偏移地址来得到这个参数,由于0x21d4位于.rdata节,此时的rdata虚拟偏移是0x2000而实际偏移则是0x1000通过使用2000h-1000h=1000h,接着再通过0x21d4h-0x1000h=11D4h同样可以得到相对FOA文件偏移。

我们通过使用WinHex工具跳转到11d4位置处,读者此时能看到如下图所示的地址信息。

如上图就是导入表中的IID数组,每个IID结构包含一个装入DLL的描述信息,现在有三个导入DLL文件,则第四个是一个全部填充为0的结构,标志着IID数组的结束,每一个结构有五个四字节构成,该结构体定义如下所示;

typedef struct _IMAGE_IMPORT_DESCRIPTOR
{union{DWORD   Characteristics;DWORD   OriginalFirstThunk;} DUMMYUNIONNAME;DWORD   TimeDateStamp;DWORD   ForwarderChain;DWORD   Name;DWORD   FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;

我们以第一个调用动态链接库为例,其地址与结构的说明如下所示:

  • 0000 22C0 => OrignalFirstThunk => 指向输入名称表INT的RVA
  • 0000 0000 => TimeDateStamp => 指向一个32位时间戳,默认此处为0
  • 0000 0000 => ForwardChain => 转向API索引,默认为0
  • 0000 244A => Name => 指向DLL名字的指针
  • 0000 209C => FirstThunk => 指向输入地址表IAT的RVA

每个IID结构的第四个字段指向的是DLL名称的地址,以第一个动态链接库为例,其RVA是0000 244A 将其减去1000h得到文件偏移144A,跳转过去看看,调用的是USER32.dll库。

上方提到的两个字段OrignalFirstThunkFirstThunk都可以指向导入结构,在实际装入中,当程序中的OrignalFirstThunk值为0时,则就要看FirstThunk里面的数据,FirstThunk常被叫做IAT它是在程序初始化时被动态填充的,而OrignalFirstThunk常被叫做INT,它是不可改变的,之所以会保留两份是因为,有些时候会存在反查的需求,保留两份是为了更方便的实现。

在上述流程中,我们找到了User32.dllOrignalFirstThunk,其地址为22C0,使用该值减去1000h 得到 12c0h,在偏移为12c0h处保存的就是一个IMAGE_THUNK_DATA32数组,他存储的内容就是指向 IMAGE_IMPORT_BY_NAME 结构的地址,最后一个元素以一串0000 0000作为结束标志,先来看一下IMAGE_THUNK_DATA32的定义规范。

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

直接使用WinHex定位到12c0h地址处,此处就是OrignalFirstThunk中保存的INT的内容,如下图,除去最后一个结束符00000000以外,一共有19个四字节,则说明User32.dll中导入了19API函数。

再来看一下FirstThunk也就是IAT中的内容,由于User32FirstThunk字段默认值是209C,使用该值减去1000h即可得到109ch,此处就是IAT的内容,使用WinHex定位过去,可以发现两者内容时完全一致的。

接着我们以第一个导入RVA地址0000243Eh,用该值减去1000h得到143Eh,定位过去正好是EndDialog的字符串,同样的方式,第二个导入RVA地址0000242ch,用该值减去1000h得到142ch 定位过去正好是PostQuitMessage的字符串,如下图绿色部分所示。

如上图中我们已第二个函数PostQuitMessage为例,前两个字节0271h表示的是Hint值,后面的蓝色部分则是PostQuitMessage字符串,最后的0标志结束标志。

当程序被运行前,它的FirstThunk值与OrignalFirstThunk字段都指向同一片INT中,此处我们使用LyDebugger工具对程序进行内存转存,执行命令LyDebugger DumpMemory --path Win32Project.exe生成dump.exe文件,该文件则是内存中的镜像数据。

当程序运行后,OrignalFirstThunk字段不会发生变化,但是FirstThunk值的指向已经改变,系统在装入内存时会自动将FirstThunk指向的偏移转化为一个个真正的函数地址,并回写到原始空间中,定位到dump.exe文件FirstThunk 输入表RVA地址处209Ch查看,如下图;

接着定位到OrignalFirstThunk处,也就是22c0h,观察可发现,绿色的INT并没有变化,但是黄色的IAT则相应的发生了变化

我们以IAT中第一个0x75f8ab90为例,使用x64dbg跟进一下,则可知是载入内存后EngDialog的内存地址。

当系统装入内存后,其实只会用到IAT中的地址解析,输入表中的INT就已经不需要了,此地址每个系统之间都会不同,该地址是操作系统动态计算后填入的,这也是为什么会存在导入表这个东西的原因,就是为了解决不同系统间的互通问题。

有时我们在脱壳时,由于IAT发生了变化,所以程序会无法被正常启动,我们Dump出来的文件由于使用的是内存地址,导入表不一致所以也就无法正常运行,可以使用原始的未脱壳的导入表地址对脱壳后的文件导入表进行覆盖替换,以此来修复导入表错误。

要实现这段代码,读者可依次读入脱壳前与脱壳后的两个文件,通过循环的方式将脱壳前的导入表地址覆盖到脱壳后的程序中,以此来实现对导入表的修复功能,如下代码BuildIat则是笔者封装首先的一个修复程序,读者可自行体会其中的原理;

#include <stdio.h>
#include <Windows.h>
#include <TlHelp32.h>
#include <ImageHlp.h>
#pragma comment(lib,"Dbghelp")DWORD RvaToFoa(PIMAGE_NT_HEADERS pImgNtHdr, LPVOID lpBase, DWORD dwRva)
{PIMAGE_SECTION_HEADER pImgSecHdr;pImgSecHdr = ImageRvaToSection(pImgNtHdr, lpBase, dwRva);return dwRva - pImgSecHdr->VirtualAddress + pImgSecHdr->PointerToRawData;
}void BuildIat(char *pSrc, char *pDest)
{PIMAGE_DOS_HEADER pSrcImgDosHdr, pDestImgDosHdr;PIMAGE_NT_HEADERS pSrcImgNtHdr, pDestImgNtHdr;PIMAGE_SECTION_HEADER pSrcImgSecHdr, pDestImgSecHdr;PIMAGE_IMPORT_DESCRIPTOR pSrcImpDesc, pDestImpDesc;HANDLE hSrcFile, hDestFile;HANDLE hSrcMap, hDestMap;LPVOID lpSrcBase, lpDestBase;// 打开源文件与目标文件hSrcFile = CreateFile(pSrc, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);if (hSrcFile == INVALID_HANDLE_VALUE)return;hDestFile = CreateFile(pDest, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);if (hDestFile == INVALID_HANDLE_VALUE)return;// 分别创建两份磁盘映射hSrcMap = CreateFileMapping(hSrcFile, NULL, PAGE_READONLY, 0, 0, 0);hDestMap = CreateFileMapping(hDestFile, NULL, PAGE_READWRITE, 0, 0, 0);// MapViewOfFile 设置到指定位置lpSrcBase = MapViewOfFile(hSrcMap, FILE_MAP_READ, 0, 0, 0);lpDestBase = MapViewOfFile(hDestMap, FILE_MAP_WRITE, 0, 0, 0);pSrcImgDosHdr = (PIMAGE_DOS_HEADER)lpSrcBase;pDestImgDosHdr = (PIMAGE_DOS_HEADER)lpDestBase;printf("[+] 原DOS头: 0x%08X --> 目标DOS头: 0x%08X \n", pSrcImgDosHdr, pDestImgDosHdr);pSrcImgNtHdr = (PIMAGE_NT_HEADERS)((DWORD)lpSrcBase + pSrcImgDosHdr->e_lfanew);pDestImgNtHdr = (PIMAGE_NT_HEADERS)((DWORD)lpDestBase + pDestImgDosHdr->e_lfanew);printf("[+] 原NT头: 0x%08X --> 目标NT头: 0x%08X \n", pSrcImgNtHdr, pDestImgNtHdr);pSrcImgSecHdr = (PIMAGE_SECTION_HEADER)((DWORD)&pSrcImgNtHdr->OptionalHeader + pSrcImgNtHdr->FileHeader.SizeOfOptionalHeader);pDestImgSecHdr = (PIMAGE_SECTION_HEADER)((DWORD)&pDestImgNtHdr->OptionalHeader + pDestImgNtHdr->FileHeader.SizeOfOptionalHeader);printf("[+] 原节表头: 0x%08X --> 目标节表头: 0x%08X \n", pSrcImgSecHdr, pDestImgSecHdr);DWORD dwImpSrcAddr, dwImpDestAddr;dwImpSrcAddr = pSrcImgNtHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;dwImpDestAddr = pDestImgNtHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;printf("[-] 原始IAT虚拟地址: 0x%08X --> 目标IAT虚拟地址: 0x%08X \n", dwImpSrcAddr, dwImpDestAddr);dwImpSrcAddr = (DWORD)lpSrcBase + RvaToFoa(pSrcImgNtHdr, lpSrcBase, dwImpSrcAddr);dwImpDestAddr = (DWORD)lpDestBase + RvaToFoa(pDestImgNtHdr, lpDestBase, dwImpDestAddr);printf("[+] 导入表原始偏移: 0x%08X --> 导入表目的偏移: 0x%08X \n", dwImpSrcAddr, dwImpDestAddr);// 定位导入表pSrcImpDesc = (PIMAGE_IMPORT_DESCRIPTOR)dwImpSrcAddr;pDestImpDesc = (PIMAGE_IMPORT_DESCRIPTOR)dwImpDestAddr;printf("[*] 定位原始导入表地址: 0x%08X --> 定位目的导入表地址: 0x%08X \n\n\n", pSrcImpDesc, pDestImpDesc);PIMAGE_THUNK_DATA pSrcImgThkDt, pDestImgThkDt;// 循环遍历导入表,条件是两者都不为空while (pSrcImpDesc->Name && pDestImpDesc->Name){char *pSrcImpName = (char*)((DWORD)lpSrcBase + RvaToFoa(pSrcImgNtHdr, lpSrcBase, pSrcImpDesc->Name));char *pDestImpName = (char*)((DWORD)lpDestBase + RvaToFoa(pDestImgNtHdr, lpDestBase, pDestImpDesc->Name));pSrcImgThkDt = (PIMAGE_THUNK_DATA)((DWORD)lpSrcBase + RvaToFoa(pSrcImgNtHdr, lpSrcBase, pSrcImpDesc->FirstThunk));pDestImgThkDt = (PIMAGE_THUNK_DATA)((DWORD)lpDestBase + RvaToFoa(pDestImgNtHdr, lpDestBase, pDestImpDesc->FirstThunk));printf("\n [*] 链接库: %10s 原始偏移: 0x%08X --> 修正偏移: 0x%08X \n\n", pDestImpName, *pDestImgThkDt, *pSrcImgThkDt);// 开始赋值,将原始的IAT表中索引赋值给目标地址while (*((DWORD *)pSrcImgThkDt) && *((DWORD *)pDestImgThkDt)){DWORD dwIatAddr = *((DWORD *)pSrcImgThkDt);*((DWORD *)pDestImgThkDt) = dwIatAddr;printf("\t --> 源RVA: 0x%08X --> 拷贝地址: 0x%08X --> 修正为: 0x%08X \n", pSrcImgThkDt, pDestImgThkDt, dwIatAddr);pSrcImgThkDt++;pDestImgThkDt++;}pSrcImpDesc++;pDestImpDesc++;}UnmapViewOfFile(lpDestBase); UnmapViewOfFile(lpSrcBase);CloseHandle(hDestMap); CloseHandle(hSrcMap);CloseHandle(hDestFile); CloseHandle(hSrcFile);
}void Banner()
{printf(" ____        _ _     _    ___    _  _____  \n");printf("| __ ) _   _(_) | __| |  |_ _|  / \\|_   _| \n");printf("|  _ \\| | | | | |/ _` |   | |  / _ \\ | |  \n");printf("| |_) | |_| | | | (_| |   | | / ___ \\| |  \n");printf("|____/ \\__,_|_|_|\\__,_|  |___/_/   \\_\\_|   \n");printf("                                           \n");printf("IAT 修正拷贝工具 By: LyShark \n");printf("Usage: BuildIat [脱壳前文件] [脱壳后文件] \n\n\n");
}int main(int argc, char * argv[])
{Banner();if (argc == 3){// 使用原始的IAT表覆盖dump出来的镜像BuildIat(argv[1], argv[2]);}return 0;
}

代码的使用很简单,分别传入脱壳前文件路径,以及脱壳后的路径,则读者可看到如下图所示的输出信息,至此即实现了脱壳修复功能。

本文作者: 王瑞
本文链接: https://www.lyshark.com/post/ff060496.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/73256.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【群智能算法改进】一种改进的鹈鹕优化算法 IPOA算法[2]【Matlab代码#58】

文章目录 【获取资源请见文章第5节&#xff1a;资源获取】1. 原始POA算法2. 改进后的IPOA算法2.1 随机对立学习种群初始化2.2 动态权重系数2.3 透镜成像折射方向学习 3. 部分代码展示4. 仿真结果展示5. 资源获取 【获取资源请见文章第5节&#xff1a;资源获取】 1. 原始POA算法…

【JavaScript】在指定dom元素前面创建标签元素

一、基础操作过程 要在指定的DOM元素前面创建标签元素&#xff0c;有以下步骤&#xff1a; 获取指定的DOM元素&#xff1a;使用document.querySelector()或document.getElementById()等方法来获取指定的DOM元素。 const targetElement document.querySelector(#targetElement…

数字展厅有什么优势,一文了解数字展厅建设方案

引言&#xff1a; 在当今数字化风潮的席卷下&#xff0c;企业们正积极寻求创新的方式来吸引和互动他们的客户。数字展厅作为一种新型的虚拟宣传工具&#xff0c;已经开始引起广泛的关注。 一&#xff0e;什么是数字展厅&#xff1f; 数字展厅是一种基于虚拟现实&#xff08;V…

【前端】在Vue页面中引入其它vue页面 数据传输 相互调用方法等

主页面 home 从页面 headView 需求 在 home.vue 中引用 headView.Vue 方案: home.vue 代码: 只需要在home.vue 想要的地方添加 <headView></headView> <script>//聊天页面 import headView /view/headView.vueexport default {components: {headView},…

淘宝商品销量接口API更新(总销+精准月销API)

不少客户有获取淘宝商品销量的需求&#xff0c;淘宝商品销量接口主要用于以下业务场景。有不齐全的欢迎大家补充。 库存管理&#xff1a;商家可以通过接口获取到实时的销量信息&#xff0c;更好地进行库存管理。供应链计划&#xff1a;商家可以通过接口了解到商品的销售趋势&a…

Unity的UI面板基类

使用这个组件实现淡入淡出 public abstract class BasePanel : MonoBehaviour {//控制面板透明度 用于淡入淡出private CanvasGroup canvasGroup;//淡入淡出速度private float alphaSpeed 10;//隐藏还是显示public bool isShow false;//隐藏完毕后做的事private UnityAction …

企业电子招标采购系统源码Spring Boot + Mybatis + Redis + Layui + 前后端分离 构建企业电子招采平台之立项流程图

功能模块&#xff1a; 待办消息&#xff0c;招标公告&#xff0c;中标公告&#xff0c;信息发布 描述&#xff1a; 全过程数字化采购管理&#xff0c;打造从供应商管理到采购招投标、采购合同、采购执行的全过程数字化管理。通供应商门户具备内外协同的能力&#xff0c;为外部供…

Web安全——穷举爆破下篇(仅供学习)

Web安全 一、常见的端口服务穷举1、hydra 密码穷举工具的使用2、使用 hydra 穷举 ssh 服务3、使用 hydra 穷举 ftp 服务4、使用 hydra 穷举 mysql 服务5、使用 hydra 穷举 smb 服务6、使用 hydra 穷举 http 服务7、使用 hydra 穷举 pop3 服务8、使用 hydra 穷举 rdp 服务9、使用…

适用于基于晶圆键合的3D集成应用的高效单晶圆清洗

引言 不同的微电子工艺需要非常干净的表面以防止颗粒污染。其中&#xff0c;晶圆直接键合对颗粒清洁度的要求非常严格。直接晶圆键合包括通过简单地将两种材料的光滑且干净的表面接触来将两种材料连接在一起&#xff08;图1&#xff09;。在室温和压力下&#xff0c;两种材料表…

OMRON G9SP和NB触摸屏使用232口通讯

G9SP和NB触摸屏使用232口通讯 实验时间&#xff1a;2023/9/7 实验设备&#xff1a;G9SP-N20S、CP1W-CIF01&#xff08;232串口选减板&#xff09;、NB5Q-TW00B、XW2Z-200T&#xff08;串口线&#xff09;&#xff0c;CP1W-20EDT1&#xff0c;D4GS-N4T&#xff08;安全门开关&a…

【数据结构】二叉树的顺序结构实现及时间复杂度计算(二)

目录 一&#xff0c;二叉树的顺序结构实现 1&#xff0c;二叉树的顺序结构 2&#xff0c;堆的概念及结构 3&#xff0c;堆的接口实现 1&#xff0c;堆的创建 2&#xff0c;接口函数 3&#xff0c;初始化 4&#xff0c;销毁 5&#xff0c;是否增容 6&#xff0c;交换数据…

适用于Linux的Windows子系统(PHP搭建lmap、redis、swoole环境)

目录 前言 一、Windows安装Linux子系统 二、Ubuntu搭建PHP开发环境 1.PHP 安装 2.Apache2 安装 3.MySQL安装 4.Redis安装 5.Swoole安装 总结 前言 系列分为三章&#xff08;从安装到项目使用&#xff09;&#xff1a; 一、适用于Linux的Windows子系统&#xff08;系统安装步骤…

springboot整合mybatis实现增删改查(xml)--项目阶段1

目录 一、前言 二、创建项目 创建MySQL数据库和表 创建springboot项目 本文总体代码结构图预览 三、编写代码 &#xff08;一&#xff09;新建实体层属性类 &#xff08;二&#xff09;新建数据层mapper接口 &#xff08;三&#xff09;新建mapper的映射SQL&#xff08…

项目实战:ES的增加数据和查询数据

文章目录 背景在ES中增加数据新建索引删除索引 在ES中查询数据查询数据总数量 项目具体使用&#xff08;实战&#xff09;引入依赖方式一&#xff1a;使用配置类连接对应的es服务器创建配置类编写业务逻辑----根据关键字查询相关的聊天内容在ES中插入数据 总结提升 背景 最近需…

安达发|APS排程系统解决各类制造业难题方案

APS(Advanced Product Scheduling,先进产品计划)软件是一种基于计算机技术的生产计划和调度系统&#xff0c;广泛应用于汽车制造、电子制造、注塑、化工、纺织等行业。本文将详细介绍APS软件在这些行业的应用场景及其优势。 一、汽车制造 1. 零部件生产计划&#xff1a;APS软件…

SVN基本使用笔记——广州云科

简介 SVN是什么? 代码版本管理工具 它能记住你每次的修改 查看所有的修改记录 恢复到任何历史版本 恢复己经删除的文件 SVN跟Git比&#xff0c;有什么优势 使用简单&#xff0c;上手快 目录级权限控制&#xff0c;企业安全必备 子目录Checkout&#xff0c;减少不必要的文件检出…

开机性能-如何抓取开机systrace

一、理论 1.背景 抓取开机 trace 需要使用 userdebug 版本&#xff0c;而我们测试开机性能问题时都要求使用 user 版本&#xff0c;否则会有性能损耗问题。因此想要在抓取开机性能trace 时&#xff0c;需要在 user 版本上打开 atrace 功能之后才能抓取 trace&#xff0c;默认 …

CRM软件系统能否监控手机的使用

CRM可以监控手机吗&#xff1f;答案是不可以。CRM是一款帮助企业优化业务流程&#xff0c;提高销售效率的工具。例如Zoho CRM&#xff0c;最多也就是听一下销售的通话录音&#xff0c;却不可以监控手机&#xff0c;毕竟CRM不是一款监控软件。 CRM的主要作用有以下几点&#xf…

【Linux】工具GCC G++编译器轻度使用(C++)

目录 一、关联知识背景 二、GCC如何的编译过程 【2.1】预处理(进行宏替换) 【2.2】编译(生成汇编) 【2.3】连接(生成可执行文件或库文件) 三、GCC命令的常用选项 四、动静态链接 一、关联知识背景 gcc 与 g 分别是 gnu 的 c & c 编译器 gcc/g 在执行编译工作的时候…

重建与发展:数字资产借贷行业朝着可持续发展迈进!

纵观历史&#xff0c;贷款和货币一样古老&#xff0c;无论哪种形式的货币都需要有其借贷市场。现在&#xff0c;比特币以其分散和透明的性质&#xff0c;在加密领域占据龙头地位。 就像之前的货币一样&#xff0c;比特币要真正蓬勃发展&#xff0c;也需要一个强大的借贷市场。然…