首页
社区
课程
招聘
[原创]ring3注入学习(1)导入表注入
2021-8-18 20:47 20595

[原创]ring3注入学习(1)导入表注入

2021-8-18 20:47
20595

原理

导入表的结构

在编程中常常用到“导入函数”(Import functions),导入函数就是被程序调用但其执行代码又不在程序中的函数,这些函数的代码位于一个或者多个DLL中,在调用者程序中只保留一些函数信息,包括函数名及其驻留的DLL名等。

 

于磁盘上的PE 文件来说,它无法得知这些输入函数在内存中的地址,只有当PE 文件被装入内存后,Windows 加载器才将相关DLL 装入,并将调用输入函数的指令和函数实际所处的地址联系起来。这就是“动态链接”的概念。动态链接是通过PE 文件中定义的“导入表”来完成的,导入表中保存的正是函数名和其驻留的DLL 名等。

 

导入表由一系列IMAGE_IMPORT_DESCRIPTOR结构体组成:

 

 

结构的数量取决于程序要使用的DLL文件的数量,每一个结构对应一个DLL文件

 

该结构体的定义如下:

1
2
3
4
5
6
7
8
9
10
struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;
        DWORD   OriginalFirstThunk;        
    } DUMMYUNIONNAME;
    DWORD   TimeDateStamp;
    DWORD   ForwarderChain;        
    DWORD   Name;//导入模块名的RVA
    DWORD   FirstThunk;             
} IMAGE_IMPORT_DESCRIPTOR;

具体在PE文件中,就像这个样子:

 

 

在IMAGE_IMPORT_DESCRIPTOR的结构体的最后面,附带着许多小结构体,里面记录的信息是该导入表的DLL要导入使用的API

1
2
3
4
typedef struct _IMAGE_IMPORT_BY_NAME {
    WORD    Hint;
    BYTE    Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

 

以上便是导入表的基础结构

导入表的注入

 

由《加密与解密》中得知。Windows装载PE文件时,会检查导入表,将导入表所包含要使用的DLL加载进程序中,这也就产生了一个DLL注入的点,如果将恶意DLL写入导入表中,就可以让程序在运行时调用恶意DLL中的代码,达到DLL注入的效果

实现

要实现导入表注入,需要把要注入的DLL构造的结构体塞进导入表里,但是由于导入表默认不是在PE文件的最后,所以导入表原来所在的位置不一定有足够大的空间塞入一个新的结构体,这时我们就需要把导入表移动到新的足够大的位置,有两种办法,扩大最后一个节和直接新增一个节,比较方便的方法是选择新增一个节,在新增节后将原导入表放进去,如何在最后写入自己的导入表,再新增8字节的INT和8字节IAT,至于为什么要增加8字节的INT表和8字节的IAT表,可以这样解释:

 

 

如图,当我们新写入一个IMAGE_IMPORT_DESCRIPTOR结构,我们就需要建立INT和IAT对于该结构的映射,此时就需要扩充IAT和INT表

 

具体实现步骤如下:

  1. 找到原导入表
  2. 在程序最后开辟一个新节(也可扩大最后一个节)
  3. 拷贝原来的导入表到新节中
  4. 在新节拷贝的导入表后新增一个导入表
  5. 增加8字节的INT表和8字节的IAT表
  6. 存储要注入的dll的名称
  7. 增加一个_IMAGE_IMPORT_BYNAME结构,并将函数名称存进结构体第一个变量后的内存中
  8. 将_IMAGE_IMPORT_BY_NAME结构的地址的RVA赋值给INT表和IAT表第一项
  9. 将dll名称所在位置的首地址的RVA赋值给新增导入表的Name
  10. 修改IMAGE_DATA_DIRECTORY结构的VirtualAddress和Size

代码

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
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
 
 
extern "C" __declspec(dllexport) void puts()
{
    MessageBoxA(0, "hi", "hello", 0);
    return;
}
 
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        puts();
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

注入程序代码

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
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
#include<Windows.h>
#include<stdio.h>
#define DLLNAMELENGTH 0xE
#define FUNCTIONNAMELENGTH 0xF
#define FUNCTIONNAME "puts"
#define DLLNAME "Dll1.dll"
 
//获取DOS头
PIMAGE_DOS_HEADER GetDosHeader(_In_ char* pBase) {
    return PIMAGE_DOS_HEADER(pBase);
}
 
//获取NT头
PIMAGE_NT_HEADERS GetNtHeader(_In_ char* pBase) {
    return PIMAGE_NT_HEADERS(GetDosHeader(pBase)->e_lfanew + (SIZE_T)pBase);
}
 
//获取文件头
PIMAGE_FILE_HEADER GetFileHeader(_In_ char* pBase) {
    return &(GetNtHeader(pBase)->FileHeader);
}
 
//获取OPT头
PIMAGE_OPTIONAL_HEADER32 GetOptHeader(_In_ char* pBase) {
    return &(GetNtHeader(pBase)->OptionalHeader);
}
 
PIMAGE_SECTION_HEADER GetSecByName(_In_ char* pBase, _In_ const char* name) {
    DWORD Secnum = GetFileHeader(pBase)->NumberOfSections;
    PIMAGE_SECTION_HEADER Section = IMAGE_FIRST_SECTION(GetNtHeader(pBase));
    char buf[10] = { 0 };
    for (DWORD i = 0; i < Secnum; i++) {
        memcpy_s(buf, 8, (char*)Section[i].Name, 8);
        if (!strcmp(buf, name)) {
            return Section + i;
        }
    }
    return nullptr;
}
 
//获取最后一个区段
PIMAGE_SECTION_HEADER GetLastSec(_In_ char* PeBase) {
    DWORD SecNum = GetFileHeader(PeBase)->NumberOfSections;
    PIMAGE_SECTION_HEADER FirstSec = IMAGE_FIRST_SECTION(GetNtHeader(PeBase));
    PIMAGE_SECTION_HEADER LastSec = FirstSec + SecNum - 1;
    return LastSec;
}
 
char* OpenPeFiles(_In_ const char* Path, _Out_opt_ DWORD* nFileSize)
{
    //读文件
    HANDLE hFile = CreateFileA(Path,
        GENERIC_READ,
        FILE_SHARE_READ,
        NULL,
        OPEN_ALWAYS,
        FILE_ATTRIBUTE_NORMAL,
        NULL);
    if (hFile == INVALID_HANDLE_VALUE)
    {
        //printf("打开文件失败");
        return NULL;
    }
    DWORD PeSize = GetFileSize(hFile, NULL);
    if (nFileSize)
        *nFileSize = PeSize;
    DWORD ReadSize = 0;
    char* PeBase = new CHAR[PeSize]{ 0 };
    ReadFile(hFile, PeBase, PeSize, &ReadSize, NULL);
 
    PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)PeBase;
    //检测DOS头和NT头
    if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
    {
        //printf("不是PE文件\n");
        //system("pause");
        return NULL;
    }
    PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)(PeBase + pDosHeader->e_lfanew);
    if (pNtHeader->Signature != IMAGE_NT_SIGNATURE)
    {
        //printf("不是PE文件\n");
        //system("pause");
        return NULL;
    }
 
    CloseHandle(hFile);
    return PeBase;
}
 
