Hack.lu CTF 2017 Writeup

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

b64 (Crypto 100)

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

secret:ランダムな8バイトデータ。
secretと入力文字列のXORを取り、その結果に64をプラスし、256で割った余りをASCIIコードとして文字にする。
その結果からBase64で使われる文字以外を削除する。
文字列長を4で割って、1余る場合はその1文字を削除する。
余りが2または3の場合は、=でパディングし、4の倍数の文字列長にする。
Base64デコードしたものが提示される。

Base64文字以外がないものを探すことができれば、XORでsecretを算出することができる。それをコードにしたものは以下の通り。

import socket

PAD = 64

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

def decrypt(s1, s2):
    dec = ''
    for i in range(len(s1)):
        code = ord(s1[i]) ^ ((ord(s2[i]) - PAD) % 256)
        dec += chr(code)
    return dec

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('flatearth.fluxfingers.net', 1718))

data = recvuntil(s, '\n')
print data

in_str = ''
for i in range(4):
    found = False
    for code1 in range(0, 256, 16):
        for code2 in range(0, 256, 16):
            data = recvuntil(s, '>')
            data += recvuntil(s, ' ')
            print data + '1'
            s.sendall('1')
            data = recvuntil(s, '>')
            data += recvuntil(s, ' ')

            try_str = in_str + chr(code1) + chr(code2)
            print data + try_str
            s.sendall(try_str)
            data = recvuntil(s, '\n')
            print data
            dec = data[30:-1]
            if len(dec) > 0:
                b64 = dec.encode('base64').strip()
                b64_no_pad = b64.replace('=', '')
                if len(b64_no_pad) == ((i+1) * 2):
                    in_str += chr(code1) + chr(code2)
                    found = True
                    break
        if found:
            break

data = recvuntil(s, '>')
data += recvuntil(s, ' ')
print data + '2'
s.sendall('2')
data = recvuntil(s, '>')
data += recvuntil(s, ' ')

secret = decrypt(in_str, b64).encode('hex')
print data + secret
s.sendall(secret)
data = recvuntil(s, '\n')
print data
data = recvuntil(s, '\n')
print data
flag{7h3_b35t_w4y_of_h1ding_s3cr3t5_the_w0r1d_h4s_ev3r_seen_period!}

Hack Dat Kiwi 2017 Writeup

この大会は2017/10/13 13:00(JST)~2017/10/15 13:00(JST)に開催されました。
今回もチームで参戦。結果は605点で306チーム中53位でした。
自分で解けた問題をWriteupとして書いておきます。

PS 1 (Crypto 100)

AESの変形問題。内部の処理の回数などを変更し、簡易化されている。16バイトのブロック暗号で、pboxだけが位置を変更していることに注目して、復号してprintableなテキストに復号されるよう総当たりする。その際、sboxやpboxはphpのソースにデバッグログを入れて、具体的な値を取り出しておく。問題の暗号をテキストファイルenc.txtに保存しておき、次のコードで復号する。

from math import *

BLOCK_SIZE = 16

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

def get_pbox_for_seed_1396_rounds_1():
    pbox = [4, 10, 7, 5, 6, 13, 8, 14, 15, 9, 11, 2, 0, 1, 3, 12]
    return pbox

def pad(message, block_size):
    length = int(max(floor((len(message)-1)/block_size)+1, 1)*block_size)
    return message + ' ' * (length - len(message))

def sbox(block, reverse = False):
    sbox = get_sbox_for_seed_1396_rounds_1()
    out = ''
    if reverse:
        for i in range(BLOCK_SIZE):
            out += chr(sbox.index(ord(block[i])))
    else:
        for i in range(BLOCK_SIZE):
            out += chr(sbox[ord(block[i])])
    return out

def pbox(block, reverse = False):
    pbox = get_pbox_for_seed_1396_rounds_1()
    out = [' '] * BLOCK_SIZE
    if reverse:
        for i in range(BLOCK_SIZE):
            out[pbox[i]] = block[i]
    else:
        for i in range(BLOCK_SIZE):
            out[i] = block[pbox[i]]
    return ''.join(out)

def xbox(block, key):
    out = ''
    for i in range(BLOCK_SIZE):
        out += chr((ord(block[i]) ^ ord(key[i])) % 256)
    return out

def ps2_block(block, key, decrypt = False):
    key = pad(key, BLOCK_SIZE)

    if decrypt:
        block = xbox(block, key)
        block = pbox(block, decrypt)
        block = sbox(block, decrypt)
    else:
        block = sbox(block, decrypt)
        block = pbox(block, decrypt)
        block = xbox(block, key)
    return block

with open('enc.txt', 'r') as f:
    data = f.read().split(' ')

blocks = []
block = ''
for i in range(len(data)):
    block += chr(int(data[i], 16))
    if i % BLOCK_SIZE == BLOCK_SIZE - 1:
        blocks.append(block)
        block = ''

keys = []
indexes = [4, 10, 7, 5, 6, 13, 8, 14, 15, 9, 11, 2, 0, 1, 3, 12]

for index in indexes:
    for code in range(256):
        fail = False
        len_keys = len(keys)
        if len_keys > 0:
            try_key = keys[len_keys - 1] + chr(code)
        else:
            try_key = chr(code)
        for block in blocks:
            res = ps2_block(block, try_key, True)
            if (ord(res[index]) >= 32 and ord(res[index]) <= 126) \
                or ord(res[index]) == 10 or ord(res[index]) == 13:
                pass
            else:
                fail = True
                break
        if fail == False:
            keys.append(try_key)
            break

dec = ''
for block in blocks:
    dec += ps2_block(block, keys[15], True)

print dec

復号結果は次の通り。

A flag is a piece of fabric (most often rectangular or quadrilateral) with a distinctive design that is used as a symbol, as a signaling device, or as decoration. The term flag is also used to refer to the graphic design employed, and flags have since evolved into a general tool for rudimentary signalling and identification, especially in environments where communication is similarly challenging (such as the maritime environment where semaphore is used). National flags are potent patriotic symbols with varied wide-ranging interpretations, often including strong military associations due to their original and ongoing military uses. Flags are also used in messaging, advertising, or for other decorative purposes. The study of flags is known as vexillology, from the Latin word vexillum, meaning flag or banner.

Due to the use of flags by CTFs, "flag" is also '2qR4twRCoMq7SQb3' in this challenge.
2qR4twRCoMq7SQb3

PS 2 (Crypto 100)

考え方はPS 1と同じ。ただRound数が2になり、複雑性が増す。影響する位置が2箇所に増えることを考慮して、総当たりする。

from math import *

BLOCK_SIZE = 16
ROUNDS = 2

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

def get_pbox_for_seed_7_rounds_2():
    pbox = []
    pbox1 = [7, 14, 9, 2, 0, 8, 15, 1, 10, 3, 11, 4, 13, 12, 6, 5]
    pbox2 = [3, 12, 13, 0, 7, 1, 6, 9, 2, 5, 10, 14, 4, 8, 11, 15]
    pbox.append(pbox1)
    pbox.append(pbox2)
    return pbox

def pad(message, block_size):
    length = int(max(floor((len(message)-1)/block_size)+1, 1)*block_size)
    return message + ' ' * (length - len(message))

def sbox(block, round = 0, reverse = False):
    sbox = get_sbox_for_seed_7_rounds_2()
    out = ''
    if reverse:
        for i in range(BLOCK_SIZE):
            out += chr(sbox[round].index(ord(block[i])))
    else:
        for i in range(BLOCK_SIZE):
            out += chr(sbox[round][ord(block[i])])
    return out

