【问题标题】:Struct unpack on win32file.DeviceIoControl在 win32file.DeviceIoControl 上解压结构
【发布时间】:2019-04-18 12:36:01
【问题描述】:

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

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 不会让我有任何收获

【问题讨论】:

  • volh 的创建过程是什么样的(CreateFile 调用)?

标签: python struct pywin32 deviceiocontrol


【解决方案1】:

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

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

    上的 Python 包装器
  • DeviceIoControl 行为不同,具体取决于 dwIoControlCode(2nd 参数)。对于 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 em>(第 #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 模块,用于二进制和“正常”数据之间的转换。它包含所有格式字符的含义(qQL、...)等等。您也可以查看[SO]: Python struct.pack() behavior 了解更多(实用)细节

看了上面的资料,事情应该就清楚了。

几点说明:

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

我还准备了一个虚拟 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\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,我得到了 的正确字节数(由 Win 报告) E: 开车。

【讨论】:

  • 感谢@CristiFati。实际上,经过一番尝试和研究,我能够找到您评论中提到的资源,并且正在考虑回答我自己的问题,如果没有人回答但您有
  • “保护用户免受自己的伤害”正如你所说,所以我知道我在使用这些命令时遇到的风险
  • “每次尝试都只是表示缺乏经验”,但尝试会增加经验,这反过来又帮助我发现了这些文档
  • 我现在可以通过日志查询,但我需要建立父文件的路径,这是我目前卡住的地方。我发现有办法让它工作,一种是通过 MFT(我不想要那个),另一种是用文件参考号打开文件。你能帮我做第二个吗?
  • 恐怕我对“文件参考号”的了解。接近 0。所以如果没有一些代码示例,我将无能为力。但是,这不是一个不同的问题吗(因为这个问题的代码与音量属性有关,而不是音量变化)?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-12
  • 2010-10-13
  • 1970-01-01
  • 2011-12-17
  • 1970-01-01
  • 2017-03-22
相关资源
最近更新 更多