系统调用001 API从三环进零环的过程
文章目錄
- 前言
 - 逆向分析ReadProcessMemory
 - NtReadVirtualMemory
 - _KUSER_SHARED_DATA
 - SystemCall
 - 通過int 0x2E中斷門進入零環
 - 通過sysenter快速調用進入零環
 
- 總結
 - 總結
 
前言
在三環操作系統提供了各種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: 05b8DirBase: 7f4b64c0 ObjectTable: a7725ab8 HandleCount: 14034.Image: OEM8.exekd> .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 retnKiIntSystemCall就只有三行代碼,利用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,bxKiSystemService函數的地址是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`00000008sysenter快速調用指令完成的事情就是從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從三環進到零環過程如圖:
總結
以上是生活随笔為你收集整理的系统调用001 API从三环进零环的过程的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: [保护模式]PAE模式
 - 下一篇: 系统调用002 KiSystemServ