强网杯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 |
|