首页
社区
课程
招聘
[X64内核]SMAP,SMEP
发表于: 2020-8-31 18:07 10345

[X64内核]SMAP,SMEP

2020-8-31 18:07
10345

前言

实验环境:win10+VS2015+WDK10

 

SMAP(Supervisor Mode Access Prevention,管理模式访问保护)和SMEP(Supervisor Mode Execution Prevention,管理模式执行保护)的作用分别是禁止内核CPU访问用户空间的数据和执行用户空间的代码,并不会因为你权限高就能访问/执行低权限的资源,你的就是你的,我的就是我的,而之前零环权限就很牛逼了,你的就是我的,我的还是我的

 

如何区分内核态和用户态虚拟空间呢,32位OS用00000000-ffffffff寻址整个4GB虚拟空间,其中0000000-7ffffffff是用户态的虚拟地址空间,80000000-ffffffff是内核态的虚拟地址空间。到了X64,虽然CPU的寻址从32位变成64位,因为虚拟地址的高16位在用户模式下总是被设置为0000,而在内核模式下总是被置为FFFF。所以实际上只支持48位的虚拟地址空间供软件使用。用户态的虚拟地址空间范围为0000 0000 00000000 ~ 0000 ffff ffffffff,内核模式的地址空间范围为ffff 0000 00000000 ~ ffff ffff ffffffff,所以用户态和内核态之间隔了非常远,对操作系统可见的内核虚拟地址空间的大小为256TB。注意我没有提内核页表隔离(KPTI)等安全机制,所以这只是个大概的划分

 

SMEP和SMAP导致我们不能像从前那样,利用恶意进程提权到0环权限后,扭头去执行布置在用户态的恶意shellcode,三环shellcode注入也不好使了(如果把shellcode布置在内核空间,又会遇到PatchGuard的强力阻击)。本文将通过实验一步一步来感知SMEP,SMAP如何起作用,并找出如何在最新win10安全防护体系中绕过他们的具体方法。

1.构造X64下的IDT后门

首先新建一个 Visual C++ 空项目,选择Debug和X64后,还需要配置一下项目的工程属性,先把警告等级调成3

 

 

把增量链接改成否

 

 

把随机基址改成否,把固定基址改成是。

 

 

下一步就是如何在VS2015里写汇编,64位VS编译器因为不再支持内联汇编,所以我们再也不能像32位的那样,首先写一个裸函数,在里面放置我们提权到0环后要执行的汇编代码,然后等着提权后执行了。也不知道微软为什么把如此优秀的机制取消了。

 

那怎么才能把汇编代编译进我们的代码呢,答案是把汇编函数写到一个汇编文件,然后和c文件一起编译。汇编函数在主函数里作为外部符号去使用,我每次都参照这个帖子操作:https://www.cnblogs.com/talenth/p/9135626.html ,如果您使用的是其他版本的VS步骤和方法可能会有所不同。文件如下,下面的汇编文件包含了三个函数IntEntry,go和int3,如下所示。

semap : none

EXTERN x : qword

.data
; ttt qword ?
.code

IntEntry    Proc
    iretq
IntEntry    Endp

go Proc
    int 21h
    ret
go Endp

int3 Proc
    int 3h
    ret
int3 Endp
END

main函数如下所示

#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>

extern "C" void IntEntry();
extern "C" void go();
extern "C" void int3();
extern "C" ULONG64 x;
ULONG64 x;

void main()
{
    if ((ULONG64)IntEntry != 0x0000000140001000)
    {
        printf("wrong IntEntery at %p", IntEntry);
        system("pause");
        exit(-1);
    }
    go();
    printf("%p\n", x);
    system("pause");
}

编译出来后在1903运行。然后我们在操作系统中人为构造一个IDT后门,我构造自定义的中断int21,之所以用int 21h 因为系统没占用21号中断。并把中断处理函数的地址设为0x0000000140001000,由于之前指定了工程属性为“随机基址否,固定基址是”使得IntEntery函数的入口地址始终是0x0000000140001000而不是随机数。

 

 

由于64位中断门已经从8字节变成16字节,1号中断门描述符正在fffff80545a5b010,2号在这基础上+10h变成fffff80545a5b020,以此类推21号在fffff80545a5b210。用Windbg构造21号中断如下:

 

eq fffff8010845b210 4000ee0000101000

 

eq fffff801 0845b218 1

 

我们用!idt指令来查看一下,发现21号的中断处理函数的确已经改成了我们自己的0x0000000140001000函数,即IntEntry的基址。

 

2.触发SMEP

编译并运行这个程序,虚拟机立即崩溃。一个典型的三重错误。

 

 

目前我们的中断处理函数里就只有iretq,如果是在32位的XP系统甚至64位的Win7系统,程序应该会顺利的退出,但在win10-1903下运行结果是三重错误,原因就是win10引入了最新的安全机制:SMAP(Supervisor Mode Access Prevention)和SMEP(Supervisor Mode Execution Prevention)

 

SMEP就是内核权限的CPU不能执行用户权限的代码页,本次实验中,21中断的处理函数地址是0x0000000140001000,显然是位于用户态的代码页,因此提权到内核权限的CPU执行该函数时就会崩溃。

 

