這個問題關系到這一個,但它并沒有填補一些我有差距的,所以我決定用更多的一些細節再問一下,也許把賞金這個。
無論如何,通常如果您在 ntdll 上查找 Nt/Zw 函式,您會看到如下內容:
ZwClose proc near
mov r10, rcx
mov eax, 0Fh
test byte ptr ds:7FFE0308h, 1
jnz short loc_a
syscall
retn
loc_a:
int 2Eh
retn
NtClose endp
現在我知道這是比較KUSER_SHARED_DATA的偏移量并決定是否執行syscall與INT 2E。起初我認為如果正在運行的程式是 32 位應用程式,則 INT 2E 將被執行,但在閱讀了一些關于 WOW64 的內容后,似乎這些應用程式將使用 32 位版本的 ntdll,它不執行 int 2e 而是執行通過天堂之門到達內核:
public ZwClose
ZwClose proc near
mov eax, 3000Fh ; NtClose
mov edx, offset j_Wow64Transition
call edx ; j_Wow64Transition
retn 4
ZwClose endp
所以據我所知,Wow64Transition最終會跳轉到我首先列出的 ntdll 的 64 位版本,對嗎?如果是這樣,是不是在執行 INT 2E 而不是 syscall 時?我還被告知 INT 2E 的原因之一是CET兼容性,所以我對 INT 2E 有點困惑。
uj5u.com熱心網友回復:
所以據我所知,Wow64Transition 最終會跳轉到我首先列出的 ntdll 的 64 位版本,對嗎?
是的。
如果是這樣,是不是在執行 INT 2E 而不是 syscall 時?
不。
首先,讓我們避開明顯的問題:您仍然可以在現代 Windows 系統上毫無問題地呼叫 INT 0x2E,中斷向量仍然存在并指向系統呼叫調度程式:
0: kd> !idt 0x2e
Dumping IDT: fffff8010a900000
2e: fffff8010ca11ec0 nt!KiSystemServiceShadow
是什么使它稱為 int 0x2E?
如您所見,執行 ring3 / ring0 轉換的代碼段在 KUSER_SHARED_DATA 結構中進行了一些檢查。
在偏移量 0x308 處,我們有一個名為 的欄位SystemCall:
0: kd> dt _kuser_shared_data
nt!_KUSER_SHARED_DATA
...
0x308 SystemCall : Uint4B
...
KUSER_SHARED_DATA 被映射到兩個不同的地址:一個在用戶域 (0x7FFE0000) 和一個在內核域 (0xFFFFF78000000000)。這兩個地址都由同一個物理頁面支持(用戶地址顯然是只讀的)。
請注意,這些地址是不變的,不受 ASLR 的影響。因此,我們可以在內核中搜索0xFFFFF78000000308地址(即,KUSER_SHARED_DATA.SystemCall但在內核中)并查看是否匹配。
在名為的函式中實際上只有一個匹配項 KiInitializeKernel
PAGELK:00000001405A36A2 mov r14d, 1
PAGELK:00000001405A36A8 cmp cs:KiSystemCallSelector, r14d
PAGELK:00000001405A36AF jnz loc_1405A3161
;...
PAGELK:00000001405A9236 test cs:HvlEnlightenments, 80000h
PAGELK:00000001405A9240 jz loc_1405A3161
PAGELK:00000001405A9246 mov eax, r14d
PAGELK:00000001405A9249 mov ds:0FFFFF78000000308h, eax
因此,如果KiSystemCallSelector是 1 并且HvlEnlightenments設定了第 19 位,則KUSER_SHARED_DATA.SystemCall設定。
HvlEnlightenments是一個位域,當虛擬化作業系統知道它實際上是虛擬化的(這些作業系統稱為“啟蒙作業系統”)時設定。這意味著很好,該功能(呼叫 INT 0x2E 而不是 SYSCALL)與虛擬化作業系統相關。
我們只剩下KiSystemCallSelector; 此變數設定在名為 的函式中KiInitializeBootStructures:
PAGELK:00000001405A1E48 mov rsi, rcx ; rsi = rcx (1st function param)
; ...
PAGELK:00000001405A2052 mov rdx, [rsi 0F0h]
PAGELK:00000001405A2059 mov eax, [rdx 74h]
; ...
PAGELK:00000001405A206A loc_1405A206A:
PAGELK:00000001405A206A bt eax, 8
PAGELK:00000001405A206E jnb short loc_1405A2077
PAGELK:00000001405A2070 mov cs:KiSystemCallSelector, r13d ; r13d = 1
我們可以看到這個函式的第一個引數很重要;碰巧它是一個名為的全域內核變數KeLoaderBlock:
PAGELK:0000000140597154 mov rcx, cs:KeLoaderBlock_0
PAGELK:000000014059715B call KiInitializeBootStructures
它的型別是已知的_LOADER_PARAMETER_BLOCK,它的定義在內核符號中是公開可用的,所以前面的代碼看起來像這樣,帶有符號資訊:
PAGELK:00000001405A2052 mov rdx, [rsi _LOADER_PARAMETER_BLOCK.Extension] ; _LOADER_PARAMETER_EXTENSION*
PAGELK:00000001405A2059 mov eax, [rdx _LOADER_PARAMETER_EXTENSION._bf_74] ; bit field
; ...
PAGELK:00000001405A206A loc_1405A206A:
PAGELK:00000001405A206A bt eax, 8
PAGELK:00000001405A206E jnb short loc_1405A2077
PAGELK:00000001405A2070 mov cs:KiSystemCallSelector, r13d ; r13d = 1
At offset 0x74 of the _LOADER_PARAMETER_EXTENSION structure we have a bitfield:
struct // 22 elements, 0x4 bytes (sizeof)
{
/*0x074*/ ULONG32 LastBootSucceeded : 1; // 0 BitPosition
/*0x074*/ ULONG32 LastBootShutdown : 1; // 1 BitPosition
/*0x074*/ ULONG32 IoPortAccessSupported : 1; // 2 BitPosition
/*0x074*/ ULONG32 BootDebuggerActive : 1; // 3 BitPosition
/*0x074*/ ULONG32 StrongCodeGuarantees : 1; // 4 BitPosition
/*0x074*/ ULONG32 HardStrongCodeGuarantees : 1; // 5 BitPosition
/*0x074*/ ULONG32 SidSharingDisabled : 1; // 6 BitPosition
/*0x074*/ ULONG32 TpmInitialized : 1; // 7 BitPosition
/*0x074*/ ULONG32 VsmConfigured : 1; // 8 BitPosition
/*0x074*/ ULONG32 IumEnabled : 1; // 9 BitPosition
/*0x074*/ ULONG32 IsSmbboot : 1; // 10 BitPosition
/*0x074*/ ULONG32 BootLogEnabled : 1; // 11 BitPosition
/*0x074*/ ULONG32 DriverVerifierEnabled : 1; // 12 BitPosition
/*0x074*/ ULONG32 SuppressMonitorX : 1; // 13 BitPosition
/*0x074*/ ULONG32 SuppressSmap : 1; // 14 BitPosition
/*0x074*/ ULONG32 Unused : 6; // 15 BitPosition
/*0x074*/ ULONG32 FeatureSimulations : 6; // 21 BitPosition
/*0x074*/ ULONG32 MicrocodeSelfHosting : 1; // 27 BitPosition
/*0x074*/ ULONG32 XhciLegacyHandoffSkip : 1; // 28 BitPosition
/*0x074*/ ULONG32 DisableInsiderOptInHVCI : 1; // 29 BitPosition
/*0x074*/ ULONG32 MicrocodeMinVerSupported : 1; // 30 BitPosition
/*0x074*/ ULONG32 GpuIommuEnabled : 1; // 31 BitPosition
};
The bt eax, 8 instruction is testing for bit 8, thus the VsmConfigured bit.
Thus if we are virtualized and VsmConfigured is 1, then we use INT 0x2E.
Why?
VSM stands for Virtual Secure Mode which introduces VTLs (Virtual Trust Level) which are used to segregate parts of the OS itself: for example VTL0 is the so-called "normal world" where the "usual" part of the OS resides (including the kernel and its virtual space) while VTL1 harbors the secure kernel and very specific processes known as "truslets" (see IUM for a longer explanation).
At that point I can only guess; my first thought is that calling INT 0x2E is only applied to a specific kernel (not the "normal" kernel in VTL0, but which one, I still don't know).
It is actually easier for a VMM (the hypervisor) to catch VM-exits for interruption than syscalls; A VM-exit occurs when some events (e.g. specific instructions like INT, RDMSR, WMSR) happen that make the code transition from its normal execution flow back into the hypervisor, so the hypervisor can actually look at what triggered the VM-exit and act accordingly (e.g. redirecting the code flow or "lying" to the OS).
寫完這個答案后,我看到有人在一篇更詳盡解釋的博客文章中發現了同樣的路徑: Windows 10 TH2 INT 2E 之謎。不過,他們不確定內核將在哪種情況下使用 INT2E。我們只能猜測到這一點。
uj5u.com熱心網友回復:
Windows 上的 64 位應用程式可以執行 INT 2E 而不是系統呼叫嗎?
這取決于您究竟想知道什么:
如果您要問您的應用程式是否可以安全地呼叫
int 2e,答案是:不!根據在互聯網上發現了一個表,
EAX=0Fh是ZwClose在Windows 10,但ZwOpenKey在Windows 7中。正如您在表中所見,某些值(例如
EAX=06Dh)的含義甚至在 Windows 10 更新期間發生了變化!如果直接使用
INT 2E,有可能你的應用程式現在可以正常運行,但下次更新后就無法運行了!這同樣適用
syscall- 因此您的應用程式不能在您自己的應用程式中使用它們。(Microsoft DLL 只能使用
INT 2E或syscall在內核更新時替換 DLL。)如果您想知道如果在 64 位應用程式中使用
INT 2E而不是使用會發生什么syscall:我只能推測 - 特別是因為 Microsoft 也可能會
INT 2E在下次更新后更改行為(如果從 64 位應用程式使用會發生什么情況)。但是,行為可能類似于 Linux:
在 Linux 中,
INT 80將所有地址(指標)解釋為 32 位值;syscall將它們解釋為 64 位值。出于這個原因,INT 80如果沒有指標(地址)傳遞給內核(在 Windows 下,ZwClose將是一個示例),則可以從 64 位應用程式使用。然而,將指標傳遞給內核是不可能的(例如ZwOpenFile)。
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/364067.html
