基于uffd的任意地址写
基于list_del的任意地址写
函数调用链
do_msgrcv()->[find_msg()]->list_del()->__list_del_entry()->__list_del()
源码分析
#define POISON_POINTER_DELTA 0
#define LIST_POISON1 ((void *) 0x100 + POISON_POINTER_DELTA)
#define LIST_POISON2 ((void *) 0x122 + POISON_POINTER_DELTA)
static inline void list_del(struct list_head *entry)
{
__list_del_entry(entry);
entry->next = LIST_POISON1;
entry->prev = LIST_POISON2;
}
static inline void __list_del_entry(struct list_head *entry)
{
if (!__list_del_entry_valid(entry))
return;
__list_del(entry->prev, entry->next);
}
static inline bool __list_del_entry_valid(struct list_head *entry)
{
return true;
}
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
next->prev = prev;
WRITE_ONCE(prev->next, next);
}
和glibc不同的是,Linux Kernel的内核双链表的检查并不严格
在正常情况下,只需要保证next、prev指针所指向内存地址可写即可
仅有在开启CONFIG_DEBUG_LIST时,__list_del_entry会被替换
bool __list_del_entry_valid(struct list_head *entry)
{
struct list_head *prev, *next;
prev = entry->prev;
next = entry->next;
if (CHECK_DATA_CORRUPTION(next == LIST_POISON1,
"list_del corruption, %px->next is LIST_POISON1 (%px)\n",
entry, LIST_POISON1) ||
CHECK_DATA_CORRUPTION(prev == LIST_POISON2,
"list_del corruption, %px->prev is LIST_POISON2 (%px)\n",
entry, LIST_POISON2) ||
CHECK_DATA_CORRUPTION(prev->next != entry,
"list_del corruption. prev->next should be %px, but was %px. (prev=%px)\n",
entry, prev->next, prev) ||
CHECK_DATA_CORRUPTION(next->prev != entry,
"list_del corruption. next->prev should be %px, but was %px. (next=%px)\n",
entry, next->prev, next))
return false;
return true;
}
此时便有了严格的双链表的完整性检查,难以进行下一步利用
但是默认情况下CONFIG_DEBUG_LIST是未设置的,因此该利用方法仍然拥有存在意义
利用方法
和musl-libc中的双链表unsafe_unlink相同,都是任意地址写一个任意地址
然而,目前而言,Linux Kernel全局变量段上并不存在一个可劫持的结构体指针
能够通过劫持后方便的控制内核程序流
因此该方法目前仅能在泄漏堆地址后对堆上的函数虚表等进行更改来劫持RIP
注意事项
- 设置MSG_COPY标志可以拷贝msg后,保留msg而不会进行unlink以及free_msg()
- 如果Linux Kernel全局变量段存在一个可劫持的函数虚表,该方法将几乎可以使用任意大小的heap_overlap