def pbox(block, round = 0, reverse = False):
    pbox = get_pbox_for_seed_7_rounds_2()
    out = [' '] * BLOCK_SIZE
    if reverse:
        for i in range(BLOCK_SIZE):
            out[pbox[round][i]] = block[i]
    else:
        for i in range(BLOCK_SIZE):
            out[i] = block[pbox[round][i]]
    return ''.join(out)

def xbox(block, key):
    out = ''
    for i in range(BLOCK_SIZE):
        out += chr((ord(block[i]) ^ ord(key[i])) % 256)
    return out

def ps2_block(block, key, decrypt = False):
    key = pad(key, BLOCK_SIZE)

    if decrypt:
        for i in range(ROUNDS-1, -1, -1):
            block = xbox(block, key)
            block = pbox(block, i, decrypt)
            block = sbox(block, i, decrypt)
    else:
        for i in range(ROUNDS):
            block = sbox(block, i, decrypt)
            block = pbox(block, i, decrypt)
            block = xbox(block, key)
    return block

with open('enc.txt', 'r') as f:
    data = f.read().split(' ')

blocks = []
block = ''
for i in range(len(data)):
    block += chr(int(data[i], 16))
    if i % BLOCK_SIZE == BLOCK_SIZE - 1:
        blocks.append(block)
        block = ''

keys = []
indexes1 = [7, 14, 9, 2, 0, 8, 15, 1, 10, 3, 11, 4, 13, 12, 6, 5]
indexes2 = [3, 12, 13, 0, 7, 1, 6, 9, 2, 5, 10, 14, 4, 8, 11, 15]

for index1 in indexes2:
    index = indexes1[index1]
    for code1 in range(256):
        for code2 in range(256):
            fail = False
            len_keys = len(keys)
            if len_keys > 0:
                try_key = ''.join(keys) + chr(code1)
            else:
                try_key = chr(code1)
            try_key += chr(code2) * (16 - len(try_key))
            for block in blocks:
                res = ps2_block(block, try_key, True)
                if (ord(res[index]) >= 32 and ord(res[index]) <= 126) \
                    or ord(res[index]) == 10 or ord(res[index]) == 13:
                    pass
                else:
                    fail = True
                    break
            if fail == False:
                keys.append(chr(code1))
                break
        if fail == False:
            break

dec = ''
for block in blocks:
    dec += ps2_block(block, ''.join(keys), True)

print dec

実行結果は以下の通り。

An Easter egg is an intentional inside joke, hidden message, or feature in an interactive work such as a computer program, flag is 4y7u1t2CELXdPVoI, video game or DVD menu screen. The name is used to evoke the idea of a traditional Easter egg hunt.

Released in 1979, Atari's Adventure contained the first hidden message in a video game to have been discovered by its players; the message is "Created by Warren Robinett", and was inserted by Robinett, the game's programmer.[2] According to Robinett, the term Easter egg was applied by Atari personnel on being alerted to the secret addition.[3] Since Atari did not publicly credit game designers, Robinett inserted the message after the game's completion partially in an attempt to gain some recognition for his work.[2] In 2004, an earlier Easter egg was found in Video Whizball, a 1978 game for the Fairchild Channel F system, displaying programmer Bradley Reid-Selth's surname.
4y7u1t2CELXdPVoI

Survey (25)

アンケートに答えるだけ。

hsABf8jG3VK93hQf

Square CTF 2017 Writeup

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

Book of Allies (Grace Hopper 5)

scheduleタブを選択し、そこからリンクされているPDFを見てみる。
ホッチキスの露出しているページ、つまり真ん中のページの左側の一番上のセッションの会社名がフラグということらしい。全部で35ページあるので、18ページ目の左側を見る。

flag-thought-leadership-lab

Password checker (Web Security 50)

OSコマンドが実行できるが、結果は1行だけ表示される。

https://neget-kymud-nihat-fagoh-mifip.capturethesquare.com/run.php?cmd=cat%20../password.txt
password123

https://neget-kymud-nihat-fagoh-mifip.capturethesquare.com/run.php?cmd=ls%20-l%20..
-rw-r--r-- 3 root root 15 Oct 4 10:34 xxx_not_a_flag.txt

https://neget-kymud-nihat-fagoh-mifip.capturethesquare.com/run.php?cmd=ls%20-l%20../..
drwxr-xr-x 3 root root 4096 Oct 4 10:47 www

https://neget-kymud-nihat-fagoh-mifip.capturethesquare.com/run.php?cmd=ls%20-l%20../../..
drwxr-xr-x 1 root root 4096 Oct 4 10:47 var

https://neget-kymud-nihat-fagoh-mifip.capturethesquare.com/run.php?cmd=ls%20../f*
../flag.txt

https://neget-kymud-nihat-fagoh-mifip.capturethesquare.com/run.php?cmd=cat%20../flag.txt
line 2: flap-31aac7e26de449ee

https://neget-kymud-nihat-fagoh-mifip.capturethesquare.com/run.php?cmd=tac%20../flag.txt
line 1: flag-bc0a804287546c09
flag-bc0a804287546c09

The General's Cat (Crypto 50)

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

Rotation 17:
The domestic cat (Felis silvestris catus or Felis catus) is a small, typically furry, carnivorous mammal. They are often called house cats when kept as indoor pets or simply cats when there is no need to distinguish them from other felids and felines. Cats are often valued by humans for companionship and for their ability to hunt vermin. There are more than 70 cat breeds, though different associations proclaim different numbers according to their standards. The flag is the phrase with dashes: flag what is a domestic cat.
flag-what-is-a-domestic-cat

The Robot's Grandmother (Forensics 50)

SMTPTCP Streamで見る。

220 x.shh.sh ESMTP Exim 4.86 Wed, 06 Sep 2017 22:11:43 +0000
ehlo x.shh.sh
250-x.shh.sh Hello x.shh.sh [::1]
250-SIZE 52428800
250-8BITMIME
250-PIPELINING
250-AUTH LOGIN
250-STARTTLS
250-PRDR
250 HELP
auth login
334 VXNlcm5hbWU6
bWFsbG9yeQ==
334 UGFzc3dvcmQ6
ZmxhZy1zcGluc3Rlci1iZW5lZml0LWZhbHNpZnktZ2FtYmlhbg==
535 Incorrect authentication data
421 x.shh.sh lost input connection
$ echo ZmxhZy1zcGluc3Rlci1iZW5lZml0LWZhbHNpZnktZ2FtYmlhbg== | base64 -d
flag-spinster-benefit-falsify-gambian
flag-spinster-benefit-falsify-gambian

Can-CWIC CTF 2017 Writeup

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

Beautiful Blue (Steg 25)

高さ10pxごとに色が微妙に変わっている。青の色情報をASCIIコードとして文字にする。

from PIL import Image

img = Image.open('beautiful_blue.png').convert('RGB')
w, h = img.size

flag = ''
for y in range(0, h, 10):
    r, g, b = img.getpixel((0, y))
    flag += chr(b)

print flag
FLAG{Such_a_beautiful_color}

sudo (Steg 25)

解凍しようとすると、同じファイル名のファイルが2つ入っていることがわかる。1つを別名で展開し、ファイルサイズの小さい方を見てみると、フラグが書いてある。

FLAG{archives_are_waaaaayyy_easier_than_sudokus}

Coffee Stain (Steg 50)

ピクロスパズルのJPG画像。一部見えにくくなっている。
JPGの終端の後に、以下のデータあり。

h14:6,1,1,4,1,4,1
h15:7,2,2,3,1,5,2
h16:1,3,6,7,2
v29:2,5,2,7,10,2
v30:2,5,2,7,10,2

