PlaidCTF 2018 Writeup

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

macsh (Crypto 125)

'<|>'区切りで1個目がmac, 2個目がcmdline

$ nc macsh.chal.pwning.xxx 64791
|$|> aaa<|>ls
macsh: bad tag
|$|> aaa<|>oo
macsh: oo: command not found
|$|> aaa<|>tag echo 123
feb086773d17f6438c7630d7da1dba79
|$|> bbb<|>tag echo 123
feb086773d17f6438c7630d7da1dba79
|$|> aaa<|>tag echo 456
d610717ba31f48a0cbde88926e95bd82
|$|>

k0, k1はランダムな16バイト文字列でmtagを先頭につけたときはmacを表示してくれる。
k0の鍵を割り出そうと考えたが、それは難しい。よく見ると129ブロック目は1ブロック目から鍵を再度使うようになっている。このことを使い、例えば以下のように考える。

ブロック データ1 データ2
1ブロック目 echo 0123456789; echo 0123456789;
2ブロック目 echo 0123456789; echo 0123456789;
128ブロック目 echo 0123456789; echo 0123456789;
129ブロック目 echo ls .
130ブロック目 コマンド長情報 コマンド長情報
131ブロック目 padding(\x10*16) padding(\x10*16)

こう考えると、データ1とデータ2の129ブロック以外の各ブロックの暗号化のXORは0となるため、echoコマンドとlsコマンドのmacのXORの値がわかる。さらに1ブロック目がechoコマンドの場合とXORを取れば1ブロック目がlsコマンドの場合のmacを取得できる。
lsコマンドでflag.txtがあることがわかるので、cat flag.txtでフラグが得られる。最終的なコードは以下の通り。

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(('macsh.chal.pwning.xxx', 64791))

data = recvuntil(s, '|$|> ')
print data

cmd_head = 'a<|>tag '

#### ls exec ####
cmd1 = 'echo 0123456789;' * 128 + 'echo'
cmd = cmd_head + cmd1
print cmd
s.sendall(cmd + '\n')
data = recvuntil(s, '|$|> ')
print data
mac1 = data.split('\n')[0]

cmd2 = 'echo 0123456789;' * 128 + 'ls .'
cmd = cmd_head + cmd2
print cmd
s.sendall(cmd + '\n')
data = recvuntil(s, '|$|> ')
print data
mac2 = data.split('\n')[0]

cmd3 = 'echo'
cmd = cmd_head + cmd3
print cmd
s.sendall(cmd + '\n')
data = recvuntil(s, '|$|> ')
print data
mac3 = data.split('\n')[0]

mac_ls = '%032x' % (int(mac1, 16) ^ int(mac2, 16) ^ int(mac3, 16))
cmd_ls = mac_ls + '<|>ls .'
print cmd_ls
s.sendall(cmd_ls + '\n')
data = recvuntil(s, '|$|> ')
print data

#### cat exec ####
cmd1 = 'echo 0123456789;' * 128 + 'echo 0123456'
cmd = cmd_head + cmd1
print cmd
s.sendall(cmd + '\n')
data = recvuntil(s, '|$|> ')
print data
mac1 = data.split('\n')[0]

cmd2 = 'echo 0123456789;' * 128 + 'cat flag.txt'
cmd = cmd_head + cmd2
print cmd
s.sendall(cmd + '\n')
data = recvuntil(s, '|$|> ')
print data
mac2 = data.split('\n')[0]

cmd3 = 'echo 0123456'
cmd = cmd_head + cmd3
print cmd
s.sendall(cmd + '\n')
data = recvuntil(s, '|$|> ')
print data
mac3 = data.split('\n')[0]

mac_cat = '%032x' % (int(mac1, 16) ^ int(mac2, 16) ^ int(mac3, 16))
cmd_cat = mac_cat + '<|>cat flag.txt'
print cmd_cat
s.sendall(cmd_cat + '\n')
data = recvuntil(s, '|$|> ')
print data

