内核态使用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; }
核心排查方向
内核态调用GDI函数的合法性
BitBlt、CreateCompatibleDC等属于用户态GDI API,内核态直接调用存在上下文兼容性问题。内核态无用户态的GDI上下文,强行调用可能导致函数执行失败(如BitBlt返回FALSE但未被校验),无法正确捕获像素数据。- 内核态获取桌面DC需通过未公开的内核接口(如
NtUserGetDesktopWindow),直接调用GetDC(NULL)在驱动环境下可能返回无效句柄。
BMP文件结构与数据写入问题
- 初始版本1KB文件:仅写入了BMP文件头和信息头,未写入像素数据,需检查是否遗漏
GetDIBits获取像素缓冲区、或WriteFile未写入缓冲区数据的逻辑。 - 修改版本全黑文件:
- 校验
BitBlt返回值,确认位图复制操作是否成功;若源DC无效,BitBlt无法捕获屏幕数据,缓冲区保持初始0值(即黑色)。 - 检查
GetDIBits的参数:确保传入的DC、位图句柄有效,像素缓冲区的大小计算正确(需考虑字节对齐,即每行像素数据需对齐到4字节)。
- 校验
- 初始版本1KB文件:仅写入了BMP文件头和信息头,未写入像素数据,需检查是否遗漏
内存与权限问题
- 内核态操作用户态内存需用
ProbeForRead/ProbeForWrite验证合法性,若像素缓冲区分配在用户态,直接写入可能导致数据丢失或系统崩溃。 - 内核态创建文件应使用
ZwCreateFile等内核API,而非用户态CreateFile,混用可能导致文件写入权限不足或数据写入不完整。
- 内核态操作用户态内存需用
内容的提问来源于stack exchange,提问作者trapstar
相关产品推荐
相关产品推荐

