抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

内存直接加载运行

  • 病毒木马具有模拟PE加载器的功能, 它们可以把DLL或者exe等PE文件从内存中直接加载到病毒木马的内存中去执行, 不需要通过LoadLibrary等现成的API函数去操作;
  • 假如程序需要动态调用DLL文件, 内存加载运行技术可以把DLL作为资源插入到自己的程序中, 此时可直接在内存中加载运行即可;
  • 内存加载技术的核心就在于模拟PE加载器PE文件, 也就是对导入表、导出表、重定位表的操作过程;

实现原理

首先, 需要将DLL文件加载到内存中, 按照映像大小进行对齐后映射到内存中, 然后根据重定位表修改硬编码数据, 最后根据导出表函数地址修正导入表;

流程总结

  1. 根据映像大小SizeOfImage申请可读可写可执行的内存空间, 首地址即DLL加载基址;
  2. 获取其映像对齐大小SectionAlignment并复制到该内存空间中(FileBuffer => ImageBuffer);
  3. 修正重定位表;
  4. 修正导入表, 根据PE结构中的导入表, 加载所需的Dll, 并获取导入函数的地址将其写入导入表中;
  5. 修改DLL的加载基址ImageBase;
  6. 获取DLL入口地址, 构造DllMain函数实现加载;

对于exe文件, 重定位表不是必须的。因为对于exe进程来说, 进程最早加载的模块是exe模块, 所以它可以按照默认加载基址加载到内存中; exe和Dll唯一的区别在于构造入口函数的差别, exe不需要构造入口函数, 而是根据PE结构获取exe的入口地址偏移AddressOfEntryPoint并计算出入口地址, 然后直接跳转。

1. 将Dll文件读入内存

PELodader_Demo.cpp
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
wchat_t szFileName[MAX_PATH] =  L"C:\\Users\\dell\\OneDrive\\桌面\\哔哩哔哩学习\\PE Learn\\PELoader_Demo\\Debug\\TestDLL_01.dll";
HANDLE hFile = CreateFile(
szFileName,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_ARCHIVE,
NULL
);
if (hFile == INVALID_HANDLE_VALUE)
{
printf("CreateFile Failed\n");
return 1;
}
DWORD dwFileSize = GetFileSize(hFile, NULL);
printf("GetFileSize: %d\n", dwFileSize);
// 申请动态内存
PBYTE lpData = new BYTE[dwFileSize];
if (NULL == lpData)
{
printf("申请内存出错\n");
return 1;
}
// 将文件读取到内存中
DWORD dwRead = 0;
if (FALSE == ReadFile(hFile, lpData, dwFileSize, &dwRead, NULL))
{
printf("ReadFile Failed\n");
return 1;
}
printf("lpData: %08x\n", *(short*)lpData); // 5A4D
CreateFile

创建或打开文件或I/O设备, 此函数区分多字节和Unicode两种模式

1
2
3
4
5
6
7
8
9
10
11
12
13
// UNICODE
HANDLE
WINAPI
CreateFileW(
_In_ LPCWSTR lpFileName, // 要创建或打开的文件或设备名称
_In_ DWORD dwDesiredAccess, // 请求对文件或设备的访问权限, 常用值GENERIC_READ、GENERIC_WRITE
_In_ DWORD dwShareMode, // 请求的文件或设备的共享模式(可以是读取、写入、删除)
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 指向LPSECURITY_ATTRIBUTES结构的指针, 此参数可以为NULL
_In_ DWORD dwCreationDisposition, // 要对存在或不存在的文件或设备执行的操作, 此参数通常设置为OPEN_EXISTING
_In_ DWORD dwFlagsAndAttributes, // 文件或设备属性和标志
_In_opt_ HANDLE hTemplateFile // 具有GENERIC_READ访问权限的模板文件的有效句柄, 此参数可以为NULL
);

  • 如果函数成功, 则返回值是指定文件、设备、命名管道或邮件槽的打开句柄;
  • 如果函数失败, 则返回值为INVALID_HANDLE_VALUE;
