Simple Device Driver Makefile

64-bit Device Driver Development

This article is intended to be a quickstart guide for building, loading, and debugging a simple device driver for a 64-bit Windows 7 system. Basic familiarity with device driver development and kernel debugging is assumed. For more of an introduction I recommend Toby Opferman s articles at CodeProject which are still mostly relevant despite being several years old. Also, as a security professional my interest and expertise with device drivers is limited to the use of kernel-mode code to modify operating system behavior, rather than interfacing with actual hardware devices.

Instructions are provided for using WinDbg for remote kernel debugging to a VirtualBox virtual machine. The procedure is similar for using VMware and other virtualization software. Note that running a 64-bit guest operating system in a virtual machine requires a CPU which supports hardware-assisted virtualization Intel-VT or AMD-V. I ended up having to upgrade the processor in one of my systems after finding out that it lacked Intel-VT support. It also may be necessary to go into the BIOS setup and ensure that the hardware virtualization is enabled.

Differences

There are several differences from earlier versions of Windows affecting device driver developers:

Driver Signing. 64-bit Windows systems will not allow drivers to be loaded unless they have a valid digital signature, which requires a signing key to be purchased from Microsoft. This is only made available to approved vendors and is not really an option for the hobbyist or researcher. For development and testing there is an option that can be selected from the boot menu to disable driver signing requirements. This cannot be made the default and must be manually selected at every boot. This is a nuisance for driver developers but is generally a good thing as it makes it harder for malware writers to install kernel rootkits.

Debugging Message Filtering. Beginning with Vista including 32-bit versions, all debugging messages printed by drivers are not necessarily displayed in the kernel debugger. Rather, the system must be configured to display messages matching certain criteria.

Kernel Patch Protection PatchGuard. Modifying certain kernel data structures is no longer allowed, again in an effort to crack down on kernel rootkits. Drivers that rely on certain techniques for hooking system calls may need to use an alternate approach. For details see Microsoft s Patching Policy for x64-Based Systems.

Sample Code

In addition to the minimal DriverEntry and DriverUnload routines, this sample driver also implements a Device I/O Control IOCTL interface for communicating from user to kernel mode.

//testdrv.c

include

define DEVICE_NAME L Device Testdrv

define DOS_DEVICE_NAME L DosDevices Testdrv

//numeric value 0x22a001

define IOCTL_TESTDRV CTL_CODE FILE_DEVICE_UNKNOWN, 0x800, METHOD_IN_DIRECT, FILE_WRITE_DATA

//macros for OACR

DRIVER_INITIALIZE DriverEntry;

DRIVER_UNLOAD TestdrvUnload;

__drv_dispatchType IRP_MJ_CREATE

__drv_dispatchType IRP_MJ_CLOSE

__drv_dispatchType IRP_MJ_DEVICE_CONTROL

DRIVER_DISPATCH TestdrvDispatch;

pragma alloc_text INIT, DriverEntry

pragma alloc_text PAGE, TestdrvUnload

pragma alloc_text PAGE, TestdrvDispatch

//log message with filter mask IHVDRIVER and severity level INFO

void DebugInfo char str

DbgPrintEx DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, testdrv: s n, str ;

NTSTATUS TestdrvDispatch PDEVICE_OBJECT DeviceObject, PIRP Irp

PIO_STACK_LOCATION iostack;

NTSTATUS status STATUS_NOT_SUPPORTED;

PCHAR buf;

ULONG len;

PAGED_CODE ;

iostack IoGetCurrentIrpStackLocation Irp ;

switch iostack- MajorFunction

case IRP_MJ_CREATE:

case IRP_MJ_CLOSE:

status STATUS_SUCCESS;

break;

case IRP_MJ_DEVICE_CONTROL:

if iostack- Parameters.DeviceIoControl.IoControlCode IOCTL_TESTDRV

len iostack- Parameters.DeviceIoControl.InputBufferLength;

buf PCHAR Irp- AssociatedIrp.SystemBuffer;

