逆向基础知识练习题解答

本篇文章只是记录学习过程中逆向知识相关的练习题解答,要系统的知识梳理请参考其他书籍或文章。


一、进制

1.1 数据进制

  • 进制是由元素组成的,N 进制就是有 N 个元素组成,逢 N 进一。

练习:
1、2+3=1 成立吗?说明理由。

成立,可定义一个十进制,符号为0 5 2 3 4 1 6 7 8 9 组成,逢十进一。

2、将下面的二进制数用16进制表示。

1100 1011 0101 0100 1110 1011 0101 0111 1011 0100 1010 1011
C B 5 4 E B 5 7 B 4 A B

3、将下面的十六进制数用二进制表示

487FDC120ACE69B953FE
0100 1000 0111 1111 1101 1100 0001 0010 0000 1010 1100 1110 0110 1001 0101 0011 1111 1110

4、二进制从0写到100(是100个数,每行写10个便于统计)
进制

1.2 进制运算

  • 任何一种进制,他自身就是一个完美的体系结构,可以直接加减乘除开方。

练习:
1、对照99乘法表,建立66乘法表。
6*6乘法表
2、将16进制的元素用2进制的元素下定义。

  • 二进制与十六进制的映射
十六进制0123456789ABCDEF
二进制0000000100100011010001010110011110001001101010111100110111101111

3、将16进制的元素和二进制对比,发现其中的规律

计算的规律就是,二进制的每一位为2的N-1次方。

  • 第一位为2^0 = 1
  • 第二位为2^1 = 2
  • 第三位为2^2 = 4
  • 第四位为2^3 = 8 等等

1.3 十六进制与数据宽度

  • 计算机是定宽的。字节(Byte)占据 8 位二进制宽度;字(Word)占据 16 位二进制宽度;
    双字(Doubleword)占据 32 位二进制宽度;四字(Quadword)占据 64 位二进制宽度。

练习:
1、八进制数2-5 在计算器中的的结果是:1777777777777777777775 为什么?

理解思路:在计算机中二进制没有正负之分,只是我们表示的时候将某部分的值表示成负数。2-5结果对应的十进制结果是-3,使用八进制表示就是1777777777777777777775,使用十六进制表示就是FD。只是不同进制的表示数不一样而已,使用常用的十进制表示就是-3。

1.4 逻辑运算

  • 二进制实现了逻辑运算和算术运算的统一。

练习:
1、使用异或对 87AD6 进行加密后再进行解密,加解密密钥:5。
异或运算


二、寄存器与汇编指令

2.1 通用寄存器

  • 汇编就是在“寄存器与寄存器”或者“寄存器与内存”之间来回移动数据。

练习:
1、记住8个通用寄存器的名称,按照顺序。
通用寄存器

2.2 内存

  • 内存的通用格式:reg+reg*数+立即数。

练习:
1、练习复杂地址表达形式的操作如: lea [eax +eax*0x02+0x12548],eax。

可在ollydbg中尝试不同的指令。

2.3 汇编指令

  • 操作和运算本身也是代码,并存在于内存中。

练习:
1、使用2种方式分别实现:push ecx、pop ecx、push esp、pop esp。
汇编指令

2.4 EFLAGS寄存器

  • 标志寄存器主要是记录当前的程序状态

练习:
1、写汇编指令只影响CF位的值(不能影响其他标志位)

MOV AL,0xF1
ADD AL,0x10

2、写汇编指令只影响PF位的值(不能影响其他标志位)

MOV AL,2
ADD AL,1

3、写汇编指令只影响AF位的值(不能影响其他标志位)

MOV AL,0x0F
ADD AL,1

4、写汇编指令只影响SF位的值(不能影响其他标志位)

MOV AL,0x80
ADD AL,0

5、写汇编指令只影响OF位的值(不能影响其他标志位)

MOV AL,0x70
ADD AL,0x10


三、C语音

3.1 C的汇编表示

  • 申明变量(比如 int a)就是给内存取名。

练习:
1、用__declspec(naked)裸函数实现下面的功能。
练习目的:
(1)熟悉堆栈结构
(2)参数、局部变量的位置
(3)返回值存储的位置

int plus(int x, int y, int z)
{
	int a = 2;
	int b = 3;
	int c = 4;
	return  x+y+z+a+b+c;
}
void __declspec(naked) plus(int x, int y, int z)
{
	__asm
	{
	//保存栈底
	push ebp
	//提升堆栈
	mov ebp, esp
	sub esp, 0x40
	//保护现场
	push ebx
	push esi
	push edi
	//为缓冲区填充数据
	mov eax, 0xCCCCCCCC
	mov ecx, 0x10
	lea edi, dword ptr ds:[ebp-0x40]
	rep stosd
	
	//函数功能实现
	mov dword ptr ds:[ebp-0x4], 0x2
	mov dword ptr ds:[ebp-0x8], 0x3
	mov dword ptr ds:[ebp-0xC], 0x4
	mov eax, dword ptr ds:[ebp+0x8]
	add eax, dword ptr ds:[ebp+0xC]
	add eax, dword ptr ds:[ebp+0x10]
	add eax, dword ptr ds:[ebp-0x4]
	add eax, dword ptr ds:[ebp-0x8]
	add eax, dword ptr ds:[ebp-0xC]
	
	//恢复现场
	pop edi
	pop esi
	pop ebx
	//降低堆栈
	mov esp, ebp
	//恢复栈底
	pop ebp
	//函数返回
	ret
	}
}

3.2 函数

  • 函数格式:前面是个 int,后面是个名称、左圆括号和右圆括号。

练习:
1、函数的汇编格式。
函数的汇编格式

3.3 内存结构

  • 函数调用都会引发 CALL 指令的执行。

练习:
1、编写一个函数能够对任意2个整数实现加法,并分析函数的反汇编。

int Plus1(int x, int y)
{
	return x + y;
}

2、编写一个函数,能够对任意3个整数实现加法,并分析函数的反汇编。

int Plus2(int x, int y, int z)
{
	int t;
	int r;
	t = Plus1(x, y);
	r = Plus1(t, z);
	return r;
}

3、编写一个函数,能够实现对任意5个整数实现加法(使用Plus1和Plus2),并分析一个函数的反汇编代码。

int Plus3(int a, int b, int c, int d, int e)
{
	int t;
	int r;
	t = Plus1(a, b);
	r = Plus2(c, d, e);
	//r = Plus1(t, r);  之前练习时少了这条语句
	return r;
}

反汇编代码。
反汇编代码

3.4 条件执行

  • 条件执行指令是根据 EFLAG 寄存器中相应的标志位为判断条件。

练习:
1、定义4个int类型的全局变量,分别是g_x,g_y,g_z,g_r,使用if…else造成最大值。

int g_x = 5;
int g_y = 4;
int g_z = 7;
int g_r = 0;
void Max()
{
	//添加代码
	if(g_x > g_y){
		if(g_x > g_z){
			g_r = g_x;
		}else{
			g_r = g_z;
		}
	}else{
		if(g_y > g_z){
			g_r = g_y;
		}else{
			g_r = g_z;
		}
	}
	printf("%d\n", g_r);
}

3.5 移位指令

  • 算术移位涉及符号位的运算,而逻辑移位不涉及。

练习:
1、定义一个unsiged char 类型,通过程序为第3、5、7位赋值,赋值时不能影响其他位原来的值。(使用位操作指令,比如:& | ~ ^ << >> 等)

void Test()
{
	unsigned char x = 0;  // 0000 0000
	unsigned char x2 = 0;
	unsigned char y = 1;

	//第3位赋值
	x = x | 0x04;	// 置1
	x2 = x2 | (y << 2);
	printf("%x, %x\n", x, x2);
	x = x & 0xFB;	// 置0
	x2 = x2 & ~(y << 2);
	printf("%x, %x\n", x, x2);

	//第5位赋值
	x = x | 0x10;	// 置1
	x2 = x2 | (y << 4);
	printf("%x, %x\n", x, x2);
	x = x & 0xEF;	// 置0
	x2 = x2 & ~(y << 4);
	printf("%x, %x\n", x, x2);

	//第7位赋值
	x = x | 0x40;	// 置1
	x2 = x2 | (y << 6);
	printf("%x, %x\n", x, x2);
	x = x & 0xBF;	// 置0
	x2 = x2 & ~(y << 6);
	printf("%x, %x\n", x, x2);
}

3.6 表达式

  • 凡是由变量、常量和算术符号链接起来的都可以称为表达式。

练习:
1、找出下列对错。

//最好的方法是在编译器中调试。
int #33;   	//变量名不能由特殊字符或数字开头
int a;
int 2a;		//变量名不能由特殊字符或数字开头
int i;
int 3_Alafun()	//函数名不能由特殊字符或数字开头
{
	int v;
	int a;
	int b>a;	//b > a 是表达式,不是变量名
	v += i++++;	//后加加两边还需要变量
	v += ++++i;
	return 0;
}

3.7 if语句

  • if 语句又称选择语句,用于在可选择的几个动作之间进行选择。

练习:
1、找出数组里面最大的值,并存储到全局变量中(要求使用while循环)

int arr[10] = {2,5,7,9,22,4,8,22,3,18};
int g_r;
void ArrMax()
{
	//添加代码
	int i = 1;
	g_r = arr[0];
	while(i < 10){
		if(g_r < arr[i]){
			g_r = arr[i];
		}
		i++;
	}
	printf("%d\n", g_r);
}

3.8 循环语句

  • c 语言中所有句型都可以用 if 和 goto 实现。

练习:
1、将数组所有的元素相加,将结果存储到g_r中(要求使用do…while循环)

int arr[10] = {2,5,7,9,22,4,8,22,3,18};
int g_r;
void ArrMax()
{
	int i = 0;
	g_r = arr[0];
	do{
		i++;
		if(g_r < arr[i]){
			g_r = arr[i];
		}
	}while(i < 10);
	printf("%d\n", g_r);
}

3.9 变量

  • 变量是内存中的一个位置,可以在该位置上存储值以供程序使用。

练习:
1、将float类型的12.5转换为16进制。
变量

3.10 数组

-数组表示一串变量。在定义数组时,如:int student[2][3][6][8/2];[ ]内在定义时必须为常量,应用时[]内可为表达式。

