启动技术

第四章 启动技术

创建进程API

在一个进程中创建并启动一个新进程,最简单的方法无非是直接通过调用WIN32 API函数创建新进程。用户层上,微软提供了WinExec、ShellExecute和CreateProcess等函数来实现进程创建。

WinExecShellExecute以及CreateProcess除了可以创建进程外,还能执行CMD命令等功能。

代码实现

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
#include <SDKDDKVer.h>
#include <stdio.h>
#include <tchar.h>
#include <Windows.h>

BOOL WinExec_Test(char* pszExePath, UINT uiCmdShow)
{
UINT uiRet = 0;
uiRet = ::WinExec(pszExePath, uiCmdShow);
if (31 < uiRet)
{
return TRUE;
}
return FALSE;
}


BOOL ShellExecute_Test(char* pszExePath, UINT uiCmdShow)
{
HINSTANCE hInstance = 0;
hInstance = ::ShellExecute(NULL, NULL, pszExePath, NULL, NULL, uiCmdShow);
if (32 < (DWORD)hInstance)
{
return TRUE;
}
return FALSE;
}


BOOL CreateProcess_Test(char* pszExePath, UINT uiCmdShow)
{
STARTUPINFO si = { 0 };
PROCESS_INFORMATION pi;
BOOL bRet = FALSE;
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW; //指定wShowWindow成员有效
si.wShowWindow = uiCmdShow;
bRet = ::CreateProcess(NULL, pszExePath, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
if (bRet)
{
//不使用的句柄最好关掉
::CloseHandle(pi.hThread);
::CloseHandle(pi.hProcess);
return TRUE;
}
return FALSE;
}


int _tmain(int argc, _TCHAR* argv[])
{
BOOL bRet = FALSE;
// WinExec.exe
bRet = WinExec_Test("C:\\Users\\admin\\Desktop\\C++\\WinExec.exe", SW_SHOWNORMAL);
if (bRet)
{
printf("WinExec.exe Run OK.\n");
}
else
{
printf("WinExec.exe Run ERROR.\n");
}
// ShellExecute.exe
bRet = ShellExecute_Test("C:\\Users\\admin\\Desktop\\C++\\ShellExecute.exe", SW_SHOWNORMAL);
if (bRet)
{
printf("ShellExecute.exe Run OK.\n");
}
else
{
printf("ShellExecute.exe Run ERROR.\n");
}
// CreateProcess.exe
bRet = CreateProcess_Test("C:\\Users\\admin\\Desktop\\C++\\CreateProcess.exe", SW_SHOWNORMAL);
if (bRet)
{
printf("CreateProcess.exe Run OK.\n");
}
else
{
printf("CreateProcess.exe Run ERROR.\n");
}

system("pause");
return 0;
}

突破SESSION 0隔离创建用户进程(+创建服务)

病毒木马通常会把自已注入系统服务进程或是伪装成系统服务进程,并运行在SESSION 0中。处于SESSION 0中的程序能正常执行普通程序的绝大部分操作,但是个别操作除外。例如,处于SESSION 0中的系统服务进程,无法与普通用户进程通信,不能通过Windows消息机制进行通信,更不能创建普通的用户进程。

从Windows VISTA开始,只有服务可以托管到SESSION 0中,用户应用程序和服务之间会进行隔离。虽然Windows7及以上版本的SESSION 0给服务层和应用层间的通信造成了很大的难度,但这并不代表没有办法实现服务层与应用层间的通信与交互。微软提供了一系列以WTS(Windows Terminal Service,Windows终端服务)开头的函数,从而可以完成服务层与应用层的交互。

实现原理

由于SESSION 0的隔离,使得在系统服务进程内不能直接调用CreateProcess等函数创建进程,而只能通过CreateProcessAsUser函数来创建。这样,创建的进程才会显示UI界面,与用户进行交互。

在SESSION 0中创建用户桌面进程具体的实现流程如下所示:

首先,调用WTSGetActiveConsoleSessionId函数来获取当前程序的会话ID,即Session Id。根据Session Id继续调用WTSQueryUserToken函数来检索用户令牌,并获取对应的用户令牌句柄。在不需要使用用户令牌句柄时,可以调用CloseHandle函数来释放句柄。

其次,使用DuplicateTokenEx函数创建一个新令牌,并复制上面获取的用户令牌。设置新令牌的访问权限为MAXIMUM_ALLOWED,这表示获取所有令牌权限。新访问令牌的模拟级别为SecurityIdentification,而且令牌类型为TokenPrimary,这表示新令牌是可以在CreateProcessAsUser函数中使用的主令牌。

最后,根据新令牌调用CreateEnvironmentBlock函数创建一个环境块,用来传递给CreateProcessAsUser使用。在不需要使用进程环境块时,可以通过调用DestroyEnvironmentBlock函数进行释放。获取环境块后,就可以调用CreateProcessAsUser来创建用户桌面进程。

上述方法创建的用户桌面进程并没有继承服务程序的系统权限,只具有普通权限。要想创建一个有系统权限的子进程,这可以通过设置进程访问令牌的安全描述符来实现。

代码实现

因为程序要实现的是突破SESSION 0隔离,所以,在系统服务程序中创建用户桌面进程程序必须注册成为一个系统服务进程,这样才处于SESSION 0中。

服务程序的入口点与普通程序的人口点不同,需要通过调用函数StartServiceCtrlDispatcher来设置服务人口点函数。

ServiceLoader.exe

ServiceOperate.h

1
2
3
4
5
6
7
8
9
#ifndef _SERVICE_OPERATE_H_
#define _SERVICE_OPERATE_H_

#include <Windows.h>
#include <Shlwapi.h>
#pragma comment(lib, "Shlwapi.lib")
// 0 加载服务 1 启动服务 2 停止服务 3 删除服务
BOOL SystemServiceOperate(char* lpszDriverPath, int iOperateType);
#endif

ServiceLoader.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
// ServiceLoader.cpp : 定义控制台应用程序的入口点。
//
#include "ServiceOperate.h"
#include<stdio.h>
#include<tchar.h>

int _tmain(int argc, _TCHAR* argv[])
{
BOOL bRet = FALSE;
char szExePath[] = "C:\\Users\\admin\\Desktop\\C++\\CreateProcessAsUser.exe";

// 加载服务
bRet = SystemServiceOperate(szExePath, 0);
if (bRet)
{
printf("INSTALL OK.\n");
}
else
{
printf("INSTALL ERROR.\n");
}
// 启动服务
bRet = SystemServiceOperate(szExePath, 1);
if (bRet)
{
printf("START OK.\n");
}
else
{
printf("START ERROR.\n");
}


system("pause");

// 停止服务
bRet = SystemServiceOperate(szExePath, 2);
if (bRet)
{
printf("STOP OK.\n");
}
else
{
printf("STOP ERROR.\n");
}
// 卸载服务
bRet = SystemServiceOperate(szExePath, 3);
if (bRet)
{
printf("UNINSTALL OK.\n");
}
else
{
printf("UNINSTALL ERROR.\n");
}

return 0;
}

ServiceOperate.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
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
#include "ServiceOperate.h"
#include <stdio.h>
#include<tchar.h>


void ShowError(char* lpszText)
{
char szErr[MAX_PATH] = { 0 };
::wsprintf(szErr, "%s Error!\nError Code Is:%d\n", lpszText, ::GetLastError());
#ifdef _DEBUG
::MessageBox(NULL, szErr, "ERROR", MB_OK | MB_ICONERROR);
#endif
}


// 0 加载服务 1 启动服务 2 停止服务 3 删除服务
BOOL SystemServiceOperate(char* lpszExePath, int iOperateType)
{
BOOL bRet = TRUE;
char szName[MAX_PATH] = { 0 };

lstrcpy(szName, lpszExePath);
// 过滤掉文件目录,获取文件名
PathStripPath(szName);

SC_HANDLE shOSCM = NULL, shCS = NULL;
SERVICE_STATUS ss;
DWORD dwErrorCode = 0;
BOOL bSuccess = FALSE;
// 打开服务控制管理器数据库
shOSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (!shOSCM)
{
ShowError("OpenSCManager");
return FALSE;
}

if (0 != iOperateType)
{
// 打开一个已经存在的服务
shCS = OpenService(shOSCM, szName, SERVICE_ALL_ACCESS);
if (!shCS)
{
ShowError("OpenService");
::CloseServiceHandle(shOSCM);
shOSCM = NULL;
return FALSE;
}
}

switch (iOperateType)
{
case 0:
{
// 创建服务
// SERVICE_AUTO_START 随系统自动启动
// SERVICE_DEMAND_START 手动启动
shCS = ::CreateService(shOSCM, szName, szName,
SERVICE_ALL_ACCESS,
SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS,
SERVICE_AUTO_START,
SERVICE_ERROR_NORMAL,
lpszExePath, NULL, NULL, NULL, NULL, NULL);
if (!shCS)
{
ShowError("CreateService");
bRet = FALSE;
}
break;
}
case 1:
{
// 启动服务
if (!::StartService(shCS, 0, NULL))
{
ShowError("StartService");
bRet = FALSE;
}
break;
}
case 2:
{
// 停止服务
if (!::ControlService(shCS, SERVICE_CONTROL_STOP, &ss))
{
ShowError("ControlService");
bRet = FALSE;
}
break;
}
case 3:
{
// 删除服务
if (!::DeleteService(shCS))
{
ShowError("DeleteService");
bRet = FALSE;
}
break;
}
default:
break;
}
// 关闭句柄
if (shCS)
{
::CloseServiceHandle(shCS);
shCS = NULL;
}
if (shOSCM)
{
::CloseServiceHandle(shOSCM);
shOSCM = NULL;
}

return bRet;
}

CreateProcessAsUser.exe CreateProcessAsUser.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
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
#include <Windows.h>
#include <UserEnv.h>
#include <WtsApi32.h>
#include <tchar.h>
#pragma comment(lib, "UserEnv.lib")
#pragma comment(lib, "WtsApi32.lib")


// 服务入口函数以及处理回调函数
void __stdcall ServiceMain(DWORD dwArgc, char* lpszArgv);
void __stdcall ServiceCtrlHandle(DWORD dwOperateCode);
void DoTask();
// 显示消息对话框
void ShowMessage(TCHAR* lpszMessage, TCHAR* lpszTitle);
// 创建用户进程
BOOL CreateUserProcess(char* lpszFileName);

// 全局变量
char g_szServiceName[MAX_PATH] = "002.exe"; // 服务名称
SERVICE_STATUS g_ServiceStatus = { 0 };
SERVICE_STATUS_HANDLE g_ServiceStatusHandle = { 0 };


int _tmain(int argc, _TCHAR* argv[])
{
// 注册服务入口函数
SERVICE_TABLE_ENTRY stDispatchTable[] = { { g_szServiceName, (LPSERVICE_MAIN_FUNCTION)ServiceMain }, { NULL, NULL } };
::StartServiceCtrlDispatcher(stDispatchTable);

return 0;
}


void __stdcall ServiceMain(DWORD dwArgc, char* lpszArgv)
{
g_ServiceStatus.dwServiceType = SERVICE_WIN32;
g_ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
g_ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
g_ServiceStatus.dwWin32ExitCode = 0;
g_ServiceStatus.dwServiceSpecificExitCode = 0;
g_ServiceStatus.dwCheckPoint = 0;
g_ServiceStatus.dwWaitHint = 0;

g_ServiceStatusHandle = ::RegisterServiceCtrlHandler(g_szServiceName, ServiceCtrlHandle);

g_ServiceStatus.dwCurrentState = SERVICE_RUNNING;
g_ServiceStatus.dwCheckPoint = 0;
g_ServiceStatus.dwWaitHint = 0;
::SetServiceStatus(g_ServiceStatusHandle, &g_ServiceStatus);

// 自己程序实现部分代码放在这里
DoTask();
}


void __stdcall ServiceCtrlHandle(DWORD dwOperateCode)
{
switch (dwOperateCode)
{
case SERVICE_CONTROL_PAUSE:
{
// 暂停
g_ServiceStatus.dwCurrentState = SERVICE_PAUSED;
break;
}
case SERVICE_CONTROL_CONTINUE:
{
// 继续
g_ServiceStatus.dwCurrentState = SERVICE_RUNNING;
break;
}
case SERVICE_CONTROL_STOP:
{
// 停止
g_ServiceStatus.dwWin32ExitCode = 0;
g_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
g_ServiceStatus.dwCheckPoint = 0;
g_ServiceStatus.dwWaitHint = 0;
::SetServiceStatus(g_ServiceStatusHandle, &g_ServiceStatus);
break;
}
case SERVICE_CONTROL_INTERROGATE:
{
// 询问
break;
}
default:
break;
}
}


void DoTask()
{
// 自己程序实现部分代码放在这里
// 显示对话框
ShowMessage("Hi Demon·Gan\nThis Is From Session 0 Service!\n", "HELLO");
// 创建用户桌面进程
CreateUserProcess("C:\\Users\\admin\\Desktop\\C++\\WinExec.exe");
}


void ShowMessage(TCHAR* lpszMessage, TCHAR* lpszTitle)
{
// 获取当前的Session ID
DWORD dwSessionId = ::WTSGetActiveConsoleSessionId();
// 显示消息对话框
DWORD dwResponse = 0;
::WTSSendMessage(WTS_CURRENT_SERVER_HANDLE, dwSessionId,
lpszTitle, (1 + ::lstrlen(lpszTitle)),
lpszMessage, (1 + ::lstrlen(lpszMessage)),
0, 0, &dwResponse, FALSE);
}


// 突破SESSION 0隔离创建用户进程
BOOL CreateUserProcess(char* lpszFileName)
{
BOOL bRet = TRUE;
DWORD dwSessionID = 0;
HANDLE hToken = NULL;
HANDLE hDuplicatedToken = NULL;
LPVOID lpEnvironment = NULL;
STARTUPINFO si = { 0 };
PROCESS_INFORMATION pi = { 0 };
si.cb = sizeof(si);

do
{
// 获得当前Session ID
dwSessionID = ::WTSGetActiveConsoleSessionId();

// 获得当前Session的用户令牌
if (FALSE == ::WTSQueryUserToken(dwSessionID, &hToken))
{
ShowMessage("WTSQueryUserToken", "ERROR");
bRet = FALSE;
break;
}

// 复制令牌
if (FALSE == ::DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL,
SecurityIdentification, TokenPrimary, &hDuplicatedToken))
{
ShowMessage("DuplicateTokenEx", "ERROR");
bRet = FALSE;
break;
}

// 创建用户Session环境
if (FALSE == ::CreateEnvironmentBlock(&lpEnvironment,
hDuplicatedToken, FALSE))
{
ShowMessage("CreateEnvironmentBlock", "ERROR");
bRet = FALSE;
break;
}

// 在复制的用户Session下执行应用程序,创建进程
if (FALSE == ::CreateProcessAsUser(hDuplicatedToken,
lpszFileName, NULL, NULL, NULL, FALSE,
NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT,
lpEnvironment, NULL, &si, &pi))
{
ShowMessage("CreateProcessAsUser", "ERROR");
bRet = FALSE;
break;
}

} while (FALSE);
// 关闭句柄, 释放资源
if (lpEnvironment)
{
::DestroyEnvironmentBlock(lpEnvironment);
}
if (hDuplicatedToken)
{
::CloseHandle(hDuplicatedToken);
}
if (hToken)
{
::CloseHandle(hToken);
}
return bRet;
}