GetFileSize

检索指定文件的大小(以字节为单位)

1
2
3
4
5
6
DWORD
WINAPI
GetFileSize(
_In_ HANDLE hFile, // 文件句柄
_Out_opt_ LPDWORD lpFileSizeHigh // 指向变量的指针, 其中返回了文件大小的高位双字。如果应用程序不需要高位双字, 此参数可以为NULL
);

  • 如果函数成功, 则返回值为文件大小的低位双字, 如果lpFileSizeHigh为非NULL, 则该函数会将文件大小的高位双字放入该参数指向的变量中;
  • 如果函数失败且lpFileSizeHigh为NULL, 则返回值INVALID_FILE_SIZE;
ReadFile

从指定的文件或输入/输出 (I/O) 设备读取数据

1
2
3
4
5
6
7
8
9
BOOL
WINAPI
ReadFile(
_In_ HANDLE hFile, // 文件/设备句柄
_Out_writes_bytes_to_opt_(nNumberOfBytesToRead, *lpNumberOfBytesRead) __out_data_source(FILE) LPVOID lpBuffer, // 指向接收从文件或设备读取的数据的缓冲区的指针
_In_ DWORD nNumberOfBytesToRead, // 要读取的最多字节数
_Out_opt_ LPDWORD lpNumberOfBytesRead, // 指向变量的指针
_Inout_opt_ LPOVERLAPPED lpOverlapped // 此参数可为NULL
);

  • 如果函数成功,则返回值为非零 (TRUE);
  • 如果函数失败或正在异步完成,则返回值为零 (FALSE);

2. FileBuffer => ImageBuffer

将FileBuffer转换成ImageBuffer可以分为两步:

  1. 先将PE头部复制至内存中;
  2. 然后循环将节内容复制过去;

代码示例

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
BOOL MmMapFile(LPVOID lpData, LPVOID lpBaseAddress)
{
// 解析PE文件格式
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpData;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + (DWORD)lpData);
// 获取PE header大小
DWORD dwSizeOfHeaders = pNt->OptionalHeader.SizeOfHeaders;
// 获取节数量
DWORD dwNumOfSections = pNt->FileHeader.NumberOfSections;
// 将头部数据复制过去
RtlCopyMemory(lpBaseAddress, lpData, dwSizeOfHeaders);
// 解析第一个节
PIMAGE_SECTION_HEADER pSec = IMAGE_FIRST_SECTION(pNt);
// 循环节
for(size_t i = 0; i < dwNumOfSections; i++)
{
if((0 == pSec[i].VirtualAddress) || (0 == pSec[i].PointerToRawData))
{
continue;
}
LPVOID lpSrcMem = (LPVOID)(pSec[i].PointerToRawData + (DWORD)lpData);
LPVOID lpDesMem = (LPVOID)(pSec[i].VirtualAddress + (DWORD)lpBaseAddress);
DWROD dwSizeOfRawData = pSec[i].SizeOfRawData;
RtlCopyMemory(lpDesMem, lpSrcMem, dwSizeOfRawData);
}
return TRUE;
}

3. 修正重定位表

重定位表

  • Relocation(重定位)是一种将程序中的一些地址修正为运行时可用的实际地址的机制。在程序编译过程中, 由于程序中使用了各种全局变量和函数, 这些变量和函数的地址还没有确定, 因此它们的地址只能暂时使用一个相对地址。当程序被加载到内存中运行时, 这些相对地址需要被修正为实际的绝对地址, 这个过程就是重定位;
  • 在Windows操作系统中, 程序被加载到内存中运行时, 需要将程序中的各种内存地址进行重定位, 以使程序能正确运行。Windows系统使用PE(Portable Executable)文件格式来存储可执行程序, 其中包括重定位信息。当程序被加载到内存中时, 系统会解析这些重定位信息, 并将程序中的各种内存地址进行重定位。
  • 重定位表一般出现在Dll中, 因为Dll都是动态加载, 所以地址不固定, Dll的入口点在整个执行过程中至少要执行2次, 一次时在开始时执行初始化工作, 一次则是在结束时做最后的收尾工作, 重定位表则是解决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