見えにくい部分はこれでわかる。この情報と合わせて、パズルを解いていくと、QRコードのようになる。
f:id:satou-y:20171015150457p:plain
このままだとうまく読み取れないので、3角の部分を少し調整すると、読み取ることができた。
f:id:satou-y:20171015150543p:plain

FLAG{NQNQR_C0D3}

Always Backup (Forensic 30)

http://159.203.38.169:5676/.git/にアクセスすると、flagファイルがある。
http://159.203.38.169:5676/.git/flagにアクセスしたら、フラグが書いてあった。

FLAG{ALWAYS-BACKUP!!!-and-use-git-to-backup...}

All is lost (Forensic 34)

http://159.203.38.169:5676/.git/objects/9d/dbc445f674f2891526db63b7bc5b5faea1e748をダウンロード

$ python -c 'import zlib; print zlib.decompress(open("dbc445f674f2891526db63b7bc5b5faea1e748").read())'
blob 77F
L
A
G
{
n
o
t
h
i
n
g
-
i
s
-
l
o
s
t
-
f
o
r
e
v
e
r
-
w
i
t
h
-
g
i
t
}
FLAG{nothing-is-lost-forever-with-git}

Tri it 0 (Prog 60)

$ nc 159.203.38.169 5672
Read https://en.wikipedia.org/wiki/Balanced_ternary
TT00T + TT010
a
Wrong answer.

https://en.wikipedia.org/wiki/Balanced_ternaryを参考に数値にして計算して、元の形式に戻すというプログラムを作成する。

import socket

bal3_letter = '01T'

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

def get_elem(s):
    elems = []
    elem = ''
    for i in range(len(s)):
        if s[i] in bal3_letter:
            elem += s[i]
        else:
            if elem != '':
                elems.append(elem)
                elem = ''
        if i == len(s) - 1:
            if elem != '':
                elems.append(elem)

    return elems

def get_formula(s, elems):
    formula = ''
    elem = ''
    index = 0
    for i in range(len(s)):
        if s[i] in bal3_letter:
            elem += s[i]
            if elem == elems[index]:
                formula += str(dec_bal3(elem))
                elem = ''
                index += 1
        else:
            formula += s[i]
    return formula

def dec_bal3(s):
    val = 0
    for i in range(len(s)):
        bit = len(s) - i - 1
        if s[i] == '0':
            bit_val = 0
        elif s[i] == '1':
            bit_val = 3 ** bit
        else:
            bit_val = - 3 ** bit
        val += bit_val
    return val

def enc_bal3(n):
    s = ''
    while n:
        s = bal3_letter[n % 3] + s
        n = -~n/3
    return s or 0

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('159.203.38.169', 5672))

data = recvuntil(s, '\n')
print data

# Try it 0
for i in range(2):
    print 'Round %d' % (i+1)
    data = recvuntil(s, '\n')
    print data
    data = data.replace('\n', '')
    elems = get_elem(data)
    formula = get_formula(data, elems)
    ans = enc_bal3(eval(formula))
    print ans
    s.sendall(ans + '\n')

data = recvuntil(s, '\n')
print data

2回答えると、フラグが表示された。

FLAG{I_CAN_MATH_LIKE_A_10RD_GRADER!}

Tri it 1 (Prog 150)

Tri it 0 の続き。足し算だけでなく、引き算、掛け算、かっこなども式に含まれる。

import socket

bal3_letter = '01T'

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

def get_elem(s):
    elems = []
    elem = ''
    for i in range(len(s)):
        if s[i] in bal3_letter:
            elem += s[i]
        else:
            if elem != '':
                elems.append(elem)
                elem = ''
        if i == len(s) - 1:
            if elem != '':
                elems.append(elem)

    return elems

def get_formula(s, elems):
    formula = ''
    elem = ''
    index = 0
    for i in range(len(s)):
        if s[i] in bal3_letter:
            elem += s[i]
            if elem == elems[index]:
                formula += str(dec_bal3(elem))
                elem = ''
                index += 1
        else:
            formula += s[i]
    return formula

def dec_bal3(s):
    val = 0
    for i in range(len(s)):
        bit = len(s) - i - 1
        if s[i] == '0':
            bit_val = 0
        elif s[i] == '1':
            bit_val = 3 ** bit
        else:
            bit_val = - 3 ** bit
        val += bit_val
    return val

def enc_bal3(n):
    s = ''
    while n:
        s = bal3_letter[n % 3] + s
        n = -~n/3
    return s or 0

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('159.203.38.169', 5672))

data = recvuntil(s, '\n')
print data

# Try it 0
for i in range(2):
    print 'Round %d' % (i+1)
    data = recvuntil(s, '\n')
    print data
    data = data.replace('\n', '')
    elems = get_elem(data)
    formula = get_formula(data, elems)
    ans = enc_bal3(eval(formula))
    print ans
    s.sendall(ans + '\n')

data = recvuntil(s, '\n')
print data

# Try it 1
for i in range(17):
    print 'Round %d' % (i+1)
    data = recvuntil(s, '\n')
    print data
    data = data.replace('\n', '')
    elems = get_elem(data)
    formula = get_formula(data, elems)
    ans = enc_bal3(eval(formula))
    print ans
    s.sendall(ans + '\n')

data = recvuntil(s, '\n')
print data

17回答えると、フラグが表示された。

FLAG{ONE_PLUS_ONE_EQ_ONE_TON}

Rich 2 (Crypto 200)

タイトル的な順番では逆だが、結果的にはRich 1よりこちらの方が先に解けた。

$ nc 159.203.38.169 5682
Please give me your wallet and I'll determine if you are rich enough to access out VIP area.
Here is an example wallet:
56e3fc849a2c06c2f858615a2a17b0525a1a5020874b2ceebaf411f2753f91864dced78d862c46daf45a204e3c18b35e40360675d70c7db1f9a417e5337ec4cf01de87d8d67a5ae0e4471a5c7854d613033d2b6ad5117780eee964a36471f6cd15d8ac9c9e330ebfb3521a5c7846de1f04362b7acd0b66939b9209c43d41d1dd028987ecc97d4df5c66c3e5f5c46ee1511351b19ef1052a4fad30ff5103792d237e1a88eec2254d3f9057c155351a262
56e3fc849a2c06c2f858615a2a17b0525a1a5020874b2ceebaf411f2753f91864dced78d862c46daf45a204e3c18b35e40360675d70c7db1f9a417e5337ec4cf01de87d8d67a5ae0e4471a5c7854d613033d2b6ad5117780eee964a36471f6cd15d8ac9c9e330ebfb3521a5c7846de1f04362b7acd0b66939b9209c43d41d1dd028987ecc97d4df5c66c3e5f5c46ee1511351b19ef1052a4fad30ff5103792d237e1a88eec2254d3f9057c155351a262
Plebs be plebs. No money, no access.
Guessing you cant math... 1137 is way too low.

提示された暗号文字列をそのまま入れると、amountの値が1137になっていることがわかる。
コードを読むと、暗号の概要は以下の通りとわかる。

plaintextは以下の形式
{"CCCCCC": "TTTTTT", "EEEEEE": "FLAG{DDDDDD}", "RRRRRR": "EEEEEE", "DDDDDD": "AAAAAA"}

enc_xor_cbc(plaintext, key, iv):暗号化16進数文字列を表示
plaintextを16の倍数になるようパディング(16の倍数の場合は16バイトパディング)

16バイトごとに以下の処理
1ブロック目暗号:1ブロック目平文 ^ key ^ IV
2ブロック目暗号:2ブロック目平文 ^ key ^ 1ブロック目暗号
3ブロック目暗号:3ブロック目平文 ^ key ^ 2ブロック目暗号
          :