练习:
1、将两个数组中所有数据进行从小到大的排序,存储到另一个数组中。
数组一:[3,5,7,9,12,25,34,55]
数组二:[4,7,9,11,13,16]

void MergeSort(){
	int arr1[8] = {3,5,7,9,12,25,34,55};
	int arr2[6] = {4,7,9,11,13,16};
	int sort[14] = {0};
	int i = 0, j = 0;
	int k = 0;
	while(i < 8 && j < 6){
		if(arr1[i] < arr2[j]){
			sort[k] = arr1[i];
			i++;
		}else {
			sort[k] = arr2[j];
			j++;
		}
		k++;
	}
	while(i < 8){
		sort[k] = arr1[i];
		i++;
		k++;
	}
	while(j < 6){
		sort[k] = arr2[j];
		j++;
		k++;
	}
	for(k = 0; k < 14; k++){
		printf("%d ", sort[k]);
	}
}

3.11 结构体

  • 结构体就是不同数据类型的集合。变量“占用”内存空间不仅与其定义的类型有关,和其定义的位置也有关系,所以相同类型的变量应放在一起,这样可以避免空间浪费;变量内部由低地址开始存储,变量之间由高地址开始存储。结构体为单个变量,数组为一串变量(由低地址存储)。

练习:
1、定义一个结构体Gamer用来存储一个游戏中的角色信息,包括血值、等级、坐标等要求。
2、定义一个函数,用来给这个结构体变量赋值。
3、定义一个函数,用来显示这个结构体变量的所有成员信息。

struct Point{
	float x;
	float y;
	float z;
};
struct Gamer{
	int life;
	int grade;
	Point site;
};
Gamer player;

void setGamer(){
	player.life = 100;
	player.grade = 90;
	player.site.x = 10;
	player.site.y = 20;
	player.site.z = 30;
}
void getGamer(){
	printf("life:%d\n", player.life);
	printf("grade:%d\n", player.grade);
	printf("point x:%.1f y:%.1f z:%.1f\n", player.site.x, player.site.y, player.site.z);
}

3.12 switch语句

  • switch 语句是特殊条件下的 if 语句,但是比 if 语句执行效率高。

练习:
1、写一个switch语句,生成大表和小表,贴出对应的反汇编。

void TestSwitch(int x)
{
	switch(x)
	{
	case 100:printf("100\n");break;
	case 107:printf("101\n");break;
	case 108:printf("102\n");break;
	case 109:printf("110\n");break;
	default:printf("other\n");break;
	}
}

反汇编

3.13 define与typedef

  • 给常量或表达式取别名只是为了方便书写或修改。

练习(正向基础):
1、判断数组是否是对称的,如果是返回1,不是返回0。

int function()
{
	int arr[10] = {1, 2, 3, 4, 5, 5, 6, 3, 2, 1};
	int i = 0;
	while(i < 5){
		if(arr[i] != arr[9-i]){
			return 0;
		}
		i++;
	}
	return 1;
}

3.14 指针

  • 带 * 号的变量宽度一定是 4。
  • 凡是带 * 号的变量,赋值时使用标准赋值语句,不能简写。
  • 带 * 号的变量的加减和普通变量有区别,加减的宽度是去掉一个“*”后新数据类型的宽度。

练习:
1、模拟实现CE的数据搜索功能:
这一堆数据中存储了角色的血值信息,假设血值的类型为int类型,值为100。
请列出所有可能的值以及该值对应的地址。
1.通过char指针遍历数据。
2.通过short指针遍历数据。
3.通过int指针遍历数据。

char data[100] = 
{
	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x07, 0x09,
	0x00, 0x20, 0x10, 0x03, 0x03, 0x0C, 0x00, 0x00, 0x44, 0x00,
	0x00, 0x33, 0x00, 0x47, 0x0C, 0x0E, 0x00, 0x0D, 0x00, 0x11,
	0x00, 0x00, 0x00, 0x02, 0x64, 0x00, 0x00, 0x00, 0xAA, 0x00,
	0x00, 0x00, 0x64, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x02, 0x00, 0x74, 0x0F, 0x41, 0x00, 0x00, 0x00,
	0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x0A, 0x00,
	0x00, 0x02, 0x74, 0x0F, 0x41, 0x00, 0x06, 0x08, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x64, 0x00, 0x0F, 0x00, 0x00, 0x0D, 0x00,
	0x00, 0x00, 0x23, 0x00, 0x00, 0x64, 0x00, 0x00, 0x64, 0x00
};
//填写代码
void Find()
{
	char* pchar = &data[0];
	short* pshort = (short*)&data[0];
	int* pint = (int*)&data[0];
	int i = 0;
	//char*
	printf("char* \n");
	for(i = 0; i < 100; i++){
		if(*(pchar+i) == 0x64){
			printf("%x %d\n", pchar+i, i);
		}
	}
	//short*
	printf("short* \n");
	for(i = 0; i < 100; i++){
		pshort = (short*)(pchar+i);
		if(*pshort == 0x64){
			printf("%x %d\n", pshort, i);
		}
	}
	//int*
	printf("int* \n");
	for(i = 0; i < 100; i++){
		pint = (int*)(pchar+i);
		if(*pint == 0x64){
			printf("%x %d\n", pshort, i);
		}
	}
}

3.15 结构体指针

  • 两个指针相减为指针间的距离,所以要将结果除以去掉一颗*号数据类型的长度。
  • 当数组作为传参时,函数只将数组的首地址调入。
  • 在结构体指针运用时,->可替代 * . 取结构体内的子变量。

练习:
1、将一个函数存储到数据区,通过指针进行访问。

unsigned char data[] = 
{
	0x55,
	0x8B, 0xEC,
	0x81, 0xEC, 0xC0, 0x00, 0x00, 0x00,
	0x53,
	0x56,
	0x57,
	0x8D, 0xBD, 0x40, 0xFF, 0xFF, 0xFF,
	0xB9, 0x30, 0x00, 0x00, 0x00,
	0xB8, 0xCC, 0xCC, 0xCC, 0xCC,
	0xF3, 0xAB,
	0x8B, 0x45, 0x08,
	0x03, 0x45, 0x0C,
	0x5F,
	0x5E,
	0x5B,
	0x8B, 0xE5,
	0x5D,
	0xC3
};
//程序的入口
int main(int argc, char argv[])
{
	int x = 0;
	int (*pFun)(int, int);
	pFun = (int (*)(int,int))&data;

	x = pFun(3, 4);
	printf("%d\n", x);
	
	return 0;
}

四、硬编码

4.1 定长编码

  • 0x40-0x47:inc erx//erx 代表 eax 到 edi
  • 0x48-0x4f:dec erx
  • 0x50-0x57:push erx
  • 0x58-0x5f:pop erx
  • 0x90-0x97:xchg erx,eax
  • 0xb0-0xb7:mov rb,ib//rb 为 8 位宽度的寄存器
  • 0xb8-0xbf:mov erx,id
  • 指令长度最短为 1 个字节,最长为 15 个字节

练习:
1、定长指令,熟记。
定长指令

4.2 不确定长度编码

  • 指令由指令前缀(Instruction Prefixes,最多加四个,每个 1 字节,不要求顺序)、主操作码(最多 3 个字节)、寻址格式符(addressing-form specifier,视需要而定)、位移(displacement,视需要而定)和立即数。
  • 指令格式。
    指令格式

练习:
1、变长指令,查看白皮书相关表格。
变长指令描述

4.3 其他指令编码

  • Intel 指令有成千上万条,平时常用的只有几百条。每个编译器对指令的支持不同,在编译器不支持的情况下,只有通过硬编码查看,所以要熟记常用指令编码。

练习:
1、查表找出对应的汇编指令。
89 2C 15
89 AC 15
89 84 61
查表


五、PE

5.1 PE

  • PE 的格式是 0x4d5a。

练习(以notepad为例):
1、找出所有DOS头数据,并统计DOS头大小。

DOS头:64bytes
WORD e_magic;		5A4D
WORD e_cblp;		0090
WORD e_cp;			0003
WORD e_crlc;		0000
WORD e_cparhdr;		0004
WORD e_minalloc;	0000
WORD e_maxalloc;	FFFF
WORD e_ss;			0000
WORD e_sp;			00B8
WORD e_csum;		0000
WORD e_ip;			0000
WORD e_cs;			0000
WORD e_lfarlc;		0040
WORD e_ovno;		0000
WORD e_res[4];		0000 0000 0000 0000
WORD e_oemid;		0000
WORD e_oeminfo;		0000
WORD e_res2[10];	200
DWORD e_lfanew;		000000E8

2、找出所有标准PE头数据,并统计标准PE头大小。

标准PE头:20bytes
WORD Machine;				8664
WORD NumberOfSections;		0006
DWORD TimeDateStamp;		55 9E A8 BE
DWORD PointerToSymbolTable;	00 00 00 00
DWORD NumberOfSymbols;		00 00 00 00
WORD SizeOfOptionalHeader;	00F0
WORD Characteristics;		0022

3、找出所有可选PE头数据,并统计可选PE头大小。

WORD Magic;							020B
BYTE MajorLinkerVersion;			09
BYTE MinorLinkerVersion;			00
DWORD SizeOfCode;					0000A800
DWORD SizeOfInitializeDData;		00025800
DWORD SizeOfUninitializedData;		00000000
DWORD AddressOfEntryPoint;			00003ACC
DWORD BaseOfCode;					00001000
DWORD BaseOfData;					00000000
DWORD ImageBase;					0000000100000000
DWORD SectionAlignment;				00001000
DWORD FileAlignment;				00000200
WORD MajorOperatingSystemVersion;	0006
WORD MinorOperatingSystemVersion;	0001
WORD MajorImageVersion;				0006
WORD MinorImageVersion;				0001
WORD MajorSubsystemVersion;			0006
WORD MinorSubsystemVersion;			0001
DWORD Win32VersionValue;			00000000
DWORD SizeOfimage;					00035000
DWORD SizeOfHeaders;				00000600
DWORD CheckSum;						00036DA2
WORD Subsystem;						0002
WORD DllCharacteristics;			8140
DWORD SizeOfStackReserve;			00080000
DWORD SizeOfStackCommit;			00000000
DWORD SizeOfHeapReserve;			00011000
DWORD SizeOfHeapCommit;				00000000
DWORD LoaderFlags;					00100000
DWORD NumberOfRvaAndSizes;			00000000
IMAGE_DATA_DIRECTORY DataDirectory[16];	//后面先不用做