测试

运行ServiceLoader.exe,注册CreateProcessAsUser.exe为服务,并执行CreateProcessAsUser.exe;CreateProcessAsUser.exe处于SESSION 0,实现创建用户桌面进程Winexec.exe(处于SESSION 1)

image-20221028135342744

记得把创建的服务删除,sc delete CreateProcessAsUser.exe

其实这一章不太懂,为什么说是突破SESSION 0隔离创建用户进程。创建服务就是从SESSION 1到SESSION 0,这也是突破隔离,还是从由低等级到高等级。

内存直接加载运行

把DLL或者exe等PE文件从内存中直接加载到病毒木马的内存中去执行,不需要通过LoadLibrary等线程的API函数去操作,以此躲过杀毒软件的拦截检测。

假如程序需要动态调用DLL文件,内存加载运行技术可以把这些DLL作为资源插入到自己的程序中。此时直接在内存中加载运行即可,不需要再将DLL释放到本地。

实现原理

内存直接加载运行技术的核心就是模拟PE加载器加载PE文件的过程,也就是对导入表、导出表以及重定位表的操作过程。

从内存中加载运行DLL,具体的实现流程总结如下:

  1. 在DLL文件中,根据PE结构获取其加载映像的大小SizeOfImage,并根据SizeOfImage在自己的程序中申请可读、可写、可执行的内存,那么这块内存的首地址就是DLL的加载基址。
  2. 根据DLL中的PE结构获取其映像对齐大小SectionAlignment,然后把DLL文件数据按照SectionAlignment复制到上述申请的可读、可写、可执行的内存中。
  3. 根据PE结构的重定位表,重新对重定位表进行修正。
  4. 根据PE结构的导入表,加载所需的DLL,并获取导入函数的地址并写入导入表中。
  5. 修改DLL的加载基址ImageBase。
  6. 根据PE结构获取DLL的入口地址,然后构造并调用DllMain函数,实现DLL加载。

