系列汇总
- 写一个PE的壳_Part 1:加载PE文件到内存
- 写一个PE的壳_Part 2:ASLR+修复输入表(IAT)+重定位表支持(.reloc)
- 写一个PE的壳_Part 3:Section里实现PE装载器
- 写一个PE的壳_Part 4:修复对ASLR支持+lief构建新PE
- 写一个PE的壳_Part 5:PE格式修复+lief源码修改
- 写一个PE的壳_Part 6:简单的混淆
重要的事情说3遍:
笔记更新换位置了!
笔记更新换位置了!
笔记更新换位置了!
新位置
:我写的新系列,刚开始写没几天,后续文章主要在新地址更新,欢迎支持;写作不易,且看且珍惜(点击跳转,欢迎收藏)
文章目录
Part 2:ASLR+修复输入表(IAT)+重定位表支持(.reloc)
本文主要处理Part 1中遗留的2张表:输入表和重定位表(执行一个
ASLR
使能的文件不会出错)
当前现状:Part 1中执行loader.exe C:\Windows\SysWOW64\calc.exe
没有出现计算器,loader.exe
没有处理C:\Windows\SysWOW64\calc.exe
的输入表是一个主要原因
处理2个表前,最好先复习一下ASLR
的相关知识
1.ASLR预备知识
ASLR
(Address Space Layout Randomization,地址空间布局随机化)是一种针对缓冲区溢出的安全保护技术,从Vista开始支持;主要是使exe文件运行时加载到内存中的地址是随机的
- 产生原因
没有ASLR前,一般情况下,exe文件都会加载在OS给分配的虚拟内存0x400000上,微软自己的DLL总会加载在固定的地址上,这给程序安全带来的很大的隐患;因此需要将PE文件每次加载到内存的地址进行随机化
- 编译器支持
支持ASLR的前提是OS的内核版本必须是6
以上,且编译工具必须支持/DYNMAICBASE
;下面的界面中还可以设置PE文件的ImageBase(exe文件默认是0x400000,dll文件默认是0x10000000,默认值都是可以修改的)
.reloc
节区
支持ASLR的PE文件多了一个名为.reloc
的节区,一般情况下普通的exe文件不存在这个节区,开了ASLR技术的二进制才出现这个节区,是由编译器在编译时指定并保存在可执行文件中的
PE文件加载进入内存时,.reloc
节区主要是做重定位参考的
- PE header变化
File Header
中Characteristics
属性里,IMGE_FILE_RELOCS_STRIPPED
在不在支持ASLR时默认是勾选的,支持ASLR时不会勾选;增加ASLR的支持后,同一套源码产生的PE文件区段个数也会多一个
Optional Header
中DllCharacteristics
属性里,IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE
在不在支持ASLR时默认是不勾选的,支持ASLR时会勾选(是否勾选不要与上面弄混了)
题外话:逆向时,如果一个文件支持ASLR会给逆向增加难度,可以直接将
Optional Header
中DllCharacteristics
属性里的IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE
删掉,这样就不支持ASLR了;这样程序每次运行都会加载到同一地址,方便分析
2.输入表支持
结论:输入表支持就是我们要自己遍历INT,给IAT填写真实内存地址的过程
detail 1:什么是输入表?
简单归纳:就是将程序使用的第三方库的函数放在一个表格里进行统一管理的表
一般情况下,当一个PE二进制用额外的库时,它通常将输入表存储在.idata
区段;下面以一个ShellExecuteW
(被calc.exe导入)函数为例,说明一下输入表
calc.exe要调用ShellExecuteW
,是需要知道ShellExecuteW
函数在内存中的位置的;dll文件只有在运行时才会加载进内存,因此在编译阶段,编译器是不知道ShellExecuteW
在内存中的确切位置
输入表中的IAT就是为了解决上面的编译时和运行时的矛盾的
- 编译时:在调用
ShellExecuteW
的地方,ShellExecuteW
使用的是中转地址,或者可以理解成指向输入表中的IAT
- 运行时:当运行程序时,相应的dll文件被加载时,PE装载器会先将
ShellExecuteW
的真实地址在IAT
保留;都处理完后,才会执行用户写的代码,此时执行ShellExecuteW
就会通过IAT
找到真正的加载地址
结论:PE被装载进入内存后,输入表中真正有用的只有IAT了
下面是x32dbg加载calc.exe的截图,可以看到输入表中IAT
的身影
- 位置1:外部call指令,调用的是外部模块(
shell32.dll
),call指令(用FF15
操作码)的参数(38306700)来自于IAT
,被ShellExecuteW标识;具体是用函数名还是用序号(ordinal)取决取IAT
加载的方式 - 位置2:内部call指令,编译器知道被调用函数的目的地址,因此用
E8
操作码(“realtive call”
)
输入表的基本知识介绍完了,下面使用CFF查看一下输入表在PE中的相关部分
detail 2:PE Header描述
输入表信息这么重要,PE头文件哪里描述呢?可以从Data Directories
(是Optional Header
的一部分)开始学习
目录的顺序是事先确定的,默认个数是16
;与导入相关的有2个directories(都位于名为.idata
的section里):
Import Directory
:编号是01
,指向“Import Directory Table” (IDT
),告诉我们什么函数要被程序导入,即what
Import Address Table Directory
:编号是12
,指向“Import Address Table” (IAT
),将要导入的函数的地址放在IAT里,即where
题外话:Part 4中会看到
LIEF
建立一个空白PE时,Data Directories数组的个数指定的是15,导致输入表不能识别
detail 3:真实的输入表
CFF
查看IDT
的内容如下:
每一个dll都有一个条目记录需要导入的函数,下面通过编程处理输入表
detail 4:编程处理
现在直接说编程处理输入表,估计还是会不知所云,因为还有最后一项没有介绍,即输入表需要的数据结构
数据结构
Data Directories
中的Import Directory
指向一个数组,数组里每个元素都是IMAGE_IMPORT_DESCRIPTOR
的结构体,数组终止条件是一个全为0的IMAGE_IMPORT_DESCRIPTOR
的结构
每一个DLL都用一个IMAGE_IMPORT_DESCRIPTOR
的结构体进行描述,定义如下:
//每一个DLL都用一个