【信息安全】缓冲区溢出攻击原理

1.要测试的程序源码

  我们将一个占用32bytes空间的字符串拷贝到只分配了16bytes空间的字符数组中,由于字符数组所占空间不能满足要拷贝的字符串大小,因此会产生缓冲区溢出的情况。

#include <stdio.h>
#include <string.h>
#define ATTACK_BUFF_LEN 1024
char attackStr[ATTACK_BUFF_LEN] = "01234567890123456789========ABCD"; // 32 bytes
void overflow()
{
    char buff[16];	// 最多容纳16 bytes
    strcpy (buff, attackStr);
}
int main(int argc, char * argv[])
{
    overflow();
    return 0;
}

2. 运行时的栈

  要了解缓冲区溢出攻击原理,我们必须先要了解C程序函数运行时的栈存储情况。我们以下面代码为例观察运行时栈的存储情况:

int cal(int a,int b)
{
	int x = 2;
	int y = 3;
	return x*a + y*b;
}
void main()
{
	int a = 1;
	int b = 2;
	int c = cal(a,b);
}

在这里插入图片描述
  通过以上图示我们可以观察到,对于栈空间的分配是从高地址到低地址的,当某一函数要调用另一函数时首先要将要传入的参数逆序推入堆栈,然后将返回地址推入堆栈。

  分析要测试程序可知,程序流程为 mian 函数调用 overflow函数,overflow 函数调用 strcopy 函数,运行时的栈存储情况应如下图所示:
在这里插入图片描述

3. 实验过程

本实验通过 Linux 命令行编译运行调试该程序,观察缓冲区的溢出情况

  1. 为了快速观察到实验结果,用以下命令关闭地址随机化机制:
sudo sysctl -w kernel.randomize_va_space=0
  1. 使用以下命令编译 attack_overflow.c 函数
gcc -fno-stack-protector -o attack_overflow attack_overflow.c

其中 -fno-stack-protector 参数是用来防止 C 编译时添加防止缓冲区溢出的指令

  1. 使用以下命令对程序进行调试
gdb attack_overflow

会出现以下界面
在这里插入图片描述
4. 输入 disas 命令对 main 函数与 overflow 函数进行反汇编

(gdb) disas main
Dump of assembler code for function main:
   0x08048428 <+0>:	lea    0x4(%esp),%ecx
   0x0804842c <+4>:	and    $0xfffffff0,%esp
   0x0804842f <+7>:	pushl  -0x4(%ecx)
   0x08048432 <+10>:	push   %ebp
   0x08048433 <+11>:	mov    %esp,%ebp
   0x08048435 <+13>:	push   %ecx
   0x08048436 <+14>:	sub    $0x4,%esp
   0x08048439 <+17>:	call   0x804840b <overflow>
   0x0804843e <+22>:	mov    $0x0,%eax
   0x08048443 <+27>:	add    $0x4,%esp
   0x08048446 <+30>:	pop    %ecx
   0x08048447 <+31>:	pop    %ebp
   0x08048448 <+32>:	lea    -0x4(%ecx),%esp
   0x0804844b <+35>:	ret    
End of assembler dump.
(gdb) disas overflow
Dump of assembler code for function overflow:
   0x0804840b <+0>:	push   %ebp
   0x0804840c <+1>:	mov    %esp,%ebp
   0x0804840e <+3>:	sub    $0x18,%esp
   0x08048411 <+6>:	sub    $0x8,%esp
   0x08048414 <+9>:	push   $0x804a040
   0x08048419 <+14>:	lea    -0x18(%ebp),%eax
   0x0804841c <+17>:	push   %eax
   0x0804841d <+18>:	call   0x80482e0 <strcpy@plt>
   0x08048422 <+23>:	add    $0x10,%esp
   0x08048425 <+26>:	nop
   0x08048426 <+27>:	leave  
   0x08048427 <+28>:	ret    
End of assembler dump.
  1. overflow 函数的汇编代码以下几段设置断点
0x0804840b <+0>:	push   %ebp
0x0804841d <+18>:	call   0x80482e0 <strcpy@plt>
0x08048427 <+28>:	ret

通过以下命令:

(gdb) b*(overflow+0)
Breakpoint 1 at 0x804840b
(gdb) b*(overflow+18)
Breakpoint 2 at 0x804841d
(gdb) b*(overflow+28)
Breakpoint 3 at 0x8048427
(gdb)
  1. 运行该程序,并通过以下指令显示即将执行的指令的汇编代码。

