实战DeviceIoControl 之五:列举已安装的存储设备

2023-10-30

Q 前几次我们讨论的都是设备名比较清楚的情况,有了设备名(路径),就可以直接调用CreateFile打开设备,进行它所支持的I/O操作了。如果事先并不能确切知道设备名,如何去访问设备呢?

A访问设备必须用设备句柄,而得到设备句柄必须知道设备路径,这个套路以你我之力是改变不了的。每个设备都有它所属类型的GUID,我们顺着这个GUID就能获得设备路径。

GUID是同类或同种设备的全球唯一识别码,它是一个128 bit(16字节)的整形数,真实面目为

typedef struct _GUID
{
    unsigned long  Data1;
    unsigned short Data2;
    unsigned short Data3;
    unsigned char  Data4[8];
} GUID, *PGUID;

例如,Disk类的GUID为“53f56307-b6bf-11d0-94f2-00a0c91efb8b”,在我们的程序里可以定义为

const GUID DiskClassGuid = {0x53f56307L, 0xb6bf, 0x11d0, {0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b)};

或者用一个宏来定义

DEFINE_GUID(DiskClassGuid, 0x53f56307L, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b);

通过GUID找出设备路径,需要用到一组设备管理的API函数

SetupDiGetClassDevs, SetupDiEnumDeviceInterfaces, SetupDiGetInterfaceDeviceDetail, SetupDiDestroyDeviceInfoList,

以及结构SP_DEVICE_INTERFACE_DATA, SP_DEVICE_INTERFACE_DETAIL_DATA。

有关信息请查阅MSDN,这里就不详细介绍了。

实现GUID到设备路径的代码如下:

// SetupDiGetInterfaceDeviceDetail所需要的输出长度,定义足够大
#define INTERFACE_DETAIL_SIZE    (1024)
  
// 根据GUID获得设备路径
// lpGuid: GUID指针
// pszDevicePath: 设备路径指针的指针
// 返回: 成功得到的设备路径个数,可能不止1个
int GetDevicePath(LPGUID lpGuid, LPTSTR* pszDevicePath)
{
    HDEVINFO hDevInfoSet;
    SP_DEVICE_INTERFACE_DATA ifdata;
    PSP_DEVICE_INTERFACE_DETAIL_DATA pDetail;
    int nCount;
    BOOL bResult;
  
    // 取得一个该GUID相关的设备信息集句柄
    hDevInfoSet = ::SetupDiGetClassDevs(lpGuid,     // class GUID 
        NULL,                    // 无关键字 
        NULL,                    // 不指定父窗口句柄 
        DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);    // 目前存在的设备
  
    // 失败...
    if (hDevInfoSet == INVALID_HANDLE_VALUE)
    {
        return 0;
    }
  
    // 申请设备接口数据空间
    pDetail = (PSP_DEVICE_INTERFACE_DETAIL_DATA)::GlobalAlloc(LMEM_ZEROINIT, INTERFACE_DETAIL_SIZE);
  
    pDetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
  
    nCount = 0;
    bResult = TRUE;
  
    // 设备序号=0,1,2... 逐一测试设备接口,到失败为止
    while (bResult)
    {
        ifdata.cbSize = sizeof(ifdata);
  
        // 枚举符合该GUID的设备接口
        bResult = ::SetupDiEnumDeviceInterfaces(
            hDevInfoSet,     // 设备信息集句柄
            NULL,            // 不需额外的设备描述
            lpGuid,          // GUID
            (ULONG)nCount,   // 设备信息集里的设备序号
            &ifdata);        // 设备接口信息
  
        if (bResult)
        {
            // 取得该设备接口的细节(设备路径)
            bResult = SetupDiGetInterfaceDeviceDetail(
                hDevInfoSet,    // 设备信息集句柄
                &ifdata,        // 设备接口信息
                pDetail,        // 设备接口细节(设备路径)
                INTERFACE_DETAIL_SIZE,    // 输出缓冲区大小
                NULL,           // 不需计算输出缓冲区大小(直接用设定值)
                NULL);          // 不需额外的设备描述
  
            if (bResult)
            {
                // 复制设备路径到输出缓冲区
                ::strcpy(pszDevicePath[nCount], pDetail->DevicePath);
  
                // 调整计数值
                nCount++;
            }
        }
    }
  
    // 释放设备接口数据空间
    ::GlobalFree(pDetail);
  
    // 关闭设备信息集句柄
    ::SetupDiDestroyDeviceInfoList(hDevInfoSet);
  
    return nCount;
}

