构造PE文件需要将所需的结构逐一构建出来,即需要将IMAGE_DOS_HEADER、IMAGE_FILE_HEADER、IMAGE_OPTIONAL_HEADER、IMAGE_SECTION_HEADER、IMAGE_IMPORT_DESCRIPTOR和数据节构造好,进而完成整个PE文件的代码。
1、构造IMAGE_DOS_HEADER结构
在PE文件格式中以IMAGE_DOS_HEADER结构开始,根据对其结构题进行分析可知其大小为40h字节(十进制中的64字节)。此时可以都打开C32Asm编辑器,选择“文件”、“新建十六进制文件”,在所弹出的“新建文件”对话框中设置“新文件大小”为“64”,即如下所示:
设置好“新文件大小”后,单击“确定”按钮,在C32Asm中即可插入有64字节的文件,即如下所示:
通过上述步骤可创建一个全0的64字节的数据,根据IMAGE_DOS_HEADER结构体对该64字节数据进行修改。在IMAGE_DOS_HEADER结构体中,关键字段只有两个,分别为e_magic和e_lfanew,其取值如下表所示:
字段 |
取值 |
备注 |
e_magic |
4D 5A |
MZ头标识 |
e_lfanew |
40 00 00 00 |
指向PE标识符的偏移 |
在IMAGE_DOS_HEADER中,只须为这两个字段赋值,其余字段值均为0,赋值后的数据情况如下图所示:
在IMAGE_DOS_HEADER结构体中,最后4字节指向PE标识符的偏移,由于手写PE文件不会像编译器一样插入DOS Stub所以在DOS头后紧跟着PE标识符,此处所给出的值为0x00000040。
2、构造PE标识符
构造完DOS头后则需要构建PE标识符,PE标识符占4字节,因此,C32Asm中需要新增4字节的数据。在C32Asm中选择“编辑”、“插入数据”选项,在所弹出的“插入数据”对话框中设置“插入数据大小”为“4”点击“确定”即可,此时所插入的4字节十六进制数据均为“0”。已知PE标识符对应的十六进制数据为“50 45 00 00”,因此,需要将插入的4字节数据的前两字节修改为“50 45”,修改后的数据情况如下图所示:
3、构造IMAGE_FILE_HEADER结构体
构造好PE标识符后则需要构造IMAGE_FILE_HEADER结构体,该结构体的大小为14h字节(十进制的20字节),同样需要在C32Asm中插入20字节的全0数据,并根据需求进行相应的数据修改,其中IMAGE_FILE_HEADER结构体的关键字段取值如下表所示:
字段 |
取值 |
备注 |
Machine |
4C 01 |
表示i 386类型的CPU |
NumberOfSections |
03 00 |
该PE文件共计3字节 |
SizeOfOptionalHeader |
E0 00 |
在Win32环境下可选头的大小 |
Characteristics |
03 01 |
没有重定位信息的32位平台的可执行文件 |
填充数据时,SizeOfOptionalHeader字段值大小与32位平台的IMAGE_OPTIONAL_HEADER结构体的大小相同,Characteristics字段的值由多个值组合而成。填充的数据均为word类型,其余没有填充的字段值均为0,填充数据后的情况如下图所示:
4、构造IMAGE_OPTIONAL_HEADER结构体
IMAGE_OPTIONAL_HEADER结构体是PE文件中最为重要且体量较大的结构体之一,该结构体划分为32位和64位两个版本,此处以32位版本为例。该结构体大小为0E0h字节,即十进制224字节,因此,需要在C32Asm中填充224字节的全0十六进制数。
由于没有填充数据目录,所以根据IMAGE_OPTIONAL_HEADER结构体中字段的取值可先将结构体中的字段填充一半,填充数据是将0的部分同时在表格中进行展示,其具体填充数据情况如下图所示:
填充完IMAGE_OPTIONAL_HEADER结构体的基础数据部分后,话需要填充其数据目录部分。由于此处为手写一个EXE文件,所以数据目录只须存在两项,分别为第1个数据目录项和第13个数据目录项。其中数据目录的第1项是导入表,第13项是导入地址表
根据对PE文件的规划可在0x00003000起始处存放导入表的相关内容,在0x00003000中先存放导入地址表,即数据目录的第13项,再在导入地址表后存放导入表,即数据目录的第1项。导入地址表占用16字节,即从0x00003000处起始,在0x0000300f处结束。又导入表从0x00003010处起始。按照该布局在数据目录中输入导入表和导入地址表的RVA。此时IMAGE_OPTIONAL_HEADER数据目录部分的填充数据情况如下所示:
IMAGE_OPTIONAL_HEADER结构体的字段相对较多,可将其划分为普通字段信息和数据两个部分进行数据填充。
5、构造IMAGE_SECTION_HEADER结构
构造完IMAGE_OPTIONAL_HEADER结构体后则需要构造节表,节表中一共包含3个节表项,也就是需要构造3个IMAGE_SECTION_HEADER结构,每个IMAGE_SECTION_HEADER结构体的大小为40字节,由于需要构造3个节表项,因此,节表大小为120字节,节表中字段的填充情况如下表所示:
Name |
.text |
.data |
.idata |
VirtualSize |
0x00001000 |
0x00001000 |
0x00001000 |
VirtualAddress |
0x00001000 |
0x00002000 |
0x00003000 |
SizeOfRawData |
0x00001000 |
0x00001000 |
0x00001000 |
PointerToRawData |
0x00001000 |
0x00002000 |
0x00003000 |
Characteristics |
0x60000020 |
0xC0000040 |
0xC0000040 |
在C32Asm中填充120字节的全0数据并根据上表填充,填充完成后的数据情况如下所示:
6、0数据的填充
PE文件格式的头部到此填充完毕,在IMAGE_OPTIONAL_HEADER结构体结构体中,SizeOfHeader字段的值是0x00001000。因此,为了对齐粒度需要将头部的大小用0字节补足。目前已填充的PE文件格式头部的大小为432字节,在C32Asm中,将光标移动到最后一字节处,可以看到C32Asm右下角“光标”的值为“000001B0”,“文件长度”为432 bytes,即如下图所示:
由于需要按照0x00001000长度进行对齐,因此用0x1000-0x01B0=0x0E50,即十进制数的3664。在C32Asm中插入“3664”个“0”字符将PE文件头部按照IMAGE_OPTIONAL的SizeOfHeader对齐。插入3664个0字符后,文件的结束偏移地址是0x00000FFF。在填充PE文件头部后,需要继续填充0x00001000字节的0字符,该0x00001000字节的数据用来存放.text节的内容,即代码节内容。继续使用C32Asm插入4096个0字符。
7、填充.data节的数据
.data节用来保存程序运行时弹出的提示对话框中显示的字符串。提示对话框使用MessageBox函数来实现。MessageBox函数的第二个参数和第三个参数分别是两个字符串,第二个参数lpText是提示对话框中用于显示的字符串,第三个参数lpCaption是提示对话框中标题显示的字符串。
lpText显示的字符串是“Hello,PE 文件”,lpCaption显示的字符串是“Binary Diy”。在C32Asm中先插入4096个0字符后,再在0x00002000地址处写入lpText的值,在0x00002020地址处写入lpCaption的值,即如下所示:
8、填充.idata节的数据
.idata节用于保存PE文件中重要的两个部分,即导入表和导入地址表。在填充.idata节的数据之前,先来对.idata节的数据进行分析。
导入表和导入地址表地址分别由数据目录给出,在数据目录中,导入表的RVA为0x00003010,导入地址表的RVA为0x00003000。由于此所构造的PE文件的RVA与FOA地址相同,故不需要进行转换,RVA即为FOA。因此,导入地址表的偏移地址为0x00003000,而导入表的偏移地址为0x00003010。
在本例中需要导入两个DLL文件,因此需构造3个IMAGE_IMPORT_DESCRIPTOR,因为导入表需要由一个全0的IMAGE_IMPORT_DESCRIPTOR来结束。因为导入表中的字段大部分是具体的RVA值,所以先来构造一个占位用的导入表,导入表中字段的填充表如下所示:
OriginalFirstThunk |
AA AA AA AA |
BB BB BB BB |
CC CC CC CC |
TimeDateStamp |
00 00 00 00 |
00 00 00 00 |
CC CC CC CC |
ForwarderChain |
00 00 00 00 |
00 00 00 00 |
CC CC CC CC |
Name |
AA AA AA AA |
BB BB BB BB |
CC CC CC CC |
FirstThunk |
AA AA AA AA |
BB BB BB BB |
CC CC CC CC |
按照上表可以在文件偏移地址0x00003010处构造占位用的导入表,即如下图所示:
在本例中导入了两个DLL文件,分别是user32.dll和kernel32.dll。在user32.dll中调用了MessageBoxA函数,在kernel32.dll中调用了ExitProcess函数。先构造user32.dll的导入信息,按照IMAGE_IMPORT_DESCRIPTOR结构体来进行构造。
在0x00003050地址处构造导入表的Name字段的值“user32.dll”。
在0x00003060地址处构造导入表的OriginalFirstThunk字段的值“0x00003070”。OriginalFirstThunk指向一个IMAGE_THUNK_DATA,而IMAGE_THUNK_DATA在高位不为1的情况下指向一个IMAGE_IMPORT_BY_NAME结构体。
在0x00003070地址处根据IMAGE_IMPORT_BY_NAME结构体导入函数的名称。
0x00003000地址处是导入地址表,该值由FirstThunk来指向,当磁盘中时,该值与OriginalFirstThunk相同。因此,在文件偏移地址0x00003000处输入0x00003070。按照构造user32.dll的方式构造kernel32.dll的导入信息,导入表信息的填充情况如下所示:
随后则需要根据如下的导入表进行数据填充,下表如下所示:
OriginalFirstThunk |
60 30 00 00 |
90 30 00 00 |
00 00 00 00 |
TimeDateStamp |
00 00 00 00 |
00 00 00 00 |
00 00 00 00 |
ForwarderChain |
00 00 00 00 |
00 00 00 00 |
00 00 00 00 |
Name |
50 30 00 00 |
80 30 00 00 |
00 00 00 00 |
FirstThunk |
00 30 00 00 |
08 30 00 00 |
00 00 00 00 |
填充后的导入表情况如下所示:
在LordPE.EXE中打开该EXE文件查看器导入表信息,其步骤为单击右侧列中的“PE Editor”按钮,找到所需要打开的文件(PE.exe文件),在弹出的对话框中单击“Directories”,在接下来所弹出的对话框中找到“ImportTable”,点击其RVA、Size后的“…”按键打开其输出表,其具体步骤如下图所示:
此时则可以查看器导入表的相关信息,具体情况如下所示:
9、填充.text节数据
手写PE文件的最后一步是填充PE文件.text节的内容,即可执行程序的的代码。将按照前面步骤所构造的PE文件使用OD打开,在反汇编窗口,OD会自动定位到0x00401000地址处,即如下图所示:
由上图可知OD反汇编窗口中“HEX数据”列(第二列)显示的全是0字符,这是因为在构造PE文件是并未对.text节填充任何内容。
随后再查看OD的数据窗口,数据窗口是从0x00402000处开始显示的,即如下所示:
如上图可知OD数据窗口中显示字符串“Hello,PE File!!!”和“Binary Diy”,这是在.data节中填充的数据。
在0D数据窗口中通过快捷键“Ctrl+G”跳转至地址0x00403000处,查看导入表信息,即如下图所示:
由上图可知导入地址的信息已经与构造PE文件时有所差别,这是因为导入地址表在载入内存后其中的值会发生变化,其会被填充为实际的导入地址。在OD数据窗口右键单击,在所弹出的快捷菜单栏中选择“Long”、“Address”选项则可以直观地查看导入地址表信息,即如下所示:
通过上图中“数值”列(第二列)显示的“7709CB80”和“76307F40”即是被填充后的函数地址。
在OD中查看手写PE文件后还需要再次查看手写PE文件,这是因为编写代码时会使用到.data节和.idata节的内容,而此时内存中与磁盘中的地址会发生些许改变,因此需要在OD中再次查看构造的数据。
在OD中查看再次查看构造的数据可以得到如下结果:
①.text节的位置是从0x00401000处起始。
②“Hello,PE File!!!”字符串的地址为0x0040200。
③“Binary Diy”字符串的地址为0x403000。
④“MessageBoxA”函数的导入地址为0x403000。
⑤“ExitProcess”函数的导入地址为0x403008。
综上所述,需要在OD反汇编窗口的0x00401000地址起始依次将9个地址的反汇编代码修改成以下反汇编代码:
push 0
push 00402020
push 00402000
push 0
call 0040101A
push 0
call 00401020
jmp [00403000]
jmp [00403008]
首先,双击0x00401000地址处的“反汇编”列(第三列),即可修改其反汇编代码,依次将上述反汇编代码进行填充修改,最终修改结果如下所示:
将上述反汇编代码写入后选中并右键单击执行快捷菜单栏,选中“Copy to executable”执行“Selection”命令即可进入到文件编辑窗口,在空白处单击右键执行快捷菜单中的“Save File”命令,即可将修改保存到文件之中将其并命名为“ple.ex”。至此,手写可执行文件任务完成。
此时双击刚修改后并保存的“pel.exe”文件,此时即可查看到其运行结果如下图所示:
标签:文件,00,填充,IMAGE,HEADER,导入,PE,手写 From: https://www.cnblogs.com/Auion-idiot/p/17493430.html