###### tags: `InterKosenCTF2020` `crypto` # [InterKosenCTF 2020] bitcrypto ## 問題 サーバーで動いてるスクリプトだけもらえる 以下、スクリプトのmain部分 ```python= def main(): pubkey, privkey = key_gen(256) keyword = "yoshiking, give me ur flag" m = input("your query: ") if any([c in keyword for c in m]): print("yoshiking: forbidden!") exit() if len(m) > 8: print("yoshiking: too long!") exit() c = enc(pubkey, bytes_to_long(m.encode())) print("token to order yoshiking: ", c) c = [int(x) for x in input("your token: ")[1:-1].split(",")] if len(c) != len(set(c)): print("yoshiking: invalid!") exit() if any([x < 0 for x in c]): print("yoshiking: wow good unintended-solution!") exit() m = long_to_bytes(dec(privkey, c)) if m == keyword.encode(): print("yoshiking: hi!!!! flag!!!! here!!! wowowowowo~~~~~~") print(flag) else: print(m) print("yoshiking: ...?") ``` `m`の入力時に弾かれる条件は、 - keywordに含まれている文字を使用している - `m`が9文字以上だとダメ これを通ると、`m`を暗号化したもの(`token`)がもらえる. その後、`token`を入力し、復号したものが`keyword`と同じであれば、flagゲット 入力のバイパスはどうやってもできないので、keygen, enc, decを見てみる. まず、keygenで気になるのは`pubkey`に`z`が含まれるところ. この`z`は $(\frac{z}{p})=(\frac{z}{q})=-1$を満たすものである. encでは、`b`を`m`の1ビット分にして、$z^b \cdot rand(2,n)^2$ を計算していった結果を返す. decは、与えられた数字`b`が $(\frac{b}{p}) = (\frac{b}{q}) = 1$ だったら`0`を違ったら`1`とし、`m`を復元する. ## 解法 legendre symbolがみそっぽい. encの式をlegendre symbolのみに注目すると、 $$ z^b = \begin{cases} -1 & (b=1)\\ 1 & (b=0) \end{cases}\\ rand(2,n)^2 = 1 $$ となる. つまり、入力した`m`のビットが`1`のところは`token`のlegendre symbolが`-1`になり.`0`のところはlegendre symbolが`1`となる.decの方法がうまく行くことがわかる. 入力した`m`のビットが`1`となる`token`と`m`のビットが`0`の`token`を使えば任意のビット列が生成できる. しかし、mainに ```python if len(c) != len(set(c)): print("yoshiking: invalid!") exit() ``` とあるように、同じ値を入れるとexitしてしまう. これをパスするためには、legendre symbolの性質を使って異なる値となるようにする.legendre symbolは$a^2 \bmod{n}$ の解が存在すれば`1`となるので、適当な値$a$を2乗したものはlegendre symbolが`1`となる. legendre symbol同士の乗算は普通に成り立つ?(証明はしらん)ので、legendre symbolが`1`となるようなものをかけても全体の値のlegendre symbolは変わらない. よくわからない文を生成してしまったけど、とりあえず適当な値を2乗したものをかけとけば条件をパスできる. ```python= from ptrlib import * from Crypto.Util.number import bytes_to_long from random import randrange, choice keyword = b"yoshiking, give me ur flag" sock = Socket("localhost", 13003) sock.recvuntil("query: ") sock.sendline(b"@") sock.recvuntil("yoshiking: ") token = eval(sock.recvline().decode()) payload = [] for bit in bin(bytes_to_long(keyword))[2:]: if bit == '1': payload.append(randrange(10000)**2 * token[0]) else: payload.append(randrange(10000)**2 * choice(token[1:])) print(len(payload)) print(len(set(payload))) sock.recvuntil("your token: ") sock.sendline(str(payload)) sock.interactive() ``` 何回か試すと、flagゲット ``` $ python solver.py [+] __init__: Successfully connected to localhost:13003 207 207 [ptrlib]$ yoshiking: hi!!!! flag!!!! here!!! wowowowowo~~~~~~ KosenCTF{yoshiking_is_clever_and_wild_god_of_crypt} ^C[+] close: Connection to localhost:13003 closed ``` ## 感想 interkosenだとmediumかhardぐらい? legendre symbolの勉強になった
×
Sign in
Email
Password
Forgot password
or
Sign in via Google
Sign in via Facebook
Sign in via X(Twitter)
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
Continue with a different method
New to HackMD?
Sign up
By signing in, you agree to our
terms of service
.