首页 -> 安全研究

安全研究

绿盟月刊
绿盟安全月刊->第24期->技术专题
期刊号: 类型: 关键词:
关于Winnt/2k IDT的一些思考

作者:suxm < suxm@nsfocus.com >
主页:http://www.nsfocus.com
日期:2001-08-13

几乎每一个不太守纪律的Windows developer都想在Windows中挂一个ISR,就象在DOS下那样,接管Windows的中断,然后,做一些离奇的事情。由于ISR是很特殊的程序(常驻性、触发性、Ring0级),所以有很多很特殊的用途。最基本的用途,应该算是硬件驱动开发了:要想接管硬件中断,没有ISR是不行的。
迄今为止,在Windows 3.x & 9X下,ISR的编写、调试已经不是秘密了。但是在Winnt/2k下,这还是很痛苦的事情。我听一个国外的哥们说,他本科毕业论文就是<<Discovering How Winnt/2k Maps the IRQs from the PIC (Programable Interrupt Controller) to Its Own IRQLs >>,怎么样,faint了吧?呵呵。
让我们先来回忆一下Win9x下ISR是怎么回事(参见我的文章<<VxD技术内幕>>)。
Win9x下,IDT中的每一项,也就是每一个描述符,都定义了256个中断中的一个。还记得实模式下MS-DOS环境中的中断向量表吧(就是那张从内存的0000:0000开始的向量表,每一个表项有4个字节,一共有256个中断向量)?在保护模式下,中断描述表IDT代替了中断向量表IVT。虽说中断向量表IDT可以容纳8192个中断描述符,可是CPU能利用的只有处于前面的256个。所以中断描述表(IDT)的长度限制应该是7FFh( ),



    其实中断描述表(IDT)中可以有两种描述符:Interrupt gate描述符、Trap gate描述符(更确切的应该说有三种:Interrupt gate描述符、Trap gate描述符、Task gate描述符,但是一般来说是不会包含Task gate描述符的)。图1显示了Trap/Interrupt gate描述符的结构。这里提到了gate这个词,一般译作“门”。如果有人跟你说起“中断门”,千万不要大惊小怪,“中断门”更形像地直意了中断调用的过程:中断调用就像就经过一扇门一样,这个门就是中断描述符,因为中断描述符中有DPL等权限盘查的标志,所以要想通过这扇门调用相应的中断服务程序是需要一定的资格的(CPL<=DPL)。
    Trap和Interrupt gate非常相似,一般来说Trap gate是用来捕获系统异常,而Interrupt gate用来响应中断。在具体的实现上,只有一点不同:Interrupt gate会将IF置为0,这样可以屏蔽硬件中断。但是Trap gate却不会改变IF的值。
    下面的代码示意了如何设置一个键盘中断描述符(interrupt 51h):
    int51        dw        kb_int    ;keyboard-handler    offset
                dw        kb_sel    ;keyboard-handler    code selector
                db        0        
                db        8eh        ;386 interrupt-gate
                dw        0        ;offset is in first 64KB of segment
    是不是有些困惑?为什么键盘中断变成int 51h了?以前在DOS下不是int 9h吗?
