I use Lazarus 1.2.2 and Freepascal 2.6.4.
I have a program called QuickHash that hashes files and, when I run it on Linux, it can be used to hash physcial disks too (/dev/sdXX). However, I'd like to add the ability to the Windows version.
I gather that to access physcial devices, like disks, one has to use CreateFile. Specifically, CreateFileW.
So, the user clicks a button which scans the computer for disks and lists them in a List Box. The one the user double clicks is then parsed (ListBox.GetSelectedText) for the string '\.\PhyscialDiskX' and that is assigned to a string variable,
strDiskID := getDiskID(Listbox.GetSelectedText);
That works fine.
I then try to create a handle to that disk :
hSelectedDisk := Windows.CreateFileW(PWideChar(strDiskID), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, 0);
Based on this article, specifcally "You must use both the CreateFile() FILE_SHARE_READ and FILE_SHARE_WRITE flags to gain access to the drive" I have also tried the other two combinations below :
hSelectedDisk := CreateFileW(PWideChar(strDiskID), GENERIC_READ, FILE_SHARE_READ AND FILE_SHARE_WRITE, nil, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, 0);
hSelectedDisk := CreateFileW(PWideChar(strDiskID), GENERIC_READ, FILE_SHARE_READ OR FILE_SHARE_WRITE, nil, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, 0);
All three successfully assign a handle. But, the top syntax and the bottom syntax eventually generate an error (explained below). The middle option immediately returns the default initialisation hash for a zero byte file, i.e. DA39... for SHA1.
My problem is I am unable to pass that handle (which is an integer) to the SHA1File and MD5FILE functions of the Freepascal md5 and SHA1 units. They expect a filename, which has to be a string.
So, if I pass it the strDiskID ('\.\PhyscialDiskX') (which defeats the objects of assigning a handle at all) I do get disk activity and the program appears to be working.
strDiskHashValue := SHA1Print(SHA1File(strDiskID));
ShowMessage(strDiskHashValue);
CloseHandle(hSelectedDisk);
But even when run on really small disks like a 500Mb USB drive, it takes many minutes and eventually returns "Run Error 1117" which according to this means
"ERROR_IO_DEVICE
1117 (0x45D)
The request could not be performed because of an I/O device error."
However, I have tried it on several working disks and the error continues.
So, my question, ultimately, is how on earth do I pass that successfully assigned THandle to the hashing functions? (UI have also asked the question at the Lazarus forums but sometimes I get answers here from members who don't see the threads there)
You aren't going to be able to pass volume handles to functions that aren't expecting volume handles. These are very special handles with quite stringent requirements on their use. Not the least of which is that you must read blocks that are sector aligned, and whose sizes are multiples of the sector size.
So the solution is for you to take charge of reading the data. Read it into a buffer, and then pass that buffer to the hashing library. This means that you'll need a hashing library that can be called repeatedly to process new data. All comprehensive hashing libraries will offer such functionality.
The share mode flags are combined with bitwise or:
FILE_SHARE_READ or FILE_SHARE_WRITE
I would create the handle like this:
hSelectedDisk := CreateFileW(PWideChar(strDiskID), FILE_READ_DATA,
FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
First of all I would concentrate on reading the content of the volume. Once you can do that, hashing will be routine.
It seems from the comments that you are having some trouble writing the hashing code. First of all you need to allocate a buffer that is a multiple of the sector size:
var
Buffer: Pointer;
....
GetMem(Buffer, BufferSize);
Use IOCTL_DISK_GET_DRIVE_GEOMETRY to find out the sector size. And take note of this text from the documentation:
To read or write to the last few sectors of the volume, you must call DeviceIoControl and specify FSCTL_ALLOW_EXTENDED_DASD_IO. This signals the file system driver not to perform any I/O boundary checks on partition read or write calls. Instead, boundary checks are performed by the device driver.
Now that you have a buffer, you can read and hash the content.
var
ctx: TSHA1Context;
Digest: TSHA1Digest;
BytesRead: DWORD;
....
SHA1Init(ctx);
repeat
if not ReadFile(hSelectedDisk, Buffer^, BufferSize, BytesRead, nil) then
// handle error, raise exception
SHA1Update(ctx, Buffer^, BytesRead);
until BytesRead < BufferSize;
SHA1Final(ctx, Digest);
I've not attempted to compile or test this code. It's not meant to be complete or comprehensive. It is just intended to show you how to tackle the problem.
Related
How do i write to a physical drive in Windows 7?
I am trying to write to a physical disk (e.g. \\.\PhysicalDrive0) in Windows 7.
This question has been asked to death, but has never been answered. It is something that used to work in Windows XP, but Microsoft intentionally broke in Windows Vista. Microsoft provides hints about how to do it, but nobody has even been able to figure it out.
It used to work
In the olden days, writing to a physical disk was allowed (as long as you were an administrator). The method to do it was even documented in a Knowledge Base article:
INFO: Direct Drive Access Under Win32
To open a physical hard drive for direct disk access (raw I/O) in a Win32-based application, use a device name of the form
\\.\PhysicalDriveN
where N is 0, 1, 2, and so forth, representing each of the physical drives in the system.
You can open a physical or logical drive using the CreateFile() application programming interface (API) with these device names provided that you have the appropriate access rights to the drive (that is, you must be an administrator). You must use both the CreateFile() FILE_SHARE_READ and FILE_SHARE_WRITE flags to gain access to the drive.
All that changed in Windows Vista, when addition security restrictions were put in place.
How do you write to a physical disk?
Many people, and many answers, on many stackoverflow questions confuse:
writing to a physical disk (e.g. \\.\PhysicalDrive0), and
writing to a logical volume (e.g. \\.\C$)
Microsoft notes the restrictions placed on both kinds of operations:
Blocking Direct Write Operations to Volumes and Disks
Write operations on a DASD (Direct access storage device) volume handle will succeed if:
the file system is not mounted, or if
The sectors being written to are the boot sectors.
The sectors being written to reside outside file system space.
The file system has been locked implicitly by requesting exclusive write access.
The file system has been locked explicitly by sending down a lock/dismount request.
The write request has been flagged by a kernel-mode driver that indicates that this check should be bypassed. The flag is called SL_FORCE_DIRECT_WRITE and it is in the IrpSp->flags field. This flag is checked by both the file system and storage drivers.
In my case i am asking about writing to a Physical, not a Logical one. Microsoft notes the new set of restrictions on writing to a physical disk handle:
Write operations on a disk handle will succeed if:
The sectors being written to do not fall within a file system.
The sectors being written to fall within a mounted file system that is locked explicitly.
The sectors being written to fall within a file system that is not mounted or the volume has no file system.
My sectors being written do fall within a file system --> fail
My sectors being written do fall within mounted, unlocked, file system --> fail
My sectors being written do fall within a file system that is mounted, and in inside a logical volume that has a file system.
The hints on how to make it work revolve around:
unmounting a file system
locking a file system
But the question is how do you unmount a file system? How do you lock a file system?
What are you doing now?
I am able to read all physical sectors of a disk; that is no problem. The problem is when i want to write to a physical sector of the disk.
The current code i have is, in pseudo-code:
void ZeroSector(Int64 PhysicalSectorNumber)
{
String diskName := '\\.\PhysicalDrive0';
DWORD desiredAccess := GENERIC_READ or GENERIC_WRITE;
//INFO: Direct Drive Access Under Win32
//https://support.microsoft.com/en-us/kb/100027
//says you nedd both
DWORD shareMode := FILE_SHARE_READ or FILE_SHARE_WRITE;
//Open the physical disk
hDisk := CreateFile(diskName, desiredAccess, shareMode,
nil, OPEN_EXISTING, 0, 0);
if hDisk = INVALID_HANDLE_VALUE
RaiseLastWin32Error();
try
{
Int32 bytesPerPhysicalSector := 4096; //Determined elsewhere using IOCTL_STORAGE_QUERY_PROPERTY+STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR
//Setup buffer for what we will be writing
Byte[] buffer = new Byte[bytesPerPhysicalSector];
//Calculate the byte offset of where the sector is
Int64 byteOffset = PhysicalSectorNumber * bytesPerPhysicalSector;
//Seek to that byte offset
SetFilePointer(hDisk, byteOffset.Lo, byteOffset.Hi, FILE_BEGIN);
//Write the buffer
DWORD numberOfBytesWritten;
if (!WriteFile(hDisk, buffer, bytesPerPhysicalSector, out numberOfBytesWritten, nil))
RaiseLastWin32Error();
}
finally
{
CloseHandle(hDisk);
}
}
Surprisingly:
i can open the physical disk for GENERIC_READ + GENERIC_WRITE access
it doesn't fail until the actual WriteFile, which fails with:
ERROR_ACCESS_DENIED
How to do what Microsoft says
Microsoft said that my write would fail, and they were right. They said that i need to explicitly lock the file system:
Write operations on a disk handle will succeed if:
The sectors being written to fall within a mounted file system that is locked explicitly.
Except i don't know how to do that.
I know i probably have to use DeviceIoControl and one of the IOCTLS to "lock" a volume. But that presents three challenges:
figuring out which volume(s) are on the physical disk selected
figuring out which IOCTL to use
figuring out how to unlock the locked volumes
Ignoring those problems, i blindly tried the LockFile API. Just before calling WriteFile:
//Try to lock the physical sector we will be writing
if (!LockFile(DiskHandle, byteOffset.Lo, byteOffset.Hi, bytesPerPhysicalSector, 0)
RaiseLastWin32Error();
That fails with:
ERROR_INVALID_FUNCTION (1)
Check out FSCTL_LOCK_VOLUME, FSCTL_DISMOUNT_VOLUME control codes. I believe you would have to enum all volumes you have on your disk, and then dismount-and-lock them. After lock succeeded, the disk is all yours.
You probably won't be able to do this on a system drive though. I'd also guess that there will be caveats with volumes that contain page files.
after I have done this, I can write to the corresponding \\.\PhysicalDrive3. I could not before:
HANDLE hVol = CreateFileA(
"\\\\.\\X:",
FILE_READ_DATA | FILE_WRITE_DATA,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
DWORD unused;
BOOL b = DeviceIoControl(hVol, FSCTL_DISMOUNT_VOLUME, NULL, 0, NULL, 0, &unused, NULL);
if (!b) {
printf("%u", GetLastError());
abort();
}
...
HANDLE h = CreateFileA(
argv[1], // that's my \\physicaldrive3
FILE_READ_DATA | FILE_WRITE_DATA,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0,
NULL);
...
bResult = WriteFile(h, buf, cb, &dwWritten, &foo);
if (!bResult) {
// used to fail without messing with vol handle
printf("Failed writing data. Error = %d.\n", GetLastError());
return 0;
}
I think I need some clarification on how to read from a named pipe and have it return immediately, data or not. What I am seeing is ReadFile fails, as expected, but GetLastError returns either ERROR_IO_PENDING or ERROR_PIPE_NOT_CONNECTED, and it does this until my surrounding code times out. I get these errors EVEN THOUGH THE DATA HAS IN FACT ARRIVED. I know this by checking my read buffer and seeing what I expect. And the pipe keeps working. I suspect I am not using the overlapped structure correctly, I'm just setting all fields to zero. My code looks like this:
gPipe = CreateFile(gPipename, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
pMode = PIPE_READMODE_MESSAGE;
bret = SetNamedPipeHandleState(gPipe, &pMode, NULL, NULL);
OVERLAPPED ol;
memset(&ol, 0, sizeof(OVERLAPPED));
// the following inside a loop that times out after a period
bret = ReadFile(gPipe, &tmostat, sizeof(TMO64STAT), NULL, &ol);
if (bret) break;
err = GetLastError();
// seeing err == ERROR_IO_PENDING or ERROR_PIPE_NOT_CONNECTED
So I can do what I want by ignoring the errors and checking for arrived data, but it troubles me. Any idea why I am getting this behavior?
Windows OVERLAPPED I/O doesn't work like the non-blocking flag on other OSes (For example on Linux, the closest equivalent is aio_*() API, not FIONBIO)
With OVERLAPPED I/O, the operation hasn't failed, it proceeds in the background. But you are never checking on it... you just try again. There's a queue of pending operations, you're always starting new ones, never checking on the old ones.
Fill in the hEvent field in the OVERLAPPED structure, and use it to detect when the operation completes. Then call GetOverlappedResult() to get the number of bytes actually transferred.
Another important note -- the OS owns the OVERLAPPED structure and the buffer until the operation completes, you must take care to make sure these stay valid and not deallocate them or use them for any other operation until you confirm that the first operation completed.
Note that there is an actual non-blocking mode for Win32 pipes, but Microsoft strongly recommends against using it:
The nonblocking-wait mode is supported for compatibility with Microsoft LAN Manager version 2.0. This mode should not be used to achieve overlapped input and output (I/O) with named pipes. Overlapped I/O should be used instead, because it enables time-consuming operations to run in the background after the function returns.
Named Pipe Type, Read, and Wait Modes
Supposedly it is possible to actually open and read directories on NTFS volumes. However, my code to try this wasn't working, so I tried google, which found me this.
The key observation there seems to be that you must use FILE_FLAG_BACKUP_SEMANTICS. So, trimming that down, I basically get:
HANDLE hFile = CreateFile(L"C:\\temp", GENERIC_READ, FILE_SHARE_READ,
0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
DWORD dwFileSize = GetFileSize(hFile, 0);
char* buf = new char[dwFileSize];
DWORD dwBytesRead = 0;
BOOL b = ReadFile(hFile, buf, dwFileSize, &dwBytesRead, 0);
Seems pretty straight-forward. Unfortunately, it doesn't work.
The CreateFile and GetFileSize both work (handle is not INVALID_HANDLE_VALUE, non-zero and plausible file size), but the ReadFile returns FALSE, dwBytesRead is zero, and GetLastError returns 1 ("Incorrect function"). Huh.
While I was typing this question, the 'Similar Questions' prompt showed me this. That business about using AdjustTokenPrivileges made a lot of sense. However, it didn't help. Adding ReadFile (and using c:\temp) to that example gives the same behavior. A closer reading of the CreateFile docs shows that even without the SE_BACKUP_NAME privilege, I should be able to open the file due to admin privileges.
I've tried a number of permutations:
Different ways of specifying the directory name (c:\temp, c:\temp\, \\.\c:\temp, \\?\c:\temp\, etc).
Different directories
Different drives
Different share options (0, FILE_SHARE_READ, FILE_SHARE_READ | FILE_SHARE_WRITE)
Different access permissions (GENERIC_READ, FILE_LIST_DIRECTORY, FILE_LIST_DIRECTORY + FILE_READ_EA + FILE_READ_ATTRIBUTES, FILE_LIST_DIRECTORY + FILE_READ_EA + FILE_READ_ATTRIBUTES + FILE_TRAVERSE)
I can't see any flags that might apply other than FILE_FLAG_BACKUP_SEMANTICS (which I assume is required), but I tried FILE_FLAG_NO_BUFFERING and a 4096 byte aligned buffer. Nope.
I'm (currently) trying 152 permutations, and none of the ReadFiles are working. What am I missing?
Is my original assumption here incorrect? Is it not really possible to 'read' from a directory? Or is there just some trick I'm still missing?
What else should I mention?
I'm running as an admin, and can do a CreateFile on the volume.
My program is 64bit, built for unicode.
Windows 7 x64
NTFS 3.1 volume
It's cloudy outside (Hey, you never know what might matter...)
If you want to open a stream then you need to include the stream name and/or type as part of the path:
c:\foo:bar A.K.A. c:\foo:bar:$DATA
c:\foo::$INDEX_ALLOCATION
The default $DATA stream is used if you don't specify a stream. $DATA stores a files "normal data".
If you want the list of files in the directory then you can use GetFileInformationByHandleEx(FileIdBothDirectoryInfo) (and NtQueryDirectoryFile on older systems).
It looks like Jonathan Potter has given the correct answer. Despite prompting, he has elected not to post his comments as an answer. So I'm going to create one based on his responses in order to close the question.
In short: "You can open a handle to a directory to do certain things, but calling ReadFile on it isn't one of them."
What things? These things. This list includes:
BackupRead
BackupSeek
BackupWrite
GetFileInformationByHandle
GetFileSize
GetFileTime
GetFileType
ReadDirectoryChangesW
SetFileTime
In summary: While you can "open" directories and "read" certain information about them, you can't actually use ReadFile. If you want to read the DirName::$INDEX_ALLOCATION information, you'll have to use a different approach.
Starting with the ndisprot sample from Microsoft I try to write a NDIS protocol driver. From User space I try to read and write to the device simultaneous (out of two threads). Since I don't receive any packets, the ReadFile system call blocks. I'm not able to complete a WriteFile system call in this state.
CHAR NdisProtDevice[] = "\\\\.\\\\NDISprot";
CHAR * pNdisProtDevice = &NdisProtDevice[0];
this.iHandle = CreateFile(pNdisProtDevice,
GENERIC_WRITE | GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
// Blocks, because no frames arrive
bSuccess = (BOOLEAN)ReadFile(Handle,
(LPVOID)pReadBuf,
PacketLength,
&BytesRead,
NULL);
...
// Called some seconds later from another thread, while ReadFile still blocking...
bSuccess = (BOOLEAN)WriteFile(Handle,
pWriteBuf,
PacketLength,
&BytesWritten,
NULL);
I added some debug messages and discovered that the driver function associated with IRP_MJ_WRITE (NdisprotWrite) gets not even called! Something between the user space application and the driver blocks concurrent access to the device \Device\NDISprot.
How can I concurrent Read and Write to the file?
By default, you can only have one outstanding I/O request per usermode handle. Either open multiple handles, or open your one handle with FILE_FLAG_OVERLAPPED. (Once you use FILE_FLAG_OVERLAPPED, you also generally need to use OVERLAPPED structures - make sure you've got the gist of it by skimming this and this.)
I'm trying to call IOCTL_BTH_GET_LOCAL_INFO using DeviceIoControl, which I believe it can be done (accordingly to Bluetooth Profile Driver IOCTLs).
I'm on a Windows 7 x64 using Visual Studio 2012 (probably with default configuration).
The handle have a valid value (I removed the validation code) but DeviceIoControl always returns ERROR_INVALID_USER_BUFFER (error 1784).
Here's the code:
int main() {
BTH_LOCAL_RADIO_INFO buffer;
BOOL fStatus;
HANDLE h;
DWORD returned = 0;
h = CreateFile(
TEXT("\\\\.\\BthPan"),
GENERIC_READ | GENERIC_WRITE ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0,
NULL);
fStatus = DeviceIoControl(
h,
IOCTL_BTH_GET_LOCAL_INFO,
NULL, 0,
(LPVOID)&buffer, sizeof(BTH_LOCAL_RADIO_INFO),
&returned,
(LPOVERLAPPED) NULL
);
(...)
After some research I tried the following solutions:
Changing the structure pack alignment to 1/4/8 byte (with VS options);
Using values which are 8-byte aligned (later I've found out that
this was already happening, even with data types smaller than 8 bytes). After a while I've read somewhere that DeviceIoControl deals with misaligment for you, so probably no need to worry about that.
All of the solutions above have failed. What do you think it is? VS have a bunch of configurations for Win32, but that never gave me a problem before (first time with IOCTL though).
I've seen some of that code on 32feet.NET, so probably it's just an error of mine (I can't see any difference).
You're sending IOCTL_BTH_GET_LOCAL_INFO to the wrong device (Bluetooth Personal Area Network instead of Bluetooth Radio).
So I suggest you to use BluetoothFindFirstRadio, BluetoothFindNextRadio and BluetoothFindRadioClose to simply iterate through local Bluetooth radios, rather than to guess the correct DOS Device Names for them.