TJCTF 2019 Writeup

この大会は2019/4/6 8:00(JST)~2019/4/10 8:00(JST)に開催されました。
今回は久々に個人で参戦。結果は330点で608チーム中98位でした。
解けた問題をWriteupとして書いておきます。

Blurry (Web 5)

HTMLソースにフラグが書いてある。

tjctf{cl0se_1nspecti0n}

Double Duty (Crypto 5)

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

tjctf{sekret_code}

Touch Base (Crypto 5)

Base64デコードする。

$ echo dGpjdGZ7ajJzdF9zMG0zX2I0c2U2NH0= | base64 -d
tjctf{j2st_s0m3_b4se64}
tjctf{j2st_s0m3_b4se64}

Corsair (Forensics 5)

Stegsolveで開き、Blue plane 4を見る。
f:id:satou-y:20190416211811p:plain

tjctf{c0l0r_pl4n3s_ar3_c00l}

Python in One Line (Reversing 10)

jとmが同じコードになっているので、調整してデコードする。

table = {'a':'...-', 'b':'--..', 'c':'/', 'd':'-.--', 'e':'.-.', 'f':'...', 'g':'.-..', 'h':'--', 'i':'---', 'j':'-', 'k':'-..-', 'l':'-..', 'm':'..', 'n':'.--', 'o':'-.-.', 'p':'--.-', 'q':'-.-', 'r':'.-', 's':'-...', 't':'..', 'u':'....', 'v':'--.', 'w':'.---', 'y':'..-.', 'x':'..-', 'z':'.--.', '{':'-.', '}':'.'}

codes = '.. - / .. ... -. - / -- --- .-. ... / -.-. --- -.. .'.split(' ')

flag = ''
for code in codes:
    for k, v in table.items():
        if v == code:
            flag += k
            break

flag = flag.replace('m', 't')
print flag
tjctf{jchiefcoil}

Sportsmanship (Cryptography 10)

Playfair暗号。https://www.dcode.fr/playfair-cipherで復号する。

PRACTICALPLAYFAIRX
tjctf{PRACTICALPLAYFAIRX}

Guess My Hashword (Cryptography 10)

条件から総当たりで目的のmd5になるものを探す。

import hashlib
import string
import itertools

target = '31f40dc5308fa2a311d2e2ba8955df6c'

found = False
for a in string.uppercase:
    for b in string.lowercase:
        for c in string.lowercase:
            for d in string.digits:
                chars = a + b + c + d + '_'
                for s in list(itertools.permutations(chars)):
                    word = ''.join(s)
                    flag = 'tjctf{%s}' % word
                    if hashlib.md5(flag).hexdigest() == target:
                        print flag
                        found = True
                        break
                if found:
                    break
            if found:
                break
        if found:
            break
    if found:
        break
tjctf{w0w_E}

Easy as RSA (Cryptography 20)

nをfactordbで素因数分解する。

379557705825593928168388035830440307401877224401739990998883 =
564819669946735512444543556507 * 671998030559713968361666935769

あとはそのまま復号する。

from Crypto.Util.number import *

n = 379557705825593928168388035830440307401877224401739990998883
e = 65537
c = 29031324384546867512310480993891916222287719490566042302485

p = 564819669946735512444543556507
q = 671998030559713968361666935769

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

flag = long_to_bytes(m)
print flag
tjctf{RSA_2_3asy}

Galaxy (Forensics 20)

Stegsolveで開き、Data ExtractでRGBのLSBにチェックをつけると、フラグが文字として現れた。

tjctf{last_but_n0t_l3ast}

Mind Blown (Forensics 30)

EXIFを見てみる。

$ exiftool 694003b3deecf2382b3aa510e5f3e5f5153bb9c062e4f20878c0d343bc297767_meme.jpg 
ExifTool Version Number         : 10.10
File Name                       : 694003b3deecf2382b3aa510e5f3e5f5153bb9c062e4f20878c0d343bc297767_meme.jpg
Directory                       : .
File Size                       : 87 kB
File Modification Date/Time     : 2019:04:06 18:39:14+09:00
File Access Date/Time           : 2019:04:06 18:45:37+09:00
File Inode Change Date/Time     : 2019:04:06 18:39:14+09:00
File Permissions                : rwxrwxrwx
File Type                       : JPEG
File Type Extension             : jpg
MIME Type                       : image/jpeg
JFIF Version                    : 1.01
Exif Byte Order                 : Big-endian (Motorola, MM)
X Resolution                    : 1
Y Resolution                    : 1
Resolution Unit                 : None
Y Cb Cr Positioning             : Centered
Compression                     : JPEG (old-style)
Thumbnail Offset                : 202
Thumbnail Length                : 62058
Comment                         : Compressed by jpeg-recompress
Image Width                     : 524
Image Height                    : 332
Encoding Process                : Progressive DCT, Huffman coding
Bits Per Sample                 : 8
Color Components                : 3
Y Cb Cr Sub Sampling            : YCbCr4:2:0 (2 2)
Image Size                      : 524x332
Megapixels                      : 0.174
Thumbnail Image                 : (Binary data 62058 bytes, use -b option to extract)
$ exiftool -b 694003b3deecf2382b3aa510e5f3e5f5153bb9c062e4f20878c0d343bc297767_meme.jpg > extract.jpg

