DASCTF 2025上半年赛-re

鱼音乐

直接解Exe后pyc反编译得到

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
# uncompyle6 version 3.9.2
# Python bytecode version base 3.8.0 (3413)
# Decompiled from: Python 3.6.12 (default, Feb 9 2021, 09:19:15)
# [GCC 8.3.0]
# Embedded file name: main.py
import sys, os
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QPushButton, QLabel, QVBoxLayout, QFileDialog, QMessageBox
from PyQt5.QtGui import QPixmap
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent
from PyQt5.QtCore import QUrl
from xianyu_decrypt import load_and_decrypt_xianyu

class MainWindow(QMainWindow):

def __init__(self):
super().__init__()
self.setWindowTitle("Fish Player - 鱼音乐")
self.resize(600, 400)
self.player = QMediaPlayer(self)
self.open_button = QPushButton("打开 .xianyu 文件")
self.open_button.clicked.connect(self.open_xianyu)
self.cover_label = QLabel("专辑封面展示")
self.cover_label.setScaledContents(True)
self.cover_label.setFixedSize(300, 300)
layout = QVBoxLayout()
layout.addWidget(self.open_button)
layout.addWidget(self.cover_label)
container = QWidget()
container.setLayout(layout)
self.setCentralWidget(container)

def open_xianyu(self):
file_path, _ = QFileDialog.getOpenFileName(self, "选择 .xianyu 文件", "", "Xianyu Files (*.xianyu)")
if not file_path:
return
try:
info = load_and_decrypt_xianyu(file_path)
meta = info["meta"]
cover_path = info["cover_path"]
audio_path = info["audio_path"]
if cover_path and os.path.exists(cover_path):
pixmap = QPixmap(cover_path)
self.cover_label.setPixmap(pixmap)
else:
self.cover_label.setText("无封面")
url = QUrl.fromLocalFile(audio_path)
self.player.setMedia(QMediaContent(url))
self.player.play()
name = meta.get("name", "未知")
artist = meta.get("artist", "未知歌手")
fl4g = meta.get("fl4g", "where_is_the_flag?")
FLAG = meta.get("")
QMessageBox.information(self, "音乐提示您", f"正在播放:{name}\n歌手:{artist}\nfl4g:{fl4g}\nFLAG:{FLAG}")
except Exception as e:
try:
QMessageBox.critical(self, "错误", str(e))
finally:
e = None
del e


def main():
app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())


if __name__ == "__main__":
main()

注意到getflag这是空白的,创建一个python3.8的项目试试(因为XYCTFmoon那题非预期直接出了,看看这里会不会有啥信息泄露)

结果直接出了,值得注意的是这里需要把模块重命名为xianyu_decrypt.pyd

1
2
3
4
5
6
7
8
9
10
11
12
from xianyu_decrypt import load_and_decrypt_xianyu

info = load_and_decrypt_xianyu(r'./miao.xianyu')
meta = info["meta"]
cover_path = info["cover_path"]
audio_path = info["audio_path"]

name = meta.get("name", "未知")
artist = meta.get("artist", "未知歌手")
fl4g = meta.get("fl4g", "where_is_the_flag?")
FLAG = meta.get("flag")
print("音乐提示您", f"正在播放:{name}\n歌手:{artist}\nfl4g:{fl4g}\nFLAG:{FLAG}")

image-20250624163850112

看官解看了一下文件应该是从几个part里出的,但是有点麻烦,看官解复现下:

image-20250625165445652

IDA-diff插件-diaphora使用 | fxc233’s blog

我IDA打开并没有像官解中一样有函数名不知道咋回事(这个有人知道可以评论下教教我),看了下,他这个官解写的也不是很好,还主动调用查看key,那我为什么不直接输出info,还要分析pyd,所以不复现这种方法

xuans

做的时候只发现了SM4,自己一直在看是否是SM4魔改了,一开始奇怪了下前面的那一串,没发现是shellcode

写下了如下代码:

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
131
132
133
134
135
136
import struct


def rot_left(wd, bit):
return (wd << bit & 0xffffffff) | (wd >> (32 - bit))


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

