手动PE编辑 写出MessageBox

自己第一遍想手动写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;

其中有个sizeofimage字段一开始一直搞不清楚,自己就参考了下面这篇文章

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这么混乱,指来指去什么的最麻烦了,索性自己把它改的整齐一点不是很好吗??

第一次改的时候不知道哪里出差错了,一直不行,可能是函数地址计算的时候出错了,但是一直找不到错误。

没办法,只好一步一步地来了。

尝试了第二遍,发现好像这里的结构是有顺序的,随便变换顺序的话程序会出错无法运行,网上查了下资料好像也没有解释。

那么这里究竟有什么顺序呢?自己这里先研究下,等研究出来了给大伙汇报一下。呵呵




阅读更多

更多精彩内容