剖析脏牛4_madvise()与漏洞成因

VSole2021-11-04 17:03:21

测试程序

int fd;struct stat st;void *mem;
void processMem(void){    int f = open("/proc/self/mem", O_RDWR);    lseek(f, mem, SEEK_SET);    write(f, "BBB", 3);
    printf("%s", (char*)mem);
    madvise(mem ,100 ,MADV_DONTNEED);}
int main(void){    fd = open("./test", O_RDONLY);    fstat(fd, &st);    mem = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    processMem();}

sys_madvise()

sys_madvise()首先进行简单的参数处理

如果需要的话获取mmap_sem信号量, 然后遍历[start, start+len)内所有的VMA, 对于每个VMA调用madvise_vma()进行处理. 这里我们只关注behavior = MADV_DONTNEED

madvise_vma()

madvice_vma()根据behavior把请求分配到对应处理函数, 对于MADV_DONTNEED会调用madvise_dontneed()处理

madvise_dontneed()

排除掉一些无法丢弃的情况后, 会调用zap_page_range()处理

zap_page_range()

zap_page_range()会遍历给定范围内所有的VMA, 对每一个VMA调用unmap_single_vma(…)

后续会沿着unmap_single_vma() => unmap_page_range() => zap_pud_range() => zap_pmd_range() => zap_pte_range()的路径遍历各级页表项, 最后调用zap_pte_range()遍历每一个PTE

zap_pte_range()

zap_pte_range()会释放范围内所有的页, 函数头如下

然后遍历范围内所有页, 清空页表中对应的PTE, 并减少对应页的引用计数, 当页的引用计数为0时会被内核回收

dirty_COW漏洞

回想一下利用/proc/self/mem写入进程只读内存区的过程: access_remote_vm()会先调用get_user_pages()锁定要写入的页, get_user_pages()会通过while( !follow_page_mask(foll_flag) ){ faultin_page(foll_flag); } 这个循环分配满足foll_flag要求的页

 __get_user_pages()第一次循环

faultin_page()判断属于写入只读区域的情况, 因此会调用do_cow_fault()

do_cow_fault()会复制原始的文件缓存页到一个新页中, 并设置PTE映射到这个新页, 但由于VMA不可写入, 因此这个新页的PTE页没有设置RW标志

__get_user_pages()第二次循环

由于foll_flags中有FOLL_WRITE标志, 但是页对应的PTE没有RW标志, 因此follow_page_mask()判断权限有问题, 再次进入faultin_page()

faultin_page()判断, 属于写入只读的已存在的页造成的问题, 因此会调用do_wp_page()处理

do_wp_page()发现对应页是只有一个引用的匿名页,因此会调用wp_page_reuse()直接重用这个页

wp_page_reuse()由于对应VMA只读, 因此只会给PTE设置一个Dirty标志, 而不会设置RW标志, 然后返回一个VM_FAULT_WRITE表示内核可以写入这个页

返回到faultin_page()中, 由于handle_mm_fault()返回了VM_FAULT_WRITE, 因此会去掉FOLL_WRITE标志, 含义为: 虽然此页对应PTE不可写入, 但是已经COW过了, 内核是可以写入的, 后续follow_page_mask()就不要检查能不能写入了

如果说在清除FOLL_WRITE标志之后, 第三次调用follow_page_mask()之前, 我们通过madivse()设置此页对应PTE为空会发生什么?

首先follow_page_mask()会因为对应PTE为NULL而再次失败, 进入faultin_page(), 但是注意, 这次进入的时候没有FOLL_WRITE标志

faultin_page()因此设置fault_flags时是没有FAULT_FALG_WRITE标志的, 也就是说faultin_page()对handle_mm_fault()承诺不会写入这个页

handle_mm_fault()由于pte为none, 并且不要求写入, 因此最终会分派给do_read_fault()处理

