认识windows PE

认识windows PE

PE——Portable Executable,可执行文件(.exe,.dll,.sys...)

PE文件结构

image-20230112210346496

有个更详细的https://bbs.kanxue.com/upload/attach/202111/919233_VRFJBFMKXTBUQ8K.jpg

两个标志位可确认为PE文件

image-20230112204755083

运行模板查看结构

image-20230112210636186

DOS头部

关注两个字段e_magice_lfanew

image-20230112212816658

e_magic是固定指纹4D5A,即MZ,e_lfanew指出PE的相对偏移地址,即PE头在内存中的位置,例如图中字段值08000000(内存地址从右往左读,高位在右边,低位在左边)

在DOS头部分,除这两字段其余字段值可自由修改,不影响PE文件的执行,如图将其余字段值全改为0

image-20230112213236076

PE头

PE头即IMAGE_NT_HEADERS,也叫NT头,三部分组成:标志、标准PE头、可选PE头

image-20230112213434429

Signature

标志字段,固定值50 45 00 00

image-20230112214130289

IMAGE_FILE_HEADER

结构如下

// 00000084 - 00000085 8664 = Machine                   //运行平台处理器的类型
// 00000086 - 00000087 000D = NumberOfSections           //文件的区块数(节的数量)
// 00000088 - 0000008B 00000000 = TimeDateStamp         //编译器创建PE时的时间戳
// 0000008C - 0000008F 001B6600 = PointerToSymbolTable   //指向符号表(调试)
// 00000090 - 00000093 000008F3 = NumberOfSymbols       //符号表中符号的个数(调试)
// 00000094 - 00000095 00F0 = SizeOfOptionalHeader      //标识扩展PE头(IMAGE_OPTIONAL_HEADER32)大小
// 00000096 - 00000097 0222 = Characteristics           //文件属性

image-20230112215854231

注意两个字段SizeOfOptionalHeaderCharacteristics

SizeOfOptionalHeader字段标明了PE文件是32位还是64位,相应位数的字段值分别为224(E0 00)和240(F0 00)

Characteristics

表明文件属性,选择几个值的运算得到

#define IMAGE_FILE_RELOCS_STRIPPED     0x0001 // 文件中不存在重定位信息
#define IMAGE_FILE_EXECUTABLE_IMAGE    0x0002 // 文件可执行
#define IMAGE_FILE_LINE_NUMS_STRIPPED   0x0004 // 文件中不存在行信息
#define IMAGE_FILE_LOCAL_SYMS_STRIPPED   0x0008 // 文件中不存在符号信息
#define IMAGE_FILE_AGGRESIVE_WS_TRIM    0x0010 // 调整工作集
#define IMAGE_FILE_LARGE_ADDRESS_AWARE   0x0020 // 程序能处理大于2G的地址
#define IMAGE_FILE_BYTES_REVERSED_LO    0x0080 // 小尾方式
#define IMAGE_FILE_32BIT_MACHINE      0x0100 // 只在32位平台上运行
#define IMAGE_FILE_DEBUG_STRIPPED     0x0200 // 不包含调试信息
#define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP 0x0400 // 不能从可移动盘运行
#define IMAGE_FILE_NET_RUN_FROM_SWAP    0x0800 // 不能从网络运行 
#define IMAGE_FILE_SYSTEM          0x1000 // 系统文件(如驱动程序),不能直接运行
#define IMAGE_FILE_DLL            0x2000 // 是一个dll文件
#define IMAGE_FILE_UP_SYSTEM_ONLY      0x4000 // 文件不能在多处理器计算机上运行
#define IMAGE_FILE_BYTES_REVERSED_HI     0x8000 // 大尾方式

IMAGE_OPTIONAL_HEADER64

可选头,子属性:

