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

news2024/12/23 17:29:13

脱壳修复是指在进行加壳保护后的二进制程序脱壳操作后,由于加壳操作的不同,有些程序的导入表可能会受到影响,导致脱壳后程序无法正常运行。因此,需要进行修复操作,将脱壳前的导入表覆盖到脱壳后的程序中,以使程序恢复正常运行。一般情况下,导入表被分为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.coloradmin.cn/o/987962.html

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

相关文章

速看!外滩大会发布银行数字科技5大趋势

通用人工智能风起云涌&#xff0c;金融行业将如何应对&#xff1f; 9月8日&#xff0c;由中国银行业协会指导&#xff0c;网商银行承办的外滩大会银行业数字化论坛上&#xff0c;IDC中国副总裁兼首席分析师武连峰发布了《银行数字科技五大趋势》&#xff1a;随身银行、AI风控、…

群晖(Synology)NAS 后台安装 Docker 后配置 PostgreSQL

群晖&#xff08;Synology&#xff09;NAS 的后台在新版本对 Docker 不再称为 Docker&#xff0c;现在改称为 Container Manager 了。 单击进入后运行 Container Manager。 PostgreSQL 容器 针对 PostgreSQL 的容器&#xff0c;我们选择容器后&#xff0c;如果你已经安装了 P…

buffer pool原理总结

innodb buffer pool原理总结 文章目录 innodb buffer pool原理总结1. 缓存的重要性2. innodb buffer pool2.1 buffer pool的内部组成2.2 FREE链表2.3 FLUSH链表2.4 LRU链表2.4.1 LRU链表的功能预读 1. 缓存的重要性 我们都知道&#xff0c;对于innodb存储引擎的表来说&#xf…

快手用户活跃度分析(未完成)

目标 为期30天的用户数据&#xff0c;但是不是所有的用户都有30天的信息数据&#xff0c;比如用户A第7天注册的&#xff0c;则其前6天没有数据。 预测未来用户活跃度的可能性。 预测7天后的&#xff0c;基于第7天&#xff0c;预测第14天&#xff0c;基于第8天&#xff0c;预测…

2023数模A题——定日镜场的优化问题

A题——定日镜场的优化问题 思路&#xff1a;该题主要考察的几何知识和天文学知识&#xff0c;需要不同角度下的镜面和遮挡情况。 资料获取 问题1&#xff1a; 若将吸收塔建于该圆形定日镜场中心&#xff0c;定日镜尺寸均为 6 m6 m&#xff0c;安装高度均为 4 m&#xff0c;且…

解密Kubernetes(K8s)集群的创建过程和关键步骤

文章目录 1. 准备环境2. 安装Docker3. 安装Kubernetes在Master节点上执行以下步骤&#xff1a;安装kubeadm、kubelet和kubectl初始化Master节点 在工作节点上执行以下步骤&#xff1a;加入集群 4. 设置Kubeconfig5. 安装网络插件6. 验证集群7. 部署应用程序8. 扩展和管理集群9.…

Weblogic反序列化漏洞

文章目录 1、搭建环境2、漏洞特征3、漏洞利用1)获取用户名密码2)后台上传shell 4、检测工具 1、搭建环境 漏洞环境基于vulhub搭建–进入weak_password的docker环境 sudo docker-compose up -d拉取靶场 2、漏洞特征 404特征Weblogic常用端口&#xff1a;7001 3、漏洞利用…

centos7使用docker-compose一键搭建mysql高可用主从集群

docker部署 环境准备 卸载旧版本 yum remove -y docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-selinux \docker-engine-selinux \docker-engine 安装依赖 yum install -y yum-utils \…

PCB - 封装焊盘阻焊层的检查

文章目录 PCB - 封装焊盘阻焊层的检查概述检查做出的实际PCB正反面厂家提供的生产稿PCB对应的原始gerber文件查封装拿一个插件电阻为例插件封装焊盘的基本数据END PCB - 封装焊盘阻焊层的检查 概述 打样回来, 看到要焊接的几个插件管脚有阻焊, 无法焊接. 这几个封装是直接从第…

腾讯发布超千亿参数规模的混元大模型;深度学习与音乐分析与生成课程介绍

&#x1f989; AI新闻 &#x1f680; 腾讯发布超千亿参数规模的混元大模型 摘要&#xff1a;腾讯在2023腾讯全球数字生态大会上发布混元大模型&#xff0c;该模型拥有超千亿的参数规模和超2万亿 tokens 的预训练语料。混元大模型将支持多轮对话、内容创作、逻辑推理、知识增强…

