Skip to content

3.9 如何关闭ASLR

https://scz.617.cn/unix/201205021022.txt

A: scz

将内核参数randomize_va_space置0,会在系统范围内关闭ASLR:

sysctl -w kernel.randomize_va_space=0

echo 0 > /proc/sys/kernel/randomize_va_space

从GDB 7开始,默认会调用personality( ADDR_NO_RANDOMIZE ),这将关闭被调试进 程的ASLR。

(gdb) show disable-randomization Disabling randomization of debuggee's virtual address space is on. (gdb) shell cat /proc/$(pidof -s smbd)/personality 00040000

如果想更接近真实情形:

(gdb) set disable-randomization off

注意,personality()必须在exec*()之前完成,否则无效。所以Attach的情形就是真 实情形,只有start、run受影响。

A: Mkrtich Soghomonyan 2011-03-25

内核参数randomize_va_space置0会关闭整个系统的ASLR,有时候只想关闭单个进程 的ASLR,可以用setarch命令实现这点。

$ setarch uname -m -R cat /proc/self/maps > 1.txt $ setarch uname -m -R cat /proc/self/maps > 2.txt $ diff 1.txt 2.txt

奇怪的是,setarch关闭单个进程ASLR时,stack的地址有时会变,并不总是固定的, 而内核参数randomize_va_space置0时,就没有观察到这种现象。

我们来研究一下"setarch -R"到底干了什么。

echo 0 > /proc/sys/kernel/randomize_va_space

strace -o 1.txt setarch uname -m -R which col

/usr/bin/col

strace -o 2.txt setarch uname -m which col

/usr/bin/col

diff 1.txt 2.txt

... 80c80 < personality(0x40008 / PER_??? /) = 0


personality(PER_LINUX32) = 0 ...

排除明显无意义的区别,注意到personality( 0x40008 )。"man setarch"时看到-R 参数对应ADDR_NO_RANDOMIZE,在头文件里搜一下:

find /usr/include -name "*.h" -exec grep -Hn ADDR_NO_RANDOMIZE {} \;

/usr/include/sys/personality.h:30: ADDR_NO_RANDOMIZE = 0x0040000, /usr/include/linux/personality.h:11: ADDR_NO_RANDOMIZE = 0x0040000, / disable randomization of VA space / /usr/include/linux/personality.h:29:#define PER_CLEAR_ON_SETID (READ_IMPLIES_EXEC|ADDR_NO_RANDOMIZE)

在"sys/personality.h"中找到两个值:

ADDR_NO_RANDOMIZE = 0x0040000 PER_LINUX32 = 0x0008

0x40008就是"ADDR_NO_RANDOMIZE|PER_LINUX32"。很明显,"setarch -R"主要就是调 用personality( ADDR_NO_RANDOMIZE | PER_LINUX32 )。

一般来说,为了编程关闭单个进程的ASLR,先fork()出子进程,在子进程中调用:

personality( original_persona | ADDR_NO_RANDOMIZE )

然后exec...()。

下面写程序测试personality()的行为:


/ * gcc-3.3 -Wall -pipe -O3 -s -o personality_demo personality_demo.c * gcc-3.3 -Wall -pipe -g -o personality_demo personality_demo.c /

include

include

include

include

include

include

include

include

include

include

int main ( int argc, char * argv[] ) { pid_t pid; int original_persona; int test_persona; int status; struct user_regs_struct regs;

if ( argc > 1 )
{
    test_persona    = 0;
}
else
{
    test_persona    = ADDR_NO_RANDOMIZE;
}
pid = fork();
if ( pid < 0 )
{
    perror( "fork() failed" );
    exit( EXIT_FAILURE );
}
if ( 0 == pid )
{
    /*
     * 子进程
     */
    errno   = 0;
    ptrace( PT_TRACE_ME, 0, 0, 0 );
    if ( errno )
    {
        perror( "child ptrace( PT_TRACE_ME ) failed" );
        exit( EXIT_FAILURE );
    }
    else
    {
        original_persona    = personality( 0xffffffff );
        if ( -1 == original_persona )
        {
            perror( "personality( 0xffffffff ) failed" );
            exit( EXIT_FAILURE );
        }
        if ( -1 == personality( original_persona | test_persona ) )
        {
            perror( "personality( original_persona | test_persona ) failed" );
            exit( EXIT_FAILURE );
        }
        if ( -1 == execl( "/usr/bin/which", "which", "col", NULL ) )
        {
            perror( "execl() failed" );
        }
        /*
         * 保护性退出
         */
        exit( EXIT_FAILURE );
    }
}
else
{
    /*
     * 父进程
     */
    printf( "child       = %u\n", pid );
    wait( &status );
    while ( WIFSTOPPED( status ) )
    {
        #if 0
        /*
         * 这个值一般是0x57F或1407,有些人会直接判断status是否等于这个
         * 值,我们不建议这种山寨搞法。
         */
        printf( "status      = 0x%08X\n", ( unsigned int )status );
        #endif
        if ( -1 == ptrace( PTRACE_GETREGS, pid, 0, &regs ) )
        {
            perror( "child ptrace( PTRACE_GETREGS ) failed" );
        }
        #if 0
        /*
         * /usr/include/sys/user.h
         *
         * struct user_regs_struct
         * {
         *     long int    ebx;
         *     long int    ecx;
         *     long int    edx;
         *     long int    esi;
         *     long int    edi;
         *     long int    ebp;
         *     long int    eax;
         *     long int    xds;
         *     long int    xes;
         *     long int    xfs;
         *     long int    xgs;
         *     long int    orig_eax;
         *     long int    eip;
         *     long int    xcs;
         *     long int    eflags;
         *     long int    esp;
         *     long int    xss;
         * };
         */
        printf
        (
        "eax         = 0x%08X\n"
        "ebx         = 0x%08X\n"
        "ecx         = 0x%08X\n"
        "edx         = 0x%08X\n"
        "esi         = 0x%08X\n"
        "edi         = 0x%08X\n"
        "ebp         = 0x%08X\n"
        "esp         = 0x%08X\n"
        "eip         = 0x%08X\n"
        "eflags      = 0x%08X\n"
        "orig_eax    = 0x%08X\n",
        ( unsigned int )regs.eax,
        ( unsigned int )regs.ebx,
        ( unsigned int )regs.ecx,
        ( unsigned int )regs.edx,
        ( unsigned int )regs.esi,
        ( unsigned int )regs.edi,
        ( unsigned int )regs.ebp,
        ( unsigned int )regs.esp,
        ( unsigned int )regs.eip,
        ( unsigned int )regs.eflags,
        ( unsigned int )regs.orig_eax
        );
        getchar();
        #else
        /*
         * 仅仅是为了产生一次阻塞,给个机会执行"cat /proc/<pid>/maps"。
         * 为什么选0xB?这纯属实验结果,因为0xB只出现了一次。
         */
        if ( 0xB == regs.orig_eax )
        {
            getchar();
        }
        #endif
        if ( -1 == ptrace( PTRACE_SYSCALL, pid, 0, 0 ) )
        {
            perror( "child ptrace( PTRACE_SYSCALL ) failed" );
        }
        wait( &status );
    }  /* end of while */
}
return( EXIT_SUCCESS );

} / end of main /