実行結果は以下の通り。

   :
|$|>
9daabce6ee4c110034527e8a432abd26<|>ls .
__pycache__
flag.txt
fmac.py
macsh.py
.bashrc
.bash_logout
.profile
   :
|$|>
e04241a0f78ab714e517439cd7f8dccb<|>cat flag.txt
PCTF{fmac_is_busted_use_PMAC_instead}
|$|>
PCTF{fmac_is_busted_use_PMAC_instead}

ASIS CTF Quals 2018 Writeup

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

Welcome (Trivia)

Twitterで@ASISCTFがフラグをツイートしている。

ASIS{Welcome_to_ASISCTF_Let's_increase_the_world's_entropy}

The Early School (Crypto)

暗号の処理は以下のイメージ。

FLAGを2進数にする。

FLAGの2進数の長さが79の場合
msg[0] + msg[1] + msg[0] ^ msg[min(1, 78)]
msg[2] + msg[3] + msg[2] ^ msg[min(3, 78)]
msg[4] + msg[5] + msg[4] ^ msg[min(5, 78)]
         :
msg[78] + msg[78] ^ msg[min(79, 78)]

roundの回数だけ繰り返す。

元の平文も含まれているので、復号はそれほど難しくない。

from Crypto.Util.number import *

def decrypt(msg):
    dec = ''
    for i in range(0, len(msg), 3):
        if (i + 2) < len(msg):
            assert int(msg[i]) ^ int(msg[i+1]) == int(msg[i+2])
            dec += msg[i:i+2]
        else:
            assert int(msg[i+1]) == 0
            dec += msg[i:i+1]
    return dec

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

ENC = bin(bytes_to_long(data))[2:]

i = 1
while True:
    try:
        print 'Round %d' % i
        ENC = decrypt(ENC)
        i += 1
    except:
        break

while True:
    if len(ENC) % 8 == 0:
        break
    ENC = '0' + ENC

FLAG = ''
for i in range(0, len(ENC), 8):
    FLAG += chr(int(ENC[i:i+8], 2))

print FLAG
ASIS{50_S1mPl3_CryptO__4__warmup____}

Blaze CTF 2018 参戦

この大会は2018/4/21 5:20(JST)~2018/4/26 1:20(JST)に開催されました。
今回もチームで参戦。結果は2100点で166チーム中39位でした。
今回は自分が得点した問題は1問もありませんでした。
Ethereum関連の問題もあり、いろいろ調べながらでも何とか解きたかったのですが、残念です。

NoNameCon CTF Quals 2018 Writeup

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

Subdomain (50)

nonameconctf2018.xyzのサブドメインを調査する問題。

>dnsrecon.py -d nonameconctf2018.xyz
[*] Performing General Enumeration of Domain: nonameconctf2018.xyz
[-] DNSSEC is not configured for nonameconctf2018.xyz
[*]      SOA dns1.registrar-servers.com 216.87.155.33
[*]      NS dns1.registrar-servers.com 216.87.155.33
[*]      NS dns1.registrar-servers.com 2620:74:19::33
[*]      NS dns2.registrar-servers.com 216.87.152.33
[*]      NS dns2.registrar-servers.com 2001:502:cbe4::33
[*]      MX eforward4.registrar-servers.com 162.255.118.62
[*]      MX eforward5.registrar-servers.com 162.255.118.62
[*]      MX eforward3.registrar-servers.com 162.255.118.61
[*]      MX eforward2.registrar-servers.com 162.255.118.62
[*]      MX eforward1.registrar-servers.com 162.255.118.61
[*]      TXT nonameconctf2018.xyz v=spf1 include:spf.efwd.registrar-servers.com ~all
[*] Enumerating SRV Records
[-] No SRV Records Found for nonameconctf2018.xyz
[+] 0 Records Found

