Ⅳ MISC
1. Warmup
Question
Description
提交管理员密码的sha256,自己补上格式hgame{}
URL
http://pmsw8b6r5.bkt.clouddn.com//ea72c72ba8808ccc22ca7f0fac63cfcb/1.zip
Base Score
100
Answer
这题主要就是用到了mimikatz
我把解压出来的1.gif改名成1.dmp
mimikatz
所以可以看到密码就是LOSER
SHA256就是dd6dffcd56b77597157ac6c1beb514aa4c59d033098f806d88df89245824d3f5
所以flag就是hgame{dd6dffcd56b77597157ac6c1beb514aa4c59d033098f806d88df89245824d3f5}
3. 暗藏玄机
Question
Description
要开学了,要开学了(悲)
URL
http://plqfgjy5a.bkt.clouddn.com/%E5%BC%80%E5%AD%A6.zip
Base Score
150
Answer
打开压缩包,里面是两张看上去一模一样的图片。
一开始我看了半天,并没有发现什么。
最后在py了学长之后拿到了hint:盲水印
网上有现成地代码(https://github.com/YvesZHI/BlindWaterMark/blob/master/bwm.py)
然后执行python bwm.py decode 开学了.png 开学啦.png flag.png
就会得到一张图片
所以flag就是hgame{h1de_in_THE_p1Cture}
(说实话,真的很简单。对我来说,主要的困难就是python2和3之间不兼容的这个硬伤,以及所依赖的库安装不上去。。。)
Ⅴ CRYPTO
1. easy_rsa
Question
Description
m为17位十进制数,提交格式hgame{m}
URL
http://pmuyzdinx.bkt.clouddn.com/1.txt
Base Score
200
Answer
打开给的题目如下:
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 e1:0x33240 e2:0x3e4f n:0x9439682bf1b4ab48c43c524778c579cc844b60872275725c1dc893b5bcb358b9 \ f136e4dab2a06318bb0c80e202a14bc54ea334519bec023934e01e9378abf329\ 893f3870979e9f2f2be8fff4df931216a77007a2509f49f697bf286285e97fac\ 5dc6e4a164b5c2cc430887b18136437ba67777bda05aafdeaf918221c812b4c7\ d1665238f84ab0fab7a77fcae92a0596e58343be7a8e6e75a5017c63a67eb119\ 64970659cd6110e9ec6502288e9e443d86229ef2364dfecb63e2d90993a75356\ 854eb874797340eece1b19974e86bee07019610467d44ec595e04af02b574a97\ fa98bdb2e779871c804219cab715f4a80fef7f8fb52251d86077560b39c1c2a1 c1:0x7c7f315a3ebbe305c1ad8bd2f73b1bb8e300912b6b8ba1b331ac2419d3da5a9 \ a605fd62915c11f8921c450525d2efda7d48f1e503041498f4f0676760b43c77\ 0ff2968bd942c7ef95e401dd7facbd4e5404a0ed3ad96ae505f87c4e12439a2d\ a636f047d84b1256c0e363f63373732cbaf24bda22d931d001dcca124f5a19f9\ e28608ebd90161e728b782eb67deeba4cc81b6df4e7ee29a156f51a0e5148618\ c6e81c31a91036c982debd1897e6f3c1e5e248789c933a4bf30d0721a18ab870\ 8d827858b77c1a020764550a7fe2ebd48b6848d9c4d211fd853b7a02a859fa0c\ 72160675d832c94e0e43355363a2166b3d41b8137100c18841e34ff52786867d c2:0xf3a8b9b739196ba270c8896bd3806e9907fca2592d28385ef24afadc2a408b7 \ 942214dad5b9e14808ab988fb15fbd93e725edcc0509ab0dd1656557019ae93c\ 38031d2a7c84895ee3da1150eda04cd2815ee3debaa7c2651b62639f785f6cab\ f83f93bf3cce7778ab369631ea6145438c3cd4d93d6f2759be3cc187651a33b3\ cc4c3b477604477143c32dfff62461fdfd9f8aa879257489bbf977417ce0fbe8\ 9e3f2464475624aafef57dd9ea60339793c69b53ca71d745d626f45e6a7beb9f\ cbd9d1a259433d36139345b7bb4f392e78f1b5be0d2c56ad50767ee851fac670\ 946356b3c05d0605bf243b89c7e683cc75030b71633632fb95c84075201352d6\ c1=pow (m, e1, n) c2=pow (m, e2, n)
可以看到这里对m进行了两次同模加密,所以可以使用同模攻击
原理如下:(来自ctf-wiki )
设两个用户的公钥分别为 e 1 e_1 e 1 和 e 2 e_2 e 2 ,且两者互质。
明文消息为 m m m ,密文分别为: c 1 = m e 1 m o d N c_1 = m^{e_1}\bmod N c 1 = m e 1 m o d N c 2 = m e 2 m o d N c_2 = m^{e_2}\bmod N c 2 = m e 2 m o d N
当攻击者截获 c 1 c_1 c 1 和 c 2 c_2 c 2 后,就可以恢复出明文。用扩展欧几里得算法求出 r e 1 + s e 2 = 1 m o d n re_1+se_2=1\bmod n r e 1 + s e 2 = 1 m o d n 的两个整数r r r 和 s s s ,由此可得:
c 1 r c 2 s = m r e 1 m s e 2 m o d n = m ( r e 1 + s e 2 ) m o d n = m m o d n c_1^r c_2^s = m^{re_1} m^{se_2}\bmod n =m^{(re_1+se_2)} \bmod n=m\bmod n c 1 r c 2 s = m r e 1 m s e 2 m o d n = m ( r e 1 + s e 2 ) m o d n = m m o d n
代码如下
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 e1 = 0x33240 e2 = 0x3e4f n = 0x9439682bf1b4ab48c43c524778c579cc844b60872275725c1dc893b5bcb358b9 f136e4dab2a06318bb0c80e202a14bc54ea334519bec023934e01e9378abf329 893f3870979e9f2f2be8fff4df931216a77007a2509f49f697bf286285e97fac 5dc6e4a164b5c2cc430887b18136437ba67777bda05aafdeaf918221c812b4c7 d1665238f84ab0fab7a77fcae92a0596e58343be7a8e6e75a5017c63a67eb119 64970659cd6110e9ec6502288e9e443d86229ef2364dfecb63e2d90993a75356 854eb874797340eece1b19974e86bee07019610467d44ec595e04af02b574a97 fa98bdb2e779871c804219cab715f4a80fef7f8fb52251d86077560b39c1c2a1 message1 = 0x7c7f315a3ebbe305c1ad8bd2f73b1bb8e300912b6b8ba1b331ac2419d3da5a9a 605fd62915c11f8921c450525d2efda7d48f1e503041498f4f0676760b43c770 ff2968bd942c7ef95e401dd7facbd4e5404a0ed3ad96ae505f87c4e12439a2da 636f047d84b1256c0e363f63373732cbaf24bda22d931d001dcca124f5a19f9e 28608ebd90161e728b782eb67deeba4cc81b6df4e7ee29a156f51a0e5148618c 6e81c31a91036c982debd1897e6f3c1e5e248789c933a4bf30d0721a18ab8708 d827858b77c1a020764550a7fe2ebd48b6848d9c4d211fd853b7a02a859fa0c7 2160675d832c94e0e43355363a2166b3d41b8137100c18841e34ff52786867d message2 = 0xf3a8b9b739196ba270c8896bd3806e9907fca2592d28385ef24afadc2a408b79 42214dad5b9e14808ab988fb15fbd93e725edcc0509ab0dd1656557019ae93c3 8031d2a7c84895ee3da1150eda04cd2815ee3debaa7c2651b62639f785f6cabf 83f93bf3cce7778ab369631ea6145438c3cd4d93d6f2759be3cc187651a33b3c c4c3b477604477143c32dfff62461fdfd9f8aa879257489bbf977417ce0fbe89 e3f2464475624aafef57dd9ea60339793c69b53ca71d745d626f45e6a7beb9fc bd9d1a259433d36139345b7bb4f392e78f1b5be0d2c56ad50767ee851fac6709 46356b3c05d0605bf243b89c7e683cc75030b71633632fb95c84075201352d6 def gcdext (a, b ): if b == 0 : return 1 , 0 , a else : x, y, q = gcdext(b, a % b) x, y = y, (x - (a // b) * y) return x, y, q def gcd (a, b ): while a != 0 : a, b = b % a, a return b def findModReverse (a, m ): if gcd(a, m) != 1 : return None u1, u2, u3 = 1 , 0 , a v1, v2, v3 = 0 , 1 , m while v3 != 0 : q = u3 // v3 v1, v2, v3, u1, u2, u3 = (u1 - q * v1), (u2 - q * v2), (u3 - q * v3), v1, v2, v3 return u1 % m s, t, pp = gcdext(e1, e2) times = s * e1 + t * e2 if s < 0 : s = -s message1 = findModReverse(message1, n) if t < 0 : t = -t message2 = findModReverse(message2, n) plain = (pow (message1, s, n) * pow (message2, t, n)) % n print(plain) print(pow (plain, 1 / times)) plain = 211655262573966881062823795220179644607412162371069 i = 59594981651654789
由于times=3,所以plain可以开三次方,得到59594981651654789
所以flag就是hgame{59594981651654789}
(我也不知道为什么。。。)
2. MixedRSA_Easy
Question
Description
47.95.212.185 38610 由CNSS友情赞助 比心
URL
http://plqbnxx54.bkt.clouddn.com/MixedRSA_Easy.py
Base Score
400
Answer
(玩了这么久,总算拿了个二血,多了3%的分数加成,开心)
题目给的代码如下
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 from Crypto.Util.number import getPrimefrom signal import alarmdef gcd (a, b ): while b != 0 : a, b = b, a % b return a def ex_gcd (m, n ): x, y, x1, y1 = 0 , 1 , 1 , 0 while m % n: x, x1 = x1 - m // n * x, x y, y1 = y1 - m // n * y, y m, n = n, m % n return n, x, y def inv (x, p ): g, y, k = ex_gcd(x, p) if y < p: y += p return y def xor (a, b ): return bytes (x ^ y for x, y in zip (a, b)) class MixedRSA : n = e = d = iv = BLOCK = 0 def __init__ (self, iv, block=256 ): self.BLOCK = block p = getPrime(block * 4 ) q = getPrime(block * 4 ) self.n = p * q phi = (p - 1 ) * (q - 1 ) self.e = getPrime(64 ) self.d = inv(self.e, phi) iv *= block // len (iv) self.iv = iv.rjust(block, b'\x00' ) def padding (self, s ): return b'\x00' * (-len (s) % self.BLOCK) + s def rsa_encrypt (self, m ): c = pow (int (m.hex (), 16 ), self.e, self.n) c = hex (c)[2 :].rjust(self.BLOCK * 2 , '0' ) return bytes .fromhex(c) def rsa_decrypt (self, c ): m = pow (int (c.hex (), 16 ), self.d, self.n) m = hex (m)[2 :].rjust(self.BLOCK * 2 , '0' ) return bytes .fromhex(m) def encrypt (self, plaintext ): plaintext = self.padding(plaintext) imd = self.iv for i in range (0 , len (plaintext), self.BLOCK): m = xor(imd[-self.BLOCK:], plaintext[i: i + self.BLOCK]) imd += self.rsa_encrypt(m) imd = imd[self.BLOCK:] cipher = self.iv for i in range (0 , len (imd), self.BLOCK): c = self.rsa_encrypt(cipher[-self.BLOCK:]) cipher += xor(c, imd[i: i + self.BLOCK]) return cipher[self.BLOCK:] def decrypt (self, cipher ): cipher = self.iv + cipher imd = b'' for i in range (self.BLOCK, len (cipher), self.BLOCK): c = self.rsa_encrypt(cipher[i - self.BLOCK: i]) imd += xor(c, cipher[i: i + self.BLOCK]) imd = self.iv + imd plaintext = b'' for i in range (self.BLOCK, len (imd), self.BLOCK): m = self.rsa_decrypt(imd[i: i + self.BLOCK]) plaintext += xor(m, imd[i - self.BLOCK: i]) return plaintext if __name__ == '__main__' : alarm(60 ) mix = MixedRSA(FLAG) while True : print("Choose:\n[1] Encrypt (hex)\n[2] Decrypt (hex)" ) op = input () if op == '1' : msg = bytes .fromhex(input ()) print(mix.encrypt(msg).hex ()) elif op == '2' : msg = bytes .fromhex(input ()) print(mix.decrypt(msg).hex ()) else : print('Bye' ) break
首先要拿到FLAG
,其实也就是要拿到mix里面的那个iv
,因为根据
以及
1 2 3 4 def __init__ (self, iv, block=256 ): ... iv *= block // len (iv) self.iv = iv.rjust(block, b'\x00' )
可以看出iv
是由FLAG不断重复,然后左边补0产生的一个大小是256B的Bytes。
举个例子
那么就会得到
1 2 3 4 5 6 7 iv = '0000000000006867616d657b2e2e2e7d6867616d657b2e2e2e7d6867616d657b2e2e2e7d6867616d657b2e2e2e7d\ 6867616d657b2e2e2e7d6867616d657b2e2e2e7d6867616d657b2e2e2e7d6867616d657b2e2e2e7d\ 6867616d657b2e2e2e7d6867616d657b2e2e2e7d6867616d657b2e2e2e7d6867616d657b2e2e2e7d\ 6867616d657b2e2e2e7d6867616d657b2e2e2e7d6867616d657b2e2e2e7d6867616d657b2e2e2e7d\ 6867616d657b2e2e2e7d6867616d657b2e2e2e7d6867616d657b2e2e2e7d6867616d657b2e2e2e7d\ 6867616d657b2e2e2e7d6867616d657b2e2e2e7d6867616d657b2e2e2e7d6867616d657b2e2e2e7d\ 6867616d657b2e2e2e7d'
很明显后面是以6867616d657b2e2e2e7d
为重复的,其实也就是hgame{...}
的ASCII的十六进制。
要拿到iv
的话,主要要关心的其实就是encrypt
和decrypt
两个函数
1 2 3 4 5 6 7 8 9 10 11 12 def encrypt (self, plaintext ): plaintext = self.padding(plaintext) imd = self.iv for i in range (0 , len (plaintext), self.BLOCK): m = xor(imd[-self.BLOCK:], plaintext[i: i + self.BLOCK]) imd += self.rsa_encrypt(m) imd = imd[self.BLOCK:] cipher = self.iv for i in range (0 , len (imd), self.BLOCK): c = self.rsa_encrypt(cipher[-self.BLOCK:]) cipher += xor(c, imd[i: i + self.BLOCK]) return cipher[self.BLOCK:]
这个看上去像是CBC,并且是256B为一个Block,如果假设这里的plaintext
刚好就是256B的长度,那么这个函数就可以化简为
1 2 3 def encrypt (self, plaintext ): return xor(self.rsa_encrypt(xor(self.iv, plaintext)),self.rsa_encrypt(self.iv))
那么对于plaintext为256B(也就是一个Block)的时候,其实用数学符号写出来就是
e n c r y p t ( p l a i n t e x t ) = R S A ( p l a i n t e x t ⊕ i v ) ⊕ R S A ( i v ) encrypt(plaintext)=RSA(plaintext \oplus iv)\oplus RSA(iv) e n c r y p t ( p l a i n t e x t ) = R S A ( p l a i n t e x t ⊕ i v ) ⊕ R S A ( i v )
上面R S A ( x ) RSA(x) R S A ( x ) 表示对x x x 进行加密,R S A − 1 ( x ) RSA^{-1}(x) R S A − 1 ( x ) 表示解密,下同
那么同理,对于decrypt,当cipher刚好为256B的时候,可以得到
d e c r y p t ( c i p h e r ) = R S A − 1 ( c i p h e r ⊕ R S A ( i v ) ) ⊕ i v decrypt(cipher)=RSA^{-1}(cipher \oplus RSA(iv))\oplus iv d e c r y p t ( c i p h e r ) = R S A − 1 ( c i p h e r ⊕ R S A ( i v ) ) ⊕ i v
但是光凭这样两个化简版的函数,并不能得到iv
另外又发现e n c r y p t ( Z E R O ) = Z E R O = d e c r y p t ( Z E R O ) = Z E R O encrypt(ZERO)=ZERO=decrypt(ZERO)=ZERO e n c r y p t ( Z E R O ) = Z E R O = d e c r y p t ( Z E R O ) = Z E R O
(设Z E R O ZERO Z E R O 为长度为256B,并且全为0的Bytes)
看上去没什么用,但是实际上根据原来的代码,我们可以控制下一个Block的“iv”。
所以可以把原来的p l a i n t e x t plaintext p l a i n t e x t 拼在Z E R O ZERO Z E R O 后面,变成两个Block。
那么根据原来的代码可以得到
e n c r y p t ( Z E R O + p l a i n t e x t ) = Z E R O + R S A ( p l a i n t e x t ⊕ R S A ( i v ) ) ⊕ R S A ( Z E R O ) = Z E R O + R S A ( p l a i n t e x t ⊕ R S A ( i v ) ) encrypt(ZERO+plaintext)=ZERO+RSA(plaintext \oplus RSA(iv))\oplus RSA(ZERO)\\=ZERO+RSA(plaintext \oplus RSA(iv)) e n c r y p t ( Z E R O + p l a i n t e x t ) = Z E R O + R S A ( p l a i n t e x t ⊕ R S A ( i v ) ) ⊕ R S A ( Z E R O ) = Z E R O + R S A ( p l a i n t e x t ⊕ R S A ( i v ) )
(上面一步是因为R S A ( Z E R O ) = Z E R O RSA(ZERO)=ZERO R S A ( Z E R O ) = Z E R O ,并且B y t e s ⊕ Z E R O = B y t e s Bytes\oplus ZERO=Bytes B y t e s ⊕ Z E R O = B y t e s )
所以取后半部分,设 e n c r y p t Z e r o ( p l a i n t e x t ) = R S A ( p l a i n t e x t ⊕ R S A ( i v ) encryptZero(plaintext)=RSA(plaintext \oplus RSA(iv) e n c r y p t Z e r o ( p l a i n t e x t ) = R S A ( p l a i n t e x t ⊕ R S A ( i v )
那么同理可得
d e c r y p t Z e r o ( c i p h e r ) = R S A − 1 ( c i p h e r ) ⊕ R S A ( i v ) decryptZero(cipher)=RSA^{-1}(cipher) \oplus RSA(iv) d e c r y p t Z e r o ( c i p h e r ) = R S A − 1 ( c i p h e r ) ⊕ R S A ( i v )
那么我们现在就有了四个函数
e n c r y p t ( p l a i n t e x t ) = R S A ( p l a i n t e x t ⊕ i v ) ⊕ R S A ( i v ) encrypt(plaintext)=RSA(plaintext \oplus iv)\oplus RSA(iv) e n c r y p t ( p l a i n t e x t ) = R S A ( p l a i n t e x t ⊕ i v ) ⊕ R S A ( i v )
d e c r y p t ( c i p h e r ) = R S A − 1 ( c i p h e r ⊕ R S A ( i v ) ) ⊕ i v decrypt(cipher)=RSA^{-1}(cipher \oplus RSA(iv))\oplus iv d e c r y p t ( c i p h e r ) = R S A − 1 ( c i p h e r ⊕ R S A ( i v ) ) ⊕ i v
e n c r y p t Z e r o ( p l a i n t e x t ) = R S A ( p l a i n t e x t ⊕ R S A ( i v ) encryptZero(plaintext)=RSA(plaintext \oplus RSA(iv) e n c r y p t Z e r o ( p l a i n t e x t ) = R S A ( p l a i n t e x t ⊕ R S A ( i v )
d e c r y p t Z e r o ( c i p h e r ) = R S A − 1 ( c i p h e r ) ⊕ R S A ( i v ) decryptZero(cipher)=RSA^{-1}(cipher) \oplus RSA(iv) d e c r y p t Z e r o ( c i p h e r ) = R S A − 1 ( c i p h e r ) ⊕ R S A ( i v )
所以
d e c r y p t ( d e c r y p t Z e r o ( Z E R O ) ) = R S A − 1 ( R S A − 1 ( Z E R O ) ⊕ R S A ( i v ) ⊕ R S A ( i v ) ) ⊕ i v = R S A − 1 ( R S A − 1 ( Z E R O ) ) ) ⊕ i v = Z E R O ⊕ i v = i v decrypt(decryptZero(ZERO))=RSA^{-1}(RSA^{-1}(ZERO) \oplus RSA(iv)\oplus RSA(iv))\oplus iv \\ =RSA^{-1}(RSA^{-1}(ZERO)))\oplus iv\\=ZERO\oplus iv\\=iv d e c r y p t ( d e c r y p t Z e r o ( Z E R O ) ) = R S A − 1 ( R S A − 1 ( Z E R O ) ⊕ R S A ( i v ) ⊕ R S A ( i v ) ) ⊕ i v = R S A − 1 ( R S A − 1 ( Z E R O ) ) ) ⊕ i v = Z E R O ⊕ i v = i v
所以netcat以后,实际操作如下
选择decrypt,发送1024个0,也就是512B。
截取接受到后半部分,再次选择decrypt,发送。
nc
所以截取iv如下: 0000000000006867616d657b415f6c6974746c655f686172645f5273343f7d6867616d657b415f6c6974746c655f686172645f5273343f7d6867616d657b415f6c6974746c655f686172645f5273343f7d6867616d657b415f6c6974746c655f686172645f5273343f7d6867616d657b415f6c6974746c655f686172645f5273343f7d6867616d657b415f6c6974746c655f686172645f5273343f7d6867616d657b415f6c6974746c655f686172645f5273343f7d6867616d657b415f6c6974746c655f686172645f5273343f7d6867616d657b415f6c6974746c655f686172645f5273343f7d6867616d657b415f6c6974746c655f686172645f5273343f7d
正如前面说过的这里的循环体(6867616d657b415f6c6974746c655f686172645f5273343f7d
)就是flag的ASCII的十六进制
解码得到hgame{A_little_hard_Rs4?}
3. Sign_in_SemiHard
Question
Description
47.95.212.185 38611 仍然是由CNSS友情赞助 大量的心! hint: 不要想着一步到位 一段一段来 或者 碰碰运气(如果你比较欧的话
URL
http://plqbnxx54.bkt.clouddn.com/Sign_in_SemiHard.py
Base Score
500
Answer
照样看源码
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 from Crypto.Cipher import AESfrom hashlib import md5from os import urandomimport stringfrom signal import alarmclass Sign : block = T = 0 key = salt = "" def __init__ (self, key, salt ): self.key = key self.salt = salt self.block = len (key) def register (self, username ): if b'admin' in username: return None sig = md5(self.salt + username).digest() padlen = self.block - len (username) % self.block username += bytes ([padlen] * padlen) iv = urandom(self.block) aes = AES.new(self.key, AES.MODE_CBC, iv) c = aes.encrypt(username) return iv + c + sig def login (self, cipher ): if len (cipher) % self.block != 0 : return None self.T -= 1 iv = cipher[:self.block] sig = cipher[-self.block:] cipher = cipher[self.block:-self.block] aes = AES.new(self.key, AES.MODE_CBC, iv) p = aes.decrypt(cipher) p = p[:-p[-1 ]] return [p, md5(self.salt + p).digest() == sig] if __name__ == '__main__' : unprintable = b"" for i in range (256 ): if chr (i) not in string.printable: unprintable += bytes ([i]) alarm(60 ) s = Sign(urandom(16 ), urandom(16 )) while True : print("Choose:\n[1] Register\n[2] Login" ) op = input () if op == '1' : user = input ("Input your username(hex): " ) token = s.register(bytes .fromhex(user)) if not token: print("Sorry, invalid username." ) else : print("Your token is: %s" % token.hex ()) elif op == '2' : token = input ("Input your token: " ) res = s.login(bytes .fromhex(token)) if not res: print("Sorry, invalid token." ) elif not res[1 ]: user = res[0 ].hex () print("Sorry, your username(hex) %s is inconsistent with given signature." % user) else : user = res[0 ].strip(unprintable).decode("Latin1" ) print("Login success. Welcome, %s!" % user) if user == "admin" : print("I have a gift for you: %s" % FLAG) else : print("See you" ) break
这个看下来就是名副其实的CBC 了,而且还是带盐的md5验证。
register
的时候不许用admin
,甚至不能包含admin
。
然后又要login
的时候,名字去掉头尾的unprintable字符刚刚好是"admin"
。
那么就要用到CBC的字节反转攻击
来让最后的名字是admin,
以及对带盐的MD5的进行哈希长度扩展攻击
。
代码如下
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 import socketpad = b'e4b8ad' PAD = b'e4b8ad80000000000000000000000000000000000000000000000000000000000000000000000000980000000000000060646d696e' START = b'\xe4\xb8\xad\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' ZERO = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' END = b'\x00\x00\x00\x00\x00\x00\x00\x00\x98\x00\x00\x00\x00\x00\x00\x00' ADMIN = b'\x61\x64\x6d\x69\x6e\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b' targets = [START, ZERO, END, ADMIN] admin = b'\x60\x64\x6d\x69\x6e\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b' .hex () def xor (a, b ): return bytes (x ^ y for x, y in zip (a, b))def xor_attack (key, origin, target ): return xor(target, xor(bytes .fromhex(origin), bytes .fromhex(key)))block = 32 hostname = '47.95.212.185' port = 38611 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((hostname, port)) repr (s.recv(1024 ))s.sendall(b'1\n' ) repr (s.recv(1024 ))s.sendall(PAD + b'\n' ) token = repr (s.recv(1024 )).split(' ' )[3 ].split('\\' )[0 ] token = token[:block * 3 ] + xor_attack(token[block * 3 :block * 4 ], admin, ADMIN).hex () + token[block * 4 :block * 6 ] s.sendall(b'2\n' ) repr (s.recv(1024 ))s.sendall(token.encode('utf-8' ) + b'\n' ) for i in range (3 ): result = repr (s.recv(1024 )).split(' ' )[3 ] start = block * (2 - i) token = token[:start] \ + xor_attack(token[start:start + block], result[start:start + block], targets[2 - i]).hex () \ + token[start + block:block * 6 ] s.sendall(b'2\n' ) repr (s.recv(1024 )) s.sendall(token.encode('utf-8' ) + b'\n' ) repr (s.recv(1024 ))s.sendall(b'1\n' ) repr (s.recv(1024 ))s.sendall(pad + b'\n' ) print(repr (s.recv(1024 )).split(' ' )[3 ].split('\\' )[0 ][-block:]) s.sendall(b'2\n' ) repr (s.recv(1024 ))s.sendall(token[:block * 5 ].encode('utf-8' ) + input ().encode('utf-8' ) + b'\n' ) print("Received:" , repr (s.recv(1024 )))
由于我没有直接在python用hashpump(安不起来。。。),所以我在倒数第二部输出了md5
然后 在linux里执行
hashpump -d 中 -k 16 -a admin -s f5e0ad400b2a12944729d217df0860a2
PS:这里的-d 中
是因为hashpump非要我输入一个data,但是按照代码又不能是string.printable
,所以就弄了个中文,也就是前面那个奇怪的\xe4\xb8\xad
假设f5e0ad400b2a12944729d217df0860a2
就是刚输出的md5
那么会得到
d5c7a0383be95b611053e20298ba3f10 48800000000000000000000000000000000000000000000000000000000000000000000000009800000000000000admin
只要把d5c7a0383be95b611053e20298ba3f10
输回到input里即可
返回得到
Login success. Welcome, admin!have a gift for you: hgame{hard_cryptooooo!}:RegisterLogin
所以flag就是hgame{hard_cryptooooo!}