Hastad广播攻击
这个题目提示说跟BBB那个题类似,更简单。绝对不更简单。
先放题:
#!/usr/local/bin/python
from Crypto.Util.number import bytes_to_long, getPrime
from random import randint
from math import gcd
from os import urandom
def generate_key(rng, seed):
e = rng(seed)
while True:
for _ in range(randint(10,100)):
e = rng(e)
p = getPrime(1024)
q = getPrime(1024)
phi = (p-1)*(q-1)
if gcd(e, phi) == 1:
break
n = p*q
return (n, e)
def generate_params():
p = getPrime(1024)
b = randint(0, p-1)
return (p,b)
def main():
p,b = generate_params()
print("[+] The parameters of RNG:")
print(f"{b=}")
print(f"{p=}")
a = int(input("[+] Inject b[a]ckdoor!!: "))
rng = lambda x: (a*x + b) % p #通过控制a,使rng(11)==11 从而控制e
keys = []
seeds = []
for i in range(5):
seed = int(input("[+] Please input seed: "))
seed %= p
if seed in seeds:
print("[!] Same seeds are not allowed!!")
exit()
seeds.append(seed)
n, e = generate_key(rng, seed)
if e <= 10:
print("[!] `e` is so small!!")
exit()
keys.append((n,e))
FLAG = open("flag.txt", "rb").read()
assert len(FLAG) < 50
FLAG = FLAG + urandom(4)
for n,e in keys:
r = urandom(16)
flag = bytes_to_long(FLAG + r)
c = pow(flag, e, n)
r = r.hex()
print("[+] Public Key:")
print(f"{n=}")
print(f"{e=}")
print(f"{r=}")
print("[+] Cipher Text:", c)
if __name__ == "__main__":
main()
一,题目通过rng生成e,可以输入rng的a作为后门这个思路与那个题相同。只不这个题是1次方程,2次方程的话有两个根,再从两个根向前推,得到5个根。这些根在一定次数运算后会得到11循环。但1次只有一个根。
看了两个WP有两个方法:
1,先求5次有限域p,1的5次方根,任选其一作为a,通过rng得到5个e。这些e中有1个11,其它的在rng运算几次后也会得到11这5个根是个循环,当这个根为偶数时会因为gcd(e,phi) != 1被跳过。大概率两个偶数的情况下5次运算会得到3个e=11
def gen_cycle(p, b):
e = 11
R.<x> = PolynomialRing(Zmod(p), "x")
eq = x**5 - 1
roots = eq.roots() #有限域求5次根不为1的任取其一
for root, _ in roots:
if root == 1:
continue
a = root
seeds = [e]
for i in range(4): #代入rng 使e1=11,e2=a*e1+b,e3=a*e2+b,...e5=a*e4+b,11=a*e5+b
seeds.append(rng(a, seeds[-1], b, p)) #由于e中有偶数,当检查gcd(e,phi)==1不通过,将有大部分落到e1上得到多个e=11的加密结果
if len(set(seeds)) != len(seeds):
continue
assert rng(a, seeds[-1], b, p) == seeds[0]
return a, seeds
assert False
2,求3次根,这样可以得到3个e,如果除11外另外两个都不互素(比如都是偶数)则可以得到3个e=11,这个概率是0.25也不算低。
def gen_cycle(p, b):
PR.<a> = PolynomialRing(GF(p))
rng = lambda x: (a*x + b)
f = rng(rng(rng(11))) - 11
a = f.roots()[0][0]
es = [11,rng(11), rng(rng(11))]
if es[1] != 11:
return a,es
assert False
二,这题的m后部被填充两次。第1次被填充使得不能通过多次攻击得到flag,第2次填充(已给出)使得5次加密时的明文都不同。所以这里用到关联信息攻击。
中国剩余定理的常见情况是m相同,当m不同的情况下先放入算式求crt后再用copper_smith法求解。
def Hastad(N, R, C): #模,已知填充,密文
e = 11
P.<x>=PolynomialRing(ZZ)
eq = [(x*2**(128) + R[i]) ^ e - C[i] for i in range(len(R))]
mod=prod(N)
ff=crt(eq,N)
Q.<x>=PolynomialRing(Zmod(mod))
ff=Q(ff)
ff=ff.monic()
flag = ff.small_roots(X=2 ** (8 * (53) ) , epsilon=0.03) #len(flag)<50,r0=4
return long_to_bytes(flag)
然后就是弄个循环去碰
from pwn import *
def attack():
e = 11
io = remote("mc.ax", 31340)
io.recvuntil(b"[+] The parameters of RNG:\n")
b = int(io.recvline()[2:])
p = int(io.recvline()[2:])
try:
a, seeds = gen_cycle(p, b)
except Exception as e:
io.close()
return False
io.sendlineafter(b"[+] Inject b[a]ckdoor!!: ", str(a).encode())
for seed in seeds:
io.sendlineafter(b"[+] Please input seed: ", str(seed).encode())
ns, rs, cs = [], [], []
for _ in range(5):
io.recvuntil(b"[+] Public Key:\n")
n = int(io.recvline)[2:])
tmpe = int(io.recvline()[2:])
r = int(io.recvline()[2:].strip("'"), 16)
io.recvuntil(b"[+] Cipher Text: ")
c = int(io.recvline())
if tmpe == e and len(ns) < 3:
ns.append(n)
rs.append(r)
cs.append(c)
print(len(ns))
if len(ns) < 3:
io.close()
return False
flag = Hastad(ns, rs, cs)
print(flag)
io.close()
return True
while True:
if attack():
break