系統調用001 API從三環進零環的過程

前言

在三環操作系統提供了各種API,這些API實際上只是一個暴露在三環的接口,真正的功能實現部分,最終都是要進到零環。

逆向分析ReadProcessMemory

###ReadProcessMemory

以ReadProcessMemory這個函數爲例,來看一下三環的API的執行流程大致是什麼樣的。用IDA打開kernel32.dll,找到ReadProcessMemory函數。

在這裏插入圖片描述

ReadProcessMemory在內部調用了NtReadVirtualMemory函數,這個函數來自於kernel32.dll的導入表。接下來我們在導入表中找到這個函數

在這裏插入圖片描述

從導入表中可以看到,NtReadVirtualMemory這個函數來自於ntdll。接着我們用IDA打開ntdll.dll。並找到NtReadVirtualMemory函數。

NtReadVirtualMemory

在這裏插入圖片描述

NtReadVirtualMemory這個函數只有下面的幾行代碼

.text:7C92E2BB                 mov     eax, 0BAh       ; NtReadVirtualMemory
.text:7C92E2C0                 mov     edx, 7FFE0300h
.text:7C92E2C5                 call    dword ptr [edx]
.text:7C92E2C7                 retn    14h

這裏call了一個[edx],那麼接下來我們就要去找[edx]指向的是哪個函數,而edx的內容則取決於7FFE0300h這個地址裏面是什麼。

而想要了解7FFE0300h這個地址裏的內容,需要先了解一個結構體->_KUSER_SHARED_DATA。

_KUSER_SHARED_DATA

在用戶層和內核層分別定義了一個_KUSER_SHARED_DATA結構區域,用於在用戶層和內核層共享某些數據。它們使用固定的地址值映射,_KUSER_SHARED_DATA結構區域在User和Kernel層地址分別爲:

  • User層地址爲:0x7ffe0000
  • Kernel層地址爲:0xffdf0000

User層和Kernel層映射同一個物理頁。雖然它們指向的是同一個物理頁,但在User層是隻讀的,在Kernel層是可寫的。

直接在windbg裏查看一下這兩個地址的內容,首先掛載到任意一個進程

PROCESS 88049c68  SessionId: 0  Cid: 0930    Peb: 7ffd9000  ParentCid: 05b8
    DirBase: 7f4b64c0  ObjectTable: a7725ab8  HandleCount: 14034.
    Image: OEM8.exe

kd> .process 88049c68  
Implicit process is now 88049c68
WARNING: .cache forcedecodeuser is not enabled

接着查看這兩個地址的內容

kd> dd 0x7ffe0000
7ffe0000  00000000 0f99a027 5283733f 00000000
7ffe0010  00000000 22ad355a 01d5b7d1 01d5b7d1
7ffe0020  f1dcc000 ffffffbc ffffffbc 014c014c
7ffe0030  003a0043 0057005c 006e0069 006f0064
7ffe0040  00730077 00000000 00000000 00000000
7ffe0050  00000000 00000000 00000000 00000000
7ffe0060  00000000 00000000 00000000 00000000
7ffe0070  00000000 00000000 00000000 00000000
kd> dd 0xffdf0000
ffdf0000  00000000 0f99a027 5283733f 00000000
ffdf0010  00000000 22ad355a 01d5b7d1 01d5b7d1
ffdf0020  f1dcc000 ffffffbc ffffffbc 014c014c
ffdf0030  003a0043 0057005c 006e0069 006f0064
ffdf0040  00730077 00000000 00000000 00000000
ffdf0050  00000000 00000000 00000000 00000000
ffdf0060  00000000 00000000 00000000 00000000
ffdf0070  00000000 00000000 00000000 00000000

兩塊地址空間的內容完全相同。接着再查看一下兩個地址的屬性

kd> !vtop 7f4b64c0 0x7ffe0000 
X86VtoP: Virt 000000007ffe0000, pagedir 000000007f4b64c0
X86VtoP: PAE PDPE 000000007f4b64c8 - 000000004fe09801
X86VtoP: PAE PDE 000000004fe09ff8 - 000000004fa07867
X86VtoP: PAE PTE 000000004fa07f00 - 80000000001e2025
X86VtoP: PAE Mapped phys 00000000001e2000
Virtual address 7ffe0000 translates to physical address 1e2000.
kd> !vtop 7f4b64c0 0xffdf0000
X86VtoP: Virt 00000000ffdf0000, pagedir 000000007f4b64c0
X86VtoP: PAE PDPE 000000007f4b64d8 - 000000004c00b801
X86VtoP: PAE PDE 000000004c00bff0 - 000000000018a063
X86VtoP: PAE PTE 000000000018af80 - 00000000001e2163
X86VtoP: PAE Mapped phys 00000000001e2000
Virtual address ffdf0000 translates to physical address 1e2000.

