系统调用-3环进0环

admin 2023年11月21日14:13:07评论66 views字数 5646阅读18分49秒阅读模式


系统调用-3环进0环

系统调用-3环进0环前篇


系统调用-3环进0环

前面我们简单的介绍了一下,当我们在3环调用WindowsAPI时,系统是如何从3环调用0环的函数,那么本篇就来详细介绍一下,到底是怎么进入的,有哪几种方式。

系统调用-3环进0环



1

_KUSER_SHARED_DATA

系统调用-3环进0环

我们先介绍一个结构体:_KUSER_SHARED_DATA

  • 在 User 层和 Kernel 层分别定义了一个 _KUSER_SHARED_DATA 结构区域,用于 User 层和 Kernel 层共享某些数据

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

    • User 层地址为:0x7ffe0000

    • Kernnel 层地址为:0xffdf0000

  • 当然,虽然他们指向的都是同一个物理页,但是在User层是只读的,在Kernel层是可读可写的。

这样说不够直观,我们直接用windbg来查看一下:

kd> dd 0x7ffe0000
7ffe0000 00000bbb 0fa00000 1bf7b5be 00000000
7ffe0010 00000000 5ab53d06 01da1b83 01da1b83
7ffe0020 f1dcc000 ffffffbc ffffffbc 014c014c
7ffe0030 003a0043 0057005c 004e0049 004f0044
7ffe0040 00530057 00000000 00000000 00000000
7ffe0050 00000000 00000000 00000000 00000000
7ffe0060 00000000 00000000 00000000 00000000
7ffe0070 00000000 00000000 00000000 00000000
kd> dd 0xffdf0000
ffdf0000 00000bbb 0fa00000 1bf7b5be 00000000
ffdf0010 00000000 5ab53d06 01da1b83 01da1b83
ffdf0020 f1dcc000 ffffffbc ffffffbc 014c014c
ffdf0030 003a0043 0057005c 004e0049 004f0044
ffdf0040 00530057 00000000 00000000 00000000
ffdf0050 00000000 00000000 00000000 00000000
ffdf0060 00000000 00000000 00000000 00000000
ffdf0070 00000000 00000000 00000000 00000000

我们可以发现,确实是一模一样。

而该位置就是_KUSER_SHARED_DATA结构体,我们再看一下该结构体的成员:

kd> dt _KUSER_SHARED_DATA
ntdll!_KUSER_SHARED_DATA
+0x000 TickCountLow : Uint4B
+0x004 TickCountMultiplier : Uint4B
+0x008 InterruptTime : _KSYSTEM_TIME
+0x014 SystemTime : _KSYSTEM_TIME
+0x020 TimeZoneBias : _KSYSTEM_TIME
+0x02c ImageNumberLow : Uint2B
+0x02e ImageNumberHigh : Uint2B
+0x030 NtSystemRoot : [260] Uint2B
+0x238 MaxStackTraceDepth : Uint4B
+0x23c CryptoExponent : Uint4B
+0x240 TimeZoneId : Uint4B
+0x244 Reserved2 : [8] Uint4B
+0x264 NtProductType : _NT_PRODUCT_TYPE
+0x268 ProductTypeIsValid : UChar
+0x26c NtMajorVersion : Uint4B
+0x270 NtMinorVersion : Uint4B
+0x274 ProcessorFeatures : [64] UChar
+0x2b4 Reserved1 : Uint4B
+0x2b8 Reserved3 : Uint4B
+0x2bc TimeSlip : Uint4B
+0x2c0 AlternativeArchitecture : _ALTERNATIVE_ARCHITECTURE_TYPE
+0x2c8 SystemExpirationDate : _LARGE_INTEGER
+0x2d0 SuiteMask : Uint4B
+0x2d4 KdDebuggerEnabled : UChar
+0x2d5 NXSupportPolicy : UChar
+0x2d8 ActiveConsoleId : Uint4B
+0x2dc DismountCount : Uint4B
+0x2e0 ComPlusPackage : Uint4B
+0x2e4 LastSystemRITEventTickCount : Uint4B
+0x2e8 NumberOfPhysicalPages : Uint4B
+0x2ec SafeBootMode : UChar
+0x2f0 TraceLogging : Uint4B
+0x2f8 TestRetInstruction : Uint8B
+0x300 SystemCall : Uint4B
+0x304 SystemCallReturn : Uint4B
+0x308 SystemCallPad : [3] Uint8B
+0x320 TickCount : _KSYSTEM_TIME
+0x320 TickCountQuad : Uint8B
+0x330 Cookie : Uint4B

