2021 AIS3 Pre-exam Write Up

Albert Huang MED student | CS enthusiast

今年第一次參加 AIS3 Pre-exam,來稍微記錄一下打的過程和心得,希望可以持續進步!

Welcome

Cat Slayer ᶠᵃᵏᵉ | Nekogoroshi

Author: splitline feat. Hojo Satoko

題目給了一行 command

1
TERM=xterm-256color ssh -p 5566 h173@quiz.ais3.org

把它拿到 Terminal 執行後可以發現他跳出了一個 Python 的執行畫面,用鍵盤可以輸入數字,輸入錯誤會鎖起來,因此可以用手動的輸入猜密碼,得到正確的密碼就可以解鎖 flag。

1
Password: 202583045529

FLAG:

1
AIS3{H1n4m1z4w4_Sh0k0gun}

MISC

Microcheese

Author: toxicpie

這題一樣給了 nc 跟 source code,但 source code 看了半天還是沒什麼想法,先去 nc 玩玩再說。

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
$ nc quiz.ais3.org 10234
+-------------------- welcome --------------------+
| omg hi! |
| |
| welcome to microchess, the minimal online chess |
| platform. |
| i am a super powerful chess AI! |
| can you win against me and get the flag? |
+---+--------------- main menu -------------------+
| 0 | read the rules of the game |
| 1 | start a new game against me |
| 2 | load a saved game |
| 3 | leave |
+---+---------------------------------------------+
what would you like to do? 1
+---+-------------- stones info ------------------+
| 0 | ooooooooooooo |
| 1 | ooooo |
| 2 | ooooooooooo |
| 3 | o |
| 4 | oooooooooooooooooooooooooooooo |
| 5 | ooooooo |
| 6 | ooooooooooooooooooooooooooo |
+---+--------------- game menu -------------------+
| 0 | make a move |
| 1 | save the current game and leave |
| 2 | resign the game |
+---+---------------------------------------------+
it's your turn to move! what do you choose? 0
which pile do you choose? 3
how many stones do you remove? 1
+--------------------- moved ---------------------+
| you removed 1 stones from pile 3 |
+---+-------------- stones info ------------------+
| 0 | ooooooooooooo |
| 1 | ooooo |
| 2 | ooooooooooo |
| 3 | oooooooooooooooooooooooooooooo |
| 4 | ooooooo |
| 5 | ooooooooooooooooooooooooooo |
+--------------------- moved ---------------------+
| i removed 1 stones from pile 0 |
+---+-------------- stones info ------------------+
| 0 | oooooooooooo |
| 1 | ooooo |
| 2 | ooooooooooo |
| 3 | oooooooooooooooooooooooooooooo |
| 4 | ooooooo |
| 5 | ooooooooooooooooooooooooooo |
+---+--------------- game menu -------------------+
| 0 | make a move |
| 1 | save the current game and leave |
| 2 | resign the game |
+---+---------------------------------------------+
it's your turn to move! what do you choose?

看起來是個遊戲,但玩了幾輪之後發現怎麼玩都輸,回去看 source code 發現game.py裡面好像有些東西。

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
import random
from typing import Tuple


class Game:
'''
a simple Nim game with normal rules.
grundy's theorem: if nim_sum() is zero, then the player to move has a
winning strategy. otherwise, the other player has a winning strategy.
'''

def __init__(self):
self.stones = []

def generate_winning_game(self) -> None:
'''generate a game such that the first player has a winning strategy'''
self.stones = []
xor_sum = 0

piles = random.randint(6, 8)
for i in range(piles):
self.stones.append(count := random.randint(1, 31))
xor_sum ^= count

if xor_sum == 0:
self.stones.append(random.randint(1, 31))

def generate_losing_game(self) -> None:
'''generate a game such that the second player has a winning strategy'''
self.stones = []
xor_sum = 0

piles = random.randint(6, 8)
for i in range(piles):
self.stones.append(count := random.randint(1, 31))
xor_sum ^= count

if xor_sum != 0:
self.stones.append(xor_sum)

def make_move(self, pile: int, count: int) -> bool:
'''makes a move, returns whether the move is legal'''

