VNCTF 2025 WriteUp

7.7k words

第一次遇到鸿蒙软件逆向,简单学了学之后成功做出来了,好耶

签到题:

欢迎!:

打开容器发现是“文本替换器”,只能执行指定的指令:sed's/{.\*}/ text /g' path,并给出了一个示例文本文档,题目说明flag在环境变量内,首先在右侧路径的位置输入环境变量:/proc/self/environ,返回以下内容:

1
RET2SHELL\_34\_656\_PORT=tcp://10.233.17.192:3000RET2SHELL\_34\_656\_SERVICE\_PORT=3000KUBERNETES\_SERVICE\_PORT=443KUBERNETES\_PORT=tcp://10.233.0.1:443RET2SHELL\_34\_388\_PORT=tcp://10.233.2.153:3000RET2SHELL\_34\_388\_SERVICE\_PORT=3000RET2SHELL\_34\_488\_SERVICE\_PORT=3000RET2SHELL\_34\_488\_PORT=tcp://10.233.29.73:3000HOSTNAME=ret2shell-34-76BUN\_INSTALL\_BIN=/usr/local/binSHLVL=1RET2SHELL\_34\_76\_PORT\_3000\_TCP\_ADDR=10.233.56.123HOME=/home/ctfRET2SHELL\_34\_78\_PORT\_3000\_TCP\_ADDR=10.233.37.202RET2SHELL\_34\_322\_PORT\_3000\_TCP\_ADDR=10.233.46.29RET2SHELL\_34\_76\_PORT\_3000\_TCP\_PORT=3000RET2SHELL\_34\_76\_PORT\_3000\_TCP\_PROTO=tcpRET2SHELL\_34\_78\_PORT\_3000\_TCP\_PORT=3000RET2SHELL\_34\_513\_PORT\_3000\_TCP\_ADDR=10.233.38.34RET2SHELL\_34\_261\_PORT\_3000\_TCP\_ADDR=10.233.32.69RET2SHELL\_34\_370\_PORT\_3000\_TCP\_ADDR=10.233.50.48RET2SHELL\_34\_322\_PORT\_3000\_TCP\_PORT=3000RET2SHELL\_34\_78\_PORT\_3000\_TCP\_PROTO=tcpRET2SHELL\_34\_335\_PORT\_3000\_TCP\_ADDR=10.233.36.17RET2SHELL\_34\_322\_PORT\_3000\_TCP\_PROTO=tcpRET2SHELL\_34\_261\_PORT\_3000\_TCP\_PORT=3000RET2SHELL\_34\_513\_PORT\_3000\_TCP\_PORT=3000RET2SHELL\_34\_642\_PORT\_3000\_TCP\_ADDR=10.233.7.77RET2SHELL\_34\_261\_PORT\_3000\_TCP\_PROTO=tcpRET2SHELL\_34\_513\_PORT\_3000\_TCP\_PROTO=tcpRET2SHELL\_34\_370\_PORT\_3000\_TCP\_PORT=3000RET2SHELL\_34\_76\_SERVICE\_PORT\_SIGNIN\_TEST\_FILEREPLACER=3000RET2SHELL\_34\_652\_PORT\_3000\_TCP\_ADDR=10.233.4.122RET2SHELL\_34\_335\_PORT\_3000\_TCP\_PORT=3000RET2SHELL\_34\_370\_PORT\_3000\_TCP\_PROTO=tcpRET2SHELL\_34\_78\_SERVICE\_PORT\_SIGNIN\_TEST\_FILEREPLACER=3000RET2SHELL\_34\_335\_PORT\_3000\_TCP\_PROTO=tcpRET2SHELL\_34\_654\_PORT\_3000\_TCP\_ADDR=10.233.10.93RET2SHELL\_34\_642\_PORT\_3000\_TCP\_PORT=3000RET2SHELL\_34\_642\_PORT\_3000\_TCP\_PROTO=tcpRET2SHELL\_34\_322\_SERVICE\_PORT\_SIGNIN\_TEST\_FILEREPLACER=3000RET2SHELL\_34\_652\_PORT\_3000\_TCP\_PORT=3000RET2SHELL\_34\_76\_PORT\_3000\_TCP=tcp://10.233.56.123:3000RET2SHELL\_34\_652\_PORT\_3000\_TCP\_PROTO=tcpRET2SHELL\_34\_76\_SERVICE\_HOST=10.233.56.123RET2SHELL\_34\_656\_PORT\_3000\_TCP\_ADDR=10.233.17.192KUBERNETES\_PORT\_443\_TCP\_ADDR=10.233.0.1RET2SHELL\_34\_261\_SERVICE\_PORT\_SIGNIN\_TEST\_FILEREPLACER=3000RET2SHELL\_34\_654\_PORT\_3000\_TCP\_PORT=3000RET2SHELL\_34\_78\_PORT\_3000\_TCP=tcp://10.233.37.202:3000RET2SHELL\_34\_513\_SERVICE\_PORT\_SIGNIN\_TEST\_FILEREPLACER=3000PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/bun-node-fallback-binRET2SHELL\_34\_370\_SERVICE\_PORT\_SIGNIN\_TEST\_FILEREPLACER=3000RET2SHELL\_34\_654\_PORT\_3000\_TCP\_PROTO=tcpRET2SHELL\_34\_388\_PORT\_3000\_TCP\_ADDR=10.233.2.153RET2SHELL\_34\_322\_PORT\_3000\_TCP=tcp://10.233.46.29:3000RET2SHELL\_34\_78\_SERVICE\_HOST=10.233.37.202RET2SHELL\_34\_656\_PORT\_3000\_TCP\_PORT=3000RET2SHELL\_34\_488\_PORT\_3000\_TCP\_ADDR=10.233.29.73RET2SHELL\_34\_322\_SERVICE\_HOST=10.233.46.29RET2SHELL\_34\_335\_SERVICE\_PORT\_SIGNIN\_TEST\_FILEREPLACER=3000KUBERNETES\_PORT\_443\_TCP\_PORT=443RET2SHELL\_34\_261\_PORT\_3000\_TCP=tcp://10.233.32.69:3000KUBERNETES\_PORT\_443\_TCP\_PROTO=tcpRET2SHELL\_34\_656\_PORT\_3000\_TCP\_PROTO=tcpRET2SHELL\_34\_513\_PORT\_3000\_TCP=tcp://10.233.38.34:3000RET2SHELL\_34\_642\_SERVICE\_PORT\_SIGNIN\_TEST\_FILEREPLACER=3000RET2SHELL\_34\_652\_SERVICE\_PORT\_SIGNIN\_TEST\_FILEREPLACER=3000RET2SHELL\_34\_513\_SERVICE\_HOST=10.233.38.34RET2SHELL\_34\_388\_PORT\_3000\_TCP\_PORT=3000RET2SHELL\_34\_370\_PORT\_3000\_TCP=tcp://10.233.50.48:3000RET2SHELL\_34\_261\_SERVICE\_HOST=10.233.32.69RET2SHELL\_34\_335\_PORT\_3000\_TCP=tcp://10.233.36.17:3000RET2SHELL\_34\_388\_PORT\_3000\_TCP\_PROTO=tcpRET2SHELL\_34\_488\_PORT\_3000\_TCP\_PORT=3000RET2SHELL\_34\_370\_SERVICE\_HOST=10.233.50.48RET2SHELL\_34\_76\_SERVICE\_PORT=3000RET2SHELL\_34\_488\_PORT\_3000\_TCP\_PROTO=tcpRET2SHELL\_34\_654\_SERVICE\_PORT\_SIGNIN\_TEST\_FILEREPLACER=3000RET2SHELL\_34\_76\_PORT=tcp://10.233.56.123:3000RET2SHELL\_34\_335\_SERVICE\_HOST=10.233.36.17RET2SHELL\_34\_642\_PORT\_3000\_TCP=tcp://10.233.7.77:3000RET2SHELL\_34\_642\_SERVICE\_HOST=10.233.7.77RET2SHELL\_34\_652\_PORT\_3000\_TCP=tcp://10.233.4.122:3000RET2SHELL\_34\_78\_PORT=tcp://10.233.37.202:3000RET2SHELL\_34\_78\_SERVICE\_PORT=3000RET2SHELL\_34\_656\_SERVICE\_PORT\_SIGNIN\_TEST\_FILEREPLACER=3000RET2SHELL\_34\_652\_SERVICE\_HOST=10.233.4.122RET2SHELL\_34\_322\_PORT=tcp://10.233.46.29:3000RET2SHELL\_34\_322\_SERVICE\_PORT=3000RET2SHELL\_34\_654\_PORT\_3000\_TCP=tcp://10.233.10.93:3000RET2SHELL\_34\_388\_SERVICE\_PORT\_SIGNIN\_TEST\_FILEREPLACER=3000RET2SHELL\_34\_654\_SERVICE\_HOST=10.233.10.93KUBERNETES\_SERVICE\_PORT\_HTTPS=443KUBERNETES\_PORT\_443\_TCP=tcp://10.233.0.1:443RET2SHELL\_34\_261\_PORT=tcp://10.233.32.69:3000RET2SHELL\_34\_488\_SERVICE\_PORT\_SIGNIN\_TEST\_FILEREPLACER=3000RET2SHELL\_34\_513\_SERVICE\_PORT=3000RET2SHELL\_34\_261\_SERVICE\_PORT=3000RET2SHELL\_34\_656\_PORT\_3000\_TCP=tcp://10.233.17.192:3000RET2SHELL\_34\_513\_PORT=tcp://10.233.38.34:3000RET2SHELL\_34\_370\_SERVICE\_PORT=3000RET2SHELL\_34\_656\_SERVICE\_HOST=10.233.17.192RET2SHELL\_34\_370\_PORT=tcp://10.233.50.48:3000RET2SHELL\_34\_335\_SERVICE\_PORT=3000RET2SHELL\_34\_335\_PORT=tcp://10.233.36.17:3000RET2SHELL\_34\_388\_PORT\_3000\_TCP=tcp://10.233.2.153:3000KUBERNETES\_SERVICE\_HOST=10.233.0.1PWD=/home/ctfRET2SHELL\_34\_488\_PORT\_3000\_TCP=tcp://10.233.29.73:3000RET2SHELL\_34\_642\_PORT=tcp://10.233.7.77:3000RET2SHELL\_34\_388\_SERVICE\_HOST=10.233.2.153RET2SHELL\_34\_642\_SERVICE\_PORT=3000RET2SHELL\_34\_488\_SERVICE\_HOST=10.233.29.73RET2SHELL\_34\_652\_SERVICE\_PORT=3000RET2SHELL\_34\_652\_PORT=tcp://10.233.4.122:3000FLAG=VNCTFRET2SHELL\_34\_654\_PORT=tcp://10.233.10.93:3000RET2SHELL\_34\_654\_SERVICE\_PORT=3000NODE\_ENV=productionBUN\_RUNTIME\_TRANSPILER\_CACHE\_PATH=0