0x7ffe0000這個三環的地址PTE屬性是隻讀的,而0xffdf0000這個零環的地址的PTE屬性是可讀可寫的。

SystemCall

接下來回到NtReadVirtualMemory這個函數

.text:7C92E2BB                 mov     eax, 0BAh       ; NtReadVirtualMemory
.text:7C92E2C0                 mov     edx, 7FFE0300h
.text:7C92E2C5                 call    dword ptr [edx]
.text:7C92E2C7                 retn    14h

現在我們已經知道了0x7ffe0000這個內存是一塊共享的內存區域,接下來看一下偏移0x300的位置也就是7FFE0300這個地址的值是什麼。

kd> dt _KUSER_SHARED_DATA 0x7ffe0000
nt!_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
   +0x274 ProcessorFeatures : [64]  ""
   +0x2b4 Reserved1        : 0x7ffeffff
   +0x2b8 Reserved3        : 0x80000000
   +0x2bc TimeSlip         : 0
   +0x2c0 AlternativeArchitecture : 0 ( StandardDesign )
   +0x2c4 AltArchitecturePad : [1] 0
   +0x2c8 SystemExpirationDate : _LARGE_INTEGER 0x0
   +0x2d0 SuiteMask        : 0x310
   +0x2d4 KdDebuggerEnabled : 0x3 ''
   +0x2d5 NXSupportPolicy  : 0x2 ''
   +0x2d8 ActiveConsoleId  : 1
   +0x2dc DismountCount    : 0
   +0x2e0 ComPlusPackage   : 0xffffffff
   +0x2e4 LastSystemRITEventTickCount : 0
   +0x2e8 NumberOfPhysicalPages : 0x7ff7e
   +0x2ec SafeBootMode     : 0 ''
   +0x2ed TscQpcData       : 0 ''
   +0x2ed TscQpcEnabled    : 0y0
   +0x2ed TscQpcSpareFlag  : 0y0
   +0x2ed TscQpcShift      : 0y000000 (0)
   +0x2ee TscQpcPad        : [2]  ""
   +0x2f0 SharedDataFlags  : 0xc
   +0x2f0 DbgErrorPortPresent : 0y0
   +0x2f0 DbgElevationEnabled : 0y0
   +0x2f0 DbgVirtEnabled   : 0y1
   +0x2f0 DbgInstallerDetectEnabled : 0y1
   +0x2f0 DbgSystemDllRelocated : 0y0
   +0x2f0 DbgDynProcessorEnabled : 0y0
   +0x2f0 DbgSEHValidationEnabled : 0y0
   +0x2f0 SpareBits        : 0y0000000000000000000000000 (0)
   +0x2f4 DataFlagsPad     : [1] 0
   +0x2f8 TestRetInstruction : 0xc3
   +0x300 SystemCall       : 0x776c70b0
   +0x304 SystemCallReturn : 0x776c70b4
   +0x308 SystemCallPad    : [3] 0
   +0x320 TickCount        : _KSYSTEM_TIME
   +0x320 TickCountQuad    : 0x22a9
   +0x320 ReservedTickCountOverlay : [3] 0x22a9
   +0x32c TickCountPad     : [1] 0
   +0x330 Cookie           : 0xe0c0696a
   +0x334 CookiePad        : [1] 0
   +0x338 ConsoleSessionForegroundProcessId : 0n1600
   +0x340 Wow64SharedInformation : [16] 0
   +0x380 UserModeGlobalLogger : [16] 0
   +0x3a0 ImageFileExecutionOptions : 0
   +0x3a4 LangGenerationCount : 1
   +0x3a8 Reserved5        : 0
   +0x3b0 InterruptTimeBias : 0
   +0x3b8 TscQpcBias       : 0
   +0x3c0 ActiveProcessorCount : 1
   +0x3c4 ActiveGroupCount : 1
   +0x3c6 Reserved4        : 0
   +0x3c8 AitSamplingValue : 0
   +0x3cc AppCompatFlag    : 1
   +0x3d0 SystemDllNativeRelocation : 0xff7c0000
   +0x3d8 SystemDllWowRelocation : 0
   +0x3dc XStatePad        : [1] 0
   +0x3e0 XState           : _XSTATE_CONFIGURATION

找到+300的位置

 +0x300 SystemCall       : 0x776c70b0

這個地方是一個SystemCall,查看一下對應的反彙編代碼,看看NtReadVirtualMemory函數的call dword ptr [edx]具體是做了什麼。

kd> u 0x776c70b0
ntdll!KiFastSystemCall
776c70b0 8bd4            mov     edx,esp
776c70b2 0f34            sysenter
776c70b4 c3              ret

這個函數叫KiFastSystemCall,實際上就只有三行代碼,首先把esp保存到edx,目的是爲了在零環能夠方便的找到三環的堆棧。接着用sysenter指令進到零環,最後通過ret指令返回。

