LseKit 发表于 2018-2-19 18:43

【原创】浅谈XP下最小PE

本帖最后由 LseKit 于 2018-4-16 21:48 编辑

一.题记近日,科锐一季度一次的最小PE比赛来临,规则就是手写能弹出对话框的最小PE文件,当然为了照顾我们初学PE对字段还不是很熟悉,允许我们参考Mspaint.exe的PE结构.
课堂上Boss钱给我们讲解了PE最小PE思路,并带领着我们写了一个留有优化余地的185字节的版本PE文件.此文是将课堂笔记整理而成,虽力求完美,但难免因本人的学识浅薄而存在不足之处,望大神们批评指正.二.环境
[*]OS:Windows XP
[*]IDE:010 Editor
[*]参考:Mspaint.exe
* 优化PE大小的思路是基于结构重叠,结构重叠是指将两个结构合并存一个位置上.举个例子,假如一个正常PE文件的PE标识从(FOR:40h)开始,这时候将NT头挪到(FOR:04h)的位置,再将DOS头中e_lfanew成员改为0x0000004,此时DOS头与PE头重叠.
* 之所以选择XP,是因为Win7对可执行文件的检查很严格,基本没什么文章可做.不支持重叠,而XP支持重叠.三.185字节版本PE3.1 1024字节PE模版3.1.1 DOS头
[*]将Mspaint.exe中的DOS头拷贝到新建的1024.exe中.
https://blog.050k.com/wp-content/uploads/2018/04/3043a709d5801214a3b1137be5bded05.png
3.1.2 NT头
[*]将Mspaint.exe中的NT头(PE标识+PE头+可选PE头(可选PE头中目录项只拷贝4项))拷贝到新建的1024.exe中.
https://blog.050k.com/wp-content/uploads/2018/04/7e597ed4f6f93ded028b4e59485c6f25.png
3.1.3 节表
[*]将Mspaint.exe中的一项节表拷贝到新建的1024.exe中.
https://blog.050k.com/wp-content/uploads/2018/04/4cfef71426eaf70019f63ca0cc2d18c1.png
3.1.4 修改PE文件
[*]根据下列备注修改1024.exe.不重要的字段填充AA,并适当修改对齐.目录项先全部填充0.
[*]1. DOS头
struct _IMAGE_DOS_HEADER {
WORD e_magic;         //5A 4D                  //(!重要) |"MZ标记"
WORD e_cblp;         //AA CC                  //CC是为了让程序断下
WORD e_cp;               //AA AA                  
WORD e_crlc;         //AA AA                  
WORD e_cparhdr;      //AA AA                  
WORD e_minalloc;       //AA AA                  
WORD e_maxalloc;       //AA AA                  
WORD e_ss;             //AA AA                  
WORD e_sp;             //AA AA                  
WORD e_csum;         //AA AA                  
WORD e_ip;             //AA AA                  
WORD e_cs;             //AA AA                  
WORD e_lfarlc;         //AA AA                  
WORD e_ovno;         //AA AA                  
WORD e_res;         //AA AA AA AA AA AA AA AA
WORD e_oemid;          //AA AA                  
WORD e_oeminfo;      //AA AA                  
WORD e_res2;       //20个A                  
DWORD e_lfanew;      //00 00 00 40            //(!重要) |用于定位PE标识
};                                                            

2. NT头                                                         
struct _IMAGE_NT_HEADERS{                                       
DWORD Signature;       //00 00 45 50                            //(!重要) |PE标识
_IMAGE_FILE_HEADER FileHeader;                                 
_IMAGE_OPTIONAL_HEADER OptionalHeader;
}

2. 标准PE头(大小固定)
struct _IMAGE_FILE_HEADER {
WORDMachine;                               //01 4C         //(!重要)
WORDNumberOfSections;                      //00 01         //(!重要) |节总数(1)
DWORD TimeDateStamp;                         //AA AA AA AA         
DWORD PointerToSymbolTable;                  //AA AA AA AA
DWORD NumberOfSymbols;                     //AA AA AA AA
WORD SizeOfOptionalHeader;                   //00 80         //(!重要) |可选PE头的大小
WORD Characteristics;                        //01 0F         //(!重要) |可执行文件值为10F
};

