前言

这也是受到此大佬博客:https://seeker-fang.github.io/的启发,决定来一波省赛题目复现。

具体这是什么比赛,我也不知道,总之是一个省级赛事,类似于河南的御网杯应该,难度也不算特别大(我说前两题)

基本上花一天一夜给复现了,还是有一点菜

Re-1 你是我的天命人吗

首先查壳,没有壳

image-20260318205256338

看到这个函数基本上就可确定这是一个SMC了。

为什么这么说呢?

首先,where_flag函数本身是反编译不出来的

其次,这个函数一般是和内存某部分的权限(如可读可写等)相关。第三个参数就是一个内存保护常量,0x40是宏\#define PAGE_EXECUTE_READWRITE 0x40 给的权限是可读可写可执行

对于这种SMC,除了用python写脚本改代码,还可以直接动调来写

1
2
3
4
5
start=0x1400018C5 
for i in range(648):
byte=ida_bytes.get_byte(start+i)
ida_bytes.patch_byte(start+i,byte^0x33)
print("success!")

主要用的ida_bytes的字节读写api。

然后就会变得很养眼:
image-20260318210442415

下面演示动调做法。直接在关键语句下一个断点即可

image-20260318210505115

运行之,按U解除定义,按C转为代码,按P定义为函数

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
int where_flag()
{
char Buffer[2]; // [rsp+2Dh] [rbp-53h] BYREF
char v2; // [rsp+2Fh] [rbp-51h]
char Destination[8]; // [rsp+30h] [rbp-50h] BYREF
__int64 v4; // [rsp+38h] [rbp-48h]
_BYTE buf[1008]; // [rsp+40h] [rbp-40h] BYREF
char Str2[82]; // [rsp+430h] [rbp+3B0h] BYREF
__int16 v7; // [rsp+482h] [rbp+402h]
int v8; // [rsp+484h] [rbp+404h]
__int64 v9; // [rsp+488h] [rbp+408h]
int v10; // [rsp+490h] [rbp+410h]
char input[103]; // [rsp+4A0h] [rbp+420h] BYREF
char v12; // [rsp+507h] [rbp+487h]
int i_1; // [rsp+508h] [rbp+488h]
int i; // [rsp+50Ch] [rbp+48Ch]

printf(
"yyyyyyyyyyyyyoooooooooooouuuuuuuuuuuuuuuuffffffffffffffffffffffiiiiiiiiiiiiiiiiiiinnnnnnnnnnndddddddddddddddddddmeee"
"eeeeeeemememememeem!!!\n");
printf("the flag is what you input,xdxd~~(#^.^#)\n");
printf(",;,,;\n");
printf(",;;'( 马\n");
printf(" ' ''~~'~' /'.) 到\n");
printf(",;' ) / |. ┇\n");
printf(",;' /-.,,( ) 成\n");
printf(" ) / ) / ) ┇ \n");
printf(" || || ) 功\n");
printf(" (_ (_')\n");
scanf("%s", input);
strcpy(Str2, "4440514050437D3E386C3A3F3E6F386D232124202D7420742C2F2E282525782D124344471015405A");
Str2[81] = 0;
v7 = 0;
v8 = 0;
v9 = 0;
v10 = 0;
*(_QWORD *)Destination = 0;
v4 = 0;
memset(buf, 0, sizeof(buf));
i_1 = strlen(input);
for ( i = 0; i < i_1; ++i )
{
v12 = input[i];
input[102] = i;
*(_WORD *)Buffer = 0;
v2 = 0;
sprintf(Buffer, "%02X", (unsigned __int8)i ^ (unsigned __int8)v12);
strcat(Destination, Buffer);
}
if ( !strcmp(Destination, Str2) )
return printf("you win ,I surrender\n");
else
return printf("You are not a destined persono(╥﹏╥)o\n");
}

看出来是一个简单的异或加密,解密即可

exp:

1
2
3
4
5
6
cipher=bytes.fromhex("4440514050437D3E386C3A3F3E6F386D232124202D7420742C2F2E282525782D124344471015405A")
flag=''
for i in range(len(cipher)):
flag+=chr(cipher[i]^i)
print(flag)
#DASCTF{90e042b6b30639a6c464398f22bfd40f}