而exe文件相对于DLL文件实现原理唯一的区别就在于构造入口函数的差别,exe不需要构造DllMain函数,而是根据PE结构获取exe的入口地址偏移AddressOfEntryPoint并计算出入口地址,然后直接跳转到入口地址处执行即可

要特别注意的是,对于exe文件来说,重定位表不是必需的,即使没有重定位表,exe也可正常运行。因为对于exe进程来说,进程最早加载的模块是exe模块,所以它可以按照默认的加载基址加载到内存。对于那些没有重定位表的程序,只能把它加载到默认的加载基址上。如果默认加载基址已被占用,则直接内存加载运行会失败。

代码实现

dllload.exe

MmLoadDll.h

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
#ifndef _MM_LOAD_DLL_H_
#define _MM_LOAD_DLL_H_

#include <Windows.h>

typedef BOOL(__stdcall* typedef_DllMain)(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved);

void ShowError(char* lpszText);

// 模拟LoadLibrary加载内存DLL文件到进程中
// lpData: 内存DLL文件数据的基址
// dwSize: 内存DLL文件的内存大小
// 返回值: 内存DLL加载到进程的加载基址
LPVOID MmLoadLibrary(LPVOID lpData, DWORD dwSize);

// 根据PE结构,获取PE文件加载到内存后的镜像大小
// lpData: 内存DLL文件数据的基址
// 返回值: 返回PE文件结构中IMAGE_NT_HEADERS.OptionalHeader.SizeOfImage值的大小
DWORD GetSizeOfImage(LPVOID lpData);