在0x7ffe0300的位置是一个SystemCall。

系统调用-3环进0环

我们再来看一下对应的PTE和PDE:

首先我们需要在windbg里面查看某个低2G的内存,需要先关联:

  • .process 81e4d7a0

系统调用-3环进0环

接着就可以拆分了:

kd> !vtop 16f61000 0x7ffe0000
X86VtoP: Virt 000000007ffe0000, pagedir 0000000016f61000
X86VtoP: PDE 0000000016f617fc - 16e39067
X86VtoP: PTE 0000000016e39f80 - 00041025
X86VtoP: Mapped phys 0000000000041000
Virtual address 7ffe0000 translates to physical address 41000.
kd> !vtop 16f61000 0xffdf0000
X86VtoP: Virt 00000000ffdf0000, pagedir 0000000016f61000
X86VtoP: PDE 0000000016f61ffc - 0003a163
X86VtoP: PTE 000000000003a7c0 - 00041163
X86VtoP: Mapped phys 0000000000041000
Virtual address ffdf0000 translates to physical address 41000.

系统调用-3环进0环

可以看到User对应的物理页读写位(第1位)为0,Kernel读写位为1

我们继续往下分析:

根据我们昨天的分析:

系统调用-3环进0环

也就是说我们通过kernel32.dll!ReadProcessMemory->ntdll.dll!NtReadVirtualMemory,其实本质上就是通过调用systemcall进入0环的。

这里补充一下,mov eax,0BAh其实0BAh就是调用号,下文会说到。

那么这个0x7ffe0300的systemcall到底存储的是什么呢?


2

0x7FFE0300到底存储的是什么?

系统调用-3环进0环

0x7ffe0300会根据当前处理器是否支持sysenter/sysexit指令,存储不同的函数。

  • 支持:ntdll.dll!KiFastSystemCall()

  • 不支持:ntdll.dll!KiIntSystemCall()

那么如何判断当前系统是否支持呢?

我们可以通过eax=1来执行cpuid指令,该指令需要传递一个参数,就是eax=1,执行后,会将特征信息放在ecx和edx寄存器中,其中edx包含了一个SEP位(11位)。该位为1,代表当前CPU支持sysenter/sysexit指令,如果该位为0,代表不支持。如下图,我这个系统(XP)是支持的

系统调用-3环进0环

也就是说:

  • 支持:会将0x7ffe0300替换成ntdll.dll!KiFastSystemCall()函数

  • 不支持:会将0x7ffe0300替换成ntdll.dll!KiIntSystemCall()函数



3

知识补充-进0环需要更改哪些寄存器?

系统调用-3环进0环


这个其实在前面的保护模式中已经介绍过了,怕大家忘了,所以再次分析一下:

  • CS的权限由3变为0,意味值需要新的CS

  • SS与CS的权限永远一致,需要新的SS

  • 权限发生切换时,堆栈也会切换,所以需要新的ESP

  • 进入0环后,需要知道入口点,所以需要EIP

因此ntdll.dll!KiFastSystemCall()ntdll.dll!KiIntSystemCall()的区别就在于,上述四个寄存器该如何寻找,其他没有啥区别。



4

ntdll.dll!KiIntSystemCall()

系统调用-3环进0环


ntdll.dll!KiIntSystemCall()如下:

系统调用-3环进0环

我们可以看到,他是通过中断门进入0环。在该函数执行前,我们往EAX寄存器中存储了一个函数的编号,也就是系统调用号。而EDX则存储的是我要用的参数地址在哪。接着在通过中断门进入内核。



5

ntdll.dll!KiFastSystemCall()

系统调用-3环进0环


该函数通过快速调用进入0环

系统调用-3环进0环

同样,系统调用号在eax寄存器中,edx存储3环栈顶,用来寻找参数在哪。接着使用sysenter进入内核,而sysenter我们可以理解为快速调用(因为没有读内存的过程)。


