CPCTF 2019に参加していました。最終的に8100点で3位でした。嬉しいです。全ての問題のwriteupを書くのは大変なので、面白かった問題や回答数の少ない問題についてのwriteupをします。
- [Crypto 300]worm_eaten
- [Crypto 300]SHAII_we_collide?
- [Binary 300]fast_fibonacci
- [Crypto 300] [input]
こうしてみると僕の解いた問題は回答数の多い、誰でも解ける問題ばかりで挑戦をしてこなかったことがよくわかります。深く反省
[Crypto 300]worm_eaten
フラグ 虫に 食べられて 穴が 空いた
$ ruby encrypt.rb > output.txt
http://files.problem.cpctf.space/worm_eaten/encrypt.rb
http://files.problem.cpctf.space/worm_eaten/output.txt
500の方は解けませんでした。Coppersmith's methodを使うんだろうという推測はたちましたが、知識も実装の経験もなく、短時間のCTFでこれに挑戦するリスクはとれませんでした。
300点の方は一般的なRSAですが、平文の殆どがみえています。というわけで3文字分をブルートフォースしてeとnを用いて暗号化、cと同じ暗号文が生成されるものを探します。
from Crypto.Util.number import bytes_to_long from binascii import unhexlify, hexlify from ptrlib import * n = 143683020644322299318564543310032550731362229816998143115532955396082143235946288551633953435584612777128870426064762700722474983419557070478193865039092489162972540442193198467681495674219822666567094248226655965731005254314292588896865658797569039580229607586511231175110747375426393447205505697507722501469 e = 3 c = 0x4bbd08e630c706c6d9e5757d3392f4aef603ed7e34f649666b8796afe772bcee84ca6882fec351dac1003861fc3a8f306a232d381f36714f41119f8df37fab0e07e02965898913bc349ca4aa4a275498ab85444664480d3ff72b707844d57a1b40658529d375ada1df10a1f867527f2686ea27392af83cdbf8f4bd9ebc46604a for password in brute_force_attack(3): x = brute_force_pattern(password) m = "FLAG_300{{{}0513a1db877145db49b38f80f8fe7a6c6c9912b67d6a9a6d2c8dada7e15e}}".format(x) m2 = int(hexlify(m.encode()), 16) if pow(m2, e, n) == c: print(m) exit()
[Crypto 300]SHAII_we_collide?
HEY can you find SHA-256 collision?
問題文に示されたページへいくと、次のようなソースコードが示されています。
from wsgiref.simple_server import make_server import cgi import hashlib def app(env, start_response): method = env['REQUEST_METHOD'] path = env['PATH_INFO'] if method == 'GET' and path == '/': # index headers = [('Content-Type', 'text/html; charset=utf-8')] start_response('200 OK', headers) with open('./index.html') as f: return [bytes(f.read(), encoding='utf-8')] elif method == 'POST' and path == '/collide': # collide query headers = [('Content-Type', 'text/plain; charset=utf-8')] start_response('200 OK', headers) # post data form = cgi.FieldStorage(fp=env["wsgi.input"], environ=env, keep_blank_values=True) x = form["x"].value y = form["y"].value # !!! collision check !!! if x == y: return [bytes('not collide!', encoding='utf-8')] ## get sha256 shax = hashlib.sha256(x.encode()).hexdigest() shay = hashlib.sha256(y.encode()).hexdigest() ## assert shax and shay is 32byte shax = int(shax,16) & ((1<<32)-1) shay = int(shay,16) & ((1<<32)-1) ## check if shax != shay: return [bytes('not collide!', encoding='utf-8')] # congrats! flag = 'Flag is Here: ***CENSORED***' return [bytes(flag, encoding='utf-8')] # doesn't match headers = [('Content-Type', 'text/plain; charset=utf-8')] start_response('400 Bad Request', headers) return [bytes('Error', encoding='utf-8')] PORT = 8383 httpd = make_server('', PORT, app) httpd.serve_forever()
どうやらsha256をとって衝突する異なる2つの文字列を入力すれば良いようですが、それぞれ int(shax,16) & ((1<<32)-1)
のように切り詰められていて簡単に衝突させることが出来ます。
書くだけです。すぐに衝突する組み合わせ 01@[
, 09xt
が見つかります。
from ptrlib import * import hashlib table = {} for password in brute_force_attack(4): x = brute_force_pattern(password) shax = hashlib.sha256(x.encode()).hexdigest() shax = int(shax,16) & ((1<<32)-1) if shax in table: print(table[shax]) print(x) exit() else: table[shax] = x
[Binary 300]fast_fibonacci
fib(1)=fib(2)=1
binary注:そのまま実行すると
Segmentation fault (core dumped)
と表示されるか、何も表示されず実行が終了もしない、のどちらかになると思いますが、それが正常です。
問題名を見ると、フィボナッチ数列の第n項の計算が遅いのでその後のフラグ出力パートが実行できていないことがわかります。オーバーフローするように注意しながら早いフィボナッチを書いて fib(0x53e691)
の結果が f0bbeb3d
になることを突き止め、 gdbでステップ実行しながら fib
関数の呼び出しを飛ばして、 eax
に f0bbeb3d
を代入した状態で decode
関数を呼び出すとフラグが手に入ります。(b main
r
set $pc=0x565557b8
set $eax=0xf0bbeb3d
)
[Crypto 300] [input]
stage7だけ解法を書いておきます。
def f(s): m = 0 for c in s: m += (ord(c) - ord('a')) + 1 return "".join([chr((ord(c) - ord('a') + m) % 26 + ord('a')) for c in s]) from ptrlib import * import string for password in brute_force_attack(5, table_len=26): x = brute_force_pattern(password, table=string.ascii_lowercase) if f(x) == "input": print(x) exit()