Click here to Skip to main content
Click here to Skip to main content

Reading and Writing to Raw Disk Sectors

By , 2 Aug 2008
 

Introduction

This is a tool to read and write raw disk sectors on Windows systems (NT5.0, 5.1 kernels)
Inspiration to write this tool came to me when I had my laptop infected with some malware which was sitting on top of disk class driver as an upper filter and not allowing me to write to disk sectors using user mode disk editing tools like WinHex.

After a few days, I thought I should write a utility to read and write raw disk sectors by directly
communicating with disk class driver.

Background

To understand this article, one should have knowledge of C Programming and Windows Driver Programming.

We will go through the following topics to understand the utility in a better way:

  1. Device stack for storage drivers
  2. Enumerating device objects representing disks and partitions
  3. How to read/write to sectors

1. Device Stack for Storage Drivers

Microsoft provides generic storage drivers for managing the storage on a logical level and thus abstracting hardware details from upper level file system and other file drivers. This is called disk class driver (a driver to handle disk class of hardware, i.e. "disk.sys").

Similarly to handle SCSI, IDE hardware devices, Microsoft provides generic port interface drivers to which drivers supplied by specific vendors for their disk devices can be dynamically linked.

E.g. scsiport.sys (old interface) storport.sys (new interface) is used as an interface to SCSI port while Pciidex.sys is used as an interface to IDE port.

2. Enumerating Device Objects Representing Disks and Partitions

There is a question which needs to be answered first.

How does the OS come to know that a harddisk has been attached to the system?

Whenever a new disk device is attached to the system, SCSI and IDE port drivers create device object (although PCI driver is the first one which comes into the picture) to represent a SCSI/IDE device and inform I/O manager about it. I/O manager in turn queries the devices to know their device id and vendor id. Depending on dev id and vendor id, I/O manager decides (through registry or INF file mechanism) which driver is suitable to handle this device (driver supplied by vendor) and loads the hardware device driver which creates device objects representing the Functions device objects for the device and attaches itself to lower devices created by respective port drivers.

I/O manager informs disk class driver (disk.sys) of new disks added into the system. Disk class driver then creates the device objects representing the raw disks.

If a valid partition is present on the system, then it creates device objects for the respective partitions too.

E.g. Device objects created by disk class driver are as follows:

  • \Device\Harddisk0\DR(0) --> Represents Raw Harddisk 0
  • \Device\Harddisk0\DP(1)0x7e000-0x7ff50c00+2 --> Represents partition 2 of disk 0

The first hexadecimal digit shows the start and thsecond shows the length of partition.

That means all the device objects representing disks and partitions are chained in driver object of disk class driver (i.e. disk.sys).

Now to enumerate the device objects created, you first need to have access to the driver object of disk class driver.

The solution is to use undocumented Object management kernel function "ObReferenceObjectByName" prototype:

NTSTATUS ObReferenceObjectByName(
        PUNICODE_STRING, 
        DWORD, 
        PACCESS_STATE, 
        ACCESS_MASK,
        POBJECT_TYPE,
        KPROCESSOR_MODE,
        PVOID,
        PVOID *Object); 

The first argument is a Unicode string, i.e. "\Driver\disk", object receives the pointer to the DRIVER_OBJECT of disk.sys.

From DRIVER_OBJECT, you can enumerate all the device objects created by disk class driver and store pointer to device objects responsible for raw disks and partitions. The following snippet will clear the things:

PDEVICE_OBJECT pDeviceObject;
  ..... 
// DeviceType 7 corresponds to FILE_DISK_DEVICE Type Device Object and

 // It should have name too that's why Flags checks for 0x40 (DO_DEVICE_HAS_NAME )

                if (pDeviceObject->DeviceType == 7
                        && (pDeviceObjectTemp->Flags & 0x40))

3. How to Read/Write to Sectors

Once you have pointers to device objects for raw disks and partitions, reading and writing to those raw disks/partitions is not a difficult thing. You only have to do a IoCallDriver on the respective device object with IRP_MJ_READ/IRP_MJ_WRITE function codes initialized in the IRPs.

The following code will make things clear:

LARGET_INTEGER lDiskOffset; 

PDEVICE_OBJECT pDevObj; //Device object representing disk/partition

KEVENT Event; 

// Trying to read some arbitrary sector number 1169944 and 
// by default assuming sector size 

// 512 

..........