このことから以下のようにして平文がわかるはず。

3ブロック目平文:3ブロック目暗号 ^ key ^ 2ブロック目暗号
2ブロック目平文:2ブロック目暗号 ^ key ^ 1ブロック目暗号
1ブロック目平文:1ブロック目暗号 ^ key ^ IV

11ブロック目暗号 ^ key ^ 10ブロック目暗号 = 11ブロック目平文

パディングの長さを1~16で総当たり、さらに部分的に復号された値と平文の一部を頭に入れながら、復号する。最終的には以下のコードでフラグが得られた。

import socket

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

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('159.203.38.169', 5682))

data = recvuntil(s, "\n")
print data
data = recvuntil(s, "\n")
print data
data = recvuntil(s, "\n")
print data

c_hex = data.strip()
c_list = []
for i in range(0, len(c_hex), 32):
    c_list.append(c_hex[i:i+32].decode('hex'))

for pad_length in range(16, 0, -1):
    p10_tail = '"}' + chr(pad_length) * pad_length
    block_chk_size = len(p10_tail)
    if block_chk_size > 16:
        block_chk_size = 16
    c09_tail = c_list[9][-block_chk_size:]
    c10_tail = c_list[10][-block_chk_size:]

    key = ''
    for i in range(block_chk_size):
        c09_char = c09_tail[-block_chk_size+i]
        c10_char = c10_tail[-block_chk_size+i]
        p10_char = p10_tail[-block_chk_size+i]
        code = ord(c09_char) ^ ord(c10_char) ^ ord(p10_char)
        key += chr(code)

    plain_list = []
    error = False
    for i in range(9):
        c_pre_tail = c_list[8-i][-block_chk_size:]
        c_cur_tail = c_list[9-i][-block_chk_size:]
        plain_tail = ''
        for j in range(block_chk_size):
            c_pre_char = c_pre_tail[-block_chk_size+j]
            c_cur_char = c_cur_tail[-block_chk_size+j]
            key_char = key[-block_chk_size+j]
            code = ord(c_pre_char) ^ ord(c_cur_char) ^ ord(key_char)
            if code < 32 or code > 126:
                error = True
                break
            else:
                plain_tail += chr(code)
        if error:
            break
        plain_list.append(plain_tail)

    if error == False:
        print plain_list

# 6th block
c_pre_char = c_list[4][12]
c_cur_char = c_list[5][12]
plain_char = '"'
key1_char = chr(ord(c_pre_char) ^ ord(c_cur_char) ^ ord(plain_char))

# 7th block
c_pre_char = c_list[5][:12]
c_cur_char = c_list[6][:12]
plain_char = 'unt": 1137, '
key2_char = ''
for i in range(len(c_cur_char)):
    key2_char += chr(ord(c_pre_char[i]) ^ ord(c_cur_char[i]) ^ ord(plain_char[i]))

key = key2_char + key1_char + key

# Decrypt the Later Parts
plain_tail = ''
for i in range(10):
    plain_part = ''
    c_pre = c_list[9 - i]
    c_cur = c_list[10 - i]
    for j in range(16):
        code = ord(c_pre[j]) ^ ord(c_cur[j]) ^ ord(key[j])
        plain_part += chr(code)
    plain_tail = plain_part + plain_tail

print plain_tail
FLAG{CBse4_Butch3reD_Cryp7o!WoW_R3cur51vE_@Cr0NymZ!?}

Rich 1 (Crypto 150)

Rich 2の続き。amountを1337にして、そのブロック以降を暗号化して投入する。

import socket

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

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('159.203.38.169', 5682))

data = recvuntil(s, "\n")
print data
data = recvuntil(s, "\n")
print data
data = recvuntil(s, "\n")
print data

c_hex = data.strip()
c_list = []
for i in range(0, len(c_hex), 32):
    c_list.append(c_hex[i:i+32].decode('hex'))

for pad_length in range(16, 0, -1):
    p10_tail = '"}' + chr(pad_length) * pad_length
    block_chk_size = len(p10_tail)
    if block_chk_size > 16:
        block_chk_size = 16
    c09_tail = c_list[9][-block_chk_size:]
    c10_tail = c_list[10][-block_chk_size:]

    key = ''
    for i in range(block_chk_size):
        c09_char = c09_tail[-block_chk_size+i]
        c10_char = c10_tail[-block_chk_size+i]
        p10_char = p10_tail[-block_chk_size+i]
        code = ord(c09_char) ^ ord(c10_char) ^ ord(p10_char)
        key += chr(code)

    plain_list = []
    error = False
    for i in range(9):
        c_pre_tail = c_list[8-i][-block_chk_size:]
        c_cur_tail = c_list[9-i][-block_chk_size:]
        plain_tail = ''
        for j in range(block_chk_size):
            c_pre_char = c_pre_tail[-block_chk_size+j]
            c_cur_char = c_cur_tail[-block_chk_size+j]
            key_char = key[-block_chk_size+j]
            code = ord(c_pre_char) ^ ord(c_cur_char) ^ ord(key_char)
            if code < 32 or code > 126:
                error = True
                break
            else:
                plain_tail += chr(code)
        if error:
            break
        plain_list.append(plain_tail)

    if error == False:
        print plain_list

# 6th block
c_pre_char = c_list[4][12]
c_cur_char = c_list[5][12]
plain_char = '"'
key1_char = chr(ord(c_pre_char) ^ ord(c_cur_char) ^ ord(plain_char))

# 7th block
c_pre_char = c_list[5][:12]
c_cur_char = c_list[6][:12]
plain_char = 'unt": 1137, '
key2_char = ''
for i in range(len(c_cur_char)):
    key2_char += chr(ord(c_pre_char[i]) ^ ord(c_cur_char[i]) ^ ord(plain_char[i]))

key = key2_char + key1_char + key

# Decrypt the Later Parts
plain_tail = []
for i in range(10):
    plain_part = ''
    c_pre = c_list[9 - i]
    c_cur = c_list[10 - i]
    for j in range(16):
        code = ord(c_pre[j]) ^ ord(c_cur[j]) ^ ord(key[j])
        plain_part += chr(code)
    plain_tail.append(plain_part)

print plain_tail

# Encrypt "amount": 1337
mod_c_list = []
for i in range(5):
    p_part = plain_tail[4-i]
    if i == 0:
        p_part = plain_tail[4-i][:7] + '3' + plain_tail[4-i][8:]
        c_pre = c_list[5+i]
    else:
        c_pre = mod_c_list[-1]
    c_part = ''
    for j in range(16):
        code = ord(p_part[j]) ^ ord(c_pre[j]) ^ ord(key[j])
        c_part += chr(code)
    mod_c_list.append(c_part)

new_c_hex = ''
for i in range(6):
    new_c_hex += c_list[i].encode('hex')

for i in range(5):
    new_c_hex += mod_c_list[i].encode('hex')

print new_c_hex
s.sendall(new_c_hex + '\n')

data = recvuntil(s, "\n")
print data
data = recvuntil(s, "\n")
print data
FLAG{nVm_74BLeZ_FL1P_B!Ts=O}

Kaspersky Industrial CTF Quals 2017 Writeup

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

Security Home Cameras (Crypto 300)

16バイトごとのXORになっていると推定して、復号する。

with open('secret_encrypted.png', 'rb') as f:
    data = f.read()

PNG_HEAD = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
    0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52]

key = []
for i in range(len(PNG_HEAD)):
    key.append(ord(data[i]) ^ PNG_HEAD[i])

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