if pile not in range(0, len(self.stones)):
return False
if count not in range(1, self.stones[pile] + 1):
return False

self.stones[pile] -= count
if self.stones[pile] == 0:
self.stones.pop(pile)

return True

def nim_sum(self) -> int:
xor_sum = 0
for count in self.stones:
xor_sum ^= count
return xor_sum

def ended(self) -> bool:
'''
checks if the game has ended, i.e., the player has no more moves.
if True, the current player loses the game
'''
return len(self.stones) == 0

def show(self) -> None:
print('+---+-------------- stones info ------------------+')
for pile, count in enumerate(self.stones):
print(f'| {pile} | {"o" * count:<43} |')

def load(self, game_str: str) -> None:
'''loads a saved game from string'''
self.stones = list(map(int, game_str.split(',')))

def save(self) -> str:
'''returns the current game as a string'''
return ','.join(map(str, self.stones))


class AIPlayer:
'''
a perfect Nim player. if there exists a winning strategy for a game, this
player will always win.
'''

def __init__(self):
pass

def get_move(self, game: Game) -> Tuple[int, int]:
'''
if there is a winning strategy, returns a move that guarantees a win.
otherwise, returns a random move.
'''
nim_sum = game.nim_sum()

if nim_sum == 0:
# losing game, make a random move
pile = random.randint(0, len(game.stones) - 1)
count = random.randint(1, game.stones[pile])

else:
# winning game, make a winning move
for i, v in enumerate(game.stones):
target = v ^ nim_sum
if target < v:
pile = i
count = v - target
break

return (pile, count)

從程式內容跟註解可以很明顯的看到,比賽的必勝關鍵便是盤面所有行棋數的 xor 為 0。但是程式裡面的 AIPlayer 便是利用這個規則,讓自己下完時的盤面保持 0 的狀態,果然是必勝訣竅阿…

原本這題賽中我應該是解不出來的,但是因為有一次把號碼按錯之後發現一個超級大的 bug,當我在選擇下一個步驟時,我按了3(不在按鈕內),結果他就直接跳過了我的回合,讓盤面不再保持 xor 為 0 的狀態,接著就只需要讓最後一顆棋是由我拿走的就可以拿到 flag 了。

P.S. 這應該是因為沒有過濾其他輸入的問題…總之我拿到 flag 了 XD

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
+---+--------------- game menu -------------------+
| 0 | make a move |
| 1 | save the current game and leave |
| 2 | resign the game |
+---+---------------------------------------------+
it's your turn to move! what do you choose? 3
+--------------------- moved ---------------------+
| you removed 3 stones from pile 1 |
+---+-------------- stones info ------------------+
| 0 | o |
| 1 | o |
+--------------------- moved ---------------------+
| i removed 1 stones from pile 1 |
+---+-------------- stones info ------------------+
| 0 | o |
+---+--------------- game menu -------------------+
| 0 | make a move |
| 1 | save the current game and leave |
| 2 | resign the game |
+---+---------------------------------------------+
it's your turn to move! what do you choose? 0
which pile do you choose? 0
how many stones do you remove? 1
+---------------- congratulations ----------------+
| you are a true grandmaster of chess! here is |
| the flag for you: |
| AIS3{5._e3_b5_6._a4_Bb4_7._Bd2_a5_8._axb5_Bxc3} |
+-------------------------------------------------+

FLAG:

1
AIS3{5._e3_b5_6._a4_Bb4_7._Bd2_a5_8._axb5_Bxc3}

Web

ⲩⲉⲧ ⲁⲛⲟⲧⲏⲉꞅ 𝓵ⲟ𝓰ⲓⲛ ⲣⲁ𝓰ⲉ

Author: splitline

這題給了一個很難按的題目,連進去之後發現是一個 login 畫面,還有一個 sauce link,先點進去看原始碼,發現他是一個 json 資料庫,裡面存有登入的資訊:

用了 guest 成功登入,但沒有 flag。接著用 admin 試試,因為 os.environ.get()的意思是如果 key 不存在就用後面的值來代替,但是還是登入失敗了。

