认识windows PE
PE——Portable Executable,可执行文件(.exe,.dll,.sys...)
PE文件结构
有个更详细的https://bbs.kanxue.com/upload/attach/202111/919233_VRFJBFMKXTBUQ8K.jpg
两个标志位可确认为PE文件
运行模板查看结构
DOS头部
关注两个字段e_magic和e_lfanew
e_magic是固定指纹4D5A,即MZ,e_lfanew指出PE的相对偏移地址,即PE头在内存中的位置,例如图中字段值08000000(内存地址从右往左读,高位在右边,低位在左边)
在DOS头部分,除这两字段其余字段值可自由修改,不影响PE文件的执行,如图将其余字段值全改为0
PE头
PE头即IMAGE_NT_HEADERS,也叫NT头,三部分组成:标志、标准PE头、可选PE头
Signature
标志字段,固定值50 45 00 00
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 //文件属性
注意两个字段SizeOfOptionalHeader和Characteristics
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工具验证
入口点在进程初始化和关闭时及线程创建和毁灭时被调用
节表
存放在DOS头和PE头之后,表示PE文件和内存之间的映射关系,每节占用0x28B,记录PE文件内容拷贝至内存的位置、拷贝大小及一些内存属性
每个IMAGE_SECTION_HEADER结构包含一些子属性
name
强制8字节,不够8字节时用null填充,可自定义名称或编译器自动生成
常见节数据名称
.text:代码段,是在编译或汇编结束时产生的一种块,它的内容全部是指令代码。也有的编译器将该段命名为.code
.data:初始化的数据块,是初始化的数据块,包含那些编译时被初始化的变量、字符串
.idata:输入表,包含其他外来dll的函数和数据信息,也就是输入表,也有人称之为导入表。
.rsrc:资源数据块,包含模块的全部资源数据,如图标、菜单、位图等。
.reloc:重定位表,用于保存基址的重定位表。即当装在程序不能按照连接器所指定的地址装载文件时,需要对指令或已经初始化的变量进行调整,该块中也包含了调整过程中所需要的一些数据,如果装载能够正常装在则忽略此段中的数据。
.edata:导出表,是pe文件的输出表,以供其他模块使用,并不是每个pe文件都有此数据段,因为有的文件并不需要输出一些函数,该数据段常见于动态连接库文件中。
.radata:存放调试目录、说明字符串,该数据块并不常见主要是用于存放一些调试信息。
Misc.VirtualSize
文件在对齐之前实际的大小(所谓对齐是各种数据类型都要一定的规则进行排列,是编译器对数据进行优化,增加内存的使用率和数据高效传输的操作)
这一字段的值可以随意修改,不影响文件执行
VirtualAddress
节区在内存中的偏移地址(RVA),加上 ImageBase 为内存中的真正地址
SizeOfRawData
对齐后的文件大小,文件偏移量
PointerToRawData
节在磁盘文件中所处的位置(偏移),从文件头开始算起的偏移量即与DOS头的距离
Characteristics
节的属性,可读写等。子属性如下:
导入表(Import Table)
导入表记录了PE文件使用的API以及相关的dll模块,一般来说,杀软会对导入表进行查杀,如果发现存在恶意的API,比如VirtualAlloc,CreateThread等,就会认为文件是一个恶意文件。在免杀中,我们可以通过自定义API的方式隐藏导入表中的恶意API。
动态链接通过输入表来完成,输入表中保存调用函数名和其驻留的DLL名等。
如图表示输入了KERNEL32.dll
OriginalFirstThunk和FirstThunk保存的都是指向.idata的地址
缺失太多,后面遇到具体问题再慢慢补吧
参考
https://www.freebuf.com/articles/system/329052.html