key = bytes([
0x39, 0x5A, 0x3B, 0x5D, 0x3C, 0x0A, 0x3E, 0x08, 0x3E, 0x5F,
0x6F, 0x5D, 0x65, 0x07, 0x61, 0x03
])

FK = [0xa3b1bac6, 0x56aa3350, 0x677d9197, 0xb27022dc]

CK = [
0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269,
0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9,
0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249,
0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9,
0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229,
0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299,
0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209,
0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279
]

def bys_to_wd(bys):
ret = 0
for i in range(4):
bits = 24 - i * 8
ret |= (bys[i] << bits)
return ret

def s_box(wd):
ret = []
for i in range(0, 4):
byte = (wd >> (24 - i * 8)) & 0xff
row = byte >> 4
col = byte & 0x0f
index = (row * 16 + col)
ret.append(S_BOX[index])
return bys_to_wd(ret)


def Tx(k1, k2, k3, ck):
xor = k1 ^ k2 ^ k3 ^ ck
t = s_box(xor)
return t ^ rot_left(t, 13) ^ rot_left(t, 23)

def T(x1, x2, x3, rk):
t = x1 ^ x2 ^ x3 ^ rk
t = s_box(t)
return t ^ rot_left(t, 2) ^ rot_left(t, 10) ^ rot_left(t, 18) ^ rot_left(t, 24)

def key_extend(main_key):
MK = [(main_key >> (128 - (i + 1) * 32)) & 0xffffffff for i in range(4)]
keys = [FK[i] ^ MK[i] for i in range(4)]
RK = []
for i in range(32):
k = keys[i] ^ Tx(keys[i + 1], keys[i + 2], keys[i + 3], CK[i])
keys.append(k)
RK.append(k)
return RK

def R(x0, x1, x2, x3):
x0 &= 0xffffffff
x1 &= 0xffffffff
x2 &= 0xffffffff
x3 &= 0xffffffff
s = f"{x3:08x}{x2:08x}{x1:08x}{x0:08x}"
return s

def decode(ciphertext, rk):
ciphertext = int(ciphertext, 16)
X = [ciphertext >> (128 - (i + 1) * 32) & 0xffffffff for i in range(4)]
for i in range(32):
t = T(X[1], X[2], X[3], rk[31 - i])
c = (t ^ X[0])
X = X[1:] + [c]
m = R(X[0], X[1], X[2], X[3])
return m

if __name__ == '__main__':
main_key = bytes([
0x39, 0x5A, 0x3B, 0x5D, 0x3C, 0x0A, 0x3E, 0x08, 0x3E, 0x5F,
0x6F, 0x5D, 0x65, 0x07, 0x61, 0x03
])
main_key = int.from_bytes(main_key, byteorder='big')
rk = key_extend(main_key)

enc = bytes([
0xE4, 0x9F, 0xE4, 0x58, 0x81, 0xB2, 0xE7, 0x93, 0x18, 0xA3,
0x23, 0xD8, 0x09, 0x46, 0x53, 0xE9, 0xC2, 0x6C, 0x62, 0xC5,
0x17, 0x13, 0x3B, 0x28, 0x1E, 0xB9, 0x00, 0x0B, 0x8C, 0xF4,
0x0C, 0xF1
])

block1 = enc[:16].hex()
block2 = enc[16:].hex()

part1 = decode(block1, rk)
part2 = decode(block2, rk)

flag_content = part1 + part1

print(f"Flag bytes: {flag_content}")
print(f"DASCTF{{{bytes.fromhex(flag_content).decode('utf-8',errors='ignore')}}}")

image-20250626190750139

问了GPT,这段代码包含了一个反调试和一段注入shellcode的代码

那串shellcode放入在线网站转汇编