接著觀察輸入後的行為:

他把我們的輸入塞進了%s的地方包裝成 json 格式,但他並沒有過濾%s的內容,所以可以從輸入動手腳。

從上面可以看出來,他把新輸入的使用者 showflag 參數一律設為,所以我們拿不到 flag。但如果我們在輸入中塞入 json 格式的文字,他就會被包到這個 json 裡面送進去執行。

但因為我們必須構造一個不存在於原 database 的使用者,而 json 找不到 username 會回傳,因此我們將 password 也設為即可。

構造 payload:

1
2
username payload: M3t30r", "showflag": true, "username": "m3t30r
password payload: M3T30r", "password": null, "username": "M3T30R

FLAG:

1
AIS3{/r/badUIbattles?!?!}

HaaS

Author: anonymous

連進這題的網址後會是/haas 的分頁,但會顯示出 405 Method Not Allowed Error

稍微在網頁中翻找一下會發現根目錄裡面是一個”HealthCheck as a Service”網站,有一個可以輸入網址的欄位,用 F12 翻一下還可以發現一個 hidden 的 status code 參數。

一開始試了一些 Command Injection 之類的東西,但發現好像不太行。後來打開 Hint 裡面寫了SSRF,就開始往 localhost 的方向走了,但是如果直接連進 localhost 的 IP(https://127.0.0.1/)跟domain會發現他被過濾了:

於是開始嘗試 SSRF 的 bypass 方法,這裡嘗試了幾個發現https://127.000000.000000.1/(要注意它只吃絕對網址)這個 bypass 可以成功讓 haas 顯示"Alive",但我們必須拿到裡面的內容,所以將 status code 改掉讓 haas 噴出 Error 後即可拿到 flag。

FLAG:

1
QQ我忘了,然後題目機把localhost關掉了TAT

Crypto

Microchip

Author: toxicpie

這題給了一個 microchip.cpp、output.txt 與 python.h 檔,打開觀察後可以發現它利用匯入 python.h 的 library 進行 python 語法的混淆,所以應只需要觀察程式邏輯即可。

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
#include "python.h"


def track(name, id) -> str ?? {

if len(name) % 4 == 0 ?? ){
padded = name + "4444" ;}
elif len(name) % 4 == 1 ?? ){
padded = name + "333" ;}
elif len(name) % 4 == 2 ?? ){
padded = name + "22" ;}
elif len(name) % 4 == 3 ?? ){
padded = name + "1" ;}

keys = list() ;
temp = id ;
for i in range(4) ?? ){
keys.append(temp % 96) ;
temp = int(temp / 96) ;}

result = "" ;
for i in range(0, len(padded), 4) ?? ){

nums = list() ;
for j in range(4) ?? ){
num = ord(padded[i + j]) - 32 ;
num = (num + keys[j]) % 96 ;
nums.append(num + 32) ;}

result += chr(nums[3]) ;
result += chr(nums[2]) ;
result += chr(nums[1]) ;
result += chr(nums[0]) ;}

return result ;}


def main() -> int ?? {

name = open("flag.txt", "r").read().strip() ;
id = int(input("key = ")) ;

print("result is:", track(name, id)) ;
return 0 ;}

下面是 output.txt 的內容:

1
result is:=Js&;*A`odZHi'>D=Js&#i-DYf>Uy'yuyfyu<)Gu

從程式中可以發現他先把 flag 長度補成 4 的倍數後,用一組四個 key 來對 flag 進行一次 4 字元的運算。但是,我們沒有 key 也沒有 flag,要怎麼逆運算回去?

後來發現我們可以利用 flag formatAIS3{...}來解決這件事,先用前四個已知 flag 字元搭配 output 算出 key 之後,我們就能利用 output 推回 flag 了。

值得注意的是,他把 output 四個逆過來輸出,所以我們先把 output 逆回來比較容易計算。

1
&sJ=`A*;HZdoD>'i&sJ=D-i#U>fYuy'yuyfyuG)<

AIS3&sJ=可以算出四個 key 的值:

1
{69,42,87,10}

