what
what

Reputation: 41

Using BitBlt to capture a screenshot

I've been trying to create a .bmp file using functions like BitBlt, CreateCompatibleDC... in the kernel. The problem is whenever I run this code it creates a .bmp file successfully, the only issue is that the file is 1 kB of size. Even thought it's supposed to be way more

Here is my code:

#include "Drawing.h"

NTSTATUS NTAPI
__NE_CreateFile(
    _In_ LPCWSTR                FilePath,
    _Inout_ PHANDLE             FileHandle
);

NTSTATUS NTAPI
__NE_WriteFile(
    _In_ HANDLE                 FileHandle,
    _In_ PVOID                  Buffer,
    _In_ ULONG                  BufferSize
);

NTSTATUS NTAPI
__NE_CloseFile(
    _In_ HANDLE                 FileHandle
);

BOOLEAN NTAPI
__NE_CaptureScreenshot(
    VOID
);

NTSTATUS NTAPI
DriverEntry(
    _In_ PDRIVER_OBJECT         DriverObject,
    _In_ PUNICODE_STRING        RegistryPath
) {
    UNREFERENCED_PARAMETER(DriverObject);
    UNREFERENCED_PARAMETER(RegistryPath);

    // Ignore this, it's supposed to retrieve GDI routines
    BOOLEAN Stats = InitializeSequence();
    if (Stats == FALSE) {
        KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Failed to initialize sequence.\xA"));
    }

    __NE_CaptureScreenshot();

    return STATUS_SUCCESS;
}

NTSTATUS NTAPI
__NE_CreateFile(
    _In_ LPCWSTR                FilePath,
    _Inout_ PHANDLE             FileHandle
) {
    NTSTATUS Status;
    OBJECT_ATTRIBUTES ObjectAttributes;
    KEVENT Event;
    IO_STATUS_BLOCK IoStatusBlock;
    UNICODE_STRING FileName;

    RtlInitUnicodeString(&FileName, FilePath);

    KeInitializeEvent(&Event, NotificationEvent, FALSE);

    InitializeObjectAttributes(
        &ObjectAttributes,
        &FileName,
        OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
        NULL,
        NULL
    );

    Status = ZwCreateFile(
        FileHandle,
        GENERIC_WRITE,
        &ObjectAttributes,
        &IoStatusBlock,
        NULL,
        FILE_ATTRIBUTE_NORMAL,
        0,
        FILE_CREATE,
        FILE_SYNCHRONOUS_IO_NONALERT,
        NULL,
        0
    );

    if (NT_SUCCESS(Status) == FALSE) {
        KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[IN] Failed to create file. Status: 0x%08X, IoStatus: 0x%08X\xA", Status, IoStatusBlock.Status));
        return Status;
    }

    return STATUS_SUCCESS;
}


NTSTATUS NTAPI
__NE_WriteFile(
    _In_ HANDLE                 FileHandle,
    _In_ PVOID                  Buffer,
    _In_ ULONG                  BufferSize
) {
    NTSTATUS                    Status;
    IO_STATUS_BLOCK             IoStatusBlock;

    Status = ZwWriteFile(
        FileHandle,
        NULL,
        NULL,
        NULL,
        &IoStatusBlock,
        Buffer,
        BufferSize,
        NULL,
        NULL
    );

    if (NT_SUCCESS(Status) == FALSE) {
        KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Failed to write to file.\xA"));
        return Status;
    }

    return STATUS_SUCCESS;
}

NTSTATUS NTAPI
__NE_CloseFile(
    _In_ HANDLE                 FileHandle
) {
    NTSTATUS                    Status;

    Status = ZwClose(FileHandle);

    if (NT_SUCCESS(Status) == FALSE) {
        KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Failed to close file.\xA"));
        return Status;
    }

    return STATUS_SUCCESS;
}

