PE格式:导入表与IAT内存修正

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 关于Dump内存原理,我们可以使用调试API启动调试事件,然后再程序的OEP位置写入CC断点让其暂停在OEP位置,此时程序已经在内存解码,同时也可以获取到程序的OEP位置,转储就是将程序原封不动的读取出来并放入临时空间中,然后对空间中的节表和OEP以及内存对齐进行修正,最后将此文件在内存保存出来即可。

本章教程中,使用的工具是上次制作的PE结构解析器,如果还不会使用请先看前一篇文章中对该工具的介绍,本章节内容主要复习导入表结构的基础知识点,并通过前面编写的一些小案例,实现对内存的转储与导入表的脱壳修复等。

关于Dump内存原理,我们可以使用调试API启动调试事件,然后再程序的OEP位置写入CC断点让其暂停在OEP位置,此时程序已经在内存解码,同时也可以获取到程序的OEP位置,转储就是将程序原封不动的读取出来并放入临时空间中,然后对空间中的节表和OEP以及内存对齐进行修正,最后将此文件在内存保存出来即可。

脱壳修复:输入表一般分为IAT与INT,由于加壳后程序可能会加密或者破坏IAT结构,导致脱壳后IAT不一致了,脱壳修复就是使用未脱壳的源程序的输入表覆盖到新程序中,就这麽简单。

解析 IMAGE_IMPORT_DESCRIPTOR

数据目录表第二个成员指向输入表,该指针在PE开头位置向下偏移80H处,PE开始位置就是B0H , B0H+80H= 130H处。

image.png

此处存放着一个指针,00002040 即输入表在内存中的偏移量为 2040,使用前面制作的工具可以快速定位到此处。

image.png

2040是一个RVA,需要将其转换为磁盘文件FOA偏移才能定位到输入表在文件中的位置,使用工具快速完成计算任务,转换为文件偏移为 00000640

image.png

也可以这样来找到640的位置,首先2040位于rdata,rdata的虚拟偏移是2000h,而实际偏移是600h 使用 2000h - 600h = 1a00h

image.png

将相对偏移地址2040转为文件偏移,使用2040-1a00同样可得出640h 用winhex打开后跳转过去看看。

image.png

下面将重点解析一下这几个结构的含义。

image.png

如上就是导入表中的IID数组,每个IID结构包含一个装入DLL的描述信息,现在有两个DLL,第三个是一个全部填充为0的结构,标志着IID数组的结束。

结构定义如下。

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;            // 0 for terminating null import descriptor
        DWORD   OriginalFirstThunk;         // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
    } DUMMYUNIONNAME;
    DWORD   TimeDateStamp;                  // 0 if not bound,
                                            // -1 if bound, and real date\time stamp
                                            //     in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
                                            // O.W. date/time stamp of DLL bound to (Old BIND)

    DWORD   ForwarderChain;                 // -1 if no forwarders
    DWORD   Name;
    DWORD   FirstThunk;                     // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;

以第一个字段为例:

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

每个IID结构的第四个字段指向的是DLL名称的地址,以第一个为例,其RVA是0000 2174 将其减去1a00得到文件偏移774,跳转过去看看,调用的是USER32.dll库。

image.png

使用工具同样可以快速转换出来。

image.png

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

解析 IMAGE_THUNK_DATA32

如上,我们找到了User32.dll的OrignalFirstThunk,其地址为208C,使用该值减去1A00h 得到 68Ch,在偏移为68Ch处保存的就是一个IMAGE_THUNK_DATA32数组,他存储的内容就是指向 IMAGE_IMPORT_BY_NAME 结构的地址,最后一个元素以一串0000 0000作为结束标志,先来看一下IMAGE_THUNK_DATA32的定义规范。

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

直接使用WinHex定位到68Ch处,此处就是OrignalFirstThunk中保存的INT的内容,如下图中,出去最后一个00000000以外,一共有11个四字节,则说明User32.dll中导入了11个API函数。

image.png

再来看一下FirstThunk也就是IAT中的内容,由于User32的FirstThunk字段默认值是2010h,使用该值减去1a00h即可得到610h,此处就是IAT的内容,定位过去看看,完全一致的。

image.png

我们以第一个导入RVA地址 00002110h,用该值减去 1a00h得到 710h,定位过去正好是 LoadIconA的字符串。
接着来看第二个导入RVA地址 0000211ch,用该值减去 1a00h得到 71c0 定位过去正好是 PostQuitMessage的字符串。

image.png

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

当程序被运行前,它的FirstThunk值与OrignalFirstThunk字段都指向同一片INT中,如下使用上次编写的MyDump工具对其内存进行dump转储,观察内存变化。

image.png

观察发现 OrignalFirstThunk字段并不会发生变化,但是FirstThunk值的指向已经改变,系统在装入内存时会自动将FirstThunk指向的偏移转化为一个个真正的函数地址,并回写到原始空间中,定位到输入表RVA地址处2040h查看。

image.png

