Much like the main() function that you may be familiar with in C/C++ programming, a driver must specify an entry point, DriverEntry.
DriverEntry has many responsibilities, such as creating the device object and symbolic link used for communication with the driver and definitions of key functions (IRP handlers, unload functions, callback routines, etc.).
DriverEntry first creates the device object with a call to IoCreateDevice() or IoCreateDeviceSecure(), the latter typically being used to apply a security descriptor to the device object in order to restrict access to only local administrators and NT AUTHORITY\SYSTEM.
Next, DriverEntry uses IoCreateSymbolicLink() with the previously created device object to set up a symbolic link which will allow for user mode processes to communicate with the driver.
Here’s how this looks in code:
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
UNREFERENCED_PARAMETER(RegistryPath);
NTSTATUS status;
// Create the device object
UNICODE_STRING devName = RTL_CONSTANT_STRING(L"\\Device\\MyDevice");
PDEVICE_OBJECT DeviceObject;
NTSTATUS status = IoCreateDevice(DriverObject, 0, &devName, FILE_DEVICE_UNKNOWN, 0, FALSE, &DeviceObject);
if (!NT_SUCCESS(status)) {
KdPrint(("Failed to create device object");
return status;
}
// Create the symbolic link
UNICODE_STRING symLink = RTL_CONSTANT_STRING(L"\\??\\MySymlink");
status = IoCreateSymbolicLink(&symLink, &devName);
if (!NT_SUCCESS(status)) {
KdPrint(("Failed to create symbolic link"));
IoDeleteDevice(DeviceObject);
return status;
}
return status;
}The last thing that DriverEntry does is defines the functions for IRP handlers.