有了 output 與 key 就能寫程式來逆推 flag 了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<bits/stdc++.h>
using namespace std;
string s="&sJ=`A*;HZdoD>'i&sJ=D-i#U>fYuy'yuyfyuG)<";
int k[4]={69,42,87,10};

int main(){
for(int i=0;i<40;i++){
for(int j=48;;j++){
if(char((((j-32)+k[i%4])%96)+32)==s[i]){
cout<<char(j);
break;
}
}
}
cout<<endl;
return 0;
}

P.S. 從我們的結果可以發現 flag 一開始塞了”22”到後面喔~XD

FLAG:

1
AIS3{w31c0me_t0_AIS3_cryptoO0O0o0Ooo0}

ReSident evil villAge

Author: Kuruwa

這題給了一個 nc 跟 source code,打開 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
import socketserver
from Crypto.PublicKey import RSA
from Crypto.Util.number import *
from binascii import unhexlify

class Task(socketserver.BaseRequestHandler):

def recv(self):
return self.request.recv(1024).strip()

def send(self, msg):
self.request.sendall(msg + b'\n')

def handle(self):
privkey = RSA.generate(1024)

n = privkey.n
e = privkey.e

self.send(b'Welcome to ReSident evil villAge, sign the name "Ethan Winters" to get the flag.')
self.send(b'n = ' + str(n).encode())
self.send(b'e = ' + str(e).encode())

while True:
self.request.sendall(b'1) sign\n2) verify\n3) exit\n')
option = self.recv()

if option == b'1':
self.request.sendall(b'Name (in hex): ')
msg = unhexlify(self.recv())
if msg == b'Ethan Winters' or bytes_to_long(msg) >= n: # msg+k*n not allowed
self.send(b'Nice try!')
else:
sig = pow(bytes_to_long(msg), privkey.d, n) # TODO: Apply hashing first to prevent forgery
self.send(b'Signature: ' + str(sig).encode())

elif option == b'2':
self.request.sendall(b'Signature: ')
sig = int(self.recv())
verified = (pow(sig, e, n) == bytes_to_long(b'Ethan Winters'))
if verified:
self.send(b'AIS3{THIS_IS_A_FAKE_FLAG}')
else:
self.send(b'Well done!')

else:
break

class ForkingServer(socketserver.ForkingTCPServer, socketserver.TCPServer):
pass

if __name__ == "__main__":
HOST, PORT = '0.0.0.0', 42069
print(HOST, PORT)
server = ForkingServer((HOST, PORT), Task)
server.allow_reuse_address = True
server.serve_forever()

從裡面可以發現 verify 的部分會檢查計算的結果是否等於bytes_to_long(b'Ethan Winters'),而它有給定 n 與 e,因此原本的想法是爆搜,但發現搜不到於是放棄這條路。

再來看看有沒有其他的後門可以繞。sign 的部分,它會把你的註冊用 d 計算後存為 signature。用 RSA 的小常識,用 d 再用 e 運算後會把原本的明文算回來,但它前面出現了一個限制-要用 16 進位輸入而且不能註冊 Ethan Winters 的值

在這裡要怎麼繞過呢?直觀的想法便是在前面加 00,因為這樣數字運算的時候會把 00 省略,但是字串比較時會與 Ethan Winters 的值不同,所以用 Ethan Winters 的 16 進位值再加上 00 前綴試試看。

1
2
>>> "Ethan Winters".encode().hex()
'457468616e2057696e74657273'