//verify null-terminated and print string to debugger

if buf len-1 0

DebugInfo buf ;

else

status STATUS_INVALID_PARAMETER;

default:

status STATUS_INVALID_DEVICE_REQUEST;

Irp- IoStatus.Status status;

Irp- IoStatus.Information 0;

IoCompleteRequest Irp, IO_NO_INCREMENT ;

return status;

VOID TestdrvUnload PDRIVER_OBJECT DriverObject

UNICODE_STRING dosdev;

DebugInfo driver unloading ;

RtlInitUnicodeString dosdev, DOS_DEVICE_NAME ;

IoDeleteSymbolicLink dosdev ;

IoDeleteDevice DriverObject- DeviceObject ;

NTSTATUS DriverEntry PDRIVER_OBJECT DriverObject,

PUNICODE_STRING RegistryPath

UNICODE_STRING devname, dosname;

PDEVICE_OBJECT devobj;

NTSTATUS status;

DebugInfo driver initializing ;

DriverObject- MajorFunction IRP_MJ_CREATE TestdrvDispatch;

DriverObject- MajorFunction IRP_MJ_CLOSE TestdrvDispatch;

DriverObject- MajorFunction IRP_MJ_DEVICE_CONTROL TestdrvDispatch;

DriverObject- DriverUnload TestdrvUnload;

RtlInitUnicodeString devname, DEVICE_NAME ;

RtlInitUnicodeString dosname, DOS_DEVICE_NAME ;

status IoCreateDevice DriverObject, 0, devname, FILE_DEVICE_UNKNOWN,

FILE_DEVICE_SECURE_OPEN, FALSE, devobj ;

if . NT_SUCCESS status

DebugInfo error creating device ;

status IoCreateSymbolicLink dosname, devname ;

DebugInfo error creating symbolic link ;

return STATUS_SUCCESS;

Building

There are two additional files required to build a device driver: sources and makefile the file names do not have extensions. sources should contain the following lines:

TARGETNAME testdrv

TARGETTYPE DRIVER

SOURCES testdrv.c

makefile only needs to contain this one line:

.INCLUDE NTMAKEENV makefile.def

Build the driver as follows:

Of course the Windows Driver Kit WDK must be installed. The WDK also includes the Debugging Tools for Windows WinDbg.

From the Windows Driver Kit start menu group, launch the command-line build environment for the desired target platform and architecture, e.g. Windows 7 x64 Checked Build Environment.

Change to the directory containing  testdrv.c, sources, and makefile, and enter the command build.

If all goes well the driver file testdrv.sys will be built in a subdirectory named according the selected platform and architecture, e.g. objchk_win7_amd64.

By default the WDK will start a program called OACR Auto Code Review that will display an icon in the taskbar and automatically detect when a driver is being built and analyze the code for common programming errors. There are some declarations in the testdrv.c source code for preventing OACR warning messages. For details see Microsoft Auto Code Review.

Debugging Message Filtering

Note in the sample source code that the DbgPrintEx function is used with additional parameters, as opposed to the standard DbgPrint. The additional parameters are the Component ID and Severity Level. Only the component IDs defined in dpfilter.h that start with DPFLTR_IHV_ should be used by driver developers. IHV stands for Independent Hardware Vendor, i.e. any third-party non-Microsoft driver.

This example uses the generic DPFLTR_IHVDRIVER but other more specific IDs are available for video/audio/network/etc. drivers. The severity level can technically be any 32-bit number but standard constants exist for ERROR, WARNING, TRACE, and INFO levels corresponding to the numeric values 0, 1, 2, and 3, respectively. ERROR should only be used to indicate a truly serious error, but otherwise it is a matter of preference. This example logs everything at INFO level.

For each Component ID, the system maintains a kernel variable named Kd__Mask which is a 32-bit number representing a bit mask for the severity levels of debugging messages that should be displayed. For example, if bit 3 in this variable is set to 1, then INFO level messages will come through. The full list of these variables can be seen in WinDbg with this command:

