KMDF内核驱动调用C++ DLL遇函数未定义错误,求解决方法
KMDF驱动调用C++ DLL的编译错误与解决方案
一、编译错误原因
你遇到的几个未定义函数错误,本质是以下问题:
- ZwQuerySystemInformation:属于未公开的内核函数,WDK公开头文件未声明,且
SystemModuleInformation信息类对应的结构体也未公开。 - PsLoadImage/PsUnloadImage:同样是未公开的内核函数,用于加载用户态镜像,但WDK不提供公开声明。
- LdrGetProcedureAddress:这是用户态NTDLL中的函数,内核模式下无法直接调用,编译器自然找不到该函数的声明。
二、编译错误的临时修复(仅解决编译,运行仍会崩溃)
1. 添加未公开函数与结构体的声明
在你的驱动代码开头添加以下声明:
// 定义SystemModuleInformation信息类 #define SystemModuleInformation 11 // 未公开的SYSTEM_MODULE_INFORMATION结构体 typedef struct _SYSTEM_MODULE_INFORMATION { ULONG Reserved[2]; PVOID Base; ULONG Size; ULONG Flags; USHORT Index; USHORT Unknown; USHORT LoadCount; USHORT ModuleNameOffset; CHAR ImageName[256]; } SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION; // 声明未公开的内核函数 NTSTATUS NTAPI ZwQuerySystemInformation( ULONG SystemInformationClass, PVOID SystemInformation, ULONG SystemInformationLength, PULONG ReturnLength ); NTSTATUS NTAPI PsLoadImage( PUNICODE_STRING ImageName, PUNICODE_STRING DllPath, PUNICODE_STRING ImagePath, ULONG Flags, PVOID *BaseAddress ); NTSTATUS NTAPI PsUnloadImage( PVOID BaseAddress );
2. 替换LdrGetProcedureAddress为内核态导出表解析
内核无法调用用户态的LdrGetProcedureAddress,需要手动解析PE文件的导出表来获取函数地址,示例函数如下:
PVOID GetExportedFunctionAddress(PVOID ModuleBase, PUNICODE_STRING FunctionName) { PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)ModuleBase; if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) return NULL; PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((PUCHAR)ModuleBase + dosHeader->e_lfanew); if (ntHeaders->Signature != IMAGE_NT_SIGNATURE) return NULL; PIMAGE_EXPORT_DIRECTORY exportDir = (PIMAGE_EXPORT_DIRECTORY)( (PUCHAR)ModuleBase + ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress ); PULONG functionNames = (PULONG)((PUCHAR)ModuleBase + exportDir->AddressOfNames); PULONG functionAddresses = (PULONG)((PUCHAR)ModuleBase + exportDir->AddressOfFunctions); PUSHORT functionOrdinals = (PUSHORT)((PUCHAR)ModuleBase + exportDir->AddressOfNameOrdinals); for (ULONG i = 0; i < exportDir->NumberOfNames; i++) { PCHAR name = (PCHAR)((PUCHAR)ModuleBase + functionNames[i]); UNICODE_STRING currentName; RtlInitAnsiString(¤tName, name); if (RtlEqualUnicodeString(¤tName, FunctionName, TRUE)) { return (PVOID)((PUCHAR)ModuleBase + functionAddresses[functionOrdinals[i]]); } } return NULL; }
然后在LoadDllAndCallFunction中替换LdrGetProcedureAddress的调用为这个函数:
// 替换原来的LdrGetProcedureAddress调用 PVOID functionAddress = GetExportedFunctionAddress(moduleBase, FunctionName); if (!functionAddress) { // 处理函数未找到的错误 PsUnloadImage(dllBase); ZwClose(fileHandle); return STATUS_NOT_FOUND; }
三、核心问题:内核直接调用用户态DLL的致命错误
即使编译通过,你的代码运行时必然崩溃,原因包括:
- 用户态DLL依赖用户态CRT(比如
std::cout是用户态控制台输出函数,内核没有对应的执行环境,调用会直接触发系统崩溃)。 - 内核线程运行在Ring0权限,用户态DLL的代码是Ring3权限,直接执行会引发权限违规。
- 用户态DLL的初始化逻辑(如DllMain)是针对用户态进程设计的,在内核上下文执行会导致内存访问错误或逻辑混乱。
四、正确的实现方案
1. 内核与用户态程序IPC通信
这是最安全的方式:
- 编写一个用户态程序,负责加载并调用C++ DLL的函数。
- 内核驱动通过IOCTL、共享内存等方式向用户态程序发送请求。
- 用户态程序执行DLL函数后,将结果返回给内核。
2. 移植DLL逻辑到内核模式
如果必须在内核中执行该逻辑,将C++ DLL的代码改造成内核模式兼容的代码:
- 替换所有用户态CRT函数(比如
std::cout改为KdPrint或DbgPrint)。 - 避免使用内核不支持的C++特性(如异常处理需启用
/EHsc并使用内核模式异常处理,STL大部分容器无法使用)。 - 将代码编译为内核模式库或直接整合到KMDF驱动中。
3. 使用用户态回调机制
内核通过IoRegisterDeviceInterface注册设备接口,用户态程序监听该接口,当内核需要调用DLL功能时,通过接口触发用户态程序执行对应逻辑,再将结果返回内核。
内容的提问来源于stack exchange,提问作者Ferrus
相关产品推荐
相关产品推荐