系统调用-3环进0环
系统调用-3环进0环

什么是快速调用?


中断门进入0环,需要的CS、EIP在IDT表中,需要查内存(SS与ESP由TSS提供),而CPU如果支持sysenter指令时,操作系统会提前将CS/SS/ESP/EIP的值存储在MSR寄存器中,sysenter指令执行时,CPU会将MSR寄存器中的值直接写入相关寄存器,没有读内存的过程,所以叫快速调用,本质是一样的!


那么我们知道了系统会根据CPU是否支持sysenter指令,来判断是通过中断门(0x2E,IDT表)还是sysenter进入0环。

那么不论是通过中断门还是sysenter执行指令,执行之后,下一步要干什么呢?

我们先来看中断门:



6

INT 0x2E进0环

系统调用-3环进0环


通过INT 0x2E进0环后,步骤如下:

  1. 在IDT表中找到0x2E号门描述符

    • 系统调用-3环进0环

  1. 根据0x2E对应的中断门:804dee00`0008f631我们找到了CS:0008,EIP:804df631,那么SS与ESP则是从TSS寄存器中来。

  2. 来看一下执行函数地址:804df6311

kd> u 804df631
nt!KiSystemService:
804df631 6a00 push 0
804df633 55 push ebp
804df634 53 push ebx
804df635 56 push esi
804df636 57 push edi
804df637 0fa0 push fs
804df639 bb30000000 mov ebx,30h
804df63e 8ee3 mov fs,bx

我们可以看到,不在是ntdll模块了,而是真正的内核模块中的nt!KiSystemService函数。



7

sysenter进入0环

系统调用-3环进0环


在执行sysenter指令之前,操作系统必须指定0环的CS段、SS段、EIP以及ESP。操作系统会提前将CS/SS/ESP/EIP的值存储在MSR寄存器中,CPU会将MSR寄存器中的值直接写入相关寄存器。

系统调用-3环进0环
系统调用-3环进0环

MSR寄存器

MSR

地址

IA32_SYSENTER_CS

174H

IA32_SYSENTER_ESP

175H

IA32_SYSENTER_EIP

176H

我们发现还少了一个SS,那么SS值从何而来呢?

SS是通过计算来的,怎么算呢?当我们执行sysenter指令时,会将IA32_SYSENTER_CS这个值加上8,对应的就是SS的段选择子。以上都是CPU做的

可以通过RDMSR/WRMST来进行读写(操作系统使用WRMST写该寄存器):

  • kd> rdmsr 174   //查看CS

  • kd> rdmsr 175   //查看ESP

  • kd> rdmsr 176   //查看EIP

kd> rdmsr 174
msr[174] = 00000000`00000008
kd> rdmsr 175
msr[175] = 00000000`f8974000
kd> rdmsr 176
msr[176] = 00000000`804df6f0
kd> u 804df6f0
nt!KiFastCallEntry:
804df6f0 b923000000 mov ecx,23h
804df6f5 6a30 push 30h
804df6f7 0fa1 pop fs
804df6f9 8ed9 mov ds,cx
804df6fb 8ec1 mov es,cx
804df6fd 8b0d40f0dfff mov ecx,dword ptr ds:[0FFDFF040h]
804df703 8b6104 mov esp,dword ptr [ecx+4]
804df706 6a23 push 23h

参考:Intel白皮书第二卷(搜索sysenter)

总结

  • API通过中断门进0环:

    • 固定中断号为0x2E

    • CS/EIP由门描述符提供   ESP/SS由TSS提供

    • 进入0环后执行的内核函数:NT!KiSystemService

  • API通过sysenter指令进0环:

    • CS/ESP/EIP由MSR寄存器提供(SS是算出来的)

    • 进入0环后执行的内核函数:NT!KiFastCallEntry

内核模块:ntoskrnl.exe(10-10-12分页)/ntkrnlpa.exe(2-9-9-12分页)

我么我

系统调用-3环进0环


# end

系统调用-3环进0环

系统调用-3环进0环



原文始发于微信公众号(loochSec):系统调用-3环进0环

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年11月21日14:13:07
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   系统调用-3环进0环http://cn-sec.com/archives/2224599.html

发表评论

匿名网友 填写信息