//粒度对齐处理
int AlignMent(_In_ int size, _In_ int alignment) {
    return (size) % (alignment) == 0 ? (size) : ((size) / alignment + 1) * (alignment);
}
 
//新增节
char* AddSection(_In_ char*& PeBase, _In_ DWORD& PeSize, _In_ const char* Section_name, _In_ const int Section_size)
{
    GetFileHeader(PeBase)->NumberOfSections++;
    PIMAGE_SECTION_HEADER LastPeSection = GetLastSec(PeBase);
 
    memcpy(LastPeSection->Name, Section_name, 8);
    LastPeSection->Misc.VirtualSize = Section_size;
    LastPeSection->VirtualAddress = (LastPeSection - 1)->VirtualAddress + AlignMent((LastPeSection - 1)->SizeOfRawData, GetOptHeader(PeBase)->SectionAlignment);
    LastPeSection->SizeOfRawData = AlignMent(Section_size, GetOptHeader(PeBase)->FileAlignment);
    LastPeSection->PointerToRawData = AlignMent(PeSize, GetOptHeader(PeBase)->FileAlignment);
    LastPeSection->Characteristics = 0xc0000040;//节表属性设为该值,意为该节表可读可写且包含已初始化的数据
 
    GetOptHeader(PeBase)->SizeOfImage = LastPeSection->VirtualAddress + LastPeSection->SizeOfRawData;
 
    int NewSize = LastPeSection->PointerToRawData + LastPeSection->SizeOfRawData;
 
    char* NewPeBase = new char [NewSize] {0};
    //向新缓冲区录入数据
    memcpy(NewPeBase, PeBase, PeSize);
    //缓存区更替
    delete PeBase;
    PeSize = NewSize;
    return NewPeBase;
}
 
