强网杯2021 notebook
强网杯2021 notebook
前置知识
userfaultfd的使用
借助linux man page ,分析下官方的demo
以下为完整版
1 | |
关于注册uffd系统调用的部分
涉及的变量
1 | |
涉及的结构体
uffdio_api
1 | |
uffdio_register
1 | |
uffdio_range
1 | |
注册uffd,默认的变量赋值,抄linxu man page🤣(不是
1 | |
这里是申请一块地址,也就是uffd monitor 监视的地址
结合上面的结构体,这里地址的起始地址被赋值给了uffdio_register.range.start ,然后长度是uffdio_register.range.len = len
1 | |
创建监视的线程,也就是具体的操作在这个函数fault_handler_thread 实现
1 | |
fault_handler_thread
涉及的变量
1 | |
涉及的结构体
pollfd
1 | |
uffdio_copy
1 | |
uffd_msg
1 | |
handler() 将 msg发送给uffd monitor,uffd msg 也就是的msg的结构体类型
可以观察到有五种类型,分别是pagefault fork remap remove reversed
目前使用到的类型也就是pagefault
这里主要就是一个for循环,不断的轮询,直至发生缺页异常
1 | |
这里有一个计算页起始地址的表达式
1 | |
例如当前的地址为0x12345678,通过计算后即为0x12345000
userfaultfd利用模板
模板介绍
最外层的调用函数Userfaultfd_Exploit
1 | |
下一层调用为Register_Userfaultfd ,显而易见是注册uffd
1 | |
因为利用的时候需要一个可控的地址,所以移除了原本改函数里的mmap操作
下面是线程函数
1 | |
在nread之后进行sleep,可以实现阻塞这个线程的效果,也就是达到了条件竞争的假象
使用方法
首先申请一块内存区域,用于被uffd monitor监视,然后直接调用Userfaultfd_Exploit即可
1 | |
例题解析
题目分析
启动脚本
1 | |
稍微修改了下,出题人给的太阴间了(🔨
保护机制是smep smap kaslr ,然后两核四线程
bss段上的三个全局变量


交互所需要的结构体
1 | |
菜单堆,功能还算全

同时存在读写堆块的功能
读

写

add功能

存在读锁,可以多线程访问
申请堆块的大小要≤ 0x60,然后会将name字段赋值,接着再申请堆块
del功能

写锁,无法多线程访问
正常的free流程,先free,如果size字段存在,那么就将置为0
edit功能

读锁,可以多线程访问
如果size与之前申请的相同,那么不发生任何操作
如果与之前的不同,调用krealloc 更改大小,赋值name
同时需要注意的是没有size检查,也就是说,这里的size可以任意更改
gift

将notebook的内容拷贝到用户态,意味着所有的分配的堆地址都是已知的
add和edit功能存在读锁,可以多线程访问,因此可以尝试通过userfaultfd来构造uaf堆块。
存在堆块读写,也就是说堆块的内容是可以控制的
通过gift可以得到所有分配堆块的地址
漏洞利用
构造uaf堆块
当访问到一块存在缺页异常,且被uffd monitor监视的内存时,会触发到handler,通过sleep操作将handler线程卡住,从而实现条件竞争的效果
edit功能是先进行krealloc ,在copy数据到name上,借助这个copy_from_user ,可以触发uffd
也就说,如果我修改了size为0,那么就可以实现uaf的效果
泄露kernel offset
因为开启了kaslr保护,所以需要泄露偏移量。
这里使用的是tty_struct ,通过泄露PTY_UNIX98_OPS或者PTM_UNIX98_OPS
泄露的时候需要使用到读堆块的功能,需要注意的是,无论读还是写堆块,都存在一个size的检查,所以还需要将uaf堆块的size修改成非0的值
修改的方式也是使用uffd,可以留意到add函数里,先进行size的赋值,接着是copy_from_user,最后kmalloc。借助uffd,可以卡在copy_from_user处,这样也就不会将原本的堆块地址覆盖
修改tty_opeartions 劫持控制流work_for_cpu_fn
work_for_cpu_fn利用方式的成因后补….
上面构造了一个uaf object,所以可以通过打开/dev/ptmx来获取这个object。接着在申请一个堆块,用于伪造tty_ops,然后通过gift泄露这两个堆的地址,通过读写功能修改数据,将tty_struct的tty_operations修改成提前布置好的tty_ops,下面就是布置rop链,最后提权,拿shell
work_for_cpu_fn利用方式简单的说,tty_ops调用函数时,参数一般都是tty_struct的地址,也就是rdi=&tty_struct
这里将tty_ops.ioctl修改成work_for_cpu_fn
将需要调用的函数写于rdi+0x20处
将需要设置的参数写于rdi+0x28处
将调用后的返回结果写于rdi+0x30处
这样可以写成一个简单的rop链,常见的提权手段就是commit_creds(prepare_kernel_cred(NULL))
所以可以分批次写,先执行内层的函数prepare_kernel_cred(NULL) 接着执行外层….
不考虑KPTI绕过的原因:因为此时在内核的页表上执行system,不需要进行切换页表
执行完毕后内核已经提权成功,接着需要开一个root sh,记得将tty_struct的内容还原,不然会panic
利用脚本
可以通过注释掉debug函数中的getchar(),来调试,自用(👴觉得好用就是好用
编译指令gcc exp.c -static -masm=intel -g -o exp -lpthread
1 | |
