How to access data on an unmounted partition

Part 2

2. Retrieving the drive parameters

One has to declare:

Declare Function DeviceIoControl Lib "kernel32" (hDevice AS INTEGER, dwIoControlCode AS INTEGER, lpInBuffer AS PTR, nInBufferSize AS INTEGER, lpOutBuffer AS PTR, nOutBufferSize AS INTEGER,  ByRef lpBytesReturned AS INTEGER, lpOverlapped AS PTR) AS BOOLEAN 

The necessary DeviceIoControl command, DIM IOCTL_DISK_GET_DRIVE_GEOMETRY, can be constructed (mimicking a table structure it is defined in)

DIM IOCTL_DISK_GET_DRIVE_GEOMETRY AS  INT64 
IOCTL_DISK_GET_DRIVE_GEOMETRY = &h00000007
IOCTL_DISK_GET_DRIVE_GEOMETRY = bitwise.ShiftLeft(IOCTL_DISK_GET_DRIVE_GEOMETRY, 16)

This particular call will only use an output-buffer, for returning the pdg-structure.

DIM bResult AS BOOLEAN
DIM pdg AS New MemoryBlock(24)   // will contain the drive-geometry structure after call

DIM NumReturned AS INTEGER       // should be 24 after call

DIM Cylinders AS INT64
DIM MediaType AS INTEGER
DIM TracksPerCylinder AS INTEGER
DIM SectorsPerTrack AS INTEGER
DIM BytesPerSector AS INTEGER

DIM DiskSize AS INT64     // calculate it afterwards, for control purposes only

bResult = DeviceIoControl(hDisk, IOCTL_DISK_GET_DRIVE_GEOMETRY, PTR(0), 0, pdg,  24,  numReturned, PTR(0))

Cylinders = pdg.Long(0)
Media_Type = pdg.Long(8)
TracksPerCylinder = pdg.Long(12)
SectorsPerTrack = pdg.Long(16)
BytesPerSector = pdg.Long(20)
     
DiskSize = Cylinders * TracksPerCylinder * SectorsPerTrack * BytesPerSector   // should give disksize, in BYTES

Note: Not only phyical hard drives, but also USB memorysticks etc. will mimick this pdg-structure, although
internally they have a different structure. But one still needs BytesPerSector for a proper read- or write- call later.

Part 3.

3. Read from the disk (or file) with handle hDisk

Very simplified, one can only read from a disk that is not recognized as valid Windows disk (with or without partitions),
after retreaving the file handle with a different “open” parameter AND with Administrator privilige:

CONST GENERIC_READ = &h80000000
 
hDisk = CreateFileA("\\\\.\\PhysicalDrive1", GENERIC_READ, (FILE_SHARE_READ OR FILE_SHARE_WRITE), NIL, OPEN_EXISTING, 0, NullHandleTemplateFile)

Note: the combination (FILE_SHARE_READ OR FILE_SHARE_WRITE) seems the only combination working;
if NOT run as administrator, the result will be INVALID_HANDLE (= -1)

Now create a buffer for reading. The number of bytes to read (of write) must be a positive INTEGER multiple of BytesPerSector.

Example: choose buffersize = 1024 (= 2 * 512), so NumToRead must be 1024 then.

DIM NumToRead     AS INTEGER
DIM NumActualRead AS INTEGER
DIM lpBuffer      AS MemoryBlock(NumToRead)

Note: for a very large blocksize, Xojo.MemoryBlock() should be used instead

The ReadFile function is declared:

Declare Function ReadFile Lib "kernel32" (hFile AS INTEGER, lpBuffer AS PTR, nNumberOfBytesToRead AS INTEGER, byRef lpNumberOfBytesRead AS INTEGER, lpOverlapped AS PTR) AS Boolean

Now reading is possible. The lpOverlapped -pointer is not used, so use NIL.

DIM bResult AS BOOLEAN

bResult = ReadFile(hDisk, lpBuffer, NumToRead, NumActualRead, NIL)  

This will read NumToRead bytes from the current position on the disk c.q. file (e.g. 0, when starting). If succesfull, succeeding reads start at positions NumToRead, 2*NumToRead etc. NumActualRead will give the number of bytes that could be read (possibly partially, e.g. if arrived at end of disk/file).

If reading failed for some reason, one can inquire the system by using the function GetLastError:

Declare Function GetLastError Lib "kernel32" AS INTEGER

It will return an errornumber that can be searched for in MSDN; errornumber 87 points to “invalid parameter”,
most likely meaning that NumToRead was NOT an integer multiple of BytesPerSector

To make it easier to start reading from a specified position in the drive/file, one can use the function SetFilePointer.

Declare Function SetfilePointer(hDisk AS INTEGER, lDistanceToMoveLOW AS INTEGER, lpDistanceToMoveHIGH AS PTR, dwMoveMethod AS INTEGER)  AS INTEGER

For not too large, non-compressed, (<= 2GB) disks/files generally only lDistanceToMoveLOW is used (non-zero).
For large disks/files a variable must be created to hold the high-order part of the offset.

dwMoveMethod can hold one of three values: File_Begin (=0): relative to start of disk/file, File_Current (=1): rel. to current position in file, File_End (=2): (backwards from) end of file. The specified offset may be negative (with File_Current).

