文章总结: 该文档复现2025长城杯CTF中一道使用Unicorn引擎的RISC-V虚拟机逆向题目。通过bindiff工具恢复符号识别出ucmemwrite、ucregwrite等UnicornAPI,并利用IDAPython脚本提取字节码,分析出程序采用密钥哈希、PRNG流生成、异或解密的逻辑链,最终通过提供的密钥成功解密得到flag。文档完整呈现了从虚拟机识别到解密脚本编写的实战流程。 综合评分: 85 文章分类: 逆向分析,CTF,二进制安全,漏洞分析,安全工具
2025长城杯vvvmmm复现
studying-egg studying-egg
正在思考ing
2026年5月4日 13:10 江苏
在小说阅读器读本章
去阅读
参考链接
https://www.cnblogs.com/x0rrrrr/p/19439591
前言
今天是五四青年节,这里祝奋斗在网络安全一线的兄弟们节日快乐!
VMP一直是让我头疼的考点😭,想起来长城杯初赛有一道VMP,这里简单复现一下。
复现
在字符串列表中看到UC和qemu,结合题目的名字可以推断出这道题考察的是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_407DD0为uc_reg_write,sub_4099D0为uc_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 << 64) - 1
EXPECTED = [
0x45034F63, 0x534762D2, 0x44B36D04, 0x44C3ED6A,
0x79BB60B0, 0x42A1E767, 0x3EDB7E6C, 0x30E1551D,
0x4D3ABAA4, 0x6AA29948, 0x51CE8847, 0x51623FAF,
]
def key_hash(key: str) -> tuple[int, int]:
"""Hash key string in 64-bit arithmetic. Returns (v5, v4)."""
v4 = 1
for ch in key:
v4 = ((v4 * 31) + ord(ch)) & MASK64
return (v4 >> 16) & 0xFFFFFFFF, v4 & 0xFFFFFFFF
def generate_stream(v5: int, v4: int) -> list[int]:
"""Generate 12 PRNG 32-bit words."""
stream = []
for _ in range(6):
v8 = v5 % MOD
v9 = v4 % MOD
v5 = pow(v8, 13, MOD)
v4 = pow(v9, 13, MOD)
stream.append(v5)
stream.append(v4)
return stream
def decrypt(key: str) -> bytes:
"""Recover original input by XORing expected values with PRNG stream."""
v5, v4 = key_hash(key)
stream = generate_stream(v5, v4)
data = b""
for i in range(12):
word = EXPECTED[i] ^ stream[i]
data += struct.pack("<I", word)
return data
if __name__ == "__main__":
result = decrypt(KEY)
print(f"Found: flag{{{result.decode('ascii')}}}")
# flag{fANUES0XtUXBDEbOXs4xFcXDb3Q5kMU87bZLMZJfuRnCvfwX}
结语
复现过程中我遇到最大的难题是符号恢复(之前一直没用过bindiff😢)。这里非常感谢W0w1F师傅的指导💐
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:正在思考ing studying-egg studying-egg《2025长城杯vvvmmm复现》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论