脱壳

010打开

UPX改了特征码,版本5.1.0

直接拖到ida分析不出来

这里我们选择动态调试脱壳,先找OEP,f9运行到入口

触发TLS回调函数断点,停在了ret上

64位UPX壳中不会用一个明显的跨段jmp指令跳向OEP,而是把OEP的地址压入堆栈,然后执行一条ret指令,这样CPU就会把堆栈顶部的OEP地址当作返回地址弹出来,并跳转过去

我们直接f8过去

此时进入了系统DLL地址,不用管,执行到用户代码

标准的UPX壳保存环境的方式,步过4次

此时当前的栈顶已经被修改了,内存中转到RSP,在栈顶位置下硬件断点,然后一直f9到触发硬件断点

此时正在恢复保存的寄存器状态,最后的jmp即是尾部跳转,直接f4过去,然后步过一下

可以看到已经解密完成,0x4014E0就是OEP

打开Scylla插件搜索IAT

找到两个IAT大小,因为这个exe只有几十kb,528字节的输入表正好,选择否,然后获取输入表

dump之后修复得到最终脱壳后的程序

运行报错,但不影响ida分析

第一层EXE

通过ida逆向

可以看到这里是获取了用户的输入并和Str2进行比较,Str 为 NGeQwv8eCRpINEcO

标准的windows可执行文件

如果正确就会通过sub_401660函数进行base64解码并向缓存路径内写入一个随机名称的exe,直接执行原文件输入密码就能得到

第二层EXE

运行发现需要输入key

继续通过ida逆向这个文件,通过main函数找到处理逻辑(sub_401550函数)

分析可知,这里的逻辑也是接收用户输入的key,然后通过nullsub_3函数判断key是否正确

中间的do-while步骤是优化后的strlen函数实现,输入长度不超过240

现在我们的思路就是分析nullsub_3函数,不过nullsub在ida中代表为只有一个ret的空函数,说明针对反编译器做了修改,我们需要看看到底是不是真的空函数

跟进后发现是一个奇怪的hello节,应该就是题目提示了

这里我们选择通过x64dbg进行动态调试,首先加载后找到主逻辑函数地址

查找Enter the key关键字引用

ida中找到地址

输出Faild和Success中间的call就是,直接跳转然后下断点0x401623

然后一直f9运行到等待用户输入,返回窗口随便输入一串key:12345678

跟进

这里可以看到明显是有内容的,分析逻辑如下:

1
2
3
1、逐字节读取刚刚输入的key
2、计算离16字节的填充并补齐,经典的PKCS#7填充
3、为404CB0函数准备参数(rcx、r9、r8)

基本可以判断是AES或者SM4

运行到404F86(调用404CB0函数之前)拿到三个参数

1
2
3
4
5
Key: C2 30 12 AB 39 10 18 33 F8 ED 4E 46 8D A1 5D 8D 8C FB F0 72 68 99 DC 7C 84 6E 7E CF 32 BB DA F8

IV: AE BA 0D BB CA 26 7F 99 06 ED 7C 70 E3 8D 8B 11

CipherText: 9B 5E 1E 8F D7 C3 43 62 A2 37 86 C0 CE 3D 3C F4 C3 B6 88 FF 3C 9C 13 D2 BB 6F 49 CE FF 59 A2 5C 36 E4 61 9E 60 61 C3 BB 3F 63 AF 00 3B 3D 8D A7

然而AES和SM4解密都失败了,说明题目对标准加密流程进行了魔改

跟进404CB0函数内部:

这里往栈上压入了一个参数E(十进制14),而加密轮数14的分组密码只有AES,我们上一步解密失败了,最大的可能就是魔改了标准的S盒

往下看,压栈后调用了404B60函数,直接跟进

这里就是AES的核心算法,先把内存地址 4071E0 装载到了 r14 寄存器里,然后把明文字节当做索引 rdx,去 r14 也就是 4071E0 这个地址里查表替换,这就是 AES 加密中最核心的字节替换操作,那么4071E0地址就是魔改S盒的地址

然而进去后发现就是标准的S盒

接下来就该排查行移位ShiftRows和列混淆MixColumns步骤有没有问题

这里调用了2个函数,分别跟进看一下

404070:

0, 5, 10, 15、13, 4, 9, 14、8, 13, 2, 7

正确对应行移位后的矩阵排列

404190:

0x1B 是 AES 有限域乘法的标准多项式常数,2 和 3 也是标准的乘法矩阵

到这里我们总结一下:

1
2
3
1、标准S盒
2、标准行移位
3、标准列混淆

还剩最后一个密钥扩展算法,这里我们想要重新分析算法就比较复杂了,但是最终是要生成好放进内存,直接在密钥生成器之后在内存中获取即可

重新运行跟进到404B60内部:

在字节替换操作之前,调用了一个404940函数,多半就是轮密钥的生成逻辑,运行到此处步过一次,而生成的轮密钥随后就被存放在了rbx寄存器指向的地址,内存窗口中找到

正好240字节

