节表
1.联合体
有如下的需求:
我们想存储一个人的学号和身份证号,而且只要存储一个就可以了.
学号需要一个字节
身份证需要四个字节
如果设计成:
struct Student
{
char 学号;
int 身份证号;
}
因为每次最多用一个成员,那另一个成员的空间永远是浪费的.
关于联合类型:
union TestUnion
{
char x;
int y;
};
特点:
1、联合体的成员是共享内存空间的
2、联合体的内存空间大小是联合体成员中对内存空间大小要求最大的空间大小
3、联合体最多只有一个成员有效 (也就是最多只能按联合体中的一种类型来获取数据)
union TestUnion{
char x;
int y;
};
与
union{
char x;
int y;
}TestUnion;
意义是不同的.
前面一种时定义了一个联合体;
后面一种是定义了一个匿名的联合体,同时声明了一个联合体变量;
2.关于节表
pe头对pe文件做概要性描述,相当于一本书的前言部分;
节表中的信息包括:每个节在文件中从哪里开始?有多大?在内存中从哪里开始,有多大?等信息;相当于一本书书的目录;
1)节表的位置
节表紧跟在可选pe头后面;
但可选pe头大小不确定;
而可选pe头的大小保存在标准pe头的SizeOfOptionalHeader中;
标准pe头的
节表的地址为:dos头中的e_flanew + nt头Signature的4个字节 + 标准pe头的大小(固定的) + 标准pe头的SizeOfOptionalHeader
2)节表的数量
每有一个节,就会有一个节表,用来描述该节的信息;
节表的数量在标准pe头的NumberOfSections中;
3)节表的结构
#define IMAGE_SIZEOF_SHORT_NAME 8 //节的名字长度最大为8
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; //节的名字,例如.text
union {
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc; //一个联合,表示该节在没对齐前的真实尺寸,也就是不算为了对齐填充的0,文件对齐和内存对齐都不算;可以被修改不影响程序运行
DWORD VirtualAddress; //内存中节区离ImageBase多少个字节;也就是内存中的节在哪
DWORD SizeOfRawData; //文件对齐后的尺寸
DWORD PointerToRawData; //文件中的节区离0有多远,也就是文件中节在哪
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics; //节的属性,每个位都有意义
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
1、Name 8个字节 一般情况下是以"\0"结尾的ASCII吗字符串来标识的名称,内容可以自定义.
注意:该名称并不遵守必须以"\0"结尾的规律,如果不是以"\0"结尾,系统会截取8个字节的长度进行处理.
2、Misc 双字 是该节在没有对齐前的真实尺寸,该值可以不准确。
3、VirtualAddress 节区在内存中的偏移地址。加上ImageBase才是在内存中的真正地址.
4、SizeOfRawData 节在文件中对齐后的尺寸.
5、PointerToRawData 节区在文件中的偏移.
6、PointerToRelocations 在obj文件中使用 对exe无意义
7、PointerToLinenumbers 行号表的位置 调试的时候使用
8、NumberOfRelocations 在obj文件中使用 对exe无意义
9、NumberOfLinenumbers 行号表中行号的数量 调试的时候使用
10、Characteristics 节的属性
关于节表中各个字段的图:
3.PE加载的过程
1、根据SizeOfImage的大小,开辟一块缓冲区(ImageBuffer).
2、根据SizeOfHeader的大小,将头信息从FileBuffer拷贝到ImageBuffer
3、根据节表中的信息循环讲FileBuffer中的节拷贝到ImageBuffer中.
4.手动找节表
用winhex、ue等工具以二进制的方式打开exe文件:
5.用程序查找节表
这里只找第一个节表,代码:
#include "stdafx.h"
#include <stdlib.h>
#include <windows.h>
//加载pe文件到内存
LPVOID fRead(const char* path){
//1.读取文件
FILE* file = fopen(path, "rb");
if(file == NULL){
printf("文件加载失败\n");
return NULL;
}
//2.计算文件大小
fseek(file, 0, SEEK_END);
long len = ftell(file);
fseek(file, 0, SEEK_SET); //指向文件开头
//3.申请内存
LPVOID buf = malloc(len);
if(buf == NULL){
printf("申请内存失败\n");
fclose(file);
return NULL;
}
//4.将文件读到缓冲区
size_t n = fread(buf, len, 1, file);
if(n==0 || n>1){
printf("读取数据失败\n");
free(buf);
fclose(file);
return NULL;
}
//5.返回缓冲区指针;注意释放内存
printf("读取数据成功\n");
fclose(file);
return buf;
}
//解析节表
void showSectionHead(){
LPVOID buf = NULL;
//windows.h中定义的pe头的结构
PIMAGE_DOS_HEADER dosHeader = NULL;
PIMAGE_NT_HEADERS ntHeader = NULL;
PIMAGE_FILE_HEADER peHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 optionHeader = NULL;
PIMAGE_SECTION_HEADER sectionHeader = NULL;
buf = fRead("C:\\Users\\Administrator\\Desktop\\CRACKME.EXE");
if(!buf){
printf("读取文件到缓冲区失败\n");
return;
}
//判断是否是有效MZ标记;
if(*((PWORD)buf) != IMAGE_DOS_SIGNATURE){
printf("不是有效mz标记\n");
free(buf);
return;
}
dosHeader = (PIMAGE_DOS_HEADER)buf;
//输出nt头标记
ntHeader = (PIMAGE_NT_HEADERS)((DWORD)buf + dosHeader->e_lfanew);
//判断是否是有效pe标记
if(ntHeader->Signature != IMAGE_NT_SIGNATURE){
printf("不是有效pe标记\n");
free(buf);
return;
}
printf("**************nt头**************\n");
printf("pe标记 -> %x\n",ntHeader->Signature);
//输出pe头中的关于节表的信息
peHeader = (PIMAGE_FILE_HEADER)((DWORD)ntHeader+4);
printf("**************pe头**************\n");
printf("节的数量 -> %x\n",peHeader->NumberOfSections);
printf("可选pe头的大小 -> %x\n",peHeader->SizeOfOptionalHeader);
//输出可选pe头
optionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)peHeader + IMAGE_SIZEOF_FILE_HEADER);
printf("**************可选pe头**************\n");
printf("内存对齐SectionAlignment -> %x\n",optionHeader->SectionAlignment);
printf("文件对齐FileAlignment -> %x\n",optionHeader->FileAlignment);
//输出第一个节表的信息
sectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)optionHeader + peHeader->SizeOfOptionalHeader);
printf("*********第一个节表*********\n");
char name[9] = {0};
for(int i=0;i<8;i++){
name[i] = (sectionHeader->Name)[i];
}
printf("节名:%s\n", name);
printf("真实尺寸misc ->%x\n",sectionHeader->Misc);
printf("映像偏移VirtualAddress ->%x\n",sectionHeader->VirtualAddress);
printf("文件中对齐后节大小SizeOfRawData ->%x\n",sectionHeader->SizeOfRawData);
printf("文件偏移PointerToRawData ->%x\n",sectionHeader->PointerToRawData);
printf("节属性Characteristics ->%x\n",sectionHeader->Characteristics);
//释放内存
free(buf);
}
int main(int argc, char* argv[])
{
showSectionHead();
getchar();
return 0;
}
结果以及和pe工具的对比: