众所周知,在Windows95/98
的Win32 on Intel x86 体系中利用了处理器的三环保护模型中的零环(Ring0,最高权限级别)和三环(Ring3,最低权限级别)。一般应用程序都运行在Ring3
下,受到严格的" 保护",只能规矩地使用Win32API。如果我们想进行一些系统级的操作,例如在嵌入汇编中使用诸如"Mov
EAX,CR0",或像在DOS 下那样调用一些必不可少的系统服务(如BIOS,DPMI
服务)而用"Int xx",都会导致" 非法操作"。但这种能力有时是必不可少的,一到这种时候Microsoft
就" 建议编写一个VxD"。VxD 大家早有所闻了,在VxD
里,不但可以执行CPU 的所有指令,而且可以调用VMM(
虚拟机管理器)和其他VxD
提供的上千个系统级服务。获得这一能力的最本质原因在于它运行在Ring0,与系统内核同一级别。但是它体系的复杂性、开发工具的不易获得、帮助文档的不完备,使Microsoft
排除了一大批程序员和竞争对手。而将在Windows2000(Windows98
也开始支持)中取代VxD 的WDM 对Win95 程序员也是个噩梦,它需要了解Windows
NT 核心驱动模型。
有没有简单一些的办法呢?我们可以令一个普通Win32
应用程序运行在Ring0 下,从而获得VxD
的能力吗?答案是肯定的。下面我们就简述一下这一技巧,有关Intel
x86 保护模式的基础知识请大家看有关书籍。
首先此技巧基于以下理论根据:
一、SIDT 指令(将中断描述符表寄存器IDTR --64 位宽,16 ~47Bit
存有中断描述符表IDT
基地址--的内容存入指定地址单元)不是特权指令,就是说我们可以在Ring3
下执行该指令,获得IDT 的基地址,从而修改IDT,增加一个中断门安置我们的中断服务,一旦Ring3
程序中产生此中断,VMM
就会调用此中断服务程序,而此中断服务程序就运行在Ring0
下了。这一点与在DOS 下非常相似。
二、Windows95 Win32 应用程序运行一个映射到全部4G
内存的段中,选择子为0137h,Ring0 中的VxD 运行在另一个映射到全部4G
内存的段中,选择子028h,这两个段除了选择子决定的访问权限不同外,没什么不同,各自段中相同的偏移量对应了相同的线性地址。所以我们放在Win32
应用程序中的中断服务程序可以以Ring3 的段偏移量被Ring0 中的VMM
寻址。
下面我们以具体例子进一步说明,程序中有详细注释。
这是一个Win32 Console Program(控制台应用程序),虽然运行中看起来很像DOS
筐中运行的实模式DOS 程序,但它是货真价实的运行在Ring3 下的Win32
程序。用Visual C ++5.0 AppWizard 创建一个Win32 Console Program 项目,
添加以下.CPP 文件, 编译即可。
#include<conio.h>
#include<iostream.h>
#include<windows.h>
#include<vmm.h>
// 若无DDK 带下划线的可略去,
这些语句演示了调用VMM/VXD 服务
DWORDLONG IDTR,SavedGate;
WORD OurGate[4]={0,0x0028,0xee00,0x0000};
// 中断门描述符格式如下:
DWORD _eax,_ecx,_cr0;
WORD vmmver;
HVM sysvm;
void nothing()
{
//Used to test call in Ring0
sysvm=Get_Sys_VM_Handle();
}
void __declspec( naked ) Ring0Proc(void)
// 中断例程,运行在Ring0
{
_asm{
mov _eax,eax //
mov _ecx,ecx //
mov eax, CR0
// 测试Ring3 中不能执行的特权指令
mov _cr0,eax //
}
VMMCall(Get_VMM_Version);
// 调用VMM 服务
_asm{
mov vmmver,ax
}
nothing();
// 测试在运行于Ring0 的
中断例程中调用子
_asm iretd
// 中断返回, 与在实模式
编程无本质区别
}
void main() // 主程序
{
_asm{
mov eax, offset Ring0Proc
mov [OurGate], ax // 将中断函数的地址
shr eax, 16 // 填入新造的中断门
mov [OurGate +6], ax // 描述符
sidt fword ptr IDTR
// 将中断描述符表寄存器(IDTR)
的内容取出
mov ebx, dword ptr [IDTR +2]
// 取出中断描述符表(IDT)基地址
add ebx, 8 *9
// 计算Int 9 的描述符应放置的地址选用
Int9 是因为它在Win32 保护模式下未占用
mov edi, offset SavedGate
mov esi, ebx
movsd // 保存原来的Int 9 描述符到
movsd //SavedGate 以便恢复
mov edi, ebx
mov esi, offset OurGate
movsd // 替换原来的中断门描述符
movsd // 以安装中断服务例程
mov eax,0x6200
// 用以测试放在EAX 中的数据
能否正确传到Ring0 中断
mov ecx,0
// 用以测试放在ECX 中的数据
能否正确传到Ring0 中断
mov ecx,0
// 用以测试放在ECX 中的数据
能否正确传到Ring0 中断
// 因为很多VxD 服务都用
此二寄存器传递参数
int 9h
// 人为触发中断, 平时会出现
保护错误蓝屏或非法操
// 作对话框,现在安装了
// 中断服务例程后,就会通过
//VMM 在Ring0 调用中断服务例程
--Ring0Proc
mov edi, ebx
mov esi, offset SavedGate
movsd // 恢复原来的中断门描述符
movsd
}
cout<<"CR0="<<_cr0<
运行结果:
此方法的好处一是回避了奇特的VxD文件格式,不用使用汇编语言编程,二是应用程序不用带一个单独的VxD
文件,干净利索。
值得一提的是,许多文章描述著名的CIH 病毒都说其利用了VxD
技术,是说它带一个单独的VxD文件吗?显然不可能,实际上CIH
病毒使用的就是以上技巧,进入Ring0后调用VMM
服务分配一块内存,把自身拷贝进去,然后用IFS VxD 的IFSMgr_InstallFileSystemApiHook
服务安装文件系统监视以感染其他文件,只不过CIH 病毒安装的是Int 3
中断,这跟在DOS 下没什么两样。我们也可以对此了解以更好地防范CIH
病毒。
最后还要指出其缺陷,这个技巧仅能用在Windows95/97/98 下,在Windows
NT 下行不通。不过听说CIH 病毒的作者已经开发了NT 版的CIH
病毒,说明NT 中也有类似的" 后门"。