注意到了其中的FLAG=VNCTF,后面则是其他的内容,则已经定位到flag了,接下来要绕过sed的替换就很简单了,直接输入\0绕过替换,得到FLAG=VNCTF{W3lc0M3_4nd_HAvE_@-g0OD_tlmE_qfX08kJju3Sr@39@G3V7Pe}.

Crypto方向:

Easymath:

题目给出了一个多项式,是x减去三个质数的数的乘积,则二次项、一次项、常数项系数分别对应-(n0+n1+n2)(n0n1+n1n2+n0n2)n0n1n2,写出脚本求解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from sympy import symbols, solve

x = symbols('x')
a = 15264966144147258587171776703005926730518438603688487721465 # m + n + p
b = 76513250180666948190254989703768338299723386154619468700730085586057638716434556720233473454400881002065319569292923 # mn + mp + np
c = 125440939526343949494022113552414275560444252378483072729156599143746741258532431664938677330319449789665352104352620658550544887807433866999963624320909981994018431526620619 # mnp
polynomial = x**3 - a*x**2 + b*x - c

roots = solve(polynomial, x)
print("The roots (values of m, n, p) are: ", roots)
for i in range(3):
print(roots[i])
n = roots[0]*roots[1]*roots[2]
phin = (roots[0]-1)*(roots[1]-1)*(roots[2]-1)
print(n)
print(phin)

得到解和N:

1
2
3
4
5
The roots (values of m, n, p) are:  
[3868765709106144154703556118635822400623994075212553582411,
5487564316951417093934647798659941512646442958127439071827,
5908636118089697338533572785710162817248001570348495067227]
N=125440939526343949494022113552414275560444252378483072729156599143746741258532431664938677330319449789665352104352620658550544887807433866999963624320909981994018431526620619

然后flag是用e=2加密的,用中国剩余定理求解:

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
from functools import reduce
from sympy import mod_inverse
from Crypto.Util.number import *

n0 = 3868765709106144154703556118635822400623994075212553582411
n1 = 5487564316951417093934647798659941512646442958127439071827
n2 = 5908636118089697338533572785710162817248001570348495067227
c = 24884251313604275189259571459005374365204772270250725590014651519125317134307160341658199551661333326703566996431067426138627332156507267671028553934664652787411834581708944
N = n0 * n1 * n2

