非常精彩的利用文章。
Vulnerability
漏洞的成因是允许用户同时指定NF_DROP
和NF_ACCEPT
值(即NF_DROP(1)
,这里的1
本来应该是错误代码),内核在解析到NF_DROP
后便会释放skb
,但由于最后返回的却是NF_ACCEPT
,skb
会继续留在内核网络栈上,随后又被释放,造成double free
。
该漏洞会同时释放掉两个结构体:struct sk_buff
和sk_buff->head
,而sk_buff->head
存放的是网络数据包的内容,大小是用户完全控制的,大小最大可以是16个页面。
Exploitation
Double Free
在两次free
之间,需要喷大量的PTE
,因此需要留出一定的时间窗口。然而,内核网络站本身时间窗口并不多,作者这里采用了IP
分片技术,以延迟第二次free
的时间。具体来讲:
设置IP_MF
表示还有更多的数据。
内核中的选项/proc/sys/net/ipv4/ipfrag_time
可以延长内核的等待时间。
DirtyPageDirectory
dirtypagetable
会需要一个同样使用page_alloc
分配的对象,例如/dev/dma_heap
(在安卓不需要特权可行,但在一般的linux
系统需要特权),io_uring
,这里也需要实现cross cache
攻击,其又会引入额外的不稳定因素。
而dirtypagedirectory
主要思路是造成PMD
和PTE
的重叠。这样,给用户态分配的一个物理页实际上也作为PTE
,从而允许用户任意修改虚拟内存映射,也就是可以任意修改物理页面,只读页面仍然可以修改。
Page Table Spraying
PTE
页表可以通过mmap
一个2M
的虚拟内存,然后访问触发缺页异常来分配。PMD
也是同样的道理,1G
大小的虚拟内存来对应一个PMD
所支持的内容。
这里的index
可以和虚拟地址对应起来。
PCP list Draining
PTE
是由page_alloc
来分配的,阶为0,即一个表项。然而,kmalloc
只有大于两个页面时,即阶>=1
,才会转为使用page allocator
来分配。
作者的方法是先分配一个四阶的页面,再通过喷PTE
消耗掉当前PCP
的freelist
。由于无法知道什么时候freelist
被消耗完,作者是喷了大量的PTE
上去。
(图来自原文)
在喷PTE
的时候,会将4阶的页面中拿掉,分配pages
。从作者给的图里可以清楚地看到这部分逻辑。
Flush TLB
在做内核开发时,一个需要注意的事项就是页表修改后需要使用flushtls
指令进行刷新。
在exp
中,将mmap
的内存指定为共享页,fork
出的子进程执行unmap
操作来刷新TLB
。
Trivia
相对来讲琐碎一点的细节…
Patches
modprobe
作者采用memfd
,可以将内存的内容创建一个fd
与之对应起来,modprobe_path
如果指定到了这个fd
的路径(从/proc/{pid}/fd/{mem_fd}
中获取),就可以执行内存里的提权脚本。这种方法不需要创建文件,即便文件系统只读仍然有效。
Reference