在 win32file.DeviceIoControl 上解压结构

Struct unpack on win32file.DeviceIoControl

我正在尝试理解和使用 win32file。我需要获取 USN 期刊,并且很难理解我在网上找到的代码片段。这是我找到的代码片段 -

format = 'qqqqqLLLLqqqqq'
length = struct.calcsize(format)
out_buffer = win32file.DeviceIoControl(volh, winioctlcon.FSCTL_GET_NTFS_VOLUME_DATA, None, length)
data = struct.unpack(format, out_buffer)

现在我对 C 及其结构感到很生疏。我现在所了解的是 format 是 96 字节缓冲区,它将从 DeviceIoControl

获得输出

所以我尝试将格式更改为 'QQQQQQQQQQQQQQQQQQQ' 看看会发生什么(因为我有点不知道实际会发生什么)结果我得到了一个更大的 out_buffer 这个时间。所以我想解压它 -

struct.unpack(format, out_buffer)

令我惊讶的是,我得到了 -

struct.error: unpack requires a string argument of length 152

所以我添加了另一个 'Q' 来增加大小并得到相同的结果。我不明白为什么 'qqqqqLLLLqqqqq' 有效而 'QQQQQQQQQQQQQQQQQQQ' 无效。所以我的问题是 -

向我指出资源也将是一个额外的好处,因为我需要在代码的基础上构建以阅读 USN 期刊,而且我不认为点击尝试会让我到任何地方