>dnsrecon.py -d nonameconctf2018.xyz -t brt
[*] No file was specified with domains to check.
[*] Using file provided with tool: C:\CTF\Tool\dnsrecon\namelist.txt
[*]      A chat.nonameconctf2018.xyz 188.166.99.124
[*]      A hidden.nonameconctf2018.xyz 188.166.102.232
[*]      A info.nonameconctf2018.xyz 188.166.126.109
[*]      A www.nonameconctf2018.xyz 188.166.46.238
[+] 4 Records Found

http://hidden.nonameconctf2018.xyz/にアクセスすると、フラグが表示された。

That was easy. Flag for 50 points: nnc{ef62e674aa6585412e1ee529b4bd9090}
nnc{ef62e674aa6585412e1ee529b4bd9090}

*ctf 2018 参戦

この大会は2018/4/21 10:00(JST)~2018/4/23 10:00(JST)に開催されました。
今回もチームで参戦。結果は444点で146チーム中70位でした。
今回は自分が得点した問題は1問もありませんでした。
1問くらいは解きたかったのですが、残念です。

STEM CTF: Cyber Challenge 2018 Writeup

この大会は2018/4/21 8:00(JST)~2018/4/22 8:00(JST)に開催されました。
今回もチームで参戦。結果は2600点で229チーム中6位、Professionalでは1位でした。
自分で解けた問題をWriteupとして書いておきます。

Keyboard Shuffle (Crypto 100)

問題の暗号は以下の通り。