def modular_sqrt(a, p):
def legendre_symbol(a, p):
ls = pow(a, (p - 1) // 2, p)
if ls == p - 1:
return -1
return ls

if legendre_symbol(a, p) != 1:
return 0
elif a == 0:
return 0
elif p == 2:
return p
elif p % 4 == 3:
return pow(a, (p + 1) // 4, p)
s = p - 1
e = 0
while s % 2 == 0:
s //= 2
e += 1

n = 2
while legendre_symbol(n, p) != -1:
n += 1

x = pow(a, (s + 1) // 2, p)
b = pow(a, s, p)
g = pow(n, s, p)
r = e

while True:
t = b
m = 0
for m in range(r):
if t == 1:
break
t = pow(t, 2, p)

if m == 0:
return x

gs = pow(g, 2 ** (r - m - 1), p)
g = (gs * gs) % p
x = (x * gs) % p
b = (b * g) % p
r = m

sqrt_n0 = modular_sqrt(c % n0, n0)
sqrt_n1 = modular_sqrt(c % n1, n1)
sqrt_n2 = modular_sqrt(c % n2, n2)

def chinese_remainder_theorem(items):
total = 0
prod = reduce(lambda a, b: a*b, (item[1] for item in items))
for a, n in items:
p = prod // n
total += a * mod_inverse(p, n) * p
return total % prod

possible_flags = []
for sign0 in [1, -1]:
for sign1 in [1, -1]:
for sign2 in [1, -1]:
items = [(sign0*sqrt_n0, n0), (sign1*sqrt_n1, n1),
(sign2*sqrt_n2, n2)]
possible_flag = chinese_remainder_theorem(items)
possible_flags.append(possible_flag)

for i in range(len(possible_flags)):
print(long_to_bytes(possible_flags[i]))

输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
b"9\xb3\x11\x03\*o\xd4v\xbcU@\x14R\xd4\xa5\xf1'nX\xe0V\x0e\x98\x92\x05\x19\x0750\x19\xdc\x8d\xb9\xee\xc9\x90\x00\xe9\xa4\xab:\xc6qh5`\x83G\x80@\n\xf6`\x0b|#\xd9\x90,\xb1\x07\xa5\xcba\x81\xb1\x19f\xcb\xd2\xb8\xc6"

b'>PO\x0eF\x7f\\$\xf8\x9e!\xf6\x05\x11\x0c\xd5v\x0cY\xa3\xe4\xbf\xf4\xe8|y \xa2\xeet\x0bm\\\xf7\x0cX\x80<\x1d\xd06\x9b0\xf5%\x16\xc0\xb6v\x96)\n\xde\x1a\xc5!\xfa%\x11$\xe6h\x84\xc1\xb4\xda\x01\x11\xb5kFS'

b'}\x8f\x96I\xdf\xd5j\xfa\xff\xe2N\x02\x9a@\x90vO\xef\xe9\xa7<7\xb72\xa5\xeb6\x88\x19\xbcs\xec\xb2H\xd4\xe1\_\x8b\xab\xfd\xdc\x9b:\n\xad\x15\xa3m\xd1k\xceb\x00\xb3\x06P\x99\xf1\x93\xc6z/\x888\x9dI\x91h\xf4Ex\xbb'

b'VNCTF{90dcfb2dfb21a21e0c8715cbf3643f4a47d3e2e4b3f7b7975954e6d9701d9648}'

b'\x81\x807\xceSW\xfc6\xdbbi\x17\x88\x18.\x8d\xda)\x87\xa8f\x86I(V\xda\xe1\x93mO\xdd\xff\xee\xe4\xafC\x12ub\xbai\xa7\x92\xcd8\x01x\x18`\xf5}\xb2\x10O\xe0\xe4H\x1c\x0fo\xec)\xcf\*p\x0f\xb0\xa1qu\x95N'

b"\x04F\xef\xc7\xc7\xc9\x0cu\x0b\xe4~{P\n\x02}\xeck\xcfb\\\x7f\xf7&\x14'\xe2<\x88\xf6\xccyo\xd2\x0e\x95\x19\x1e\x17\xf0\xc4p\x8c'\xbdQ\t\x0c\xc2\xef\xe6\xb2F\xd6\x11\xc8\xe7\_\xb0\x0e\xa8^\x80)\x02\xf7\x83q\xb3dU\x10"

b'C\x867\x03a\x1f\x1bK\x13(\xaa\x87\xe59\x86\x1e\xc6O\_e\xb3\xf7\xb9p=\x99\xf8!\xb4?4\xf8\xc5#\xd7\x1d\xf8m\xa6\x1ejp\x95=EO\xeb\xc4\x1d\xc5\x8c\tinR\xf7\x87,2\xb0<%\x83\x9f\xebg\x13\xc8\xf2>\x87x'

b'H#u\x0e}.\xa2\xf9Oq\x8ci\x97u\xed\x03\x14\xed`)B\xa9\x15\xc6\xb4\xfa\x11\x8fr\x99c\xd8h,\x19\xe6w\xc0\x1fCfET\xca5\x06)3\x14\x1b\xaa\x1d\xe7}\x9b\xf5\xa7\xc1\x17$\x1a\xe8=\x00\x1e\x8f\xfbs\xdb\xd7\x15\x05'

则flag为VNCTF{90dcfb2dfb21a21e0c8715cbf3643f4a47d3e2e4b3f7b7975954e6d9701d9648}.

Misc方向:

VN_Lang:

Ida打开,动态调试,搜索字符串,找到flag:VNCTF{CtJBEEFcPvlcJISEfe18ncSWLf4JB8BwlmJD0T0NM2hQQ}.

Reverse方向:

kotlindroid:

代码含有混淆jadx没法正确反编译,用jeb反编译,仔细检查后发现了一个关键的activity:SearchAcitivityKt

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
public final class SearchActivityKt {
private static final Brush gradient;

static {
List list0 = CollectionsKt.listOf(new Color[]{Color.box-impl(0xFFFF000000000000L), Color.box-impl(0xFF0000FF00000000L), Color.box-impl(0xFF00FF0000000000L)});
SearchActivityKt.gradient = Companion.horizontalGradient-8A-3gB4$default(Brush.Companion, list0, 0.0f, 0.0f, 0, 14, null);
}

public static final void Button(String text, Composer $composer, int $changed) {
Intrinsics.checkNotNullParameter(text, "text");
Composer composer1 = $composer.startRestartGroup(0xF00ABC3C);
ComposerKt.sourceInformation(composer1, "C(Button)74@2243L7,75@2255L848:SearchActivity.kt#w7hpty");
if(((($changed & 14) == 0 ? $changed | (composer1.changed(text) ? 4 : 2) : $changed) & 11) == 2 && (composer1.getSkipping())) {
composer1.skipToGroupEnd();
}
else {
ComposerKt.sourceInformationMarkerStart(composer1, 0x789C5F52, "CC:CompositionLocal.kt#9igjgp");
Object object0 = composer1.consume(((CompositionLocal)AndroidCompositionLocals_androidKt.getLocalContext()));
ComposerKt.sourceInformationMarkerEnd(composer1);
composer1.startReplaceableGroup(0xE32F0E82);
ComposerKt.sourceInformation(composer1, "CC(Column)P(2,3,1)77@3865L61,78@3931L133:Column.kt#2w3rfo");
MeasurePolicy measurePolicy0 = ColumnKt.columnMeasurePolicy(Arrangement.INSTANCE.getTop(), Alignment.Companion.getCenterHorizontally(), composer1, 0x30);
composer1.startReplaceableGroup(-1323940314);
ComposerKt.sourceInformation(composer1, "CC(Layout)P(!1,2)78@3182L23,80@3272L420:Layout.kt#80mrfh");
int v1 = ComposablesKt.getCurrentCompositeKeyHash(composer1, 0);
CompositionLocalMap compositionLocalMap0 = composer1.getCurrentCompositionLocalMap();
Function0 function00 = ComposeUiNode.Companion.getConstructor();
Function3 function30 = LayoutKt.modifierMaterializerOf(AlignmentLineKt.paddingFromBaseline-VpY3zN4(((Modifier)Modifier.Companion), 800.0f, 16.0f));
if(!(composer1.getApplier() instanceof Applier)) {
ComposablesKt.invalidApplier();
}

composer1.startReusableNode();
if(composer1.getInserting()) {
composer1.createNode(function00);
}
else {
composer1.useNode();
}

Composer composer2 = Updater.constructor-impl(composer1);
Updater.set-impl(composer2, measurePolicy0, ComposeUiNode.Companion.getSetMeasurePolicy());
Updater.set-impl(composer2, compositionLocalMap0, ComposeUiNode.Companion.getSetResolvedCompositionLocals());
Function2 function20 = ComposeUiNode.Companion.getSetCompositeKeyHash();
if((composer2.getInserting()) || !Intrinsics.areEqual(composer2.rememberedValue(), Integer.valueOf(v1))) {
composer2.updateRememberedValue(Integer.valueOf(v1));
composer2.apply(Integer.valueOf(v1), function20);
}

function30.invoke(SkippableUpdater.box-impl(SkippableUpdater.constructor-impl(composer1)), composer1, Integer.valueOf(0));
composer1.startReplaceableGroup(2058660585);
ComposerKt.sourceInformationMarkerStart(composer1, 0x107E0298, "C79@3979L9:Column.kt#2w3rfo");
ColumnScope $this$Button_u24lambda_u247 = (ColumnScope)ColumnScopeInstance.INSTANCE;
ComposerKt.sourceInformationMarkerStart(composer1, 0x86B909DE, "C83@2477L620:SearchActivity.kt#w7hpty");
ButtonKt.Button(() -> {
Intrinsics.checkNotNullParameter(text, "$text");
Intrinsics.checkNotNullParameter(((Context)object0), "$context");
Collection destination$iv$iv = (Collection)new ArrayList(8);
for(int v1 = 0; v1 < 8; ++v1) {
destination$iv$iv.add(Byte.valueOf(((byte)(new byte[]{0x76, 99, 101, 0x7E, 0x7C, 0x72, 110, 100}[v1] ^ 23))));
}

byte[] arr_b = CollectionsKt.toByteArray(((Collection)(((List)destination$iv$iv))));
Collection destination$iv$iv = (Collection)new ArrayList(8);
for(int v = 0; v < 8; ++v) {
destination$iv$iv.add(Byte.valueOf(((byte)(new byte[]{0x7B, 0x71, 109, 99, 97, 0x7A, 0x7C, 105}[v] ^ 8))));
}

SearchActivityKt.check(text, ((Context)object0), ArraysKt.plus(arr_b, CollectionsKt.toByteArray(((Collection)(((List)destination$iv$iv))))));
return Unit.INSTANCE;
}, // onClick:kotlin.jvm.functions.Function0
SizeKt.fillMaxWidth$default(PaddingKt.padding-3ABfNKs(((Modifier)Modifier.Companion), 16.0f), 0.0f, 1, null), // modifier:androidx.compose.ui.Modifier
false, // enabled:boolean
null, // shape:androidx.compose.ui.graphics.Shape
null, // colors:androidx.compose.material3.ButtonColors
null, // elevation:androidx.compose.material3.ButtonElevation
null, // border:androidx.compose.foundation.BorderStroke
null, // contentPadding:androidx.compose.foundation.layout.PaddingValues
null, // interactionSource:androidx.compose.foundation.interaction.MutableInteractionSource
ComposableSingletons.SearchActivityKt.INSTANCE.getLambda-4$app_debug(), // content:kotlin.jvm.functions.Function3
composer1, // $composer:androidx.compose.runtime.Composer
0x30000030, // $changed:int
508 // v1:int
);
ComposerKt.sourceInformationMarkerEnd(composer1);
ComposerKt.sourceInformationMarkerEnd(composer1);
composer1.endReplaceableGroup();
composer1.endNode();
composer1.endReplaceableGroup();
composer1.endReplaceableGroup();
}

ScopeUpdateScope scopeUpdateScope0 = composer1.endRestartGroup();
if(scopeUpdateScope0 != null) {
scopeUpdateScope0.updateScope((Composer composer0, /* MISSING LAMBDA PARAMETER */) -> {
Intrinsics.checkNotNullParameter(text, "$text");
SearchActivityKt.Button(text, composer0, RecomposeScopeImplKt.updateChangedFlags($changed | 1));
return Unit.INSTANCE;
});
}
}

// Detected as a lambda impl.
private static final Unit Button$lambda$7$lambda$6(String $text, Context $context) {
Intrinsics.checkNotNullParameter($text, "$text");
Intrinsics.checkNotNullParameter($context, "$context");
Collection destination$iv$iv = (Collection)new ArrayList(8);
for(int v1 = 0; v1 < 8; ++v1) {
destination$iv$iv.add(Byte.valueOf(((byte)(new byte[]{0x76, 99, 101, 0x7E, 0x7C, 0x72, 110, 100}[v1] ^ 23))));
}

byte[] arr_b = CollectionsKt.toByteArray(((Collection)(((List)destination$iv$iv))));
Collection destination$iv$iv = (Collection)new ArrayList(8);
for(int v = 0; v < 8; ++v) {
destination$iv$iv.add(Byte.valueOf(((byte)(new byte[]{0x7B, 0x71, 109, 99, 97, 0x7A, 0x7C, 105}[v] ^ 8))));
}

SearchActivityKt.check($text, $context, ArraysKt.plus(arr_b, CollectionsKt.toByteArray(((Collection)(((List)destination$iv$iv))))));
return Unit.INSTANCE;
}
/*
String Decryptor: 1 succeeded, 1 failed
- Failure: access$generateIV()<byte[]> @ Lcom/atri/ezcompose/SearchActivityKt$sec$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;+76h
*/
public static final byte[] access$generateIV() {
return new byte[]{49, 49, 52, 53, 49, 52};
}

private static final void check(String text, Context context, byte[] key) {
SearchActivityKt.sec(context, new SecretKeySpec(key, "AES"), text, (String flag) -> {
Intrinsics.checkNotNullParameter(context, "$context");
Intrinsics.checkNotNullParameter(flag, "flag");
if(Intrinsics.areEqual(flag, "MTE0NTE0HMuJKLOW1BqCAi2MxpHYjGjpPq82XXQ/jgx5WYrZ2MV53a9xjQVbRaVdRiXFrSn6EcQPzA==")) {
Toast.makeText(context, ((CharSequence)"Congratulations! :)"), 0).show();
return Unit.INSTANCE;
}

Toast.makeText(context, ((CharSequence)"Wrong :("), 0).show();
return Unit.INSTANCE;
});
}

// Detected as a lambda impl.
private static final Unit check$lambda$14(Context $context, String flag) {
Intrinsics.checkNotNullParameter($context, "$context");
Intrinsics.checkNotNullParameter(flag, "flag");
if(Intrinsics.areEqual(flag, "MTE0NTE0HMuJKLOW1BqCAi2MxpHYjGjpPq82XXQ/jgx5WYrZ2MV53a9xjQVbRaVdRiXFrSn6EcQPzA==")) {
Toast.makeText($context, ((CharSequence)"Congratulations! :)"), 0).show();
return Unit.INSTANCE;
}

Toast.makeText($context, ((CharSequence)"Wrong :("), 0).show();
return Unit.INSTANCE;
}

private static final Cipher createCipher() {
Cipher cipher0 = Cipher.getInstance("AES/GCM/NoPadding");
Intrinsics.checkNotNullExpressionValue(cipher0, "getInstance(...)");
return cipher0;
}

// String Decryptor: 1 succeeded, 0 failed
private static final byte[] generateIV() {
byte[] arr_b = "114514".getBytes(Charsets.UTF_8);
Intrinsics.checkNotNullExpressionValue(arr_b, "getBytes(...)");
return arr_b;
}

private static final GCMParameterSpec getGCMParameterSpec(byte[] iv) {
return new GCMParameterSpec(0x80, iv);
}

public static final Brush getGradient() {
return SearchActivityKt.gradient;
}

private static final void sec(Context context, SecretKeySpec secretKey, String text, Function1 onResult) {
BuildersKt__Builders_commonKt.launch$default(CoroutineScopeKt.CoroutineScope(((CoroutineContext)Dispatchers.getIO())), null, null, ((Function2)new SearchActivityKt.sec.1(secretKey, text, onResult, null)), 3, null);
}
}

观察得到很明显的AES-GCM加密,密钥是(new byte[]{0x76, 99, 101, 0x7E, 0x7C, 0x72, 110, 100}[v1] ^ 23)(new byte[]{0x7B, 0x71, 109, 99, 97, 0x7A, 0x7C, 105}[v] ^ 8)的拼合(atrikeyssyekirta),iv是new byte[]{49, 49, 52, 53, 49, 52}114514),密文base64是“MTE0NTE0HMuJKLOW1BqCAi2MxpHYjGjpPq82XXQ/jgx5WYrZ2MV53a9xjQVbRaVdRiXFrSn6EcQPzA==”,动态调试发现密文的前6字节是iv,后16字节是GCM模式特有的验证tag,至此把内容全部输入cyberchef解密发现并不正确,接着动态调试,找到一个关键的加密函数SearchActivityKt.sec.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
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
final class SearchActivityKt.sec.1 extends SuspendLambda implements Function2 {
final Function1 $onResult;
final SecretKeySpec $secretKey;
final String $text;
int label;

SearchActivityKt.sec.1(SecretKeySpec secretKeySpec0, String s, Function1 function10, Continuation continuation0) {
this.$secretKey = secretKeySpec0;
this.$text = s;
this.$onResult = function10;
super(2, continuation0);
}

@Override // kotlin.coroutines.jvm.internal.BaseContinuationImpl
public final Continuation create(Object object0, Continuation continuation0) {
return (Continuation)new SearchActivityKt.sec.1(this.$secretKey, this.$text, this.$onResult, continuation0);
}

@Override // kotlin.jvm.functions.Function2
public Object invoke(Object object0, Object object1) {
return this.invoke(((CoroutineScope)object0), ((Continuation)object1));
}

public final Object invoke(CoroutineScope coroutineScope0, Continuation continuation0) {
return ((SearchActivityKt.sec.1)this.create(coroutineScope0, continuation0)).invokeSuspend(Unit.INSTANCE);
}

@Override // kotlin.coroutines.jvm.internal.BaseContinuationImpl
public final Object invokeSuspend(Object object0) {
Object object1 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch(this.label) {
case 0: {
ResultKt.throwOnFailure(object0);
SearchActivityKt.sec.1 this = this;
try {
Cipher cipher0 = SearchActivityKt.createCipher();
byte[] arr_b = {49, 49, 52, 53, 49, 52};
GCMParameterSpec gCMParameterSpec0 = SearchActivityKt.getGCMParameterSpec(arr_b);
cipher0.init(1, ((Key)this.$secretKey), ((AlgorithmParameterSpec)gCMParameterSpec0));
byte[] arr_b1 = JNI.INSTANCE.getAt().getBytes(Charsets.UTF_8);
Intrinsics.checkNotNullExpressionValue(arr_b1, "getBytes(...)");
cipher0.updateAAD(arr_b1);
Charset charset0 = StandardCharsets.UTF_8;
Intrinsics.checkNotNullExpressionValue(charset0, "UTF_8");
byte[] arr_b2 = this.$text.getBytes(charset0);
Intrinsics.checkNotNullExpressionValue(arr_b2, "getBytes(...)");
byte[] arr_b3 = cipher0.doFinal(arr_b2);
Intrinsics.checkNotNull(arr_b3);
byte[] arr_b4 = ArraysKt.plus(arr_b, arr_b3);
String s = Base64.encode$default(((Base64)Base64.Default), arr_b4, 0, 0, 6, null);
Function2 function20 = (Function2)new Function2(s, null) {
final String $flag;
final Function1 $onResult;
int label;

{
this.$onResult = function10;
this.$flag = s;
super(2, continuation0);
}

@Override // kotlin.coroutines.jvm.internal.BaseContinuationImpl
public final Continuation create(Object object0, Continuation continuation0) {
return (Continuation)new com.atri.ezcompose.SearchActivityKt.sec.1.1(this.$onResult, this.$flag, continuation0);
}

@Override // kotlin.jvm.functions.Function2
public Object invoke(Object object0, Object object1) {
return this.invoke(((CoroutineScope)object0), ((Continuation)object1));
}

public final Object invoke(CoroutineScope coroutineScope0, Continuation continuation0) {
return ((com.atri.ezcompose.SearchActivityKt.sec.1.1)this.create(coroutineScope0, continuation0)).invokeSuspend(Unit.INSTANCE);
}

@Override // kotlin.coroutines.jvm.internal.BaseContinuationImpl
public final Object invokeSuspend(Object object0) {
if(this.label != 0) {
throw new IllegalStateException("call to \'resume\' before \'invoke\' with coroutine");
}

ResultKt.throwOnFailure(object0);
this.$onResult.invoke(this.$flag);
return Unit.INSTANCE;
}
};
this.label = 1;
Object object2 = BuildersKt.withContext(((CoroutineContext)Dispatchers.getMain()), function20, ((Continuation)this));
return object2 != object1 ? Unit.INSTANCE : object1;
label_25:
this = this;
ResultKt.throwOnFailure(object0);
return Unit.INSTANCE;
}
catch(Exception exception0) {
Log.e("sec", "Error occurred", ((Throwable)exception0));
Function2 function21 = (Function2)new Function2(null) {
final Function1 $onResult;
int label;

{
this.$onResult = function10;
super(2, continuation0);
}

@Override // kotlin.coroutines.jvm.internal.BaseContinuationImpl
public final Continuation create(Object object0, Continuation continuation0) {
return (Continuation)new com.atri.ezcompose.SearchActivityKt.sec.1.2(this.$onResult, continuation0);
}

@Override // kotlin.jvm.functions.Function2
public Object invoke(Object object0, Object object1) {
return this.invoke(((CoroutineScope)object0), ((Continuation)object1));
}

public final Object invoke(CoroutineScope coroutineScope0, Continuation continuation0) {
return ((com.atri.ezcompose.SearchActivityKt.sec.1.2)this.create(coroutineScope0, continuation0)).invokeSuspend(Unit.INSTANCE);
}

@Override // kotlin.coroutines.jvm.internal.BaseContinuationImpl
public final Object invokeSuspend(Object object0) {
if(this.label != 0) {
throw new IllegalStateException("call to \'resume\' before \'invoke\' with coroutine");
}

ResultKt.throwOnFailure(object0);
this.$onResult.invoke("Error occurred");
return Unit.INSTANCE;
}
};
this.label = 2;
return BuildersKt.withContext(((CoroutineContext)Dispatchers.getMain()), function21, ((Continuation)this)) != object1 ? Unit.INSTANCE : object1;
}
}
case 1: {
goto label_25;
}
case 2: {
ResultKt.throwOnFailure(object0);
return Unit.INSTANCE;
}
default: {
throw new IllegalStateException("call to \'resume\' before \'invoke\' with coroutine");
}
}
}
}

注意到其中有AAD的加入,动态调试到JNI那一行找到AAD:string@24461:“mysecretadd”,带入解密,成功解出,得到flag:VNCTF{Y0U_@re_th3_Ma5t3r_0f_C0mp0s3}.

Hook Fish:

观察MainActivity:

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
public class MainActivity extends AppCompatActivity {
private BroadcastReceiver downloadCompleteReceiver;
private long downloadID;
private DownloadManager downloadManager;
private File downloadedFile;
String encodeText;

public MainActivity() {
this.downloadCompleteReceiver = new BroadcastReceiver() {
@Override // android.content.BroadcastReceiver
public void onReceive(Context context, Intent intent) {
long v = intent.getLongExtra("extra_download_id", -1L);
if(MainActivity.this.downloadID == v) {
MainActivity.this.loadClass(MainActivity.this.encodeText);
MainActivity.this.fish_fade();
}
}
};
}

private static void code(char[] a, int index) {
if(index >= a.length - 1) {
return;
}

a[index] = (char)(a[index] ^ a[index + 1]);
a[index + 1] = (char)(a[index] ^ a[index + 1]);
a[index] = (char)(a[index] ^ a[index + 1]);
MainActivity.code(a, index + 2);
}

public String decode(String boy) {
try {
Class class0 = new DexClassLoader(new File(this.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "hook_fish.dex").getAbsolutePath(), this.getCacheDir().getAbsolutePath(), null, this.getClassLoader()).loadClass("fish.hook_fish");
Object object0 = class0.newInstance();
return (String)class0.getMethod("decode", String.class).invoke(object0, boy);
}
catch(Exception e) {
e.printStackTrace();
return "Error";
}
}

public String encode(String girl) {
try {
Class class0 = new DexClassLoader(new File(this.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "hook_fish.dex").getAbsolutePath(), this.getCacheDir().getAbsolutePath(), null, this.getClassLoader()).loadClass("fish.hook_fish");
Object object0 = class0.newInstance();
return (String)class0.getMethod("encode", String.class).invoke(object0, girl);
}
catch(Exception e) {
e.printStackTrace();
return "Error";
}
}

public static String encrypt(String str) {
byte[] arr_b = str.getBytes();
for(int i = 0; i < arr_b.length; ++i) {
arr_b[i] = (byte)(arr_b[i] + 68);
}

StringBuilder hexStringBuilder = new StringBuilder();
for(int v1 = 0; v1 < arr_b.length; ++v1) {
hexStringBuilder.append(String.format("%02x", ((byte)arr_b[v1])));
}

char[] arr_c = hexStringBuilder.toString().toCharArray();
MainActivity.code(arr_c, 0);
for(int i = 0; i < arr_c.length; ++i) {
arr_c[i] = arr_c[i] >= 97 && arr_c[i] <= 102 ? ((char)(arr_c[i] - 49 + i % 4)) : ((char)(arr_c[i] + 55 + i % 10));
}

Log.d("encrypt: ", new String(arr_c));
return new String(arr_c);
}

private void fish(String fileUrl) {
File file = new File(this.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "hook_fish.dex");
DownloadManager downloadManager = (DownloadManager)this.getSystemService("download");
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(fileUrl));
request.setTitle("钓鱼");
request.setDestinationUri(Uri.fromFile(file));
request.setAllowedOverRoaming(false);
request.setAllowedOverMetered(false);
this.downloadID = downloadManager.enqueue(request);
Toast.makeText(this, "Fishing......", 0).show();
}

private void fish_fade() {
new File(this.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "hook_fish.dex").delete();
}

public void loadClass(String input0) {
String s1 = this.encode(input0);
DexClassLoader dLoader = new DexClassLoader(Uri.fromFile(new File(this.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "hook_fish.dex")).toString(), null, null, ClassLoader.getSystemClassLoader().getParent());
try {
Class class0 = dLoader.loadClass("fish.hook_fish");
Object object0 = class0.newInstance();
if(((Boolean)class0.getMethod("check", new Class[]{String.class}).invoke(object0, new Object[]{s1})).booleanValue()) {
Toast.makeText(this, "恭喜,鱼上钩了!", 0).show();
return;
}
}
catch(Exception e) {
e.printStackTrace();
return;
}
}

@Override // androidx.fragment.app.FragmentActivity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(layout.activity_main);
EditText editText = (EditText)this.findViewById(id.editTextText);
String s = this.getString(string.pool);
this.downloadManager = (DownloadManager)this.getSystemService("download");
((Button)this.findViewById(id.download_button)).setOnClickListener(new View.OnClickListener() {
@Override // android.view.View$OnClickListener
public void onClick(View view) {
String s = editText.getText().toString();
if(!s.isEmpty()) {
MainActivity.this.encodeText = MainActivity.encrypt(s);
MainActivity.this.fish(s);
List list0 = Arrays.asList(new String[]{"鲈鱼", "鳕鱼", "甲鱼", "咸鱼", "金鱼", "鲮鱼", "鲅鱼", "鲫鱼", "山椒鱼", "鮰鱼"});
String s1 = "收获一条" + ((String)list0.get(new Random().nextInt(list0.size()))) + ",但是鱼逃走了";
Toast.makeText(MainActivity.this, s1, 0).show();
return;
}

Toast.makeText(MainActivity.this, "请先输入口令,并在联网条件下再钓鱼", 0).show();
}
});
this.registerReceiver(this.downloadCompleteReceiver, new IntentFilter("android.intent.action.DOWNLOAD_COMPLETE"));
}

@Override // androidx.appcompat.app.AppCompatActivity
protected void onDestroy() {
super.onDestroy();
this.unregisterReceiver(this.downloadCompleteReceiver);
}
}