可能有人会怀疑崩溃并不是SMEP导致,而是由于修改IDT表触发PG,这里我说明下,这个错误绝对不可能是PG导致的,原因是:1.该错误是运行代码后立即触发的,而PG是延迟触发的,也就是修改的那一刻也许不会立即触发,经过一段时间PG扫描到这段代码被修改就会触发。所以PG蓝屏的时间是不确定的,也许几分钟,几小时都有可能,实际攻击中可以改完后再短时间内恢复现场,以躲避PG的检测,2.这是一个非常严重的三重错误,而PG导致的错误一般仅仅只是蓝屏,错误代码109。附加调试器时可以用!analsys v 来分析崩溃现场的,而三重错误比蓝屏要严重的多,性质完全不同。

 

那么如何绕过SMEP,让提权后的CPU能执行三环代码呢

3.绕过SMEP

 

从上图可知CR4寄存器的20和21分别是SMEP和SMAP标志位,置一代表功能开启,置零就代表功能关闭,定位到这两个位问题就好解决了,

 

我们用r cr4得知cr4是0x370678,再用.formats 0000000000370678把它解析成二进制,结果是: 00000000 00000000 00000000 00000000 00000000 00110111 00000110 01111000。注意20,21不是第20,21位,而是21,22位,是从0开始的。我把这两位标记出来了,这两个位都是1,说明SMEP和SMAP都默认开启

 

只要用Windbg把21位置零就可以了,我们只需手动把CR4从370678改成270678就绕过了SMAP

4.触发SMAP

我们在中断处理函数IntEntry再加入这样两行代码 mov rax, [0fffff8010845dfb0h]mov x, rax,fffff8010845dfb0是我这台机器GDT表第二项的地址。我们试图把一个内核地址的数值通过rax转存到一个位于三环的全局变量x中,

semap : none

EXTERN x : qword

.data
; ttt qword ?
.code

IntEntry    Proc
    mov rax, [0fffff8010845dfb0h]
    mov x, rax
    iretq
IntEntry    Endp

go Proc
    int 21h
    ret
go Endp

int3 Proc
    int 3h
    ret
int3 Endp
END

编译执行后还是会产生一个三重错误,

 

那问题出在哪呢,其实第一句没问题,使用0环权限访问零环内存地址,但第二句就有问题了,全局变量x是一个三环的全局变量,写入x时发生0环访问了三环数据,从而触发SMAP,系统崩溃。我们刚才只绕过了SMEP,现在来处理SMAP

5.绕过SMAP

方法1:借鉴之前绕过SMEP的方法,故伎重演用Windbg把CR4的22位置零,21,22同时置零时的CR4是70678。

 

 

方法2:系统调用发生时,进入0环前需要拷贝3环寄存器到0环栈上,没触发SMAP,然后有时需要传数据给三环时,如果rax写不下时还需要从0环返回三环,需要拷贝0环的数据到三环,也没触发SMAP。那么系统是如何保证0环三环互相拷贝数据而不触发呢?我们需要学习系统是如何绕过的,通过对int3,就也是CC断点的中断处理函数逆向,发现stac指令可以起到关闭SMAP的作用

 

 

这条汇编指令罕见到根本百度不到,可见实际逆向+查看英特尔白皮书是内核研究的有效方法。

6.英特尔白皮书中对SMAP和SMEP的阐述(翻译)

to be done ...


[课程]Android-CTF解题方法汇总!

收藏
免费 7
支持
分享
最新回复 (9)
雪    币: 248
活跃值: (3789)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
慢慢看完了,总结就是SMAP和SMEP分别位于CR4控制寄存器的bit21和bit20
2020-8-31 22:20
3
雪    币: 21449
活跃值: (62288)
能力值: (RANK:125 )
在线值:
发帖
回帖
粉丝
3
赞!
2020-9-2 17:03
0
雪    币: 668
活跃值: (1465)
能力值: ( LV5,RANK:65 )
在线值:
发帖
回帖
粉丝
4
学习了
2020-9-2 20:05
0
雪    币: 3700
活跃值: (3817)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
5

stac,关闭smap;用来设置r3下的对齐。其实可以参考linux内核源码copy_from_user系列函数,里面就用到了stac指令。

最后于 2020-9-4 15:04 被fengyunabc编辑 ,原因: 表达有误、
2020-9-4 12:41
3
雪    币: 248
活跃值: (3789)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
fengyunabc stac,在r0下关闭smap;在r3下用来设置对齐。其实可以参考linux内核源码copy_from_user系列函数,里面就用到了stac指令。
r0关闭smap和r3设置对齐,这两种指令的机器码不一样吧?用ce提示stac指令无效
2020-9-4 14:15
0
雪    币: 3700
活跃值: (3817)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
7
yy虫子yy r0关闭smap和r3设置对齐,这两种指令的机器码不一样吧?用ce提示stac指令无效
回楼上,我表达有误,这个指令只能在r0下使用,有两个作用,一是关闭smap,二是设置r3下的对齐。
2020-9-4 15:05
1
雪    币: 47
活跃值: (31)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
目测 x64地址范围有笔误
2020-10-12 17:29
0
雪    币: 4
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
9
楼主绕过的方法就是在windbg中手动关闭吗
2020-12-30 14:59
0
雪    币: 8715
活跃值: (8619)
能力值: ( LV13,RANK:570 )
在线值:
发帖
回帖
粉丝
10
多文 楼主绕过的方法就是在windbg中手动关闭吗
嗯 不用调试器的话 可以用stac关闭SMAP
2020-12-30 17:04
0
游客
登录 | 注册 方可回帖
返回
//