5/19 から開催されていた Greycat CTF に参加してました。
今回はメンバーの都合が合わずライト参加で 131 位でした。
簡単に Writeup 書きます。
もくじ
Web-Assembly(Rev)
Handwriting assembly code is a bad idea…
問題バイナリとして以下の Javascript コードが与えられます。
Flag の検証を行っているwasmModule.instance.exports.check()
という関数はどうやら Web Assembly で実装されているようです。
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('Gimme something: ', (flag) => {
const wasmBinBuf = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 127, 2, 11, 1, 2, 106, 115, 3, 109, 101, 109, 2, 0, 1, 3, 2, 1, 0, 7, 9, 1, 5, 99, 104, 101, 99, 107, 0, 0, 10, 122, 1, 120, 1, 3, 127, 65, 0, 33, 0, 65, 1, 33, 2, 3, 64, 2, 64, 2, 64, 2, 64, 2, 64, 2, 64, 32, 0, 65, 4, 112, 14, 3, 3, 2, 1, 0, 11, 65, 137, 2, 33, 1, 12, 3, 11, 65, 59, 33, 1, 12, 2, 11, 65, 41, 33, 1, 12, 1, 11, 65, 31, 33, 1, 12, 0, 11, 32, 1, 65, 255, 1, 32, 0, 40, 2, 0, 113, 108, 65, 255, 1, 113, 32, 0, 65, 192, 0, 106, 40, 2, 0, 65, 255, 1, 113, 115, 65, 0, 70, 32, 2, 108, 33, 2, 32, 0, 65, 1, 106, 33, 0, 32, 0, 65, 46, 72, 13, 0, 11, 32, 2, 11])
const wasmMem = new WebAssembly.Memory({ initial: 10, maximum: 100 });
var strBuf = new TextEncoder().encode(flag.slice(0, 64));
const memBuf = new Uint8Array(wasmMem.buffer);
for (let i = 0; i < strBuf.length; i++) {
memBuf[i] = strBuf[i];
}
data = [121, 66, 71, 65, 229, 176, 150, 150, 43, 107, 209, 212, 12, 217, 16, 222, 129, 189, 55, 185, 82, 127, 229, 47, 45, 178, 252, 11, 107, 43, 31, 114, 20, 97, 229, 185, 237, 55, 252, 87, 12, 168, 75, 222, 121, 5]
for (let i = 0; i < data.length; i++) {
memBuf[i + 64] = data[i]
}
WebAssembly.instantiate(wasmBinBuf, {js: {mem: wasmMem}}).then(wasmModule => {
result = wasmModule.instance.exports.check();
if (result) {
console.log("Correct flag!");
} else {
console.log("?")
}
});
rl.close();
});
読み込まれる Web Assembly コードは wasmBinBuf で定義されているバイト配列で定義されていたので、Cyberchef でデコードしたものを .wasm ファイルとして保存しました。
続いて取得した wasm ファイルを Ghidra でデコンパイルし、以下のような関数を得ることができました。
int export::check(void)
{
uint *puVar1;
int iVar2;
int iVar3;
uint uVar4;
puVar1 = (uint *)0x0;
iVar3 = 1;
do {
uVar4 = (uint)puVar1 % 4;
if (uVar4 == 0) {
iVar2 = 0x1f;
}
else if (uVar4 == 1) {
iVar2 = 0x29;
}
else if (uVar4 == 2) {
iVar2 = 0x3b;
}
else {
iVar2 = 0x109;
}
iVar3 = (uint)((iVar2 * (*puVar1 & 0xff) & 0xff) == (puVar1[0x10] & 0xff)) * iVar3;
puVar1 = (uint *)((int)puVar1 + 1);
} while ((int)puVar1 < 0x2e);
return iVar3;
}
Flag の検証に使用するバイト配列は Javascript で data として定義されていたので、これを使用しつつ Flag を特定できる Solver を以下の通り作成しました。
base = [121, 66, 71, 65, 229, 176, 150, 150, 43, 107, 209, 212, 12, 217, 16, 222, 129, 189, 55, 185, 82, 127, 229, 47, 45, 178, 252, 11, 107, 43, 31, 114, 20, 97, 229, 185, 237, 55, 252, 87, 12, 168, 75, 222, 121, 5]
flag = ""
for i,b in enumerate(base):
if i % 4 == 0:
t = 0x1f
elif i % 4 == 1:
t = 0x29
elif i % 4 == 2:
t = 0x3b
elif i % 4 == 3:
t = 0x109
# (t * (x & 0xff) & 0xff) = (b & 0xff)
for x in range(0xff):
if (t * (x & 0xff) & 0xff) == (b & 0xff):
result = x
print(chr(result), end="")
# grey{0bfusc4t10n_u51ng_w3b4s53mbly_1s_4_th1ng}
これで Flag を取得できました。
ReService(Rev)
I found this file that was installed by the virus. Can you find out what it does?
It seems to connect to a c2 server and makes use of the current time.
go バイナリのデコンパイル結果を元に手動で書き起こしてみると、以下のようなコードで生成された 8 文字の HEX 文字列を URL に付与して http.Get() 関数でリクエストを発行するコードでした。
package main
import (
"fmt"
"hash/crc32"
"math/rand"
"time"
)
func main() {
now := time.Now().Unix()
rand.Seed(now)
hash := crc32.ChecksumIEEE([]byte(fmt.Sprintf("%x", rand.Int())))
result := fmt.Sprintf("%x", hash)
fmt.Println(result)
}
ここからこのファイルが作成された時刻をブルートフォースする形で特定するのかと思いきや全くそんなことはなく、バイナリ実行時のパケットストリームをみれば一発で Flag が取れる問題でした。
バイナリ解析は全く関係なく、どういう意図の問題なのかよくわかりませんでした。。