2025长城杯vvvmmm复现

admin 2026-05-11 07:22:30 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 该文档复现2025长城杯CTF中一道使用Unicorn引擎的RISC-V虚拟机逆向题目。通过bindiff工具恢复符号识别出ucmemwrite、ucregwrite等UnicornAPI,并利用IDAPython脚本提取字节码,分析出程序采用密钥哈希、PRNG流生成、异或解密的逻辑链,最终通过提供的密钥成功解密得到flag。文档完整呈现了从虚拟机识别到解密脚本编写的实战流程。 综合评分: 85 文章分类: 逆向分析,CTF,二进制安全,漏洞分析,安全工具


cover_image

2025长城杯vvvmmm复现

studying-egg studying-egg

正在思考ing

2026年5月4日 13:10 江苏

在小说阅读器读本章

去阅读

参考链接

https://www.cnblogs.com/x0rrrrr/p/19439591

前言

今天是五四青年节,这里祝奋斗在网络安全一线的兄弟们节日快乐!

VMP一直是让我头疼的考点😭,想起来长城杯初赛有一道VMP,这里简单复现一下。

复现

在字符串列表中看到UCqemu,结合题目的名字可以推断出这道题考察的是unicorn的虚拟机

什么是unicorn

qemu是模拟不同处理器架构的虚拟机,unicorn是qemu框架下只用于模拟CPU执行的模块

这里做一个符号恢复

在Ubuntu中编译好的unicorn放入IDA最终进行分析,并保存,然后用bindiff导入vvvmmm中进行匹配

应用这些匹配结果

unicorn程序中的一些API:

uc_mem_map:在unicorn中分配一段内存空间

uc_mem_write:在分配的内存空间中写入内容

uc_reg_write:给代码传入参数

uc_emu_start:运行程序

unicorn程序运行的逻辑一般为:分配空间 -> 写入字节码 -> 将参数写入寄存器 -> 运行程序

所以我们可以推断出sub_407DD0uc_reg_writesub_4099D0uc_emu_start

同时我们可以推断出第一个uc_mem_write写入的是字节码,后面两个un_mem_write写入的是传入程序的参数

根据字符串可以得出这个模拟的是riscv架构 通过IDA python脚本将字节码dump出来

import ida_bytes
start_ea = 0x64C3F0
size =  662
output_file = r"C:\Users\lenovo\Desktop\test\code.bin"
data = ida_bytes.get_bytes(start_ea, size)
if data:
    with open(output_file, "wb") as f:
        f.write(data)
    print(f"[+] Success: Dumped {len(data)} bytes from {hex(start_ea)} to {output_file}")
else:
    print("[-] Error: Failed to get bytes. Check address.")

得到的二进制文件放入IDA中,选择Risc-V架构,可以得到程序逻辑

通过分析程序可以得到逻辑:密钥→哈希→PRNG 流→XOR 解密→与期望值比对

传入的两个参数,一个是输入值,另外一个是密钥

之前提到的两个参数,其中byte_64C6C0指向密钥

最终的返回值会保存在rdi

为什么我不直接追踪 uc_reg_write 传入的参数

通过跟踪发现传入的值是虚拟内存中的地址,无法找到具体密钥(可能是我方法不对)

根据上述内容,最终可以生成解密脚本

import struct

KEY = "e4Y8YRXVzg2HRrCUy35CM0Txq91HzMGZ"

MOD = 0x13579BDF

MASK64 = (1&nbsp;<<&nbsp;64) -&nbsp;1

EXPECTED = [

&nbsp; &nbsp;&nbsp;0x45034F63,&nbsp;0x534762D2,&nbsp;0x44B36D04,&nbsp;0x44C3ED6A,

&nbsp; &nbsp;&nbsp;0x79BB60B0,&nbsp;0x42A1E767,&nbsp;0x3EDB7E6C,&nbsp;0x30E1551D,

&nbsp; &nbsp;&nbsp;0x4D3ABAA4,&nbsp;0x6AA29948,&nbsp;0x51CE8847,&nbsp;0x51623FAF,

]

def&nbsp;key_hash(key: str)&nbsp;-> tuple[int, int]:

&nbsp; &nbsp;&nbsp;"""Hash key string in 64-bit arithmetic. Returns (v5, v4)."""

&nbsp; &nbsp; v4 =&nbsp;1

&nbsp; &nbsp;&nbsp;for&nbsp;ch&nbsp;in&nbsp;key:

&nbsp; &nbsp; &nbsp; &nbsp; v4 = ((v4 *&nbsp;31) + ord(ch)) & MASK64

&nbsp; &nbsp;&nbsp;return&nbsp;(v4 >>&nbsp;16) &&nbsp;0xFFFFFFFF, v4 &&nbsp;0xFFFFFFFF

def&nbsp;generate_stream(v5: int, v4: int)&nbsp;-> list[int]:

&nbsp; &nbsp;&nbsp;"""Generate 12 PRNG 32-bit words."""

&nbsp; &nbsp; stream = []

&nbsp; &nbsp;&nbsp;for&nbsp;_&nbsp;in&nbsp;range(6):

&nbsp; &nbsp; &nbsp; &nbsp; v8 = v5 % MOD

&nbsp; &nbsp; &nbsp; &nbsp; v9 = v4 % MOD

&nbsp; &nbsp; &nbsp; &nbsp; v5 = pow(v8,&nbsp;13, MOD)

&nbsp; &nbsp; &nbsp; &nbsp; v4 = pow(v9,&nbsp;13, MOD)

&nbsp; &nbsp; &nbsp; &nbsp; stream.append(v5)

&nbsp; &nbsp; &nbsp; &nbsp; stream.append(v4)

&nbsp; &nbsp;&nbsp;return&nbsp;stream

def&nbsp;decrypt(key: str)&nbsp;-> bytes:

&nbsp; &nbsp;&nbsp;"""Recover original input by XORing expected values with PRNG stream."""

&nbsp; &nbsp; v5, v4 = key_hash(key)

&nbsp; &nbsp; stream = generate_stream(v5, v4)

&nbsp; &nbsp; data =&nbsp;b""

&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;in&nbsp;range(12):

&nbsp; &nbsp; &nbsp; &nbsp; word = EXPECTED[i] ^ stream[i]

&nbsp; &nbsp; &nbsp; &nbsp; data += struct.pack("<I", word)

&nbsp; &nbsp;&nbsp;return&nbsp;data

if&nbsp;__name__ ==&nbsp;"__main__":

&nbsp; &nbsp; result = decrypt(KEY)

&nbsp; &nbsp; print(f"Found: flag{{{result.decode('ascii')}}}")

# flag{fANUES0XtUXBDEbOXs4xFcXDb3Q5kMU87bZLMZJfuRnCvfwX}

结语

复现过程中我遇到最大的难题是符号恢复(之前一直没用过bindiff😢)。这里非常感谢W0w1F师傅的指导💐


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:正在思考ing studying-egg studying-egg《2025长城杯vvvmmm复现》

e0e1-wx重构gui大更新 网络安全文章

e0e1-wx重构gui大更新

文章总结: 本文介绍e0e1-wx工具的重大GUI重构更新,新增自动化反编译源代码、正则匹配、代码优化、devtools_cdp、路由跳转、云函数扫描和加密解密
评论:0   参与:  0