// 将内存DLL数据按SectionAlignment大小对齐映射到进程内存中
// lpData: 内存DLL文件数据的基址
// lpBaseAddress: 内存DLL数据按SectionAlignment大小对齐映射到进程内存中的内存基址
// 返回值: 成功返回TRUE,否则返回FALSE
BOOL MmMapFile(LPVOID lpData, LPVOID lpBaseAddress);

// 对齐SectionAlignment
// dwSize: 表示未对齐前内存的大小
// dwAlignment: 对齐大小值
// 返回值: 返回内存对齐之后的值
DWORD Align(DWORD dwSize, DWORD dwAlignment);

// 修改PE文件重定位表信息
// lpBaseAddress: 内存DLL数据按SectionAlignment大小对齐映射到进程内存中的内存基址
// 返回值: 成功返回TRUE,否则返回FALSE
BOOL DoRelocationTable(LPVOID lpBaseAddress);

// 填写PE文件导入表信息
// lpBaseAddress: 内存DLL数据按SectionAlignment大小对齐映射到进程内存中的内存基址
// 返回值: 成功返回TRUE,否则返回FALSE
BOOL DoImportTable(LPVOID lpBaseAddress);

// 修改PE文件加载基址IMAGE_NT_HEADERS.OptionalHeader.ImageBase
// lpBaseAddress: 内存DLL数据按SectionAlignment大小对齐映射到进程内存中的内存基址
// 返回值: 成功返回TRUE,否则返回FALSE
BOOL SetImageBase(LPVOID lpBaseAddress);