with open('flag.png', 'wb') as f:
    f.write(flag)

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

KLCTF{it_was_just_atbash_encryption}

DefCamp CTF Qualification 2017 Writeup

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

HEX Warm Up (Junior 1)

コードを見ながら、lock.isoをバイナリエディタで見ると、0x70バイト目から0x22Dバイト目までがbackup.zipであることがわかる。このzipを展開すると、index.txtに次のメッセージが書いてあった。

Hi man!

This is your flag!

DCTF{474dac08d29d013515a312d1a8460050634f9b3cb6a696a4c73652d1802a1872}

You could do that as:
buf = ""
g = open("lock.iso", "r")
buf += g.read()
print buf
buf = buf.encode('hex')
print buf
n = 7 * 32
buf = buf[n:]
n = 31 * 32
n = n + 8
buf = buf[:n]
print buf
buf = buf.decode('hex')
print buf
f = open("sol.zip", "w")
f.write(buf)
f.close()
g.close()

Or my simple you could change the extension.
DCTF{474dac08d29d013515a312d1a8460050634f9b3cb6a696a4c73652d1802a1872}

Loyal book (Junior 2)

各ファイルの差分をつなげていく。

$ diff 0001.txt 0002.txt
2217c2217
< glimpse last summer at the Palais-Royal. Some of 
---
> glimpse last summer DC at the Palais-Royal. Some of 

$ diff 0001.txt 0003.txt
3745c3745
< benches ranged along the walls, and in the centre of 
---
> benches ranged along TFthe walls, and in the centre of

$ diff 0001.txt 0004.txt
27559c27559
< And it must have been very strong to endure after 
---
> And it must h{ave been very strong to endure after

$ diff 0001.txt 0005.txt
27559c27559
< And it must have been very strong to endure after 
---
> And it must have been 7ba61 very strong to endure after

$ diff 0001.txt 0006.txt
5933c5933
< and Frederick, feeling sleepy, was in no great haste 
---
> and Frederick, feeling sleepy, wa0cc5ds in no great haste

$ diff 0001.txt 0007.txt
18816c18816
< communicated to him ; and he had only two phrases : 
---
> communicated to him ; aa3966nd he had only two phrases :

$ diff 0001.txt 0008.txt
25872c25872
< derstand. She longed for wealth, in order to crush 
---
> derstand. She longed fob7c64r wealth, in order to crush

$ diff 0001.txt 0009.txt
24835c24835
< They all took advantage of the occasion to denounce 
---
> They all took advantage a81c3 of the occasion to denounce

$ diff 0001.txt 0010.txt
749c749
< " Ha ! your chum ! " said Madame Moreau, with a 
---
> " Ha ! your chum ! " said Madame Morcfcdbeau, with a 
769c769
< barely maintained him. Made bitter by continuous 
---
> barely maintained him. 1b1d0 Made bitter by continuous 
3854c3854
< 82 GUSTAVE FLAUBERT 
---
> 82 GUSTAVE FLAUBERT 9e3de
7419c7419
< and the 'longshore-woman exclaimed : 
---
> and the 'longshore-woman exclaimed : 5ad11
9279c9279
< The advocate went on : 
---
> The advocate 89268 went on : 
11727c11727
< ceive either. 
---
> ceive either. bf0e6
14277c14277
< places at which the principals in the duel were to 
---
> places at 18ff7 which the principals in the duel were to 
20565c20565
< deur. 
---
> deur. 1f08}
DCTF{7ba610cc5da3966b7c64a81c3cfcdb1b1d09e3de5ad1189268bf0e618ff71f08}

Forgot my Key (Misc 400)

messageの一部のkeyの部分に注目して計算し、すべて16進数表記の文字になる鍵を探し、復号する。

$encrypted[72] = (($key[0] + $key[7] + $encrypted[71]) % 126)
$encrypted[73] = (($key[1] + $key[8] + $encrypted[72]) % 126)
$encrypted[74] = (($key[2] + $key[9] + $encrypted[73]) % 126)
    :
$encrypted[101] = (($key[29] + $key[4] + $encrypted[100] ) % 126)
$encrypted[102] = (($key[30] + $key[5] + $encrypted[101] ) % 126)
$encrypted[103] = (($key[31] + $key[6] + $encrypted[102] ) % 126)
import string

def decrypt(enc, key):
    dec = ''
    for i in range(1, len(enc)/2):
        dec += chr((int(enc[i*2:(i+1)*2], 16)
               - ord(key[(i-1)%len(key)])
               - int(enc[(i-1)*2:i*2], 16)) % 126)
    return dec

e = '5616f5962674d26741d2810600a6c5647620c4e3d2870177f09716b2379012c342d3b584c5672195d653722443f1c39254360007010381b721c741a532b03504d2849382d375c0d6806251a2946335a67365020100f160f17640c6a05583f49645d3b557856221b2'

enc = ''
for i in range(0, len(e), 2):
    e_part = e[i:i+2]
    enc += e_part[::-1]

def decrypt(enc, key):
    dec = ''
    for i in range(1, len(enc)/2):
        dec += chr((int(enc[i*2:(i+1)*2], 16)
               - ord(key[(i-1)%len(key)])
               - int(enc[(i-1)*2:i*2], 16)) % 126)
    return dec

key_str = ''
for h in string.hexdigits:
    key = [0] * 32
    key[0] = ord(h)
    for i in range(32):
        ecode1 = int(enc[(72+((i*7)%32))*2:(73+((i*7)%32))*2], 16)
        ecode0 = int(enc[(71+((i*7)%32))*2:(72+((i*7)%32))*2], 16)
        key[((i+1)*7)%32] = (ecode1 - key[(i*7)%32] - ecode0) % 126
    success = True
    for i in range(32):
        if chr(key[i]) not in string.hexdigits:
            success = False
            break
    if success:
        for i in range(len(key)):
            key_str += chr(key[i])

flag = decrypt(enc, key_str)
print flag
DCTF{0d940de38493d96dc6255cbb2c2ac7a2db1a7792c74859e95215caa6b57c69b2}

Survey(Junior 3)

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

DCTF{a6a2729cbf6bcadce577a31f7f76201d5ce63c57d6c53318000d67714bb354ef}

BackdoorCTF 2017 Writeup

この大会は2017/9/24 0:30(JST)~2017/9/25 0:30(JST)に開催されました。
今回もチームで参戦。結果は全問制覇の3600点で、212チーム中6位でした。
自分で解けた問題をWriteupとして書いておきます。

IMAGEREV (200)

RGBの値をASCIIコードとして結合(3バイト)。それに対して、暗号化してその結果、64バイトになる。暗号化処理の内容は復号するコードにすることがかなり難しい。
そこでencrypted.txtを見ると、64バイトごとに同じようなデータが多数あるので、RとGとBの値はすべて同じであると推定して、256パターンの総当たりで復元する。
またピクセルの数はデータの長さから7371。素因数分解し、その組み合わせの積を考慮して、画像の幅と高さをいろいろと試してみる。

7371 = 3^4 * 7 * 13

結果は幅が351の時にフラグが書かれた画像に復元することができた。そのときのコードは次の通り。

from PIL import Image

def bin_return(dec):
    return(str(format(dec,'b')))

def bin_8bit(dec):
    return(str(format(dec,'08b')))

def convert_32bit(dec):
    return(str(format(dec,'032b')))

def convert_64bit(dec):
    return(str(format(dec,'064b')))

def hex_return(dec):
    return expand(hex(dec).replace('0x','').replace('L',''))

def dec_return_bin(bin_string):
    return(int(bin_string,2))

def dec_return_hex(hex_string):
    return(int(hex_string,16))

