Sunshine CTF 2017 Writeup

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

Snowflake (Crypto 1)

問題に記載してあるフラグを投入するだけ。

sun{74K3_Y0UR_p4r7icIP47i0N_7r0PhY_y0U_5N0Wfl4k3}

Nonlinear 2 (Stego 100)

なんとなくQRコードになりそうな16進数が並んでいる。2進数に変換する。

with open('nonlinear2.txt', 'r') as f:
    lines = f.readlines()

for line in lines:
    h_str = line.strip()[2:]
    bin_str = ''
    for c in h_str:
        bin_str += format(int(c, 16), '04b')
    print bin_str

この結果、やはりQRコードのようだ。

00000000000000000000000000000000
00011111110001010101011111110000
00010000010101001011010000010000
00010111010000010101010111010000
00010111010111011101010111010000
00010111010010000111010111010000
00010000010110110111010000010000
00011111110101010101011111110000
00000000000011100000000000000000
00011111011110101011101010100000
00010010000101010100001001000000
00010111110001000010110011110000
00010100101100010000010000110000
00001101111111011011011101110000
00010100000110000101100011100000
00010101011011110010011101110000
00010000001100100011100000100000
00010010111000111111111101010000
00000000000110010101000110100000
00011111110111000001010100110000
00010000010011000011000100110000
00010111010110101011111101000000
00010111010111100101110110110000
00010111010110010001101011010000
00010000010111000100011100010000
00011111110110011100101100110000
00000000000000000000000000000000

端の0を削除し、sqrd.pyで読み込む。

>sqrd.py qr.txt
sun{qr_c0d3s_4r3_st00p1d}
sun{qr_c0d3s_4r3_st00p1d}

Easy 1 (Web 25)

レスポンスヘッダのflagパラメータにフラグあり。

sun{k4rEfUL_D0nT_H1T_y0uR_HE4dEr}

Teaser CONFidence CTF 2017 Writeup

この大会は2017/4/2 7:30(JST)~2017/4/3 7:30(JST)に開催されました。
今回もチームで参戦。結果は1点で258チーム中99位でした。
暗号の問題を解けなかったのが非常に残念!
自分で解けた問題をWriteupとして書いておきます。

Sanity check (Miscellaneous 1)

freenodeの#dragonsectorチャネルに入ると、フラグを見つけることができる。

07:32 *topic : Dragon Sector CTF Team | Confidence CTF Teaser: https://ctf.dragonsector.pl | DrgnS{Good_job!_This_is_what_a_flag_looks_like} | utf8: żółwiątko
DrgnS{Good_job!_This_is_what_a_flag_looks_like}

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?