The function will return the actual position in the disk/file after repositioning the pointer.

After ending all read-actions, the disk/file should be closed, by using CloseHandle(hDisk), declared earlier in this post.

(
4. Writing to disk ??"

This should be possible, without to much complexity, for disks that do not contain recognized (Windows) partitions etc.
The device has to be opened with CreateFileA with specific parameters that allow writing. One then uses WriteFile().
In some situations this “raw” access might be useful, e.g. if you use that drive exclusively for your own data. I have not tried this.
Of course the program must run with priviliges as Administrator.

The IBored program from Thomas Tempelmann might work in some situations, but he doesn’t guarantee that for the Windows version.

For accessing already existing partitions one would use the standard functions for fileaccess on the filesystems instead, to prevent accidental data-loss or even damage to the filesystem itself.

)

Thanks for sharing the code. Had you made it clear that you needed the code for Windows and not just for Linux, I could have shared it with you, as I do quite much the same in iBored.

Thanks, Thomas.

My original plan was to use a Linux-version, as it should have been able to access a drive with an ufs-like filesystem on BSD.
Unfortunately, as the drive does seem to use a rather customized version of filesystem, this would not have given much.

So I decided, after exploiting some info from the drive with your nice IBored program, that access from Windows would do in
this situation (I have more free space on the Windows system). Direct access for a healthy drive bypasses the need for a large extra, intermediate, diskimage file.

   It would be interesting if you could share your code for retreiving the disk-handle for a disk in Linux. The info I could find for Linux always seemed to need an already [i]existing[/i] handle ...

Interestingly, the existing C-code from mr. Leecher, for older versions of the recorders, compiles also with the OpenWatcom compiler, without errors. With the new knowledge reguarding the disk handle for disks, it is easy to adapt it to enable direct disk access, in the case of healthy drives. But, as the implementation details of the registration process on the disks seem to have been adapted considerably, I will have to test it in the (Watcom-) debugger before even trying a longer run, to avoid crazy/huge output …

Of course I am testing on my new drive, with only a few programs recorded, for easier (?) interpretation of the results.

As I wrote last year, opening a disk on Linux is like opening a file. And just like on macOS:

[code] declare function open lib “libc.so” (path as CString, flags as Integer) As Integer

dim devIdx) as Integer = 0 // use the drive number you want to open, e.g 0 for "sda"
dim devPath as String = "/dev/sd"+Chr(Asc("a")+devIdx)

const O_RDONLY = 0
const O_RDWR = 2
const O_SHLOCK = &h10

self.fd = open (devPath, RDONLY)
self.devIsOpen = self.fd <> -1[/code]

Also, take a look at http://kaitai.io - that helps a lot with analysing such structures, and I used it a lot for understanding how APFS works.

And here is the code to get the disk attributes on Linux, for 32 bit builds:

[code] declare function ioctl Lib “libc.so” (dev as Integer, request as Integer, arg as Ptr) as Integer

// from: include/linux/fs.h
//   #define BLKGETSIZE _IO(0x12,96) /* return device size /512 (long *arg) */
//   #define BLKSSZGET  _IO(0x12,104)/* get block device sector size */
//   #define BLKBSZGET  _IOR(0x12,112,size_t)
//   #define BLKBSZSET  _IOW(0x12,113,size_t)
//   #define BLKGETSIZE64 _IOR(0x12,114,size_t)      /* return device size in bytes (u64 *arg) */
//
//   #define IOCPARM_MASK    0x7f            /* parameters must be < 128 bytes */
//   #define IOC_VOID        0x20000000      /* no parameters */
//   #define IOC_OUT         0x40000000      /* copy out parameters */
//   #define IOC_IN          0x80000000      /* copy in parameters */
//   #define IOC_INOUT       (IOC_IN|IOC_OUT)
//   #define _IO(x,y)        (IOC_VOID|(x<<8)|y)
//   #define _IOR(x,y,t)     (IOC_OUT|((sizeof(t)&IOCPARM_MASK)<<16)|(x<<8)|y)
//   #define _IOW(x,y,t)     (IOC_IN|((sizeof(t)&IOCPARM_MASK)<<16)|(x<<8)|y)
//   #define _IOWR(x,y,t)    (IOC_INOUT|((sizeof(t)&IOCPARM_MASK)<<16)|(x<<8)|y)

DebugLog "disk geom "+devPath+"..."

dim res as Integer
dim mb as new MemoryBlock (8)

res = ioctl (me.fd, &H1200+104, mb)
bsize = mb.UInt32Value(0)

res = ioctl (me.fd, &h80041272, mb)
if res = 0 then
  blocks = mb.UInt64Value(0) \\ bsize
  DebugLog "  ioctl_114: bsize "+Str(bsize)+", total 0x"+Hex(mb.UInt64Value(0))+", -> "+Str(blocks)
else
  res = ioctl (me.fd, &H1200+96, mb)
  blocks = mb.UInt32Value(0) \\ (bsize \\ 512)
  DebugLog "  ioctl_96: bsize "+Str(bsize)+", total "+Str(mb.UInt32Value(0))+", -> "+Str(blocks)
end[/code]

Very much appreciated, Thomas !!

Just enough time between lots of ADSL-interruptions for this
quick response.