WindowsPE文件格式入门03.节表
### dump我们点击运行程序进程加载时时,是把文件里面的数据映射进内存,这样进程里面的内存就拿到了各种各样的代码,数据等资源,但是如果我们反着来,就可以从进程的内存里把 exe 文件提出来,这个过程叫做dump过程
dump过程在对抗里面经常用到
---
第一代的壳功能比较简单,当时的加壳软件在运行时一定会在内存中还原代码。OS可以根据PE信息,将文件的数据映射到内存中,那么可以进行反操作,将解密后的内存中的代码保存到文件中,这就称为内存dump。
内存dump可以跳过壳的各种复杂算法,直面原始代码。内存dump可以称为万能的脱壳方式。
!(./mdimg/pe/1636184371874-3d37f392-0ad8-49bf-98e6-63176ccf29fd.jpeg)
- **映射:按照节表的内容,将PE文件按照节表信息和节数据依次映射进入内存。**
- **DUMP:将内存中的数据按节表数据将目标数据拷贝出来成为一个PE.exe文件**
**dump的先决条件****:在OEP处dump,此时全局变量里的值未被初始化,不会存在全局变量的访问异常。**
#### 手动Dump
**描述Dump的原理,内存中需要dump的数据,PE头,节数据。**
1. **拷贝PE文件头。**
2. 1. **根据PE模块确定模块基址,在内存中找到文件头的位置,根据文件头大小将头数据dump到新文件中 。**
3. **dump节数据。**
4. 1. **获取节的RVA(相对虚拟地址),计算出VA(内存绝对地址),根据文件大小和内存大小字段dump到新文件中。**
5. **依次拷贝完所有节,保存关闭。**
6. 1. **文件大小 = 0,则不需要dump,表示该节中都是未初始化的数据**
2. **文件大小 > 内存大小,则dump文件大小的数据**
3. **文件大小< 内存大小,同样dump文件大小的数据,表示其他数据是未初始化的数据**
##### 演示
可以把整个内存数据拷贝出来放到文件,但是这样数据偏移就会有问题,需要自己去修节表,还可以按照节表结构去拷贝出来,下面就是演示按节表结构拷贝
1. 首先用winhex打开需要dump的进程
2. 把PE头拷贝出来
3. 按照节表结构解析数据,拷贝出来
```
从内存S3处开始,拷贝S2大小的数据,到文件S1处
typedef struct _IMAGE_SECTION_HEADER {...
DWORD VirtualAddress; // S3:内存地址:基于模块基址,与SectionAlignment对齐(0x1000)
DWORD SizeOfRawData; // S2:文件大小,与FileAlignment对齐(0x200)
DWORD PointerToRawData; // S1:文件偏移,与FileAlignment对齐(0x200)
...
}
```
!(./mdimg/pe/1655515347951-99ba9b75-e374-405a-9317-d4b3e1515c60.png)
!(./mdimg/pe/1655513742263-a6ed2545-5882-4eca-8734-ab49e8d4874d.png)
!(./mdimg/pe/1655514009653-5bff81eb-9272-4220-a7c5-86ed448b81f8.png)
拷贝节数据
!(./mdimg/pe/1655514213615-6e17eb5d-ab9f-4682-aec1-1449da290541.png)
!(./mdimg/pe/1655514286494-f76076a3-96f5-437e-9aae-7eb067d6de37.png)
!(./mdimg/pe/1655514476161-7c315e0b-d73d-4fe6-9ba6-71ff2d7ceb58.png)
!(./mdimg/pe/1655514628951-57370083-ddfe-4f6d-a5e7-f29dcac07646.png)
!(./mdimg/pe/1655514756360-26a93dfc-f42b-4779-862d-69848571fc24.png)
!(./mdimg/pe/1655514842263-24606151-ef97-4f59-a3f7-e65874b8918b.png)
!(./mdimg/pe/1655514975596-b3579234-3cdc-4eb7-a4ad-3a15a42310ed.png)
!(./mdimg/pe/1655515038204-10ddedb2-b029-4918-86ac-028e3508594d.png)
至此数据拷贝完了,包数据保存去运行,发现功能正常,所以拷贝是成功的
#### 插件Dump
一般的调试工具或者PE工具 例如x64,OD本身有dump的的功能和插件
##### OD
!(./mdimg/pe/1655516949750-439fb34e-dbfc-43ba-9c33-079b74255f81.png)
!(./mdimg/pe/1655518049643-16494346-178b-479e-98da-be47d455db73.png)
##### X64dbug
!(./mdimg/pe/1655518131092-105d60bb-8b7a-4f8c-aac5-bbd12f8e1da0.png)
!(./mdimg/pe/1655518393000-26f66581-c9bc-4df8-b925-4c1aeba6b23e.png)
#### Dump时机不对原理:
1. **原因:**dump的时机不对。程序运行中,此时全局变量中的值需要做修改,不为NULL,跳过了程序的空指针检查,导致指针访问异常。
如果程序运行起来以后,全局变量里面已经有值了,此时dump,则会将全局变量的值也dump到新文件。当dump后的文件运行时,里面有值,就越过了空检查,然而此时的的值是错误的,导致了内存访问异常。
Dump有值的原因
```
LPBYTE g_p = NULL
if g_p != NULL
{
g_p = new BYTE;
}
//使用
```
因此 dump最好的时间就是代码还没执行的时候,即在程序的入口点,所以要找oep,可以用调试器把程序在入口点挂起,再去dump
####
#### 反内存Dump
##### 依据Dump原理
根据dump原理,删除或者改变PE头中用到的关键信息,或者破坏PE信息,使得dump到的新文件,windwos不支持。
比如:
- 修改节的内存地址,欺骗攻击者或者工具,使得dump到错误的数据
- 修改PE中的关键数据,使得即使dump到正确的代码,也不能正确执行,比如PE镜像大小(和计算出的大小不一致,OS就有可能不承认)
- 加载PE后抹除导入表数据。
##### 对抗方法
直接从PE文件中Dump获取数据。
导入表对抗:
加壳软件分次解密:
- 对抗方法:分层dump
### 节表注入
---
代码注入的一种,将代码添加到现有节数据中,或则是新增一个节当中,然后修改OEP指向新增的代码处,OEP执行完成后重新跳转到OldOEP处。
#### 利用节空隙
1. 添加节区数据
2. 节表添加一项,节表个数 NumberOfSections+ 1
3. 修改SizeOfImage大小,原大小加上节区数据对齐后的大小
将自定义机器码直接写入PE文件内的空闲位置处。
- **缺陷:**节之间的大小,可能不够使用。且仅限用于手动做,写代码做就很麻烦。对 00 00 00 00 是否有效不好掌控。
- **应用举例:**CHI病毒。写PE.exe ->运行程序执行流程 -> jmp 到原来 OEP
##### 注入流程:
1. 寻找节与节之间的空隙,很多 00 00 00 00 处,手动构造机器码,注意绝对地址的使用。
!(./mdimg/pe/1636266556918-2b904ef9-d237-4f03-b0ba-cf9fe6f379f4.png)
1. 修改OEP
2. 执行自己的流程
3. 执行完流程jmp到原来的OEP
##### 操作演示
把winmine 加入到我们的 PE 程序中
1. 首先在 winhex 中 打开2个 可执行文件
!(./mdimg/pe/1655546293602-3943dc4d-f1c6-4cf8-803e-131e91bd8fe4.png)
1. 把winmin的所有数据拷到PE.exe 最后面
!(./mdimg/pe/1655546553531-ccb7d0a8-263c-4b17-bf6f-ef85ee4ce205.png)
!(./mdimg/pe/1655546596614-12aae99a-084b-4570-beef-b6401eacc1ef.png)
!(./mdimg/pe/1655546639506-6742065a-f79a-4d71-80f7-0a8184f1d9c7.png)
1. 增加一个节
!(./mdimg/pe/1655547621779-542b57cb-41e1-4e0a-8bc1-11ac5390e02d.png)
1. 修改内存总大小和节数量
!(./mdimg/pe/1655548404767-b6a9e99a-34d1-4935-b547-d385fe42ca4e.png)
用OD打开程序,可以看到扫雷数据成功写入了我们程序中
!(./mdimg/pe/1655548587044-d9924cbe-a1bc-41e8-9732-c4a7b7678560.png)
注意:像这种节表后面有数据的是没有办法使用添加节的
!(./mdimg/pe/1655550736913-5273a722-35fd-4871-8acf-522515e79a71.png)
#### 新增一个节
- 新增一个节,将数据放到最后。
- 优点:数据有多大,就放多大。
- 缺陷:对于有的程序,当节表后面没有空隙,即有附加数据,直接跟了数据的程序不适用
- 影响的字段:
- - **IMAGE_FILE_HEADER**
- - - **NumberOfSections** 节的个数
- - **IMAGE_OPTIONAL_HEADER**
- - - **OEP**
- **SizeOfImage:在内存中所占内存的大小**
- **SIzeOfHeaders:PE文件格式总大小。如果节表够多,一般不小于400h**
**步骤:**
1. **IMAGE_FILE_HEADER 中的NumberOfSections 增加1**
2. **节表中增加一项**
3. **PE中添加节数据,数据必须跟文件对齐值对齐。**
4. **修改IMAGE_OPTIONAL_HEADER 中的SizeOfImage 和 AddressOfEntryPoint**
5. 增加节,很明显,我们首先要该的是节的个数,并将新的节的属性增加到IMAGE_SECTION_HEADER的后面
6. 1. 这里会遇到一个问题,通过修改,PE的头部大小可能不支持我们增加一个节,这时需要我们先插入一个文件对齐值大小的控件,再将节数据写入文件
2. 这时就会增加文件头部大小,我们需要对PE的数据进行修正,包括头部大小以及所有节的文件地址,所有节的文件地址+文件对齐值
7. 增加节的内容,即注入代码,需要注意的是,增加的文件内容也是需要文件对齐的,不足补0
8. 修改镜像大小,由于这里我们是新加的节,OS在映射到内存时,必然会从一个新的内存页开始,所以镜像大小必然要变大
9. 要使注入的代码能够运行,就要修改entry point
**不适用以下情况:节表和节数据之间没有空隙**
!(./mdimg/pe/1636268603162-65b812a7-f19e-43f4-9654-a51491783735.png)
#### 扩展最后一个节
思路是直接将注入的代码当作最后一个节的内容,需要修改的地方首先能想到的就是节数组中最后一个节的文件大小,以及内存大小。
比较隐秘的坑,由于原PE文件,节的大小是按照文件对齐值来设置的,现在新增了数据,新增的数据要以文件对齐值的大小为单位。如新增的数据超过内存对齐值,我们就要相应的修改PE镜像大小。
**缺陷:**这种方式最方便,但是修改的代码很大,稳定性很差。必须是最后一个,前面不可以
##### 步骤:
1. 添加节数据
2. 修改节表最后一项,拓展文件大小和内存大小 IMAGE_OPTIONAL_HEADER 中 的SizeOfImage 和 AddressOfEntryPoint
3. 修改 SizeOfImage
##### 操作演示
把winmine 加入到我们的 PE 程序中
1. 首先在 winhex 中 打开2个 可执行文件
!(./mdimg/pe/1655554232468-a813701b-7dbc-4c4a-9131-3c57a31a5704.png)
!(./mdimg/pe/1655554393569-06dec42d-ce2e-4753-94c4-026b1ea69874.png)
1. 把winmine数据拷贝到 pe.exe
!(./mdimg/pe/1655555513514-1ab00853-5a0b-4cdd-a260-6f315b6989d5.png)
!(./mdimg/pe/1655555543542-81d1fb7b-c728-4967-95ec-1b73889bde65.png)
1. 修改节表最后一项
!(./mdimg/pe/1655555661539-9f55b7e6-74dc-4ad0-a91f-6b3f4df3a1eb.png)
1. 修改 SizeOfImage
!(./mdimg/pe/1655556360401-d26a7f11-ca2e-46a9-8b95-0ba48d5078b0.png)
在通过OD去看,发现winmain数据已经 写入 PE
!(./mdimg/pe/1655556532462-311f6b8d-4e1d-41fd-b65f-f417c31433f1.png)
注意
!(./mdimg/pe/1655557040966-26939354-1453-4e5e-a176-b41317c05d37.png)
#### 使用空节
页:
[1]