PE文件结构(五)

Last updated on October 7, 2023 pm

PE练习,新增一个节并添加shellcode

回顾

前面已经手动在原代码区空白地方增加了shellcode,并且完成代码实现。但是往往要添加的shellcode不仅仅只有这么一点,那么就会面临代码空白区不够的地方,这时就可以考虑新增一个节,并在新增节里面添加shellcode

练习

要求

1.新增一个节后,源文件可以正常运行

2.新增一个节后,添加原来的shellcode

要解决的问题

Q1:如何新增一个节

A1:应当立即想到的是要修改FileHeader里的NumberOfSections成员,将其加1

Q2:如何设置新增节各个成员的属性

A2:在新增节成员属性之前,需要考虑这样一个问题,即原来的头空间区域内是否还有足够空间增加一个节目录项。在Windows中,判断一个表是否遍历完的基本操作就是查看这个表项最后是否还有同样大小的0。比如判断字符串结束的标志是0,这里就是0x24个0,因为节目录项的大小就是40个字节。如果说SizeOfHeader的大小是0x1000,而已经使用的数据占0x976及以上,那么这个时候就没有足够空间再增加一个节目录项,只有已经使用数据项小于0x952才可以再增加一个节目录项,这样添加完后,就能保证末尾一定有0x24个0来结束。

note:你一定想问要是真的不够怎么办呢。是否还记得DOS头里有一个DOS stub的垃圾数据,这时就可以考虑把NT头后面的头们全部往上提,占据DOS stub,而且只需要修改DOS Header的e_lfanew成员就行。

一般来说,这样就已经可以解决99%的问题,那如果还是不够呢?其实也不一定非要增加一个节啦,还可以扩大一个节或者合并其他节的嘛,不过这是后面的内容了。

现在来考虑各个成员如何设置:

  • Name 可以随意一个8字节内的字符串
  • Misc 和 SizeOfRawData,前者可以是一个不准确的值,后者是文件对齐后的大小,二者可以设为同样的值。那么这个值是不是随意的呢,理论上是的。但是为了对齐方便,最好是文件对齐FileAlignment和内存对齐SectionAlignment的公倍数,这样可以省下很多麻烦
  • VisualAddress:在内存中的起始位置,其实就是上一个节区的末尾位置。而上一个节区的末尾位置就是其VisualAddress + Misc 或者VisualAddress + SizeofRawData,两者要取大的那一个,然后再内存对齐的值就是新节区VisualAddress
  • PointerToRawData:在文件中的起始位置,算法与VisualAddress基本相同

Q3:还应该改变哪些值

A3:在OptionalHeader中还有一个成员SizeOfImage记录内存大小,在新增一个节区后这个值自然就要变大。而之前提到新增的节区大小最好是文件对齐FileAlignment和内存对齐SectionAlignment的公倍数,这样的话SizeOfImage也可以直接加上这个值,不需要再做额外功夫

代码实现

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
#include<stdio.h>
#include<malloc.h>
#include<windows.h>

#define ADD_SIZE 0x1000
#define ImgaeBase 0x10000
#define MessageBox_Addr 0x75C93D90
BYTE Shell[] = {0x6A,0x00,0x6A,0x00,0x6A,0x00,0x6A,0x00,
0xE8,0x00,0x00,0x00,0x00,
0xe9,0x00,0x00,0x00,0x00};


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


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 + 0x100;

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_HEADER 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_HEADER)(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 = 0x60000020;
//到这里就已经完成节的增加了

//插入shellcode
char* shell_begin = NewBuffer + NewSection->PointerToRawData;

DWORD begin_FOA = NewSection->PointerToRawData;


DWORD e8_FOA = begin_FOA + 8;
DWORD e9_FOA = e8_FOA + 5;

DWORD begin_RVA = FOA_TO_RVA(begin_FOA,NewBuffer);
//计算call地址的编码 即e8 xxxxx
DWORD e8_x_addr = MessageBox_Addr - (FOA_TO_RVA(e9_FOA,NewBuffer)+ImgaeBase);
//计算原oep的地址编码 即e9 xxxxx
DWORD e9_x_addr = OptionalHeader->AddressOfEntryPoint - (FOA_TO_RVA(e9_FOA,NewBuffer)+5);

//先修改原OEP
OptionalHeader->AddressOfEntryPoint = NewSection->VirtualAddress;
// OptionalHeader->AddressOfEntryPoint = 0x5DEFC;
printf("%X",FOA_TO_RVA(begin_FOA,NewBuffer));

memcpy(shell_begin,Shell,18);

//这里要注意memcpy的用法
DWORD* e8 = (DWORD*)(shell_begin + 9);
DWORD* e9 = (DWORD*)(shell_begin + 14);
memcpy(e8,&e8_x_addr,4);//这里也可能是1,把4改为1
memcpy(e9,&e9_x_addr,4);


return NewBuffer;
}

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

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

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

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

new_buffer = Section_add(fp1,buffer);
fwrite(new_buffer,size+0x1000+0x100,1,fp2);

return 0;
}




PE文件结构(五)
http://example.com/2023/07/04/PE_4/
Author
yring
Posted on
July 4, 2023
Licensed under