这个问题的答案是这样的,由于在保护模式下,有很多中断号分配给了系统异常处理(比如说int 9h分配给了CPU No NPX异常处理),所以,硬件中断处理的中断号都作了调整:实模式下的中断号08h——0Fh和70h——77h(即硬件中断号IRQ 0~16)对应着保护模式下的中断号50h——5Fh。
    提问:Win9x中,是如何区分硬件中断IRQ 12和int 5Ch 调用(NetBIOS调用)的呢?
    答案:由于IDT的50h——5Fh中断描述符的DPL=0,这是专门用来截获int指令的,而硬件中断不管DPL是多少都可以被调用。Win9x就是通过这点区别来分辨到底是int 5Ch这条指令还是IRQ 12的硬件中断,从而调用系统服务或ISR。
    在单CPU的Winnt下,实模式下的中断号08h——0Fh和70h——77h(即硬件中断号IRQ 0~16)对应着保护模式下的中断号30h——3Fh(看到有些资料上说Winnt下这个映射关系不是确定的,但是我见过的单CPU的Winnt中基本上都是这样的映射关系,而双CPU的情况下,情况确实有所不同)。从下面这张统计表(http://www.wischrop-net.de/nt/kapitel7.htm#7_2)可以看到单CPU的情况与双CPU的情况是大不相同的。
     Aladin BasysGate BIG_WWW Hugo_C  STAT_02 STAT_13  OTTO  SanderD  Notebook
IRQL    
1     3D               3D
2     41               41
3
4     51               51                                      51
5     61               61                                      61
6     71               71
7     81/82            81/82                                   81
8     91/92            91/92                                   91/92
9     A2               A2                                      A1
10     B1               B1                                      B1/B2
11
12              3F      
13                              3E      3E              3E              3E
14
15              3C                      3C      3C      3C              3C
16              3B              3B      3B
17                                              3A                      3A
18                                              39      39
19
20              37                                      37              37
21              36              36      36      36      36              36
22                                              35                      35
23              34              34      34      34      34              34
24                                                      33
25              
26              31              31      31      31      31              31
27      C1      38      C1      38      38      38      38      C1      38
28      D1      30      D1      30      30      30      30      D1      30
29      E1              E1                                      E1
30      FE/FD           FD/FE                                   FD/FE
31      1F/FF   32      1F/FF   32      32      32      32      1F/FF   32

255     50              50                                      50
DPL3    2A-2E   2A-2E   2A-2E   2A-2E   2A-2E   2A-2E   2A-2E   2A-2E   2A-2E

机器配置:
ALADIN            -  Dual Pentium II 266 MHz, NT 4.0 (free) SP3
BasysGate           -  Pentium II 233 MHz, NT 4.0 (free) SP3
BIG_WWW          -  Dual Pentium II 266 Mhz, NT 4.0 (free) SP3
Hugo_C             -  AMD K6 200 Mhz, NT 4.0 (checked) SP1
Stat_02              -  Pentium 166 MHz, NT 4.0 (free) SP3
Stat_13             -  Pentium Pro 200 Mhz, NT 4.0 (free) SP2
OTTO                -  Pentium II 300 MHz, NT 4.0 (free) SP4
SanderD             -  Dual Pentium 166 MHz, NT 4.0 (free), SP3
Notebook            -  Pentium 200 MHz, NT 4.0 (free), SP3

在Win2k下,实模式下的中断号08h----9Fh和70h----77h不再对应着保护模式下的中断号50h------5Fh或30h-----3Fh。那么,究竟是怎样一种映射关系呢,至今还没有公开的答案。我们可以用HalGetInterruptVector(参见Win2k DDK Help)这个函数获得硬件中断对应于Win2k的中断号(即IDT中的位置)。但是这不是固定映射的关系,也许明天开机的时候,就会得到不同的返回值。
    Win2k下,在SoftIce中用:idt命令可以看到当前系统的IDT状态。
:idt
Int   Type     Sel:Offset     Attributes Symbol/Owner
IDTbase=80036400  Limit=07FF        从这里我们可以看到IDT的Base Address和Limit
0000  IntG32   0008:80465946  DPL=0  P   ntoskrnl!Kei386EoiHelper+0590
0001  IntG32   0008:80465A96  DPL=3  P   ntoskrnl!Kei386EoiHelper+06E0
……
00FD  IntG32   0008:804646F2  DPL=0  P   ntoskrnl!ExReleaseResourceForThread+092
00FE  IntG32   0008:804646F9  DPL=0  P   ntoskrnl!ExReleaseResourceForThread+092
00FF  IntG32   0008:80464700  DPL=0  P   ntoskrnl!ExReleaseResourceForThread+093
    从上面的输出我们可以看到在Win2k下,IDT的有效长度还是7FFh,这与前面讲到的Win9x下的IDT的长度是一样的。其实这是由CPU只能利用IDT的前256项决定的。
    在Win2k下,在命令行敲入winmsd.exe,可以看到自己机器上硬件中断号的分布状况。



    提问:IDT表的Base Address怎样得到的呢?
    答案:sidt指令,比如说sidt  [eax],就是把IDT 寄存器(其中含有IDT的Base Address)中的值放到[eax]指向的地址。请看一下
http://developer.intel.com/design/intarch/techinfo/Pentium/instform.htm的说明:
SIDT - Store Interrupt Descriptor Table Register

     Guido Wischrop提供了下面的代码可以读取IDT的所有256项。

        UCHAR *mBuffer;
        ULONG dummy;
        ……
        _asm
        {
                mov   esi,mBuffer    
                sidt  [esi]                    //load IDTR to *mBuffer
                movzx ecx,word ptr [esi]        //load size of IDT to ECX
                mov   ebx,dword ptr [esi+2]        //load Base Address of LDT to EBX
                inc   ecx                    
                mov   edi,mBuffer            //mBuffer is the target
                mov   dummy,ecx                //store size in dummy
                mov   esi,ebx                //store IDT Base Address to ESI
                shr   ecx,2                      
                rep   movsd                    //copy IDT to mBuffer
        }

    这样我们就可以得到想要的IDT中的某一项,那么,我们是不是可以通过改写内存的方式直接替换相应的IDT表项呢?答案是肯定的,有人声称这样能成功。我觉是没有太大的必要,因为通过标准的Kernel API调用也可以实现。
    我们可以用Windriver生成代码来简单地体会一下挂硬件中断ISR的感觉。
(1)    Windriver---> Driver Wizard---->New Project----->ISA CARD
(2)    Interrupts---> New
Interrupt Number = 14(或别的) & Edge Triggered & Shared
(3)    Listen to Interrupts
如果没有出现错误,则表明这个硬件中断可以挂我们的ISR。
(4)    Generate Code
(5)    Build and Run
(6)    Enable Interrupt
(7)    切换到SoftIce中去(Ctrl + D),然后敲:intobj命令,可以看到如下的输入信息:
:intobj
Object            Service   Service               Affinity
Address   Vector  Address   Context   IRQL  Mode    Mask    Symbol
FD349508    31    F7861900  FD4683E0   1A   Edge     01     i8042prt!.text+1600
FD348D88    3C    F786798C  FD35D020   0F   Edge     01    i8042prt!PAGEMOUC+020C
FD189708    3D    FC8F22C0  00000001   0E   Edge     01     WINDRVR!.text+2040
FD48F788    3E    FD10FE42  FD4BD030   0D   Edge     01     atapi!.text+5AE2
FD34A888    3F    FD057AA0  FD34E0DC   0C   Level    01     NDIS!PAGENDSM
NTICE: Exit32 PID=70  MOD=ps2_diag

    从上面可以看到,0Eh(即14)号硬件中断被映射到了IDT的3Dh项。还不相信吗?好,让我们来测试一下。在SoftIce中敲入如下的指令:genint 3d,看到什么了?呵呵,屏幕显示如下:



我们用:genint 3d这条指令模拟了一次硬件中断,结果表明我们的ISR确实是挂在了IDT的3Dh项处。在Softice中再运行几次genint 3d试试看,屏幕上会输出什么样的结果呢?当然是Got Interrupt0 number 2,Got Interrupt0 number 3之类的信息啦。

    一个问题马上被提出来了
    提问:如何理解IDT&ISR与Interrupt Object的关系?Interrupt Dispatcher是如何通过IDT和Interrupt Object找到合适的ISR进行中断调用的?
    答案:见下期
版权所有,未经许可,不得转载