65.9K
CodeProject 正在变化。 阅读更多。
Home

Vga 文本模式:地狱之旅

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.64/5 (9投票s)

2018年5月10日

CPOL

2分钟阅读

viewsIcon

21024

本文档展示了如何在 Windows 7 上进入 vga 文本模式并从中返回。

引言

在 Windows 7 中,我们处理的是 显示端口/迷你端口 + 视频端口/迷你端口 对。

迷你端口向相应的端口驱动程序提供回调例程,然后端口调用迷你端口以获得帮助,而迷你端口又调用端口导出的例程(因此它们相互调用)。显示驱动程序通过发出 EngDeviceIoControl 调用与视频驱动程序通信(此例程由 win32k.sys 导出)。

显示迷你端口和视频迷你端口驱动程序由显示适配器供应商提供。但是,如果我们未安装显卡驱动程序或禁用了显卡设备(在设备管理器中),系统可以使用默认的显示迷你端口和视频迷你端口对。 在我的系统上,默认对是 framebuf.dll(显示迷你端口)和 vga.sys(视频迷你端口);供应商提供的对是 vboxdisp.dll(显示迷你端口)和 vboxvideo.sys(视频迷你端口)。 不要被 .dll 扩展名混淆,它是一个内核模式驱动程序,但是它以 win32k.sys 的方式加载到会话空间中,并且更像一个 .dll 文件。

Using the Code

Ioctl 代码定义在 ntddvdeo.h 中,用于 EngDeviceIoControl

有些 ioctls 必须支持,其他的是可选的。供应商提供的驱动程序定义了额外的 私有 ioctls,因此我们需要切换到默认驱动程序对,并使用内核模式调试器观察 ioctl 序列。

为此,我们需要在 EngDeviceIoControl 上设置断点,并通过查看堆栈来确定目标设备对象和 ioctl 代码。

!devstack <address> 命令将向我们提供设备对象的名称和相应驱动对象的名称。

Device name: \Device\Video3
Driver name: \Driver\VgaSave

观察到的 ioctl 序列

IOCTL_VIDEO_QUERY_NUM_AVAIL_MODES
IOCTL_VIDEO_QUERY_AVAIL_MODES
IOCTL_VIDEO_QUERY_COLOR_CAPABILITIES
IOCTL_VIDEO_QUERY_POINTER_CAPABILITIES
IOCTL_VIDEO_SET_CURRENT_MODE   // vga enters graphics mode, screen turns black
IOCTL_VIDEO_MAP_VIDEO_MEMORY
... write to frame buffer ...
IOCTL_VIDEO_RESET_DEVICE      // vga enters text mode, screen turns black 
                              // and cursor blinks in top left corner
IOCTL_VIDEO_UNMAP_VIDEO_MEMORY

现在,让我们看看代码。 首先,禁用所有显示适配器,以便系统切换到默认驱动程序对(我们可以在设备管理器中手动执行此操作)。

#include <Windows.h>
#include <setupapi.h>

#pragma comment(lib,"setupapi.lib")

void SetGpuState(BOOL Enable)
{
    HDEVINFO hDevInfo;
    SP_DEVINFO_DATA spDevInfoData;
    SP_PROPCHANGE_PARAMS spPropChangeParams;

    GUID ClassGuid = { 0x4d36e968, 0xe325, 0x11ce, 
    { 0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18 } };        // display adapter setup class

    hDevInfo = SetupDiGetClassDevsW(&ClassGuid, NULL, NULL, DIGCF_PRESENT);

    spDevInfoData.cbSize = sizeof(SP_DEVINFO_DATA);

    for (ULONG i = 0; SetupDiEnumDeviceInfo(hDevInfo, i, &spDevInfoData); i++)
    {
        spPropChangeParams.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER);
        spPropChangeParams.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE;
        spPropChangeParams.Scope = DICS_FLAG_GLOBAL;
        spPropChangeParams.StateChange = Enable ? DICS_ENABLE : DICS_DISABLE;

        SetupDiSetClassInstallParamsW(hDevInfo, &spDevInfoData, 
            (SP_CLASSINSTALL_HEADER*)&spPropChangeParams, sizeof(spPropChangeParams));

        SetupDiCallClassInstaller(DIF_PROPERTYCHANGE, hDevInfo, &spDevInfoData);
    }

    SetupDiDestroyDeviceInfoList(hDevInfo);
}

