You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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(&currentName, name);
        if (RtlEqualUnicodeString(&currentName, 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通信

这是最安全的方式:

  1. 编写一个用户态程序,负责加载并调用C++ DLL的函数。
  2. 内核驱动通过IOCTL、共享内存等方式向用户态程序发送请求。
  3. 用户态程序执行DLL函数后,将结果返回给内核。

2. 移植DLL逻辑到内核模式

如果必须在内核中执行该逻辑,将C++ DLL的代码改造成内核模式兼容的代码:

  • 替换所有用户态CRT函数(比如std::cout改为KdPrint或DbgPrint)。
  • 避免使用内核不支持的C++特性(如异常处理需启用/EHsc并使用内核模式异常处理,STL大部分容器无法使用)。
  • 将代码编译为内核模式库或直接整合到KMDF驱动中。

3. 使用用户态回调机制

内核通过IoRegisterDeviceInterface注册设备接口,用户态程序监听该接口,当内核需要调用DLL功能时,通过接口触发用户态程序执行对应逻辑,再将结果返回内核。

内容的提问来源于stack exchange,提问作者Ferrus

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 06:28:10