5.2 PE结构分布

  • 文件的地址和内存的地址是不一样的。

练习(以notepad为例):
1、编写程序读取一个.exe文件,输出所有的PE头信息。

//输出所有PE头信息 
LPVOID ReadPEFile(LPSTR lpszFile)
{
	FILE *pFile = NULL;
	DWORD fileSize = 0;
	LPVOID pFileBuffer = NULL;

	//打开文件
	pFile = fopen(lpszFile, "rb");
	if(!pFile){
		printf("无法打开 EXE 文件!");
		return NULL;
	}
	//读取文件大小
	fseek(pFile, 0, SEEK_END);
	fileSize = ftell(pFile);
	fseek(pFile, 0, SEEK_SET);
	//分配缓冲区
	pFileBuffer = malloc(fileSize);

	if(!pFileBuffer)
	{
		printf("分配空间失败!");
		fclose(pFile);
		return NULL;
	}

	//将文件数据读取到缓冲区
	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:/test/notepad.exe");
	if(!pFileBuffer){
		printf("文件读取失败\n");
		return ;
	}

	//判断是否是有效的MZ标志
	if(*((PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE)
	{
		printf("不是有效的MZ标志\n");
		free(pFileBuffer);
		return ;
	}
	pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
	//打印DOS头
	printf("*****************DOS头*******************\n");
	printf("MZ标志: %x\n",pDosHeader->e_magic);
	printf("e_cblp: %x\n",pDosHeader->e_cblp);
	printf("e_cp: %x\n",pDosHeader->e_cp);
	printf("e_crlc: %x\n",pDosHeader->e_crlc);
	printf("e_cparhdr: %x\n",pDosHeader->e_cparhdr);
	printf("e_minalloc: %x\n",pDosHeader->e_minalloc);
	printf("e_maxalloc: %x\n",pDosHeader->e_maxalloc);
	printf("e_ss: %x\n",pDosHeader->e_ss);
	printf("e_sp: %x\n",pDosHeader->e_sp);
	printf("e_csum: %x\n",pDosHeader->e_csum);
	printf("e_ip: %x\n",pDosHeader->e_ip);
	printf("e_cs: %x\n",pDosHeader->e_cs);
	printf("e_lfarlc: %x\n",pDosHeader->e_lfarlc);
	printf("e_ovno: %x\n",pDosHeader->e_ovno);
	printf("e_res[4]:");
	for(int i = 0; i < 4; i++){
		 printf(" %04x",pDosHeader->e_res[i]);
	}
	printf("\n");
	printf("e_oemid: %x\n",pDosHeader->e_oemid);
	printf("e_oeminfo: %x\n",pDosHeader->e_oeminfo);
	//printf("e_res2[10]: %4x\n",pDosHeader->e_res2[0]);
	printf("e_res2[10]:");
	for(int i = 0; i < 10; i++){
		 printf(" %04x",pDosHeader->e_res2[i]);
	}
	printf("\n");
	printf("PE偏移: %x\n",pDosHeader->e_lfanew);
	//判断是否是有效的PE标志
	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);
	//打印NT头
	printf("*****************NT头*******************\n");
	printf("NT: %x\n", pNTHeader->Signature);
	pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);
	printf("*****************PE头*******************\n");
	printf("PE: %x\n",pPEHeader->Machine);
	printf("节的数量: %x\n",pPEHeader->NumberOfSections);
	printf("TimeDateStamp: %x\n",pPEHeader->TimeDateStamp);
	printf("PointerToSymbolTable: %x\n",pPEHeader->PointerToSymbolTable);
	printf("NumberOfSymbols: %x\n",pPEHeader->NumberOfSymbols);
	printf("SizeOfOptionalHeader: %x\n",pPEHeader->SizeOfOptionalHeader);
	printf("Characteristics: %x\n",pPEHeader->Characteristics);
	
	//可选PE头
	pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER);
	printf("****************OPTION_PE头************\n");
	printf("OPTION_PE: %x\n",pOptionHeader->Magic);
	printf("MajorLinkerVersion: %x\n",pOptionHeader->MajorLinkerVersion);
	printf("MinorLinkerVersion: %x\n",pOptionHeader->MinorLinkerVersion);
	printf("SizeOfCode: %x\n",pOptionHeader->SizeOfCode);
	printf("SizeOfInitializeDData: %x\n",pOptionHeader->SizeOfInitializedData);
	printf("SizeOfUninitializedData: %x\n",pOptionHeader->SizeOfUninitializedData);
	printf("AddressOfEntryPoint: %x\n",pOptionHeader->AddressOfEntryPoint);
	printf("BaseOfCode: %x\n",pOptionHeader->BaseOfCode);
	//printf("BaseOfData: %x\n",pOptionHeader->BaseOfCode);
	printf("ImageBase: %016llx\n",pOptionHeader->ImageBase);
	printf("SectionAlignment: %x\n",pOptionHeader->SectionAlignment);
	printf("FileAlignment: %x\n",pOptionHeader->FileAlignment);
	printf("MajorOperatingSystemVersion: %x\n",pOptionHeader->MajorOperatingSystemVersion);
	printf("MinorOperatingSystemVersion: %x\n",pOptionHeader->MinorOperatingSystemVersion);
	printf("MajorImageVersion: %x\n",pOptionHeader->MajorImageVersion);
	printf("MinorImageVersion: %x\n",pOptionHeader->MinorImageVersion);
	printf("MajorSubsystemVersion: %x\n",pOptionHeader->MajorSubsystemVersion);
	printf("MinorSubsystemVersion: %x\n",pOptionHeader->MinorSubsystemVersion);
	printf("Win32VersionValue: %x\n",pOptionHeader->Win32VersionValue);
	printf("SizeOfimage: %x\n",pOptionHeader->SizeOfImage);
	printf("SizeOfHeaders: %x\n",pOptionHeader->SizeOfHeaders);
	printf("CheckSum: %x\n",pOptionHeader->CheckSum);
	printf("Subsystem: %x\n",pOptionHeader->Subsystem);
	printf("DllCharacteristics: %x\n",pOptionHeader->DllCharacteristics);
	printf("SizeOfStackReserve: %x\n",pOptionHeader->SizeOfStackReserve);
	printf("SizeOfStackCommit: %x\n",pOptionHeader->SizeOfStackCommit);
	printf("SizeOfHeapReserve: %x\n",pOptionHeader->SizeOfHeapReserve);
	printf("SizeOfHeapCommit: %x\n",pOptionHeader->SizeOfHeapCommit);
	printf("LoaderFlags: %x\n",pOptionHeader->LoaderFlags);
	printf("NumberOfRvaAndSizes: %x\n",pOptionHeader->NumberOfRvaAndSizes);
	//释放内存
	free(pFileBuffer);
}

5.3 PE节表

  • 一个应用程序可以启多份,为了节约内存空间。
  • 错开的本质是节省磁盘空间。

练习(以notepad为例):
1、编写程序打印节表中的信息。

//打印节表中的信息
VOID PrintSectionHeaders()
{
	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:/test/notepad.exe");
	if(!pFileBuffer){
		printf("文件读取失败\n");
		return ;
	}

	//判断是否是有效的MZ标志
	if(*((PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE)
	{
		printf("不是有效的MZ标志\n");
		free(pFileBuffer);
		return ;
	}
	pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
	//打印DOS头
	printf("*****************DOS头*******************\n");
	printf("MZ标志: %x\n",pDosHeader->e_magic);
	printf("PE偏移: %x\n",pDosHeader->e_lfanew);
	//判断是否是有效的PE标志
	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);
	//打印NT头
	printf("*****************NT头*******************\n");
	printf("NT: %x\n", pNTHeader->Signature);
	pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);
	printf("*****************PE头*******************\n");
	printf("节的数量: %x\n",pPEHeader->NumberOfSections);
	printf("SizeOfOptionalHeader: %x\n",pPEHeader->SizeOfOptionalHeader);
	
	//可选PE头
	pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER);
	printf("****************OPTION_PE头***************\n");
	printf("OPTION_PE: %x\n",pOptionHeader->Magic);

	//节表
	pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader+pPEHeader->SizeOfOptionalHeader);
	PIMAGE_SECTION_HEADER pTempSectionHeader = pSectionHeader;
	printf("****************节表信息*****************\n");
	for(int n = 0; n < pPEHeader->NumberOfSections; n++)
	{
		if(pTempSectionHeader->Name[0] == 0){
			pTempSectionHeader = NULL;
			break;
		}
		//打印节表信息
		char name[9] = {0};
		for(int i = 0; i < IMAGE_SIZEOF_SHORT_NAME; i++)
		{
			if(pTempSectionHeader->Name[i] != 0){
				name[i] = pTempSectionHeader->Name[i];
				//printf("%c", pSectionHeader->Name[i]);
			}else{
				name[i] = 0;
				break;
			}
		}
		printf("NAME: %s\n", name);
		printf("Misc: %08x\n", pTempSectionHeader->Misc.PhysicalAddress);
		printf("VirtualAddress: %08x\n", pTempSectionHeader->VirtualAddress);
		printf("SizeOfRawData: %08x\n", pTempSectionHeader->SizeOfRawData);
		printf("PointerToRawData: %08x\n", pTempSectionHeader->PointerToRawData);
		printf("PointerToRelocations: %08x\n", pTempSectionHeader->PointerToRelocations);
		printf("PointerToLinenumbers: %08x\n", pTempSectionHeader->PointerToLinenumbers);
		printf("NumberOfRelocations: %04x\n", pTempSectionHeader->NumberOfRelocations);
		printf("NumberOfLinenumbers: %04x\n", pTempSectionHeader->NumberOfLinenumbers);
		printf("Characteristics: %08x\n", pTempSectionHeader->Characteristics);
		printf("*******************************************\n");
		//指针++指向下一个节表
		pTempSectionHeader++;
	}

	//释放内存
	free(pFileBuffer);
}

5.4 拷贝节表

  • 文件节表是可以交叉,可以重叠,可以乱序。内存中不可以交叉乱序重叠。

练习(以notepad为例):
1、实现PE加载功能。
PE加载功能