总结:简单的SMC,恐怕属于签到题

这里本人太无知了,python有一个内置库binascii,功能:

二进制数据各种文本表示形式之间的转换,

比如:

十六进制字符串 → 原始字节 binascii.unhexlify()

原始字节 → 十六进制字符串 binascii.hexlify()

二进制 -> base64 形式 binascii.b2a_base64

base64 -> 二进制 binascii.a2b_base64

CRC 校验 binascii.crc32(data)

搬运的我师傅的博客:

1
2
3
4
5
6
7
import binascii
enc=bytearray(binascii.unhexlify('4440514050437D3E386C3A3F3E6F386D232124202D7420742C2F2E282525782D124344471015405A'))
print(enc)
flag=''
for i in range(len(enc)):
flag+=chr(enc[i]^i)
print(flag)

另一种:

1
2
3
4
5
6
7
enc=bytes.fromhex('4440514050437D3E386C3A3F3E6F386D232124202D7420742C2F2E282525782D124344471015405A')
print(enc)
flag=''
for i in range(len(enc)):
flag+=chr(enc[i]^i)
print(flag)
#DASCTF{90e042b6b30639a6c464398f22bfd40f}

Re-2 androidtest

这是一个apk文件,对于apk文件怎么查壳等呢?

可以用老牌工具MT管理器,抑或是pc端专门查壳的工具,我这里就选择不查壳,直接硬刚。我目前只在取证比赛见过加固的app,说句题外话取证比赛真的很仿真,很多病毒小流氓软件确实是360加固

jadx部分代码没有反编译出来,选择用JEB看看

主逻辑先调用了这个i函数,点进去分析,其中j0.a.a疑似是base64解码。

image-20260318211924303

重命名函数,发现以下内容:
image-20260318212233467

猜测只需要逐个将第一个参数的值进行异或解密就好了

这里用cyberchef实现。

image-20260318212533366

第一段结果:ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=

第二段:0.0.0.0

第三段:divide with zero

第四段:如上图所示

这里没有明显的flag,还得继续往下看代码分析

看到最后有一行:public native boolean ret_str(String arg1)

因此还需要去ida进行分析

这里说一嘴main函数 onCreate里面这部嗯充斥对控件的操作,最后有个设置监听事件需要特别留意,其他的都是和UI相关的

image-20260319123525421

更改变量类型之后(需要现在结构体引入下jni),这个函数就会变得十分清晰。从中可看到,就是一个对比的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
bool __fastcall Java_com_example_myapplication_MainActivity_ret_1str(
JNIEnv *JNINativeInterface,
jobject a2,
jstring a3)
{
int v5; // r6
const char *s1; // r0
const jchar *s1_1; // r4
int v8; // r6

v5 = 0;
s1 = (*JNINativeInterface)->GetStringUTFChars(JNINativeInterface, a3, 0);
if ( s1 )
{
s1_1 = s1;
v8 = strcmp(s1, "NBWX633YNJLU2QSILZBUKSDTJVBFQRLTJVBEQ42YJFPVQ42FL5ZUKQSYJFPESX2YIVBEWUI=");
(*JNINativeInterface)->ReleaseStringChars(JNINativeInterface, a3, s1_1);
return v8 == 0;
}
return v5;
}

JEB交叉引用看这个函数被谁调用,发现正是上文提及的监听函数k类

我们进去分析代码看看

这里有一个判断句,结果是Success!(异或解密之后)