3. 可选PE头((大小不固定,32和64不同))
struct _IMAGE_OPTIONAL_HEADER {
WORD Magic;                                  //01 0B         //(!重要) |10B-32位下的PE文件
BYTE MajorLinkerVersion;                     //AA            
BYTE MinorLinkerVersion;                     //AA            
DWORD SizeOfCode;                            //AA AA AA AA   
DWORD SizeOfInitializedData;               //AA AA AA AA   
DWORD SizeOfUninitializedData;               //AA AA AA AA   
DWORD AddressOfEntryPoint;                   //00 00 00 02   //(!重要) |程序入口点(02,断在CC处)
DWORD BaseOfCode;                            //AA AA AA AA   
DWORD BaseOfData;                            //AA AA AA AA   
DWORD ImageBase;                           //00 40 00 00   //(!重要) |内存镜像基址
DWORD SectionAlignment;                      //00 00 10 00   //(!重要) |内存对齐
DWORD FileAlignment;                         //00 00 02 00   //(!重要) |文件对齐
WORD MajorOperatingSystemVersion;            //AA AA         
WORD MinorOperatingSystemVersion;            //AA AA         
WORD MajorImageVersion;                      //AA AA         
WORD MinorImageVersion;                      //AA AA         
WORD MajorSubsystemVersion;                  //00 04         //(!重要) |子系统版本号            
WORD MinorSubsystemVersion;                  //AA AA         
DWORD Win32VersionValue;                     //AA AA AA AA   
DWORD SizeOfImage;                           //00 00 20 00   //(!重要) |PE文件映射到内存后的尺寸,SectionAlignment的倍数
DWORD SizeOfHeaders;                         //00 00 02 00   //(!重要) |所有头+节表按照文件对齐后的大小
DWORD CheckSum;                              //AA AA AA AA   
WORD Subsystem;                              //00 02         //(!重要) |子系统
WORD DllCharacteristics;                     //00 00         //(!重要) |
DWORD SizeOfStackReserve;                  //00 40 00 00   //(!重要) |初始化时保留的栈大小(桟最大值)
DWORD SizeOfStackCommit;                     //00 00 10 00   //(!重要) |初始化时实际提交的栈大小(实际使用桟大小)
DWORD SizeOfHeapReserve;                     //00 10 00 00   //(!重要) |初始化时保留的堆大小(堆最大值)
DWORD SizeOfHeapCommit;                      //00 01 00 00   //(!重要) |初始化时实际提交的堆大小(实际使用堆大小)
DWORD LoaderFlags;                           //AA AA AA AA   
DWORD NumberOfRvaAndSizes;                   //00 00 00 04   //(!重要) |目录项数目(4),其实最优是2项,有导入表即可
_IMAGE_DATA_DIRECTORY DataDirectory;                     //目录项(4个目录项,先全初始化为0)
};

4. 节表
#define IMAGE_SIZEOF_SHORT_NAME 8
typedef struct _IMAGE_SECTION_HEADER
{
    BYTE Name;    //EE EE EE EE EE EE EE EE               
    union                                             
      {
            DWORD PhysicalAddress;                     
            DWORD VirtualSize;
      } Misc;                            //00 00 10 00//(!重要) |提交到内存中大小
    DWORD VirtualAddress;                  //00 00 10 00//(!重要) |提交到内存中的偏移
    DWORD SizeOfRawData;                   //00 00 20 00//(!重要) |节在文件中对齐后的尺寸
    DWORD PointerToRawData;                //00 00 02 00//(!重要) |节在文件中的偏移                        
    DWORD PointerToRelocations;            //AA AA AA AA         
    DWORD PointerToLinenumbers;            //AA AA AA AA         
    WORD NumberOfRelocations;            //AA AA      
    WORD NumberOfLinenumbers;            //AA AA
    DWORD Characteristics;               //60 AA AA AA//(!重要) |节的属性 高位给运行属性即可
};