先頭のごみを削除すると、jpgになる。またEXIFを見てみる。

$ exiftool extract.jpg
ExifTool Version Number         : 10.10
File Name                       : extract.jpg
Directory                       : .
File Size                       : 61 kB
File Modification Date/Time     : 2019:04:06 18:47:38+09:00
File Access Date/Time           : 2019:04:06 18:48:17+09:00
File Inode Change Date/Time     : 2019:04:06 18:47:38+09:00
File Permissions                : rwxrwxrwx
File Type                       : JPEG
File Type Extension             : jpg
MIME Type                       : image/jpeg
JFIF Version                    : 1.01
Exif Byte Order                 : Big-endian (Motorola, MM)
X Resolution                    : 1
Y Resolution                    : 1
Resolution Unit                 : None
Y Cb Cr Positioning             : Centered
Compression                     : JPEG (old-style)
Thumbnail Offset                : 202
Thumbnail Length                : 25641
Comment                         : Compressed by jpeg-recompress
Image Width                     : 534
Image Height                    : 380
Encoding Process                : Progressive DCT, Huffman coding
Bits Per Sample                 : 8
Color Components                : 3
Y Cb Cr Sub Sampling            : YCbCr4:2:0 (2 2)
Image Size                      : 534x380
Megapixels                      : 0.203
Thumbnail Image                 : (Binary data 25641 bytes, use -b option to extract)
$ exiftool -b extract.jpg > flag.jpg

先頭のごみを削除する。flag.jpgにフラグが書いてある。

tjctf{kn0w_y0ur_m3tad4ta}

Checker (Reversing 30)

Javaコードの処理は以下のようになっている。

1.flagの先頭からASCIIコードをpush
2.popしていき、2進数にして連結する。
3.0と1を逆にする。
4.9ビットシフトする。

これを逆算していき、途中いろいろ試しながら、2進数を分離し、フラグに戻す。

def dec_wow(b, s):
    r = ''
    for x in range(len(b)):
        r += b[(x-s)%len(b)]
    return r

def dec_woah(b):
    r = ''
    for x in range(len(b)):
        if b[x] == '0':
            r += '1'
        else:
            r += '0'
    return r

encoded = '1100001110000111000011000010100001110000111000010100001110000010000110010001011001110000101010001011000001000'

b = dec_wow(encoded, 9)
b = dec_woah(b)
print b

## manual decode ##
codes = '1111101 110011 1100011 110001 1110011 1101011 1100011 110001 1110101 1110001 1111011 1100110 1110100 1100011 1101010 1110100'
###################
codes = codes.split(' ')

flag = ''
for code in codes:
    flag += chr(int(code, 2))

flag = flag[::-1]
print flag
tjctf{qu1cks1c3}

Cable Selachimorpha (Forensics 40)

tcp.stream eq 14でHTTP Streamを見る。認証でPOSTパケットが見える。

POST /verify.php HTTP/1.1
Host: 192.168.56.101
Connection: keep-alive
Content-Length: 42
Cache-Control: max-age=0
Origin: http://192.168.56.101
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Referer: http://192.168.56.101/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9

usr=omkar&pwd=tjctf%7Bb0mk4r_br0k3_b10n%7D
tjctf{b0mk4r_br0k3_b10n}

Comprehensive (Reversing 50)

以下のような処理の流れ。

★f
k[0] ^ m[0+0]
k[1] ^ m[1+0]
    :
k[7] ^ m[7+0]

k[0] ^ m[0+8]
k[1] ^ m[1+8]
    :
k[7] ^ m[7+8]

k[0] ^ m[0+16]
k[1] ^ m[1+16]
    :
k[7] ^ m[7+16]


★g(fを1つの配列に!)
k[0] ^ m[0+0]
k[1] ^ m[1+0]
    :
k[7] ^ m[7+0]
k[0] ^ m[0+8]
k[1] ^ m[1+8]
    :
k[7] ^ m[7+8]
k[0] ^ m[0+16]
k[1] ^ m[1+16]
    :
k[7] ^ m[7+16]

