自己第一遍想手动写PE格式的时候,以为一个字段都不能错,小心翼翼的开始写。这得写到猴年马月去了呀。每次字段都仔细地琢磨一下,研究一下,结果到头来程序还是不能运行。
第二遍写的时候,参考了网上的“手工打造微型win32可执行文件”这篇文章。
http://www.xfocus.net/articles/200302/482.html
文章写的很早了,自己看了这篇文章以后觉得收获非常的大。
在下面分析的过程中不懂的可以参考下小甲鱼的课件
http://www.fishc.com/a/shipin/jiemixilie/_PExilie_/1033.html
自己首先将他的PE文件分析了一遍,在这次分析的过程中明白了很多东西:很多PE结构的字段都是鸡肋, 而有些字段十分重要,绝对不能出差错。
那么哪些字段不能出差错呢?其实不外乎就是涉及到地址的,涉及到offset的,RVA的,这些都是一点都不能出差错的。
如果自己手工编写PE文件的话,自己应该先在草稿纸上画下草图,先设定好对齐方式,然后算好每个区块的大小以及它的RVA,把全部数据都算好后在填充数据结构字段的时候就可以减小差错了。
首先还是来分析一下手工打造微型win32可执行文件里面的第1个例子
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F 00000000 4D 5A D5 E2 C0 EF BB F9 B1 BE B6 BC C3 BB D3 D0 MZ这里基本都没有 00000010 D3 C3 2C 44 4F 53 B1 A3 C1 F4 B5 C4 CD B7 B2 BF 用,DOS保留的头部 00000020 D0 C5 CF A2 2C CE D2 C3 C7 B6 BC B2 BB D3 C3 2E 信息,我们都不用. 00000030 D2 BB D6 B1 B5 BD D5 E2 C0 EF C0 B2 40 00 00 00 一直到这里啦@... 00000040 50 45 00 00 4C 01 02 00 00 00 00 00 00 00 00 00 PE..L........... 00000050 00 00 00 00 E0 00 0F 01 0B 01 00 00 00 02 00 00 ....?.......... 00000060 00 00 00 00 00 00 00 00 00 10 00 00 00 00 00 00 ................ 00000070 00 00 00 00 00 00 40 00 00 10 00 00 00 02 00 00 ......@......... 00000080 00 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00 ................ 00000090 00 30 00 00 00 02 00 00 00 00 00 00 02 00 00 00 .0.............. 000000A0 00 01 00 00 00 00 00 00 00 01 00 00 00 10 00 00 ................ 000000B0 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 ................ 000000C0 10 11 00 00 3C 00 00 00 00 00 00 00 00 00 00 00 ....<........... 000000D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000110 00 00 00 00 00 00 00 00 00 11 00 00 10 00 00 00 ................ 00000120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000130 00 00 00 00 00 00 00 00 43 6C 6F 75 64 00 00 00 ........Cloud... 00000140 00 02 00 00 00 10 00 00 00 02 00 00 00 02 00 00 ................ 00000150 00 00 00 00 00 00 00 00 00 00 00 00 60 00 00 60 ............`..` 00000160 4E 53 46 4F 43 55 53 00 00 02 00 00 00 20 00 00 NSFOCUS...... .. 00000170 00 02 00 00 00 02 00 00 00 00 00 00 00 00 00 00 ................ 00000180 00 00 00 00 60 00 00 60 D5 E2 C0 EF BF AA CA BC ....`..`这里开始 00000190 CA C7 B6 D4 C6 EB CC EE B3 E4 D0 C5 CF A2 A3 AC 是对齐填充信息, 000001A0 C3 BB D3 D0 D3 C3 2E 20 D2 BB D6 B1 B5 BD 30 78 没有用. 一直到0x 000001B0 32 30 30 68 BF AA CA BC CE AA 2E 74 65 78 74 28 200h开始为.text( 000001C0 D2 B2 BE CD CA C7 43 6C 6F 75 64 29 B6 CE BF AA 也就是Cloud)段开 000001D0 CA BC 2C CE D2 C3 C7 B0 D1 20 49 6D 70 6F 72 74 始,我们把 Import 000001E0 B1 ED D2 B2 B7 C5 B5 BD D5 E2 B8 F6 B6 CE C0 EF 表也放到这个段里 000001F0 C0 B4 C1 CB 2F 2F BA C7 BA C7 20 3A 29 20 20 20 来了//呵呵 :) 00000200 33 DB 74 13 58 53 50 83 C0 07 50 53 68 2A 10 40 3踭.XSP兝.PSh*.@ 00000210 00 68 30 10 40 00 C3 E8 E8 FF FF FF 45 78 65 44 .h0.@.描?��ExeD 00000220 49 59 00 48 65 6C 6C 6F 21 00 FF 25 00 11 40 00 IY.Hello!.�%..@. 00000230 FF 25 08 11 40 00 00 00 00 00 00 00 00 00 00 00 �%..@........... 00000240 B4 FA C2 EB BE CD D5 E2 C3 B4 D2 BB B5 E3 A3 AC 刖驼饷匆坏悖? 00000250 D5 E2 C0 EF D3 D6 CA C7 CC EE B3 E4 A3 AC 49 6D 这里又是填充,Im 00000260 70 6F 72 74 B1 ED CE D2 C3 C7 D2 B2 B7 C5 B5 BD port表我们也放到 00000270 D5 E2 B8 F6 B6 CE C0 B4 C1 CB A3 AC CE AA C1 CB 这个段来了,为了 00000280 BC C6 CB E3 B5 D8 D6 B7 B7 BD B1 E3 CE D2 C3 C7 计算地址方便我们 00000290 B0 D1 CB FC B7 C5 B5 BD 30 78 33 31 30 68 B4 A6 把它放到0x310h处 000002A0 C1 CB C6 E4 B6 D4 D3 A6 C4 DA B4 E6 B5 D8 D6 B7 了其对应内存地址 000002B0 CE AA A3 BA 30 78 34 30 31 31 30 30 00 00 00 00 为:0x401100.... 000002C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000002D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000002E0 00 00 00 00 00 00 00 00 B5 BD D5 E2 C0 EF BF AA ........到这里开 000002F0 CA BC 49 6D 70 6F 72 74 B1 ED D0 C5 CF A2 A3 BA 始Import表信息: 00000300 5C 11 00 00 00 00 00 00 78 11 00 00 00 00 00 00 \.......x....... 00000310 4C 11 00 00 00 00 00 00 00 00 00 00 6A 11 00 00 L...........j... 00000320 00 11 00 00 54 11 00 00 00 00 00 00 00 00 00 00 ....T........... 00000330 86 11 00 00 08 11 00 00 00 00 00 00 00 00 00 00 ?.............. 00000340 00 00 00 00 00 00 00 00 00 00 00 00 5C 11 00 00 ............\... 00000350 00 00 00 00 78 11 00 00 00 00 00 00 AB 00 45 78 ....x.......?Ex 00000360 69 74 50 72 6F 63 65 73 73 00 4B 45 52 4E 45 4C itProcess.KERNEL 00000370 33 32 2E 64 6C 6C 00 00 BB 01 4D 65 73 73 61 67 32.dll..?Messag 00000380 65 42 6F 78 41 00 55 53 45 52 33 32 2E 64 6C 6C eBoxA.USER32.dll 00000390 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000003A0 BA C3 C1 CB A3 AC BD E1 CA F8 A3 AC D5 E2 C0 EF 好了,结束,这里 000003B0 CA C7 D7 EE BA F3 D2 BB B8 F6 B6 D4 C6 EB D3 C3 是最后一个对齐用 000003C0 B5 C4 CC EE B3 E4 D6 B5 C1 CB A1 A3 20 20 20 20 的填充值了。 000003D0 77 61 74 65 72 63 6C 6F 75 64 40 6E 73 66 6F 63 watercloud@nsfoc 000003E0 75 73 2E 63 6F 6D 20 20 32 30 30 32 C4 EA 31 32 us.com 2002年12 000003F0 D4 C2 31 38 C8 D5 20 20 20 CD FB B8 AB D5 FD 21 月18日 望斧正!
(一)DOS头
对于DOS头而言,只有最开始的e_magic字段MZ 和e_lfanew 指向PE文件头开始是有用的,其它全部可以随便填!!!
dos_stub也是鸡肋,我们根本连写都可以不写它!!!
(二)NT头
然后是NT头,先发个NT头的数据结构上来
IMAGE_NT_HEADERS STRUCT { +0h DWORDSignature +4h IMAGE_FILE_HEADER FileHeader +18h IMAGE_OPTIONAL_HEADER32OptionalHeader } IMAGE_NT_HEADERS ENDS typedef struct _IMAGE_FILE_HEADER { +04h WORD Machine; // 运行平台 +06h WORD NumberOfSections; // 文件的区块数目 这里自己尝试了一下,好像不能是1个,必须是两个以上,否则程序会报错误 +08h DWORD TimeDateStamp; // 文件创建日期和时间 +0Ch DWORD PointerToSymbolTable; // 指向符号表(主要用于调试) +10h DWORD NumberOfSymbols; // 符号表中符号个数(同上) +14h WORD SizeOfOptionalHeader; // IMAGE_OPTIONAL_HEADER32 结构大小 00E0 +16h WORD Characteristics; // 文件属性 } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER; typedef struct _IMAGE_OPTIONAL_HEADER { // // Standard fields. // +18h WORD Magic; // 标志字, ROM 映像(0107h),普通可执行文件(010Bh) +1Ah BYTE MajorLinkerVersion; // 链接程序的主版本号 +1Bh BYTE MinorLinkerVersion; // 链接程序的次版本号 +1Ch DWORD SizeOfCode; // 所有含代码的节的总大小 +20h DWORD SizeOfInitializedData; // 所有含已初始化数据的节的总大小 +24h DWORD SizeOfUninitializedData; // 所有含未初始化数据的节的大小 +28h DWORD AddressOfEntryPoint; // 程序执行入口RVA +2Ch DWORD BaseOfCode; // 代码的区块的起始RVA +30h DWORD BaseOfData; // 数据的区块的起始RVA // // NT additional fields. 以下是属于NT结构增加的领域。 // +34h DWORD ImageBase; // 程序的首选装载地址 +38h DWORD SectionAlignment; // 内存中的区块的对齐大小 +3Ch DWORD FileAlignment; // 文件中的区块的对齐大小 +40h WORD MajorOperatingSystemVersion; // 要求操作系统最低版本号的主版本号 +42h WORD MinorOperatingSystemVersion; // 要求操作系统最低版本号的副版本号 +44h WORD MajorImageVersion; // 可运行于操作系统的主版本号 +46h WORD MinorImageVersion; // 可运行于操作系统的次版本号 +48h WORD MajorSubsystemVersion; // 要求最低子系统版本的主版本号 +4Ah WORD MinorSubsystemVersion; // 要求最低子系统版本的次版本号 +4Ch DWORD Win32VersionValue; // 莫须有字段,不被病毒利用的话一般为0 +50h DWORD SizeOfImage; // 映像装入内存后的总尺寸 这里不能随便改的。 +54h DWORD SizeOfHeaders; // 所有头 + 区块表的尺寸大小 +58h DWORD CheckSum; // 映像的校检和 +5Ch WORD Subsystem; // 可执行文件期望的子系统 自己尝试了下,好像只能是1 和 2 +5Eh WORD DllCharacteristics; // DllMain()函数何时被调用,默认为 0 +60h DWORD SizeOfStackReserve; // 初始化时的栈大小 100 +64h DWORD SizeOfStackCommit; // 初始化时实际提交的栈大小 0 +68h DWORD SizeOfHeapReserve; // 初始化时保留的堆大小 100 +6Ch DWORD SizeOfHeapCommit; // 初始化时实际提交的堆大小 1000 +70h DWORD LoaderFlags; // 与调试有关,默认为 0 +74h DWORD NumberOfRvaAndSizes; // 下边数据目录的项数,这个字段自Windows NT 发布以来一直是16 +78h IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; // 数据目录表 } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
http://hi.baidu.com/triumphyuan/blog/item/b642423ee436413570cf6cf9.html
然后是区块个数,在XP下好像必须是两个或者是两个以上的区块,在win7下自己尝试了一下好像也必须是两个或者两个以上的区块,百度了一下好像也没有具体的回答,不知道有没有人能够写出1个区块的PE程序来。请大家指教(当然在这个例子中,两个区块指向了同一个地方,这样就减小了PE文件的大小,但是在加载到内存后,显示两个区块的RVA分别是1000 和 2000,不知道这里的映射是如何进行的,等会再研究研究!!)
这里的PE头自己就不多介绍了,大家自己尝试着分析一下好了,能熟悉每个字段都是什么意思。呵呵
然后是数据目录表部分,在这个例子中,只用到了输入表,但是我们知道数据目录表有16项,我们在填写数据目录表个数这个字段的时候,必须是大于等于2
因为输入表在第2项的位置嘛!!但是数据目录表的16项的位置必须保留,你可以全部用0来填充!!
(三)程序入口的代码段(在第一个区块里面):
通过换算得知offset为200的时候是程序入口点,这里执行的是messageboxA 和ExitProcess两个函数
00000200 33 DB 74 13 58 53 50 83 C0 07 50 53 68 2A 10 40 3踭.XSP兝.PSh*.@ 00000210 00 68 30 10 40 00 C3 E8 E8 FF FF FF 45 78 65 44 .h0.@.描?��ExeD 00000220 49 59 00 48 65 6C 6C 6F 21 00 FF 25 00 11 40 00 IY.Hello!.�%..@. 00000230 FF 25 08 11 40 00 00 00 00 00 00 00 00 00 00 00 �%..@...........这里自己汇编基础不是很好,大家可以看看原文的分析
(四)输入表的分析
最后自己重点分析一下输入表的结构,是从310地方开始的,有两个IID
下面给大家分析一下第一个IID,画了张图片给大家参考一下:
第二个IID就不画了,不然太乱了。大家自己可以看下。
(五)要不自己来一下??
看看上面这个输入表的IID这么混乱,指来指去什么的最麻烦了,索性自己把它改的整齐一点不是很好吗??
第一次改的时候不知道哪里出差错了,一直不行,可能是函数地址计算的时候出错了,但是一直找不到错误。
没办法,只好一步一步地来了。
尝试了第二遍,发现好像这里的结构是有顺序的,随便变换顺序的话程序会出错无法运行,网上查了下资料好像也没有解释。
那么这里究竟有什么顺序呢?自己这里先研究下,等研究出来了给大伙汇报一下。呵呵