SECCON Beginners CTF 2019 Write-up
はじめに
2019/05/25 ~ 2019/05/26に開催されたSECCON Beginners CTFに個人で参加しました.
自分にとっての初CTFが去年のSECCON Beginners CTFだったので個人的に思い入れがあるCTFです.
去年はwarmupしか解けず,絶望してしまったので今年は時間を十分に確保して参加しました(バイトを休ませてもらった).
成績
54位(666チーム中)でした.
Misc
Welcome [warmup, 593solves, 51pts]
SECCON Beginners CTFのIRCチャンネルで会いましょう。
IRC: freenode.net #seccon-beginners-ctf
アプローチ:IRCに入る
ctf4b{welcome_to_seccon_beginners_ctf}
containers [302solves, 71pts]
Let's extract files from the container.
file
> file e35860e49ca3fa367e456207ebc9ff2f_containers e35860e49ca3fa367e456207ebc9ff2f_containers: data
アプローチ:foremost
とりあえずbinwalk
をかけるとPNG
が大量に埋め込まれていることが分かります.
> binwalk e35860e49ca3fa367e456207ebc9ff2f_containers DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 16 0x10 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 107 0x6B Zlib compressed data, compressed 738 0x2E2 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 829 0x33D Zlib compressed data, compressed 1334 0x536 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 1425 0x591 Zlib compressed data, compressed 1914 0x77A PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 2005 0x7D5 Zlib compressed data, compressed 2856 0xB28 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 2947 0xB83 Zlib compressed data, compressed 3666 0xE52 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 3757 0xEAD Zlib compressed data, compressed 4354 0x1102 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 4445 0x115D Zlib compressed data, compressed 5156 0x1424 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 5247 0x147F Zlib compressed data, compressed 5846 0x16D6 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 5937 0x1731 Zlib compressed data, compressed 6722 0x1A42 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 6813 0x1A9D Zlib compressed data, compressed 7757 0x1E4D PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 7848 0x1EA8 Zlib compressed data, compressed 8338 0x2092 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 8429 0x20ED Zlib compressed data, compressed 9243 0x241B PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 9334 0x2476 Zlib compressed data, compressed 10319 0x284F PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 10410 0x28AA Zlib compressed data, compressed 11042 0x2B22 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 11133 0x2B7D Zlib compressed data, compressed 12118 0x2F56 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 12209 0x2FB1 Zlib compressed data, compressed 12809 0x3209 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 12900 0x3264 Zlib compressed data, compressed 13845 0x3615 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 13936 0x3670 Zlib compressed data, compressed 14592 0x3900 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 14683 0x395B Zlib compressed data, compressed 15535 0x3CAF PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 15626 0x3D0A Zlib compressed data, compressed 16440 0x4038 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 16531 0x4093 Zlib compressed data, compressed 17313 0x43A1 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 17404 0x43FC Zlib compressed data, compressed 18218 0x472A PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 18309 0x4785 Zlib compressed data, compressed 19123 0x4AB3 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 19214 0x4B0E Zlib compressed data, compressed 19926 0x4DD6 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 20017 0x4E31 Zlib compressed data, compressed 20869 0x5185 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 20960 0x51E0 Zlib compressed data, compressed 21742 0x54EE PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 21833 0x5549 Zlib compressed data, compressed 22465 0x57C1 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 22556 0x581C Zlib compressed data, compressed 23408 0x5B70 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 23499 0x5BCB Zlib compressed data, compressed 23989 0x5DB5 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 24080 0x5E10 Zlib compressed data, compressed 24810 0x60EA PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 24901 0x6145 Zlib compressed data, compressed 25753 0x6499 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 25844 0x64F4 Zlib compressed data, compressed 26788 0x68A4 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 26879 0x68FF Zlib compressed data, compressed 27599 0x6BCF PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 27690 0x6C2A Zlib compressed data, compressed 28504 0x6F58 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 28595 0x6FB3 Zlib compressed data, compressed 29085 0x719D PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 29176 0x71F8 Zlib compressed data, compressed 29808 0x7470 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 29899 0x74CB Zlib compressed data, compressed 30844 0x787C PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 30935 0x78D7 Zlib compressed data, compressed 31524 0x7B24 PNG image, 128 x 128, 8-bit/color RGBA, non-interlaced 31615 0x7B7F Zlib compressed data, compressed
foremost
で抽出します.
> foremost e35860e49ca3fa367e456207ebc9ff2f_containers
PNG
イメージとflagが1対1で対応しているみたいです.
ctf4b{e52df60c058746a66e4ac4f34db6fc81}
Dump [163solves, 138pts]
Analyze dump and extract the flag!!
> file fc23f13bcf6562e540ed81d1f47710af_dump fc23f13bcf6562e540ed81d1f47710af_dump: tcpdump capture file (little-endian) - version 2.4 (Ethernet, capture length 262144)
アプローチ:Oct to Bytes
tcpdump capture file
であることがわかったのでWiresharkで覗いてみます.
GET /webshell.php?cmd=ls -l /home/ctf4b/flag HTTP/1.1 GET /webshell.php?cmd=hexdump -e '16/1 "%02.3o " "\n"' /home/ctf4b/flag
flag
をhexdump
しているっぽいのでdump
されたOctを集めてflag
を復元します.
#!/usr/bin/env python3 # -*- coding: utf-8 -*- with open('./message.txt') as f: messages = [s.strip() for s in f.readlines()] output = bytearray() for line in messages: m = line.split(' ') for x in m: if len(x) == 3: output.append(int(x,8)) with open('dump', 'wb') as f: f.write(output)
> file dump dump: gzip compressed data, last modified: Sun Apr 7 10:46:34 2019, from Unix, original size 798720
復元結果がgzip
になっているので解凍してあげます.
ctf4b{hexdump_is_very_useful}
Sliding puzzle [106solves, 206pts]
スライドパズルを解いてください。すべてのパズルを解き終わったとき FLAG が表示されます。
nc 133.242.50.201 24912
0 はブランクで動かすことが可能です。操作方法は以下のとおりです。
0 : 上
1 : 右
2 : 下
3 : 左操作手順は以下の形式で送信してください。
1,3,2,0, ... ,2
> nc 133.242.50.201 24912 ---------------- | 03 | 04 | 01 | | 08 | 02 | 05 | | 06 | 00 | 07 | ----------------
アプローチ:拾ってきたソルバ(A-star)をよしなに書き換える
ソルバをググると以下のコードがヒットします.
これを今回の問題の形式に対応させると以下のようなソルバになります.
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import numpy as np from solver import Solver from socket import * def A_star(init_state, goal_state, max_iter, heuristic): command = '' solver = Solver(init_state, goal_state, heuristic, max_iter) path = solver.solve_a_star() if len(path) == 0: exit(1) init_idx = init_state.flatten().tolist().index(0) init_i, init_j = init_idx // goal_state.shape[0], init_idx % goal_state.shape[0] for node in reversed(path): cur_idx = node.get_state().index(0) cur_i, cur_j = cur_idx // goal_state.shape[0], cur_idx % goal_state.shape[0] new_i, new_j = cur_i - init_i, cur_j - init_j if new_j == 0 and new_i == -1: command += '0,' print('Moved UP from ' + str((init_i, init_j)) + ' --> ' + str((cur_i, cur_j))) elif new_j == 0 and new_i == 1: command += '2,' print('Moved DOWN from ' + str((init_i, init_j)) + ' --> ' + str((cur_i, cur_j))) elif new_i == 0 and new_j == 1: command += '1,' print('Moved RIGHT from ' + str((init_i, init_j)) + ' --> ' + str((cur_i, cur_j))) else: command += '3,' print('Moved LEFT from ' + str((init_i, init_j)) + ' --> ' + str((cur_i, cur_j))) init_i, init_j = cur_i, cur_j return command def parseRow(rec): row = [int(x) for x in rec.split(' ') if len(x) > 1] return row def main(): max_iter = 5000 heuristic = "manhattan" algorithm = "a_star" n = 3 goal_state = np.array([[0, 1, 2],[3, 4, 5],[6, 7, 8]]) s = socket(AF_INET, SOCK_STREAM) s.connect(('133.242.50.201',24912)) while True: rec = s.recv(1024).decode('utf-8') print(rec) rec = rec.split('\n') row1 = parseRow(rec[1]) row2 = parseRow(rec[2]) row3 = parseRow(rec[3]) init_state = np.array([row1, row2, row3]) command = A_star(init_state, goal_state, max_iter, heuristic) s.send(command[:-1].encode('utf-8') + b'\n') if __name__ == '__main__': main()
---------------- | 04 | 06 | 02 | | 01 | 05 | 00 | | 07 | 03 | 08 | ---------------- Moved LEFT from (1, 2) --> (1, 1) Moved UP from (1, 1) --> (0, 1) Moved LEFT from (0, 1) --> (0, 0) Moved DOWN from (0, 0) --> (1, 0) Moved RIGHT from (1, 0) --> (1, 1) Moved DOWN from (1, 1) --> (2, 1) Moved LEFT from (2, 1) --> (2, 0) Moved UP from (2, 0) --> (1, 0) Moved RIGHT from (1, 0) --> (1, 1) Moved UP from (1, 1) --> (0, 1) Moved LEFT from (0, 1) --> (0, 0) [snip] ---------------- | 03 | 00 | 02 | | 04 | 01 | 05 | | 06 | 07 | 08 | ---------------- Moved DOWN from (0, 1) --> (1, 1) Moved LEFT from (1, 1) --> (1, 0) Moved UP from (1, 0) --> (0, 0) [+] Congratulations! ctf4b{fe6f512c15daf77a2f93b6a5771af2f723422c72}
100問解いたらflag
が落ちてきました.
ctf4b{fe6f512c15daf77a2f93b6a5771af2f723422c72}
Reversing
Seccompare [warmup, 407solves, 57pts]
> file seccompare seccompare: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=4a607c82ea263205071c80295afe633412cda6f7, not stripped
アプローチ:ltrace
問題名にcompare
が入っているのでstrcmp
で愚直に比較してる読みltrace
をします.
> ltrace ./seccompare hoge strcmp("ctf4b{5tr1ngs_1s_n0t_en0ugh}", "hoge") = -5 puts("wrong"wrong ) = 6 +++ exited (status 0) +++
ctf4b{5tr1ngs_1s_n0t_en0ugh}
Web
Ramen [warmup, 280solves, 77pts]
アプローチ:UNION Query Injection
SQLi
できそうな見た目をしているのでいつものやつ(' or 1 = 1 --
)を投げてみます.
攻撃自体は成功していますがこのテーブルにはflag
はないようです.
そのため,UNION
句を使ってdb
のテーブル名とカラム名一覧を取得します.
' UNION SELECT table_name, GROUP_CONCAT(column_name) FROM INFORMATION_SCHEMA.COLUMNS GROUP BY table_name --
flag
テーブルにflag
カラムがあることがわかったので表示してあげます.
' UNION SELECT null,flag FROM flag --
ctf4b{a_simple_sql_injection_with_union_select}
katsudon [214solves, 102pts]
Rails 5.2.1で作られたサイトです。
https://katsudon.quals.beginners.seccon.jp
クーポンコードを復号するコードは以下の通りですが、まだ実装されてないようです。
フラグは以下にあります。 https://katsudon.quals.beginners.seccon.jp/flag
アプローチ:Base64
各店舗のシリアルコード(Base64 + 謎ハッシュ値)が表示されているのでBase64
の方だけデコードしてみます.
> echo 'BAhJIhByZWl3YWhhbnRlbgY6BkVU' | base64 -D I"reiwahanten:ET% > echo 'BAhJIhNoZWlzZWlzaG9rdWRvdQY6BkVU' | base64 -D I"heiseishokudou:ET% > echo 'BAhJIhRyZXN0YXVyYW50c2hvd2EGOgZFVA==' | base64 -D I"restaurantshowa:ET%
意味のある文字列だったのでflagページの方のBase64
もデコードしてみます.
> echo 'BAhJIiVjdGY0YntLMzNQX1kwVVJfNTNDUjM3X0szWV9CNDUzfQY6BkVU' | base64 -D I"%ctf4b{K33P_Y0UR_53CR37_K3Y_B453}:ET%
ctf4b{K33P_Y0UR_53CR37_K3Y_B453}
一応flag
は取れたのですがRails 5.2.1
と全く関係ない方法で取れてしまったので正直よくわかりませんでした.
Crypto
So Tired [warmup, 192solves, 115pts]
最強の暗号を作りました。 暗号よくわからないけどきっと大丈夫!
> file encrypted.txt encrypted.txt: ASCII text, with very long lines, with no line terminators
eJwUm7V25UAQBT9IgZj[snip]4+xY8VKB7Iqh9I+5+K5v4B8HzgzA==
アプローチ:Base64 + zlib
encrypted.txt
はBase64
だったのでとりあえずデコードしてみると
import base64 import zlib with open('./encrypted.txt') as f: text = f.read().encode() output = base64.b64decode(text) with open('output_file', 'wb') as f: f.write(output)
> file output_file output_file: zlib compressed data
zlib compressed data
であることが分かります.
次にzlib
をdecompress
してあげるとまたBase64
テキストになることが分かりました.
めんどくさくなったので自動化します.
import base64 import zlib with open('output_file', 'rb') as f: data = f.read() count = 1 while True: print(count) if data[:2] == b'x\x9c': dc = zlib.decompress(data) data = base64.b64decode(dc) count += 1 else: break
500回目でflagになります.
ctf4b{very_l0ng_l0ng_BASE64_3nc0ding}
Party [96solves, 223pts]
Let's 暗号パーティ
encrypt.py
from flag import FLAG from Crypto.Util.number import bytes_to_long, getRandomInteger, getPrime def f(x, coeff): y = 0 for i in range(len(coeff)): y += coeff[i] * pow(x, i) return y N = 512 M = 3 secret = bytes_to_long(FLAG) assert(secret < 2**N) coeff = [secret] + [getRandomInteger(N) for i in range(M-1)] party = [getRandomInteger(N) for i in range(M)] val = map(lambda x: f(x, coeff), party) output = list(zip(party, val)) print(output)
encrypted
[(5100090496682565208825623434336918311864447624450952089752237720911276820495717484390023008022927770468262348522176083674815520433075299744011857887705787, 222638290427721156440609599834544835128160823091076225790070665084076715023297095195684276322931921148857141465170916344422315100980924624012693522150607074944043048564215929798729234427365374901697953272928546220688006218875942373216634654077464666167179276898397564097622636986101121187280281132230947805911792158826522348799847505076755936308255744454313483999276893076685632006604872057110505842966189961880510223366337320981324768295629831215770023881406933), (3084167692493508694370768656017593556897608397019882419874114526720613431299295063010916541874875224502547262257703456540809557381959085686435851695644473, 81417930808196073362113286771400172654343924897160732604367319504584434535742174505598230276807701733034198071146409460616109362911964089058325415946974601249986915787912876210507003930105868259455525880086344632637548921395439909280293255987594999511137797363950241518786018566983048842381134109258365351677883243296407495683472736151029476826049882308535335861496696382332499282956993259186298172080816198388461095039401628146034873832017491510944472269823075), (6308915880693983347537927034524726131444757600419531883747894372607630008404089949147423643207810234587371577335307857430456574490695233644960831655305379, 340685435384242111115333109687836854530859658515630412783515558593040637299676541210584027783029893125205091269452871160681117842281189602329407745329377925190556698633612278160369887385384944667644544397208574141409261779557109115742154052888418348808295172970976981851274238712282570481976858098814974211286989340942877781878912310809143844879640698027153722820609760752132963102408740130995110184113587954553302086618746425020532522148193032252721003579780125)]
アプローチ:ラグランジュ補間
どうみてもSecret Sharing
なのでラグランジュ係数の演算誤差に注意しながらラグランジュ補間をします.
#!/usr/bin/env python3 # -*- coding: utf-8 -*- from Crypto.Util.number import long_to_bytes output = [(5100090496682565208825623434336918311864447624450952089752237720911276820495717484390023008022927770468262348522176083674815520433075299744011857887705787, 222638290427721156440609599834544835128160823091076225790070665084076715023297095195684276322931921148857141465170916344422315100980924624012693522150607074944043048564215929798729234427365374901697953272928546220688006218875942373216634654077464666167179276898397564097622636986101121187280281132230947805911792158826522348799847505076755936308255744454313483999276893076685632006604872057110505842966189961880510223366337320981324768295629831215770023881406933), (3084167692493508694370768656017593556897608397019882419874114526720613431299295063010916541874875224502547262257703456540809557381959085686435851695644473, 81417930808196073362113286771400172654343924897160732604367319504584434535742174505598230276807701733034198071146409460616109362911964089058325415946974601249986915787912876210507003930105868259455525880086344632637548921395439909280293255987594999511137797363950241518786018566983048842381134109258365351677883243296407495683472736151029476826049882308535335861496696382332499282956993259186298172080816198388461095039401628146034873832017491510944472269823075), (6308915880693983347537927034524726131444757600419531883747894372607630008404089949147423643207810234587371577335307857430456574490695233644960831655305379, 340685435384242111115333109687836854530859658515630412783515558593040637299676541210584027783029893125205091269452871160681117842281189602329407745329377925190556698633612278160369887385384944667644544397208574141409261779557109115742154052888418348808295172970976981851274238712282570481976858098814974211286989340942877781878912310809143844879640698027153722820609760752132963102408740130995110184113587954553302086618746425020532522148193032252721003579780125)] lag0 = (output[0][1] * output[1][0] * output[2][0]) // ((output[0][0]-output[1][0]) * (output[0][0]-output[2][0])) lag1 = (output[1][1] * output[0][0] * output[2][0]) // ((output[1][0]-output[0][0]) * (output[1][0]-output[2][0])) lag2 = (output[2][1] * output[1][0] * output[0][0]) // ((output[2][0]-output[1][0]) * (output[2][0]-output[0][0])) secret = lag0 + lag1 + lag2 print(long_to_bytes(secret))
ctf4b{just_d0ing_sh4mir}
Secret Sharing
は通常であれば (素数)上で行うはずなのですが今回の問題はちょっと違いましたね(getPrime
をimport
してるので草案ではそうだったのかもしれない)
Go RSA [37solves, 363pts]
Nだけなくしちゃったんだよなあ……。
Server: nc 133.242.17.175 1337
> nc 133.242.17.175 1337 Encrypted flag is: 4725892326131640511938836467356380640397395755148515442238144307716268382252628227752706337031081867656763601262225583418303010193239356145844773869421583928956830475001659989802417519525376977915742938040734455026586792580629138505932764823343548099242875945669696790179649220582806677338952996620085062222609753771468773503312464080667581802039545168540557775175910139783773568895672923857876387792456057914870803099574255916087021356112254136797301781099631817901879990750639939793954026851169923804748143896554243498372979547574635377196600744448195794904851780679885454328745360685620374662405322841325901266342 > 11 7637811207979739219669490743378336650776535942943752504812029481377761911058098069129370129891186065700013565658425623145096181174940284569170173845864097024499447436745302222316078164333521436075178658111338798129491626771375186572329383908383189341163934643713551924178771397720534193926131973485274138576551879941437401429052365352919721199518788548926065434907685968560503200611173822384479590055822627949819328899143710503731634878295102670058980960754156195359495395859997444231704641045830011633563005247272318388623636227814230719956741962610601123833306215246334889580406317875472898523048605130307312142405 > 27 9083236756510502667672149597920937345609065952381103744458773609677349181137664898132336299978787391428145194106398285831203807691539715388732580593315467304747140152910340572357534656173881218457206571537060957819436232216333145441782251558467372248774534265689018805410968856154304032189923153055338920877482978347723674178822530503588597019097743749899880561404545399165006564054140566630136178882852665776958989723666213308348480085079148320569989618818179565755270024216590124150820307469348296537784213056635374932270363584956103955814113622691900216592869655412483832137357451499594296512566943069908225210734 > 100 7026445835542647141907783190328483047682576235769713858426579438967936914699835557760650020607977551407452309826139518542937401053779667154528749498335633652039979184783915183555701404386624160304188959930900255234658247046723155024892615238236151937654211279613994878056685883680731033253494552504451907528560837434514304089104648323475985198621962943979282704998385067331133496621660306924791131855783998482073799172052170009672789638003863302229835592540595367819302032212643072356316510889051286933794282881878035554552646333541242222793357920058043907289691535546821516658857953954016748437167985559279211706133 The D was 11458711755035670464038023339470449045516641995506883118289541007057383750687501319340094124932721557270059748633889547682404537144455516634669377475861767368934592310210016407301606085157477328662317671491707721936501722619798842708958774830172877965800990263700253906048588526355356847729377612163313112308312564511078609962986973393588081618659656723726978199229317195649278770467451124046419253881031382495199996086694246635234831202278493431951587700512798998873022490223975989484868695392247040865016421693345721234250068247064936395381184432538492079017858523226444063058574726651206459901461956681780685609953
アプローチ:GCD
C
とD
が既知なのであとはN
が分かればm
を求めることができます.
しかし,問題文にもある通りN
が公開されていなのでm
とC
の対応関係を使ってN
を求める必要があります.
RSAでは は以下のように計算します.
そのため, は次のように表すことができます.
したがって, により が取得可能になります.
あとは 同士のGCD
を計算すれば が求められます.
の値によっては上手くいかない場合がありますが上手くいくまでをガチャれば大丈夫です(は?
以下ソルバです.
#!/usr/bin/env python3 # -*- coding: utf-8 -*- from Crypto.Util.number import * e = 0x10001 c = 4725892326131640511938836467356380640397395755148515442238144307716268382252628227752706337031081867656763601262225583418303010193239356145844773869421583928956830475001659989802417519525376977915742938040734455026586792580629138505932764823343548099242875945669696790179649220582806677338952996620085062222609753771468773503312464080667581802039545168540557775175910139783773568895672923857876387792456057914870803099574255916087021356112254136797301781099631817901879990750639939793954026851169923804748143896554243498372979547574635377196600744448195794904851780679885454328745360685620374662405322841325901266342 d = 11458711755035670464038023339470449045516641995506883118289541007057383750687501319340094124932721557270059748633889547682404537144455516634669377475861767368934592310210016407301606085157477328662317671491707721936501722619798842708958774830172877965800990263700253906048588526355356847729377612163313112308312564511078609962986973393588081618659656723726978199229317195649278770467451124046419253881031382495199996086694246635234831202278493431951587700512798998873022490223975989484868695392247040865016421693345721234250068247064936395381184432538492079017858523226444063058574726651206459901461956681780685609953 m1 = 11 c1 = 7637811207979739219669490743378336650776535942943752504812029481377761911058098069129370129891186065700013565658425623145096181174940284569170173845864097024499447436745302222316078164333521436075178658111338798129491626771375186572329383908383189341163934643713551924178771397720534193926131973485274138576551879941437401429052365352919721199518788548926065434907685968560503200611173822384479590055822627949819328899143710503731634878295102670058980960754156195359495395859997444231704641045830011633563005247272318388623636227814230719956741962610601123833306215246334889580406317875472898523048605130307312142405 m2 = 27 c2 = 9083236756510502667672149597920937345609065952381103744458773609677349181137664898132336299978787391428145194106398285831203807691539715388732580593315467304747140152910340572357534656173881218457206571537060957819436232216333145441782251558467372248774534265689018805410968856154304032189923153055338920877482978347723674178822530503588597019097743749899880561404545399165006564054140566630136178882852665776958989723666213308348480085079148320569989618818179565755270024216590124150820307469348296537784213056635374932270363584956103955814113622691900216592869655412483832137357451499594296512566943069908225210734 m3 =100 c3 = 7026445835542647141907783190328483047682576235769713858426579438967936914699835557760650020607977551407452309826139518542937401053779667154528749498335633652039979184783915183555701404386624160304188959930900255234658247046723155024892615238236151937654211279613994878056685883680731033253494552504451907528560837434514304089104648323475985198621962943979282704998385067331133496621660306924791131855783998482073799172052170009672789638003863302229835592540595367819302032212643072356316510889051286933794282881878035554552646333541242222793357920058043907289691535546821516658857953954016748437167985559279211706133 k1_n = pow(m1,e) - c1 k2_n = pow(m2,e) - c2 k3_n = pow(m3,e) - c3 n = GCD(k1_n, GCD(k2_n, k3_n)) print(long_to_bytes(pow(c, d, n)))
ctf4b{f1nd_7he_p4ramet3rs}
も公開されていませんがエスパーで0x10001
を使いました.
Bit Flip [28solves, 393pts]
平文を1ビットランダムで反転させる能力を手に入れた!
Server: nc 133.242.17.175 31337
bitflip.py
from Crypto.Util.number import bytes_to_long import random N = 82212154608576254900096226483113810717974464677637469172151624370076874445177909757467220517368961706061745548693538272183076941444005809369433342423449908965735182462388415108238954782902658438063972198394192220357503336925109727386083951661191494159560430569334665763264352163167121773914831172831824145331 e = 3 FLAG = bytes_to_long(open('flag', 'rb').read()) r = 1 << random.randrange(0, FLAG.bit_length() // 4) C = pow(FLAG ^ r, e, N) print(C)
> nc 133.242.17.175 31337 34495772845776138053860574863082807629902875416466919427415045743727265142914336137794413529141543617083815519863768748750221150257700972178855168455330281760754772707935321008538904121600699189837041293041987846498541772956761968152857045790864281449660662376827310046502947581341524518919435263611890512450
> nc 133.242.17.175 31337 22847704804485272897240201332364615448076265560473081494130345961236707114724029595074930665254174643883050394417095822457682230300066684688366644862329503747236653245028193028050352131020229282412626915605242797100427600649337929655211677780729739319416990174820944195984397134699611486337684117580918892985
> nc 133.242.17.175 31337 43035312703842017954042292254907266700617316162695453879829757160086337037615079359132390226750471117197542576308742513788463886773570509421068480648480633408018559488856706451532969717573877686330681208899968785429205284758238175636453962046579776676915523262861472762948841759403707556065773671701666530758
アプローチ:Related Message Attack
ぱっと見e
が3なのでLow Public Exponent Attack
が使えるのでは?と思ったのですがnc
して返ってくる暗号文を見ても分かる通りメッセージがパディングされており,そう簡単にはいきませんでした.
気を取り直して平文における1bit反転について考えてみます.
暗号化の際にflag
とXOR
をとっているので一見複雑そうに見えますが,数式に落とすと次のように簡単化されます.
または
そのため,2つの暗号文 , は次のように表されます.
ここからゴリゴリ計算すると は以下のようになります.
詳しくはpaper
以下ソルバです.
#!/usr/bin/env python3 # -*- coding: utf-8 -*- from Crypto.Util.number import * def getMessage(b, c1, c2, n, e): b3 = pow(b, e, n) part1 = b * (c1 + 2 * c2 - b3) % n part2 = (c1 - c2 + 2 * b3) % n part2 = inverse(part2, n) return (part1 * part2) % n def main(): N = 82212154608576254900096226483113810717974464677637469172151624370076874445177909757467220517368961706061745548693538272183076941444005809369433342423449908965735182462388415108238954782902658438063972198394192220357503336925109727386083951661191494159560430569334665763264352163167121773914831172831824145331 e = 3 c1 = 48435977819599167293699473693205310917010250721248336492061287216379692891690691913767776069993911112239078185069288585022377993407270013401637539048567366526297730605589951913845745872046158333042631889854531385928428660788578942630939788805433004268454651608813773788184844655098499352542867632893087089862 c2 = 32832657981817352905083775530972899460590019159991145524259682040379084401983824033739329107366301756520684118701358144664961003685650874802305450356329470616987673919308922191127619492477891253202306385458337433034756100386997791550557924840412386588071364098074412967888671978045411920247972161545432892891 b1_list = [] b2_lsit = [] for i in range(200): x = 1 << i b1_list.append(x) b1_list.append(-x) b2_lsit.append(x) b2_lsit.append(-x) for b1 in b1_list: for b2 in b2_lsit: if b1 == b2: continue else: b = b1 - b2 message = getMessage(b, c1, c2, N, e) - b2 flag = long_to_bytes(message) if b'ctf4b' in flag: print(flag) return if __name__ == '__main__': main()
> python solve.py b'ctf4b{b1tfl1pp1ng_1s_r3lated_m3ss4ge} DUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMY\n'
ctf4b{b1tfl1pp1ng_1s_r3lated_m3ss4ge}
のベースがずれているのでb
は予め想定値を生成しておくことで差分を全探索しています.
まとめ
- CTFを始めた1年前(warmupしか解けなかった)と比較すると少しは成長できたのではないかと思う
- Crypto全完できたのでやっとCrypto Beginnerになれた(は?
- Web, Rev, Pwn 何も分からないので勉強します(低レイヤも高レイヤも何も分からないのはまずい)
- Pwnに至ってはwarmupすら解けなかったので本当に悲しくなった