kd x nt.Kd_ _Mask

fffff800 02e03198 nt.Kd_LDR_Mask

fffff800 02e031ec nt.Kd_WDI_Mask

fffff800 02e0304c nt.Kd_SETUP_Mask

fffff800 02e03150 nt.Kd_DMIO_Mask

The current value of the IHVDRIVER mask can be displayed as follows it should be zero by default :

kd dd nt.Kd_IHVDRIVER_Mask L1

fffff800 02e03178  00000000

This value can be modified directly in the debugger and will take effect immediately and persist until the system is rebooted. For example, to set the lowest 4 bits and therefore enable debugging messages for levels ERROR thru INFO, set the value to hex 0x0000000f:

kd ed nt.Kd_IHVDRIVER_Mask f

To make this change permanent, create a registry value IHVDRIVER must be uppercase in HKEY_LOCAL_MACHINE SYSTEM CurrentControlSet Control Session Manager Debug Print Filter. The value should be a DWORD corresponding to the desired mask, e.g. 0x0000000f. The change will not take effect until the next reboot.

Refer to Reading and Filtering Debugging Messages for more information.

Note that debug messages will not display in the kernel debugger during local kernel debugging no matter what you do. The only options for seeing messages in local kernel debugging are to execute the. dbgprint command periodically or use the DebugView tool from SysInternals.

Test Program

The following user-mode application can be used to invoke the DeviceIoControl IOCTL interface and pass data into the driver.

//ioctltest.c

define UNICODE

define _UNICODE

define DEVICE_NAME L. Testdrv

define IOCTL_CODE 0x22a001

const char message Greetings from user mode ;

void debug char text

LPTSTR msgbuf;

//get error message string for last error

FormatMessage FORMAT_MESSAGE_ALLOCATE_BUFFER FORMAT_MESSAGE_FROM_SYSTEM,

NULL, GetLastError, LANG_USER_DEFAULT,

LPTSTR msgbuf, 0, NULL ;

printf s: S n, text, msgbuf ;

int wmain int argc, wchar_t argv

HANDLE hDevice;

char outBuf 16 ;

DWORD returned;

hDevice CreateFile DEVICE_NAME, GENERIC_READ GENERIC_WRITE,

FILE_SHARE_READ FILE_SHARE_WRITE, NULL, OPEN_EXISTING,

FILE_ATTRIBUTE_NORMAL, NULL ;

if hDevice INVALID_HANDLE_VALUE

debug error opening device ;

if 0 DeviceIoControl hDevice, IOCTL_CODE, message, sizeof message,

outBuf, sizeof outBuf, returned, NULL

debug ioctl error ;

CloseHandle hDevice ;

return 0;

Build with:

cl ioctltest.c

Virtual Machine Configuration

This section assumes that Windows 7 64-bit has been installed in a VirtualBox virtual machine. The following steps will prepare the virtual system for driver loading and kernel debugging. First, with the virtual machine powered off, configure the virtual serial port to point to a named pipe for the kernel debugger to connect to:

Next, enable kernel debugging in the Boot Configuration Database using the built-in bcdedit command line tool, and also configure the boot menu to always display at boot. There is also a nice free GUI tool EasyBCD available for modifying these settings. Start a command prompt with administrator privileges and run the following commands:

bcdedit /set current debug yes

bcdedit /set bootmgr displaybootmenu yes

Run bcdedit with no parameters to confirm the current configuration.

Finally, reboot the virtual machine. When the boot menu is displayed, press F8 and then select Disable Driver Signature Enforcement from the menu.

Attaching Debugger

This section assumes that debugging symbols have already been configured in WinDbg on the host system. For more information see Debugging Tools and Symbols on MSDN. Launch WinDbg on the host system, select File- Kernel Debug and specify the following settings:

Press Ctrl-Break or select Debug- Break from the menu and wait patiently. Kernel debugging over the virtual serial port is SLOW – it may take several seconds before any feedback is received at all from the Break request, and may take 30 seconds or more for the prompt to display:

