April Fools' GTF 2017 Writeup

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

Welcome!! (Misc 500)

問題は「あなたのパスワードを入力してください(今日はエイプリルフールです.)」。
答えは何でもよかったらしいが、こう入れてみた。

password

O-mikuji (Guess 100, -500)

問題は「フラグは数字です.」とだけ。
それっぽい数字を選んだら間違いでフラグ2を取って、-500点になってしまった。URLに含まれている問題番号が正解のようだった。

6

Thank You! (Misc 9)

アンケートに答えるだけ。

AFCTF{thank_you_for_wasting_your_time}

Securinets CTF Quals 2017 Writeup

この大会は2017/3/26 2:00(JST)~2017/3/27 2:00(JST)に開催されました。
今回もチームで参戦。結果は1500点で78チーム中8位でした。
自分で解けた問題をWriteupとして書いておきます。

Fix It, Or May Be Not? (Binary 50)

SSHでログインし、damagedファイルを調べる。

DemBruiser@vps389317:~$ xxd damaged | head
00000000: 454c 463e 4000 0000 0000 4000 0000 0000  ELF>@.....@.....
00000010: 0000 981a 0000 0000 0000 0000 0000 4000  ..............@.
00000020: 3800 0040 001f 001c 0006 0000 0005 0000  8..@............
00000030: 0040 0000 0000 0000 0040 0040 0000 0000  .@.......@.@....
00000040: 0040 0040 0000 0000 00f8 0100 0000 0000  .@.@............
00000050: 00f8 0100 0000 0000 0008 0000 0000 0000  ................
00000060: 0003 0000 0004 0000 0038 0200 0000 0000  .........8......
00000070: 0038 0240 0000 0000 0038 0240 0000 0000  .8.@.....8.@....
00000080: 001c 0000 0000 0000 001c 0000 0000 0000  ................
00000090: 0001 0000 0000 0000 0001 0000 0005 0000  ................

ELFとしては先頭に1バイト「7f」が足りない。

DemBruiser@vps389317:~$ strings damaged
ELF>@
/lib64/ld-linux-x86-64.so.2
vX#&I
s7"6cW
libc.so.6
__stack_chk_fail
rand
__libc_start_main
snprintf
__gmon_start__
GLIBC_2.2.5
GLIBC_2.4
UH-H
AWAVA
AUATL
[]A\A]A^A_
yeah it's a boring program
;*3$"
NDc2ZjZmNjQ1ZjRhNmY2MjVmNDI3Mjc1Njg1Zjc0Njg2NTVmNzc2ZjcyNzM3NDVmNjk3MzVmNzM3\nNDY5NmM2YzVmNzQ2ZjVmNjM2ZjZkNjU=\n ★あやしい文字列発見!
GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.4)5.4.020160609
crtstuff.c
__JCR_LIST__
deregister_tm_clones
__do_global_dtors_aux
completed.7585
__do_global_dtors_aux_fini_array_entry
frame_dummy
__frame_dummy_init_array_entry
damaged.c
__FRAME_END__
__JCR_END__
__init_array_end
_DYNAMIC
__init_array_start
__GNU_EH_FRAME_HDR
_GLOBAL_OFFSET_TABLE_
__libc_csu_fini
_ITM_deregisterTMCloneTable
do_other_stuff
_edata
__stack_chk_fail@@GLIBC_2.4
snprintf@@GLIBC_2.2.5
__libc_start_main@@GLIBC_2.2.5
__data_start
__gmon_start__
__dso_handle
_IO_stdin_used
__libc_csu_init
__bss_start
main
_Jv_RegisterClasses
__TMC_END__
_ITM_registerTMCloneTable
do_stuff
rand@@GLIBC_2.2.5
.symtab
.strtab
.shstrtab
.interp
.note.ABI-tag
.note.gnu.build-id
.gnu.hash
.dynsym
.dynstr
.gnu.versio
waaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
DemBruiser@vps389317:~$ echo NDc2ZjZmNjQ1ZjRhNmY2MjVmNDI3Mjc1Njg1Zjc0Njg2NTVmNzc2ZjcyNzM3NDVmNjk3MzVmNzM3NDY5NmM2YzVmNzQ2ZjVmNjM2ZjZkNjU= | base64 -d
476f6f645f4a6f625f427275685f7468655f776f7273745f69735f7374696c6c5f746f5f636f6d65
DemBruiser@vps389317:~$ echo 476f6f645f4a6f625f427275685f7468655f776f7273745f69735f7374696c6c5f746f5f636f6d65 | xxd -r -p
Good_Job_Bruh_the_worst_is_still_to_come
Good_Job_Bruh_the_worst_is_still_to_come

VolgaCTF 2017 Quals Writeup

この大会は2017/3/25 0:00(JST)~2017/3/27 0:00(JST)に開催されました。
今回もチームで参戦。結果は1650点で1024チーム中26位でした。
自分で解けた問題をWriteupとして書いておきます。

