DEF CON CTF Qualifier 2019 Writeup

この大会は2019/5/11 9:00(JST)~2019/5/13 9:00(JST)に開催されました。
今回もチームで参戦。結果は727点で1262チーム中62位でした。
ReversingとCryptoの融合問題をずっとやっていたけど、
解析が間違っていたのか、復号できず。
今回もメインの問題は1問も解けず、残念!
Welcome問題ですが、自分で解けた問題をWriteupとして書いておきます。

welcome_to_the_game (WELCOMING)

$ cat flag
OOO{Game on!}
OOO{Game on!}

INS'hAck 2019 Writeup

この大会は2019/5/3 2:00(JST)~2019/5/6 1:30(JST)に開催されました。
今回もチームで参戦。結果は2841点で765チーム中7位でした。
自分で解けた問題をWriteupとして書いておきます。

Sanity (Misc 1)

問題にフラグが書いてあった。

INSA{Welcome}

Telegram (Misc 50)

Telegramのリンク先にアクセスすると、フラグが書かれている。
f:id:satou-y:20190509071807p:plain

INSA{is_e2e_8annEd_1n_Russi4_too}

Dashlame - Part 1 (Reverse 50)

Easy Python Decompilerでpycをデコンパイルする。

# Embedded file name: dashlame.py
from Crypto.Cipher import AES
import os
import random
import sys
import sqlite3
import time
import zlib
HEADER = "      /.m.\\\n     /.mnnm.\\                                              ___\n    |.mmnvvnm.\\.                                     .,,,/`mmm.\\\n    |.mmnnvvnm.\\:;,.                           ..,,;;;/.mmnnnmm.\\\n    \\ mmnnnvvnm.\\::;;,                    .,;;;;;;;;/.mmmnnvvnnm.|\n     \\`mmnnnvvnm.\\::;::.sSSs      sSSs ,;;;;;;;;;;/.mmmnnvvvnnmm'/\n       \\`mmnnnvnm.\\:::::SSSS,,,,,,SSSS:::::::;;;/.mmmnnvvvnnmmm'/\n          \\`mnvvnm.\\::%%%;;;;;;;;;;;%%%%:::::;/.mnnvvvvnnmmmmm'/\n             \\`mmmm.%%;;;;;%%%%%%%%%%%%%%%::/.mnnvvvnnmmmmm'/ '\n                \\`%%;;;;%%%%s&&&&&&&&&s%%%%mmmnnnmmmmmm'/ '\n     |           `%;;;%%%%s&&.%%%%%%.%&&%mmmmmmmmmm'/ '\n\\    |    /       %;;%%%%&&.%;`    '%.&&%%%////// '\n  \\  |  /         %%%%%%s&.%%   x   %.&&%%%%%//%\n    \\  .:::::.  ,;%%%%s&&&&.%;     ;.&&%%%%%%%%/,\n-!!!- ::#:::::%%%%%%s&&&&&&&&&&&&&&&&&%%%%%%%%%%%\n    / :##:::::&&&&&&&&&&&&&&&&&&&&&%%%%%%%%%%%%%%,\n  /  | `:#:::&&&&&&&&&&&&&&&&&&&&&&&&%%%%%%%%%%%%%\n     |       `&&&&&&&&&,&&&&&&&&&&&&SS%%%%%%%%%%%%%\n               `~~~~~'~~        SSSSSSS%%%%%%%%%%%%%\n                               SSSSSSSS%%%%%%%%%%%%%%\n                              SSSSSSSSSS%%%%%%%%%%%%%.\n                            SSSSSSSSSSSS%%%%%%%%%%%%%%\n                          SSSSSSSSSSSSS%%%%%%%%%%%%%%%.\n                        SSSSSSSSSSSSSSS%%%%%%%%%%%%%%%%\n                      SSSSSSSSSSSSSSSS%%%%%%%%%%%%%%%%%.\n                    SSSSSSSSSSSSSSSSS%%%%%%%%%%%%%%%%%%%\n                  SSSSSSSSSSSSSSSSSS%%%%%%%%%%%%%%%%%%%%.\n\n                          WELCOME TO DASHLAME\n"
PEARSON_TABLE = [199,
 229,
 151,
 178,
 53,
 6,
 131,
 42,
 248,
 110,
 39,
 28,
 51,
 216,
 32,
 14,
 77,
 34,
 166,
 213,
 157,
 150,
 115,
 197,
 228,
 221,
 254,
 172,
 84,
 27,
 36,
 156,
 69,
 96,
 12,
 220,
 225,
 137,
 246,
 141,
 44,
 208,
 191,
 109,
 163,
 21,
 173,
 250,
 98,
 227,
 203,
 162,
 188,
 3,
 105,
 171,
 215,
 15,
 207,
 218,
 234,
 56,
 136,
 235,
 97,
 79,
 189,
 102,
 134,
 11,
 224,
 117,
 177,
 222,
 100,
 129,
 78,
 18,
 130,
 187,
 9,
 184,
 99,
 108,
 202,
 13,
 238,
 17,
 94,
 70,
 180,
 144,
 185,
 168,
 123,
 71,
 176,
 91,
 4,
 153,
 103,
 242,
 80,
 127,
 198,
 82,
 169,
 148,
 48,
 120,
 59,
 55,
 230,
 209,
 50,
 73,
 31,
 49,
 142,
 149,
 167,
 249,
 116,
 1,
 7,
 86,
 143,
 101,
 29,
 52,
 114,
 154,
 160,
 128,
 19,
 170,
 46,
 214,
 38,
 67,
 186,
 252,
 181,
 145,
 212,
 183,
 22,
 231,
 107,
 43,
 47,
 122,
 251,
 217,
 5,
 62,
 88,
 244,
 200,
 93,
 240,
 219,
 124,
 58,
 161,
 89,
 211,
 158,
 247,
 60,
 236,
 65,
 106,
 113,
 66,
 81,
 165,
 194,
 223,
 40,
 233,
 126,
 139,
 72,
 132,
 61,
 135,
 57,
 87,
 182,
 164,
 35,
 159,
 118,
 8,
 83,
 210,
 243,
 104,
 76,
 75,
 119,
 90,
 138,
 20,
 206,
 95,
 16,
 74,
 33,
 245,
 237,
 111,
 64,
 253,
 125,
 23,
 232,
 193,
 37,
 175,
 92,
 30,
 241,
 255,
 133,
 0,
 140,
 2,
 155,
 85,
 10,
 146,
 179,
 25,
 26,
 226,
 201,
 195,
 121,
 190,
 63,
 68,
 152,
 45,
 147,
 41,
 204,
 192,
 205,
 196,
 54,
 174,
 239,
 112,
 24]

def pad(s):
    mark = chr(16 - len(s) % 16)
    while len(s) % 16 != 15:
        s += chr(random.randint(0, 255))

    return s + mark


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


def get_random_passphrase():
    sys.stdout.write('Getting random data from atmospheric noise and mouse movements')
    sys.stdout.flush()
    for i in range(10):
        sys.stdout.write('.')
        sys.stdout.flush()
        time.sleep(random.randint(1, 20) / 10.0)

    print ''
    with open('wordlist.txt', 'rb') as fi:
        passwords = fi.read().strip().split('\n')
    return (random.choice(passwords), random.choice(passwords))


def get_pearson_hash(passphrase):
    key, iv = ('', '')
    for i in range(32):
        h = (i + ord(passphrase[0])) % 256
        for c in passphrase[1:]:
            h = PEARSON_TABLE[h ^ ord(c)]

        if i < 16:
            key += chr(h)
        else:
            iv += chr(h)

    return (key, iv)


