CTF逆向综合题解:虚拟机、SEH异常与反混淆技术分析
该题目要求通过分析一个自定义虚拟机、一段基于SEH的异常处理流程以及一个被OLLVM混淆的程序,逐步还原出验证逻辑并提取flag。整个解题过程分为三个独立的部分。
虚拟机解析与约束求解
程序首先接收一串输入,用于生成一个名为check.dll的动态库,随后调用其中的check函数对后续输入进行验证。通过逆向,提取出虚拟机字节码序列,并编写解释器将其转换为可读的伪指令。
bytecode = [162, 0, 132, 163, 8, 0, 163, 8, 1, 176, 8, 316, 178, 163, 9, 1, 163, 9, 2, 163, 9, 3, 176, 9, 158, 178, 166, 4, 22, 163, 0, 4, 176, 0, 889, 178, 164, 5, 11, 161, 8, 5, 163, 8, 6, 176, 8, 38, 178, 163, 7, 6, 176, 7, 96, 178, 161, 9, 1, 163, 9, 2, 165, 9, 5, 176, 9, 111, 178, 166, 5, 7, 161, 8, 0, 165, 8, 6, 163, 8, 5, 176, 8, 859, 178, 163, 3, 4, 176, 3, 706, 178, 192]
dispatch = {
160: "mov [{}], {}",
161: "mov [{}], [{}]",
162: "add [{}], {}",
163: "add [{}], [{}]",
164: "sub [{}], {}",
165: "sub [{}], [{}]",
166: "mul [{}], {}",
167: "mul [{}], [{}]",
176: "cmp [{}], {}",
177: "cmp [{}], [{}]",
178: "jnz end",
192: "halt"
}
ip = 0
while ip < len(bytecode):
op = bytecode[ip]
if op == 178:
print(dispatch[op])
ip += 1
elif op == 192:
print(dispatch[op])
break
else:
print(dispatch[op].format(bytecode[ip+1], bytecode[ip+2]))
ip += 3
上述脚本输出一系列算术与比较操作,整理后可以得到一组关于8个内存单元的约束方程。使用Z3求解器可计算出这8个值,再将其拼接即得到第一段输入字符串。
from z3 import *
mem = [Int(f'm{i}') for i in range(8)]
s = Solver()
for v in mem:
s.add(v < 100)
s.add(mem[0] + mem[1] + 132 == 316)
s.add(mem[1] + mem[2] + mem[3] == 158)
s.add(mem[4] * 22 + mem[0] + 132 == 889)
s.add(mem[5] - 11 + mem[6] == 38)
s.add(mem[7] + mem[6] == 96)
s.add(mem[1] + mem[2] - mem[5] + 11 == 111)
s.add((mem[0] + 132 + mem[4]*22) - mem[6] + (mem[5]-11)*7 == 859)
s.add(mem[3] + mem[4]*22 == 706)
if s.check() == sat:
m = s.model()
solution = ''.join(str(m[v].as_long()) for v in mem)
print(solution) # 输出: 9787254630123759
得到第一段输入后,即可在内存中生成正确的check.dll。其check函数仅对44字节的密文进行异或解密,直接还原出flag。
cipher = bytes([0x0D, 0x08, 0x1A, 0x0A, 0x1D, 0x0F, 0x32, 0x78, 0x2A, 0x7B, 0x2A, 0x7B, 0x7C, 0x7D, 0x71, 0x64,
0x7A, 0x2C, 0x7B, 0x7D, 0x64, 0x28, 0x7D, 0x71, 0x2C, 0x64, 0x78, 0x78, 0x7D, 0x7A, 0x64, 0x28,
0x7A, 0x7D, 0x70, 0x7F, 0x28, 0x7A, 0x2B, 0x7E, 0x7D, 0x79, 0x79, 0x34])
flag = ''.join(chr(b ^ 0x49) for b in cipher)
print(flag) # DASCTF{1c2c2548-3e24-a48e-1143-a3496a3b7400}
SEH异常处理与TEA/XXTEA解密
程序入口处通过ReadProcessMemory和WriteProcessMemory将自身代码段的一处数据从0x96B30C修改为0x96B31C,随后执行int 3指令触发异常。操作系统派发异常时,SEH链将执行流导向0x962079处的异常处理函数。
该函数对硬编码的密文进行两次TEA解密(轮数均为0x24)和一次XXTEA解密,密钥为{0x44, 0x41, 0x53, 0x21}。以下为解密脚本,使用Python复现该过程。
def tea_decrypt_block(v, key, rounds):
delta = 0x9E3779B9
s = (delta * rounds) & 0xFFFFFFFF
v0, v1 = v[0], v[1]
for _ in range(rounds):
v1 -= (( (v0 << 4) ^ (v0 >> 5) ) + v0) ^ (s + key[(s >> 11) & 3])
v1 &= 0xFFFFFFFF
s = (s - delta) & 0xFFFFFFFF
v0 -= (( (v1 << 4) ^ (v1 >> 5) ) + v1) ^ (s + key[s & 3])
v0 &= 0xFFFFFFFF
return v0, v1
def xxtea_decrypt(v, n, key):
delta = 0x9E3779B9
rounds = 6 + 52 // n
s = (delta * rounds) & 0xFFFFFFFF
y = v[0]
for _ in range(rounds):
e = (s >> 2) & 3
for i in range(n - 1, 0, -1):
z = v[i - 1]
y = v[i] - ((((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4))) ^ ((s ^ y) + (key[(i ^ e) & 3] ^ z)))
v[i] = y & 0xFFFFFFFF
z = v[n - 1]
y = v[0] - ((((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4))) ^ ((s ^ y) + (key[(0 ^ e) & 3] ^ z)))
v[0] = y & 0xFFFFFFFF
s = (s - delta) & 0xFFFFFFFF
enc = [0x87AAA7C1, 0x857321B6, 0x0E71D28C, 0xCADF39F2, 0x58EFCA14, 0xD7E7D9D8, 0xF29F5C5D, 0x5F5ED45E]
key = [0x44, 0x41, 0x53, 0x21]
for i in range(4):
enc[i*2], enc[i*2+1] = tea_decrypt_block(enc[i*2:i*2+2], key, 0x24)
for i in range(4):
enc[i*2], enc[i*2+1] = tea_decrypt_block(enc[i*2:i*2+2], key, 0x24)
xxtea_decrypt(enc, 8, key)
print(bytes(enc).decode()) # 输出flag
OLLVM反混淆与Anti函数
最后一个部分使用OLLVM控制流平坦化。通过d810插件还原后,在GLOBAL__sub_I_prese_cpp初始化函数中发现对crypto_flag进行了异或和取反操作,实际解密逻辑为:
def anti_decode(data, length):
for i in range(length):
data[i] = (~(data[i] ^ 0x22) ^ length) & 0xFF
enc_flag = bytearray([0x86, 0x83, 0x91, 0x81, 0x96, 0x84, 0xB9, 0xA5, 0xAD, 0xAD, 0xA6, 0x9D, 0xB6, 0xAA, 0xA7, 0x9D,
0xB0, 0xA7, 0x9D, 0xAB, 0xB1, 0x9D, 0xA7, 0xA3, 0xB1, 0xBB, 0xAA, 0xAA, 0xAA, 0xAA, 0xBF])
anti_decode(enc_flag, 31)
print(enc_flag.decode()) # 最终flag
执行后即可得到该部分的明文结果。