HITCON CTF 2018 Writeup

この大会は2018/10/20 11:00(JST)~2018/10/22 11:00(JST)に開催されました。
今回もチームで参戦。結果は 330点で1816チーム中116位でした。
Welcome問題だけでしたが、自分で解けた問題をWriteupとして書いておきます。

Lumosity (Welcome)

プレースホルダにフラグが書いてある。

hitcon{Th1s_f13ld-1s^r3qu1r3d}

Hack.lu CTF 2018 Writeup

この大会は2018/10/16 19:00(JST)~2018/10/18 19:00(JST)に開催されました。
今回もチームで参戦。結果は 1109点で1035チーム中51位でした。
自分で解けた問題をWriteupとして書いておきます。

Relations (Crypto)

いろいろ試してみる。

$ nc arcade.fluxfingers.net 1821
------------------------------
Welcome to theory world
------------------------------

------------------------------
Possible Oracles
(XOR) Choose XOR Oracle
(ADD) Choose ADD Oracle
(DEC) For trying to decrypt
-----------------------------*
XOR

Please choose the operand in hex >>> 01
Ciphertext is  sY4v3laEDC9hcW7OLMoOkxrz7SVVioYl+4ni7XawrypvCKuodcSPtwpvEJyvNGxDQgEM+qCeKcaq
xk1a7I65qQ==

------------------------------
Possible Oracles
(XOR) Choose XOR Oracle
(ADD) Choose ADD Oracle
(DEC) For trying to decrypt
-----------------------------*
XOR

Please choose the operand in hex >>> 0001
Ciphertext is  sY4v3laEDC9hcW7OLMoOkxrz7SVVioYl+4ni7XawrypvCKuodcSPtwpvEJyvNGxDQgEM+qCeKcaq
xk1a7I65qQ==

------------------------------
Possible Oracles
(XOR) Choose XOR Oracle
(ADD) Choose ADD Oracle
(DEC) For trying to decrypt
-----------------------------*
XOR

Please choose the operand in hex >>> 56
Ciphertext is  NWwcbySi44ud+TDvirbaZbwH0P5iXI0DIce9fPsp1PAapdofgXXHF2Up8cVdgmQZff4z9IUxemHh
7vHVQ6pz+w==

------------------------------
Possible Oracles
(XOR) Choose XOR Oracle
(ADD) Choose ADD Oracle
(DEC) For trying to decrypt
-----------------------------*
ADD

Please choose the operand in hex >>> 56
Ciphertext is  YGuF1Rl5Xj44kM+21Fazt7IFH0pDb5VV9QhnWghpIfCQr6fjwi2ko9RHEdw+s7dgEMKTWaf3eum/
QeqPcV6Vng==

$ nc arcade.fluxfingers.net 1821
------------------------------
Welcome to theory world
------------------------------

------------------------------
Possible Oracles
(XOR) Choose XOR Oracle
(ADD) Choose ADD Oracle
(DEC) For trying to decrypt
-----------------------------*
DEC

Enter the key base64 encoded >>> MDEy                                    
Traceback (most recent call last):
  File "/home/chall/rka.py", line 113, in <module>
    main()
  File "/home/chall/rka.py", line 107, in main
    aes = pyaes.AESModeOfOperationECB(key)
  File "/home/chall/pyaes/aes.py", line 304, in __init__
    self._aes = AES(key)
  File "/home/chall/pyaes/aes.py", line 134, in __init__
    raise ValueError('Invalid key size')
ValueError: Invalid key size

どうやらAESの鍵にXORやADDして暗号化した結果を表示していると推定できる。各ビットに対して、XORした鍵とADDした鍵が同じ場合は、ビットが立っていないことになることを利用して、鍵を1ビットずつ割り出し、128ビット分判明したら復号する。

import socket
import re
import base64

def recvuntil(s, tail):
    data = ''
    while True:
        if tail in data:
            return data
        data += s.recv(1)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('arcade.fluxfingers.net', 1821))

b_key = ''
for i in range(128):
    print 'Round %d' % (i + 1)
    print b_key
    send_data = hex(2**i)[2:].rstrip('L')

    data = recvuntil(s, '*\n')
    print data + 'XOR'
    s.sendall('XOR\n')
    data = recvuntil(s, '>>> ')
    print data + send_data
    s.sendall(send_data + '\n')

    data = recvuntil(s, '\n\n')
    pattern = 'Ciphertext is  (.+)\n(.+)\n'
    m = re.search(pattern, data)
    ciphertext = m.group(1) + m.group(2)
    print data

    data = recvuntil(s, '*\n')
    print data + 'ADD'
    s.sendall('ADD\n')
    data = recvuntil(s, '>>> ')
    print data + send_data
    s.sendall(send_data + '\n')

    data = recvuntil(s, '\n\n')
    pattern = 'Ciphertext is  (.+)\n(.+)\n'
    m = re.search(pattern, data)
    ciphertext2 = m.group(1) + m.group(2)
    print data

    if ciphertext == ciphertext2:
        b_key = '0' + b_key
    else:
        b_key = '1' + b_key

