mmap的随机化

Posted by rk700 on November 22, 2016

pwnable.kr上的题目tiny,我一直没有能够做出来,所以就决定先看看tiny_easy。tiny_easy与tiny很相似,只是栈变成了可执行的,所以可以将shellcode放在栈上。但是,由于栈的地址被随机化了,我们仍然需通过暴力尝试的方法来克服ASLR。

直到最近,我在网上搜索了解到mmap地址随机化的一些坑,才明白这两道题目可以通过某些手段来克服ASLR,从而避免暴力尝试。

正常情况下mmap的随机化

mmap的方法声明如下:

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

当第一个参数是0时,会由系统选择一个地址,在这里可以产生随机化。我们编写以下测试代码:

#include <sys/mman.h>

#include <stdio.h>

int main(int argc, char *argv[]) {
    char mapsPath[256];
    void *addr = mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
    printf("mmap at %p\n", addr);
    return 0;
}

在32位系统和64位系统上编译后运行,发现每次mmap得到的地址均是不同的,而这正是ASLR所起到的保护作用。

CVE-2016-3672

这个漏洞指出,mmap在某些情况下不会随机化。具体地,当通过ulimit -s unlimited将栈的大小设置为无限制后,32位程序(在32位系统和64位系统上)的mmap不会随机化。再次使用上面的测试代码可以验证这一结论。

这一漏洞的起因,是为了支持旧的32位架构。在那些比较旧的机器上,ASLR只会将栈随机化,而mmap得到的(如共享库等)地址是固定的。

具体地,阅读内核源码arch/x86/mm/mmap.c,可以找到以下方法:

/*
 * This function, called very early during the creation of a new
 * process VM image, sets up which VM layout function to use:
 */
void arch_pick_mmap_layout(struct mm_struct *mm)
{
    mm->mmap_legacy_base = mmap_legacy_base();
    mm->mmap_base = mmap_base();

    if (mmap_is_legacy()) {
        mm->mmap_base = mm->mmap_legacy_base;
        mm->get_unmapped_area = arch_get_unmapped_area;
        mm->unmap_area = arch_unmap_area;
    } else {
        mm->get_unmapped_area = arch_get_unmapped_area_topdown;
        mm->unmap_area = arch_unmap_area_topdown;
    }   
}

可以看到,如果mmap_is_legacy()返回为真,那么mmap的基址就是旧的mmap_legacy_base。而这个mmap_legacy_base的获取如下:

/*
 * Bottom-up (legacy) layout on X86_32 did not support randomization, X86_64
 * does, but not when emulating X86_32
 */
static unsigned long mmap_legacy_base(void)
{   
    if (mmap_is_ia32())
        return TASK_UNMAPPED_BASE;
    else
        return TASK_UNMAPPED_BASE + mmap_rnd();
}

通过这里的注释也可以看出,如果是32位的程序,那么在32位系统和64位系统上,mmap_legacy_base均是一个固定的值,不会加上随机的偏移。

综上,我们只要设法使得mmap_is_legacy()返回真,那么就可以让mmap不进行随机化。而mmap_is_legacy()的定义如下:

static int mmap_is_legacy(void)
{
    if (current->personality & ADDR_COMPAT_LAYOUT)
        return 1;

    if (rlimit(RLIMIT_STACK) == RLIM_INFINITY)
        return 1;

    return sysctl_legacy_va_layout;
}

其中的第二个判断条件,便是检查栈的大小。我们可以通过ulimit命令来设置这个值。所以,只要将栈设置为unlimited,那么随后运行的程序的mmap都会得到固定的地址。进一步来说,这就会使得共享库、vdso等,均处于固定的位置,从而影响ASLR的保护效果。

根据漏洞发现者的介绍,这一问题已经存在了很久,包括最新的Linux仍然受到影响。而根据Redhat的公告,这个漏洞的影响低,近期内应该也不会修复。不过,对于题目tiny和tiny_easy则是非常有价值的,因为可以通过这一手段将vdso的地址固定,从而稳定地进行ROP。


参考文献