Online x86 and x64 Intel Instruction Assembler

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
0:  55                      push   ebp
1: 48 dec eax
2: 89 e5 mov ebp,esp
4: 48 dec eax
5: 89 7d e8 mov DWORD PTR [ebp-0x18],edi
8: c7 45 fc 0f 00 00 00 mov DWORD PTR [ebp-0x4],0xf
f: eb 3a jmp 0x4b
11: 8b 45 fc mov eax,DWORD PTR [ebp-0x4]
14: 48 dec eax
15: 63 d0 arpl ax,dx
17: 48 dec eax
18: 8b 45 e8 mov eax,DWORD PTR [ebp-0x18]
1b: 48 dec eax
1c: 01 d0 add eax,edx
1e: 0f b6 30 movzx esi,BYTE PTR [eax]
21: 8b 45 fc mov eax,DWORD PTR [ebp-0x4]
24: 48 dec eax
25: 98 cwde
26: 48 dec eax
27: 8d 50 ff lea edx,[eax-0x1]
2a: 48 dec eax
2b: 8b 45 e8 mov eax,DWORD PTR [ebp-0x18]
2e: 48 dec eax
2f: 01 d0 add eax,edx
31: 0f b6 08 movzx ecx,BYTE PTR [eax]
34: 8b 45 fc mov eax,DWORD PTR [ebp-0x4]
37: 48 dec eax
38: 63 d0 arpl ax,dx
3a: 48 dec eax
3b: 8b 45 e8 mov eax,DWORD PTR [ebp-0x18]
3e: 48 dec eax
3f: 01 d0 add eax,edx
41: 31 ce xor esi,ecx
43: 89 f2 mov edx,esi
45: 88 10 mov BYTE PTR [eax],dl
47: 83 6d fc 01 sub DWORD PTR [ebp-0x4],0x1
4b: 83 7d fc 00 cmp DWORD PTR [ebp-0x4],0x0
4f: 7f 7f jg 0xd0
51: c0 90 90 5d c3 00 00 rcl BYTE PTR [eax+0xc35d90],0x0

逻辑就是解密过程是:buf[i] ^= buf[i - 1]

sub_402729是注入shellcode,在跟踪发现是改变了key

1
2
3
4
5
enc = [0x39, 0x5A, 0x3B, 0x5D, 0x3C, 0xA, 0x3E, 0x8, 0x3E, 0x5F, 0x6F, 0x5D, 0x65, 0x7, 0x61, 0x3]
for i in range(len(enc) - 1, 0, -1):
enc[i] ^= enc[i - 1]

print(''.join(chr(i) for i in enc))

9cafa6466a028bfb

image-20250626195841296

假flag说明enc错了

sub_40320C返回一个偏移(loc_4019A0 - v24),可能用于动态 patch,有可能是这修改了

直接去4019A0附近找

image-20250626200157049

这个玩意应该是flag

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
from z3 import *

# Create a solver instance
solver = Solver()

# Define 32 8-bit bit-vectors
# a1 = [BitVec('x[%d]' % i, 32) for i in range(32)]
a1 = [BitVec(f"x{i}", 32) for i in range(32)]

# Add constraints from the C code
solver.add(118 * a1[2] + 173 * a1[0] + 48 * a1[1] + 193 * a1[3] == 66131)
solver.add(196 * a1[1] + 68 * a1[0] + 104 * a1[2] + 10 * a1[3] == 52620)
solver.add(88 * a1[2] + 22 * a1[0] + 37 * a1[1] + 71 * a1[3] == 36011)
solver.add(59 * a1[2] + 141 * a1[1] + 89 * a1[0] + 194 * a1[3] == 61842)

solver.add(175 * a1[6] + 88 * a1[5] + 40 * a1[4] + 89 * a1[7] == 65258)
solver.add(26 * a1[6] + 166 * a1[5] + 82 * a1[4] + 78 * a1[7] == 58176)
solver.add(149 * a1[6] + 73 * a1[4] + 10 * a1[5] + 116 * a1[7] == 62478)
solver.add(176 * a1[6] + 198 * a1[4] + 80 * a1[5] + 193 * a1[7] == 114069)

solver.add(178 * a1[10] + 100 * a1[9] + 83 * a1[8] + 30 * a1[11] == 56170)
solver.add(143 * a1[10] + 148 * (a1[8] + a1[9]) + 168 * a1[11] == 70647)
solver.add(33 * a1[8] + 194 * a1[9] + 10 * a1[10] + 186 * a1[11] == 53174)
solver.add(32 * a1[10] + (a1[8] << 7) + 33 * a1[9] + 152 * a1[11] == 26118)

