16. 系统调用 上

news/2025/2/9 8:28:04 标签: c++, 安全

SYSENTER

如果我们已经稍微熟悉了ntdll里面的基本结构,我们就可以知道从R3转向R0的入口大概都是一个汇编,这里以OpenProccess函数为例

在这里插入图片描述

这里强调一下,图中红框标出的位置就是同一段汇编的两个不同的声明,所以Nt开头和Zw开头是一样的

可以看到,汇编就只有短短的几行,这里注释一下

mov     eax, 0BEh //系统服务例程号
mov     edx, 7FFE0300h // 取得 KiFastCallEntry() stub 函数
call    dword ptr [edx] //调用这个 stub 函数

这是什么意思呢?这就说明,在ntdll封装的实现中,本质上也是一个转发,在找到了不同的系统服务例程号(不同函数不同)后,去找到KiFastCallEntry这个函数,然后根据具体的调用号来进入内核调用

那第二个这个将7ffe0300移入edx有什么讲究吗?

有的兄弟,有的

_KUSER_SHARED_DATA

在 User 层和 Kernel 层分别定义了一个 _KUSER_SHARED_DATA 结构区域,用于 User 层和 Kernel 层共享某些数据,在 sysenter 快速切入机制里就使用了这个区域。

它们使用固定的地址值映射,_KUSER_SHARED_DATA 结构区域在 User 和 Kernel 层地址分别为:

  • User 层地址为:0x7ffe0000
  • Kernnel 层地址为:0xffdf0000

介绍到这里,这个7ffe0300就是KUSER_SHARED_DATA这个结构里面偏移0x300的位置,就是我们需要的系统调用的入口

0: kd> dt _KUSER_SHARED_DATA 0x7ffe0000 
ntdll!_KUSER_SHARED_DATA
   +0x000 TickCountLowDeprecated : 0
   +0x004 TickCountMultiplier : 0xf99a027
   +0x008 InterruptTime    : _KSYSTEM_TIME
   +0x014 SystemTime       : _KSYSTEM_TIME
   +0x020 TimeZoneBias     : _KSYSTEM_TIME
   +0x02c ImageNumberLow   : 0x14c
   +0x02e ImageNumberHigh  : 0x14c
   +0x030 NtSystemRoot     : [260]  "C:\Windows"
   +0x238 MaxStackTraceDepth : 0
   +0x23c CryptoExponent   : 0
   +0x240 TimeZoneId       : 0
   +0x244 LargePageMinimum : 0x200000
   +0x248 Reserved2        : [7] 0
   +0x264 NtProductType    : 1 ( NtProductWinNt )
   +0x268 ProductTypeIsValid : 0x1 ''
   +0x26c NtMajorVersion   : 6
   +0x270 NtMinorVersion   : 1
。。。。。。。。。。。。。。。。。。。。
   +0x2f4 DataFlagsPad     : [1] 0
   +0x2f8 TestRetInstruction : 0xc3
   +0x300 SystemCall       : 0x772670b0//系统调用stub的位置

而它又会转入内核层里面的KiFastSystemcall,这里就看见了我们要介绍的主角 sysenter

; _DWORD __stdcall KiFastSystemCall()
 public _KiFastSystemCall@0
 _KiFastSystemCall@0 proc near 
 
 mov     edx, esp
 sysenter
 
 _KiFastSystemCall@0 endp

指令执行过程

在R3的代码调用了sysenter指令之后,CPU会做出如下操作

1.将SYSENTER_CS_MSR的值装载到cs寄存器 174

2.将SYSENTER_EIP_MSR的值装载到eip寄存器 176

3.将SYSENTER_CS_MSR的值加8,装载到ss寄存器

4.将SYSENTER_ESP_MSR的值装载到esp寄存器 175

5.将特权基本切换到Ring0

6.如果EFLAGs寄存器的VM标志被置位,则清除该标志

7.开始执行指定的R0代码

咋一看,有点复杂,走了七步

但是我们仔细一看,前4步都是在设置环境,也是我们之前在保护模式里面所强调的R3进R0的本质

先来看看这些SYSENTER_MSR里面到底是什么

0: kd> rdmsr 176
msr[176] = 00000000`83e5c0c0
0: kd> rdmsr 175
msr[175] = 00000000`80790000
0: kd> rdmsr 174
msr[174] = 00000000`00000008//这里cs的值是0x8

我们还可以顺便看看这些eip指向的是什么,这里我没有加载符号表,但是这个函数就是KiFastCallEntry

0: kd> u 00000000`83e5c0c0
ReadVirtual: 83e5c0c0 not properly sign extended
83e5c0c0 b923000000      mov     ecx,23h
83e5c0c5 6a30            push    30h
83e5c0c7 0fa1            pop     fs
83e5c0c9 8ed9            mov     ds,cx
83e5c0cb 8ec1            mov     es,cx
83e5c0cd 648b0d40000000  mov     ecx,dword ptr fs:[40h]
83e5c0d4 8b6104          mov     esp,dword ptr [ecx+4]
83e5c0d7 6a23            push    23h

最后看看esp,这里esp就是一个过渡的值,这个esp值在win32里面并没有被直接使用

0: kd> u 00000000`80790000
80790000 ??              ???
                         ^ Memory access error in 'u 00000000`80790000'

而是使用 KPCR 结构TSS 块里的 ESP 值

KPCR

当线程进入0环时,FS:[0]指向KPCR
每个CPU都有一个KPCR结构体
KPCR中存储了CPU本身要用的一些重要数据:GDT、IDT以及线程相关的一些信息。
0: kd> dt _KPCR
ntdll!_KPCR
   +0x000 NtTib            : _NT_TIB
   +0x000 Used_ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD
   +0x004 Used_StackBase   : Ptr32 Void
。。。。。。。。。。。。。。。。。。。。。。。。。。。
   +0x040 TSS              : Ptr32 _KTSS//TSS块
   +0x044 MajorVersion     : Uint2B
   +0x046 MinorVersion     : Uint2B
   +0x048 SetMember        : Uint4B
   +0x04c StallScaleFactor : Uint4B
。。。。。。。。。。。。。。。。。。。。。。。。。

0: kd> dt _ktss
ntdll!_KTSS
。。。。。。。。。。。。。。。。。。。。。。。。
   +0x002 Reserved0        : Uint2B
   +0x004 Esp0             : Uint4B//这里才是真正的esp的值
   +0x008 Ss0              : Uint2B
。。。。。。。。。。。。。。。。

最后看一个设置VM位的解释

VM(bit 17) [Virtual-8086 mode flag] 置1以允许虚拟8086模式,清除则返回保护模式。

KTRAP_FRAME

还有一点点逻辑没有补全,我们之前一直在介绍R3跳入R0时候的几个值从哪里来,那么R3的值此时是被覆盖了的,所以我们也需要保存这些原来的值,不然在返回R3时肯定就会发生崩溃

保存这些值的结构就是KTRAP_FRAME

