数学建模社区-数学中国
标题:
关于Winnt/2k IDT的一些思考(续)
[打印本页]
作者:
韩冰
时间:
2004-10-9 14:30
标题:
关于Winnt/2k IDT的一些思考(续)
文章内容:
) L) e3 v4 j3 _% Y9 x6 x& x; H
--------------------------------------------------------------------------------
. F9 s* e; Z; C, v! ^" a$ Q
作者:suxm < suxm@nsfocus.com >
. a5 o6 Y. S2 f. p; H& |1 @* \ W* @
主页:http://www.nsfocus.com
) y7 \3 \# {; m
日期:2001-09-10
* {0 N( f+ x: B
" I* z0 y. d, A' f# F4 r
<<接上期>>
. J9 \. R& p" z" E3 [7 C
- R- M" M( H9 w5 `* ^/ D6 a6 f
IDT是定义硬件中断映射的表,当硬件中断发生的时候,CPU会直接把控制权交到IDT的相应ISR中去运行,根本不去关心是否有Kernel Interrupt Object(CPU根本不知道Kernel Interrupt Object是什么)。
3 q+ m7 Q* q9 |6 o
% P9 g- i; t. J2 _. R& @
[ 读者小强:啊???!!!想不到Interrupt Object这么不重要???那Interrupt Object在中断发生的过程中扮演着什么角色呢?]
+ C7 I+ q2 c4 Z8 x, U2 U4 t& Z
呵呵,别忙,Interrupt Object还是很重要的。当你调用IoConnectInterrupt()的时候,Kernel会构造Interrupt Object. 然后Kernel把Interrupt Object的地址放到IDT的相应表项中。这就是说,当中断发生的时候,CPU会把控制权交到IDT相应表项所对应的ISR中,而这个ISR,就 是Interrupt Object中的代码。现在明白了吧?我们可以这样理解,中断发生的时候,相应的Interrupt Object被调用。
" c6 u6 F% @3 c7 V" [( a! Z; f
下面我们将要演练一下如何在NT下hook系统中断,以使读者对以上基础知识理解得更加深刻。
" u0 L6 S" W- |' @% b% H8 w8 T; X
在开始激动人心的练习之前,我们有必要再强调一下上面的几个概念:Trap/Interrupt gate描述符、IDTR、Interrupt Object。
/ F7 T N. i$ Z% Y" Q4 F( _
通过sidt这条汇编指令,可以获得IDTR的内容,由此,可以得到IDT的Base Address(基址)和Limit(最大的长度限制)。一旦得到IDT 的Base Address,我们就可以找到想要hook的中断号,然后@#@!$!@$@#$@#%$^$……,对不起,刚才晕倒了,太激动了。然后,我们改变 那个我们想要替换的IDT表项的Code Segment Selector和Offset,当然,别忘了备份好被替换的Code Segment Selector和Offset。并要确保新 的中断处理函数能正常调用原来的中断处理函数(这就相当于把新的中断处理函数插在了原中断处理函数之前,但是不影响原中断处理函 数的使用)。
5 ]$ V$ [6 C B0 N# U4 I* e" |9 Z
[ 读者小强:什么是IDT表项的Code Segment Selector和Offset呀?]
5 G' m" K1 i1 }) n( f* t. l
[ suxm: 去看看上一期月刊的图1 ]
* `( u& [& r# w% i
下面这些代码出自《Undocumented WindowsNT》,实现上述“搜索IDT、替换并备份IDT表项”的功能。
% ]) A$ L: z0 f( C5 [9 ]+ k9 ~
( Z2 D% a; x! h. S( b& l
/* 在Winnt和Win2k下,RTC时钟中断被映射到IDT的第0x38项 */
/ {: q& c2 Q7 {2 O: F
#define HOOKINT 0x38
1 D; }' z" v @' P* @0 l9 x: ^
NTSTATUS DriverSpecificInitialization( )
2 {* ~3 W6 d" L0 u6 {' s- u/ J
{
1 a$ Q R; j9 f4 g' O
PIdtEntry_t IdtEntry;
& l) G: Q8 _/ Q* P& [& `2 C
extern PServiceDescriptorTableEntry_t KeServiceDescriptorTable;
9 z; U, C3 t: f$ Y0 F' B& D& |
# ]0 Q3 N, [( L
NumberOfServices = KeServiceDescriptorTable->NumberOfServices;
9 N' m% t( \* B- {3 s+ @
ServiceCounterTableSize = (NumberOfServices+1)*sizeof(int);
/ W% H" a9 I9 e8 K
ServiceCounterTable = ExAllocatePool(PagedPool, ServiceCounterTableSize);
5 ~- J5 B5 X, N& h7 h9 q
3 \/ z8 o; l8 m q" m
/* 有必要检查一下,有时候内存不足会导致ExAllocatePool返回NULL */
4 p- q: I* F" O6 E
if (!ServiceCounterTable)
# ]5 w6 J* o; c. @- Q& Y7 y8 m
return STATUS_INSUFFICIENT_RESOURCES;
1 W; C; L- L2 ^, F( @" y0 ?
7 ]: s. |. t/ d
memset(ServiceCounterTable, 0, ServiceCounterTableSize);
. Z( R* P7 e* J- v; R; H
*ServiceCounterTable=NumberOfServices;
r: G6 b) Y( S
; T' a: k6 f Q- G" X [2 k
/* 获得IDTR Register 的Base Address和Limit*/
. K( r0 n) ~* T5 U$ E6 O/ S
_asm sidt buffer
% [$ j/ {% k* M
IdtEntry=(PIdtEntry_t)Idtr->Base;
+ ~" a" k9 ]8 H* s
! g% }( [1 }# M3 X* Z3 J7 g) p
/* HOOKINT就是我们想要hook的中断号
( V6 |; j2 z8 q4 Z) o8 m+ F! I
这条语句的作用是找到我们要hook的IDT表项,然后备份原来的内容*/
" y* D, o) d M& V
OldHandler = ((unsigned int)IdtEntry[HOOKINT].OffsetHigh<<16U)|
, P+ [6 X, t* h9 |* w2 ^
(IdtEntry[HOOKINT].OffsetLow);
# C+ \6 g q0 g. {
) r/ O' `7 I% w4 p
/* 在IDT表项的相应位置写入新的中断处理函数的地址
& v" z, c1 g8 _' J" P9 L
用新的中断处理函数替换原中断处理函数 */
( P8 h- m8 m: V( P/ G
_asm cli /* 这里注意,因为中断说不准会在这时候发生,所以要先diable中断 */
2 z) h9 G$ X( f, [" ^$ t" ~
IdtEntry[HOOKINT].OffsetLow = (unsigned short)NewHandler;
, U" Z3 ?, F7 [) z) C9 d
IdtEntry[HOOKINT].OffsetHigh = (unsigned short)((unsigned int)NewHandler>16);
9 @$ j7 c. f7 M7 l# m3 H
_asm sti /* Enable中断请求 */
1 f4 K# ?! S$ J, a
1 P" I( l h; n! f; }+ L7 x' |2 B4 p8 Q
return STATUS_SUCCESS;
% {* `1 M# H. U4 _2 B2 D
}
5 z" J! m! q: n+ H
3 f2 W4 `) h9 I5 ~
上面是对IDT表项进行hook,我们只是修改了相应表项的Code Segment Offset。经过替换后,新的中断处理函数与原中断处理函数的“性 质相似”,即表项中其余的字段相同。如果读者对这句话不理解,那就请看IDT表项的数据结构。
% R# t. c! l4 X
typedef struct InterruptGate
" z& p# S' G' }0 g, f2 G
{
" G0 H4 ?, K7 ^, @' P
unsigned short OffsetLow;
6 W3 `- V7 ]# N: G
unsigned short Selector;
- j- n7 j5 \. q/ }4 G: a7 |, ^
unsigned char Reserved;
2 o1 J9 t4 X& l' @2 U0 A
unsigned char SegmentType;
+ m1 K3 [2 p3 \4 }* V
unsigned char SystemSegmentFlag;
. t, b \5 ~" b+ |2 R
unsigned char Dpl;
& ~4 K7 K( T2 t. F
unsigned char Present;
6 v4 \9 z6 L, F
unsigned short OffsetHigh;
8 d8 ^5 T3 c) H
} InterruptGate_t;
' I* x+ T U. G. K
- J5 X6 h' X7 t: v6 v8 U
在上面的例子中,OffsetLow和OffsetHigh被改写了,从而hook了中断。让我们思考这样一个问题:如果IDT的相应表项没有内容呢,也就 是说,如果我们想让IDT的空表项指向我们的中断处理函数,又该怎么办呢?
8 Y2 F: T) |. t( R6 b
! f" y' F: ~* R1 K, R
[ 读者小强:Winnt和2K的IDT中有空表项吗?]
* }0 c8 K# @/ D% u; t/ H
是的,比如说,在Winnt下,IDT表项的第22h---29h就是空表项。 这就是说我们可以给Winnt加入新的中断。但是,这里有了新的难度, 我们必须认真理解和填写上面数据结构InterruptGate中的所有字段。
# N4 a, ^2 b |3 o
下面这些代码出自《Undocumented WindowsNT》,实现上述“搜索IDT、填写IDT表项所有字段”的功能。
: a Y* O/ @. C' J8 ?
NTSTATUS AddInterrupt()
( ~% K+ w' Y; x
{
: X+ E/ C) _ G+ s$ J6 Q9 {* t/ j0 \
PIdtEntry_t IdtEntry;
. T: Q" u, D5 M; l2 m1 |0 h
2 q/ M: [8 j) r
/* 获得IDTR Register 的Base Address和Limit*/
0 Z* M9 ?7 K4 y
_asm sidt buffer
6 o1 P5 L# M% m" Q/ i- V) |5 @
IdtEntry=(PIdtEntry_t)Idtr->Base;
& p$ t7 Y9 ^8 V; \) F; [2 B
if((IdtEntry[ADDINT].OffsetLow!=0)||(IdtEntry[ADDINT].OffsetHigh!=0))
9 R4 b' D' J) E. f$ k9 W1 n: h9 _
return STATUS_UNSUCCESSFUL;
* C3 H: J* U4 T
5 v4 P: A+ g9 J$ J" D4 `# s3 M
_asm cli
8 d0 U. T: |0 f p8 ?$ _
$ n6 W8 Q4 e7 `: h9 C& ?
/* 填写IDT表项的所有字段,使新的IDT表项指向我们的中断处理函数 */
v( l! I3 w* T5 h
IdtEntry[ADDINT].OffsetLow=(unsigned short)InterruptHandler;
2 P" ]# v0 B& ?3 a8 V
IdtEntry[ADDINT].Selector=8;
; w8 }8 v7 R' `2 W3 V' _. ]1 B$ ^
IdtEntry[ADDINT].Reserved=0;
' H1 p+ t* X+ t: h
IdtEntry[ADDINT].Type=0xE;
, p8 X6 r( U+ \( F- u
IdtEntry[ADDINT].Always0=0;
; z4 P. F. E8 _
IdtEntry[ADDINT].Dpl=3;
+ C4 O/ G3 s- S9 F
IdtEntry[ADDINT].Present=1;
4 L0 L& R4 `: [" h
IdtEntry[ADDINT].OffsetHigh=
, U1 X# C5 ?* h) b
(unsigned short)((unsigned int) InterruptHandler>16);
3 J6 c: e" E( v% e
_asm sti
{7 f1 [1 t8 H
( ?5 T$ e0 a+ ?# j6 F- l I: E# q! w' ~
return STATUS_SUCCESS;
Z" s5 W2 }3 u- l
}
& M, {7 k5 ^; i/ x9 t" D2 K+ a% k
4 }0 t, C/ e& z+ A R, r8 C" ^
让我们就上面的代码提一些问题。
# `4 Q e" m7 j O1 \: f- H- V6 Y7 ^
Q: 上面的代码IdtEntry[ADDINT].Selector=8; 这是怎么回事?
" c' h( ?- R: v# ^ b0 j0 M
A:按Ctrl+D进到Softice里,你会发现,所以的system代码,都存在于一个段中。这
, k' M: r0 x" h1 L& O
个段的名称就是8(CS=8)。
& W9 |) T& e2 n4 K0 B' [
: B8 |) ^' u4 f9 d) Z
Q: 上面的代码IdtEntry[ADDINT].Dpl=3; 这是怎么回事?
q8 n5 H! M c. B. [- Q ^
A:问得好,小强。这说明,这个中断可以在应用程序中,通过int xxx的形式触发。因为
" [, G$ |2 G }$ I( ?: p p
应用程序的DPL=3,所以,可以直接触发这个中断。
+ \+ r2 L% D* c# X2 G# o. v
* {( e7 u. e& n/ J, {! k5 p; G# x# f
Q: 上面的代码IdtEntry[ADDINT].Type=0xE; 这是怎么回事?
# {. Y8 _0 l" p4 J. w5 Y2 X
A:按Ctrl+D进到Softice里,然后敲入指令idt。输出如下
5 M7 ~5 k; r6 F4 ]
:idt
& [" n! U+ z- H6 z0 @+ B" ~
Int Type Sel:Offset Attributes Symbol/Owner
: H" I% z. W) a ?6 ?
IDTbase=80036400 Limit=07FF
/ ?, |4 I7 D9 F# q( t$ T
0000 IntG32 0008:80465946 DPL=0 P ntoskrnl!Kei386EoiHelper+0590
! a+ x3 s3 W/ `* J# O
0001 IntG32 0008:80465A96 DPL=3 P ntoskrnl!Kei386EoiHelper+06E0
7 U/ \7 C, b& L9 U- H J
……
1 ^/ _! A; \7 P
可见,0xE表示IntG32。
j% K V( G: j% L
: s/ f5 S, B) n5 j
终于快到下课的时间了,让我再多说几句好吗?
7 [: s' ?7 k5 J! C$ K' ^8 l4 @
通过对Winnt/2k IDT的分析,我们对IDT有了一些基本认识。上面介绍的通过修改IDT来hook中断的方法,是很危险的。笔者强烈建议读 者尽量通过Winnt/2k DDK官方资料提供的API来实现hook中断的功能。
欢迎光临 数学建模社区-数学中国 (http://www.madio.net/)
Powered by Discuz! X2.5