# ochazuke [InterKosenCTF 2020]
###### tags: `InterKosenCTF2020` `crypto`
## 概要
何がochazukeなのかわからないけどとにかくsageのスクリプトが与えられて、それがサーバで走ってるらしい。
```python=
from Crypto.Util.number import bytes_to_long
from binascii import unhexlify
from hashlib import sha1
import re
EC = EllipticCurve(
GF(0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff),
[-3, 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b]
)
n = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551 # EC.order()
Zn = Zmod(n)
G = EC((0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296,
0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5))
def sign(private_key, message):
z = Zn(bytes_to_long(message))
k = Zn(ZZ(sha1(message).hexdigest(), 16)) * private_key
assert k != 0
K = ZZ(k) * G
r = Zn(K[0])
assert r != 0
s = (z + r * private_key) / k
assert s != 0
return (r, s)
def verify(public_key, message, signature):
r, s = signature[0], signature[1]
if r == 0 or s == 0:
return False
z = Zn(bytes_to_long(message))
u1, u2 = z / s, r / s
K = ZZ(u1) * G + ZZ(u2) * public_key
if K == 0:
return False
return Zn(K[0]) == r
if __name__=="__main__":
from secret import flag, d
public_key = ZZ(d) * G
print("public key:", public_key)
your_msg = unhexlify(input("your message(hex): "))
if len(your_msg) < 10 or b"ochazuke" in your_msg:
print("byebye")
exit()
your_sig = sign(d, your_msg)
print("your signature:", your_sig)
sig = input("please give me ochazuke's signature: ")
r, s = map(Zn, re.compile("\((\d+), (\d+)\)").findall(sig)[0])
if verify(public_key, b"ochazuke", (r, s)):
print("thx!", flag)
else:
print("it's not ochazuke :(")
```
内容はいわゆるECDSAで、楕円曲線のパラメータもいわゆる**prime256v1** というやつで安全そう。とくに捻ったところは見受けられない。
`ochazuke`の署名を作成するのが目的だが、`ochazuke`を含む文字列の署名は受け付けてくれない。
## 解法
ECDSAで異なる平文に対して同じ`k`を使ってはいけないというのは有名な話。だからこの場合も`k = Zn(ZZ(sha1(message).hexdigest(), 16)) * private_key`としてハッシュをとっている。
SHA1で。
まさかな、でもshatteredを使えばこれ解けるんだよな。
```python=
from sage.all import *
from binascii import hexlify, unhexlify
from hashlib import sha1
from ptrlib import Socket
EC = EllipticCurve(
GF(0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff),
[-3, 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b]
)
n = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551 # EC.order()
Zn = Zmod(n)
G = EC((0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296,
0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5))
def sign(private_key, message):
z = Zn(int.from_bytes(message, "big"))
k = Zn(ZZ(sha1(message).hexdigest(), 16)) * private_key
assert k != 0
K = ZZ(k) * G
r = Zn(K[0])
assert r != 0
s = (z + r * private_key) / k
assert s != 0
return (r, s)
def verify(public_key, message, signature):
r, s = signature[0], signature[1]
if r == 0 or s == 0:
return False
z = Zn(int.from_bytes(message, "big"))
u1, u2 = z / s, r / s
K = ZZ(u1) * G + ZZ(u2) * public_key
if K == 0:
return False
return Zn(K[0]) == r
with open("shattered-1.pdf", "rb") as f:
data1 = f.read(320)
with open("shattered-2.pdf", "rb") as f:
data2 = f.read(320)
assert data1 != data2
assert sha1(data1).hexdigest() == sha1(data2).hexdigest()
sock = Socket("localhost", 13005)
x, y = sock.recvregex("([0-9]+) : ([0-9]+)")
pubkey = EC((int(x), int(y)))
sock.sendlineafter("(hex): ", hexlify(data1))
r1, s1 = sock.recvregex("([0-9]+), ([0-9]+)")
sock.close()
sock = Socket("localhost", 13005)
sock.sendlineafter("(hex): ", hexlify(data2))
r2, s2 = sock.recvregex("([0-9]+), ([0-9]+)")
z1 = int.from_bytes(data1, "big")
z2 = int.from_bytes(data2, "big")
k = Zn(z1 - z2) / Zn(int(s1) - int(s2))
d = int((Zn(int(s1)) * k - z1) / Zn(int(r1)))
assert (d*G) == pubkey
ochazuke = b"ochazuke"
r,s = sign(d, ochazuke)
assert verify(pubkey, ochazuke, (r,s))
sock.sendlineafter("signature: ", "({}, {})".format(r, s))
print(sock.recvline())
```
`KosenCTF{ahhhh_ochazuke_oisi_geho!geho!!gehun!..oisii...}`
詰まらせてますやんか。
## 感想
あまりにも脆弱性がわからなさすぎて、まさかな……と思ったが、まさかだった。実際にはmediumくらいの難易度になってそう