solver.add(164 * a1[12] + 115 * a1[13] + 184 * a1[14] + 29 * a1[15] == 81254)
# solver.add(184 * a1[14] + 115 * a1[13] + 164 * a1[12] + 29 * a1[15] == 81254)
solver.add(129 * a1[13] + 35 * a1[12] + 129 * a1[14] + 165 * a1[15] == 78646)
solver.add(134 * a1[13] + 54 * a1[12] + 39 * a1[14] + 18 * a1[15] == 29827)
solver.add(80 * a1[12] + 133 * a1[13] + 106 * a1[14] + 43 * a1[15] == 53660)

solver.add(121 * a1[18] + 187 * a1[16] + 32 * a1[17] + 2 * a1[19] == 24667)
solver.add(170 * a1[17] + 66 * a1[16] + 58 * a1[18] + 36 * a1[19] == 44188)
solver.add(103 * a1[16] + 120 * a1[17] + 12 * a1[18] + 175 * a1[19] == 52310)
solver.add(83 * a1[16] + 92 * a1[17] + 129 * a1[18] + 143 * a1[19] == 46020)

solver.add(141 * a1[22] + 54 * a1[21] + 100 * a1[20] + 122 * a1[23] == 66732)
solver.add(85 * a1[21] + 171 * a1[20] + 69 * a1[22] + 7 * a1[23] == 46817)
solver.add((a1[22] << 7) + 197 * a1[20] + 48 * a1[21] + 132 * a1[23] == 83536)
solver.add(181 * a1[21] + 101 * a1[20] + 79 * a1[22] + 144 * a1[23] == 80587)

solver.add(149 * a1[24] + 187 * a1[25] + 24 * a1[26] + 142 * a1[27] == 92687)
solver.add(49 * a1[26] + 86 * a1[25] + 118 * a1[24] + 50 * a1[27] == 49285)
solver.add(164 * a1[26] + 170 * a1[25] + 70 * a1[24] + 193 * a1[27] == 92711)
solver.add(95 * a1[26] + 198 * a1[25] + 96 * a1[24] + a1[27] == 61904)

solver.add(114 * a1[28] + 179 * a1[29] + 37 * a1[30] + 163 * a1[31] == 53864)
solver.add(49 * a1[28] + 94 * a1[29] + 132 * a1[30] + 99 * a1[31] == 36980)
solver.add(43 * a1[28] + 113 * a1[29] + 150 * a1[30] + 128 * a1[31] == 40829)
solver.add(1 * a1[28] + 139 * a1[29] + 115 * a1[30] + 44 * a1[31] == 22448)

# Check for a solution
if solver.check() == sat:
m = solver.model()
result = [m[a1[i]].as_long() % 0xff for i in range(32)]
print(','.join([hex(x) for x in result]))
else:
print("No solution found")

image-20250626202805963

更适合CTF宝宝体质的app

java层没发现什么,就发现Native调用,直接看so找check

发现check

image-20250627101944206

在其他函数也发现了花指令(也不算花指令吧,就是个跳转混淆)

image-20250627110135680

写个批量脚本处理

image-20250627110344867

image-20250627110945295

UC即可

image-20250627111010205

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
from idc import *
from idaapi import *
from idautils import *

# 目标混淆字节序列(8字节)
obfuscation_bytes = bytes([0x00, 0x01, 0x1F, 0xD6, 0x61, 0x14, 0x91, 0xF0])
pattern_len = len(obfuscation_bytes)

# AArch64 NOP 指令 4 字节,小端序
aarch64_nop = bytes([0x1F, 0x20, 0x03, 0xD5]) # 0xD503201F

count = 0

print("Start searching for AArch64 obfuscation patterns...")

for seg in Segments():
seg_start = get_segm_start(seg)
seg_end = get_segm_end(seg)

ea = seg_start
while ea <= seg_end - pattern_len:
data = get_bytes(ea, pattern_len)
if data == obfuscation_bytes:
# patch 两个 AArch64 NOP 指令,覆盖8字节
patch_bytes(ea, aarch64_nop)
patch_bytes(ea + 4, aarch64_nop)
print(f"Patched AArch64 NOP @ {hex(ea)}")
count += 1
ea += pattern_len
else:
ea += 1

