抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)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执行

待补充…

仓库地址

参考资料

参考书籍

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

参考Blog

评论