def some_LP(l,n):
    l1=[]
    j=0
    k=n
    while k<len(l)+1:
        l1.append(l[j:k])
        j=k
        k+=n 
    return(l1)

def rotate_right(bit_string,n):
    bit_list = list(bit_string)
    count=0
    while count <= n-1:
        list_main=list(bit_list)
        var_0=list_main.pop(-1)
        list_main=list([var_0]+list_main)
        bit_list=list(list_main)
        count+=1
    return(''.join(list_main))

def shift_right(bit_string,n):
    bit_list=list(bit_string)
    count=0
    while count <= n-1:
        bit_list.pop(-1)
        count+=1
    front_append=['0']*n
    return(''.join(front_append+bit_list))

def addition(input_set):
    value=0
    for i in range(len(input_set)):
        value+=input_set[i]
    mod_32 = 4294967296
    return(value%mod_32)

def str_xor(s1,s2):
    return ''.join([str(int(i)^int(j)) for i,j in zip(s1,s2)])

def str_and(s1,s2):
    return ''.join([str(int(i)&int(j)) for i,j in zip(s1,s2)])

def str_not(s):
    return ''.join([str(int(i)^1) for i in s])

def not_and_and_xor(x,y,z):
    return(str_xor(str_and(x,y),str_and(str_not(x),z)))

def and_and_and_xor_xor(x,y,z):
    return(str_xor(str_xor(str_and(x,y),str_and(x,z)),str_and(y,z)))

def some_e0(x):
    return(str_xor(str_xor(rotate_right(x,2),rotate_right(x,13)),rotate_right(x,22)))

def some_e1(x):
    return(str_xor(str_xor(rotate_right(x,6),rotate_right(x,11)),rotate_right(x,25)))

def some_s0(x):
    return(str_xor(str_xor(rotate_right(x,7),rotate_right(x,18)),shift_right(x,3)))

def some_s1(x):
    return(str_xor(str_xor(rotate_right(x,17),rotate_right(x,19)),shift_right(x,10)))

def expand(s):
        return '0'*(8-len(s))+s

def get_pixels_list(filename):
    im = Image.open(filename)
    return list(im.getdata())

def data_encrypted(list_of_pixels):
        data = ''
        for i in list_of_pixels:
                d = ''.join([chr(j) for j in i])
                d = encryption(d)
                data += ''.join(d)
                print len(data)
        return data

def message_pad(bit_list):
    pad_one = bit_list + '1'
    pad_len = len(pad_one)
    k=0
    while ((pad_len+k)-448)%512 != 0:
        k+=1
    back_append_0 = '0'*k
    back_append_1 = convert_64bit(len(bit_list))
    return(pad_one+back_append_0+back_append_1)

def message_bit_return(string_input):
    bit_list=[]
    for i in range(len(string_input)):
        bit_list.append(bin_8bit(ord(string_input[i])))
    return(''.join(bit_list))

def message_pre_pro(input_string):
    bit_main = message_bit_return(input_string)
    return(message_pad(bit_main))

def message_parsing(input_string):
    return(some_LP(message_pre_pro(input_string),32))

def message_schedule(index,w_t):
    new_word = convert_32bit(addition([int(some_s1(w_t[index-2]),2),int(w_t[index-7],2),int(some_s0(w_t[index-15]),2),int(w_t[index-16],2)]))
    return(new_word)

initial=['6a09e667','bb67ae85','3c6ef372','a54ff53a','510e527f','9b05688c','1f83d9ab','5be0cd19']

values=['428a2f98','71374491','b5c0fbcf','e9b5dba5','3956c25b','59f111f1','923f82a4','ab1c5ed5','d807aa98','12835b01','243185be','550c7dc3','72be5d74','80deb1fe','9bdc06a7','c19bf174','e49b69c1','efbe4786','0fc19dc6','240ca1cc','2de92c6f','4a7484aa','5cb0a9dc','76f988da','983e5152','a831c66d','b00327c8','bf597fc7','c6e00bf3','d5a79147','06ca6351','14292967','27b70a85','2e1b2138','4d2c6dfc','53380d13','650a7354','766a0abb','81c2c92e','92722c85','a2bfe8a1','a81a664b','c24b8b70','c76c51a3','d192e819','d6990624','f40e3585','106aa070','19a4c116','1e376c08','2748774c','34b0bcb5','391c0cb3','4ed8aa4a','5b9cca4f','682e6ff3','748f82ee','78a5636f','84c87814','8cc70208','90befffa','a4506ceb','bef9a3f7','c67178f2']

def encryption(input_string):
    w_t=message_parsing(input_string)
    a=convert_32bit(dec_return_hex(initial[0]))
    b=convert_32bit(dec_return_hex(initial[1]))
    c=convert_32bit(dec_return_hex(initial[2]))
    d=convert_32bit(dec_return_hex(initial[3]))
    e=convert_32bit(dec_return_hex(initial[4]))
    f=convert_32bit(dec_return_hex(initial[5]))
    g=convert_32bit(dec_return_hex(initial[6]))
    h=convert_32bit(dec_return_hex(initial[7]))
    for i in range(0,64):
        if i <= 15:
            t_1=addition([int(h,2),int(some_e1(e),2),int(not_and_and_xor(e,f,g),2),int(values[i],16),int(w_t[i],2)])
            t_2=addition([int(some_e0(a),2),int(and_and_and_xor_xor(a,b,c),2)])
            h=g
            g=f
            f=e
            e=addition([int(d,2),t_1])
            d=c
            c=b
            b=a 
            a=addition([t_1,t_2])
            a=convert_32bit(a)
            e=convert_32bit(e)
        if i > 15:
            w_t.append(message_schedule(i,w_t))
            t_1=addition([int(h,2),int(some_e1(e),2),int(not_and_and_xor(e,f,g),2),int(values[i],16),int(w_t[i],2)])
            t_2=addition([int(some_e0(a),2),int(and_and_and_xor_xor(a,b,c),2)])
            h=g
            g=f
            f=e
            e=addition([int(d,2),t_1])
            d=c
            c=b
            b=a 
            a=addition([t_1,t_2])
            a=convert_32bit(a)
            e=convert_32bit(e)
    value_0 = addition([dec_return_hex(initial[0]),int(a,2)])
    value_1 = addition([dec_return_hex(initial[1]),int(b,2)])
    value_2 = addition([dec_return_hex(initial[2]),int(c,2)])
    value_3 = addition([dec_return_hex(initial[3]),int(d,2)])
    value_4 = addition([dec_return_hex(initial[4]),int(e,2)])
    value_5 = addition([dec_return_hex(initial[5]),int(f,2)])
    value_6 = addition([dec_return_hex(initial[6]),int(g,2)])
    value_7 = addition([dec_return_hex(initial[7]),int(h,2)])
    value = (hex_return(value_0),hex_return(value_1),hex_return(value_2),hex_return(value_3),hex_return(value_4),hex_return(value_5),hex_return(value_6),hex_return(value_7))
    return(value)

def get_string(d):
    remain = d
    s = ''
    for i in range(3):
        code = remain / (256 ** (2 - i))
        s += chr(code)
        remain = remain % (256 ** (2 - i))
    return s

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

e = []
for i in range(256):
    val = i + i*256 + i*256*256
    s = get_string(val)
    e.append(''.join(encryption(s)))

width = 351
height = 7371 / width
img = Image.new('RGB', (width, height), (255, 255, 255))

for i in range(0, len(data), 64):
    pixel_data = data[i:i+64]
    color_code = e.index(pixel_data)
    x = (i / 64) % width
    y = (i / 64) / width
    img.putpixel((x, y), (color_code, color_code, color_code))