def encrypt_stream(data, passphrase):
    key, iv = get_pearson_hash(passphrase)
    aes = AES.new(key, AES.MODE_CBC, iv)
    data = pad(data)
    return aes.encrypt(data)


def decrypt_stream(data, passphrase):
    key, iv = get_pearson_hash(passphrase)
    aes = AES.new(key, AES.MODE_CBC, iv)
    data = unpad(aes.decrypt(data))
    return data


def encrypt_archive(archive_filename, passphraseA, passphraseB):
    with open(archive_filename, 'rb') as db_fd:
        with open(archive_filename.replace('.db', '.dla'), 'wb') as dla_fd:
            enc1 = encrypt_stream(db_fd.read(), passphraseA)
            enc2 = encrypt_stream(enc1, passphraseB)
            dla_fd.write(enc2)
    os.unlink(archive_filename)


def decrypt_archive(archive_filename, passphraseA, passphraseB):
    with open(archive_filename, 'rb') as dla_fd:
        with open(archive_filename.replace('.dla', '.db'), 'wb') as db_fd:
            dec1 = decrypt_stream(dla_fd.read(), passphraseB)
            dec2 = decrypt_stream(dec1, passphraseA)
            db_fd.write(dec2)
    os.unlink(archive_filename)


def createArchive():
    archive_name = raw_input('Please enter your archive name: ')
    passphraseA, passphraseB = get_random_passphrase()
    print 'This is your passphrase :', passphraseA, passphraseB
    print 'Please remember it or you will lose all your passwords.'
    archive_filename = archive_name + '.db'
    with open(archive_filename, 'wb') as db_fd:
        db_fd.write(zlib.decompress('x\x9c\x0b\x0e\xf4\xc9,IUH\xcb/\xcaM,Q0f`a`ddpPP````\x82b\x18`\x04b\x164>!\xc0\xc4\xa0\xfb\x8c\x9b\x17\xa4\x98y.\x03\x10\x8d\x82Q0\n\x88\x05\x89\x8c\xec\xe2\xf2\xf2\x8c\x8d\x82%\x89I9\xa9\x01\x89\xc5\xc5\xe5\xf9E)\xc5p\x06\x93s\x90\xabc\x88\xabB\x88\xa3\x93\x8f\xab\x02\\X\xa3<5\xa9\x18\x94\xabC\\#Bt\x14J\x8bS\x8b\xf2\x12sa\xdc\x02\xa820W\x13\x927\xcf0\x00\xd1(\x18\x05\xa3`\x08\x03#F\x16mYkh\xe6\x8fO\xadH\xcc-\xc8I\x85\xe5~O\xbf`\xc7\xea\x90\xcc\xe2\xf8\xa4\xd0\x92\xf8\xc4\xf8`\xe7"\x93\x92\xe4\x8cZ\x00\xa8&=\x8f'))
    encrypt_archive(archive_filename, passphraseA, passphraseB)
    print 'Archive created successfully.'


def updateArchive():
    archive_name = raw_input('Please enter your archive name: ')
    passphrase = raw_input('Please enter your passphrase: ')
    passphraseA, passphraseB = passphrase.split()
    website = raw_input('Website: ')
    username = raw_input('Username: ')
    password = raw_input('Password: ')
    dla_filename = archive_name + '.dla'
    db_filename = archive_name + '.db'
    decrypt_archive(dla_filename, passphraseA, passphraseB)
    conn = sqlite3.connect(db_filename)
    cur = conn.cursor()
    cur.execute('INSERT INTO Passwords VALUES(?,?,?)', (website, username, password))
    conn.commit()
    conn.close()
    encrypt_archive(db_filename, passphraseA, passphraseB)
    print 'Update done.'


def accessArchive():
    archive_name = raw_input('Please enter your archive name: ')
    passphrase = raw_input('Please enter your passphrase: ')
    passphraseA, passphraseB = passphrase.split()
    website = raw_input('Website: ')
    dla_filename = archive_name + '.dla'
    db_filename = archive_name + '.db'
    decrypt_archive(dla_filename, passphraseA, passphraseB)
    conn = sqlite3.connect(db_filename)
    cur = conn.cursor()
    cur.execute('SELECT Username, Password FROM Passwords WHERE Website=?', (website,))
    results = cur.fetchall()
    conn.close()
    encrypt_archive(db_filename, passphraseA, passphraseB)
    if len(results) == 0:
        print 'No results.'
    else:
        for result in results:
            print result[0], ':', result[1]


if __name__ == '__main__':
    print HEADER
    print '1. Create a new password archive'
    print '2. Add a password to an archive'
    print '3. Access a password from an existing archive'
    try:
        res = raw_input()
        if res == '1':
            createArchive()
        elif res == '2':
            updateArchive()
        elif res == '3':
            accessArchive()
        else:
            print 'Wrong choice'
    except:
        print 'Error.'

dbを暗号化してdlaを作成したあと、dbを削除している。dbを削除する処理をコメントアウトして実行してみる。

$ python dashlame.py
      /.m.\
     /.mnnm.\                                              ___
    |.mmnvvnm.\.                                     .,,,/`mmm.\
    |.mmnnvvnm.\:;,.                           ..,,;;;/.mmnnnmm.\
    \ mmnnnvvnm.\::;;,                    .,;;;;;;;;/.mmmnnvvnnm.|
     \`mmnnnvvnm.\::;::.sSSs      sSSs ,;;;;;;;;;;/.mmmnnvvvnnmm'/
       \`mmnnnvnm.\:::::SSSS,,,,,,SSSS:::::::;;;/.mmmnnvvvnnmmm'/
          \`mnvvnm.\::%%%;;;;;;;;;;;%%%%:::::;/.mnnvvvvnnmmmmm'/
             \`mmmm.%%;;;;;%%%%%%%%%%%%%%%::/.mnnvvvnnmmmmm'/ '
                \`%%;;;;%%%%s&&&&&&&&&s%%%%mmmnnnmmmmmm'/ '
     |           `%;;;%%%%s&&.%%%%%%.%&&%mmmmmmmmmm'/ '
\    |    /       %;;%%%%&&.%;`    '%.&&%%%////// '
  \  |  /         %%%%%%s&.%%   x   %.&&%%%%%//%
    \  .:::::.  ,;%%%%s&&&&.%;     ;.&&%%%%%%%%/,
-!!!- ::#:::::%%%%%%s&&&&&&&&&&&&&&&&&%%%%%%%%%%%
    / :##:::::&&&&&&&&&&&&&&&&&&&&&%%%%%%%%%%%%%%,
  /  | `:#:::&&&&&&&&&&&&&&&&&&&&&&&&%%%%%%%%%%%%%
     |       `&&&&&&&&&,&&&&&&&&&&&&SS%%%%%%%%%%%%%
               `~~~~~'~~        SSSSSSS%%%%%%%%%%%%%
                               SSSSSSSS%%%%%%%%%%%%%%
                              SSSSSSSSSS%%%%%%%%%%%%%.
                            SSSSSSSSSSSS%%%%%%%%%%%%%%
                          SSSSSSSSSSSSS%%%%%%%%%%%%%%%.
                        SSSSSSSSSSSSSSS%%%%%%%%%%%%%%%%
                      SSSSSSSSSSSSSSSS%%%%%%%%%%%%%%%%%.
                    SSSSSSSSSSSSSSSSS%%%%%%%%%%%%%%%%%%%
                  SSSSSSSSSSSSSSSSSS%%%%%%%%%%%%%%%%%%%%.

                          WELCOME TO DASHLAME

1. Create a new password archive
2. Add a password to an archive
3. Access a password from an existing archive
1
Please enter your archive name: nora
Getting random data from atmospheric noise and mouse movements..........
This is your passphrase : foundationalist oudated
Please remember it or you will lose all your passwords.
Archive created successfully.