★h(7個飛ばしで、3個ずつの組み合わせにする)
[g[0], g[8], g[16]], [g[1], g[9], g[17],...

★i
h[0][0] ^ k[0] = g[0] ^ k[0] = m[0] ^ k[0] ^ k[0]
h[0][1] ^ k[1] = g[8] ^ k[1] = m[8] ^ k[0] ^ k[1]
h[0][2] ^ k[2] = g[16] ^ k[2] = m[16] ^ k[0] ^ k[2]
h[1][0] ^ k[0] = g[1] ^ k[0] = m[1] ^ k[1] ^ k[0]
h[1][1] ^ k[1] = g[9] ^ k[1] = m[9] ^ k[1] ^ k[1]
h[1][2] ^ k[2] = g[17] ^ k[2] = m[17] ^ k[1] ^ k[2]
h[2][0] ^ k[0] = g[2] ^ k[0] = m[2] ^ k[2] ^ k[0]
h[2][1] ^ k[1] = g[10] ^ k[1] = m[10] ^ k[2] ^ k[1]
h[2][2] ^ k[2] = g[18] ^ k[2] = m[18] ^ k[2] ^ k[2]
    :
h[7][0] ^ k[0] = g[7] ^ k[0] = m[7] ^ k[7] ^ k[0]
h[7][1] ^ k[1] = g[15] ^ k[0] = m[15] ^ k[7] ^ k[1]
h[7][2] ^ k[2] = g[23] ^ k[0] = m[23] ^ k[7] ^ k[2]

★print
k[0]プラス

このことから以下が言える。

k[1] = (o[3] - k[0]) ^ m[1] ^ k[0]
k[2] = (o[6] - k[0]) ^ m[2] ^ k[0]
k[3] = (o[9] - k[0]) ^ m[3] ^ k[0]
k[4] = (o[12] - k[0]) ^ m[4] ^ k[0]
k[5] = (o[15] - k[0]) ^ m[5] ^ k[0]
k[7] = (o[23] - k[0]) ^ m[23] ^ k[2]

以上のことを踏まえスクリプトにすると、以下のようになりフラグが得られる。

enc = '225, 228, 219, 223, 220, 231, 205, 217, 224, 231, 228, 210, 208, 227, 220, 234, 236, 222, 232, 235, 227, 217, 223, 234, 2613'
sum_code = int(enc.split(', ')[-1])
enc = map(int, enc.split(', ')[:-1])

def decrypt(enc, key):
    m = [0] * len(enc)
    for i in range(len(enc)):
        idx = (i % 3) * 8 + i // 3
        m[idx] = (enc[i] - key[0]) ^ key[i//3] ^ key[i%3]
    s = ''
    for code in m:
        s += chr(code)
    return s

def get_sum(s):
    sum = 0
    for c in s:
        sum += ord(c)
    return sum

flag_head = 'tjctf{'
flag_tail = '}'

ks = [-1] * 8
ks[0] = enc[0] - ord(flag_head[0])

for i in range(1, 6):
    ks[i] = (enc[i*3] - ks[0]) ^ ord(flag_head[i]) ^ ks[0]

ks[7] = (enc[-1] - ks[0]) ^ ord(flag_tail) ^ ks[2]
print chr(ks[7])

for k_6 in range(32, 127):
    ks[6] = k_6
    flag = decrypt(enc, ks)
    if get_sum(flag) == sum_code:
        print flag
        break
tjctf{oooowakarimashita}

Is this the real life (Cryptography 90)

処理をデバッグしながら確認すると、以下のようなことがわかる。

block = 1
n = 1.00000000000000
phi = phiの先頭4バイト+(phiの末尾4バイトの逆)
nlistはphiのリスト(8個)

flagの各文字について
・nm = flagの文字のASCIIコード(2進数) いつも同じ
・cipher[i] = nm[i] * nlist[i]
 例) nm[0] = '01101110', nlist[0] = 42798447
・cipher[i] *= nm[i]
・cipher[i] = cipher[i] % modulus

平文1文字が暗号文1文字に対応するので、1文字ずつブルートフォースでフラグを求める。

nlist = [42798447, 60070844, 64372735, 50740679, 96064802, 42258424, 44356317, 77336984]
ct = [292296909762800, 215653060477940, 208434519524352, 292296909762800, 265338299876870, 338411113077906, 217961079953287, 344375715089844, 205288667438400, 16912457697000, 11315622010000, 341537164927645, 314135413320574, 169124576970000, 32145056144756, 344375715089844, 15964193777715, 292296909762800, 344375715089844, 388451782884159, 16912457697000, 26533829987687, 344375715089844, 150925396356060, 281783803794437, 87154851154764, 398222847250224]

def decrypt(c):
    for code in range(32, 127):
        nm = str(int(bin(code).replace('0b', '')))
        cipher = 0
        for b in range(len(nm)):
            cipher += int(nm[b]) * nlist[b]
        cipher = cipher * int(nm)
        if cipher == c:
            return chr(code)
    return chr(0)

flag = ''
for i in range(len(ct)):
    flag += decrypt(ct[i])

print flag
tjctf{i_T40ugh7_1t_w43_RsA}