You are seeing this message because you pressed either

CTRL C if you run kd.exe or,

CTRL BREAK if you run WinDBG,

on your debugger machine s keyboard.

THIS IS NOT A BUG OR A SYSTEM CRASH

If you did not intend to break into the debugger, press the g key, then

press the Enter key now. This message might immediately reappear. If it

does, press g and Enter again.

nt.DbgBreakPointWithStatus:

fffff800 026c87a0 cc int 3

This confirms that kernel debugging is working. If nothing happens, double-check the virtual serial port settings, debug boot configuration, and WinDbg connection settings.

Configure the debugging message filter mask as previously discussed:

Finally, enter g at the prompt to resume execution of the virtual machine.

Testing driver

Copy the testdrv.sys driver to the virtual machine using the drag-and-drop or shared folder support. Also copy over the test program ioctltest.exe.

The driver can be installed using the built-in Windows command-line tool sc.exe. This can also be done using the GUI tool OSRLoader available from OSR Online free registration required. To use sc.exe, start a command prompt with administrative privileges and run the following commands replacing c: testdrv.sys with the path where testdrv.sys is located on the virtual machine :

sc create testdrv binPath c: testdrv.sys type kernel

Note that the spaces after the equals signs are required. A pop-up warning may be received about the driver not having a valid digital signature, which can be ignored. The sc create command only needs to be run once, and from then on the driver can be just be started and stopped as desired. All the sc create command does is create the necessary registry entries under HKLM System CurrentControlSet Services testdrv. The registry entries can be removed if needed with sc delete testdrv.

To load the driver:

sc start testdrv

If the driver starts successfully, the following message should be seen in the kernel debugger if the message is not seen, double-check the filter mask settings :

testdrv: driver initializing

Next, run ioctltest.exe and the following output should be seen in the debugger:

testdrv: Greetings from user mode

The driver can be unloaded with sc stop and the following message should be seen:

testdrv: driver unloading.

This article is intended to be a quickstart guide for building, loading, and debugging a simple device driver for a 64-bit Windows 7 system.

A Simple Block Driver for Linux Kernel 2.6.31 « Superpatterns

The Linux Kernel Module Programming Guide is a free book; you may reproduce and/or modify it under the terms of the Open Software License, version 1.1.

Do you pine for the nice days of Minix-1.1, when men were men and wrote their own device drivers. Linus Torvalds Pre-requisites In order to develop Linux device.

/dev/hello_world: A Simple Introduction to Device Drivers under Linux by Valerie Henson 07/05/2007 Since the misty days of yore, the first step in learning a new.

Linux Device Drivers, 3rd Edition

My current work involves writing my first Linux block device driver. Going to the web to find a sample, I discovered Jonathan Corbet s Simple Block Driver article with its associated block driver example code. It s a nice succinct implementation of a ramdisk - pretty much the simplest working block device. There s only one problem, though, the article was written in 2003, when kernel 2.6.0 was the new kid on the block. Trying to build it on openSUSE 11.2 with kernel 2.6.31 just produced a slew of compile errors. A bit of research revealed that there were major changes to the kernel block device interface in 2.6.31, so I would have to port the example to get it working.

About a day and a half of poring through the kernel source and the excellent LDD3 hardcopy later, I had a running simple block driver for kernel 2.6.31. I ve also tested it successfully on SUSE 11 SP1 Beta, which uses kernel 2.6.32. Here s the code, followed by instructions for getting it working.

sbd.c

/

A sample, extra-simple block driver. Updated for kernel 2.6.31.

C 2003 Eklektix, Inc.

C 2010 Pat Patterson

Redistributable under the terms of the GNU GPL.

/

include

include / printk /

include / everything /

include / error codes /

include / size_t /

MODULE_LICENSE Dual BSD/GPL ;

static char Version 1.4 ;

static int major_num 0;

module_param major_num, int, 0 ;

static int logical_block_size 512;

module_param logical_block_size, int, 0 ;