image-20260319125052133

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
case 2: {
MainActivity mainActivity0 = (MainActivity)this.b;
String s = mainActivity0.y.getText().toString();
if(s.length() != 44) {
throw new ArithmeticException(w.i("4nC+/ChD4PbvbaC1NkOy7g==\n", "hhnIlUwmwIE=\n"));
}

int v1 = (byte)s.length();
byte[] arr_b = s.getBytes();
byte[] arr_b1 = new byte[arr_b.length];
for(int v3 = 0; v3 < arr_b.length; ++v3) {
arr_b1[v3] = (byte)(arr_b[v3] ^ v1);
}

StringBuilder plaintext = new StringBuilder();
int v4 = 0;
int v5 = 0;
for(int i = 0; true; ++i) {
base_table = MainActivity.z;
if(i >= arr_b.length) {
break;
}

v5 = v5 << 8 | arr_b1[i] & 0xFF;
v4 += 8;
while(v4 >= 5) {
plaintext.append(base_table.charAt(v5 >>> v4 - 5 & 0x1F));
v4 -= 5;
}
}

if(v4 > 0) {
v6 = base_table.charAt(v5 << 5 - v4 & 0x1F);
plaintext.append(((char)v6));
}

while(plaintext.length() % 8 != 0) {
v6 = 61;
plaintext.append(((char)v6));
}

if(mainActivity0.cmp_str(plaintext.toString())) {
s2 = "7F+v030i4Hk=\n";
s3 = "vyrMsBhRk1g=\n";
}
else {
s2 = "mrVcl8J8\n";
s3 = "/8cu+LBdMiY=\n";
}

Toast.makeText(mainActivity0, w.i(s2, s3), 1).show();
return;
}

从中可看出,这就是一个base32,首先进行长度校验,传入的W.i实际上就是上文解密得到的一个base32表,注意看刚开始还会进行异或操作,所以异或也要算上!

用厨子得到flag

image-20260319125651849

Re-3 VM(待复现)

这是一道虚拟机逆向

image-20260319204442925

main函数存在一个虚拟机函数,一看就是虚拟机

这个虚拟机似乎很多取值是在栈上取值

刚开始需要知道,main函数的三个参数分别是:参数个数,参数数组和环境变量

而且代表参数数组的argv[0]一般是程序名字,所以argv[1]对应第一个命令行参数,以此类推

所以main函数整体我们已经能大概分析出来:

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
__int64 __fastcall main(int argc, char **argv, char **envr)
{
FILE *stream; // rbp
int len_program; // ebx
char *src; // rbp
size_t arg_1_len; // rax
int arg_1_len_1; // ebx
__int64 rdx; // rdx
__int64 r8; // r8
__int64 r9; // r9
int vm_code_full[1552]; // [rsp-1C40h] [rbp-34A8h] BYREF
_BYTE content[1024]; // [rsp-400h] [rbp-1C68h] BYREF
_BYTE vm_code[6208]; // [rsp+0h] [rbp-1868h] BYREF
__int64 v15; // [rsp+1840h] [rbp-28h] BYREF

if ( argc <= 1 )
return 0xFFFFFFFFLL; // 参数个数必须大于等于1
memset(vm_code, 0, sizeof(vm_code));
stream = fopen("program", "rb"); // 应该是VM指令集
fseek(stream, 0, 2);
len_program = ftell(stream);
fseek(stream, 0, 0);
memset(content, 0, sizeof(content));
fread(content, 1u, len_program, stream);
memcpy(vm_code, content, len_program);
src = argv[1];
if ( !*src )
return 0xFFFFFFFFLL;
arg_1_len = strlen(argv[1]);
arg_1_len_1 = arg_1_len; // 第一个命令行参数长度得是小于0x40
if ( arg_1_len > 0x40 )
return 0xFFFFFFFFLL;
memcpy(&vm_code[2048], src, arg_1_len); // 从2048开始赋值参数的值给vm_code
qmemcpy(vm_code_full, vm_code, sizeof(vm_code_full));
vm(arg_1_len_1, &v15, rdx, 0, r8, r9, vm_code_full[0]);
return 0;
}

简单来说就是先读取program的值,然后读取第一个参数,俩合在一起传给VM函数

紧接着我们仔细看看vm函数内部实现:

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
__int64 __fastcall init(
unsigned __int64 *p_STACK[0x1010],
unsigned __int64 *p_STACK[0x1050],
unsigned __int64 *p_STACK[0x810],
int len)
{
unsigned __int64 v4; // rax
__int64 n11; // rax
__m128i si128; // xmm0
__int64 v7; // [rsp+0h] [rbp-44h]
int n6845537; // [rsp+8h] [rbp-3Ch]
__m128i v9[2]; // [rsp+1Ch] [rbp-28h] BYREF

v4 = p_STACK[0x1010][1];
n6845537 = 6845537;
p_STACK[0x1050][v4] = len;
++p_STACK[0x1010][1];
v7 = 0x65446F5465766F4CLL;
for ( n11 = 0; n11 != 11; ++n11 )
*(p_STACK[0x810] + n11 + 64) = *(&v7 + n11);
v9[0] = _mm_load_si128(&xmmword_2070);
si128 = _mm_load_si128(&xmmword_2080);
*(p_STACK[0x810] + 96) = _mm_load_si128(&xmmword_2060);
*(v9 + 14) = si128;
*(p_STACK[0x810] + 97) = _mm_load_si128(v9);
*(p_STACK[0x810] + 1566) = si128;
return n11;
}