//保存文件
void SaveFile(_In_ const char* path, _In_ const char* data, _In_ int FileSize) {
    HANDLE hFile = CreateFileA(
        path,
        GENERIC_WRITE,
        FILE_SHARE_READ,
        NULL,
        CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL,
        NULL
    );
    DWORD Buf = 0;
    WriteFile(hFile, data, FileSize, &Buf, NULL);
    CloseHandle(hFile);
}
 
//将RVA的值转换成FOA
LPVOID RvaToFoa(LPVOID pFileBuffer, LPSTR virtualAddress) {
    LPSTR sectionAddress = NULL;//记录距离节头的距离
    LPSTR fileAddress = 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;
 
    if (pFileBuffer == NULL) {
        printf("文件写入内存失败!\n");
        return NULL;
    }
 
    pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
    pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
    pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + 4);
    pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + 20);
    pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
 
    if ((DWORD)virtualAddress <= pOptionHeader->SizeOfHeaders) {
        return virtualAddress;
    }
 
    for (DWORD i = 1; i <= pPEHeader->NumberOfSections; i++) {
        if ((DWORD)virtualAddress < pSectionHeader->VirtualAddress) {
            pSectionHeader--;
            break;
        }
        else if (i == pPEHeader->NumberOfSections) {
            break;
        }
        else {
            pSectionHeader++;
        }
 
    }
 
    //距离该节头的距离
    sectionAddress = virtualAddress - pSectionHeader->VirtualAddress;
    fileAddress = pSectionHeader->PointerToRawData + sectionAddress;
 
    return (LPVOID)fileAddress;
}
 
//将FOA的值转换成RVA
LPVOID FoaToRva(LPVOID pFileBuffer, LPSTR fileaddress) {
    LPSTR sectionAddress = NULL;//记录距离节头的距离
    LPSTR virtualaddress = 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;
 
    if (pFileBuffer == NULL) {
        printf("文件写入内存失败!\n");
        return NULL;
    }
 
    pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
    pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
    pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + 4);
    pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + 20);
    pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
 
    if ((DWORD)fileaddress <= pOptionHeader->SizeOfHeaders) {
        return fileaddress;
    }
 
    for (DWORD i = 1; i <= pPEHeader->NumberOfSections; i++) {
        if ((DWORD)fileaddress < pSectionHeader->PointerToRawData) {
            pSectionHeader--;
            break;
        }
        else if (i == pPEHeader->NumberOfSections) {
            break;
        }
        else {
            pSectionHeader++;
        }
 
    }
 
    //距离该节头的距离
    sectionAddress = fileaddress - pSectionHeader->PointerToRawData;
    virtualaddress = pSectionHeader->VirtualAddress + sectionAddress;
 
    return (LPVOID)virtualaddress;
}
 