img.save('flag.png')

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

$ echo -n 'CTF{d4mm17_y0u_f0und_my_h1dd3n_5h4_h45h}' | sha256sum
d66f0a384abd48764b5caecb3e204c88746661c3d7f25471d79ac265c59c9085  -
d66f0a384abd48764b5caecb3e204c88746661c3d7f25471d79ac265c59c9085

COMPLEX-RSA (200)

公開鍵が2つと暗号ファイルが渡される。2回別の鍵で暗号化しているので、それを復号する問題。

$ openssl rsa -pubin -text < pubkey1
Public-Key: (1026 bit)
Modulus:
    03:15:4c:a2:cd:44:39:ad:97:e0:38:7a:51:4c:97:
    bf:21:0c:f7:5f:3b:cf:26:25:94:68:3b:b5:3c:e6:
    99:48:12:df:6a:19:55:c4:4f:9f:12:e9:9c:2a:69:
    8c:b9:7f:0e:47:e8:e8:d1:48:a7:f3:47:2d:2b:4d:
    e2:54:10:ad:f2:1e:49:8b:b9:b4:66:5a:0c:66:d8:
    d2:66:ff:20:63:7e:ac:f2:71:8d:9a:59:c1:c4:a3:
    cf:02:2f:0b:f2:8d:53:89:d5:86:41:f3:dd:99:4e:
    f2:ff:fa:4b:70:d3:d3:98:e9:48:42:00:f8:77:93:
    06:10:13:a8:ed:07:2c:d7:7f
Exponent: 37507589401 (0x8bba06519)
writing RSA key
-----BEGIN PUBLIC KEY-----
MIGhMA0GCSqGSIb3DQEBAQUAA4GPADCBiwKBgQMVTKLNRDmtl+A4elFMl78hDPdf
O88mJZRoO7U85plIEt9qGVXET58S6ZwqaYy5fw5H6OjRSKfzRy0rTeJUEK3yHkmL
ubRmWgxm2NJm/yBjfqzycY2aWcHEo88CLwvyjVOJ1YZB892ZTvL/+ktw09OY6UhC
APh3kwYQE6jtByzXfwIFCLugZRk=
-----END PUBLIC KEY-----
yoshinori@yoshinori-virtual-machine:/mnt/hgfs/Shared$ openssl rsa -pubin -text < pubkey2
Public-Key: (1026 bit)
Modulus:
    03:15:4c:a2:cd:44:39:ad:97:e0:38:7a:51:4c:97:
    bf:21:0c:f7:5f:3b:cf:26:25:94:68:3b:b5:3c:e6:
    99:48:12:df:6a:19:55:c4:4f:9f:12:e9:9c:2a:69:
    8c:b9:7f:0e:47:e8:e8:d1:48:a7:f3:47:2d:2b:4d:
    e2:54:10:ad:f2:1e:49:8b:b9:b4:66:5a:0c:66:d8:
    d2:66:ff:20:63:7e:ac:f2:71:8d:9a:59:c1:c4:a3:
    cf:02:2f:0b:f2:8d:53:89:d5:86:41:f3:dd:99:4e:
    f2:ff:fa:4b:70:d3:d3:98:e9:48:42:00:f8:77:93:
    06:10:13:a8:ed:07:2c:d7:7f
Exponent:
    1a:1b:48:c3:a6:ce:4a:6a:f6:e3:cb:78:c5:5f:ac:
    7f:59:29:6f:41:bb:11:e5:8a:30:1f:b8:d2:f3:c4:
    7e:bb:15:fa:3e:68:e9:f7:7f:8c:52:39:3b:e2:22:
    17:5d:e4:a7:bc:55:d9:9d:51:80:1b:5a:1d:be:72:
    dd:6e:ff:ae:52:39:f3:a4:48:d3:e9:ef:5a:a1:c9:
    54:5e:1a:38:49:18:9b:e6:3c:49:fe:3b:94:92:54:
    d4:20:d9:81:76:77:a7:db:4a:d1:f3:f5:df:99:b3:
    25:7d:d6:04:c5:72:ce:e2:5e:43:c2:69:43:cf:d1:
    64:91:80:3b
writing RSA key
-----BEGIN PUBLIC KEY-----
MIIBGjANBgkqhkiG9w0BAQEFAAOCAQcAMIIBAgKBgQMVTKLNRDmtl+A4elFMl78h
DPdfO88mJZRoO7U85plIEt9qGVXET58S6ZwqaYy5fw5H6OjRSKfzRy0rTeJUEK3y
HkmLubRmWgxm2NJm/yBjfqzycY2aWcHEo88CLwvyjVOJ1YZB892ZTvL/+ktw09OY
6UhCAPh3kwYQE6jtByzXfwJ8GhtIw6bOSmr248t4xV+sf1kpb0G7EeWKMB+40vPE
frsV+j5o6fd/jFI5O+IiF13kp7xV2Z1RgBtaHb5y3W7/rlI586RI0+nvWqHJVF4a
OEkYm+Y8Sf47lJJU1CDZgXZ3p9tK0fP135mzJX3WBMVyzuJeQ8JpQ8/RZJGAOw==
-----END PUBLIC KEY-----

nの値が同じで、eの値はそれぞれ次の通り。

n = 0x03154ca2cd4439ad97e0387a514c97bf210cf75f3bcf262594683bb53ce6994812df6a1955c44f9f12e99c2a698cb97f0e47e8e8d148a7f3472d2b4de25410adf21e498bb9b4665a0c66d8d266ff20637eacf2718d9a59c1c4a3cf022f0bf28d5389d58641f3dd994ef2fffa4b70d3d398e9484200f87793061013a8ed072cd77f
e1 = 0x8bba06519
e2 = 0x1a1b48c3a6ce4a6af6e3cb78c55fac7f59296f41bb11e58a301fb8d2f3c47ebb15fa3e68e9f77f8c52393be222175de4a7bc55d99d51801b5a1dbe72dd6effae5239f3a448d3e9ef5aa1c9545e1a3849189be63c49fe3b949254d420d9817677a7db4ad1f3f5df99b3257dd604c572cee25e43c26943cfd16491803b
c1 = pow(m, e1, n)
c = pow(c1, e2, n)
→ c = pow(m, e1*e2, n)

Wiener's attackで復号する。

from fractions import Fraction

def egcd(a, b):
    x,y, u,v = 0,1, 1,0
    while a != 0:
        q, r = b//a, b%a
        m, n = x-u*q, y-v*q
        b,a, x,y, u,v = a,r, u,v, m,n
        gcd = b
    return gcd, x, y

def decrypt(p, q, e, c):
    n = p * q
    phi = (p - 1) * (q - 1)
    gcd, a, b = egcd(e, phi)
    d = a
    pt = pow(c, d, n)
    return hex(pt)[2:-1].decode('hex')

def continued_fractions(n,e):
    cf = [0]
    while e != 0:
        cf.append(int(n/e))
        N = n
        n = e
        e = N%e
    return cf

def calcKD(cf):
    kd = list()
    for i in range(1,len(cf)+1):
        tmp = Fraction(0)
        for j in cf[1:i][::-1]:
            tmp = 1/(tmp+j)
        kd.append((tmp.numerator,tmp.denominator))
    return kd

def int_sqrt(n):
    def f(prev):
        while True:
            m = (prev + n/prev)/2
            if m >= prev:
                return prev
            prev = m
    return f(n)

def calcPQ(a,b):
    if a*a < 4*b or a < 0:
        return None
    c = int_sqrt(a*a-4*b)
    p = (a + c) /2
    q = (a - c) /2
    if p + q == a and p * q == b:
        return (p,q)
    else:
        return None