//1、文件到FileBuffer
//将文件读取到缓冲区
//读取失败返回0,否则返回实际读取的大小
DWORD ReadPEFile(IN LPSTR lpszFile, OUT LPVOID* pFileBuffer)
{
	FILE *pFile = NULL;
	DWORD fileSize = 0;

	//打开文件
	pFile = fopen(lpszFile, "rb");
	if(!pFile){
		printf("无法打开 EXE 文件!");
		return NULL;
	}
	//读取文件大小
	fseek(pFile, 0, SEEK_END);
	fileSize = ftell(pFile);
	fseek(pFile, 0, SEEK_SET);
	//分配缓冲区
	*pFileBuffer = malloc(fileSize);

	if(!(*pFileBuffer))
	{
		printf("分配空间失败!");
		fclose(pFile);
		return NULL;
	}

	//初始化分配的内存空间
	memset(*pFileBuffer, 0, fileSize);

	//将文件数据读取到缓冲区
	size_t n = fread(*pFileBuffer, fileSize, 1, pFile);
	if(!n){
		printf("读取数据失败!");
		free(*pFileBuffer);
		fclose(pFile);
		return NULL;
	}
	//关闭文件
	fclose(pFile);
	return fileSize;
}

//2、FileBuffer到ImageBuffer
//将FileBuffer中的数据复制到ImageBuffer
//读取失败返回0,否则返回复制的大小
DWORD CopyFileBufferToImageBuffer(IN LPVOID pFileBuffer, OUT LPVOID* pImageBuffer)
{
	//文件中的PE结构
	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;

	//判断是否是有效的MZ标志
	if(*((PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE)
	{
		printf("不是有效的MZ标志\n");
		return NULL;
	}
		
	//DOS头
	pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;

	//判断是否是有效的PE标志
	if(*((PDWORD)((DWORD)pFileBuffer+pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
	{
		printf("不是有效的PE标志\n");
		return NULL;
	}
	pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer+pDosHeader->e_lfanew);

	//NT头
	pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);
	
	//可选PE头
	pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER);

	//分配缓冲区
	*pImageBuffer = malloc(pOptionHeader->SizeOfImage);

	if(!(*pImageBuffer))
	{
		printf("分配空间失败!");
		return NULL;
	}
	//初始化分配的内存空间
	memset(*pImageBuffer, 0, pOptionHeader->SizeOfImage);
	//printf("SizeOfImage : %d\n", pOptionHeader->SizeOfImage);

	//将FileBuffer复制到ImageBuffer,大小为SizeOFHeaders
	memcpy(*pImageBuffer, pFileBuffer, pOptionHeader->SizeOfHeaders);

	//节表
	pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader+pPEHeader->SizeOfOptionalHeader);
	PIMAGE_SECTION_HEADER pTempSectionHeader = pSectionHeader;
	char* pSection = NULL;
	for(int n = 0; n < pPEHeader->NumberOfSections; n++)
	{
		if(pTempSectionHeader->Name[0] == 0){
			pTempSectionHeader = NULL;
			return NULL;
		}

		//从PointerToRawData地址开始复制大小SizeOfRawData到VirtualAddress地址中
		pSection = (char*)memcpy((void*)((DWORD)(*pImageBuffer) + pTempSectionHeader->VirtualAddress), 
			(void*)((DWORD)pFileBuffer + pTempSectionHeader->PointerToRawData), pTempSectionHeader->SizeOfRawData);

		if(pSection == NULL){
			printf("复制FileBuffer到ImageBuffer过程失败,SectionNumber : %d\n", n);
			return NULL;
		}
		//指针++指向下一个节表
		pTempSectionHeader++;
	}

	//释放内存
	return pOptionHeader->SizeOfImage;
}

//3、ImageBuffer到NewBuffer
//将ImageBuffer中的数据复制到NewBuffer
//读取失败返回0,否则返回复制的大小
DWORD CopyImageBufferToNewBuffer(IN LPVOID pImageBuffer, OUT LPVOID* pNewBuffer)
{
	//文件中的PE结构
	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;

	//判断是否是有效的MZ标志
	if(*((PWORD)pImageBuffer) != IMAGE_DOS_SIGNATURE)
	{
		printf("不是有效的MZ标志\n");
		return NULL;
	}
		
	//DOS头
	pDosHeader = (PIMAGE_DOS_HEADER)pImageBuffer;

	//判断是否是有效的PE标志
	if(*((PDWORD)((DWORD)pImageBuffer+pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
	{
		printf("不是有效的PE标志\n");
		return NULL;
	}
	pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pImageBuffer+pDosHeader->e_lfanew);

	//NT头
	pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);
	
	//可选PE头
	pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER);
	
	//计算节表中总的SizeOfRawData
	pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader+pPEHeader->SizeOfOptionalHeader);
	PIMAGE_SECTION_HEADER pTempSectionHeader = pSectionHeader;
	int newSize = pOptionHeader->SizeOfHeaders;
	for(int n = 0; n < pPEHeader->NumberOfSections; n++)
	{
		if(pTempSectionHeader->Name[0] == 0){
			pTempSectionHeader = NULL;
			return NULL;
		}

		//节表的SizeOfRawData相加
		newSize = newSize + pTempSectionHeader->SizeOfRawData;

		//指针++指向下一个节表
		pTempSectionHeader++;
	}

	//分配缓冲区
	*pNewBuffer = malloc(newSize);

	if(!(*pNewBuffer))
	{
		printf("分配空间失败!");
		return NULL;
	}
	//初始化分配的内存空间
	memset(*pNewBuffer, 0, newSize);

	//将ImageBuffer复制到NewBuffer,大小为SizeOFHeaders
	memcpy(*pNewBuffer, pImageBuffer, pOptionHeader->SizeOfHeaders);

	//将ImageBuffer中的节复制到NewBuffer中
	pTempSectionHeader = pSectionHeader;
	char* pSection = NULL;
	for(int n = 0; n < pPEHeader->NumberOfSections; n++)
	{
		if(pTempSectionHeader->Name[0] == 0){
			pTempSectionHeader = NULL;
			return NULL;
		}

		//从VirtualAddress地址开始复制大小SizeOfRawData到PointerToRawData地址中
		pSection = (char*)memcpy((void*)((DWORD)(*pNewBuffer) + pTempSectionHeader->PointerToRawData), 
			(void*)((DWORD)pImageBuffer + pTempSectionHeader->VirtualAddress), pTempSectionHeader->SizeOfRawData);

		if(pSection == NULL){
			printf("复制ImageBuffer到NewBuffer过程失败,SectionNumber : %d\n", n);
			return NULL;
		}
		//指针++指向下一个节表
		pTempSectionHeader++;
	}

	//释放内存
	return newSize;
}

//4、NewBuffer到文件
//将内存数据保存到文件
//读取失败返回0,否则返回复制的大小
BOOL MemeryToFile(IN LPVOID pMemBuffer, IN size_t size, OUT LPSTR lpszFile)
{
	FILE *fw = NULL;
	size_t writeCount;

	//打开文件
	fw = fopen(lpszFile, "wb");
	if(fw == NULL){
		return 0;
	}

	//将内存中的数据存储到一个文件中
	writeCount = fwrite(pMemBuffer, size, 1, fw);
	
	//关闭文件
	fclose(fw);

	if(writeCount != 1){
		return 0;
	}

	return size;
}

2、编写一个函数,能够将RVA的值转换成FOA。

