【Windows Internal】DllMain
DLLMain 是 DLL 的進入點 function,loader 在 load/unload DLL 或 threads 生滅的時候會自動呼叫他,
所以我們可以把惡意邏輯放在 DLL_PROCESS_ATTACH,
DLL 一被 map 就會以宿主權限執行。
組成&應用
進入點簽章:
BOOL WINAPI DllMain(
HINSTANCE hinstDLL, // DLL 模組的 handle(= 映射後的 Base Address)
DWORD fdwReason, // 為什麼被呼叫
LPVOID lpvReserved // 保留,可用來區分 static/dynamic 載入、process/thread 終止
);
四種 fdwReason:
| reason | 時機 | 典型用途 |
|---|---|---|
DLL_PROCESS_ATTACH | DLL 被映射進 Process(啟動時解析 import,或 LoadLibrary) | 初始化;惡意邏輯放這裡 |
DLL_THREAD_ATTACH | Process 內有新 Thread 誕生 | 每 Thread 初始化(多數 DLL 會停用此通知) |
DLL_THREAD_DETACH | Thread 結束 | 每 Thread 清理 |
DLL_PROCESS_DETACH | DLL 即將從 process detach | 收尾清理 |
DLL_PROCESS_ATTACH = 免費的自動執行
Attacker 不需要 Victim 呼叫任何匯出 Function,
只要 DLL 被 Load 進來,DLL_PROCESS_ATTACH 就一定會跑到,
這就是 DLL Hijacking 的原理,載入本身就等於執行。
DllMain 是可選的
沒有 DllMain 的 DLL 還是能照常載入、提供匯出 Function,
寫了 DllMain 只是多了在載入當下搶先執行一段程式碼的能力。
Loader Lock: 為什麼要把 DllMain 設計得極簡?
DllMain 是在 loader 持有 process 級 loader lock 的狀態下被呼叫的,
所以微軟官方也說 DllMain 裡面什麼都不能安全做(不能 LoadLibrary、建立 thread、跑複雜邏輯),
專業的 DllMain 只做最少的事,然後把真正的工作移交出去。
實作例子
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
PrintAttachMessage(true); // ① 用 OutputDebugString 留下足跡
g_instance = hModule;
if (IsUIAccessBool()) { // ② 確認自己真的在 UIAccess 行程裡(防誤觸發)
OutputDebugStringA("Starting process\n");
std::wstring path = GetRealExecutablePath(); // 再吃一次 mount point 轉向,取回主程式真身
if (path.size() > 0) {
StartProcess(path.c_str()); // ③ token 手術 + 以 "bypass 1" 重啟主程式
}
}
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
PrintAttachMessage(false);
break;
}
return TRUE;
}
然後我們可以用 DebugView 觀察,DLL 載入的瞬間應該會跑出類似:
osk.exe (Attach) - PID: 12345 Elevated: true UIAccess: true
Starting process
這個就代表 DLL 已經在 osk.exe 的權限上下文裡醒來了。