哈工大新生赛,记录3道Cyrpto 0解题
 
isoDream 
题目描述 
1 2 3 4 5 又是一个漫长的夏夜,你合上书本观察起房间里的吊灯,萦回的光影仿佛在告诉你它的补空间的基本群是<a,b|a^2=b^3> “是时候去睡觉了”,你想着,期待着在梦里能遇见更加广阔的域,好让你用除法狠狠地切割那些多项式,把它们分解成再不分离的素元。 你闭上眼,耳边似乎听见某种曲线的纽结。你顺着一条椭圆的同源路径走去,远方的地平线闪烁着超奇异的光芒,群和域的边界正缓缓向你靠近。 Author: Swizzer 
 
题目附件 
chall.py 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 from  sage.all  import  GF, EllipticCurve, polygen, PolynomialRing, Zmodfrom  Crypto.Util.number import  getPrime, isPrime, bytes_to_longimport  randomdef  getSafePrime (bits: int  ) -> int :  while  True :     p = getPrime(bits - 1 )     q = 2  * p + 1      if  isPrime(q):       return  q def  encrypt (m: int , p: int , exponents: list [int ] ):  modulus = p**3    y = PolynomialRing(Zmod(modulus), "x" ).quotient("x**3 + x + 1" , "y" ).gen()   g = 13  * y + 37    powers = [pow (m, e, modulus) for  e in  exponents]   return  [g**e for  e in  powers] def  transform (secret: int  ):  while  True :     try :       p = getSafePrime(384 )       x = polygen(GF(p))       F = GF(p**2 , "i" , modulus=x**2  + 1 )       j = F(secret)       E = EllipticCurve(j=j)       kerl = E(0 ).division_points(5 )[1 :]       E2 = E.isogeny(random.choice(kerl)).codomain()       return  p, E2.j_invariant()     except  Exception as  _:       continue  if  __name__ == "__main__" :  m = bytes_to_long(open ("flag.txt" , "rb" ).read().strip())   p_m, (re, im) = transform(m)   p = getSafePrime(256 )   pt = int (re + im)      exps = [(getPrime(384 ) - 1 ) * p for  _ in  range (8 )] + [getPrime(384 ) * (p - 1 ) for  _ in  range (8 )]      ct = encrypt(pt, p, exps)   print (p_m)   print (exps)   print (ct) 
还有一个输出文件: output.txt 
解题思路 
首先用flag作为j不变量生成了一个曲线,然后又生成了这条曲线的一个度为5的同源曲线,然后对同源曲线的j不变量传入encrypt,得到输出。
所以思路就是先逆向encrypto,然后拿到同源曲线,计算相邻的曲线的j不变量就是flag。
encrypto函数流程如下:
对消息m进行8次加密:\(c_i = m^{e_i} \mod
p^3\)  
在\(\mathbb{Z}_{p^3}[x]/(x^3 + x +
1)\) 上求\(g^{c_i}\) ,其中\(g = 13x + 37\)  
 
首先是8个DLP,且基环是\(\mathbb{Z}_{p^3}[x]/(x^3 + x +
1)\) ,但是由于 \(x^3 + x + 1\) 
在 \(\mathbb{Z}_{p^3}\) 
上能完全分解,所以其实只用在 \(\mathbb{Z}_{p^3}\)  上求解DLP 。\(\mathbb{Z}_{p^k}\) 上的DLP可以参考[3] 。
所以可以得到8个 \(c_i \mod
p^2\) ,之后不能直接套用RSA解密,因为加密指数和 \(\phi(p^2)\) 
有较大的公因子。所以要换一个思路:共模攻击。这样加密指数就会降到2,然后再当e=2的RSA解就行。
最后,相邻同源可以用classical_modular_polynomial关联起来。
exp 
1 2 3 4 5 6 7 8 9 10 11 12 def  padic_dlp (g:int , y:int , p:int , s:int  ) -> int :    """Solve p-adic DLP      refer to https://math.stackexchange.com/questions/1863037/discrete-logarithm-modulo-powers-of-a-small-prime          return x mod(p^(s - 1)) that satisify g^x = y mod(p^s)     """     def  theta (k, p, s ):         return  (pow (k, (p - 1 ) * p ** (s - 1 ), p ** (2  * s - 1 )) - 1 ) // (p ** s)     g, y, p, s = int (g), int (y), int (p), int (s)     return  pow (theta(g, p, s), -1 , p ** (s - 1 )) * theta(y, p, s) % (p ** (s - 1 )) 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 from  sage.all  import  GF, EllipticCurve, polygen, PolynomialRing, Zmod, factor, vector, Qp, lcm, gcd, xgcd, classical_modular_polynomialfrom  Crypto.Util.number import  getPrime, isPrime, bytes_to_long, GCD, long_to_bytesimport  randomfrom  ctf_tools.crypto.padic_DLP import  padic_dlpPhi5 = classical_modular_polynomial(5 ) with  open ("/home/root_xu/CTF/Challenge/2025/H3CTF/isoDream/output.txt" , "r" ) as  f:    pm = int (f.readline())     exps = eval (f.readline())     ct = f.readline() p = gcd(exps[:8 ]) // 2  assert  isPrime(p)F = PolynomialRing(Zmod(p**3 ), "x" ) x = F.gen() ct = eval (ct, globals ={"y" : x}) fp = x**3  + x + 1  fp = fp.change_ring(Qp(p, 3 )) roots = [Zmod(p**3 )(root) for  root in  fp.roots(multiplicities=False )] f = x**3  + x + 1  g = 13  * x + 37  g_eval = g(roots[0 ]) cl = [] for  c in  ct:    c_eval = c(roots[0 ])     rsa_c = padic_dlp(int (g_eval), int (c_eval), p, 3 )     cl.append(rsa_c) e0, e1 = exps[7 ], exps[8 ] c0, c1 = cl[7 ], cl[8 ] _, s, t = xgcd(e0, e1) c2 = (pow (c0, s, p**2 ) * pow (c1, t, p**2 )) % (p**2 ) f = x**2  - c2 f = f.change_ring(Qp(p, 2 )) pts = [int (Zmod(p**2 )(root)) for  root in  f.roots(multiplicities=False )] x = polygen(GF(pm)) F = GF(pm**2 , "i" , modulus=x**2  + 1 ) for  pt in  pts:    if  pt.bit_length() > 400 :         continue      j = F(pt)     ms = Phi5(X = j).univariate_polynomial().change_ring(F).roots(multiplicities=False )     for  m in  ms:         print (long_to_bytes(int (m))) 
 
Another Encrypt And Decrypt 
题目描述 
1 2 Another...? 有问题请联系@ZhouMo hint1 : Forbidden Attack 
 
题目附件 
附件太多了,这里就放个压缩包: 
handout.zip 
接口分析 
题目一共有2个重要文件:client.py 和 server.py
,他们共享一个AES-GCM的密钥,提供如下接口:
client.py : 
可以发送包含 username 字段的POST数据包,且必须满足
2 <= len(username) <= 10,然后将其封装为 
payload_bytes = json.dumps({"route": "/chat", "data": username}).encode("utf-8")
交给AES-GCM加密,然后把
username、nonce、data
发送给server, data 包含密文和消息验证码。
server.py : 
可发送POST数据包,且必须包含
username、nonce、data
这3个字段。server会从 data
中提取密文和消息验证码,然后进行解密和验证,如果验证失败会返回错误。成功验证后,
如果解密消息的 routre 字段为 "/chat"
的话,会从 GREETINGS 里随机选一个作为明文。 
如果解密消息的 routre 字段为 "/getflag"
的话,则将FLAG作为明文。 
 
然后生成
nonce = username + get_random_bytes(12 - len(username))
,用它对明文进行加密并返回 data 、
nonce,data 包含密文和消息验证码。
解题思路 
题目提示了
forbidden attack,去网上搜搜能发现github上有讲解文章[1] 。 
就这题顺便学习了一下GCM模式,参考[2] 。
首先只能借助client的接口,因为GCM是带验证的模式,我们没有密钥来生成消息验证码。
下面是GCM模式的认证加密过程: 
\(C\)  是CRT模式加密的密文,\(A\)  是关联消息,\(H\)  是 \(GHASH\)  的密钥,其中 \(GHASH(H, A, C) = X_{m + n + 1}\) ,\(X_i\) 定义如下: 
由于题目未设置关联信息,所以 \(m=0\) 。 而且这里使用的 \(\cdot\)  是 \(GF(2^{128})\)  上的乘法,且异或对应 \(GF(2^{128})\)  上的加法,所以 \(GHSH(H, A, C)\) 可以在 \(GF(2^{128})\) 上 表示成如下形式: \[
GHASH(H, A, C) = C_1H^n + C_2H^{n - 1} + \cdots + C_nH
\] 
消息验证码 \(T\)  可以表示成: \[
T = C_1H^n + C_2H^{n - 1} + \cdots + C_nH + S\\
S = E(K, Y_0)
\] 
由于初始向量长度是96bits的时候,初始计数器 \(Y_0\)  只与 \(IV\)  有关,这里的 \(IV\)  其实就算服务端使用的
nonce。而服务器使用的 nonce
的前面10bytes字节都是可控的,只有最后2bytes是随机的。那么根据生日攻击的原理,只要我们用恒定的10bytes的
username 多次交互,就很有可能出现 nonce
复用的情况。这样会能到2个消息认证码 \(T,T'\) : \[
\begin{aligned}
T = C_1H^n + C_2H^{n - 1} + \cdots + C_nH + S\\
T' = C'_1H^n + C'_2H^{n - 1} + \cdots + C'_nH + S
\end{aligned}
\] 
这是一个二元二次方程组,可以解得 \(H,S\) 。之后,为了加密,我们还要获取密钥流,这点非常简单,由于使用CRT模式,将明文与密文异或即可活动密钥流。密钥流是和
\(IV\) 相关的,也就是一个 \(IV\)  对应一个密钥流。
一切准备工作都做好后,我们就可以将 route
字段的消息设置为 /getflag,然后选一个前面重复出现过的
nonce,用它对应的密钥流来加密消息,同时用它对应的 \(H,S\) 
来计算消息认证码。发送给server后,服务器会将加密的FLAG发送给我们。为了解密,我们要获取到服务器使用的
nonce
对应的密钥流,所以可以接着向client发送消息,来查看是否出现了加密flag使用的
nonce ,由于有2个随机字节,所以每次有 \(\frac{1}{2^{16}}\) 
的概率成功。为了提高成功的概率,可以多获得几次加密的flag,比如1000次,这样每次成功的概率会达到
\(\frac{1000}{2^{16}}\) 。最后还有一个问题是flag可能比较长,有些通过异或获取的密钥流长度可能不够,解决的方法就是多爆几次。
exp 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 import  requestsimport  jsonimport  randomfrom  tqdm import  trangefrom  sage.all  import  GF, PolynomialRingfrom  os import  urandomMAP_GREETINGS = {     26 : b'{"result": "Hello World!"}' ,      27 : b'{"result": "BV1iXXhYpEcw!"}' ,      31 : b'{"result": "Capture The Flag!"}' ,      32 : b'{"result": "Welcome To H^3CTF!"}' ,     39 : b'{"result": "Miss Suzuran is our light"}' ,     43 : b'{"result": "You\'ve found the secret of 42"}'  } CLIENT = "http://127.0.0.1:5001/"  SERVER = "http://127.0.0.1:5000/"  R = 0xe1000000000000000000000000000000  F = GF(2 **128 , modulus=[int (i) for  i in  bin (R)[2 :].zfill(128 )] + [1 ], name="x" ) def  bytes2GF (a: bytes  ):    assert  len (a) == 16      return  F.from_integer(int (bin (int .from_bytes(a, byteorder='big' ))[2 :].zfill(128 )[::-1 ], 2 )) def  GF2bytes (a ):    return  int (bin (a.to_integer())[2 :].zfill(128 )[::-1 ], 2 ).to_bytes(16 , byteorder='big' ) def  pad (x: bytes  ):    return  x + b"\x00"  * (-len (x) % 16 ) request_data = {"username" : "a" *10 } print ("search for plain-cipher pairs with the same nonce" )nonces = [] ciphers = [] tags = [] pairs = [] for  _ in  trange(10000 ):    if  len (pairs) == 2 :         break      response = requests.post(CLIENT + "/send" , data=request_data)     data = response.json()     nonce = bytes .fromhex(data["nonce" ])     cipher = bytes .fromhex(data["data" ])[:-16 ]     tag = bytes .fromhex(data["data" ])[-16 :]     if  nonce in  nonces:         idx = nonces.index(nonce)         if  len (cipher) == len (ciphers[idx]):             continue          pairs.append((nonce, (cipher, ciphers[idx]), (tag, tags[idx])))     nonces.append(nonce)     ciphers.append(cipher)     tags.append(tag) print ("solve authenticate key H and S" )PR = PolynomialRing(F, ['H' , 'S' ]) H, S = PR.gens() roots = [] all_poly = [] for  pair in  pairs:    c1, c2 = pair[1 ]     nonce = pair[0 ]     t1, t2 = pair[2 ]     t1, t2 = bytes2GF(t1), bytes2GF(t2)     polys = []     for  c, t in  [[c1, t1], [c2, t2]]:         X = pad(c) + b"\x00"  * 8  + (len (c) * 8 ).to_bytes(8 , byteorder='big' )         assert  len (X) % 16  == 0          f = 0          for  i in  range (0 , len (X), 16 ):             block = bytes2GF(X[i:i+16 ])             f = (f + block) * H         f += S         f += t         polys.append(f)     all_poly.append(polys)     f1, f2 = polys     poly = (f1 + f2).univariate_polynomial()     roots.append([root[0 ] for  root in  poly.roots()]) r1, r2 = roots r1 = set (r1) r2 = set (r2) H_ = list (r1.intersection(r2))[0 ] S1 = all_poly[0 ][0 ](H=H_).univariate_polynomial().roots()[0 ][0 ] S2 = all_poly[1 ][0 ](H=H_).univariate_polynomial().roots()[0 ][0 ] S_ = [S1, S2] print ("recv encrypted flag" )def  find_maxlen (pairs ):    tmp = [len (pairs[0 ][1 ][0 ]), len (pairs[0 ][1 ][1 ]), len (pairs[1 ][1 ][0 ]), len (pairs[1 ][1 ][1 ])]     tmp = tmp.index(max (tmp))     return  tmp // 2 , tmp % 2  msg = json.dumps({"route" : "/getflag" }).encode() idx0, idx1 = find_maxlen(pairs) nonce = pairs[idx0][0 ] c1 = pairs[idx0][1 ][idx1] m1 = MAP_GREETINGS[len (c1)] xor_key = [i ^ j for  i, j in  zip (m1, c1)] assert  len (msg) <= len (xor_key)print (f"{len (xor_key) = } " )print (f"{m1 = } " )cipher =  bytes ([i ^ j for  i, j in  zip (xor_key, msg)]) X = pad(cipher) + b"\x00"  * 8  + (len (cipher) * 8 ).to_bytes(8 , byteorder='big' ) assert  len (X) % 16  == 0 f = 0  for  i in  range (0 , len (X), 16 ):    block = bytes2GF(X[i:i+16 ])     f = (f + block) * H_ f += S_[idx0] tag = GF2bytes(f) data = {     "username" : "a"  * 10 ,     "nonce" : nonce.hex (),     "data" : (cipher + tag).hex () } nonces = [] ciphers = [] for  _ in  trange(1000 ):    response_json = requests.post(SERVER + "/encrypted" , json=data).json()     cipher = bytes .fromhex(response_json["data" ][:-16 ])     nonce = bytes .fromhex(response_json["nonce" ])          if  nonce not  in  nonces:         nonces.append(nonce)         ciphers.append(cipher)     else :         idx = nonces.index(nonce)         if  len (cipher) > len (ciphers[idx]):             ciphers[idx] = cipher assert  len (ciphers) == len (nonces)print ("finding xor_key for encryptedflag..." )request_data = {"username" : "a" *10 } for  i in  trange(1000 ):    response = requests.post(CLIENT + "/send" , data=request_data)     data = response.json()     nonce = bytes .fromhex(data["nonce" ])     cipher = bytes .fromhex(data["data" ])[:-16 ]     if  nonce in  nonces:         idx = nonces.index(nonce)         xor_key = [a ^ b for  a, b in  zip (cipher, MAP_GREETINGS[len (cipher)])]         flag =  [a ^ b for  a, b in  zip (ciphers[idx], xor_key)]         print (bytes (flag)) 
 
