PE格式分析_5(IMAGE_OPTIONAL_HEADER)
本文最后更新于 325 天前,其中的信息可能已经有所发展或是发生改变。

IMAGE_OPTIONAL_HEADER

typedef struct _IMAGE_OPTIONAL_HEADER {
  WORD  Magic;  // 判断32位0x10b,64位0x20b  
  BYTE  MajorLinkerVersion; // 主链接器版本号,仅供参考
  BYTE  MinorLinkerVersion; // 副链接器版本号,仅供参考

  //系统分配内存不看着3个值,对于调试器有影响 
  DWORD SizeOfCode;             // 代码所占空间大小,仅供参考,OD反汇编代码,通过这个字段获得内存
                                // SizeOfCode可以尝试出极限值,让OD调试可以使用,但是会卡很久
                                // OS是从节表里算的代码段的大小

  DWORD SizeOfInitializedData;  // 已初始化数据所占空间大小,仅供参考,微软没使用
  DWORD SizeOfUninitializedData;// 未初始化数据所占空间大小,仅供参考,微软没使用

  DWORD AddressOfEntryPoint;    // ***** oep:原本的程序入口
                                //这个值可以修改,但是修改过后必须跳转到在该偏移处跳转到真正入口

  DWORD BaseOfCode; // 代码基址 (无用)
  DWORD BaseOfData; // 数据基址 (无用)

  DWORD ImageBase;  // ****** 建议模块地址:exe映射加载到内存中的首地址 = PE 0处,即实例句柄hInstance
                    // 一般而言,exe文件可遵从装载地址建议,但dll文件无法满足 (开了随机基址可能也不是这个值,是通过重定位表得到)
                    //这个值最好不要改,改的话要改动大量地方,因为函数和全局变量的地址也需要跟着改变

  DWORD SectionAlignment;   //****** 内存对齐值,数据在内存的对齐值,很多内存地址,大小都要依赖他来计算
                            // OS要求是对齐值是0x1000的倍数 ,否则拒绝运行(从win7开始)
                            //默认1000h 一个分页大小,系统管理内存是以分页为单位

  DWORD FileAlignment;      //****** 文件对齐值, 0x200 => 512, 磁盘的一个扇区大小  (vc6是1000h)
                            // OS要求是对齐值是0x200的倍数,否则拒绝运行(从win7开始)
                            // 对齐值都是2的倍数 如果把所有节合并了 就可以设为1,否则不可以随便修改因为要配合节检查

   //主副系统相关版本号   除了  MajorSubsystemVersion 不可修改,其他5个可以
  WORD  MajorOperatingSystemVersion;
  WORD  MinorOperatingSystemVersion;
  WORD  MajorImageVersion;
  WORD  MinorImageVersion;
  WORD  MajorSubsystemVersion;   //主子系统版本号   不可以修改   这里改成4可以在xp运行,系统检查这个
  WORD  MinorSubsystemVersion;   
/* VS编译的软件在低版本系统中无法运行,实际上是把这个版本号写入到了PE格式里了,版本号不对就没法运行 */

  DWORD Win32VersionValue;   // win32版本值  xp上不可以改 ,win7和win10可以修改

  //改值是通过节表计算得到的   
  DWORD SizeOfImage;     //****** 表示PE文件都加载到内存中需要多大的内存,与 SectionAlignment 对齐,改的话不可以改变分页数量(但最好对齐)
                         // 系统先拿到这个字段,看下自己是否能给这么大的内存,内存不够就不运行了
                         // 这个值是对齐以后的值
                         // 系统在加载运行之前会计算一遍PE的大小,看和这里是否一致,不一致就拒绝运行;防止随便填写值,把内存搞挂
  DWORD SizeOfHeaders;   // ****** PE头所有头加起来总大小,与 FileAlignment 对齐
                        // 选项头后面的节有多少项,是由头的大小这个字段决定的,这个字段决定软件有几个段

  DWORD CheckSum;       //校验值  3环程序随便改, 0环程序会检查,不允许改 可以用 MapFileAndCheckSum 计算值
                        // 单独看这个字段没用,这个字段必须和数字签名配合使用

  WORD  Subsystem;      //******* 子系统 不能随意修改    /subsystem  dll、exe、sys ...
                       // VS中 设置-链接器-所有选项-子系统

  WORD  DllCharacteristics;   //描述应用程序的一些相关信息(例如是否开了随机基址等)   这个字段的差异比较大
                              // 区分是否是DLL,根据宏0x0040来区分
    // 随机基址可以修改为不是随机基址,相反则不行,随机基址要在PE中存放CPU代码信息(代码重定位信息),不是随机基址就没有信息;
    // 随机基址主要防止远程攻击
// 开了随机基址后,ImageBase 字段怎么办? 仅会参考下这个字段, 反汇编工具用的ImageBase确定地址,如果开启了随机基址,反汇编的地址仅供参考
// 反汇编中的地址是否是真实的地址,要看这个程序是否随机,直接改成不随机就行
    /*
    #define IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA    0x0020  // Image can handle a high entropy 64-bit virtual address space.
    #define IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE 0x0040     // DLL can move.
    #define IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY    0x0080     // Code Integrity Image
    #define IMAGE_DLLCHARACTERISTICS_NX_COMPAT    0x0100     // Image is NX compatible    和DEP(数据执行保护)一样 堆栈段不可以执行代码,把这个标志位去掉就可以执行代码了
    #define IMAGE_DLLCHARACTERISTICS_NO_ISOLATION 0x0200     // Image understands isolation and doesn't want it
    #define IMAGE_DLLCHARACTERISTICS_NO_SEH       0x0400     // Image does not use SEH.  No SE handler may reside in this image
    #define IMAGE_DLLCHARACTERISTICS_NO_BIND      0x0800     // Do not bind this image.
    #define IMAGE_DLLCHARACTERISTICS_APPCONTAINER 0x1000     // Image should execute in an AppContainer
    #define IMAGE_DLLCHARACTERISTICS_WDM_DRIVER   0x2000     // Driver uses WDM model
    #define IMAGE_DLLCHARACTERISTICS_GUARD_CF     0x4000     // Image supports Control Flow Guard.
    #define IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE     0x8000
    */

  //这四个值可以改,但是不能改得太离谱
  DWORD SizeOfStackReserve;  //栈保留,为线程保留栈大小,一开始提交一部分,必要的时候才提交剩下的部分,就是占了一段内存,只是保留内存地址,不是保留内存,保证内存的连续性
                             // 这个值如果是4096,PE运行不起来,为什么?  保留太少了,容易溢出
  DWORD SizeOfStackCommit;   //栈提交,一开始给的内存
  DWORD SizeOfHeapReserve;   //堆保留,OS先给一块内存,然后慢慢分配;malloc的地址是有规律的,虽然不连续,但是靠的很近,其实是在一块区域给的;
                             //malloc底层是HeapAlloc,底层有很多可以申请空间的api,不同api在不同区域分配空间
  DWORD SizeOfHeapCommit;    //堆保留
/*
64位  一个进程可以给到 128T
32                    2G
*/

  DWORD LoaderFlags;     //跟调试相关,目前用不到,值可以随便改
  DWORD NumberOfRvaAndSizes;   //下面数组个数(最小可以改为2,前2个表是导入,导出表,必须要有)
 //数据目录表(成员2个 dword 第一个是内存偏移,第二个是大小 )  
 //描述PE中各种个样的表的位置和大小,每个下标对应一个固定的表(前2个不能改,导入,导出表,改了无法调API)
  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];  //柔性数组 个数由上面值决定,但是总大小为16个
    // 最多15个表,为什么会有16项呢? 最后一项是0 
    // 通过调整文件头中的SizeOfOptionalHeader 来拓展这个数组大小
    /*
    #define IMAGE_DIRECTORY_ENTRY_EXPORT          0   // Export Directory   导出函数的表
    #define IMAGE_DIRECTORY_ENTRY_IMPORT          1   // Import Directory   调用api会在这个表里有记录
    #define IMAGE_DIRECTORY_ENTRY_RESOURCE        2   // Resource Directory
    #define IMAGE_DIRECTORY_ENTRY_EXCEPTION       3   // Exception Directory
    #define IMAGE_DIRECTORY_ENTRY_SECURITY        4   // Security Directory
    #define IMAGE_DIRECTORY_ENTRY_BASERELOC       5   // Base Relocation Table 
    #define IMAGE_DIRECTORY_ENTRY_DEBUG           6   // Debug Directory
    //      IMAGE_DIRECTORY_ENTRY_COPYRIGHT       7   // (X86 usage)
    #define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    7   // Architecture Specific Data
    #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR       8   // RVA of GP
    #define IMAGE_DIRECTORY_ENTRY_TLS             9   // TLS Directory
    #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    10   // Load Configuration Directory
    #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT   11   // Bound Import Directory in headers
    #define IMAGE_DIRECTORY_ENTRY_IAT            12   // Import Address Table
    #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   13   // Delay Load Import Descriptors
    #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14   // COM Runtime descriptor
    */

} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