运行程序:

(gdb) r

显示即将执行指令的汇编代码:

(gdb) display/i $eip
1: x/i $eip
=> 0x804840b <overflow>:	push   %ebp

  显示即将执行的指令我们可知,程序已经执行到了第一个断点处即 overflow 汇编指令的第一条 0x0804840b <+0>: push %ebp,我们观察此时的运行时的栈顶存储情况:

(gdb) x/x $esp
0xbffff04c:	0x0804843e
(gdb) x/i 0x0804843e
   0x804843e <main+22>:	mov    $0x0,%eax
(gdb) 

  根据指令所显示的栈顶存储情况可知,当前栈顶的地址为 0xbffff04c ,其中所存储的内容为地址 0x0804843e ,我们通过查看 0x0804843e 地址,可以看到其中存储的是 main 函数在调用完 overflow 函数后所执行的第一条指令,由此我们可以得知该地址记录了调用完 overflow 的返回地址。此时运行时的栈存储情况如下图所示:
在这里插入图片描述

  1. 继续调试该程序
(gdb) c
Continuing.

Breakpoint 2, 0x0804841d in overflow ()
1: x/i $eip
=> 0x804841d <overflow+18>:	call   0x80482e0 <strcpy@plt>
(gdb) 

  此时,程序已经开始调用 strcopy 函数了,我们观察运行时的栈存储情况:

(gdb) x/x $esp
0xbffff020:	0xbffff030
(gdb) 
0xbffff024:	0x0804a040
(gdb) 

  strcopy函数传参为:strcpy (buff, attackStr),由于C语言默认将参数逆序推入堆栈,因此,src(全局变量attackStr的地址) 先进栈 (高地址),des(overflow()中buff的首地址) 后进栈 (低地址),与上面的地址相结合我们可以推测,此时栈顶地址为 0xbffff020 其中存储的内容为 0xbffff030 应该为 buff 变量的首地址,此栈顶地址为 0xbffff024 其中存储的内容为 0x0804a040 应该为 attackStr 变量的首地址,我们通过指令观察一下:

(gdb) x/i 0x0804a040
   0x804a040 <attackStr>:	xor    %dh,(%ecx)
(gdb) 

次栈顶中果然存储的是全局变量 attackStr 的首地址,此时我们可以的到运行时的栈存储状况如下图所示:
在这里插入图片描述

  1. 计算存储返回地址的首地址 0xbffff04cbuff 首地址 0xbffff030 的差值
(gdb) p 0xbffff04c-0xbffff030
$1 = 28
(gdb) p/x 0xbffff04c-0xbffff030
$2 = 0x1c
(gdb) 

  可以看到两地址之间的差值为28bytes,也就是说当 attackStr 所占的存储空间大于28bytes时其多余的部分就会将返回地址所覆盖,attackStr的长度为32字节,其中最后的4个字节为 ABCD
  我们知道 attackStr 的首地址为 0x0804a040 通过下图计算我们可以得知其最后的四个字节为:0x44434241。因此我们可以得知因此,在执行strcpy(des, src)之后,返回地址由原来的 0x0804843e 变为 ABCD(0x44434241),即返回地址被改写。

(gdb) x/x 0x0804a040+0x1c
0x804a05c <attackStr+28>:	0x44434241
(gdb) 
  1. 继续调试程序观察返回地址的变化:
(gdb) c
Continuing.

Breakpoint 3, 0x08048427 in overflow ()
1: x/i $eip
=> 0x8048427 <overflow+28>:	ret    
(gdb) x/x $esp
0xbffff04c:	0x44434241
(gdb)  

通过指令的反馈我们可以看到,即将执行的指令为ret,执行ret等价于以下三条指令:

  • eip的值=esp指针指向的堆栈内容
  • 跳转到eip执行指令
  • esp=esp+4

  执行ret之前的esp的内容为 ABCD,即 0x44434241,可以推断执行ret后将跳到地址 0x44434241 去执行。由于 0x44434241 是不可访问的地址,因此发生段错误。esp=0x44434241,正好是 ABCD 倒过来,这是由于IA32默认字节序为little_endian(小端字节序,低字节存放在低地址)。

  通过修改 attackStr 的内容(将 ABCD 改成期望的地址),,就可以设置需要的返回地址,从而可以将 eip 变为可以控制的地址,也就是说可以控制程序的执行流程。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值