前言
也是荣幸当上长城杯陪跑的炮灰,看到这个出个vm尿吓出了两滴,赛后被提示是塞了unicorn,牛魔的你说的vm不自己写,塞个别人的引擎是吧,没用把符号表删了几个人知道是unicorn?难道你要逆向unicorn的代码,反推出逻辑?那你是高手,跪了orz
最后在赛后被提示几个用ai辅助一下出了
解题
进去一个upx,直接upx -d脱了
用鸡神的lumina把符号全部恢复就看到好多unicorn的函数
start里有个函数,点进去就是主体了sub_401C60,长得有点恶心
f5看到switch,明显的控制流扁平化特征,直接对opcode进行恢复有点超纲了,我直接hook几个块然后执行一下获取顺序吧
先获取所有的块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import ida_funcs
import ida_gdl
import ida_kernwin
import idaapi
def dump_bb_starts(func_ea=0x401C60, out_path=None):
f = ida_funcs.get_func(func_ea)
if not f:
raise RuntimeError("No function at 0x%X" % func_ea)
starts = []
fc = ida_gdl.FlowChart(f, flags=ida_gdl.FC_PREDS) # 基本块图
for b in fc:
starts.append(b.start_ea)
starts = sorted(set(starts))
lines = [("0x%X" % ea) for ea in starts]
text = "\n".join(lines)
print("[sub_401C60] basic blocks:", len(starts))
print(text)
if out_path:
with open(out_path, "w", encoding="utf-8") as fp:
fp.write(text + "\n")
print("written to:", out_path)
# 方便你直接粘贴到 writeup
try:
ida_kernwin.set_clipboard_text(text)
print("copied to clipboard")
except Exception:
pass
dump_bb_starts(0x401C60)
#output
block_list = [{"block_start":0x401C60,"block_end":0x401DD6},{"block_start":0x401DD8,"block_end":0x401DEF},{"block_start":0x401DF2,"block_end":0x401DFD},{"block_start":0x401E00,"block_end":0x401E0B},{"block_start":0x401E0E,"block_end":0x401E1C},{"block_start":0x401E1F,"block_end":0x401E36},{"block_start":0x401E39,"block_end":0x401E50},{"block_start":0x401E53,"block_end":0x401E6A},{"block_start":0x401E6D,"block_end":0x401E80},{"block_start":0x401E83,"block_end":0x401EA4},{"block_start":0x401EB0,"block_end":0x401EC7},{"block_start":0x401ECA,"block_end":0x401EE1},{"block_start":0x401EE4,"block_end":0x401EF7},{"block_start":0x401EFA,"block_end":0x401F21},{"block_start":0x401F30,"block_end":0x401F47},{"block_start":0x401F4A,"block_end":0x401F6C},{"block_start":0x401F70,"block_end":0x401F87},{"block_start":0x401F8A,"block_end":0x4021B2},{"block_start":0x4021B5,"block_end":0x4021CC},{"block_start":0x4021CF,"block_end":0x40704F},{"block_start":0x407052,"block_end":0x40706E}]
然后hook这些块,随便输入48字节,按住f9不松手,就可以得到执行顺序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import glob
import idaapi
import ida_dbg
import ida_bytes
import idc
block_list = [{"block_start":0x401C60,"block_end":0x401DD6},{"block_start":0x401DD8,"block_end":0x401DEF},{"block_start":0x401DF2,"block_end":0x401DFD},{"block_start":0x401E00,"block_end":0x401E0B},{"block_start":0x401E0E,"block_end":0x401E1C},{"block_start":0x401E1F,"block_end":0x401E36},{"block_start":0x401E39,"block_end":0x401E50},{"block_start":0x401E53,"block_end":0x401E6A},{"block_start":0x401E6D,"block_end":0x401E80},{"block_start":0x401E83,"block_end":0x401EA4},{"block_start":0x401EB0,"block_end":0x401EC7},{"block_start":0x401ECA,"block_end":0x401EE1},{"block_start":0x401EE4,"block_end":0x401EF7},{"block_start":0x401EFA,"block_end":0x401F21},{"block_start":0x401F30,"block_end":0x401F47},{"block_start":0x401F4A,"block_end":0x401F6C},{"block_start":0x401F70,"block_end":0x401F87},{"block_start":0x401F8A,"block_end":0x4021B2},{"block_start":0x4021B5,"block_end":0x4021CC},{"block_start":0x4021CF,"block_end":0x40704F},{"block_start":0x407052,"block_end":0x40706E}]
index = 0
class hook_all(ida_dbg.DBG_Hooks):
def dbg_bpt(self, tid, ea):
global index
for block in block_list:
if ea == block['block_start']:
print(f'{index} : {hex(ea)}')
index += 1
return 0
def add_breakpoint():
for block in block_list:
addr = block['block_start']
ida_dbg.add_bpt(addr)
print(f"Breakpoint set at {hex(addr)}")
def install_hook():
# 清理旧的 hook (如果存在于全局变量中)
global my_hook
try:
if 'my_hook' in globals():
my_hook.unhook()
print("Removed old hook")
except:
pass
# 安装新的 hook
my_hook = hook_all()
my_hook.hook()
print("Hook installed. Please set the breakpoint manually.")
if __name__ == "__main__":
add_breakpoint()
install_hook()
#output:
''''
[0x401e0e,
0x401e1f,
0x401e39,
0x401f30,
0x401f4a,
0x401e00,
0x401e0e,
0x401e1f,
0x401e39,
0x401e53,
0x4021b5,
0x4021cf,
0x401e00,
0x401e0e,
0x401e1f,
0x401eb0,
0x401f70,
0x401f8a,
0x401e00,
0x401e0e,
0x401e1f,
0x401e39,
0x401e53,
0x401e6d,
0x401e83,
0x401e00,
0x401e0e,
0x401e1f,
0x401eb0,
0x401eca,
0x401dd8,
0x407052
]
'''
其实我们注意看,里面好多是子分发器,这是没用的
.text:00000000004021B5 xor ecx, ecx ; jumptable 0000000000401DFD case 5
.text:00000000004021B5 ; jumptable 0000000000401E0B case 5
.text:00000000004021B5 ; jumptable 0000000000401E1C case 5
.text:00000000004021B5 ; jumptable 0000000000401E6A case 1
.text:00000000004021B5 ; jumptable 0000000000401EA4 case 5
.text:00000000004021B5 ; jumptable 0000000000401F21 case 5
.text:00000000004021B5 ; jumptable 0000000000401F6C case 5
.text:00000000004021B5 ; jumptable 00000000004021B2 case 5
.text:00000000004021B5 ; jumptable 000000000040704F case 5
.text:00000000004021B7 cmp eax, 105F52AFh
.text:00000000004021BC setz cl
.text:00000000004021BF mov [rbp+var_14C], ecx
.text:00000000004021C5 mov rax, [rbp+var_100]
.text:00000000004021CC jmp (jpt_4021CC - 64CCC0h)[rax+rcx*8] ; switch 2 cases
最后真实块只有4个
1
2
3
4
5
6
flow = [
0x401f4a,
0x4021cf,
0x401f8a,
0x407052
]
其实不用看也能猜出来这个顺序。。。
最重要两个块
- 0x4021cf 解密opcode
- 0x401f8a 运行虚拟机
我们动态到运行虚拟机的部分,这时候opcode已经解密了,可以直接提取出来
稍微恢复下 运行虚拟机的部分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
dword_68E7B0 = 1;
v70 = &v23[-64];
uc_engine = &v23[-16];
v55 = &v23[-16];
v56 = &v23[-16];
v57 = &v23[-16];
final_reg = &v23[-16];
IO_puts(&byte_64C980); //print banner
_printf(&off_64C714, &byte_64C6ED, p_jpt_401F87_2, p_n222989985_2, v13, v14, v23[0]);
v70[48] = 0;
_isoc99_scanf(&byte_64C68B, v70, v15, v16, v17, v18, v23[0]); //get input
sub_4075E0(8, 8, uc_engine);
uc_mem_map(*uc_engine, 0, 4096, 7);
uc_mem_write(*uc_engine, 0, &byte_64C3F0, 662); //这是会执行的汇编
uc_mem_map(*uc_engine, 1879048192, 0x10000, 7);
*v55 = 1879113472;
uc_reg_write(*uc_engine, 3, v55);
uc_mem_map(*uc_engine, 0x10000000, 4096, 7);
uc_mem_map(*uc_engine, 268439552, 4096, 7);
v19 = *uc_engine;
v20 = j_ifunc_542170(v70);
uc_mem_write(v19, 0x10000000, v70, v20); //把我们输入的写入虚拟机内存
uc_mem_write(*uc_engine, 268439552, &byte_64C6C0, 32); //写入一些计算时用的到的常量到内存
*v56 = 0x10000000;
*v57 = 268439552;
uc_reg_write(*uc_engine, 11, v56);
uc_reg_write(*uc_engine, 12, v57);
vm_run(*uc_engine, 0, 662, 2000000, 0);
uc_reg_read(*uc_engine, 11, final_reg);
p_n222989985_8 = *final_reg == 0;
n2096283220 = -1114784493;
if ( !*final_reg )
n2096283220 = 2096283220;
p_n222989985_1 = p_n222989985;
*p_n222989985 = n2096283220;
n222989985_1 = n19_3;
switch ( n19_3 )
...
逻辑是:创建虚拟机,初始化内存,把输入写入内存,然后运行虚拟机,最后看某个寄存器的值,是否为0,如果不是就会跳转到0x407052也就是退出的部分,等于就会跳转到正确的逻辑
偷看写字符串列表,可以看到一些risc-v的汇编代码,比如rotr_i32,基本可以断点是risc-v的的虚拟机 这一步就可以丢ai,叫ai改写成py的unicorn 首先用用capstone反汇编一下
0x0000: c.addi16sp sp, -0x60
0x0002: c.sdsp s0, 0x58(sp)
0x0004: c.sdsp s1, 0x50(sp)
0x0006: c.sdsp s2, 0x48(sp)
0x0008: c.sdsp s3, 0x40(sp)
0x000A: c.sdsp s4, 0x38(sp)
0x000C: lbu a2, 0(a1)
0x0010: c.beqz a2, 0x18
0x0012: c.addi a1, 1
0x0014: c.li a4, 1
0x0016: sub a3, a4, a2
0x001A: lbu a2, 0(a1)
0x001E: c.slli a4, 5
0x0020: c.sub a4, a3
0x0022: c.addi a1, 1
0x0024: c.bnez a2, -0xe
0x0026: c.j 4
0x0028: c.li a4, 1
0x002A: lui a3, 0x1357a
0x002E: srli a2, a4, 0x10
0x0032: c.addi4spn a1, sp, 8
0x0034: c.addi a0, 4
0x0036: addi a7, a3, -0x421
0x003A: addi a6, sp, 0x38
0x003E: c.swsp a7, 0x34(sp)
0x0040: c.lwsp a3, 0x34(sp)
0x0042: c.lwsp a5, 0x34(sp)
0x0044: lwu s0, 0x34(sp)
0x0048: lwu s2, 0x34(sp)
0x004C: lwu s1, 0x34(sp)
0x0050: lwu s4, 0x34(sp)
0x0054: lwu s3, 0x34(sp)
0x0058: lwu t6, 0x34(sp)
0x005C: lwu t5, 0x34(sp)
0x0060: lwu t4, 0x34(sp)
0x0064: lwu t3, 0x34(sp)
0x0068: lwu t2, 0x34(sp)
0x006C: lwu t1, 0x34(sp)
0x0070: lwu t0, 0x34(sp)
0x0074: c.swsp a7, 0x34(sp)
0x0076: remuw a2, a2, a3
0x007A: c.lwsp a3, 0x34(sp)
0x007C: remuw a3, a4, a3
0x0080: remuw a4, a2, a5
0x0084: c.lwsp a5, 0x34(sp)
0x0086: remuw a5, a3, a5
0x008A: c.slli a2, 0x20
0x008C: c.slli a4, 0x20
0x008E: mulhu a4, a4, a2
0x0092: remu a4, a4, s0
0x0096: lwu s0, 0x34(sp)
0x009A: c.slli a3, 0x20
0x009C: c.slli a5, 0x20
0x009E: mulhu a5, a5, a3
0x00A2: remu a5, a5, s0
0x00A6: lwu s0, 0x34(sp)
0x00AA: c.srli a2, 0x20
0x00AC: mul a4, a4, a2
0x00B0: remu a4, a4, s2
0x00B4: c.srli a3, 0x20
0x00B6: mul a5, a5, a3
0x00BA: remu a5, a5, s0
0x00BE: lwu s0, 0x34(sp)
0x00C2: mul a4, a4, a2
0x00C6: remu a4, a4, s1
0x00CA: mul a5, a5, a3
0x00CE: remu a5, a5, s0
0x00D2: lwu s0, 0x34(sp)
0x00D6: mul a4, a4, a2
0x00DA: remu a4, a4, s4
0x00DE: mul a5, a5, a3
0x00E2: remu a5, a5, s0
0x00E6: lwu s0, 0x34(sp)
0x00EA: mul a4, a4, a2
0x00EE: remu a4, a4, s3
0x00F2: mul a5, a5, a3
0x00F6: remu a5, a5, s0
0x00FA: lwu s0, 0x34(sp)
0x00FE: mul a4, a4, a2
0x0102: remu a4, a4, t6
0x0106: mul a5, a5, a3
0x010A: remu a5, a5, s0
0x010E: lwu s1, 0x34(sp)
0x0112: mul a4, a4, a2
0x0116: remu a4, a4, t5
0x011A: mul a5, a5, a3
0x011E: remu a5, a5, s1
0x0122: lwu s1, 0x34(sp)
0x0126: mul a4, a4, a2
0x012A: remu a4, a4, t4
0x012E: mul a5, a5, a3
0x0132: remu a5, a5, s1
0x0136: lwu s1, 0x34(sp)
0x013A: mul a4, a4, a2
0x013E: remu a4, a4, t3
0x0142: mul a5, a5, a3
0x0146: remu a5, a5, s1
0x014A: lwu s1, 0x34(sp)
0x014E: mul a4, a4, a2
0x0152: remu a4, a4, t2
0x0156: mul a5, a5, a3
0x015A: remu a5, a5, s1
0x015E: lwu s1, 0x34(sp)
0x0162: mul a4, a4, a2
0x0166: remu a4, a4, t1
0x016A: mul a5, a5, a3
0x016E: remu a5, a5, s1
0x0172: lwu s1, 0x34(sp)
0x0176: mul a2, a4, a2
0x017A: remu a2, a2, t0
0x017E: mul a3, a5, a3
0x0182: remu a4, a3, s1
0x0186: lw a3, -4(a0)
0x018A: c.lw a5, 0(a0)
0x018C: c.xor a3, a2
0x018E: c.xor a5, a4
0x0190: sw a3, -4(a1)
0x0194: c.sw a5, 0(a1)
0x0196: c.addi a1, 8
0x0198: c.addi a0, 8
0x019A: bne a1, a6, -0x15c
0x019E: lui a0, 0x45035
0x01A2: lui a1, 0x53476
0x01A6: lui a6, 0x44b37
0x01AA: c.lwsp a3, 4(sp)
0x01AC: c.lwsp a4, 8(sp)
0x01AE: c.lwsp a5, 0xc(sp)
0x01B0: c.lwsp s1, 0x10(sp)
0x01B2: lui t0, 0x44c3f
0x01B6: lui a7, 0x79bb6
0x01BA: lui t1, 0x42a1e
0x01BE: lui t2, 0x3edb8
0x01C2: addi a0, a0, -0x9d
0x01C6: addi a1, a1, 0x2d2
0x01CA: xor t3, a3, a0
0x01CE: xor t4, a4, a1
0x01D2: c.lwsp a3, 0x14(sp)
0x01D4: c.lwsp a4, 0x18(sp)
0x01D6: c.lwsp a2, 0x1c(sp)
0x01D8: c.lwsp s0, 0x20(sp)
0x01DA: addi a0, a6, -0x2fc
0x01DE: xor t5, a5, a0
0x01E2: lui a5, 0x30e15
0x01E6: addi a1, t0, -0x296
0x01EA: xor t0, s1, a1
0x01EE: lui a6, 0x4d3ac
0x01F2: addi s1, a7, 0xb0
0x01F6: addi a0, t1, 0x767
0x01FA: addi a1, t2, -0x194
0x01FE: addi a5, a5, 0x51d
0x0202: xor t2, a3, s1
0x0206: xor t1, a4, a0
0x020A: xor a7, a2, a1
0x020E: xor t6, s0, a5
0x0212: c.lwsp a2, 0x24(sp)
0x0214: c.lwsp a4, 0x28(sp)
0x0216: c.lwsp s1, 0x2c(sp)
0x0218: c.lwsp s0, 0x30(sp)
0x021A: addi a1, a6, -0x55c
0x021E: c.xor a1, a2
0x0220: lui a2, 0x6aa2a
0x0224: addi a2, a2, -0x6b8
0x0228: c.xor a2, a4
0x022A: lui a4, 0x51ce9
0x022E: addi a4, a4, -0x7b9
0x0232: c.xor a4, s1
0x0234: lui s1, 0x51624
0x0238: addi s1, s1, -0x51
0x023C: c.xor s0, s1
0x023E: seqz s1, t3
0x0242: seqz a0, t4
0x0246: c.add a0, s1
0x0248: seqz s1, t5
0x024C: seqz a3, t0
0x0250: c.add a3, s1
0x0252: seqz s1, t2
0x0256: seqz a5, t1
0x025A: c.add a5, s1
0x025C: seqz s1, t6
0x0260: seqz a1, a1
0x0264: c.add a1, s1
0x0266: c.add a0, a3
0x0268: seqz a3, a7
0x026C: c.add a3, a5
0x026E: seqz a2, a2
0x0272: c.add a1, a2
0x0274: c.add a0, a3
0x0276: seqz a2, a4
0x027A: c.add a1, a2
0x027C: c.add a0, a1
0x027E: seqz a1, s0
0x0282: c.addw a0, a1
0x0284: c.addi a0, -0xc
0x0286: seqz a0, a0
0x028A: c.ldsp s0, 0x58(sp)
0x028C: c.ldsp s1, 0x50(sp)
0x028E: c.ldsp s2, 0x48(sp)
0x0290: c.ldsp s3, 0x40(sp)
0x0292: c.ldsp s4, 0x38(sp)
0x0294: c.addi16sp sp, 0x60
[stats] parsed_bytes=2674 code_size=662 loads(a0)=1 loads(a1)=2 stores=1 branches=1 calls=0
逻辑很简单
- 用 key(byte_64C6C0 那 32 字节 + 后面的 0 终止)生成一组 48 字节的 XOR 掩码(mask)(可看作“解密/加密流”);
- 对输入 48 字节(12 个 little-endian dword)做 out[i] = in[i] XOR mask[i];
- 把 out[0..11] 逐个跟 固定常量比对,全相等则返回 A0=1,否则 A0=0。
改写的py脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
from __future__ import annotations
import argparse
from pathlib import Path
from ida_db_dump import build_blob, parse_ida_db_dump
def _parse_hex_int(s: str) -> int:
s = s.strip().lower()
if s.startswith("0x"):
return int(s, 16)
return int(s, 10)
def main() -> int:
ap = argparse.ArgumentParser(description="Re-run the embedded Unicorn VM from opcode_dump")
ap.add_argument("--dump", default="opcode_dump", help="path to opcode_dump text")
ap.add_argument(
"--input",
default="",
help="ASCII input string (will be written to 0x10000000, like the binary)",
)
ap.add_argument(
"--arch",
default="riscv",
choices=["riscv"],
help="unicorn arch to use (based on uc_open(8,8) this should be RISCV)",
)
ap.add_argument(
"--mode",
default="64",
choices=["32", "64"],
help="riscv mode (IDA decompile shows uc_open(8,8); in practice this matches RISCV64)",
)
ap.add_argument("--trace", action="store_true", help="trace a few instructions")
ap.add_argument("--trace-count", type=int, default=200, help="max traced instructions")
args = ap.parse_args()
# Layout recovered from decompile around 0x401F8A
CODE_SRC_ADDR = 0x64C3F0
CODE_SIZE = 662 # 0x296
KEY_SRC_ADDR = 0x64C6C0
KEY_SIZE = 32
CODE_DST_ADDR = 0x0
STACK_BASE = 0x70000000
STACK_SIZE = 0x10000
STACK_INIT = 0x7000FF00
INPUT_ADDR = 0x10000000
INPUT_MAP_SIZE = 0x1000
KEY_ADDR = 0x10001000
KEY_MAP_SIZE = 0x1000
# Register IDs are used as raw integers in the binary: uc_reg_write(engine, 3/11/12, &val)
REG_STACK = 3
REG_ARG1 = 11
REG_ARG2 = 12
mapping, stats = parse_ida_db_dump(args.dump)
code = build_blob(mapping, CODE_SRC_ADDR, CODE_SIZE)
key = build_blob(mapping, KEY_SRC_ADDR, KEY_SIZE)
try:
from unicorn import Uc, UcError
from unicorn import UC_ARCH_RISCV
from unicorn import UC_PROT_ALL
from unicorn import UC_MODE_RISCV32, UC_MODE_RISCV64
from unicorn import UC_HOOK_CODE
except Exception as e: # pragma: no cover
raise SystemExit(
"Python unicorn 未安装。\n"
"在当前环境执行:pip install unicorn\n"
f"原始错误: {e}"
)
mode = UC_MODE_RISCV32 if args.mode == "32" else UC_MODE_RISCV64
uc = Uc(UC_ARCH_RISCV, mode)
# Map memory
uc.mem_map(0x0, 0x1000, UC_PROT_ALL)
uc.mem_write(CODE_DST_ADDR, code)
uc.mem_map(STACK_BASE, STACK_SIZE, UC_PROT_ALL)
uc.mem_map(INPUT_ADDR, INPUT_MAP_SIZE, UC_PROT_ALL)
uc.mem_map(KEY_ADDR, KEY_MAP_SIZE, UC_PROT_ALL)
# Seed data
user_bytes = args.input.encode("ascii", errors="ignore")
if len(user_bytes) > 48:
user_bytes = user_bytes[:48]
uc.mem_write(INPUT_ADDR, user_bytes + b"\x00")
uc.mem_write(KEY_ADDR, key)
# Set regs (match the binary: raw numeric IDs)
uc.reg_write(REG_STACK, STACK_INIT)
uc.reg_write(REG_ARG1, INPUT_ADDR)
uc.reg_write(REG_ARG2, KEY_ADDR)
if args.trace:
state = {"n": 0}
def _hook_code(_uc, address, size, _user):
n = state["n"]
if n < args.trace_count:
print(f"[trace] pc=0x{address:X} size={size}")
state["n"] = n + 1
uc.hook_add(UC_HOOK_CODE, _hook_code)
# vm_run(engine, begin=0, until=662, timeout=2000000, count=0)
try:
uc.emu_start(0x0, CODE_SIZE, timeout=2_000_000, count=0)
except UcError as e:
print(f"[emu] stopped with error: {e}")
r11 = uc.reg_read(REG_ARG1)
print(f"[result] reg11(A0) = 0x{r11:X}")
if r11 != 0:
print("[result] looks like SUCCESS path (binary checks reg11!=0)")
else:
print("[result] looks like FAIL path (binary checks reg11!=0)")
return 0
if __name__ == "__main__":
raise SystemExit(main())
根据xor的特点,我们如果输入全是0,栈上得到的就是mask
1
0x104d0e05, 0x0b773197, 0x06eb3870, 0x0ba1a82e, 0x18f13e8, 0x06f98421, 0x0b8a4d0e, 0x08b41876, 0x0160d893, 0x0ce8c305, 0x12a0da32, 0x091559d9
至于密文,得到的方式好多,直接分析分析dump的数据也行,用unicorn hook那几个对比地址把对比的值输出也行也行
最后脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
from __future__ import annotations
import struct
def u32(x: int) -> int:
return x & 0xFFFFFFFF
# 来自前面提取到的数据:
# mask_u32:VM 在输入全 0 时生成/写出的 12 个 dword(小端)
MASK_U32 = [
0x104D0E05,
0x0B773197,
0x06EB3870,
0x0BA1A82E,
0x018F13E8,
0x06F98421,
0x0B8A4D0E,
0x08B41876,
0x0160D893,
0x0CE8C305,
0x12A0DA32,
0x091559D9,
]
# expected_u32:RISC-V 校验阶段硬编码的 12 个目标常量(小端 dword)
EXPECTED_U32 = [
0x45034F63,
0x534762D2,
0x44B36D04,
0x44C3ED6A,
0x79BB60B0,
0x42A1E767,
0x3EDB7E6C,
0x30E1551D,
0x4D3ABAA4,
0x6AA29948,
0x51CE8847,
0x51623FAF,
]
def derive_input_bytes() -> bytes:
if len(MASK_U32) != 12 or len(EXPECTED_U32) != 12:
raise ValueError("MASK_U32/EXPECTED_U32 must be 12 dwords each")
inp_u32 = [u32(m ^ e) for m, e in zip(MASK_U32, EXPECTED_U32)]
return struct.pack("<12I", *inp_u32)
def main() -> int:
b = derive_input_bytes()
try:
s = b.decode("ascii")
except UnicodeDecodeError:
# 如果不是纯可打印 ASCII,就用 \x?? 形式展示
s = "".join(chr(x) if 32 <= x < 127 else f"\\x{x:02x}" for x in b)
print("[input-bytes-hex]", b.hex())
print("[flag-body]", s)
print("[flag]", f"flag{{{s}}}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
运行结果:flag{fANUES0XtUXBDEbOXs4xFcXDb3Q5kMU87bZLMZJfuRnCvfwX}
后记
就喜欢这种加密简单的逆向
唉,要是早点知道是unicorn就好了。。。
这次也是有点遗憾,我还是太菜了。。。没ai我和区一样