//将内存偏移转换为文件偏移
//返回转换后的FOA,失败返回0
DWORD RvaToFileOffset(IN LPVOID pFileBuffer, IN DWORD dwRva)
{
	//文件中的PE结构
	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;
	DWORD Foa = 0;

	//判断是否是有效的MZ标志
	if(*((PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE)
	{
		printf("不是有效的MZ标志\n");
		return NULL;
	}
		
	//DOS头
	pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;

	//判断是否是有效的PE标志
	if(*((PDWORD)((DWORD)pFileBuffer+pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
	{
		printf("不是有效的PE标志\n");
		return NULL;
	}
	pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer+pDosHeader->e_lfanew);

	//NT头
	pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);
	
	//可选PE头
	pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER);
	
	//计算节表中总的SizeOfRawData
	pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader+pPEHeader->SizeOfOptionalHeader);
	PIMAGE_SECTION_HEADER pTempSectionHeader = pSectionHeader;
	//如果Rva小于第一个节的地址,返回原值
	if(dwRva < pTempSectionHeader->VirtualAddress){
		return dwRva;
	}
	for(int n = 0; n < pPEHeader->NumberOfSections; n++)
	{
		if(pTempSectionHeader->Name[0] == 0){
			pTempSectionHeader = NULL;
			return NULL;
		}

		//判断是否处于目前的节中
		if(dwRva >= pTempSectionHeader->VirtualAddress && dwRva < pTempSectionHeader->VirtualAddress + pTempSectionHeader->Misc.VirtualSize){
			//计算rva转foa的值
			Foa = pTempSectionHeader->PointerToRawData + (dwRva - pTempSectionHeader->VirtualAddress);
			break;
		}

		//指针++指向下一个节表
		pTempSectionHeader++;
	}

	//返回文件偏移Foa
	return Foa;
}

5.5 PE拷贝节

  • 在节表里面写入函数,可以在这个函数里面做任何动作。

练习:
1、在代码空白区添加代码。
1.获取MessageBox地址,构造ShellCode练习;
2.E8 E9计算公式
真正要跳转的地址 = E8这条指向的下一行地址 + X
E8下一行的地址为内存中的地址,如:E8+5 - 400 + 1000 + ImageBase
3.在代码区手动添加代码
4.修改OEP,指向ShellCode。

#define FILEPATH_IN			"C:/test/notepad.exe"
#define FILEPATH_OUT		"C:/test/notepad_testAdd.exe"
#define SHELLCODELENGTH		0x12
#define MESSAGEBOXADDR		0x77D507EA
//全局变量声明
BYTE shellCode[] = 
{
	0x6A, 00, 0x6A, 00, 0x6A, 00, 0x6A, 00,
	0xE8, 00, 00, 00, 00,
	0xE9, 00, 00, 00, 00
};

void TestAddCodeInSection(int n)
{
	//定义PE各结构体
	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;

	LPVOID pFileBuffer = NULL;
	LPVOID pImageBuffer = NULL;
	LPVOID pNewBuffer = NULL;
	PBYTE codeBegin = NULL;
	
	int fileSize = 0;
	int imageSize = 0;
	int newSize = 0;
	int newFileSize = 0;

	//读取文件到FileBuffer
	fileSize = ReadPEFile(FILEPATH_IN, &pFileBuffer);
	if(fileSize == 0){
		printf("读取文件失败\n");
		return ;
	}
	printf("读取文件成功,filesize:%d\n", fileSize);

	//将FileBuffer转为ImageBuffer
	imageSize = CopyFileBufferToImageBuffer(pFileBuffer, &pImageBuffer);
	if(imageSize == 0){
		printf("将FileBuffer复制到ImageBuffer失败\n");
		free(pFileBuffer);
		return ;
	}
	printf("将FileBuffer复制到ImageBuffer成功,imageSize:%d\n", imageSize);

	//加载PE结构体
	//DOC头
	pDosHeader = (PIMAGE_DOS_HEADER)pImageBuffer;
	pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pImageBuffer+pDosHeader->e_lfanew);
	//NT头
	pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);
	//可选PE头
	pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER);
	//节表头
	pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader+pPEHeader->SizeOfOptionalHeader);

	//添加代码
	//定位要添加的节
	if(pPEHeader->NumberOfSections <= n-1){
		printf("超过节的数量,无法添加\n");
		free(pFileBuffer);
		free(pImageBuffer);
		return ;
	}
	pSectionHeader = pSectionHeader + n - 1;
	printf("%x  %x\n", pSectionHeader->Misc.VirtualSize, pSectionHeader->SizeOfRawData);
	//判断空白区大小是否小于shellcode长度
	if((int)((pSectionHeader->SizeOfRawData) - (pSectionHeader->Misc.VirtualSize)) < SHELLCODELENGTH)
	{
		printf("节空白区小于shellcode长度\n");
		free(pFileBuffer);
		free(pImageBuffer);
		return ;
	}
	//定位要复制的始地址
	codeBegin = (PBYTE)((DWORD)pImageBuffer + pSectionHeader->VirtualAddress + pSectionHeader->Misc.VirtualSize);
	//复制shellcoded到ImageBuffer
	memcpy(codeBegin, shellCode, SHELLCODELENGTH);

	//计算要跳转的地址
	DWORD callAddr = (MESSAGEBOXADDR - (pOptionHeader->ImageBase + ((DWORD)codeBegin + 0xD - (DWORD)pImageBuffer)));
	*(PDWORD)(codeBegin + 9) = callAddr;

	//计算OEP地址
	DWORD jmpAddr = ((pOptionHeader->ImageBase + pOptionHeader->AddressOfEntryPoint) - (pOptionHeader->ImageBase + ((DWORD)codeBegin + SHELLCODELENGTH -(DWORD)pImageBuffer)));
	*(PDWORD)(codeBegin + 0xE) = jmpAddr;

	//修改入口地址
	pOptionHeader->AddressOfEntryPoint = (DWORD)codeBegin - (DWORD)pImageBuffer;

	//将ImageBuffer转为NewBuffer
	newSize = CopyImageBufferToNewBuffer(pImageBuffer, &pNewBuffer);
	if(newSize == 0){
		printf("将ImageBuffer复制到NewBuffer失败\n");
		free(pFileBuffer);
		free(pImageBuffer);
		return ;
	}
	printf("将ImageBuffer复制到NewBuffer成功,newSize:%d\n", newSize);

	//保存NewBuffer
	newFileSize = MemeryToFile(pNewBuffer, newSize, FILEPATH_OUT);
	if(newFileSize == 0){
		printf("保存文件失败\n");
		free(pFileBuffer);
		free(pImageBuffer);
		free(pNewBuffer);
		return ;
	}
	printf("保存文件成功,newFileSize:%d\n", newFileSize);

	free(pFileBuffer);
	free(pImageBuffer);
	free(pNewBuffer);
	return ;
}

5.6 PE添加节

  • 全局变量,变量,函数 字符串都在节表里面。
  • 添加函数时代码是代码,数据是数据,分开放。

练习:
1、新增节表与节。

//新增节表与节
void AddSection()
{
	//定义PE各结构体
	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;
	PIMAGE_SECTION_HEADER pTempSectionHeader = NULL;

	LPVOID pFileBuffer = NULL;
	LPVOID pNewFileBuffer = NULL;
	
	int fileSize = 0;
	int newFileSize = 0;
	int offsetOperSign = 0;
	
	//读取文件到FileBuffer
	fileSize = ReadPEFile(FILEPATH_IN, &pFileBuffer);
	if(fileSize == 0){
		printf("读取文件失败\n");
		return ;
	}
	printf("读取文件成功,filesize:%d\n", fileSize);

	//加载PE结构体
	//DOC头
	pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
	pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer+pDosHeader->e_lfanew);
	//NT头
	pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);
	//可选PE头
	pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER);
	//节表头
	pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader+pPEHeader->SizeOfOptionalHeader);

	//判断是否有足够的空白区添加节表
	//标记是否需要进行偏移操作
	pTempSectionHeader = pSectionHeader + pPEHeader->NumberOfSections;
	if(pTempSectionHeader->Name[0] == 0 && pOptionHeader->SizeOfHeaders - ((DWORD)pTempSectionHeader - (DWORD)pDosHeader) > 80){
			offsetOperSign = 0;
	}else{
		if(pDosHeader->e_lfanew - 64 > 80){
			offsetOperSign = 1;
		}else{
			printf("没有足够的空间新增节表\n");
			free(pFileBuffer);
			return ;
		}
	}

	//条件成立,则进行偏移操作
	if(offsetOperSign == 1){
		//将PE头的内容复制到DOS头后面
		memcpy((void*)((DWORD)pDosHeader + 64), (void*)((DWORD)pDosHeader + (DWORD)pDosHeader->e_lfanew), (4 + IMAGE_SIZEOF_FILE_HEADER + pPEHeader->SizeOfOptionalHeader + pPEHeader->NumberOfSections*40));
		//清空PE头尾部到原PE头尾部的内容
		memset((void*)((DWORD)pTempSectionHeader - ((DWORD)pDosHeader->e_lfanew - 64)), 0, ((DWORD)pDosHeader->e_lfanew - 64));
		//修改DOS头的偏移
		pDosHeader->e_lfanew = 0x40; //64 = 0x40

		//修改指向PE头的指针
		pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer+pDosHeader->e_lfanew);
		//NT头
		pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);
		//可选PE头
		pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER);
		//节表头
		pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader+pPEHeader->SizeOfOptionalHeader);
		//指向节表尾部的指针
		pTempSectionHeader = pSectionHeader + pPEHeader->NumberOfSections;

	}

	//进行新增节操作
	//复制第一个节表的内容到节表尾部
	memcpy((void*)pTempSectionHeader, (void*)pSectionHeader, 40);
	//修改新增节表的内容
	//修改name
	char name[9] = ".tttt";
	for(int i = 0; i < 9; i++){
		if(name[i] == 0){
			break;
		}
		pTempSectionHeader->Name[i] = name[i];
	}
	//定位最后一个节表
	PIMAGE_SECTION_HEADER pLastSectionHeader = pSectionHeader + pPEHeader->NumberOfSections - 1;
	//修改VirtualSize,此大小为实际大小,留出空白区添加代码
	pTempSectionHeader->Misc.VirtualSize = 0x200;
	//修改VirtualAddress
	pTempSectionHeader->VirtualAddress =  pLastSectionHeader->VirtualAddress + ((pLastSectionHeader->Misc.VirtualSize > pLastSectionHeader->SizeOfRawData)?pLastSectionHeader->Misc.VirtualSize:pLastSectionHeader->SizeOfRawData);
	pTempSectionHeader->VirtualAddress = Align(pTempSectionHeader->VirtualAddress, 0x1000);
	//修改sizeOfRawData
	pTempSectionHeader->SizeOfRawData = 0x1000;
	//修改PointerToRawData
	pTempSectionHeader->PointerToRawData = pLastSectionHeader->PointerToRawData + pLastSectionHeader->SizeOfRawData;
	
	//修改PE头
	//修改节表个数
	pPEHeader->NumberOfSections = pPEHeader->NumberOfSections + 1;
	//修改SizeOfImage的大小
	pOptionHeader->SizeOfImage = pOptionHeader->SizeOfImage + 0x1000;
	
	//新增节大小
	//注意FileBuffer已在realloc中释放
	pNewFileBuffer = realloc(pFileBuffer, fileSize+0x1000);
	if(!pNewFileBuffer){
		printf("新增节大小失败\n");
		return ;
	}
	printf("新增节大小成功\n");

	//保存fileBuffer
	newFileSize = MemeryToFile(pNewFileBuffer, fileSize+0x1000, FILEPATH_OUT);
	if(newFileSize == 0){
		printf("保存文件失败\n");
		free(pNewFileBuffer);
		return ;
	}
	printf("保存文件成功,newFileSize:%d\n", newFileSize);

	free(pNewFileBuffer);
	return ;
}

2、编程实现:扩大最后一个节。