可以看到一个加密和比较的过程,还有一个dex在比较时联网下载,先用frida把dex在动态调试时dump出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import frida
import sys
import os

def on_message(message, data):
if message['type'] == 'send':
payload = message['payload']
if payload.get('type') == 'dex_content':
dex_content = bytes(payload.get('content'))
output_path = "E:/CTF/hook_fish_dumped.dex"
with open(output_path, 'wb') as f:
f.write(dex_content)
print(f"Dex dumped to {os.path.abspath(output_path)}")

# JavaScript注入代码
jscode = """
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
Java.perform(function() {
var File = Java.use('java.io.File');
var FileInputStream = Java.use('java.io.FileInputStream');
var ByteArrayOutputStream = Java.use('java.io.ByteArrayOutputStream');
File.delete.overload().implementation = function() {
var path = this.getAbsolutePath();
if (path.endsWith('hook_fish.dex')) {
console.log('Detected deletion of Dex file: ' + path);
if (!this.exists()) {
console.error("File does not exist: " + path);
return this.delete();
}
if (!this.canRead()) {
console.error("No read permission for the file: " + path);
return this.delete();
}
try {
var fis = FileInputStream.$new(this);
console.log("File opened successfully.");
var bos = ByteArrayOutputStream.$new();
var buffer = Java.array('byte', Array(1024).fill(0));
var bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
bos.write(buffer, 0, bytesRead);
}
fis.close();
var byteArray = bos.toByteArray();
var resultArray = [];
for (var i = 0; i < byteArray.length; i++) {
resultArray.push(byteArray[i] & 0xFF);
}
bos.close();
send({type: "dex_content", content: resultArray});
} catch (e) {
console.error("Exception occurred while reading file: " + e);
console.error("Stack trace: " + (e.stack ? e.stack : "No stack trace available"));
return this.delete();
}
}
return this.delete();
};
});
"""
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def main(target_process):
device = frida.get_usb_device()
pid = device.spawn([target_process])
session = device.attach(pid)