key = ''
for i in range(0, 128, 8):
    key += chr(int(b_key[i:i+8], 2))

print key.encode('hex')
b64_key = base64.b64encode(key)

data = recvuntil(s, '*\n')
print data + 'DEC'
s.sendall('DEC\n')
data = recvuntil(s, '>>> ')
print data + b64_key
s.sendall(b64_key + '\n')

data = recvuntil(s, '\n')
print data
flag{r3l4t3d_k3y_der1iviNg_fuNct1on5_h4ve_to_be_a_l1mit3d_cla55}

Multiplayer Part 1 (Crypto)

jsonデータ入力。パラメータは x, y, c, d, groupIDで、以下の形式。

{"x": val, "y": val, "c": val, "d": val, "groupID": val}

X = E*1
X == c*P + d*Q
response = get_response(data["x"], data["y"], data["c"], data["d"], data["groupID"])

DBにない場合は登録。指定GroupIDのデータを201個以上登録できたら、フラグが表示される。c, dをランダムな値にしてXを算出。x, yを指定すればよい。

import socket
import re
from gmpy2 import invert
from random import randint

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

        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

p = 889774351128949770355298446172353873
a = 12345
b = 67890
px = 238266381988261346751878607720968495
py = 591153005086204165523829267245014771
qx = 341454032985370081366658659122300896
qy = 775807209463167910095539163959068826

def recvuntil(s, tail):
    data = ''
    while True:
        if tail in data:
            return data
        data += s.recv(1)

def make_json(x, y, c, d):
    groupID = 'n0r4n3c0_m1k3n3c0'
    json_format = '{"x": "%d", "y": "%d", "c": "%d", "d": "%d", "groupID": "%s"}'
    json = json_format % (x, y, c, d, groupID)
    return json

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('arcade.fluxfingers.net', 1822))

E = EllipticCurve(a, b, p)
P = Point(E, px, py)
Q = Point(E, qx, qy)

while True:
    c = randint(1, p - 1)
    d = randint(1, p - 1)
    X = c * P + d * Q
    x = X.x
    y = X.y
    json = make_json(x, y, c, d)
    print json
    s.sendall(json + '\n')
    data = recvuntil(s, '}')
    if 'flag1' in data:
        data += recvuntil(s, '}')
        print data
        break
    else:
        print data
flag{d1str1but3_the_rh0w_p0W3r}

*1:x, y

HumanCTF Writeup

この大会は2018/10/13 10:00(JST)~2018/10/13 17:00(JST)に開催されました。
今回もチームで参戦。結果は 2009点で112チーム中2位でした。
自分で解けた問題をWriteupとして書いておきます。

Disgruntled (Misc 10)

bijereg@gmail.comに次の文面でメールをしたら、フラグを返してくれた。

Please tell me your secrets!
Best Regards
HumanCTF{nin3_to_fiv3_j0b}

Lottery (Misc 10)

ヒントのメニューが10個あり、すべて-1点。どうやらくじ引きでこのうち1個にフラグが書いてあるらしい。なんとなく7つ目を開いたら、フラグが表示された。

HumanCTF{WINNER!}

Meet the Überbank (Web 20)

HTMLソースのコメントにフラグが書いてあった。

    <!--
    Developed by Sergey Belousov (bijereg@gmail.com) for HumanCTF.
    Telegram: @bijereg
    https://vk.com/bijereg


    FLAG:
    HumanCTF{ha57_du_3in3n_V3r7rag}
    -->
HumanCTF{ha57_du_3in3n_V3r7rag}

What you have tamed? (Web 50)

robotsの話が問題文にあるので、http://ctf.knastu.ru/webch/robots.txt にアクセスすると、フラグが書いてあった。

User-Agent: *
Allow: /
Allow: /jobs
Flag: HumanCTF{skynet_d035n7_pa55}
Disallow: /jobs/manage
Allow: /about
Allow: /cards
Allow: /deposits
HumanCTF{skynet_d035n7_pa55}

More than privacy (Web 80)

Privacy Policyをクリックすると、以下のページにアクセスできる。

http://ctf.knastu.ru/webch/read?file=privacy.txt