//扩展最后一个节
void expansionSection()
{
	//定义PE各结构体
	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;
	PIMAGE_SECTION_HEADER pTempSectionHeader = NULL;

	LPVOID pFileBuffer = NULL;
	LPVOID pNewFileBuffer = NULL;
	
	int fileSize = 0;
	int newFileSize = 0;
	
	//读取文件到FileBuffer
	fileSize = ReadPEFile(FILEPATH_IN, &pFileBuffer);
	if(fileSize == 0){
		printf("读取文件失败\n");
		return ;
	}
	printf("读取文件成功,filesize:%d\n", fileSize);

	//加载PE结构体
	//DOC头
	pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
	pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer+pDosHeader->e_lfanew);
	//NT头
	pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);
	//可选PE头
	pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER);
	//节表头
	pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader+pPEHeader->SizeOfOptionalHeader);

	//定位最后一个节表
	PIMAGE_SECTION_HEADER pLastSectionHeader = pSectionHeader + pPEHeader->NumberOfSections - 1;
	//修改sizeOfRawData
	if(pLastSectionHeader->Misc.VirtualSize > pLastSectionHeader->SizeOfRawData){
		printf("节实际大小大于对齐大小,扩展节失败\n");
		free(pFileBuffer);
		return ;
	}
	pLastSectionHeader->SizeOfRawData = pLastSectionHeader->SizeOfRawData+0x1000;
	//修改实际的大小
	pLastSectionHeader->Misc.VirtualSize = pLastSectionHeader->Misc.VirtualSize + 0x1000;
	//修改PE头
	//判断是否超出SizeOfImage的大小,超出则修改其大小	
	printf("大小:%x\n", pLastSectionHeader->VirtualAddress + (pLastSectionHeader->Misc.VirtualSize + 0x1000)&0xFFFFF000);
	printf("sizeOfImage:%x\n", pOptionHeader->SizeOfImage);
	
	if((pLastSectionHeader->VirtualAddress + (pLastSectionHeader->Misc.VirtualSize + 0x1000)&0xFFFFF000) > pOptionHeader->SizeOfImage){
		pOptionHeader->SizeOfImage = pLastSectionHeader->VirtualAddress + (pLastSectionHeader->Misc.VirtualSize + 0x1000)&0xFFFFF000;
		printf("SizeOfImage大小,%x\n", pOptionHeader->SizeOfImage);
	}
	//pOptionHeader->SizeOfImage = pOptionHeader->SizeOfImage + 0x1000;
	
	//新增节大小
	//注意FileBuffer已在realloc中释放
	pNewFileBuffer = realloc(pFileBuffer, fileSize+0x1000);
	if(!pNewFileBuffer){
		printf("扩展节大小失败\n");
		return ;
	}
	printf("扩展节大小成功\n");

	//保存fileBuffer
	newFileSize = MemeryToFile(pNewFileBuffer, fileSize+0x1000, FILEPATH_OUT);
	if(newFileSize == 0){
		printf("保存文件失败\n");
		free(pNewFileBuffer);
		return ;
	}
	printf("保存文件成功,newFileSize:%d\n", newFileSize);

	free(pNewFileBuffer);
	return ;
}

5.7 导出表结构

  • 导出表:本程序提供多少函数给别人用。导入表:本程序用了哪个 dll 里面的函数。

练习:
1、编写程序打印所有的导出表信息。

//打印导出表
void printExportDirectory(IN LPSTR lpszFile)
{
	//定义PE各结构体
	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;
	PIMAGE_EXPORT_DIRECTORY pExportDirectory = NULL;
	
	LPVOID pFileBuffer = NULL;
	
	int fileSize = 0;

	//读取文件到FileBuffer
	fileSize = ReadPEFile(lpszFile, &pFileBuffer);
	if(fileSize == 0){
		printf("读取文件失败\n");
		return ;
	}
	printf("读取文件成功,filesize:%d\n", fileSize);

	//加载PE结构体
	//DOC头
	pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
	pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer+pDosHeader->e_lfanew);
	//NT头
	pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);
	//可选PE头
	pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER);
	//节表头
	pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader+pPEHeader->SizeOfOptionalHeader);
	
	//输出数据目录内容
	char* dataName[16] = 
	{
		"导出表********************",
		"导入表********************",
		"资源表********************",
		"异常信息表****************",
		"安全证书表****************",
		"重定位表******************",
		"调试信息表****************",
		"版权所有表****************",
		"全局指针表****************",
		"TLS表*********************",
		"加载配置表****************",
		"绑定导入表****************",
		"IAT表*********************",
		"延迟导入表****************",
		"COM信息表*****************",
		"保留**********************"

	};
	
	//打印导出表的地址信息
	int i = 0;
	printf("*********数据目录***************\n");
	printf("%s\n", dataName[i]);
	printf("内存偏移:%x\n", pOptionHeader->DataDirectory[i].VirtualAddress);
	printf("大小:%x\n", pOptionHeader->DataDirectory[i].Size);
	//打印导出表的信息
	pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, pOptionHeader->DataDirectory[i].VirtualAddress));
	printf("*********导出表*****************\n");
	printf("Characteristics:%x\n", pExportDirectory->Characteristics);
	printf("TimeDateStamp:%x\n", pExportDirectory->TimeDateStamp);
	printf("MajorVersion:%x\n", pExportDirectory->MajorVersion);
	printf("MinorVersion:%x\n", pExportDirectory->MinorVersion);
	printf("Name:%s\n", (DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, pExportDirectory->Name));
	printf("Base:%x\n", pExportDirectory->Base);
	printf("NumberOfFunctions:%x\n", pExportDirectory->NumberOfFunctions);
	printf("NumberOfNames:%x\n", pExportDirectory->NumberOfNames);
	printf("AddressOfFunctions:%x\n", pExportDirectory->AddressOfFunctions);
	printf("AddressOfNames:%x\n", pExportDirectory->AddressOfNames);
	printf("AddressOfNameOrdinals:%x\n", pExportDirectory->AddressOfNameOrdinals);

	//打印导出函数地址表
	PDWORD pAddressOfFunction = (PDWORD)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, pExportDirectory->AddressOfFunctions));
	printf("*********函数地址表**************\n");
	for(i = 0; i < pExportDirectory->NumberOfFunctions; i++)
	{
		printf("函数地址:%x\n", pAddressOfFunction[i]);
	}

	//打印导出函数名称表
	PDWORD pAddressOfName = (PDWORD)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, pExportDirectory->AddressOfNames));
	printf("*********函数名称表**************\n");
	for(i = 0; i < pExportDirectory->NumberOfNames; i++)
	{
		printf("函数名称:%s\n", (DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, pAddressOfName[i]));
	}
	
	//打印导出函数序号表
	PWORD pAddressOfNameOrdinal = (PWORD)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, pExportDirectory->AddressOfNameOrdinals));
	printf("*********函数序号表**************\n");
	for(i = 0; i < pExportDirectory->NumberOfNames; i++)
	{
		printf("函数序号:%x\n", pAddressOfNameOrdinal[i]);
	}
	

	free(pFileBuffer);

	return ;
}

2、编写程序打印notepad.exe导入表的全部信息。

//打印导入表
void printImportTable(IN LPSTR lpszFile)
{
	//定义PE各结构体
	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;
	PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor = NULL;
	
	LPVOID pFileBuffer = NULL;
	
	int fileSize = 0;

	//读取文件到FileBuffer
	fileSize = ReadPEFile(lpszFile, &pFileBuffer);
	if(fileSize == 0){
		printf("读取文件失败\n");
		return ;
	}
	printf("读取文件成功,filesize:%d\n", fileSize);

	//加载PE结构体
	//DOC头
	pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
	pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer+pDosHeader->e_lfanew);
	//NT头
	pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);
	//可选PE头
	pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER);
	//节表头
	pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader+pPEHeader->SizeOfOptionalHeader);
	
	//输出数据目录内容
	char* dataName[16] = 
	{
		"导出表********************",
		"导入表********************",
		"资源表********************",
		"异常信息表****************",
		"安全证书表****************",
		"重定位表******************",
		"调试信息表****************",
		"版权所有表****************",
		"全局指针表****************",
		"TLS表*********************",
		"加载配置表****************",
		"绑定导入表****************",
		"IAT表*********************",
		"延迟导入表****************",
		"COM信息表*****************",
		"保留**********************"
	};
	
	//打印导入表的地址信息
	int i = 1;
	printf("*********数据目录***************\n");
	printf("%s\n", dataName[i]);
	printf("内存偏移:%x\n", pOptionHeader->DataDirectory[i].VirtualAddress);
	printf("大小:%x\n", pOptionHeader->DataDirectory[i].Size);
	//打印导入表的信息
	pImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, pOptionHeader->DataDirectory[i].VirtualAddress));
	PIMAGE_IMPORT_DESCRIPTOR pTempImportDescriptor = pImportDescriptor;
	PIMAGE_IMPORT_BY_NAME pImportByName = NULL;
	PDWORD pOriginalThunk = NULL;
	PDWORD pFirstThunk = NULL;
	printf("*********导入表*****************\n");
	while(pTempImportDescriptor->OriginalFirstThunk != 0){
		//打印基础信息
		printf("OriginalFirstThunk:%x\n", pTempImportDescriptor->OriginalFirstThunk);
		printf("TimeDateStamp:%x\n", pTempImportDescriptor->TimeDateStamp);
		printf("ForwarderChain:%x\n", pTempImportDescriptor->ForwarderChain);
		printf("Name:%s\n", (DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, pTempImportDescriptor->Name));
		printf("FirstThunk:%x\n", pTempImportDescriptor->FirstThunk);

		//遍历OriginalFirstThunk
		printf("-----------OriginalFirstThunk----------------\n");
		pOriginalThunk = (PDWORD)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, pTempImportDescriptor->OriginalFirstThunk));
		printf("pOriginalThunk:%x\n", (DWORD)pOriginalThunk - (DWORD)pFileBuffer);
		for(i = 0; pOriginalThunk[i] != 0; i++){
			printf("pOriginalThunk[%d] = %x\n", i, pOriginalThunk[i]);
			//判断最高位是否为1,是则为序号
			if((pOriginalThunk[i]&0x80000000) != 0){
				printf("最高位为1,序号为:%x\n", pOriginalThunk[i]&0x7FFFFFFF);
			}else{
				pImportByName = (PIMAGE_IMPORT_BY_NAME)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, pOriginalThunk[i]));
				printf("最高位为0,名字为:%x, %s\n", pImportByName->Hint, pImportByName->Name);
			}
		}

		//遍历FirstThunk
		pFirstThunk = (PDWORD)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, pTempImportDescriptor->FirstThunk));
		printf("-----------FirstThunk----------------\n");
		printf("pFirstThunk:%x\n", (DWORD)pFirstThunk - (DWORD)pFileBuffer);
		
		if(pTempImportDescriptor->TimeDateStamp == 0xFFFFFFFF){
			printf("导入表已绑定\n");
		}else{
			for(i = 0; pFirstThunk[i] != 0; i++){
				printf("pFirstThunk[%d] = %x\n", i, pFirstThunk[i]);
				//判断最高位是否为1,是则为序号
				if((pFirstThunk[i]&0x80000000) != 0){
					printf("最高位为1,序号为:%x\n", pFirstThunk[i]&0x7FFFFFFF);
				}else{
					pImportByName = (PIMAGE_IMPORT_BY_NAME)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, pFirstThunk[i]));
					printf("最高位为0,名字为:%x, %s\n", pImportByName->Name, pImportByName->Name);
				}
			}
		}
		
		//指向下一个导入表
		pTempImportDescriptor++;
		printf("----------------------------------------\n");
	}
	
	free(pFileBuffer);

	return ;
}

