当然fs指向的地址未必一定要被映射,但fs指向的地址 + rsp 所指向的地址一定是被映射的地址,因为这就是影子栈的真实内存区域。
另外,在x64位下,cs,ds,ss,es都是平坦模式,也就是基地址都是从0地址开始,gs,fs特殊一下。windows 在x64下使用gs这个段寄存器代
替原来的fs段寄存器的功能,例如,gs:[30]指向TEB,不再是x86下面的fs:[30]指向TEB,对gs的操作一般通过swapgs指令把
IA32_KERNEL_GS_BASE值装填到GS.base当中,从而得到内核的数据结构完成相应的服务例程,例如下面的操作。
.text:0000000140172A80 KiSystemService proc near
..
.text:0000000140172A80
.text:0000000140172A80 cmp [rsp+arg_0], 23h
.text:0000000140172A86 jz KiSystemService32User
.text:0000000140172A8C swapgs <******<
.text:0000000140172A8F mov rcx, r10
.text:0000000140172A92 sub rsp, 8
...
.text:0000000140172ABB lea r11, KiSystemServiceUser
.text:0000000140172AC2 jmp r11
.text:0000000140172AC2 KiSystemService endp
但fs并没有对应的swap fs指令,在此之前,fs段寄存器一直是被保留的。在设计RFG功能后被重新启用了,使其指向“影子栈”地址。
在Insider Preview14986版本的下,windows10只针对少量应用程序开启了RFG功能,例如针对svchost.exe,但其中Edge是
不支持RFG功能的。虽然可以在函数开头看到这样的指令,但这时fs指向的线性地址是0。
mov qword ptr fs:[rsp],rax fs:00000073`11dfb588=00007ffd5bb757b5
也就是说此时,fs指向值是0,那么“影子栈”同真实栈是重合的,而在Insider Preview 15002版本后Edge开启了RFG保护,fs指向了真实的“影子栈”地址。
总之,攻击者不能在用户态通过控制fs段寄存器来伪造“影子栈”。当然构造出写操作mov fs:[...],eax指令并且也能获得控制权
执行的情况除外(在Edge浏览器中,这样做的前提是你需要绕过CFG),我们强调的是fs指向的地址是不能在用户态被操纵的。
[0x02.2] 内核层面的分析
首先fs = 53的选择子的赋值来自于内核方面,我们可以在KiSystemStartup函数内部看到
PAGELK:00000001403B10C1 assume ds:nothing
PAGELK:00000001403B10C1 mov es, ax
PAGELK:00000001403B10C4 assume es:nothing
PAGELK:00000001403B10C4 mov ax, 53h
PAGELK:00000001403B10C8 mov fs, ax
PAGELK:00000001403B10CB assume fs:nothing
PAGELK:00000001403B10CB test cs:VslVsmEnabled, 0FFh
PAGELK:00000001403B10D2 jnz short loc_1403B10D9
1: kd> r gdtr
gdtr=ffffd68137fd0fb0
我们关心的是fs指向的具体地址,在x86下,我们可以通过选择子53,和gdtr的地址推算获得fs的具体的地址值。但windows x64 long模式
下fs,gs 并不是通过GDT来获得的,而是通过读/写模式寄存器MSR寄存器来读取和设置的。
我们可以看到在内核KiSwapThreadControlStack中看到,
KiSwapThreadControlStack proc near
...
.text:00000001401726FE test r8, r8
.text:0000000140172701 jz loc_14017285B
.text:0000000140172707 mov rcx, rsi
.text:000000014017270A mov rdx, [rbp+0E8h+var_128]
.text:000000014017270E call KiSwapThreadControlStackDispatch
.text:0000000140172713 test al, al
.text:0000000140172715 jz loc_14017285B
.text:000000014017271B mov eax, [rsi+7A0h]
.text:0000000140172721 mov edx, [rsi+7A4h]
.text:0000000140172727 mov ecx, 0C0000100h <<********<<
.text:000000014017272C wrmsr <<********<<
.text:000000014017272E cli
.text:000000014017272F test [rbp+0E8h+arg_0], 1
.text:0000000140172736 jz loc_140172810
...
ecx = 0xc0000100 对应的是fs段寄存器,gs对应的是0xc0000101。由上面的代码我们可推测出
KiSwapThreadControlStackDispatch负责获得线程的“影子栈”区域的地址,其地址值存储在eax,edx中是一个64bit地址,
然后通过wrmsr来写入fs,此时fs指向的地址就是由edx,eax共同决定的一个64位地址值了。
此时的rsi实际上指向了当前要“切换”线程的ethread首地址,所以0x7A0对应一个叫做UserFsBase的64位地址值。
0: kd> dt _ethread fffff801c0a2da40
ntdll!_ETHREAD
+0x000 Tcb : _KTHREAD
+0x5e8 CreateTime : _LARGE_INTEGER 0x0
...
+0x798 PicoContext : (null)
+0x7a0 UserFsBase : 0
+0x7a8 UserGsBase : 0
...
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。