0: kd> dt _KTRAP_FRAME
ntdll!_KTRAP_FRAME
   +0x000 DbgEbp           : Uint4B
   +0x004 DbgEip           : Uint4B
   +0x008 DbgArgMark       : Uint4B
   +0x00c DbgArgPointer    : Uint4B
   +0x010 TempSegCs        : Uint2B
   +0x012 Logging          : UChar
   +0x013 Reserved         : UChar
   +0x014 TempEsp          : Uint4B
   +0x018 Dr0              : Uint4B
   +0x01c Dr1              : Uint4B
   +0x020 Dr2              : Uint4B
   +0x024 Dr3              : Uint4B
   +0x028 Dr6              : Uint4B
   +0x02c Dr7              : Uint4B
   +0x030 SegGs            : Uint4B
   +0x034 SegEs            : Uint4B
   +0x038 SegDs            : Uint4B
   +0x03c Edx              : Uint4B
   +0x040 Ecx              : Uint4B
   +0x044 Eax              : Uint4B
   +0x048 PreviousPreviousMode : Uint4B
   +0x04c ExceptionList    : Ptr32 _EXCEPTION_REGISTRATION_RECORD
   +0x050 SegFs            : Uint4B
   +0x054 Edi              : Uint4B
   +0x058 Esi              : Uint4B
   +0x05c Ebx              : Uint4B
   +0x060 Ebp              : Uint4B
   +0x064 ErrCode          : Uint4B
   +0x068 Eip              : Uint4B
   +0x06c SegCs            : Uint4B
   +0x070 EFlags           : Uint4B
   +0x074 HardwareEsp      : Uint4B
   +0x078 HardwareSegSs    : Uint4B
   +0x07c V86Es            : Uint4B
   +0x080 V86Ds            : Uint4B
   +0x084 V86Fs            : Uint4B
   +0x088 V86Gs            : Uint4B

可以看见我们一般涉及的寄存器都出现了,这也是为我们所保存的现场

那么这个突然跳出来的KTRAP_FRAME又和谁有关联呢?不卖关子了,就是esp0

KTRAP_FRAME 结构基址等于 Esp0 值减 0x7c,同理也就是说Esp0的值指向了上面KTRAP_FRAME 结构里面的V86Es