调用GetDevicePath函数时要注意,pszDevicePath是个指向字符串指针的指针,例如可以这样

 int i;
    char* szDevicePath[MAX_DEVICE];        // 设备路径
  
    // 分配需要的空间
    for (i = 0; i < MAX_DEVICE; i++)
    {
        szDevicePath[i] = new char[256];
    }
  
    // 取设备路径
    nDevice = ::GetDevicePath((LPGUID)&DiskClassGuid, szDevicePath);
  
    // 逐一获取设备信息
    for (i = 0; i < nDevice; i++)
    {
        // 打开设备
        hDevice = ::OpenDevice(szDevicePath[i]);
  
        if (hDevice != INVALID_HANDLE_VALUE)
        {
            ... ...        // I/O操作
  
            ::CloseHandle(hDevice);
        }
    }
  
    // 释放空间
    for (i = 0; i & lt; MAX_DEVICE; i++)
    {
        delete []szDevicePath[i];
    }

本例的Project中除了要包含winioctl.h外,还要包含initguid.h,setupapi.h,以及连接setupapi.lib。

Q 得到设备路径后,就可以到下一步,用CreateFile打开设备,然后用DeviceIoControl进行读写了吧?

A 是的。尽管该设备路径与以前我们接触的那些不太一样。本是“//./PhysicalDrive0”,现在鸟枪换炮,变成了类似这样的一副尊容:

“//?/ide#diskmaxtor_2f040j0__________________________vam51jj0#3146563447534558202020202020202020202020#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}”。

其实这个设备名在注册表的某处可以找到,例如在Win2000中这个名字可以位于

HKEY_LOCAL_MACHINE/System/CurrentControlSet/Services/Disk/Enum/0,

只不过“#”换成了“/”。分析一下这样的设备路径,你会发现很有趣的东西,它们是由接口类型、产品型号、固件版本、序列号、计算机名、GUID等信息组合而成的。当然,它是没有规范的,不能指望从这里面得到你希望知道的东西。

用CreateFile打开设备后,对于存储设备,IOCTL_DISK_GET_DRIVE_GEOMETRY,IOCTL_STORAGE_GET_MEDIA_TYPES_EX等I/O控制码照常使用。

今天我们讨论一个新的控制码:IOCTL_STORAGE_QUERY_PROPERTY,获取设备属性信息,希望得到系统中所安装的各种固定的和可移动的硬盘、优盘和CD/DVD-ROM/R/W的接口类型、序列号、产品ID等信息。

// IOCTL控制码
#define IOCTL_STORAGE_QUERY_PROPERTY   CTL_CODE(IOCTL_STORAGE_BASE, 0x0500, METHOD_BUFFERED, FILE_ANY_ACCESS)
// 存储设备的总线类型
typedef enum _STORAGE_BUS_TYPE {
    BusTypeUnknown = 0x00,
    BusTypeScsi,
    BusTypeAtapi,
    BusTypeAta,
    BusType1394,
    BusTypeSsa,
    BusTypeFibre,
    BusTypeUsb,
    BusTypeRAID,
    BusTypeMaxReserved = 0x7F
} STORAGE_BUS_TYPE, *PSTORAGE_BUS_TYPE;
  
// 查询存储设备属性的类型
typedef enum _STORAGE_QUERY_TYPE {
    PropertyStandardQuery = 0,          // 读取描述
    PropertyExistsQuery,                // 测试是否支持
    PropertyMaskQuery,                  // 读取指定的描述
    PropertyQueryMaxDefined             // 验证数据
} STORAGE_QUERY_TYPE, *PSTORAGE_QUERY_TYPE;
  