print(f"Finished. Total patched blocks: {count}")

官解:

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
import ida_bytes
import ida_segment
import idautils

def remove_patterns():
patterns = [
[0x00, 0x01, 0x1F, 0xD6],
[0x61, 0x14, 0x91, 0xF0]
]

for seg_ea in idautils.Segments():
seg = ida_segment.getseg(seg_ea)
if not seg:
continue

for pattern in patterns:
ea = seg.start_ea
while ea < seg.end_ea - len(pattern) + 1:
match = True
for i, byte_val in enumerate(pattern):
if ida_bytes.get_byte(ea + i) != byte_val:
match = False
break

if match:
pattern_len =20

# 用4字节NOP填充整个模式区域
for i in range(0, pattern_len, 4):
ida_bytes.patch_dword(ea + i, 0xd503201f)


ea += pattern_len
else:
ea += 1

remove_patterns()

找到AES特征

image-20250627114631163

找到AES特征了,下面的代码不知道是啥,不过可以猜到是AES

image-20250627114927553

然后后面就不大会了,得找密钥密文,看了wp,说是白盒AES加密

白盒AES算法详解(一)-密码应用-看雪-安全社区|安全招聘|kanxue.com

使用frida分析白盒aes,DFA攻击 - GGBomb - 博客园

白盒AES和SM4实现的差分故障分析 - ㅤ浮虚千年 - 博客园

稍微学习下

官解:根据很大的索引以及不正常的加密步骤应该可以看得出来这是白盒AES,解密方式就是,先获取密钥

在n==8时改变此时的十六字节中的一个就能获取一份加密错误的密文,相应操作获取大约十组以上的密文就可以通过差分攻击获取密钥,网上有很多文章,这里不多介绍了。

之前翻了下其他代码有ptrace检测,我们可以使用魔改的firda进行hook

image-20250627173120393

image-20250627173207124

init_array是早于JNI_ONload启动的

我想试一下hulda,但是没成功,只能用官解的nop试试

nop掉后hook出了点问题

这是我的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
function hook_native(){
const moduleName = "libBlackMamBa.so";
const so = Process.findModuleByName(moduleName);
if (!so) {
console.log("[!] Module not loaded.");
return;
}
console.log("[*] Module found at: " + so.base);
console.log("[*] Hook started at offset 0X418C");

Interceptor.attach(so.base.add(0X418C), {
onEnter: function (params) {
console.log("=== onEnter ===");

if (this.context["w8"] == 8) {
console.log("[*] Round 8 detected");
var plainchar = this.context["x0"];
console.log("[*] x0: " + plainchar);
Memory.writeU8(plainchar.add(0), 0x01);
}
},
onLeave: function (retval) {}
});
}

setImmediate(hook_native);

解出密钥

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import phoenixAES
with open('tracefile', 'wb') as t:
t.write("""df59d8a5137cfeea687846d0ca070b06
5159d8a5137cfecb687854d0ca480b06
df59d8d8137cd3ea68bb46d0b7070b06
df59eba513a1feeac07846d0ca070b9e
df56d8a5107cfeea6878467eca073b06
df7fd8a54c7cfeea687846a6ca075806
1359d8a5137cfea568788ed0ca970b06
df59d82e137cd1ea686146d063070b06
df59fda51369feea967846d0ca070b3d
df59b6a51315feea557846d0ca070bbb
df56d8a5f07cfeea687846cdca076f06
8059d8a5137cfe29687863d0ca4d0b06
""".encode('utf8'))

phoenixAES.crack_file('tracefile')

400D59138E5C1E1A65598EC3A842D3CA

crc在Java_com_example_test_MainActivity_Get里

密文在xmm里