5.8 导出表原理

  • 函数名字表、序号表、函数地址表、各一份,最主要的原因就是节省空间。

练习:
1、实现GetFunctionAddrByName(FileBuffer指针, 函数名指针)。

//通过函数名获取函数地址
DWORD GetFunctionAddrByName(LPVOID pFileBuffer, IN LPSTR lpFunName)
{
	//定义PE各结构体
	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;
	PIMAGE_EXPORT_DIRECTORY pExportDirectory = NULL;
	
	//加载PE结构体
	//DOC头
	pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
	pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer+pDosHeader->e_lfanew);
	//NT头
	pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);
	//可选PE头
	pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER);
	//节表头
	pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader+pPEHeader->SizeOfOptionalHeader);
	
	//导出表的信息
	int i = 0;
	pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, pOptionHeader->DataDirectory[i].VirtualAddress));
	//函数地址信息
	PDWORD pAddressOfFunction = (PDWORD)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, pExportDirectory->AddressOfFunctions));
	PDWORD pAddressOfName = (PDWORD)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, pExportDirectory->AddressOfNames));
	PWORD pAddressOfNameOrdinal = (PWORD)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, pExportDirectory->AddressOfNameOrdinals));

	WORD ordName = 0;
	for(i = 0; i < pExportDirectory->NumberOfNames; i++)
	{
		if(strcmp(lpFunName, (char*)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, pAddressOfName[i]))) == 0){
			//函数地址
			printf("函数名称:%s\n", (DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, pAddressOfName[i]));
			//获取函数序号
			ordName = pAddressOfNameOrdinal[i];
			printf("函数序号:%x\n", ordName);
			//通过序号打印函数地址
			printf("函数地址:%x\n", pAddressOfFunction[ordName]);
			break;
		}
	}
	
	if(i == pExportDirectory->NumberOfNames){
		return NULL;
	}

	return pAddressOfFunction[ordName];
}

2、实现GetFunctionAddrByOrdinals(FileBuffer指针, 函数名导出序号)。

//通过函数序号获取函数地址
DWORD GetFunctionAddrByOrdinals(LPVOID pFileBuffer, WORD nameOrdinal)
{
	//定义PE各结构体
	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;
	PIMAGE_EXPORT_DIRECTORY pExportDirectory = NULL;
	
	//加载PE结构体
	//DOC头
	pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
	pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer+pDosHeader->e_lfanew);
	//NT头
	pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);
	//可选PE头
	pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER);
	//节表头
	pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader+pPEHeader->SizeOfOptionalHeader);
	
	//导出表的信息
	int i = 0;
	pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, pOptionHeader->DataDirectory[i].VirtualAddress));
	//函数地址信息
	PDWORD pAddressOfFunction = (PDWORD)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, pExportDirectory->AddressOfFunctions));
	PDWORD pAddressOfName = (PDWORD)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, pExportDirectory->AddressOfNames));
	PWORD pAddressOfNameOrdinal = (PWORD)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, pExportDirectory->AddressOfNameOrdinals));


	WORD ordName = nameOrdinal - pExportDirectory->Base;
	if(ordName < 0 || ordName > pExportDirectory->NumberOfFunctions){
		return NULL;
	}

	return pAddressOfFunction[ordName];
}

5.9 PE重定位表

  • 重定位就是修正数据的。

练习:
1、打印所有重定位信息。

//打印重定位表
void printRelocationTable(IN LPSTR lpszFile)
{
	//定义PE各结构体
	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;
	PIMAGE_BASE_RELOCATION pBaseRelocation = NULL;
	
	LPVOID pFileBuffer = NULL;
	
	int fileSize = 0;

	//读取文件到FileBuffer
	fileSize = ReadPEFile(lpszFile, &pFileBuffer);
	if(fileSize == 0){
		printf("读取文件失败\n");
		return ;
	}
	printf("读取文件成功,filesize:%d\n", fileSize);

	//加载PE结构体
	//DOC头
	pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
	pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer+pDosHeader->e_lfanew);
	//NT头
	pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);
	//可选PE头
	pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER);
	//节表头
	pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader+pPEHeader->SizeOfOptionalHeader);
	
	//输出数据目录内容
	char* dataName[16] = 
	{
		"导出表********************",
		"导入表********************",
		"资源表********************",
		"异常信息表****************",
		"安全证书表****************",
		"重定位表******************",
		"调试信息表****************",
		"版权所有表****************",
		"全局指针表****************",
		"TLS表*********************",
		"加载配置表****************",
		"绑定导入表****************",
		"IAT表*********************",
		"延迟导入表****************",
		"COM信息表*****************",
		"保留**********************"

	};
	
	//打印重定位表的地址信息
	int i = 5;
	printf("*********数据目录***************\n");
	printf("%s\n", dataName[i]);
	printf("内存偏移:%x\n", pOptionHeader->DataDirectory[i].VirtualAddress);
	printf("大小:%x\n", pOptionHeader->DataDirectory[i].Size);
	//循环打印重定位表的信息
	pBaseRelocation = (PIMAGE_BASE_RELOCATION)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, pOptionHeader->DataDirectory[i].VirtualAddress));
	PIMAGE_BASE_RELOCATION pTempRelocation = pBaseRelocation;
	PWORD pChangeAddress = NULL;
	int numberOfBlock = 1;

	printf("*********重定位表*****************\n");
	while(pTempRelocation->VirtualAddress != 0 && pTempRelocation->SizeOfBlock != 0){
		pChangeAddress = (PWORD)((DWORD)pTempRelocation + 8);
		printf("第%d块\n", numberOfBlock);
		printf("地址:%x\n", pTempRelocation->VirtualAddress);
		printf("大小:%x\n", pTempRelocation->SizeOfBlock);
		for(i = 0; i < (pTempRelocation->SizeOfBlock-8)/2; i++){
			printf("%d : %x  ", i+1, pChangeAddress[i]);
			if(((pChangeAddress[i]&0x3000)^0x3000) != 0){
				printf("无需修改\n");
			}else {
				printf("\n");
			}
		}
		numberOfBlock++;
		pTempRelocation = (PIMAGE_BASE_RELOCATION)((DWORD)pTempRelocation + pTempRelocation->SizeOfBlock);
		printf("---------------------------------\n");
	}

	free(pFileBuffer);

	return ;	
}

5.10 IAT表

  • 学会移动 IAT 表等重要的 PE 结构体才能更有效地给文件加密。

练习:
1、代码实现导入表注入。

//为新增表添加节
DWORD addSectionForMoveTable(IN LPSTR lpszFile, DWORD size)
{
	//定义PE各结构体
	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;
	PIMAGE_SECTION_HEADER pTempSectionHeader = NULL;

	LPVOID pFileBuffer = NULL;
	LPVOID pNewFileBuffer = NULL;
	
	int fileSize = 0;
	int newFileSize = 0;
	int offsetOperSign = 0;
	DWORD FOA = 0;
	
	//读取文件到FileBuffer
	fileSize = ReadPEFile(lpszFile, &pFileBuffer);
	if(fileSize == 0){
		printf("读取文件失败\n");
		return 0;
	}
	//printf("读取文件成功,filesize:%d\n", fileSize);

	//加载PE结构体
	//DOC头
	pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
	pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer+pDosHeader->e_lfanew);
	//NT头
	pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);
	//可选PE头
	pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER);
	//节表头
	pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader+pPEHeader->SizeOfOptionalHeader);

	//判断是否有足够的空白区添加节表
	//标记是否需要进行偏移操作
	pTempSectionHeader = pSectionHeader + pPEHeader->NumberOfSections;
	if(pTempSectionHeader->Name[0] == 0 && pOptionHeader->SizeOfHeaders - ((DWORD)pTempSectionHeader - (DWORD)pDosHeader) > 80){
			offsetOperSign = 0;
	}else{
		if(pDosHeader->e_lfanew - 64 > 80){
			offsetOperSign = 1;
		}else{
			printf("没有足够的空间新增节表\n");
			free(pFileBuffer);
			return 0;
		}
	}

	//条件成立,则进行偏移操作
	if(offsetOperSign == 1){
		//将PE头的内容复制到DOS头后面
		memcpy((void*)((DWORD)pDosHeader + 64), (void*)((DWORD)pDosHeader + (DWORD)pDosHeader->e_lfanew), (4 + IMAGE_SIZEOF_FILE_HEADER + pPEHeader->SizeOfOptionalHeader + pPEHeader->NumberOfSections*40));
		//清空PE头尾部到原PE头尾部的内容
		memset((void*)((DWORD)pTempSectionHeader - ((DWORD)pDosHeader->e_lfanew - 64)), 0, ((DWORD)pDosHeader->e_lfanew - 64));
		//修改DOS头的偏移
		pDosHeader->e_lfanew = 0x40; //64 = 0x40

		//修改指向PE头的指针
		pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer+pDosHeader->e_lfanew);
		//NT头
		pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);
		//可选PE头
		pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER);
		//节表头
		pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader+pPEHeader->SizeOfOptionalHeader);
		//指向节表尾部的指针
		pTempSectionHeader = pSectionHeader + pPEHeader->NumberOfSections;

	}

	//进行新增节操作
	//复制第一个节表的内容到节表尾部
	memcpy((void*)pTempSectionHeader, (void*)pSectionHeader, 40);
	//修改新增节表的内容
	//修改name
	char name[9] = ".table";
	for(int i = 0; i < 9; i++){
		if(name[i] == 0){
			break;
		}
		pTempSectionHeader->Name[i] = name[i];
	}
	//定位最后一个节表
	PIMAGE_SECTION_HEADER pLastSectionHeader = pSectionHeader + pPEHeader->NumberOfSections - 1;
	//修改VirtualSize,此大小为实际大小,留出空白区添加代码
	pTempSectionHeader->Misc.VirtualSize = 0;
	//修改VirtualAddress
	pTempSectionHeader->VirtualAddress =  pLastSectionHeader->VirtualAddress + (pLastSectionHeader->Misc.VirtualSize > pLastSectionHeader->SizeOfRawData?pLastSectionHeader->Misc.VirtualSize:pLastSectionHeader->SizeOfRawData);
	pTempSectionHeader->VirtualAddress = Align(pTempSectionHeader->VirtualAddress, 0x1000);
	//修改sizeOfRawData
	pTempSectionHeader->SizeOfRawData = size;
	//修改PointerToRawData
	pTempSectionHeader->PointerToRawData = pLastSectionHeader->PointerToRawData + pLastSectionHeader->SizeOfRawData;
	
	//记录FOA的值
	FOA = pTempSectionHeader->PointerToRawData;

	//修改PE头
	//修改节表个数
	pPEHeader->NumberOfSections = pPEHeader->NumberOfSections + 1;
	//修改SizeOfImage的大小
	pOptionHeader->SizeOfImage = pOptionHeader->SizeOfImage + size;
	
	//新增节大小
	//注意FileBuffer已在realloc中释放
	pNewFileBuffer = realloc(pFileBuffer, fileSize+size);

	if(!pNewFileBuffer){
		printf("新增节大小失败\n");
		return 0;
	}
	printf("新增节大小成功\n");

	//保存fileBuffer
	newFileSize = MemeryToFile(pNewFileBuffer, fileSize+size, lpszFile);
	if(newFileSize == 0){
		printf("保存文件失败\n");
		free(pNewFileBuffer);
		return 0;
	}
	printf("保存文件成功,newFileSize:%d\n", newFileSize);

	free(pNewFileBuffer);

	//返回新增节的FOA
	return FOA;
}

