当前位置:首页 > 技术 > 正文内容

CTF逆向综合题解:虚拟机、SEH异常与反混淆技术分析

访客 技术 2026年8月16日 1

该题目要求通过分析一个自定义虚拟机、一段基于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解密

程序入口处通过ReadProcessMemoryWriteProcessMemory将自身代码段的一处数据从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

执行后即可得到该部分的明文结果。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。