From KMD:
Most strange thing here is that we have accessed the SMOS memory without the system stops us. As I have already mentioned above, the access to I/O ports is protected under Windows NT. Executing IN or OUT instruction in user-mode will cause process termination. But we have touched them. How it can be? Well, it becomes possible due to the giveio driver.
The driver's code is based on well-known example (giveio) by Dale Roberts. I have decided it will be appropriate to mention here.
Our driver changes the I/O permission bit map (IOPM) that allows the process free access to the I/O ports. Each process has its own I/O permission bit map, thus access to the individual I/O ports can be granted to the individual process. Each bit in the I/O permission bit map corresponds to the byte I/O port. If this bit is set, the access to the corresponding port is forbidden, if it is clear the process may access this I/O port. Since the I/O address space consists of 64K individually addressable 8-bit I/O ports, the maximum IOPM size is 2000h bytes.
The purpose of the TSS is to save the state of the processor during task or context switches. For performance reasons, Windows NT does not use this architectural feature and maintains one base TSS that all processes share. This means that IOPM is also shared. So any changes to it are not private for particular process but are system-wide.
There are some undocumented functions in the ntoskrnl.exe to manipulate with the IOPM:
Ke386QueryIoAccessMap and Ke386SetIoAccessMap.
Ke386QueryIoAccessMap proto stdcall dwFlag:DWORD, pIopm:PVOID
Ke386QueryIoAccessMap copies current IOPM by the size of 2000h bytes from TSS to the memory buffer pointed to by pIopm parameter.
Ke386SetIoAccessMap copies specified IOPM by the size of 2000h from the memory buffer pointed to by pIopm parameter to TSS.
[some comment from http://www.nsfocus.net/index.php?act=magazine&do=view&mid=2205]
I think these comments are extremely necessary, so I decided to concatenate them here.
通过前面对 NT 系统中 KTSS 结构和实际内存的分析,我们可以了解:NT 环境下,每个进程单独维护了一个 TSS 内存区域,其中由 TSS 内部维护了一个全部标志位置 1 的 IOPM 表,在 TSS 末尾还维护了另外一个实际中承担端口管理工作的 IOPM 表。Ke386SetIoAccessMap 函数(ntos\ke\i386\iopm.c:80)和 Ke386QueryIoAccessMap 函数(ntos\ke\i386\iopm.c:235)就是系统提供用来读写这两个 IOPM 表的函数。而 Ke386IoSetAccessProcess 函数(ntos\ke\i386\iopm.c:318)则指定进程到底使用哪个 IOPM 表。
对前两个函数来说,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 < iopm_count =" 1)。伪代码如下:
#define IOPM_COUNT 1
#define IOPM_SIZE 8192 // Size of map callers can set.
BOOLEAN Ke386QueryIoAccessMap(ULONG MapNumber, PKIO_ACCESS_MAP IoAccessMap)
{
if(MapNumber > IOPM_COUNT) return FALSE;
if(MapNumber == IO_ACCESS_MAP_NONE)
{
memset(IoAccessMap, -1, IOPM_SIZE);
}
else
{
void *pIOPM = &(KiPcr()->TSS->IoMaps[MapNumber-1].IoMap);
memcpy(IoAccessMap, pIOPM, IOPM_SIZE);
}
return TRUE;
}
而 Ke386SetIoAccessMap 在 MapNumber 为 0 时直接返回 FALSE,因为 TSS 后的那个表是不允许修改的;对其他情况,函数将 IoAccessMap 中的内容复制回 TSS 的 IOPM 表中,并在多处理器情况下通知其他处理器重新载入 IOPM 表。伪代码如下:
BOOLEAN Ke386SetIoAccessMap(ULONG MapNumber, PKIO_ACCESS_MAP IoAccessMap)
{
if((MapNumber > IOPM_COUNT) (MapNumber == IO_ACCESS_MAP_NONE)) return FALSE;
void *pIOPM = &(KiPcr()->TSS->IoMaps[MapNumber-1].IoMap);
memcpy(pIOPM, IoAccessMap, IOPM_SIZE);
KiPcr()->TSS->IoMapBase = GetCurrentProcess()->IopmOffset;
// 通知其他处理器重设 IOPM
return TRUE;
}
Ke386IoSetAccessProcess 函数则简单地修改当前 TSS 的 IOPM 偏移为 MapNumber 指定的 IOPM 表偏移,并在多 CPU 情况下通知其他 CPU 重新载入 IOPM 偏移。计算偏移算法如下:
#define KiComputeIopmOffset(MapNumber) \
(MapNumber == IO_ACCESS_MAP_NONE) ? \
(USHORT)(sizeof(KTSS)) : \
(USHORT)(FIELD_OFFSET(KTSS, IoMaps[MapNumber-1].IoMap))
USHORT MapOffset = KiComputeIopmOffset(MapNumber);
完整的使用流程代码如下:
#define IOPM_SIZE 8192 // Size of map callers can set.
typedef UCHAR KIO_ACCESS_MAP[IOPM_SIZE];
typedef KIO_ACCESS_MAP *PKIO_ACCESS_MAP;
PKIO_ACCESS_MAP IOPM_local = MmAllocateNonCachedMemory(sizeof(IOPM));
if(IOPM_local == 0)
return STATUS_INSUFFICIENT_RESOURCES;
Ke386QueryIoAccessMap(1, IOPM_local);
// 修改 IOPM_Local 内容打开需要使用的端口
Ke386SetIoAccessMap(1, IOPM_local);
Ke386IoSetAccessProcess(PsGetCurrentProcess(), 1);
I will make the English translation later.