注解

01 链接器版本号


判断软件是用什么版本编译的手段:
MajorLinkerVersion:主链接器版本号14
MinorLinkerVersion:副链接器版本号29

file

操作系统不看这两个字段,可以随意修改,版本号仅供参考;
一般的软件不会修改这个版本号,有一定的参考价值;


02: AddressOfEntryPoint

地址概念:

Virtual Address(VA) : 00401000 程序运行起来的地址
Relative Virtual Address(RVA) : 1000 相对模块基址的偏移
File Address(FA) : PE格式中C8就是文件(指的是PE文件)地址


问题1:为什么VA用的少?
.dll 模块基址不固定,VA就没用了,因此PE结构中很少有结构体用VA表示;


问题2:RVA和FA区别?
情况1:分析文件开头的4D5A:4D5A在文件中FA是0,当程序被加载到内存之后,模块基址开始也是4D5A,RVA也是0,这个时候:RVA == FA
情况2: 分析最后的位置:OS申请内存一次申请一个页,不会一个字节一个字节申请,如下图:
前面100个字节放进前面申请4K内存,最后部分有可能放到再申请4K内存中,RVA != FA

file


问题3: AddressOfEntryPoint (EP) 是什么地址?
是RVA,OS只需要模块基址+RVA就知道在哪里开始执行代码了;
所以,OD调试,取得地址是模块基址加上RVA的值就知道了

