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すら解けなかったので本当に悲しくなった
HarekazeCTF 2019 Write-up
はじめに
2019/05/18 ~ 2019/05/19に開催されたHarekazeCTFにチームで参加しました.
今回は自分が解いた問題についてのWrirte-upです.
成績
510pts獲得して68位(523チーム中)でした.
ONCE UPON A TIME [Crypto, 71solves, 100pts]
Now!! Let the games begin!!
- problem.py
- result.txt
prblem.py
#!/usr/bin/python3 import random import binascii import re from keys import flag flag = re.findall(r'HarekazeCTF{(.+)}', flag)[0] flag = flag.encode() #print(flag) def pad25(s): if len(s) % 25 == 0: return b'' return b'\x25'*(25 - len(s) % 25) def kinoko(text): text = text + pad25(text) mat = [] for i in range(0, len(text), 25): mat.append([ [text[i], text[i+1], text[i+2], text[i+3], text[i+4]], [text[i+5], text[i+6], text[i+7], text[i+8], text[i+9]], [text[i+10], text[i+11], text[i+12], text[i+13], text[i+14]], [text[i+15], text[i+16], text[i+17], text[i+18], text[i+19]], [text[i+20], text[i+21], text[i+22], text[i+23], text[i+24]], ]) return mat def takenoko(X, Y): W = [[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0]] for i in range(5): for j in range(5): for k in range(5): W[i][j] = (W[i][j] + X[i][k] * Y[k][j]) % 251 return W def encrypt(m1, m2): c = b"" for mat in m1: g = random.randint(0,1) if g == 0: mk = takenoko(m2, mat) else: mk = takenoko(mat, m2) for k in mk: c += bytes(k) return c if __name__ == '__main__': m1 = kinoko(flag) m2 = [[1,3,2,9,4], [0,2,7,8,4], [3,4,1,9,4], [6,5,3,-1,4], [1,4,5,3,5]] print("Encrypted Flag:") enc_flag = binascii.hexlify(encrypt(m1, m2)).decode() print(enc_flag)
result.txt
Encrypted Flag: ea5929e97ef77806bb43ec303f304673de19f7e68eddc347f3373ee4c0b662bc37764f74cbb8bb9219e7b5dbc59ca4a42018
アプローチ:逆行列
problem.py
とresult.txt
を確認すると以下のことが分かります.
- padding済のflag長は50文字っぽい
- kinokoではflagを行列に変換している
- takenokoではを計算している
- encryptでは乱数に応じて行列演算の順番を決定している
そのため,
$$ {m_{2}}^{-1} \cdot W_{i} $$
または
$$ W_{i} \cdot m_{2}^{-1} $$
を計算すればflagを復号できることが分かります.
ここで, は
$$ m_{2} = \begin{bmatrix} 1 & 3 & 2 & 9 & 4 \\ 0 & 2 & 7 & 8 & 4 \\ 3 & 4 & 1 & 9 & 4 \\ 6 & 5 & 3 & -1 & 4 \\ 1 & 4 & 5 & 3 & 5 \end{bmatrix} $$
なので
$$ m_{2}^{-1} = \frac{1}{243} \begin{bmatrix} 247 & 11 & -194 & 121 & -148 \\ -935 & 41 & 757 & -278 & 332 \\ -198 & 63 & 126 & -36 & 36 \\ -59 & 20 & 67 & -23 & -4 \\ 932 & -110 & -733 & 248 & -221 \end{bmatrix} $$
となります.
通常の行列演算であればあとはやるだけになるのですがこの問題の行列演算は上で行われているためもうひと工夫必要になります.
ここで は 上での逆元となるので として扱えます .したがって,演算結果にを乗じて すれば復号が可能になります.
以下ソルバです.
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import binascii import numpy as np def kinoko(text): mat = [] for i in range(0, len(text), 25): mat.append([ [text[i], text[i+1], text[i+2], text[i+3], text[i+4]], [text[i+5], text[i+6], text[i+7], text[i+8], text[i+9]], [text[i+10], text[i+11], text[i+12], text[i+13], text[i+14]], [text[i+15], text[i+16], text[i+17], text[i+18], text[i+19]], [text[i+20], text[i+21], text[i+22], text[i+23], text[i+24]], ]) return mat def decrypt(mat): flag = '' for row in mat: for x in row: flag += chr((int((round(x) * 94) % 251))) return flag def main(): encode_flag = 'ea5929e97ef77806bb43ec303f304673de19f7e68eddc347f3373ee4c0b662bc37764f74cbb8bb9219e7b5dbc59ca4a42018' W = kinoko(binascii.unhexlify(encode_flag)) w1 = W[0] w2 = W[1] m2 = np.array([[1,3,2,9,4], [0,2,7,8,4], [3,4,1,9,4], [6,5,3,-1,4], [1,4,5,3,5]]) inv_m2 = np.linalg.inv(m2) * np.linalg.det(m2) print(decrypt(np.dot(w1, inv_m2))) print(decrypt(np.dot(w2, inv_m2))) if __name__ == '__main__': main()
Op3n_y0ur_3y3s_1ook_up_t0 _th3_ski3s_4nd_s33%%%%%%%
HarekazeCTF{Op3n_y0ur_3y3s_1ook_up_t0_th3_ski3s_4nd_s33}
まとめ
- さんすうができない
- バイトで時間がとれなかった(これは言い訳)
- Cryptoできるようになりたい
INS'hAck CTF Write-up
- はじめに
- Telegram [Misc]
- gflag [Misc]
- Dashlame - Part 1 [Rev]
- intergover [Pwn]
- signed or not signed [Pwn]
- Yet Another RSA Challenge - Part 1 [Crypto]
- まとめ
はじめに
2019/05/03 ~ 2019/05/06に開催されたINS'hAck CTFに参加しました.
初日しか参加してないので最終的な順位は分かりません.
Telegram [Misc]
Join our Telegram channel !
アプローチ:リンク先にとぶ
INSA{is_e2e_8annEd_1n_Russi4_too}
gflag [Misc]
My brother likes esoteric programming. He sent me this file but I don't see what it is for. Could you help me ?
> file gflag gflag: ASCII text
アプローチ:gcode
> wc -l gflag 1754 gflag
> head gflag M73 P0 R2 M201 X9000 Y9000 Z500 E10000 M203 X500 Y500 Z12 E120 M204 P2000 R1500 T2000 M205 X10.00 Y10.00 Z0.20 E2.50 M205 S0 T0 M107 M115 U3.1.0 M83 M204 S2000 T1500
既視感があるなと思ったらNCプログラムでした(5,6年前にCNC旋盤使ったことがある)
(gcode
だから gflag
ってことか〜)
適当なNCViewerに流すとflagが表示されます.
INSA{3d_pr1nt3d_fl49}
Dashlame - Part 1 [Rev]
Can you try our new password manager ? There's a free flag in every password archive created !
This challenge contains a second part in the Crypto category.
> file dashlame.pyc dashlame.pyc: python 2.7 byte-compiled
アプローチ:uncompyle
バイトコンパイルされたPythonファイルが渡されるのでとりあえずuncompyle
します.
> uncompyle6 dashlame.pyc # uncompyle6 version 3.2.6 # Python bytecode 2.7 (62211) # Decompiled from: Python 2.7.15 |Anaconda, Inc.| (default, May 1 2018, 18:37:05) # [GCC 4.2.1 Compatible Clang 4.0.1 (tags/RELEASE_401/final)] # Embedded file name: dashlame.py # Compiled at: 2019-04-17 06:56:53
from Crypto.Cipher import AES import os, random, sys, sqlite3, time, zlib HEADER = " /.m.\\\n /.mnnm.\\ ___\n |.mmnvvnm.\\. .,,,/`mmm.\\\n |.mmnnvvnm.\\:;,. ..,,;;;/.mmnnnmm.\\\n \\ mmnnnvvnm.\\::;;, .,;;;;;;;;/.mmmnnvvnnm.|\n \\`mmnnnvvnm.\\::;::.sSSs sSSs ,;;;;;;;;;;/.mmmnnvvvnnmm'/\n \\`mmnnnvnm.\\:::::SSSS,,,,,,SSSS:::::::;;;/.mmmnnvvvnnmmm'/\n \\`mnvvnm.\\::%%%;;;;;;;;;;;%%%%:::::;/.mnnvvvvnnmmmmm'/\n \\`mmmm.%%;;;;;%%%%%%%%%%%%%%%::/.mnnvvvnnmmmmm'/ '\n \\`%%;;;;%%%%s&&&&&&&&&s%%%%mmmnnnmmmmmm'/ '\n | `%;;;%%%%s&&.%%%%%%.%&&%mmmmmmmmmm'/ '\n\\ | / %;;%%%%&&.%;` '%.&&%%%////// '\n \\ | / %%%%%%s&.%% x %.&&%%%%%//%\n \\ .:::::. ,;%%%%s&&&&.%; ;.&&%%%%%%%%/,\n-!!!- ::#:::::%%%%%%s&&&&&&&&&&&&&&&&&%%%%%%%%%%%\n / :##:::::&&&&&&&&&&&&&&&&&&&&&%%%%%%%%%%%%%%,\n / | `:#:::&&&&&&&&&&&&&&&&&&&&&&&&%%%%%%%%%%%%%\n | `&&&&&&&&&,&&&&&&&&&&&&SS%%%%%%%%%%%%%\n `~~~~~'~~ SSSSSSS%%%%%%%%%%%%%\n SSSSSSSS%%%%%%%%%%%%%%\n SSSSSSSSSS%%%%%%%%%%%%%.\n SSSSSSSSSSSS%%%%%%%%%%%%%%\n SSSSSSSSSSSSS%%%%%%%%%%%%%%%.\n SSSSSSSSSSSSSSS%%%%%%%%%%%%%%%%\n SSSSSSSSSSSSSSSS%%%%%%%%%%%%%%%%%.\n SSSSSSSSSSSSSSSSS%%%%%%%%%%%%%%%%%%%\n SSSSSSSSSSSSSSSSSS%%%%%%%%%%%%%%%%%%%%.\n\n WELCOME TO DASHLAME\n" PEARSON_TABLE = [ 199, 229, 151, 178, 53, 6, 131, 42, 248, 110, 39, 28, 51, 216, 32, 14, 77, 34, 166, 213, 157, 150, 115, 197, 228, 221, 254, 172, 84, 27, 36, 156, 69, 96, 12, 220, 225, 137, 246, 141, 44, 208, 191, 109, 163, 21, 173, 250, 98, 227, 203, 162, 188, 3, 105, 171, 215, 15, 207, 218, 234, 56, 136, 235, 97, 79, 189, 102, 134, 11, 224, 117, 177, 222, 100, 129, 78, 18, 130, 187, 9, 184, 99, 108, 202, 13, 238, 17, 94, 70, 180, 144, 185, 168, 123, 71, 176, 91, 4, 153, 103, 242, 80, 127, 198, 82, 169, 148, 48, 120, 59, 55, 230, 209, 50, 73, 31, 49, 142, 149, 167, 249, 116, 1, 7, 86, 143, 101, 29, 52, 114, 154, 160, 128, 19, 170, 46, 214, 38, 67, 186, 252, 181, 145, 212, 183, 22, 231, 107, 43, 47, 122, 251, 217, 5, 62, 88, 244, 200, 93, 240, 219, 124, 58, 161, 89, 211, 158, 247, 60, 236, 65, 106, 113, 66, 81, 165, 194, 223, 40, 233, 126, 139, 72, 132, 61, 135, 57, 87, 182, 164, 35, 159, 118, 8, 83, 210, 243, 104, 76, 75, 119, 90, 138, 20, 206, 95, 16, 74, 33, 245, 237, 111, 64, 253, 125, 23, 232, 193, 37, 175, 92, 30, 241, 255, 133, 0, 140, 2, 155, 85, 10, 146, 179, 25, 26, 226, 201, 195, 121, 190, 63, 68, 152, 45, 147, 41, 204, 192, 205, 196, 54, 174, 239, 112, 24] def pad(s): mark = chr(16 - len(s) % 16) while len(s) % 16 != 15: s += chr(random.randint(0, 255)) return s + mark def unpad(s): return s[:-ord(s[-1])] def get_random_passphrase(): sys.stdout.write('Getting random data from atmospheric noise and mouse movements') sys.stdout.flush() for i in range(10): sys.stdout.write('.') sys.stdout.flush() time.sleep(random.randint(1, 20) / 10.0) print '' with open('wordlist.txt', 'rb') as (fi): passwords = fi.read().strip().split('\n') return (random.choice(passwords), random.choice(passwords)) def get_pearson_hash(passphrase): key, iv = ('', '') for i in range(32): h = (i + ord(passphrase[0])) % 256 for c in passphrase[1:]: h = PEARSON_TABLE[h ^ ord(c)] if i < 16: key += chr(h) else: iv += chr(h) return ( key, iv) def encrypt_stream(data, passphrase): key, iv = get_pearson_hash(passphrase) aes = AES.new(key, AES.MODE_CBC, iv) data = pad(data) return aes.encrypt(data) def decrypt_stream(data, passphrase): key, iv = get_pearson_hash(passphrase) aes = AES.new(key, AES.MODE_CBC, iv) data = unpad(aes.decrypt(data)) return data def encrypt_archive(archive_filename, passphraseA, passphraseB): with open(archive_filename, 'rb') as (db_fd): with open(archive_filename.replace('.db', '.dla'), 'wb') as (dla_fd): enc1 = encrypt_stream(db_fd.read(), passphraseA) enc2 = encrypt_stream(enc1, passphraseB) dla_fd.write(enc2) os.unlink(archive_filename) def decrypt_archive(archive_filename, passphraseA, passphraseB): with open(archive_filename, 'rb') as (dla_fd): with open(archive_filename.replace('.dla', '.db'), 'wb') as (db_fd): dec1 = decrypt_stream(dla_fd.read(), passphraseB) dec2 = decrypt_stream(dec1, passphraseA) db_fd.write(dec2) os.unlink(archive_filename) def createArchive(): archive_name = raw_input('Please enter your archive name: ') passphraseA, passphraseB = get_random_passphrase() print 'This is your passphrase :', passphraseA, passphraseB print 'Please remember it or you will lose all your passwords.' archive_filename = archive_name + '.db' with open(archive_filename, 'wb') as (db_fd): db_fd.write(zlib.decompress('x\x9c\x0b\x0e\xf4\xc9,IUH\xcb/\xcaM,Q0f`a`ddpPP````\x82b\x18`\x04b\x164>!\xc0\xc4\xa0\xfb\x8c\x9b\x17\xa4\x98y.\x03\x10\x8d\x82Q0\n\x88\x05\x89\x8c\xec\xe2\xf2\xf2\x8c\x8d\x82%\x89I9\xa9\x01\x89\xc5\xc5\xe5\xf9E)\xc5p\x06\x93s\x90\xabc\x88\xabB\x88\xa3\x93\x8f\xab\x02\\X\xa3<5\xa9\x18\x94\xabC\\#Bt\x14J\x8bS\x8b\xf2\x12sa\xdc\x02\xa820W\x13\x927\xcf0\x00\xd1(\x18\x05\xa3`\x08\x03#F\x16mYkh\xe6\x8fO\xadH\xcc-\xc8I\x85\xe5~O\xbf`\xc7\xea\x90\xcc\xe2\xf8\xa4\xd0\x92\xf8\xc4\xf8`\xe7"\x93\x92\xe4\x8cZ\x00\xa8&=\x8f')) encrypt_archive(archive_filename, passphraseA, passphraseB) print 'Archive created successfully.' def updateArchive(): archive_name = raw_input('Please enter your archive name: ') passphrase = raw_input('Please enter your passphrase: ') passphraseA, passphraseB = passphrase.split() website = raw_input('Website: ') username = raw_input('Username: ') password = raw_input('Password: ') dla_filename = archive_name + '.dla' db_filename = archive_name + '.db' decrypt_archive(dla_filename, passphraseA, passphraseB) conn = sqlite3.connect(db_filename) cur = conn.cursor() cur.execute('INSERT INTO Passwords VALUES(?,?,?)', (website, username, password)) conn.commit() conn.close() encrypt_archive(db_filename, passphraseA, passphraseB) print 'Update done.' def accessArchive(): archive_name = raw_input('Please enter your archive name: ') passphrase = raw_input('Please enter your passphrase: ') passphraseA, passphraseB = passphrase.split() website = raw_input('Website: ') dla_filename = archive_name + '.dla' db_filename = archive_name + '.db' decrypt_archive(dla_filename, passphraseA, passphraseB) conn = sqlite3.connect(db_filename) cur = conn.cursor() cur.execute('SELECT Username, Password FROM Passwords WHERE Website=?', (website,)) results = cur.fetchall() conn.close() encrypt_archive(db_filename, passphraseA, passphraseB) if len(results) == 0: print 'No results.' else: for result in results: print result[0], ':', result[1] if __name__ == '__main__': print HEADER print '1. Create a new password archive' print '2. Add a password to an archive' print '3. Access a password from an existing archive' try: res = raw_input() if res == '1': createArchive() else: if res == '2': updateArchive() else: if res == '3': accessArchive() else: print 'Wrong choice' except: print 'Error.'
ぱっと見dbファイル作成してあれこれしてっるぽいですね.
パスワードアーカイブ作成
> python dashlame.py /.m.\ /.mnnm.\ ___ |.mmnvvnm.\. .,,,/`mmm.\ |.mmnnvvnm.\:;,. ..,,;;;/.mmnnnmm.\ \ mmnnnvvnm.\::;;, .,;;;;;;;;/.mmmnnvvnnm.| \`mmnnnvvnm.\::;::.sSSs sSSs ,;;;;;;;;;;/.mmmnnvvvnnmm'/ \`mmnnnvnm.\:::::SSSS,,,,,,SSSS:::::::;;;/.mmmnnvvvnnmmm'/ \`mnvvnm.\::%%%;;;;;;;;;;;%%%%:::::;/.mnnvvvvnnmmmmm'/ \`mmmm.%%;;;;;%%%%%%%%%%%%%%%::/.mnnvvvnnmmmmm'/ ' \`%%;;;;%%%%s&&&&&&&&&s%%%%mmmnnnmmmmmm'/ ' | `%;;;%%%%s&&.%%%%%%.%&&%mmmmmmmmmm'/ ' \ | / %;;%%%%&&.%;` '%.&&%%%////// ' \ | / %%%%%%s&.%% x %.&&%%%%%//% \ .:::::. ,;%%%%s&&&&.%; ;.&&%%%%%%%%/, -!!!- ::#:::::%%%%%%s&&&&&&&&&&&&&&&&&%%%%%%%%%%% / :##:::::&&&&&&&&&&&&&&&&&&&&&%%%%%%%%%%%%%%, / | `:#:::&&&&&&&&&&&&&&&&&&&&&&&&%%%%%%%%%%%%% | `&&&&&&&&&,&&&&&&&&&&&&SS%%%%%%%%%%%%% `~~~~~'~~ SSSSSSS%%%%%%%%%%%%% SSSSSSSS%%%%%%%%%%%%%% SSSSSSSSSS%%%%%%%%%%%%%. SSSSSSSSSSSS%%%%%%%%%%%%%% SSSSSSSSSSSSS%%%%%%%%%%%%%%%. SSSSSSSSSSSSSSS%%%%%%%%%%%%%%%% SSSSSSSSSSSSSSSS%%%%%%%%%%%%%%%%%. SSSSSSSSSSSSSSSSS%%%%%%%%%%%%%%%%%%% SSSSSSSSSSSSSSSSSS%%%%%%%%%%%%%%%%%%%%. WELCOME TO DASHLAME 1. Create a new password archive 2. Add a password to an archive 3. Access a password from an existing archive 1 Please enter your archive name: cawayui Getting random data from atmospheric noise and mouse movements.......... This is your passphrase : sabally discombobulation Please remember it or you will lose all your passwords. Archive created successfully.
パスワード追加
[snip] 1. Create a new password archive 2. Add a password to an archive 3. Access a password from an existing archive 2 Please enter your archive name: cawayui Please enter your passphrase: sabally discombobulation Website: satto.hatenadiary.com Username: satto1237 Password: pass Update done.
パスワード確認
[snip] 1. Create a new password archive 2. Add a password to an archive 3. Access a password from an existing archive 3 Please enter your archive name: cawayui Please enter your passphrase: sabally discombobulation Website: satto.hatenadiary.com satto1237 : pass
生成ファイル確認
> file cawayui.dla cawayui.dla: data
> head cawayui.dla IZ���]6���)��9-ei�l�00���ײZ��A\�L� eKFp�1�G&z]�S�0��9�� >D�����OFK��` ��O��0{�� �{��̵�_FZ �ןy�|g�� {>�z�A������}yX����>�%oH�"�f7�y�i�^�0"M��AF�=�ʋT�dA�&W|?�w�������R��:{Y,�H� �xM�j� ����S\�0����/(�D��'���� 9��?]#���Ṳ���1 Q��OI��ퟳ �e���8R�A���y�*��q�-A������i�#zѕp!e��/]gv��U�t�5��7�#�RXD ���B�#�J�m�&�߯��30M�x���o�B+��(�"S�Ow�K�� Q"~� �^1��%"1N�[�MR�+q̏Fm"��.�GbY�� NN}S������b��$|����)`�R�B��<��#���{��j�d���?>�H��)q-�x���Eԩ !��i��s�A>i������n�l?�U�#�0�g Tէ�v{O��y�|χ+�ƒ&[�_�;�.!җ��o'��[�%���:$ 2UfjW�j�4{�qjƥB���.�{aj���]?.���;g$W�d���ūm���;wM�BY����]C��ޖ Ns�)[�I�y-r(��f�U�o I4ٱ*ݢ(&à-N�E�^0���,���|ť������~Ae8X�:y���"沬��B{�k�Ka%�I��� �����^ٱux� ��?���[�љ���[�]�� ^(���UÒ�,:�����o��⪻�B�R�7�õXM]r5�P��O�Q����]��z���L2��O�����-B��� �N��W;a� �P���f��L��FkYU�Xt�T���,������j��ς8G!�?�mvW��Ȩ�w���.���|����`y5�&� S�{�1o����+ũ]/nڎ� �
ソースコードを読む限り新しいアーカイブを作成するときに謎データを書き込んでいるのでAdd
したユーザとパスワード以外にもユーザデータが存在すると考えられます.
また,accessArchive
内でデータを読み出した後(decrypt_archive
した後)にencrypt_archive
しているためこの処理をコメントアウトすることでdecryptされたdbファイルにアクセスできるようになることが分かります.
> sqlite3 sqlite> .open cawayui.db sqlite> .tables Passwords sqlite> .schema CREATE TABLE Passwords(website TEXT, username TEXT, password TEXT); sqlite> SELECT * FROM Passwords; website_example|username|INSA{Tis_bUt_a_SCr4tch} satto.hatenadiary.com|satto1237|pass
INSA{Tis_bUt_a_SCr4tch}
intergover [Pwn]
I hope you know how integers are stored.
ssh -i <your_keyfile> -p 2223 user@intergover.ctf.insecurity-insa.fr To find your keyfile, look into your profile on this website.
> ssh -i ~/.ssh/id_inshack -p 2223 user@intergover.ctf.insecurity-insa.fr ___ _ _ _ ____ ___ _ ___ |_ _|_ __ ___| | | | __ _ ___| | __ |___ \ / _ \/ |/ _ \ | || '_ \/ __| |_| |/ _` |/ __| |/ / __) | | | | | (_) | | || | | \__ \ _ | (_| | (__| < / __/| |_| | |\__, | |___|_| |_|___/_| |_|\__,_|\___|_|\_\ |_____|\___/|_| /_/ =========================================================== You are accessing a sandbox challenge over SSH This sandbox will be killed soon enough. Please wait while we launch your sandbox... =========================================================== Give me one param: 1127 No, I can't give you the flag: 1127
アプローチ:読む
与えられたバイナリファイルをida
で開くと242をパラメータとして与えればいいことが分かります.
[snip] Give me one param: 242 INSA{B3_v3rY_c4r3fUL_w1tH_uR_1nt3g3r_bR0}
INSA{B3_v3rY_c4r3fUL_w1tH_uR_1nt3g3r_bR0}
signed or not signed [Pwn]
Signed or not signed, this is the question :) ssh -i <your_keyfile> -p 2228 user@signed-or-not-signed.ctf.insecurity-insa.fr To find your keyfile, look into your profile on this website.
アプローチ:unsigned
> ssh -i ~/.ssh/id_inshack -p 2228 user@signed-or-not-signed.ctf.insecurity-insa.fr - ___ _ _ _ ____ ___ _ ___ |_ _|_ __ ___| | | | __ _ ___| | __ |___ \ / _ \/ |/ _ \ | || '_ \/ __| |_| |/ _` |/ __| |/ / __) | | | | | (_) | | || | | \__ \ _ | (_| | (__| < / __/| |_| | |\__, | |___|_| |_|___/_| |_|\__,_|\___|_|\_\ |_____|\___/|_| /_/ =========================================================== You are accessing a sandbox challenge over SSH This sandbox will be killed soon enough. Please wait while we launch your sandbox... =========================================================== Please give me a number: 1127 Bro, it's really too big.
とりあえず与えられたバイナリファイルをida
で開きます.
10以下かつ64870である数値を入力しなければいけませんがunsigned
であることを利用すればいけます.
[snip] Please give me a number:-666 INSA{Th3_qU3sTi0n_1s_S1gN3d_0r_x90} Connection to signed-or-not-signed.ctf.insecurity-insa.fr closed.
INSA{Th3_qU3sTi0n_1s_S1gN3d_0r_x90}
Yet Another RSA Challenge - Part 1 [Crypto]
Buy an encrypted flag, get a (almost intact) prime factor for free !
You can find a harder version of this challenge in the Programming category.
import subprocess p = subprocess.check_output('openssl prime -generate -bits 2048 -hex') q = subprocess.check_output('openssl prime -generate -bits 2048 -hex') flag = int('INSA{REDACTED}'.encode('hex'), 16) N = int(p,16) * int(q,16) print N print '0x'+p.replace('9F','FC') print pow(flag,65537,N)
719579745653303119025873098043848913976880838286635817351790189702008424828505522253331968992725441130409959387942238566082746772468987336980704680915524591881919460709921709513741059003955050088052599067720107149755856317364317707629467090624585752920523062378696431510814381603360130752588995217840721808871896469275562085215852034302374902524921137398710508865248881286824902780186249148613287250056380811479959269915786545911048030947364841177976623684660771594747297272818410589981294227084173316280447729440036251406684111603371364957690353449585185893322538541593242187738587675489180722498945337715511212885934126635221601469699184812336984707723198731876940991485904637481371763302337637617744175461566445514603405016576604569057507997291470369704260553992902776099599438704680775883984720946337235834374667842758010444010254965664863296455406931885650448386682827401907759661117637294838753325610213809162253020362015045242003388829769019579522792182295457962911430276020610658073659629786668639126004851910536565721128484604554703970965744790413684836096724064390486888113608024265771815004188203124405817878645103282802994701531113849607969243815078720289912255827700390198089699808626116357304202660642601149742427766381 0xDCC5A0BD3A1FC0BEB0DA1C2E8CF6B474481B7C12849B76E03C4C946724DB577D2825D6AA193DB559BC9DBABE1DDE8B5E7805E48749EF002F622F7CDBD7853B200E2A027E87E331AFCFD066ED9900F1E5F5E5196A451A6F9E329EB889D773F08E5FBF45AACB818FD186DD74626180294DCC31805A88D1B71DE5BFEF3ED01F12678D906A833A78EDCE9BDAF22BBE45C0BFB7A82AFE42C1C3B8581C83BF43DFE31BFD81527E507686956458905CC9A660604552A060109DC81D01F229A264AB67C6D7168721AB36DE769CEAFB97F238050193EC942078DDF5329A387F46253A4411A9C8BB71F9AEB11AC9623E41C14FCD2739D76E69283E57DDB11FC531B4611EE3 596380963583874022971492302071822444225514552231574984926542429117396590795270181084030717066220888052607057994262255729890598322976783889090993129161030148064314476199052180347747135088933481343974996843632511300255010825580875930722684714290535684951679115573751200980708359500292172387447570080875531002842462002727646367063816531958020271149645805755077133231395881833164790825731218786554806777097126212126561056170733032553159740167058242065879953688453169613384659653035659118823444582576657499974059388261153064772228570460351169216103620379299362366574826080703907036316546232196313193923841110510170689800892941998845140534954264505413254429240789223724066502818922164419890197058252325607667959185100118251170368909192832882776642565026481260424714348087206462283972676596101498123547647078981435969530082351104111747783346230914935599764345176602456069568419879060577771404946743580809330315332836749661503035076868102720709045692483171306425207758972682717326821412843569770615848397477633761506670219845039890098105484693890695897858251238713238301401843678654564558196040100908796513657968507381392735855990706254646471937809011610992016368630851454275478216664521360246605400986428230407975530880206404171034278692756
アプローチ:全探索
配布されたプログラムを確認すると素数p
の9F
をFC
に置換していることが分かります.
置換後のp
のFC
の出現数は4なので元々FC
だった場合も考慮し計算すると置換前のパターン数は16になることが分かります.
あとは全探索して正規のp
を見つければ終わりです.
以下が頭ガバガバソルバです.
#!/usr/bin/env python3 # -*- coding: utf-8 -*- from Crypto.Util.number import * str_p = 'DCC5A0BD3A1FC0BEB0DA1C2E8CF6B474481B7C12849B76E03C4C946724DB577D2825D6AA193DB559BC9DBABE1DDE8B5E7805E48749EF002F622F7CDBD7853B200E2A027E87E331AFCFD066ED9900F1E5F5E5196A451A6F9E329EB889D773F08E5FBF45AACB818FD186DD74626180294DCC31805A88D1B71DE5BFEF3ED01F12678D906A833A78EDCE9BDAF22BBE45C0BFB7A82AFE42C1C3B8581C83BF43DFE31BFD81527E507686956458905CC9A660604552A060109DC81D01F229A264AB67C6D7168721AB36DE769CEAFB97F238050193EC942078DDF5329A387F46253A4411A9C8BB71F9AEB11AC9623E41C14FCD2739D76E69283E57DDB11FC531B4611EE3' n = 719579745653303119025873098043848913976880838286635817351790189702008424828505522253331968992725441130409959387942238566082746772468987336980704680915524591881919460709921709513741059003955050088052599067720107149755856317364317707629467090624585752920523062378696431510814381603360130752588995217840721808871896469275562085215852034302374902524921137398710508865248881286824902780186249148613287250056380811479959269915786545911048030947364841177976623684660771594747297272818410589981294227084173316280447729440036251406684111603371364957690353449585185893322538541593242187738587675489180722498945337715511212885934126635221601469699184812336984707723198731876940991485904637481371763302337637617744175461566445514603405016576604569057507997291470369704260553992902776099599438704680775883984720946337235834374667842758010444010254965664863296455406931885650448386682827401907759661117637294838753325610213809162253020362015045242003388829769019579522792182295457962911430276020610658073659629786668639126004851910536565721128484604554703970965744790413684836096724064390486888113608024265771815004188203124405817878645103282802994701531113849607969243815078720289912255827700390198089699808626116357304202660642601149742427766381 e = 65537 c = 596380963583874022971492302071822444225514552231574984926542429117396590795270181084030717066220888052607057994262255729890598322976783889090993129161030148064314476199052180347747135088933481343974996843632511300255010825580875930722684714290535684951679115573751200980708359500292172387447570080875531002842462002727646367063816531958020271149645805755077133231395881833164790825731218786554806777097126212126561056170733032553159740167058242065879953688453169613384659653035659118823444582576657499974059388261153064772228570460351169216103620379299362366574826080703907036316546232196313193923841110510170689800892941998845140534954264505413254429240789223724066502818922164419890197058252325607667959185100118251170368909192832882776642565026481260424714348087206462283972676596101498123547647078981435969530082351104111747783346230914935599764345176602456069568419879060577771404946743580809330315332836749661503035076868102720709045692483171306425207758972682717326821412843569770615848397477633761506670219845039890098105484693890695897858251238713238301401843678654564558196040100908796513657968507381392735855990706254646471937809011610992016368630851454275478216664521360246605400986428230407975530880206404171034278692756 index = [11, 143, 475, 499] def searchP(s, d): if d == 4: p = int(s,16) if n % p == 0: print(p) else: searchP(s[:index[d]] + 'FC' + s[index[d]+2:], d + 1) searchP(s[:index[d]] + '9F' + s[index[d]+2:], d + 1) def solve(): p = 27869881035956015184979178092922248885674897320108269064145135676677416930908750101386898785101159450077433625380803555071301130739332256486285289470097290409044426739584302074834857801721989648648799253740641480496433764509396039330395579654527851232078667173592401475356727873045602595552393666889257027478385213547302885118341490346766830846876201911076530008127691612594913799272782226366932754058372641521481522494577124999360890113778202218378165756595787931498460866236502220175258385407478826827807650036729385244897815805427164434537088709092238894902485613707990645011133078730017425033369999448757627854563 q = n // p phi = (p - 1) * (q - 1) d = inverse(e, phi) m = pow(c, d, n) print(long_to_bytes(m)) if __name__ == '__main__': searchP(str_p, 0) solve()
27869881035956015184979178092922248885674897320108269064145135676677416930908750101386898785101159450077433625380803555071301130739332256486285289470097290409044426739584302074834857801721989648648799253740641480496433764509396039330395579654527851232078667173592401475356727873045602595552393666889257027478385213547302885118341490346766830846876201911076530008127691612594913799272782226366932754058372641521481522494577124999360890113778202218378165756595787931498460866236502220175258385407478826827807650036729385244897815805427164434537088709092238894902485613707990645011133078730017425033369999448757627854563 b'INSA{I_w1ll_us3_OTp_n3xT_T1M3}'
INSA{I_w1ll_us3_OTp_n3xT_T1M3}
まとめ
- また簡単な問題しかとけなかった
- べんきょうする
ツ令和CTF Write-up (供養)
はじめに
令和CTFに参加しました.
成績
Misc全完して187位(かなしい)
フラグの例は?
平成最後の最後、令和最初のSECCON CTFにようこそ。 フラグはSECCON{reiwa}です。
アプローチ:問題文を読む
SECCON{reiwa}
bREInWAc
元号が変わる。記号も変わる。
令和和和和和和和和和和和和和和和和「令和 和和和和令和和和和令和和和和和和和令和和 和和和和令和和平平平平平成」令和和和。令 和和和和和。成成。。平成成成成。成。令令 和和和和和和和和和和和。令和和。平平平和 和和和。令和和。和和和和。令令和和和和和 和和和和和和和。平平平和和和和和和和和和 和和和和。成成成成成成成成。令成成成成成 成成成。令令。成成成成成。成成成成成成。 令和。平平和和。令令令和和和和和和和和和 和。
アプローチ:適当に置換して実行
#!/usr/bin/env python3 # -*- coding: utf-8 -*- if __name__ == '__main__': dic = {'令':'>', '和':'+', '「':'[', '」':']', '。':'.', '平':'<', '成':'-'} with open('./flag.bw') as f: lines = [s.strip() for s in f.readlines()] flag = '' for line in lines: for x in line: flag += dic[x] print(flag)
>++++++++++++++++[>+++++>++++>+++++++>++++++>++<<<<<-]>+++.>+++++.--..<----.-.>>+++++++++++.>++.<<<++++.>++.++++.>>++++++++++++.<<<+++++++++++++.--------.>--------.>>.-----.------.>+.<<++.>>>++++++++++.
SECCON{bREIn_WAnic!}
零は?
nc zerois-o-reiwa.seccon.jp 23615
example
[1/100] 0=40-? ?=40 [2/100] 0=45*18-? ?=
アプローチ:SymPyに頑張ってもらう
100問解くとflagがもらえるっぽいので100問解くソルバを書く.
#!/usr/bin/env python3 # -*- coding: utf-8 -*- from socket import * from sympy import * from sympy.parsing.sympy_parser import parse_expr if __name__ == '__main__': s = socket(AF_INET, SOCK_STREAM) s.connect(('zerois-o-reiwa.seccon.jp', 23615)) for i in range(101): rec = s.recv(1024).decode('utf-8') print(rec) rec = s.recv(1024).decode('utf-8') prob = rec.split('\n')[0].split('=')[1].replace('?','x') print(prob) ans = solve(Eq(parse_expr(prob),0)) if ans == True: print('True') s.send(str(0).encode('utf-8') + b'\n') else: print(ans[0]) s.send(str(ans[0]).encode('utf-8') + b'\n')
[snip] [98/100] 96-39+52*89-84+68*73-87+30*79+79*99-61*34+47-62-84*18+77-99*61+5+2*72-38*55+48-88*99-21+30*18+60-7+52-29*70*50-15+88-87+8*34+55*70-3+18*64-55+37*22-10*35+82-43+18*6-40+65*89-6-0+43*76+85*41-37-42*65+69*96+19-38*70-4+66-92*86+47*2-64+36*41+71-23-57+90*8*30-37+25+32*23-47+63-15*94*32+x 110987 [99/100] 60-87+44*7+51-34*80+70*43-18-25*4+97*63-35+16+93*51-8*36-57+47-76+72*62*17-60+79-96+58*92+97*58-96-4*51+17-25+9*35-26*61+39+67*15-36-51+90*71+40-77*30*98+47-3*21-23+12*88-74+20-70+35*34-82+49*97-31*34+19+11*74-68+56-9*17-36*94+61*20+58-16-81*24+45-99+46*94*66-0+8+10-28*46+x*0-62-84+31-163413 True [100/100] 12+37-58*62+51*1-26+1-2*35-50+63*36*23-18+16-36+88*65+77*48-74+50-30*53-68*95+12-29*95+32+72-17*36*62-22+3-96+14*28+27*83-97*87-55+11+75*35-67-66*15+6*3+17-26-13+14*89*45+3-67+17-88*48-45*71+68-40+50*2*91+14-42-18+51*48*43-64+61*58+51-22*82-61+89+28-93*51+68*54-91*6-17+62+10*35-88-0*x+2-168262 True Congratulations! The flag is SECCON{REIWA_is_not_ZERO_IS}. (Enter RETURN key if connection is not disconnected)
SECCON{REIWA_is_not_ZERO_IS}
まとめ
- 平成終わったのにQRコード解けなくてツ令和
ångstromCTF 2019 Write-up
はじめに
2019/04/20 ~ 2019/04/25 に開催されたångstromCTFにチーム(NekoChanNano!)で参加しました.
今回は自分が解いた問題についてのWrite-upです.
成績
59位でした (1問以上解いた1374チーム中).
Misc
IRC [10pts, 895solves]
We have an IRC channel, #angstromctf on freenode! Join us to ask questions, have fun, and get a flag.
アプローチ:読む
actf{like_discord_but_worse}
Survey [10pts, 363solves]
We have a short survey for you to fill out for a flag! Even though it's a single challenge, we encourage every individual to submit a response.
アプローチ:wget
アンケートに回答するとflagをもらえるみたいですが回答しなくてもとれます.
> wget https://forms.gle/72by8ViMv3yM9JeU6 --2019-04-26 20:22:28-- https://forms.gle/72by8ViMv3yM9JeU6 Resolving forms.gle (forms.gle)... 151.101.65.195, 151.101.1.195 Connecting to forms.gle (forms.gle)|151.101.65.195|:443... connected. HTTP request sent, awaiting response... 302 Found Location: https://docs.google.com/forms/d/e/1FAIpQLSfGgWe6Gl1mcyOnD59NfT2luWa4N5fVKyhlTGszZ1FZyT_stw/viewform?usp=send_form [following] --2019-04-26 20:22:29-- https://docs.google.com/forms/d/e/1FAIpQLSfGgWe6Gl1mcyOnD59NfT2luWa4N5fVKyhlTGszZ1FZyT_stw/viewform?usp=send_form Resolving docs.google.com (docs.google.com)... 172.217.31.174 Connecting to docs.google.com (docs.google.com)|172.217.31.174|:443... connected. HTTP request sent, awaiting response... 200 OK Length: unspecified [text/html] Saving to: `72by8ViMv3yM9JeU6' 72by8ViMv3yM9JeU6 [ <=> ] 441.15K --.-KB/s in 0.1s 2019-04-26 20:22:29 (4.22 MB/s) - `72by8ViMv3yM9JeU6' saved [451738] > grep "actf{" 72by8ViMv3yM9JeU6 ,["Thank you for completing our survey! We hope you enjoyed ångstromCTF, and as always: actf{we_hope_to_see_you_next_year}",0,0,0,0]
actf{we_hope_to_see_you_next_year}
Crypto
Classy Cipher [20pts, 718solves]
Every CTF starts off with a Caesar cipher, but we're more classy.
from secret import flag, shift def encrypt(d, s): e = '' for c in d: e += chr((ord(c)+s) % 0xff) return e assert encrypt(flag, shift) == ':<M?TLH8<A:KFBG@V'
アプローチ:シフトを探索
シフト数が不明ですがflagフォーマットをベースに全探索すれば解けます.
#!/usr/bin/env python3 # -*- coding: utf-8 -*- # :<M?TLH8<A:KFBG@V # actf{***********} def encrypt(d, s): e = '' for c in d: e += chr((ord(c)+s) % 0xff) return e def searchShift(): flag = 'actf{' for shift in range(0xff): enc = encrypt(flag, shift) if enc == ':<M?T': print(shift) return shift if __name__ == '__main__': enc_flag = ':<M?TLH8<A:KFBG@V' shift = searchShift() dec_flag = '' for e in enc_flag: dec_flag += chr(ord(e) - shift + 0xff) print(dec_flag)
> python solve.py 216 actf{so_charming}
actf{so_charming}
Really Secure Algorithm [30pts, 548solves]
I found this flag somewhere when I was taking a walk, but it seems to have been encrypted with this Really Secure Algorithm!
p = 8337989838551614633430029371803892077156162494012474856684174381868510024755832450406936717727195184311114937042673575494843631977970586746618123352329889 q = 7755060911995462151580541927524289685569492828780752345560845093073545403776129013139174889414744570087561926915046519199304042166351530778365529171009493 e = 65537 c = 7022848098469230958320047471938217952907600532361296142412318653611729265921488278588086423574875352145477376594391159805651080223698576708934993951618464460109422377329972737876060167903857613763294932326619266281725900497427458047861973153012506595691389361443123047595975834017549312356282859235890330349
アプローチ:普通にRSA暗号を復号する
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import gmpy import binascii if __name__ == '__main__': p = 8337989838551614633430029371803892077156162494012474856684174381868510024755832450406936717727195184311114937042673575494843631977970586746618123352329889 q = 7755060911995462151580541927524289685569492828780752345560845093073545403776129013139174889414744570087561926915046519199304042166351530778365529171009493 e = 65537 c = 7022848098469230958320047471938217952907600532361296142412318653611729265921488278588086423574875352145477376594391159805651080223698576708934993951618464460109422377329972737876060167903857613763294932326619266281725900497427458047861973153012506595691389361443123047595975834017549312356282859235890330349 n = p * q l = gmpy.lcm(p-1, q-1) gcd, u, v = gmpy.gcdext(e, l) while u < 0: u += l m = pow(c, u, n) print(m) print(binascii.unhexlify(hex(m)[2:]))
> python solve.py 172070576318285777902351017014850513943749891499547486454156569029770767741 b'actf{really_securent_algorithm}'
actf{really_securent_algorithm}
Half and Half [50pts, 348solves]
Mm, coffee. Best served with half and half!
from secret import flag def xor(x, y): o = '' for i in range(len(x)): o += chr(ord(x[i])^ord(y[i])) return o assert len(flag) % 2 == 0 half = len(flag)//2 milk = flag[:half] cream = flag[half:] assert xor(milk, cream) == '\x15\x02\x07\x12\x1e\x100\x01\t\n\x01"'
encoder
を読むと次のことが分かります.
以上を考慮するとflagフォーマットから
milk -> actf{******* cream -> ***********}
milk -> actf{******_ cream -> taste******}
となります.
次に6文字の単語を推測しなければいけませんが,ここは問題文にあるcoffee
が入ります(エスパー).
# -*- coding: utf-8 -*- #!/usr/bin/env python3 def decrypt(known_flag, mc_xor): partial_flag = '' for f, x in zip(known_flag, mc_xor): partial_flag += chr(ord(f) ^ x) return partial_flag if __name__ == '__main__': partial_flag1 = decrypt('}',b'"') partial_flag2 = decrypt('actf{', b'\x15\x02\x07\x12\x1e') partial_flag3 = decrypt('coffee', b'\x100\x01\t\n\x01') print('actf{' + 'coffee' + partial_flag1 + partial_flag2 + partial_flag3 + '}')
> python solve.py actf{coffee_tastes_good}
actf{coffee_tastes_good}
Runes [70pts, 235solves]
The year is 20XX. ångstromCTF only has pwn challenges, and the winner is solely determined by who can establish a socket connection first. In the data remnants of an ancient hard disk, we've recovered a string of letters and digits. The only clue is the etching on the disk's surface: Paillier.
n: 99157116611790833573985267443453374677300242114595736901854871276546481648883 g: 99157116611790833573985267443453374677300242114595736901854871276546481648884 c: 2433283484328067719826123652791700922735828879195114568755579061061723786565164234075183183699826399799223318790711772573290060335232568738641793425546869
アプローチ:Paillier暗号
Paillier暗号については以下を参照してください(詳しくまとめられています).
256bit
程度のn
が与えられますがFacotrDBでサクッと素因数分解できます.
あとはやるだけです.
# -*- coding: utf-8 -*- #!/usr/bin/env python3 import gmpy import binascii def L(u,n): return (u-1) // n def decrypt(): n = 99157116611790833573985267443453374677300242114595736901854871276546481648883 g = 99157116611790833573985267443453374677300242114595736901854871276546481648884 c = 2433283484328067719826123652791700922735828879195114568755579061061723786565164234075183183699826399799223318790711772573290060335232568738641793425546869 p = 310013024566643256138761337388255591613 q = 319848228152346890121384041219876391791 lmd = gmpy.lcm(p-1, q-1) m = L(pow(c, lmd, n*n), n) * gmpy.invert(L(pow(g, lmd, n*n), n), n) % n return m if __name__ == '__main__': m = decrypt() print(binascii.unhexlify(hex(m)[2:]))
> python solve.py b'actf{crypto_lives}'
actf{crypto_lives}
Rev
Intro to Rev [10pts, 961solves]
Many of our problems will require you to run Linux executable files (ELFs). This problem will help you figure out how to do it on our shell server. Use your credentials to log in, then navigate to
/problems/2019/intro_to_rev
. Run the executable and follow its instructions to get a flag!
アプローチ:問題をよむ
> ./intro_to_rev Welcome to your first reversing challenge! If you are seeing this, then you already ran the file! Let's try some input next. Enter the word 'angstrom' to continue: angstrom Good job! Some programs might also want you to enter information with a command line argument. When you run a file, command line arguments are given by running './introToRev argument1 argument2' where you replace each argument with a desired string. To get the flag for this problem, run this file again with the arguments 'binary' and 'reversing' (don't put the quotes).
> ./intro_to_rev binary reversing Welcome to your first reversing challenge! If you are seeing this, then you already ran the file! Let's try some input next. Enter the word 'angstrom' to continue: angstrom Good job! Some programs might also want you to enter information with a command line argument. When you run a file, command line arguments are given by running './introToRev argument1 argument2' where you replace each argument with a desired string. Good job, now go solve some real problems! actf{this_is_only_the_beginning}
actf{this_is_only_the_beginning}
I Like It [40pts, 606solves]
Now I like dollars, I like diamonds, I like ints, I like strings. Make Cardi like it please.
> file i_like_it i_like_it: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=5b91e9b31dffd010d9f32b21580ac3675db92a62, not stripped
アプローチ:2次方程式をとく?
> ./i_like_it I like the string that I'm thinking of: aaaa Cardi don't like that.
とりあえずida
で開くと
次のことが分かります.
I like the string that I'm thinking of:
にはokrrrrrrr
- 以下の条件を満たす2つの整数を入力する
したがって,2つの整数については の解を入力すればいいことになります.
> ./i_like_it I like the string that I'm thinking of: okrrrrrrr I said I like it like that! I like two integers that I'm thinking of (space separated): 39 97 I said I like it like that! Flag: actf{okrrrrrrr_39_97}
actf{okrrrrrrr_39_97}
One Bite [60pts, 521solves]
Whenever I have friends over, I love to brag about things that I can eat in a single bite. Can you give this program a tasty flag that fits the bill?
> file one_bite one_bite: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=1378c7ef8cdf59c2cbe4d84274295b2567a09e91, not stripped
アプローチ:XOR
> ./one_bite Give me a flag to eat: aaaaaaaaaa That didn't taste so good :(
とりあえずida
で開くと
次のことが分かります.
- 入力文字列を1文字ずつ
0x3c
とXOR
してる XOR
した結果が]_HZGUcHTURWcUQc[SUR[cHSc^YcOU_WA
になるのがflag
#!/usr/bin/env python3 # -*- coding: utf-8 -*- def solve(): target = ']_HZGUcHTURWcUQc[SUR[cHSc^YcOU_WA' flag = '' for x in target: flag += chr(ord(x) ^ 0x3c) print(flag) if __name__ == '__main__': solve()
> python solve.py actf{i_think_im_going_to_be_sick}
actf{i_think_im_going_to_be_sick}
High Quality Checks [110pts, 268solves]
After two break-ins to his shell server, kmh got super paranoid about a third! He's so paranoid that he abandoned the traditional password storage method and came up with this monstrosity! I reckon he used the flag as the password, can you find it?
> file high_quality_checks high_quality_checks: 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]=e7556b55e0c73b4de8b3f387571dd59c3535a0ee, not stripped
アプローチ:Ghidraでデコンパイル
とりあえずida
で開くと
次のことが分かります.
- flag長は19文字
check
関数はflagの判定を行っているcheck
関数の動作を理解するにはやる気と根気と時間が必要
ゆとりなのでGhidra
でデコンパイルします.
undefined8 check(char *pcParm1) { int iVar1; iVar1 = d(pcParm1 + 0xc); if ((((((iVar1 != 0) && (iVar1 = v((ulong)(uint)(int)*pcParm1), iVar1 != 0)) && (iVar1 = u((ulong)(uint)(int)pcParm1[0x10],(ulong)(uint)(int)pcParm1[0x11], (ulong)(uint)(int)pcParm1[0x11]), iVar1 != 0)) && ((iVar1 = k((ulong)(uint)(int)pcParm1[5]), iVar1 == 0 && (iVar1 = k((ulong)(uint)(int)pcParm1[9]), iVar1 == 0)))) && ((iVar1 = w(pcParm1 + 1), iVar1 != 0 && ((iVar1 = b(pcParm1,0x12), iVar1 != 0 && (iVar1 = b(pcParm1,4), iVar1 != 0)))))) && ((iVar1 = z(pcParm1,0x6c), iVar1 != 0 && (iVar1 = s(pcParm1), iVar1 != 0)))) { return 1; } return 0; }
大分読みやすくなりましたね.
check
関数内の各関数の動作を読み解いてflag:*******************
を復元していきます(文字は0-indexで数えます).
d
関数
ulong d(int *piParm1) { return (ulong)(*piParm1 == 0x30313763); }
check
関数でflagの12文字目以降を渡しているのでflagは************c710***
となります.
n
関数
ulong n(int iParm1) { return (ulong)(uint)(iParm1 >> 1); }
引数を1bit右シフトしてます.
v
関数
ulong v(byte bParm1) { int iVar1; iVar1 = n(0xac); return (ulong)((int)(char)(bParm1 ^ 0x37) == iVar1); }
(0xac >> 1) ^ 0x37 = 97
なのでflagはa***********c710***
となります.
o
関数
ulong o(char cParm1) { int local_c; if (cParm1 < 'a') { local_c = (int)cParm1 + -0x30; } else { local_c = (int)cParm1 + -0x57; } local_c = (int)cParm1 * 0x100 + local_c; return (ulong)(uint)(local_c * 0x10001); }
文字を引数として渡すと変換結果(数値)を返してくれます.
u
関数
undefined8 u(char cParm1,char cParm2) { int iVar1; iVar1 = n(0xdc); if (((int)cParm1 == iVar1) && (iVar1 = o((ulong)(uint)(int)cParm2), iVar1 == 0x35053505)) { return 1; } return 0; }
check
関数ではu
関数の引数としてflagの16文字目と17文字目を渡しています.
n(oxdc)
は110なのでflagの16文字目はn
となることが分かり,o
関数の返り値が0x35053505
になる文字は5
なのでflagの17文字目は5
となります(a***********c710n5*
).
k
関数
ulong k(char cParm1) { int iVar1; iVar1 = o((ulong)(uint)(int)cParm1); return (ulong)(iVar1 != 0x660f660f); }
check
関数ではflagの5文字目と9文字目を引数としてk
関数に渡しています.
o
関数が0x660f660
になる文字はf
なのでflagはa****f***f**c710n5*
となります.
w
関数
ulong w(char *pcParm1) { return (ulong)((int)*pcParm1 + (int)pcParm1[2] * 0x10000 + (int)pcParm1[1] * 0x100 == 0x667463); }
check
関数ではflagの1文字目以降を引数としてw
関数に渡しています.
0x667463
はctf
なのでflagはactf*f***f**c710n5*
となります.
e
関数
ulong e(int iParm1) { uint uVar1; uVar1 = (uint)(iParm1 >> 0x1f) >> 0x1e; uVar1 = (iParm1 + uVar1 & 3) - uVar1; return (ulong)(uint)((int)(uVar1 + (uVar1 >> 0x1f)) >> 1); }
引数に対する変換結果を返してくれます.
b
関数
ulong b(long lParm1,uint uParm2) { char cVar1; int iVar2; int iVar3; cVar1 = *(char *)(lParm1 + (long)(int)uParm2); iVar2 = n(0xf6); iVar3 = e((ulong)uParm2); return (ulong)((int)cVar1 == iVar3 * 2 + iVar2); }
check
関数ではflagと数値(0x12,4)を引数としてb
関数に渡しています.
引数が0x12
の場合は18文字目が}
になることが分かり,引数が4
の場合は4文字目が{
になることが分かります.
そのためflag
はactf{f***f**c710n5}
となります.
z
関数
undefined8 z(long lParm1,char cParm2) { char cVar1; int iVar2; char local_17; char local_16; uint local_14; local_17 = 0; local_16 = 0; local_14 = 0; while ((int)local_14 < 8) { cVar1 = (char)(((int)cParm2 & 1 << ((byte)local_14 & 0x1f)) >> ((byte)local_14 & 0x1f)); if ((local_14 & 1) == 0) { local_16 = local_16 + (char)((int)cVar1 << ((byte)((int)(local_14 + (local_14 >> 0x1f)) >> 1) & 0x1f)); } else { local_17 = local_17 + (char)((int)cVar1 << ((byte)((int)(local_14 + (local_14 >> 0x1f)) >> 1) & 0x1f)); } local_14 = local_14 + 1; } if ((((*(char *)(lParm1 + (long)local_17) == 'u') && (cVar1 = *(char *)(lParm1 + (long)local_17 + 1), iVar2 = n(0xdc), (int)cVar1 == iVar2)) && (cVar1 = *(char *)(lParm1 + (long)local_16), iVar2 = n(0xea), (int)cVar1 == iVar2)) && (*(char *)(lParm1 + (long)local_16 + 1) == 'n')) { return 1; } return 0; }
一見ややこしい処理をしているように感じますが引数が0x6c
なのでflagの6,7,10,11文字目がそれぞれu
,n
,u
,n
になっているか確認しているだけです.
そのためflag
はactf{fun*func710n5}
となります.
s
関数
ulong s(long lParm1) { int iVar1; int local_10; int local_c; local_10 = 0; local_c = 0; while (local_c < 0x13) { iVar1 = o((ulong)(uint)(int)*(char *)(lParm1 + (long)local_c)); if (iVar1 == 0x5f2f5f2f) { local_10 = local_10 + local_c + 1; } local_c = local_c + 1; } return (ulong)(local_10 == 9); }
flagの8文字目を引数としたo
関数の返り値が0x5f2f5f2f
になっていればいいことが分かります.
o
関数の返り値が0x5f2f5f2f
になる文字は_
なので最終的なflag
はactf{fun_func710n5}
となります.
actf{fun_func710n5}
まとめ
- Crypto担当なのにCrypto全く解けなくて申し訳ない😢
- 就活のせいでまともに参加できなくて申し訳ない😢
- 簡単な問題しか解けないのでWrite-up読んで勉強する
- 今更感あるけどGhidraすごくないですか?
チームメンバのWrite-up
#angstromCTF の「Chain of Rope」の解き方です(ROP無し) pic.twitter.com/ZmXVpcq1OE
— デンパ (@ousanko) 2019年4月25日
PIE Shopの解答です #angstromCTF pic.twitter.com/3hxI5jnUsX
— デンパ (@ousanko) 2019年4月25日
WPICTF 2019 Write-up
- はじめに
- 成績
- can you read [Intro, 1pts, 496solves]
- Discord [Intro, 5pts, 370solves]
- Source pt1 [Pwn, 100pts, 132solves]
- strings [Reversing, 50pts, 398solves]
- WebInspect [Web, 25pts, 465solves]
- suckmore-shell [Linux, 100pts, 210solves]
- zoomercrypt [Cryptography, 50pts, 91solves]
- jocipher [Cryptography, 100pts, 190solves]
- bogged [Cryptography, 150pts, 23solves]
- まとめ
はじめに
2019/04/13 ~ 2019/04/15 に開催されたWPICTFに1人で参加しました.
成績
9問解いて88位(1問以上正解した586チーム中)でした.
can you read [Intro, 1pts, 496solves]
WPI{y3s_y0u_cAN_r33d}
All flags, unless otherwise stated, will be in the form WPI{S0M3_flag_here}
WPI{y3s_y0u_cAN_r33d}
Discord [Intro, 5pts, 370solves]
Look at the pinned msgs.
WPI{Welcome_to_our_discord}
Source pt1 [Pwn, 100pts, 132solves]
ssh source@source.wpictf.xyz -p 31337 (or 31338 or 31339).
Password is sourcelockerHere is your babybuff.
アプローチ:BOF
ssh
するとhttps://www.imdb.com/title/tt0945513/にアクセスするためのパスワードの入力を求められます.
> ssh source@source.wpictf.xyz -p 31337 source@source.wpictf.xyz's password: Enter the password to get access to https://www.imdb.com/title/tt0945513/ A Pasword auth failed exiting Connection to source.wpictf.xyz closed.
babybuff
とか書かれているので何も考えずにBOFしそうな文字列を流します.
> ssh source@source.wpictf.xyz -p 31337 source@source.wpictf.xyz's password: Enter the password to get access to https://www.imdb.com/title/tt0945513/ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
予想通りソースコードが表示されます.
#define _GNU_SOURCE #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> //compiled with gcc source.c -o source -fno-stack-protector -no-pie //gcc (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0 //flag for source1 is WPI{Typos_are_GrEaT!} int getpw(void){ int res = 0; char pw[100]; fgets(pw, 0x100, stdin); *strchrnul(pw, '\n') = 0; if(!strcmp(pw, getenv("SOURCE1_PW"))) res = 1; return res; } char *lesscmd[] = {"less", "source.c", 0}; int main(void){ setenv("LESSSECURE", "1", 1); printf("Enter the password to get access to https://www.imdb.com/title/tt0945513/\n"); if(!getpw()){ printf("Pasword auth failed\nexiting\n"); return 1; } execvp(lesscmd[0], lesscmd); return 0; }
fgets(pw, 0x100, stdin);
に不自然なtypoがありますね.
WPI{Typos_are_GrEaT!}
strings [Reversing, 50pts, 398solves]
A handy tool for your RE efforts!
アプローチ:strings
> file strings strings: ELF 64-bit LSB shared object x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=4e539e560b4d7729f7926e1b594dc623ce4c8e0d, not stripped
> strings strings | grep WPI WPI{ warbleglarblesomejunkWPI{What_do_you_mean_I_SEE_AHH_SKI}0x13376969
WPI{What_do_you_mean_I_SEE_AHH_SKI}
WebInspect [Web, 25pts, 465solves]
Something is lurking at https://www.wpictf.xyz
WPI{Inspect0r_Gadget}
suckmore-shell [Linux, 100pts, 210solves]
Here at Suckmore Software we are committed to delivering a truly unparalleled user experience. Help us out by testing our latest project.
- ssh ctf@107.21.60.114
- pass: i'm a real hacker nowBrought to you by acurless and SuckMore Software, a division of WPI Digital Holdings Ltd.
アプローチ:unalias
ssh
するとsuckmore shell
に繋がります.
> ssh ctf@107.21.60.114 ctf@107.21.60.114's password: SuckMORE shell v1.0.1. Note: for POSIX support update to v1.1.0 suckmore>env HOSTNAME=7eaed2c0fee9 PWD=/ HOME=/home/ctf FBR=f DISTTAG=fcontainer FGC=f TERM=xterm SHLVL=2 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin PS1=suckmore> _=/usr/bin/env
いくつかのコマンドを実行すると挙動がおかしいことに気が付きます(ls
してるのにsleep
のhelp
を見ろとか言われているので).
suckmore>ls suckmore>ls -a sleep: invalid option -- 'a' Try 'sleep --help' for more information. suckmore>pwd Linux suckmore>uname Linux
alias
を確認します.
suckmore>alias alias bash='sh' alias cat='sleep 1 && vim' alias cd='cal' alias cp='grep' alias dnf='' alias find='w' alias less='echo "We are suckMORE, not suckless"' alias ls='sleep 1' alias more='echo "SuckMORE shell, v1.0.1, (c) SuckMore Software, a division of WPI Digital Holdings Ltd."' alias nano='touch' alias pwd='uname' alias rm='mv /u/' alias sh='echo "Why would you ever want to leave suckmore shell?"' alias sl='ls' alias vi='touch' alias vim='touch' alias which='echo "Not Found"'
unalias
して元に戻します.
suckmore>unalias pwd suckmore>unalias cd suckmore>unalias ls
$HOME
に移動してflag
を探します.
suckmore>pwd / suckmore>cd $HOME suckmore>pwd /home/ctf suckmore>ls bash: /usr/bin/ls: Permission denied
ls
が使えないらしいのでgrep
で探します.
suckmore>grep -r "WPI{" ./ ./flag:WPI{bash_sucks0194342}
WPI{bash_sucks0194342}
zoomercrypt [Cryptography, 50pts, 91solves]
My daughter is using a coded language to hide her activities from us!!!! Please, help us find out what she is hiding!
アプローチ:換字式暗号 + エスパー
絵文字が換字式暗号に見えるのでdecode
します.
以下のスクリプトで絵文字をASCII
に置き換えます.
#!/usr/bin/env python3 # -*- coding: utf-8 -*- if __name__ == '__main__': emoji_cipher = '😃😁😕😗😈😗😇😋😄😗😆😓😄😓😂😈😎😃😃😁😓😆😇' emoji_translated = {} ascii_cipher = '' x = 0 for emoji in emoji_cipher: if emoji not in emoji_translated: emoji_translated[emoji] = chr(65 + x) x += 1 for emoji in emoji_cipher: ascii_cipher += emoji_translated[emoji] print(ascii_cipher)
> python solve.py ABCDEDFGHDIJHJKELAABJIF
換字式暗号のソルバを使うためにスペースを挿入してあげます.
ABC DE DF GHD IJHJKE LAABJIF
ここまで来たらquipqiup
で解けます.
OMG IT IS WPI REPENT BOOMERS
あとはフラグの形式に直してあげます.
WPI{REPENT_ZOOMERS}
ZOOMER
はスラングっぽいのでquipqiup
では出力されないっぽい
jocipher [Cryptography, 100pts, 190solves]
Decrypt PIY{zsxh-sqrvufwh-nfgl} to get the flag!
アプローチ:uncompyle
バイトコンパイルされたPython
ファイルが渡されます.
> file jocipher.pyc jocipher.pyc: python 2.7 byte-compiled
Python
のバイトコンパイルは簡単に復元できるのでuncompyle
を使って元に戻します(pyc
にソース保護は期待できない)
> uncompyle6 jocipher.pyc # uncompyle6 version 3.2.6 # Python bytecode 2.7 (62211) # Decompiled from: Python 2.7.15 |Anaconda, Inc.| (default, May 1 2018, 18:37:05) # [GCC 4.2.1 Compatible Clang 4.0.1 (tags/RELEASE_401/final)] # Embedded file name: ./jocipher.py # Compiled at: 2019-03-02 02:41:21
import argparse, re num = '' first = '' second = '' third = '' def setup(): global first global num global second global third num += '1' num += '2' num += '3' num += '4' num += '5' num += '6' num += '7' num += '8' num += '9' num += '0' first += 'q' first += 'w' first += 'e' first += 'r' first += 't' first += 'y' first += 'u' first += 'i' first += 'o' first += 'p' second += 'a' second += 's' second += 'd' second += 'f' second += 'g' second += 'h' second += 'j' second += 'k' second += 'l' third += 'z' third += 'x' third += 'c' third += 'v' third += 'b' third += 'n' third += 'm' def encode(string, shift): result = '' for i in range(len(string)): char = string.lower()[i] if char in num: new_char = num[(num.index(char) + shift) % len(num)] result += new_char elif char in first: new_char = first[(first.index(char) + shift) % len(first)] if string[i].isupper(): result += new_char.upper() else: result += new_char elif char in second: new_char = second[(second.index(char) + shift) % len(second)] if string[i].isupper(): result += new_char.upper() else: result += new_char elif char in third: new_char = third[(third.index(char) + shift) % len(third)] if string[i].isupper(): result += new_char.upper() else: result += new_char else: result += char print result return 0 def decode(string, shift): result = '' shift = -1 * shift for i in range(len(string)): char = string.lower()[i] if char in num: new_char = num[(num.index(char) + shift) % len(num)] result += new_char elif char in first: new_char = first[(first.index(char) + shift) % len(first)] if string[i].isupper(): result += new_char.upper() else: result += new_char elif char in second: new_char = second[(second.index(char) + shift) % len(second)] if string[i].isupper(): result += new_char.upper() else: result += new_char elif char in third: new_char = third[(third.index(char) + shift) % len(third)] if string[i].isupper(): result += new_char.upper() else: result += new_char else: result += char print result return 0 def main(): parser = argparse.ArgumentParser() parser.add_argument('--string', '-s', type=str, required=True, help='the string to encode or decode') parser.add_argument('--shift', '-t', type=int, required=True, help='the shift value to use') parser.add_argument('--encode', '-e', required=False, action='store_true', help='encode the string') parser.add_argument('--decode', '-d', required=False, action='store_true', help='decode the string') args = parser.parse_args() setup() p = re.compile('[a-zA-Z0-9\\-{}]') if p.match(args.string) is not None: if args.encode: ret = encode(args.string, args.shift) else: if args.decode: ret = decode(args.string, args.shift) if ret is not 0: print 'Sorry, this cipher only uses the [a-zA-Z0-9\\-{}]' else: print 'Sorry, this cipher only uses the [a-zA-Z0-9\\-{}]' return if __name__ == '__main__': main()
なんとなくやってることは分かりました.
ただ,encoder
, decoder
を完全に理解するよりもシフトを総当たりした方が速く解けそうなので今回は総当たりします.
> for x in `seq 10`; do echo "$x"; python uncompyle_jocipher.py -s PIY{zsxh-sqrvufwh-nfgl} -t $x -d; done 1 OUT{mazg-apecydqg-bdfk} 2 IYR{nlmf-lowxtspf-vsdj} 3 UTE{bknd-kiqzraod-cash} 4 YRW{vjbs-jupmelis-xlag} 5 TEQ{chva-hyonwkua-zklf} 6 RWP{xgcl-gtibqjyl-mjkd} 7 EQO{zfxk-fruvphtk-nhjs} 8 WPI{mdzj-deycogrj-bgha} 9 QOU{nsmh-swtxifeh-vfgl} 10 PIY{bang-aqrzudwg-cdfk}
シフトを100まで増やしてWPI
でgrep
します.
> for x in `seq 100`; do echo "$x"; python uncompyle_jocipher.py -s PIY{zsxh-sqrvufwh-nfgl} -t $x -d; done | grep WPI WPI{mdzj-deycogrj-bgha} WPI{vsbh-seymofrh-xfgl} WPI{zaxg-aeyvodrg-ndfk} WPI{blnf-leyzosrf-csdj} WPI{xkcd-keyboard-mash} WPI{njms-jeyxolrs-vlag} WPI{chva-heynokra-zklf} WPI{mgzl-geycojrl-bjkd} WPI{vfbk-feymohrk-xhjs} WPI{zdxj-deyvogrj-ngha}
さらにここから目grep
でflag感
のある文字列を探します.
WPI{xkcd-keyboard-mash}
フラグを見てから元ネタを知りました.
bogged [Cryptography, 150pts, 23solves]
Two strange men called me last night. They call themselves the Bogdanoff twins. I don't know much about cryptocurrency can you help them with their scheme?
nc bogged.wpictf.xyz 31337 (or 31338 or 31339)
アプローチ:Length Extension Attack
nc
するとcryptowojak123
の通貨をnot_b0gdan0ff
に送金しろと言われます.
> nc bogged.wpictf.xyz 31337 BOGDANOFF: Bonjour... We have access to the Binance backdoor, and got you into a compromised teller station. We need you to steal tethered cryptocurrency from people's wallets. We were halted by an unfortunate countermeasure in the teller system, but we have an account ready to recieve the stolen crypto. Steal the currency from cryptowojak123. Transfer it to not_b0gdan0ff. Transfer everything... then we will kill him, and find another. Do not fail us. Welcome to the Binance Teller Terminal! Please remember to use admin-issued auth tokens with each account transfer! Either enter a command or one of the following keywords: accounts: List of accounts currently on the system. history: A history of prior terminal commands. help: A reminder on how to use this terminal. Command: >>>
とりあえずhelp
コマンドで概要を把握します.
Command: >>>help You may either withdraw funds from an account or deposit funds to an account. Withdraw with the following command: withdraw ACCOUNT_NAME Deposit with the following command: deposit ACCOUNT_NAME Commands may be chained, as follows: withdraw ACCOUNT_NAME;deposit ACCOUNT_NAME;... An authorization token unique to the command contents must exist for the transaction to succeed! (Sorry, but we have to protect from malicious employees.) Contact admin@dontactuallyemailthis.net to get auth tokens for different transfer commands!
help
を読む限り,withdraw cryptowojak123;deposit not_b0gdan0ff
を実行すればフラグが取れそうです.
Command: >>>withdraw cryptowojak123;deposit not_b0gdan0ff Auth token: >>>a Error: Auth token does not match provided command..
Auth token
が必要みたいです(helpにも書かれているのでそれはそう).
ここで,問題に添付されていたleaked_source.py
を確認してみます.
import hashlib secret = "" def generate_command_token(command, secret): hashed = hashlib.sha1(secret+command).hexdigest() return hashed def validate_input(command, token_in): token = hash_command(command, secret) if token == token_in: return True else: return False while(True): print("Command:") command = raw_input(">>>") print('Auth token:') token = raw_input(">>>") print if validate_input(command, token) == False: print("Error: Auth token does not match provided command..") else: execute_command(command) print
hash(salt || command)
が Auth token
になっていることが分かります.
つまり,withdraw cryptowojak123;deposit not_b0gdan0ff
を実行するには
hash(salt || 'withdraw cryptowojak123;deposit not_b0gdan0ff')
が必要になります.
しかし,salt
はsecret
になっているため,Auth token
は簡単には求められません.
このままではまだちょっと情報が足りないのでhistory
コマンドの結果を確認します.
Command: >>>history ///// TRANSACTION HISTORY ////////////////////////// Command: >>>withdraw john.doe Auth token: >>>b4c967e157fad98060ebbf24135bfdb5a73f14dc Action successful! Command: >>>withdraw john.doe;deposit xXwaltonchaingangXx Auth token: >>>455705a6756fb014a4cba2aa0652779008e36878 Action successful! Command: >>>withdraw cryptowojak123;deposit xXwaltonchaingangXx Auth token: >>>e429ffbfe7cabd62bda3589576d8717aaf3f663f Action successful! Command: >>>withdraw john.doe Auth token: >>>b4c967e157fad98060ebbf24135bfdb5a73f14dc Action successful! ////////////////////////////////////////////////////
コマンドの履歴だけではなく,Auth token
まで見れますね.
ここで怪しい点をまとめると以下のようになります.
- チェインルール
Auth token
の生成方法- 過去の
Auth token
が閲覧可能
したがって,Length Extension Attack
ができることが分かります.
Length Extension Attack
とはhash(salt || message1)
とmessage1
が既知のとき,hash(salt || message1 || message2)
が求められる攻撃です.
具体的には
message1
:withdraw john.doe
hash(salt || message1)
:b4c967e157fad98060ebbf24135bfdb5a73f14dc
message2
:;withdraw cryptowojak123;deposit not_b0gdan0ff
hash(salt || message1 || message2)
: 求めたいAuth token
のようになります.
実際にLength Extension Attack
を行うツールとしてはHashPump
を利用します.
以下ソルバです.
HashPump
を利用する際(Length Extension Attack
を行う際)にはsalt
の文字列長が必要になるのでそこは総当たりしてます.
#!/usr/bin/env python3 # -*- coding: utf-8 -*- from socket import * import subprocess import binascii def encodeHex(s): enc = '' xcode = '' for i in range(len(s)): if s[i] == '\\' and len(xcode) == 0: xcode += s[i] elif s[i] == 'x' and len(xcode) == 1: xcode += s[i] elif len(xcode) == 2: xcode += s[i] elif len(xcode) == 3: xcode += s[i] enc += xcode[2:] xcode = '' else: enc += '%02x' % ord(s[i]) return enc def LEA(cmd): proc = subprocess.run(cmd,stdout = subprocess.PIPE, stderr = subprocess.PIPE) ret = proc.stdout.decode('utf-8').split('\n') token = ret[0].encode('utf-8') init = ret[1][:17].encode('utf-8') lines = ret[1][17:].split(';') encHex = encodeHex(lines[0]) pad = binascii.unhexlify(encHex) append = (';' + lines[1] + ';' + lines[2]).encode('utf-8') data = init + pad + append print(data) return (data,token) def main(): cmd_format = ['hashpump', '-s', 'b4c967e157fad98060ebbf24135bfdb5a73f14dc', '-d', 'withdraw john.doe', '-k', 'n', '-a', ';withdraw cryptowojak123;deposit not_b0gdan0ff'] s = socket(AF_INET, SOCK_STREAM) s.connect(('bogged.wpictf.xyz', 31337)) # recv 'description' for _ in range(2): temp_rec = s.recv(1024).decode('utf-8') for n in range(1, 33): print('SALT Length: {}'.format(n)) cmd_format[6] = str(n) LEA_data, token = LEA(cmd_format) s.send(LEA_data + b'\n') # recv 'Auth token' temp_rec = s.recv(1024).decode('utf-8') print(temp_rec) s.send(token + b'\n') print(token) # recv '>>>' temp_rec = s.recv(1024).decode('utf-8') print(temp_rec) # recv 'result' temp_rec = s.recv(1024).decode('utf-8') print(temp_rec) if 'Error' not in temp_rec: break if __name__ == '__main__': main()
> python solve.py [snip] Command: >>> SALT Length: 16 b'withdraw john.doe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x08;withdraw cryptowojak123;deposit not_b0gdan0ff' Auth token: b'050a162f6ee310d345821b402f436b19677495dc' >>> A subcommand was unreadable... Action successful! Action successful! BOGDANOFF: The money is transferred. You have done... well. Your service has demonstrated your loyalty. You have truly swallowed the bogpill. You will be among the first to behold the enlightenment we will soon unleash. ... Quoi? You want more? ... Somewhere in the cosmos, a secret calls out to us, lost in the wrinkles of time. We shall relay this secret to you. Au revoir. WPI{duMp_33t_aNd_g@rn33sh_H1$_wAg3$}
WPI{duMp_33t_aNd_g@rn33sh_H1$_wAg3$}
まとめ
- 簡単な問題しか解けなくて悲しくなった
- Web, Cryptoがあと1問ずつ解けそうだったけど解けなかった(Write-up読んでべんきょうする)
- PlaidCTFが難しかったのでこっちに参加した
UTCTF2019 Write-up (供養)
- はじめに
- 成績
- [basics] forensics (Forensics, 100 pts, 437 solves)
- [basics] re (Reverse Engineering, 100 pts, 462 solves)
- [basics] crypto (Cryptography, 200 pts, 426 solves)
- Jacobi's Chance Encryption (Cryptography, 750 pts, 76 solves)
- まとめ
はじめに
2019/03/09 ~ 2019/03/11 に開催されたUTCTFに個人で少しだけ参加しました.
時間があまりとれなかったのでwarm-upとCrypto問中心に取り組みました.
成績
4問解いて187位 (1問以上解いた581チーム中) でした.
[basics] forensics (Forensics, 100 pts, 437 solves)
My friend said they hid a flag in this picture, but it's broken!
[sectret.jpg]
アプローチ:fileコマンド
とりあえずopen
しようとします.
> open secret.jpg
open
できなかったのでfile
でフォーマットを確認します.
> file secret.jpg secret.jpg: ASCII text
そもそも.jpg
ではなかったようです.
strings
で中身を確認します.
> strings secret.jpg utflag{d0nt_tru5t_f1l3_3xt3ns10n5}
utflag{d0nt_tru5t_f1l3_3xt3ns10n5}
[basics] re (Reverse Engineering, 100 pts, 462 solves)
I know there's a string in this binary somewhere.... Now where did I leave it?
[calculator]
アプローチ:strings
一応
> file calculator calculator: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=12d2c94aa02e2ebaca1b262031791914ebae7936, not stripped
問題文の通りにやります.
> strings calculator | grep utflag utflag{str1ng5_15_4_h4ndy_t00l}
utflag{str1ng5_15_4_h4ndy_t00l}
[basics] crypto (Cryptography, 200 pts, 426 solves)
Can you make sense of this file?
binary.txt
アプローチ:base64 + rot15 + 換字式暗号
添付されていたbinary.txt
は以下のようになっています.
01010101 01101000 00101101 01101111 01101000 00101100 00100000 01101100 01101111 01101111 01101011 01110011 00100000 01101100 01101001 01101011 01100101 00100000 01110111 01100101 00100000 01101000 01100001 01110110 01100101 00100000 01100001 01101110 01101111 01110100 01101000 01100101 01110010 00100000 01100010 01101100 01101111 01100011 01101011 00100000 01101111 01100110 00100000 01110100 01100101 01111000 01110100 00101100 00100000 01110111 01101001 01110100 01101000 00100000 01110011 01101111 01101101 01100101 00100000 01110011 01101111 01110010 01110100 00100000 01101111 01100110 00100000 01110011 01110000 01100101 01100011 01101001 01100001 01101100 00100000 01100101 01101110 01100011 01101111 01100100 01101001 01101110 01100111 00101110 00100000 01000011 01100001 01101110 00100000 01111001 01101111 01110101 00100000 01100110 01101001 01100111 01110101 01110010 01100101 00100000 01101111 01110101 01110100 00100000 01110111 01101000 01100001 01110100 00100000 01110100 01101000 01101001 01110011 00100000 01100101 01101110 01100011 01101111 01100100 01101001 01101110 01100111 00100000 01101001 01110011 00111111 00100000 00101000 01101000 01101001 01101110 01110100 00111010 00100000 01101001 01100110 00100000 01111001 01101111 01110101 00100000 01101100 01101111 01101111 01101011 00100000 01100011 01100001 01110010 01100101 01100110 01110101 01101100 01101100 01111001 00101100 00100000 01111001 01101111 01110101 00100111 01101100 01101100 00100000 01101110 01101111 01110100 01101001 01100011 01100101 00100000 01110100 01101000 01100001 01110100 00100000 01110100 01101000 01100101 01110010 01100101 00100000 01101111 01101110 01101100 01111001 00100000 01100011 01101000 01100001 01110010 01100001 01100011 01110100 01100101 01110010 01110011 00100000 01110000 01110010 01100101 01110011 01100101 01101110 01110100 00100000 01100001 01110010 01100101 00100000 01000001 00101101 01011010 00101100 00100000 01100001 00101101 01111010 00101100 00100000 00110000 00101101 00111001 00101100 00100000 01100001 01101110 01100100 00100000 01110011 01101111 01101101 01100101 01110100 01101001 01101101 01100101 01110011 00100000 00101111 00100000 01100001 01101110 01100100 00100000 00101011 00101110 00100000 01010011 01100101 01100101 00100000 01101001 01100110 00100000 01111001 01101111 01110101 00100000 01100011 01100001 01101110 00100000 01100110 01101001 01101110 01100100 00100000 01100001 01101110 00100000 01100101 01101110 01100011 01101111 01100100 01101001 01101110 01100111 00100000 01110100 01101000 01100001 01110100 00100000 01101100 01101111 01101111 01101011 01110011 00100000 01101100 01101001 01101011 01100101 00100000 01110100 01101000 01101001 01110011 00100000 01101111 01101110 01100101 00101110 00101001 00001010 01010100 01101101 01010110 00110011 01001001 01000111 01001110 01101111 01011001 01010111 01111000 01110011 01011010 01010111 00110101 01101110 01011010 01010011 01000101 01100111 01010001 00110010 01000110 01110101 01001001 01001000 01101100 01110110 01100100 01010011 01000010 01101101 01100001 01010111 01100100 00110001 01100011 01101101 01010101 01100111 01100010 00110011 01010110 00110000 01001001 01001000 01100100 01101111 01011001 01011000 01010001 01101110 01100011 01111001 01000010 01101110 01100010 00110010 01101100 01110101 01011010 01111001 01000010 01110110 01100010 01101001 01000010 01101111 01011010 01011000 01001010 01101100 01010000 01111001 01000010 01001010 01100100 01000011 01000010 01110011 01100010 00110010 00111001 01110010 01100011 01111001 01000010 01110011 01100001 01010111 01110100 01101100 01001001 01001000 01010010 01101111 01011010 01010011 01000010 01110011 01011010 01011000 01010010 00110000 01011010 01011000 01001010 01111010 01001001 01000111 01000110 01111001 01011010 01010011 01000010 01111010 01100001 01000111 01101100 01101101 01100100 01000111 01010110 01101011 01001001 01000111 01001010 00110101 01001001 01001000 01001110 01110110 01100010 01010111 01010101 01100111 01011001 00110010 00111001 01110101 01100011 00110011 01010010 01101000 01100010 01101110 01010001 01110101 01001001 01000011 01101000 01101111 01100001 01010111 00110101 00110000 01001111 01101001 01000010 00110101 01100010 00110011 01010101 01100111 01100010 01010111 01101100 01101110 01100001 01001000 01010001 01100111 01100100 00110010 01000110 01110101 01100100 01000011 01000010 00110000 01100010 01111001 01000010 01111010 01100100 01000111 01000110 01111001 01100100 01000011 01000010 01110011 01100010 00110010 00111001 01110010 01100001 01010111 00110101 01101110 01001001 01001000 01010110 01110111 01001001 01000110 01001010 01110110 01100010 01010111 01000110 01110101 01001001 01001000 01000010 01101100 01100010 00110011 01000010 01110011 01011010 01010011 01101011 01110101 01000011 01101101 01110100 00110010 01011001 01101110 01001110 01111000 01100011 01101101 01010001 01110011 01001001 01000111 01101100 00110101 01011010 01010011 01100100 01101001 01100010 01111001 01000010 01110010 01100100 01101110 01100100 00110101 01011001 00110010 01010001 01100111 01011010 01001000 01001010 01110110 01011001 01101101 00111000 01101000 01001001 01000110 01101000 00110101 01011010 01111001 01000010 01110111 01100101 01010111 01001001 01100111 01011010 01001000 01001010 01110110 01001001 01001000 01000010 01111010 01100101 01000111 01110100 00110010 01001001 01000011 01101000 01110010 01100101 01000111 00110100 01100111 01100100 00110010 01110100 01110000 01100010 01000111 00111000 01100111 01011010 01001000 01001010 01110110 01001001 01001000 01001010 01110010 01011001 01101101 00110101 01110110 01011001 00110010 01010001 01110101 01001100 01101001 00110100 01110000 01001001 01001000 01110000 01110010 01011001 01101101 01010001 00110110 01001001 01000111 01110011 01100111 01011001 00110010 01010110 01110011 01011001 00110010 01010010 01111010 01011010 01000111 01010110 01101011 01100011 00110011 01101100 00110100 01001001 01000111 00110001 01111010 01100101 01101110 01001010 01110110 01011001 01101001 00110100 01100111 01010101 00110011 01100111 01100111 01011010 01001000 01001010 01110110 01001001 01001000 01000010 00110101 01100100 01101110 01011010 00110101 01011010 00110011 01001110 00110100 01100011 01010011 01000010 01101011 01100010 00110010 01101000 01101011 01001100 01000011 01000010 01010100 01001010 00110010 01011010 01110110 01001001 01000111 01010010 01110010 01100100 01010111 00111001 00110100 01001001 01001000 01100100 01110000 01001001 01001000 01100100 01110110 01011001 00110010 01001110 01110010 01100011 01010111 00111000 01100111 01100001 00110011 01101000 01110101 01001001 01000111 01001010 01110110 01100101 01101110 01011010 01110010 01100010 01010111 00111001 01110101 01001001 01000111 00111001 01101101 01100010 00110010 01001010 01110000 01001001 01000111 01110100 00110010 01100101 01101110 01001010 01110010 01100010 01000111 00111001 01101011 01100011 00110010 00110000 01100111 01100010 01011000 01001010 01110010 01011001 01101101 01110100 01110100 01011010 01000111 00111001 01101001 01001001 01000111 01100100 01111010 01011010 01001000 01001001 01100111 01100001 01111001 01000010 01110100 01100101 01010111 01001010 01101001 01100010 00110010 01001110 00110110 01100101 01011000 01101000 01110101 01100010 00110011 01101000 01110100 01100010 01111001 01000010 01101011 01100101 01010011 01000010 01110010 01001001 01000111 00110101 01111010 01100011 01001000 01000010 01110110 01011001 01101101 00111001 00110100 01011010 01000011 01000010 01110100 01100011 01101101 01110100 01101001 01100001 00110010 00110001 01101011 01100010 00110010 01001001 01100111 01001100 01010011 01000010 00110001 01100101 01001000 01101100 01101110 01100101 01000011 01000010 01110010 01011001 01111001 01000010 01110010 01001001 01000111 01001110 01101100 01100010 01000111 01001110 01101011 01100011 00110010 01010010 01101100 01011010 01001000 01001110 00110101 01100101 01000011 01000010 01110100 01100011 00110011 01110000 01111001 01100010 00110010 01001001 01110101 01001001 01000101 00110001 01110010 01100101 01000011 01000010 01110000 01100101 01010111 01010101 01100111 01100011 01001000 01001110 00110100 01100010 01101001 01000010 01101011 01100011 01101101 00111000 01100111 01100011 01001000 01001110 00110100 01100001 00110011 01011001 01100111 01100011 01001000 01011010 01110010 01100011 01010100 00111000 01100111 01100011 01101110 01001110 00110100 01011010 01000100 01101111 01100111 01010010 00110010 00111000 01100111 01100100 01011000 01101000 00110101 01011010 01111001 01000010 01101011 01100011 01101101 01110100 01101011 01001001 01000111 01010010 01111001 01100010 01111001 01000010 01110111 01100100 01101101 01110100 01111000 01001001 01001000 01001110 01101010 01001001 01001000 01000110 00110101 01100011 00110011 01101000 01111000 01001001 01000111 01010010 00110101 01001001 01000111 01111000 01110110 01001001 01001000 01101100 01110111 01001001 01000111 01010010 01111001 01100010 01111001 01000010 01110111 01100101 01010111 01001010 00110011 01100001 00110010 01010001 01100111 01011010 01010111 01010010 01110111 01100100 01101101 01110100 01111000 01100101 01111001 00110100 01110101 01001100 01101110 00110000 01100111 01001100 01010011 01000010 01101110 01100011 01101110 01001110 01110100 01100011 01101001 01000010 00110011 01100010 00110010 01110100 00110100 01011001 01111001 01000010 01101011 01100011 01101101 01110100 01101011 01001001 01001000 01001110 01110111 01001001 01000111 01101100 00110101 01011010 01010011 01000010 01101010 01100010 00110010 00111000 01100111 01011010 01001000 01001010 01110010 01011010 01000011 01000010 00110110 01100001 00110010 01010010 01101011 01100010 00110010 01001010 00110100 01001100 01000011 01000010 01110000 01100101 01010111 01010101 01100111 01100100 01011000 01101000 00110101 01011010 01111001 01000010 01101110 01100011 01101101 01110100 01101011 01001001 01000111 01010010 01111001 01100010 01111001 01000010 01110100 01100101 01010111 01001010 01101001 01100010 00110010 01001110 00110110 01100101 01011000 01101000 01110101 01100010 00110011 01101000 01110100 01100010 00110010 01001101 01100111 01100011 01001000 01101100 01101001 01001001 01000111 01010101 01110011 01001001 01000111 01010001 01110011 01001001 01001000 01000001 01110011 01001001 01001000 01011001 01100111 01100001 01111001 01110111 01100111 01100001 00110011 01101000 01110101 01001001 01001000 01000101 01100111 01100001 00110010 01001010 01110110 01001100 01101001 01000010 01001010 01100101 01010111 01010101 01100111 01100010 01010111 01110100 00110100 01001001 01001000 01110000 01101001 01100101 01010111 01111000 01110010 01100010 01001000 01011010 01110000 01001001 01000111 01100100 00110101 01011001 01101110 01010101 01100111 01100101 01010111 01010110 01101011 01001001 01000111 01010010 01111001 01100010 01111001 01000010 01101001 01100010 00110011 01100100 01110010 01100011 00110011 01101000 01111010 01100101 01001000 01000101 01100111 01100010 01011000 01001010 01110010 01011001 01101101 01110100 01110100 01011010 01000111 00111001 01101001 01011001 01111001 01000010 01110011 01100001 01010011 01000010 01101001 01100010 00110011 01110000 00110010 01100001 00110010 00110001 01111010 01100101 01001000 01000101 01100111 01011010 01001000 01001010 01110110 01100100 01111001 01000010 01110010 01100101 01000111 00110100 01100111 01100011 00110011 01101000 01110111 01100010 00110010 01001010 01101001 01100011 00110011 01101000 01111000 01001001 01000111 00110001 00110101 01100100 00110011 01100100 00110101 01100101 01000011 01000010 01101110 01100101 01010111 01001010 01110101 01011001 01111001 01000010 01111010 01100101 01000011 01000010 01101011 01100011 01101101 00111000 01100111 01010100 00110011 01101000 01111000 01100100 01101110 01001110 01101010 01100011 01101001 01000010 00110010 01100001 00110011 01101000 01111000 01011010 01010111 01110100 01111000 01100010 01111001 00110100 01100111 01010011 00110011 01101000 00110101 01011010 01001000 01001010 01110110 01011001 01101001 01000010 01111000 01011001 01101101 00111001 01110010 01011010 01000011 01000010 00110011 01100010 00110010 01010010 01111001 01100101 01010111 00110100 01100111 01100011 00110010 01001101 01100111 01011010 01001000 01101011 01100111 01011010 01010111 01001110 01110110 01001001 01001000 01000010 01101001 01100010 00110010 01000110 01101100 01100010 00110011 01101000 01110100 01100001 01010011 01000010 01110010 01100101 01000111 01110100 00110010 01100001 01010111 01001110 01111010 01011001 01111010 01101111 01100111 01011010 00110010 00111000 01100111 01100100 01011000 01101000 00110101 01011010 01111001 01000010 01101011 01100011 01101101 01110100 01101011 01001001 01000011 01100100 01110110 01001010 01111001 01000010 01101010 01100011 01101110 01101100 01101110 01011001 01111001 01000010 01101100 01100101 01101001 01000010 00110011 01100101 01010111 01001110 01101011 01001001 01001000 01101100 01110111 01011010 01000111 00111001 00110100 01001001 01001000 01001110 00110100 01001001 01000111 01010010 01111001 01100010 01111001 01000010 01110010 01100100 01101110 01110000 01111001 01100001 00110010 01111000 01110110 01011010 01000011 01110111 01100111 01011001 00110011 01101011 01100111 01011010 01001000 01001010 01110010 01011010 01000011 01100100 01101010 01001001 01001000 01110000 01101001 01100101 01010111 01111000 01110010 01100010 01001000 01011010 01110000 01001001 01000111 01010010 01111001 01100010 01111001 01000010 00110011 01100101 01010111 01001110 01101011 01001001 01000111 00110001 00110101 01100100 00110011 01100100 00110101 01100101 01000011 01000010 01110100 01100011 01101101 01110100 01101001 01100001 00110010 00110001 01101011 01100010 00110010 01001001 01100111 01100011 00110011 01100111 01100111 01011010 01001000 01001010 01110110 01001001 01000111 01010010 01110110 01100001 01000111 01010001 01110011 01001001 01001000 01000010 00110101 01100100 01101110 01011010 00110101 01011010 00110010 00111001 01110101 01001001 01000111 01111000 01110000 01001001 01000011 01100100 01101011 01001010 01111001 01110111 01100111 01100001 00110011 01101000 01110101 01001001 01000111 01001110 00110101 01001001 01001000 01101100 00110100 01001100 01101001 01000010 01011010 01100101 01000111 00110001 01110110 01001001 01000111 01101100 00110101 01011010 01010011 01000010 00110001 01100101 01001000 01101100 01101110 01001001 01000111 01110011 01100111 01100011 01000111 00111001 01101110 01001001 01000111 00110001 01111001 01100001 00110010 01001010 01110010 01100010 01010111 01010010 01110110 01011001 01101101 01001101 01110011 01001001 01000111 01101100 00110101 01011010 01010011 01000010 01110100 01100001 00110011 01100111 01100111 01100011 00110011 01101000 01110111 01100010 00110010 01001001 01100111 01011010 01001000 01001010 01110110 01001001 01000111 01001010 01110110 01011001 00110010 01010001 01100111 01100101 01011000 01000001 01100111 01011010 01001000 01001010 01110110 01001001 01000111 01100100 00110101 01011001 01101101 00110101 01101010 01001001 01000111 01111000 01110010 01011001 00110010 00111001 01110101 01001001 01001000 01101100 00110100 01001001 01000111 00110001 00110101 01100100 00110011 01100100 00110101 01100101 01000011 01000010 01101110 01100101 01010111 01001010 01110101 01011001 01111001 01000010 01101011 01100011 01101101 01110100 01101011 01001001 01000111 01001110 01111001 01100101 01010111 01100011 01100111 01011010 01011000 01101111 01100111 01100011 00110011 01100111 01100111 01011010 01001000 01001010 01110110 01001001 01000101 00111001 00110100 01100011 01011000 01011010 01111010 01011001 00110011 01001001 01100111 01100100 01101101 01110100 00110100 01100011 01010111 01010110 01110010 01100011 01010111 00111000 01110101 01000011 01101110 01001010 01101110 01100001 01000111 00110101 00110100 01100011 00110010 01010010 01101101 01100101 01011000 01001110 01101011 01100100 01000111 01100100 01101111 01100100 01010011 01000101 01100111 01100011 01010111 01100100 01101101 01001001 01000111 01101100 01111010 01011001 01010111 01110011 01100111 01011001 00110011 01010010 01101111 01100100 01001000 01010110 01110000 01100001 00110010 01010101 01100111 01011010 01000111 01101100 01110010 01001001 01001000 01110000 01110010 01100010 01101110 01010010 01101111 01100001 01000111 01110100 00110100 01001001 01001000 01001010 00110100 01100011 01010111 01111000 01101011 01011010 00110010 00110101 00110100 01100011 00110010 01111000 01110000 01100011 01010011 01000010 01111001 01100001 01011000 01001110 00110101 01100101 01010111 01110100 01101111 01100010 01101101 01110011 01110101 01001001 01000111 01101100 01110010 01100101 01000111 01110011 01100111 01100100 01001000 01010101 01100111 01100011 01111001 01000010 01101010 01100101 01011000 01001110 01110101 01001001 01000111 01001110 01101110 01100101 01000011 01000010 01111010 01100101 01011000 01101011 01100111 01100011 01010111 01100100 01101101 01100101 01000011 01000010 01110000 01100011 00110011 01101000 01101100 01001001 01000111 01110100 01101010 01011001 00110010 01100100 00110100 01011010 01001000 01010101 00110110 01001001 01000111 01011010 01101011 01011001 00110011 01101100 01111010 01100010 01101110 01110011 01111010 01100001 01001000 01001010 00110100 01100011 01010111 01111000 01101011 01001101 01010100 01000010 01101111 01011000 01111010 01000101 00110001 01011000 00110011 01001001 01110111 01001101 01001000 01101100 00111001 01001100 01101001 01000010 01111000 01011010 00110010 01011001 01100111 01100100 01101110 01010010 00110101 01100101 01010011 01000010 01101010 01100100 01000111 01101000 01101100 01001001 01000111 01010010 01110000 01100011 00110010 01010001 01100111 01100011 01111001 01000010 00110101 01011010 00110010 01010001 01100111 01011010 00110010 01001101 01100111 01100011 01101110 01101000 01111000 01100010 01000111 01010010 01101110 01100010 01101110 01101000 01111010 01100010 01000111 01101100 01111000 01001001 01001000 01010010 00110001 01001001 01001000 01000010 01101101 01100100 01010111 01010001 01100111 01100101 01101101 01011010 00110000 01100101 01010111 01010110 00110000 01100001 01000111 00110100 01100111 01011010 00110010 01001110 01101010 01001001 01000111 01010010 01110000 01100100 01001000 01010101 01100111 01100100 01010111 01100100 00110100 01011010 01000011 01000010 01101110 01011001 01111001 01000010 00110110 01100011 00110011 01010110 00110000 01100011 01101001 01000010 01101001 01100001 01000111 01100100 00110010 01100101 01010111 01110100 01101100 01100010 01101101 01110011 01110011 01001001 01001000 01001110 01101111 01011010 01010011 01000010 00110000 01011010 01000011 01000010 00110100 01100001 00110011 01001110 00110101 01100101 01011000 01000101 01100111 01100100 01001000 01010101 01100111 01100001 01000111 01100100 01101011 01001001 01001000 01010110 01101110 01001001 01001000 01110000 01111010 01011010 01010011 01000010 01111010 01011001 00110010 01010010 01110010 01100101 01000011 01000010 01111010 01100101 01011000 01101011 01110101 01001001 01000111 01101100 01101110 01100010 01000111 01110011 01100111 01100011 01010111 01100100 01101101 01001001 01000111 01110100 01101111 01100011 01000111 01100100 01111000 01100001 00110010 01010101 01100111 01011010 01000111 01101100 01110010 01001001 01001000 01001010 01110000 01100011 00110011 01101100 00110101 01100001 00110010 01101000 01110101 01100001 01111001 01000101 00111101
どう見てもASCIIなので変換スクリプトを書いてあげると以下のようになります.
Uh-oh, looks like we have another block of text, with some sort of special encoding. Can you figure out what this encoding is? (hint: if you look carefully, you'll notice that there only characters present are A-Z, a-z, 0-9, and sometimes / and +. See if you can find an encoding that looks like this one.) TmV3IGNoYWxsZW5nZSEgQ2FuIHlvdSBmaWd1cmUgb3V0IHdoYXQncyBnb2luZyBvbiBoZXJlPyBJdCBsb29rcyBsaWtlIHRoZSBsZXR0ZXJzIGFyZSBzaGlmdGVkIGJ5IHNvbWUgY29uc3RhbnQuIChoaW50OiB5b3UgbWlnaHQgd2FudCB0byBzdGFydCBsb29raW5nIHVwIFJvbWFuIHBlb3BsZSkuCmt2YnNxcmQsIGl5ZSdibyBrdnd5Y2QgZHJvYm8hIFh5ZyBweWIgZHJvIHBzeGt2IChreG4gd2tpbG8gZHJvIHJrYm5vY2QuLi4pIHprYmQ6IGsgY2VsY2RzZGVkc3l4IG1zenJvYi4gU3ggZHJvIHB5dnZ5Z3N4cSBkb2hkLCBTJ2ZvIGRrdW94IHdpIHdvY2NrcW8ga3huIGJvenZrbW9uIG9mb2JpIGt2enJrbG9kc20gbXJrYmttZG9iIGdzZHIgayBteWJib2N6eXhub3htbyBkeSBrIG5zcHBvYm94ZCBtcmtia21kb2IgLSB1eHlneCBrYyBrIGNlbGNkc2RlZHN5eCBtc3pyb2IuIE1reCBpeWUgcHN4biBkcm8gcHN4a3YgcHZrcT8gcnN4ZDogR28gdXh5ZyBkcmtkIGRybyBwdmtxIHNjIHF5c3hxIGR5IGxvIHlwIGRybyBweWJ3a2QgZWRwdmtxey4uLn0gLSBncnNtciB3b2t4YyBkcmtkIHNwIGl5ZSBjb28gZHJrZCB6a2Rkb2J4LCBpeWUgdXh5ZyBncmtkIGRybyBteWJib2N6eXhub3htb2MgcHliIGUsIGQsIHAsIHYgaywga3huIHEga2JvLiBJeWUgbWt4IHpieWxrbHZpIGd5YnUgeWVkIGRybyBib3drc3hzeHEgbXJrYmttZG9iYyBsaSBib3p2a21zeHEgZHJvdyBreG4gc3hwb2Jic3hxIG15d3d5eCBneWJuYyBzeCBkcm8gT3hxdnNjciB2a3hxZWtxby4gS3h5ZHJvYiBxYm9rZCB3b2RyeW4gc2MgZHkgZWNvIHBib2Flb3htaSBreGt2aWNzYzogZ28gdXh5ZyBkcmtkICdvJyBjcnlnYyBleiB3eWNkIHlwZG94IHN4IGRybyBrdnpya2xvZCwgY3kgZHJrZCdjIHpieWxrbHZpIGRybyB3eWNkIG15d3d5eCBtcmtia21kb2Igc3ggZHJvIGRvaGQsIHB5dnZ5Z29uIGxpICdkJywga3huIGN5IHl4LiBZeG1vIGl5ZSB1eHlnIGsgcG9nIG1ya2JrbWRvYmMsIGl5ZSBta3ggc3hwb2IgZHJvIGJvY2QgeXAgZHJvIGd5Ym5jIGxrY29uIHl4IG15d3d5eCBneWJuYyBkcmtkIGNyeWcgZXogc3ggZHJvIE94cXZzY3Igdmt4cWVrcW8uCnJnaG54c2RmeXNkdGdodSEgcWdmIGlzYWsgY3RodHVpa2UgZGlrIHprbnRoaGt4IHJ4cWxkZ254c2xpcSByaXN5eWtobmsuIGlreGsgdHUgcyBjeXNuIGNneCBzeXkgcWdmeCBpc3hlIGtjY2d4ZHU6IGZkY3lzbnszaHJ4cWxkMTBoXzE1X3IwMHl9LiBxZ2YgdnR5eSBjdGhlIGRpc2QgcyB5Z2QgZ2MgcnhxbGRnbnhzbGlxIHR1IHBmdWQgemZ0eWV0aG4gZ2NjIGRpdHUgdWd4ZCBnYyB6c3V0ciBiaGd2eWtlbmssIHNoZSB0ZCB4a3N5eXEgdHUgaGdkIHVnIHpzZSBzY2RreCBzeXkuIGlnbGsgcWdmIGtocGdxa2UgZGlrIHJpc3l5a2huayE=
どう見てもbase64なので変換スクリプトを書いてあげると以下のようになります.
New challenge! Can you figure out what's going on here? It looks like the letters are shifted by some constant. (hint: you might want to start looking up Roman people). kvbsqrd, iye'bo kvwycd drobo! Xyg pyb dro psxkv (kxn wkilo dro rkbnocd...) zkbd: k celcdsdedsyx mszrob. Sx dro pyvvygsxq dohd, S'fo dkuox wi wocckqo kxn bozvkmon ofobi kvzrklodsm mrkbkmdob gsdr k mybboczyxnoxmo dy k nsppoboxd mrkbkmdob - uxygx kc k celcdsdedsyx mszrob. Mkx iye psxn dro psxkv pvkq? rsxd: Go uxyg drkd dro pvkq sc qysxq dy lo yp dro pybwkd edpvkq{...} - grsmr wokxc drkd sp iye coo drkd zkddobx, iye uxyg grkd dro mybboczyxnoxmoc pyb e, d, p, v k, kxn q kbo. Iye mkx zbylklvi gybu yed dro bowksxsxq mrkbkmdobc li bozvkmsxq drow kxn sxpobbsxq mywwyx gybnc sx dro Oxqvscr vkxqekqo. Kxydrob qbokd wodryn sc dy eco pboaeoxmi kxkvicsc: go uxyg drkd 'o' crygc ez wycd ypdox sx dro kvzrklod, cy drkd'c zbylklvi dro wycd mywwyx mrkbkmdob sx dro dohd, pyvvygon li 'd', kxn cy yx. Yxmo iye uxyg k pog mrkbkmdobc, iye mkx sxpob dro bocd yp dro gybnc lkcon yx mywwyx gybnc drkd cryg ez sx dro Oxqvscr vkxqekqo. rghnxsdfysdtghu! qgf isak cthtuike dik zknthhkx rxqldgnxsliq risyykhnk. ikxk tu s cysn cgx syy qgfx isxe kccgxdu: fdcysn{3hrxqld10h_15_r00y}. qgf vtyy cthe disd s ygd gc rxqldgnxsliq tu pfud zftyethn gcc ditu ugxd gc zsutr bhgvykenk, she td xksyyq tu hgd ug zse scdkx syy. iglk qgf khpgqke dik risyykhnk!
rot系っぽいので総当たりします.
alright, you're almost there! Xow for the final (and maybe the hardest...) part: a substitution cipher. Sn the following text, S've taken my message and replaced every alphabetic character with a correspondence to a different character - known as a substitution cipher. Man you find the final flag? hint: Ge know that the flag is going to be of the format utflagq...s - which means that if you see that pattern, you know what the correspondences for u, t, f, l a, and g are. Iou can probably work out the remaining characters by replacing them and inferring common words in the Onglish language. Knother great method is to use frequency analysis: we know that 'e' shows up most often in the alphabet, so that's probably the most common character in the text, followed by 't', and so on. Ynce you know a few characters, you can infer the rest of the words based on common words that show up in the Onglish language. hwxdnitvoitjwxk! gwv yiqa sjxjkyau tya padjxxan hngbtwdnibyg hyiooaxda. yana jk i soid swn ioo gwvn yinu asswntk: vtsoid{3xhngbt10x_15_h00o}. gwv ljoo sjxu tyit i owt ws hngbtwdnibyg jk fvkt pvjoujxd wss tyjk kwnt ws pikjh rxwloauda, ixu jt naioog jk xwt kw piu istan ioo. ywba gwv axfwgau tya hyiooaxda!
rot15でした.
どうやら次が最終パート(換字式暗号)みたいです.
問題文では文字の出現頻度を利用して換字を推定すると良いみたいなことが書かれていますが,手作業でやると少し時間がかかるのでquipqiup
を使います.
congratulations! you have finished the beginner cryptography challenge. here is a flag for all your hard efforts: utflag{3ncrypt10n_15_c00l}. you will find that a lot of cryptography is just building off this sort of basic knowledge, and it really is not so bad after all. hope you enjoyed the challenge!
utflag{3ncrypt10n_15_c00l}
ソルバは以下のようになっています.
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import base64 def phase1(): with open('./binary.txt') as f: b = f.read() b_list = b.strip().split(' ') b_text = [] for x in b_list: b_text.append(chr(int(x,2))) return ''.join(b_text) def phase2(p1_result): enc64_text = p1_result.split('\n')[1] dec64_text = base64.b64decode(enc64_text) return dec64_text.decode('utf-8') def phase3(p2_result): enc_text = p2_result.split('\n')[1] dec_text = [] for x in enc_text: if 97 <= ord (x) and ord(x) <= 127: dec_text.append(chr((ord(x) + 15 - 96) % 26 + 97)) else: dec_text.append(x) print(''.join(dec_text)) enc_text2 = p2_result.split('\n')[2] dec_text2 = [] for x in enc_text2: if 97 <= ord (x) and ord(x) <= 122: dec_text2.append(chr((ord(x) + 15 - 96) % 26 + 97)) else: dec_text2.append(x) return ''.join(dec_text2) def phase4(p3_result): dec_dict = {'a':'e', 'b':'p', 'd':'g', 'f':'j', 'g':'y', 'h':'c', 'i':'a', 'j':'i', 'k':'s', 'l':'w', 'n':'r', 'o':'l', 'p':'b', 'q':'v', 'r':'k', 's':'f', 't':'t','u':'d', 'v':'u', 'w':'o', 'x':'n', 'y':'h'} flag = [] for x in p3_result: dec_x = dec_dict.get(x) if dec_x is None: flag.append(x) else: flag.append(dec_x) return ''.join(flag) if __name__ == '__main__': p1_result = phase1() print(p1_result) p2_result = phase2(p1_result) print(p2_result) p3_result = phase3(p2_result) flag = phase4(p3_result) print(flag)
> python solve.py Uh-oh, looks like we have another block of text, with some sort of special encoding. Can you figure out what this encoding is? (hint: if you look carefully, you'll notice that there only characters present are A-Z, a-z, 0-9, and sometimes / and +. See if you can find an encoding that looks like this one.) TmV3IGNoYWxsZW5nZSEgQ2FuIHlvdSBmaWd1cmUgb3V0IHdoYXQncyBnb2luZyBvbiBoZXJlPyBJdCBsb29rcyBsaWtlIHRoZSBsZXR0ZXJzIGFyZSBzaGlmdGVkIGJ5IHNvbWUgY29uc3RhbnQuIChoaW50OiB5b3UgbWlnaHQgd2FudCB0byBzdGFydCBsb29raW5nIHVwIFJvbWFuIHBlb3BsZSkuCmt2YnNxcmQsIGl5ZSdibyBrdnd5Y2QgZHJvYm8hIFh5ZyBweWIgZHJvIHBzeGt2IChreG4gd2tpbG8gZHJvIHJrYm5vY2QuLi4pIHprYmQ6IGsgY2VsY2RzZGVkc3l4IG1zenJvYi4gU3ggZHJvIHB5dnZ5Z3N4cSBkb2hkLCBTJ2ZvIGRrdW94IHdpIHdvY2NrcW8ga3huIGJvenZrbW9uIG9mb2JpIGt2enJrbG9kc20gbXJrYmttZG9iIGdzZHIgayBteWJib2N6eXhub3htbyBkeSBrIG5zcHBvYm94ZCBtcmtia21kb2IgLSB1eHlneCBrYyBrIGNlbGNkc2RlZHN5eCBtc3pyb2IuIE1reCBpeWUgcHN4biBkcm8gcHN4a3YgcHZrcT8gcnN4ZDogR28gdXh5ZyBkcmtkIGRybyBwdmtxIHNjIHF5c3hxIGR5IGxvIHlwIGRybyBweWJ3a2QgZWRwdmtxey4uLn0gLSBncnNtciB3b2t4YyBkcmtkIHNwIGl5ZSBjb28gZHJrZCB6a2Rkb2J4LCBpeWUgdXh5ZyBncmtkIGRybyBteWJib2N6eXhub3htb2MgcHliIGUsIGQsIHAsIHYgaywga3huIHEga2JvLiBJeWUgbWt4IHpieWxrbHZpIGd5YnUgeWVkIGRybyBib3drc3hzeHEgbXJrYmttZG9iYyBsaSBib3p2a21zeHEgZHJvdyBreG4gc3hwb2Jic3hxIG15d3d5eCBneWJuYyBzeCBkcm8gT3hxdnNjciB2a3hxZWtxby4gS3h5ZHJvYiBxYm9rZCB3b2RyeW4gc2MgZHkgZWNvIHBib2Flb3htaSBreGt2aWNzYzogZ28gdXh5ZyBkcmtkICdvJyBjcnlnYyBleiB3eWNkIHlwZG94IHN4IGRybyBrdnpya2xvZCwgY3kgZHJrZCdjIHpieWxrbHZpIGRybyB3eWNkIG15d3d5eCBtcmtia21kb2Igc3ggZHJvIGRvaGQsIHB5dnZ5Z29uIGxpICdkJywga3huIGN5IHl4LiBZeG1vIGl5ZSB1eHlnIGsgcG9nIG1ya2JrbWRvYmMsIGl5ZSBta3ggc3hwb2IgZHJvIGJvY2QgeXAgZHJvIGd5Ym5jIGxrY29uIHl4IG15d3d5eCBneWJuYyBkcmtkIGNyeWcgZXogc3ggZHJvIE94cXZzY3Igdmt4cWVrcW8uCnJnaG54c2RmeXNkdGdodSEgcWdmIGlzYWsgY3RodHVpa2UgZGlrIHprbnRoaGt4IHJ4cWxkZ254c2xpcSByaXN5eWtobmsuIGlreGsgdHUgcyBjeXNuIGNneCBzeXkgcWdmeCBpc3hlIGtjY2d4ZHU6IGZkY3lzbnszaHJ4cWxkMTBoXzE1X3IwMHl9LiBxZ2YgdnR5eSBjdGhlIGRpc2QgcyB5Z2QgZ2MgcnhxbGRnbnhzbGlxIHR1IHBmdWQgemZ0eWV0aG4gZ2NjIGRpdHUgdWd4ZCBnYyB6c3V0ciBiaGd2eWtlbmssIHNoZSB0ZCB4a3N5eXEgdHUgaGdkIHVnIHpzZSBzY2RreCBzeXkuIGlnbGsgcWdmIGtocGdxa2UgZGlrIHJpc3l5a2huayE= New challenge! Can you figure out what's going on here? It looks like the letters are shifted by some constant. (hint: you might want to start looking up Roman people). kvbsqrd, iye'bo kvwycd drobo! Xyg pyb dro psxkv (kxn wkilo dro rkbnocd...) zkbd: k celcdsdedsyx mszrob. Sx dro pyvvygsxq dohd, S'fo dkuox wi wocckqo kxn bozvkmon ofobi kvzrklodsm mrkbkmdob gsdr k mybboczyxnoxmo dy k nsppoboxd mrkbkmdob - uxygx kc k celcdsdedsyx mszrob. Mkx iye psxn dro psxkv pvkq? rsxd: Go uxyg drkd dro pvkq sc qysxq dy lo yp dro pybwkd edpvkq{...} - grsmr wokxc drkd sp iye coo drkd zkddobx, iye uxyg grkd dro mybboczyxnoxmoc pyb e, d, p, v k, kxn q kbo. Iye mkx zbylklvi gybu yed dro bowksxsxq mrkbkmdobc li bozvkmsxq drow kxn sxpobbsxq mywwyx gybnc sx dro Oxqvscr vkxqekqo. Kxydrob qbokd wodryn sc dy eco pboaeoxmi kxkvicsc: go uxyg drkd 'o' crygc ez wycd ypdox sx dro kvzrklod, cy drkd'c zbylklvi dro wycd mywwyx mrkbkmdob sx dro dohd, pyvvygon li 'd', kxn cy yx. Yxmo iye uxyg k pog mrkbkmdobc, iye mkx sxpob dro bocd yp dro gybnc lkcon yx mywwyx gybnc drkd cryg ez sx dro Oxqvscr vkxqekqo. rghnxsdfysdtghu! qgf isak cthtuike dik zknthhkx rxqldgnxsliq risyykhnk. ikxk tu s cysn cgx syy qgfx isxe kccgxdu: fdcysn{3hrxqld10h_15_r00y}. qgf vtyy cthe disd s ygd gc rxqldgnxsliq tu pfud zftyethn gcc ditu ugxd gc zsutr bhgvykenk, she td xksyyq tu hgd ug zse scdkx syy. iglk qgf khpgqke dik risyykhnk! alright, you're almost there! Xow for the final (and maybe the hardest...) part: a substitution cipher. Sn the following text, S've taken my message and replaced every alphabetic character with a correspondence to a different character - known as a substitution cipher. Man you find the final flag? hint: Ge know that the flag is going to be of the format utflagq...s - which means that if you see that pattern, you know what the correspondences for u, t, f, l a, and g are. Iou can probably work out the remaining characters by replacing them and inferring common words in the Onglish language. Knother great method is to use frequency analysis: we know that 'e' shows up most often in the alphabet, so that's probably the most common character in the text, followed by 't', and so on. Ynce you know a few characters, you can infer the rest of the words based on common words that show up in the Onglish language. congratulations! you have finished the beginner cryptography challenge. here is a flag for all your hard efforts: utflag{3ncrypt10n_15_c00l}. you will find that a lot of cryptography is just building off this sort of basic knowledge, and it really is not so bad after all. hope you enjoyed the challenge!
Jacobi's Chance Encryption (Cryptography, 750 pts, 76 solves)
Public Key:
569581432115411077780908947843367646738369018797567841
[flag.enc]
def encrypt(m, pub_key): bin_m = ''.join(format(ord(x), '08b') for x in m) n, y = pub_key def encrypt_bit(bit): x = randint(0, n) if bit == '1': return (y * pow(x, 2, n)) % n return pow(x, 2, n) return map(encrypt_bit, bin_m)
3ad3750f859c2c8fc1eb2076f876322cd17421c1cff88,0,0,0,3a46387fd709d3b4692cab59248ad3f426e9b8cd5bffe,0,2a6670878e3f48cfd5abcb7940b6df1d22650a438e905,0,5b9c517cca0d3ab82864545d1c943d8ab198650830b38,0,0,0,38e4b15d3ce5567cc7257cc8bb6b205caf2019b35f2a6,0,35bd6c7234811aef80bfee6245fbde8242b056730138a,ec3fffb29bfe1b8473bd2ea04898d3b8ae280cdd599,21498339f1250e7a6ad9d3de38ab4570cc5ae6e1abf1d,0,0,2e41759047b358d16001106dbed035c2988cc70e3465a,515d722c387c1ff1b3d82df07d9ab8d768601fd289977,0,0,5dcd5fb5ed0889a57b6d7f861465ab8bb0bacdf3c9015,3bea61e3ae79cfce65313ad0f3a5c06756718cdfe6dbc,0,0,32526a618b4a68f0f500e2c28b28657a685ef921ccb1e,0,0,2b2de511c67be3942f813642fc86ed58d3336cc545366,8b388f5ad9f9bcbfdbfd56448d31109cbda3b9a6a19e,1849b58830e2b2a4aec0f2e2402baf24e359bb2d2d597,0,0,182a15af9f447ae092b99cd4a92d3cbfa3e59b873df9d,47805e2beb12898f8ca786bf44c574e14fda72413441e,5e428ab03b0a59994e2665ab47b460c07a5c287ffa83e,c2b05078b5d5e4b4b16d63d3605e8e9168c094f9e49e,0,137e41ffaab7b673f9f66b124ec7c3d5cf6f35a6c027b,0,0,7013fd7be70e60d8466620b91322d2df1423fc3448c7,20a04d2503190d2dc212f3142954ad03b034d350705d0,0,0,0,38674cf6d907f0489eab987a92a226e696cb57756c1fd,0,0,0,0,49c93d181739024dfba0249ecdecd85d35fc2e3438c68,0,0,412b046edde3a94a3f1a0eb6e3c7239e143d0858c7579,0,0,21dc7b7db7e1f67072e1012df0f95da988984602c5040,2482d2a9798e2f818aca743ade524ad78d426787964e0,0,6481395ddbf7ff011854984fbfcdb68a4646d3d0a46c,5047bc50b4c717d50e70767b6337c0e288130689dd21d,b4c98854e437c12beb0092f5ab92e0ef03f24147bd57,0,0,3345f0bc3edf8c21b2fdf591557284f4ddcc90ec468b,0,4a34e17584c919b7dd2ba15f61a6761079590b274f236,4c4e57670dd3386e3f1d5ffb4a03e67c3cce8124fea02,0,20afc75c61bb13ecd4b8022e46ccb61430a1e9b1e3686,0,0,1c82bfae00e66d895126efa79559ce865dd3d10f48416,28afec1451036a90be5da04dbf130597fa7b5b9b96849,0,ee9fbef355f06e79fa1266786f203e67ba9ec5feebe8,350d1cefe02c55b2e53b2179bd61e5e4a8c56b0e767f7,5041e4ee83daf2e49d780330159b6cc1f0b2b04e69a1f,0,4cfbf617c552b79041891c882676111b05737f14c4427,0,0,0,0,0,49649168966fafd1ad13a557fbf288f1f334215035746,0,0,0,152e4e8604810791cffc63625c7bba32186d0b447ccc0,0,47e19816c3d743a62f8667030cf586ab9c74fe3d1e589,0,2447268356aac34514fd1a20f3ee9a5a6c68d5f9a20eb,0,21d82ba5f1dbee7b7d2a277fdf8778d979a6e6103a45c,0,0,0,0,0,3530fd4cfc21cfbe5cea85b058759768c67573b2a28a4,0,0,0,5c18b79bc7dbc03b158187afdca3e1667f20e1b44159e,417e9f2a0820298963144bbb205b9598baebeb6c952c7,d873894e2c210d5a5abecf22fb2b933bc3591bc42165,1053349f683a86ccf76667690c4e08a0a1359da67dea,30a7e917ad4a7edb8387f60a7d90bcc68b2abf1891059,0,0,1a0bb932e2d13139f649c98715952fb7986c663a6f24a,2ca00c675427fef81114c1052759a4e9313b2a4b4fe74,491769cbfb1825c85b02ac643547e0c5ffebb12a05d2,43ca5a52e5780ff99d51348f8f299c7f48b6bde7f8721,0,3dab706ec6906ee37c29cd807a7964dc95ec0d6c34968,0,0,0,0,8076cbc4fa82b9109f695e38d7fb8802c4b140238d5c,862773d42f56151c94b3c24606edaab8d12b4f38aa56,0,3d8ea2f2a54bc99dbd5a9c4a09bbeeb9f8f89f2fd4ec6,0,1a1162d8005591a6d9d255a3c64963dd6c2b5a63235ae,0,0,0,0,0,1ef1bd3f838744fb34cf07a4c4ba688a02c9fb550fea3,0,0,424d57bf5cdbe479ea7596d30348635b7017de83045af,422611e1297e811988effe0ae7973639b6fe95640feae,5925ea727f426aad4faa619c692f2040da956dde9c0b4,21b03f7303387078d1ecc723b5b9b82d864a950e8ae18,0,2acf2d1844e64941997d42f6634222cfea9bb8f1c6c3a,0,0,0,2652f587a28e0bc9fccc9266f4d4fd24dd009b311c029,0,1b018b971ad55025ce3150dfcab3f476b5a8a51e97cc9,31b3629e20a25d917acda299cee182df23e84933e1fc7,2cdecd0635ef68af1b49deba4d4c5f368290731b12955,0,0,0,59af6719c0d73625fb24c6054ddc947d9007777832bb5,0,3f51ceffcda71936ed3d49505d2e855a93f70bcf4d680,56b5003cf8674e0b50e41a7ead88b35c934c665f99eaf,1e13bf35815787e44009499c4d828e33136f4b934e8c6,0,0,1dc0e943e8bdd0afb8c769fe57b59249fd8a513f6472c,4995b3983e52801940feca4e4ad772f7f988d794808e3,0,5a4d18df6303ae5d27d730e9ec03879a9354d6ac9bb6e,0,576ff5eb2ef65e2742980cd4dcd908c92ee777792c1ba,0,0,69455d800e45b163ebabf42796fa489b37674d36e9be,0,0,0,21acac2b8223dff389be96cdc894a7a99a32599d472ce,870c33409d4bc8049b72a5ffce00f229821bcdafe053,0,0,0,52b4d9258e96c4be687f712a08c9ec9c7daba01bcd74c,0,3643b13ffb3ee5208e557abb03697468ff9ce6c5d8bf3,50ef19f0f625bb8e3ab2915201bcf9ae0b40656bbee49,1056670d4c3ea98038f8ecd74440033aeff3064a2e005,0,0,22698fa2d377aee3f2a63c288f17fe2d1061b61b62b94,0,26faa98d38033cfb0653b447d8a7f268f68ab8e51f252,1f5b0997e693c897caec45f5ea38cb24b4ddd27422fec,0,4bb0e445302f5b28ee3e7b36a93dd2e8d12eaa3119772,0,0,4d42df71d8e24a1424eed881c1302f6c21114b1ba84f4,0,0,0,0,312607440a6ec3fdb8401d1db690de41dfeae70cfaefe,0,0,3f620fe471b5b59218c1e554fffc64066e576e09c9b6,0,0,0,23cd34716eaa5762d59728699783cb51d8f87525ab2c8,27f24f062089f67afac376186335fbbd6da4ab6803bf1,0,27811be1b34c349949a7d7de701438ace3f22ab3857b4,0,0,0,0,0,c2937375f4db8464dc81875295ff47d897b749572cb0,0,0,1a2425e6231a8b2dfd59b171ac2fa39a54c1c5f2bc18c,0,4601f4a99029129f673dae90c2b8f08ac77c9acb2b67a,2ce224a2c3f3927658c98890201c41e1485ab81773447,0,17f9f932dbb1f2aa279be9b5751d3e461ea97b79fec18,0,0,467cd79faa40ffef1f69490c3205efef24847eb95c313,0,0,0,931f3df14edaf21ebfc1243b1bb065fa128d6f112f10,30671798eb74ea9593b5dd38710f672d25a48d5cab263,0,1e39d859b972edb5f25c0fc197137e89892361ed6c919,0,0,0,0,0,fb0da1dfe3749d91d7260425b08f23b35d22b4a60512,0,0,61718b9028558be04f9af538877bd5fea45000351480,0,0,0,5a225a78daed3e0a2ca3b09629d2e98d37e0ecb48806c,12862b5946fef0d125e0a24fadb0ea567f40b37c8f984,0,0,0,206ecdbbd50a6800ab5bc293b47c2c784bac7578a8ba5,0,481528aa7c00de41818b4a24ebddefa48533dc43a678d,0,4d09c1ead0061318a6410214fbe0b0ccc72056835583d,0,0,45c40da5f6ed58e0851f635117d8162094814d4a10f6e,0,0,49d96e0bcf458bf606c812d4c3c9a7a3b8b4cb41482ef,0,2a0381519bd15dfce996317d3582f858eaa4a0ec0d5e1,0,0,2c1051612093e8e2149f4e2b9b71fc08bf2b1830d7b0b,486bd7ef1b04a2b282d6232e33b6bc72aeb7e971c1238,1c6afa8a2da6e0ea764fd4e0d8a7866111e1afc80ca3e,0,208090933fe494e5e2866f08875a43c0bad38e2138513,406221453e74594ff0a78ad722752655387ac5d17361,0,0,5ebf43af7a6387ee99da56a5135fca5a2c3776c074315,1b5340564c3fcfa9b7aff92de27759b62b2b8e73d5f1e,0,2739b0fd92f8a9304fba5495b0e191087d754ce0b394,0,6416febf2962928a8ef5120576b85de04cf822fe49fe,0,0,0,3e5b4cd5335bce2416b2bdcf69749a058ca3086823af3,33e4a2d274d064bc3b411571cca3d6c2d85404f3ea7b4,0,115ac281bf28f5059ec51c4650d23b6c4a9f454e1b20c,25d011ee737648a87e60e3b97be76c8a9dd3268361796,0,425ed19ba98db56a50e4a298fda5fa9715ab74017a13a,0,0,0,0,0,98ac380fadcde93784206cdbae7a9bc1dda3a92f0e8d,0,0,0,14a9f8eefe6c1aea36c0f011bc6f5fc87fad4510a2868,0,2fe9b18543777aa26ebcdf46e1fc3c652525a2a4a082f,4689ab7aaddd26b88e44abbf52ce6cbf741000c4306e2,87fa26f9a0e9edb1d0ae9145b79d537ce31e5757d8da,0,0,352d826b6b3dfab4c5ada55f2820bea15f98a1e52d304,0,36492d9686ff9b087f76d6ca0be42a9d4a4fd210836a7,dec4371651f6be401e348fdeddb35a94f3b4df6f787c,2450d35d7987a8f8925f4f237b074a40579370a702a7a,bbffc03f22ee8828ad4440c51ab64c8489f443587ade,0,0,1eb996e05dd1768b73e431f67bbdd6c8407cf05539bdd,42af69f6f1de85ebd85d524ee0c880d17a54717b725a4,0,58a7fd168e149a79b4a4334d1d45166fb819e9bae98c,0,23ee10dbd81466fa2f8ecd88262acc81fd55ea565b2ae,0,0,17af0a96a9b2e7e3312bd69df2a5e30c0f196fbaf8d9,0,0,0,0,3b07b9ac09699fb2e689e24323069f5bf18f9cbcbfd12,0,0,0,21696dbfd09e07b904268332fdbfe9183c9128af58580,59ac9552519d6071903d42e04505f36e46ff7e4dc8f04,0,5cf547a07a1aa5f22d735f13e03dd9c218e69a98742ad,f5328872b06391032c8e4f43667c2817a4edad3a2bf1,0,0,0,0,4e590f3ecab6d14a5fe0ca2e77c4aa15a067e96ac3756,dfb2129fe24a885115f47ffe263b606b98e811f57956,0,2540f861746a56c303d83bf411ed5306c584d66ffaab5,0,0,0,0,0,3d9c6045b72e8c8e8681770056e640adf00770a3f93b5,0,
アプローチ:modの性質
encryptのコードとflag.enc
が与えられます.
encyrptのコードではメッセージ(flag)1文字1文字を8桁の2進数に変換し,
変換後のbitに応じてencryptしているようです(1のときは謎encrypt, 0のときは似非RSA).
次に似非RSAのdecryptが可能かどうかについて考えていきます.
結論から述べると似非RSAのdecryptは簡単に行なえます.なぜなら,pub_key
がとても小さいので簡単に素因数分解できるためです.
しかし,似非RSAのdecryptを行えたとしてもその値が(y * pow(x, 2, n)) % n
によってencryptされた値なのか,pow(x, 2, n)
によってencryptされた値なのかを判断する方法がないように思われます.
ここでもう一度flag.enc
に注目すると0の出現頻度がおかしいことに気づきます.
'a'
をencryptして動作を確かめてみます.
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import random def encrypt_bit(bit): n = 569581432115411077780908947843367646738369018797567841 y = 569581432115411077780908947843367646738369018797567841 x = random.randint(0, n) if bit == '1': return (y * pow(x, 2, n)) % n return pow(x, 2, n) if __name__ == '__main__': bin_m = '01100001' # 'a' x = map(encrypt_bit, bin_m) print(list(x))
> python example.py [503907194163742347950749151189780952424783277180090743, 0, 0, 232013060288249869474521860545175323741752601394883211, 32029176599138398957796327722421222608836960101753808, 125529946533928951618339723926128537231579346326887135, 558211320207958083662854954506953622060168929465128232, 0]
ビット列が1になっているところが0としてencryptされていることに気づきます.
冷静に考えてみればそれはそうで,(y * pow(x, 2, n)) % n
におけるy
とn
は一致するのでmodの性質上この式は0になります.
encrypt方式の欠点が分かったのでソルバに落とします.
#!/usr/bin/env python3 # -*- coding: utf-8 -*- if __name__ == '__main__': with open('./flag.enc') as f: s = f.read() enc_text = s.split(',') flag = [] bits = '' for x in enc_text[:-1]: if x == '0': bits += '1' else: bits += '0' if len(bits) == 8: flag.append(chr(int(bits,2))) bits = '' print(''.join(flag))
> python solve.py utflag{did_u_pay_attention_in_number_theory}
utflag{did_u_pay_attention_in_number_theory}
まとめ
- Crypto問簡単なのしか解けなくてかなしい
- 就活でいそがしい
- 時間に余裕を持ってCTFしたい