启动脚本
#!/bin/sh
qemu-system-x86_64 -enable-kvm -cpu kvm64,+smep -kernel ./bzImage -append "console=ttyS0 root=/dev/ram rw oops=panic panic=1 quiet kaslr" -initrd ./rootfs.cpio -nographic -m 2G -smp cores=2,threads=2,sockets=1 -monitor /dev/null -nographic
开了smep 开了kaslr
看到可以所线程
那就考虑条件竞争了
init脚本
#!/bin/sh
mount -nvt tmpfs none /dev
mknod -m 622 /dev/console c 5 1
mknod -m 666 /dev/null c 1 3
mknod -m 666 /dev/zero c 1 5
mknod -m 666 /dev/ptmx c 5 2
mknod -m 666 /dev/tty c 5 0
mknod -m 0660 /dev/ttyS0 c 4 64
mknod -m 444 /dev/random c 1 8
mknod -m 444 /dev/urandom c 1 9
chown root:tty /dev/console
chown root:tty /dev/ptmx
chown root:tty /dev/tty
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts
mount -t proc proc /proc
mount -t sysfs sysfs /sys
cat /root/signature
echo 0 > /proc/sys/kernel/kptr_restrict
echo 0 > /proc/sys/kernel/dmesg_restrict
insmod /list.ko
mknod /dev/klist c 137 1
chmod a+rw /dev/klist
cat /proc/kallsyms |grep commit_cred
lsmod
#cat /sys/module/list/sections/.text
setsid cttyhack setuidgid 1000 sh
umount /proc
umount /sys
halt -d 1 -n -f
init脚本看得到
驱动是list.ko
而且改了/dev/ptmx权限,那么我们就无法劫持tty结构体,无法覆盖ptmx。
分析一下整个程序
ioctl看得出来四个功能
0x1337
0x1338
0x1339
0x133A
0x1337是add
参数是一个地址,这地址放着两个值
第一个是size 第二个是指针。
然后kmalloc申请一个(size + 24)大小的object
object里面的结构长这样
00000000 item struc ; (sizeof=0x20, mappedto_5)
00000000 refcount dd ?
00000004 field_4 dd ?
00000008 size dq ?
00000010 next dq ? ; offset
00000018 data dq ?
00000020 item ends
第一个值记录这个item被引用过多少次
然后直接设为1
size是传入的size
data是从传进来的那个指针那里拷贝进来的数据。
如果一个字节没拷贝进来就说明size是0
就没用 free掉object就行
拷贝成功就放到glist中
然后next指针是记录之前glist直接管理那个object
就是形成了一个链表。
select_item
传入一个参数idx
然后在glist的链表中寻找
如果idx太大等问题就退出
找到之后调用了一下这里的get
这是加一的原子操作
就是把object中的记录item被引用多少次那个指针加一。
puts
原子减一操作
如果减完是0就free掉
他整个的逻辑就是找一个放在(fd+200)
对所选的gets
之前所选的也就是(fd+200)的puts
remove_item
逻辑简单
就通过idx在链表中找到
拿走
然后电泳puts
原子减一
如果为0free掉。
list_head
把链表头拿出来,里面的数据拷贝给用户。
list_open
申请了一个object放在了fd_200
list_read
从fd+200那个object那里来读最多size个字节
list_write
往进写最多size字节
list_release
原子操作减一
漏洞在list_head
list_head原本的逻辑是先get 然后链表头取出来把数据拷贝出来,然后puts
但是上的锁在get之后puts之前就解锁了
如果这里有另一个进程进来通过add_item来改变了glist
add之后count是1 新的item puts之后减一会直接free
那么这里就造成了一个uaf
所以就是double fetch 造成的一个uaf。
利用方法
1、uaf我们之前遇到过一个 2017 babydriver
我们直接通过uaf分配到一个cred 然后改权限
但是问题来了
我们的write只能write从24个字节开始
刚好把提权那部分避过去了
2、我们又想到可以打开/dev/ptmx结构体覆盖ptmx结构体或者tty结构体
但是init脚本中对ptmx文件更改了权限
普通用户打不开了 也行不通
我们之前学习的几种利用方式
因为ptmx相关的几种不能用了
剩下几种也都是需要任意地址写来完成
那依然如此我们的思路就转化成我们现在已有的这个uaf如何能转化成任意地址写?
这里学习大佬思路学到了能用管道。
在创建pipe管道的时候会申请一个这样的结构
struct pipe_buffer {
struct page *page;
unsigned int offset, len;
const struct pipe_buf_operations *ops;
unsigned int flags;
unsigned long private;
};
其中page是pipe存放数据的缓冲区
offset和len是数据的偏移和长度
offset len可以根据我们在管道中传输的内容来变化
那么如果我们一开始申请一个跟这个结构一样大的堆
然后条件竞争释放掉
申请管道申请到
我们就可以通过控制管道来控制结构体里面的内容
我们控制offset len的时候刚好可以控制结构体的size位
直接改的很大
就又是任意读写。
网上找了找exp
感觉还挺多
都拿过来学习学习