dev.nlited.com

>>

Devices and Files

<<<< prev
next >>>>

2017-08-19 04:22:16 chip Page 1998 📢 PUBLIC

August 19 2017

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.

See also: Remote Debugging

DEVICE_OBJECT

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.h: /*************************************************************************/ /** CryptDisk.h: Universal declarations. **/ /** (C)2017 nlited systems, chip doran. **/ /*************************************************************************/ #pragma once #define __CRYPTDISK_H__ #define CRYPTDISK_SERVICE_NAME "CryptDisk" #define CRYPTDISK_DRIVER_NAME "CryptDriver.sys" #define CRYPTDISK_CONTROL_NAME "CryptControl" #define CRYPTDISK_CONTROL_ID 0xCD01 //EOF: CRYPTDISK.H

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"); }

I need to declare everything in Globals.h.

Globals.h: /*************************************************************************/ /** Globals.h: Global declarations for the CryptDisk kernel driver. **/ /** (C)2017 nlited systems inc, Chip Doran **/ /*************************************************************************/ #pragma once #define __GLOBALS_H__ #include <ntddk.h> #include <WinDef.h> #include <ntstrsafe.h> #include "StdTypes.h" #include "Errors.h" #include "OsKernel.h" //DbgFilter masks #define DBG_INIT 0x00000001 //Device.pp EXTERNC NTSTATUS DeviceCtrlCreate(DRIVER_OBJECT *pDriver); EXTERNC NTSTATUS DeviceDestroy(DEVICE_OBJECT *pDevice); EXTERNC NTSTATUS DeviceIrp(DEVICE_OBJECT *pDevice, IRP *pIrp); //CtrlFile.cpp EXTERNC NTSTATUS CtrlFileCreate(FILE_OBJECT *pFileObj); EXTERNC NTSTATUS CtrlFileDestroy(FILE_OBJECT *pFileObj); EXTERNC NTSTATUS CtrlFileRead(FILE_OBJECT *pFileObj, BYTE *pBuf, UINT BufSz, UINT64 *pFilePos, UINT *pReadCt); //EOF: GLOBALS.H

My loader app needs to be improved a bit to test multiple load/unload cycles and reading from the device.

CryptDisk/Driver.cpp: class Driver { public: static Driver *Ptr(HDRIVER hDriver); static int Create(HDRIVER &hDriver); int Destroy(void); int Load(void); int Unload(void); int Install(void); int Uninstall(void); int Remove(void); int Start(void); int Stop(void); int Open(void); //Data DWORD Signature_Driver; private: Driver(void); ~Driver(void); int Create2(void); int Close(void); //Data HANDLE hCtrl; //Control file handle WCHAR ServiceName[100]; //Name of the service to load the driver. WCHAR DisplayName[100]; //Display name for the service. WCHAR DriverName[100]; //Driver file name ("CryptDriver.sys") WCHAR ControlName[100]; //Control file name ("CryptCtrl") int StartError; //Windows error code }; ... int DriverUninstall(HDRIVER hDriver) { Driver *pDrv= Driver::Ptr(hDriver); return(pDrv ? pDrv->Uninstall():ERR_BAD_HANDLE); } Driver::Driver(void) { WCHAR InstallPath[MAX_PATH]; Signature_Driver= SIGNATURE_DRIVER; StrFormatW(ServiceName,STRSIZE(ServiceName),L"%S",CRYPTDISK_SERVICE_NAME); StrFormatW(DisplayName,STRSIZE(DisplayName),L"CryptDisk Driver"); GetModuleFileName(0,InstallPath,STRSIZE(InstallPath)); *wcsrchr(InstallPath,'\\')= 0; StrFormatW(DriverName,STRSIZE(DriverName),L"%s\\%S",InstallPath,CRYPTDISK_DRIVER_NAME); StrFormatW(ControlName,STRSIZE(ControlName),L"\\\\.\\%S",CRYPTDISK_CONTROL_NAME); } int Driver::Load(void) { int Err= ERR_OK; if(IsErr(Err= Install())) Warn(Err,"Driver:Load: Install() failed."); if(IsErr(Err= Start())) Warn(Err,"Driver:Load: Start() failed."); if(IsErr(Err= Open())) { Err= Error(Err,"Driver:Load: Unable to open the driver."); } else { char Text[100]; for(UINT n1=0;n1<10;n1++) { DWORD ReadCt; if(!ReadFile(hCtrl,Text,STRSIZE(Text),&ReadCt,0)) { Warn(ERR_FILE_READ,"%02u: ReadFile() failed [%d]",n1,GetLastError()); } else { Print(PRINT_INFO,"%S",Text); } } } return(Err); } int Driver::Unload(void) { int Err= ERR_OK; if(IsErr(Err= Close())) Warn(Err,"Driver:Unload: Close() failed."); if(IsErr(Err= Stop())) Warn(Err,"Driver:Unload: Stop() failed."); return(Err); } int Driver::Uninstall(void) { int Err= ERR_OK; if(IsErr(Err= Close())) Warn(Err,"Driver:Unload: Close() failed."); if(IsErr(Err= Stop())) Warn(Err,"Driver:Unload: Stop() failed."); if(IsErr(Err= Remove())) Warn(Err,"Driver:Unload: Remove() failed."); return(Err); }

I can now load and unload the driver repeatedly and read text from it.

Creating a new driver project.



WebV7 (C)2018 nlited | Rendered by tikope in 53.289ms | 44.220.184.63