VC (crypto 50)

2つのノイズのような画像が与えられている。2つの画像の差がある部分を黒にしてみる。

from PIL import Image

img_a = Image.open('A.png').convert('RGB')
img_b = Image.open('B.png').convert('RGB')
w, h = img_a.size

img_o = Image.new('RGB', (w, h), (255, 255, 255))

for y in range(0, h):
    for x in range(0, w):
        r1, g1, b1 = img_a.getpixel((x, y))
        r2, g2, b2 = img_b.getpixel((x, y))
        if r1 == r2 and g1 == g2 and b1 == b2:
            img_o.putpixel((x, y), (255, 255, 255))
        else:
            img_o.putpixel((x, y), (0, 0, 0))

img_o.save('flag.png')

f:id:satou-y:20170331221319p:plain
フラグが表示された。

VolgaCTF{Classic_secret_sharing_scheme}

Curved (crypto 200)

$ nc curved.quals.2017.volgactf.ru 8786
Solve a puzzle: find an x such that 26 last bits of SHA1(x) are set, len(x)==29 and x[:24]=='875378c8c528de3c569e29a6'

最初にこの問題を解く必要がある。その後、ECDSAの問題となる。
添付のスクリプトを見てみると、以下のことがわかる。

・サーバOS上でls、dir、cd、catコマンド、また抜けるためにexitやleaveというコマンドが使える。
・ただし、該当するシグネチャと合わせて、チェックを通らないと実行できない。
・exitとleaveのシグネチャは添付されており、rは同じ値になっている。

cat flagのシグネチャを作ることができれば、フラグを奪取できそうだ。
rが同じで、シグネチャが分かっているものが2組あれば、秘密鍵を導き出すことができる。

e1 = int(hashlib.sha512('exit').hexdigest(), 16)
e2 = int(hashlib.sha512('leave').hexdigest(), 16)
z1 = e1 >> 512 - nのビット長
z2 = e2 >> 512 - nのビット長
k = ((z1 - z2) % n) * invert(((s1 - s2) % n), n)
privatekey = (((((s1 * k) % n) - z1) % n) * invert(r1, n)) % n

秘密鍵がわかれば、どのコマンドでも添付のソースコード記載の通り、シグネチャを作成できる。プログラムは以下の通り。

#!/usr/bin/env python
import socket
import re
import string
import itertools
import hashlib
from gmpy2 import invert, bit_length

def import_public_key(file):
    with open(file, 'r') as f:
        data = f.read()
        d = data.split('\n')
        QAx = int(d[0])
        QAy = int(d[1])
        return QAx, QAy

def import_cmd_signature(cmd):
    file = '{0}.sig'.format(cmd)
    with open(file, 'r') as f:
        data = f.read()
        d = data.split('\n')
        (r, s) = (int(d[0]), int(d[1]))
        return r, s

class EllipticCurve(object):
    def __init__(self, a, b, p, n):
        self.a = a
        self.b = b
        self.p = p
        self.n = n

        self.discriminant = -16 * (4 * a * a * a + 27 * b * b)
        if not self.isSmooth():
            raise Exception("The curve %s is not smooth!" % self)

    def isSmooth(self):
        return self.discriminant != 0

    def testPoint(self, x, y, p):
        return (y ** 2) % p == (x ** 3 + self.a * x + self.b) % p

    def __str__(self):
        return 'y^2 = x^3 + %Gx + %G (mod %G)' % (self.a, self.b, self.p)

    def __eq__(self, other):
        return (self.a, self.b, self.p) == (other.a, other.b, other.p)


class Point(object):
    def __init__(self, curve, x, y):
        self.curve = curve
        self.x = x
        self.y = y
        if not curve.testPoint(x, y, curve.p):
            raise Exception("The point %s is not on the given curve %s" % (self, curve))

    def __neg__(self):
        return Point(self.curve, self.x, -self.y)

    def __add__(self, Q):
        if isinstance(Q, Ideal):
            return self
        x_1, y_1, x_2, y_2 = self.x, self.y, Q.x, Q.y
        if (x_1, y_1) == (x_2, y_2):
            if y_1 == 0:
                return Ideal(self.curve)
            s = (3 * x_1 * x_1 + self.curve.a) * int(invert(2 * y_1, self.curve.p))
        else:
            if x_1 == x_2:
                return Ideal(self.curve)
            s = (y_2 - y_1) * int(invert(x_2 - x_1, self.curve.p))
        x_3 = (s * s - x_2 - x_1) % self.curve.p
        y_3 = (s * (x_3 - x_1) + y_1) % self.curve.p
        return Point(self.curve, x_3, self.curve.p - y_3)

    def __sub__(self, Q):
        return self + -Q

    def __mul__(self, n):
        if not (isinstance(n, int) or isinstance(n, long)):
            raise Exception("Can't scale a point by something which isn't an int!")
        else:
            if n < 0:
                return -self * -n
            if n == 0:
                return Ideal(self.curve)
            else:
                Q = self
                R = self if n & 1 == 1 else Ideal(self.curve)
                i = 2
                while i <= n:
                    Q = Q + Q
                    if n & i == i:
                        R = Q + R
                    i = i << 1
        return R

    def __rmul__(self, n):
        return self * n