刚开始调用了这个函数,看样子是给STACK里面赋了一些值,怀疑是加密时的密钥或者密文。再往下看看虚拟机是怎么实现的

里面调用了一些小的函数形如以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
__int64 __fastcall sub_13E0(__int64 a1, __int64 r9)
{
unsigned __int8 v2; // dl

v2 = *(a1 + 1);
if ( (v2 & 1) != 0 )
{
*(r9 + 8LL * ((*(a1 + 1) >> 1) & 7)) += *(r9 + 8LL * ((v2 >> 4) & 7));
return 2;
}
else
{
*(r9 + 8LL * (*(a1 + 1) >> 1)) += *(a1 + 2);
return 3;
}
}

可看出只是对寄存器进行了赋值操作。其他的都是形如此类

常规对抗VM的思路就是提取指令集,分析其每个指令的含义,自己写一个解释器

但是在这里借鉴了大佬博客:https://seeker-fang.github.io/2026/03/17/浙江省决赛re复现/#25年浙江省决赛re复现

可以用trace的方法跟踪关键流程,拿到关键流程上的信息,进行分析

由于静态分析看到主要进行数值变换的地方在这里:
image-20260323190752091

我们可在这里下断点,让ida每次运行到这里就来一个寄存器的值的输出。看看到底是进行了什么变化

看对应地方的汇编:
image-20260323191017097

注意这里就是r9+rdx*8和rax异或

如何trace呢,直接右键添加断点,然后再次右键点击编辑断点