[*]附上修改好的图.
https://blog.050k.com/wp-content/uploads/2018/04/fd412c0e876c8ae2789576cd713e7a07.png
3.1.5 节数据填充00
[*]因为节表中PointerToRawData的值是200h,SizeOfRawData的值也是200h.所以要在FOR:100h-FOR:3F0h的位置填充上00,保持对齐.
https://blog.050k.com/wp-content/uploads/2018/04/ff37bfc2f5ff3f7c127e588aa136cb36.png
https://blog.050k.com/wp-content/uploads/2018/04/2e181f493a2e578ed96b678341a4ae54.png
[*]填充后文件大小为1024KB.
https://blog.050k.com/wp-content/uploads/2018/04/9cb89380a78774402340af6492b64704.png
3.1.6 0x80000003错误
[*]将修改好的文件保存为1024.exe,双击运行,如果修改无误,会报0x80000003错误,此时不要灰心,出现这个错误说明PE格式正确.然后以此为模版新建一个文件开始重叠结构.(忘了怎么设置,重现XP经典又亲切的大红叉...)
https://blog.050k.com/wp-content/uploads/2018/04/18840fe163df280d2381d7e2e924c8cf.png
3.2 185字节PE文件3.2.1 DOS头
[*]以上文中的1024.exe文件为模版,在010Edid中新建一名为180.EXE的文件.并将1024.exe中的DOS拷贝到180.EXE中.
https://blog.050k.com/wp-content/uploads/2018/04/09a1ea0c3a41a02600b2938dcb70561a.png
3.2.2 初次重叠-NT头
[*]将1024.EXE中PE标识(FOR:40h)-数据目录(FOR:D7h)处的NT头数据往185.EXE中DOS头(FOR:04h)的位置往后覆盖.
https://blog.050k.com/wp-content/uploads/2018/04/839aa10a757a541a2b511c0a3d207e51.png
3.2.3 修改DOS头e_lfanew
[*]因为上文中将NT头与DOS头重叠,DOS头中e_lfanew(用于定位PE标识的偏移)成员(FOR:3Ch),被PE头中的SectionAlignment(内存对齐)对齐覆盖成(00 00 10 00h).而我们现在的PE标识如今在(FOR:04h),巧的是WinXp最小支持4对齐.所以我们将内存对齐(FOR:3Ch)与文件对齐(FOR:40h)都更改为(00 00 00 04h).
https://blog.050k.com/wp-content/uploads/2018/04/b27dba33156b4a0ef70822a5e5c1e287.png
3.2.4 修改节表3.2.4.1 拷贝节表
[*]将1024.EXE中节表拷贝到185.EXE尾部.
https://blog.050k.com/wp-content/uploads/2018/04/eb253eb12702fe7f2716bc4ee0377f3c.png
3.2.4.2 修改节表Name成员
[*]因为节名称只是说明性意义,操作系统不参考,所以此处8字节可以随意更改.将(FOR:9Ch)-(FOR:A3h)的位置填充EE(EE并没有特定意义,只是为了区分).
3.2.4.3 修改节表Misc成员
[*]因为我们整个头部所占的大小为C4h,将Misc成员(FOR:A4h)修改为(00 00 00 C4h).
3.2.4.4 修改节表VirtualAddress成员
[*]两节合一,所以在内存中从0偏移开始.将VirtualAddress成员(FOR:A8h)修改为(00 00 00 00h)
3.2.4.5 修改节表SizeOfRawData成员
[*]我们整个头部大小为C4h,满足4对齐,可将SizeOfRawData成员(FOR:ACh)修改为(00 00 00 C4h).
3.2.4.6 修改节表PointerToRawData成员
[*]文件偏移从0h开始.将PointerToRawData成员(FOR:B0h)修改为(00 00 00 00h).
https://blog.050k.com/wp-content/uploads/2018/04/d394739877c15083be4ef916e604d909.png3.2.5 修改可选PE头SizeOfImage成员
[*]映射到内存中要以1000对齐,所以将SizeOfImage成员(FOR:54h)修改为(00 00 10 00h).
https://blog.050k.com/wp-content/uploads/2018/04/80decfa71fa73f1b93f6e0dd64c3ee8e.png
3.2.6 修改可选PE头SizeOfHeaders成员
[*]所有头与节表的大小为C4h,正好是4对齐,所以将SizeOfHeaders成员(FOR:58h)修改为(C4 00 00 00h).
https://blog.050k.com/wp-content/uploads/2018/04/62ad1dc7d1536e56021893aa0472e731.png
3.2.7 修改可选PE头SizeOfStackReserve|SizeOfStackCommit|SizeOfHeapReserve|SizeOfHeapCommit成员
[*]这四个成员分别为桟保留(FOR:64h)|桟提交(FOR:68h)|堆保留(FOR:6Ch)|堆提交(FOR:70h). 高字节保留为0即可,低3字节可填充为AA.
https://blog.050k.com/wp-content/uploads/2018/04/eaec71d1ab304458bb36be16dd2170a7.png
3.2.8 再见0x80000003错误
[*]将刚修改的文件保存,运行.再次弹出不久前见到的0x80000003错误报告.说明刚才修改的格式正确.
https://blog.050k.com/wp-content/uploads/2018/04/8baa90167697511307be3b9b874d599a.png
3.2.9 导入表3.2.9.1 API-MessageBoxa
[*]MessageBoxa我们从(FOR:Ch)填充.
https://blog.050k.com/wp-content/uploads/2018/04/4fd39fc2aa08d3d4300c3df4c47ef0fa.png
3.2.9.2 库名称-user32.dll
[*]库名称 user32 我们从(FOR:30h)填充.
https://blog.050k.com/wp-content/uploads/2018/04/0779fbbf8f6eb51188ebb62a14c2fc63.png
3.2.9.3 导入表Name成员与FirstThunk成员
[*]Name成员从(FOR:B4h)填充(00 00 00 30h).
[*](FOR:44h)刚好有8个字节可用,此处可以放IAT表.所以FirstThunk成员(FOR:B8h)我们填充(00 00 00 44h).
https://blog.050k.com/wp-content/uploads/2018/04/273877b25b89c2fddeb2e2f2962a43d0.png
3.2.9.4 IAT表
[*]IAT表以0结尾,所以在(FOR:48h)填充4字节0.
[*]MessageBoxA在(FOR:04h)处,前面还有两字节字段,所以在(FOR:44h)填充(00 00 00 44h).
https://blog.050k.com/wp-content/uploads/2018/04/3d585e5875970575ab1462aef1cf463b.png
3.2.9.5 导入表目录项
[*]导入表起始位置为(FOR:A8h),因此目录项第二项(FOR:84h)填入(00 00 00 A8).
https://blog.050k.com/wp-content/uploads/2018/04/90044deea984837e3025b683dd226a32.png
3.2.10 验证导入表
[*]保存修改,用OD打开185.EXE文件.转到内存0x00400000,查看导入表是否填充正确.
https://blog.050k.com/wp-content/uploads/2018/04/6f41fc92c592d128305709184cdfab4c.png
[*]MessageBoxA的地址已经填入.说明我们导入表填充正确.
3.2.11 对话框内容与标题
[*]对话框标题MyPE,就在(FOR:02h)填充"My",与后面的"PE"刚好组成字符串"MyPE".
https://blog.050k.com/wp-content/uploads/2018/04/754dfc72db6d2c22474ba1d238510ad5.png
[*]对话框内容就选(FOR:0Ch)其实的字符串"MessageBoxA"吧.
https://blog.050k.com/wp-content/uploads/2018/04/edd96bac78ac8953d930c2c33bfd3617.png
3.2.12 调用代码
[*]一般情况下,调用185.EXE中MessageBoxA的汇编代码如下:
6A 00            push 068 02 00 40 00      push 0x0040000268 0C 00 40 00      push 0x0040000c6A 00            push 0FF 15 44 00 40 00 call [0x00400044]C3                  ret
[*]但此文件中明显没有足够的空间让我们正常填写此代码,只能通过跳转来塞入指令.
从0x1E开始填充6A 00            push 068 02 00 40 00      push 0x00400002EB 3E             jmp0x00400065从0x65开始填充68 0C 00 40 00      push 0x0040000c6A 00               push 0FF 15 44 00 40 00 call [0x00400044]C3                  rethttps://blog.050k.com/wp-content/uploads/2018/04/1160d936de25ad5c492880d815d79a29.png3.2.13 修改OEP
[*]将OEP(FOR:2Ch)修改为(00 00 00 1Eh)
https://blog.050k.com/wp-content/uploads/2018/04/dd8614df5fff80340681bdb5e5498ff3.png
3.2.14 PE特性PE有一个特性,PE文件加载到内存后,不足一个分页的位置会用0填充,也就是说,PE在文件末尾,有初值为0的默认数据,也就是说如果文件末尾的字节为0,就可以把这个字节删掉,进而减小文件体积.
而且操作系统不对执行属性负责(前提为DEP数据执行保护.如果开了,那么必须要有执行属性才能运行,默认DEP关闭),即使节中没有执行属性,也可运行.如果把读属性去掉,会报C00000005.如果手写PE出现C05错误,那么说明PE格式没有问题,请检查代码.操作系统装载,尝试运行没有错误,因为没有读的权限,所以会报错.如果格式错误,则提示不是有效XX位程序.
而文件头默认就有读属性,所以本文件将节表中文件属性去掉并不影响运行.
[*]从本文件中从(FOR:BCh)-(FOR:C3h)删掉.保存文件后检验成果吧.
https://blog.050k.com/wp-content/uploads/2018/04/bb3ce75b7ffe2d68aa74683dda2eb09d.png
https://blog.050k.com/wp-content/uploads/2018/04/d5e75e2949fa1862b83d459b6f045e3a.png
[*]185字节,完美运行.
3.2.15 再减小体积
[*]节表中节表名字段还剩8字节,可以将导出表(FOR:B4h)挪到节表名(FOR:9Ch)的位置覆盖.
https://blog.050k.com/wp-content/uploads/2018/04/5acda38a176c6dcaf107c66ec0741a47.png
[*]将目录项中导出表(FOR:84h)目录更新为新地址(00 00 00 90h).
https://blog.050k.com/wp-content/uploads/2018/04/d3bd9163be94cfebe467d82fe06b3ca0.png
[*]删除尾部,保存运行.
https://blog.050k.com/wp-content/uploads/2018/04/7ec42698e206b5f60de2a180a5c35501.png
https://blog.050k.com/wp-content/uploads/2018/04/6b84ab1007b22de5776fec5491eff2c0.png四.尾声
[*]上文是将课堂笔记整理而成,因本人学识浅薄难免有遗漏之处,若有不足之处望各位大哥指出让小弟改正.
[*]最小PE只是为了初学PE的我们熟悉PE结构而已,实战中也并没有谁抠字节写个最小PE文件就为了弹个对话框吧...并且在Win7以上的系统检查越来越严格,不支持重叠.
[*]最小PE比赛中,为了避免在网上参考现成的代码,不能在第四字节放NT头,其他地方随意,因为要做到最优,只能在第四字节放NT头.比赛过后,参考了其他同学的思路,完成了141字节的最小PE.过两天放假了再整理141字节最小PE的笔记发上来吧.
[*]时间不早了,洗洗睡吧...

