# CIS ACTVN 2024 - Crypto
# **Guessme**
```python
from secp256k1 import *
import secrets
from hashlib import sha256
from flag import FLAG
import json
def gen_publickey(a, gen_proof=False): # generate public key with a Schnorr proof
A = multiply(G, a)
if gen_proof:
k = secrets.randbelow(N)
R = multiply(G, k)
e = int(sha256((str(P) + str(Gx) + str(Gy) +
str(A[0]) + str(A[1]) + str(R[0]) + str(R[1])).encode()).hexdigest(), 16)
z = (e*a + k) % N
assert multiply(G, z) == add(multiply(A, e), R)
return A, (R, z)
return A
def verify_publickey(A, proof):
R, z = proof
e = int(sha256((str(P) + str(Gx) + str(Gy) +
str(A[0]) + str(A[1]) + str(R[0]) + str(R[1])).encode()).hexdigest(), 16)
return multiply(G, z) == add(multiply(A, e), R)
def round(b):
print("Give me your public key and its Schnorr proof.")
user_input = input()
user_input = json.loads(user_input)
A = user_input["publickey"]
proof = user_input["proof"]
assert verify_publickey(A, proof)
bit = secrets.randbits(1)
if bit:
B = add(multiply(G, b), A)
else:
B = multiply(G, b)
print(f"B = {B}")
print("Guess my bit.")
user_input = input()
user_input = json.loads(user_input)
guess = user_input["bit"]
if guess == bit:
return True
else:
return False
if __name__ == "__main__":
for i in range(1, 129):
try:
print(f"Round [{i}/128]")
res = round(secrets.randbelow(N))
if not res:
break
except:
break
if i == 128:
print(FLAG)
```
Challenge có tổng cộng 128 vòng, mỗi vòng có 2 lần kiểm tra:
**Lần 1:**
```python
def verify_publickey(A, proof):
R, z = proof
e = int(sha256((str(P) + str(Gx) + str(Gy) +
str(A[0]) + str(A[1]) + str(R[0]) + str(R[1])).encode()).hexdigest(), 16)
return multiply(G, z) == add(multiply(A, e), R)
...
assert verify_publickey(A, proof)
```
**Lần 2:**
```python
bit = secrets.randbits(1)
if bit:
B = add(multiply(G, b), A)
else:
B = multiply(G, b)
print(f"B = {B}")
print("Guess my bit.")
user_input = input()
user_input = json.loads(user_input)
guess = user_input["bit"]
if guess == bit:
return True
else:
return False
```
Ta thấy chương trình không kiểm tra điểm ra nhập có thuộc đường cong $secp256k1$ hay không, nên nếu ta gửi một điểm $A$ không thuộc $secp256k1$ thì có thể vượt qua Lần 2 dễ dàng bằng cách kiểm tra xem điểm $B$ được trả về có thuộc đường còng $secp256k1$ hay không, nếu có thì `bit = 0` - nếu không thì `bit = 1`
Để vượt qua Lần 1, ta chỉ cần chọn điểm $A = (0, 1)$ (không thuộc $secp256k1$) và tìm điểm $R = (0, k)$ sao cho thỏa `multiply(G, z) == add(multiply(A, e), R)` → $k = 2$
```python
from secp256k1 import *
import secrets
from hashlib import sha256
import json
from pwn import *
P = 2**256 - 2**32 - 977
N = 115792089237316195423570985008687907852837564279074904382605163141518161494337
A = 0
B = 7
Gx = 55066263022277343669578718895168534326250603453777594175500187360389116729240
Gy = 32670510020758816978083085130507043184471273380659243275938904335757337482424
G = cast("PlainPoint2D", (Gx, Gy))
conn = remote('103.173.227.108', 10001)
z = N
Ax = 0
Ay = 1
Rx = 0
Ry = 2
R = cast("PlainPoint2D", (Rx, Ry))
A = cast("PlainPoint2D", (Ax, Ay, 1))
e = int(sha256((str(P) + str(Gx) + str(Gy) +
str(A[0]) + str(A[1]) + str(R[0]) + str(R[1])).encode()).hexdigest(), 16)
z = N
for i in range(128):
conn.recvline()
conn.recvline()
to_send = json.dumps({"publickey": A, "proof": [R, z]})
conn.sendline(to_send.encode())
conn.recvuntil(b'B = ')
B = eval(conn.recvline().strip().decode())
bit = 1
Bx, By = B
if By**2 % P == (Bx**3 + + 7) % P:
bit = 0
conn.recvline()
ts = json.dumps({"bit" : bit})
conn.sendline(ts)
conn.interactive()
CIS2024{n0_n33d_t0_gu3ss_1F_y0u_h4v3_1nv4L1D_Curv3_4tt4ck}
```
# **Chamomile Chameleon 1**
source:
```rust
from os import urandom
from hashlib import sha256
import string
import json
# from flag import FLAG
FLAG = b'hello'
num_layers = 11
class Server:
def __init__(self) -> None:
self.N = 2**num_layers
self.tree = []
self.users = []
self.k = urandom(192)
self.p = 0xffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca237327ffffffffffffffff
self.q = (self.p - 1) // 2
self.g = 0x2
self.x = int.from_bytes(self.k, "big")
self.y = pow(self.g, self.x, self.p)
self.buile_merkle_tree()
self.root = self.get_root_hash()
def keyed_random(self, identifier: bytes, index: int) -> int: # -> not random
return int.from_bytes(sha256(identifier + self.k + str(index).encode()).digest(), "big")
def chameleon_hash(self, m: int, r: int) -> bytes:
# g^m * y^r mod p
return (pow(self.g, m, self.p) * pow(self.y, r, self.p) % self.p).to_bytes(192, "big")
def chameleon_extract(self, m1: int, r1: int, m2: int) -> int:
# r2 = (x * r1 + m1 - m2) * x^-1 mod q
r2 = (self.x * r1 + m1 - m2) * pow(self.x, -1, self.q) % self.q
return r2
def get_leaf(self, index: int) -> bytes:
# (H(m + k + index ), H(r + k + index)) -> (m, r)
# g^m * y^r mod p
return self.chameleon_hash(self.keyed_random(b"m", index), self.keyed_random(b"r", index))
def buile_merkle_tree(self) -> None:
# create N leaf nodes
curr_layer = [self.get_leaf(i) for i in range(self.N)]
self.tree.append(curr_layer)
while len(curr_layer) != 1:
curr_layer = [sha256(self.tree[-1][i]+self.tree[-1][i+1]).digest()
for i in range(0, len(self.tree[-1]), 2)]
self.tree.append(curr_layer)
def get_root_hash(self) -> str:
return self.tree[-1][0].hex()
def add_user(self, username: str) -> str:
if not all(char in string.printable for char in username):
return "Invalid username."
if username in self.users:
return "Username already exists."
if "Chamomile" in username:
return "No Chamomile no..."
else:
index = len(self.users) % self.N
self.users.append(username)
r = self.chameleon_extract(self.keyed_random(b"m", index), self.keyed_random(
b"r", index), int.from_bytes(sha256(username.encode()).digest(), "big"))
assert self.chameleon_hash(int.from_bytes(
sha256(username.encode()).digest(), "big"), r) == self.get_leaf(index)
proof = {"username": username, "r": hex(r), "index": index}
path = []
for layer in self.tree[:-1]:
is_right_node = index % 2
sibling_index = index - 1 if is_right_node else index + 1
path.append(layer[sibling_index].hex())
index //= 2
proof["path"] = path
return json.dumps(proof)
def verify_user(self, proof: str) -> str:
proof = json.loads(proof)
username = proof["username"]
index = proof["index"]
assert 0 <= index < self.N
tmp = self.chameleon_hash(int.from_bytes(
sha256(username.encode()).digest(), "big"), int(proof["r"], 16))
for level in range(num_layers):
is_right_node = index % 2
if is_right_node:
tmp = sha256(bytes.fromhex(
proof["path"][level]) + tmp).digest()
else:
tmp = sha256(
tmp + bytes.fromhex(proof["path"][level])).digest()
index //= 2
if tmp.hex() == self.root:
if username == "Chamomile":
return FLAG
return f"Welcome back, {username}."
else:
return "Invalid user."
def main():
print("Wait for the server to initialize...")
s = Server()
print(f"Public committed root: {s.root}")
print(f"Chameleon public key: {hex(s.y)}")
while True:
try:
user_input = input()
user_input = json.loads(user_input)
option = user_input["option"]
if option == 1:
msg = s.add_user(user_input["username"])
elif option == 2:
proof = input()
msg = s.verify_user(proof)
else:
print("Invalid option, goodbye.")
return
print(msg)
except:
return
if __name__ == "__main__":
main()
```
Để lấy được flag ta phải tạo proof tương ứng với giá trị của username là `Chamomile`
**Điểm cần chú ý:**
`self.buile_merkle_tree()`: Ta thấy cây merkle với N lá được tạo ra ngay từ đầu và không hề có sự thay đổi diễn ra → với bất kì giá trị của username nào được thêm thì giá trị của các lá vẫn sẽ không thay đổi. Như vậy việc khai thác thông qua thay đổi phần tử trong cây merkle là không hợp lý.
> hint: This server can store unlimited users, but the tree can only hold N entries. Where is the leaf for the (N+1)-th user ?
>
Thông qua hint được cung cấp, trước mắt ta cần kiểm tra phương thức khởi tạo user của challenge
```python
index = len(self.users) % self.N
self.users.append(username)
r = self.chameleon_extract(self.keyed_random(b"m", index), self.keyed_random(
b"r", index), int.from_bytes(sha256(username.encode()).digest(), "big"))
assert self.chameleon_hash(int.from_bytes(
sha256(username.encode()).digest(), "big"), r) == self.get_leaf(index)
proof = {"username": username, "r": hex(r), "index": index}
path = []
for layer in self.tree[:-1]:
is_right_node = index % 2
sibling_index = index - 1 if is_right_node else index + 1
path.append(layer[sibling_index].hex())
index //= 2
proof["path"] = path
return json.dumps(proof)
```
Có thể hiểu đơn giản như sau, server sẽ cung cấp cho ta một giá trị $r_2$ sao cho từ $r_2$, username, index và $r_2$ có thể tính lại giá trị đại nút lá mà `username` đại diện trong cây merkle:
```python
assert self.chameleon_hash(int.from_bytes(
sha256(username.encode()).digest(), "big"), r) == self.get_leaf(index)
```
Và $r_2$ mà ta được sever cung cấp được khởi tạo như sau:
$$
r_2 = (x r_1 + m_1 - m_2) x^{-1} \mod q
$$
Tiếp đến ta sẽ liên hệ với hint đã được author cung cấp, giả sử ta gọi hàm `add_user` lần thứ $N+1$ thì index của user sẽ trở về $0$ (`index = len(self.users) % self.N`). Lúc này ta sẽ được 2 phương trình như sau:
$$
r_2 = (x r_1 + m_1 - m_2) x^{-1} \mod q
$$
$$
r_2' = (x r_1 + m_1 - m_2') x^{-1} \mod q
$$
Như vậy, để tính lại $x$ ta chỉ cần trừ 2 phương trình cho nhau:
$$
r_2 - r_2' = x^{-1} (m_2' - m_2) \mod q
$$
$$
x = (m_2' - m_2) (r_2 - r_2')^{-1} \mod q
$$
Khi có được $x$ ta dễ dàng tính $k$ và khởi tạo một giá trị $r$ mới sao cho phù hợp với username là `Chamomile`
```python
from pwn import *
import json
from tqdm import tqdm
from hashlib import sha256
num_layers = 11
N = 2**num_layers
p = 0xffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca237327ffffffffffffffff
q = (p - 1) // 2
g = 0x2
# conn = process(['python3', 'chameleon.py'])
conn = remote('103.173.227.108', 10002)
conn.recvuntil(b"root: ")
root = int(conn.recvline().strip().decode() , 16)
conn.recvuntil(b"Chameleon public key: ")
pk = int(conn.recvline().strip().decode()[2:], 16)
user1 = 'a'
user2 = 'b'
tosend = json.dumps({"option": 1, "username": user1})
conn.sendline(tosend.encode())
rs1 = json.loads(conn.recvline().strip().decode())
print(rs1["index"])
for i in tqdm(range(N - 1)):
tosend = json.dumps({"option": 1, "username": str(i)})
conn.sendline(tosend.encode())
rs = json.loads(conn.recvline().strip().decode())
print(rs["index"])
tosend = json.dumps({"option": 1, "username": user2})
conn.sendline(tosend)
rs2 = json.loads(conn.recvline().strip().decode())
print(rs2["index"])
x_inv = eval(rs1["r"]) - eval(rs2["r"])
t1 = int.from_bytes(sha256(user1.encode()).digest(), "big")
t2 = int.from_bytes(sha256(user2.encode()).digest(), "big")
x = pow(x_inv*pow(t2-t1, -1, q), -1, q)
assert pk == pow(g, x, p)
k = x.to_bytes(192, "big")
m = int.from_bytes(sha256(b"m" + k + str(0).encode()).digest(), "big")
r = int.from_bytes(sha256(b"r" + k + str(0).encode()).digest(), "big")
username = b'Chamomile'
m2 = int.from_bytes(sha256(username).digest(), "big")
r2 = (x * r + m - m2) * pow(x, -1, q) % q
path = rs2["path"]
tosend = json.dumps({"option": 2})
conn.sendline(tosend.encode())
tosend = json.dumps({"username": username.decode(), "index": 0, "r": hex(r2), "path": path})
conn.sendline(tosend.encode())
conn.interactive()
#CIS2024{Ch4m3l30n_h4sh_C0ll1s10n_m4y_r3v34l_Tr4pd00r}
```
# **Chamomile Chameleon 2**
source: thay đổi một chút so với **Chamomile Chameleon 1**
```python
def add_user(self, username: str) -> str:
if not all(char in string.printable for char in username):
return "Invalid username."
if username in self.users:
return "Username already exists."
if "Chamomile" in username:
return "No Chamomile no..."
else:
index = len(self.users) % self.N
self.users.append(username)
r = self.chameleon_extract(self.keyed_random(b"m", index), self.keyed_random(
b"r", index), self.keyed_random(username.encode(), index))
assert self.chameleon_hash(self.keyed_random(
username.encode(), index), r) == self.get_leaf(index)
proof = {"username": username, "r": hex(r), "index": index}
path = []
for layer in self.tree[:-1]:
is_right_node = index % 2
sibling_index = index - 1 if is_right_node else index + 1
path.append(layer[sibling_index].hex())
index //= 2
proof["path"] = path
return json.dumps(proof)
def verify_user(self, proof: str) -> str:
proof = json.loads(proof)
username = proof["username"]
index = proof["index"]
assert 0 <= index < self.N
tmp = self.chameleon_hash(self.keyed_random(
username.encode(), index), int(proof["r"], 16))
for level in range(num_layers):
is_right_node = index % 2
if is_right_node:
tmp = sha256(bytes.fromhex(
proof["path"][level]) + tmp).digest()
else:
tmp = sha256(
tmp + bytes.fromhex(proof["path"][level])).digest()
index //= 2
if tmp.hex() == self.root:
if username == "Chamomile":
return FLAG
return f"Welcome back, {username}."
else:
return "Invalid user."
```
Lúc này thay vì dùng `sha256(username.encode()).digest(), "big"), r)` như challenge trên thì sever dùng `keyed_random(username.encode(), index)` có cơ chế như sau:
```python
def keyed_random(self, identifier: bytes, index: int) -> int:
return int.from_bytes(sha256(identifier + self.k + str(index).encode()).digest(), "big")
```
Như vậy ta không thể tính trước được $m_2$ vì không biết được $k$. Sau khi xem xét kĩ các đặc tính của tham số thì nhận thấy như sau các giá trị m được hash bằng sha256 → tối đa 32 bytes, trong khi độ lớn của giá trị $x$ nếu qui ra bytes là `192 bytes`.
Như vậy có thể thấy m nhỏ hơn rất nhiều so với $x$. Nếu ta lập được một lattice sử dụng mối quan hệ này thì khả năng cao là sẽ thu được một thứ gì đó có giá trị.
Tiếp đến, bằng phương pháp đã đề cập ở trên, ta thu được 3 phương trình sau:
$$
r_1 = (x r_0 + m_0 - m_1) x^{-1} \mod q
$$
$$
r_2 = (x r_0 + m_0 - m_2) x^{-1} \mod q
$$
$$
r_3 = (x r_0 + m_0 - m_3) x^{-1} \mod q
$$
Biến đổi một chút:
$$
r1 - r2 = x^{-1}(m_2 - m_1) \mod q
$$
$$
r2 - r3 = x^{-1}(m_3 - m_2) \mod q
$$
Như vậy nếu ta lập ma trận như sau:
$$
B =
\begin{pmatrix}
q & 0 \\
0 & q \\
r_1 - r_2 & r_2 - r_3
\end{pmatrix}
$$
Thì có khả năng ta sẽ nhận được vector có dạng:
$$
(\pm(m_1 - m_2), \pm(m_2 - m_3))
$$
Như vậy ta đã có thể tính lại $x$ và tìm được giá trị $r$ thỏa mãn cho username là `Chamomile`
```python
from pwn import *
import json
from tqdm import tqdm
from hashlib import sha256
num_layers = 11
N = 2**num_layers
p = 0xffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca237327ffffffffffffffff
q = (p - 1) // 2
g = 0x2
while True:
#conn = process(['python3', 'chameleon.py'])
conn = remote('103.173.227.108', 10003)
conn.recvuntil(b"root: ")
root = int(conn.recvline().strip().decode() , 16)
conn.recvuntil(b"Chameleon public key: ")
pk = int(conn.recvline().strip().decode()[2:], 16)
user1 = 'a'
user2 = 'b'
user3 = 'c'
tosend = json.dumps({"option": 1, "username": user1})
conn.sendline(tosend.encode())
rs1 = json.loads(conn.recvline().strip().decode())
print(rs1["index"])
for i in tqdm(range(N - 1)):
tosend = json.dumps({"option": 1, "username": str(i)})
conn.sendline(tosend.encode())
rs = json.loads(conn.recvline().strip().decode())
tosend = json.dumps({"option": 1, "username": user2})
conn.sendline(tosend.encode())
rs2 = json.loads(conn.recvline().strip().decode())
print(rs2["index"])
for i in tqdm(range(N - 1)):
tosend = json.dumps({"option": 1, "username": str(i+N)})
conn.sendline(tosend.encode())
rs = json.loads(conn.recvline().strip().decode())
tosend = json.dumps({"option": 1, "username": user3})
conn.sendline(tosend.encode())
rs3 = json.loads(conn.recvline().strip().decode())
print(rs3["index"])
r1 = eval(rs1["r"])
r2 = eval(rs2["r"])
r3 = eval(rs3["r"])
from sage.all import Matrix
m = [
[q, 0 ],
[0, q ],
[r1-r2, r2-r3]
]
m = Matrix(m)
m = m.LLL()
# +-(m1 - m2), +-(m2 - m3)
a,b = list(m[1])
print(m[1])
t1 = a * pow(r2-r1, -1, q) % q
t2 = a * pow(r1-r2, -1, q) % q
x = 0
if pow(g, t1, p) == pk:
x = t1
if pow(g, t2, p) == pk:
x = t2
if x == 0:
print("Failed")
conn.close()
continue
print("x = ", x)
k = x.to_bytes(192, "big")
m = int.from_bytes(sha256(b"m" + k + str(0).encode()).digest(), "big")
r = int.from_bytes(sha256(b"r" + k + str(0).encode()).digest(), "big")
username = b'Chamomile'
m2 = int.from_bytes(sha256(b'Chamomile' + k + str(0).encode()).digest(), "big")
r4 = (x * r + m - m2) * pow(x, -1, q) % q
path = rs3["path"]
tosend = json.dumps({"option": 2})
conn.sendline(tosend.encode())
tosend = json.dumps({"username": username.decode(), "index": 0, "r": hex(r4), "path": path})
conn.sendline(tosend.encode())
f = conn.recvline()
if b'CIS' in f:
print(f)
conn.interactive()
conn.close()
#CIS2024{Ch4m0m1l3_Ch4m3l3On_Ch4m0m1l3_Ch4m3l3On}
```