class Ideal(Point):

    def __init__(self, curve):
        self.curve = curve

    def __str__(self):
        return "Ideal"

    def __neg__(self):
        return self

    def __add__(self, Q):
        return Q

    def __mul__(self, n):
        if not isinstance(n, int):
            raise Exception("Can't scale a point by something which isn't an int!")
        else:
            return self

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('curved.quals.2017.volgactf.ru', 8786))

data = s.recv(1024)
print data

pattern = 'x\[:24\]==\'(.+)\''
m = re.search(pattern, data)
x_head = m.group(1)

for c in itertools.product(string.printable, repeat=5):
    x = x_head + ''.join(c)
    if int(hashlib.sha1(x).hexdigest(), 16) & 0x3ffffff == 0x3ffffff:
        break

print x
s.sendall(x + '\n')

data = s.recv(1024)
print data

p = 39402006196394479212279040100143613805079739270465446667948293404245721771496870329047266088258938001861606973112319
n = 39402006196394479212279040100143613805079739270465446667946905279627659399113263569398956308152294913554433653942643
a = -3
b = int('b3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef', 16)
NIST384 = EllipticCurve(a, b, p, n)

Gx = int('aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7', 16)
Gy = int('3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f', 16)
G = Point(NIST384, Gx, Gy)

QA = import_public_key('key.public')
QA = Point(NIST384, QA[0], QA[1])

r1, s1 = import_cmd_signature('exit')
r2, s2 = import_cmd_signature('leave')
e1 = int(hashlib.sha512('exit').hexdigest(), 16)
e2 = int(hashlib.sha512('leave').hexdigest(), 16)

Ln = bit_length(n)
z1 = e1 >> (512 - Ln)
z2 = e2 >> (512 - Ln)
k = int(((z1 - z2) % n) * invert(((s1 - s2) % n), n))
priv = int((((((s1 * k) % n) - z1) % n) * invert(r1, n)) % n)

cmd = 'cat flag'
e3 = int(hashlib.sha512(cmd).hexdigest(), 16)
z3 = e3 >> (512 - Ln)
xy = k * G
r3 = xy.x % n
s3 = int(invert(k, n) * (z3 + r3 * priv) % n)

ans = str(r3) + ' ' + str(s3) + ' ' + cmd
print ans
s.sendall(ans + '\n')

data = s.recv(1024)
print data

実行すると、フラグが表示された。

VolgaCTF{N0nce_1s_me@nt_to_be_used_0n1y_Once}

DoubleS1405 CTF Writeup

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

Mic Check (Misc 1)

問題に記載してあるフラグを解凍するだけ。"flag{" と"}"は不要。

W3lc0me_T0_DoubleS1405_CTF!

Easy_Crypt (Crypto 50)

Vigenere暗号の問題。
オンラインツールhttps://www.guballa.de/vigenere-solverで復号してみると、次のような文章になっていた。

the vigenere cipher is a method of encrypting alphabetic text by using a series of interwoven caesar ciphers based on the letters of a keyword. it is a form of polyalphabetic substitution. flag is hello_vigenere
hello_vigenere

RSA (Crypto 150)

RSA暗号の問題。

$ nc 203.251.182.94 4000
enc : 524088215288945245117812840274234074214259867311424793488872316310812706883024313867933264975831745029054523235181695352586709977208196363681301896057064415274078567043967757970801714625738226262293721554006107104236101335115913713660091583447557282002619134700442555482433678036710317021694260495168589479860504255753735295684993178728329372305366606635533145127946686273344730509844775300122864925154558295745613688004661516962622482022264505375004460809348994246393125753388290161988082612174365368309952527753924526213766797054879514133711596108047008600960292092135854237304270147666380464744930028252443649819646184914979910712331358503515497366868391163434331196745240129334051449430357442795848755908012895351467558418431211464970160313835649205450891868176931866059746834981469604038607844868902540578729908708475089242253961021291237310436998767128596611554846948529983930986516799540905413759161964474942642639146239239417647305400198714676910394008062192060561130474884192131791415273201113911520884357202482483443690793539529640607301455522584558165763889673353291204724196936406524674532675658355516720800608675712275967358182807215410869264526079077702286496666298128451842286840129878459991344435756626173655145826593
e : 65537
p : 32317006071311007300714876688669951960444102669715484032130345427524655138867890893197201411522913463688717960921898019494119559150490921095088152386448283120630877367300996091750197750389652106796057638384067568276792218642619756161838094338476170470581645852036305042887575891541065808607552399123930385521954285833276606292740174507176908054077273016103644389803261062635470374515595892199454891155463898488297024308700957247533881208055894474582694028535079545281620566442541400114261729854235365927395115457109476960042332821732358509197923144094801013581965651112146928918286923938064987973879624251895591220179
q : 32317006071311007300714876688669951960444102669715484032130345427524655138867890893197201411522913463688717960921898019494119559150490921095088152386448283120630877367300996091750197750389652106796057638384067568276792218642619756161838094338476170470581645852036305042887575891541065808607552399123930385521990685772174514834944123086486002362345153147580453526134037171595087108668773961917317502849945855689432886442889958513294157709640362363734479327004391952407569596153273880472331909250263593691635107321048666489395316204775782962517724272901158130972610802371589601746375325078943967095960733617174538141999

