Kport 第 2 部分:在 Win NT/2000/XP 下直接访问 I/O 端口






4.52/5 (13投票s)
现在,VC++ 6 中包含完整的代码并有更多解释。
引言
这是文章 kport.dll 的第二部分,用于在 Windows NT/2K/XP 中直接访问 I/O 端口。
我写了一个新的示例,用于在 VC++ 6 中使用此 DLL。
背景
请记住,要编译 kioport.sys,您可能需要安装 DDK。
Kioport.sys
这篇文章来自 Beyondlogic Portalk。
在 NT/2000/XP 下访问 I/O 端口
有两种解决方案可以解决 Windows NT/2000/XP 下的 I/O 访问问题。第一种解决方案是编写一个在 ring 0(I/O 权限级别 0)下运行的设备驱动程序,以代表您访问 I/O 端口。数据可以通过 IOCTL 调用在您的用户模式程序和设备驱动程序之间传递。驱动程序然后可以执行您的 I/O 指令。这样做的问题是,它假定您拥有源代码来进行此类更改。
另一种可能的替代方法是修改 I/O 权限位图,允许特定任务访问某些 I/O 端口。这使得您的用户模式程序在 ring 3 中运行,可以根据 I/O 权限位图对选定的端口执行无限制的 I/O 操作。此方法并不真正推荐,但它为允许现有应用程序在 Windows NT/2000/XP 下运行提供了一种方式。编写设备驱动程序来支持您的硬件是首选方法。设备驱动程序应在访问端口之前检查任何冲突。
但是,使用 Kioport 这样的驱动程序可能会变得相当低效。每次发出 IOCTL 调用以读写端口的一个字节或字时,处理器必须从 ring 3 切换到 ring 0 来执行操作,然后切换回来。
兼容性 - 在 Windows NT/2000/XP 下使用现有应用程序
如您所知,任何 32 位程序都会导致特权指令异常。由于如今大多数编程语言都不提供读取和写入端口的函数,因此为 Windows 9x 和 ME 制作了许多“技巧”,例如 .DLL 库。但是,如果您需要在 Windows NT 下运行此类程序,将会发生异常。
16 位 Windows 和 DOS 程序将在虚拟机上运行。在许多情况下,现有应用程序在 Windows NT/2000/XP 下应该是透明的。然而,有些就是无法运行。虚拟机支持通信端口、视频、鼠标和键盘。因此,任何使用这些常见 I/O 端口的程序都应该可以运行,但经常会出现时序问题。访问特定硬件的其他 MS-DOS 程序需要 VDD(虚拟设备驱动程序)来使它们能够与 Windows NT 一起使用。
虚拟机将拦截 I/O 操作并将其发送给 I/O 处理程序进行处理。虚拟机执行此操作的方式是,赋予 I/O 操作不足的权限,并创建一个异常处理程序来回溯堆栈,查找最后一个指令并对其进行解码。通过赋予 VDM 对 I/O 端口的完全权限,它就无法拦截 I/O 操作,从而减少了时序问题或提供 VDD 来处理更晦涩的硬件的需求。
为了更改进程的 IOPM,我们必须首先拥有要授予访问权限的进程的进程 ID。这是通过创建我们自己的进程来实现的,以便我们可以将 ProcessID
传递给我们的设备驱动程序。使用一个接受程序名作为参数的小型应用程序。该应用程序然后创建进程(即执行程序),该程序将作为系统中的另一个进程启动和继续。
注意:我们还可以向操作系统注册一个回调,该回调会通知我们的驱动程序任何启动的进程及其 ID。然后,我们可以维护一个我们希望拥有对某些端口的访问权限的进程目录。当执行此进程时,回调会通知驱动程序它已启动及其 processID
。然后,我们可以自动更改该进程的 IOPM。
当使用 CreateProcess()
启动 Windows 32 位程序时,它将返回 32 位程序的 ProcessID
。这通过 IOCTL 调用传递给设备驱动程序。
DOS 程序没有自己的 ProcessID
。它们运行在 Windows NT 虚拟 DOS 机 (NTVDM.EXE) 下,这是一个模拟 MS-DOS 的受保护环境子系统。当使用此程序调用 DOS 程序时,它将获得 NTVDM.EXE 的 ProcessID
,并因此更改 NTVDM 的 IOPM。但是,如果 NTVDM 已经驻留(如果另一个 DOS 程序正在运行),它将返回进程 ID 零。如果 NT 虚拟 DOS 机的 IOPM 已设置为允许任何 IO 操作,则这不会造成问题,但如果第一个 DOS 程序是从命令行调用的,而没有使用 "AllowIo
",则 NTVDM 将没有修改的 IOPM。Windows 3.1 程序将使用 WOW(Windows on Win32)运行。这是另一个在 NTVDM 进程内运行的受保护子系统。运行 Win3.1 程序将返回 NTVDM 的 ProcessID
,符合上述问题。
当设备驱动程序获得 ProcessID
后,它会找到我们新创建程序的进程指针,并将 IOPM 设置为允许所有 I/O 指令。一旦 ProcessID
被提供给我们的 PortTalk 设备驱动程序,allowio 程序就会完成。正常运行 DOS/Win 3.1 程序在 NTVDM.EXE 下不应造成任何重大问题。NTVDM 通常会拦截大多数 IO 调用,并根据注册表检查这些资源,以确保它们未被使用。如果它们正在使用,则会出现一个消息框,让用户选择终止程序或忽略错误。如果用户选择忽略错误,将不会授予对冲突的 I/O 端口的访问权限。
操纵 IOPM(I/O 权限位图)
在内核模式驱动程序中更改 IOPM 需要了解一些未公开的调用。它们是 Ke386IoSetAccessProcess
、Ke386SetIoAccessMap
和 PsLookupProcessByProcessId
。
PsLookupProcessByProcessId(IN ULONG ulProcId,OUT struct _EPROCESS ** pEProcess);
IOPM 例程使用进程指针而不是 ProcessID
。因此,我们的第一个任务是将 ProcessID
转换为进程指针。有一些公开的调用,如 PsGetCurrentProcess()
,但我们不想要当前进程,而是我们希望授予访问权限的进程指针。此信息以 processID
的形式传递给驱动程序。然后,我们必须使用未公开的调用 LookupProcessByProcessId
将我们的 ProcessID
转换为进程指针。一旦我们有了进程指针,我们就可以使用以下未公开的调用开始操纵 I/O 权限位图。
void Ke386SetIoAccessMap(int, IOPM *); void Ke386QueryIoAccessMap(int, IOPM *); void Ke386IoSetAccessProcess(PEPROCESS, int);
Ke386SetIoAccessMap
将指定的 IOPM 复制到 TSS。Ke386QueryIoAccessMap
将从 TSS 读取它。IOPM 是一个 8192 字节的数组,指定哪些端口允许访问,哪些不允许。每个地址由一个位表示,因此 8192 字节将指定高达 64K 的访问。任何零位都允许访问,而一位则禁止访问。将 IOPM 复制到 TSS 后,必须调整 IOPM 偏移指针以指向我们的 IOPM。这是使用 Ke386IoSetAccessProcess
完成的。int
参数必须设置为 1
才能设置它。使用零调用该函数将删除指针。
与设备驱动程序通信 - 用户模式 API
Kioport 还具有 IOCTL,允许读取和写入 I/O 端口。在这种情况下,您的用户模式程序将打开 Kioport 设备驱动程序,并通过 IOCTL 调用将数据传递给驱动程序。然后,驱动程序在 ring 0 中与 I/O 端口通信。
Kport.Dll
这是我的 DLL,部分代码来自 Portalk。
Kport 在加载我们的应用程序时调用 OpenPort()
,在卸载时调用 ClosePort()
,这样我们就无需加载驱动程序。
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: OpenPort(); break; //.... case DLL_PROCESS_DETACH: ClosePort(); break; } return TRUE; }
OpenPort()
首先验证它是否已将驱动程序安装到 system32\driver。如果无法打开它,则尝试安装并启动它。过程如图所示。
这两个函数在 kioport.sys 中进行读写。
// Write a value to specific ports. KPORT_API void APIENTRY Outportb(WORD PortNum, BYTE byte) { UINT error; DWORD BytesReturned; BYTE Buffer[3]; WORD * pBuffer; pBuffer = (WORD *)&Buffer[0]; *pBuffer = PortNum; Buffer[2] = byte; LPSTR szt = NULL; error = DeviceIoControl(hKport, IOCTL_WRITE_PORT_UCHAR, &Buffer, 3, NULL, 0, &BytesReturned, NULL); if (!error) { wsprintf(szt,"Error occured during outportb while" " talking to KioPort driver %d\n",GetLastError()); MessageBox(NULL,szt,"Kport-DLL",MB_ICONINFORMATION); } }
// Returns a value from specific ports. KPORT_API BYTE APIENTRY Inportb(WORD PortNum) { UINT error; DWORD BytesReturned; BYTE Buffer[3]; WORD *pBuffer; pBuffer = (WORD*)&Buffer; *pBuffer = PortNum; LPSTR szt = NULL; error = DeviceIoControl(hKport, IOCTL_READ_PORT_UCHAR, &Buffer, 2,&Buffer, 1,&BytesReturned, NULL); if (!error) { wsprintf(szt,"Error occured during inportb while" " talking to KioPort driver %d\n", GetLastError()); MessageBox(NULL,szt,"Kport-DLL",MB_ICONINFORMATION); } return(Buffer[0]); }
我现在添加了 IrpMan、Microsoft® Windows Server 2003 DDK,其中包含有关制作驱动程序的大量信息。
最后,对我的糟糕英语表示歉意。我正在学习英语的第二年,所以我不太会写这个语言。
历史
- 2005年4月3日
First version.
- 2005年4月30日
第二版。