前言
节表:相当于这本书的目录
联合体
联合体类型
union TestUnion { char x; int y; }
特点
联合体的成员是共享内存空间的 联合体的内存空间大小是联合体成员中对内存空间大小要求最大的空间大小 空间分配上,联合体最多只有一个成员有效
分析
一:
union TestUnion
{
char x;
int y;
};
二:
union
{
char x;
int y;
}TestUnion;
以上两者是有不同的意义的
一:TestUnion是联合体类型
二:联合体是匿名的,TestUnion是变量
结构and位置
节表结构
每一个节表的结构是一个结构体类型的,长度固定为0x28,即40字节。如下:
#define IMAGE_SIZEOF_SHORT_NAME 8 //宏定义
typedef struct _IMAGE_SECTION_HEADER{
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; * //每一个节都可以取一个名字,最大长度为8字节
union{
DWORD PhysicalAddress;
DWORD VirtualSize;
}Misc; * //Misc就是此联合体类型的变量
DWORD VirtualAddress; *
DWORD SizeOfRawData; *
DWORD PointerToRawData; *
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics; *
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
一个节对应一个节表,即一个节表由一个结构体类型的节表记录信息。节表数据紧接着可选PE头数据后面,节表中会循环上述的结构,因为一个节就对应一个结构,且这些数据都是挨着顺序存放的
位置
找节表的位置:在标准PE头中SizeOfOptionalHeader
找到可选PE头的大小
然后DOC头+PE标识+标准PE头+可选PE头 就找到了节表的位置
在标准PE头中,NumberOfSections
记录了节的数量
注意这里->SizeOfHeaders:是包含节表之后,还要按照文件对齐之后的大小
根据这张图 分析节表 属性
属性
1、Name
8字节,一般情况下是以\0
结尾的 ASCII吗字符串来标识的名称,内容可以自定义
-
注意容易出现的安全问题*:
-
因为Name数组的长度最大为8字节,如果定义节的名称为.text:.的ASCII码为0x2E;t的ASCII码为0x74;e的ASCII码为0x65;x的ASCII码为0x78。所以Name数组中的数据为2E 74 65 78 74 00 00 00,一共8字节,如果此时我们定位到了此节表的地址,使用char np = Name所在的地址,接着使用printf("%s",np);来打印此节表的名字,那么由于使用%s和char的方式来访问一个数组会从数组首地址一直打印直到遇到\0,即0x00就结束打印,所以此时就会打印出来.text,没有任何问题
-
但是如果我们自定义名字或者编译器帮我们定义的名字长度等于8,把这8位全占了,没有给\0留位置存储。比如.abcdefg,每个字符转换成1字节的ASCII码,最后Name数组中的数据为2E 61 62 63 64 65 66 67,一共8字节。那么如果我们还是使用char* p = Name的起始地址,使用printf("%s",*p);来打印名字,就可能会问题,因为此时数组由于没有\0结尾了,那么就会接着把Name后面的内存中的数据打印出来,直到遇到一个0x00,为止才停止打印。那么打印的名字就可能是.abcdefg癑5J@.??.等乱码
-
此时如果想解决此这个极端的名字打印,我们可以自己定义一个char arr[9];然后使用库函数strcpy(arr,name);,或者自己写一个循环一个一个赋值进我们自定义的数组中,最后一位补\0即可,然后使用%s的方法打印我们自定义的数组中的值即可
-
-
操作系统也是通过这种方法来处理8字节长度的名字:即8字节名称并不遵守必须以"\0"结尾的规律,如果不是以"\0"结尾,系统会截取8个字节的长度进行处理
2、Misc
Misc:4字节
-
它是节在补齐规定大小之前的真实尺寸,该值改了对程序的运行没有影响,如果一个程序没被修改过,那这个值就是准确的,如果被其他的软件加工过,就可能变的不准确
-
A到B,后面的内存是为了内存对齐
-
为什么定义成联合体,因为有些编译器或者软件喜欢用PhysicalAddress这个变量名表示,有些又喜欢用VirtualSize这个变量名表示,那么为了使用哪一个都可以,而且共用一个内存不占用多余的内存,就使用联合体,想使用PhysicalAddress就用Misc.PhysicalAddress;想使用VirtualSize就用Misc.VirtualSize
3、VirtualAddress
VirtualAddress:4字节
它是节区在内存中的相对偏移地址。这个值就是离ImageBase多远,加上ImageBase才是在内存中的真正地址
它只在内存中有意义
4、SizeOfRawData
SizeOfRawDVirtualAddressata:4字节
节在文件中对齐后的尺寸,就是这个绿色框
5、PointerToRawData
PointerToRawData:4字节
节区在文件中的偏移,他一定是文件对齐的整数倍,因为文件是有整数大小
它是在文件中,注意和VirtualAddress
区分
6、PointerToRelocations
4字节,在obj文件中使用 对exe无意义
7、PointerToLinenumbers
4字节,行号表的位置 调试的时候使用
8、NumberOfRelocations
2字节,在obj文件中使用 对exe无意义
9、NumberOfLinenumbers
2字节,行号表中行号的数量 调试的时候使用
10、Characteristics
Characteristics:4字节
节的属性:同样的,4字节(32位),每一位都表示此节的一个属性。我们一般关注的属性是是否可读、可写、可执行
4字节的16进制:00000000~FFFFFFFF
看标志(属性块)特征值对照
0020->二进制表示:0000000000100000->就是第6位
包含已初始化的数据,可写,可执行,还有共享块
总结
输出节表中的信息
#include<stdafx.h>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include<malloc.h>
LPVOID ReadPEFile(LPSTR lpszFile)
{
FILE *pFile = NULL;
DWORD fileSize = 0;
LPVOID pFileBuffer = NULL;
if ( (pFile = fopen(lpszFile, "rb")) == NULL )
puts("Fail to open file!");
fseek(pFile,0,SEEK_END);
fileSize=ftell(pFile);
pFileBuffer = malloc(fileSize);
fseek(pFile,0,SEEK_SET);
if(pFileBuffer == NULL)
puts("申请失败");
size_t n = fread(pFileBuffer, fileSize, 1, pFile);
if(!n)
{
printf(" 读取数据失败! ");
free(pFileBuffer);
fclose(pFile);
return NULL;
}
fclose(pFile);
return pFileBuffer;
}
VOID PrintNTHeaders()
{
LPVOID pFileBuffer = NULL;
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNTHeader = NULL;
PIMAGE_FILE_HEADER pPEHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;
pFileBuffer = ReadPEFile("C:\\Windows\\system32\\notepad.exe");
if(*((PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE)
{
printf("不是有效的MZ标志\n");
free(pFileBuffer);
return ;
}
pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
printf("********************DOC头********************\n");
printf("MZ标志:%x\n",pDosHeader->e_magic);
printf("PE偏移:%x\n",pDosHeader->e_lfanew);
//if(*((PWORD)pFileBuffer+pDosHeader->e_lfanew) != IMAGE_NT_SIGNATURE)
if(*((PDWORD)((DWORD)pFileBuffer+pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
{
printf("不是有效的PE标志\n");
free(pFileBuffer);
return ;
}
pNTHeader=(PIMAGE_NT_HEADERS)((DWORD)pFileBuffer+pDosHeader->e_lfanew);
//printf("********************NT头********************\n");
//printf("Signature:%x\n",pNTHeader->Signature);
pPEHeader=(PIMAGE_FILE_HEADER)(((DWORD)pNTHeader)+4);
// printf("********************PE头********************\n");
//printf("Machine:%x\n",pPEHeader->Machine);
// printf("NumberOfSections:%x\n",pPEHeader->NumberOfSections);
// printf("SizeOfOptionalHeader:%x\n",pPEHeader->SizeOfOptionalHeader);
pOptionHeader=(PIMAGE_OPTIONAL_HEADER32)((DWORD)pNTHeader+0x18);
// printf("********************OPTIONAL_PE头********************\n");
// printf("OPTION_PE:%x\n",pOptionHeader->Magic);
// printf("AddressOfEntryPoint:%x\n",pOptionHeader->AddressOfEntryPoint);
// printf("ImageBase:%x\n",pOptionHeader->ImageBase);
// printf("SectionAlignment:%x\n",pOptionHeader->SectionAlignment);
// printf("FileAlignment:%x\n",pOptionHeader->FileAlignment);
pSectionHeader=(PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader+pPEHeader->SizeOfOptionalHeader);
printf("********************节表********************\n");
for(int i=0;i<pPEHeader->NumberOfSections;i++)
{
printf("Name:%s\n",pSectionHeader->Name);
printf("Misc:%x\n",pSectionHeader->Misc);
printf("VirtualAddress:%x\n",pSectionHeader->VirtualAddress);
printf("SizeOfRawData:%x\n",pSectionHeader->SizeOfRawData);
printf("PointerToRawData:%x\n",pSectionHeader->PointerToRawData);
printf("Characteristics:%x\n",pSectionHeader->Characteristics);
printf("\n");
pSectionHeader+=1;//这里应该加什么?
}
free(pFileBuffer);
}
int main()
{
PrintNTHeaders();
return 0;
}