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

内核态使用BitBlt生成BMP截图异常问题求助

内核态使用GDI函数生成BMP截图:文件异常/全黑问题排查

问题概述

尝试在内核态调用BitBlt、CreateCompatibleDC等GDI函数生成BMP格式截图,遇到两类问题:

  • 初始代码:可成功创建文件,但生成的BMP仅1KB,远小于预期大小
  • 修改后代码:文件大小恢复正常,但内容为全黑空白
    已参考微软官方截图示例调整代码,问题仍未解决,附上两段相关代码寻求排查方向。

相关代码

初始版本(生成1KB小文件)

// 示例代码框架
#include <ntddk.h>
#include <windef.h>

NTSTATUS GenerateScreenshot() {
    HDC hDesktopDC = GetDC(NULL);
    if (!hDesktopDC) return STATUS_UNSUCCESSFUL;

    HDC hMemDC = CreateCompatibleDC(hDesktopDC);
    int width = GetDeviceCaps(hDesktopDC, HORZRES);
    int height = GetDeviceCaps(hDesktopDC, VERTRES);

    HBITMAP hBitmap = CreateCompatibleBitmap(hDesktopDC, width, height);
    SelectObject(hMemDC, hBitmap);

    // 此处可能缺少BitBlt调用或像素数据写入逻辑
    BitBlt(hMemDC, 0, 0, width, height, hDesktopDC, 0, 0, SRCCOPY);

    // 文件写入部分:仅写入了文件头,未处理像素数据
    BITMAPFILEHEADER bmfHeader;
    BITMAPINFOHEADER bmiHeader;
    memset(&bmfHeader, 0, sizeof(bmfHeader));
    memset(&bmiHeader, 0, sizeof(bmiHeader));

    bmfHeader.bfType = 0x4D42; // "BM"
    bmfHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
    bmfHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

    bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmiHeader.biWidth = width;
    bmiHeader.biHeight = height;
    bmiHeader.biPlanes = 1;
    bmiHeader.biBitCount = 24;
    bmiHeader.biCompression = BI_RGB;

    HANDLE hFile = CreateFile(L"\\??\\C:\\test.bmp", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile != INVALID_HANDLE_VALUE) {
        DWORD bytesWritten;
        WriteFile(hFile, &bmfHeader, sizeof(bmfHeader), &bytesWritten, NULL);
        WriteFile(hFile, &bmiHeader, sizeof(bmiHeader), &bytesWritten, NULL);
        // 缺少像素数据写入步骤
        CloseHandle(hFile);
    }

    DeleteObject(hBitmap);
    DeleteDC(hMemDC);
    ReleaseDC(NULL, hDesktopDC);
    return STATUS_SUCCESS;
}

修改版本(文件大小正常但全黑)

// 示例代码框架
#include <ntddk.h>
#include <windef.h>

NTSTATUS GenerateScreenshot() {
    HDC hDesktopDC = GetDC(NULL);
    if (!hDesktopDC) return STATUS_UNSUCCESSFUL;

    HDC hMemDC = CreateCompatibleDC(hDesktopDC);
    int width = GetDeviceCaps(hDesktopDC, HORZRES);
    int height = GetDeviceCaps(hDesktopDC, VERTRES);

    HBITMAP hBitmap = CreateCompatibleBitmap(hDesktopDC, width, height);
    HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemDC, hBitmap);

    // BitBlt返回值未校验
    BitBlt(hMemDC, 0, 0, width, height, hDesktopDC, 0, 0, SRCCOPY);

    BITMAPINFO bmi;
    memset(&bmi, 0, sizeof(bmi));
    bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmi.bmiHeader.biWidth = width;
    bmi.bmiHeader.biHeight = height;
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biBitCount = 24;
    bmi.bmiHeader.biCompression = BI_RGB;

    // 像素缓冲区分配可能存在问题
    DWORD bufferSize = ((width * 24 + 31) / 32) * 4 * height;
    PBYTE pBuffer = ExAllocatePoolWithTag(NonPagedPool, bufferSize, 'BMPS');
    if (!pBuffer) {
        // 资源清理
        SelectObject(hMemDC, hOldBitmap);
        DeleteObject(hBitmap);
        DeleteDC(hMemDC);
        ReleaseDC(NULL, hDesktopDC);
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    // GetDIBits调用参数或上下文错误
    int result = GetDIBits(hDesktopDC, hBitmap, 0, height, pBuffer, &bmi, DIB_RGB_COLORS);
    if (result == 0) {
        ExFreePool(pBuffer);
        // 资源清理
        SelectObject(hMemDC, hOldBitmap);
        DeleteObject(hBitmap);
        DeleteDC(hMemDC);
        ReleaseDC(NULL, hDesktopDC);
        return STATUS_UNSUCCESSFUL;
    }

    // 写入完整文件头和像素数据
    BITMAPFILEHEADER bmfHeader;
    memset(&bmfHeader, 0, sizeof(bmfHeader));
    bmfHeader.bfType = 0x4D42;
    bmfHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + bufferSize;
    bmfHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

    HANDLE hFile = CreateFile(L"\\??\\C:\\test.bmp", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile != INVALID_HANDLE_VALUE) {
        DWORD bytesWritten;
        WriteFile(hFile, &bmfHeader, sizeof(bmfHeader), &bytesWritten, NULL);
        WriteFile(hFile, &bmi.bmiHeader, sizeof(bmi.bmiHeader), &bytesWritten, NULL);
        WriteFile(hFile, pBuffer, bufferSize, &bytesWritten, NULL);
        CloseHandle(hFile);
    }

    ExFreePool(pBuffer);
    SelectObject(hMemDC, hOldBitmap);
    DeleteObject(hBitmap);
    DeleteDC(hMemDC);
    ReleaseDC(NULL, hDesktopDC);
    return STATUS_SUCCESS;
}

核心排查方向

  1. 内核态调用GDI函数的合法性

    • BitBlt、CreateCompatibleDC等属于用户态GDI API,内核态直接调用存在上下文兼容性问题。内核态无用户态的GDI上下文,强行调用可能导致函数执行失败(如BitBlt返回FALSE但未被校验),无法正确捕获像素数据。
    • 内核态获取桌面DC需通过未公开的内核接口(如NtUserGetDesktopWindow),直接调用GetDC(NULL)在驱动环境下可能返回无效句柄。
  2. BMP文件结构与数据写入问题

    • 初始版本1KB文件:仅写入了BMP文件头和信息头,未写入像素数据,需检查是否遗漏GetDIBits获取像素缓冲区、或WriteFile未写入缓冲区数据的逻辑。
    • 修改版本全黑文件:
      • 校验BitBlt返回值,确认位图复制操作是否成功;若源DC无效,BitBlt无法捕获屏幕数据,缓冲区保持初始0值(即黑色)。
      • 检查GetDIBits的参数:确保传入的DC、位图句柄有效,像素缓冲区的大小计算正确(需考虑字节对齐,即每行像素数据需对齐到4字节)。
  3. 内存与权限问题

    • 内核态操作用户态内存需用ProbeForRead/ProbeForWrite验证合法性,若像素缓冲区分配在用户态,直接写入可能导致数据丢失或系统崩溃。
    • 内核态创建文件应使用ZwCreateFile等内核API,而非用户态CreateFile,混用可能导致文件写入权限不足或数据写入不完整。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 01:40:13