BOOLEAN NTAPI
__NE_CaptureScreenshot(
    VOID
) {
    BITMAP Bitmap = { 0 };
    HANDLE FileHandle = { 0 };

    HDC ScreenDC = NtUserGetDC(NULL);

    if (ScreenDC == NULL) {
        KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Failed to get screen DC.\xA"));
        return FALSE;
    }

    HDC CompatibleDC = GreCreateCompatibleDC(ScreenDC);

    if (CompatibleDC == NULL) {
        KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Failed to create compatible DC.\xA"));
        return FALSE;
    }

    INT ScreenWidth = GreGetDeviceCaps(ScreenDC, HORZRES);
    INT ScreenHeight = GreGetDeviceCaps(ScreenDC, VERTRES);

    HBITMAP CompatibleBitmap = NtGdiCreateCompatibleBitmap(
        ScreenDC,
        ScreenWidth,
        ScreenHeight
    );

    if (CompatibleBitmap == NULL) {
        KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Failed to create compatible bitmap.\xA"));
        return FALSE;
    }

    NtGdiSelectBitmap(CompatibleDC, CompatibleBitmap);

    BOOLEAN BitBlitStatus = NtGdiBitBlt(
        CompatibleDC,
        0,
        0,
        ScreenWidth,
        ScreenHeight,
        ScreenDC,
        0,
        0,
        SRCCOPY
    );

    if (BitBlitStatus == FALSE) {
        KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Failed to bitblt.\xA"));
        return FALSE;
    }

    NtGdiExtGetObjectW(CompatibleBitmap, sizeof(BITMAP), &Bitmap);

    BITMAPFILEHEADER BitmapFileHeader = { 0 };
    BITMAPINFOHEADER BitmapInfoHeader = { 0 };

    BitmapInfoHeader.biSize = sizeof(BITMAPINFOHEADER);
    BitmapInfoHeader.biWidth = Bitmap.bmWidth;
    BitmapInfoHeader.biHeight = Bitmap.bmHeight;
    BitmapInfoHeader.biPlanes = 1;
    BitmapInfoHeader.biBitCount = 32;
    BitmapInfoHeader.biCompression = BI_RGB;
    BitmapInfoHeader.biSizeImage = Bitmap.bmWidthBytes * Bitmap.bmHeight;
    BitmapInfoHeader.biXPelsPerMeter = 0;
    BitmapInfoHeader.biYPelsPerMeter = 0;
    BitmapInfoHeader.biClrUsed = 0;
    BitmapInfoHeader.biClrImportant = 0;

    DWORD BitmapSize = BitmapInfoHeader.biSizeImage;

    HANDLE DIBHandle = ExAllocatePool2(
        POOL_FLAG_NON_PAGED,
        BitmapSize,
        'dib0'
    );

    if (DIBHandle == NULL) {
        KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Failed to allocate DIB handle.\xA"));
        return FALSE;
    }

    NtGdiGetDIBitsInternal(
        ScreenDC,
        CompatibleBitmap,
        0,
        (UINT)Bitmap.bmHeight,
        DIBHandle,
        (PBITMAPINFO)&BitmapInfoHeader,
        DIB_RGB_COLORS,
        0
    );

    __NE_CreateFile(
        L"\\??\\C:\\Screenshot.bmp",
        &FileHandle
    );

    DWORD DIBSize = BitmapSize + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

    BitmapFileHeader.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER);
    BitmapFileHeader.bfSize = DIBSize;
    BitmapFileHeader.bfType = 0x4D42;

    PVOID buffer = ExAllocatePool2(
        POOL_FLAG_NON_PAGED,
        DIBSize,
        'buf0'
    );

    if (buffer == NULL) {
        KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Failed to allocate buffer.\xA"));
        ExFreePool(DIBHandle);
        return FALSE;
    }

    RtlCopyMemory(buffer, &BitmapFileHeader, sizeof(BITMAPFILEHEADER));
    RtlCopyMemory((PBYTE)buffer + sizeof(BITMAPFILEHEADER), &BitmapInfoHeader, sizeof(BITMAPINFOHEADER));
    RtlCopyMemory((PBYTE)buffer + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER), DIBHandle, BitmapSize);

    __NE_WriteFile(
        FileHandle,
        (LPSTR)buffer,
        DIBSize
    );

    ExFreePool(buffer);
    ExFreePool(DIBHandle);

    __NE_CloseFile(FileHandle);

    return TRUE;
}

I've tried to change the Size calculations etc.. but none of it worked. I followed this PoC https://learn.microsoft.com/en-us/windows/win32/gdi/capturing-an-image

EDIT:

BOOLEAN NTAPI
__NE_CaptureScreenshot(
    VOID
) {
    HDC hScreenDC = NtUserGetDC(NULL);
    if (!hScreenDC) return FALSE;

    int width = GreGetDeviceCaps(hScreenDC, HORZRES);
    int height = GreGetDeviceCaps(hScreenDC, VERTRES);

    HDC hMemDC = GreCreateCompatibleDC(hScreenDC);
    HBITMAP hBitmap = NtGdiCreateCompatibleBitmap(hScreenDC, width, height);
    NtGdiSelectBitmap(hMemDC, hBitmap);

    NtGdiBitBlt(hMemDC, 0, 0, width, height, hScreenDC, 0, 0, SRCCOPY);

    BITMAPINFO bmi = { 0 };
    bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmi.bmiHeader.biWidth = width;
    bmi.bmiHeader.biHeight = -height;
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biBitCount = 32;
    bmi.bmiHeader.biCompression = BI_RGB;

    PVOID pBits = ExAllocatePool2(POOL_FLAG_NON_PAGED, width * height * 4, 'BMP0');
    if (!pBits) return FALSE;

    NtGdiGetDIBitsInternal(hMemDC, hBitmap, 0, height, pBits, &bmi, DIB_RGB_COLORS, 0);

    HANDLE hFile;
    UNICODE_STRING fileName;
    OBJECT_ATTRIBUTES objAttr;
    IO_STATUS_BLOCK ioStatusBlock;
    RtlInitUnicodeString(&fileName, L"\\??\\C:\\screen.bmp");
    InitializeObjectAttributes(&objAttr, &fileName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);

    NTSTATUS status = ZwCreateFile(&hFile, GENERIC_WRITE, &objAttr, &ioStatusBlock, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_OVERWRITE_IF, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
    if (NT_SUCCESS(status) == FALSE)
        return FALSE;

    BITMAPFILEHEADER bmfHeader = { 0 };
    bmfHeader.bfType = 0x4D42;
    bmfHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
    bmfHeader.bfSize = bmfHeader.bfOffBits + (width * height * 4);

    ZwWriteFile(hFile, NULL, NULL, NULL, &ioStatusBlock, &bmfHeader, sizeof(BITMAPFILEHEADER), NULL, NULL);
    ZwWriteFile(hFile, NULL, NULL, NULL, &ioStatusBlock, &bmi.bmiHeader, sizeof(BITMAPINFOHEADER), NULL, NULL);
    ZwWriteFile(hFile, NULL, NULL, NULL, &ioStatusBlock, pBits, width * height * 4, NULL, NULL);

    ZwClose(hFile);

    return TRUE;
}

This code creates a correct .bmp but it's blank black

Upvotes: 1

Views: 100

Answers (0)

Related Questions