在平时的剖析当中,常常会碰到PE构造的文件,尽管 010 Editor 等工具会提供一个模板,把各个局部都具体的标记进去,然而在调试的时候,常常会须要在 VS 等程序框中进行调试,所以,就须要对PE构造有肯定的理解,才可能疾速定位到本人想要的中央。
为了更好的理解PE构造中的每一位的作用,最好的方法就是本人手写一个PE文件,这样对每一个局部的了解,都会清晰很多。
目录
0x00 筹备工作
0x01 结构DOS头
0x02 结构File头
0x03 结构Optional头
0x04 结构节表
0x05 结构导入表
0x06 执行代码
0x00 筹备工作
在开始之前,有一些细节是须要提前思考好的,这些细节对于整个PE构造来说是十分重要的。
因为只须要实现一个弹窗的成果,代码量是非常少的,所以在程序的设计上,一个节表就齐全足够了,同时,咱们心愿保障文件尽可能小,所以将文件对齐设置为200,将内存对齐设置为1000。
再加上头部和对齐的思考,文件就须要占用400个字节了,到内存开展当前就占用2000字节。
注:PE格局中,所有能够被笼罩掉,而不影响程序运行的地位,我都会用CC来填充,这些地位能够写入字节的shellcode等。
0x01 结构DOS头
DOS头部在编辑器中占用了4行,其中的少数数据都是在16位的DOS环境下运行时所必备的,在当初看来,曾经是能够占用的内容,只有两个参数是必须的:e_magic和e_lfanew。
e_magic
这个位是识别性的头部(MZ),这个地位是会被作为一个非法PE文件的检测位。
e_lfanew
用来指向一个新的构造,这个也就是咱们当初来说,最重要的构造,所有的参数信息都是在这个构造中定义的。
在DOS头部前面还有一个Stub数据区,是16位程序的残留数据,是能够去掉的,所以就间接将e_lfanew指向了0x40,在这个地位开始新的构造。
0x02 结构File头
PE标识、File头以及Optional头统称为NT头,这里就不提NT的概念了,PE标识有4字节。
File头有1.4行,有4个重要的参数。
Machine
这是一组宏,示意在什么硬件下运行,肯定要依据理论的状况来进行更改,以后是Intel386,所以填0x014c。
NumberOfSections
形容节表的个数,在后面布局的时候提到了,应用一个节表就足够了。
SizeOfOptionalHeader
形容Optional头的长度,在本人写程序进行剖析的时候,肯定要留神这一点,原版的OD间接应用sizeof来取得了,漠视了这个值是变长的,咱们能够本人来更改,达到反调试的目标,这里填写0xE0。
Characteristics
形容可执行文件的属性,具体参照上面这张图。
最终填写如下
0x03 结构Optional头
Optional头波及到的参数就比拟多了,也是最重要的一个局部
Magic
类型辨认,能够来判断到底是32位还是64位,再或者是其余的,咱们应用32位的,为0x10b
AddressOfEntryPoint
程序入口点,因为头部对齐后为200字节,所以程序就从文件的200地位开始写,对应到内存中就是1000,所以填写0x1000
ImageBase
倡议装载地址,这个地址并不是肯定能占住的,如果没有到这个地位的话,会依据重定位表来修改程序中的地址,因为要写一个exe文件,个别默认是0x400000
SectionAlignment
内存对齐,填写0x1000
FileAlignment
文件对齐,填写0x200
MajorSubsystemVersion
这个版本号是不能进行批改的,目前零碎个别都是NT4的,填写0x4
SizeOfImage
内存中的文件大小,在后面布局的时候也提到过了,这里填0x2000
SizeOfHeaders
头部的大小,这里都是要思考对齐后大小的,所以填0x200
CheckSum
校验和,在3环程序中,是不会检测这个地位的,在0环中才会进行校验,然而计算这个值的算法是公开的,所以能够本人计算并填写,检测的意义不大,咱们把这个位填0。
Subsystem
这个地位是程序运行在什么状况下,填3,是命令行下,2是图形化界面下,1是内核文件中,依据理论状况填写。
DllCharacteristics
是否是基于WDM的驱动程序,填0就能够了,如果是的话,填0x2000
SizeOfStackReserve
筹备保留多大的栈空间,本人填写,正当即可
SizeOfStackCommit
程序运行的时候,占用多大的栈空间,本人填写,正当即可
SizeOfHeapReserve
筹备保留多大的堆空间,本人填写,正当即可
SizeOfHeapCommit
程序运行的时候,占用多大的堆空间,本人填写,正当即可
NumberOfRvaAndSizes
数据目录的长度,默认是16个,填写0x10
紧接着前面就是数据目录的形容了,一个形容占用8个字节,4个字节的RVA,4个字节的长度,只有导出表和重定位表的长度是会被应用的,其余的数据目录的长度都是能够笼罩掉的,他们通过一个全零构造来判断结尾。
最终填写如下
0x04 结构节表
在数据目录的形容完结当前就是节表形容了,一个节表的形容是两行半
Name
节表名字的长度是固定的,而且是能够轻易写的,并不是说.data就肯定是数据段,肯定不能通过名字来判断其中的内容。
VirtualSize
这是一个共用体,个别咱们应用的都是VirtualSize位,节表在内存中的长度,无效字节的长度,这个是对齐前的长度,这里能够填0。
VirtualAddress
节表的开始地位在内存中的RVA,依照后面的构想,这里应该填0x1000
SizeOfRawData
节表在文件中的长度,这个是对齐后的大小,依照后面的构想,这里应该填0x200
PointerToRawData
节表的起始地位,依照后面的构想,这里应该填0x200
Characteristics
节属性,形容这个节是可读的,可写的还是可执行的。
对于下面的那四个参数,能够形容为,从文件中起始地址为PointerToRawData的中央,复制SizeOfRawData的数据,粘贴到内存中RVA为VirtualAddress的地位,理论字节为VirtualSize的中央。
最终填写如下,还须要对齐
0x05 结构导入表
在实现了这些内容当前,就须要开始结构导入表了,因为咱们须要调用MessageBoxA函数来实现弹窗的性能。
导入表也是整个PE构造中最简单的中央,占用1.4行,在程序执行前和执行后,导入表的构造是不一样的。
OriginalFirstThunk
导入名称表,这里是一个RVA,它指向了一个构造,说它是一个数组更为适合,外面存储的也是一个RVA,指向了_IMAGE_THUNK_DATA构造,这个构造也是一个共用体,能够填一个序号,也能够填一个函数名称,因为导出表有按名字导出和按序号导出两种模式。
这里咱们应用按名字导出的形式,这样就又波及到了一个构造_IMAGE_IMPORT_BY_NAME
在这个构造中,Hint属于废除的状态,所以只须要写上函数的名字就能够了,这个名字是一个字节的,因为不晓得函数名字的长短,也就没法应用定长的形式,为了防止空间的节约,所以它只记录了名字的起始地位,通过00来判断结尾
Name
动态链接库的名称,也是一个RAV,它指向了名字
FirstThunk
导入地址表,这里也是一个RVA,一样指向了一个数组,与导入名称表是对应的,在运行前,与导入名称表一样,都指向了_IMAGE_THUNK_DATA构造,结构图下
当程序执行当前,导入地址表就会依照名称进行搜寻,失去函数的地址,而后把地址填入到对应的地位中,结构图就变成了上面这个样子
在执行的时候,也就是间接调用的函数地址表
咱们先把导入表的构造写进去,地址的地位先用0来补充,这里将导入表也到250的地位
文件偏移是250,对应的内存偏移是1050,所以在数据目录的第二项,也就是导入表的地位,写上导入表的RVA,长度是能够轻易写的
因为导入表是依附一个全零构造来判断结尾的,咱们须要给它留下足够的空间,咱们将 dll 名称写到 2C0 的地位,最初的 .dll 是能够不必写的,操作系统不依附后缀名来判断
文件偏移2C0对应的内存偏移是10C0,写到导入表中对应的地位
而后安排函数名称MessageBoxA,结尾的两个字节是能够随便填写的,咱们将它放到 2E0 的地位,最初以00来结尾
文件偏移2E0对应的内存偏移是10E0,这个先记住,等一下再进行填写
而后是导入名称表和导入地址表,在执行前,这两个的内容是一样的,都指向了函数名称,也就是下面的10E0,因为这里咱们只用一个函数,所以导入名称表和导入地址表都只有一项,后面也说过了,它们相当于是一个数组,是须要一个00来结尾的,所以每一个都须要占用8个字节,刚好是一行,所以,咱们把导入名称表放到2D0的地位,将导入地址表放到2D8的地位。
文件偏移2D0对应的内存偏移是10D0,文件偏移2D8对应的内存偏移是10D8,而后将它们填到导入表中对应的地位。
到这里为止,导入表的编写也就实现了。
0x06 执行代码
最初就是代码的编写了,咱们先设置一下弹窗的题目和内容,咱们将题目放到2F0的地位,对应的内存偏移是10F0,因为ImageBase是400000,所以咱们须要push的地址是4010F0,而后把弹窗的内容放到2F4的地位,对应的内存偏移是10F4,须要push的地址是4010F4
后面曾经提到过了,在代码执行的时候,咱们须要调用的函数地址在导入地址表中,所以须要调用的地址是4010D8
这样,所须要调用的内容也就都有了,接下来就是硬编码的事件了,如果对硬编码不相熟的话,咱们能够通过在OD中写汇编,而后把硬编码扣下来
而后把这段代码写到200的地位
这样就功败垂成了,而后保留运行,看看成果
胜利弹窗,也就实现了手写PE构造的工作,尽管还有导出表,重定位表等都没有波及到,然而通过这样的一次小的练习,也就对整个PE构造都有了更粗浅的理解了。
了解原理,方可变动
欢送关注公众号:信安本原(sec-source)
本文由博客一文多发平台 OpenWrite 公布!