// 查询存储设备还是适配器属性
typedef enum _STORAGE_PROPERTY_ID {
    StorageDeviceProperty = 0,          // 查询设备属性
    StorageAdapterProperty              // 查询适配器属性
} STORAGE_PROPERTY_ID, *PSTORAGE_PROPERTY_ID;
  
// 查询属性输入的数据结构
typedef struct _STORAGE_PROPERTY_QUERY {
    STORAGE_PROPERTY_ID PropertyId;     // 设备/适配器
    STORAGE_QUERY_TYPE QueryType;       // 查询类型 
    UCHAR AdditionalParameters[1];      // 额外的数据(仅定义了象征性的1个字节)
} STORAGE_PROPERTY_QUERY, *PSTORAGE_PROPERTY_QUERY;
  
// 查询属性输出的数据结构
typedef struct _STORAGE_DEVICE_DESCRIPTOR {
    ULONG Version;                    // 版本
    ULONG Size;                       // 结构大小
    UCHAR DeviceType;                 // 设备类型
    UCHAR DeviceTypeModifier;         // SCSI-2额外的设备类型
    BOOLEAN RemovableMedia;           // 是否可移动
    BOOLEAN CommandQueueing;          // 是否支持命令队列
    ULONG VendorIdOffset;             // 厂家设定值的偏移
    ULONG ProductIdOffset;            // 产品ID的偏移
    ULONG ProductRevisionOffset;      // 产品版本的偏移
    ULONG SerialNumberOffset;         // 序列号的偏移
    STORAGE_BUS_TYPE BusType;         // 总线类型
    ULONG RawPropertiesLength;        // 额外的属性数据长度
    UCHAR RawDeviceProperties[1];     // 额外的属性数据(仅定义了象征性的1个字节)
} STORAGE_DEVICE_DESCRIPTOR, *PSTORAGE_DEVICE_DESCRIPTOR;
  
// 取设备属性信息
// hDevice -- 设备句柄
// pDevDesc -- 输出的设备描述和属性信息缓冲区指针(包含连接在一起的两部分)
BOOL GetDriveProperty(HANDLE hDevice, PSTORAGE_DEVICE_DESCRIPTOR pDevDesc)
{
    STORAGE_PROPERTY_QUERY Query;    // 查询输入参数
    DWORD dwOutBytes;                // IOCTL输出数据长度
    BOOL bResult;                    // IOCTL返回值
  
    // 指定查询方式
    Query.PropertyId = StorageDeviceProperty;
    Query.QueryType = PropertyStandardQuery;
  
    // 用IOCTL_STORAGE_QUERY_PROPERTY取设备属性信息
    bResult = ::DeviceIoControl(hDevice, // 设备句柄
        IOCTL_STORAGE_QUERY_PROPERTY,    // 取设备属性信息
        &Query, sizeof(STORAGE_PROPERTY_QUERY),    // 输入数据缓冲区
        pDevDesc, pDevDesc->Size,        // 输出数据缓冲区
        &dwOutBytes,                     // 输出数据长度
        (LPOVERLAPPED)NULL);             // 用同步I/O    
  
    return bResult;
}

Q 我用这个方法从IOCTL_STORAGE_QUERY_PROPERTY返回的数据中,没有得到CDROM和USB接口的外置硬盘的序列号、产品ID等信息。但从设备路径上看,明明是有这些信息的,为什么它没有填充到STORAGE_DEVICE_DESCRIPTOR中呢?再就是为什么硬盘序列号本是“D22P7KHE ”,为什么它填充的是“3146563447534558202020202020202020202020”这种形式呢?

A 对这两个问题我也是心存疑惑,但又不敢妄加猜测,正琢磨着向微软请教呢。

[相关资源]

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

