Hgame Div 3,4题解 0.About 终于在接触CTF差不多大约6个月后,尝试打了一下杭州电子科大的CTF招新题(总体感觉只有4个字:我太菜了
只做了密码学,其他的题目不看,因为不是我研究的内容(
第3,4两周的题目确实难度变大了很多
#define huangx607087 feiwu
1. LikiPrime 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import randomfrom libnum import s2nfrom secret import secrets, flagdef get_prime (secret ): prime = 1 for _ in range (secret): prime = prime << 1 return prime - 1 random.shuffle(secrets) m = s2n(flag) p = get_prime(secrets[0 ]) q = get_prime(secrets[1 ]) n = p * q e = 0x10001 c = pow (m, e, n)print ("n = {}.format(n)" )print ("e = {}.format(e)" )print ("c = {}.format(c)" )
题目最后给出的是$n,e,c$的值。
通过简单地观察代码,我们可以发现,$p,q$都具有$2^k-1$的形式,并且都是素数。$n$的位数为$5072$
$p.q$的值。
枚举出$p,q$之后。之后就是正常的RSA解密了。
hgame{Mers3nne~Pr!Me^re4l1y_s0+5O-li7tle!}
2.HappyNewYear 题目描述:Liki 的朋友们在新年的时候给她发送了新年祝福。好家伙,一看就是群发的,有几个朋友发送的内容还是相同的
然后题目给出了很多$n,c$的值,并且$e=3$。不出意外应该是低指数广播攻击。因为提示有一样的,因此我们可以两两枚举,使用CRT求解。
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 from Crypto.Util.number import *from gmpy2 import *def crt2 (m1,m2,r1,r2 ): M=m1*m2 t1,t2=inverse(m2,m1),inverse(m1,m2) return (r1*t1*m2+r2*t2*m1)%M f=open ("data2.txt" ,"r" ) n,c=[],[] a=[] b=[]for i in range (7 ): n.append(int (f.readline())) c.append(int (f.readline()))for i in range (7 ): for j in range (i+1 ,7 ): now=crt2(n[i],n[j],c[i],c[j]) if (now in a): if now not in b: b.append(now) else : a.append(now)for i in b: c,d=iroot(i,3 ) if (d): print (long_to_bytes(c))
3.EncryptedChats 题目首先给出了两个人的对话,还有AES加密的过程。
1.Dialogue 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 Switch: 你好老伙汁 Million: 你好 Switch: 所以为什么这家伙在这里 Liki: 因为我是来记录你们谈话内容的 Million: 好吧 Million: 不过我可以提一个要求吗 Liki: ? Million: 我们...换一个群聊 Switch: 好啊! 我看看, 就换到加法群聊吧! Switch: 喂, 帮忙选一个质数 g 吧 Liki: ??, 12602983924735419868428783329859102652072837431735895060811258460532600319539509800915989811879506790207025505003183121812480524033163157114086741486989697 Million: 这位女士,可以劳烦您再为我们选择一个质数 p 吗 Liki: ???...那就, 30567260905179651419358486099834315837354102714690253338851161207042846254351374572818884286661092938876675032728700590336029243619773064402923830209873155153338320502164587381848849791304214084993139233581072431814555885408821184652544361671134564827265516331283076223247829980225591857643487356406284913560960657053777612115591241983729716542192518684003840806442329098770424504275465756739925434019460351138273272559738332984560095465809481270198689251655392941966835733947437503158486731906649716026200661065054914445245468517406404904444261196826370252359102324767986314473183183059212009545789665906197844518119 吧, 够大了吗? Million: 好的, 我选好我的 a 了, 那么 A = 6407001517522031755461029087358686699246016691953286745456203144289666065160284103094131027888246726980488732095429549592118968601737506427099198442788626223019135982124788211819831979642738635150279126917220901861977041911299607913392143290015904211117118451848822390856596017775995010697100627886929406512483565105588306151304249791558742229557096175320767054998573953728418896571838697779621641522372719890056962681223595931519174265357487072296679757688238385898442549594049002467756836225770565740860731932911280385359763772064721179733418453824127593878917184915316616399071722555609838785743384947882543058635 Switch: okay, b 也选好了, B = 5522084830673448802472379641008428434072040852768290130448235845195771339187395942646105104638930576247008845820145438300060808178610210847444428530002142556272450436372497461222761977462182452947513887074829637667167313239798703720635138224358712513217604569884276513251617003838008296082768599917178457307640326380587295666291524388123169244965414927588882003753247085026455845320527874258783530744522455308596065597902210653744845305271468086224187208396213207085588031362747352905905343508092625379341493584570041786625506585600322965052668481899375651376670219908567608009443985857358126335247278232020255467723 Liki: ???? Million: {'iv': 'd3811beb5cd2a4e1e778207ab541082b' , 'encrypted_flag': '059e9c216bcc14e5d6901bcf651bee361d9fe42f225bc0539935671926e6c092' }Switch: {'iv': 'b4259ed79d050dabc7eab0c77590a6d0' , 'encrypted_flag': 'af3fe410a6927cc227051f587a76132d668187e0de5ebf0608598a870a4bbc89' }Million: 再见伙汁 Switch: 再见 Liki: ?????
2.AES encryption 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from Crypto.Cipher import AESfrom Crypto.Util.Padding import pad, unpadimport hashlibimport osfrom secret import shared_secret, FLAGdef encrypt_flag (shared_secret: int ): sha1 = hashlib.sha1() sha1.update(str (shared_secret).encode('ascii' )) key = sha1.digest()[:16 ] iv = os.urandom(16 ) cipher = AES.new(key, AES.MODE_CBC, iv) ciphertext = cipher.encrypt(pad(FLAG, 16 )) data = {} data['iv' ] = iv.hex () data['encrypted_flag' ] = ciphertext.hex () return dataprint (encrypt_flag(shared_secret))
3.Solution 很显然,看到两个人的对话,就知道这道题考点是数字签名。也就是著名的DH密钥交换算法。这个在我的DLP Notes 1 里面有较为详细的介绍。
不过在这边有一个新的概念就是群:简单来说,群就是一个封闭的运算集合。在一个群$G$里面,使用的运算符号是$\mathrm{op}$。那么对于任意的$a,b,\in G$,满足交换律律$a \text{ op }b=b\text{ op }a$,结合律$a\text{ op }b\text{ op }c=a\text{ op }(b\text{ op }c)$。并且群中有一个中性元$e$,每个元素$a$都有一个逆元$a^{-1}$,使得$a\text{ op } a^{-1}=e$。当然,$a\text{ op }a$在群中可以记作$a^2$。
从上述对话中我们可以看到:这里使用的是加法群,因此$\mathrm{op}=+$,而表面上的指数,实际上应该是数学计算中的乘号。
根据DH密钥交换系统,最后共享密钥是群里面的$g^{ab}$。由于这里是加法群,因此共享内容是$gab$三者乘积模$p$的值。因此直接求个逆元就可以得到$a,b$。
至于AES部分,直接模仿他的加密过程构建密钥即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from Crypto.Util.number import *from Crypto.Cipher import AESimport hashlibfrom os import urandomfrom given import g,p,A,B d=inverse(g,p) a,b=A*d%p,B*d%p S=g*a*b%p sha1=hashlib.sha1() sha1.update(str (S).encode('ascii' )) key=sha1.digest()[:16 ] iv=[0xd3811beb5cd2a4e1e778207ab541082b ,0xb4259ed79d050dabc7eab0c77590a6d0 ] c=[0x059e9c216bcc14e5d6901bcf651bee361d9fe42f225bc0539935671926e6c092 ,0xaf3fe410a6927cc227051f587a76132d668187e0de5ebf0608598a870a4bbc89 ]for i in range (2 ): iv[i]=long_to_bytes(iv[i]) c[i]=long_to_bytes(c[i]) cipher1 = AES.new(key, AES.MODE_CBC, iv[0 ]) cipher2 = AES.new(key, AES.MODE_CBC, iv[1 ]) d1=cipher1.decrypt(c[0 ]) d2=cipher2.decrypt(c[1 ])print (d1,d2)
4.夺宝大冒险1 Step 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 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 import os flag = "xxxx" class Cxx1ff : c4ff1x = int .from_bytes(os.urandom(8 ),'big' ) c66f6 = int .from_bytes(os.urandom(8 ),'big' ) c4ff10 = int .from_bytes(os.urandom(8 ),'big' ) def __init__ (self, seed ): self .state = seed def next (self ): self .state = (self .state * self .c4ff1x + self .c66f6) % self .c4ff10 return self .stateclass Cxx2ff : c4ff1x = int .from_bytes(os.urandom(8 ),'big' ) c66f6 = int .from_bytes(os.urandom(8 ),'big' ) c4ff10 = int .from_bytes(os.urandom(8 ),'big' ) def __init__ (self, seed ): self .state = seed def next (self ): self .state = (self .state * self .c4ff1x + self .c66f6) % self .c4ff10 return self .stateclass Cxx3ff : c4ff1x = int .from_bytes(os.urandom(8 ),'big' ) c66f6 = int .from_bytes(os.urandom(8 ),'big' ) c4ff10 = int .from_bytes(os.urandom(8 ),'big' ) def __init__ (self, seed ): self .state = seed def next (self ): self .state = (self .state * self .c4ff1x + self .c66f6) % self .c4ff10 return self .statedef test1 (): gen = Cxx1ff(123 ) print ((Cxx1ff.c4ff1x,Cxx1ff.c4ff10)) print (gen.next ()) print (gen.next ()) t1 = input () try : if int (t1.strip())==Cxx1ff.c66f6: return 1 except : pass return 0 def test2 (): gen = Cxx2ff(123 ) print ((Cxx2ff.c4ff10)) print (gen.next ()) print (gen.next ()) print (gen.next ()) t1 = input () t2 = input () try : if (int (t1.strip())==Cxx2ff.c4ff1x) and (int (t2.strip())==Cxx2ff.c66f6): return 1 except : pass return 0 def test3 (): gen = Cxx3ff(123 ) print (gen.next ()) print (gen.next ()) print (gen.next ()) print (gen.next ()) print (gen.next ()) print (gen.next ()) print (gen.next ()) t1 = input () try : if int (t1.strip())==Cxx3ff.c4ff10: return 1 except : pass return 0 if __name__ == "__main__" : ans = 0 ans += test1() ans += test2() ans += test3() if ans>=3 : print ("win" ) print (flag) else : print ("fail" )
通过阅读代码可以发现,这道题实际上是线性同余生成器的一个题目。线性同余生成器就是通过递推式$X_{n+1}\equiv AX_{n}+B \pmod M$的计算来生成随机数,它包括一个线性函数$Ax+B$和模$M$运算。
而这道题,实际上分成了三个小问,当三个小问都答对的时候,服务器会直接给你flag,下面我们逐一解决这三小问。
Step 2: 分步解决问题,提出初步方案。 Question 1 第一问是已知$A,M,X_{0}=123,X_1,X_2$,求$B$。那么我们根据 $$ X_{1}\equiv AX_{0}+B \pmod M $$ 可以直接推出 $$ B\equiv X_{1}-AX_{0} \pmod M $$ 所以,第一问真正的计算只有一行代码(所有的数据都是我进行本地测试时使用的数据,服务器给出的数据可能不一样)。
1 2 3 4 5 a=3423853412712492282 m=17055887075354437978 x1=8976750431089074058 b=(x1-123 *a)%mprint (b)
Question 2 第二问,已知$M,X_0=123,X_1,X_2,X_3$。要求出$A,B$那么我们根据$X_1\equiv AX_0+B \pmod M$、$X_2\equiv AX_1+B \pmod M$、$X_3\equiv AX_2+B \pmod M$。第二个式子减去第一个式子,第三个式子减去第二个式子,消去了$B$,就得到 $$ X_2-X_1\equiv A(X_1-X_0)\pmod M $$
$$ X_3-X_2\equiv A(X_2-X_1)\pmod M $$
所以,以第一个式子两边除以$X_1-X_0$,我们就可以得到: $$ (X_2-X_1)(X_1-X_0)^{-1}\equiv A \pmod M $$ 当然,我们也可以用第二个式子 $$ (X_3-X_2)(X_2-X_1)^{-1}\equiv A \pmod M $$ $A$求出来之后,那么我们根据 $$ AX_0+B\equiv X_1\pmod M $$ 可以得到 $$ B\equiv X_1-AX_0 \pmod M $$ 这样题目就得出了解。
上一下第二问的代码
1 2 3 4 5 6 7 8 9 10 11 12 from Crypto.Util.number import *def G (d1,d2,m ): return (d2)*inverse(d1,m)%m m=17339855340627587175 x0=123 x1=11280784678567285991 x2=16229269394156588360 x3=12807800121938543462 detla1,detla2,detla3=x1-x0,x2-x1,x3-x2 a=G(detla1,detla2,m) b=(x1-123 *a)%mprint (a,b)
Question 3 第三问,已知$X_0$到$X_7$的值,要求出模数$M$。
根据我们知道的$X_0$到$X_7$的递推式,我们依然想到了消元。
根据第二问我们已经获得的两个式子: $$ (X_2-X_1)(X_1-X_0)^{-1}\equiv A \pmod M $$
$$ (X_3-X_2)(X_2-X_1)^{-1}\equiv A \pmod M $$
联立这两个式子,我们可以得到 $$ (X_2-X_1)(X_1-X_0)^{-1}\equiv(X_3-X_2)(X_2-X_1)^{-1} \pmod M $$ 干脆把分母也去了吧 $$ (X_2-X_1)^2\equiv(X_3-X_2)(X_1-X_0) \pmod M $$ 然后把同余号也去了吧( $$ (X_2-X_1)^2=(X_3-X_2)(X_1-X_0)+k_2M $$ 最后移项 $$ (X_2-X_1)^2-(X_3-X_2)(X_1-X_0)=+k_2M $$ 同理可得: $$ (X_3-X_2)^2-(X_4-X_3)(X_2-X_1)=+k_3M $$ 我们可以通过这一手段获得$k_2M,k_3M,k_4M,k_5M,k_6M$的值,然后直接求GCD就可以了。
上一下第三问的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from Crypto.Util.number import *i x0=123 x1=9994867626161635096 x2=5753461374688204354 x3=11648048026234191592 x4=1202418284492042518 x5=10468479901118246086 x6=8754702082052904382 x7=4553263904866307968 detla1,detla2,detla3,detla4,detla5=x1-x0,x2-x1,x3-x2,x4-x3,x5-x4 detla6,detla7=x6-x5,x7-x6 P2=detla2**2 -detla3*detla1 P3=detla3**2 -detla4*detla2 P4=detla4**2 -detla5*detla3 P5=detla5**2 -detla6*detla4 P6=detla6**2 -detla7*detla5 P2,P3,P4,P5,P6=abs (P2),abs (P3),abs (P4),abs (P5),abs (P6) E=GCD(P2,P3) E=GCD(E,P4) E=GCD(E,P5) E=GCD(E,P6)print (E)
Step 3.回顾并完善方案 内测的时候,这三个代码都能够得出正确答案,但都是概率性正确(也就是总是会出错),原因可能如下:
第一问的$M>A$且$\gcd(A,M)\not =1$的时候,大概有$70\text{percent}$的概率会出现第一问的错误。因为两边的模是不能月的,之前就提出过$2x\equiv2 \pmod 6$的解是$x_1=1$和$x_2=4$。
第二问可能同时出现$\gcd (X_1-X_0,M)\not =1$和$\gcd (X_2-X_1,M)\not =1$,此时逆元无法求出,解题会非常麻烦。
第三问可能会出现$M$的比特位长$≥65$的情况,因为最后求$\gcd$的时候,$M$前面的系数没有消成$1$。
总之,这些问题处理起来,会非常麻烦,但在数据比较好的情况下,这三个做法都能做出来。
所以,如果最后搞不出来,那就干脆重连算了。比如说,在第二问$\gcd$全部不等于$1$的时候程序自动raise ValueError
,第三问解出的数字比特位长于$64$也raise
一个ValueError
。第三问解决出来后把服务器的反馈全盘接受,一旦出现fail
自动重连,因此只要我们最后接收到的字符串的长度大于$5$,就说明我们获得了flag,然后直接输出就行了。
由于我们不能预估这些概率,所以我打算多次连接(,脚本如下。
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 from Crypto.Util.number import *from pwn import *def s1 (a,m,x1,x2 ): return (x1-123 *a)%mdef s2 (m,x1,x2,x3 ): def G (d1,d2,m ): assert GCD(d1,m)==1 return (d2)*inverse(d1,m)%m x0=123 detla1,detla2,detla3=x1-x0,x2-x1,x3-x2 try : a=G(detla1,detla2,m) b=(x1-123 *a)%m return (a,b) except : try : a=G(detla2,detla3,m) b=(x1-123 *a)%m return (a,b) except : raise ValueErrordef s3 (x1,x2,x3,x4,x5,x6,x7 ): x0=123 detla1,detla2,detla3,detla4,detla5=x1-x0,x2-x1,x3-x2,x4-x3,x5-x4 detla6,detla7=x6-x5,x7-x6 P2=detla2**2 -detla3*detla1 P3=detla3**2 -detla4*detla2 P4=detla4**2 -detla5*detla3 P5=detla5**2 -detla6*detla4 P6=detla6**2 -detla7*detla5 P2,P3,P4,P5,P6=abs (P2),abs (P3),abs (P4),abs (P5),abs (P6) E=GCD(P2,P3) E=GCD(E,P4) E=GCD(E,P5) E=GCD(E,P6) if E.bit_length()>64 : raise ValueError return Edef getamin1 (s1 ): arr,now,ind=[],0 ,0 for i in s1: if ord (i) in range (48 ,58 ): ind,now=1 ,now*10 +ord (i)-48 else : if ind: arr.append(now) now,ind=0 ,0 assert len (arr)==2 return arr[0 ],arr[1 ]def run (): sh=remote("182.92.108.71" ,30641 ) so1=sh.recvline(keepends=False ) x1=sh.recvline(keepends=False ) x2=sh.recvline(keepends=False ) a,m=getamin1(so1) x1,x2=int (x1),int (x2) ans1=s1(a,m,x1,x2) print (ans1.bit_length()) sh.sendline(str (ans1)) m=int (sh.recvline(keepends=False )) x1=int (sh.recvline(keepends=False )) x2=int (sh.recvline(keepends=False )) x3=int (sh.recvline(keepends=False )) a,b=s2(m,x1,x2,x3) sh.sendline(str (a)) sh.sendline(str (b)) x1=int (sh.recvline(keepends=False )) x2=int (sh.recvline(keepends=False )) x3=int (sh.recvline(keepends=False )) x4=int (sh.recvline(keepends=False )) x5=int (sh.recvline(keepends=False )) x6=int (sh.recvline(keepends=False )) x7=int (sh.recvline(keepends=False )) ans=s3(x1,x2,x3,x4,x5,x6,x7) sh.sendline(str (ans)) final=sh.recvall() sh.close() return final T,F=1 ,"" while True : print (str (T)+'th time trying' ) F="" try : F=run() except : pass if len (F)>6 : print (F) print ('with using' +str (T)+'times to get the flag' ) break else : T+=1
运行截图,这里是用了$24$次,多测了几次,最好的一次重置$3$次获得flag,最坏的一次重置了$91$次。
Step 4 Reference 不知道为什么出题人生成模数的时候不使用getPrime(64)
而是使用bytes_to_long(urandom(8))
,是大E了还是另有想法,只能坐等官方的week4的wp了。这说明自己还是太菜了(
Update 2021.7.12
当时打完了之后问了一下作者。实际上是打完不久,但4个半月后才想起来Update,此处实际上使用的是重复攻击意识,因为有的时候由于各种数据的原因,虽然算法是对的,但有可能也做不出最终结果。因此遇到这种情况的时候可以多试几次,就可以得出正确答案
5.夺宝大冒险2 看一下题目,是lfsr求种子和随机数。
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 class LXFIQNN (): def __init__ (self, init, mask, length ): self .init = init self .mask = mask self .lengthmask = 2 **(length+1 )-1 def next (self ): nextdata = (self .init << 1 ) & self .lengthmask i = self .init & self .mask & self .lengthmask output = 0 while i != 0 : output ^= (i & 1 ) i = i >> 1 nextdata ^= output self .init = nextdata return output def random (self, nbit ): output = 0 for _ in range (nbit): output <<= 1 output |= self .next () return outputfrom secret import init, FLAG"""secret.py import os init = int.from_bytes(os.urandom(5), 'big') FLAG = 'hgame{xxx}' """ prng = LXFIQNN(init, 0b1011001010001010000100001000111011110101 , 40 ) score = 0 for r in range (100 ): print (f"round {r} :: score {score} " ) try : guess = int (input ("guess: " )) except : break secret = prng.random(4 ) if secret == guess: print ("Right" ) score += 1 else : print (f"Wrong, the secret is {secret} " )if score >= 80 : print (FLAG)
CTFWIKI上说过,一个$n$位的lfsr,在知道掩码mask的情况下,只需要知道$n$位的输出,就可以恢复种子值,而如果在连mask也不知道的情况下,如果知道了$2n$位的输出,那么可以先通过矩阵运算得到mask的值,然后求个逆矩阵就可以得到种子的值了。
具体内容见自己博客的**Lfsr Notes[2021.1.27]**相关内容,下面直接上一下做题脚本。相关内容的构造可以直接模仿题目给出的方法。
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 from Crypto.Util.number import *from pwn import *class LXFIQNN (): def __init__ (self, init, mask, length ): self .init = init self .mask = mask self .lengthmask = 2 **(length+1 )-1 def next (self ): nextdata = (self .init << 1 ) & self .lengthmask i = self .init & self .mask & self .lengthmask output = 0 while i != 0 : output ^= (i & 1 ) i = i >> 1 nextdata ^= output self .init = nextdata return output def random (self, nbit ): output = 0 for _ in range (nbit): output <<= 1 output |= self .next () return outputdef padbin (x,b ): s=bin (x)[2 :] s='0' *(b-len (s))+s return s sh=remote("182.92.108.71" ,30607 ) arr=[] lj=[5 ,22 ,23 ]for i in range (10 ): sh.recvuntil("guess: " ) sh.sendline('0' ) judge=sh.recvline(keepends=False ) assert len (judge) in lj if len (judge)==5 : arr.append(0 ) else : arr.append(int (judge[21 :]))print (arr) mask = 0xb28a108ef5 N = 40 key = '' for i in arr: key+=padbin(i,4 ) idx = 0 ans = "" key = key[39 ] + key[:40 ]while idx < 40 : tmp = 0 for i in range (40 ): if mask >> i & 1 : tmp ^= int (key[39 - i]) ans = str (tmp) + ans idx += 1 key = key[39 ] + str (tmp) + key[1 :39 ] num = int (ans, 2 )print (hex (num)) prng=LXFIQNN(num, mask, 40 ) anses=[]for i in range (300 ): anses.append(prng.random(4 ))assert anses[0 :10 ]==arrfor i in range (10 ,100 ): tr1=sh.recvline(keepends=False ) print (str1) sh.recvuntil("guess: " ) sh.sendline(str (anses[i])) str1=sh.recvline(keepends=False ) print (str1) flag=sh.recvline(keepends=False )print (flag)