00457468616e2057696e74657273丟進去註冊,再把跑出來的Signatureverify 即可拿到 flag。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ nc quiz.ais3.org 42069
Welcome to ReSident evil villAge, sign the name "Ethan Winters" to get the flag.
n = 116446349250477461211548564037305096646246352712613922877939621221682086442262006076658058243799666178199942844412251167013582517469698767681352409472676329468186602987766214457609857069612827869171818746620035374912701350547179213366848809313278051695960906542268549406826272373673111354786397335987091196949
e = 65537
1) sign
2) verify
3) exit
1
Name (in hex): 00457468616e2057696e74657273
Signature: 81461102639376645458445563890933604042666832496108067724180262424539703394619135196334363979456757148465714312599695970577392038142353089971541912561007212572874317390985050129115895139824645075534050013217923053352965259741321376550049801845665852962177362515169292380016928834541247529513691701369654301864
1) sign
2) verify
3) exit
2
Signature: 81461102639376645458445563890933604042666832496108067724180262424539703394619135196334363979456757148465714312599695970577392038142353089971541912561007212572874317390985050129115895139824645075534050013217923053352965259741321376550049801845665852962177362515169292380016928834541247529513691701369654301864
AIS3{R3M383R_70_HAsh_7h3_M3Ssa93_83F0r3_S19N1N9}

FLAG:

1
AIS3{R3M383R_70_HAsh_7h3_M3Ssa93_83F0r3_S19N1N9}

Republic of South Africa

Author: Kuruwa

這題給了一個 chall.py 與 output.txt,先來看看他的 chall.py 裡面做了什麼事情。

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
from Crypto.Util.number import *
from secret import flag
import random
import gmpy2
gmpy2.get_context().precision = 1024

def collision(m1, v1, m2, v2):
return v1*(m1-m2)/(m1+m2) + v2*(2*m2)/(m1+m2), v1*(2*m1)/(m1+m2) + v2*(m2-m1)/(m1+m2)

def keygen(digits): # Warning: slow implementation
m1 = 1
m2 = 10 ** (2*digits-2)
v1 = gmpy2.mpfr(0)
v2 = gmpy2.mpfr(-1)

count = 0 # p+q
while abs(v1) > v2 or v1 < 0:
if v1 < 0:
v1 = -v1
else:
v1, v2 = collision(m1, v1, m2, v2)
count += 1