...

SetGpuState(FALSE);

...

接下来,调用我们的驱动程序

#include <Windows.h>
#include <stdio.h>

...

    SC_HANDLE hSCManager;
    SC_HANDLE hService;
    SERVICE_STATUS ServiceStatus;

    hSCManager = OpenSCManagerW(NULL, NULL, SC_MANAGER_CREATE_SERVICE);

    if (!hSCManager) ...

    hService = CreateServiceW(hSCManager, L"Text Mode Service Name",
        L"Text Mode Display Name",
        SERVICE_START | DELETE | SERVICE_STOP,
        SERVICE_KERNEL_DRIVER,
        SERVICE_DEMAND_START,
        SERVICE_ERROR_IGNORE,
        L"C:\\TextMode.sys",
        NULL, NULL, NULL, NULL, NULL);

    if (!hService)
    {
        hService = OpenServiceW(hSCManager, L"Text Mode Service Name",
            SERVICE_START | DELETE | SERVICE_STOP);

        if (!hService) ...
    }

    if (!StartServiceW(hService, 0, NULL)) ...

    printf("Press Enter to close service\n");
    getchar();

...

驱动程序

Header.h:

#ifndef _INBV_
#define _INBV_

extern "C"
{
    typedef enum _INBV_DISPLAY_STATE
    {
        INBV_DISPLAY_STATE_OWNED,     // we own the display
        INBV_DISPLAY_STATE_DISABLED,  // we own but should not use
        INBV_DISPLAY_STATE_LOST       // we lost ownership
    } INBV_DISPLAY_STATE;

    typedef
        BOOLEAN
        (*INBV_RESET_DISPLAY_PARAMETERS)(
        ULONG Cols,
        ULONG Rows
        );

    typedef
        VOID
        (*INBV_DISPLAY_STRING_FILTER)(
        PUCHAR *Str
        );

    NTKERNELAPI
        VOID
        InbvNotifyDisplayOwnershipLost(
        INBV_RESET_DISPLAY_PARAMETERS ResetDisplayParameters
        );

    NTKERNELAPI
        VOID
        InbvInstallDisplayStringFilter(
        INBV_DISPLAY_STRING_FILTER DisplayStringFilter
        );

    NTKERNELAPI
        VOID
        InbvAcquireDisplayOwnership(
        VOID
        );

    BOOLEAN
        InbvDriverInitialize(
        IN PVOID LoaderBlock,
        IN ULONG Count
        );

    NTKERNELAPI
        BOOLEAN
        InbvResetDisplay(
        );

    VOID
        InbvBitBlt(
        PUCHAR Buffer,
        ULONG x,
        ULONG y
        );

    NTKERNELAPI
        VOID
        InbvSolidColorFill(
        ULONG x1,
        ULONG y1,
        ULONG x2,
        ULONG y2,
        ULONG color
        );

    NTKERNELAPI
        BOOLEAN
        InbvDisplayString(
        PUCHAR Str
        );

    VOID
        InbvUpdateProgressBar(
        ULONG Percentage
        );

    VOID
        InbvSetProgressBarSubset(
        ULONG   Floor,
        ULONG   Ceiling
        );

    VOID
        InbvSetBootDriverBehavior(
        PVOID LoaderBlock
        );

    VOID
        InbvIndicateProgress(
        VOID
        );

    VOID
        InbvSetProgressBarCoordinates(
        ULONG x,
        ULONG y
        );

    NTKERNELAPI
        VOID
        InbvEnableBootDriver(
        BOOLEAN bEnable
        );

    NTKERNELAPI
        BOOLEAN
        InbvEnableDisplayString(
        BOOLEAN bEnable
        );

    NTKERNELAPI
        BOOLEAN
        InbvIsBootDriverInstalled(
        VOID
        );

    PUCHAR
        InbvGetResourceAddress(
        IN ULONG ResourceNumber
        );

    VOID
        InbvBufferToScreenBlt(
        PUCHAR Buffer,
        ULONG x,
        ULONG y,
        ULONG width,
        ULONG height,
        ULONG lDelta
        );

    VOID
        InbvScreenToBufferBlt(
        PUCHAR Buffer,
        ULONG x,
        ULONG y,
        ULONG width,
        ULONG height,
        ULONG lDelta
        );

    BOOLEAN
        InbvTestLock(
        VOID
        );

    VOID
        InbvAcquireLock(
        VOID
        );

    VOID
        InbvReleaseLock(
        VOID
        );

    NTKERNELAPI
        BOOLEAN
        InbvCheckDisplayOwnership(
        VOID
        );

    NTKERNELAPI
        VOID
        InbvSetScrollRegion(
        ULONG x1,
        ULONG y1,
        ULONG x2,
        ULONG y2
        );

    NTKERNELAPI
        ULONG
        InbvSetTextColor(
        ULONG Color
        );
}

