Windows内核分析索引目录:https://www.cnblogs.com/onetrainee/p/11675224.html
PE文件
1. DOS头文件
DOS中
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header
WORD e_magic; // Magic number
WORD e_cblp; // Bytes on last page of file
WORD e_cp; // Pages in file
WORD e_crlc; // Relocations
WORD e_cparhdr; // Size of header in paragraphs
WORD e_minalloc; // Minimum extra paragraphs needed
WORD e_maxalloc; // Maximum extra paragraphs needed
WORD e_ss; // Initial (relative) SS value
WORD e_sp; // Initial SP value
WORD e_csum; // Checksum
WORD e_ip; // Initial IP value
WORD e_cs; // Initial (relative) CS value
WORD e_lfarlc; // File address of relocation table
WORD e_ovno; // Overlay number
WORD e_res[4]; // Reserved words
WORD e_oemid; // OEM identifier (for e_oeminfo)
WORD e_oeminfo; // OEM information; e_oemid specific
WORD e_res2[10]; // Reserved words
LONG e_lfanew; // File address of new exe header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
2. NT头
NT头包括三类:PE指纹、NT文件头、NT可选头
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
1)NT文件头
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; // 可以运行在什么CPU上
WORD NumberOfSections; // 表示节的数量
DWORD TimeDateStamp; // 编译时间
DWORD PointerToSymbolTable; // 调试相关
DWORD NumberOfSymbols; // 调试相关
WORD SizeOfOptionalHeader; // 可选头PE的大小
WORD Characteristics; // 文件属性(动态机制可以被修改)
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
2)NT文件头
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic; // 32位 或 64位
BYTE MajorLinkerVersion; // 链接器主版本号
BYTE MinorLinkerVersion; // 链接器次版本号
DWORD SizeOfCode; // 代码节数据总和
DWORD SizeOfInitializedData; // 初始化的数据总和
DWORD SizeOfUninitializedData; // 未初始化的数据总和
DWORD AddressOfEntryPoint; // 代码执行入口
DWORD BaseOfCode; // 代码开始的基址 编译器填写 没有
DWORD BaseOfData; // 数据开始的基址 编译器填写 没有
DWORD ImageBase; // 内存镜像基址
DWORD SectionAlignment; // 内存对齐
DWORD FileAlignment; // 文件对齐
WORD MajorOperatingSystemVersion; // 操作系统主版本号
WORD MinorOperatingSystemVersion; // 操作系统次版本号
WORD MajorImageVersion; // PE文件自身主版本号
WORD MinorImageVersion; // PE文件自身次版本号
WORD MajorSubsystemVersion; // 运行时所需子系统版本号
WORD MinorSubsystemVersion; // 运行时所需子系统版本号
DWORD Win32VersionValue; // 子系统版本的值 必须为0
DWORD SizeOfImage; // 内存中整个PE文件的映射的尺寸
DWORD SizeOfHeaders; // 所有头+节表对齐后的大小,否则加载会出错
DWORD CheckSum; // 校验和 一些系统文件有要求,用来判断文件是否被修改
WORD Subsystem; // 子系统 驱动(1) 图形(2) 控制台、DLL(3)
WORD DllCharacteristics; // 文件特性(不是针对DLL)
DWORD SizeOfStackReserve; // 初始化时保留的栈大小
DWORD SizeOfStackCommit; // 初始化时实际提交的栈大小
DWORD SizeOfHeapReserve; // 初始化时保留的堆大小
DWORD SizeOfHeapCommit; // 初始化时实际提交的大小
DWORD LoaderFlags; // 调试目录
DWORD NumberOfRvaAndSizes; // 目录项数据
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
3.PE节表
NT可选头最后部分有一个 IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES] 。
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // 节区名称,可自定义,只截取前8个
union { // 该节没有对齐前的真实尺寸,该值可以不准确
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc;
DWORD VirtualAddress; // 在内存中的偏移地址,加上ImageBase才是真正的内存地址
DWORD SizeOfRawData; // 节在文件中对齐后的尺寸
DWORD PointerToRawData; // 节在文件中对齐后的尺寸
DWORD PointerToRelocations; // 节区在文件中的偏移
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics; // 节的属性
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
1)没有初始值的全局变量,在文件中没有位置,但一旦加载到内存其会存在位置。
4. RVA 与 FOA 的转换
RVA 相对虚拟地址 FOA 文件偏移地址
5. 导出表
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name; //
DWORD Base; // 基数
DWORD NumberOfFunctions;
DWORD NumberOfNames;
DWORD AddressOfFunctions; // 函数地址表
DWORD AddressOfNames; // 名称地址表
DWORD AddressOfNameOrdinals; // 名称序号表
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
其操作如下图所示:
① 如果直接用序号来找函数,直接搜索函数地址表即可,因为其是按照函数索引来进行排序的。
② 函数名称表,其是按照字母顺序排序的,其结合函数序号表来进行找到其地址。
比如找Plus函数,其通过名字对比其在函数表中的第2个位置,其查表Table[2] = 0,则说明其在函数地址表中第0号位置,很轻松能定位到函数。
6. 函数地址表
① 导入表是一串连续的_IMAGE_IMPORT_DESCRIPTOR导出数组,一个DLL存在一个这种数组,直到0为止。
② _IMAGE_THUNK_DATA 看最高位,如果是1则表示序号,并且将最高位清0,如果最高位不是1,则其指向如下数据结构
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
CHAR Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
其中连续的名字是以零结尾。
③ 注意,我们从函数导出表中并没法看到其对应的函数地址,这是当然的,既然导入表,我们没有DLL,其通过名字和序号完成创建的。
7. 重定位表
注意,全局变量直接计算一个伪地址写入到硬编码中的,因此其必须要重定位。
重定位的结构为 _IMAGE_BASE_RELOCATION
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;
DWORD SizeOfBlock;
} IMAGE_BASE_RELOCATION;
其并不是这么简单,其实质是重定位块,SizeOfBlock表示重定位块的大小,当下一个重定位块为0时则表示重定位表的结束。
8. DLL文件的加载过程
① 扫描母进程的输入表,查看需要加载进入母进程地址空间的DLL和数量;
② 扫描循环加载所有的DLL文件,当DLL被加载完成后,从导入表中查找所需要的函数,
在DLL文件的输出表中搜索,定位后,将得到的地址放入母函数的IAT表中,依次循环,直到将所有的DLL函数定位;
③ 将DLL中重定位表中的内容重新定位;
④ 完成对接。