吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 11704|回复: 24
收起左侧

[原创] 【原创】浅谈XP下最小PE

  [复制链接]
LseKit 发表于 2018-2-19 18:43
本帖最后由 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中.

3.1.2 NT头
  • 将Mspaint.exe中的NT头(PE标识+PE头+可选PE头(可选PE头中目录项只拷贝4项))拷贝到新建的1024.exe中.

3.1.3 节表
  • 将Mspaint.exe中的一项节表拷贝到新建的1024.exe中.

3.1.4 修改PE文件
  • 根据下列备注修改1024.exe.不重要的字段填充AA,并适当修改对齐.目录项先全部填充0.
  • [C++] 纯文本查看 复制代码
    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[4];         //AA AA AA AA AA AA AA AA  
    WORD e_oemid;          //AA AA                    
    WORD e_oeminfo;        //AA AA                    
    WORD e_res2[10];       //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 {
    WORD  Machine;                               //01 4C           //(!重要) 
    WORD  NumberOfSections;                      //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[16];                       //目录项(4个目录项,先全初始化为0)
    };
    
    4. 节表
    #define IMAGE_SIZEOF_SHORT_NAME 8
    typedef struct _IMAGE_SECTION_HEADER
     {
        BYTE Name[IMAGE_SIZEOF_SHORT_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  //(!重要) |节的属性 高位给运行属性即可
    };

  • 附上修改好的图.

3.1.5 节数据填充00
  • 因为节表中PointerToRawData的值是200h,SizeOfRawData的值也是200h.所以要在FOR:100h-FOR:3F0h的位置填充上00,保持对齐.

  • 填充后文件大小为1024KB.

3.1.6 0x80000003错误
  • 将修改好的文件保存为1024.exe,双击运行,如果修改无误,会报0x80000003错误,此时不要灰心,出现这个错误说明PE格式正确.然后以此为模版新建一个文件开始重叠结构.(忘了怎么设置,重现XP经典又亲切的大红叉...)

3.2 185字节PE文件3.2.1 DOS头
  • 以上文中的1024.exe文件为模版,在010Edid中新建一名为180.EXE的文件.并将1024.exe中的DOS拷贝到180.EXE中.

3.2.2 初次重叠-NT头
  • 将1024.EXE中PE标识(FOR:40h)-数据目录(FOR:D7h)处的NT头数据往185.EXE中DOS头(FOR:04h)的位置往后覆盖.

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).

3.2.4 修改节表3.2.4.1 拷贝节表
  • 将1024.EXE中节表拷贝到185.EXE尾部.

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).
3.2.5 修改可选PE头SizeOfImage成员
  • 映射到内存中要以1000对齐,所以将SizeOfImage成员(FOR:54h)修改为(00 00 10 00h).

3.2.6 修改可选PE头SizeOfHeaders成员
  • 所有头与节表的大小为C4h,正好是4对齐,所以将SizeOfHeaders成员(FOR:58h)修改为(C4 00 00 00h).

3.2.7 修改可选PE头SizeOfStackReserve|SizeOfStackCommit|SizeOfHeapReserve|SizeOfHeapCommit成员
  • 这四个成员分别为桟保留(FOR:64h)|桟提交(FOR:68h)|堆保留(FOR:6Ch)|堆提交(FOR:70h). 高字节保留为0即可,低3字节可填充为AA.

3.2.8 再见0x80000003错误
  • 将刚修改的文件保存,运行.再次弹出不久前见到的0x80000003错误报告.说明刚才修改的格式正确.

3.2.9 导入表3.2.9.1 API-MessageBoxa
  • MessageBoxa我们从(FOR:Ch)填充.

3.2.9.2 库名称-user32.dll
  • 库名称 user32 我们从(FOR:30h)填充.

3.2.9.3 导入表Name成员与FirstThunk成员
  • Name成员从(FOR:B4h)填充(00 00 00 30h).
  • (FOR:44h)刚好有8个字节可用,此处可以放IAT表.所以FirstThunk成员(FOR:B8h)我们填充(00 00 00 44h).

