基于WDK与C++:内核驱动监测notepad启动后运行osk.exe的实现问询
内核驱动中监测notepad启动后启动osk.exe的实现方案
一、核心思路说明
内核态无法直接调用用户态的CreateProcess,常用两种可行路径:
- 用内核原生的
ZwCreateProcessEx+ZwCreateThreadEx直接创建进程 - 创建系统线程,切换到目标用户进程(如notepad)的上下文,调用用户层
CreateProcessW启动osk.exe(这种方式更贴合用户态逻辑,能保证osk在同一桌面会话中显示)
以下重点讲解第二种方案,同时补充你提到的ZwOpenProcess/ZwOpenFile的使用场景。
二、具体实现步骤
1. 在LoadImage回调中触发启动逻辑
PsSetLoadImageNotifyRoutine的回调运行在PASSIVE_LEVEL,可直接创建系统线程执行启动逻辑,但要先判断是否是notepad.exe加载:
VOID LoadImageCallback( PUNICODE_STRING FullImageName, HANDLE ProcessId, PIMAGE_INFO ImageInfo ) { if (FullImageName && ImageInfo->ImageBase != NULL) { // 提取文件名并判断是否为notepad.exe(忽略大小写) PWCHAR fileName = wcsrchr(FullImageName->Buffer, L'\\'); if (fileName && _wcsicmp(fileName + 1, L"notepad.exe") == 0) { // 创建系统线程,传入notepad的进程ID HANDLE hThread = NULL; NTSTATUS status = PsCreateSystemThread( &hThread, THREAD_ALL_ACCESS, NULL, NULL, NULL, StartOskThread, (PVOID)ProcessId ); if (NT_SUCCESS(status) && hThread != NULL) { ZwClose(hThread); } } } }
2. 系统线程函数:切换到notepad进程上下文启动osk
系统线程默认运行在内核态,需附加到notepad进程,调用用户态CreateProcessW:
typedef BOOL(WINAPI* PCreateProcessW)( LPCWSTR lpApplicationName, LPWSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation ); VOID StartOskThread(PVOID Context) { HANDLE hNotepadProcess = NULL; NTSTATUS status; HANDLE hUserThread = NULL; PPEB pProcessPeb = NULL; PVOID kernel32Base = NULL; PCreateProcessW pCreateProcessW = NULL; WCHAR oskPath[] = L"C:\\Windows\\System32\\osk.exe"; STARTUPINFOW si = {0}; si.cb = sizeof(STARTUPINFOW); HANDLE notepadPid = (HANDLE)Context; // 用ZwOpenProcess打开notepad进程(你关注的函数之一) OBJECT_ATTRIBUTES objAttr; InitializeObjectAttributes(&objAttr, NULL, 0, NULL, NULL); CLIENT_ID clientId = {.UniqueProcess = notepadPid, .UniqueThread = NULL}; status = ZwOpenProcess(&hNotepadProcess, PROCESS_ALL_ACCESS, &objAttr, &clientId); if (!NT_SUCCESS(status)) goto Cleanup; // 获取notepad进程的PEB,定位kernel32.dll基址 pProcessPeb = PsGetProcessPeb(hNotepadProcess); if (!pProcessPeb) goto Cleanup; PLIST_ENTRY moduleList = &pProcessPeb->Ldr->InMemoryOrderModuleList; PLIST_ENTRY currentEntry = moduleList->Flink; while (currentEntry != moduleList) { PLDR_DATA_TABLE_ENTRY pModuleEntry = CONTAINING_RECORD(currentEntry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); if (pModuleEntry->BaseDllName.Length && _wcsicmp(pModuleEntry->BaseDllName.Buffer, L"kernel32.dll") == 0) { kernel32Base = pModuleEntry->DllBase; break; } currentEntry = currentEntry->Flink; } if (!kernel32Base) goto Cleanup; // 内核态获取用户态函数地址:用RtlFindExportedRoutineByName替代用户态的GetProcAddress pCreateProcessW = (PCreateProcessW)RtlFindExportedRoutineByName(kernel32Base, "CreateProcessW"); if (!pCreateProcessW) goto Cleanup; // 在notepad进程中创建用户线程,调用CreateProcessW启动osk status = RtlCreateUserThread( hNotepadProcess, NULL, FALSE, 0, 0, 0, (PVOID)pCreateProcessW, oskPath, &hUserThread, NULL ); if (NT_SUCCESS(status) && hUserThread != NULL) { ZwWaitForSingleObject(hUserThread, FALSE, NULL); ZwClose(hUserThread); } Cleanup: if (hNotepadProcess != NULL) ZwClose(hNotepadProcess); PsTerminateSystemThread(NT_SUCCESS(status) ? STATUS_SUCCESS : STATUS_UNSUCCESSFUL); }
3. ZwOpenProcess/ZwOpenFile的补充说明
如果选择用ZwCreateProcessEx直接在核态创建进程,才会用到这两个函数:
- ZwOpenFile:打开osk.exe的镜像文件,获取文件句柄作为
ZwCreateProcessEx的输入参数 - ZwOpenProcess:若要继承某个进程的环境/句柄,需先打开该进程获取句柄
简化示例片段:
// 用ZwOpenFile打开osk.exe文件(你关注的函数之二) UNICODE_STRING oskFilePath; RtlInitUnicodeString(&oskFilePath, L"\\??\\C:\\Windows\\System32\\osk.exe"); OBJECT_ATTRIBUTES fileAttr; InitializeObjectAttributes(&fileAttr, &oskFilePath, OBJ_CASE_INSENSITIVE, NULL, NULL); IO_STATUS_BLOCK ioStatus; HANDLE hOskFile = NULL; NTSTATUS status = ZwOpenFile(&hOskFile, GENERIC_READ | SYNCHRONIZE, &fileAttr, &ioStatus, FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT); // 用ZwCreateProcessEx创建进程(需配合ZwCreateThreadEx初始化线程) PROCESS_INFORMATION pi = {0}; status = ZwCreateProcessEx( &pi.hProcess, PROCESS_ALL_ACCESS, NULL, NtCurrentProcess(), TRUE, hOskFile, NULL, NULL, FALSE );
三、关键注意事项
- IRQL限制:
PsSetLoadImageNotifyRoutine回调运行在PASSIVE_LEVEL,可执行线程创建、文件打开等操作,但不能调用高IRQL函数 - 权限要求:驱动需持有
SeCreateTokenPrivilege、SeAssignPrimaryTokenPrivilege等权限,否则进程创建会失败 - 会话一致性:必须在用户进程(如notepad)的上下文启动osk,否则osk会运行在会话0,用户无法看到
- 重复启动防护:需添加全局标记或进程判断,避免notepad多次加载时重复启动osk
内容的提问来源于stack exchange,提问作者김재희
相关产品推荐
相关产品推荐