BOOL DoRelocationTable(LPVOID lpBaseAddress)
{
// 解析PE
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpBaseAddress;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + (DWORD)lpBaseAddress);
// 定位重定位位置
PIMAGE_DATA_DIRECTORY pDataDir = (PIMAGE_DATA_DIRECTORY)(pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_BASERELOC);
// 获取重定位表
PIMAGE_BASE_RELOCATION pLoc = (PIMAGE_BASE_RELOCATION)(pDataDir->VirtualAddress + (DWORD)lpBaseAddress);
// 判断是否有重定位表, 数据目录表不存在时, VirtualAddress为0, 也就是指向映像基址
if ((LPVOID)pLoc == lpBaseAddress)
{
return FALSE;
}
// 循环重定位表, 重定位表VirtualAddress和SizeOfBlock都为0表示重定位表结束
while((pLoc.VirtualAddress + pLoc->SizeOfBlock) != 0)
{
// 重定位数据, 位于IMAGE_BASE_RELOCATION表开头8字节之后
PWORD pLocData = (PWORD)((PBYTE)pLoc + sizeof(IMAGE_BASE_RELOCATION));
// 计算本节需要修正的重定位项(地址)的数目, 每个数据都是16字节(4+12字节, 高4位表示重定位类型, 低12位为RVA)
// SizeOfBlock的值包括了SizeOfBlock和VirtualAddress的大小, 8字节需要减去
DWORD dwNumOfpLoc = (pLoc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
for (size_t i = 0; i < dwNumOfpLoc; i++)
{
// 高位为3表示有效重定位
if ((DWORD)(pLocData[i] & 0x0000F000) == 0x00003000)
{
// 需要修正的数据
// 修正重定位数据, 重定位表记录的是存在硬编码的地址, 以基址+偏移的形式
// 存在硬编码的地址 = 重定位基址 + 重定位表数据偏移
// = 基址 + 重定位地址 + 重定位数据(数据后12位)
PDWORD pAddress = (PDWORD)((PBYTE)pDos + pLoc->VirtualAddress + (pLocData[i] & 0x0FFF));
// 重定位地址 = 硬编码地址 - ImageBase + 实际基地址
// = 实际基地址 - ImageBase + 硬编码地址
// *pAddress = *pAddress - pNt->OptionalHeader.ImageBase + (DWORD)pDos;
DWORD dwDelta = (DWORD)pDos + pNt->OptionalHeader.ImageBase;
*pAddress += dwDelta;
}
}
// 循环下一个重定位区段
pLoc = (PIMAGE_BASE_RELOCATION)((PBYTE)pLoc + pLoc->SizeOfBlock);
}
return TRUE;
}

4. 修正导入表

PE加载器在加载PE的时候会将导入函数的地址填入导入地址表中, 导入表结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA) 导入名称表RVA
} DUMMYUNIONNAME;
DWORD TimeDateStamp; // 0 if not bound,
// -1 if bound, and real date\time stamp
// in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
// O.W. date/time stamp of DLL bound to (Old BIND)

DWORD ForwarderChain; // -1 if no forwarders
DWORD Name;
DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses) 导入地址表RVA
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;

主要用到的是OriginalFirstThunk和FirstThunk; 这两个表用到的结构体是一样的(IMAGE_THUNK_DATA):

1
2
3
4
5
6
7
8
9
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; // PBYTE
DWORD Function; // PDWORD
DWORD Ordinal;
DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;

如果指向导入名称表; 则内容是AddressOfData, 指向IMAGE_IMPORT_BY_NAME结构体;
如果指向导入地址表:

  • 序号导入的话, Ordinal首位是1, 低4位是导入序号;
  • 名称导入的话, Function的值是函数地址;