Ut awwna U;n cwrt vS r rtoubfm rgBJAB DIE VWUBF AI YBSWEARndubf BTQt~ nxPRTOUBF)UA)ooEWBRKT)Ges{

英語のキーボードで本来の左側を押してしまっていることが多いようだ。
正しくキー操作しているときと、CAPS Lockを押している可能性に気をつける(aを押すべき時にCaps Lock押してしまっているときがある)。特に大文字小文字が入れ替わっているときはaを押している可能性がある。
正しい入力にすると以下のような感じになるはず。

It seems I'm very bad at typing, thanksn for being so understanding anyway! MCA{TYPING_IS_APPARENTLY_HARD}
MCA{TYPING_IS_APPARENTLY_HARD}

Blue Codes of Death (Crypto 200)

FLAGの各文字について以下の処理を行う。

md5(salt + c + idx(0-19))
※saltは2**16未満の数値文字列

このハッシュ値を2バイトごとに青の数値として32x32の画像に入れていく。この処理をFLAG20バイト分行っている。
画像からハッシュ値がわかるので、saltとFLAG文字をブルートフォースで探り当てる。

from PIL import Image
import hashlib

img = Image.open('release.png').convert('RGB')

flag = ''
for i in range(20):
    h = ''
    for y in range(0, 32, 8):
        for x in range(i*32, (i+1)*32, 8):
            r, g, b = img.getpixel((x, y))
            h += '%02x' % b

    found = False
    for salt in range(2**16):
        for code in range(32, 127):
            text = str(salt) + chr(code) + str(i)
            myhash = hashlib.md5(text).hexdigest()
            if myhash == h:
                found = True
                flag += chr(code)
                print chr(code),
                break
        if found:
            break

print
print flag
MCA-27c0384c33a93172

Timisoara CTF 2018 Quals Writeup

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

Welcome (Misc 1)

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

timctf{w3lcom3_t0_timctf_2018_quals}

Accord (Misc 5)

Accordの#generalチャネルの名前の横にフラグが書かれている。

timctf{fr33_v01c3_4nd_text_ch4t_f0r_ctfers}

History (Reverse 30)

バイナリエディタで見ると、UNICODEでフラグが記載されている。
f:id:satou-y:20180503214352p:plain

timctf{unic0d3_1s_mr_w0rldw1d3}

Picassor (Forensics 50)

XORキー1文字でjpgのフォーマットに復号する。

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

key = 0xff ^ ord(data[0])

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

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

f:id:satou-y:20180503214711j:plain

timctf{x0r_and_rul3_un1t3_and_l34d}

Strange behavior (Forensics 100)

FTK Imagerで開き、[root]-4_next_lecturesにある、削除済みのschedule.xlsxをエクスポートする。このExcelを開き、F10のセルを見ると、フラグが書いてある。

timctf{d0nt_f0rg3t_t0_h4v3_fun}

Those Are Rookie Numbers (Crypto 30)

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

p = 176773485669509339371361332756951225661
q = 333197218785800427026869958933009188427

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

n = 58900433780152059829684181006276669633073820320761216330291745734792546625247
e = 65537
c = 56191946659070299323432594589209132754159316947267240359739328886944131258862
p = 176773485669509339371361332756951225661
q = 333197218785800427026869958933009188427

phi = (p - 1) * (q - 1)

x = 0
while True:
    if (phi * x + 1) % e == 0:
        d = (phi * x + 1) / e
        break
    x = x + 1

m = pow(c, d, n)

flag = ('%x' % m).decode('hex')
print flag
timctf{th0sE_rOoKIe_numB3rz}

Back in Time (Crypto 50)

以下の英小文字だけの換字式暗号を復号する。

hsijhk{Pc3nvO_R4NvwM_1S_Nwh_RArD0M}

timctf{}の形式なり、英文として意味がありそうなように変換する。

暗号	平文
h	t
s	i
i	m
j	c
k	f
c	s
n	u
v	d
w	o
r	n
timctf{Ps3udO_R4NdoM_1S_Not_RAnD0M}

SSS - Part 1 (Crypto 75)

差が同じなので、添付のグラフから考えると、同じ差で1つ目の値から引いたものがフラグになる。

c1 = '4612c90f5d8cd5d616193257336d92af1f66df92443b4ee69f5c885f0173ad80113844e393d194e3'
c2 = '8c25921e46b03e48b7cbe94c3267f41adf618abd16422f660b59df6fae81e8aff2242852be33db49'
c3 = 'd2385b2d2fd3a6bb597ea041316255869f5c35e7e8490fe5775736805b9023dfd3100bc1e89621af'

val1 = int(c1, 16)
val2 = int(c2, 16)
val3 = int(c3, 16)

diff1 = val2 - val1
diff2 = val3 - val2
assert diff1 == diff2

val_flag = val1 - diff1
flag = ('%x' % val_flag).decode('hex')
print flag
timctf{b4s1C_l4gr4ng3_1NTerP0LatioN}

Substitute Teacher (Crypto 100)

換字式暗号。ただし英小文字しか変換されない。小文字部分だけquipqiupを補助的に使って、対応付けをする。

暗号	平文
a	g
b	e
c	t
d	u
e	o
f	c
g	r
h	f
i	i
k	a
l	w
m	m
n	n
o	h
q	k
s	s
t	l
u	b
x	d
y	j
z	p
rev_sub_dict = {
    'a': 'g',
    'b': 'e',
    'c': 't',
    'd': 'u',
    'e': 'o',
    'f': 'c',
    'g': 'r',
    'h': 'f',
    'i': 'i',
    'j': 'x',
    'k': 'a',
    'l': 'w',
    'm': 'm',
    'n': 'n',
    'o': 'h',
    'p': 'v',
    'q': 'k',
    'r': 'y',
    's': 's',
    't': 'l',
    'u': 'b',
    'v': 'z',
    'w': 'q',
    'x': 'd',
    'y': 'j',
    'z': 'p'
}

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

plain = ''
for i in range(len(data)):
    if rev_sub_dict.has_key(data[i]):
        plain += rev_sub_dict[data[i]]
    else:
        plain += data[i]

with open('plaintext.txt', 'w') as f:
    f.write(plain)

復号すると、文中にフラグがある。

"She left." Fache glanced out at the darkened hallway. Apparently Sophie had been in no mood to
stop by and chat with the other officers on her way out.

For a moment, Fache considered radioing the guards in the entresol and telling them to stop Sophie
and drag her back up here before she could leave the premises. He thought better of it. That was
only his pride talking... wanting the last word. He'd had enough distractions tonight.

Deal with Agent Neveu later, he told himself, already looking forward to firing her.

Pushing Sophie from his mind, Fache stared for a moment at the miniature knight standing on
Sauniere's desk. Then he turned back to Collet. "Do you have him?"
timctf fr3quencyan4lyS1s1sc0ol
Collet gave a curt nod and spun the laptop toward Fache. The red dot was clearly visible on the
floor plan overlay, blinking methodically in a room marked TOILETTES PUBLIQUES.

"Good," Fache said, lighting a cigarette and stalking into the hall. I've got a phone call to make. Be
damned sure the rest room is the only place Langdon goes."
timctf{fr3quencyan4lyS1s1sc0ol}

Not Your Average RSA (Crypto 100)

nを素因数分解する。

$ python -m primefac 18086135173395641986123054725350673124644081001065528104355398467069161310728333370888782472390469310073117314933010148415971838393130403883412870626619053053672200815153337045022984003065791405742151350233540671714100052962945261324862393058079670757430356345222006961306738393548705354069502196752913415352527
18086135173395641986123054725350673124644081001065528104355398467069161310728333370888782472390469310073117314933010148415971838393130403883412870626619053053672200815153337045022984003065791405742151350233540671714100052962945261324862393058079670757430356345222006961306738393548705354069502196752913415352527: 19459483 18145913 20197313 27409927 22685197 16904777 31696261 18313601 31737131 31881917 18646361 17901463 29511773 21321539 25808239 24525821 20010041 21647243 27138691 33322589 22576643 17673199 27739163 30342329 23554169 21891889 33098557 31703933 17730961 22050221 24996157 25671797 27606707 27289543 28863719 25963459 20390129 24946057 33381329 29488469 20013121 30580789

この結果から、Muti-Prime RSAの復号プログラムを書く。eは通常の65537にした。

import gmpy

def chinese_remainder(n, a):
    sum = 0
    prod = reduce(lambda a, b: a*b, n)

    for n_i, a_i in zip(n, a):
        p = prod / n_i
        sum += a_i * gmpy.invert(p, n_i) * p
    return sum % prod

c = 9074407119435549226216306717104313210750146895081726439798095976354600576814818348656600684713830051655944443364224597709641982342039946659987121376590618828822446965847273448794324003758131816407702456966504389655568712152599077538994030379567217702587542326383955580601916478060973206347266442527564009737910
e = 65537
n = 18086135173395641986123054725350673124644081001065528104355398467069161310728333370888782472390469310073117314933010148415971838393130403883412870626619053053672200815153337045022984003065791405742151350233540671714100052962945261324862393058079670757430356345222006961306738393548705354069502196752913415352527

primes = [
    19459483, 18145913, 20197313, 27409927, 22685197, 16904777, 31696261,
    18313601, 31737131, 31881917, 18646361, 17901463, 29511773, 21321539,
    25808239, 24525821, 20010041, 21647243, 27138691, 33322589, 22576643,
    17673199, 27739163, 30342329, 23554169, 21891889, 33098557, 31703933,
    17730961, 22050221, 24996157, 25671797, 27606707, 27289543, 28863719,
    25963459, 20390129, 24946057, 33381329, 29488469, 20013121, 30580789]

n_ary = []
a_ary = []
for p in primes:
    phi = p - 1
    d = gmpy.invert(e, phi)
    mk = pow(c, d, p)
    n_ary.append(p)
    a_ary.append(mk)

m = chinese_remainder(n_ary, a_ary)
flag = ('%x' % m).decode('hex')
print flag
timctf{mUlt1_PriM3_rS4_c0ULD_B3_DAngEr0us}

Hush Hush (Crypto 150)

inputの2回目は、1回目に入力したものの前に\x00(いくつでもよい)を入力すればよい。
例えば、以下を指定する。

・\x00
・\x00\x00
import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('89.38.210.129', 6665))