enc, e, p, qがわかり、enc以外は固定になっている。そのまま復号処理をし、hexデコードやbase64デコードで正しい答えになるよう調整する。

import socket
import re

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('203.251.182.94', 4000))
data = s.recv(4096)
print data

pattern = 'enc : (.+)\ne : (.+)\np : (.+)\nq : (.+)'
m = re.search(pattern, data)
c = int(m.group(1))
e = int(m.group(2))
p = int(m.group(3))
q = int(m.group(4))
n = p * q

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

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

m = pow(c, d, n)

ans = ('%x' % m).decode('hex').decode('base64')
ans = ('%x' % int(ans)).decode('hex')

print ans
s.sendall(ans)
data = s.recv(4096)
print data

実行結果は以下の通り。

enc : 943323076663715024141647584331931588504059538634678245280425831973796289347224969397647303454937690940952859232063793733404967720884450831759307523716676374906805977677219485079131899110251690387052195431428481983362303508265715568364926129160708402287993207521665278959090563863929142276264132832262357813131882224629836702376792898930239886372171786808974048556564459127393421203100595023420176207941095860228262228123206815043761469110111545721812944012788553149575087398528786583978437383550919700413294635921293905268700691986832657511626585629156289537911662116698677989724589794181878086199351516800977257909086655330248139060177962291764419430342443557667861917686953473395731524558266643177815366887860803117307072934899459045728454706856296407965798284164935167469656739110378295698864119610217189620023320017254576611161419886036093861180781769495008397890178322624327006809008967677442747797792584907829610673111299004805651549603121773706860385941777176103149272425054779791817810966316678169201560717394130219015631420356160614120412760419002013131762642338218434316378193883589201195199752227857462248942614326911643627127945748100365516522106686625206332033784564337959925495322684860188773445710912068399610295000708
e : 65537
p : 32317006071311007300714876688669951960444102669715484032130345427524655138867890893197201411522913463688717960921898019494119559150490921095088152386448283120630877367300996091750197750389652106796057638384067568276792218642619756161838094338476170470581645852036305042887575891541065808607552399123930385521954285833276606292740174507176908054077273016103644389803261062635470374515595892199454891155463898488297024308700957247533881208055894474582694028535079545281620566442541400114261729854235365927395115457109476960042332821732358509197923144094801013581965651112146928918286923938064987973879624251895591220179
q : 32317006071311007300714876688669951960444102669715484032130345427524655138867890893197201411522913463688717960921898019494119559150490921095088152386448283120630877367300996091750197750389652106796057638384067568276792218642619756161838094338476170470581645852036305042887575891541065808607552399123930385521990685772174514834944123086486002362345153147580453526134037171595087108668773961917317502849945855689432886442889958513294157709640362363734479327004391952407569596153273880472331909250263593691635107321048666489395316204775782962517724272901158130972610802371589601746375325078943967095960733617174538141999

49U6KMTW0P2m3baysLHcH4WKUjS4WBrk
flag{Y34hh_RSA_1S_S0_E4sy}
Y34hh_RSA_1S_S0_E4sy

Cute(Forensic 50)

$ file file
file: Matroska data

字幕が隠れていそうなので、ffmpegでテキスト化する。

$ ffmpeg -i file -map 0:s:0 file-subs.srt
          : (省略)

file-subs.srtの内容は以下の通り。

   :
80
00:00:10,517 --> 00:00:10,538
eh~!@#~!!#!

81
00:00:10,539 --> 00:00:10,539
f

82
00:00:10,539 --> 00:00:10,539
l

83
00:00:10,539 --> 00:00:10,539
a

84
00:00:10,539 --> 00:00:10,539
g

