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   
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.
GeneralRe: Disk/Partition Sectorsmembertrlacey1 Nov '09 - 3:58 
Hi Deepak,
 
The volume names are \\.\PhysicalDriveX and \\.\X: They both work. I'm reading drives 0 and 1 ( c: and d: ). They both have an MBR on sector 0. (d: is a bootable drive as well). I can see the MBR's and the MFT's, etc. on both drives.
 
I run your software with the command line:
 
disksector /disk 1 /read 0
 
Totally different numbers on sector 0. It's really odd. As far as I can tell, everything looks correct in your source code. Can you see your MBR using this software? I have no idea yet what I could be doing wrong.
GeneralRe: Disk/Partition Sectorsmemberdkg04141 Nov '09 - 18:35 
hmm ok.
AFAIK, MBR is just one, it can't be present on both the drives. Infact it is not at all present on drives (partitions).
I think you are talking about Boot Sector (or Boot Record) of specific drives (partitions). MBR stands for Master Boot Record and is present on the start of properly partitioned hard disk. MBR contains info about partitions on the hard drive in a Partition Table (Like start and end of partition, type of partition, etc). It contains other info also (I dont remember offhand right now). Typically after 64 starting sectors (it is a typical value, it can vary also), first partition area starts.
So when you read \\.\PhysicalDriveX, you are reading from the starting of the harddisk (i.e MBR will be read). When you read drives i.e (\\.\C:\), then you are reading at a offset on the harddisk (typically 64 sectors as said above) and it's first sector will be a boot sector (if bootable partition).
Ok So far so good. Now about the my utility. Below I have pasted the usage of utility again.
 
DiskSector {/disk | /partition} <rawdisk number | partition number> {/read | /write} <sectornumber> {/unload}

Options Meanings :--

{/disk | /partition} <rawdisk number | partition number>
Disk and Parition options are mutually exclusive
Disk numbering starts from 0 while partition starts from 1


{/read | /write} <sectornumber>
Read and Write options are mutually exclusive
Sector numbering starts from 0

{/unload} This option simply unloads the support driver

e.g "DiskSector /disk 0 /read 0" will read raw sector 0 of harddisk 0
 
From the above usage it is evident that I am referring /disk as the physicaldrive (i.e the hard disk) and it's numbering starts from 0 (and not 1) while /partition refers to partitions (i.e drives) and their numbering starts from 1 (and not 0).
 
I hope all above helped, please forgive me if all above was confusing Frown | :-(
GeneralRe: Disk/Partition Sectorsmembertrlacey2 Nov '09 - 5:00 
Thanks Deepak. Sorry. Yes, I meant Boot Sector. The command line I'm using is:
 
disksector /disk 1 /read 0
 
The response is this:
 
33C0 8ED0 BC00 7C8E C08E D8BE 007C BF00 06B9 0002 FCF3 A450 681C 06CB FBB9 0400
....
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 55AA
 
When I use ReadFile() to read the same sector from the same drive the response is this:
 
EB52 904E 5446 5320 2020 2000 0208 0000 0000 0000 00F8 0000 3F00 FF00 0008 0000
....
6F20 7265 7374 6172 740D 0A00 0000 0000 0000 0000 0000 0000 809D B2CA 0000 55AA
 
Note the fixups are the same, and the output from ReadFile() contains the NTFS designator after the first 3 bytes, which is correct. I can't figure out where the data from your utility is coming from.
GeneralRe: Disk/Partition Sectorsmemberdkg04142 Nov '09 - 6:19 
hmm Are you intending to read the first disk attached (i.e PhysicalDrive0) to your system?
If yes then your command line should be "disksector /disk 0 /read 0"
I already told you that disk numbering starts from 0, 1, 2 and so on.
 
Let me know if you were intending to read first disk of your system.
GeneralRe: Disk/Partition Sectorsmembertrlacey2 Nov '09 - 6:25 
Thanks Deepak. No, I was reading drive D: "/disk 1". But I've tested both drives with both pieces of software. Same results on both drives.
GeneralRe: Disk/Partition Sectorsmemberdkg04142 Nov '09 - 6:36 
Ok since you are reading a drive, it is a partition and not a physical disk.
Even if it is on a different disk, you should specify it as partition.
try "/partition 2".
 
See usage correctly, partition numbering starts as 1,2,3,... while disk numbering starts as 0,1,2,...

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

Permalink | Advertise | Privacy | Mobile
Web03 | 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