script = session.create_script(jscode)
script.on('message', on_message)
print('[*] Attaching from the process.')
script.load()
device.resume(pid)
try:
sys.stdin.read()
except KeyboardInterrupt:
print("[*] Detaching from the process.")
session.detach()

if __name__ == "__main__":
process = frida.get_usb_device(-1).enumerate_processes()
print(process)
if len(sys.argv) != 2:
print("Usage: python dump_dex.py <package_name>")
sys.exit(1)
target_process = sys.argv[1]
main(target_process)

成功获得dex文件后反编译查看:

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
package fish;

import java.util.HashMap;

public class hook_fish {
private HashMap fish_dcode;
private HashMap fish_ecode;
private String strr;

public hook_fish() {
this.strr = "jjjliijijjjjjijiiiiijijiijjiijijjjiiiiijjjjliiijijjjjljjiilijijiiiiiljiijjiiliiiiiiiiiiiljiijijiliiiijjijijjijijijijiilijiijiiiiiijiljijiilijijiiiijjljjjljiliiijjjijiiiljijjijiiiiiiijjliiiljjijiiiliiiiiiljjiijiijiijijijjiijjiijjjijjjljiliiijijiiiijjliijiijiiliiliiiiiiljiijjiiliiijjjliiijjljjiijiiiijiijjiijijjjiiliiliiijiijijijiijijiiijjjiijjijiiiljiijiijilji";
this.encode_map();
this.decode_map();
}

public boolean check(String s) {
return s.equals(this.strr);
}

public String decode(String s) {
StringBuilder stringBuilder0 = new StringBuilder();
int v1 = 0;
for(int v = 0; v1 < s.length() / 5; v += 5) {
stringBuilder0.append(this.fish_dcode.get(s.substring(v, v + 5)));
++v1;
}

return stringBuilder0.toString();
}

public void decode_map() {
HashMap hashMap0 = new HashMap();
this.fish_dcode = hashMap0;
hashMap0.put("iiijj", Character.valueOf('a'));
this.fish_dcode.put("jjjii", Character.valueOf('b'));
this.fish_dcode.put("jijij", Character.valueOf('c'));
this.fish_dcode.put("jjijj", Character.valueOf('d'));
this.fish_dcode.put("jjjjj", Character.valueOf('e'));
this.fish_dcode.put("ijjjj", Character.valueOf('f'));
this.fish_dcode.put("jjjji", Character.valueOf('g'));
this.fish_dcode.put("iijii", Character.valueOf('h'));
this.fish_dcode.put("ijiji", Character.valueOf('i'));
this.fish_dcode.put("iiiji", Character.valueOf('j'));
this.fish_dcode.put("jjjij", Character.valueOf('k'));
this.fish_dcode.put("jijji", Character.valueOf('l'));
this.fish_dcode.put("ijiij", Character.valueOf('m'));
this.fish_dcode.put("iijji", Character.valueOf('n'));
this.fish_dcode.put("ijjij", Character.valueOf('o'));
this.fish_dcode.put("jiiji", Character.valueOf('p'));
this.fish_dcode.put("ijijj", Character.valueOf('q'));
this.fish_dcode.put("jijii", Character.valueOf('r'));
this.fish_dcode.put("iiiii", Character.valueOf('s'));
this.fish_dcode.put("jjiij", Character.valueOf('t'));
this.fish_dcode.put("ijjji", Character.valueOf('u'));
this.fish_dcode.put("jiiij", Character.valueOf('v'));
this.fish_dcode.put("iiiij", Character.valueOf('w'));
this.fish_dcode.put("iijij", Character.valueOf('x'));
this.fish_dcode.put("jjiji", Character.valueOf('y'));
this.fish_dcode.put("jijjj", Character.valueOf('z'));
this.fish_dcode.put("iijjl", Character.valueOf('1'));
this.fish_dcode.put("iiilj", Character.valueOf('2'));
this.fish_dcode.put("iliii", Character.valueOf('3'));
this.fish_dcode.put("jiili", Character.valueOf('4'));
this.fish_dcode.put("jilji", Character.valueOf('5'));
this.fish_dcode.put("iliji", Character.valueOf('6'));
this.fish_dcode.put("jjjlj", Character.valueOf('7'));
this.fish_dcode.put("ijljj", Character.valueOf('8'));
this.fish_dcode.put("iljji", Character.valueOf('9'));
this.fish_dcode.put("jjjli", Character.valueOf('0'));
}

public String encode(String s) {
StringBuilder stringBuilder0 = new StringBuilder();
for(int v = 0; v < s.length(); ++v) {
stringBuilder0.append(((String)this.fish_ecode.get(Character.valueOf(((char)s.charAt(v))))));
}

return stringBuilder0.toString();
}

public void encode_map() {
HashMap hashMap0 = new HashMap();
this.fish_ecode = hashMap0;
hashMap0.put(Character.valueOf('a'), "iiijj");
this.fish_ecode.put(Character.valueOf('b'), "jjjii");
this.fish_ecode.put(Character.valueOf('c'), "jijij");
this.fish_ecode.put(Character.valueOf('d'), "jjijj");
this.fish_ecode.put(Character.valueOf('e'), "jjjjj");
this.fish_ecode.put(Character.valueOf('f'), "ijjjj");
this.fish_ecode.put(Character.valueOf('g'), "jjjji");
this.fish_ecode.put(Character.valueOf('h'), "iijii");
this.fish_ecode.put(Character.valueOf('i'), "ijiji");
this.fish_ecode.put(Character.valueOf('j'), "iiiji");
this.fish_ecode.put(Character.valueOf('k'), "jjjij");
this.fish_ecode.put(Character.valueOf('l'), "jijji");
this.fish_ecode.put(Character.valueOf('m'), "ijiij");
this.fish_ecode.put(Character.valueOf('n'), "iijji");
this.fish_ecode.put(Character.valueOf('o'), "ijjij");
this.fish_ecode.put(Character.valueOf('p'), "jiiji");
this.fish_ecode.put(Character.valueOf('q'), "ijijj");
this.fish_ecode.put(Character.valueOf('r'), "jijii");
this.fish_ecode.put(Character.valueOf('s'), "iiiii");
this.fish_ecode.put(Character.valueOf('t'), "jjiij");
this.fish_ecode.put(Character.valueOf('u'), "ijjji");
this.fish_ecode.put(Character.valueOf('v'), "jiiij");
this.fish_ecode.put(Character.valueOf('w'), "iiiij");
this.fish_ecode.put(Character.valueOf('x'), "iijij");
this.fish_ecode.put(Character.valueOf('y'), "jjiji");
this.fish_ecode.put(Character.valueOf('z'), "jijjj");
this.fish_ecode.put(Character.valueOf('1'), "iijjl");
this.fish_ecode.put(Character.valueOf('2'), "iiilj");
this.fish_ecode.put(Character.valueOf('3'), "iliii");
this.fish_ecode.put(Character.valueOf('4'), "jiili");
this.fish_ecode.put(Character.valueOf('5'), "jilji");
this.fish_ecode.put(Character.valueOf('6'), "iliji");
this.fish_ecode.put(Character.valueOf('7'), "jjjlj");
this.fish_ecode.put(Character.valueOf('8'), "ijljj");
this.fish_ecode.put(Character.valueOf('9'), "iljji");
this.fish_ecode.put(Character.valueOf('0'), "jjjli");
}
}