SSS??? 
题目描述 
1 2 3 4 5 6 你,去学密码了吧,和我不认识的多项式一起。 为什么?那个多项式的模数是多少?为什么要跟她一起去?你怎么没有告诉我?我跟她的确不互素,可是我想了解你啊。我最想了解你,最想待在你身边,我希望我是最亲近你的人,而且我不希望自己不是最亲近你的那个人。明明我就想和你变得更要好,你为什么要这么做? 我!讨厌你在我不知道的有限域里对别的多项式露出笑容!也讨厌你和别的多项式做Shamir Secret Sharing!我希望你只和我分享密钥!只待在我身旁! 密码学也是,我也很想学啊! Author: Swizzer 
 
题目附件 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 from  random import  SystemRandomfrom  Crypto.Util.number import  getPrime, isPrime, bytes_to_longrandom = SystemRandom() class  Polynomial :  def  __init__ (self, p: int , deg: int  ):     self .p = p     self .deg = deg     self .coeffs = [random.randint(0 , p) for  _ in  range (self .deg + 1 )]   def  evaluate (self, x: int  ) -> int :     result = 0      for  coeff in  reversed (self .coeffs):       result = (result * x + coeff) % self .p     return  result   def  get_coeffs (self ) -> list [int ]:     return  self .coeffs   def  set_coeff (self, idx: int , value: int  ):     self .coeffs[idx] = value % self .p def  level1 ():  p = 2  * getPrime(512 )   f = Polynomial(p, random.randint(8 , 16 ))   secret = f.get_coeffs()[0 ]   print (f"[*] p: {p} " )   x = int (input ("gimme your x > " ))   assert  0  < x < p, "😡No cheating!"    print (f"[*] share: {f.evaluate(x)} " )   guess = int (input ("🔐 > " ))   assert  guess == secret, "😎Try harder~"    flag = open ("flag1.txt" , "r" ).read().strip()   print ("🥳🥳🥳" )   print (f"[+] Here's level1's flag: {flag} " ) def  level2 ():  p = int (input ("gimme your p > " ))   assert  isPrime(p) and  p.bit_length() > 128 , "😡No cheating!"    f = Polynomial(p, 16 )   for  _ in  range (10 ):     x = int (input ("gimme your x > " ))     assert  0  < x < p, "😡No cheating!"      print (f"[*] share: {f.evaluate(x)} " )   idx, guess = map (int , input ("🔐 > " ).split("," ))   secret = f.get_coeffs()[idx]   assert  guess == secret, "😎Try harder~"    flag = open ("flag2.txt" , "r" ).read().strip()   print ("🥳🥳🥳" )   print (f"[+] Here's level2's flag: {flag} " ) def  level3 ():  p = getPrime(512 )   f = Polynomial(p, 9 )   flag = open ("flag3.txt" , "r" ).read().strip()   assert  len (flag) == 44    secret = bytes_to_long(flag.encode())   f.set_coeff(0 , secret)   xs = [random.randrange(1 , p) for  _ in  range (10 )]   shares = [f.evaluate(x) for  x in  xs]   print (f"[*] p: {p} " )   print (f"[*] xs: {xs} " )   for  i, share in  enumerate (shares, 1 ):     print (hex (share)[:-i] + "?"  * i) def  challenge ():  print ("🥰Welcome to my SSS backroom!" )   while  True :     try :       choice = int (input ("📝Choose a level to escape > " ))       if  choice == 1 :         level1()       elif  choice == 2 :         level2()       elif  choice == 3 :         level3()       else :         print ("🚫Invalid choice." )         exit(0 )     except  Exception as  e:       print (f"😵Error: {e} " ) if  __name__ == "__main__" :  challenge() 
 