static int nsectors 1024; / How big the drive is /

module_param nsectors, int, 0 ;

We can tweak our hardware sector size, but the kernel talks to us

in terms of small sectors, always.

define KERNEL_SECTOR_SIZE 512

Our request queue.

static struct request_queue Queue;

The internal representation of our device.

static struct sbd_device

unsigned long size;

spinlock_t lock;

u8 data;

struct gendisk gd;

Device;

Handle an I/O request.

static void sbd_transfer struct sbd_device dev, sector_t sector,

unsigned long nsect, char buffer, int write

unsigned long offset sector logical_block_size;

unsigned long nbytes nsect logical_block_size;

if offset nbytes dev- size

printk KERN_NOTICE sbd: Beyond-end write ld ld n, offset, nbytes ;

return;

if write

memcpy dev- data offset, buffer, nbytes ;

else

memcpy buffer, dev- data offset, nbytes ;

static void sbd_request struct request_queue q

struct request req;

req blk_fetch_request q ;

while req. NULL

// blk_fs_request was removed in 2.6.36 - many thanks to

// Christian Paro for the heads up and fix

//if . blk_fs_request req

if req NULL req- cmd_type. REQ_TYPE_FS

printk KERN_NOTICE Skip non-CMD request n ;

__blk_end_request_all req, -EIO ;

continue;

sbd_transfer Device, blk_rq_pos req, blk_rq_cur_sectors req,

req- buffer, rq_data_dir req ;

if . __blk_end_request_cur req, 0

The HDIO_GETGEO ioctl is handled in blkdev_ioctl, which

calls this. We need to implement getgeo, since we can t

use tools such as fdisk to partition the drive otherwise.

int sbd_getgeo struct block_device block_device, struct hd_geometry geo

long size;

/ We have no real geometry, of course, so make something up. /

size Device.size logical_block_size / KERNEL_SECTOR_SIZE ;

geo- cylinders size 0x3f 6;

geo- heads 4;

geo- sectors 16;

geo- start 0;

return 0;

The device operations structure.

static struct block_device_operations sbd_ops

.owner THIS_MODULE,

.getgeo sbd_getgeo

;

static int __init sbd_init void

/

Set up our internal device.

Device.size nsectors logical_block_size;

spin_lock_init Device.lock ;

Device.data vmalloc Device.size ;

if Device.data NULL

return -ENOMEM;

Get a request queue.

Queue blk_init_queue sbd_request, Device.lock ;

if Queue NULL

goto out;

blk_queue_logical_block_size Queue, logical_block_size ;

Get registered.

major_num register_blkdev major_num, sbd ;

if major_num 0

printk KERN_WARNING sbd: unable to get major number n ;

And the gendisk structure.

Device.gd alloc_disk 16 ;

if . Device.gd

goto out_unregister;

Device.gd- major major_num;

Device.gd- first_minor 0;

Device.gd- fops sbd_ops;

Device.gd- private_data Device;

strcpy Device.gd- disk_name, sbd0 ;

set_capacity Device.gd, nsectors ;

Device.gd- queue Queue;

add_disk Device.gd ;

out_unregister:

unregister_blkdev major_num, sbd ;

out:

vfree Device.data ;

static void __exit sbd_exit void

del_gendisk Device.gd ;

put_disk Device.gd ;

blk_cleanup_queue Queue ;

module_init sbd_init ;

module_exit sbd_exit ;

Makefile

obj-m : sbd.o

KDIR : /lib/modules/ shell uname -r /build

PWD : shell pwd

default:

MAKE -C KDIR SUBDIRS PWD modules

There are two main areas of change compared with Jonathan s original:

sbd_request uses the blk_fetch_request, blk_rq_pos, blk_rq_cur_sectors and __blk_end_request_cur functions rather than elv_next_request, req- sector, req- current_nr_sectors and end_request respectively. The structure of the loop also changes so we handle each sector from the request individually. One outstanding task for me is to investigate whether req- buffer holds all of the data for the entire request, so I can handle it all in one shot, rather than sector-by-sector. My first attempt resulted in the virtual machine hanging when I installed the driver, so I clearly need to do some more work in this area.

