Communication between the user-mode CryptDisk.exe application and
the kernel-mode CryptDriver.sys driver is accomplished using file IO.
The driver will create a device object (DEVICE_OBJECT) that will
perform actions in response to IO Request Packets (IRP) from the
system, including Read, Write, and DeviceIoControl. The driver then
creates a new file object that is visible to both user-mode and
kernel-mode code. CryptDisk can open this file just like any other
file and perform file operations on it, which will ultimately be
handled by CryptDriver.
The most important file operation is DeviceIoControl, which is
really a catch-all for any custom operation. In fact, it would be
entirely possible to create a complete device driver using nothing by
DeviceIoControl codes, which would probably include read and write
commands. DeviceIoControl simply passes the contents of a block of
memory from the app to the driver and back. It is up to the app and
driver to decide how to interpret the contents, which are usually
commmands or update requests.
The OS creates a DRIVER_OBJECT when the driver is loaded and passes
that to DriverEntry(). I can then create any number of virtual
devices, defined by the DEVICE_OBJECT, and attach them to the
DRIVER_OBJECT. If I were writing a device driver for a physical
device, I would wait to create the device objects until I was notified
of a system power event or plug-and-play (PnP) event. CryptDriver is a
software driver, so I create the devices immediately after the driver
is loaded.
The CryptDriver will create two types of devices: a single CONTROL
device that is used to manage the driver and all the disks, and one or
more DISK devices that expose the individual virtual disks. I need to
write the CONTROL device first, which will also be the simplest.
First I need to define some names that shared between the app and
driver. Create a new header file named "CryptDisk.h" in the solution
Include directory.
CRYPTDISK_SERVICE_NAME is the name of the service that is
used to load and unload the driver.
CRYPTDISK_DRIVER_NAME is the name of the driver binary
file.
CRYPTDISK_CONTROL_NAME is the name of the device file that
will be used to control the driver. The app will open this file using
CreateFile(), send commands using DeviceIoControl(), and read status
using ReadFile().
CRYPTDISK_CONTROL_ID is a 16bit numeric identifier for the
device. Values 0-0x8000 are reserved, 0x8000-0xFFFF are open use. I'm
not sure what will happen if I happen to use the same value as another
driver in the system.
Create a new code file named "Device.cpp". I know I will eventually
need (at least) two different types of devices so I am creating a base
device (DeviceExt) and a derived control device (DeviceCtrl).
Device.cpp:
/*************************************************************************/
/** Device.cpp: Creates the device objects. **/
/** (C)2017 nlited systems inc, Chip Doran **/
/*************************************************************************/
#include <new>
#include "Globals.h"
#include "CryptDisk.h"
#include "Handles.h"
#include "VerID.h"
#pragma message(__FILE__": Optimizer disabled.")
#pragma optimize("",off)
class DeviceExt {
public:
static DeviceExt *Ptr(DEVICE_OBJECT *pDevice);
virtual NTSTATUS Destroy(void);
static NTSTATUS IrpDispatch(DEVICE_OBJECT *pDevice, IRP *pIrp);
protected:
DeviceExt(DEVICE_OBJECT *pDevice);
virtual ~DeviceExt(void);
virtual NTSTATUS IrpDispatch2(IRP *pIrp);
//Data
DEVICE_OBJECT *pDevice;
private:
DWORD Signature_DeviceExt;
};
class DeviceCtrl: public DeviceExt {
public:
static DeviceCtrl *Ptr(DEVICE_OBJECT *pDevice);
static NTSTATUS Create(DRIVER_OBJECT *pDriver);
NTSTATUS Destroy(void);
private:
DeviceCtrl(DEVICE_OBJECT *pDevice);
~DeviceCtrl(void);
static void MakeNameNT(WCHAR *pBuf, UINT BufSz, UNICODE_STRING &Name);
static void MakeNameDOS(WCHAR *pBuf, UINT BufSz, UNICODE_STRING &Name);
NTSTATUS IrpDispatch2(IRP *pIrp);
NTSTATUS Open(IO_STACK_LOCATION *pIrp);
NTSTATUS Close(IO_STACK_LOCATION *pIrp);
NTSTATUS Read(IRP *pIrp, IO_STACK_LOCATION *pStack);
//Data
DWORD Signature_DeviceCtrl;
};
class DeviceDisk: public DeviceExt {
public:
private:
};
/*************************************************************************/
/** Public interface **/
/*************************************************************************/
NTSTATUS DeviceCtrlCreate(DRIVER_OBJECT *pDriver) {
return(DeviceCtrl::Create(pDriver));
}
NTSTATUS DeviceDestroy(DEVICE_OBJECT *pDevice) {
NTSTATUS Status= STATUS_SUCCESS;
DeviceExt *pExt= pExt->Ptr(pDevice);
if(pExt)
Status= pExt->Destroy();
IoDeleteDevice(pDevice);
return(Status);
}
NTSTATUS DeviceIrp(DEVICE_OBJECT *pDevice, IRP *pIrp) {
return(DeviceExt::IrpDispatch(pDevice,pIrp));
}
/*************************************************************************/
/** My base Device extension. **/
/*************************************************************************/
DeviceExt::DeviceExt(DEVICE_OBJECT *_pDevice) {
Signature_DeviceExt= SIGNATURE_DEVICE_EXT;
pDevice= _pDevice;
}
DeviceExt::~DeviceExt(void) {
Signature_DeviceExt|= SIGNATURE_INVALID;
Debug(DBG_INIT,"DeviceExt:Destructor");
}
DeviceExt *DeviceExt::Ptr(DEVICE_OBJECT *pDevice) {
DeviceExt *pExt= (DeviceExt*)pDevice->DeviceExtension;
if(!pExt || pExt->Signature_DeviceExt!=SIGNATURE_DEVICE_EXT)
pExt= 0;
return(pExt);
}
NTSTATUS DeviceExt::Destroy(void) {
this->~DeviceExt();
return(STATUS_SUCCESS);
}
NTSTATUS DeviceExt::IrpDispatch(DEVICE_OBJECT *pDevice, IRP *pIrp) {
DeviceExt *pExt= Ptr(pDevice);
if(!pExt) {
Warn(ERR_BAD_HANDLE,"DeviceExt:IrpDispatch: No DeviceExt(%p)",pDevice);
return(STATUS_INVALID_DEVICE_REQUEST);
}
pIrp->IoStatus.Status= STATUS_SUCCESS;
pIrp->IoStatus.Information= 0;
NTSTATUS Status= pExt->IrpDispatch2(pIrp);
pIrp->IoStatus.Status= Status;
IoCompleteRequest(pIrp,IO_NO_INCREMENT);
return(Status);
}
NTSTATUS DeviceExt::IrpDispatch2(IRP *pIrp) {
return(STATUS_SUCCESS);
}
/*************************************************************************/
/** My CONTROL device extension. **/
/** The control device is used to send commands the driver and to get **/
/** status information about all the disks. **/
/*************************************************************************/
DeviceCtrl::DeviceCtrl(DEVICE_OBJECT *pDevice)
:DeviceExt(pDevice)
{
Signature_DeviceCtrl= SIGNATURE_DEVICE_CTRL;
}
DeviceCtrl::~DeviceCtrl(void) {
Signature_DeviceCtrl|= SIGNATURE_INVALID;
Debug(DBG_INIT,"DeviceCtrl:Destructor");
}
DeviceCtrl *DeviceCtrl::Ptr(DEVICE_OBJECT *pDevice) {
DeviceCtrl *pCtrl= (DeviceCtrl*)pDevice->DeviceExtension;
if(!pCtrl || pCtrl->Signature_DeviceCtrl!=SIGNATURE_DEVICE_CTRL)
pCtrl= 0;
return(pCtrl);
}
NTSTATUS DeviceCtrl::Create(DRIVER_OBJECT *pDriver) {
NTSTATUS Status= STATUS_SUCCESS;
WCHAR NameNT[80];
UNICODE_STRING DevCtrlNT;
DEVICE_OBJECT *pDevice;
DeviceCtrl *pCtrl;
Debug(DBG_INIT,"DeviceCtrl:Create");
// Create the NT path to the device.
// This is a bit convoluted because IoCreateDevice() will allocate memory for
// my DeviceCtrl object in DeviceExtension, so I can't call my constructor until
// after IoCreateDevice() returns. But I need to create the device name before
// calling IoCreateDevice().
// QUESTION: Do I need to provide persistent storage for the names? ANSWER: No.
MakeNameNT(NameNT,STRSIZE(NameNT),DevCtrlNT);
if(!NT_SUCCESS(Status= IoCreateDevice(pDriver,sizeof(*pCtrl),&DevCtrlNT,CRYPTDISK_CONTROL_ID,0,0,&pDevice))) {
Error(Status,"DeviceCreate: IoCreateDevice(%ws) failed. [%X]",NameNT,Status);
} else if(!(pCtrl= new(pDevice->DeviceExtension) DeviceCtrl(pDevice))) {
Status= Error(STATUS_NO_MEMORY,"DeviceCtrl:Create: constructor failed?");
IoDeleteDevice(pDevice);
} else {
// Create the DOS path.
WCHAR NameDOS[80];
UNICODE_STRING DevCtrlDOS;
pDevice->Flags|= DO_BUFFERED_IO;
MakeNameDOS(NameDOS,STRSIZE(NameDOS),DevCtrlDOS);
if(!NT_SUCCESS(IoCreateSymbolicLink(&DevCtrlDOS,&DevCtrlNT)))
Warn(ERR_SYSCREATE,"DeviceCreate: IoCreateSymbolicLink(%ws) failed. [%X]",NameDOS,Status);
pDevice->Flags&= ~DO_DEVICE_INITIALIZING;
}
return(Status);
}
NTSTATUS DeviceCtrl::Destroy(void) {
NTSTATUS Status= STATUS_SUCCESS;
WCHAR NameDOS[80];
UNICODE_STRING DevCtrlDOS;
Debug(DBG_INIT,"DeviceCtrl:Destroy");
MakeNameDOS(NameDOS,STRSIZE(NameDOS),DevCtrlDOS);
if(!NT_SUCCESS(IoDeleteSymbolicLink(&DevCtrlDOS)))
Warn(ERR_SYSCREATE,"DeviceCtrl:Destroy: IoDeleteSymbolicLink(%ws) failed. [%X]",NameDOS,Status);
this->~DeviceCtrl(); //Don't call delete, since I didn't allocate it.
return(Status);
}
void DeviceCtrl::MakeNameNT(WCHAR *pBuf, UINT BufSz, UNICODE_STRING &Name) {
RtlStringCbPrintfW(pBuf,BufSz,L"\\Device\\%S",CRYPTDISK_CONTROL_NAME);
RtlInitUnicodeString(&Name,pBuf);
}
void DeviceCtrl::MakeNameDOS(WCHAR *pBuf, UINT BufSz, UNICODE_STRING &Name) {
RtlStringCbPrintfW(pBuf,BufSz,L"\\DosDevices\\%S",CRYPTDISK_CONTROL_NAME);
RtlInitUnicodeString(&Name,pBuf);
}
NTSTATUS DeviceCtrl::IrpDispatch2(IRP *pIrp) {
NTSTATUS Status= STATUS_SUCCESS;
IO_STACK_LOCATION *pStack= IoGetCurrentIrpStackLocation(pIrp);
switch(pStack->MajorFunction) {
case IRP_MJ_CREATE: Status= Open(pStack); break;
case IRP_MJ_CLOSE: Status= Close(pStack); break;
case IRP_MJ_READ: Status= Read(pIrp,pStack); break;
}
return(Status);
}
NTSTATUS DeviceCtrl::Open(IO_STACK_LOCATION *pIrp) {
NTSTATUS Status= STATUS_SUCCESS;
FILE_OBJECT *pFileObj;
if(!(pFileObj= pIrp->FileObject)) {
Status= Warn(STATUS_INVALID_ADDRESS,"DeviceCtrl:Open: No FileObject. [%p]",pIrp);
} else if(!NT_SUCCESS(Status= CtrlFileCreate(pFileObj))) {
Status= Warn(Status,"DeviceCtrl:Open: CtrlFileCreate() failed.");
}
return(Status);
}
NTSTATUS DeviceCtrl::Close(IO_STACK_LOCATION *pIrp) {
NTSTATUS Status= STATUS_SUCCESS;
FILE_OBJECT *pFileObj;
if(pFileObj= pIrp->FileObject)
Status= CtrlFileDestroy(pFileObj);
return(Status);
}
NTSTATUS DeviceCtrl::Read(IRP *pIrp, IO_STACK_LOCATION *pStack) {
NTSTATUS Status= STATUS_SUCCESS;
FILE_OBJECT *pFileObj;
UINT ReadCt= 0;
if(pFileObj= pStack->FileObject) {
BYTE *pBuf= (BYTE*)pIrp->AssociatedIrp.SystemBuffer;
UINT ByteCt= pStack->Parameters.Read.Length;
UINT64 FilePos= pFileObj->CurrentByteOffset.QuadPart;
if(NT_SUCCESS(Status= CtrlFileRead(pFileObj,pBuf,ByteCt,&FilePos,&ReadCt))) {
pFileObj->CurrentByteOffset.QuadPart= FilePos;
}
}
pIrp->IoStatus.Information= ReadCt;
return(Status);
}
/*************************************************************************/
/** My DISK device extension. **/
/*************************************************************************/
//EOF: DEVICE.CPP
⚛
The IRP requests are handled by device objects, but the IRP
dispatch table is defined in the DRIVER_OBJECT. This seems strange to
me, especially when writing a single driver that handles two (or more)
different types of device. It seems that the entire dispatch table in
the driver should be a single vector in the device object.
Furthermore, even though the DRIVER_OBJECT contains a large
dispatch table with a vector for every major IRP function, the
arguments are all the same and the function code is contained in the
IRP. This means the entire table is actually redundant and I can set
all the dispatch vectors to point to the same function, which is
actually better than having many different entry points to my
code.
Open DrvEntry.cpp and fill in the IRP dispatch table.
DrvEntry.cpp:
NTSTATUS DriverEntry(DRIVER_OBJECT *pDriver, UNICODE_STRING *RegPath) {
int Err= 0;
//NOTE: By default, only error messages are printed by Windows.
DbgPrintEx(DPFLTR_IHVDRIVER_ID,DPFLTR_ERROR_LEVEL,"CryptDriver has arrived! [%llX]\r\n",DriverEntry);
DbgPrintEx(DPFLTR_IHVDRIVER_ID,DPFLTR_ERROR_LEVEL,"CryptDriver version %s %s",gVerID.BuildStr,gVerID.Builder);
//if(KD_DEBUGGER_ENABLED && !KD_DEBUGGER_NOT_PRESENT)
// DbgBreakPoint();
pDriver->DriverUnload= DriverNtUnload;
if(!NT_SUCCESS(DeviceCtrlCreate(pDriver))) {
Warn(STATUS_FAILED_DRIVER_ENTRY,"CryptDriver: CreateCtrlDevice() failed.");
} else {
// Map all the IRP requests to a common handler.
for(UINT n1=0;n1<IRP_MJ_MAXIMUM_FUNCTION;pDriver->MajorFunction[n1++]= DeviceIrp);
}
return(STATUS_SUCCESS);
}
static void DriverNtUnload(DRIVER_OBJECT *pDriver) {
//I need to destroy all my devices.
if(pDriver->DeviceObject)
DeviceDestroy(pDriver->DeviceObject);
DbgPrintEx(DPFLTR_IHVDRIVER_ID,DPFLTR_ERROR_LEVEL,"CryptDriver has left the building.\r\n");
}