Introduction
In Windows 7, we deal with display port/miniport
+ video port/miniport
pair:
Miniport provides callback routines to corresponding port driver, after that port calls miniport for assistance and miniport calls routines exported by port (so they call each other). Display driver communicates with video driver by making EngDeviceIoControl
call (this routine is exported by win32k.sys).
Display miniport and video miniport drivers are supplied by display adapter vendor. However, the system can use default display miniport and video miniport pair if we haven't installed video card drivers or have disabled video card device (in device manager). On my system default pair is framebuf.dll (display miniport) and vga.sys (video miniport); and vendor supplied pair is vboxdisp.dll (display miniport) and vboxvideo.sys (video miniport). Do not get confused by .dll extension, it is kernel mode driver, however it is loaded in session space with win32k.sys and is treated more like .dll file.
Using the Code
Ioctl
codes for EngDeviceIoControl
are defined in ntddvdeo.h:
Some ioctls
are mandatory to support, other ones are optional. Vendor supplied drivers define additional private ioctls
, so we need to switch to default driver pair and observe ioctl
sequence with kernel mode debugger.
To do this, we need to set breakpoint on EngDeviceIoControl
and determine the target device object and ioctl
code by looking at stack.
!devstack <address>
command will give us the name of device object and the name of corresponding driver object:
Device name: \Device\Video3
Driver name: \Driver\VgaSave
The observed ioctl
sequence:
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
Now, let's see the code. First, disable all display adapters so the system switches to default driver pair (we can do it manually in device manager):
#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 } };
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);
...
Next, call our driver:
#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();
...
The driver:
Header.h:
#ifndef _INBV_
#define _INBV_
extern "C"
{
typedef enum _INBV_DISPLAY_STATE
{
INBV_DISPLAY_STATE_OWNED, INBV_DISPLAY_STATE_DISABLED, INBV_DISPLAY_STATE_LOST } 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(); InbvSetTextColor(VGA_COLOR_BRIGHT_RED);
InbvInstallDisplayStringFilter(NULL);
InbvEnableDisplayString(TRUE);
InbvDisplayString((UCHAR*)"HELLO!\n");
InbvNotifyDisplayOwnershipLost(NULL);
Interval.QuadPart = DELAY_SECOND * 10;
KeDelayExecutionThread(KernelMode, FALSE, &Interval);
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;
}
}
This driver will black out the screen and display string
in the top left corner. When we return to previous mode, the screen turns black again, however, we will be able to see Press Enter to close service string. When we hit enter, the following code should be executed:
#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(); pShellDisp->Release();
...
Finally, we need to enable all gpu devices again:
#include <Windows.h>
#include <setupapi.h>
#pragma comment(lib,"setupapi.lib")
...
SetGpuState(TRUE);
...
If you would be interested, I will investigate Windows 8 and Windows 10 cases on how to do the same thing.