//导入表注入
DWORD injectImportTable(IN LPSTR lpszFile,  LPSTR dllName, LPSTR exportFunctionName)
{
	//循环2次,第一次用于判断是否需要新增节
	for(int number = 0; number < 2; number++){
		//计算各节空白区的大小
		//定义PE各结构体
		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;

		LPVOID pFileBuffer = NULL;
	
		int fileSize = 0;

		//读取文件到FileBuffer
		fileSize = ReadPEFile(lpszFile, &pFileBuffer);
		if(fileSize == 0){
			printf("读取文件失败\n");
			return 0;
		}
		//printf("读取文件成功,filesize:%d\n", fileSize);

		//加载PE结构体
		//DOC头
		pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
		pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer+pDosHeader->e_lfanew);
		//NT头
		pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);
		//可选PE头
		pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER);
		//节表头
		pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader+pPEHeader->SizeOfOptionalHeader);

		//修改全部节的属性 加上可写属性 0x80000000;
		for(int countSection = 0; countSection < pPEHeader->NumberOfSections; countSection++){
			//printf("原节表属性:%x\n", pSectionHeader[countSection].Characteristics);
			if((pSectionHeader[countSection].Characteristics & 0x80000000) == 0){
				pSectionHeader[countSection].Characteristics = pSectionHeader[countSection].Characteristics | 0x80000000;
			}
			//printf("修改后节表属性:%x\n", pSectionHeader[countSection].Characteristics);
			//printf("-----------------------\n");
		}
		
		//将绑定导入表和IAT表的地址和大小置零
		//绑定导入表第12项 i = 11
		pOptionHeader->DataDirectory[11].VirtualAddress = 0;
		pOptionHeader->DataDirectory[11].Size = 0;

		//IAT表第11项 i = 12
		pOptionHeader->DataDirectory[12].VirtualAddress = 0;
		pOptionHeader->DataDirectory[12].Size = 0;

		//计算导入表的大小
		int i = 1;
		PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor = NULL;

		//打印导入表的信息
		pImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, pOptionHeader->DataDirectory[i].VirtualAddress));
		PIMAGE_IMPORT_DESCRIPTOR pTempImportDescriptor = pImportDescriptor;
		PIMAGE_IMPORT_BY_NAME pImportByName = NULL;
		PDWORD pOriginalThunk = NULL;
		PDWORD pFirstThunk = NULL;
	
		int sumOfImportTable = 0;
		int numberOfImportTable = 0;
		printf("*********导入表*****************\n");
		while(pTempImportDescriptor->OriginalFirstThunk != 0){
			numberOfImportTable++;
			sumOfImportTable = sumOfImportTable + 20;
		
			//指向下一个导入表
			pTempImportDescriptor++;
		}
		printf("共有%d个dll,大小:%x\n", numberOfImportTable, sumOfImportTable);

		//判断各节空白区的大小,并记录地址
		//20 字节的导入表、20字节的0作为结束
		int newImportTableSize = 40;
		int INTSize = 8;
		int IATSize = 8;
		int dllNameSize = strlen(dllName) + 1;
		int exportFunctionNameSize = strlen(exportFunctionName) + 1;

		PIMAGE_SECTION_HEADER pTempSectionHeader = pSectionHeader;
		int blankSize = 0;
		printf("共%d个节\n", pPEHeader->NumberOfSections);
		for(i = 0; i < pPEHeader->NumberOfSections; i++){
			blankSize = 0;
			printf("第%d个节\n", i);
			printf("对齐大小:%x\n", pTempSectionHeader->SizeOfRawData);
			printf("实际大小:%x\n", pTempSectionHeader->Misc.VirtualSize);
			if(pTempSectionHeader->SizeOfRawData < pTempSectionHeader->Misc.VirtualSize){
				printf("无空白区\n");
			}else{
				blankSize = pTempSectionHeader->SizeOfRawData - pTempSectionHeader->Misc.VirtualSize;
				printf("空白大小:%x\n", blankSize);		
			}

			//判断空白区大小与导入表大小之前的关系, 加2字节为PIMAGE_IMPORT_BY_NAME结构的HINT 
			int allSize = sumOfImportTable + newImportTableSize + INTSize + IATSize + dllNameSize + exportFunctionNameSize + 2;
			printf("导入表需要的空间:%x\n", allSize);
		
			if(blankSize > allSize){
				//可存放所有的数据
				printf("可存放所有的数据\n");

				//复制原导入表到空白区
				DWORD newImportAddress = (DWORD)pFileBuffer + pTempSectionHeader->PointerToRawData + pTempSectionHeader->Misc.VirtualSize;
				//将新增节的空间清0
				memset((void*)newImportAddress, 0, blankSize);
				//复制原导入表
				memcpy((void*)newImportAddress, pImportDescriptor, sumOfImportTable);
				//记录导入表的RVA和大小
				DWORD newImportAddressRva = pTempSectionHeader->VirtualAddress + pTempSectionHeader->Misc.VirtualSize;
				DWORD newImportDescriptorSize = sumOfImportTable + 20;

				//在新的导入表后,追加一个导入表
				memcpy((void*)(newImportAddress + sumOfImportTable), (void*)(newImportAddress + sumOfImportTable - 20), newImportTableSize);

				//指向新增导入表的地址,并修复相关值的RVA
				PIMAGE_IMPORT_DESCRIPTOR pNewImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)(newImportAddress + sumOfImportTable);
				pNewImportDescriptor->OriginalFirstThunk = newImportAddressRva + sumOfImportTable + newImportTableSize;
				pNewImportDescriptor->FirstThunk = pNewImportDescriptor->OriginalFirstThunk + 8;
				pNewImportDescriptor->TimeDateStamp = 0;
				pNewImportDescriptor->ForwarderChain = 0;

				//复制8个字节的INT表、8个字节的IAT表
				//存储INT的地址,指向FOA
				PIMAGE_THUNK_DATA32 pNewINT = (PIMAGE_THUNK_DATA32)(newImportAddress + sumOfImportTable + newImportTableSize);
				PIMAGE_THUNK_DATA32 pNewIAT = (PIMAGE_THUNK_DATA32)((DWORD)pNewINT + 8);

				//复制一个IMAGE_IMPORT_BY_NAME结构,前2个字节是0 后面是函数名称字符串
				//存储function的地址
				PIMAGE_IMPORT_BY_NAME pNewImportByName = (PIMAGE_IMPORT_BY_NAME)((DWORD)pNewIAT + 8);
				pNewImportByName->Hint = 0;
				strcpy((char*)pNewImportByName->Name, exportFunctionName);
				pNewImportByName->Name[exportFunctionNameSize - 1] = 0;

				//记录名字结构表的RVA
				DWORD ImportByNameRva = (pNewImportDescriptor->FirstThunk + 8);

				//将IMAGE_IMPORT_BY_NAME结构的RVA赋值给INT和IAT表的第一项
				pNewINT[0].u1.AddressOfData = ImportByNameRva; 
				pNewINT[1].u1.AddressOfData = 0;

				pNewIAT[0].u1.AddressOfData = ImportByNameRva; 
				pNewIAT[1].u1.AddressOfData = 0;

				//分配空间存储DLL名称字符串,并将字符串的RVA赋值给Name属性
				//存储dllname的地址
				PCHAR pNewDllName = (PCHAR)((DWORD)pNewImportByName + exportFunctionNameSize + 2);
				strcpy(pNewDllName, dllName);
				pNewDllName[dllNameSize - 1] = 0;
				//记录dll名字的RVA
				DWORD dllNameRva = ImportByNameRva + exportFunctionNameSize + 2;
				//将字符串的RVA赋值给Name属性
				pNewImportDescriptor->Name = dllNameRva;

				//修正IMAGE_DATA_DIRECTORY结构的VirtualAddress和Size
				pOptionHeader->DataDirectory[1].VirtualAddress = newImportAddressRva;
				pOptionHeader->DataDirectory[1].Size = newImportDescriptorSize;

				//修改节表的实际大小
				pTempSectionHeader->Misc.VirtualSize = pTempSectionHeader->Misc.VirtualSize + allSize;

				break;
			}
			printf("无足够空间存放所有的数据\n");
			printf("----------------------------\n");
			pTempSectionHeader++;
		}

		if(i != pPEHeader->NumberOfSections){
			//保存到文件中
			printf("导入表注入结束\n");
			DWORD newFileSize = MemeryToFile(pFileBuffer, fileSize, lpszFile);
			if(newFileSize == 0){
				printf("保存文件失败\n");
				free(pFileBuffer);
				return 0;
			}
			printf("保存文件成功,newFileSize:%d\n", newFileSize);

			free(pFileBuffer);
			break;
		}
		//新增节来复制导入表
		printf("新增节来复制导入表\n");
		free(pFileBuffer);
		//新增节操作
		addSectionForMoveTable(lpszFile, 0x1000);
	}

	return 1;
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值