85
00:00:10,539 --> 00:00:10,539
{

86
00:00:10,539 --> 00:00:10,539
v

87
00:00:10,539 --> 00:00:10,539
3

88
00:00:10,539 --> 00:00:10,539
r

89
00:00:10,539 --> 00:00:10,539
_

90
00:00:10,539 --> 00:00:10,539
c

91
00:00:10,539 --> 00:00:10,539
u

92
00:00:10,539 --> 00:00:10,539
t

93
00:00:10,539 --> 00:00:10,539
3

94
00:00:10,539 --> 00:00:10,539
_

95
00:00:10,539 --> 00:00:10,539
p

96
00:00:10,539 --> 00:00:10,539
4

97
00:00:10,539 --> 00:00:10,539
R

98
00:00:10,539 --> 00:00:10,539
r

99
00:00:10,539 --> 00:00:10,539
0

100
00:00:10,539 --> 00:00:10,539
t

101
00:00:10,539 --> 00:00:10,539
_

102
00:00:10,539 --> 00:00:10,539
1

103
00:00:10,539 --> 00:00:10,539
2

104
00:00:10,539 --> 00:00:10,539
n

105
00:00:10,539 --> 00:00:10,539
t

106
00:00:10,539 --> 00:00:10,539
_

107
00:00:10,539 --> 00:00:10,539
!

108
00:00:10,539 --> 00:00:10,539
t

109
00:00:10,539 --> 00:00:10,539
?

110
00:00:10,539 --> 00:00:10,539
}

111
00:00:10,539 --> 00:00:10,560
eh~!@#~!!#!@
   :

これを見ると、最後の方にフラグが隠れている。

flag{v3r_cut3_p4Rr0t_12nt_!t?}
v3r_cut3_p4Rr0t_12nt_!t?

WhiteHat Challenge 02 Writeup

この大会は2017/3/25 18:00(JST)~2017/3/26 2:00(JST)に開催されました。
今回もチームで参戦。結果は80点で81チーム中20位でした。
自分で解けた問題をWriteupとして書いておきます。

Crypto001 (Cryptography 20)

n(modulus)をFermat法で素因数分解し、復号する。

