关于ring3访问硬件,今天我们将采用另外的方法来进行I/O访问。为了找出方法,我们还是要继续看看I/O保护的八条规则:呵呵,正所谓“知己知彼,百战不殆。“
(1)若CPL<=IOPL,则直接转步骤(8);
(2)取得I/O位图开始偏移;
(3)计算I/O地址对应位所在字节在I/O许可位图内的偏移;
(4)计算位偏移以形成屏蔽码值,即计算I/O地址对应位在字节中的第几位;
(5)把字节偏移加上位图开始偏移,再加1,所得值与TSS界限比较,若越界,则产生出错码为0的通用保护故障;
(6)若不越界,则从位图中读对应字节及下一个字节;
(7)把读出的两个字节与屏蔽码进行与运算,若结果不为0表示检查未通过,则产生出错码为0的通用保护故障;
(8)进行I/O访问。
上一篇中我们已经介绍了修改IOPL = 3,使其顺利通过保护规则。因此八条规则中的第一条我们已经用过了,今天我们从第二条开始找办法。
我们先看看I/O许可位图,它位于任务状态段TSS中。要找出tss段所在的线性地址,需要先从tr寄存器中,找出任务状态段的选择子,根据这个选择子我们可以在GDT中找到任务状态段描述符,根据这个描述符,我们可以找出TSS在内存中的位置。TSS是保存一个任务重要信息的特殊段。我们先看看其内存结构。
lkd> dt _ktss
nt!_KTSS
+0x000 Backlink : Uint2B
+0x002 Reserved0 : Uint2B
+0x004 Esp0 : Uint4B
+0x008 Ss0 : Uint2B
+0x00a Reserved1 : Uint2B
+0x00c NotUsed1 : [4] Uint4B
+0x01c CR3 : Uint4B
+0x020 Eip : Uint4B
+0x024 EFlags : Uint4B
+0x028 Eax : Uint4B
+0x02c Ecx : Uint4B
+0x030 Edx : Uint4B
+0x034 Ebx : Uint4B
+0x038 Esp : Uint4B
+0x03c Ebp : Uint4B
+0x040 Esi : Uint4B
+0x044 Edi : Uint4B
+0x048 Es : Uint2B
+0x04a Reserved2 : Uint2B
+0x04c Cs : Uint2B
+0x04e Reserved3 : Uint2B
+0x050 Ss : Uint2B
+0x052 Reserved4 : Uint2B
+0x054 Ds : Uint2B
+0x056 Reserved5 : Uint2B
+0x058 Fs : Uint2B
+0x05a Reserved6 : Uint2B
+0x05c Gs : Uint2B
+0x05e Reserved7 : Uint2B
+0x060 LDT : Uint2B
+0x062 Reserved8 : Uint2B
+0x064 Flags : Uint2B
+0x066 IoMapBase : Uint2B
+0x068 IoMaps : [1] _KiIoAccessMap
+0x208c IntDirectionMap : [32] UChar
lkd> dt _KiIoAccessMap
nt!_KiIoAccessMap
+0x000 DirectionMap : [32] UChar
+0x020 IoMap : [8196] UChar
根据这个结构我们可以计算出其长度为 :0x208c + 32 - 1 = 0x20ab
其中的IoMapBase就是I/O许可位图的在TSS段中的偏移位置。也就是说在TSS段中从这个位置开始就是I/O许可位图了。
对于本机操作系统,我们可以通过WINDBG察看下该结构的值。
首先我们找到TSS的内存位置,我们可以通过如下指令得到TSS: 80042000
lkd> !PCR
KPCR for Processor 0 at ffdff000:
Major 1 Minor 1
NtTib.ExceptionList: b2616c7c
NtTib.StackBase: b2616df0
NtTib.StackLimit: b2614000
NtTib.SubSystemTib: 00000000
NtTib.Version: 00000000
NtTib.UserPointer: 00000000
NtTib.SelfTib: 7ffde000
SelfPcr: ffdff000
Prcb: ffdff120
Irql: 00000000
IRR: 00000000
IDR: ffffffff
InterruptMode: 00000000
IDT: 8003f400
GDT: 8003f000
TSS: 80042000
CurrentThread: 88fb6da8
NextThread: 00000000
IdleThread: 80552d20
DpcQueue:
接下来,我们看看这个结构中每一项的实际值。
lkd> dt _ktss 80042000
nt!_KTSS
+0x000 Backlink : 0
+0x002 Reserved0 : 0x838d
+0x004 Esp0 : 0xb1da5de0
+0x008 Ss0 : 0x10
+0x00a Reserved1 : 0xb9fc
+0x00c NotUsed1 : [4] 0x1000
+0x01c CR3 : 0x12640740
+0x020 Eip : 0x14453b02
+0x024 EFlags : 0x8a870f
+0x028 Eax : 0xc18b0000
+0x02c Ecx : 0x8d02e9c1
+0x030 Edx : 0xfb8b0272
+0x034 Ebx : 0xc88ba5f3
+0x038 Esp : 0xf303e183
+0x03c Ebp : 0xc5d03a4
+0x040 Esi : 0x3bf8558b
+0x044 Edi : 0x7174fc5d
+0x048 Es : 0x458b
+0x04a Reserved2 : 0x8314
+0x04c Cs : 0xfec0
+0x04e Reserved3 : 0xd03b
+0x050 Ss : 0x6777
+0x052 Reserved4 : 0xc2f6
+0x054 Ds : 0x7401
+0x056 Reserved5 : 0x8a0d
+0x058 Fs : 0x8802
+0x05a Reserved6 : 0x1045
+0x05c Gs : 0x428a
+0x05e Reserved7 : 0x8801
+0x060 LDT : 0
+0x062 Reserved8 : 0x7eb
+0x064 Flags : 0
+0x066 IoMapBase : 0x20ac
+0x068 IoMaps : [1] _KiIoAccessMap
+0x208c IntDirectionMap : [32] "???"
我们看IoMapBase值为: 20ac. 而在上面我们看到TSS的大小是0x20ab,说明已经越界,根据前面我们列出的检测规则第五条,产生出错码为0的通用保护故障;因此,看到这里,我们可以考虑一种办法,就是扩充下这个结构的大小,让tss的结构大小大于0x20ac,
也就是说在tss中给I/O许可位图留出一块空间来。考虑到增加部分不能增加物理分页,因此我们可以设置的结构体最大值就是0x2fff。这样,我们扩充的大小是0x2fff-0x20ab = 0xf54;
因为I/O许可位图中每一bit代表一个端口,因此,0xf54字节对应的可访问的最大端口是:
0xf54 * 8 = 0x7aa0,对应于10进制,就是31392。因此我们可访问的i/o口范围是0 ~ 31392。
因为每个进程都有自己的I/O许可位图,每个单独的I/O端口的访问权限都可以对每个进程进行单独授权,如果相关的位被设置的话,对对应端口的访问就是被禁止的,如果相关的位被清除,那么进程就可以访问对应的端口。
TSS的设计意图是为了在任务切换的时候保存处理器状态,从执行效率的考虑出发,Windows NT并没有使用这个特征,它只维护一个TSS供多个进程共享,因此本篇中我们扩展了tss结构大小,扩展部分会影响到所有进程。
其实这种做法,早在1995年Dale Roberts 写了一个名叫totalio的例子影响较广,后来在2002年,sinister大牛在此基础上改写了一个。虽然做法早就有了,但是网上并没有过多地介绍其中的原理。
今天我们贴出的代码就是sinister大牛的。大家一起来学习。我在其代码上作了一个小小的调整,就是原来其扩展到0x2fab, 我把这个扩展范围扩大到了0x2fff. 反正分页是0x1000大小对齐,剩余的空间闲着也是浪费。
测试代码:
#include "stdio.h"
void main()
{
_asm
{
mov dx,0x64
mov al,0xfe
out dx,al
}
}
- 标 题:rootkit 直接访问硬件之[二]
- 作 者:combojiang
- 时 间:2008-03-28 11:17
- 链 接:http://bbs.pediy.com/showthread.php?t=62061