是对a-z以及0-9的重映射,编写解密脚本:

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
decode_map = {
"iiijj": 'a', "jjjii": 'b', "jijij": 'c', "jjijj": 'd', "jjjjj": 'e',
"ijjjj": 'f', "jjjji": 'g', "iijii": 'h', "ijiji": 'i', "iiiji": 'j',
"jjjij": 'k', "jijji": 'l', "ijiij": 'm', "iijji": 'n', "ijjij": 'o',
"jiiji": 'p', "ijijj": 'q', "jijii": 'r', "iiiii": 's', "jjiij": 't',
"ijjji": 'u', "jiiij": 'v', "iiiij": 'w', "iijij": 'x', "jjiji": 'y',
"jijjj": 'z', "iijjl": '1', "iiilj": '2', "iliii": '3', "jiili": '4',
"jilji": '5', "iliji": '6', "jjjlj": '7', "ijljj": '8', "iljji": '9',
"jjjli": '0'
}

def decode(encoded_str):
decoded_chars = []
for i in range(0, len(encoded_str), 5):
segment = encoded_str[i:i+5]
if segment in decode_map:
decoded_chars.append(decode_map[segment])
else:
print(f"Warning: Unknown segment '{segment}'")
return ''.join(decoded_chars)

if __name__ == "__main__":
encoded_string = "jjjliijijjjjjijiiiiijijiijjiijijjjiiiiijjjjliiijijjjjljjiilijijiiiiiljiijjiiliiiiiiiiiiiljiijijiliiiijjijijjijijijijiilijiijiiiiiijiljijiilijijiiiijjljjjljiliiijjjijiiiljijjijiiiiiiijjliiiljjijiiiliiiiiiljjiijiijiijijijjiijjiijjjijjjljiliiijijiiiijjliijiijiiliiliiiiiiljiijjiiliiijjjliiijjljjiijiiiijiijjiijijjjiiliiliiijiijijijiijijiiijjjiijjijiiiljiijiijilji"
decoded_string = decode(encoded_string)
print("Decoded string:", decoded_string)

得到中间密文:0qksrtuw0x74r2n3s2x3ooi4ps54r173k2os12r32pmqnu73r1h432n301twnq43prruo2h5,再接着看MainActivity中的加密,发现分为几步:①每个字节的ASCII+68并转为十六进制 ②调用静态方法code让相邻两个字符的值交换 ③根据每个字符是否在a-f之间来应用不同的映射(两种映射的值域交集为空集),据此写出解密脚本:

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
def reverse_transform(cipher_char, index):
cipher_ascii = ord(cipher_char)

if 48 <= cipher_ascii <= 56:
original_ascii = cipher_ascii - index % 4 + 49
return chr(original_ascii)
else:
original_ascii = cipher_ascii - 55 - index % 10
return chr(original_ascii)

def decrypt(cipher_str):
plain_chars = []
for index, char in enumerate(cipher_str):
plain_chars.append(reverse_transform(char, index))
return ''.join(plain_chars)

cipher_text = "0qksrtuw0x74r2n3s2x3ooi4ps54r173k2os12r32pmqnu73r1h432n301twnq43prruo2h5"
print("Encrypted:", cipher_text)
stg1 = decrypt(cipher_text)
print("DecStage1:", stg1)
stg2 = list(stg1)
for i in range(0, len(stg2)-1, 2):
stg2[i], stg2[i+1] = stg2[i+1], stg2[i]
resultstg2 = ''.join(stg2)
print("DecStage2:", resultstg2)
hex_array = [resultstg2[i:i+2] for i in range(0, len(resultstg2), 2)]
result = ''.join(chr(int(hex_str, 16) - 68) for hex_str in hex_array)
print("Result:", result)

输出flag:VNCTF{u_re4l1y_kn0w_H0Ok_my_f1Sh!1l}.

1
2
3
4
5
6
7
Encrypted: 0qksrtuw0x74r2n3s2x3ooi4ps54r173k2os12r32pmqnu73r1h432n301twnq43prruo2h5

DecStage1: a9297889a8fb9b3a6b9a870b57db3afa2b47bb3ac84739fa3a1bdb3aaa5779ca56570b1c

DecStage2: 9a9287988abfb9a3b6a978b075bda3afb274bba38c7493afa3b1bda3aa7597ac6575b0c1

Result: VNCTF{u\_re4l1y\_kn0w\_H0Ok\_my\_f1Sh!1l}.

Fuko’s starfish:

反编译查看是一个小游戏,分为猜数字、贪吃蛇和第三步的输入密钥,然后整个程序不知道塞了多少反调试反正一调就直接闪退,再观察附带的starfish.dll,发现第三步游戏在dll里面,前两个游戏没啥关键信息就不分析了,仔细观察所给的dll,发现好几个函数有花(特征是call $+5后一大段然后一个jz一个retn),把jz改jmp,其他没用的全抹掉,观察到几个关键函数:

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
void __noreturn sub_1800025F0()
{
size_t i; // rdi
__int64 v1; // rcx
__int64 v2; // rax
const char *v3; // rcx
char v4[16]; // [rsp+20h] [rbp-60h] BYREF
__int128 v5; // [rsp+30h] [rbp-50h]
char Str[16]; // [rsp+90h] [rbp+10h] BYREF
_BYTE v7[30]; // [rsp+A0h] [rbp+20h]
__int128 v8; // [rsp+C0h] [rbp+40h]
char v9; // [rsp+D0h] [rbp+50h]
_BYTE v10[31]; // [rsp+D1h] [rbp+51h] BYREF
char v11; // [rsp+F0h] [rbp+70h]
__int128 v12; // [rsp+100h] [rbp+80h] BYREF
unsigned __int8 v13; // [rsp+110h] [rbp+90h]

*(_OWORD *)&v7[14] = *(__int128 *)((char *)&xmmword_18000A9C0 + 14);
*(_OWORD *)v7 = xmmword_18000A9C0;
*(_OWORD *)Str = xmmword_18000A9B0;
for ( i = 0i64; strlen(Str) > i; ++i )
Str[i] ^= 0x17u;
puts(Str); //这部分是加密的输出文字,让提供一个final key
vfscanf(v1, v4);
v11 = 0;
v12 = 0i64;
v13 = 0;
*(_OWORD *)&v10[15] = v5;
AES(&v10[15], &v12); //这个函数比较复杂(我自己命的名),在下面详细分析
v5 = 0i64;
AES(v4, &v10[15]);
v9 = v11;
v8 = *(_OWORD *)&v10[15];
*(_OWORD *)v10 = v12;
*(_WORD *)&v10[16] = v13;
v2 = 0i64;
while ( *((_BYTE *)&v8 + 2 * v2) == byte_18000A890[2 * v2] //显而易见的比较flag部分,flag长度为32
&& *((_BYTE *)&v8 + 2 * v2 + 1) == byte_18000A890[2 * v2 + 1] )
{
if ( ++v2 == 16 )
{
v3 = "right!";
goto LABEL_10;
}
}
v3 = "wrong";
LABEL_10:
puts(v3);
Sleep(0xFA0u);
exit(0);
}

至此已经找到了flag的密文:[3d 01 1c 19 0b a0 90 81 5f 67 27 31 a8 9a a4 74 97 36 21 67 ab 2e b4 a0 94 18 d3 7d 93 e6 46 e7],对于那两个标橙色的函数详细分析:

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
  v145 = a2;