3.2.9.4 IAT表
  • IAT表以0结尾,所以在(FOR:48h)填充4字节0.
  • MessageBoxA在(FOR:04h)处,前面还有两字节字段,所以在(FOR:44h)填充(00 00 00 44h).

3.2.9.5 导入表目录项
  • 导入表起始位置为(FOR:A8h),因此目录项第二项(FOR:84h)填入(00 00 00 A8).

3.2.10 验证导入表
  • 保存修改,用OD打开185.EXE文件.转到内存0x00400000,查看导入表是否填充正确.
  • MessageBoxA的地址已经填入.说明我们导入表填充正确.
3.2.11 对话框内容与标题
  • 对话框标题MyPE,就在(FOR:02h)填充"My",与后面的"PE"刚好组成字符串"MyPE".
  • 对话框内容就选(FOR:0Ch)其实的字符串"MessageBoxA"吧.

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             jmp  0x004000650x65开始填充68 0C 00 40 00      push 0x0040000c6A 00               push 0FF 15 44 00 40 00 call [0x00400044]C3                  ret3.2.13 修改OEP
  • 将OEP(FOR:2Ch)修改为(00 00 00 1Eh)

3.2.14 PE特性
PE有一个特性,PE文件加载到内存后,不足一个分页的位置会用0填充,也就是说,PE在文件末尾,有初值为0的默认数据,也就是说如果文件末尾的字节为0,就可以把这个字节删掉,进而减小文件体积.
而且操作系统不对执行属性负责(前提为DEP数据执行保护.如果开了,那么必须要有执行属性才能运行,默认DEP关闭),即使节中没有执行属性,也可运行.如果把读属性去掉,会报C00000005.如果手写PE出现C05错误,那么说明PE格式没有问题,请检查代码.操作系统装载,尝试运行没有错误,因为没有读的权限,所以会报错.如果格式错误,则提示不是有效XX位程序.
而文件头默认就有读属性,所以本文件将节表中文件属性去掉并不影响运行.
  • 从本文件中从(FOR:BCh)-(FOR:C3h)删掉.保存文件后检验成果吧.

  • 185字节,完美运行.
3.2.15 再减小体积
  • 节表中节表名字段还剩8字节,可以将导出表(FOR:B4h)挪到节表名(FOR:9Ch)的位置覆盖.
  • 将目录项中导出表(FOR:84h)目录更新为新地址(00 00 00 90h).
  • 删除尾部,保存运行.

四.尾声
  • 上文是将课堂笔记整理而成,因本人学识浅薄难免有遗漏之处,若有不足之处望各位大哥指出让小弟改正.
  • 最小PE只是为了初学PE的我们熟悉PE结构而已,实战中也并没有谁抠字节写个最小PE文件就为了弹个对话框吧...并且在Win7以上的系统检查越来越严格,不支持重叠.
  • 最小PE比赛中,为了避免在网上参考现成的代码,不能在第四字节放NT头,其他地方随意,因为要做到最优,只能在第四字节放NT头.比赛过后,参考了其他同学的思路,完成了141字节的最小PE.过两天放假了再整理141字节最小PE的笔记发上来吧.
  • 时间不早了,洗洗睡吧...

免费评分

参与人数 8威望 +1 吾爱币 +20 热心值 +7 收起 理由
Hmily + 1 + 10 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
cn52pojie + 1 + 1 热心回复!
erh + 1 + 1 热心回复!
Ganlv + 3 + 1 非常感谢楼主的分析,很到位
hejialong + 2 + 1 谢谢@Thanks!
yaksacn2001 + 1 + 1 谢谢@Thanks!
liphily + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
xiaofeiyang + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

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

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

免费评分

参与人数 1吾爱币 +2 热心值 +1 收起 理由
hejialong + 2 + 1 谢谢@Thanks!

查看全部评分

 楼主| 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
提示: 作者被禁止或删除 内容自动屏蔽
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-27 02:36

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表