作成されたdbをDB Browserで開き、Passwordsテーブルを見ると、フラグが格納されていた。
f:id:satou-y:20190509072056p:plain

INSA{Tis_bUt_a_SCr4tch}

Dashlame - Part 2 (Crypto 400)

暗号化の処理概要は以下の通り。

パスフレーズ2つをwordlist.txtから選択
・DBファイルを生成
・パスフレーズAでAES暗号化、さらにパスフレーズBでAES暗号化

ブルートフォースパスフレーズを探り、復号してSQLiteファイルのヘッダ部になるよう復号する。

from Crypto.Cipher import AES
import random

PEARSON_TABLE = [199,
 229,
 151,
 178,
 53,
 6,
 131,
 42,
 248,
 110,
 39,
 28,
 51,
 216,
 32,
 14,
 77,
 34,
 166,
 213,
 157,
 150,
 115,
 197,
 228,
 221,
 254,
 172,
 84,
 27,
 36,
 156,
 69,
 96,
 12,
 220,
 225,
 137,
 246,
 141,
 44,
 208,
 191,
 109,
 163,
 21,
 173,
 250,
 98,
 227,
 203,
 162,
 188,
 3,
 105,
 171,
 215,
 15,
 207,
 218,
 234,
 56,
 136,
 235,
 97,
 79,
 189,
 102,
 134,
 11,
 224,
 117,
 177,
 222,
 100,
 129,
 78,
 18,
 130,
 187,
 9,
 184,
 99,
 108,
 202,
 13,
 238,
 17,
 94,
 70,
 180,
 144,
 185,
 168,
 123,
 71,
 176,
 91,
 4,
 153,
 103,
 242,
 80,
 127,
 198,
 82,
 169,
 148,
 48,
 120,
 59,
 55,
 230,
 209,
 50,
 73,
 31,
 49,
 142,
 149,
 167,
 249,
 116,
 1,
 7,
 86,
 143,
 101,
 29,
 52,
 114,
 154,
 160,
 128,
 19,
 170,
 46,
 214,
 38,
 67,
 186,
 252,
 181,
 145,
 212,
 183,
 22,
 231,
 107,
 43,
 47,
 122,
 251,
 217,
 5,
 62,
 88,
 244,
 200,
 93,
 240,
 219,
 124,
 58,
 161,
 89,
 211,
 158,
 247,
 60,
 236,
 65,
 106,
 113,
 66,
 81,
 165,
 194,
 223,
 40,
 233,
 126,
 139,
 72,
 132,
 61,
 135,
 57,
 87,
 182,
 164,
 35,
 159,
 118,
 8,
 83,
 210,
 243,
 104,
 76,
 75,
 119,
 90,
 138,
 20,
 206,
 95,
 16,
 74,
 33,
 245,
 237,
 111,
 64,
 253,
 125,
 23,
 232,
 193,
 37,
 175,
 92,
 30,
 241,
 255,
 133,
 0,
 140,
 2,
 155,
 85,
 10,
 146,
 179,
 25,
 26,
 226,
 201,
 195,
 121,
 190,
 63,
 68,
 152,
 45,
 147,
 41,
 204,
 192,
 205,
 196,
 54,
 174,
 239,
 112,
 24]

def pad(s):
    mark = chr(16 - len(s) % 16)
    while len(s) % 16 != 15:
        s += chr(random.randint(0, 255))

    return s + mark

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

def get_pearson_hash(passphrase):
    key, iv = ('', '')
    for i in range(32):
        h = (i + ord(passphrase[0])) % 256
        for c in passphrase[1:]:
            h = PEARSON_TABLE[h ^ ord(c)]

        if i < 16:
            key += chr(h)
        else:
            iv += chr(h)

    return (key, iv)

def encrypt_stream(data, passphrase):
    key, iv = get_pearson_hash(passphrase)
    aes = AES.new(key, AES.MODE_CBC, iv)
    data = pad(data)
    return aes.encrypt(data)


def decrypt_stream(data, passphrase):
    key, iv = get_pearson_hash(passphrase)
    aes = AES.new(key, AES.MODE_CBC, iv)
    data = unpad(aes.decrypt(data))
    return data

with open('admin.dla', 'rb') as f:
    dla = f.read()

with open('wordlist.txt', 'r') as f:
    wl = f.read()

words = wl.split('\n')[:-1]

SQLITE_HEADER = 'SQLite format 3\x00'

enc_list = {}
for word in words:
    enc = encrypt_stream(SQLITE_HEADER, word)
    enc_list[enc[:16]] = word

for word in words:
    dec1 = decrypt_stream(dla, word)
    if dec1[:16] in enc_list:
        word2 = enc_list[dec1[:16]]
        print word2, word
        dec2 = decrypt_stream(dec1, word2)
        break

with open('admin.db', 'wb') as f:
    f.write(dec2)

復号を試した結果、以下のパスフレーズで暗号化したことがわかる。

spanish inquisition

復号したdbをDB Browserで開き、Passwordsテーブルを見ると、フラグが格納されていた。
f:id:satou-y:20190509072331p:plain

INSA{D0_you_f1nD_it_Risible_wh3N_I_s4y_th3_name}

Jean-Sébastien Bash (Crypto 500)

コマンドをAES-CBC暗号化したものを指定すると、そのコマンドが実行される。例としてls -l の暗号化データのみがわかっている。flag.txtがあるので、その内容を見ることができればよい。いろいろ試した結果、CBC Oracle Padding Attackで2ブロック目でcat flag.txtに復号できるような暗号データを割り出すことができればフラグが得られそう。
本来はプログラムでやりたいところだが、SSHでこのttyに接続する方法がわからず、手動でCBC Oracle Padding Attackを実行する。

$ ssh -i ~/.ssh/id_inshack -p 2227 user@jean-sebastien-bash.ctf.insecurity-insa.fr
The authenticity of host '[jean-sebastien-bash.ctf.insecurity-insa.fr]:2227 ([51.83.110.181]:2227)' can't be established.
ECDSA key fingerprint is SHA256:Hp/VF/ZZ75+zXkpM05kpIoN/0YXe5Fqlt+pTr3O/kVE.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '[jean-sebastien-bash.ctf.insecurity-insa.fr]:2227,[51.83.110.181]:2227' (ECDSA) to the list of known hosts.
 ___           _   _            _      ____   ___  _  ___