data = s.recv(256)
print data
data = s.recv(256)

input1 = '\x00'
print data + input1
s.sendall(input1 + '\n')
data = s.recv(256)
input2 = '\x00\x00'
print data + input2
s.sendall(input2 + '\n')
data = s.recv(256)
print data
timctf{d0UbT_3verYTH1nG}

SSS - Part 2 (Crypto 250)

10バイトごとに暗号化。暗号化は以下のような処理。

x = 1の場合
c1 * 1 + c2 * 1^2 + c3 * 1^3 + ... + c12 * 1^12 + flag = y1
x = 2の場合
c1 * 2 + c2 * 2^2 + c3 * 2^3 + ... + c12 * 2^12 + flag = y2
x = 3の場合
c1 * 3 + c2 * 3^2 + c3 * 3^3 + ... + c12 * 3^12 + flag = y3
     :
x = 16の場合
c1 * 16 + c2 * 16^2 + c3 * 16^3 + ... + c12 * 16^12 + flag = y16

10バイトの暗号化間では差はflagの差でしかない。同じxの値では、flag2 - flag1 = y21 - y11
timctf{で始まり}で終わることを条件にブルートフォースで求める。

import itertools

def is_printable(s):
    for i in range(len(s)):
        if ord(s[i]) < 32 or ord(s[i]) > 126:
            return False
    return True

