联合体
假设我们需要存储一个人的身份证和学号中的一个,如果用结构体定义两个变量,但是每次都只用到其中一个,那么另一个成员的空间永远是浪费的,为了节省内存,可以使用联合体。联合体有以下两种定义方式:
union Test
{
int x;
char y;
};
union
{
int x;
char y;
}Test;
联合体的特点:
1.联合体成员共享内存空间。
2.联合体空间大小是联合体成员中最大成员的空间大小。
3.联合体中最多有一个成员有效。
节表
#define IMAGE_SIZEOF_SHORT_NAME 8
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // ASCII字符串(节名),可自定义,只截取8个字节,可以8个字节都是名字
union { // Misc,2个字节,是该节在没有对齐前的真实尺寸,该值可以不准确
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;
节表的起始位置为pe文件头末尾,计算方法为:
基址+DOS头里的e_lfanew(指向pe文件头开始)+signature(4字节)+IMAGE_FILE_HEADER大小(20字节)+SizeOfOptionalHeader(IMAGE_FILE_HEADER里的)
例如:
这里得知e_lfanew是E0,从E0往后找20个字节
说明SizeOfOptionalHeader是E0。则节表起始位置为:0 + 0xE0 + 4 + 20 + 0xE0 = 0x1D8
在IMAGE_FILE_HEADER里的NumberOfSections记录了节的数量,每一个节表占40个字节。
Misc表示当文件加载到内存后,该节的大小,而SizeOfRawData是节在文件中对齐后的尺寸,一般情况下SizeOfRawData会比Misc大,但也有特例。当有全局变量没有初始化赋值时,在文件中全局变量没有赋值就不占空间,但是在内存中必须要赋值,所以当文件加载到内存后的尺寸就有可能会比在文件中对齐的尺寸大。这种情况下,多出的空间在内存中会用0填充。
Characteristics代表的是节的属性,占4个字节32位,每一位代表有没有某种属性(实际上没用到32位)。对照表:
比如里面的第一个是0x20换算为二进制是0010 0000,说明如果Characteristics存的值从右往左第6位如果是1的话说明该节包含代码。
RVA与FOA的转换
- RVA是“Relative Virtual Address”(相对虚拟地址)的缩写。它是一个“相对”地址,或者说是数据在内存中的“偏移量”。
- FOA是“File Offset Address”(文件偏移地址)的缩写。FOA可以理解为文件在磁盘上存放时相对于文件开头的偏移地址。
假如想修改位于内存中的某个节的数据,只知道内存中该数据的地址是无法直接更改的,因为PE文件在文件中的状态和在内存中的是不一样的,在文件中是静态的,在内存中是动态(运行)的。想要修改,就必须要将内存中的地址转化为文件中的地址,这就是RVA与FOA的转化。
在PE文件由文件状态转化为内存状态时,DOS、PE文件头、节表这几部分是看作一个整体的,这个整体复制过去后再对齐。然后循环将每个节都复制过去后分别对齐。所以,DOS、PE文件头、节表这几部分位置的数据的偏移地址是不会改变的,而在节里面的数据虽然偏移地址会改变,但是这个数据与节的开始位置的数据也是不会变的。因此,对于在节中的数据。只需要计算他在节开始位置的偏移,然后再文件上对应就可以算到他在文件中的地址。步骤如下;
计算RVA:
RVA = 内存地址 - ImageBase地址
判断位置:
1.先判断是否在DOS、PE文件头、节表这几部分里。如果RVA<=SizeOfHeaders,那么说明在,否则说明不在。如果在,那么RVA = FOA。
2.判断在哪个节里。假如某个节满足:该节的VirtualAddress <= RVA <= VirtualAddress + Misc,那么在该节上。
计算:
假设在第x个节上,那么RVA 减该节的VirtualAddress得到的差值就是相对于节的偏移。该差值加上该节在文件中起始地址就是该数据的偏移,即FOA = PointerToRawData + 偏移。