PE文件结构(END)

Last updated on October 7, 2023 pm

导入表注入

回顾

导入表的意义是告诉操作系统我需要使用其他PE提供的什么函数,然后操作系统就会帮忙把这些DLL给加载进此4GB空间中。导入表里面又有两张表,分别是INT表和IAT表,两者在加载前的内容完全相同,在加载时IAT表则会发生变化,会更改为函数地址。

注入

原理

在操作系统加载DLL时,会自动执行DLL本身的函数

比如这里switch中的DLL_PROCESS_ATTACH,也即操作系统在加载此DLL时会自动执行这个分支里的语句,而操作系统又是根据导入表来加载DLL的,那么就可以由此制作一个DLL,在此分支里面执行想要的操作,在加载时就会自动执行,从而达到某种目的。

换句话说,我们需要人为在导入表里添加一项DLL,来骗过操作系统。但是会面临一个问题,即原来的导入表不够位置,因为操作系统是根据导入表最后的全0结构来判断是否导入表是否结束,如果说原来的导入表后面只有20个零(一个导入表结构的大小),那么显然是不能在此基础上添加导入表的,因为这样就会加载进错误的导入表,从而导致程序崩溃。

也就是说我们需要移动导入表!

移动导入表

移动最好的方式是新开一个节,然后根据目录项开始逐表逐表copy,你可能有一个疑问——为什么要逐表,而不直接使用目录项的size成员呢?原因有二,第一这个size是不准确的,在许多编译器中都会将此值置0,你也可以自己把它设置成0,程序一样可以正常运行;第二这个size可能不只包含导入表,还可能包含了INT表,IAT表等所有的大小,但是我们只想copy单纯的导入表。

因此需要逐表逐表copy,然后用20字节来做新表的大小,再用20字节做结束的标志

新增INT表、IAT表

仅仅创建新表是不够的,因此此时表结构全为0,即使设置了DLL的名字。因为操作系统会看这个表的INT表、IAT表,如果这两张表为0,就说明没有用到这个DLL,就不会把他加载程序4GB空间。因此需要新增INT表、IAT表,我们只需要新增一项就可以使得操作系统加载DLL了。

同理INT表、IAT表也需要全零0结构作为结束,而这两张表表项大小是4字节,因此每一张表都需要8字节来完成新增操作。此后就是设置这两张表的表项,使其指向PIMAGE_IMPORT_BY_NAME结构(当然使用序号的方式也是可以的)

简单来说,就是重新构这几张表的指向关系,也就是如图的所有箭头

只要重新建立这些箭头指向,也就代表一个新的导入表成功建立

注入成功标志就是在双击运行exe时,会执行注入DLL里的DLL_PROCESS_ATTACH分支里的语句,在本例中就是简单弹一个窗,在结束运行时则是运行DLL_PROCESS_DETACH分支里的语句,也是弹一个窗。

代码

多说无益,直接看代码

NOTE:要注入的程序如果是32位,则只能使用32位的DLL;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
#include<stdio.h>
#include<malloc.h>
#include<windows.h>

#define ADD_SIZE 0x3000

DWORD Size(FILE* fp){
DWORD size = 0;
fseek(fp,0,SEEK_END);
size = ftell(fp);
fseek(fp,0,SEEK_SET);
return size;
}

DWORD RVA_TO_FOA(DWORD RVA,char* Buffer){
PIMAGE_DOS_HEADER DosHeader = NULL;
PIMAGE_NT_HEADERS NTHeader = NULL;
PIMAGE_FILE_HEADER FileHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 OptionalHeader = NULL;
PIMAGE_SECTION_HEADER SectionHeader = NULL;

DosHeader = (PIMAGE_DOS_HEADER)Buffer;
FileHeader = (PIMAGE_FILE_HEADER)(Buffer + DosHeader->e_lfanew + 4);
OptionalHeader = (PIMAGE_OPTIONAL_HEADER32)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER);
SectionHeader = (PIMAGE_SECTION_HEADER)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER + FileHeader->SizeOfOptionalHeader);

if (RVA < OptionalHeader->SizeOfHeaders)
{
return RVA;
}

for(int i=0;i<FileHeader->NumberOfSections-1;i++){
if(RVA >= SectionHeader->VirtualAddress && RVA < (SectionHeader+1)->VirtualAddress){
DWORD offset = RVA - SectionHeader->VirtualAddress;
return SectionHeader->PointerToRawData + offset;
}
else{
SectionHeader +=1;
}
}

//最后一个节区
DWORD offset = RVA - SectionHeader->VirtualAddress;
return SectionHeader->PointerToRawData + offset;
}