可以发现,黄色的INT并没有变化,但是绿色的IAT则相应的发生了变化,以第一个0x766bd680则是载入内存后LoadIconA的内存地址,我们使用X64DBG跟过去看看,没错吧!

image.png

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

有时我们在拖壳时,由于IAT发生了变化,所以程序会无法被正常启动,我们Dump出来的文件可能收入表已经被破坏了,导入表不一致,我们可以使用原始的未脱壳的导入表地址对脱壳后的导入表地址进行覆盖,来修复文件,使用修复工具修复即可。

例如dump前导入表是这样的。

image.png

dump 后变成了这样。

image.png

由于导入表错误导致dump文件无法正常运行,这是需要使用修复工具来对导入表进行修正。

image.png

修正后文件就可以正常被打开了,我们来看一下dump后的文件导入表。

image.png

是不是很清晰了,就是将原来的导入函数的RVA拷贝过来,就这麽简单。


工具学习篇

lyshark.exe 是一个加过UPX壳的程序,现在演示如何流程化脱壳处理。

image.png

先查节表,发现UPX

image.png

定位到数据目录表中第二个字段,也就是输入表的存储位置,直接使用工具计算出foa地址。

image.png

加过壳就是这样 442cc

image.png

将内存文件转储出来,保存到dump.exe

image.png

跳过去看看,空的

image.png

尝试打开文件,出现错误。

image.png

使用buid工具修正即可。脱壳后文件体积变大了

image.png

不过我自己捣鼓的这些脱壳工具只是用来学习的,很多壳还得借助专业的脱壳工具进行修复,我这个修不了。

既然到这份上了,来演示一下专业脱壳,先查壳,upx

image.png

压缩壳,使用ESP定律脱掉。F8一次,ESP右击内存窗口转到

image.png

断点设置硬件访问断点,四字节,选择,让程序跑起。

image.png

然后运行到jmp 即可到达OEP

image.png

获取OEP删除无效函数,直接dump转储文件。

image.png

文件转储打不开

image.png

使用工具修复buitIAT即可。

image.png

脱壳完成,程序可运行起来。

image.png

相关文章
|
8天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
27 4
|
存储 安全 API
2.1 PE结构:文件映射进内存
PE结构是`Windows`系统下最常用的可执行文件格式,理解PE文件格式不仅可以理解操作系统的加载流程,还可以更好的理解操作系统对进程和内存相关的管理知识,在任何一款操作系统中,可执行程序在被装入内存之前都是以文件的形式存放在磁盘中的,在早期DOS操作系统中,是以COM文件的格式存储的,该文件格式限制了只能使用代码段,堆栈寻址也被限制在了64KB的段中,由于PC芯片的快速发展这种文件格式极大的制约了软件的发展。
862 0
【PE准备阶段】将内存中的数据读取到内存,将内存中的数据读取到文件中【滴水逆向39期作业】
【PE准备阶段】将内存中的数据读取到内存,将内存中的数据读取到文件中【滴水逆向39期作业】
|
Java
Java 二维数组格式、二维数组内存图解、二维数组操作
Java 二维数组格式、二维数组内存图解、二维数组操作
151 0
将RGB转换为JPG格式到内存的代码
将RGB转换为JPG格式到内存的代码
159 0
|
存储 IDE Java
【精通内核】计算机程序的本质、内存组成与ELF格式
精通真正的高并发编程,不仅仅是API的使用和原理!计算机最基础的程序是怎么组成的呢?本文深入浅出,讲解程序的本质(编译的过程)、组成(程序所需的内存)与格式(ELF),希望读者可以构建计算机从写代码到编译到执行的链路的底层思维。
|
API Android开发
【Android 内存优化】自定义组件长图组件 ( 获取图像宽高 | 计算解码区域 | 设置图像解码属性 复用 像素格式 | 图像绘制 )
【Android 内存优化】自定义组件长图组件 ( 获取图像宽高 | 计算解码区域 | 设置图像解码属性 复用 像素格式 | 图像绘制 )
171 0
【Android 内存优化】自定义组件长图组件 ( 获取图像宽高 | 计算解码区域 | 设置图像解码属性 复用 像素格式 | 图像绘制 )
|
存储 算法 API
【Android 内存优化】Android 原生 API 图片压缩代码示例 ( PNG 格式压缩 | JPEG 格式压缩 | WEBP 格式压缩 | 动态权限申请 | Android10 存储策略 )
【Android 内存优化】Android 原生 API 图片压缩代码示例 ( PNG 格式压缩 | JPEG 格式压缩 | WEBP 格式压缩 | 动态权限申请 | Android10 存储策略 )
288 0
|
3月前
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
366 0
|
21天前
|
存储 C语言
数据在内存中的存储方式
本文介绍了计算机中整数和浮点数的存储方式,包括整数的原码、反码、补码,以及浮点数的IEEE754标准存储格式。同时,探讨了大小端字节序的概念及其判断方法,通过实例代码展示了这些概念的实际应用。
44 1