file

file


问题4: 如果恶意的把AddressOfEntryPoint改掉怎么办?
这个时候就可以注入代码了,在stdub[]中藏点代码
Entry Point (EP)
Old Entry Point (OEP)
无论刚开始EP跳转到哪里,最终还是要跳转回OEP执行函数入口的代码;


问题5:常说找OPE,为什么要找OEP?
找到旧的OEP,填回到EP,删除恶意代码;


问题6:脱壳
如果代码是被加密的,解密的代码在恶意代码里怎么办?
这样把OEP改回去,代码还是没法运行;通过加密代码的RVA算出FA,把代码全部填回到文件去,这样就做到了在文件中提前解密了;

file


问题7:AddressOfEntryPoint值位0,是有效的PE格式吗?,可以正常运行?
有效,可以,4D5A的字符串反汇编是一个减法,一个出栈,因此要反向操作回去,才可以正常运行;

file


03 ImageBase

ImageBase 建议模块基址
命令行编译的时候可以通过 /base修改
link /subsystem:windows /base:0x01000000 pe.obj

开了随机基址,PE里面就会有一个重定位表,记录了所有需要修改的地方,没开没有

04 DataDirectory

系统会通过格式来便利这个表,而不是大小,这个大小随便改,每个字段的大小都可以改,有的软件会用这个大小来遍历表,让他崩

file

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