Access a raw disk in Random Access mode C++

1.2k views Asked by At

Now, I'm already familiar with the DeviceIoControl (ioctl) process and can read from a disk sequentially, 512 bytes at a time.

I create a handle from the list of \.\PhysicalDrive(s) and identify it via IOCTL_STORAGE_QUERY_PROPERTY command. Then process the desired device for data.

At this point, I can read it progressively by creating a loop that advances the read area 1 sector each time with this code (Qt C++ environment)

#include <minwindef.h>

#include <devioctl.h>
#include <ntdddisk.h>
#include <ntddscsi.h>

#include <ioapiset.h>
#include <fileapi.h>
#include <handleapi.h>
#include <winbase.h>

...

HANDLE devHandle = NULL;
devHandle = CreateFile(charArray, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0 ,NULL);

unsigned long secCount = 0;
while(true)
{
        bSuccess = ReadFile(devHandle, lpSecBuf, 512, &dwRead, NULL);

        if (bSuccess & (dwRead < 512))
        {
            qDebug()<<"EOF";
            break;
        }

        if(!bSuccess)
        {
            qDebug()<<"No data could be read from the device.";
            break;
        }

        if(bSuccess && dwRead>0)
        {
            qDebug()<<"Sector "<<secCount<<" data : "<<lpSecBuf;
        }

        secCount++;
}

Doing this sequentially means I have to progress through the sectors one by one, counting, until I reach the sector number I want to access. And this is not very optimal in performance.

What if I wanted to access a specific region directly, like "go to sector 45535 and read 512 bytes"? Does IOCTL operation even allow random access like this? I understand there are Random Access flags to a CreateFile call but after that, what? The read function still doesn't allow me to pass any arguments as "destination" or something like that, as far as I can see.

For example, HxD the hex editor can count the number of total sectors in a disk immediately and can go to a certain sector at any time. The function I need is similar to this capability.

HxD Disk Read Function

I have collected various tips as to how it can be done but I'm practically at an impasse here.

Any ideas would be welcome.

1

There are 1 answers

3
RbMm On BEST ANSWER

The read function still doesn't allow me to pass any arguments as "destination" or something like that, as far as I can see.

this is not true - read again about ReadFile

lpOverlapped [in, out, optional]

For an hFile that supports byte offsets, if you use this parameter you must specify a byte offset at which to start reading from the file or device. This offset is specified by setting the Offset and OffsetHigh members of the OVERLAPPED structure.

and this:

Considerations for working with file handles:

•If lpOverlapped is not NULL, the read operation starts at the offset that is specified in the OVERLAPPED structure ...


the I/O Manager maintains the current file position. ( look for LARGE_INTEGER CurrentByteOffset member of FILE_OBJECT ). ReadFile and WriteFile updates the current file position by adding the number of bytes read or written when it completes the operation. also we can set this position by call SetFilePointer[Ex]

if we open file in synchronous mode (without FILE_FLAG_OVERLAPPED flag) all operations with file is sequential - any new operation not begin execute until previous not finished.

in this case we have two options :

  1. we can specify that the current file position offset be used. this specification can be made by pass a NULL pointer for lpOverlapped
  2. we can reset this position by passing an explicit lpOverlapped value to ReadFile or WriteFile. Doing this automatically changes the current file position to that (Offset, OffsetHigh) value, performs the read(write) operation, and then updates the position according to the number of bytes actually read(write). This technique gives the caller atomic seek-and-read(write) service.

so by code we have 2 variants:

BOOL _ReadFile(HANDLE hFile, LARGE_INTEGER ByteOffset, PVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead)
{
#if 1
    OVERLAPPED ov = {};
    ov.Offset = ByteOffset.LowPart;
    ov.OffsetHigh = ByteOffset.HighPart;

    return ReadFile(hFile, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, &ov);
#else
    if (SetFilePointerEx(hFile, ByteOffset, 0, FILE_BEGIN))
    {
        return ReadFile(hFile, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, 0);
    }
    return FALSE;
#endif
}

of course atomic seek-and-read(write) much more effective compare using SetFilePointer[Ex] (additional API call to kernel).

if we open file in asynchronous mode (with FILE_FLAG_OVERLAPPED flag) - multiple read/write operations with file can executed at same time. in this case I/O Manager can not use the file position in FILE_OBJECT - because this undefined behavior here.

so we must always explicit pass lpOverlapped with valid offset. if we pass a NULL pointer for lpOverlapped - we got ERROR_INVALID_PARAMETER error

at the last but important. from MSDN (at ReadFile and WriteFile pages)

The system updates the OVERLAPPED offset before ReadFile (WriteFile) returns.

this is not true and mistake in documentation - you can check yourself that OVERLAPPED offset not updated after operation. may be mean that current file position (CurrentByteOffset in FILE_OBJECT) is updated. but again OVERLAPPED offset is not updated after operation