本文展示一個核心模式驅動 (Kernel Mode Driver) 範例,透過修改 SSDT (System Service Descriptor Table) 將 NtCreateSection 重導向至自定義 Proxy 函數,檢測映射映像檔 (SEC_IMAGE) 行為以判斷進程即將啟動,並依使用者策略決定允許或拒絕 (STATUS_ACCESS_DENIED),實現類防毒軟體的進程攔截功能。
NtCreateSection 參數 SectionPageProtection 含 SEC_IMAGE (0x01000000) 且 AllocationAttributes 為 0 時,表示映射可執行映像 (EXE/DLL)。FileHandle 取得 FILE_OBJECT,進而獲取完整路徑,判斷副檔名是否為 .exe。NtCreateSection,拒絕則返回 0xC0000022。// 用戶模式傳入目標服務索引 (從 ntdll.dll 的 MOV EAX, Index 取得)
if (IrpSp->Parameters.DeviceIoControl.IoControlCode == IOCTL_HOOK_SSDT) {
UCHAR* buf = (UCHAR*)Irp->AssociatedIrp.SystemBuffer;
ULONG index; RtlCopyMemory(&index, buf, 4); // 取得索引
ULONG* pServiceTable = (ULONG*)KeServiceDescriptorTable->ServiceTableBase;
ULONG targetAddr = pServiceTable[index];
// 映射物理記憶體以繞過寫保護 (MmMapIoSpace)
ULONG* pMapped = (ULONG*)MmMapIoSpace(MmGetPhysicalAddress((PVOID)targetAddr), 4, MmNonCached);
_asm {
mov eax, pMapped
mov ebx, [eax] // 保存原函數地址
mov RealCallee, ebx
mov ebx, Proxy // 我們的 Hook 函數
mov [eax], ebx // 覆寫 SSDT 項
}
// ... 映射輸出緩衝區供用戶模式寫入決策 ...
}
check() (判斷邏輯)ULONG __stdcall check(PULONG args) {
// args 為 NtCreateSection 參數陣列
if ((args[4] & 0xF0) == 0) return 1; // AllocationAttributes 檢查
if ((args[5] & 0x01000000) == 0) return 1; // SectionPageProtection 無 SEC_IMAGE
HANDLE hFile = (HANDLE)args[6]; // FileHandle
PFILE_OBJECT pFileObj;
ObReferenceObjectByHandle(hFile, 0, NULL, KernelMode, (PVOID*)&pFileObj, NULL);
if (!pFileObj) return 1;
// 轉 ANSI 路徑
ANSI_STRING aStr; RtlUnicodeStringToAnsiString(&aStr, &pFileObj->FileName, TRUE);
ObDereferenceObject(pFileObj);
// 簡易判斷 .exe 副檔名
char* p = aStr.Buffer + aStr.Length - 1;
while (p > aStr.Buffer && *p != '.') p--;
if (_stricmp(p, ".exe") != 0) { RtlFreeAnsiString(&aStr); return 1; }
// 通知用戶模式決策 (共享緩衝區 output)
strcpy(&output[8], aStr.Buffer); // 傳遞路徑
RtlFreeAnsiString(&aStr);
output[0] = 1; // 等待標誌
KeWaitForSingleObject(&gEvent, Executive, KernelMode, FALSE, NULL); // 等待用戶回應
ULONG decision; RtlCopyMemory(&decision, &output[4], 4); // 讀取用戶決定
KeSetEvent(&gEvent, 0, FALSE);
return decision; // 1=允許, 0=拒絕
}
Proxy (保存上下文、調用 check、決定跳轉)__declspec(naked) void Proxy() {
_asm {
pushfd; pushad // 保存所有暫存器/旗標
mov ebx, esp; add ebx, 40 // 計算參數堆疊位址
push ebx; call check // 調用 C 函數 check(args)
cmp eax, 1
jne BlockExecution
// 允許:恢復上下文,跳轉原函數
popad; popfd; jmp RealCallee
BlockExecution:
// 拒絕:恢復上下文,返回 STATUS_ACCESS_DENIED
popad
mov ebx, [esp+8] // 取得返回地址位置
mov dword ptr [ebx], 0 // 清零返回值 (NTSTATUS 在 eax)
mov eax, 0xC0000022L // STATUS_ACCESS_DENIED
popfd; ret 32 // 返回 8 參數 * 4 bytes
}
}
DWORD WINAPI DecisionThread(LPVOID) {
while (TRUE) {
if (output[0] == 1) { // 驅動請求決策
char* path = &output[8];
BOOL allow = FALSE;
// 檢查白名單...
if (!InWhiteList(path)) {
CString msg; msg.Format(_T("允許執行 %s ?"), path);
allow = (IDYES == MessageBox(NULL, msg, _T("警告"), MB_YESNO|MB_ICONQUESTION|MB_TOPMOST));
if (allow) AddToWhiteList(path);
} else allow = TRUE;
output[4] = allow; // 寫入決策
output[0] = 0; // 通知驅動可讀取
}
Sleep(50);
}
return 0;
}
MmMapIoSpace 繞過寫保護僅適用於無 PatchGuard 環境 (x86 或關閉測試模式 x64)。KeWaitForSingleObject) 極易造成系統卡死,生產環境必須設計超時機制與非同步決策。本代碼僅供教學研究理解 SSDT 機制與進程創建流程,請勿用於生產環境。