def wiener(n,e):
    kd = calcKD(continued_fractions(n,e))
    for (k,d) in kd:
        if k == 0:
            continue
        if (e*d-1) % k != 0:
            continue
        phin = (e*d-1) / k
        if phin >= n:
            continue
        ans = calcPQ(n-phin+1,n)
        if ans is None:
            continue
        return (ans[0],ans[1])

n = 0x03154ca2cd4439ad97e0387a514c97bf210cf75f3bcf262594683bb53ce6994812df6a1955c44f9f12e99c2a698cb97f0e47e8e8d148a7f3472d2b4de25410adf21e498bb9b4665a0c66d8d266ff20637eacf2718d9a59c1c4a3cf022f0bf28d5389d58641f3dd994ef2fffa4b70d3d398e9484200f87793061013a8ed072cd77f
e1 = 0x8bba06519
e2 = 0x1a1b48c3a6ce4a6af6e3cb78c55fac7f59296f41bb11e58a301fb8d2f3c47ebb15fa3e68e9f77f8c52393be222175de4a7bc55d99d51801b5a1dbe72dd6effae5239f3a448d3e9ef5aa1c9545e1a3849189be63c49fe3b949254d420d9817677a7db4ad1f3f5df99b3257dd604c572cee25e43c26943cfd16491803b

e = e1 * e2
p, q = wiener(n, e)

with open('flag.enc', 'rb') as f:
    c = int(f.read().encode('hex'), 16)

flag = decrypt(p, q, e, c)
print flag

実行結果は CTF{c0n6r47zzz_y0u_f0und_0ur_h1dd3n_w13n3r!!}

$ echo -n 'CTF{c0n6r47zzz_y0u_f0und_0ur_h1dd3n_w13n3r!!}' | sha256sum
c0081b21d2c1d3f581914be8bc4ce98158b9eafbcf68dda83f6eddae99d4b1e2  -
c0081b21d2c1d3f581914be8bc4ce98158b9eafbcf68dda83f6eddae99d4b1e2

STEREOTYPES (300)

$ openssl rsa -pubin -text < pubkey
Public-Key: (2048 bit)
Modulus:
    00:87:ae:91:10:c6:f2:df:9a:d5:a7:47:c9:c2:5b:
    60:57:6a:c2:bf:17:76:00:2d:4e:5d:ba:17:72:ae:
    a0:b8:70:34:94:29:9c:f8:51:38:be:84:bf:2d:9f:
    4f:88:79:5e:9c:cd:63:45:4f:df:55:32:57:da:09:
    5a:0d:ea:cd:44:6f:d9:a9:4d:90:e4:06:d3:86:1c:
    c7:4e:cb:5c:a1:1c:6e:ce:b3:fd:0b:13:bc:23:26:
    7f:9f:14:ef:20:19:3e:61:02:70:3c:c3:b3:fd:f7:
    a7:f3:e7:20:3b:9d:43:4e:c9:66:d9:04:ab:d8:0b:
    68:37:3f:fb:87:f3:b4:06:ff:53:af:8b:9f:85:48:
    dd:3a:da:63:4e:06:f0:87:ed:4a:d5:98:cd:a1:75:
    20:02:45:e0:b5:ec:d4:5e:83:11:60:ef:7e:c0:b9:
    32:7a:28:18:70:97:1e:c7:0d:63:2e:ed:79:a2:1d:
    a6:35:18:77:b9:46:dd:54:b2:eb:a1:6d:ad:7b:f5:
    d2:11:73:d1:5b:22:3a:4c:58:12:c6:8c:e9:41:4f:
    3f:ba:bf:03:ee:d2:8f:fc:65:64:6d:4f:32:81:cc:
    6a:fe:8d:21:c1:45:27:ef:50:dd:0d:50:d6:58:99:
    69:5b:53:c6:b6:37:4c:ec:05:e3:81:53:ca:65:e8:
    73:f1
Exponent: 7 (0x7)
writing RSA key
-----BEGIN PUBLIC KEY-----
MIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0AMIIBCAKCAQEAh66REMby35rVp0fJwltg
V2rCvxd2AC1OXboXcq6guHA0lCmc+FE4voS/LZ9PiHlenM1jRU/fVTJX2glaDerN
RG/ZqU2Q5AbThhzHTstcoRxuzrP9CxO8IyZ/nxTvIBk+YQJwPMOz/fen8+cgO51D
Tslm2QSr2AtoNz/7h/O0Bv9Tr4ufhUjdOtpjTgbwh+1K1ZjNoXUgAkXgtezUXoMR
YO9+wLkyeigYcJcexw1jLu15oh2mNRh3uUbdVLLroW2te/XSEXPRWyI6TFgSxozp
QU8/ur8D7tKP/GVkbU8ygcxq/o0hwUUn71DdDVDWWJlpW1PGtjdM7AXjgVPKZehz
8QIBBw==
-----END PUBLIC KEY-----
n = 0x0087ae9110c6f2df9ad5a747c9c25b60576ac2bf1776002d4e5dba1772aea0b8703494299cf85138be84bf2d9f4f88795e9ccd63454fdf553257da095a0deacd446fd9a94d90e406d3861cc74ecb5ca11c6eceb3fd0b13bc23267f9f14ef20193e6102703cc3b3fdf7a7f3e7203b9d434ec966d904abd80b68373ffb87f3b406ff53af8b9f8548dd3ada634e06f087ed4ad598cda175200245e0b5ecd45e831160ef7ec0b9327a281870971ec70d632eed79a21da6351877b946dd54b2eba16dad7bf5d21173d15b223a4c5812c68ce9414f3fbabf03eed28ffc65646d4f3281cc6afe8d21c14527ef50dd0d50d65899695b53c6b6374cec05e38153ca65e873f1
e = 7

平文は末尾以外わかっているので、Coppersmith's Attackを実行。

# coppersmith_attack.sage
n = 0x0087ae9110c6f2df9ad5a747c9c25b60576ac2bf1776002d4e5dba1772aea0b8703494299cf85138be84bf2d9f4f88795e9ccd63454fdf553257da095a0deacd446fd9a94d90e406d3861cc74ecb5ca11c6eceb3fd0b13bc23267f9f14ef20193e6102703cc3b3fdf7a7f3e7203b9d434ec966d904abd80b68373ffb87f3b406ff53af8b9f8548dd3ada634e06f087ed4ad598cda175200245e0b5ecd45e831160ef7ec0b9327a281870971ec70d632eed79a21da6351877b946dd54b2eba16dad7bf5d21173d15b223a4c5812c68ce9414f3fbabf03eed28ffc65646d4f3281cc6afe8d21c14527ef50dd0d50d65899695b53c6b6374cec05e38153ca65e873f1
e = 7

with open('ciphertext', 'rb') as f:
    c = int(f.read().encode('hex'), 16)

with open('plaintext', 'r') as f:
    m0 = f.read()

m0 = int(m0.replace('X', '\x00').encode('hex'), 16)

kbits = 15 * 8

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

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

復号結果は次の通り。

Welcome to BackdoorCTF17. Just one advice Never ignore the challenge names, they provides the maximum hint. Okay now let's leave the boring stuff apart i know you are eagerly waiting to gain some points So here it is : CTF{y0u_607_17}
$ echo -n 'CTF{y0u_607_17}' | sha256sum
229eab315f374015ad0e8a0374b6df89a2510538a305cbd2b88df81eb05349a6  -
229eab315f374015ad0e8a0374b6df89a2510538a305cbd2b88df81eb05349a6