让我们把问题分解成更小的部分,一次一个。

  • win32file 模块是 [GitHub]: mhammond/pywin32 - Python for Windows (pywin32) Extensions 的一部分,它是 Python 的包装器WinAPIs

  • DeviceIoControl 行为不同,取决于 dwIoControlCode (2nd 争论)。对于 FSCTL_GET_NTFS_VOLUME_DATA,它用特定于卷的数据填充缓冲区。来自 [MS.Docs]: FSCTL_GET_NTFS_VOLUME_DATA IOCTL:

    lpOutBuffer
    A pointer to the output buffer, an NTFS_VOLUME_DATA_BUFFER (@CristiFati: !!! Broken URL !!!) structure. The file record associated with the file identifier specified in the input buffer is returned in this buffer. Refer to the Remarks section of the documentation for the NTFS_VOLUME_DATA_BUFFER structure for specific information on how to determine the correct size of this buffer.

    这里有一个替代上述损坏的URL[MSDN]: NTFS_VOLUME_DATA_BUFFER structure。由于我不确定它的有效期有多长,我将结构定义粘贴到下面(来自 Windows Kits 8.1winioctl.h(行#4987)):

    typedef struct {
    
        LARGE_INTEGER VolumeSerialNumber;
        LARGE_INTEGER NumberSectors;
        LARGE_INTEGER TotalClusters;
        LARGE_INTEGER FreeClusters;
        LARGE_INTEGER TotalReserved;
        DWORD BytesPerSector;
        DWORD BytesPerCluster;
        DWORD BytesPerFileRecordSegment;
        DWORD ClustersPerFileRecordSegment;
        LARGE_INTEGER MftValidDataLength;
        LARGE_INTEGER MftStartLcn;
        LARGE_INTEGER Mft2StartLcn;
        LARGE_INTEGER MftZoneStart;
        LARGE_INTEGER MftZoneEnd;
    
    } NTFS_VOLUME_DATA_BUFFER, *PNTFS_VOLUME_DATA_BUFFER;
    
  • [Python 3.Docs]: struct - Interpret bytes as packed binary data module, is used for conversions between binary and "normal" data. It contains all the format characters meanings (q, Q, L, ...), and much more. You could also take a look at [SO]: Python struct.pack() behavior更多(实用)细节

看完上面的材料,事情应该变得更清楚了。

几个注意事项:

  • 如果不知道函数的作用 (returns),他们可能不应该使用它(当然不阅读手册)。尽管如今,Win(对普通用户总是有很多限制)和 Nix "protect users from themselves"(例如:root 不再允许登录,写保护 %SystemDrive%, ...)
  • 尝试(试错)显示出一些经验不足(可能每个人都在某个时候做过,关键是不要完全依赖它)
  • "每次我想从 DeviceIoControl 中获取某些内容时,我是否必须记住这些格式?"?
    • 同样,如果不知道一个函数的作用,那么调用它的原因是什么?如果你的意思是背诵NTFS_VOLUME_DATA_BUFFER,那肯定不是这样的。只有在使用它时你才应该知道它的结构(正如你所注意到的,你可以从一些地方得到它 - 包括这个post :))
  • "我的理解是如果缓冲区大于输出我们可以解压,那么为什么解压不起作用?"
    • 您的理解是正确的。但是 win32file.DeviceIoControl 似乎 有时 (可能在达到 1st NULL96 字节之后)在传递大于预期值的值时截断输出缓冲区(通过 长度 参数)。通过较小的时,它会失败(如预期的那样)

我还准备了一个虚拟的Python例子。

code00.py:

#!/usr/bin/env python3

import sys
import struct
import win32file
import win32api
import win32con
import winioctlcon


VOLUME_LETTER = "E"

FILE_READ_ATTRIBUTES = 0x0080
FILE_EXECUTE = 0x0020

vol_data_buf_fmt = "qqqqqLLLLqqqqq"  # This is the format that matches NTFS_VOLUME_DATA_BUFFER definition (96 bytes). Note: Instead of each 'q' you could also use 'Ll' as 'LARGE_INTEGER' is an union

BINARY_FORMAT_LIST = [
    vol_data_buf_fmt,
    "QQQQQQQQQQQQQQQQQQQ",
]


def print_formats():  # Dummy func
    print("Formats and lengths:")
    for format in BINARY_FORMAT_LIST:
        print("    {:s}: {:d}".format(format, struct.calcsize(format)))


def main():
    #print_formats()
    vol_unc_name = "\\.\{:s}:".format(VOLUME_LETTER)
    print("volume: ", vol_unc_name)
    access_flags = FILE_READ_ATTRIBUTES | FILE_EXECUTE  # Apparently, doesn't work without FILE_EXECUTE
    share_flags = win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE  # Doesn't work withou FILE_SHARE_WRITE
    creation_flags = win32con.OPEN_EXISTING
    attributes_flags = win32con.FILE_ATTRIBUTE_NORMAL
    vol_handle = win32file.CreateFile(vol_unc_name, access_flags, share_flags, None, creation_flags, attributes_flags, None)

    buf_len = struct.calcsize(vol_data_buf_fmt)
    for i in [buf_len]:
        print("    Passing a buffer size of: {:d}".format(i))
        buf = win32file.DeviceIoControl(vol_handle, winioctlcon.FSCTL_GET_NTFS_VOLUME_DATA, None, i)
        print("    DeviceIocontrol returned a {:d} bytes long {:}".format(len(buf), type(buf)))
        out = struct.unpack_from(vol_data_buf_fmt, buf)
        print("\n    NumberSectors: {:}\n    TotalClusters: {:}\n    BytesPerCluster: {:}".format(out[1], out[2], out[6]))
    win32api.CloseHandle(vol_handle)


if __name__ == "__main__":
    print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
    main()

输出:

(py35x64_test) e:\Work\Dev\Whosebug\q053318932>"e:\Work\Dev\VEnvs\py35x64_test\Scripts\python.exe" ./code00.py
Python 3.5.4 (v3.5.4:3f56838, Aug  8 2017, 02:17:05) [MSC v.1900 64 bit (AMD64)] on win32

volume:  \.\E:
    Passing a buffer size of: 96
    DeviceIocontrol returned a 96 bytes long <class 'bytes'>

    NumberSectors: 494374911
    TotalClusters: 61796863
    BytesPerCluster: 4096

不用说,将 TotalClusters 乘以 BytesPerCluster,我得到了正确的字节数(由 Win 报告) 我的 E: 驱动器。