偷下大佬图片:
![img](2025浙江省决赛re复现/202511212215613.png

image-20260323191246280

我们只要拿到对应的寄存器值就可以了,可写出以下trace脚本:
idx乘以的是8,因此长度为8字节,也就是QWORD

1
2
3
4
5
6
7
8
9
10
11
import idc

r9 = idc.get_reg_value('r9')
rdx = idc.get_reg_value('rdx')
rax = idc.get_reg_value('rax')

op1 = idc.read_dbg_qword(r9+rdx*8)
op2 = rax

print(f'xor {hex(op1)} , {hex(op2)} == {hex(op1^op2)}')

动调需要加上命令行参数才可以。这里我在ubuntu虚拟机运行出现段错误,换了wsl好了

image-20260323202528233

这里可怀疑第一个参数就是输入。因此我们可任意输入

Re-4 U.hap

我不太懂这里的矩阵加密,因此分析也是借助了AI

是一个鸿蒙文件

解压出.abc文件后使用工具分析,我这里用的https://github.com/Sciencekex/-wp-OHapp_re?tab=readme-ov-file博客提及的方法分析

在index里面是以下代码:

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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
package p003entry/src/main/ets/pages;

/* renamed from: &entry/src/main/ets/pages/Index&, reason: invalid class name */
/* loaded from: F:\Desktop\浙江省赛决赛re\re4\U\modules.abc */
public class Index {
public Object pkgName@entry;
public Object isCommonjs;
public Object hasTopLevelAwait;
public Object isSharedModule;
public Object moduleRecordIdx;

/* JADX WARN: Multi-variable type inference failed */
/* JADX WARN: Type inference failed for: r17v0, types: [&entry/src/main/ets/pages/Index&] */
/* JADX WARN: Type inference failed for: r24v36 */
/* JADX WARN: Type inference failed for: r24v60, types: [int] */
public Object Index(Object functionObject, Object newTarget, Index this, Object arg0, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5) {
obj = arg3;
obj2 = arg4;
if ((0 == obj ? 1 : 0) != 0) {
obj = -1;
}
if ((0 == obj2 ? 1 : 0) != 0) {
obj2 = null;
}
obj3 = super(arg0, arg2, obj, arg5);
if (("function" == typeof(obj2) ? 1 : 0) != 0) {
obj3.paramsGenerator_ = obj2;
}
obj3.__message = ObservedPropertySimplePU("一个非常粗糙的界面", obj3, "message");
obj3.__inputValue = ObservedPropertySimplePU("", obj3, "inputValue");
obj3.arc4Key = "HarMonyOS_S3cur3_K3y!2025";
obj3.theSecondKey = Uint8Array(createarraywithbuffer([90, 60, 231, 145, 47]));
r24 = [Object];
r24[0] = createarraywithbuffer([1, 2, 3]);
r24[1] = createarraywithbuffer([0, 1, 4]);
r24[2] = createarraywithbuffer([0, 0, 1]);
obj3.MatrixCrypto = r24;
obj3.cipherBase64 = "37L9UF8uNl1TSgYMLIW/RosGPMxVYXNcUoTTQXihX8ZyaQVgxY9Ywz/0fIwRzI4H";
obj3.setInitiallyProvidedValue(arg1);
obj3.finalizeConstruction();
return obj3;
}

public Object message(Object functionObject, Object newTarget, Index this) {
obj = this.__message;
return obj.get();
}

public Object rerender(Object functionObject, Object newTarget, Index this) {
this.updateDirtyElements();
return null;
}

public Object inputValue(Object functionObject, Object newTarget, Index this) {
obj = this.__inputValue;
return obj.get();
}

/* JADX WARN: Type inference failed for: r15v5, types: [boolean, int] */
public Object func_main_0(Object functionObject, Object newTarget, Index this) {
newlexenvwithname([1, "Index", 0], 1);
if (isIn("finalizeConstruction", ViewPU.prototype) == false) {
Reflect.set(ViewPU.prototype, "finalizeConstruction", #9935825373502646411#);
}
Index = ViewPU.Index(Object2, Object3, ViewPU, ["setInitiallyProvidedValue", "&entry/src/main/ets/pages/Index&.setInitiallyProvidedValue", 1, "updateStateVars", "&entry/src/main/ets/pages/Index&.updateStateVars", 1, "purgeVariableDependenciesOnElmtId", "&entry/src/main/ets/pages/Index&.purgeVariableDependenciesOnElmtId", 1, "aboutToBeDeleted", "&entry/src/main/ets/pages/Index&.aboutToBeDeleted", 0, 4]);
obj = Index.prototype;
obj["message"].getter = obj.message;
obj["message"].setter = obj.#5963142812496208016#message;
obj["inputValue"].getter = obj.inputValue;
obj["inputValue"].setter = obj.#10964717288343131102#inputValue;
obj.initialRender = obj.initialRender;
obj.showResultDialog = obj.showResultDialog;
obj.rerender = obj.rerender;
Index.getEntryName = Index.getEntryName;
_lexenv_0_0_ = Index;
registerNamedRoute(#5653493969998192850#, "", createobjectwithbuffer(["bundleName", "com.example.mytestapp", "moduleName", "entry", "pagePath", "pages/Index", "pageFullPath", "entry/src/main/ets/pages/Index", "integratedHsp", "false", "moduleType", "followWithHap"]));
return null;
}

public Object getEntryName(Object functionObject, Object newTarget, Index this) {
return "Index";
}

public Object initialRender(Object functionObject, Object newTarget, Index this) {
newlexenvwithname([1, "this", 0], 1);
_lexenv_0_0_ = this;
ldlexvar = _lexenv_0_0_;
ldlexvar.observeComponentCreation2(#11045136256518667928#, RelativeContainer);
ldlexvar2 = _lexenv_0_0_;
ldlexvar2.observeComponentCreation2(#1459791540083249900#, Column);
ldlexvar3 = _lexenv_0_0_;
ldlexvar3.observeComponentCreation2(#14949563130755530212#, Text);
Text.pop();
ldlexvar4 = _lexenv_0_0_;
ldlexvar4.observeComponentCreation2(#11762078410968332207#, TextArea);
ldlexvar5 = _lexenv_0_0_;
ldlexvar5.observeComponentCreation2(#1873697688084456659#, Button);
Button.pop();
Column.pop();
RelativeContainer.pop();
return null;
}

public Object updateStateVars(Object functionObject, Object newTarget, Index this, Object arg0) {
return null;
}

public Object aboutToBeDeleted(Object functionObject, Object newTarget, Index this) {
obj = this.__message;
obj.aboutToBeDeleted();
obj2 = this.__inputValue;
obj2.aboutToBeDeleted();
Get = SubscriberManager.Get();
Get.delete(this.id__());
this.aboutToBeDeletedInternal();
return null;
}

public Object showResultDialog(Object functionObject, Object newTarget, Index this, Object arg0) {
promptAction = import { default as promptAction } from "@ohos:promptAction";
obj = promptAction.showToast;
obj2 = createobjectwithbuffer(["message", 0, "duration", 2000]);
obj2.message = arg0;
obj(obj2);
return null;
}

public Object #1459791540083249900#(Object functionObject, Object newTarget, Index this, Object arg0, Object arg1) {
Column.create();
return null;
}

public Object #1873697688084456659#(Object functionObject, Object newTarget, Index this, Object arg0, Object arg1) {
Button.createWithLabel("加密并比较");
Button.id("encryptButton");
Button.width("80%");
Button.height(50);
Button.margin(createobjectwithbuffer(["bottom", 20]));
Button.onClick(#4441499115937063224#);
return null;
}

public Object #4441499115937063224#(Object functionObject, Object newTarget, Index this) {
MatrixCrypto = import { default as MatrixCrypto } from "@normalized:N&&&entry/src/main/ets/EUtils&";
if ((_lexenv_0_0_.cipherBase64 == MatrixCrypto.encrypt(_lexenv_0_0_.inputValue, _lexenv_0_0_.MatrixCrypto, _lexenv_0_0_.theSecondKey, _lexenv_0_0_.arc4Key) ? 1 : 0) != 0) {
ldlexvar = _lexenv_0_0_;
ldlexvar.showResultDialog("you are right!");
return null;
}
ldlexvar2 = _lexenv_0_0_;
ldlexvar2.showResultDialog("try again!");
return null;
}

public Object #4589776965841385317#(Object functionObject, Object newTarget, Index this, Object arg0) {
_lexenv_0_0_.inputValue = arg0;
return null;
}

/* JADX WARN: Type inference failed for: r10v0, types: [java.lang.Class] */
public Object #5653493969998192850#(Object functionObject, Object newTarget, Index this) {
return _lexenv_0_0_(null, createemptyobject());
}

public Object #9935825373502646411#(Object functionObject, Object newTarget, Index this) {
return null;
}

public Object #11045136256518667928#(Object functionObject, Object newTarget, Index this, Object arg0, Object arg1) {
RelativeContainer.create();
RelativeContainer.height("100%");
RelativeContainer.width("100%");
return null;
}

public Object #11762078410968332207#(Object functionObject, Object newTarget, Index this, Object arg0, Object arg1) {
obj = TextArea.create;
obj2 = createobjectwithbuffer(["text", 0, "placeholder", "请输入要加密的文本"]);
obj2.text = _lexenv_0_0_.inputValue;
obj(obj2);
TextArea.id("inputField");
TextArea.width("80%");
TextArea.height(50);
TextArea.margin(createobjectwithbuffer(["bottom", 20]));
TextArea.onChange(#4589776965841385317#);
return null;
}

public Object #14949563130755530212#(Object functionObject, Object newTarget, Index this, Object arg0, Object arg1) {
Text.create(_lexenv_0_0_.message);
Text.id("HelloWorld");
Text.fontSize(30);
Text.fontWeight(FontWeight.Bold);
Text.margin(createobjectwithbuffer(["bottom", 20]));
return null;
}

public Object setInitiallyProvidedValue(Object functionObject, Object newTarget, Index this, Object arg0) {
if ((0 != arg0.message ? 1 : 0) != 0) {
this.message = arg0.message;
}
if ((0 != arg0.inputValue ? 1 : 0) != 0) {
this.inputValue = arg0.inputValue;
}
if ((0 != arg0.arc4Key ? 1 : 0) != 0) {
this.arc4Key = arg0.arc4Key;
}
if ((0 != arg0.theSecondKey ? 1 : 0) != 0) {
this.theSecondKey = arg0.theSecondKey;
}
if ((0 != arg0.MatrixCrypto ? 1 : 0) != 0) {
this.MatrixCrypto = arg0.MatrixCrypto;
}
if ((0 != arg0.cipherBase64 ? 1 : 0) == 0) {
return null;
}
this.cipherBase64 = arg0.cipherBase64;
return null;
}

public Object #5963142812496208016#message(Object functionObject, Object newTarget, Index this, Object arg0) {
obj = this.__message;
obj.set(arg0);
return null;
}

public Object #10964717288343131102#inputValue(Object functionObject, Object newTarget, Index this, Object arg0) {
obj = this.__inputValue;
obj.set(arg0);
return null;
}