实战DeviceIoControl 之五:列举已安装的存储设备 的相关文章

  • 如何通过文件关联执行已启动应用程序的事件?

    在尝试了一个新的 Windows 窗体项目后 我发现当您将文件类型与 Windows 中的可执行文件关联时 您可以使用以下命令找到启动应用程序的文件的文件路径args 0 from static void Main string args
  • Go 编译器有窗口化设置选项吗?

    我正在使用 Go 6g 编译 GTK 应用程序 我想知道是否有编译器 链接器选项使其成为 Windows 可执行文件而不是控制台可执行文件 MinGW 有一个 mwindows 选项来实现此目的 目前我必须使用十六进制编辑器手动更改 PE
  • 如何告诉 IntelliJ 使用 Java 1.6 JDK 启动 gradle?

    一个简单的问题 即使经过几个小时的尝试和搜索 我也无法弄清楚 我安装了 Java 6 和 7 如何告诉 IntelliJ 使用 JDK 版本 1 6 启动 Gradle 构建 无论我做什么 IntelliJ 都会以以下方式开始我的 grad
  • 命令行字符串的最大长度

    在Windows中 命令行字符串的最大长度是多少 意思是如果我指定一个在命令行上接受参数的程序 例如abc exe name abc 我编写的一个简单的控制台应用程序通过命令行获取参数 我想知道最大允许数量是多少 来自微软文档 命令提示符
  • 检查 Win32 线程是否正在运行或处于挂起状态

    如何检查 Win32 线程是否正在运行或处于挂起状态 我找不到任何提供线程状态的 Win32 API 那么如何获取线程状态呢 我认为 最初 没有提供此信息 因为任何提供此信息的 API 都会产生误导且无用 考虑两种可能的情况 当前线程已挂起
  • UnicodeDecodeError:“charmap”编解码器|安装 pip python-stdnum==1.8 时出错

    我对编程还很陌生 所以请耐心等待 当我为正在使用的模块安装一些必需的软件包时 我无法安装python stdnum 1 8 我收到以下错误消息 File C Users 59996 AppData Local Programs Python
  • Hadoop Windows 设置。运行 WordCountJob 时出错:“任何本地目录中都没有可用空间”

    我正在按照此视频教程尝试在我的计算机上设置 hadoop 如何在 Windows 10 上安装 Hadoop https www youtube com watch v zujpglKP0Nw 我已经成功设置它 从 sbin 目录执行 st
  • JScript:如何运行外部命令并获取输出?

    我正在使用 cscript exe 运行 JScript 文件 在脚本中我需要调用外部console命令并获取输出 Tried var oShell WScript CreateObject WScript Shell var oExec
  • 对 .NET Windows 安装程序应用程序执行注册表搜索

    我有一个 NET winform 安装程序应用程序 在安装之前 我会进行注册表搜索以检查计算机上是否安装了 MS Access Runtime 搜索目标机器 搜索目标机器的属性 启动条件 启动条件的属性 但是我想避免对路径进行硬编码 例如
  • netstat 中未显示正在使用的端口,但尝试使用该端口被 Windows 拒绝

    我已经找到了这个问题的答案 只是想记录我的发现 在我最近的一个项目中 我发现某个端口不会在 netstat 中显示为正在使用 但是当我的项目尝试使用该端口时 会抛出错误 例如 假设我想使用端口 53000 netstat ano finds
  • 最低 DirectX 9.0c 版本以及如何检查它

    我们基于 Windows C Ogre 的游戏即将完成 在我们公开发布之前 我们必须解决这个问题 如果未更新到最新的 Dx9 0c 版本 Ogre 在许多测试计算机上都会崩溃 所有这些计算机都已经安装了 9 0c 但这一定是旧操作系统预安装
  • WMI 不返回 Windows 7 64 上的所有安装程序

    今天 我们尝试使用以下脚本列出每个虚拟机上所有已安装的程序来查询 WMI 我们发现它会列出所有 64 位应用程序 以及一些 32 位应用程序 但并非所有应用程序 32 位 64 位 都会列出 param string ExportPath
  • Windows docker:权限被拒绝 /var/run/docker.sock

    当我尝试使用自动发现运行 filebeat 时 出现以下错误 退出 自动发现提供程序设置中出现错误 已获得权限 尝试连接到 Docker 守护程序套接字时被拒绝 unix var run docker sock 获取http 2Fvar 2
  • endl 不适用于 wstring (unicode)

    这是代码 std ofstream f file1 txt f lt lt 123 lt
  • 使用“for”循环迭代目录中的所有文件

    如何使用 a 迭代目录中的每个文件for loop 我如何判断某个条目是目录还是文件 这会递归地列出当前目录及其子目录中的所有文件 并且仅列出文件 for r i in do echo i 此外 如果您在批处理文件中运行该命令 则需要将 符
  • 如何让 Internet Explorer 正确处理自定义协议处理程序?

    我想要打开我正在开发的网站PuTTY on ssh 0 0 0 0输入网址 我在 Chrome 和 Firefox 中使用了此功能 但在 Internet Explorer 中出现以下错误 Windows cannot access the
  • 虚拟USB设备的安装与仿真

    我已经读过创建虚拟USB设备 https stackoverflow com questions 5016363 creating a virtual usb device and 虚拟USB设备 https stackoverflow c
  • 无法在 Powershell 中运行 R.exe

    我经常发现在命令行 Windows 上运行 R 更有用 然而 当我在 Powershell 中尝试时 我往往会遇到问题 但这可以通过第一次运行轻松克服cmd然后就可以了 这是我执行此操作时遇到的错误R CMD BATCH Invoke Hi
  • C++/Windows:如何报告内存不足异常(bad_alloc)?

    我目前正在为 Windows MSVC 9 0 应用程序开发基于异常的错误报告系统 即异常结构和类型 继承 调用堆栈 错误报告和日志记录等 我现在的问题是 如何正确报告和记录内存不足错误 当发生此错误时 例如作为bad alloc抛出的ne
  • TCHAR 仍然相关吗?

    我是 Windows 编程新手 在读完 Petzold 的书后 我想知道 使用TCHAR类型和 T 函数来声明字符串或者我应该使用wchar t and L 新代码中的字符串 我将仅针对现代 Windows 截至撰写本文时版本 10 和 1