http://ctf.knastu.ru/webch/read?file=flag.txt にアクセスしてみると、フラグが表示された。

HumanCTF{n0b0dy_r3ad5_privacy}

Stealer (Stego 90)

そのまま解凍すると、flag.txtが展開されるが、「It is not the flag」と書いてあるだけでフラグは書かれていない。
rarファイルなので、ADSにフラグが隠されていると推測して、WinRARで解凍する。解凍したディレクトリで、以下を実行する。

>dir /R
 ドライブ C のボリューム ラベルは S3A8244D001 です
 ボリューム シリアル番号は 50D2-38C8 です

 C:\CTF\work\Flag のディレクトリ

2018/10/13  11:34    <DIR>          .
2018/10/13  11:34    <DIR>          ..
2018/10/10  22:46                18 Flag.txt
                                 28 Flag.txt:Flag.txt:$DATA
                                345 Flag.txt:Zone.Identifier:$DATA
               1 個のファイル                  18 バイト
               2 個のディレクトリ  1,242,034,556,928 バイトの空き領域

>more < Flag.txt:Flag.txt:$DATA
HumanCTF{d0ub13_fak3_f1ag}
HumanCTF{d0ub13_fak3_f1ag}

InCTF 2018 Writeup

この大会は2018/10/6 20:00(JST)~2018/10/8 8:00(JST)に開催されました。
今回もチームで参戦。結果は 2120点で306チーム中24位でした。
自分で解けた問題は参加賞の問題だけですが、Writeupとして書いておきます。

Sanity check (Trivia)

freenodeで#bi0s-ctfチャネルに入る。

20:03 *topic : Official channel for InCTF | flag for Sanity Check - inctf{W3lc0me_to_our_CTF!!} | CTF has started!! |  Register at https://ctf.inctf.in/ | Read rules at https://ctf.inctf.in/rules/ | Organised by team bi0s - https://bi0s.in
inctf{W3lc0me_to_our_CTF!!}

Hackover CTF 2018 Writeup

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

UnbreakMyStart (forensics)

xzのフォーマットだとしたら、先頭が壊れている。以下のように修正する。

(修正前)50 4B 03 04 14 00 08 00 08 00 04
(修正後)FD 37 7A 58 5A 00 00 04

解凍すると、flag.txtが展開され、フラグが書いてあった。

hackover18{U_f0und_th3_B3st_V3rs10n}

Teaser Dragon CTF 2018 参戦

この大会は2018/9/29 21:00(JST)~2018/9/30 21:00(JST)に開催されました。
今回もチームで参戦。結果は254点で233チーム中74位でした。
今回は自分が得点した問題は1問もありませんでした。
暗号の問題を解きたかったけど、まだまだ精進が足りないってことか…。

DefCamp CTF Qualification 2018 Writeup

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

XORnigma (Junior)

