65.9K
CodeProject 正在变化。 阅读更多。
Home

允许用户模式下的 I/O 访问

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (4投票s)

2015年4月26日

CPOL

6分钟阅读

viewsIcon

33487

downloadIcon

800

迟了 12 年,但现在它起作用了:用于 64 位 Windows 的 giveio 和 dlportio 的替代驱动程序

引言

使用(主要是)并行端口的应用程序的用户通常会抱怨 giveio.sysdlportio.sys 在 64 位 Windows 版本上无法正常工作。因此,建议要么使用 32 位 Windows 版本(原生或虚拟机),要么升级硬件到 USB 版本,淘汰旧硬件+软件(如果可用)。由于并行端口仍然可以作为 PCI、PCIexpress 或 ExpressCard 扩展卡可用,因此向 64 位升级的问题依然存在。(我假设读者期望的是 AMD64,而不是完全不兼容且几乎过时的 Intel64 架构。)

现在,giveio.sys + dlportio.sys 的替代品已经可用。

调试会话结果

受影响的应用程序必须是 32 位,因为 16 位程序在 64 位 Windows 上也无法运行。此外,这些程序将 I/O 指令内联,而不是为每次 I/O 调用一个已知的 DLL,通常是 inpout32.dll。对于后一种情况,升级的 inpout32.dll 几年前就可以完成这项工作。

AMD64 架构已经推出很久了,我奇怪为什么没有人在此之前找到这样的解决方案。我通过将 DLL 注入到相关进程中来处理这个问题,该 DLL 模拟运行 Windows 9x 并捕获异常 0xC0000005(特权指令),通过一些指令解码在内核模式下执行实际的 I/O。但这非常耗时,并且对数据采集软件来说性能很差。

当我阅读 AMD64 手册时,我发现 TSS(任务状态段)的结构与 32 位相同,IOPM(输入输出权限映射)也以同样的方式工作。因此,研究 Win64 TSS 的实现方式以及是否可能启用 IOPM 是值得的。

使用内核模式调试器(windbg 和串行零调制解调器电缆),我检测到 TSS 是一个每个 CPU 的结构,限制为 0x67。因此,它长 0x68 字节,仅够用,没有为 IOPM 留出空间。有趣的是,偏移量 0x66 处的 IOPM 指针包含“正确”的值 0x68,正好在限制之外,并且每个处理器的 TSS 的内存至少被填充了 8 KByte 的零。(零位允许从用户模式访问,而一位则拒绝访问。任何导致超出 TSS 限制的端口地址的访问也将被拒绝,因此,TSS 限制值决定了 IOPM 的有效长度和允许的最大端口地址。通常将 TSS 限制设置为具有完整的 64 KBit = 8 KByte IOPM,以简化系统软件。)

因此,只需调整 GDT 中 TSS 条目的限制值即可完成工作

eb @gdtr+@tr+1 20

这个调试器命令将 8 KByte 添加到 TSS 的限制值中。TR(任务寄存器)的值在 Windows 中似乎固定为 0x40。

必须为每个处理器重复此命令。要切换活动处理器,请使用

$1s

其中“1”是所需的处理器编号,从零开始计数。或者,必须以某种方式将相关进程设置为特定的处理器亲和性。

完成这些步骤后,任何带有内联 I/O 的应用程序现在都可以执行而不会出现权限异常。大多数程序会抱怨驱动程序仍无法加载,但这很容易解决。在某些情况下,你可以欺骗程序在不检查 Windows 版本或驱动程序的情况下执行内联 I/O。

制作驱动程序

现在,是时候将解决方案制作成一个功能性的驱动程序了。我使用 giveio.sys 的源代码作为起点。但是,32 位 Windows 中的 IOPM 是线程特定的,而 64 位 Windows 则不是。此外,还必须添加代码以在每个处理器上执行一段代码。要执行特定的汇编指令,需要一个汇编辅助文件,因为 Microsoft 的 C 编译器不允许为 64 位目标使用内联汇编。

驱动程序本身非常直接:加载时,它扩展 GDT 的限制条目;卸载时,它将其收缩回原始值。“open”入口点仅为了与 giveio.sys dlportio.sys 兼容。因为这两个知名的 32 位驱动程序的工作方式相同,但具有不同的 Win32 入口名称,所以我的驱动程序提供两个名称。因此,CreateFile() 可以成功打开“\\.\giveio”或“\\.\dlportio”。如上所述,那里什么也没发生。

运行每个处理器上代码的代码我从另一个项目中复制出来,即我的 USB2LPT,它需要它来修补 IDT(中断描述符表)以捕获内核模式 I/O 进行重定向。主力是一个 DPC(延迟过程调用)数组,可以提前为将要运行该过程的处理器分配。

在测试驱动程序时,发生了一些不希望的行为

  • 在没有连接调试器的情况下启动时,PatchGuard 会在几分钟后以蓝屏 0x109 终止 Windows 会话。要禁用 PatchGuard,有现成的解决方案。此外,驱动程序根本无法工作:仍然无法访问端口。
  • 连接调试器启动时,它有时会起作用。

后一种情况表明我忘记显式重新加载任务寄存器。断点,无论是按 Ctrl+Break 还是 `__debugbreak()` 宏,都会作为内核调试器的副作用重新加载任务寄存器。添加 `str + ltr` 序列很容易,但结果是立即发生双重故障蓝屏。再次阅读 AMD64 手册,它指出 `ltr` 到忙任务会出错。下次我删除了 GDT 条目中的忙位,它就起作用了

     mov    byte ptr[rdx+1],0x20    ;rdx points to the GDT entry: Expand size by 8 KByte
     str    ax
     and    byte ptr[rdx+5],not 2   ;Clear busy bit
     ltr    ax

需要 `ltr`(加载任务寄存器)的原因是 CPU 内部缓存保存了 GDT 条目的副本。因此,当以后没有人执行 `ltr` 时,可以将 GDT 条目恢复到之前的状态,然后——瞧!——PatchGuard 就看不到这个 hack 了!

     mov    byte ptr[rdx+1],0       ;revert GDT entry to old value

我为此运行了一段时间的软件进行了测试:它确实有效!我很幸运,没有人检查或覆盖任务寄存器缓存值。不幸的是,任何 Microsoft 工程师都可以使用此发布来在未来的 PatchGuard 更新中包含 `ltr` 指令,如果是这样,该驱动程序的功能将毫无征兆地消失。(不会出现蓝屏。)另一方面,用户模式组件可以捕获此异常并重新启用 giveio.sys。由于 `ltr` 执行耗时,PatchGuard 无法高频率使用它,因此,Microsoft 目前无法在没有处理器(硬件)更新的情况下拒绝这种端口访问启用方式。

使用驱动程序

请参阅文档。存档包含完整的源代码、驱动程序二进制文件和 readme.txt 文件。

驱动程序服务可以通过 `sc`(服务控制)命令行工具安装。可以使用 `sc` 或 `net`(`net` 是旧方法)来启动和停止服务。启动内核模式服务等于加载内核驱动程序(insmod)。因此,我没有添加用户模式安装软件。

此外,驱动程序现在是数字签名的。因此,它可以在没有任何驱动程序签名强制麻烦的情况下使用。

历史

  • 150409 在调试器中成功激活 IOPM
  • 150420 开始编写驱动程序
  • 150514 驱动程序签名
© . All rights reserved.