// 调用DLL的入口函数DllMain,函数地址即为PE文件的入口点IMAGE_NT_HEADERS.OptionalHeader.AddressOfEntryPoint
// lpBaseAddress: 内存DLL数据按SectionAlignment大小对齐映射到进程内存中的内存基址
// 返回值: 成功返回TRUE,否则返回FALSE
BOOL CallDllMain(LPVOID lpBaseAddress);

// 模拟GetProcAddress获取内存DLL的导出函数
// lpBaseAddress: 内存DLL文件加载到进程中的加载基址
// lpszFuncName: 导出函数的名字
// 返回值: 返回导出函数的的地址
LPVOID MmGetProcAddress(LPVOID lpBaseAddress, PCHAR lpszFuncName);

// 释放从内存加载的DLL到进程内存的空间
// lpBaseAddress: 内存DLL数据按SectionAlignment大小对齐映射到进程内存中的内存基址
// 返回值: 成功返回TRUE,否则返回FALSE
BOOL MmFreeLibrary(LPVOID lpBaseAddress);
#endif

dllload.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
// dllload.cpp : 定义控制台应用程序的入口点。
//

#include<tchar.h>
#include<stdio.h>
#include "MmLoadDll.h"


int _tmain(int argc, _TCHAR* argv[])
{
char szFileName[MAX_PATH] = "TestDll.dll";
// 打开DLL文件并获取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)
{
ShowError("CreateFile");
return 1;
}
DWORD dwFileSize = GetFileSize(hFile, NULL);
// 申请动态内存并读取DLL到内存中
BYTE* lpData = new BYTE[dwFileSize];
if (NULL == lpData)
{
ShowError("new");
return 2;
}
DWORD dwRet = 0;
ReadFile(hFile, lpData, dwFileSize, &dwRet, NULL);

// 将内存DLL加载到程序中
LPVOID lpBaseAddress = MmLoadLibrary(lpData, dwFileSize);
if (NULL == lpBaseAddress)
{
ShowError("MmLoadLibrary");
return 3;
}
printf("DLL加载成功\n");

// 获取DLL导出函数并调用
typedef BOOL(*typedef_ShowMessage)(char* lpszText, char* lpszCaption);
typedef_ShowMessage ShowMessage = (typedef_ShowMessage)MmGetProcAddress(lpBaseAddress, "ShowMessage");
if (NULL == ShowMessage)
{
ShowError("MmGetProcAddress");
return 4;
}
ShowMessage("I am Demon·Gan\n", "Who Are You");

// 释放从内存加载的DLL
BOOL bRet = MmFreeLibrary(lpBaseAddress);
if (FALSE == bRet)
{
ShowError("MmFreeLirbary");
}

// 释放
delete[] lpData;
lpData = NULL;
CloseHandle(hFile);

system("pause");
return 0;
}

MmLoadDll.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
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
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
#include "MmLoadDll.h"
#include<windows.h>


void ShowError(char* lpszText)
{
char szErr[MAX_PATH] = { 0 };
::wsprintf(szErr, "%s Error!\nError Code Is:%d\n", lpszText, ::GetLastError());
::MessageBox(NULL, szErr, "ERROR", MB_OK | MB_ICONERROR);
}