do_read_fault()会查找这片VMA映射的地址空间中, address对应的原始缓存页, 然后返回这个原始缓存页

如果是用户映射一片只读内存页到文件, 返回原始缓存页是没有问题的, 因为用户无权对其进行写入. 但是在这里access_remote_vm()后续会调用copy_to_user_page() 写入__get_user_pages()锁定的页, 由此污染了文件的原始缓存页.

一段时间后当进行磁盘同步时(sync, kflushd….)内核会把被污染的页面回写到磁盘中, 从而写入特权文件完成攻击

那么下一个问题这个条件竞争的时间窗口有多大? 由于faultin_page()返回之后会调用cond_resched()切换到别的任务, 因此时间窗口是非常大的

受攻击时对/proc/self/mem进行写入时的执行流程:

EXP伪代码

Main:    fd = open(filename, O_RDONLY)        //打开一个文件    fstat(fd, &st)    map = mmap(NULL, st.st_size , PROT_READ, MAP_PRIVATE, fd, 0)    //把文件映射到map指向的内存区域    start Thread1    start Thread2
Thread1:    f = open("/proc/self/mem", O_RDWR)    //打开mem文件    while (1):        lseek(f, map, SEEK_SET)    //偏移到map映射的区域        write(f, shellcode, strlen(shellcode))    //写入
Thread2:    while (1):        madvise(map, 100, MADV_DONTNEED)    //取消映射

反思

对于进程中的只读内存区域, 如果通过地址进行写入会得到一个段错误, 但是通过mem文件进行写入, 就会得到一个dirty的COW的只读页, 为什么会有这样的差异?

对于段错误, 这个很好理解, 但是通过mem文件写入一个进程的只读内存区, 破坏了进程的地址空间. 那么为什么内核还要引入这种外部访问机制呢? 这是为了方便调试器和一些跟踪程序而加入的设计

这个漏洞的修复也很简单, COW之后不去掉FOLL_WRITE标志, 而引入一个新的标志FOLL_COW.

  • pte设置了RW标志, 表示页可写入
  • flags设置了FOLL_COW标志, 表示这是一个COW之后的页面, 虽然PTE说不可写入, 但是内核实际可以写入
  • 这样就算进入follow_page_mask()前这个PTE被设为nonoe ,但foll_flags保留了FOLL_WRITE标志, 仍然会要求faultin_page分配一个要写入的页.
  • 在follow_page_mask()检查的时候如果foll_flags中设置了FOLL_WRITE要求写入, 那么下面两种情况都会被判断为页可写入
  • 修复的diff如下