这里我们需要将导入函数的地址填入导入地址表中, 所以需要知道这个函数是怎么导入的, 然后通过GetProcAddress API获取函数地址, 然后将函数地址填入导入地址表中;

通过GetProcAddress获取函数地址, 需要知道Dll名称, 通过Dll名称获取模块句柄

所以代码流程是:

修正导入表流程

  • 先获取导入表数组的数量和第一个成员的地址;
  • 根据导入表的数量, 进行循环遍历;
    1. 获取导入名称表;
    2. 获取导入地址表;
    3. 进行导入名称表的遍历(导入名称表数组以0作为最后一个成员结束);
      • 获取导入函数的名称或序号;
      • 加载这个Dll, 通过名称或序号, 获取其函数地址;
      • 将地址填入导入地址表;
      • 进入下一次循环;
    4. 进入下一次循环;
  • 两次遍历完成后, 导入表就已经完成了修正;

代码示例

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
BOOL DoImportTable(LPVOID lpBaseAddress)
{
// 解析PE结构
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpBaseAddress;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + (DWORD)pDos);
PIMAGE_DATA_DIRECTORY pDir = (PIMAGE_DATA_DIRECTORY)(pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_IMPORT);
PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)(pDir->VirtualAddress + (DWORD)pDos);
// 循环遍历Dll导入表中的Dll以及获取导入表中的函数地址
char* szDllName = NULL;
HMODULE hDll = NULL;
PIMAGE_THUNK_DATA pImportNameArray = NULL;
PIMAGE_IMPORT_BY_NAME pImportByName = NULL;
PIMAGE_THUNK_DATA pImportFuncAddrArray = NULL;
FARPROC pfFuncAddress = NULL;
DWORD i = 0;
while(TRUE)
{
if(0 == pImport->OriginalFirstThunk)
{
break;
}
// 获取导入表中Dll的名称并加载Dll
szDllName = (char*)(pImport->Name + (DWORD)pDos);
hDll = GetModuleHandleA(szDllName);
if(hDll == NULL)
{
hDll = LoadLibraryA(szDllName);
if(hDll == NULL)
{
pImport++;
continue;
}
}
i = 0;
// 获取OriginalFirstThunk以及对应的导入函数名称表首地址
pImportNameArray = (PIMAGE_THUNK_DATA)(pImport->OriginalFirstThunk + (DWORD)pDos);
// 获取FirstThunk以及对应的导入函数地址表首地址
pImportFuncAddrArray = (PIMAGE_THUNK_DATA)(pImport->FirstThunk + (DWORD)pDos);
while(TRUE)
{
if(0 == pImportNameArray[i].u1.AddressOfData)
{
break;
}
// 获取IMAGE_IMPORT_BY_NAME结构
pImportByName = (PIMAGE_IMPORT_BY_NAME)(pImportNameArray[i].u1.AddressOfData);
// 判断导出函数是序号导出还是函数名称导出
if(0x80000000 & pImportNameArrar[i].u1.Ordinal)
{
// 序号导出
// 当IMPORT_THUNK_DATA值的最高位为1时, 表示函数以序号方式导出, 此时低位被看作是一个函数序号
pfFuncAddress = GetProcAddress(hDll, (LPCSTR)(pImportNameArray[i].u1.Ordinal & 0x0000FFFF));
}
else
{
// 名称导出
pfFuncAddress = GetProcAddress(hDll, (LPCSTR)pImportByName->Name);
}
// 注意此处的函数地址表的赋值, 要对照PE格式进行装载
pImportFuncAddrArray[i].u1.Function = (DWORD)pfFuncAddress;
i++;
}
pImport++;
}
return TRUE;
}

5. 修改Dll的加载基址ImageBase

  • PE加载器在加载PE的时候会将进程分配的基地址填入扩展头的ImageBase中;
1
2
3
4
5
6
7
8
BOOL SetImageBase(LPVOID lpBaseAddress)
{
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpBaseAddress;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + (DWORD)pDos);
pNt->OptionalHeader.ImageBase = (DWORD)lpBaseAddress;