// 模拟LoadLibrary加载内存DLL文件到进程中
// lpData: 内存DLL文件数据的基址
// dwSize: 内存DLL文件的内存大小
// 返回值: 内存DLL加载到进程的加载基址
LPVOID MmLoadLibrary(LPVOID lpData, DWORD dwSize)
{
LPVOID lpBaseAddress = NULL;
// 获取镜像大小
DWORD dwSizeOfImage = GetSizeOfImage(lpData);
// 在进程中开辟一个可读、可写、可执行的内存块
lpBaseAddress = VirtualAlloc(NULL, dwSizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (NULL == lpBaseAddress)
{
ShowError("VirtualAlloc");
return NULL;
}
RtlZeroMemory(lpBaseAddress, dwSizeOfImage);
// 将内存DLL数据按SectionAlignment大小对齐映射到进程内存中
if (FALSE == MmMapFile(lpData, lpBaseAddress))
{
ShowError("MmMapFile");
return NULL;
}

// 修改PE文件重定位表信息
if (FALSE == DoRelocationTable(lpBaseAddress))
{
ShowError("DoRelocationTable");
return NULL;
}

// 填写PE文件导入表信息
if (FALSE == DoImportTable(lpBaseAddress))
{
ShowError("DoImportTable");
return NULL;
}

//修改页属性。应该根据每个页的属性单独设置其对应内存页的属性。
//统一设置成一个属性PAGE_EXECUTE_READWRITE
DWORD dwOldProtect = 0;
if (FALSE == ::VirtualProtect(lpBaseAddress, dwSizeOfImage, PAGE_EXECUTE_READWRITE, &dwOldProtect))
{
ShowError("VirtualProtect");
return NULL;
}

// 修改PE文件加载基址IMAGE_NT_HEADERS.OptionalHeader.ImageBase
if (FALSE == SetImageBase(lpBaseAddress))
{
ShowError("SetImageBase");
return NULL;
}

// 调用DLL的入口函数DllMain,函数地址即为PE文件的入口点IMAGE_NT_HEADERS.OptionalHeader.AddressOfEntryPoint
if (FALSE == CallDllMain(lpBaseAddress))
{
ShowError("CallDllMain");
return NULL;
}

return lpBaseAddress;
}


// 根据PE结构,获取PE文件加载到内存后的镜像大小
// lpData: 内存DLL文件数据的基址
// 返回值: 返回PE文件结构中IMAGE_NT_HEADERS.OptionalHeader.SizeOfImage值的大小
DWORD GetSizeOfImage(LPVOID lpData)
{
DWORD dwSizeOfImage = 0;
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpData;
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((ULONG32)pDosHeader + pDosHeader->e_lfanew);
dwSizeOfImage = pNtHeaders->OptionalHeader.SizeOfImage;
return dwSizeOfImage;
}


// 将内存DLL数据按SectionAlignment大小对齐映射到进程内存中
// lpData: 内存DLL文件数据的基址
// lpBaseAddress: 内存DLL数据按SectionAlignment大小对齐映射到进程内存中的内存基址
// 返回值: 成功返回TRUE,否则返回FALSE
BOOL MmMapFile(LPVOID lpData, LPVOID lpBaseAddress)
{
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpData;
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((ULONG32)pDosHeader + pDosHeader->e_lfanew);
// 获取SizeOfHeaders的值: 所有头+节表头的大小
DWORD dwSizeOfHeaders = pNtHeaders->OptionalHeader.SizeOfHeaders;
// 获取节表的数量
WORD wNumberOfSections = pNtHeaders->FileHeader.NumberOfSections;
// 获取第一个节表头的地址
PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pNtHeaders + sizeof(IMAGE_NT_HEADERS));
// 加载 所有头+节表头的大小
RtlCopyMemory(lpBaseAddress, lpData, dwSizeOfHeaders);
// 对齐SectionAlignment循环加载节表
WORD i = 0;
LPVOID lpSrcMem = NULL;
LPVOID lpDestMem = NULL;
DWORD dwSizeOfRawData = 0;
for (i = 0; i < wNumberOfSections; i++)
{
if ((0 == pSectionHeader->VirtualAddress) ||
(0 == pSectionHeader->SizeOfRawData))
{
pSectionHeader++;
continue;
}
lpSrcMem = (LPVOID)((DWORD)lpData + pSectionHeader->PointerToRawData);
lpDestMem = (LPVOID)((DWORD)lpBaseAddress + pSectionHeader->VirtualAddress);
dwSizeOfRawData = pSectionHeader->SizeOfRawData;
::RtlCopyMemory(lpDestMem, lpSrcMem, dwSizeOfRawData);
pSectionHeader++;
}
return TRUE;
}


// 对齐SectionAlignment
// dwSize: 表示未对齐前内存的大小
// dwAlignment: 对齐大小值
// 返回值: 返回内存对齐之后的值
DWORD Align(DWORD dwSize, DWORD dwAlignment)
{
DWORD dwRet = 0;
DWORD i = 0, j = 0;
i = dwSize / dwAlignment;
j = dwSize % dwAlignment;
if (0 != j)
{
i++;
}
dwRet = i * dwAlignment;
return dwRet;
}


