数学建模社区-数学中国
标题:
关于Winnt/2k IDT的一些思考(续)
[打印本页]
作者:
韩冰
时间:
2004-10-9 14:30
标题:
关于Winnt/2k IDT的一些思考(续)
文章内容:
; j3 Y F. E( \* e8 b, k/ p* X
--------------------------------------------------------------------------------
8 O! {5 q" P2 ^; [8 A* a
作者:suxm < suxm@nsfocus.com >
; e# O, `; ?& u( d7 C$ v. w
主页:http://www.nsfocus.com
3 q' i! N; Y0 ]
日期:2001-09-10
1 F( ^1 I; g+ \. A) P2 x0 q g/ a
+ U6 b! J; N& n4 [4 l
<<接上期>>
' ]* x! P) V& j2 H3 O$ j+ g D' ^# P
! G1 b) Z7 {5 v H8 E7 W2 c
IDT是定义硬件中断映射的表,当硬件中断发生的时候,CPU会直接把控制权交到IDT的相应ISR中去运行,根本不去关心是否有Kernel Interrupt Object(CPU根本不知道Kernel Interrupt Object是什么)。
5 A# H3 f1 ?% \7 |3 h& Z7 d
( y V, s7 C- h
[ 读者小强:啊???!!!想不到Interrupt Object这么不重要???那Interrupt Object在中断发生的过程中扮演着什么角色呢?]
" ?% z- b7 i7 ^2 X, W
呵呵,别忙,Interrupt Object还是很重要的。当你调用IoConnectInterrupt()的时候,Kernel会构造Interrupt Object. 然后Kernel把Interrupt Object的地址放到IDT的相应表项中。这就是说,当中断发生的时候,CPU会把控制权交到IDT相应表项所对应的ISR中,而这个ISR,就 是Interrupt Object中的代码。现在明白了吧?我们可以这样理解,中断发生的时候,相应的Interrupt Object被调用。
3 ^! h8 d H2 Q0 F1 e) J" l4 ]
下面我们将要演练一下如何在NT下hook系统中断,以使读者对以上基础知识理解得更加深刻。
" ?" I- x8 n" F; y- d
在开始激动人心的练习之前,我们有必要再强调一下上面的几个概念:Trap/Interrupt gate描述符、IDTR、Interrupt Object。
8 L5 } Q$ J& Q7 H" P
通过sidt这条汇编指令,可以获得IDTR的内容,由此,可以得到IDT的Base Address(基址)和Limit(最大的长度限制)。一旦得到IDT 的Base Address,我们就可以找到想要hook的中断号,然后@#@!$!@$@#$@#%$^$……,对不起,刚才晕倒了,太激动了。然后,我们改变 那个我们想要替换的IDT表项的Code Segment Selector和Offset,当然,别忘了备份好被替换的Code Segment Selector和Offset。并要确保新 的中断处理函数能正常调用原来的中断处理函数(这就相当于把新的中断处理函数插在了原中断处理函数之前,但是不影响原中断处理函 数的使用)。
+ c3 A6 o& N; d5 L! f1 S, U
[ 读者小强:什么是IDT表项的Code Segment Selector和Offset呀?]
. K4 |; V) S: J* k& G6 Q
[ suxm: 去看看上一期月刊的图1 ]
7 |+ @( z% V4 U$ i7 K T
下面这些代码出自《Undocumented WindowsNT》,实现上述“搜索IDT、替换并备份IDT表项”的功能。
! J& n+ x3 M7 ^5 A" h
* |5 M0 _4 G+ c* w
/* 在Winnt和Win2k下,RTC时钟中断被映射到IDT的第0x38项 */
6 _* E% Y& {! t) @4 [% y
#define HOOKINT 0x38
4 h, t$ z% y s$ H1 \1 D% o! o: A
NTSTATUS DriverSpecificInitialization( )
q' g+ Z& T8 L8 t4 w9 O' {
{
: z4 \( l# T2 g \5 l
PIdtEntry_t IdtEntry;
& z; I" u2 g# o( [7 g7 h5 o
extern PServiceDescriptorTableEntry_t KeServiceDescriptorTable;
. ^8 p, `, |6 h! q- Z3 U/ P
1 y$ g9 v$ N6 z
NumberOfServices = KeServiceDescriptorTable->NumberOfServices;
" J2 r, n1 G' w" Q
ServiceCounterTableSize = (NumberOfServices+1)*sizeof(int);
3 ~% v- ]0 T% Y/ w! m. E2 `
ServiceCounterTable = ExAllocatePool(PagedPool, ServiceCounterTableSize);
0 }# R# B- F- x( Q$ b7 g8 I
7 ] e4 p a: K% f) F) u
/* 有必要检查一下,有时候内存不足会导致ExAllocatePool返回NULL */
, ?2 c' d1 Z' [$ k! ]% t9 H
if (!ServiceCounterTable)
1 u) t m W2 Z1 b$ W( U/ l; K i" D0 l
return STATUS_INSUFFICIENT_RESOURCES;
. O* \; G$ O: p+ S+ e
6 U2 \5 Y, |8 e- [" d& P4 M
memset(ServiceCounterTable, 0, ServiceCounterTableSize);
9 R. J+ X+ y( p. P
*ServiceCounterTable=NumberOfServices;
# s. h! c; }7 h
& O7 s; d* i4 R6 x/ [
/* 获得IDTR Register 的Base Address和Limit*/
# i7 S/ z; ? w1 i# r6 f: E
_asm sidt buffer
% N& I7 A* a) ?9 Y/ k1 z2 J! g
IdtEntry=(PIdtEntry_t)Idtr->Base;
2 u4 ~6 i; }5 l, S. v7 d% C; T
2 B6 \! `5 f/ A
/* HOOKINT就是我们想要hook的中断号
$ N( r0 q7 c! M
这条语句的作用是找到我们要hook的IDT表项,然后备份原来的内容*/
7 s5 R& P& C( T/ J! R$ V- G/ T2 ~
OldHandler = ((unsigned int)IdtEntry[HOOKINT].OffsetHigh<<16U)|
' ~& l) g& [: e b" i3 B
(IdtEntry[HOOKINT].OffsetLow);
) B) M1 J; f2 `
' @5 H; n5 s* F/ } q& i
/* 在IDT表项的相应位置写入新的中断处理函数的地址
3 T0 j( L* c0 T* ? G- u
用新的中断处理函数替换原中断处理函数 */
/ H/ _/ m$ S+ v* b. d: \
_asm cli /* 这里注意,因为中断说不准会在这时候发生,所以要先diable中断 */
6 D& j2 B8 f# u0 I5 Z. y9 ^
IdtEntry[HOOKINT].OffsetLow = (unsigned short)NewHandler;
# J4 i6 e2 y2 v
IdtEntry[HOOKINT].OffsetHigh = (unsigned short)((unsigned int)NewHandler>16);
' T7 n7 U$ C4 j' O: C4 ~1 ?
_asm sti /* Enable中断请求 */
$ ?4 |5 l* u2 v) A# d% ~
+ M) C& ~+ ]( K
return STATUS_SUCCESS;
! Q( }/ B4 I4 ^0 K' h' |4 F
}
* k6 F" Z/ f. S
3 @* N( ], [" R; c: E
上面是对IDT表项进行hook,我们只是修改了相应表项的Code Segment Offset。经过替换后,新的中断处理函数与原中断处理函数的“性 质相似”,即表项中其余的字段相同。如果读者对这句话不理解,那就请看IDT表项的数据结构。
+ t5 G+ r, e; v# _" u6 H/ }
typedef struct InterruptGate
: W& k3 ~7 P& q
{
( I0 E. O' p3 V/ q( w3 G$ v$ D7 Y
unsigned short OffsetLow;
0 l- c- s8 c: D. h; I* W1 Z1 D. ~
unsigned short Selector;
& W1 n+ H1 |5 Z: q) j P0 M
unsigned char Reserved;
2 g; R) ?% r* Z+ N H6 c/ s
unsigned char SegmentType;
. j% y$ `% j0 H% H5 J1 R
unsigned char SystemSegmentFlag;
$ z5 R" S! v. e
unsigned char Dpl;
% R0 C4 [: U6 n- h
unsigned char Present;
% |9 N3 x; }: B+ a
unsigned short OffsetHigh;
7 y( x5 j: I3 R; R4 f
} InterruptGate_t;
$ r" h8 O8 _9 _! ^/ c, [. p% ~8 |1 G
9 Y9 ]; B) ]- [2 U+ S
在上面的例子中,OffsetLow和OffsetHigh被改写了,从而hook了中断。让我们思考这样一个问题:如果IDT的相应表项没有内容呢,也就 是说,如果我们想让IDT的空表项指向我们的中断处理函数,又该怎么办呢?
- M3 ]# Z$ R4 I$ d
* [, g; e3 c) ]% K+ g- u1 g; I
[ 读者小强:Winnt和2K的IDT中有空表项吗?]
* p) k4 |& }, f2 ]- m5 T* y9 }
是的,比如说,在Winnt下,IDT表项的第22h---29h就是空表项。 这就是说我们可以给Winnt加入新的中断。但是,这里有了新的难度, 我们必须认真理解和填写上面数据结构InterruptGate中的所有字段。
$ c5 V) c1 ~ s6 g2 J. b3 ^( t
下面这些代码出自《Undocumented WindowsNT》,实现上述“搜索IDT、填写IDT表项所有字段”的功能。
' d' c) c* }+ N% w- O7 X; ]% I& ~4 i
NTSTATUS AddInterrupt()
8 \6 l6 D7 ^7 M( J) N
{
: i, K% j0 l/ I$ G
PIdtEntry_t IdtEntry;
$ V4 L! s% s: ^* ^
* G) A' P: s2 l2 E+ x7 h
/* 获得IDTR Register 的Base Address和Limit*/
1 b* s) Q2 W( I8 d# m" G
_asm sidt buffer
1 v+ t* ~& w8 [
IdtEntry=(PIdtEntry_t)Idtr->Base;
3 |/ }* {' j# Q. G, G4 I
if((IdtEntry[ADDINT].OffsetLow!=0)||(IdtEntry[ADDINT].OffsetHigh!=0))
* W" A! ?! ?% O$ N
return STATUS_UNSUCCESSFUL;
. d; o% v/ _ p5 q- e) x$ o1 Y
9 T# B5 `4 y$ e; b# T$ H, O
_asm cli
( A; c$ J+ ~' ?1 r; ?9 X' t
# D" N+ [" ? p* {7 j. Y
/* 填写IDT表项的所有字段,使新的IDT表项指向我们的中断处理函数 */
6 A9 e2 e* ~% c6 O
IdtEntry[ADDINT].OffsetLow=(unsigned short)InterruptHandler;
8 M' }/ [4 ~7 S1 O
IdtEntry[ADDINT].Selector=8;
2 H( T; c2 l! t% p' d
IdtEntry[ADDINT].Reserved=0;
?4 a- G0 s8 m6 m
IdtEntry[ADDINT].Type=0xE;
) v- i- L: G/ Y; T2 a7 V) O5 |/ C" c
IdtEntry[ADDINT].Always0=0;
+ N1 p8 X: M6 X! m# m9 M2 L9 E/ t
IdtEntry[ADDINT].Dpl=3;
& F# H; x9 Q/ R" S# E
IdtEntry[ADDINT].Present=1;
7 p" M5 b$ P: A; I. Y
IdtEntry[ADDINT].OffsetHigh=
/ g. q) A& J: s0 ?
(unsigned short)((unsigned int) InterruptHandler>16);
4 c9 ]1 i R. N4 c" M5 x7 {
_asm sti
3 i& z( ^5 }4 P
# q% @6 R6 v+ n. \
return STATUS_SUCCESS;
+ t/ _2 H5 n; d5 Q% ]$ k3 @% Y& S
}
) j, j& q( n0 o% M c3 x/ v6 w1 W
y* k2 t3 o9 z, h8 `7 z4 I. U: Y
让我们就上面的代码提一些问题。
) x* ?# V: |" }/ w% w( K5 A2 n
Q: 上面的代码IdtEntry[ADDINT].Selector=8; 这是怎么回事?
+ L3 y# P, F) u6 ]6 m4 G4 G% ~7 [. N: Q
A:按Ctrl+D进到Softice里,你会发现,所以的system代码,都存在于一个段中。这
2 \& D) [: s7 A2 B9 W
个段的名称就是8(CS=8)。
) F3 w5 N6 C0 q* Z; h) k6 ?$ X6 u
. Y5 I" I( s' K) Z, u
Q: 上面的代码IdtEntry[ADDINT].Dpl=3; 这是怎么回事?
1 x O* I6 \; h# D/ V; j$ Y
A:问得好,小强。这说明,这个中断可以在应用程序中,通过int xxx的形式触发。因为
: \2 G, e8 k$ N6 e! Q
应用程序的DPL=3,所以,可以直接触发这个中断。
! X, B% J" x- d. Y5 Y* e N- l3 i! R
6 [, |2 R/ O1 K+ W& s" d
Q: 上面的代码IdtEntry[ADDINT].Type=0xE; 这是怎么回事?
; g" r& d. X5 I& ]8 A# \
A:按Ctrl+D进到Softice里,然后敲入指令idt。输出如下
3 N5 @/ Q4 C% Z; `5 }' R A3 \
:idt
7 I8 N. @/ n- H3 P+ @
Int Type Sel:Offset Attributes Symbol/Owner
# q; k& Z$ |& m6 ]$ P1 t1 x
IDTbase=80036400 Limit=07FF
) V7 R3 M: }( E: ^1 c$ c* t4 V
0000 IntG32 0008:80465946 DPL=0 P ntoskrnl!Kei386EoiHelper+0590
4 ]# A" I& Q( y7 e" `* s
0001 IntG32 0008:80465A96 DPL=3 P ntoskrnl!Kei386EoiHelper+06E0
/ R6 ^: X0 e2 a. D/ C) k
……
4 t6 {' y6 i# E- h
可见,0xE表示IntG32。
/ C* s& n& A! f D$ T$ M
6 d. w" y+ ? ~; ?9 s' c
终于快到下课的时间了,让我再多说几句好吗?
2 n: C+ ~) V3 U5 T
通过对Winnt/2k IDT的分析,我们对IDT有了一些基本认识。上面介绍的通过修改IDT来hook中断的方法,是很危险的。笔者强烈建议读 者尽量通过Winnt/2k DDK官方资料提供的API来实现hook中断的功能。
欢迎光临 数学建模社区-数学中国 (http://www.madio.net/)
Powered by Discuz! X2.5