在 Windows 上使用 CreateFileW 在物理磁盘上运行 Freepascal 散列函数

Posted

技术标签:

【中文标题】在 Windows 上使用 CreateFileW 在物理磁盘上运行 Freepascal 散列函数【英文标题】:Running Freepascal hashing functions over Physical Disks using CreateFileW on Windows 【发布时间】:2014-06-11 19:56:52 【问题描述】:

我使用 Lazarus 1.2.2 和 Freepascal 2.6.4。

我有一个名为 QuickHash 的程序,它可以对文件进行哈希处理,当我在 Linux 上运行它时,它也可以用于对物理磁盘进行哈希处理 (/dev/sdXX)。但是,我想在 Windows 版本中添加该功能。

我认为要访问物理设备(如磁盘),必须使用CreateFile。具体来说,CreateFileW。

因此,用户单击一个按钮,该按钮会扫描计算机中的磁盘并将它们列在列表框中。然后将用户双击的字符串解析 (ListBox.GetSelectedText) 为字符串 '\.\PhyscialDiskX' 并将其分配给字符串变量,

  strDiskID := getDiskID(Listbox.GetSelectedText);

效果很好。

然后我尝试创建该磁盘的句柄:

    hSelectedDisk := Windows.CreateFileW(PWideChar(strDiskID), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING,  FILE_FLAG_RANDOM_ACCESS, 0);

基于this article,特别是“您必须同时使用 CreateFile() FILE_SHARE_READ 和 FILE_SHARE_WRITE 标志才能访问驱动器”我还尝试了以下其他两种组合:

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);

三个都成功分配了句柄。但是,顶部语法和底部语法最终会产生错误(解释如下)。中间选项立即返回零字节文件的默认初始化哈希,即 DA39... 用于 SHA1。

我的问题是我无法将该句柄(它是一个整数)传递给 Freepascal md5 和 SHA1 单元的 SHA1File 和 MD5FILE 函数。他们期望一个文件名,它必须是一个字符串。

因此,如果我将 strDiskID ('\.\PhyscialDiskX') 传递给它(这完全破坏了分配句柄的对象),我确实会获得磁盘活动并且程序似乎正在运行。

strDiskHashValue := SHA1Print(SHA1File(strDiskID));
ShowMessage(strDiskHashValue);
CloseHandle(hSelectedDisk);

但即使在非常小的磁盘上运行,如 500Mb USB 驱动器,也需要很多分钟并最终返回“运行错误 1117”,根据this 的意思是

"ERROR_IO_DEVICE
1117 (0x45D)
The request could not be performed because of an I/O device error."

但是,我已经在几个工作磁盘上尝试过,但错误仍然存​​在。

所以,我的问题归根结底是如何将成功分配的 THandle 传递给散列函数? (UI 也在Lazarus forums 上问过这个问题,但有时我会从那里没有看到线程的成员那里得到答案)

【问题讨论】:

【参考方案1】:

您将无法将音量句柄传递给不需要音量句柄的函数。这些是非常特殊的手柄,对它们的使用要求非常严格。其中最重要的一点是您必须读取扇区对齐的块,并且其大小是扇区大小的倍数。

所以解决方案是让您负责读取数据。将其读入缓冲区,然后将该缓冲区传递给散列库。这意味着您需要一个可以重复调用的哈希库来处理新数据。所有综合散列库都将提供这样的功能。

共享模式标志与位或组合:

FILE_SHARE_READ or FILE_SHARE_WRITE

我会这样创建句柄:

hSelectedDisk := CreateFileW(PWideChar(strDiskID), FILE_READ_DATA,
  FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);

首先,我会专注于阅读本书的内容。一旦你能做到这一点,散列将成为常规。


从 cmets 看来,您在编写散列代码时遇到了一些麻烦。首先,您需要分配一个扇区大小倍数的缓冲区:

var
  Buffer: Pointer;
....
GetMem(Buffer, BufferSize);

使用IOCTL_DISK_GET_DRIVE_GEOMETRY 找出扇区大小。并记下文档中的这段文字:

要读取或写入卷的最后几个扇区,您必须调用 DeviceIoControl 并指定 FSCTL_ALLOW_EXTENDED_DASD_IO。这指示文件系统驱动程序不要对分区读取或写入调用执行任何 I/O 边界检查。相反,边界检查由设备驱动程序执行。

现在您有了缓冲区,您可以读取内容并对其进行哈希处理。

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);

我没有尝试编译或测试此代码。它并不意味着完整或全面。它只是为了向您展示如何解决问题。

【讨论】:

嗨,大卫。我非常感谢你的帮助。当你说“你需要一个可以重复调用的散列库来处理新数据。所有综合散列库都将提供这样的功能”时,如果 Freepascal 中内置的 md5 和 sha1 单元不是这样的例子,你知道吗?一个是?我也将 DCPCrypt 用于 SHA256 和 SHA512,但不确定它是否提供您所指的内容。 我不熟悉 FPC 哈希库。关键是想要散列不适合主内存的文件是很常见的。例如,在 32 位进程中,如何散列一个 8GB 的​​文件?您不能一次将其全部加载到内存中。所以你要做的是读取一个块,传递给哈希器。读取另一个块,传递给哈希器。散列器保持散列的运行状态。我确信任何好的和有用的散列库都会提供这样的接口。 嗨,大卫。那么 FPC 哈希函数可以做到这一点。如果我将一个 20Gb 的大文件传递给它,它会很好地散列它。我收集它实际上以 1024 字节块读取数据。如果我使用我的程序的 Linux 版本并选择 /dev/ 设备,如果以 root 身份运行,它将散列磁盘。但是对于 Windows,我不能这样做,因为在 Windows 中,您无法将物理磁盘作为文件传递给 SHA1File 或 MD5File。 在内部,库将使用我描述的机制。不公开该机制是不寻常的。你确定不是吗?我在哪里可以找到这些库的来源以进行检查? 我的机器上有这些文件。和我想的一样。您需要SHA1InitSHA1Update(反复调用)然后SHA1Final 才能完成工作。 MD5 完全相同,但使用 MD5xxx 而不是 SHA1xxx。阅读SHA1File 是如何实现的。

以上是关于在 Windows 上使用 CreateFileW 在物理磁盘上运行 Freepascal 散列函数的主要内容,如果未能解决你的问题,请参考以下文章

无法在 Windows 中以编程方式 (C++) 从其他用户帐户读取文件

用于跟踪 CreateFile 调用的 Pin 工具

RING3到RING0的函数跟踪

006 异步IO操作

Intel pin:检测运行过程

C++ 下载文件 WinInet - 0kb 写入文件