// 修改PE文件重定位表信息
// lpBaseAddress: 内存DLL数据按SectionAlignment大小对齐映射到进程内存中的内存基址
// 返回值: 成功返回TRUE,否则返回FALSE
BOOL DoRelocationTable(LPVOID lpBaseAddress)
{
/* 重定位表的结构:
// DWORD sectionAddress, DWORD size (包括本节需要重定位的数据)
// 例如 1000节需要修正5个重定位数据的话,重定位表的数据是
// 00 10 00 00 14 00 00 00 xxxx xxxx xxxx xxxx xxxx 0000
// ----------- ----------- ----
// 给出节的偏移 总尺寸=8+6*2 需要修正的地址 用于对齐4字节
// 重定位表是若干个相连,如果address 和 size都是0 表示结束
// 需要修正的地址是12位的,高4位是形态字,intel cpu下是3
*/
//假设NewBase是0x600000,而文件中设置的缺省ImageBase是0x400000,则修正偏移量就是0x200000
//注意重定位表的位置可能和硬盘文件中的偏移地址不同,应该使用加载后的地址

PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress;
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((ULONG32)pDosHeader + pDosHeader->e_lfanew);
PIMAGE_BASE_RELOCATION pLoc = (PIMAGE_BASE_RELOCATION)((unsigned long)pDosHeader + pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);

// 判断是否有 重定位表
if ((PVOID)pLoc == (PVOID)pDosHeader)
{
// 重定位表 为空
return TRUE;
}

while ((pLoc->VirtualAddress + pLoc->SizeOfBlock) != 0) //开始扫描重定位表
{
WORD* pLocData = (WORD*)((PBYTE)pLoc + sizeof(IMAGE_BASE_RELOCATION));
//计算本节需要修正的重定位项(地址)的数目
int nNumberOfReloc = (pLoc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);

for (int i = 0; i < nNumberOfReloc; i++)
{
// 每个WORD由两部分组成。高4位指出了重定位的类型,WINNT.H中的一系列IMAGE_REL_BASED_xxx定义了重定位类型的取值。
// 低12位是相对于VirtualAddress域的偏移,指出了必须进行重定位的位置。
/*
#ifdef _WIN64
if ((DWORD)(pLocData[i] & 0x0000F000) == 0x0000A000)
{
// 64位dll重定位,IMAGE_REL_BASED_DIR64
// 对于IA-64的可执行文件,重定位似乎总是IMAGE_REL_BASED_DIR64类型的。

ULONGLONG* pAddress = (ULONGLONG *)((PBYTE)pNewBase + pLoc->VirtualAddress + (pLocData[i] & 0x0FFF));
ULONGLONG ullDelta = (ULONGLONG)pNewBase - m_pNTHeader->OptionalHeader.ImageBase;
*pAddress += ullDelta;

}
#endif
*/
if ((DWORD)(pLocData[i] & 0x0000F000) == 0x00003000) //这是一个需要修正的地址
{
// 32位dll重定位,IMAGE_REL_BASED_HIGHLOW
// 对于x86的可执行文件,所有的基址重定位都是IMAGE_REL_BASED_HIGHLOW类型的。

DWORD* pAddress = (DWORD*)((PBYTE)pDosHeader + pLoc->VirtualAddress + (pLocData[i] & 0x0FFF));
DWORD dwDelta = (DWORD)pDosHeader - pNtHeaders->OptionalHeader.ImageBase;
*pAddress += dwDelta;

}
}

//转移到下一个节进行处理
pLoc = (PIMAGE_BASE_RELOCATION)((PBYTE)pLoc + pLoc->SizeOfBlock);
}

return TRUE;
}


// 填写PE文件导入表信息
// lpBaseAddress: 内存DLL数据按SectionAlignment大小对齐映射到进程内存中的内存基址
// 返回值: 成功返回TRUE,否则返回FALSE
BOOL DoImportTable(LPVOID lpBaseAddress)
{
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress;
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((ULONG32)pDosHeader + pDosHeader->e_lfanew);
PIMAGE_IMPORT_DESCRIPTOR pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pDosHeader +
pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

// 循环遍历DLL导入表中的DLL及获取导入表中的函数地址
char* lpDllName = NULL;
HMODULE hDll = NULL;
PIMAGE_THUNK_DATA lpImportNameArray = NULL;
PIMAGE_IMPORT_BY_NAME lpImportByName = NULL;
PIMAGE_THUNK_DATA lpImportFuncAddrArray = NULL;
FARPROC lpFuncAddress = NULL;
DWORD i = 0;

while (TRUE)
{
if (0 == pImportTable->OriginalFirstThunk)
{
break;
}

// 获取导入表中DLL的名称并加载DLL
lpDllName = (char*)((DWORD)pDosHeader + pImportTable->Name);
hDll = ::GetModuleHandle(lpDllName);
if (NULL == hDll)
{
hDll = ::LoadLibrary(lpDllName);
if (NULL == hDll)
{
pImportTable++;
continue;
}
}

i = 0;
// 获取OriginalFirstThunk以及对应的导入函数名称表首地址
lpImportNameArray = (PIMAGE_THUNK_DATA)((DWORD)pDosHeader + pImportTable->OriginalFirstThunk);
// 获取FirstThunk以及对应的导入函数地址表首地址
lpImportFuncAddrArray = (PIMAGE_THUNK_DATA)((DWORD)pDosHeader + pImportTable->FirstThunk);
while (TRUE)
{
if (0 == lpImportNameArray[i].u1.AddressOfData)
{
break;
}

// 获取IMAGE_IMPORT_BY_NAME结构
lpImportByName = (PIMAGE_IMPORT_BY_NAME)((DWORD)pDosHeader + lpImportNameArray[i].u1.AddressOfData);

// 判断导出函数是序号导出还是函数名称导出
if (0x80000000 & lpImportNameArray[i].u1.Ordinal)
{
// 序号导出
// 当IMAGE_THUNK_DATA值的最高位为1时,表示函数以序号方式输入,这时,低位被看做是一个函数序号
lpFuncAddress = ::GetProcAddress(hDll, (LPCSTR)(lpImportNameArray[i].u1.Ordinal & 0x0000FFFF));
}
else
{
// 名称导出
lpFuncAddress = ::GetProcAddress(hDll, (LPCSTR)lpImportByName->Name);
}
// 注意此处的函数地址表的赋值,要对照PE格式进行装载,不要理解错了!!!
lpImportFuncAddrArray[i].u1.Function = (DWORD)lpFuncAddress;
i++;
}

pImportTable++;
}

return TRUE;
}