DWORD FOA_TO_RVA(DWORD FOA,char* Buffer){
PIMAGE_DOS_HEADER DosHeader = NULL;
PIMAGE_NT_HEADERS NTHeader = NULL;
PIMAGE_FILE_HEADER FileHeader= NULL;
PIMAGE_OPTIONAL_HEADER OptionalHeader = NULL;
PIMAGE_SECTION_HEADER SectionHeader = NULL;

DosHeader = (PIMAGE_DOS_HEADER)Buffer;
FileHeader = (PIMAGE_FILE_HEADER)(Buffer + DosHeader->e_lfanew + 4);
OptionalHeader = (PIMAGE_OPTIONAL_HEADER)(Buffer + DosHeader->e_lfanew + 4 +IMAGE_SIZEOF_FILE_HEADER);
SectionHeader = (PIMAGE_SECTION_HEADER)(Buffer + DosHeader->e_lfanew + 4 +IMAGE_SIZEOF_FILE_HEADER + FileHeader->SizeOfOptionalHeader);

if (FOA < OptionalHeader->SizeOfHeaders)
{
return FOA;
}


for(int i =0;i<FileHeader->NumberOfSections-1;i++){
if(FOA >= SectionHeader->PointerToRawData&& FOA < (SectionHeader +1)->PointerToRawData){

DWORD offset = FOA - SectionHeader->PointerToRawData;
return SectionHeader->VirtualAddress + offset;
}
else{
SectionHeader += 1;
}
}
//最后一个节区
DWORD offset = FOA - SectionHeader->PointerToRawData;
return SectionHeader->VirtualAddress + offset;
}

char* Section_add(FILE* fp,char* buffer){
DWORD size = Size(fp);
DWORD new_size = size + ADD_SIZE ;

char* NewBuffer = (char*)malloc(new_size);
if (!NewBuffer)
{
printf("failed to creat buffer!\n");
return 0;
}

PIMAGE_DOS_HEADER DosHeader = NULL;
PIMAGE_NT_HEADERS NTHeader = NULL;
PIMAGE_FILE_HEADER FILEHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 OptionalHeader = NULL;
PIMAGE_SECTION_HEADER SectionHeader = NULL;

memset(NewBuffer,0,new_size);
memcpy(NewBuffer,buffer,size);

//初始化PE头部信息
DosHeader = (PIMAGE_DOS_HEADER)NewBuffer;
NTHeader = (PIMAGE_NT_HEADERS)(NewBuffer + DosHeader->e_lfanew);
FILEHeader = (PIMAGE_FILE_HEADER)(NewBuffer+DosHeader->e_lfanew + 4);
OptionalHeader = (PIMAGE_OPTIONAL_HEADER32)(NewBuffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER);
SectionHeader = (PIMAGE_SECTION_HEADER)(NewBuffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER + FILEHeader->SizeOfOptionalHeader);

//最后一个节表地址
PIMAGE_SECTION_HEADER LastSection = (PIMAGE_SECTION_HEADER)(NewBuffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER + FILEHeader->SizeOfOptionalHeader+IMAGE_SIZEOF_SECTION_HEADER*(FILEHeader->NumberOfSections-1));
//新节表地址
PIMAGE_SECTION_HEADER NewSection = (PIMAGE_SECTION_HEADER)(NewBuffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER + FILEHeader->SizeOfOptionalHeader + IMAGE_SIZEOF_SECTION_HEADER * (FILEHeader->NumberOfSections));

//判断剩余空间
DWORD Remained_size = (DWORD)(OptionalHeader->SizeOfHeaders - DosHeader->e_lfanew - 4
- IMAGE_SIZEOF_FILE_HEADER - IMAGE_SIZEOF_SECTION_HEADER * FILEHeader->NumberOfSections);
if (Remained_size < 2 * IMAGE_SIZEOF_SECTION_HEADER)
{
printf("do not have enough space!\n");
free(NewBuffer);
return 0;
}

//===================修改信息====================
//其他头部需要修改的信息
FILEHeader->NumberOfSections += 1;
OptionalHeader->SizeOfImage += ADD_SIZE;

//填入数据
memcpy(NewSection->Name,".NewSec",8);
NewSection->Misc.VirtualSize = ADD_SIZE;
NewSection->SizeOfRawData = ADD_SIZE;
NewSection->PointerToRawData = LastSection->PointerToRawData + LastSection->SizeOfRawData;

DWORD add_size = LastSection->Misc.VirtualSize > LastSection->SizeOfRawData ? LastSection->Misc.VirtualSize : LastSection->SizeOfRawData;

NewSection->VirtualAddress = LastSection->VirtualAddress + add_size;

//找到开始的地方
if (NewSection->VirtualAddress % OptionalHeader->SectionAlignment)
{
NewSection->VirtualAddress = NewSection->VirtualAddress / OptionalHeader->SectionAlignment * OptionalHeader->SectionAlignment +OptionalHeader->SectionAlignment;
}

NewSection->Characteristics = 0xC0000040;

return NewBuffer;
}