typedef struct _IMAGE_OPTIONAL_HEADER
{
    WORD   Magic;                            //* PE标志字:32位(0x10B),64位(0x20B)
    BYTE   MajorLinkerVersion;               //  主链接器版本号
    BYTE   MinorLinkerVersion;               //  副链接器版本号
    DWORD  SizeOfCode;                        //  代码所占空间大小(代码节大小)
    DWORD  SizeOfInitializedData;         //  已初始化数据所占空间大小
    DWORD  SizeOfUninitializedData;           //  未初始化数据所占空间大小
    DWORD  AddressOfEntryPoint;               //* 程序执行入口RVA,(w)(Win)mainCRTStartup:即0D首次断下来的自进程地址
    DWORD  BaseOfCode;                        //  代码段基址
    DWORD  BaseOfData;                        //  数据段基址
    DWORD  ImageBase;                     //* 内存加载基址,exe默认0x400000,dll默认0x10000000
    DWORD  SectionAlignment;              //* 节区数据在内存中的对齐值,一定是4的倍数,一般是0x1000(4096=4K)
    DWORD  FileAlignment;                 //* 节区数据在文件中的对齐值,一般是0x200(磁盘扇区大小512)
    WORD   MajorOperatingSystemVersion;      //  要求操作系统最低版本号的主版本号
    WORD   MinorOperatingSystemVersion;      //  要求操作系统最低版本号的副版本号
    WORD   MajorImageVersion;                //  可运行于操作系统的主版本号
    WORD   MinorImageVersion;                //  可运行于操作系统的次版本号
    WORD   MajorSubsystemVersion;            //  主子系统版本号:不可修改
    WORD   MinorSubsystemVersion;            //  副子系统版本号
    DWORD  Win32VersionValue;             //  版本号:不被病毒利用的话一般为0,XP中不可修改
    DWORD  SizeOfImage;                       //* PE文件在进程内存中的总大小,与SectionAlignment对齐
    DWORD  SizeOfHeaders;                 //* PE文件头部在文件中的按照文件对齐后的总大小(所有头 + 节表)
    DWORD  CheckSum;                      //  对文件做校验,判断文件是否被修改:3环无用,MapFileAndCheckSum获取
    WORD   Subsystem;                        //  子系统,与连接选项/system相关:1=驱动程序,2=图形界面,3=控制台/Dll
    WORD   DllCharacteristics;               //  文件特性
    DWORD  SizeOfStackReserve;                //  初始化时保留的栈大小
    DWORD  SizeOfStackCommit;             //  初始化时实际提交的栈大小
    DWORD  SizeOfHeapReserve;             //  初始化时保留的堆大小
    DWORD  SizeOfHeapCommit;              //  初始化时实际提交的堆大小
    DWORD  LoaderFlags;                       //  已废弃,与调试有关,默认为 0
    DWORD  NumberOfRvaAndSizes;               //  下边数据目录的项数,此字段自Windows NT发布以来,一直是16
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];// 数据目录表
} IMAGE_OPTIONAL_HEADER32, * PIMAGE_OPTIONAL_HEADER32;

注意几个字段,程序的真正入口点 = ImageBase + AddressOfEntryPoint,通过pe工具验证

image-20230113085657569

image-20230113085616484

入口点在进程初始化和关闭时及线程创建和毁灭时被调用

节表

存放在DOS头和PE头之后,表示PE文件和内存之间的映射关系,每节占用0x28B,记录PE文件内容拷贝至内存的位置、拷贝大小及一些内存属性

image-20230113092806728

每个IMAGE_SECTION_HEADER结构包含一些子属性

image-20230113093010375

name

强制8字节,不够8字节时用null填充,可自定义名称或编译器自动生成

常见节数据名称

.text:代码段,是在编译或汇编结束时产生的一种块,它的内容全部是指令代码。也有的编译器将该段命名为.code
.data:初始化的数据块,是初始化的数据块,包含那些编译时被初始化的变量、字符串
.idata:输入表,包含其他外来dll的函数和数据信息,也就是输入表,也有人称之为导入表。
.rsrc:资源数据块,包含模块的全部资源数据,如图标、菜单、位图等。
.reloc:重定位表,用于保存基址的重定位表。即当装在程序不能按照连接器所指定的地址装载文件时,需要对指令或已经初始化的变量进行调整,该块中也包含了调整过程中所需要的一些数据,如果装载能够正常装在则忽略此段中的数据。
.edata:导出表,是pe文件的输出表,以供其他模块使用,并不是每个pe文件都有此数据段,因为有的文件并不需要输出一些函数,该数据段常见于动态连接库文件中。
.radata:存放调试目录、说明字符串,该数据块并不常见主要是用于存放一些调试信息。

Misc.VirtualSize

文件在对齐之前实际的大小(所谓对齐是各种数据类型都要一定的规则进行排列,是编译器对数据进行优化,增加内存的使用率和数据高效传输的操作)

这一字段的值可以随意修改,不影响文件执行

VirtualAddress

节区在内存中的偏移地址(RVA),加上 ImageBase 为内存中的真正地址

SizeOfRawData

对齐后的文件大小,文件偏移量

PointerToRawData

节在磁盘文件中所处的位置(偏移),从文件头开始算起的偏移量即与DOS头的距离

Characteristics

节的属性,可读写等。子属性如下:

image-20230114201228331

导入表(Import Table)

导入表记录了PE文件使用的API以及相关的dll模块,一般来说,杀软会对导入表进行查杀,如果发现存在恶意的API,比如VirtualAlloc,CreateThread等,就会认为文件是一个恶意文件。在免杀中,我们可以通过自定义API的方式隐藏导入表中的恶意API。

动态链接通过输入表来完成,输入表中保存调用函数名和其驻留的DLL名等。

如图表示输入了KERNEL32.dll

image-20230114205956308

OriginalFirstThunk和FirstThunk保存的都是指向.idata的地址

缺失太多,后面遇到具体问题再慢慢补吧

参考

https://www.freebuf.com/articles/system/329052.html

https://0range-x.github.io/2021/08/28/PE%E7%BB%93%E6%9E%84/

https://bbs.kanxue.com/thread-270210.htm#msg_header_h3_2

发表评论