The driver implements the getgeo operation in sbd_getgeo, rather than ioctl, since blkdev_ioctl now handles HDIO_GETGEO by calling the driver s getgeo function. This is a nice simplification since it moves a copy_to_user call out of each driver and into the kernel.

Before building, ensure you have the kernel source, headers, gcc, make etc - if you ve read this far, you likely have all this and/or know how to get it, so I won t spell it all out here. You ll also need to go to the kernel source directory and do the following to prepare your build environment, if you have not already done so:

cd /usr/src/ uname -r

make oldconfig make prepare

Now, back in the directory with the sbd source, you can build it:

make -C /lib/modules/ uname -r /build M pwd modules

You ll see a warning about Version being defined, but not used, but don t worry about that :-. Now we can load the module, partition the ramdisk, make a filesystem, mount it, and create a file:

opensuse:/home/pat/sbd insmod sbd.ko

opensuse:/home/pat/sbd fdisk /dev/sbd0

Device contains neither a valid DOS partition table, nor Sun, SGI or OSF disklabel

Building a new DOS disklabel with disk identifier 0x5f93978c.

Changes will remain in memory only, until you decide to write them.

After that, of course, the previous content won t be recoverable.

Warning: invalid flag 0x0000 of partition table 4 will be corrected by w rite

Command m for help : n

Command action

e extended

p primary partition 1-4

p

Partition number 1-4 : 1

First cylinder 1-16, default 1 :

Using default value 1

Last cylinder, cylinders or size K,M,G 1-16, default 16 :

Using default value 16

Command m for help : w

The partition table has been altered.

Calling ioctl to re-read partition table.

Syncing disks.

opensuse:/home/pat/sbd mkfs /dev/sbd0p1

mke2fs 1.41.9 22-Aug-2009

Filesystem label

OS type: Linux

Block size 1024 log 0

Fragment size 1024 log 0

64 inodes, 504 blocks

25 blocks 4.96 reserved for the super user

First data block 1

Maximum filesystem blocks 524288

1 block group

8192 blocks per group, 8192 fragments per group

64 inodes per group

Writing inode tables: done

Writing superblocks and filesystem accounting information: done

This filesystem will be automatically checked every 24 mounts or

180 days, whichever comes first. Use tune2fs -c or -i to override.

opensuse:/home/pat/sbd mount /dev/sbd0p1 /mnt

opensuse:/home/pat/sbd echo Hi /mnt/file1

opensuse:/home/pat/sbd cat /mnt/file1

Hi

opensuse:/home/pat/sbd ls -l /mnt

total 13

-rw-r--r-- 1 root root 3 2010-04-29 file1

drwx------ 2 root root 12288 2010-04-29 lost found

opensuse:/home/pat/sbd umount /mnt

opensuse:/home/pat/sbd rmmod sbd

Hopefully this all works for you, and is as useful for you as it has been for me. Many thanks to Jonathan for the original version and the excellent LDD3. One final piece of housekeeping - although the comment at the top of sbd.c mentions only GPL, the MODULE_LICENSE macro specifies Dual BSD/GPL. I am interpreting the original code as being under the dual GPL/BSD license and this version is similarly dual licensed.

UPDATE Feb 5 2011 See the comment by Michele regarding changes to logical_block_size.

UPDATE Apr 23 2015 See the comment by Sarge regarding changes for kernel 3.15-rc2 and later.

My current work involves writing my first Linux block device driver. Going to the web to find a sample, I discovered Jonathan Corbet s Simple Block Driver article.

Class Offerings. Class Schedule. Distance Learning. On-Site. Academic Program. Student Feedback. Certification. Courseware. Linux Device Driver Training.

Goal I am trying to write a simple device driver on Ubuntu. I want to do this using Eclipse or a better IDE that is suitable for driver programming. Here is the.