随机推荐

  • 知识蒸馏(Knowledge Distillation)

    0 Introduction 知识蒸馏 Knowledge Distillation 简记为 KD 是一种经典的模型压缩方法 核心思想是通过引导轻量化的学生模型 模仿 性能更好 结构更复杂的教师模型 或多模型的 ensemble 在不改变学
  • “Python小屋”1300篇历史文章分类速查表

    总体说明 各分类中的文章是按发布时间逆序排列的 动态更新 公众号所有代码均可作为教学案例 转载请注明出处 请勿用作商业用途 快速查找历史文章的方法 1 单击本文右上角的按钮 然后在弹出的窗口中选择 搜索页面内容 然后输入要找的关键字即可 董
  • QT中QMainWindow、QWidget、QDialog的区别

    QT中QMainWindow QWidget QDialog的区别 QT中QMainWindowQWidgetQDialog的区别 QMainWindow QWidget QDialog QMainWindow 详细描述 QMainWind
  • QT C++开发环境一键快速搭建

    QT C 开发环境一键快速搭建 很简单 只需下载QT creator 地址 http mirrors hust edu cn qtproject archive qt 5 1 5 1 1 qt windows opensource 5 1
  • 微信小程序:关于代码片段的探究

    打开微信开发者工具 在项目旁边有个代码片段的按钮 截图如下 微信官方文档介绍 https mp weixin qq com debug wxadoc dev devtools minicode html 什么是微信小程序的代码片段功能 代码
  • docker创建镜像之Dockerfile

    使用命令 docker build 从零开始来创建一个新的镜像 Dockerfile 是一个用来构建镜像的文本文件 文本内容包含了一条条构建镜像所需的指令和说明 参考 Docker 镜像使用 菜鸟教程 创建Dockerfile文件 runo
  • BigQuery基础查询语句整理

    BigQuery 是 Google Cloud Platform 上一种可以让用户以 SQL 语句来查询大规模数据的云服务 它可以让用户以低廉的价格 快速地访问大量数据 而不需要拥有自己的基础架构 BigQuery 支持多种数据格式 如 C
  • 利用PyTorch C++ API(LibTorch)加载预训练模型及预测

    利用PyTorch C API LibTorch 加载预训练模型及预测 前言 LibTorch是基于PyTorch的包含头文件 库文件和CMake编译 配置 文件的C API CMake编译配置文件并不是必须的 但官方推荐使用 且会持续维护
  • java开发工程师面试问题大全及答案大全

    前言 Alibaba作为国内互联网行业的 老大 一直以来也是很多 数码宝贝 梦寐以求的公司 我个人是做Java开发的 阿里这些年也开发了很多屌炸天的开源项目 像什么Spring Cloud Alibaba 开源Java诊断工具Arthas
  • Ubuntu18.04 安装 samba 服务器

    步骤一 直接使用命令 sudo apt get install samba 步骤二 检查下samba安装信息 使用命令 dpkg l grep i samba 步骤三 使用命令 来启动samba服务 etc init d smbd star
  • 【江苏省赛】C - Cats 找规律+模拟

    原题 输入1 1 输出1 1 输入2 3 输出2 1 2 3 题意 1 20的数字 两个相同数字不能相邻 且他们之间的最小值要严格小于它们 求长度为n的这样的序列 思路 大的插在现有的空中间 如下 1 2 1 2 3 2 3 1 3 2 3
  • 初探ROP

    文章目录 0x01 前言 0x02 什么是ROP 0x03 为什么要ROP 0x04 基本ROP ret2shellcode 含义 从原理中解析ret2shellcode 从例子中解析ret2shellcode 发现利用点 确定利用前提 调
  • 对比和消融实验

    对比实验 Comparative Experiment 和消融实验 Ablation Experiment 是在科学研究中常用的两种实验设计方法 用于评估和验证某个因素对研究结果的影响 对比实验是通过将不同的方法 模型或算法进行比较来评估它
  • Python统计分析库statsmodels的OLS

    statsmodels库官方文档http www statsmodels org stable 里面包含很多统计模型和相应计算结果 一些Linear Regression Models例子http www statsmodels org s
  • xssgame第一关至第五关

    第一关很简单 进入第二关 先尝试 查看页面源代码 可以利用闭合 答案截图 第三关 从源代码可以看到 lt gt 被转义了 所以选了几个不需要 lt gt 这个事件性的 第四关 将单引号换成双引号 第五关 第五关频频出现多余的下划线 只要sc
  • C语言-学生学号成绩读入程序(输出了学生总分、平均分、名次以及学生信息的查询)

    任务描述 成绩排名次 某班期末考试科目为数学 MT 英语 EN 和物理 PH 有最多不超过30人参加考试 考试后要求 1 计算每个学生的总分和平均分 2 按总分成绩由高到低排出成绩的名次 3 输出名次表 表格内包括学生编号 各科分数 总分和
  • 华为OD机试 - 垃圾短信识别(Java)

    题目描述 大众对垃圾短信深恶痛绝 希望能对垃圾短信发送者进行识别 为此 很多软件增加了垃圾短信的识别机制 经分析 发现正常用户的短信通常具备交互性 而垃圾短信往往都是大量单向的短信 按照如下规则进行垃圾短信识别 本题中 发送者A符合以下条件
  • android自带网页提供,Android调用系统自带浏览器打开网页的实现方法

    在Android中可以调用自带的浏览器 或者指定一个浏览器来打开一个链接 只需要传入一个uri 可以是链接地址 启动android默认浏览器 在Android程序中我们可以通过发送隐式Intent来启动系统默认的浏览器 如果手机本身安装了多
  • Acwing-42. 栈的压入、弹出序列

    每一步进行的操作有两种 将下一个数压入栈中 将当前栈顶元素弹出 判断当前栈顶元素是否和下一个要输出的数是一样的 一样 gt 必然会将当前栈顶元素弹出 不一样 gt 必然会将输入序列的下一个元素加入栈中 class Solution publ
  • 实战DeviceIoControl 之五:列举已安装的存储设备

    Q 前几次我们讨论的都是设备名比较清楚的情况 有了设备名 路径 就可以直接调用CreateFile打开设备 进行它所支持的I O操作了 如果事先并不能确切知道设备名 如何去访问设备呢 A访问设备必须用设备句柄 而得到设备句柄必须知道设备路径