然而並不是所有的CPU都支持sysenter快速調用指令。這就要了解一下另外一個問題?0x7ffe0300到底存儲的是什麼?

###兩種從三環進零環的方式

操作系統在啓動的時候,需要初始化_KUSER_SHARED_DATA這個結構體,其中最重要的就是初始化0x300這個位置。操作系統要往這裏面寫一個函數,這個函數決定了所有的三環的API進入零環的方式。

操作系統在寫入之前會通過cpuid這個指令來檢查當前的CPU是否支持快速調用,如果支持的話,就往0x300這個位置寫入KiFastSystemCall。如果不支持,則寫入KiIntSystemCall。

我們可以在IDA中看到KiIntSystemCall的函數內容

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-uUumiE8A-1576921324496)(assets/1576917420769.png)]

.text:7C92EBA5                 lea     edx, [esp+arg_4]
.text:7C92EBA9                 int     2Eh            
.text:7C92EBAB                 retn

KiIntSystemCall就只有三行代碼,利用int 0x2E這條指令通過中斷門的方式進入零環。

也就是說Windows使用了兩種從三環進零環的方式。一種是中斷門,一種是sysenter快速調用。

通過int 0x2E中斷門進入零環

如果是通過中斷門的方式進入到零環的話,最終EIP會指向哪呢?這就要查看IDT表了。

首先查看idt表的基址

kd> r idtr
idtr=80b95400

接着查看IDT表項0x2E的位置的段描述符

kd> dq 80b95400+0x2E*8
80b95570  83e8ee00`00083fee 83e88e00`000876b0
80b95580  83e88e00`000836b0 83e88e00`000836ba
80b95590  83e88e00`000836c4 83e88e00`000836ce
80b955a0  83e88e00`000836d8 83e88e00`000836e2
80b955b0  83e88e00`000836ec 83e28e00`00089104
80b955c0  83e88e00`00083700 83e88e00`0008370a
80b955d0  83e88e00`00083714 83e88e00`0008371e
80b955e0  83e88e00`00083728 83e88e00`00083732

通過拆分83e8ee00`00083fee這個中斷門描述符可以得出CS段選擇子爲0008,EIP爲83e83fee。也就是說API通過中斷門的方式最終會跳轉到0x83e83fee。接着查看一下這個地址的反彙編

kd> u 83e83fee
nt!KiSystemService:
83e83fee 6a00            push    0
83e83ff0 55              push    ebp
83e83ff1 53              push    ebx
83e83ff2 56              push    esi
83e83ff3 57              push    edi
83e83ff4 0fa0            push    fs
83e83ff6 bb30000000      mov     ebx,30h
83e83ffb 668ee3          mov     fs,bx

KiSystemService函數的地址是8開頭的,而且模塊是nt不再是ntdll。到這裏,API已經完成了從三環進入零環的過程。

通過sysenter快速調用進入零環

想要從三環進入到零環首先必須要提權,提權需要切換CS SS EIP ESP。如果通過中斷門進入零環,門描述符裏保存有CS和EIP,而SS和ESP來自於TSS。

在瞭解sysenter指令之前,要先了解一個寄存器,叫MSR。操作系統並沒有公開這個寄存器的內部細節。但是我們可以知道這個寄存器的部分含義:

  • 0x174保存的是CS
  • 0x175保存的是ESP
  • 0x176保存的是EIP

如果想查看msr寄存器174就可以使用下面的指令

kd> rdmsr 174
msr[174] = 00000000`00000008

sysenter快速調用指令完成的事情就是從msr寄存器裏拿到174 175和176的值,覆蓋原來寄存器的值。int 0x2E和sysenter兩種進入零環的方式的本質都是切換寄存器。

還有一個問題在於,通過msr寄存器只能拿到三個值,分別是CS ESP和EIP,那麼SS來自於哪呢?這個SS的值實際上是寫死的。舉個例子來說,如果提權之後的CS的值爲8,那麼SS=CS+8=0x10。(具體細節請參考Intel白皮書第二卷 搜索sysenter)

總結

API從三環進到零環過程如圖:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-vBdovwgd-1576921324505)(assets/API三環進零環的過程.png)]

sysenter快速調用指令完成的事情就是從msr寄存器裏拿到174 175和176的值,覆蓋原來寄存器的值。int 0x2E和sysenter兩種進入零環的方式的本質都是切換寄存器。

還有一個問題在於,通過msr寄存器只能拿到三個值,分別是CS ESP和EIP,那麼SS來自於哪呢?這個SS的值實際上是寫死的。舉個例子來說,如果提權之後的CS的值爲8,那麼SS=CS+8=0x10。(具體細節請參考Intel白皮書第二卷 搜索sysenter)

總結

API從三環進到零環過程如圖:

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章