val0_1 = 7714685804569579757659784
val1_1 = 7534756766114272409848924
val3_1 = 7164946524794170391755686

val0_2 = 3547087743966283680068135913
val1_2 = 3546907814927828372720325053
val2_2 = 3547059021367945143469206505

val0_7 = 3756478321347707411745123850684568
val2_7 = 3756478321318984813406587251755160
val3_7 = 3756478320797968131969714484780470

assert val1_1 - val0_1 == val1_2 - val0_2
diff1 = val1_1 - val0_1

assert val2_2 - val0_2 == val2_7 - val0_7
diff2 = val2_2 - val0_2

assert val3_1 - val0_1 == val3_7 - val0_7
diff3 = val3_1 - val0_1

flag_head = 'timctf{'

chars = ''
for code in range(32, 127):
    chars += chr(code)

for c in itertools.product(chars, repeat=3):
    flag0 = flag_head + ''.join(c)
    int_flag0 = int(flag0.encode('hex'), 16)
    int_flag1 = int_flag0 + diff1
    int_flag2 = int_flag0 + diff2
    int_flag3 = int_flag0 + diff3
    hex_flag1 = '%x' % int_flag1
    hex_flag2 = '%x' % int_flag2
    hex_flag3 = '%x' % int_flag3
    if len(hex_flag1) % 2 == 0:
        flag1 = hex_flag1.decode('hex')
        if is_printable(flag1) == False:
            continue
    if len(hex_flag2) % 2 == 0:
        flag2 = hex_flag2.decode('hex')
        if is_printable(flag2) == False:
            continue
    if len(hex_flag3) % 2 == 0:
        flag3 = hex_flag3.decode('hex')
        if is_printable(flag3) == False:
            continue
        if flag3[-1] != '}':
            continue
    flag = flag0 + flag1 + flag2 + flag3
    print flag

候補はたくさん出てくるので、英語の文章になるものをsubmitしていく。

timctf{d0_NOt_R3inV3nT_CrYpt0_Pl34sE}

Feedback (Misc 10)

アンケートに答えて、再度アンケートフォームにアクセスすると、フラグが表示されていた。

timctf{we_hope_it_was_fun_and_youll_be_back_next_year}