1

我正在尝试理解和使用 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”无效。所以我的问题是——

  • 我的理解是,如果缓冲区大于输出,我们可以解包,那么为什么解包不起作用?

  • 每次我想从 DeviceIoControl 中获取一些东西时,我是否必须记住这些格式?

向我指出资源也是一个额外的好处,因为我需要在代码的基础上阅读 USN 期刊,而且我认为 hit-and-try 不会让我到任何地方

4

1 回答 1

3

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

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

  • DeviceIoControl的行为不同,具体取决于dwIoControlCode第二个参数)。对于FSCTL_GET_NTFS_VOLUME_DATA,它用特定于卷的数据填充缓冲区。来自[MS.Docs]: FSCTL_GET_NTFS_VOLUME_DATA IOCTL

    lpOutBuffer
    一个指向输出缓冲区的指针,一个NTFS_VOLUME_DATA_BUFFER (@CristiFati: !!! Broken URL !!! ) 结构。与输入缓冲区中指定的文件标识符关联的文件记录在此缓冲区中返回。有关如何确定此缓冲区的正确大小的具体信息,请参阅NTFS_VOLUME_DATA_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 - 将字节解释为打包的二进制数据模块,用于二进制和“普通”数据之间的转换。它包含所有格式字符的含义(qQL、...)等等。您还可以查看[SO]: Python struct.pack() 行为了解更多(实用)细节

看完以上材料,事情应该就清楚了。

几点注意事项:

  • 如果一个人不知道函数的作用(返回),他们可能不应该使用它(当然,不阅读手册)。虽然现在,Win(对普通用户总是有很多限制)和Nix “保护用户免受他们自己”(例如:不再允许root登录,写保护%SystemDrive%,...)
  • 尝试(试错)表明缺乏经验(可能每个人都在某个时候这样做,关键是不要仅仅依赖它)
  • 每次我想从 DeviceIoControl 中获取一些东西时,我是否必须记住这些格式”?
    • 再说一次,如果不知道一个函数是做什么的,那么调用它的原因是什么?如果您的意思是背诵 NTFS_VOLUME_DATA_BUFFER,那绝对不是这样。您应该仅在使用它时才知道它的结构(并且您已经注意到,您可以从一些地方获得它 - 包括这篇文章 :))
  • 我的理解是,如果缓冲区大于输出,我们可以解包,那么为什么解包不起作用?
    • 你的理解是正确的。但是win32file.DeviceIoControl似乎有时(可能在96个字节后达到第一个 NULL时)在传递大于预期值的值(通过长度参数)时截断输出缓冲区。当传递一个较小的时,它会失败(如预期的那样)

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

代码00.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\StackOverflow\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 ,我得到了E:驱动器的正确字节数(由Win报告) 。

于 2018-11-17T01:30:48.597 回答