CleanShot X for mac安装下载,mac系统录屏、截图、标注软件

您是否经常需要截图、录屏或者标注图片&#xff1f;如果是&#xff0c;那么您一定会喜欢CleanShot X for mac&#xff0c;这是一款专为Mac用户设计的强大而简洁的工具。 CleanShot X for mac可以让您轻松地截取任何区域的屏幕&#xff0c;无论是整个屏幕、窗口还是选定的部分。…

Say Goodbye to OOM Crashes

内存管理 --- 在计算机编程中&#xff0c;内存管理是一项关键任务&#xff0c;用于在程序运行时正确分配和释放内存。一个有效的内存管理系统可以帮助程序提高性能&#xff0c;减少内存泄露和访问错误等问题。 内存管理涉及以下几个方面&#xff1a; 1. 内存分配&#xff1a…

MMDetection实验记录踩坑记录

AP值始终为0 在实验MMDetection的DAB-DETR模型进行实验时&#xff0c;AP值始终上不去。 可以看到&#xff0c;在第22个epoch时的AP值仅为0.002 因为在此之前已经运行过YOLOX,Faster-RCNN等模型&#xff0c;所以数据集的设置肯定是没有问题的&#xff0c;而博主也只是修改了DAB…

嵌入式学习笔记(18)代码重定位实战 下篇

adr和ldr伪指令的区别 ldr和adr都是伪指令&#xff0c;区别是ldr是长加载、adr是短加载。 adr指令加载的是运行时地址&#xff1b;ldr指令加载的是链接地址。 &#xff08;通过反汇编文件可以深入分析adr和ldr的区别&#xff09; 重定位&#xff08;代码拷贝&#xff09; …

行业Demo分享|「园区智慧安防可视化系统」实现园区安防全面保障

在当今社会&#xff0c;园区安全问题备受关注。**为了解决园区的安全隐患并提升安全管理水平&#xff0c;园区智慧安防可视化系统应运而生。**这一系统利用先进的技术手段&#xff0c;将智能监控与安全管理完美地融合在一起&#xff0c;并通过可视化的方式&#xff0c;为园区提…

无swing,高级javaSE毕业之贪吃蛇游戏(含模块构建,多线程监听服务)

JavaSE&#xff0c;无框架实现贪吃蛇 文章目录 JavaSE&#xff0c;无框架实现贪吃蛇1.整体思考2.可能的难点思考2.1 如何表示游戏界面2.2 如何渲染游戏界面2.3 如何让游戏动起来2.4 蛇如何移动 3.流程图制作4.模块划分5.模块完善5.0常量优化5.1监听键盘服务i.输入存储ii.键盘监…

win10 sourcetree打开一闪就退出

参考文档: 解决方案参考文档一: Solved: cant install Sourcetree on Windows 10Solved: when I double-click the installation file or run as administrator,I can see a splash screen and disappear in 10 seconds,then I open thehttps://community.atlassian.com/t5…

GoFrame框架

GoFrame框架 GoFrame简介第一个GoFrame应用使用GoFrame实现一个完整的用户案例GoFrame对象管理GoFrame配置管理-配置对象GoFrame配置管理-文件配置GoFrame日志组件GoFrame错误处理GoFrame数据校验GoFrame Web服务简介GoFrame Web服务-路由规则GoFrame Web服务-路由注册GoFrame…

长胜证券:A股三大指数缩量探底券商股逆势拉升

周四&#xff0c;A股全天低开低走&#xff0c;三大指数均跌超1%&#xff0c;创业板指领跌。截至收盘&#xff0c;上证综指报收3122.35点&#xff0c;跌落1.13%&#xff1b;深证成指报收10321.44点&#xff0c;跌落1.84%&#xff1b;创业板指报收2056.98点&#xff0c;跌落2.11%…

索尼 toio ™应用创意开发征文|一个理想的绘画小助手

引言 toio™机器人是索尼推出的一款创意玩具&#xff0c;它的小巧和可编程性使其成为一个理想的绘画助手。通过编程控制机器人的运动和绘画工具&#xff0c;我们可以为小朋友提供一个有趣的绘画体验。 创意描述 我们可以通过JavaScript编程来控制toio™机器人的运动和绘画工具…