char* inject_dll(_In_ char*& PeBase, _In_ DWORD& PeSize)
{
    PIMAGE_DATA_DIRECTORY pDataDirectory = NULL;//定位表目录
    PIMAGE_IMPORT_DESCRIPTOR importTableAddress = NULL;//定位导入表的真正位置
    LPVOID returnAddress = NULL;//记录RVAtoFOA的返回值
 
    //定位到新节的位置和导入表的位置
    pDataDirectory = (PIMAGE_DATA_DIRECTORY)GetOptHeader(PeBase)->DataDirectory;
    pDataDirectory += 0x1;
 
    DWORD sectionLength = pDataDirectory->Size + 0x28 + +0x10 + DLLNAMELENGTH + FUNCTIONNAMELENGTH + 0x2;
    sectionLength = AlignMent(sectionLength, GetOptHeader(PeBase)->FileAlignment);
 
    char SecName[] = ".ddjsq";
    char* NewPeBase = AddSection(PeBase, PeSize, SecName, sectionLength);
 
    pDataDirectory = (PIMAGE_DATA_DIRECTORY)GetOptHeader(NewPeBase)->DataDirectory;
    pDataDirectory += 0x1;
 
    PDWORD pNewSection = (PDWORD)(GetLastSec(NewPeBase)->PointerToRawData + (DWORD)NewPeBase);
    returnAddress = RvaToFoa(NewPeBase, (LPSTR)pDataDirectory->VirtualAddress);
    importTableAddress = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)returnAddress + (DWORD)NewPeBase);
 
    //复制原导入表,在原导入表后新增一个导入表
    memcpy(pNewSection, importTableAddress, pDataDirectory->Size);
    importTableAddress = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pNewSection + pDataDirectory->Size - 0x14);
 
    //增加8字节INT
    PIMAGE_THUNK_DATA32 pIntTable = (PIMAGE_THUNK_DATA32)((DWORD)importTableAddress + 0x28);//保留20字节的0
    PIMAGE_THUNK_DATA32 repairIntTable = pIntTable;
    pIntTable++;
    pIntTable->u1.Ordinal = 0x0;
    pIntTable++;
 
    //增加8字节IAT表
    PIMAGE_THUNK_DATA32 pIatTable = (PIMAGE_THUNK_DATA32)(pIntTable);
    PIMAGE_THUNK_DATA32 repairIatTable = pIatTable;
    pIatTable++;
    pIatTable->u1.Ordinal = 0x0;
    pIatTable++;
 
    //分配空间存储DLL名称字符串
    PDWORD dllNameAddress = (PDWORD)pIatTable;
    memcpy(dllNameAddress, DLLNAME, DLLNAMELENGTH);
 
    //增加IMAGE_IMPORT_BY_NAME 结构
    PIMAGE_IMPORT_BY_NAME functionNameAddress = (PIMAGE_IMPORT_BY_NAME)((DWORD)dllNameAddress + DLLNAMELENGTH);
    PDWORD pFunctionName = (PDWORD)((DWORD)functionNameAddress + 0x2);
    memcpy(pFunctionName, FUNCTIONNAME, FUNCTIONNAMELENGTH);
 
    //将IMAGE_IMPORT_BY_NAME结构的RVA赋值给INT和IAT表中的第一项
    repairIntTable->u1.AddressOfData = (DWORD)FoaToRva(NewPeBase, (LPSTR)((DWORD)functionNameAddress - (DWORD)NewPeBase));
    repairIatTable->u1.AddressOfData = repairIntTable->u1.Ordinal;
 
    //修正导入表Name、OriginalFirstThunk、FirstThunk
    importTableAddress->Name = (DWORD)FoaToRva(NewPeBase, (LPSTR)((DWORD)dllNameAddress - (DWORD)NewPeBase));
    importTableAddress->OriginalFirstThunk = (DWORD)FoaToRva(NewPeBase, (LPSTR)((DWORD)repairIntTable - (DWORD)NewPeBase));
    importTableAddress->FirstThunk = (DWORD)FoaToRva(NewPeBase, (LPSTR)((DWORD)repairIatTable - (DWORD)NewPeBase));
 
    //修正IMAGE_DATA_DIRECTORY结构的VirtualAddress和Size
    pDataDirectory->VirtualAddress = (DWORD)FoaToRva(NewPeBase, (LPSTR)((DWORD)pNewSection - (DWORD)NewPeBase));
    pDataDirectory->Size += 0x14;
 
    return NewPeBase;
}
 
int main()
{
    char path[] = "路径";
    DWORD pesize;
    char* PeBase = OpenPeFiles(path, &pesize);
    if (!PeBase)
    {
        printf("wrong");
        return 0;
    }
    char* NewPeBase = inject_dll(PeBase, pesize);
    SaveFile("路径", NewPeBase, pesize);
}

运行结果

注入前

 

 

注入后

 

 

运行时

 

防范手段

由于是静态注入,直接修改了exe文件,所以可使用校验的方式去检测有无被注入

参考链接

《32位PE解析、PE修改、导入表注入》
PE基础之导入表注入
Ring3注入总结及编程实现


[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

最后于 2021-8-18 21:27 被Ddjsq_2333编辑 ,原因: 添加参考链接
收藏
点赞2
打赏
分享
最新回复 (5)
雪    币: 248
活跃值: (3784)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
luskyc 2021-8-19 16:06
2
0
还可以把代码存放在区块里面,然后注入到exe,就不用dll了
雪    币: 3561
活跃值: (541)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
朱年吉祥 2021-8-19 17:34
3
0

good

最后于 2021-8-19 17:35 被朱年吉祥编辑 ,原因:
雪    币: 1058
活跃值: (1172)
能力值: ( LV3,RANK:25 )
在线值:
发帖
回帖
粉丝
Ddjsq_2333 2021-8-20 13:14
4
0
yy虫子yy 还可以把代码存放在区块里面,然后注入到exe,就不用dll了
确实,不过把它们独立开也可以,这样做其他注入时就不用再跑到这个项目把代码复制过去
雪    币: 560
活跃值: (309)
能力值: ( LV13,RANK:1370 )
在线值:
发帖
回帖
粉丝
laomms 34 2021-12-2 12:14
5
0
非常不错,再放个64位的。
雪    币: 20
活跃值: (1010)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mb_ehlxsliq 2023-5-12 10:13
6
0
有点牛
游客
登录 | 注册 方可回帖
返回