public Object purgeVariableDependenciesOnElmtId(Object functionObject, Object newTarget, Index this, Object arg0) {
obj = this.__message;
obj.purgeDependencyOnElmtId(arg0);
obj2 = this.__inputValue;
obj2.purgeDependencyOnElmtId(arg0);
return null;
}
}

大概逻辑是输入一串文字,经过加密类加密和密文进行比较

从这个代码可提取出来以下关键信息:

cipherBase64 = “37L9UF8uNl1TSgYMLIW/RosGPMxVYXNcUoTTQXihX8ZyaQVgxY9Ywz/0fIwRzI4H”

arc4Key = “HarMonyOS_S3cur3_K3y!2025”

theSecondKey = [90, 60, 231, 145, 47]

矩阵:

[
[1,2,3],
[0,1,4],
[0,0,1]
]

解密类:

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
public Object encrypt(Object functionObject, Object newTarget, EUtils this, Object arg0, Object arg1, Object arg2, Object arg3) {
EncodeUtils = import { default as EncodeUtils } from "@normalized:N&&&entry/src/main/ets/EncodeUtils&";
stringToUtf8Bytes = EncodeUtils.stringToUtf8Bytes(arg0);
r32 = stringToUtf8Bytes.length;
newobjrange = Uint8Array((4 + r32));
newobjrange[0] = (r32 >> 24) & 255;
newobjrange[1] = (r32 >> 16) & 255;
newobjrange[2] = (r32 >> 8) & 255;
newobjrange[3] = r32 & 255;
newobjrange.set(stringToUtf8Bytes, 4);
ldlexvar = _lexenv_0_0_;
if (ldlexvar.isSquareMatrix(arg1) == false) {
throw(Error("keyMatrix must be square"));
}
ldlexvar2 = _lexenv_0_0_;
if (ldlexvar2.isMatrixInvertibleMod256(arg1) == false) {
throw(Error("keyMatrix not invertible mod 256"));
}
obj = arg1.length;
ldlexvar3 = _lexenv_0_0_;
padToBlock = ldlexvar3.padToBlock(newobjrange, obj);
ldlexvar4 = _lexenv_0_0_;
matrixEncryptBlocks = ldlexvar4.matrixEncryptBlocks(padToBlock, arg1);
XorUtils = import { default as XorUtils } from "@normalized:N&&&entry/src/main/ets/XorUtils&";
xorBytes = XorUtils.xorBytes(matrixEncryptBlocks, arg2);
obj2 = import { default as CryptoJS } from "@normalized:N&&&@ohos/crypto-js/index&2.0.5".RC4;
obj3 = obj2.encrypt;
obj4 = import { default as CryptoJS } from "@normalized:N&&&@ohos/crypto-js/index&2.0.5".lib.WordArray;
create = obj4.create(xorBytes);
obj5 = import { default as CryptoJS } from "@normalized:N&&&@ohos/crypto-js/index&2.0.5".enc.Utf8;
callthisN = obj3(create, obj5.parse(arg3));
toString = callthisN.toString();
CustomBase64 = import { default as CustomBase64 } from "@normalized:N&&&entry/src/main/ets/CustomBase64&";
return CustomBase64.fromStandard(toString);
}

