windows ssdt
Posted 不会写代码的丝丽
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了windows ssdt相关的知识,希望对你有一定的参考价值。
前言
我们 ring 3
跳转 ring0
另一种方式使用sysenter命令。
sysenter 相比起jmp,int xx方式相比速度更快,因为sysenter
指令大量的使用了MSR
寄存器 存储跳转地址等。
MSR寄存器相关读/写命令
//读取msr寄存器
rdmsr xxxx
//写入msr寄存器
wrmsr xxxx
其中xxx是msr寄存器的编号比如174h等
为方便记忆我们约定了一些编号的名字
IA32_SYSENTER_CS (MSR address 174H)
IA32_SYSENTER_EIP (MSR address 176H)
IA32_SYSENTER_ESP (MSR address 175H)
上面上个寄存器就是sysenter
指令会涉及的msr寄存器。
sysenter 规范
以32位操作系统举例说明这个指令相关流程.
sysenter 段描述符约定
使用这个intel 指令操作系统必须要做出如下妥协:
ring0
的ds段描述符必然是ring0
的 cs+8.我们可以简述成: ds0=cs0+8
ring3
的cs和ds描述如下: cs3= CS0 + 16 ,ds3= CS0 + 24.
也就是我们可以通过任意一个段描述符下标反推出其他段描述符下标。
我们可以利用windbg验证win32系统是否按照如此规定。
上图编号是16进制。
0008 00000000 ffffffff Code RE Ac 0 Bg Pg P Nl 00000c9b
0010 00000000 ffffffff Data RW Ac 0 Bg Pg P Nl 00000c93
0018 00000000 ffffffff Code RE Ac 3 Bg Pg P Nl 00000cfb
0020 00000000 ffffffff Data RW Ac 3 Bg Pg P Nl 00000cf3
第一个序号0x8是cs0,第二个序号是ds0 也就是 0x8+0x8=0x10.往后递推。
sysenter 调用约定
调用sysenter 前需要准备给以下寄存器准备数据
IA32_SYSENTER_CS (MSR address 174H) 存储跳转到ring0 的cs段描述符下标
IA32_SYSENTER_EIP (MSR address 176H) 存储跳转 ring0 的eip
IA32_SYSENTER_ESP (MSR address 175H) 存储跳转 ring0 的ESP
当调用SYSEXIT
时需要提前填充如下数据
edx 存储返回到ring3 EIP
ecx 存储返回到ring3 ESP
上述便是intel中的一些规范,在windwow中在这个基础做了一层封装。
window 封装sysenter
window
为 sysenter
构建了一个复杂的数据结构(内部包含函数地址表,参数数量表),所有在sysenter
会首先统一跳转到相同内核处理函数,在跳转前需要将参数提前压入栈中以及调用的函数下标以及服务表数组下标放入eax。内核分发函数首先会读取eax服务表下标,然后读取服务表的下标,然后从这表中( 系统服务描述表(system service descriptor table))根据eax传入的函数下标取出函数地址,以及对应的参数数量,然后拷贝ring3 栈参数到ring0 在执行跳转。
首先需要知道的知识:
window服务表数组,当前有两个数组我们分别取名为KeServiceDescriptorTable
,KeServiceDescriptorTableShadow
,数组长度上限大小为4。
KeServiceDescriptorTable
内部包含非ui内核函数,当前这个数组元素数量只有1个。
KeServiceDescriptorTableShadow
包含了ui内核函数和非ui函数,当前元素元素数量只有2个且其中一个元素和KeServiceDescriptorTable
相同。
类似结构如下
typedef struct _SYSTEM_SERVICE_TABLE
PVOID ServiceTableBase; //这个指向系统服务函数地址表
PULONG ServiceCounterTableBase;
ULONG NumberOfService; //服务函数的个数
ULONG ParamTableBase;
SYSTEM_SERVICE_TABLE,*PSYSTEM_SERVICE_TABLE;
typedef struct _SERVICE_DESCRIPTOR_TABLE
SYSTEM_SERVICE_TABLE ntoskrnel; //ntoskrnl.exe的服务函数
SYSTEM_SERVICE_TABLE win32k; //win32k.sys的服务函数,(gdi.dll/user.dll的内核支持)
SYSTEM_SERVICE_TABLE NotUsed1;
SYSTEM_SERVICE_TABLE NotUsed2;
SYSTEM_DESCRIPTOR_TABLE,*PSYSTEM_DESCRIPTOR_TABLE;
SYSTEM_DESCRIPTOR_TABLE KeServiceDescriptorTable = ntoskrnel=itemAddr ;
SYSTEM_DESCRIPTOR_TABLE KeServiceDescriptorTableShadow = itemAddr,itemAddr2 ;
当然需要注意的是KeServiceDescriptorTable
是一个被ntoskrnel
导出的变量(KeServiceDescriptorTableShadow并没有在win32k.sys导出)
所以你可以在代码中直接使用类似如下的代码直接找到地址
extern SYSTEM_DESCRIPTOR_TABLE KeServiceDescriptorTable;
关于如何获取另一个表可参阅:
Windows下如何获得KeServiceDescriptorTableShadow地址
我们用windbg验证我们的想法
我们查看这个两个数据结构第一个地址的存储的函数地址
你会发现当前进程对应的ui函数地址都是空的,是因为当前进程是没有ui的只有KeServiceDescriptorTable表,因此第二个选项地址无法查询。
我们可以切换到一个有ui进程的程序上
传入eax
规范:
eax会传入两个参数,服务表的下标,以及函数下标:
图片从转载
bits 0-11: the system service number (SSN) to be invoked.(译 被调用所在服务表的函数下标)
bits 12-13: the service descriptor table (SDT).(译 服务表下标)
bits 14-31: not used. (未使用)
查看一个记事本的案例:
我们断点一个记事本ntdll.dll的createFile函数
ntdll!NtCreateFile:
# 赋值对应的函数下标 和服务表
77313820 mov eax,16Eh
77313825 call ntdll!NtCreateFile+0xd (7731382d)
7731382a ret 2Ch
7731382d mov edx,esp
7731382f sysenter # 这行运行完成后跳转到7731382a
首先我们先分析 eax
的数值
从上面可以得知这个函数会调用最原始非UI的函数表,下标为0x16。
call ntdll!NtCreateFile+0xd
目的为了作为一个桩代码,存储返回地址7731382a
到栈中,而后在内核中可以轻易读取到返回地址方便调用SYSEXIT
读取ring 3
返回地址。
77313825 call ntdll!NtCreateFile+0xd
//存储到edx寄存器中,方便内核读取ring 3 读取到栈区地址
7731382d mov edx,esp
当我们运行到7731382f
代码时,一些寄存器堆栈数据
7731382f sysenter
msr寄存器:
msr[174] = 00000000`00000008
msr[175] = 00000000`89f7d000
msr[176] = 00000000`8234ba50
寄存器:
edx = 03d7f614
esp = 03d7f614
ebp = 03d7f6e0
栈区信息:
03d7f614 7731382a 74696b04 03d7f664 80100080
03d7f624 03d7f6a8 03d7f674 00000000 00000000
03d7f634 00000000 00000001 00000040 00000000
03d7f644 00000000 03d7f808 03d7f850 000004e8
03d7f654 00000000 00000040 00000000 00000001
03d7f664 000001bc 80100080 00000000 00000000
03d7f674 00000384 0000038c 00000000 00120010
03d7f684 03fad9f0 03fad9f0 00000000 00000000
然后我们在执行sysenter
后跳转到msr[176]
地址,这个是一个内核地址
在执行windbg
的t
命令,不会跳转到内核,而是到7731382a ret 2Ch
。
这个函数会跳入内核nt!KiFastCallEntry
函数。
这个函数分析不想写了。。。。网上有
参考命令
//寻找内核的notepad程序
!process 0 0
//切换到notepad程序
.process /r /p b060d780
//下断点
bp ntdll!NtCreateFile
//条件断点
bp 773e3825 ".if (@eax & 0x0`ffffffff) = 0x0`16E r eax .else gc"
参考
System_Service_Descriptor_Table
System Service Descriptor Table - SSDT
hooking-system-service-dispatch-table-ssdt
What is REX prefix in Instruction Encoding?
以上是关于windows ssdt的主要内容,如果未能解决你的问题,请参考以下文章
求助WIN64 HOOK SSDT NtTerminateProcess