XOR暗号。フラグはDCTF{で始まることを考慮し、キーを割り出す。キーの1バイト目と5バイト目が同じなので、4バイトと考え、復号する。

import itertools

def xor_two_str(s, key):
    key = key * (len(s) / len(key) + 1)
    return ''.join(chr(ord(x) ^ ord(y)) for (x,y) in itertools.izip(s, key))

enc = '000000003f2537257777312725266c24207062777027307574706672217a67747374642577263077777a3725762067747173377326716371272165722122677522746327743e'
enc = enc.decode('hex')
flag_head = 'DCTF{'

key = ''
for i in range(len(flag_head)):
    key += chr(ord(enc[i]) ^ ord(flag_head[i]))
key = key[:4]

flag = xor_two_str(enc, key)
print flag
DCTF{fcc34eaae8bd3614dd30324e932770c3ed139cc2c3250c5b277cb14ea33f77a0}

Multiple Flags (Junior)

手旗信号の画像。
f:id:satou-y:20180926185751p:plain
https://ja.wikipedia.org/wiki/%E6%89%8B%E6%97%97%E4%BF%A1%E5%8F%B7を参考に、文字にしていく。

(文字)DCTFSPECIALFLAG(数字)00(文字)AA(数字)00(文字)AA(数字)00991337(文字)DCTF
DCTFSPECIALFLAG00AA00AA00991337DCTF

Passport (Junior)

demo.binと異なるバイナリで、md5が同じファイルをアップロードすればフラグが表示されるようだ。
試しにdemo.binのmd5の値で調べてみる。

cee9a457e790cf20d4bdaa6d69f01e41

http://www.xefan.com/archives/83875.htmlに衝突する2つのファイルが書いてあり、片方はdemo.binと一致する。もう一つのバイナリを作成すればよい。
バイナリエディタでdemoの一部を修正する。

・オフセット0x15:0x70→0x74
・オフセット0x2b:0x5c→0xdc

この作成したバイナリファイルをアップロードすると、フラグが表示された。

DCTF{04c8d0052e3ffd8d21934e392c272a0494f23433901941c93fab82b50be27c1a}

Ransomware (Reverse)

ransomware.pycをEasyPythonDecompilerでデコンパイルする。

import string
from random import *
import itertools

def caesar_cipher(OOO0O0O00OOO0O0OO, O0O0O0O0OOOO0OOOO):
    O0O0O0O0OOOO0OOOO = O0O0O0O0OOOO0OOOO * (len(OOO0O0O00OOO0O0OO) / len(O0O0O0O0OOOO0OOOO) + 1)
    return ''.join((chr(ord(O0O0O00O0000O00O0) ^ ord(OO0000000O0OO00OO)) for O0O0O00O0000O00O0, OO0000000O0OO00OO in itertools.izip(OOO0O0O00OOO0O0OO, O0O0O0O0OOOO0OOOO)))


f = open('./FlagDCTF.pdf', 'r')
buf = f.read()
f.close()
allchar = string.ascii_letters + string.punctuation + string.digits
password = ''.join((choice(allchar) for OOO0OO0OO00OO0000 in range(randint(60, 60))))
buf = caesar_cipher(buf, password)
f = open('./youfool!.exe', 'w')
buf = f.write(buf)
f.close()

cease_cipher関数は読みにくいので、置き換える。

def caesar_cipher(x, y):
    y = y * (len(x) / len(y) + 1)
    return ''.join((chr(ord(a) ^ ord(b)) for a, b in itertools.izip(x, y)))

簡単に言えば, XOR関数で、実行している部分を見ると、鍵の長さは60であることがわかる。PDFに復号できることを前提に少しずつ調整しながら鍵を見つけていく。

def str_xor(s1, s2):
    return ''.join(chr(ord(a) ^ ord(b)) for a, b in zip(s1, s2))

with open('youfool!.exe', 'rb') as f:
    data = f.read()

key = ''
#### key 0-6
PDF_HEAD = '%PDF-1.'
key += str_xor(PDF_HEAD, data[:len(PDF_HEAD)])

#### key 7-9
AFTER_FIL = 'ter'
key += str_xor(AFTER_FIL, data[1387:1390])

#### key 10-11
AFTER_LENG = 'th'
key += str_xor(AFTER_LENG, data[8470:8472])

#### key 12-15
AFTER_CAT = 'alog'
key += str_xor(AFTER_CAT, data[672:676])

#### key 16-19
AFTER_MIDI = 'aBox'
key += str_xor(AFTER_MIDI, data[856:860])

#### key 20-22
AFTER_FLATEDEC = 'ode'
key += str_xor(AFTER_FLATEDEC, data[10220:10223])

#### key 23-24
AFTER_PARE = 'nt'
key += str_xor(AFTER_PARE, data[10043:10045])

#### key 25-27
AFTER_IM = 'age'
key += str_xor(AFTER_IM, data[9625:9628])

#### key 28-30
AFTER_LEN = 'gth'
key += str_xor(AFTER_LEN, data[1408:1411])

#### key 31-32
AFTER_FILT = 'er'
key += str_xor(AFTER_FILT, data[10531:10533])

#### key 33-34
AFTER_TY = 'pe'
key += str_xor(AFTER_TY, data[10173:10175])

#### key 35-43
AFTER_FL = 'ateDecode'
key += str_xor(AFTER_FL, data[9695:9704])

#### key 44-45
EOF_TAIL = 'OF'
key += str_xor(EOF_TAIL, data[10784:10786])

#### key 46-50
AFTER_FLATEDECODE_L = 'ength'
key += str_xor(AFTER_FLATEDECODE_L, data[1066:1071])

#### key 51-56
AFTER_EXT = 'GState'
key += str_xor(AFTER_EXT, data[10071:10077])

#### key 57-59
AFTER_RESO = 'urc'
key += str_xor(AFTER_RESO, data[897:900])

print '[+] key:', key

pdf = ''
for i in range(0, len(data), 60):
    dec = str_xor(data[i:i+60], key)
    print dec, i # for arrangement
    pdf += dec

with open('FlagDCTF.pdf', 'wb') as f:
    f.write(pdf)

復号したPDFは3ページにわたり、フラグが書かれている。
f:id:satou-y:20180926193052p:plain
f:id:satou-y:20180926193104p:plain
f:id:satou-y:20180926193113p:plain

DCTF{d915b5e076215c3efb92e5844ac20d0620d19b15d427e207fae6a3b894f91333}