Step 0:字符串 → UTF-8

1
stringToUtf8Bytes(arg0)

👉 得到:

1
P (明文字节)

Step 1:加长度头(4字节,大端)

1
2
3
4
newobjrange[0] = (len >> 24)
newobjrange[1] = (len >> 16)
newobjrange[2] = (len >> 8)
newobjrange[3] = len

👉 得到:

1
[ len(4 bytes) || P ]

Step 2:按矩阵大小 padding

1
padToBlock(data, matrix_size)

矩阵是 3×3,所以:

👉 每 3 字节一组,不够就 padding


Step 3:矩阵加密(核心)

1
matrixEncryptBlocks(data, MatrixCrypto)

矩阵:

1
2
3
[1 2 3]
[0 1 4]
[0 0 1]

👉 本质:

1
C_block = M × P_block  (mod 256)

Step 4:XOR

1
xorBytes(data, [90, 60, 231, 145, 47])

👉 循环 XOR:

1
data[i] ^= key[i % 5]

Step 5:RC4

1
RC4.encrypt(data, "HarMonyOS_S3cur3_K3y!2025")

👉 标准 RC4


Step 6:Base64(变种)

1
CustomBase64.fromStandard(toString)

👉 说明:

  • 先是 CryptoJS 标准 Base64
  • 然后做了一层 字符表替换