return TRUE;
}

6. 修改DllMain入口点

  • 调用Dll的入口函数DllMain, 函数地址则是PE文件的入口点;
1
2
3
4
5
6
7
8
9
10
11
BOOL CallDllMain(LPVOID lpBaseAddress)
{
typedef_DllMain DllMain = NULL;
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpBaseAddress;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + (DWORD)pDos);
DllMain = (typedef_DllMain)(pNt->OptionalHeader.AddressOfEntryPoint + (DWORD)pDos);
// 调用入口函数, 附加进程DLL_PROCESS_ATTACH
BOOL bRet = DllMain((HINSTANCE)lpBaseAddress, DLL_PROCESS_ATTACH, NULL);

return bRet;
}

7. 获取Dll导出函数

导出表

  • PE文件运行, 需要依赖Dll; 系统Dll包括Kernel32.dll、User32.dll等;
  • 导出表时当前PE文件提供了哪些函数给别人使用;
  • 不管是exe还是Dll, 本质都是PE文件; exe文件也可以导出函数给别人使用; 一般exe没有, 但不是不可以;

导出表结构

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics; //
DWORD TimeDateStamp; // 时间戳, 编译时间; 把秒转为时间, 可以知道这个Dll是什么时候编译出来的
WORD MajorVersion;
WORD MinorVersion;
DWORD Name; // 指向该导出表文件名的字符串, 也就是这个Dll的名称(RVA)
DWORD Base; // 导出函数的起始序号
DWORD NumberOfFunctions; // 所有导出函数的个数
DWORD NumberOfNames; // 以名称导出的函数个数
DWORD AddressOfFunctions; // RVA from base of image(导出的函数地址表)
DWORD AddressOfNames; // RVA from base of image(导出的函数名称表)
DWORD AddressOfNameOrdinals; // RVA from base of image(导出的函数序号表)
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

遍历流程

  1. 获取导出函数名称;
  2. 比较是否是要找函数名称;
  3. 如果是, 则获取函数序号(2字节);
  4. 根据函数序号获取函数地址;
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
LPVOID MmGetProcAddress(LPVOID lpBaseAddress, wchar_t* lpszFuncName)
{
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpBaseAddress;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + (DWORD)pDos);
PIMAGE_DATA_DIRECTORY pDir = (PIMAGE_DATA_DIRECTORY)(pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_EXPORT);
PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)(pDir->VirtualAddress + (DWORD)pDos);

PDWORD dwAddressOfFunctions = (PDWORD)(pExport->AddressOfFunctions + (DWORD)pDos);
PDWORD dwAddressOfNames = (PDWORD)(pExport->AddressOfNames + (DWORD)pDos);
PWORD pwAddressOfNameOrdinals = (PWORD)(pExport->AddressOfNameOrdinals + (DWORD)pDos);

DWORD dwNumberOfNames = pExport->NumberOfNames;

for(size_t i = 0; i < dwNumberOfNames; i++)
{
if(!dwAddressOfFunctions[i])
{
continue;
}
PWCHAR szFuncName = (PWCHAR)(dwAddressOfNames[i] + (DWORD)pDos);
if(lstrcmpi(lpszFuncName, szFuncName) == 0)
{
return (LPVOID)(dwAddressOfFunctions[pwAddressOfNameOrdinals[i]] + (DWORD)pDos);
}
}
return LPVOID();
}

8. 释放内存加载的Dll

释放资源

1
2
3
4
5
6
7
8
9
10
11
BOOL MmFreeLibrary(LPVOID lpBaseAddress)
{
BOOL bRet = NULL;
if (NULL == lpBaseAddress)
{
return bRet;
}
bRet = VirtualFree(lpBaseAddress, 0, MEM_RELEASE);
lpBaseAddress = NULL;
return bRet;
}

内存加载Dll执行演示

示例Dll源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"

extern "C" __declspec(dllexport)void ShowMessage() {
MessageBox(NULL, L"I'm DLL File", L"HELLO", MB_OK);
}

BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

PELoader_Demo.cpp

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
// PELoader_Demo.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <Windows.h>
#include "MmLoadDll.h"

int main()
{
wchar_t szFileName[MAX_PATH] = L"C:\\Users\\dell\\OneDrive\\桌面\\哔哩哔哩学习\\PE Learn\\PELoader_Demo\\Debug\\TestDLL_01.dll";
HANDLE hFile = CreateFile(
szFileName,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_ARCHIVE,
NULL
);
if (INVALID_HANDLE_VALUE == hFile)
{
printf("CreateFile Failed: %d\n", GetLastError());
return 0;
}
DWORD dwFileSize = GetFileSize(hFile, NULL);
printf("FileSize: %d\n", dwFileSize);
// 申请动态内存并读取DLL到内存中
PBYTE lpData = new BYTE[dwFileSize];
if (lpData == NULL)
{
printf("申请内存出错: %d\n", GetLastError());
return 0;
}
DWORD dwRet = 0;
BOOL bRet = ReadFile(hFile, lpData, dwFileSize, &dwRet, NULL);
if (!bRet)
{
printf("ReadFile Failed: %d\n", GetLastError());
return 0;
}
printf("lpData: %08x\n", *(short*)lpData);
// 将内存DLL加载到程序中
LPVOID lpBaseAddress = MmLoadLibrary(lpData, dwFileSize);
if (lpBaseAddress == NULL)
{
printf("MmLoadLibrary Failed: %d\n", GetLastError());
return 0;
}
printf("DLL加载成功\n");

// 获取DLL导出函数并调用
typedef void (*typedef_ShowMessage)();
const char* szName = "ShowMessage";
typedef_ShowMessage ShowMessage = (typedef_ShowMessage)MmGetProcAddress(lpBaseAddress, (wchar_t*)szName);
if (NULL == ShowMessage)
{
printf("MmGetProcAddress Failed\n");
return 0;
}
ShowMessage();
// 释放从内存加载的DLL
BOOL bRet1 = MmFreeLibrary(lpBaseAddress);
if (FALSE == bRet1)
{
printf("MmFreeLibrary Failed\n");
return 0;
}
// 释放
delete[] lpData;
lpData = NULL;
CloseHandle(hFile);

//system("pause");
return 0;
}

调用导出函数

内存加载exe执行

待补充…

x64位手工模拟PE加载器

x64位手工模拟PE加载器

实现原理和x32位相同, 需注意的是在修复重定位时判断是否进行修复时: x64位修复项等于0x10; x86位(IMAGE_REL_BASED_HIGHLOW)等于0x3;

代码实现

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
// LoadPE_Demo_x64.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <Windows.h>
#include <winternl.h>

typedef BOOL(WINAPI* typedef_DllMain)(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved);