void DLL_INJECT(char* Buffer){
PIMAGE_DOS_HEADER DosHeader = NULL;
PIMAGE_NT_HEADERS NTHeader = NULL;
PIMAGE_FILE_HEADER FILEHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 OptionalHeader = NULL;
PIMAGE_SECTION_HEADER SectionHeader = NULL;

//初始化PE头部信息
DosHeader = (PIMAGE_DOS_HEADER)Buffer;
NTHeader = (PIMAGE_NT_HEADERS)(Buffer + DosHeader->e_lfanew);
FILEHeader = (PIMAGE_FILE_HEADER)(Buffer+DosHeader->e_lfanew + 4);
OptionalHeader = (PIMAGE_OPTIONAL_HEADER32)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER);
SectionHeader = (PIMAGE_SECTION_HEADER)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER + FILEHeader->SizeOfOptionalHeader);

//定位到新节区开始
PIMAGE_SECTION_HEADER NewSec = SectionHeader + FILEHeader->NumberOfSections - 1;
char* pNewSec = Buffer + NewSec->PointerToRawData;
//定位到导入表
DWORD Import_Rva = OptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
DWORD Import_Foa = RVA_TO_FOA(Import_Rva,Buffer);

PIMAGE_IMPORT_DESCRIPTOR pImport_Table =(PIMAGE_IMPORT_DESCRIPTOR)(Buffer + Import_Foa);
// printf("%d",OptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size);

//复制原导入表
int index = 0;
while(pImport_Table->FirstThunk){
memcpy(pNewSec + 20*index,(pImport_Table),20);
pImport_Table++;
index++;
}


//指向新复制导入表结束
PIMAGE_IMPORT_DESCRIPTOR New_Import = PIMAGE_IMPORT_DESCRIPTOR(pNewSec + index * 20);

//新增INT表,加40即是预留20字节作为导入表结束标志
DWORD* New_INT = (DWORD*)(pNewSec + index * 20+ 40);
//新增IAT表,加48即是预留4字节作为INT结束标志
DWORD* New_IAT =(DWORD*)(pNewSec + index * 20 + 48);

//新增IMAGE_IMPORT_BY_NAME结构,加56同理
PIMAGE_IMPORT_BY_NAME New_NAME = PIMAGE_IMPORT_BY_NAME(pNewSec + index * 20 + 56);

//这里的名称一定要是DLL中导出的名称,否则程序无法正常运行
memcpy(New_NAME->Name,"_add",4);

//设置新INT、IAT表指向,使其指向PIMAGE_IMPORT_BY_NAME结构,也就是右半部分的箭头
*New_INT = FOA_TO_RVA((char*)New_NAME - Buffer,Buffer);

*New_IAT = FOA_TO_RVA((char*)New_NAME - Buffer,Buffer);

//设置导入表的指向关系,使其指向新INT表、IAT表,也就是左半部分的箭头
New_Import->OriginalFirstThunk = FOA_TO_RVA(((char*)New_INT - Buffer),Buffer);
New_Import->FirstThunk = FOA_TO_RVA((char*)New_IAT - Buffer,Buffer);

//这里加100是随意的
char* New_DLL_NAME = pNewSec + index * 20 + 100;
memcpy(New_DLL_NAME,"Func.dll",8);

//导入表的NAME成员存储的是DLL名称的RVA,而非NAME本身
New_Import->Name = FOA_TO_RVA(New_DLL_NAME - Buffer,Buffer);

//修改目录项,使其指向新导入表
OptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress = NewSec->VirtualAddress;
}

int main(){
char* buffer;
char* new_buffer;

FILE* fp1 = NULL;
FILE* fp2 = NULL;

errno_t err_1 = fopen_s(&fp1, "C:\\Users\\yongrin\\Desktop\\T.exe", "rb");
errno_t err_2 = fopen_s(&fp2, "C:\\Users\\yongrin\\Desktop\\Test_Test.exe", "wb");

DWORD size = Size(fp1);
buffer = (char*)malloc(size);
fread(buffer,size,1,fp1);

new_buffer = Section_add(fp1,buffer);
DLL_INJECT(new_buffer);

fwrite(new_buffer,size+ADD_SIZE,1,fp2);

return 0;
}




PE文件结构(END)
http://example.com/2023/07/25/PE_10/
Author
yring
Posted on
July 25, 2023
Licensed under