..........

        lDiskOffset.QuadPart = 1169944*512;
        sBuf = ExAllocatePool(NonPagedPool, size);
        
        if (!sBuf) {
            ObDereferenceObject(pFileObj);
            return STATUS_INSUFFICIENT_RESOURCES;
        }
        KeInitializeEvent(&Event, NotificationEvent, FALSE);
        memset(sBuf, '0x00', size);
        pIrp = IoBuildSynchronousFsdRequest(IRP_MJ_WRITE/*IRP_MJ_READ*/, 
			pDevObj, sBuf, size, &lDiskOffset, &Event, &ioStatus);
        
        
        if (!pIrp) {
            ExFreePool(sBuf);
            return STATUS_INSUFFICIENT_RESOURCES;
        }
        
        status = IoCallDriver(pDevObj, pIrp);

        if (status == STATUS_PENDING) {
            KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE,    NULL);
            status = ioStatus.Status;
        }
        ExFreePool(sBuf);

.......... 

Given above is just a sample code for sending a write operation to sector number 1169944.

Points of Interest

While writing the code, I was just doing a READ operation for verifying my results. I didn't take care while passing data buffer for write operations in the design (Please see driver code for more explanations). So I implemented an ugly hack for passing user mode buffer for write operations. I will improve it in future releases.

History

  • 2nd August, 2008: Initial post

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)

About the Author

dkg0414
Software Developer
India India
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralRe: Read/Write 1-2 Bytes in MBR onlymemberBrianPeterson26 Mar '10 - 15:11 
Ok, I've am doing something wrong but not sure what. Confused | :confused: I've written this quick and simple 'hello world' driver Driver Entry routine to demonstrate my dillema.
 