// 获取SizeOfImage
DWORD GetSizeOfImage(LPVOID lpBuffer)
{
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpBuffer;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + (DWORD64)lpBuffer);
return pNt->OptionalHeader.SizeOfImage;
}
// 将fileBuffer转成ImageBuffer
BOOL MemMapFile(LPVOID lpBuffer, LPVOID lpBaseAddress)
{
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpBuffer;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + (DWORD64)lpBuffer);
PIMAGE_SECTION_HEADER pSec = IMAGE_FIRST_SECTION(pNt);
DWORD dwSizeOfHeaders = pNt->OptionalHeader.SizeOfHeaders;
DWORD dwSizeOfSections = pNt->FileHeader.NumberOfSections;
RtlCopyMemory(lpBaseAddress, lpBuffer, dwSizeOfHeaders);
for (size_t i = 0; i < dwSizeOfSections; i++)
{
if ((pSec[i].VirtualAddress == 0) || (pSec[i].SizeOfRawData == 0))
{
continue;
}
LPVOID lpSrcMem = (LPVOID)(pSec[i].PointerToRawData + (DWORD64)lpBuffer);
LPVOID lpDesMem = (LPVOID)(pSec[i].VirtualAddress + (DWORD64)lpBaseAddress);
RtlCopyMemory(lpDesMem, lpSrcMem, pSec[i].SizeOfRawData);
}
return TRUE;
}
// 修复重定位表
BOOL FixRelocation(LPVOID lpBaseAddress)
{
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpBaseAddress;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + (DWORD64)lpBaseAddress);
PIMAGE_DATA_DIRECTORY pDir = (PIMAGE_DATA_DIRECTORY)(pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_BASERELOC);
PIMAGE_BASE_RELOCATION pBaseReloc = (PIMAGE_BASE_RELOCATION)(pDir->VirtualAddress + (DWORD64)lpBaseAddress);
if ((LPVOID)pBaseReloc == lpBaseAddress)
{
printf("不存在重定位表\n");
return TRUE;
}
while (pBaseReloc->VirtualAddress != 0 && pBaseReloc->SizeOfBlock != 0)
{
PWORD pLocData = (PWORD)((DWORD64)pBaseReloc + sizeof(IMAGE_BASE_RELOCATION));
DWORD dwNumOfReloc = (pBaseReloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2;
for (size_t i = 0; i < dwNumOfReloc; i++)
{
// x64位修复项等于0x10 x86 IMAGE_REL_BASED_HIGHLOW
if((pLocData[i] >> 12) == IMAGE_REL_BASED_DIR64)
{
unsigned long long int* pAddress = (unsigned long long int*)((PBYTE)pDos + pBaseReloc->VirtualAddress + (pLocData[i] & 0x0FFF));
printf("Fix Relocation: %08x\t %p\n", (pLocData[i] >> 12), pAddress);
ULONGLONG dwDelta = (ULONGLONG)pDos - pNt->OptionalHeader.ImageBase;
*pAddress += dwDelta;
}
}
pBaseReloc = (PIMAGE_BASE_RELOCATION)((PBYTE)pBaseReloc + pBaseReloc->SizeOfBlock);
}
return TRUE;
}

// 修正导入表
BOOL FixImportTable(LPVOID lpBaseAddress)
{
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpBaseAddress;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + (DWORD64)lpBaseAddress);
PIMAGE_DATA_DIRECTORY pDir = (PIMAGE_DATA_DIRECTORY)(pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_IMPORT);
PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)(pDir->VirtualAddress + (DWORD64)lpBaseAddress);
int wcharSize = 0;
WCHAR wcDllName[256]{};
HMODULE hDll = NULL;
DWORD dwIndex = 0;
PIMAGE_THUNK_DATA pINT = NULL;
PIMAGE_IMPORT_BY_NAME pName = NULL;
PIMAGE_THUNK_DATA pIAT = NULL;
FARPROC lpFuncAddress = NULL;
while (pImport->Name)
{
char* szDllName = (char*)(pImport->Name + (DWORD64)lpBaseAddress);
wcharSize = MultiByteToWideChar(CP_ACP, 0, szDllName, -1, NULL, 0);
wsprintf(wcDllName, L"%S", szDllName);
printf("[+] Fix DllName: %S\n", wcDllName);
hDll = GetModuleHandle(wcDllName);
if (hDll == NULL)
{
hDll = LoadLibrary(wcDllName);
if (hDll == NULL)
{
pImport++;
continue;
}
}
PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)((BYTE*)lpBaseAddress + pImport->FirstThunk);
while (pThunk->u1.AddressOfData)
{
if (pThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG64)
{
printf("\t[+] Fix By Ordinal: %I64d\n", pThunk->u1.Ordinal & 0x0000FFFF);
lpFuncAddress = GetProcAddress(hDll, (LPCSTR)(pThunk->u1.Ordinal & 0x0000FFFF));
}
else
{
pName = (PIMAGE_IMPORT_BY_NAME)(pThunk->u1.AddressOfData + (BYTE*)lpBaseAddress);
printf("\t[+] Fix By Name: %s\n", pName->Name);
lpFuncAddress = GetProcAddress(hDll, pName->Name);
}
pThunk->u1.Function = (ULONGLONG)lpFuncAddress;
pThunk++;
}
pImport++;

}
return TRUE;
}