#endif

Source.cpp:

#include <wdm.h>
#include <ntddvdeo.h>
#include "Header.h"

#define DELAY_SECOND                        -10000000

#define VGA_COLOR_BLACK                      0
#define VGA_COLOR_RED                        1
#define VGA_COLOR_GREEN                      2
#define VGA_COLOR_GR                         3
#define VGA_COLOR_BULE                       4
#define VGA_COLOR_DARK_MEGAENTA              5
#define VGA_COLOR_TURQUOISE                  6
#define VGA_COLOR_GRAY                       7
#define VGA_COLOR_BRIGHT_GRAY                8
#define VGA_COLOR_BRIGHT_RED                 9
#define VGA_COLOR_BRIGHT_GREEN               10
#define VGA_COLOR_BRIGHT_YELLOW              11
#define VGA_COLOR_BRIGHT_BULE                12
#define VGA_COLOR_BRIGHT_PURPLE              13
#define VGA_COLOR_BRIGHT_TURQUOISE           14
#define VGA_COLOR_WHITE                      15

extern "C"
{
    extern POBJECT_TYPE *IoDriverObjectType;

    NTKERNELAPI NTSTATUS ObOpenObjectByName(POBJECT_ATTRIBUTES pObjectAttributes,
        POBJECT_TYPE pObjectType,
        KPROCESSOR_MODE AccessMode,
        PACCESS_STATE pAccessState,
        ACCESS_MASK DesiredAccess,
        void *pOpenPacket,
        PHANDLE pHandle);

    NTKERNELAPI NTSTATUS IoEnumerateDeviceObjectList(PDRIVER_OBJECT pDriverObject,
        PDEVICE_OBJECT *pDeviceObjectList,
        ULONG          DeviceObjectListSize,
        PULONG         pActualNumberDeviceObjects);
}

NTSTATUS OpenDriver(WCHAR *pDriverName, DRIVER_OBJECT **ppDriverObject)
{
    HANDLE Handle;
    NTSTATUS Status;
    UNICODE_STRING ObjectName;
    OBJECT_ATTRIBUTES ObjectAttributes;
    DRIVER_OBJECT *pDriverObject;

    RtlInitUnicodeString(&ObjectName, pDriverName);

    InitializeObjectAttributes(&ObjectAttributes, &ObjectName, OBJ_CASE_INSENSITIVE, NULL, NULL);

    Status = ObOpenObjectByName(&ObjectAttributes, *IoDriverObjectType, 
                                KernelMode, NULL, FILE_READ_ATTRIBUTES, NULL, &Handle);

    if (NT_SUCCESS(Status))
    {
        Status = ObReferenceObjectByHandle(Handle, 0, NULL, KernelMode, (PVOID*)&pDriverObject, NULL);

        if (NT_SUCCESS(Status)) *ppDriverObject = pDriverObject;

        ZwClose(Handle);
    }

    return Status;
}

NTSTATUS VgaGetDevice(DEVICE_OBJECT **ppDeviceObject)
{
    ULONG Count;
    NTSTATUS Status;
    DRIVER_OBJECT *pDriverObject;
    DEVICE_OBJECT *pDeviceObject;

    Status = OpenDriver(L"\\Driver\\VgaSave", &pDriverObject);

    if (NT_SUCCESS(Status))
    {
        Status = IoEnumerateDeviceObjectList(pDriverObject, &pDeviceObject, 
                                             sizeof(DEVICE_OBJECT*), &Count);

        if (NT_SUCCESS(Status)) *ppDeviceObject = pDeviceObject;

        ObDereferenceObject(pDriverObject);
    }

    return Status;
}

