环境下用户态直接读写端口原理浅析
Posted a-s-m
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了环境下用户态直接读写端口原理浅析相关的知识,希望对你有一定的参考价值。
抄的:
https://blog.csdn.net/flier_lu/article/details/36426
https://www.oschina.net/question/565065_87379
《Windows驱动开发技术详解》----15.6 端口操作实现方法四 399页,
=========================================
修改TSS中端口访问控制表
NT 环境下,每个进程单独维护了一个 TSS 内存区域,其中由 TSS 内部维护了一个全部标志位置 1 的 IOPM 表,在 TSS 末尾还维护了另外一个实际中承担端口管理工作的 IOPM 表。Ke386SetIoAccessMap 函数(ntoskei386iopm.c:80)和 Ke386QueryIoAccessMap 函数(ntoskei386iopm.c:235)就是系统提供用来读写这两个 IOPM 表的函数。而 Ke386iosetAccessProcess 函数(ntoskei386iopm.c:318)则指定进程到底使用哪个 IOPM 表。
BOOLEAN Ke386QueryIoAccessMap(ULONG MapNumber, PKIO_ACCESS_MAP IoAccessMap);
BOOLEAN Ke386SetIoAccessMap(ULONG MapNumber, PKIO_ACCESS_MAP IoAccessMap);
BOOLEAN Ke386IoSetAccessProcess(PKPROCESS Process, ULONG MapNumber);
对前两个函数来说,MapNumber指定要对哪个表进行操作。系统定义了一个 IO_ACCESS_MAP_NONE = 0 常量表示在 TSS 后面那个真实 IOPM 表,而其他的索引对应于 KTSS.IoMaps[] 数组。此数组大多数情况下只有一个表项,也就是说 MapNumber 为 0 时表示 TSS 后面那个 IOPM;为 1 时表示 TSS 内部的 KTSS.IoMaps[0]。 Ke386QueryIoAccessMap 函数只是简单的根据 MapNumber 判断是将 IoAccessMap 内容全部置位(MapNumber = 0)、还是从 TSS 中复制对应的表 (0 < MapNumber <= IOPM_COUNT = 1)。
#include <ntddk.h> #include "MyPort.h" // 设备类型定义 // 0-32767被Microsoft占用,用户自定义可用32768-65535 #define FILE_DEVICE_MYPORT 0x0000f000 // I/O控制码定义 // 0-2047被Microsoft占用,用户自定义可用2048-4095 #define MYPORT_IOCTL_BASE 0xf00 #define IOCTL_MYPORT_READ_BYTE CTL_CODE(FILE_DEVICE_MYPORT, MYPORT_IOCTL_BASE, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_MYPORT_WRITE_BYTE CTL_CODE(FILE_DEVICE_MYPORT, MYPORT_IOCTL_BASE+1, METHOD_BUFFERED, FILE_ANY_ACCESS) // IOPM是65536个端口的位屏蔽矩阵,包含8192字节(8192 x 8 = 65536) // 0 bit: 允许应用程序访问对应端口 // 1 bit: 禁止应用程序访问对应端口 #define IOPM_SIZE 8192 typedef UCHAR IOPM[IOPM_SIZE]; IOPM *pIOPM = NULL; // 设备名(要求以UNICODE表示) const WCHAR NameBuffer[] = L"//Device//MyPort"; const WCHAR DOSNameBuffer[] = L"//DosDevices//MyPort"; // 这是两个在ntoskrnl.exe中的未见文档的服务例程 // 没有现成的已经说明它们原型的头文件,我们自己声明 void Ke386SetIoAccessMap(int, IOPM *); void Ke386IoSetAccessProcess(PEPROCESS, int); // 函数原型预先说明 NTSTATUS MyPortDispatch(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp); void MyPortUnload(IN PDRIVER_OBJECT DriverObject); // 驱动程序入口,由系统自动调用,就像WIN32应用程序的WinMain NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) { PDEVICE_OBJECT deviceObject; NTSTATUS status; UNICODE_STRING uniNameString, uniDOSString; // 为IOPM分配内存 pIOPM = MmAllocateNonCachedMemory(sizeof(IOPM)); if (pIOPM == 0) { return STATUS_INSUFFICIENT_RESOURCES; } // IOPM全部初始化为0(允许访问所有端口) RtlZeroMemory(pIOPM, sizeof(IOPM)); // 将IOPM加载到当前进程 Ke386IoSetAccessProcess(PsGetCurrentProcess(), 1); Ke386SetIoAccessMap(1, pIOPM); // 指定驱动名字 RtlInitUnicodeString(&uniNameString, NameBuffer); RtlInitUnicodeString(&uniDOSString, DOSNameBuffer); // 创建设备 status = IoCreateDevice(DriverObject, 0, &uniNameString, FILE_DEVICE_MYPORT, 0, FALSE, &deviceObject); if (!NT_SUCCESS(status)) { return status; } // 创建WIN32应用程序需要的符号连接 status = IoCreateSymbolicLink (&uniDOSString, &uniNameString); if (!NT_SUCCESS(status)) { return status; } // 指定驱动程序有关操作的模块入口(函数指针) // 涉及以下两个模块:MyPortDispatch和MyPortUnload DriverObject->MajorFunction[IRP_MJ_CREATE] = DriverObject->MajorFunction[IRP_MJ_CLOSE] = DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = MyPortDispatch; DriverObject->DriverUnload = MyPortUnload; return STATUS_SUCCESS; } // IRP处理模块 NTSTATUS MyPortDispatch(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { PIO_STACK_LOCATION IrpStack; ULONG dwInputBufferLength; ULONG dwOutputBufferLength; ULONG dwIoControlCode; PULONG pvIOBuffer; NTSTATUS ntStatus; // 填充几个默认值 Irp->IoStatus.Status = STATUS_SUCCESS; // 返回状态 Irp->IoStatus.Information = 0; // 输出长度 IrpStack = IoGetCurrentIrpStackLocation(Irp); // Get the pointer to the input/output buffer and it‘s length // 输入输出共用的缓冲区 // 因为我们在IOCTL中指定了METHOD_BUFFERED, pvIOBuffer = Irp->AssociatedIrp.SystemBuffer; switch (IrpStack->MajorFunction) { case IRP_MJ_CREATE: // 与WIN32应用程序中的CreateFile对应 break; case IRP_MJ_CLOSE: // 与WIN32应用程序中的CloseHandle对应 break; case IRP_MJ_DEVICE_CONTROL: // 与WIN32应用程序中的DeviceIoControl对应 dwIoControlCode = IrpStack->Parameters.DeviceIoControl.IoControlCode; switch (dwIoControlCode) { // 我们约定,缓冲区共两个DWORD,第一个DWORD为端口,第二个DWORD为数据 // 一般做法是专门定义一个结构,此处简单化处理了 case IOCTL_MYPORT_READ_BYTE: // 从端口读字节 pvIOBuffer[1] = _inp(pvIOBuffer[0]); Irp->IoStatus.Information = 8; // 输出长度为8 break; case IOCTL_MYPORT_WRITE_BYTE: // 写字节到端口 _outp(pvIOBuffer[0], pvIOBuffer[1]); break; default: // 不支持的IOCTL Irp->IoStatus.Status = STATUS_INVALID_PARAMETER; } } ntStatus = Irp->IoStatus.Status; IoCompleteRequest (Irp, IO_NO_INCREMENT); return ntStatus; } // 删除驱动 void MyPortUnload(IN PDRIVER_OBJECT DriverObject) { UNICODE_STRING uniDOSString; if(pIOPM) { // 释放IOPM占用的空间 MmFreeNonCachedMemory(pIOPM, sizeof(IOPM)); } RtlInitUnicodeString(&uniDOSString, DOSNameBuffer); // 删除符号连接和设备 IoDeleteSymbolicLink (&uniDOSString); IoDeleteDevice(DriverObject->DeviceObject); }
应用程序实现端口I/O的接口如下:
// 全局的设备句柄 HANDLE hMyPort; // 打开设备 // lpszDevicePath: 设备的路径 HANDLE OpenDevice(LPCTSTR lpszDevicePath) { HANDLE hDevice; // 打开设备 hDevice = ::CreateFile(lpszDevicePath, // 设备路径 GENERIC_READ | GENERIC_WRITE, // 读写方式 FILE_SHARE_READ | FILE_SHARE_WRITE, // 共享方式 NULL, // 默认的安全描述符 OPEN_EXISTING, // 创建方式 0, // 不需设置文件属性 NULL); // 不需参照模板文件 return hDevice; } // 打开端口驱动 BOOL OpenMyPort() { BOOL bResult; // 设备名为"MyPort",驱动程序位于Windows的"system32/drivers"目录中 bResult = StartDriver("system32//drivers//MyPort.sys", "MyPort"); // 设备路径为"//./MyPort" if (bResult) { hMyPort = OpenDevice("////.//MyPort"); } return (bResult && (hMyPort != INVALID_HANDLE_VALUE)); } // 关闭端口驱动 BOOL CloseMyPort() { return (CloseHandle(hMyPort) && StopDriver("MyPort")); } // 从指定端口读一个字节 // port: 端口 BYTE ReadPortByte(WORD port) { DWORD buf[2]; // 输入输出缓冲区 DWORD dwOutBytes; // IOCTL输出数据长度 buf[0] = port; // 第一个DWORD是端口 // buf[1] = 0; // 第二个DWORD是数据 // 用IOCTL_MYPORT_READ_BYTE读端口 ::DeviceIoControl(hMyPort, // 设备句柄 IOCTL_MYPORT_READ_BYTE, // 取设备属性信息 buf, sizeof(buf), // 输入数据缓冲区 buf, sizeof(buf), // 输出数据缓冲区 &dwOutBytes, // 输出数据长度 (LPOVERLAPPED)NULL); // 用同步I/O return (BYTE)buf[1]; } // 将一个字节写到指定端口 // port: 端口 // data: 字节数据 void WritePortByte(WORD port, BYTE data) { DWORD buf[2]; // 输入输出缓冲区 DWORD dwOutBytes; // IOCTL输出数据长度 buf[0] = port; // 第一个DWORD是端口 buf[1] = data; // 第二个DWORD是数据 // 用IOCTL_MYPORT_WRITE_BYTE写端口 ::DeviceIoControl(hMyPort, // 设备句柄 IOCTL_MYPORT_WRITE_BYTE, // 取设备属性信息 buf, sizeof(buf), // 输入数据缓冲区 buf, sizeof(buf), // 输出数据缓冲区 &dwOutBytes, // 输出数据长度 (LPOVERLAPPED)NULL); // 用同步I/O }
有了ReadPortByte和WritePortByte这两个函数,我们就能很容易地操纵CMOS和speaker了(关于CMOS值的含义以及定时器寄存器定义,请参考相应的硬件资料):
// 0x70是CMOS索引端口(只写) // 0x71是CMOS数据端口 BYTE ReadCmos(BYTE index) { BYTE data; ::WritePortByte(0x70, index); data = ::ReadPortByte(0x71); return data; } // 0x61是speaker控制端口 // 0x43是8253/8254定时器控制端口 // 0x42是8253/8254定时器通道2的端口 void Sound(DWORD freq) { BYTE data; if ((freq >= 20) && (freq <= 20000)) { freq = 1193181 / freq; data = ::ReadPortByte(0x61); if ((data & 3) == 0) { ::WritePortByte(0x61, data | 3); ::WritePortByte(0x43, 0xb6); } ::WritePortByte(0x42, (BYTE)(freq % 256)); ::WritePortByte(0x42, (BYTE)(freq / 256)); } } void NoSound(void) { BYTE data; data = ::ReadPortByte(0x61); ::WritePortByte(0x61, data & 0xfc); }
// 以下读出CMOS 128个字节 for (int i = 0; i < 128; i++) { BYTE data = ::ReadCmos(i); ... ... } // 以下用C调演奏“多-来-米” // 1 = 262 Hz ::Sound(262); ::Sleep(200); ::NoSound(); // 2 = 288 Hz ::Sound(288); ::Sleep(200); ::NoSound(); // 3 = 320 Hz ::Sound(320); ::Sleep(200); ::NoSound();
下面给出实现设备驱动程序的动态加载的源码。动态加载的好处是,你不用做任何添加新硬件的操作,也不用编辑注册表,更不用重新启动计算机。
// 安装驱动并启动服务 // lpszDriverPath: 驱动程序路径 // lpszServiceName: 服务名 BOOL StartDriver(LPCTSTR lpszDriverPath, LPCTSTR lpszServiceName) { SC_HANDLE hSCManager; // 服务控制管理器句柄 SC_HANDLE hService; // 服务句柄 DWORD dwLastError; // 错误码 BOOL bResult = FALSE; // 返回值 // 打开服务控制管理器 hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); if (hSCManager) { // 创建服务 hService = CreateService(hSCManager, lpszServiceName, lpszServiceName, SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, lpszDriverPath, NULL, NULL, NULL, NULL, NULL); if (hService == NULL) { if (::GetLastError() == ERROR_SERVICE_EXISTS) { hService = ::OpenService(hSCManager, lpszServiceName, SERVICE_ALL_ACCESS); } } if (hService) { // 启动服务 bResult = StartService(hService, 0, NULL); // 关闭服务句柄 CloseServiceHandle(hService); } // 关闭服务控制管理器句柄 CloseServiceHandle(hSCManager); } return bResult; } // 停止服务并卸下驱动 // lpszServiceName: 服务名 BOOL StopDriver(LPCTSTR lpszServiceName) { SC_HANDLE hSCManager; // 服务控制管理器句柄 SC_HANDLE hService; // 服务句柄 BOOL bResult; // 返回值 SERVICE_STATUS ServiceStatus; bResult = FALSE; // 打开服务控制管理器 hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); if (hSCManager) { // 打开服务 hService = OpenService(hSCManager, lpszServiceName, SERVICE_ALL_ACCESS); if (hService) { // 停止服务 bResult = ControlService(hService, SERVICE_CONTROL_STOP, &ServiceStatus); // 删除服务 bResult = bResult && DeleteService(hService); // 关闭服务句柄 CloseServiceHandle(hService); } // 关闭服务控制管理器句柄 CloseServiceHandle(hSCManager); } return bResult; }
以上是关于环境下用户态直接读写端口原理浅析的主要内容,如果未能解决你的问题,请参考以下文章