先启用全局ASLR:

sysctl -w kernel.randomize_va_space=2

kernel.randomize_va_space = 2

测试ASLR的效果:

./personality_demo no

child = 28684

/usr/bin/col

./personality_demo no

child = 28691

/usr/bin/col

./personality_demo no

child = 28696

/usr/bin/col

cat /proc/28684/maps > 1.txt

cat /proc/28691/maps > 2.txt

cat /proc/28696/maps > 3.txt

diff 1.txt 2.txt

4,7c4,7 < b7f54000-b7f55000 r-xp b7f54000 00:00 0 [vdso] < b7f55000-b7f70000 r-xp 00000000 08:01 1046552 /lib/ld-2.11.2.so < b7f70000-b7f72000 rw-p 0001a000 08:01 1046552 /lib/ld-2.11.2.so < bfebf000-bfed4000 rw-p bfebf000 00:00 0 [stack]


b7fb1000-b7fb2000 r-xp b7fb1000 00:00 0 [vdso] b7fb2000-b7fcd000 r-xp 00000000 08:01 1046552 /lib/ld-2.11.2.so b7fcd000-b7fcf000 rw-p 0001a000 08:01 1046552 /lib/ld-2.11.2.so bfcb4000-bfcca000 rw-p bfcb4000 00:00 0 [stack]

diff 1.txt 3.txt

4,7c4,7 < b7f54000-b7f55000 r-xp b7f54000 00:00 0 [vdso] < b7f55000-b7f70000 r-xp 00000000 08:01 1046552 /lib/ld-2.11.2.so < b7f70000-b7f72000 rw-p 0001a000 08:01 1046552 /lib/ld-2.11.2.so < bfebf000-bfed4000 rw-p bfebf000 00:00 0 [stack]


b7fcb000-b7fcc000 r-xp b7fcb000 00:00 0 [vdso] b7fcc000-b7fe7000 r-xp 00000000 08:01 1046552 /lib/ld-2.11.2.so b7fe7000-b7fe9000 rw-p 0001a000 08:01 1046552 /lib/ld-2.11.2.so bfc70000-bfc86000 rw-p bfc70000 00:00 0 [stack]

我这个系统上randomize_va_space置为2,heap也没有被随机化啊,不给力。

uname -a

Linux debian 2.6.18-4-686 #1 SMP Wed May 9 23:03:12 UTC 2007 i686 GNU/Linux

接着测试关闭单个进程ASLR的效果:

./personality_demo

child = 28709

/usr/bin/col

./personality_demo

child = 28714

/usr/bin/col

./personality_demo

child = 28717

/usr/bin/col

cat /proc/28709/maps > 4.txt

cat /proc/28714/maps > 5.txt

cat /proc/28717/maps > 6.txt

diff 4.txt 5.txt

diff 4.txt 6.txt

7c7 < bffeb000-c0000000 rw-p bffeb000 00:00 0 [stack]


bffea000-c0000000 rw-p bffea000 00:00 0 [stack]

除了stack有时候在变,其它的ASLR都消失了。

看上去personality( ADDR_NO_RANDOMIZE )对stack的处理有点古怪。

D: scz

对setuid程序,Linux内核会清除传递给personality()的如下标志位:

ADDR_NO_RANDOMIZE ADDR_COMPAT_LAYOUT MMAP_PAGE_ZERO READ_IMPLIES_EXEC

即使当前用户是root。