解题思路 
一共3个level对应3个flag。level 3为0解题。
level 1 
题目流程如下:
生成了一个秘密多项式 \(f\)  
\(8<degree(f)<16\)  
模数是 \(2p\)  
我们可以选一个 \(x\) ,求值得到
\(f(x)\) ,\(x\)  必须满足 \(0
< x < 2p\) 。 
 
要求我们获取 \(f\) 
的常数项,这里限制了 \(x\)  不能为 \(0\) ,但是可以让 \(x = p\) ,这样在模 \(p\) 意义下就能得到常数项模 \(p\)  的值,常数项有 \(1/2\)  不超过 \(p\) ,所以每次有 \(1/2\) 的成功率。
level 2 
这次要求模数必须是素数,且秘密多项式的度数为16,而我们只能进行10次求值。题目需要我们得到多项式的任意一个系数就行。
思路是选10个10次单位根 \(\{\omega_i
|\omega_i^{10} = 1, i \in \{1, \cdots, 10\}\}\) ,而由于是在模
\(p\) 
意义下,要想找到10个10次单位根,\(p\) 
必须满足 \(10 | p -
1\) ,然后我们把10次单位根带入求职可以得到: \[\begin{aligned}
f(\omega_i) &= a_0 + a_1\omega_i + a_2\omega_i^2 + \cdots +
a_{16}\omega_i^{16}\\
&=(a_0 + a_{10}) + (a_1 + a_{11})\omega_i + \cdots + (a_6 +
a_{16})\omega_i^6 + a_7\omega_i^7 + a_8\omega_i^8 + a_9\omega_i^9
\end{aligned}
\]  这样刚好10个系数10个方程,可以得到 \(a_7, a_8, a_9\) 的精确值。
level 3 
这次生成的多项式度是10,而且题目生成了11个 \(x_i, i \in \{0, 1, \cdots, 10\}\) ,每个
\(f(x_i)\)  都会抹去 \(4i\)  比特低位。记: \[
y_i = y_i^{(h)} + y_i^{(l)}
\] 
\[
\mathbf{M} = \begin{pmatrix}
1 & x_0 & \cdots & x_0^{10} \\
1 & x_1 & \cdots & x_1^{10} \\
\vdots & \vdots &  & \vdots \\
1 & x_{10} & \cdots & x_{10}^{10} \\
\end{pmatrix}
\]  则: \[
\mathbf{M}
\begin{pmatrix}
a_0 \\ a_1 \\ \vdots \\ a_{10}
\end{pmatrix}=
\begin{pmatrix}
y_0\\ y_1 \\ \vdots \\ y_{10}
\end{pmatrix}=
\begin{pmatrix}
y_0^{(h)}\\ y_1^{(h)} \\ \vdots \\ y_{10}^{(h)}
\end{pmatrix}+
\begin{pmatrix}
y_0^{(l)}\\ y_1^{(l)} \\ \vdots \\ y_{10}^{(l)}
\end{pmatrix}
\]  同时左乘\(\mathbf{M}^{-1}\) :
\[
\begin{pmatrix}
a_0 \\ a_1 \\ \vdots \\ a_{10}
\end{pmatrix}=
\mathbf{M}^{-1}
\begin{pmatrix}
y_0^{(h)}\\ y_1^{(h)} \\ \vdots \\ y_{10}^{(h)}
\end{pmatrix}+
\mathbf{M}^{-1}
\begin{pmatrix}
y_0^{(l)}\\ y_1^{(l)} \\ \vdots \\ y_{10}^{(l)}
\end{pmatrix}
\]  其中\(a_0\) 只有384bits,所以单独考虑这一项:
\[
a_0 = b + c_oy_0^{(l)} + c_1y_1^{(l)} + \cdots + c_{10}y_{10}^{(l)}
\]  此时可以造格: \[
(y_0^{l}, y_1^{l}, \cdots, y_{10}^{l}, 1, k)
\begin{pmatrix}
c_0 & 2^{36 + 344} & &\\
c_1 & & 2^{32 + 344} &\\
\vdots & & &\ddots \\
c_{11} &&&&2^{344} \\
b&&&&&2^{384}\\
p
\end{pmatrix}=
(a_0, 2^{36 + 344}y_0^{(l)}, 2^{32 + 344}y_1^{(l)}, \cdots,
2^{344}y_{10}^{(l)}, 2^{384})
\] 
但实际格出来会比 \(a_0\) 
小,需要利用flag的已知前缀H3CTF{和后缀},最后再爆破2字节。
exp 
exp没找到就不放了(
References