diff --git a/include/linux/mm.h b/include/linux/mm.hindex e9caec6..ed85879 100644--- a/include/linux/mm.h+++ b/include/linux/mm.h@@ -2232,6 +2232,7 @@ static inline struct page *follow_page(struct vm_area_struct *vma, #define FOLL_TRIED    0x800    /* a retry, previous pass started an IO */ #define FOLL_MLOCK    0x1000    /* lock present pages */ #define FOLL_REMOTE    0x2000    /* we are working on non-current tsk/mm */+#define FOLL_COW    0x4000    /* internal GUP flag */
 typedef int (*pte_fn_t)(pte_t *pte, pgtable_t token, unsigned long addr,             void *data);diff --git a/mm/gup.c b/mm/gup.cindex 96b2b2f..22cc22e 100644--- a/mm/gup.c+++ b/mm/gup.c@@ -60,6 +60,16 @@ static int follow_pfn_pte(struct vm_area_struct *vma, unsigned long address,     return -EEXIST; }
+/*+ * FOLL_FORCE can write to even unwritable pte's, but only+ * after we've gone through a COW cycle and they are dirty.+ */+static inline bool can_follow_write_pte(pte_t pte, unsigned int flags)+{+    return pte_write(pte) ||+        ((flags & FOLL_FORCE) && (flags & FOLL_COW) && pte_dirty(pte));+}+ static struct page *follow_page_pte(struct vm_area_struct *vma,         unsigned long address, pmd_t *pmd, unsigned int flags) {@@ -95,7 +105,7 @@ retry:     }     if ((flags & FOLL_NUMA) && pte_protnone(pte))         goto no_page;-    if ((flags & FOLL_WRITE) && !pte_write(pte)) {+    if ((flags & FOLL_WRITE) && !can_follow_write_pte(pte, flags)) {         pte_unmap_unlock(ptep, ptl);         return NULL;     }@@ -412,7 +422,7 @@ static int faultin_page(struct task_struct *tsk, struct vm_area_struct *vma,      * reCOWed by userspace write).      */     if ((ret & VM_FAULT_WRITE) && !(vma->vm_flags & VM_WRITE))-        *flags &= ~FOLL_WRITE;+            *flags |= FOLL_COW;     return 0; }
pteflags
本作品采用《CC 协议》,转载必须注明作者和本文链接
希望能通过这篇文章让大家对最近qemu逃逸题目学习有一点帮助。
NX实现机制浅析
2021-10-12 16:53:04
是否开启NX取决于参数-z设置,而gcc仅仅是将-z keyword传递给linker——ld,并不会真正解析该参数:
【经典回顾系列】 Windows SMB Ghost CVE-2020-0796漏洞分析与利用(三)
(由于PTE控制着4KB物理页的属性,因此目标代码所属的整个物理页都被设置为不可执行。若是,则修改PTE的执行属性并进行事件注入至 #DB,内核异常处理函数将会将该异常派发给调试器。若不是,则仍需要修复PTE的可执行属性,置位rflags.TF以便于下条指令触发 #DB 异常被vmm接管,修复cr2并进行事件注入 #PF。
上⼀篇⽂章中已经简单介绍过了CET的基本原理和实际应⽤的⼀些技术,站在防守⽅的视⻆下,CET确实是⼀个能 ⽐较有效防御ROP攻击技术的措施。那么在攻击者的视⻆来看,研究清楚CET的技术细节,进⽽判断CET是否是⼀ 个完美的防御⽅案,还是存在⼀定的局限性,则是攻击⽅的重中之重。 本⽂由浅⼊深地讲述CET的实现细节,最后提出⼏个理论可⾏的绕过⽅案,供研究者参考。
00 前言在 HWS2021 入营选拔比赛的时候,遇到了一道 QEMU 逃逸的题目,那个时候就直接莽上去分析了一通,东拼西凑的把 EXP 写了出来。但是 QEMU 逃逸这部分的内容实在是比较复杂,而且涉及到了很多我完全没有了解过的知识,所以一直鸽到了现在。System mode:系统模式,在这种模式下,QEMU 可以模拟出一个完整的计算机系统。
美国商务部工业安全局将四家公司列入出口控制的实体名单:以色列公司 NSO Group 和 Candiru,俄罗斯公司 Positive Technologies 和新加坡公司 Computer Security Initiative Consultancy PTE. LTD.。
美国制裁了四家开发监视恶意软件或销售民族国家行为者(包括 NSO Group)使用的黑客工具的公司。
本月初曝光的谷歌Chrome zero day漏洞已被积极利用,但现已修复。“我们认为这些袭击具有高度的针对性”。在黎巴嫩发现的感染序列始于攻击者破坏一家新闻机构员工使用的网站,该网站用于从演员控制的域注入恶意JavaScript代码,该域负责将潜在受害者重定向到攻击服务器。Avast评估了收集的信息,以确保漏洞仅被交付给预期目标。
OOB技术通常需要易受攻击的实体生成出站TCP/UDP/ICMP请求,然后允许攻击者泄露数据。OOB攻击的成功基于出口防火墙规则,即是否允许来自易受攻击的系统和外围防火墙的出站请求。而从域名服务器中提取数据,则被认为是最隐蔽有效的方法。
VSole
网络安全专家