LseKit 发表于 2018-2-19 22:19

hejialong 发表于 2018-2-19 21:53
弱弱的问一下 你们写文章都用的什么写的呀 各论坛排版不一样呀 ..

我用为之笔记的 markdown .吾爱破解编辑的时候把纯文本的钩去掉,就可以直接把格式复制进来了.除了代码需要重新排版,其他的基本和笔记的格式一样.

LseKit 发表于 2018-2-19 21:12

t5456290 发表于 2018-2-19 19:49
请问什麽是pe "重叠"?

兄弟你认真看完就知道什么是重叠了.开头处我讲了什么是重叠,下面也有重叠的例子和文字说明.
3.2.2 初次重叠-NT头 这有配图,已经将NT头和DOS头重叠了.

snail_ 发表于 2018-2-19 19:07

前排....

cuizheng0419 发表于 2018-2-19 19:16

谢谢大佬的细致教导

绝美之城 发表于 2018-2-19 19:43

看不懂,好像很牛逼的。

t5456290 发表于 2018-2-19 19:49

请问什麽是pe "重叠"?

linuxprobe 发表于 2018-2-19 19:51

PE只能是在XP系统才能运行吗?

yaksacn2001 发表于 2018-2-19 21:08

谢谢@Thanks!

LseKit 发表于 2018-2-19 21:11

linuxprobe 发表于 2018-2-19 19:51
PE只能是在XP系统才能运行吗?

PE是Windows的一种文件格式,之所以选择XP,是因为Win7对可执行文件的检查很严格,基本没什么文章可做.不支持重叠,而XP支持重叠.

one486 发表于 2018-2-19 21:40

页: [1] 2 3
查看完整版本: 【原创】浅谈XP下最小PE