|_ _|_ __  ___| | | | __ _  ___| | __ |___ \ / _ \/ |/ _ \
| || '_ \/ __| |_| |/ _` |/ __| |/ /   __) | | | | | (_) |
| || | | \__ \  _  | (_| | (__|   <   / __/| |_| | |\__, |
|___|_| |_|___/_| |_|\__,_|\___|_|\_\ |_____|\___/|_|  /_/

===========================================================

      You are accessing a sandbox challenge over SSH
        This sandbox will be killed soon enough.
       Please wait while we launch your sandbox...

===========================================================
Welcome on my server. /help for help  

>/help
This is a tool so that only me can execute commands on my server
(without all the GNU/Linux mess around users and rights).

- /help for help
- /exit to quit
- /cmd <encrypted> to execute a command

Notes (TODO REMOVE THAT) ---------------------------
Ex: 
/cmd AES(key, CBC, iv).encrypt(my_command)
/cmd 7bcfab368dc137d4628dcf45d41f8885


>/cmd 7bcfab368dc137d4628dcf45d41f8885
Running b'ls -l'
total 8
-rw-r--r-- 1 root root   21 Apr 25 21:18 flag.txt
-rwxr-xr-x 1 root root 2066 Apr 25 21:50 server.py

/cmd 7bcfab368dc137d4628dcf45d41f88007bcfab368dc137d4628dcf45d41f8885
What do you mean?!

/cmd 7bcfab368dc137d4628dcf45d41f88017bcfab368dc137d4628dcf45d41f8885
What do you mean?!

/cmd 7bcfab368dc137d4628dcf45d41f88027bcfab368dc137d4628dcf45d41f8885
What do you mean?!

/cmd 7bcfab368dc137d4628dcf45d41f88037bcfab368dc137d4628dcf45d41f8885
What do you mean?!

/cmd 7bcfab368dc137d4628dcf45d41f88047bcfab368dc137d4628dcf45d41f8885
What do you mean?!

/cmd 7bcfab368dc137d4628dcf45d41f88057bcfab368dc137d4628dcf45d41f8885
What do you mean?!

/cmd 7bcfab368dc137d4628dcf45d41f88067bcfab368dc137d4628dcf45d41f8885
What do you mean?!

/cmd 7bcfab368dc137d4628dcf45d41f88077bcfab368dc137d4628dcf45d41f8885
Running b'\x1c\x01\xae\xa8\x95\x1b\x02\xa4\x1b{\xe8\x152,\xcb\xf96\xe6\xba]\xda\xe7\x0c\x86\x02\xd6\xa1-\xe3r\xcc'
sh: 1: ����{�2,��6��]��
                             �֡-�r�: not found

これでXORする前の7bcfab368dc137d4628dcf45d41f8885の復号したデータがわかった。あとは前がどんな文字列であっても2ブロック目が ; cat flag.txtに復号できる暗号データを計算してやる。

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

def pad(s):
    p = 16 - len(s) % 16
    return s + chr(p) * p

ct1 = '7bcfab368dc137d4628dcf45d41f88077bcfab368dc137d4628dcf45d41f8885'.decode('hex')
pt1 = '\x1c\x01\xae\xa8\x95\x1b\x02\xa4\x1b{\xe8\x152,\xcb\xf96\xe6\xba]\xda\xe7\x0c\x86\x02\xd6\xa1-\xe3r\xcc'

ct1_1 = ct1[:16]
pt1_2 = pad(pt1[16:])

pt2_2 = pad(' ; cat flag.txt')
ct2_1 = str_xor(str_xor(ct1_1, pt1_2), pt2_2)
ct2_2 = ct1[16:]

ct2 = (ct2_1 + ct2_2).encode('hex')
print ct2

この結果、暗号データは以下のようになる。

6d12310836521b340c3a0946431530077bcfab368dc137d4628dcf45d41f8885
>/cmd 6d12310836521b340c3a0946431530077bcfab368dc137d4628dcf45d41f8885
Running b'.\x15\xabh\xa1\x18\xd2\x88\xbeF\x7f\xdb\x12\xf9\xc4\xd7 ; cat flag.txt'
sh: 1: .�h�҈�F����: not found
INSA{or4cle_P4dd1ng}
INSA{or4cle_P4dd1ng}

Yet Another RSA Challenge - Part 1 (Crypto 500)

RSA暗号で、N、e、pの16進表記で'9F'を'FC'に置換したものと、cがわかっている。pの'FC'の一部を'9F'に置換することをブルートフォースで試して、Nを割り切れるものを探す。pがわかれば、qもわかり復号することができる。

from Crypto.Util.number import *
import itertools

def gen_p(str_p, t):
    p = str_p
    for e in t:
        p = p[:e] + '9F' + p[e + 2:]
    return eval(p)

with open('output.txt', 'r') as f:
    N = int(f.readline().rstrip())
    str_p_rep = f.readline().rstrip()
    c = int(f.readline().rstrip())

e = 65537

idxes = []
index = 0
while True:
    index = str_p_rep.find('FC', index)
    if index < 0:
        break
    idxes.append(index)
    index += 1

found = False
for i in range(1, len(idxes) + 1):
    for j in list(itertools.combinations(idxes, i)):
        p = gen_p(str_p_rep, j)
        if N % p == 0:
            found = True
            break
    if found:
        break

assert N % p == 0

q = N / p

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

flag = long_to_bytes(m)
print flag
INSA{I_w1ll_us3_OTp_n3xT_T1M3}

Yet Another RSA Challenge - Part 2 (Programming 222)

Part1と同様の問題。ただ、pが複数の文字のセットを置換しているので、難易度が上がっている。解き方は同様だが、プログラムとしては複雑化しているので、さまざまな箇所で注意が必要。

from Crypto.Util.number import *
import itertools

def get_idx(str_p, to_str):
    idxes = []
    index = 0
    while True:
        index = str_p.find(to_str, index)
        if index < 0:
            break
        idxes.append(index)
        index += 1
    return idxes

def gen_p(str_p, t, from_str):
    p = str_p
    for e in t:
        p = p[:e] + from_str + p[e + 2:]
    return p

with open('output.txt', 'r') as f:
    N = int(f.readline().rstrip())
    str_p_rep = f.readline().rstrip()
    c = int(f.readline().rstrip())

e = 65537

found = False
str_p = str_p_rep

idx0 = get_idx(str_p, '3E')
for i0 in range(1, len(idx0) + 1):
    for c0 in list(itertools.combinations(idx0, i0)):
        str_p0 = gen_p(str_p, c0, '59')

        idx1 = get_idx(str_p0, 'E0')
        for i1 in range(0, len(idx1) + 1):
            for c1 in list(itertools.combinations(idx1, i1)):
                str_p1 = gen_p(str_p0, c1, '9E')

                idx2 = get_idx(str_p1, '89')
                for i2 in range(0, len(idx2) + 1):
                    for c2 in list(itertools.combinations(idx2, i2)):
                        str_p2 = gen_p(str_p1, c2, '6B')

                        idx3 = get_idx(str_p2, '38')
                        for i3 in range(0, len(idx3) + 1):
                            for c3 in list(itertools.combinations(idx3, i3)):
                                str_p3 = gen_p(str_p2, c3, 'E4')

                                idx4 = get_idx(str_p3, '95')
                                for i4 in range(0, len(idx4) + 1):
                                    for c4 in list(itertools.combinations(idx4, i4)):
                                        str_p4 = gen_p(str_p3, c4, '09')

                                        idx5 = get_idx(str_p4, 'FF')
                                        for i5 in range(0, len(idx5) + 1):
                                            for c5 in list(itertools.combinations(idx5, i5)):
                                                str_p5 = gen_p(str_p4, c5, '5E')

                                                idx6 = get_idx(str_p5, 'D4')
                                                for i6 in range(0, len(idx6) + 1):
                                                    for c6 in list(itertools.combinations(idx6, i6)):
                                                        str_p6 = gen_p(str_p5, c6, '33')

                                                        idx7 = get_idx(str_p6, '8D')
                                                        for i7 in range(0, len(idx7) + 1):
                                                            for c7 in list(itertools.combinations(idx7, i7)):
                                                                str_p7 = gen_p(str_p6, c7, '12')

                                                                p = eval(str_p7)
                                                                if N % p == 0:
                                                                    found = True
                                                                    break
                                                            if found:
                                                                break

                                                        if found:
                                                            break
                                                    if found:
                                                        break

                                                if found:
                                                    break
                                            if found:
                                                break

                                        if found:
                                            break
                                    if found:
                                        break

                                if found:
                                    break
                            if found:
                                break

                        if found:
                            break
                    if found:
                        break

                if found:
                    break
            if found:
                break

        if found:
            break
    if found:
        break

assert N % p == 0

q = N / p

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

flag = long_to_bytes(m)
print flag
INSA{Uh_never_give_4w4y_your_Pr1mes_I_m34n_duhhh}

TSG CTF Writeup

この大会は2019/5/4 16:00(JST)~2019/5/5 16:00(JST)に開催されました。
今回もチームで参戦。結果は868点で334チーム中19位でした。
メインの問題は一問も解けず残念!暗号問題を1問は解きたかった。
一応自分で解けた問題をWriteupとして書いておきます。

Sanity Check (Warmup)

Discordに入ると、#announcementsチャネルのところにフラグが書いてある。

TSGCTF{ur_here_cuz_u_absolutely_won_inshack_ctf?}

Survey (Cooldown)

アンケートに答えたら、フラグが表示された。

TSGCTF{Hosting_a_CTF_is_really..._REALLY_tough_and_challenging!}

ALLMN CTF19 Writeup

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

Free point (Warmup 10)

問題にフラグが書いてあった。

ALLMN{Y3S_TH1S_1S_Y0UR_FLAG_27SJAL}

Discord (Warmup 10)

Discordに入って、ピン止めされたメッセージを見ると、フラグが書いてあった。

ALLMN{TH4NKS_F0R_J01N1NG_US_2JKLPO}

Saturn.MN (Social Engineering 30)

タブ表示になっている、viewやaboutなどを見ていく。
http://saturn.mn/p/425にフラグが書かれていた。

ALLMN{W3LC0M3_T0_S4TURN_MN}

Easy Math (Warmup 30)

128と56のGCD(最大公約数)は8、LCMは128*56/8で896。

ALLMN{896_8}

Funny Website (Web 50)

HTMLソースコードのコメントにフラグの一部が書いてある。

<!-- ALLMN{W3LL_Y0U -->

http://chall.all.mn/funny/style.cssのコメントにフラグの一部が書いてある。

/*_C4N_1NSP3CT */

http://chall.all.mn/funny/function.jsのコメントにフラグの一部が書いてある。

// _M3_I28DJA}

全部繋げると、フラグになる。

ALLMN{W3LL_Y0U_C4N_1NSP3CT_M3_I28DJA}

Robots (Web 70)

http://chall.all.mn/robot/robots.txtにアクセスする。

User-agent: *
Disallow: /allmnflag.php

http://chall.all.mn/robot/allmnflag.phpにアクセスすると、フラグが表示された。

ALLMN{W3B_R0B0TS_TXT_7DHJ9E}

Question & Answer (Crypto 100)

添付ファイルのテキストはシーザー暗号なので、https://www.geocachingtoolbox.com/index.php?lang=en&page=caesarCipherで復号する。

Rotation 18:
hey, you did a nice job! so here is your answer: eqra_cpf_rcuvg_vjku . but make sure it is in reverse form.

さらにeqra_cpf_rcuvg_vjkuの部分を復号する。

Rotation 2:
copy_and_paste_this

http://chall.all.mn/asuult/index.php?q=1でcopy_and_paste_thisを答えると、フラグが表示された。

ALLMN{Y3S_Y0U_D1D_1T_4Y7JKP}

What is this? (Crypto 100)

Brainfuck言語なので、https://sange.fi/esoteric/brainfuck/impl/interp/i.htmlで実行すると、フラグが表示された。

ALLMN{1_L0V3_BR41N_F4CK_3IQKAP}

Damn! (Crypto 100)

ChromeデベロッパーツールのConsoleで実行してみると、以下のエラーが発生。

Uncaught ReferenceError: f4ckjs is not defined
    at eval (eval at  (local-ntp.html:1), :3:1)
    at :1:4558

f4ckjsがフラグとなるワードとして通った。

ALLMN{f4ckjs}

Find that word (Misc 100)

添付ファイルを解凍すると大量のファイルが展開される。

$ grep -ilr ALLMN findtheflag/*
findtheflag/76.txt
$ grep ALLMN findtheflag/76.txt
         Pauses in Heaven doyoureallywanttoknowtheflag?hereitisALLMN{GR3P_1S_S1mple}.
ALLMN{GR3P_1S_S1mple}

SQL (Web 100)

SQLインジェクションで攻撃。以下の通り入力してログインすると、フラグが表示された。

Username: ' or 1=1 #
ALLMN{W0W_Y0U_D1D_SQLi}

Do you know that? (Crypto 100)

https://github.com/JohnHammond/ctf-katanaのDNAコード表を見てデコードする。

RCCDE{1E_DP_UE4}

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

Rotation 17:
ALLMN{1N_MY_DN4}
ALLMN{1N_MY_DN4}

My favorite number (Reversing 150)

逆アセンブルする。関係する部分は以下のようになっている。

push    rbp
mov     rbp, rsp
sub     rsp, 10h
mov     rax, fs:28h
mov     [rbp+var_8], rax
xor     eax, eax
lea     rdi, s          ; "Minii durtai toog oruulna uu"
call    _puts
lea     rax, [rbp+x]
mov     rsi, rax
lea     rdi, format     ; "%d"
mov     eax, 0
call    _scanf
mov     eax, [rbp+x]
cmp     eax, 4D1h
jnz     short loc_797

0x4D1と比較している。Pythonで0x4D1の値を確認する。

>>> 0x4D1
1233
ALLMN{1233}

ROCK (Forensics 150)

PDFファイルにパスワードがかかっているので、pdfcrackでパスワードを割り出す。

$ pdfcrack --wordlist=dict/rockyou.txt level-1.pdf 

PDF version 1.5
Security Handler: Standard
V: 2
R: 3
P: -4
Length: 128
Encrypted Metadata: True
FileID: a474737d247fa5259a6fc058ef096d2b
U: d18066309b9c6a7058223fcfa93890430122456a91bae5134273a6db134c87c4
O: 884bb701492d79d69ff17a588a87adb263143d261bdf1dbb0a616a47944d713e
Average Speed: 34703.2 w/s. Current Word: 'avril84'
Average Speed: 34329.1 w/s. Current Word: 'porn123tip'
Average Speed: 32437.8 w/s. Current Word: 'aritzel'
Average Speed: 31514.0 w/s. Current Word: 'xwettie'
Average Speed: 35259.1 w/s. Current Word: 'target0899'
Average Speed: 33870.6 w/s. Current Word: 'salimd'
Average Speed: 34358.9 w/s. Current Word: 'penchalick'
Average Speed: 30534.2 w/s. Current Word: 'ms.kylan'
Average Speed: 30263.8 w/s. Current Word: 'm0891553577'
Average Speed: 30269.2 w/s. Current Word: 'kiri404'
Average Speed: 29818.8 w/s. Current Word: 'jaspal209'
Average Speed: 28429.8 w/s. Current Word: 'hair821sold381'
Average Speed: 28589.2 w/s. Current Word: 'erestodoparamimau'
Average Speed: 31237.2 w/s. Current Word: 'crosby87fan'
Average Speed: 35053.8 w/s. Current Word: 'bisina'
Average Speed: 35201.6 w/s. Current Word: 'ai3office1'
Average Speed: 35135.8 w/s. Current Word: 'JenJen!'
Average Speed: 33758.6 w/s. Current Word: '898989r'
found user-password: '551731'

このパスワードでPDFファイルを開くと、フラグが書いてあった。

ALLMN{Y0U_DID_IT!}

Windows XP (Web 150)

UserAgentを該当するものにして、アクセスする

例)Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)
$ curl -H "User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)" http://chall.all.mn/windows/
<!DOCTYPE html>
<!--
__  __          _____  ______   ______     __
|  \/  |   /\   |  __ \|  ____| |  _ \ \   / /
| \  / |  /  \  | |  | | |__    | |_) \ \_/ /
| |\/| | / /\ \ | |  | |  __|   |  _ < \   /
| |  | |/ ____ \| |__| | |____  | |_) | | |
|_|__|_/_/    \_\_____/|______| |____/__|_|____    _ _____ _   _  _____  ____  __  __ _   _ _____          _
/ ____|  /\   | \ | |/ ____| |  | |_   _|  __ \  ( )_   _| \ | |/ ____|/ __ \|  \/  | \ | |_   _|   /\   ( )
| (___   /  \  |  \| | |    | |__| | | | | |__) | |/  | | |  \| | (___ | |  | | \  / |  \| | | |    /  \  |/
\___ \ / /\ \ | . ` | |    |  __  | | | |  _  /      | | | . ` |\___ \| |  | | |\/| | . ` | | |   / /\ \
____) / ____ \| |\  | |____| |  | |_| |_| | \ \     _| |_| |\  |____) | |__| | |  | | |\  |_| |_ / ____ \
|_____/_/ _  \_\_| \_|\_____|_|_ |_|_____|_|  \_\   |_____|_| \_|_____/ \____/|_|  |_|_| \_|_____/_/    \_\
   /\   | |    | |      |  \/  | \ | |
  /  \  | |    | |      | \  / |  \| |
 / /\ \ | |    | |      | |\/| | . ` |
/ ____ \| |____| |____ _| |  | | |\  |
/_/    \_\______|______(_)_|  |_|_| \_|
-->
<html lang="mn" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title>ALL.MN Academy</title>
    <link rel="icon" href="icon.png">
    <link rel="stylesheet" href="style.css" type="text/css">
  </head>
  <body>
    <div class="blocked">
      <img src="icon.png" width="200px"><br><br>
      Sorry, you cannot enter!<br>
      You can enter if you are using Windows XP and Internet Explorer.<br>
      ALLMN{CH4NG3_TH3_US3R_AG3NT_WD8KKA}    </div>
  </body>
</html>
ALLMN{CH4NG3_TH3_US3R_AG3NT_WD8KKA}

Encrypted (Crypto 250)

暗号は16進数で長さは608。1文字ずつMD5にしてつなげていると、推測して復号する。

import hashlib

h_dic = {}
for code in range(32, 127):
    h = hashlib.md5(chr(code)).hexdigest()
    h_dic[h] = chr(code)

with open('encrypted.txt', 'r') as f:
    data = f.read().rstrip()

flag = ''
for i in range(0, len(data), 32):
    flag += h_dic[data[i:i+32]]

print flag
ALLMN{MD5_1S_CR4ZY}

Admin panel (Crypto 250)

John the Ripperでパスワードを割り出す。

$ unshadow passwd shadow > passwd_shadow
$ john --wordlist=../dict/rockyou.txt passwd_shadow
Loaded 1 password hash (crypt, generic crypt(3) [?/64])
Press 'q' or Ctrl-C to abort, almost any other key for status
hellokitty       (root)
1g 0:00:00:01 100% 0.7462g/s 214.9p/s 214.9c/s 214.9C/s alyssa..brenda
Use the "--show" option to display all of the cracked passwords reliably
Session completed

root/hellokittyでログインする。

$ ./login
Sain uu? Ta nevtreh ner bolon nuuts ugee oruulna uu! CTF19.ALL.MN Admin Login
Nevtreh ner: 
root
Nuuts ug: 
hellokitty
ALLMN{G00DJ0B_Y0U_AR3_T00_G00D_SHD8WK}
ALLMN{G00DJ0B_Y0U_AR3_T00_G00D_SHD8WK}

My Little Electric CodeBook (Crypto 300)

ブロック暗号のECBモードの問題。ブロックごとに暗号文が同じであれば平文が同じことを使って解く。
以下の2つに暗号文を16バイトずつ区切って同じものを探す。

4c9e192389e8791554ae342a77069573
661a44fa2a2ab220ed33af9081049b8d

すると、以下の2行が見つかる。

4c9e192389e8791554ae342a77069573fd3281ec97b52ba8817e8560af2b187d:ALLMN{KTZFQVTLIPMONKTERRSCERKLK}
2d3d123382b1fcc266bf0bc5086da8b2661a44fa2a2ab220ed33af9081049b8d:ALLMN{HBSOPKYGXEPHNMSYKYHFXQIVW}

それぞれ前半と後半を切り出す。

ALLMN{KTZFQVTLIP
PHNMSYKYHFXQIVW}

結合するとフラグになる。

ALLMN{KTZFQVTLIPPHNMSYKYHFXQIVW}

CSA Capture The Flag 2019 Writeup

この大会は2019/4/25 11:00(JST)~2019/5/2 11:00(JST)に開催されました。
今回もチームで参戦。結果は5076点で363チーム中3位でした。
自分で解けた問題をWriteupとして書いておきます。

Sanity check(0x00)

問題にフラグが書いてあった。

CSACTF{w3lc0m3_t0_csa_ctf_2019}

Zippy (Forensics)

zipのヘッダ4バイトが壊れているので、修正する。

(誤) 00 00 00 00
(正) 50 4b 03 04

展開すると、flag.txtにフラグが書いてあった。

CSACTF{z1ppy_z1p_z1p}

Down to basic (Crypto)

フラグの先頭がCSACTF{となることからXOR鍵を算出し、さらに推測して、復号する。

c = '\x13\x13eg#v\t\x05\x0f#HE\x04CC\x07\x0f0V\x14\x15\\\x17\t\x0f2AU\x02\x01\x00\x01#\x1fE{\x14\\\x13\x17#qG{\x04\x00\x1e\x11$q\x14J\n'

pre_flag = 'CSACTF{'

key = ''
for i in range(len(pre_flag)):
    code = ord(c[i]) ^ ord(pre_flag[i])
    key += chr(code)

print key

## guess key ##
key += 'd'
print key

flag = ''
for i in range(len(c)):
    code = ord(c[i]) ^ ord(key[i%len(key)])
    flag += chr(code)

print flag

これでXOR鍵はP@$$w0rdであることがわかり、復号できた。

CSACTF{a_class1c_pr0blem_requ1res_a_class1c_s0lut10n}

Flag server (Crypto)

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

・入力データはBase64Base64デコードして以下の文字列にする。
 rowdy123 + <入力> +  + 
・AES暗号化してBase64エンコードして表示

文字列の長さを変えて試すと、10文字にしたとき、80バイトになったので、フラグの長さは46バイトであることもわかり、以下のようなイメージになる。

0123456789abcdef
rowdy123XXXXXXXX
XXFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFF
PPPPPPPPPPPPPPPP
0123456789abcdef
rowdy123XXXXXXXX
XXXXXXXXXXXXXXXC
XXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXF
FFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFF
FFFFFFFFFFFFFPPP

上記のようなイメージで、1文字ずつフラグをはみ出させ、2ブロック目と5ブロック目が一致するものを探すことを繰り返し、フラグを割り出す。

import socket
from base64 import *

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

flag = ''
for i in range(46):
    for c in range(32, 127):
        print '*** flag: %s ***' % flag
        print c, chr(c)

        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect(('34.74.132.34', 1337))

        data = recvuntil(s, '\n').rstrip()
        #print data
        if i < 16:
            text = 'X' * 8 + 'X' * (15 - (i % 16)) + flag + chr(c) \
                + 'X' * (47 - len(flag))
            print text
        else:
            text = 'X' * 8 + flag[i-15:i] + chr(c) \
                + 'X' * (47 - len(flag))
            print text
        text = b64encode(text)
        #print text
        s.sendall(text + '\n')

        size = len('rowdy123') + 71 - i + 46
        size = size + 16 - size % 16

        data = recvuntil(s, '\n').rstrip()
        #print data

        b64 = ''
        for j in range(size * 4 / (3 * 76) + 1):
            data = recvuntil(s, '\n').rstrip()
            #print data
            b64 += data

        enc = b64decode(b64)
        block1 = enc[16*1:16*2]
        block4 = enc[16*4:16*5]
        if block1 == block4:
            flag += chr(c)
            break

print flag
CSACTF{ser10usly_1_d0nt_kn0w_what_t0_put_here}

Shakespeare (Crypto)

eの値が小さく、平文の上位bitの大半がわかっている。このことから、以下のようなスクリプトで復号することができる。

# solve.sage
n = 1006836100850250538339995467613886290568442651407025704748377068277336663840322807255631910003671238333129747821401677091458273063019582521876226866878065342813920204554381690287329117261946553416320027957955605766622398073392496765842697469834794925876050925562024075006141634323253929247109497487662672405697258396584137509321741864516634651999799054786449300271622909227600883634147669010834991795161523697131426220251208433781244773539764161842505052231971382919967824210675662840663441620651420266255868914927411521784353964212033587445690357323252291991393014956932340980364269027698921907294758481600334348209162447060613941156368610501127037691522939020923578832525761118629677247463329559736428451094014067976185200082383567782696893487896242261214414477694719976590065452677776148568775926581762818443660878148364426201543151986222827020715235149143660874147096198623408316970919847284217769846103787437353010406070576416824522568438694050123611781945190356349091784143598551629255439066014442622323403606886940586598393546395396681244923344136771626115764972416530481118005853215560607832082875977841855163209032040736208394910518736188520737990966314657947406636362327107362260153133199038686467983874712019134795287956681
e = 5
c = 86369436556821828495369673854162831950166653329442831115191625341047835876798749659300641169348489814397657938758507056245452483528302648820602222051650089244062216320589929210206763500165600876025118953025309329553936210852805389539140958630003420335312979086383557665369163943903938569533466433527007050293325014896256907213847510652960137337747717702046541551623527661916693185081633247755853469488660176161381955671005765896319088865538114135666529015146105463393845863266076934785270480138522718706320412272484373838264459071202162126712065303703239606240172920462289657145390815683884595752646574944533803755632028037260059034716445191212182713792979655078091574530502495357406706909373119165806528380721574497499485062395281364120812874772053936952075952742036541816610742884797177390325939200184871385346082698051189479559236698554746173964482593646082680562341702105913029374921266420784671717205191676584949585227373190247242357902713051455202286347791244123645109240863246131183709864782892090906846140511095936549733130467184197878019539182792149521284353941031177248258779486351865884845751206163429977363088579292444338819912920250044064118502755440223212479004336304705210529515747947096857605944524561963438120052349

beta = 1
epsilon = beta^2/7

nbits = n.nbits()
kbits = floor(nbits*(beta^2/e-epsilon))

with open('msg.txt', 'r') as f:
    m0 = int(f.read().rstrip().replace('X', '\x00').encode('hex'), 16)

def_string = ' Message ends.'
def_size = len(def_string)

PR.<x> = PolynomialRing(Zmod(n))
f = (m0 + x * (256**def_size))^e - c
f = f.monic()

x0 = f.small_roots(X=2^kbits, beta=1)[0]
m = m0 + x0 * (256**def_size)
msg = ('%x' % m).decode('hex')
print msg

実行結果は以下の通り。

Message begins. To be, or not to be: that is the question: Whether 'tis nobler in the mind to suffer. The slings and arrows of outrageous fortune. Or to take arms against a sea of troubles. And by opposing end them. To die: to sleep; - Hamlet contemplating suicide in his famous soliloquy. (Hamlet) - CSACTF{w1ll14m-sh4k3sp34r3} Message ends.
CSACTF{w1ll14m-sh4k3sp34r3}

A game of apples (Misc)

90個以上あるリンゴから、サーバプレーヤーと交互に取っていき、最後のリンゴを取れれば、フラグを得ることができる。
残り10個目を取ると負けるので、そうならないようスクリプトを組み実行する。

import socket
from base64 import *

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

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('34.74.132.34', 1338))

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

data = recvuntil(s, '\n').rstrip()
print data
if data.split(' ')[1] == 'I':
    data = recvuntil(s, '\n').rstrip()
    print data
    data = recvuntil(s, '\n').rstrip()
    print data

remain = 999
count = 9
last = False
while True:
    if remain > 10 and remain < 20:
        count = remain - 10
    elif remain < 10:
        count = remain
        last = True
    data = recvuntil(s, '?\n').rstrip()
    print data
    print count
    s.sendall(str(count) + '\n')
    if last:
        break
    data = recvuntil(s, '\n').rstrip()
    print data
    data = recvuntil(s, '\n').rstrip()
    print data
    data = recvuntil(s, '\n').rstrip()
    print data
    remain = int(data.split(' ')[3])

data = recvuntil(s, '\n').rstrip()
print data
CSACTF{0n3_4ppl3_tw0_4ppl3_thr33_4ppl3}

Linux 1 (Misc)

$ ssh user@35.231.176.102 -p1773
The authenticity of host '[35.231.176.102]:1773 ([35.231.176.102]:1773)' can't be established.
ECDSA key fingerprint is SHA256:jnrxrLz++wBMSIAGbBB4KOK2Qpr9LW8kQ5tNvFN5LKc.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '[35.231.176.102]:1773' (ECDSA) to the list of known hosts.
user@35.231.176.102's password: 

_________   _________   _____  _______________________________
\_   ___ \ /   _____/  /  _  \ \_   ___ \__    ___/\_   _____/
/    \  \/ \_____  \  /  /_\  \/    \  \/ |    |    |    __)  
\     \____/        \/    |    \     \____|    |    |     \   
 \______  /_______  /\____|__  /\______  /|____|    \___  /   
        \/        \/         \/        \/               \/                                                                                                  
Welcome to CSACTF 2019!

If you find any problems, please report to admin.

-[ Rule ]-
 A few rules before you get started:
     + don't leave orphan processes running
     + don't leave exploit-files laying around
     + don't annoy other players
     + don't share passwords/solutions
     + last but not least, don't spoil the fun!

Have fun!
     - Blue
user@23ee3cd1ea3b:~$ ls -la
total 44
drwxr-xr-x 1 user user 4096 Apr 25 03:04 .
drwxr-xr-x 1 root root 4096 Apr 22 20:12 ..
-rw-r--r-- 1 user user  220 Aug 31  2015 .bash_logout
-rw-r--r-- 1 user user 3796 Apr 25 03:03 .bashrc
drwx------ 2 user user 4096 Apr 25 03:04 .cache
-rw-r--r-- 1 user user  655 May 16  2017 .profile
-rw-rw-r-- 1 user user    0 Apr 25 03:04 ahahaha
-rwxrwxr-x 1 root root  267 Apr 22 20:09 flag_reader.py
-rw-rw-r-- 1 root root   40 Apr 22 19:25 readme.txt
-rw-r--r-- 1 user user   19 Apr 25 03:03 temp.txt
user@23ee3cd1ea3b:~$ cat flag_reader.py 
#!/usr/bin/python
import time
import os

f = open('flag.txt', 'r')

# ==== Reading the flag
flag = f.read()
with open('temp.txt','w') as tmp:
  tmp.write('Reading the flag...')

#print flag
time.sleep(99999)

# ==== Done, Cleaning up
os.remove('temp.txt')

f.close()
user@abb60ff15525:~$ ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 11:44 ?        00:00:00 /bin/bash /root/init.sh
user        13     1  0 11:44 ?        00:00:00 python /home/user/flag_reader.py
root        16     1  0 11:44 ?        00:00:00 /usr/sbin/sshd -D
root        18    16  0 11:44 ?        00:00:00 sshd: user [priv]
user        22    18  0 11:44 ?        00:00:00 sshd: user@pts/0
user        23    22  0 11:44 pts/0    00:00:00 -bash
user        38    23  0 11:48 pts/0    00:00:00 ps -ef

python /home/user/flag_reader.pyが実行できて、処理中のようだ。実行中のプロセスの標準出力を見る。

user@abb60ff15525:~$ ls -l  /proc/13/fd/
total 0
lr-x------ 1 user user 64 Apr 25 11:49 0 -> /dev/null
l-wx------ 1 user user 64 Apr 25 11:49 1 -> pipe:[2690442]
l-wx------ 1 user user 64 Apr 25 11:49 2 -> pipe:[2690443]
lr-x------ 1 user user 64 Apr 25 11:49 3 -> /home/user/flag.txt (deleted)
user@abb60ff15525:~$ cat /proc/13/fd/3
CSACTF{f34r_cuts_d33p3r_th4n_sw0rds}
CSACTF{f34r_cuts_d33p3r_th4n_sw0rds}

stephanography (Misc)

ステガノグラフィーの問題だが、使用しているツールに関することが問題文に書かれている。https://github.com/stncal/appaで該当するツールを見つけた。

$ python3 appa.py -d secret_new.png
    :
Appa found an extremely large string in the image. To save your console, results are saved to file: secret_new.results
String length: 187142

secret_new.resultsはhexデータだが、jpgになりそう。hexデコードすると、jpg画像にフラグが書いてあった。
f:id:satou-y:20190506160817j:plain

CSACTF{y1p_y1p!}

We are CSA (Reconnaissance)

https://ctf.utsacyber.com/challenges#We%20are%20CSAのResponceを見る。

{"data": {"files": [], "description": "There's nothing here.\r\n", "tags": [], "minimum": 50, "id": 6, "type_data": {"templates": {"create": "/plugins/dynamic_challenges/assets/create.html", "update": "/plugins/dynamic_challenges/assets/update.html", "view": "/plugins/dynamic_challenges/assets/view.html"}, "scripts": {"create": "/plugins/dynamic_challenges/assets/create.js", "update": "/plugins/dynamic_challenges/assets/update.js", "view": "/plugins/dynamic_challenges/assets/view.js"}, "id": "dynamic", "name": "dynamic"}, "hints": [], "category": "Reconnaissance", "name": "We are CSA", "solves": 28, "decay": 50, "initial": 500, "value": 369, "state": "visible", "type": "dynamic", "max_attempts": 0}, "success": true}

Responceにはこんなコメントが入っている。

<!-- Oops, you found me! Part 1: Q1NBQ1RGe1VORDNSU1Q Check out: http://utsacyber.com/resources.html. We have tons of resources for beginners, especially about RE. -->

Base64デコードしてみる。

$ echo Q1NBQ1RGe1VORDNSU1Q | base64 -d
CSACTF{UND3RSTbase64: 無効な入力

まだ他の場所にもBase64文字列が隠れていそう。http://utsacyber.com/resources.htmlにアクセスしてHTMLソースを見る。
今度はこんなコメントが入っている。

<!-- Part 2: 0TkQxTkdfQ1lCM1JfUz One of our great members, Missing, has an awesome tutorial on Bug Hunting (and MIPS), you might want to check it out.-->

先ほどのBase64文字列と結合して、Base64デコードする。

$ echo Q1NBQ1RGe1VORDNSU1Q0TkQxTkdfQ1lCM1JfUz | base64 -d
CSACTF{UND3RST4ND1NG_CYB3R_Sbase64: 無効な入力

まだ情報が足りない。このページにMissing's Bug Hunting Cookbookというリンクがある。そのページ https://bh-cookbook.github.io/ にアクセスする。
コメントにはこう書かれている。

<!-- Part 3: NDVVIxVFlfMU5fNExMX Thank you Eli for designing the fancy logo on the CTF Homepage. -->

先ほどのBase64文字列とさらに結合して、Base64デコードする。

$ echo Q1NBQ1RGe1VORDNSU1Q0TkQxTkdfQ1lCM1JfUzNDVVIxVFlfMU5fNExMX | base64 -d
CSACTF{UND3RST4ND1NG_CYB3R_S3CUR1TY_1N_4LLbase64: 無効な入力

次はhttps://ctf.utsacyber.com/のページのロゴ画像をダウンロードすると、PNGファイルの後ろにデータがある。

Part 4: zFUU19EME00MU5TfQo=

先ほどのBase64文字列とさらに結合して、デコードする。

$ echo Q1NBQ1RGe1VORDNSU1Q0TkQxTkdfQ1lCM1JfUzNDVVIxVFlfMU5fNExMXzFUU19EME00MU5TfQo= | base64 -d
CSACTF{UND3RST4ND1NG_CYB3R_S3CUR1TY_1N_4LL_1TS_D0M41NS}
CSACTF{UND3RST4ND1NG_CYB3R_S3CUR1TY_1N_4LL_1TS_D0M41NS}

SECCON 令和CTF Writeup

この大会は2019/4/30 23:00(JST)~2019/5/1 2:00(JST)に開催されました。
この大会は個人戦。結果は210点で858人中107位でした。
解けた問題をWriteupとして書いておきます。

フラグの例は?(Misc)

問題にフラグが書いてあった。

SECCON{reiwa}

bREInWAck (Misc)

このファイルが添付されている。

令和和和和和和和和和和和和和和和和「令和
和和和和令和和和和令和和和和和和和令和和
和和和和令和和平平平平平成」令和和和。令
和和和和和。成成。。平成成成成。成。令令
和和和和和和和和和和和。令和和。平平平和
和和和。令和和。和和和和。令令和和和和和
和和和和和和和。平平平和和和和和和和和和
和和和和。成成成成成成成成。令成成成成成
成成成。令令。成成成成成。成成成成成成。
令和。平平和和。令令令和和和和和和和和和
和。

問題タイトルから推測しても、Brainfuck言語で、使われている2バイト文字と1:1対応しているようだ。使われている文字の意味と数を考え、以下のように置き換えた。

>++++++++++++++++[>+
++++>++++>+++++++>++
++++>++<<<<<-]>+++.>
+++++.--..<----.-.>>
+++++++++++.>++.<<<+
+++.>++.++++.>>+++++
+++++++.<<<+++++++++
++++.--------.>-----
---.>>.-----.------.
>+.<<++.>>>+++++++++
+.

以下のBrainfuck言語のオンラインインタプリタでこのコードを実行する。
sange.fi

SECCON{bREIn_WAnic!}

零は? (Misc)

ncで接続すると?を含む式が出てくる。式が成り立つ?を求めていくPPCの問題のようだ。99問目と100問目は方程式として成り立っていないので、解なしの場合は0で答えるようにする。

import socket
import re
import sympy

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

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('zerois-o-reiwa.seccon.jp', 23615))

for i in range(100):
    data = recvuntil(s, '?=')
    formula = data.split('\n')[-2].replace('?', 'x').split('=')[1]
    sol = sympy.solve(formula)
    if len(sol) == 1:
        ans = sol[0]
    else:
        ans = 0
    print data + str(ans)
    s.sendall(str(ans) + '\n')

data = recvuntil(s, '\n').rstrip()
print data
data = recvuntil(s, '\n').rstrip()
print data
SECCON{REIWA_is_not_ZERO_IS}

*CTF 2019 Writeup

この大会は2019/4/27 10:00(JST)~2019/4/29 10:00(JST)に開催されました。
今回もチームで参戦。結果は593点で334チーム中98位でした。
Welcome問題しか解けませんでしたが、
自分で解けた問題をWriteupとして書いておきます。

checkin (Misc)

IRCのfreenodeで#*ctf2019チャネルに入る。

10:41 *topic : welcome to *CTF2019, here is first flag *CTF{welcome} :P
*CTF{welcome}