手写PE文件(不借助编译器,用十六进制数进行编写)

这几天在看科锐钱老师有关PE文件的视频,感觉通过手写PE文件确实能增加对PE文件的了解,对结构也能更加熟悉,所以自己实操了下。

由于平时分析时,都是借用CFF等PE文件分析工具,对很多结构里面的成员的具体含义也不清楚,手写下来还是有点费劲的,写完之后对PE文件也清楚了很多。

主要用到的工具有WinHex(用来主要编写),OD(主要看加载到内存后是否符合自己预期,以及调试功能),CFF小辣椒(在整体结构,起码是头的数据框架完全出来前,最好先别用,自己去查下各自的成员所代表的含义)

    记住大概的间隔,可以快速得到PE文件的相关数据: 手写PE文件(不借助编译器,用十六进制数进行编写) 当文件内容以16字节排列开时: DOS头:0x40=64字节长度,占4行空间数据;前2字节的e_magic成员"MZ"标记代表其是DOS可执行文件,为第4排往回数4字节数据e_lfanew成员,为距离NT头的偏移,两者相加得到NT头位置。 NT头:0xF8=248字节长度,占15行半空间数据; 1.NT头的PE标记,占4字节 2.文件头:占0x14=20字节,占1行4个数据;第一个word数据代表运行平台(CPU类型0x14C为X86、0x200为Intel Itanium、0x8664为X64)、第二个word代表着该PE文件的节表个数,中间为参考下数据,可填CC,倒数第二个word为可选PE头的大小,最后一个word为该文件的属性(1固定基址 2可执行文件 4删除COFF行号 8删除COFF符号表 0x20处理2G以上的地址 100支持32位 200无调试信息 400映射到可执行文件 800映射到网络 1000映射系统文件 2000 DLL文件 4000仅在单处理器运行 属性值由上述十六进制数进行&处理得到) 3.可选文件头:共占0xE0=224字节,占14行数据;其中可选PE头分别由0x60=96字节的普通成员和0x78=120的数组数据还有8字节的0组成 3.1可选文件头成员:0x60=96字节,第一个word数据为标记字(0x0107为ROM 映像,0x010B为普通可执行文件)、第5字节的dword为程序的代码长度、 第二行的第一个dword为OEP(RVA),即原始入口点位置、最后一个dword为程序加载的目标基址 第三行前2个dword分别分内存对齐大小和文件对齐大小 第四行第一个word为要求最低子系统版本的主版本号(如下图)、倒数第二个dword为程序的映射大小、倒数第一个dw ord为程序的头部大小(各种头+节表的总大小) 手写PE文件(不借助编译器,用十六进制数进行编写) 第五行第5字节的word为可执行文件期望的子系统(1驱动程序2窗口程序3控制台程序(DLL)等..)、后一个word为Dllmain( )函数被调用的时机(默认为0)、 后面接的4个dword(跨越到第六行)分别为栈保留、栈提交,堆保留,堆提交的值 (保0留即为有多少(银行有多少钱),提交即为用多少(你取多少钱)) 3.2数据结构:占0x80=128字节,共有16个元素,每个元素占8字节长度数据(先地址(RVA)后长度),其中最后一项为未使用元素,可设为CC(由于这里为手写PE,没有太多功能,所以只填写了导入表的位置,以便系统能找到调用函数的位置)即可 手写PE文件(不借助编译器,用十六进制数进行编写) 节表:占0x28=40字节,2行半的数据;前8字节为节名(非必须)、最后16字节的4个dword分别为内存大小、内存起点、文件大小、文件起点,数据末尾的4字节为节属性 以上,文件头(可选PE头->SizeOfHeaders)全部编写完,占0x138(图片上的是因为DOS头最后的成员为F8,最小为40),对齐后,文件头独占一页空间(此时为0x200对齐) 手写PE文件(不借助编译器,用十六进制数进行编写) 接下来在下一页的空间中填充信息,记得区分文件中的页和内存中的页的大小 导入表张一行半,前4字节为INT表地址,当INT表为0时,导入表以IAT(第二行第1个dword)为线索寻找函数地址,注意,这里的地址,全是RVA,而且是IAT表的位置,表里面再存放着相应的函数信息。第一行最后一个dword存放的为DLL名字的地址;由于这里只调用了MessageBoxA函数,所以导入表只有一个(后接24字节的0),IAT也只有一项(0x1040 后接4字节0结尾) 第260行为实际代码片段,也是OEP所指的位置(260位于文件第二页的60偏移,所以OEP要在内存的第二页60偏移,即0x1060),代码为将0压栈,将文本内容的地址入栈,标题地址入栈,0入栈,调用MessageBoxA函数(这里为FF15 00401050,因为IAT表地址为1050,期望的加载地址又为400000),ret(C3) 整个程序完毕,在用调试器调试时,可以跳转到IAT地址看看是否导入成功,再到OEP看下调用函数地址是否准确,至于标题和文本,可以用调试器转成十六进制再输入。   最后再上一个实际手写的PE文件的内容 手写PE文件(不借助编译器,用十六进制数进行编写)

 

 运行结果:手写PE文件(不借助编译器,用十六进制数进行编写)

 

 

 

上一篇:HTML5中的Canvas详解


下一篇:poj - 1159 - Palindrome(滚动数组dp)