#Windows复习笔记
@()[PE]
文章目录
##1. PE结构
###1.1 DosHeader
###1.2 NtHeader
###1.3 Sections
- 有多少个节就有多少个节表,节表的个数存在于文件头
节表描述了在内存中,每个节块的具体属性
节表结构体如下 :
typedef struct _IMAGE_SECTION_HEADER{
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // 8个字节,一般情况下以"\0"结尾,内容可以自定义
union{
DWORD PhysicalAddress;
DWORD VirtualSize;
}Misc; // 双字,是该节块在内存中【没有】对齐前的真实尺寸,可以不准确
DWORD VirtualAddress; // 节块在内存中的偏移地址,加上ImageBase才是内存中的真正地址
DWORD SizeOfRawData; // 节块在文件中对齐后的尺寸
DWORD PointerToRawData; // 节块在文件中的偏移
DWORD PointerToRelocations; // 在obj文件中使用 对exe无意义
DWORD PointerToLinenumbers; // 行号表的位置 调试的时候使用
WORD NumberOfRelocations; // 在obj文件中使用 对exe无意义
WORD NumberOfLinenumbers; // 行号表中行号的数量 调试的时候使用
DWORD Characteristics; // 接的属性(可读\可写\可执行)
}
节块的类型如下 :
| 名称 | 描述 |
|---|---|
.text段 |
一般是代码段 |
.data段 |
一般是数据段 |
.bss段 |
表示未初始化的数据,如static变量,可能是进入一个函数才被初始化的 |
.rdata段 |
表示只读的数据,比如字符串 |
.textbss段 |
和代码有关 |
.idata和.edata段 |
储存导入表和导出表的信息 |
.rsrc段 |
存储资源的区段(节) |
.reloc段 |
存储重定位表信息的区段 |
-**节块从文件中到内存中拉伸视图, 如下 : **
###1.4 导出表注意
-导入表可能有多个,但导出表只有一个
-数据目录项的第一个结构存储导出表的偏移
-真正导出的序号等于Base + 函数地址表下标
-函数名称表是按名称排列的,A 开头的函数在AddressOfNames中排在最前面,但A开头函数真正的地址可能排在B开头函数后面
导出表的结构体如下 :
typedef struct _IMAGE_EXPORT_DIRECTORY{
DWORD Characteristics;
DWORD TimeDateStamp; // 时间戳
WORD MajorVersion;
WORD MinorVersion;
DWORD Name; // 指向该导出表文件名字符串
DWORD Base; // 导出函数其实序号
DWORD NumberOfFunctions; // 所有导出函数的个数
DWORD NumberOfNames; // 以函数名称导出的函数个数
DWORD AddressOfFunctions; // 导出函数地址表RVA
DWORD AddressOfNames; // 导出函数名称表RVA
DWORD AddressOfNameOrdinals; // 导出函数序号表RVA
}
###1.5 导入表注意
- 数据目录项第二个结构存储导入表起始的偏移
- **优点,启动程序速度快。缺点,若程序没有占到指定位置,需要修改IAT表中地址内容 **
导入表结构如下 :
typedef struct _IMAGE_IMPORT_DESCRIPTOR{
union{
DWORD Characteristics;
DWORD OriginalFirstThunk; // RVA 指向IMAGE_THUNK_DATA结构数组
};
DWORD TimeDateStamp; // 时间戳, 若为0表示没有绑定导入表,为-1绑定了导入表, 而真正的绑定时间存在另外一张表里
DWORD ForwarderChain;
DWORD Name; // RVA, 指向dll名称,该名称已0结尾
DWORD FirstThunk; // RVA, 指向IMAGE_THUNK_DATA结构数组
}
PE文件加载前 :
PE文件加载后 :
- 从图中可以看出在文件中INT表和IAT表的内容是一致的(除去绑定导入表)
- 在内存中会根据INT表中函数名称查找函数对应的地址赋值给IAT表
####1.5.1 绑定导入表(了解就行)
- 数据目录项第12个结构存储绑定导入表的偏移
绑定导入表的作用是加快程序的启动速度,一个PE程序在启动时回去加载导入表中的dll文件,根据INT表的内容依次查询函数地址,赋值给IAT。这需要消耗时间,绑定导入表中存储了导入函数真实地址,所以当PE在启动时检测到有绑定导入表,会直接将绑定导入表地址存入FirstThunk中。
绑定导入表结构 :
typedef struct _IMAGE_BOUND_IMPORT_DESCRIPTOR{
DWORD TimeDateStamp; // 时间戳
WORD OffsetModuleName; // 相对于绑定导入表首地址的RVA,存储的是DLL的名称
WORD NumberOfModuleForwarderRefs; // 后面跟随着的_IMAGE_BOUND_FORWARDER_REF个数
// _IMAGE_BOUND_FORWARDER_REF[...];
}IMAGE_BOUND_IMPORT_DESCRIPTOR, *PIMAGE_BOUND_IMPORT_DESCRIPTOR;
typedef struct _IMAGE_BOUND_FORWARDER_REF{
DWORD TimeDateStamp;
WORD OffsetModuleName;
WORD Reserved;
}IMAGE_BOUND_FORWARDER_REF, *PIMAGE_BOUND_FORWARDER_REF;
###1.6 重定位表
注意 :
一般情况下,EXE都是按照ImageBase的地址进行加载的,因为EXE拥有自己独立的4GB的虚拟内存空间,但DLL不是,DLL只有EXE使用它,才会被加载到相关EXE的进程空间。
而无论是ImageBase还是RVA都在程序编译完成,已经被写入到文件中了。也就是说,如果程序能按照预定的ImageBase来加载的话,那么就不需要重定位表,这也是exe很少有重定位表,而Dll却大多数都有重定位表。
- 数据目录项第6个结构存储重定位表的偏移
- 重定位表不止一个
typedef struct _IMAGE_BASE_RELOCATION{
DWORD VirtualAddress; // 偏移的基地址
DWORD SizeOfBlock; // 当前块有多大
}IMAGE_BASE_RELOCATION;
###1.7 资源表
- 数据目录项第3个结构存储资源表的偏移
存储PE文件中的资源包括图片、菜单、图标等等。
###1.7 TLS表(线程本地存储表)
- 数据目录项第10个结构存储TLS表的偏移
通常一个包含了TLS表的程序,它就会拥有".tls"段这个段里面保存了变量和回调函数的数据,但是TLS表本身的结构体一般存在于".rdata"段内
TLS里面的变量和回调函数都在程序入口点(AddressOfEntry)之前执行,也就是说程序在被调试时,还没有在入口点处断下来之前,TLS中的变量和回调函数就已经执行完了,所以TLS可以用作反调试之类的操作
__declspec (thread) int g_nNum = 0x111; // 静态创建
typedef struct _IMAGE_TLS_DIRECTORY32 {
DWORD StartAddressOfRawData; // tls模板在内存中的起始VA,模板是用于创建线程时初始化TLS数据的
DWORD EndAddressOfRawData; // tls模板在内存中的结束VA
PDWORD AddressOfIndex; // 存储TLS索引的位置
PIMAGE_TLS_CALLBACK *AddressOfCallBacks; // 指向TLS注册的回调函数的函数指针数组
DWORD SizeOfZeroFill; // 用0填充TLS变量区域的大小
DWORD Characteristics; // 保留
} IMAGE_TLS_DIRECTORY32;