我们看看矩阵加密:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public Object matrixEncryptBlocks(Object functionObject, Object newTarget, EUtils this, Object arg0, Object arg1) {
r24 = arg1.length;
newobjrange = Uint8Array(arg0.length);
i = 0;
while (true) {
i2 = i;
if ((i2 < arg0.length ? 1 : 0) == 0) {
return newobjrange;
}
slice = arg0.slice(i2, i2 + r24);
ldlexvar = _lexenv_0_0_;
mulMatrixVectorMod256 = ldlexvar.mulMatrixVectorMod256(arg1, Array.from(slice));
for (i3 = 0; (i3 < r24 ? 1 : 0) != 0; i3 = tonumer(i3) + 1) {
newobjrange[i2 + i3] = mulMatrixVectorMod256[i3] & 255;
}
i = i2 + r24;
}
}


这就是做了个矩阵乘法,函数名已经给了提示

再看看变种Base64:

1
2
3
4
5
6
   public Object static_initializer(Object functionObject, Object newTarget, CustomBase64 this) {
this.CUSTOM_CHARS = "3GHIJKLMzxy01245PQRSTUFabcdefghijklmnopqrstuv6789+/NOVWXYZABCDEw";
this.STANDARD_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
return null;
}
}

码表也给了

让ChatGPT写出脚本如下:

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
import base64

cipher = "37L9UF8uNl1TSgYMLIW/RosGPMxVYXNcUoTTQXihX8ZyaQVgxY9Ywz/0fIwRzI4H"

arc4_key = b"HarMonyOS_S3cur3_K3y!2025"
xor_key = [90, 60, 231, 145, 47]

CUSTOM = "3GHIJKLMzxy01245PQRSTUFabcdefghijklmnopqrstuv6789+/NOVWXYZABCDEw"
STANDARD = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

# ================= CustomBase64 还原 =================
trans = str.maketrans(CUSTOM, STANDARD)
cipher_std = cipher.translate(trans)

# 补 padding
cipher_std += '=' * (-len(cipher_std) % 4)

# ================= RC4 =================
def rc4(data, key):
S = list(range(256))
j = 0
key = list(key)

for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]

i = j = 0
out = []
for b in data:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
k = S[(S[i] + S[j]) % 256]
out.append(b ^ k)

return bytes(out)

# ================= XOR =================
def xor(data):
return bytes([b ^ xor_key[i % len(xor_key)] for i, b in enumerate(data)])

# ================= 矩阵逆 =================
def matrix_inv(data):
res = []
for i in range(0, len(data), 3):
c0, c1, c2 = data[i:i+3]

p2 = c2
p1 = (c1 - 4 * p2) % 256
p0 = (c0 - 2 * p1 - 3 * p2) % 256

res.extend([p0, p1, p2])
return bytes(res)


# ================= 主流程 =================
# Step1: Base64 decode(注意这里只有一层!)
data = base64.b64decode(cipher_std)

# Step2: RC4 解密
data = rc4(data, arc4_key)

# Step3: XOR
data = xor(data)

# Step4: 矩阵逆
data = matrix_inv(data)

# Step5: 去长度头
length = int.from_bytes(data[:4], 'big')
plaintext = data[4:4+length]

print("flag:", plaintext)
print("string:", plaintext.decode(errors="ignore"))