zoukankan      html  css  js  c++  java
  • 用户态直接读写端口原理浅析

    抄的:

    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);
    }
    View Code
    // 以下读出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();
    View Code

    下面给出实现设备驱动程序的动态加载的源码。动态加载的好处是,你不用做任何添加新硬件的操作,也不用编辑注册表,更不用重新启动计算机。

    // 安装驱动并启动服务
    // 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;
    }
    驱动加载
  • 相关阅读:
    神秘题目4
    神秘题目3
    神秘题目2
    AC自动机
    Fence Obstacle Course 题解
    Fractal Streets
    龟速乘
    快速幂
    Stall Reservation
    Sunscreen
  • 原文地址:https://www.cnblogs.com/a-s-m/p/12380512.html
Copyright © 2011-2022 走看看