// 判断是EXE还是DLL
BOOL IsExeOrDll(LPVOID lpBaseAddress)
{
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpBaseAddress;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + (DWORD64)lpBaseAddress);

if ((pNt->FileHeader.Characteristics & 0x00002000) == 1)
{
return FALSE;
}
return TRUE;
}

BOOL CallDllMain(LPVOID lpBaseAddress)
{
typedef_DllMain DllMain = NULL;
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress;
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((DWORD64)pDosHeader + pDosHeader->e_lfanew);
if (IsExeOrDll(lpBaseAddress))
{
// 表示EXE执行
//DWORD64 lpExeEntry = (DWORD64)(pNtHeaders->OptionalHeader.AddressOfEntryPoint + (DWORD64)lpBaseAddress);
HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)((BYTE*)lpBaseAddress + pNtHeaders->OptionalHeader.AddressOfEntryPoint), NULL, 0, NULL);
WaitForSingleObject(hThread, INFINITE);
return TRUE;
}
DllMain = (typedef_DllMain)((DWORD64)pDosHeader + pNtHeaders->OptionalHeader.AddressOfEntryPoint);
// 调用入口函数,附加进程DLL_PROCESS_ATTACH
BOOL bRet = DllMain((HINSTANCE)lpBaseAddress, DLL_PROCESS_ATTACH, NULL);
if (FALSE == bRet)
{
printf("DllMain\n");
}

return bRet;
}

int main()
{
wchar_t wcFilePath[MAX_PATH] = L"D:\\xxx.exe";
// 打开文件句柄
HANDLE hFile = CreateFile(wcFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE == hFile)
{
printf("CreateFile Failed\n");
return 0;
}
DWORD dwFileSize = GetFileSize(hFile, NULL);
printf("GetFileSize: %d\n", dwFileSize);
LPVOID lpBuffer = new BYTE[dwFileSize];
DWORD lpNumberOfBytesRead = 0;
if (FALSE == ReadFile(hFile, lpBuffer, dwFileSize, &lpNumberOfBytesRead, NULL))
{
printf("ReadFile Failed\n");
return 0;
}
printf("lpBuffer: %x\n", *((PBYTE)lpBuffer + 1));
// 获取ImageSize
DWORD dwSizeOfImage = GetSizeOfImage(lpBuffer);
printf("GetSizeOfImage: %d\n", dwSizeOfImage);
LPVOID lpBaseAddress = VirtualAlloc(NULL, dwSizeOfImage, MEM_COMMIT, PAGE_READWRITE);
if (lpBaseAddress == NULL)
{
printf("VirtualAlloc Failed\n");
return 0;
}
// FileBuffer ==> ImageBuffer
if (FALSE == MemMapFile(lpBuffer, lpBaseAddress))
{
printf("MemMapFile Failed\n");
return 0;
}
printf("lpBaseAddress: %08x\n", *((PBYTE)lpBaseAddress + 1));
// 修正重定位表
if (FALSE == FixRelocation(lpBaseAddress))
{
printf("FixRelocation Failed\n");
return 0;
}
// 修正导入表
if (FALSE == FixImportTable(lpBaseAddress))
{
printf("FixImportTable Failed\n");
return 0;
}
// 修改内存属性
DWORD lpflOldProtect = 0;
if (FALSE == VirtualProtect(lpBaseAddress, dwSizeOfImage, PAGE_EXECUTE_READWRITE, &lpflOldProtect))
{
printf("VirtualProtectEx Failed\n");
return 0;
}
// 修改入口点
if (FALSE == CallDllMain(lpBaseAddress))
{
printf("CallDllMain Failed\n");
return 0;
}
}

仓库地址

参考资料

参考书籍

  • 《Windwos黑客编程技术详解》第4章第3节

参考Blog

评论