CRC的异或逻辑crc32_decrypt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
__int64 __fastcall sub_37F0(__int64 a1, __int64 a2, unsigned __int64 i_1, int a4)
{
__int64 result; // x0
unsigned __int64 i; // [xsp+8h] [xbp-38h]
int v9; // [xsp+34h] [xbp-Ch] BYREF
__int64 v10; // [xsp+38h] [xbp-8h]

v10 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
v9 = a4;
result = sub_3750(&v9, 4LL);
for ( i = 0LL; i < i_1; ++i )
*(_BYTE *)(a2 + i) = *(_BYTE *)(a1 + i) ^ ((unsigned int)result >> (8 * (i & 3)));
_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2));
return result;
}

sub_3750跟踪进去可以看到crc和calculate_crc32

1
2
3
4
5
6
7
8
9
10
11
__int64 __fastcall sub_3750(__int64 a1, unsigned __int64 i_1)
{
unsigned __int64 i; // [xsp+10h] [xbp-20h]
unsigned int v4; // [xsp+1Ch] [xbp-14h]

((void (*)(void))loc_3678)();
v4 = -1;
for ( i = 0LL; i < i_1; ++i )
v4 = dword_65FB8[(unsigned __int8)(v4 ^ *(_BYTE *)(a1 + i))] ^ (v4 >> 8);
return ~v4;
}

之后就是官解

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
import struct
from Crypto.Cipher import AES

def crc32_table_init():
"""初始化CRC32查找表"""
CRC32_POLYNOMIAL = 0xEDB88320
table = []

for i in range(256):
crc = i
for j in range(8):
if crc & 1:
crc = (crc >> 1) ^ CRC32_POLYNOMIAL
else:
crc >>= 1
table.append(crc)

return table

def calculate_crc32(data):
"""计算CRC32值"""
table = crc32_table_init()
crc = 0xFFFFFFFF

for byte in data:
crc = table[(crc ^ byte) & 0xFF] ^ (crc >> 8)

return crc ^ 0xFFFFFFFF

def crc32_decrypt(encrypted_data, key):
"""CRC32解密(实际是XOR操作)"""
key_bytes = struct.pack('<I', key) # 小端序
crc_key = calculate_crc32(key_bytes)

decrypted = bytearray()
for i, byte in enumerate(encrypted_data):
# 用CRC值的不同字节循环异或
decrypted.append(byte ^ ((crc_key >> ((i % 4) * 8)) & 0xFF))

return bytes(decrypted)

def aes_decrypt_block(ciphertext, key):
"""标准AES解密"""
cipher = AES.new(key, AES.MODE_ECB)
return cipher.decrypt(ciphertext)

def main():
# 从代码中提取的数据
eninput = bytes([
0x0b,0xcf,0x79,0x7e,0xc7,0xea,0x5f,0x31,
0xbc,0xee,0xe7,0x0b,0x1e,0x91,0xaa,0xdd,
0x6f,0x07,0xc1,0x10,0x36,0x75,0x20,0x06,
0x32,0xe3,0xd0,0x66,0xb8,0x7d,0xfc,0x90
])

# 从注释中的key
key = bytes([
0x44,0x30,0x6e,0x51,0x75,0x31,0x78,0x30,
0x74,0x65,0x35,0x61,0x6e,0x63,0x68,0x30
])

# CRC32 key
crc_key = 0x6c6f7665

print(f"Key (ASCII): {key.decode('ascii', errors='ignore')}")
print(f"CRC Key: 0x{crc_key:08x}")

# 步骤1: CRC32解密
print("\n=== CRC32解密 ===")
after_crc_decrypt = crc32_decrypt(eninput, crc_key)
print(f"CRC32解密后: {after_crc_decrypt.hex()}")

# 步骤2: AES解密
print("\n=== AES解密 ===")
# 分两个16字节块解密
block1 = after_crc_decrypt[:16]
block2 = after_crc_decrypt[16:]

decrypted_block1 = aes_decrypt_block(block1, key)
decrypted_block2 = aes_decrypt_block(block2, key)

result = decrypted_block1 + decrypted_block2
print(f"AES解密后: {result.hex()}")

flag = result.decode('ascii', errors='ignore').rstrip('\x00')
print(f"\n=== 最终结果 ===")
print(f"Flag: {flag}")

if __name__ == "__main__":
main()

Flag: {FALLEN_FLOWERS_FADE_NONE_CARES}