// 修改PE文件加载基址IMAGE_NT_HEADERS.OptionalHeader.ImageBase
// lpBaseAddress: 内存DLL数据按SectionAlignment大小对齐映射到进程内存中的内存基址
// 返回值: 成功返回TRUE,否则返回FALSE
BOOL SetImageBase(LPVOID lpBaseAddress)
{
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress;
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((ULONG32)pDosHeader + pDosHeader->e_lfanew);
pNtHeaders->OptionalHeader.ImageBase = (ULONG32)lpBaseAddress;

return TRUE;
}


// 调用DLL的入口函数DllMain,函数地址即为PE文件的入口点IMAGE_NT_HEADERS.OptionalHeader.AddressOfEntryPoint
// lpBaseAddress: 内存DLL数据按SectionAlignment大小对齐映射到进程内存中的内存基址
// 返回值: 成功返回TRUE,否则返回FALSE
BOOL CallDllMain(LPVOID lpBaseAddress)
{
typedef_DllMain DllMain = NULL;
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress;
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((ULONG32)pDosHeader + pDosHeader->e_lfanew);
DllMain = (typedef_DllMain)((ULONG32)pDosHeader + pNtHeaders->OptionalHeader.AddressOfEntryPoint);
// 调用入口函数,附加进程DLL_PROCESS_ATTACH
BOOL bRet = DllMain((HINSTANCE)lpBaseAddress, DLL_PROCESS_ATTACH, NULL);
if (FALSE == bRet)
{
ShowError("DllMain");
}

return bRet;
}


// 模拟GetProcAddress获取内存DLL的导出函数
// lpBaseAddress: 内存DLL文件加载到进程中的加载基址
// lpszFuncName: 导出函数的名字
// 返回值: 返回导出函数的的地址
LPVOID MmGetProcAddress(LPVOID lpBaseAddress, PCHAR lpszFuncName)
{
LPVOID lpFunc = NULL;
// 获取导出表
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress;
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((ULONG32)pDosHeader + pDosHeader->e_lfanew);
PIMAGE_EXPORT_DIRECTORY pExportTable = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pDosHeader + pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
// 获取导出表的数据
PDWORD lpAddressOfNamesArray = (PDWORD)((DWORD)pDosHeader + pExportTable->AddressOfNames);
PCHAR lpFuncName = NULL;
PWORD lpAddressOfNameOrdinalsArray = (PWORD)((DWORD)pDosHeader + pExportTable->AddressOfNameOrdinals);
WORD wHint = 0;
PDWORD lpAddressOfFunctionsArray = (PDWORD)((DWORD)pDosHeader + pExportTable->AddressOfFunctions);

DWORD dwNumberOfNames = pExportTable->NumberOfNames;
DWORD i = 0;
// 遍历导出表的导出函数的名称, 并进行匹配
for (i = 0; i < dwNumberOfNames; i++)
{
lpFuncName = (PCHAR)((DWORD)pDosHeader + lpAddressOfNamesArray[i]);
if (0 == ::lstrcmpi(lpFuncName, lpszFuncName))
{
// 获取导出函数地址
wHint = lpAddressOfNameOrdinalsArray[i];
lpFunc = (LPVOID)((DWORD)pDosHeader + lpAddressOfFunctionsArray[wHint]);
break;
}
}
return lpFunc;
}


// 释放从内存加载的DLL到进程内存的空间
// lpBaseAddress: 内存DLL数据按SectionAlignment大小对齐映射到进程内存中的内存基址
// 返回值: 成功返回TRUE,否则返回FALSE
BOOL MmFreeLibrary(LPVOID lpBaseAddress)
{
BOOL bRet = FALSE;

if (NULL == lpBaseAddress)
{
return bRet;
}

bRet = VirtualFree(lpBaseAddress, 0, MEM_RELEASE);
lpBaseAddress = NULL;

return bRet;
}

TestDll.dll(待加载DLL文件)
dllmain.cpp

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

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;
}

TestDll.cpp

1
2
3
4
5
6
7
8
#include<windows.h>

extern "C" __declspec(dllexport) BOOL ShowMessage(char* lpszText, char* lpszCaption)
{
MessageBox(NULL, lpszText, lpszCaption, MB_OK);

return TRUE;
}

测试

image-20221031103323202


启动技术
http://wangchenchina.github.io/2022/10/31/启动技术/
作者
Demo
发布于
2022年10月31日
许可协议