NTSTATUS VgaDeviceIoControl(DEVICE_OBJECT *pDeviceObject,
    ULONG IoControlCode,
    void *pInBuffer,
    ULONG InBufferSize,
    void *pOutBuffer,
    ULONG OutBufferSize,
    ULONG *pBytesReturned,
    BOOLEAN Internal)
{
    IRP *pIrp;
    KEVENT Event;
    NTSTATUS Status;
    IO_STATUS_BLOCK IoStatusBlock;

    KeInitializeEvent(&Event, SynchronizationEvent, FALSE);

    pIrp = IoBuildDeviceIoControlRequest(IoControlCode, pDeviceObject, 
           pInBuffer, InBufferSize, pOutBuffer, OutBufferSize, Internal, &Event, &IoStatusBlock);

    if (pIrp)
    {
        Status = IoCallDriver(pDeviceObject, pIrp);

        if (Status == STATUS_PENDING)
        {
            do
            {
                Status = KeWaitForSingleObject(&Event, UserRequest, KernelMode, TRUE, NULL);
            } while (Status == STATUS_ALERTED);

            Status = IoStatusBlock.Status;
        }

        *pBytesReturned = IoStatusBlock.Information;
    }
    else
    {
        Status = STATUS_INSUFFICIENT_RESOURCES;

        *pBytesReturned = 0;
    }

    return Status;
}

void DriverUnload(PDRIVER_OBJECT pDriverObject)
{
}

extern "C"
{
    NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath)
    {
        NTSTATUS Status;
        ULONG BytesReturned;
        VIDEO_MODE VideoMode;
        LARGE_INTEGER Interval;
        DEVICE_OBJECT *pDeviceObject;
        VIDEO_MODE_INFORMATION VideoModeInfo;

        Status = VgaGetDevice(&pDeviceObject);

        if (NT_SUCCESS(Status))
        {
            Status = VgaDeviceIoControl(pDeviceObject, IOCTL_VIDEO_QUERY_CURRENT_MODE, 
                     NULL, 0, &VideoModeInfo, sizeof(VideoModeInfo), &BytesReturned, FALSE);

            if (NT_SUCCESS(Status))
            {
                InbvAcquireDisplayOwnership();
                InbvResetDisplay();     // enter text mode
                InbvSetTextColor(VGA_COLOR_BRIGHT_RED);
                InbvInstallDisplayStringFilter(NULL);
                InbvEnableDisplayString(TRUE);
                InbvDisplayString((UCHAR*)"HELLO!\n");
                InbvNotifyDisplayOwnershipLost(NULL);

                Interval.QuadPart = DELAY_SECOND * 10;
                KeDelayExecutionThread(KernelMode, FALSE, &Interval);

                // leave text mode
                VideoMode.RequestedMode = VideoModeInfo.ModeIndex;
                VgaDeviceIoControl(pDeviceObject, IOCTL_VIDEO_SET_CURRENT_MODE, 
                                   &VideoMode, sizeof(VideoMode), NULL, 0, &BytesReturned, FALSE);
            }

            ObDereferenceObject(pDeviceObject);
        }

        pDriverObject->DriverUnload = DriverUnload;

        return STATUS_SUCCESS;
    }
}

此驱动程序将使屏幕变黑,并在左上角显示 string。 当我们返回到之前的模式时,屏幕再次变黑,但是,我们将能够看到 按 Enter 关闭服务 字符串。 当我们按下 Enter 键时,应执行以下代码

#include <Windows.h>
#include <Shldisp.h>

...

    ControlService(hService, SERVICE_CONTROL_STOP, &ServiceStatus);

    DeleteService(hService);
    CloseServiceHandle(hService);

    CloseServiceHandle(hSCManager);

    CoInitialize(NULL);
    IShellDispatch4 *pShellDisp = NULL;
    HRESULT sc = CoCreateInstance(CLSID_Shell, NULL, CLSCTX_SERVER, 
                                  IID_IDispatch, (LPVOID *)&pShellDisp);
    sc = pShellDisp->ToggleDesktop();    // refresh desktop (we can do it manually 
          // by pressing Show desktop button in right bottom corner (on task bar near the tray))
    pShellDisp->Release();

...

最后,我们需要再次启用所有 GPU 设备

#include <Windows.h>
#include <setupapi.h>

#pragma comment(lib,"setupapi.lib")

...

SetGpuState(TRUE);

...

如果您感兴趣,我将研究 Windows 8 和 Windows 10 的情况,了解如何执行相同的操作。

© . All rights reserved.