是不是一切逻辑都联系起来了(😀

KiFastCallEntry(请配合ntkrlpa.exe食用)

介绍完了基础知识,那么我们就可以来研究一下这个快速调用的入口了(也就是我们之前在R3转R0时候的那个eip)

在这里插入图片描述

为了方便起见,接下来我就不截图,改为介绍汇编了

首先就是将ds,es置为23,fs置为30,这也是之前我们所做的测试中,如果在保护模式下修改寄存器强行进入R0而其他线程运行没有报错的原因——每次进R0前都会强制把几个寄存器置为在R3的状态

mov     ecx, 23h ; '#'
push    30h ; '0'
pop     fs
mov     ds, ecx
mov     es, ecx

这里我们看见首先从fs偏0x40处取了一个值,之前通过学习我们知道,fs指向的是KPCR这么一个结构,回去复习一下,0x40也就是我们的tss,这也是我们之前说的sysenter指令所给的SYSENTER_ESP_MSR这个值实际上只是一个过渡,之后系统会自己去线程里面找这个esp的值

mov     ecx, large fs:40h
mov     esp, [ecx+4]
push    23h ; '#'
push    edx
pushf
0: kd> dt _ktss
ntdll!_KTSS
   +0x000 Backlink         : Uint2B
   +0x002 Reserved0        : Uint2B
   +0x004 Esp0             : Uint4B//取到了这里
   +0x008 Ss0              : Uint2B
   +0x00a Reserved1        : Uint2B
   +0x00c NotUsed1         : [4] Uint4B

接着上面的汇编还没完,它连续压了三个值,这时候我们又打开KTRAP_FRAME 这个结构来看

 +0x070 EFlags           : Uint4B//保存了eflags
   +0x074 HardwareEsp      : Uint4B//edx,如果还记得KiFastSystemCall的内容,就会知道这里的edx就是R3下的esp,
   +0x078 HardwareSegSs    : Uint4B//23
   +0x07c V86Es            : Uint4B//最开始我们堆栈的值指向这里
   +0x080 V86Ds            : Uint4B
   +0x084 V86Fs            : Uint4B
   +0x088 V86Gs            : Uint4B

继续向下看

loc_43E0DB:
push    2
add     edx, 8
popf

这里首先看见把一个2的值弹入了eflags中,这个edx加8又是什么意思呢?

       esp   -》 retaddress
                 previous_ebp
                 参数1
                 参数2  

之前我们说过edx此时就是R3时的esp,所以这个+8也就意味着我们的edx在这之后都指向了R3的参数

继续向下,这次截长一点

or      byte ptr [esp+1], 2
push    1Bh
push    dword ptr ds:0FFDF0304h
push    0
push    ebp
push    ebx
push    esi
push    edi
mov     ebx, large fs:1Ch
push    3Bh ; ';'
mov     esi, [ebx+124h]
push    dword ptr [ebx]
mov     dword ptr [ebx], 0FFFFFFFFh
mov     ebp, [esi+28h]
push    1
sub     esp, 48h
sub     ebp, 29Ch

首先一个or了一个esp+1的位置,此时的esp根据我们前面的分析,是eflags,加1字节也就是加8位

在这里插入图片描述

指向的TF,或上2,也就是把中断使能标志(IF)打开了,简而言之,开中断

接下来连续push了好几个值我就直接上结构了

   +0x054 Edi              : Uint4B//edi
   +0x058 Esi              : Uint4B//esi
   +0x05c Ebx              : Uint4B//ebx
   +0x060 Ebp              : Uint4B//ebp
   +0x064 ErrCode          : Uint4B//0
   +0x068 Eip              : Uint4B//0FFDF0304
   +0x06c SegCs            : Uint4B //1b

这里面,这个eip的值可以说道说道,在本文的上面说过有一个结构KUSER_SHARED_DATA,这个结构在R0中的地址是0xffdf0000,那这里的0FFDF0304打开结构一看,是我们的系统返回地址,这里后面我们会继续介绍,这里暂时按下不表

+0x304 SystemCallReturn : 0x772670b4

相信真的一点一点看到这里的人,没被我弄晕的话,应该有自己简单分析的能力了

mov     ebx, large fs:1Ch //KPCR结构+1c 也就是+0x01c SelfPcr这里
push    3Bh ; ';'压3b进KTRAP_FRAME中的segfs
mov     esi, [ebx+124h]//ebx此时是kpcr,kpcr本身只有0x120,第0x120是PrcbData,这是一个KPCRB结构,可以算是扩展,KPCRB结构再偏4就是CurrentThread这么一个地方,它是_KTHREAD结构
push    dword ptr [ebx]//有点复杂,kpcr.NtTib.ExceptionList,等价压入了这样一个异常链
mov     dword ptr [ebx], 0FFFFFFFFh//异常链的初始化
mov     ebp, [esi+28h]//此时的ebp被换为了_KTHREAD偏0x28的值,也就是InitialStack
push    1//KTRAP_FRAME中的PreviousPreviousMode,如果从R3跳R0那就是1,反之就是0

忙完了这么一段,最后是两句汇编,一下子往上提了一段栈esp和ebp都指向了同一个位置,恍然大悟,原来到这里本质上都是我们函数调用的保存现场

sub     esp, 48h
sub     ebp, 29Ch

也就是,在提栈后,我们才真正进入线程的堆栈。

之后的分析我就不具体说是哪里哪里的偏移了,直接会讲这是什么

再接下来,还是对于现场的保存,只是换成了调试寄存器

mov     byte ptr [esi+13Ah], 1          ; PreviousMode
cmp     ebp, esp                        ; 如果堆栈异常则进入KiTrap06
jnz     short loc_43E0BB

and     dword ptr [ebp+2Ch], 0          ; dr7寄存器初始化
test    byte ptr [esi+3], 0DFh          ; 这个地方就是硬件断点的检测
mov     [esi+128h], ebp                 ; 之前说过了,提栈之后,esp和ebp都指向了KTRAP_FRAME
jnz     Dr_FastCallDrSave               ; 保存调试寄存器,如果检测出硬件断点那么就会把KTRAP_FRAME里面的调试寄存器填满

继续向下走,这里就是把几个参数保存起来了,方便我们在后续如果出现了异常,可以知道几个基本的现场的参数

mov     ebx, [ebp+60h]                  ; KTRAP_FRAME ebp 保存到ebx里面
mov     edi, [ebp+68h]                  ; KTRAP_FRAME eip 保存到edi里面
mov     [ebp+0Ch], edx                  ; DbgArgPointer 临时变量里面装了edx的值,也就是我们参数的位置
mov     dword ptr [ebp+8], 0BADB0D00h   ; 不知道什么用
mov     [ebp+0], ebx                    ; DbgEbp
mov     [ebp+4], edi                    ; DbgEip
sti                                     ; 开中断

我们继续向下

mov     edi, eax                        ; eax里面存的就是ssdt号
shr     edi, 8                          ; 服务号右移8位
and     edi, 10h                        ; 与一个10,本质上是在区分ssdt和sssdt

这里插一个小知识我们这时候的ssdt号并不是索引,它还只是一个服务号还没经过处理

如果是经由ntdll的系统调用,不和UI产生关系,这样的服务号都在0x1000以下

如果是经由user32.dll GDI32.dll 这类过UI的,这样的服务号都在0x1000以上

比如我是一个0x1008的服务号,右移了8位后就是0x00000010,这样和0x10相与就不会为0

如果是0x990 同理就是0x00000009,和0x10相与后为0

所以这里就是为了区分这两种服务号

这个ssdt表在32位里面是导出的

kd> dd KeServiceDescriptorTable
83f839c0  83e97d9c 00000000 00000191 83e983e4 //83e97d9c函数表地址,00000191函数表中函数个数
83f839d0  00000000 00000000 00000000 00000000
83f839e0  83ef66af 00000000 00000000 00000bb8
83f839f0  00000011 00000100 5385d2ba d717548f
83f83a00  83e97d9c 00000000 00000191 83e983e4
83f83a10  00000000 00000000 00000000 00000000
83f83a20  00000000 00000000 83f83a24 00000340
83f83a30  00000340 00000000 00000007 00000000

同理,sssdt也是

kd> dd KeServiceDescriptorTableShadow
83f83a00  83e97d9c 00000000 00000191 83e983e4
83f83a10  00000000 00000000 00000000 00000000
83f83a20  00000000 00000000 83f83a24 00000340
83f83a30  00000340 00000000 00000007 00000000
83f83a40  00000000 00000000 00000000 00000000
83f83a50  00000000 00000000 00000000 00000000
83f83a60  00000000 00000000 00000000 00000000
83f83a70  00000000 00000000 00000000 ffffffff

继续向下看

mov     ecx, edi
add     edi, [esi+0BCh]                 ; 计算取哪个表的偏移
mov     ebx, eax
and     eax, 0FFFh                      ; 取出索引
cmp     eax, [edi+8]                    ; 判断数组是否越界,实际上就是看eax中的索引号有没有大于ssdt表中的函数个数
jnb     _KiBBTUnexpectedRange

继续

cmp     ecx, 10h                        ; 如果是UI线程
jnz     short loc_43E18E
mov     ecx, [esi+88h]                  ; 线程的teb
xor     esi, esi
loc_43E17C:                             ; DATA XREF: _KiTrap0E+156↓o
or      esi, [ecx+0F70h]
jz      short loc_43E18E
push    edx
push    eax
call    ds:_KeGdiFlushUserBatch         ; 批量刷新UI
pop     eax
pop     edx

再往后就是对参数的处理

inc     large dword ptr fs:6B0h         ; 调用一次系统调用此书就+1
mov     esi, edx                        ; 获取参数,之前说过了edx就是参数地址
xor     ecx, ecx
mov     edx, [edi+0Ch]                  ; 获取参数表
mov     edi, [edi]                      ; 函数表
mov     cl, [eax+edx]                   ; 函数索引
mov     edx, [edi+eax*4]                ; 函数地址
sub     esp, ecx                        ; 提升堆栈,准备copy参数
shr     ecx, 2                          ; 计算参数个数
mov     edi, esp
cmp     esi, ds:_MmUserProbeAddress     ; 看看是不是R3的地址
jnb     loc_43E3E5

最后一小段。最后是call了函数表里面的函数地址

loc_43E1B7:                             ; CODE XREF: _KiFastCallEntry+329↓j
rep movsd                               ; 复制参数
test    byte ptr [ebp+6Ch], 1           ; 判断TRAP_FRAME.SegCs,看看调用来自R3还是R0
jz      short loc_43E1D5                ; 如果是R0那么直接往下跳
mov     ecx, large fs:124h              ; KPCR.PrcbData.CurrentThread
mov     edi, [esp+7Ch+var_7C]
mov     [ecx+13Ch], ebx                 ; 储存ETHREAD.tcb.SystemCallNumber
mov     [ecx+12Ch], edi                 ; 存储ETHREAD.Tcb.FirstArgument
mov     ebx, edx
test    byte ptr ds:dword_537908, 40h   ; 系统日志相关
setnz   byte ptr [ebp+12h]
jnz     loc_43E574

call    ebx                             ; 这里的ebx来自于之前的edx,也就是函数地址

http://www.niftyadmin.cn/n/5845835.html

相关文章

ubuntu中 使用C++ FFmpeg拉取RTSP视频流

在C中使用FFmpeg拉取RTSP视频流涉及多个步骤,包括初始化FFmpeg库、打开RTSP流、读取帧数据等。以下是一个简单的示例代码,展示如何使用FFmpeg库拉取RTSP视频流并解码视频帧。 1. 安装FFmpeg库 首先,确保你已经安装了FFmpeg库。你可以通过以…

Windows编程:下载与安装 Visual Studio 2019

本节前言 在写作本节的时候,本来呢,我正在写的专栏,是 MFC 专栏。而 VS2010 和 VS2019,正是 MFC 学习与开发中,可以使用的两款软件。然而呢,如果你去学习 Windows API 知识的话,那么&#xff0…

10vue3实战-----实现登录的基本功能

10vue3实战-----实现登录的基本功能 1.基本页面的搭建2.账号登录的验证规则配置3.点击登录按钮4.表单的校验5.账号的登录逻辑和登录状态保存6.定义IAccount对象类型 1.基本页面的搭建 大概需要搭建成这样子的页面: 具体的搭建界面就不多讲。各个项目都有自己的登录界面&#…

基于ESP32的远程开关灯控制(ESP32+舵机+Android+物联网云平台)

目录 材料环境准备物理材料软件环境 物联网平台配置(MQTT)MQTT阿里云平台配置创建产品添加设备自定义topic esp32配置接线代码 Android部分和云平台数据流转 前言:出租屋、宿舍网上关灯问题,计划弄一个智能开关以及带一点安防能力…

ElementUI的<el-image>组件引用网络图片加载失败

1. 验证图片 URL 是否有效 直接访问图片链接,确保 URL 正确且可公开访问 如果浏览器无法加载图片,可能是图片服务器限制了外链或已失效。 解决方法:更换为可用的图片 URL。 2. 检查浏览器开发者工具 打开浏览器开发者工具(F12…

docker环境下部署face-search开源人脸识别模型

由于我们是直接将face-search部署在docker容器中的,所以,在部署之前一定要检查一下自己的docker环境,要不然部署过程中会出现各种各样的问题 我这里的docker环境是 一、安装docker环境 如果docker版本比较低或者docker-compose的版本比较低的情况下,部署的时候docker的yml…

[Meet DeepSeek] 如何顺畅使用DeepSeek?告别【服务器繁忙,请稍后再试。】

文章目录 [Meet DeepSeek] 如何顺畅使用DeepSeek?告别【服务器繁忙,请稍后再试。】引言使用渠道一:硅基流动 Chatbox AI【推荐】硅基流动 Chatbox AI的优势 使用渠道二:秘塔AI搜索秘塔AI搜索的优势 其它方案1. DeepSeek官网2. 纳…

B树详解及其C语言实现

目录 一、B树的基本原理 二、B树操作过程图形化演示 三、B树的应用场景 四、C语言实现B树及示例 五、代码执行结果说明 六、应用实例:文件系统目录索引 七、总结 一、B树的基本原理 B树(B-Tree) 是一种自平衡的树数据结构,…