def isqrt(n):
    x = n
    y = (x + n // x) // 2
    while y < x:
        x = y
        y = (x + n // x) // 2
    return x

def fermat(n):
    x = isqrt(n) + 1
    y = isqrt(x * x - n)

    while True:
        w = x * x - n - y * y
        if w == 0:
            break
        elif w > 0:
            y += 1
        else:
            x += 1
    return x - y, x + y

c = 0x42e08bb01f0cb9ba959ddcb771eb641afe87b775adfee9e02f9941d6a0f127ce3e4c794f81661295a3f6f26b5bf5b97fb244c49fe6bcebd202c337d18da4c37520461872c9e0f4d735b45f03db80288d8a4b1e2e9c8350860c0bb7bc4e461cff6e2827720450c189ff7946a48ecc234a1ad8107ed1baf3d32d4b5d678a9972e768f4f8fc854ba03c36d9f67ea1ff058c3e5390c5a3ce16e7803d6f70d2514b977911427ac92694065e1bf4f51791d0cd1e0426ced501e125fe7a03d1514b0c41dfb4c36b8c12128a84ec8bd7776089fd5918315fe4fa01a9021897f48d77ea4719f46f91ed2f07ea2cbe57214cddbccafabc2d04881ccc02cab2e6a12f9e4291
n = 22538226557359593572346358546448534316502374936010448386594814842974948185264245943532391888185780963684478526253798386491706856055721928437429885241499953118882150091346829521458455087101372254666403540557260438634164283165429965553504233799473121743076739557259202565372789362450321949620402058911471334565735242774625841491381660958586223360222338739572747101258430902493093450151421824669407116051626021283885808048507068886635263867243474018292757027989902382327148650784375980240979112130712113839597585132552988013197038652273781471554107489186108861150933302755959031058580590411316776473912540458121516196981
e = 65537

p, q = fermat(n)

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

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

m = pow(c, d, n)

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

実行すると、次の文字列になる。

flag is close_primes_is_bad
$ echo -n close_primes_is_bad | sha1sum
0732e65c5c2769ffb5ea0e408f1bf13ae6288767  -
WhiteHat{0732e65c5c2769ffb5ea0e408f1bf13ae6288767}

Crypto002 (Cryptography 15)

逆算し、keyについてブルートフォースで元に戻す。

import string

L2I = dict(zip(string.uppercase+string.lowercase+string.digits,range(64)))
I2L = dict(zip(range(64),string.uppercase+string.lowercase+string.digits))

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

for key in range(64):
    b64text = ''
    for c in ciphertext:
        if L2I.has_key(c):
            index = (L2I[c] - key) % 64
            if I2L.has_key(index):
                b64text += I2L[index]
            else:
                break
        else:
            break
    try:
        flag = b64text.decode('base64')
        print str(key) + ': ' + flag
    except:
        pass

この実行結果は以下の通り。

0: 」P08ャ杠枡・]・1枷4l%・f・l2ヲh8ォZ
8: む(ヲ0薯
}ラ:}肅呑;・)}・O」辣蓍・*・0韓
9: ~ヘ・・求ノyニモヲ礁禸∬yヤ・逃・ヲ}ル驕ユ・ネァ
10: zスヲ{qョ?・カクuテe脅ケ|qァuトェGテ}トeyノィ}ナョずf
11: vュewamヂGqヲwqウ$・xxafqエiCs$yエ$uケgyオm~ィ%
12: r・sQ,Qm・m「繝・tQ%m、(?b縉」縡ゥ&u・,z嶺
13: n後o@・@ナi・i弔怪p@臺鍋;R「q討m伜q罷v・
14: j|「k0ェw0・uエeB{yオl0」eΘ7Bamヂi愛m━rwb
15: flag is Caesar with base32 is exciting!
16: b\ c(o]U2]a゚sY3d!]c$/!゚eb゚ah"ed(jV
21: N
ロNホ綮ホI鋹喟畯ホワI゚ミ啣哺ンQ綏
22: I埇Jセ「VセシDE YZOセ妣・タYMYI廴「Q
23: E鷓FョaRョ{@緻@・V輙KョZ@ーH・DHM・
26: 9ケ・}曦}ク4イィ4マUJカゥ?}・タ・酋<タU8ナ・チ暸エV
27: 5ゥU6m]Bmw0「g0ソFヲh;mV0ーY8ー4オW8ア]=、
28: 1・2]>]6,・,ョモB・7],
31: %hQ&,Y2,s ac ~6ed+,R U・($tS(pY-c
32: !X".2Q"mマ2U#'o・マ$nマ d$`)Rミ
35: 'M・!・ _=&$`・>Q・>3O?U"
.ン・-ヒ#/フヒ"ロ
・:  ・
41:
42:
43: EMigW「X
44: 
45: ・テ?ヒ
46: 鑼る8忌8、轗皮晦
47: 臈A・I・c緡S縻 T・B縵Eア: ・ 輛C・I・
56: テメ8タ・ フ・セロ
セ銷ミ゚ナ・セ・県顆ツ・ニ・ ヒワ
59: キ。uエe}タeWイェGイキ4トョHケevイクy€w4コク4カスwコケ}ソャ5
60: ウ・ーU<シUョ・ョヲ・オU5ョィ8|fァュ6カゥ<サ幺
61: ッ€D觶Dユェ嫁ェ夢シ哉アD憐xVイイ隆ョ懾イ傴キ教
62: ォpイィ4コエ4幡y┬・ク}・4ウヲ・tFqョ?ェ原ョ絢ウ{r
63: ァ`q、$yー$S「iC「v0エmDゥ$r「wup60ェw0ヲ|sェxyッk1

keyが15のときに文になっている。

flag is Caesar with base32 is exciting!
$ echo -n "Caesar with base32 is exciting!" | sha1sum
f4637ac70451282471bb368a7566c063b87b1b24  -
WhiteHat{f4637ac70451282471bb368a7566c063b87b1b24}

Web001 (Web Security 15)

Click meをクリックすると、This is a javascript challengeと表示される。
ソースにJavaScriptがあるところを探す。
hello.jsに次のように書いてある。

function sayHello() {
	alert ("This is a javascript challenge");
	showFlag=String.fromCharCode(118,97,114,32,101,110,99,111,61,39,39,59,13,10,118,97,114,32,101,110,99,111,50,61,49,50,54,59,13,10,118,97,114,32,101,110,99,111,51,61,51,51,59,13,10,118,97,114,32,99,107,61,100,111,99,117,109,101,110,116,46,85,82,76,46,115,117,98,115,116,114,40,100,111,99,117,109,101,110,116,46,85,82,76,46,105,110,100,101,120,79,102,40,39,61,39,41,41,59,13,10,32,13,10,32,13,10,102,111,114,40,105,61,49,59,105,60,49,50,50,59,105,43,43,41,13,10,123,13,10,101,110,99,111,61,101,110,99,111,43,83,116,114,105,110,103,46,102,114,111,109,67,104,97,114,67,111,100,101,40,105,44,48,41,59,13,10,125,13,10,32,13,10,102,117,110,99,116,105,111,110,32,101,110,99,111,95,40,120,41,13,10,123,13,10,114,101,116,117,114,110,32,101,110,99,111,46,99,104,97,114,67,111,100,101,65,116,40,120,41,59,13,10,125,13,10,32,13,10,105,102,40,99,107,61,61,34,61,34,43,83,116,114,105,110,103,46,102,114,111,109,67,104,97,114,67,111,100,101,40,101,110,99,111,95,40,50,52,48,41,41,43,83,116,114,105,110,103,46,102,114,111,109,67,104,97,114,67,111,100,101,40,101,110,99,111,95,40,50,50,48,41,41,43,83,116,114,105,110,103,46,102,114,111,109,67,104,97,114,67,111,100,101,40,101,110,99,111,95,40,50,51,50,41,41,43,83,116,114,105,110,103,46,102,114,111,109,67,104,97,114,67,111,100,101,40,101,110,99,111,95,40,49,57,50,41,41,43,83,116,114,105,110,103,46,102,114,111,109,67,104,97,114,67,111,100,101,40,101,110,99,111,95,40,50,50,54,41,41,43,83,116,114,105,110,103,46,102,114,111,109,67,104,97,114,67,111,100,101,40,101,110,99,111,95,40,50,48,48,41,41,43,83,116,114,105,110,103,46,102,114,111,109,67,104,97,114,67,111,100,101,40,101,110,99,111,95,40,50,48,52,41,41,43,83,116,114,105,110,103,46,102,114,111,109,67,104,97,114,67,111,100,101,40,101,110,99,111,95,40,50,50,50,45,50,41,41,43,83,116,114,105,110,103,46,102,114,111,109,67,104,97,114,67,111,100,101,40,101,110,99,111,95,40,49,57,56,41,41,43,34,126,126,126,126,126,126,34,43,83,116,114,105,110,103,46,102,114,111,109,67,104,97,114,67,111,100,101,40,101,110,99,111,50,41,43,83,116,114,105,110,103,46,102,114,111,109,67,104,97,114,67,111,100,101,40,101,110,99,111,51,41,41,13,10,123,13,10,97,108,101,114,116,40,34,80,97,115,115,119,111,114,100,32,105,115,32,34,43,99,107,46,114,101,112,108,97,99,101,40,34,61,34,44,34,34,41,41,59,13,10,125,13,10);
	eval(showFlag);
}

Chromeデベロッパーツールで以下を実行してみる。

showFlag=String.fromCharCode(118,97,114,32,101,110,99,111,61,39,39,59,13,10,118,97,114,32,101,110,99,111,50,61,49,50,54,59,13,10,118,97,114,32,101,110,99,111,51,61,51,51,59,13,10,118,97,114,32,99,107,61,100,111,99,117,109,101,110,116,46,85,82,76,46,115,117,98,115,116,114,40,100,111,99,117,109,101,110,116,46,85,82,76,46,105,110,100,101,120,79,102,40,39,61,39,41,41,59,13,10,32,13,10,32,13,10,102,111,114,40,105,61,49,59,105,60,49,50,50,59,105,43,43,41,13,10,123,13,10,101,110,99,111,61,101,110,99,111,43,83,116,114,105,110,103,46,102,114,111,109,67,104,97,114,67,111,100,101,40,105,44,48,41,59,13,10,125,13,10,32,13,10,102,117,110,99,116,105,111,110,32,101,110,99,111,95,40,120,41,13,10,123,13,10,114,101,116,117,114,110,32,101,110,99,111,46,99,104,97,114,67,111,100,101,65,116,40,120,41,59,13,10,125,13,10,32,13,10,105,102,40,99,107,61,61,34,61,34,43,83,116,114,105,110,103,46,102,114,111,109,67,104,97,114,67,111,100,101,40,101,110,99,111,95,40,50,52,48,41,41,43,83,116,114,105,110,103,46,102,114,111,109,67,104,97,114,67,111,100,101,40,101,110,99,111,95,40,50,50,48,41,41,43,83,116,114,105,110,103,46,102,114,111,109,67,104,97,114,67,111,100,101,40,101,110,99,111,95,40,50,51,50,41,41,43,83,116,114,105,110,103,46,102,114,111,109,67,104,97,114,67,111,100,101,40,101,110,99,111,95,40,49,57,50,41,41,43,83,116,114,105,110,103,46,102,114,111,109,67,104,97,114,67,111,100,101,40,101,110,99,111,95,40,50,50,54,41,41,43,83,116,114,105,110,103,46,102,114,111,109,67,104,97,114,67,111,100,101,40,101,110,99,111,95,40,50,48,48,41,41,43,83,116,114,105,110,103,46,102,114,111,109,67,104,97,114,67,111,100,101,40,101,110,99,111,95,40,50,48,52,41,41,43,83,116,114,105,110,103,46,102,114,111,109,67,104,97,114,67,111,100,101,40,101,110,99,111,95,40,50,50,50,45,50,41,41,43,83,116,114,105,110,103,46,102,114,111,109,67,104,97,114,67,111,100,101,40,101,110,99,111,95,40,49,57,56,41,41,43,34,126,126,126,126,126,126,34,43,83,116,114,105,110,103,46,102,114,111,109,67,104,97,114,67,111,100,101,40,101,110,99,111,50,41,43,83,116,114,105,110,103,46,102,114,111,109,67,104,97,114,67,111,100,101,40,101,110,99,111,51,41,41,13,10,123,13,10,97,108,101,114,116,40,34,80,97,115,115,119,111,114,100,32,105,115,32,34,43,99,107,46,114,101,112,108,97,99,101,40,34,61,34,44,34,34,41,41,59,13,10,125,13,10);

また、次のようにJavaScriptのコードになった。

"var enco='';
var enco2=126;
var enco3=33;
var ck=document.URL.substr(document.URL.indexOf('='));
 
 
for(i=1;i<122;i++)
{
enco=enco+String.fromCharCode(i,0);
}
 
function enco_(x)
{
return enco.charCodeAt(x);
}
 
if(ck=="="+String.fromCharCode(enco_(240))+String.fromCharCode(enco_(220))+String.fromCharCode(enco_(232))+String.fromCharCode(enco_(192))+String.fromCharCode(enco_(226))+String.fromCharCode(enco_(200))+String.fromCharCode(enco_(204))+String.fromCharCode(enco_(222-2))+String.fromCharCode(enco_(198))+"~~~~~~"+String.fromCharCode(enco2)+String.fromCharCode(enco3))
{
alert("Password is "+ck.replace("=",""));
}
"

再び以下を実行する。

for(i=1;i<122;i++)
{
enco=enco+String.fromCharCode(i,0);
}
 
function enco_(x)
{
return enco.charCodeAt(x);
}
"="+String.fromCharCode(enco_(240))+String.fromCharCode(enco_(220))+String.fromCharCode(enco_(232))+String.fromCharCode(enco_(192))+String.fromCharCode(enco_(226))+String.fromCharCode(enco_(200))+String.fromCharCode(enco_(204))+String.fromCharCode(enco_(222-2))+String.fromCharCode(enco_(198))+"~~~~~~"+String.fromCharCode(enco2)+String.fromCharCode(enco3)

この結果、次のようになった。

"=youaregod~~~~~~~!"

Passwordは"="を""に置換しているので、"youaregod~~~~~~~!"がフラグ

$ echo -n "youaregod~~~~~~~!" | sha1sum
1c0e74d5f61b6c3901a277bdd490ad070265f027  -
WhiteHat{1c0e74d5f61b6c3901a277bdd490ad070265f027}

0CTF 2017 Quals Writeup

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

Welcome (Misc)

IRCでfrreenodeの#0ctf2017に入ると、フラグを見ることができる。

09:02 *topic : Welcome to 0ctf 2017! https://ctf.0ops.net  (flag{Welcome_to_0CTF_2017})
flag{Welcome_to_0CTF_2017}

integrity (Crypto)

AES暗号の問題で、ncで接続した際に"r"で指定した文字を暗号化、"l"で復号する。
その際、暗号化する文字列は32バイト以下で、"admin"を暗号化することはできない。
その状況で、復号して"admin"になる暗号文を指定することができれば、フラグが得られる。

暗号化の概要は以下の通り。
1) 指定した文字を16の倍数の文字長になるようパディング
2) 1)の文字列のMD5ダイジェストを1)の文字列の前につける。
3) 2)の文字列をランダムなIVでAES暗号化する。
4) 「IV + 3)の暗号化文字列」を16進数表記で返す。

このことから1ブロックずらして考え、目的の暗号データを取得できないか考える。
CBCモードなので、以下のようになる。
[平文1ブロック目] ^ IV --(暗号化)--> [暗号文1ブロック目]
[平文2ブロック目] ^ [暗号文1ブロック目] --(暗号化)--> [暗号文2ブロック目]
[平文3ブロック目] ^ [暗号文2ブロック目] --(暗号化)--> [暗号文3ブロック目]

平文のブロックを以下のようにする。
1ブロック目:2ブロック目+3ブロック目の文字列のMD5
2ブロック目:"admin\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"のMD5
3ブロック目:"admin\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"

これで暗号化した結果から、復号する際に以下のように指定すれば、復号して"admin"になる。
IV = [暗号結果1ブロック目]
1ブロック目:[暗号結果2ブロック目]
2ブロック目:[暗号結果3ブロック目]

これをプログラムにする。

import socket
from hashlib import md5

BS = 16
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
uname = pad('admin')
h_uname = md5(uname).digest()

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('202.120.7.217', 8221))

data = s.recv(1024).strip('\n')
print data
data = s.recv(1024).strip('\n')
print data
s.sendall('r\n')
print 'r'
name = h_uname + uname
s.sendall(name + '\n')
print h_uname + uname
data = s.recv(1024).strip('\n')
print data
data = s.recv(1024).strip('\n')
print data

enc = data.split('\n')[0]
enc = enc[BS * 2:]

s.sendall('l\n')
print 'l'
s.sendall(enc + '\n')
print enc
data = s.recv(1024).strip('\n')
print data
data = s.recv(1024).strip('\n')
print data

実行結果は以下の通り。

Welcome to 0CTF encryption service!
Please [r]egister or [l]ogin
r
!・キ・・フ└rEィ渋dmin
Here is your secret:
06f03d4dbaf5e6e99a049ce5c213fa6b1f82eaf57d71bfb74195417e9cf4c0a91389f95efc7f82af6f7c1b2decdaf2bfae0118b0d03887586632ebc95fcb2194
Please [r]egister or [l]ogin
l
1f82eaf57d71bfb74195417e9cf4c0a91389f95efc7f82af6f7c1b2decdaf2bfae0118b0d03887586632ebc95fcb2194
Welcome admin!
flag{Easy_br0ken_scheme_cann0t_keep_y0ur_integrity}
flag{Easy_br0ken_scheme_cann0t_keep_y0ur_integrity}