v140 = a1;
v146 = 0i64;
v2 = byte_18000E1E0;
v3 = byte_18000E1F0;
v4 = byte_18000E1F2;
v5 = byte_18000E200;
v6 = byte_18000E204;
v7 = byte_18000E210;
v8 = byte_18000E950;
v9 = byte_18000E220;
LOBYTE(v139) = byte_18000E228;
v126 = byte_18000E230;
v127 = byte_18000E232;
v129 = byte_18000E240;
LOBYTE(v138) = byte_18000E244;
LOBYTE(v131) = byte_18000E960;
v128 = byte_18000E962;
LOBYTE(v130) = byte_18000E970;
pbDebuggerPresent = 0;
CurrentProcess = GetCurrentProcess();
CheckRemoteDebuggerPresent(CurrentProcess, &pbDebuggerPresent);
if ( pbDebuggerPresent )
{
v132 = v5;
v133 = v4;
v134 = v3;
v135 = v9;
v125 = v2;
v11 = v8;
v136 = v7;
v137 = v6;
v12 = v127;
v13 = v126;
v14 = v128;
LOWORD(v148) = 0x1D39;
*(_DWORD *)Str = 0x397A7A5F;
for ( i = 0i64; strlen(Str) > i; ++i )
Str[i] ^= 0x17u;
vfprintf((FILE *const)"%s", Str, v16);
v17 = v14;
v18 = v139;
v19 = v12;
v20 = v137;
v21 = v129;
v22 = v136;
v23 = v11;
v24 = v125;
v25 = v135;
v26 = v134;
v27 = v133;
v28 = v132;
}
else
{
v125 = v2 ^ 0x17;
v29 = v3 ^ 0x17;
v30 = v4 ^ 0x17;
v28 = v5 ^ 0x17;
v22 = v7 ^ 0x17;
v23 = v8 ^ 0x17;
v25 = v9 ^ 0x17;
v18 = v139 ^ 0x17;
v13 = v126 ^ 0x17;
v20 = v6 ^ 0x17;
v19 = v127 ^ 0x17;
v21 = v129 ^ 0x17;
LOBYTE(v138) = v138 ^ 0x17;
LOBYTE(v131) = v131 ^ 0x17;
v17 = v128 ^ 0x17;
LOBYTE(v130) = v130 ^ 0x17;
v24 = v125;
v26 = v29;
v27 = v30;
}
Str[0] = v28;
Str[1] = v27;
Str[2] = v26;
Str[3] = v24;
v148 = v25 | (v23 << 8) | (v22 << 16) | (v20 << 24);
LODWORD(v149) = v21 | (v19 << 8) | (v13 << 16) | (v18 << 24);
v31 = v17;
v32 = RijnDael_AES_LONG_18000A8B0[v17];
……(以下省略)

可以注意到这是一个包含了AES加密的函数(由于没有初始化向量猜测是ECB模式),经过分析发现带入的两个参数里面第一个参数是要加密的内容,第二个参数是加密结果的缓冲区,那么密钥在哪?这时发现函数调用了恰好16个全局变量,找到这16个全局变量,发现是11 45 14 19 19 81 11 45 14 19 19 81 ?? ?? ?? ??,由于这个题目根本就不想要我调试(不然塞这么多反调试干嘛),密钥一定不是这个数值(毕竟还有四个字节未知),查看这些全局变量的交叉引用,发现了一个函数:

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
__int64 __fastcall ThreadProc(LPVOID lpThreadParameter)
{
v1 = rand();
byte_18000E1E0 = v1 + v1 / 255;
v2 = rand();
byte_18000E1F0 = v2 + v2 / 255;
v3 = rand();
byte_18000E1F2 = v3 + v3 / 255;
v4 = rand();
byte_18000E200 = v4 + v4 / 255;
v5 = rand();
byte_18000E204 = v5 + v5 / 255;
v6 = rand();
byte_18000E210 = v6 + v6 / 255;
v7 = rand();
byte_18000E950 = v7 + v7 / 255;
v8 = rand();
byte_18000E220 = v8 + v8 / 255;
v9 = rand();
byte_18000E228 = v9 + v9 / 255;
v10 = rand();
byte_18000E230 = v10 + v10 / 255;
v11 = rand();
byte_18000E232 = v11 + v11 / 255;
v12 = rand();
byte_18000E240 = v12 + v12 / 255;
v13 = rand();
byte_18000E244 = v13 + v13 / 255;
v14 = rand();
byte_18000E960 = v14 + v14 / 255;
v15 = rand();
byte_18000E962 = v15 + v15 / 255;
v16 = rand();
byte_18000E970 = v16 + v16 / 255;
return 6i64;
}

这就很奇怪了,16个字节居然是全随机,不动态调试根本无法获取其值,但结合密文是固定的,并且下面return了一个诡异的数:6i64,不难猜测这里可能藏了一个花,在汇编界面打开一看还真是,抹掉花之后获得真正的函数:

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
  srand(0x1BF52u);
v17 = rand();
byte_18000E1E0 = v17 + v17 / 255;
v18 = rand();
byte_18000E1F0 = v18 + v18 / 255;
v19 = rand();
byte_18000E1F2 = v19 + v19 / 255;
v20 = rand();
byte_18000E200 = v20 + v20 / 255;
v21 = rand();
byte_18000E204 = v21 + v21 / 255;
v22 = rand();
byte_18000E210 = v22 + v22 / 255;
v23 = rand();
byte_18000E950 = v23 + v23 / 255;
v24 = rand();
byte_18000E220 = v24 + v24 / 255;
v25 = rand();
byte_18000E228 = v25 + v25 / 255;
v26 = rand();
byte_18000E230 = v26 + v26 / 255;
v27 = rand();
byte_18000E232 = v27 + v27 / 255;
v28 = rand();
byte_18000E240 = v28 + v28 / 255;
v29 = rand();
byte_18000E244 = v29 + v29 / 255;
v30 = rand();
byte_18000E960 = v30 + v30 / 255;
v31 = rand();
byte_18000E962 = v31 + v31 / 255;
v32 = rand();
byte_18000E970 = v32 + v32 / 255;
return 0i64;
}

可以注意到下面又生成了16次随机值,但这次给了固定的srand,这意味着每个随机值其实都是固定的,那么不言而喻这应该就是密钥了,编写C语言程序取得随机值:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <stdlib.h>

