题目分析
题目实现的功能很容易看出来
1 | __int64 __fastcall list_ioctl(__int64 a1, unsigned int a2, __int64 a3) |
主要实现了对堆块的申请,释放,读写
1 | __int64 __fastcall add_item(__int64 a1) |
在这里可以分析出申请堆块的参数:
1 | struct create_chunk_arg |
以及堆块的结构:
1 | struct chunk{ |
1 | __int64 __fastcall remove_item(__int64 a1) |
在删除函数可以看出来这里并没有直接kfree来进行删除,而是调用了put函数:
1 | __int64 __fastcall put(volatile signed __int32 *a1) |
而这个put函数就是对chunk的inuse位进行减一的操作,如果为0则进行kfree,结合上面的remove函数其中的脱链操作也是没有问题的,不存在UAF
1 | __int64 __fastcall select_item(__int64 a1, __int64 a2) |
再看select函数,这一函数实现的功能是选取一个chunk放到(fd+0xc8)位置。
1 | unsigned __int64 __fastcall list_head(__int64 a1) |
list_head函数则是取出chunk内容,这里的size是在create时放在堆块中的size。
漏洞分析
题目中题看上去是没有任何问题的,但是在启动脚本中我们可以看到:
1 | #!/bin/sh |
启动了两个核心,虽然程序使用了互斥锁但任存在条件竞争漏洞
在create的入链操作中:
1 | mutex_lock(&list_lock); |
可以看到这里是首先上锁,然后进行操作
1 | mutex_lock(&list_lock); |
然而在list_head函数中是在获取了链中的第一个chunk就会释放锁,并且最后会进行put函数,如果我们能够在释放锁之后,put函数之前让create新建的chunk入链则会让新入链的chunk进入put函数,然而新chunk的inuse位为1,所以就会直接free掉,那么此时就存在了UAF的chunk了。
漏洞利用
这里就接着看read和write函数
1 | __int64 __fastcall list_read(__int64 a1, __int64 a2, unsigned __int64 a3) |
这里可以看到read函数操作的chunk就是我们在select函数中放到(fd+0xc8)位置的chunk,并且只要我们传入的第三个参数不大于chunk中记录size的位置就可以进行读取
1 | __int64 __fastcall list_write(__int64 a1, __int64 a2, unsigned __int64 a3) |
write函数类似于上面的read函数。
那么我们的思路就是覆盖上面chunk存放size的位置即可了,这样我们就可以实现任意地址写了。那么我们就需要用到堆喷的技术,内核的堆喷我的理解就是申请大量的chunk,那么大概率会一个chunk落在期望的位置上,而这道题目我们期望的位置也就是存在UAF的堆块的位置。这道题因为在init中的限制,这里选择的msgsnd进行堆喷,下面是进行堆喷的使用模板:
1 |
|
在一次msgsnd的过程中会申请一个size为96的chunk,其中前面的48字节为不可控的内容
1 | struct msg_msg { |
好在msg_msg结构体的前16个字节为两个指针,并且后八位正好落在chunk的size位上,那么如果有一个msg_msg结构体落在了我们的UAF的chunk上我们就可以进行任意地址读写了。接着的思路就是提权,在前两篇的kernel文章中提到了三种提权方式,相比较下使用修改cred结构体的提权方式更为简单,不熟悉的朋友可以去看一下 kernel pwn 任意地址读写提升权限[1] 不过这道题目即便是泄露了地址也无法计算当前chunk的地址与cred结构体的地址的偏移所以没法直接使用以前的方法 这里更好的办法是直接根据uid去寻找cred结构体,因为在上面那片文章cred结构体是通过kmem_cache_alloc创建的。
综上,exp
1 |
|
这里就是成功的结果图:
题目我会放在:https://github.com/196082/196082
参考链接: