文章目录
本篇文章只是记录学习过程中逆向知识相关的练习题解答,要系统的知识梳理请参考其他书籍或文章。
一、进制
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乘法表。
2、将16进制的元素用2进制的元素下定义。
- 二进制与十六进制的映射
十六进制 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
二进制 | 0000 | 0001 | 0010 | 0011 | 0100 | 0101 | 0110 | 0111 | 1000 | 1001 | 1010 | 1011 | 1100 | 1101 | 1110 | 1111 |
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]; 20个0
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加载功能。
//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;
}