while True:
p = random.randint(count//3, count//2)
q = count - p
if isPrime(p) and isPrime(q):
break
return p, q


p, q = keygen(153)
n = p*q
e = 65537
m = bytes_to_long(flag)
print('n =', n)
print('e =', e)
print('c =', pow(m, e, n))

可以發現他用一種神奇的算法來產出 RSA 參數,那就來看看他是怎麼計算的吧。

一開始真的看不太出來上面的計算是什麼意思,卡了好一陣子。後來又回頭看看題目,collision 是指碰撞,再回頭看看 source code,發現他很好心的把變數設成 m1 跟 v1 這種型態,分別代表了質量和速度!

而 collision function 裡面傳回的便是一維碰撞後兩個物體分別的速度公式,從這裡可以確定碰撞的想法是正確的了。

其中v1<0 時,程式會將它變成相反數,就像撞上了牆壁無能量損失的反彈。因此綜合起來,兩個物體在進行碰撞且 m1 方有一面牆壁,而這時候看看 count 變數,它每碰撞一次便會+1,因此是計算碰撞次數,從這裡直接聯想到物理碰撞的著名經典問題:

從物理碰撞得出圓周率

得到這個結論之後就容易許多了,它給定質量是,因此可以得到 count 便是圓周率的前 154 位(推論過程詳見影片),而從裡面可以得到 count=p+q,所以我們便得到了p+q=314159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848這個條件。

那我們需要把 p 跟 q 解出來嗎?不需要!分別得到 p,q 是因為我們需要計算的值,但現在我們有兩個條件了,這樣便可以計算了。

接下來就是用簡單的 RSA 概念來解決它囉!

1
2
3
4
5
6
7
8
9
10
from Crypto.Util.number import *

n = 23662270311503602529211462628663973377651035055221337186547659666520360329842954292759496973737109678655075242892199643594552737098393308599593056828393773327639809644570618472781338585802514939812387999523164606025662379300143159103239039862833152034195535186138249963826772564309026532268561022599227047
e = 65537
c = 11458615427536252698065643586706850515055080432343893818398610010478579108516179388166781637371605857508073447120074461777733767824330662610330121174203247272860627922171793234818603728793293847713278049996058754527159158251083995933600335482394024095666411743953262490304176144151437205651312338816540536
k = 314159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848

phi = n-k+1
d = inverse(e, phi)
print(long_to_bytes(pow(c, d, n)))

FLAG:

1
AIS3{https://www.youtube.com/watch?v=jsYwFizhncE}

原來 flag 裡面給了一個碰撞的影片啊~有趣的題目 XD

Reverse

Piano

Author: CSY54

這題給了一個 zip,裡面包了一堆額外的設定檔之類的東西。先來執行 piano.exe,但它跳出了一個 alert 要我先去下載.NET 的 framework,這為我在下一步解題時開了一道曙光。

打開之後,它是一個 GUI 琴面,但是…不知道要彈什麼。看起來應該不能從這裡下手,那應該就是要 reverse 了。

先用 IDA Pro 打開.exe 看看,但沒有發現什麼可以用的東西。後來想到一開始顯示的.NET,於是上網查了一下關鍵字.NET reverse,在這個網站裡面發現了一個叫dnSpy的工具,可以對.NET 下的 framework 進行 reverse。那就來用用它吧!

用 dnSpy 打開 piano.exe 沒有發現東西,但打開 piano.dll 之後翻找了一下,發現兩個特別可疑的 functionisValid()nya():

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
// piano.Piano
// Token: 0x06000003 RID: 3 RVA: 0x00002220 File Offset: 0x00000420
private bool isValid()
{
List<int> list = new List<int>
{
14,
17,
20,
21,
22,
21,
19,
18,
12,
6,
11,
16,
15,
14
};
List<int> list2 = new List<int>
{
0,
-3,
0,
-1,
0,
1,
1,
0,
6,
0,
-5,
0,
1,
0
};
for (int i = 0; i < 14; i++)
{
if (this.notes[i] + this.notes[(i + 1) % 14] != list[i])
{
return false;
}
if (this.notes[i] - this.notes[(i + 1) % 14] != list2[i])
{
return false;
}
}
return true;
}

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
// piano.Piano
// Token: 0x06000004 RID: 4 RVA: 0x0000236C File Offset: 0x0000056C
private string nya()
{
List<int> list = new List<int>
{
70,
78,
89,
57,
112,
60,
125,
96,
103,
104,
50,
109,
87,
115,
112,
54,
100,
97,
103,
56,
85,
101,
56,
119,
119,
100,
59,
88,
50,
48,
62,
120,
84,
58,
100,
86,
74,
92,
54,
96,
60,
117,
119,
122
};
List<char> list2 = new List<char>();
for (int i = 0; i < list.Count; i++)
{
list2.Add((char)(list[i] ^ this.notes[i % this.notes.Count]));
}
return new string(list2.ToArray());
}

觀察一下兩個的關係,發現利用isValid()的條件可以算出 notes 的值,然後送到nya()可以把 flag 計算出來,那就寫個程式來實作就完成了。

先手動算出 notes 的值(很簡單啦不需要程式 XD):

1
{7,7,10,10,11,11,10,9,9,3,3,8,8,7,7}

接著套入nya()邏輯:

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

int k[100]={ 70,
78,
89,
57,
112,
60,
125,
96,
103,
104,
50,
109,
87,
115,
112,
54,
100,
97,
103,
56,
85,
101,
56,
119,
119,
100,
59,
88,
50,
48,
62,
120,
84,
58,
100,
86,
74,
92,
54,
96,
60,
117,
119,
122
};

int n[15]={7,7,10,10,11,11,10,9,9,3,3,8,8,7,7};

int main(){
for(int i=0;i<50;i++){
cout<<char(k[i]^n[i%14]);
}
cout<<endl;
return 0;
}

FLAG:

1
AIS3{7wink1e_tw1nkl3_l1ttl3_574r_1n_C_5h4rp}

P.S. 所以用 C#調彈小星星真的可以拿到 flag 喔~XD

  • Title: 2021 AIS3 Pre-exam Write Up
  • Author: Albert Huang
  • Created at : 2021-06-06 14:08:29
  • Updated at : 2026-06-26 14:08:16
  • Link: https://blog.albert-web.tw/2021/06/06/AIS3-2021-preexam/
  • License: This work is licensed under CC BY-NC-SA 4.0.