首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >[ Windows 10 x64中的RFG(Return Flow Guard)技术研究 ]2

[ Windows 10 x64中的RFG(Return Flow Guard)技术研究 ]2

作者头像
franket
发布2022-06-29 16:45:17
发布2022-06-29 16:45:17
5580
举报
文章被收录于专栏:技术杂记技术杂记
代码语言:javascript
复制
 当然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 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档