Windwos
体系结构:
--------------------------------------
用户模式(ring3)
系统进程、服务进程、应用程序、环境子系统(向应用程序提供环境和应用程序编程接口 Appplication
Progra
mming Interface-API。Windows
2000/XP
支持三种环境子系统:Win32、POSIX
和
OS/2,其中最重要的环境子
系统是 Win32
子系统,其他子系统都要通过
Win32
子系统
接收用户的输入和显示输出。环境子系统的作用是
将基本的执行体系统服务的某些子集提供给应用程序。 用户应用程序调用系统服务时必须通过一个或多个子
系统动态链接库作为中介才可以完成)、应用程序编程接口(API)、基于 NTDLL.DLL
的本地系统服务
--------------------------------------
内核模式(ring0)
系统服务调用(SSDT)、执行体(Executive)、系统内核和设备驱动(Kernel)、硬件抽象层(HAL)
一、Windows 系统服务调用机制
Windows 2000
的陷阱调度(Trap
Dispatching)机制包括了:中断(Interrupt),延迟过程调用(Deferred
Proc
edure Call),异步过程调用(Asynchronous
Procedure
Call),异常调度(Exception
Dispatching)和系统服务
调用(System Service
Call)。在
Intel
x86
平台的
Windows
2000
使用
int
0x2e
指令进入
Windows
系统服务调用;
Windows XP
使用
sysenter
指令使系统陷入系统服务调用程序中;而
AMD
平台的
Windows
XP
系统使用
syscall
指令进入 Windows
系统服务调用。下面是
Intel
x86
平台的
Windows
2000
的系统服务调用模型。
mov eax,
ServiceId
lea edx,
ParameterTable
int 2eh
ret ParamTableBytes
其中 ServiceId
是传递给系统服务调用程序的
ID
号,内核使用这个
ID
号来查找系统服务调度表(System
Service
Dispath Table)中的对应系统服务信息。
系统服务调用也是一个接口,是面向
Windows
内核的接口。它实现
了将用户模式下的请求转发到内核模式下,并引发了处理器模式的切换。在用户看来,系统服务调用就是与 W
indows 内核通信的一个桥梁。
二、系统服务调用类型
在 Windows
2000
中默认存在两个系统服务调度表,它们对应了两类不同的系统服务。这两个系统服务调度表
分别是:KeServiceDescriptorTable
和
KeServiceDescriptorTable
Shadow。
前者有
ntoskrnl.exe
导出,后者由
Win3
2k.sys 导出。在系统中,有三个
DLL
是最重要的:Kernel32.dll、
User32.dll
和
Gdi32.dll,这些
DLL
导出的函数,
都是通过某种类型的中断进入内核态,然后调用 ntoskrnl.exe
或
Win32k.sys
中的函数。函数
KeAddSystemServic
eTable 允许
Win32.sys
和其他设备驱动程序添加系统服务表。除了
Win32k.sys
服务表外,使用
KeAddSystemServ
iceTable 添加的服务表会被同时复制到
KeServiceDescriptorTable
和
KeServiceDescriptorTable
Shadow
中去。
说明:
NTDLL..DLL 和
ntoskrnl.exe
的关系很“微妙”,用户态和内核态的调用也是有分别的,比如:参数检查。还有
N
ative API
导出了
2
套函数:Zw***和
Nt***系列,要想彻底了解这些内容,推荐看
Sunwear
写的《浅析本机
API》
综上所述,Kernel32.dll/Advapi32.dll 进入
NTDLL.DLL
后,使用
int
0x2e
中断进入内核,最后在
ntoskrnl.exe
中实
现了真正的函数调用;User32.dll 和
Gdi32.dll
则在
Win32k.sys
中实现了真正的函数调用。
三、本机 API(Native
API)
本机 API
是除了
Win32
API,NT
平台开放了另一个基本接口。本机
API
也被很多人所熟悉,因为内核模式模块
位于更低的系统级别,在那个级别上环境子系统是不可见的。尽管如此,并不需要驱动级别去访问这个接口,
普通的 Win32
程序可以在任何时候向下调用本机
API。并没有任何技术上的限制,只不过微软不支持这种应用
开发方法。User32.dll,kernel32.dll,shell32.dll,gdi32.dll,rpcrt4.dll,comctl32.dll,advapi32.dll,version.dll 等
dll
代表了
Wi
n32 API
的基本提供者。Win32
API
中的所有调用最终都转向了
ntdll.dll,再由它转发至
ntoskrnl.exe。ntdll.dll
是
本机 API
用户模式的终端。真正的接口在
ntoskrnl.exe
里完成。事实上,内核模式的驱动大部分时间用这个模
块,如果它们请求系统服务。Ntdll.dll 的主要作用就是让内核函数的特定子集可以被用户模式下运行的程序调
用。Ntdll.dll 通过软件中断
int
2Eh
进入
ntoskrnl.exe,就是通过中断门切换
CPU
特权级。比如
kernel32.dll
导出
的函数 DeviceIoControl()实际上调用
ntdll.dll
中导出的
NtDeviceIoControlFile(),反汇编一下这个函数可以看到,E
AX 载入
magic
数
0x38,实际上是系统调用号,然后
EDX
指向堆栈。目标地址是当前堆栈指针
ESP+4,所以
ED
X 指向返回地址后面一个,也就是指向在进入
NtDeviceIoControlFile()之前存入堆栈的东西。事实上就是函数的
参数。下一个指令是 int
2Eh,转到中断描述符表
IDT
位置
0x2E
处的中断处理程序。
反编汇这个函数得到:
mov eax,
38h
lea edx,
[esp+4]
int 2Eh
ret 28h
当然 int
2E
接口不仅仅是简单的
API
调用调度员,他是从用户模式进入内核模式的
main
gate。
W2k Native
API
由
248
个这么处理的函数组成,比
NT
4.0
多了
37
个。可以从
ntdll.dll
的导出列表中很容易认出
来:前缀 Nt。Ntdll.dll
中导出了
249
个,原因在于
NtCurrentTeb()为一个纯用户模式函数,所以不需要传给内核。
令人惊奇的是,仅仅 Native
API
的一个子集能够从内核模式调用。而另一方面,ntoskrnl.exe
导出了两个
Nt*符
号,它们不存在于 ntdll.dll
中:
NtBuildNumber,
NtGlobalFlag。它们不指向函数,事实上,是指向
ntoskrnl.exe
的
变量,可以被使用 C
编译器
extern
关键字的驱动模块导入。Ntdll.dll
和
ntoskrnl.exe
中都有两种前缀
Nt*,Zw*。
事实上 ntdll.dll
中反汇编结果两者是一样的。而在
ntoskrnl.exe
中,nt
前缀指向真正的代码,而
zw
还是一个
int
2Eh 的
stub。也就是说
zw*函数集通过用户模式到内核模式门传递的,而
Nt*符号直接指向模式切换以后的代码。
Ntdll.dll 中的
NtCurrentTeb()没有相对应的
zw
函数。Ntoskrnl
并不导出配对的
Nt/zw
函数。有些函数只以一种方
式出现。
四、系统服务调用示意图
------------------------------------
Win32 API
->Ntdll.h
->SSDT
(提供用户态到内核态的的转换)->ntoskrnl.exe
-------------------------------------
Win32 API
包括:User32.dll,kernel32.dll,shell32.dll,gdi32.dll,rpcrt4.dll,comctl32.dll,advapi32.dll,version.dll
等
dll
Ntdll.h :ZW**和
NT**系列函数
ntoskrnl.exe:ZW**和 NT**系列函数
五、SSDT 说明
什么是 SSDT?
什么是 SSDT?自然,这个是我必须回答的问题。不过在此之前,请你打开命令行(cmd.exe)窗口,并输入“
dir”并回车——好了,列出了当前目录下的所有文件和子目录。
那么,以程序员的视角来看,整个过程应该是这样的:
1.由用户输入 dir
命令。
2.cmd.exe 获取用户输入的
dir
命令,在内部调用对应的
Win32
API
函数
FindFirstFile、FindNextFile
和
FindClose,
获取当前目录下的文件和子目录。
3.cmd.exe 将文件名和子目录输出至控制台窗口,也就是返回给用户。
到此为止我们可以看到,cmd.exe 扮演了一个非常至关重要的角色,也就是用户与
Win32
API
的交互。——你
大概已经可以猜到,我下面要说到的 SSDT
亦必将扮演这个角色,这实在是一点新意都没有。
没错,你猜对了。SSDT 的全称是
System
Services
Descriptor
Table,系统服务描述符表。这个表就是一个把
ring
3 的
Win32
API
和
ring0
的内核
API
联系起来的角色,下面我将以
API
函数
OpenProcess
为例说明这个联系的过
程。
你可以用任何反汇编工具来打开你的 kernel32.dll,然后你会发现在
OpenProcess
中有类似这样的汇编代码:
call ds:NtOpenProcess
这就是说,OpenProcess 调用了
ntdll.dll
的
NtOpenProcess
函数。那么继续反汇编之,你会发现
ntdll.dll
中的这
个函数很短:
mov eax,
7Ah
mov edx,
7FFE0300h
call dword
ptr
[edx]
retn 10h
另外,call 的一句实质是调用了
KiFastSystemCall:
mov edx,
esp
sysenter
上面是我的 XP
Professional
sp2
中
ntdll.dll
的反汇编结果,如果你用的是
2000
系统,那么可能是这个样子:
mov eax,
6Ah
lea edx,
[esp+4]
int 2Eh
retn 10h
虽然它们存在着些许不同,但都可以这么来概括:
1.把一个数放入 eax(XP
是
0x7A,2000
是
0x6A),这个数值称作系统的服务号。
2.把参数堆栈指针(esp+4)放入 edx。
3.sysenter 或
int
2Eh。
好了,你在 ring3
能看到的东西就到此为止了。事实上,在
ntdll.dll
中的这些函数可以称作真正的
NT
系统服务
的存根(Stub)函数。分 隔
ring3
与
ring0
城里城外的这一道叹息之墙,也正是由它们打通的。接下来
SSDT
就
要出场了,come some
music。
站在城墙看城外
当程序的处理流程进入 ring0
之后,系统会根据服务号(eax)在
SSDT
这个系统服务描述符表中查找对应的表
项,这个找到的表项就是系统服务函 数
NtOpenProcess
的真正地址。之后,系统会根据这个地址调用相应的
系统服务函数,并把结果返回给 ntdll.dll
中的
NtOpenProcess。图中的“SSDT”所示即为系统服务描述符表的
各个表项;右侧的“ntoskrnl.exe”则为 Windows
系统内核服
务进程(ntoskrnl
即为
NT
OS
KerneL
的缩写),
它提供了相对应的各个系统服务函数。ntoskrnl.exe 这个文件位于
Windows
的
system32
目录下,有兴趣的朋友
可以反汇编一下。
附带说两点。根据你处理器的不同,系统内核服务进程可能也是不一样的。真正运行于系统上的内核服务进程
可能还有 ntkrnlmp.exe、ntkrnlpa.exe
这样的情况——不过为了统一起见,下文仍统称这个进程为
ntoskrnl.exe。
另外,SSDT 中
的各个表项也未必会全部指向
ntoskrnl.exe
中的服务函数,因为你机器上的杀毒监控或其它驱动
程序可能会改写 SSDT
中的某些表项——这也就是所
谓的“挂钩
SSDT”——以达到它们的“主动防御”式杀毒
方式或其它的特定目的。
KeServiceDescriptorTable
事实上,SSDT 并不仅仅只包含一个庞大的地址索引表,它还包含着一些其它有用的信息,诸如地址索引的基
地址、服务函数个数等等。 ntoskrnl.exe
中的一个导出项
KeServiceDescriptorTable
即是
SSDT
的真身,亦即它在
内核中的数据实体。SSDT 的数据结构定义如下:
typedef struct
_tagSSDT
{
PVOID
pvSSDTBase;
PVOID
pvServiceCounterTable;
ULONG
ulNumberOfServices;
PVOID
pvParamTableBase;
} SSDT,
*PSSDT;
其中,pvSSDTBase 就是上面所说的“系统服务描述符表”的基地址。pvServiceCounterTable
则指向另一个索引
表,该表包 含了每个服务表项被调用的次数;不过这个值只在
Checkd
Build
的内核中有效,在
Free
Build
的内
核中,这个值总为 NULL(注:Check/Free
是
DDK
的
Build
模式,如果你只使用
SDK,可以简单地把它们理解为
Debug/Release)。ulNumberOfServices 表示当前系统所支持的服务个数。pvParamTableBase
指向
SSPT
(Syste
m Service
Parameter
Table,即系统服务参数表),该表格包含了每个服务所需的参数字节数。
--------------------------------------
用户模式(ring3)
系统进程、服务进程、应用程序、环境子系统(向应用程序提供环境和应用程序编程接口
mming
系统是
将基本的执行体系统服务的某些子集提供给应用程序。
系统动态链接库作为中介才可以完成)、应用程序编程接口(API)、基于
--------------------------------------
内核模式(ring0)
系统服务调用(SSDT)、执行体(Executive)、系统内核和设备驱动(Kernel)、硬件抽象层(HAL)
一、Windows
Windows
edure
调用(System
Windows
指令进入
mov
lea
int
ret
其中
Dispath
了将用户模式下的请求转发到内核模式下,并引发了处理器模式的切换。在用户看来,系统服务调用就是与
indows
二、系统服务调用类型
在
分别是:KeServiceDescriptorTable
2k.sys
都是通过某种类型的中断进入内核态,然后调用
eTable
iceTable
说明:
NTDLL..DLL
ative
综上所述,Kernel32.dll/Advapi32.dll
现了真正的函数调用;User32.dll
三、本机
本机
位于更低的系统级别,在那个级别上环境子系统是不可见的。尽管如此,并不需要驱动级别去访问这个接口,
普通的
开发方法。User32.dll,kernel32.dll,shell32.dll,gdi32.dll,rpcrt4.dll,comctl32.dll,advapi32.dll,version.dll
n32
本机
块,如果它们请求系统服务。Ntdll.dll
用。Ntdll.dll
的函数
AX
X
参数。下一个指令是
反编汇这个函数得到:
mov
lea
int
ret
当然
W2k
来:前缀
令人惊奇的是,仅仅
号,它们不存在于
变量,可以被使用
事实上
2Eh
Ntdll.dll
式出现。
四、系统服务调用示意图
------------------------------------
Win32
-------------------------------------
Win32
Ntdll.h
ntoskrnl.exe:ZW**和
五、SSDT
什么是
什么是
dir”并回车——好了,列出了当前目录下的所有文件和子目录。
那么,以程序员的视角来看,整个过程应该是这样的:
1.由用户输入
2.cmd.exe
获取当前目录下的文件和子目录。
3.cmd.exe
到此为止我们可以看到,cmd.exe
大概已经可以猜到,我下面要说到的
没错,你猜对了。SSDT
3
程。
你可以用任何反汇编工具来打开你的
call
这就是说,OpenProcess
个函数很短:
mov
mov
call
retn
另外,call
mov
sysenter
上面是我的
mov
lea
int
retn
虽然它们存在着些许不同,但都可以这么来概括:
1.把一个数放入
2.把参数堆栈指针(esp+4)放入
3.sysenter
好了,你在
的存根(Stub)函数。分
要出场了,come
站在城墙看城外
当程序的处理流程进入
项,这个找到的表项就是系统服务函
系统服务函数,并把结果返回给
各个表项;右侧的“ntoskrnl.exe”则为
它提供了相对应的各个系统服务函数。ntoskrnl.exe
可以反汇编一下。
附带说两点。根据你处理器的不同,系统内核服务进程可能也是不一样的。真正运行于系统上的内核服务进程
可能还有
另外,SSDT
程序可能会改写
方式或其它的特定目的。
KeServiceDescriptorTable
事实上,SSDT
地址、服务函数个数等等。
内核中的数据实体。SSDT
typedef
}
其中,pvSSDTBase
表,该表包
核中,这个值总为
Debug/Release)。ulNumberOfServices
m