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, ®s ) ) { 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//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。