1
C2 30 12 AB 39 10 18 33 F8 ED 4E 46 8D A1 5D 8D 8C FB F0 72 68 99 DC 7C 84 6E 7E CF 32 BB DA F8 B4 67 53 88 8D 77 4B BB 75 9A 05 FD F8 3B 58 70 CD 19 9A 23 A5 80 46 5F 21 EE 38 90 13 55 E2 68 58 FF 16 F5 D5 88 5D 4E A0 12 58 B3 58 29 00 C3 A7 BC F9 0D 02 3C BF 52 23 D2 87 C2 30 87 65 AA 5C B2 BA F1 89 3A E7 BF 29 28 BF 0C 71 01 BF CF 04 C0 F1 87 06 FC 4E D5 25 2E C9 17 15 A9 AC BD 9A 23 C0 A8 13 19 27 17 3A 31 98 1B 4B 30 27 D4 B7 C4 3D CF B1 38 73 1A 94 16 BA 0D 81 BF 16 B0 8B 64 27 A4 98 7D 00 B3 A2 4C 98 A8 E9 7C BF 7C A9 D4 35 DF 18 EC 46 C5 8C FA FC C8 0D 45 EA 78 E4 E3 9B 73 7C 9E 9B C0 DE D2 03 68 37 AE BC 14 33 30 50 25 2B DC 16 E0 A7 26 EA 28 AA 63 00 50 2E 80 C8 DF 52 1E 53 1F 8C CC 50 77 BB 62 EC 63

编写自定义轮密钥的解密脚本:

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
# 自定义的轮密钥
expanded_key_hex = "C2 30 12 AB 39 10 18 33 F8 ED 4E 46 8D A1 5D 8D 8C FB F0 72 68 99 DC 7C 84 6E 7E CF 32 BB DA F8 B4 67 53 88 8D 77 4B BB 75 9A 05 FD F8 3B 58 70 CD 19 9A 23 A5 80 46 5F 21 EE 38 90 13 55 E2 68 58 FF 16 F5 D5 88 5D 4E A0 12 58 B3 58 29 00 C3 A7 BC F9 0D 02 3C BF 52 23 D2 87 C2 30 87 65 AA 5C B2 BA F1 89 3A E7 BF 29 28 BF 0C 71 01 BF CF 04 C0 F1 87 06 FC 4E D5 25 2E C9 17 15 A9 AC BD 9A 23 C0 A8 13 19 27 17 3A 31 98 1B 4B 30 27 D4 B7 C4 3D CF B1 38 73 1A 94 16 BA 0D 81 BF 16 B0 8B 64 27 A4 98 7D 00 B3 A2 4C 98 A8 E9 7C BF 7C A9 D4 35 DF 18 EC 46 C5 8C FA FC C8 0D 45 EA 78 E4 E3 9B 73 7C 9E 9B C0 DE D2 03 68 37 AE BC 14 33 30 50 25 2B DC 16 E0 A7 26 EA 28 AA 63 00 50 2E 80 C8 DF 52 1E 53 1F 8C CC 50 77 BB 62 EC 63".replace(" ", "")
iv = bytes.fromhex("AEBA0DBBCA267F9906ED7C70E38D8B11")
ct = bytes.fromhex("9B5E1E8FD7C34362A23786C0CE3D3CF4C3B688FF3C9C13D2BB6F49CEFF59A25C36E4619E6061C3BB3F63AF003B3D8DA7")

expanded_key = bytes.fromhex(expanded_key_hex)
round_keys = [list(expanded_key[i:i+16]) for i in range(0, 240, 16)]

# 标准S盒、逆S盒
SBOX = [
0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16
]
INV_SBOX = [0] * 256
for i in range(256): INV_SBOX[SBOX[i]] = i

# 逆向移位
def inv_shift_rows(s):
return [
s[0], s[13], s[10], s[7],
s[4], s[1], s[14], s[11],
s[8], s[5], s[2], s[15],
s[12], s[9], s[6], s[3]
]

def gf_mult(a, b):
p = 0
for _ in range(8):
if b & 1: p ^= a
hi_bit_set = a & 0x80
a = (a << 1) & 0xFF
if hi_bit_set: a ^= 0x1b
b >>= 1
return p

def inv_mix_columns(s):
out = [0] * 16
for i in range(0, 16, 4):
c = s[i:i+4]
out[i] = gf_mult(0x0e, c[0]) ^ gf_mult(0x0b, c[1]) ^ gf_mult(0x0d, c[2]) ^ gf_mult(0x09, c[3])
out[i+1] = gf_mult(0x09, c[0]) ^ gf_mult(0x0e, c[1]) ^ gf_mult(0x0b, c[2]) ^ gf_mult(0x0d, c[3])
out[i+2] = gf_mult(0x0d, c[0]) ^ gf_mult(0x09, c[1]) ^ gf_mult(0x0e, c[2]) ^ gf_mult(0x0b, c[3])
out[i+3] = gf_mult(0x0b, c[0]) ^ gf_mult(0x0d, c[1]) ^ gf_mult(0x09, c[2]) ^ gf_mult(0x0e, c[3])
return out

# AES解密
def decrypt_block(block, round_keys):
state = list(block)
# 第一步:异或最后一轮密钥 (Round 14)
state = [a ^ b for a, b in zip(state, round_keys[14])]

# 中间 13 轮逆向推导
for round_idx in range(13, 0, -1):
state = inv_shift_rows(state)
state = [INV_SBOX[b] for b in state]
state = [a ^ b for a, b in zip(state, round_keys[round_idx])]
state = inv_mix_columns(state)

# 最后一轮 (Round 0)
state = inv_shift_rows(state)
state = [INV_SBOX[b] for b in state]
state = [a ^ b for a, b in zip(state, round_keys[0])]
return state

# CBC 解密链
pt = bytearray()
prev = list(iv)
for i in range(0, len(ct), 16):
block = ct[i:i+16]
dec_block = decrypt_block(block, round_keys)
pt_block = [a ^ b for a, b in zip(dec_block, prev)]
pt.extend(pt_block)
prev = block

# 去除PKCS#7填充
pad_len = pt[-1]
flag = pt[:-pad_len].decode('utf-8', errors='ignore')

print(flag)

FLAG

得到flag:dart{c3d4f5cc-8aab-46ce-a188-2fc453f3b288}