int main() {
unsigned int seed = 0x1BF52u;
srand(seed);
for (int i = 0; i < 16; ++i) {
int rand_val = rand();
int result = rand_val + rand_val / 255;
printf("Random value %d: %d, Processed: %d\n", i+1, rand_val, result);
}
return 0;
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Random value 1: 13545, Processed: 13598
Random value 2: 19112, Processed: 19186
Random value 3: 12729, Processed: 12778
Random value 4: 29322, Processed: 29436
Random value 5: 16702, Processed: 16767
Random value 6: 2078, Processed: 2086
Random value 7: 10043, Processed: 10082
Random value 8: 2201, Processed: 2209
Random value 9: 6796, Processed: 6822
Random value 10: 11264, Processed: 11308
Random value 11: 9582, Processed: 9619
Random value 12: 25786, Processed: 25887
Random value 13: 17729, Processed: 17798
Random value 14: 9432, Processed: 9468
Random value 15: 11586, Processed: 11631
Random value 16: 24167, Processed: 24261

又因为全局变量限定了是byte,再转成字节并拼合:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
values = [
13598, 19186, 12778, 29436, 16767, 2086, 10082, 2209,
6822, 11308, 9619, 25887, 17798, 9468, 11631, 24261
]

def convert_to_byte_array(values):
byte_array = []
for value in values:
byte_value = value & 0xFF
byte_array.append(byte_value)
return bytes(byte_array)

byte_array = convert_to_byte_array(values)
print("Hex representation:", byte_array.hex())
print("Byte array:", list(byte_array))

得到结果:Hex representation: 1ef2eafc7f2662a1a62c931f86fc6fc5

将十六进制数组异或0x17得到09e5fdeb683175b6b13b840891eb78d2,在cyberchef选择AES-ECB/NoPadding解密得到flag:VNCTF{W0w_u_g0t_Fuk0’s_st4rf1sh}.

抽奖转盘:

发现是鸿蒙应用,先改成.zip,取出其中的.abc字节码,扔进jadx-dev进行反编译,大量寻找后得到以下几个关键信息:

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
//来自index类
//文本框
public Object #~@0>@1*#^b(Object functionObject, Object newTarget, Index this, Object arg0, Object arg1) {
obj = TextInput.create;
obj2 = createobjectwithbuffer(["controller", 0]);
obj2.controller = _lexenv_0_1_.textInputControllerX;
obj(obj2);
TextInput.type(InputType.Password);
TextInput.height(import { default as CommonContants } from "@normalized:N&&&entry/src/main/common/CommonContants&".FULL_PARENT);
obj3 = TextInput.margin;
obj4 = createobjectwithbuffer(["left", 0]);
obj5 = createobjectwithbuffer(["id", 16777265, "type", 10002, "params", 0, "bundleName", "com.game.vnctf", "moduleName", "entry"]);
obj5.params = [Object];
obj4.left = obj5;
obj3(obj4);
TextInput.layoutWeight(import { default as CommonContants } from "@normalized:N&&&entry/src/main/common/CommonContants&".TEXTINPUT_LAYOUT_WEIGHT);
TextInput.onChange(#~@0>@1*^b*#);//输入后读取并存入
return null;
}
//按钮
public Object #~@0>@1*#^d(Object functionObject, Object newTarget, Index this, Object arg0, Object arg1) {
obj = Button.createWithLabel;
obj2 = createobjectwithbuffer(["id", 16777229, "type", 10003, "params", 0, "bundleName", "com.game.vnctf", "moduleName", "entry"]);
obj2.params = [Object];
obj(obj2);
obj3 = Button.fontSize;
obj4 = createobjectwithbuffer(["id", 16777264, "type", 10002, "params", 0, "bundleName", "com.game.vnctf", "moduleName", "entry"]);
obj4.params = [Object];
obj3(obj4);
Button.fontWeight(import { default as CommonContants } from "@normalized:N&&&entry/src/main/common/CommonContants&".FONT_WEIGHT);
Button.height(import { default as CommonContants } from "@normalized:N&&&entry/src/main/common/CommonContants&".FULL_PARENT);
obj5 = Button.width;
obj6 = createobjectwithbuffer(["id", 16777244, "type", 10002, "params", 0, "bundleName", "com.game.vnctf", "moduleName", "entry"]);
obj6.params = [Object];
obj5(obj6);
Button.onClick(#~@0>@1*^d*#);//按下按钮触发libhello.so调用以及加密
return null;
}
//读取文本框中的输入值作为arg0,存入长度到_lexenv_0_1_.numX,具体值到_lexenv_0_1_.pw
public Object #~@0>@1*^b*#(Object functionObject, Object newTarget, Index this, Object arg0) {
_lexenv_0_1_.numX = arg0.length;
_lexenv_0_1_.pw = arg0;
return null;
}
//导入libhello.so,调用其中的MyCry方法,加密输入值,三个参数分别为长度、24、具体值
public Object #~@0>@1*^d*#(Object functionObject, Object newTarget, Index this) {
libHello = import { default as libHello } from "@normalized:Y&&&libhello.so&";
MyCry = libHello.MyCry(_lexenv_0_1_.numX, 24, _lexenv_0_1_.pw);
router = import { default as router } from "@ohos:router";
obj = router.pushUrl;
obj2 = createobjectwithbuffer(["url", "pages/MyPage", "params", 0]);
obj3 = createobjectwithbuffer(["result", 0]);
obj3.result = MyCry;
obj2.params = obj3;
obj(obj2);
return null;
}
//设置result属性
public Object #~@0>#result^1(Object functionObject, Object newTarget, Index this, Object arg0) {
obj = this.__result;
obj.set(arg0);
return null;
}
//来自MyPage类
/* JADX WARN: Multi-variable type inference failed */
//ASCII值+1并异或0x7
public Object #~@0>@4*#(Object functionObject, Object newTarget, MyPage this, Object arg0, Object arg1) {
_lexenv_0_0_[arg1] = (arg0 + 1) ^ 7;
return null;
}
/* JADX WARN: Type inference failed for: r14v12, types: [int] */
/* JADX WARN: Type inference failed for: r14v14, types: [Object, int] */
//比较函数
public Object #~@0>@4*#^1(Object functionObject, Object newTarget, MyPage this, Object arg0, Object arg1) {
if ((_lexenv_0_1_[arg1] == _lexenv_0_0_[arg1] ? 1 : 0) == 0) {
return null;
}
r14 = tonumer(_lexenv_0_2_) + 1;
_lexenv_0_2_;
_lexenv_0_2_ = r14;
return null;
}
//最关键的方法之一,_lexenv_0_1_的值明显是flag密文
/* JADX WARN: Type inference failed for: r13v38, types: [int] */
public Object #~@0>#startAnimator(Object functionObject, Object newTarget, MyPage this) {
newlexenvwithname([6, "array", 0, "ay", 1, "tempangle", 2, "randomAngle", 3, "4newTarget", 4, "this", 5], 6);
_lexenv_0_4_ = newTarget;
_lexenv_0_5_ = this;
_lexenv_0_3_ = null;
_lexenv_0_2_ = null;
buffer = import { default as buffer } from "@ohos:buffer";
_lexenv_0_0_ = Uint8Array(buffer.from(_lexenv_0_5_.result).buffer); //十六进制数组,libhello加密结果
_lexenv_0_1_ = createarraywithbuffer([101, 74, 76, 49, 101, 76, 117, 87, 55, 69, 118, 68, 118, 69, 55, 67, 61, 83, 62, 111, 81, 77, 115, 101, 53, 73, 83, 66, 68, 114, 109, 108, 75, 66, 97, 117, 93, 127, 115, 124, 109, 82, 93, 115]); //flag密文
ldlexvar = _lexenv_0_0_;
ldlexvar.forEach(#~@0>@4*#); //加密后的结果每一位再加上1,之后异或0x7
ldlexvar2 = _lexenv_0_0_;
ldlexvar2.forEach(#~@0>@4*#^1); //比较加密结果和密文的值
round = Math.round(104 - _lexenv_0_2_);
_lexenv_0_3_;
_lexenv_0_3_ = round;
console.log("当前角度", _lexenv_0_3_);
ldlexvar3 = _lexenv_0_5_;
obj = _lexenv_0_5_.drawModel;
ldlexvar3.prizeData = obj.showPrizeData(_lexenv_0_3_);
obj2 = Context.animateTo;
obj3 = createobjectwithbuffer(["duration", 0, "curve", 0, "delay", 0, "iterations", 1, "playMode", 0, "onFinish", 0]);
obj3.duration = import { default as CommonConstants } from "@normalized:N&&&entry/src/main/common/constants/CommonConstants&".DURATION;
obj3.curve = Curve.Ease;
obj3.playMode = PlayMode.Normal;
obj3.onFinish = #~@0>@4*#onFinish;
obj2(obj3, #~@0>@4*#^2);
return null;
}

则该应用在解题部分(加密部分)的逻辑已经清晰:输入的文本会先带入libhello.so,在其中进行加密后输出,输入值的每一位的ASCII值加上1,之后与0x7异或,并与输出的再次加密的flag密文进行比较,接下来反编译libhello.so:

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
__int64 __fastcall MyCry(__int64 a1, __int64 a2)
{
__int64 v3; // [rsp+28h] [rbp-178h]
double v4; // [rsp+40h] [rbp-160h]
__int64 v5; // [rsp+88h] [rbp-118h]
__int64 v6; // [rsp+90h] [rbp-110h] BYREF
__int64 v7[3]; // [rsp+98h] [rbp-108h] BYREF
char v8; // [rsp+B7h] [rbp-E9h] BYREF
__int64 v9; // [rsp+B8h] [rbp-E8h] BYREF
__int64 v10; // [rsp+C0h] [rbp-E0h] BYREF
__int64 v11[3]; // [rsp+C8h] [rbp-D8h] BYREF
char v12[24]; // [rsp+E0h] [rbp-C0h] BYREF
double y; // [rsp+F8h] [rbp-A8h] BYREF
double x; // [rsp+100h] [rbp-A0h] BYREF
__int64 v15; // [rsp+108h] [rbp-98h] BYREF
char dest[112]; // [rsp+110h] [rbp-90h] BYREF
__int64 s[4]; // [rsp+180h] [rbp-20h] BYREF

s[3] = __readfsqword(0x28u);
if ( a1 && a2 )
{
v15 = 3LL;
memset(s, 0, 0x18uLL);
if ( (unsigned int)napi_get_cb_info(a1, a2, &v15, s, 0LL) )
{
OH_LOG_Print(0LL, 6LL, 65280LL, (__int64)"MyCry", "api_get_cb_info failed");
return 0LL;
}
else
{
x = 0.0;
y = 0.0;
if ( (unsigned int)napi_get_value_double(a1, s[0], &x) || (unsigned int)napi_get_value_double(a1, s[1], &y) )//x和y就是长度和24
{
OH_LOG_Print(0LL, 6LL, 65280LL, (__int64)"MyCry", "napi_get_value failed");
return 0LL;
}
else
{
sub_287D0(v12, a1, s[2]);
sub_288E0(v12);
OH_LOG_Print(0LL, 6LL, 65280LL, (__int64)"MyCry", "ts_putString str = %{public}s");
sub_28900(v11);
v10 = sub_28990((__int64)v12);
v9 = sub_28A00((__int64)v12);
while ( (sub_28A80(&v10, &v9) & 1) != 0 )
{
v8 = *(_BYTE *)sub_28AB0(&v10) + 3; //字符串的十六进制值+3
sub_28AD0(v11, &v8);
sub_28B30(&v10);
}
v4 = _mm_cvtepi32_pd(_mm_cvttpd_epi32((__m128d)COERCE_UNSIGNED_INT64(hypot(x, y)))).m128d_f64[0]; //欧几里得距离
std::__n1::basic_string<char,std::__n1::char_traits<char>,std::__n1::allocator<char>>::basic_string[abi:v15004]<decltype(nullptr)>(
(__int64)v7,
(__int64)"Take_it_easy"); //密钥
OH_LOG_Print(0LL, 6LL, 65280LL, (__int64)”MyCry”, “Result: %{public}f”);
if ( v4 == 40.0 ) //这里两者距离为40,求得字符串长度为32
Alternative_RC4_1(v11, (__int64)v7, (int)v4); //魔改RC4加密,额外异或值是40
else
Alternative_RC4_2(v11, (__int64)v7); //魔改RC4加密,额外异或值是24
memset(dest, 0, 0x64uLL);
if ( (unsigned __int64)sub_27AE0(v11) < 0x5A ) //长度不超过90的情况下编码成base64
base64_encode((__int64)v11, dest);
else
strcpy(dest, "oh!you_are_toooooo_long!!!!!!");
v3 = strlen(dest);
if ( (unsigned int)napi_create_string_utf8(a1, (__int64)dest, v3, (__int64)&v6) )
{
OH_LOG_Print(0LL, 6LL, 65280LL, (__int64)"MyCry", "napi_create_double failed");
v5 = 0LL;
}
else
{
v5 = v6;
}
std::__n1::basic_string<char,std::__n1::char_traits<char>,std::__n1::allocator<char>>::~basic_string(v7);
sub_28BD0(v11);
std::__n1::basic_string<char,std::__n1::char_traits<char>,std::__n1::allocator<char>>::~basic_string(v12);
}
}
}
else
{
OH_LOG_Print(0LL, 6LL, 65280LL, (__int64)"MyCry", "env or exports is null");
return 0LL;
}
return v5;
}

至此全部加密已经解析完毕,开始解密:

flag密文异或0x7之后-1得到:aLJ5aJqO/ApBpA/C9S8gUIsa1MSDBtijKDeqYwsziTYs,共有44位,并且恰好在base64字符集的范围内,解码后得到中间密文的十六进制表达:\x68\xb2\x79\x68\x9a\x8e\xfc\x0a\x41\xa4\x0f\xc2\xf5\x2f\x20\x50\x8b\x1a\xd4\xc4\x83\x06\xd8\xa3\x28\x37\xaa\x63\x0b\x33\x89\x36\x2c,然后用RC4解密,密钥为Take_it_easy,再异或40,得到\x59\x51\x46\x57\x49\x7e\x4d\x58\x76\x77\x62\x27\x71\x68\x62\x4c\x64\x76\x77\x62\x67\x64\x71\x66\x68\x62\x35\x33\x35\x38\x24\x80\x03,再减去3,得到flag:VNCTF{JUst_$ne_Iast_dance_2025!}.