Using the methods you suggested previously, I define the first disk device, then try to open it with ZwOpenFile (I figured ZwCreateFile will make one if it doesn't exist, not something we want to have happen, so I only want to open an existing device.) This is where the driver fails... status from the ZwOpenFile operation is '-1073741788' and file_status->status is '-2141904312', so obviously the operation didn't work. But I don't understand why, and more importantly how to fix it! Likewise it never gets to the ZwReadFile operation so that I can at least in WinDbg look at the MBR in the array to ensure it is correctly read, specifically looking quickly at the '55 AA' at the end.
 
#include "ntddk.h"
#include "ntddk.h"
#include "ntdddisk.h"
#include "stdarg.h"
#include "stdio.h"
#include <ntddvol.h>
#include <mountdev.h>
#include "ntstrsafe.h"
 
typedef unsigned char	BYTE, *PBYTE, *LPBYTE;
typedef int		BOOL, *PBOOL, *LPBOOL;
typedef BYTE		BOOLEAN, *PBOOLEAN;
typedef unsigned long	DWORD, *PDWORD, *LPDWORD;
typedef unsigned long	ULONG, *PULONG;
typedef unsigned short	WORD, *PWORD, *LPWORD;
typedef char		TCHAR;
typedef void		*PVOID;
typedef unsigned short	UINT2;
typedef unsigned int	UINT4;
 
// Driver Unload routine
VOID OnUnload( IN PDRIVER_OBJECT DriverObject)
{
	DbgPrint("OnUnload called!\n");
}
 
// Driver Entry routine
NTSTATUS DriverEntry( IN PDRIVER_OBJECT theDriverObject,
		      IN PUNICODE_STRING theRegistryPath )
{
	UNICODE_STRING		PhysicalDeviceName;
        WCHAR			PhysicalDeviceNameBuffer[64];
	ULONG			DiskNumber;
	HANDLE			MBR;
	IO_STATUS_BLOCK		ioStatus;
	NTSTATUS                status;
	IO_STATUS_BLOCK		file_status;
	OBJECT_ATTRIBUTES	obj_attrib;
	DWORD			disk_MBR[512] = {0x0};
 
	DbgPrint("I loaded!\n");
	// Initialize the pointer to the unload function
	// in the DriverObject
	theDriverObject->DriverUnload = OnUnload;
 
	DiskNumber = 0;//Hard coded for disk 0
        MBR = NULL;
 
	RtlStringCbPrintfW(PhysicalDeviceNameBuffer,
			sizeof(PhysicalDeviceNameBuffer),
			L"\\Device\\Harddisk%d", DiskNumber);
 
	RtlInitUnicodeString(&PhysicalDeviceName,
                             &PhysicalDeviceNameBuffer[0]);
	DbgPrint("Disk name %ws\n", PhysicalDeviceNameBuffer);
 
	InitializeObjectAttributes(&obj_attrib, 
                                   &PhysicalDeviceName,
				   OBJ_CASE_INSENSITIVE,
				   NULL,
				   NULL);
	status = ZwOpenFile(&MBR,
			GENERIC_READ | GENERIC_WRITE,
			&obj_attrib,
			&file_status,
			0,
			FILE_NON_DIRECTORY_FILE); 
/*The file is not a directory. The file object to open can represent 
 *a data file; a logical, virtual, or physical device; or a volume.*/
 
	if (status != STATUS_SUCCESS){
		DbgPrint("Failed to open the disk!\n");
		DbgPrint("Disk Status = %x\n", file_status);
	}
	else {
		DbgPrint("Successfully opened the disk.\n");
		DbgPrint("Disk Handle = %x\n", MBR);
	}
 
	// Read the MBR from raw disk 
	if (MBR != NULL){
		status = ZwReadFile(MBR,
				NULL,
				NULL,
				NULL,
				&ioStatus,
				disk_MBR,
				512,
				NULL,
				NULL);
		if (NT_SUCCESS(status)){
		     DbgPrint("Disk %d MBR read successfully.\n", DiskNumber);
		}
		else
		{
		     DbgPrint("Disk %d MBR read failed!\n", DiskNumber);
		}
	}
	return STATUS_SUCCESS;
}
 
Now I decided to see if it was just the "\\Device\Harddisk0" that had caused the failure, so I modified the driver to look at "\\Device\Harddisk0\DR0", and the device was successfully opened.
 
RtlStringCbPrintfW(PhysicalDeviceNameBuffer,
					sizeof(PhysicalDeviceNameBuffer),
					L"\\Device\\Harddisk%d\\DR0", DiskNumber);
 
However, now the ZwReadFile is failing with status '-1073741811', so even if you can open the disk in this manner, you still can't read it. Unless I'm still doing something wrong.
 
grrr..... Mad | :mad:
GeneralRe: Read/Write 1-2 Bytes in MBR onlymemberdkg041426 Mar '10 - 18:37 
You are getting error STATUS_INVALID_PARAMETER.
You must be doing some thing wrong.
Is your driver filter in disk stack?
On which system are you trying all this?
Why didn't you use IoCallDriver mechanism.
GeneralRe: Read/Write 1-2 Bytes in MBR only [modified]memberBrianPeterson26 Mar '10 - 18:59 
I am pretty sure I'm doing something wrong with a parameter... lol. Laugh | :laugh: I am sure it has something to do with how I am trying to open it but I am running out of ideas.
 
>Is your driver filter in disk stack?
Not in this initial scenario. The code previously listed is the entire hello world driver I am experimenting in. Once it works correctly I will implement it in the infamous diskperf-based filter driver to be executed while in the disk stack.
 
>On which system are you trying all this?
XP SP3 Virtual Machine
 
>Why didn't you use IoCallDriver mechanism?
Confused | :confused: Because you had suggested to use this method instead. Besides to send an IRP down to the driver(s) below me (similarly to how you sent it in your sector.sys) would require me to have a pointer to the device I want to manipulate and as of right now, I only have a name.
 
Furthermore, it seems many say the zwcreatefile should work but I haven't found a working solution yet. http://www.osronline.com/showThread.cfm?link=23865[^] & http://www.pcreview.co.uk/forums/thread-535046.php[^]
 
Tried various names to identify the raw disk but soo far none have worked out:
 
L"\\DosDevices\\PhysicalDrive%d"
L"\\??\\PhysicalDrive%d"
L"\\\\.\\PhysicalDrive%d"
L"\\Device\\Harddisk%d"
 
Of course those name issues could really be a problem with the ZwCreateFile since I've been trying various versions of that as well.
 
status = ZwCreateFile(&MBR,
		GENERIC_READ | GENERIC_WRITE,// I have also tried FILE_READ_DATA | FILE_WRITE_DATA
		&obj_attrib,
		&file_status,
		NULL,
		FILE_ATTRIBUTE_NORMAL,
		FILE_SHARE_READ|FILE_SHARE_WRITE,
		FILE_OPEN,
		FILE_NON_DIRECTORY_FILE, // I also tried FILE_DIRECTORY_FILE
		NULL,
		0);
 
BTW,
How did you convert the -# to the text error code?
 
Brian
modified on Saturday, March 27, 2010 1:14 AM

GeneralRe: Read/Write 1-2 Bytes in MBR onlymemberBrianPeterson26 Mar '10 - 21:28 
Progress... but not entirely.
 
From the hello world driver I was able to read and write to a raw disk via:
 
DiskNumber = 0;//Hard coded for disk 0
RtlStringCbPrintfW(PhysicalDeviceNameBuffer,
		sizeof(PhysicalDeviceNameBuffer),
		L"\\DosDevices\\PhysicalDrive%d", DiskNumber);
 
RtlInitUnicodeString(&PhysicalDeviceName, &PhysicalDeviceNameBuffer[0]);
 
InitializeObjectAttributes(&obj_attrib, 
			&PhysicalDeviceName,
			OBJ_CASE_INSENSITIVE,
			NULL,
			NULL);
status = ZwCreateFile(&MBR,
		FILE_READ_DATA | FILE_WRITE_DATA,
		&obj_attrib,
		&file_status,
		NULL,
		FILE_ATTRIBUTE_NORMAL,
		FILE_SHARE_READ|FILE_SHARE_WRITE,
		FILE_OPEN,
		FILE_NON_DIRECTORY_FILE,
		NULL,
		0); 
 
if (status != STATUS_SUCCESS){
	DbgPrint("Failed to open the disk!\n");
	DbgPrint("Disk Status = %X\n", file_status);
	DbgPrint("Operation Status = %u\n", status);
}
else {
	DbgPrint("Successfully opened the disk.\n");
	DbgPrint("Disk Handle = %x\n", MBR);
}
 
	if (MBR != NULL){
		offset.QuadPart = 0;
 
		status = ZwReadFile(MBR,
				NULL,
				NULL,
				NULL,
				&ioStatus,
				readBuffer,
				sizeof(readBuffer),
    				&offset,
				NULL);
 
After inserting the equivalent of this code (few variable name changes, etc) into the diskperf equivalent of DiskPerfIrpCompletion() so that only on IRP_MN_START_DEVICE does it try to read/write from the disk. However, with same device name as the above example when ZwCreateFile runs it fails with an error of: (-1073741810) or 3221225486 = "A device which does not exist was specified." This occurs for disk0 at boot time, and disk1 (a usb I added after fully in the OS). That doesn't make any sense to me. I could possibly see disk0 failing, but not disk1 when the OS is in the same state as when the hello world driver ran. Thoughts?
GeneralRe: Read/Write 1-2 Bytes in MBR onlymemberdkg041426 Mar '10 - 23:22 
Yeah I had suspicion that in MN_START_DEVICE whether doing IO will be successful or not.
Try doing your operations in it's completion.
If that doesn't succeed, then you can do one thing:--
When a first IO (read/write) comes to the device do your own operation on the device object below (and not o n the your own filter device object). In this case then you should not use Zw calls but you should target your IRPs to lower device object using IoCallDriver. Zw call will send the IRP from top of the stack. And since your device is in the stack which is already holding the IOs will get another IO generated by you. And you will end up in a deadlock.
QuestionRe: Read/Write 1-2 Bytes in MBR onlymemberBrianPeterson27 Mar '10 - 8:52 
Unfortunately I think I am doing it in the completion routine of IRP_MN_START_DEVICE (run in DiskPerfIrpCompletion()). Ok, doing the operation in the first IO op comes sounds reasonable... it will obviously be taking operations at that time... lol. So lets see... an IO flag initialized to zero and then increment it so its only done on the first operation and not every operation... need to have a function take in IRP_MJ_READ & IRP_MJ_WRITE easy enough...
 
Now building the IRP with IoCallDriver should be pretty easy only catch I'm seeing on that is do I just send it to the next device below me as below or as in your driver you had a pointer to the specific raw disk to read.
 
myIRP_MJ_READ-WRITE_TRAP(    
          IN PDEVICE_OBJECT DeviceObject,
          IN PIRP Irp)
{
// 1) check for first operation or not. If it is build our take our actions,
// otherwise pass it on untouched. 

// 2) build the irp for the next device in the chain with a IRP_MJ_READ or 
// IRP_MJ_WRITE with the necessary buffers, etc (similar to what you did in
// sector.sys?)
MajorFunc  = (IoCtl==IOCTL_SECTOR_READ) ? IRP_MJ_READ : IRP_MJ_WRITE;
		
lDiskOffset.QuadPart = (pDiskObj->ulSectorSize) * (pDiskLoc->ullSectorNum);
		KeInitializeEvent(&Event, NotificationEvent, FALSE);
pIrp = IoBuildSynchronousFsdRequest(MajorFunc, pDiskObj->pDiskDevObj, pBuff, 
		pDiskObj->ulSectorSize, &lDiskOffset, 
		&Event, &ioStatus);
 
// 3) Modify physical device DACL to enforce BLP Security Model if necessary 
// via ZwSetSecurityObject or equivalent

// 4)Let the IRP we are holding go on through
 
IF it is the latter, I have proven I can get a working name for the raw disk (L"\\DosDevices\\PhysicalDrive%d"), but how do I convert that to something I can use as a target of my irp to gurantee I get the raw disk (sort of the reverse action of ObQueryNameString) to represent your pDiskDevObj? Can I use the deviceExtension->PhysicalDeviceObject which is set during Add_Device?
 
The next question that I see then is since I can't use the ZwRead/WriteFile operations because Zw calls will send the IRP from top of the stack. And since my device is in the stack which is already holding the IOs will get another IO generated by me and I will end up in a deadlock.... How does ZwSetSecurityObject get a Handle (an action that takes ZwCreateFile typically)? Or does that not matter now, since I've read/written to the raw disk already by this point and all other irps received are passed through me (other than this first one) or will this too end in deadlock? Is there a way to do ZwSetSecurityObject without causing deadlock prior to releasing the first trapped irp?
GeneralDisk/Partition Sectorsmembertrlacey28 Oct '09 - 8:23 
Hi. Thanks. It looks like you're doing everything right here, but oddly, I'm getting far different numbers than what this driver is returning. When I read sector 0, I get the main boot record which starts with 3 numbers and then the ASCII representation for NTFS. This software seems to be pulling data from a sector other than Logical Block Address 0.
GeneralRe: Disk/Partition Sectorsmemberdkg041429 Oct '09 - 20:11 
Can you post your command which you are executing to get the data?
GeneralRe: Disk/Partition Sectorsmembertrlacey30 Oct '09 - 7:48 
Hi Deepak,
 
Thanks. Certainly. I'm just calling ReadFile() from a Win32 usermode app on Windows Vista. The subroutine is below. Maybe I'm missing something, but I can't see what could possibly be different. When I read sector 0 using this subroutine, I get the MBR of my drive. When I use your driver to read raw disk sector 0, I get an entirely different set of numbers. But it must be reading a sector somewhere, because I can see the sector fixup in the data that your driver returns. Odd. I'm still working on the problem. There's always a reason.
 
DWORD ReadSector(LPSTR volume, ULONGLONG sector, unsigned char *q)
{
 
ULONGLONG ull, ull1;
unsigned long ul, ul1;
unsigned long bytesread = 0;
unsigned long sectorsize = 512;
char sstr[STRINGLENGTH], device[STRINGLENGTH];
 
HANDLE hDriver;
 
strcpy_s(device, volume);
_strupr_s(device);
 
hDriver = CreateFile(device,
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
0,
OPEN_EXISTING,
0,
0);
 
if(hDriver != INVALID_HANDLE_VALUE)
{
ull = sector * sectorsize;
ul = (unsigned long) ull;
ull >>= 32;
ul1 = (unsigned long) ull;
SetFilePointer(hDriver, ul, (PLONG) &ul1, FILE_BEGIN);
if(!ReadFile(hDriver, q, sectorsize, &bytesread, NULL))
{
sprintf_s(sstr, sizeof(sstr), "Error reading sector. Error = %d", GetLastError());
PleaseWait(sstr);
}
CloseHandle(hDriver);
}
else
{
sprintf_s(sstr, sizeof(sstr), "Error opening driver %s", volume);
PleaseWait(sstr);
}
 
return(bytesread);
 
}
GeneralRe: Disk/Partition Sectorsmemberdkg041431 Oct '09 - 20:29 
What is the Volume name here, I mean the input to your ReadSector function. I think you are giving input here as \\.\PhysicalDriveX. And what are you giving inputs to the "DiskSector" utility. Is /disk or /partition? /disk is for the entire disk starting from sector 0 while /partition is from the starting of partition which will not be starting of the disk.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 2 Aug 2008
Article Copyright 2008 by dkg0414
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid