周末去 NepCTF 玩了下,原本还想尝试一下其他方向的题目的,结果还是只会做密码学方向的题。太菜啦!!!不过这场比赛是个人赛,做完题目后看了一下榜,发现只要单方向 ak 了的话排名都不差,说明各个方向的题目难度设计和题量是比较合理的,真不戳~

random_RSA
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556 |
from gmpy2 import next_prime, invert as inverse_modfrom Crypto.Cipher import PKCS1_v1_5from Crypto.PublicKey import RSAfrom random import getrandbitsfrom math import lcmfrom sys import exitflag = "1234"global_bits = 1024BANNER = rb""".--------.--------.--------.--------.--------.--------.--------.--------.--------.--------.--------.| N.--. | E.--. | P.--. | C.--. | T.--. | F.--. | H.--. | A.--. | P.--. | P.--. | Y.--. || :/\: | (\/) | :(): | :/\: | :/\: | :/\: | (\/) | :(): | :/\: | :/\: | (\/) || :\/: | :\/: | ()() | (__) | :\/: | (__) | :\/: | ()() | :\/: | :\/: | :\/: || '--'n | '--'e | '--'p | '--'c | '--'t | '--'f | '--'h | '--'a | '--'p | '--'p | '--'y |`--------`--------`--------`--------'--------`--------`--------`--------`--------`--------`--------`"""def generate_prime(bits: int): p = (getrandbits(bits - 32) << 32) return next_prime(p)def generate_private_key(bits: int): p, q = generate_prime(bits), generate_prime(bits) n, phi = p * q, lcm(p-1, q - 1) d = inverse_mod(0x10001, phi) privateKey = RSA.construct((int(n), int(0x10001), int(d), int(p), int(q))) return privateKey, p > qif __name__ == "__main__": print(BANNER.decode()) print("Welcome to the world of random RSA.") print("Please make your choice.") for _ in range(8): choice = input() if choice == '1': p, q = generate_prime(global_bits), generate_prime(global_bits) N = p*q d = generate_prime(global_bits-32) e = inverse_mod(d, (p * p - 1) * (q * q - 1)) print(f"{int(N)}") print(f"{int(e)}") elif choice == '2': privateKey, signal = generate_private_key(global_bits) Cipher = PKCS1_v1_5.new(privateKey) c = (Cipher.encrypt(flag.encode())) print(c) exit() else: exit() |
题目内容是比较简单的,基于 RSA 的变体,这里的 $n$ 是 2048 比特的,$d$ 只有 992 比特,$e \cdot d \equiv 1 \pmod{(p^2-1)\cdot (q^2-1)}$ ,手里头刚好有一篇论文 《Cryptanalysis of RSA Variants with Primes Sharing Most Significant Bits》不过这里还要求 $|p-q|$ 比较小。但是论文里头提了一嘴

所以我们直接找 $\frac{e}{N^2-\frac{9}{4}N +1}$ 的连分数就能恢复 $d$,从而恢复 $p,q$ 了。注意到这里的 $p,q,d$ 的高 992 位都是由 getrandbits 生成的随机数。一共 8 次交互机会,所以能有 $992/327+960/32=644$ 个 32 字节的随机数,思路来了:恢复所有的 $p,q,d$ ,恢复一个 MT19937 的状态,然后直接生成后续的 *p,q,d** 就能解密了。
需要注意的是我们虽然能够计算出 $p,q$ ,但不能保证 $p$ 就是原来的 $p$,可能顺序反了,因此还需要组合一下。
这里首先通过交互,前 7 次拿公钥对,最后一次拿密文。然后连分数来一下子
123456789101112131415161718192021222324252627282930313233 |
#! sagemathfrom gmpy2 import irootdef attack(N,e): convergents = continued_fraction(ZZ(e) / ZZ(int(N^2-9/4*N+1))).convergents() for c in convergents: k = c.numerator() d = c.denominator() if pow(pow(2, e, N), d, N) == 2: phi = (e * d - 1) // k #(p^2-1)*(q^2-1) = n^2+1-p^2-q^2 p_a_q = iroot(N^2+1-phi+2*N,2)[0] p_s_q = iroot(N^2+1-phi-2*N,2)[0] p = (p_a_q - p_s_q)//2 q = N//p print("[+]") return int(p),int(q),int(d)def chu(a): tmp = [] while a != 0: tmp.append(a%2**32) a >>= 32 return tmpN = [...]E = [...]P = []D = []for n,e in zip(N,E): p,q,d = attack(n,e) P.append(chu(p>>32)) P.append(chu(q>>32)) D.append(chu(d>>32)) |
然后调一下 MT19937 的板子
1234567891011121314151617181920212223242526272829303132333435363738 |
#! python3import randomfrom gmpy2 import next_prime, invert as inverse_modfrom gmpy2 import mpzfrom Crypto.Cipher import PKCS1_v1_5from Crypto.PublicKey import RSAfrom random import getrandbitsfrom math import lcmimport itertoolsc = b'-#\xa6<\x05\x91.dx\x1dp\xb3\xaa6\xaa\xe5\x87\xb6\xda\\\xc4\x02\x89\xd6]\xa1\x08\xf5y\x8b\xfd\x8c0\xaaU]\xca\x1dw\xf6,\xa2\xf1\xc0[0cU\xb6\xdd\x7f\xdd\xa4\xab\x033FLGR\xe8\x99\\\tw\xa6j\x1d\x8b\xf8[\xa55\xbdx\x7f\x14\xd6\xf6\xc9*;\xf2\\\x0eN\xe0\x1fJ0\xa9\xc2\xbd]\x0cFM\xd0|\x0e\x8dQ.\xd3y\x0f+\xf3\xd1h>\x0e\x9b\xa2>\xa2\xcd\xbd\xd7\x14O\xf8$rF\r#o\x94\x87ys_\xe0\xba\x97\x9bo\xa1S\x07\'\x8au\xfbg\xf1\x99\xe4\xf0{\xef`\xbfZ\xe1a\x13\x1d\x91\x9b\xb1cJ\x7f \xec}\xeaS j,\xeb\x9d\x95\xf5a\xab\x02\xe6\xf2\x06f\x9eF\x96:\xba};\xd9\xfc\xdb"\xb5G>\xe1<\xae1\x17x\xbeN\x05\xab^\x96\xb7\x9d\xb6lS fo\x95\xb8\x19\xa4\xf8\r,V\x8e\xa1\xcf\xcf\x82\x9a\xbd~\xbd\xfb\xb3\xe2\xd83\xf7\x0c\x01\x9bW:\x92lj\x85\x92\x89Gsw\xcc'for i in itertools.product([0,1],repeat = 7): N = [] index = 0 for each in i: N+=PP[index][each] N+=PP[index][1-each] N+=D[index] index+=1 N = [int(n) for n in N] mtb = MT19937Recover() try: r2 = mtb.go_on(N) except Exception as e: continue p, q = generate_prime(1024), generate_prime(1024) n, phi = p * q, lcm(p-1, q - 1) d = inverse_mod(0x10001, phi) privateKey = RSA.construct((int(n), int(0x10001), int(d), int(p), int(q))) Cipher = PKCS1_v1_5.new(privateKey) flag = (Cipher.decrypt(c,'\x00')) print(flag) exit()b'NepCTF{c4e4356067fb3bedc53dde7af59beb1c}' |
bombe-crib
题目说明:
面对每天六点德军铺天盖地的天气预报,你突然想到了怎么确定关键信息的位置。
注:enigma机可使用pyenigma项目,但源项目有问题,可修改后使用cyberchef对拍。
是一个恩格玛机相关的题目,代码文件太多,这里只放一下交互的 handle 部分
1234567891011121314151617181920212223242526272829303132333435363738 |
from pwn import *from pwnlib.util.iters import mbruteforcefrom hashlib import sha256sh = remote("nepctf.1cepeak.cn",31211)context.log_level = 'debug'def proof_of_work(sh): sh.recvuntil("XXXX+") suffix = sh.recvuntil(')').decode("utf8")[:-1] log.success(suffix) sh.recvuntil("== ") cipher = sh.recvline().strip().decode("utf8") proof = mbruteforce(lambda x: sha256((x + suffix).encode()).hexdigest() == cipher, string.ascii_letters + string.digits, length=4, method='fixed') sh.sendlineafter("[+] Plz tell me XXXX: ", proof)def check(pos): for index in range(len(flag)): for each in s: if each[pos%52] == flag[index]: return False pos+=1 return Trueproof_of_work(sh)flag = "WETTERBERICHT"for _ in range(10): s = [] for _ in range(20): s.append(sh.recvuntil("\n")[:-1].decode()) print(s) for pos in range(len(s[0])): if check(pos): sh.recvuntil("[-] ") sh.sendline(str(pos)) breaksh.interactive() |
看一下题目流程,
- 先生成了一段随机字符串
other
- 指定一个位置
pos
,在该位置插入已知明文WETTERBERICHT
,组成text
- 指定了恩格玛机加密所需要的转子:
dayrotors
- 对
text
进行二十次加密,每次使用不同的tmpkey
和plugin
所以我们就是需要根据 text
的二十个密文来确定 WETTERBERICHT
所在的位置。
在此前我对恩格玛机也是一点都不了解的,在查相关资料的时候,看见了这么一句话

明文不可能加密回自己本身,思路来了:只需要判断从哪个位置开始,二十个密文的子串和 WETTERBERICHT
的重合率为 0 即可。
举个例子就是,假设 pos=0
,那么二十个密文的第一个字符都不会是 W
,第二个字符都不会是 E
。
1234567891011121314151617181920212223242526272829303132333435363738 |
from pwn import *from pwnlib.util.iters import mbruteforcefrom hashlib import sha256sh = remote("nepctf.1cepeak.cn",31211)context.log_level = 'debug'def proof_of_work(sh): sh.recvuntil("XXXX+") suffix = sh.recvuntil(')').decode("utf8")[:-1] log.success(suffix) sh.recvuntil("== ") cipher = sh.recvline().strip().decode("utf8") proof = mbruteforce(lambda x: sha256((x + suffix).encode()).hexdigest() == cipher, string.ascii_letters + string.digits, length=4, method='fixed') sh.sendlineafter("[+] Plz tell me XXXX: ", proof)def check(pos):for index in range(len(flag)):for each in s:if each[pos%52] == flag[index]:return Falsepos+=1return Trueproof_of_work(sh)flag = "WETTERBERICHT"for _ in range(10):s = []for _ in range(20):s.append(sh.recvuntil("\n")[:-1].decode())print(s)for pos in range(len(s[0])):if check(pos):sh.recvuntil("[-] ")sh.sendline(str(pos))breaksh.interactive() |
bombe-Rejewski
题目说明:
德军目前使用日密钥加密临时密钥两次的工作模式,相信你一定可以发现其中的端倪!
注:enigma机可使用pyenigma项目,但源项目有问题,可修改后使用cyberchef对拍。
注:本题update了以下部分
- 修改了积分逻辑,统计密钥恢复正确的正确次数;
- 修改了挑战次数,提升一次打通的概率;
- 增加了日接线板,现在无法使用爆破的方式直接破拆;
还是看到 handle 部分
123456789101112131415161718192021222324252627282930313233 |
def handle(self): signal.alarm(5) if not self.proof_of_work(): self.send(b'[!] Wrong!') return score=0 r1,r2,r3=myrotors[:3] for _ in range(20): s=product(ascii_uppercase,repeat=3) daykey="".join(choice(list(s))) k1=list(ascii_uppercase) k2=list(ascii_uppercase) k3=list(ascii_uppercase) shuffle(k1) shuffle(k2) shuffle(k3) tmpkeys=[x+y+z for x,y,z in zip(k1,k2,k3)] for key in tmpkeys: myEnigma=enigma.Enigma(myReflector,r1,r2,r3,daykey) c1=myEnigma.encipher(key) c2=myEnigma.encipher(key) self.send((c1+c2).encode()) self.send(b"now gave me the daykey:") ans=self.recv() if daykey.encode()==(ans[:3]): score+=1 if score>=10: self.send(b'your score is %d,here is your flag'%score) self.send(flag) else : self.send(b'sorry,your score is %d, plz try again'%score) |
看一下题目流程,
- 指定一个 3 字节的
daykey
- 生成三个打乱的字母表
k1,k2,k3
- 将每个字母表相同位置的字母取出来组合,生成 26 个 3 字节的随机字符串
tmpkeys
- 对每个随机字符串加密两次,输出密文。(根据恩格玛机的加密特性,本质上是加密了一串重复的字符串)
没有思路,找巨人的肩膀,根据 Rejewski
,
搜到了论文 https://people.cs.uchicago.edu/~davidcash/284-autumn-19/02-permutations-and-enigma.pdf
# Rejewski’s Theorem and Attack
在github上搜到了项目 https://github.com/NationalSecurityAgency/enigma-simulator/tree/master
简单读了下论文,了解了一下攻击流程。然后就可以跟着项目里的 MasterEnigmaCracker.ipynb
做了。不过需要调一下恩格玛机的参数。要把 rotor 和 Reflector 调成题目里的。
在 enigma-simulator-master/components.py
中设置
12345678 |
ROTOR_WIRINGS = { 'I': {'forward': 'UHQAOFBEPIKZSXNCWLGJMVRYDT', 'backward':'DGPYHFSBJTKRUOEICWMZAVQNXL'}, 'II':{'forward':'RIKHFBUJDNCGWSMZVXEQATOLYP', 'backward':'UFKISELDBHCXOJWZTANVGQMRYP'}, 'III':{'forward':'ENQXUJSIVGOMRLHYCDKTPWAFZB', 'backward':'WZQRAXJOHFSNLBKUCMGTEIVDPY'},} |
其中 backword 的计算方式在题目给的 pyenigma/rotor 中有
12345678 |
if wiring != None: self.wiring = wiringelse: self.wiring="ABCDEFGHIJKLMNOPQRSTUVWXYZ"self.rwiring = ["0"] * 26# 根据 forward 计算 backwordfor i in range(0, len(self.wiring)): self.rwiring[ord(self.wiring[i]) - ord('A')]= chr(ord('A') + i) |
然后还要在 component 中调一下 reflector
123456 |
class Reflector: ''' This class defines the reflector for the Engima machine. ''' def __init__(self): self.wiring = {'A': 'W', 'B': 'O', 'C': 'E', 'D': 'H', 'E': 'C', 'F': 'K', 'G': 'Y', 'H': 'D', 'I': 'M', 'J': 'T', 'K': 'F', 'L': 'R', 'M': 'I', 'N': 'Q', 'O': 'B', 'P': 'Z', 'Q': 'N', 'R': 'L', 'S': 'V', 'T': 'J', 'U': 'X', 'V': 'S', 'W': 'A', 'X': 'U', 'Y': 'G', 'Z': 'P'} |
另外在调 rejewski.py 中的 make_chain_length_dict() 函数生成字典的时候,根据直接把 rotors 的顺序定死
123 |
for key in tqdm(keys): # For rotor combination in rotors. for order in (['I', 'II', 'III'],): |
因为题目取得就是前三个转子 r1,r2,r3=myrotors[:3]
另外可能会出现多解的情况,我们无法判断,随便传一个就好;也有可能无解,那就随便猜一个。剩下的全靠运气了。我这里运气不错,第二次就成功了。
attack.py
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475 |
import machine as mdef generate_permutation_dicts(message_encrypts): ''' Outputs AD, BE, and CF dictionaries generated from a series of double message key encryptions. Params: message_encrypts = an iterator/list containing a double message key encryptions. ''' alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' AD = {letter:None for letter in alphabet} BE = {letter:None for letter in alphabet} CF = {letter:None for letter in alphabet} for m in message_encrypts: if any(AD[i] is None for i in AD) or any(BE[i] is None for i in BE) or any(CF[i] is None for i in CF): AD[m[0]] = m[3] if AD[m[0]] is None else AD[m[0]] BE[m[1]] = m[4] if BE[m[1]] is None else BE[m[1]] CF[m[2]] = m[5] if CF[m[2]] is None else CF[m[2]] return AD, BE, CFdef make_chains_from_permutation_dict(permutation_dict): ''' Given a dictionary linking the first and fourth (or second and fifth or third and sixth) letter combinations from the message keys, generate the length of chains (i.e. cycles of letters) found in the dictionary. ''' alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' chains = [] while len(alphabet) > 0: next_chain = permutation_dict[alphabet[0]] next_letter = permutation_dict[next_chain[0]] # must remove the first two letters in the chain. alphabet = alphabet.replace(alphabet[0], '') alphabet = alphabet.replace(next_chain, '') while next_letter != next_chain[0]: next_chain += next_letter alphabet = alphabet.replace(next_letter, '') next_letter = permutation_dict[next_letter] chains.append(next_chain) alphabet.replace(next_letter, '') return chains# Generate the indicies for your chains based on the permutation dictionaries. def generate_chain_index(chains): ''' Given a list of chains for the AD, BE, and CF pairs, find their lengths and put them in a digestable form for indexing. MAJOR ASSUMPTION: these chains will come directly from my code, so they will be in the order AD, BE, CF. ''' chain_index = 'AD:' for i, chain_list in enumerate(chains): if i == 1: chain_index = chain_index + ' BE:' elif i == 2: chain_index = chain_index + ' CF:' # Sort the chains to make sure the number is unique. chain_list.sort(key=lambda x: len(x)) for c in chain_list: chain_index = chain_index + str(len(c)) return chain_indeximport pickledef attack(s): e = m.Enigma() message_key_encrypts = s AD, BE, CF = generate_permutation_dicts(message_key_encrypts) AD_chains = make_chains_from_permutation_dict(AD) BE_chains = make_chains_from_permutation_dict(BE) CF_chains = make_chains_from_permutation_dict(CF) index = generate_chain_index([AD_chains, BE_chains, CF_chains]) chains_dict = pickle.load(open('chains.pickle', 'rb')) s = (chains_dict[index]) for each in s: return(''.join(i for i in each[0])) |
inter.py
1234567891011121314151617181920212223242526272829303132333435363738 |
import signalimport stringimport randomfrom string import ascii_uppercasefrom random import shuffle,choiceimport osfrom itertools import productfrom pwn import *from pwnlib.util.iters import mbruteforcefrom hashlib import sha256sh = remote("nepctf.1cepeak.cn",32483)context.log_level = 'debug'def proof_of_work(sh): sh.recvuntil("XXXX+") suffix = sh.recvuntil(')').decode("utf8")[:-1] log.success(suffix) sh.recvuntil("== ") cipher = sh.recvline().strip().decode("utf8") proof = mbruteforce(lambda x: sha256((x + suffix).encode()).hexdigest() == cipher, string.ascii_letters + string.digits, length=4, method='fixed') sh.sendlineafter("[+] Plz tell me XXXX: ", proof)proof_of_work(sh)from attack import attackfor _ in range(30): s = sh.recvuntil("daykey:").split("\n") s = s[:-1] try: ans = attack(s) except: ans = 'AAA' sh.recvuntil("[-] ") sh.sendline(ans)sh.interactive() |
recover
题目描述:
小A发现一段纯P盒加密的密文,但等待他还原的其实是……?
(flag格式为flag{纯小写字母},对应变量本身即包含flag{}结构)
flag变量给出了最终提交内容的具体约束,具体格式为flag{xxx}中间为18个小写字母,约束为对含有“flag{}”整体的约束。readable是可读,不只是printable可打印。请各位师傅自行估算复杂度,正解基于python的爆破时间在5min左右,请观察加密部件特征,降低复杂度
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112 |
from math import ceilfrom hashlib import md5from Crypto.Util.number import *from secret import key, flagdef MD5(x): return md5(x).hexdigest()assert (len(flag) == 58)assert (len(key) == 24)P = [[0, 2, 3, 4, 5, 6], [1, 4], [0, 3], [0, 3, 4, 5], [0, 1, 2, 3, 4, 7], [2, 3, 4, 5, 6], [0, 1, 2, 3], [1, 2, 3, 4, 5, 7], [8, 12, 13, 14], [8, 11, 12, 13, 15], [9, 12, 15], [11, 13, 15], [8, 9, 10, 12, 13, 15], [8, 9], [11, 13, 14], [10], [16, 19, 20], [16, 18, 19, 20, 21], [16, 17, 18, 19], [17, 18, 20, 22, 23], [17, 19, 23], [17, 18, 20, 21, 23], [16, 18, 20, 21, 22, 23], [16, 17, 18, 20, 21, 22, 23], [25, 26, 29, 31], [25, 26], [26, 28, 30], [27, 28, 29, 30, 31], [25, 29], [25, 26, 30, 31], [28, 29, 30, 31], [24, 26, 29, 31], [35, 36, 39], [33, 35, 38], [33, 35, 37, 39], [32, 33, 34, 35, 37, 38], [32, 33, 34, 35, 37, 39], [35, 38], [33, 34, 38, 39], [33, 34, 39], [41, 42, 43, 44, 47], [40, 41, 42, 45, 47], [41, 42, 45, 47], [40, 43, 44, 46], [41, 46, 47], [41, 42, 43, 44, 46, 47], [41, 42, 44, 45], [40, 41, 42, 45, 46], [48, 50, 51, 52, 53, 54, 55], [48, 49, 50, 52, 53, 54, 55], [49, 55], [48, 49, 50, 51, 52, 54], [52, 53], [48, 49, 53, 54, 55], [48, 49, 52, 55], [48, 49, 51, 52, 55], [58, 59], [56, 61, 63], [57, 63], [56, 59, 60], [61, 63], [57, 58, 61, 62, 63], [57, 58], [60, 62]]def enc(v, keys, le): t = v for i in keys: q = [] for j in P: tmp = 0 for k in j: tmp ^= t[k] q.append(tmp) t = [int(q[j]) ^ int(i[j]) for j in range(le)] return tkeys = []for i in range(len(key)//8): l = bytes_to_long(key[i*8:i*8+8]) m = bin(l)[2:].zfill(8*8) keys.append([int(i) for i in m])fb = bin(bytes_to_long(flag))[2:].zfill(ceil(len(flag)/8)*8*8)ciphertext = ""for i in range(0, len(fb), 64): t = enc([int(j) for j in fb[i:i+64]], keys, 64) ciphertext += "".join([str(j) for j in t])print(ciphertext)"""11101100100000110101100101100001100111011100100111000000010110000110011011000100110101110111010000100100001100010011001100010100101000110001011101000000100010101000000110000110011110001101110110110111000000100010011011011011101011101000000000100010000101001110100101011000001110010000000000100110001101110011111010001100101101111010101111101110100110101010011010011010101110100001001101100110010000010000011100100101111010010000011001000110000100110111100010101011000100100111010000101010110110001010110101111111""" |
题目流程,,不总结了,一看全是线性运算,思路来了:上 z3,加密照抄
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124 |
from z3 import *P = [[0, 2, 3, 4, 5, 6], [1, 4], [0, 3], [0, 3, 4, 5], [0, 1, 2, 3, 4, 7], [2, 3, 4, 5, 6], [0, 1, 2, 3], [1, 2, 3, 4, 5, 7], [8, 12, 13, 14], [8, 11, 12, 13, 15], [9, 12, 15], [11, 13, 15], [8, 9, 10, 12, 13, 15], [8, 9], [11, 13, 14], [10], [16, 19, 20], [16, 18, 19, 20, 21], [16, 17, 18, 19], [17, 18, 20, 22, 23], [17, 19, 23], [17, 18, 20, 21, 23], [16, 18, 20, 21, 22, 23], [16, 17, 18, 20, 21, 22, 23], [25, 26, 29, 31], [25, 26], [26, 28, 30], [27, 28, 29, 30, 31], [25, 29], [25, 26, 30, 31], [28, 29, 30, 31], [24, 26, 29, 31], [35, 36, 39], [33, 35, 38], [33, 35, 37, 39], [32, 33, 34, 35, 37, 38], [32, 33, 34, 35, 37, 39], [35, 38], [33, 34, 38, 39], [33, 34, 39], [41, 42, 43, 44, 47], [40, 41, 42, 45, 47], [41, 42, 45, 47], [40, 43, 44, 46], [41, 46, 47], [41, 42, 43, 44, 46, 47], [41, 42, 44, 45], [40, 41, 42, 45, 46], [48, 50, 51, 52, 53, 54, 55], [48, 49, 50, 52, 53, 54, 55], [49, 55], [48, 49, 50, 51, 52, 54], [52, 53], [48, 49, 53, 54, 55], [48, 49, 52, 55], [48, 49, 51, 52, 55], [58, 59], [56, 61, 63], [57, 63], [56, 59, 60], [61, 63], [57, 58, 61, 62, 63], [57, 58], [60, 62]]def enc(v, keys, le): t = v for i in keys: q = [] for j in P: tmp = z3.BitVecVal(0,1) for k in j: tmp ^= t[k] q.append(tmp) t = [q[j] ^ i[j] for j in range(le)] return tfrom Crypto.Util.number import *from z3 import *key_sym = [[z3.BitVec("key_%d_%d" % (j,i), 1) for i in range(64)] for j in range(3)]plain_sym =[[z3.BitVec("plain_%d_%d" % (j,i), 1) for i in range(64)] for j in range(8)]sol = z3.Solver()c = "11101100100000110101100101100001100111011100100111000000010110000110011011000100110101110111010000100100001100010011001100010100101000110001011101000000100010101000000110000110011110001101110110110111000000100010011011011011101011101000000000100010000101001110100101011000001110010000000000100110001101110011111010001100101101111010101111101110100110101010011010011010101110100001001101100110010000010000011100100101111010010000011001000110000100110111100010101011000100100111010000101010110110001010110101111111"for i in range(48): sol.add(plain_sym[0][i] == 0)for _ in range(8): a = enc(plain_sym[_], key_sym, 64) for i in range(64): sol.add(a[i] == c[_*64+i])plains_sym =[[z3.BitVec("plains_%d_%d" % (i,j), 8) for i in range(8)] for j in range(8)]for i in range(8): for j in range(8): for k in range(8): sol.add(plain_sym[i][8*j+k] == Extract(7-k, 7-k, plains_sym[i][j]))for j in range(6): sol.add(0==plains_sym[0][j])sol.add(125==plains_sym[-1][-1])for i in range(1,8): for j in range(8): sol.add(Or(And(48<=plains_sym[i][j],plains_sym[i][j] <= 57) , And(95<=plains_sym[i][j])) )if sol.check() == sat: m = sol.model() flag = "" for i in range(0,8): a = [m.eval(x).as_long() for x in plains_sym[i]] flag += '' .join (chr(i) for i in a) print(flag)# flag{flag_is_the_readable_key_whose_md5_starts_with_3fe04} |
然后发现原来我们需要恢复 key,那就继续 z3
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960 |
from Crypto.Util.number import *from z3 import *from recover import Pdef enc(v, keys, le): t = v for i in keys: q = [] for j in P: tmp = z3.BitVecVal(0,1) for k in j: tmp ^= t[k] q.append(tmp) t = [q[j] ^ i[j] for j in range(le)] return tc = "11101100100000110101100101100001100111011100100111000000010110000110011011000100110101110111010000100100001100010011001100010100101000110001011101000000100010101000000110000110011110001101110110110111000000100010011011011011101011101000000000100010000101001110100101011000001110010000000000100110001101110011111010001100101101111010101111101110100110101010011010011010101110100001001101100110010000010000011100100101111010010000011001000110000100110111100010101011000100100111010000101010110110001010110101111111"p = '00000000000000000000000000000000000000000000000001100110011011000110000101100111011110110110011001101100011000010110011101011111011010010111001101011111011101000110100001100101010111110111001001100101011000010110010001100001011000100110110001100101010111110110101101100101011110010101111101110111011010000110111101110011011001010101111101101101011001000011010101011111011100110111010001100001011100100111010001110011010111110111011101101001011101000110100001011111001100110110011001100101001100000011010001111101'c = [int(i) for i in list(c)]p = [int(i) for i in list(p)]key_sym = [[z3.BitVec("key_%d_%d" % (j,i), 1) for i in range(64)] for j in range(3)]keys_sym = [[z3.BitVec("key_%d_%d" % (j,i), 8) for i in range(8)] for j in range(3)]sol = z3.Solver()# 约束 key 全部为小写字母for i in range(3): for j in range(8): for k in range(8): sol.add(key_sym[i][8*j+k] == Extract(7-k, 7-k, keys_sym[i][j]))for i in range(5,23): sol.add(And(97<=keys_sym[i//8][i%8],keys_sym[i//8][i%8]<=122))# 约束flag格式sol.add(keys_sym[0][0]== ord('f'))sol.add(keys_sym[0][1]== ord('l'))sol.add(keys_sym[0][2]== ord('a'))sol.add(keys_sym[0][3]== ord('g'))sol.add(keys_sym[0][4]== ord('{'))sol.add(keys_sym[2][7]== ord('}'))# 基础约束:明文使用密钥加密后为指定密文for i in range(8): a = enc(p[i*64:i*64+64], key_sym, 64) for j in range(64): sol.add(a[j] == c[i*64+j])if sol.check() == sat: m = sol.model() key = '' for i in range(3): a = [m.eval(x).as_long() for x in keys_sym[i]] key += ''.join(chr(i) for i in a) key = key.encode() print("[+]",key) |
随后发现有多解了,而 z3 又无法给出所有的解,于是我就开始猜了。
猜key中包含哪些字符,首先根据题目名,我猜回包含字符串 recover
,但不确定 recover
的位置,那就爆 !
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879 |
from z3 import *from hashlib import md5from recover import Pdef MD5(x): return md5(x).hexdigest()def enc(v, keys, le): t = v for i in keys: q = [] for j in P: tmp = z3.BitVecVal(0,1) for k in j: tmp ^= t[k] q.append(tmp) t = [q[j] ^ i[j] for j in range(le)] return tfrom Crypto.Util.number import *c = "11101100100000110101100101100001100111011100100111000000010110000110011011000100110101110111010000100100001100010011001100010100101000110001011101000000100010101000000110000110011110001101110110110111000000100010011011011011101011101000000000100010000101001110100101011000001110010000000000100110001101110011111010001100101101111010101111101110100110101010011010011010101110100001001101100110010000010000011100100101111010010000011001000110000100110111100010101011000100100111010000101010110110001010110101111111"p = '00000000000000000000000000000000000000000000000001100110011011000110000101100111011110110110011001101100011000010110011101011111011010010111001101011111011101000110100001100101010111110111001001100101011000010110010001100001011000100110110001100101010111110110101101100101011110010101111101110111011010000110111101110011011001010101111101101101011001000011010101011111011100110111010001100001011100100111010001110011010111110111011101101001011101000110100001011111001100110110011001100101001100000011010001111101'def attack2(index): key_sym = [[z3.BitVec("key_%d_%d" % (j,i), 1) for i in range(64)] for j in range(3)] keys_sym = [[z3.BitVec("key_%d_%d" % (j,i), 8) for i in range(8)] for j in range(3)] sol = z3.Solver() for i in range(3): for j in range(8): for k in range(8): sol.add(key_sym[i][8*j+k] == Extract(7-k, 7-k, keys_sym[i][j])) for i in range(5,23): sol.add(And(97<=keys_sym[i//8][i%8],keys_sym[i//8][i%8]<=122)) sol.add(keys_sym[0][0]== ord('f')) sol.add(keys_sym[0][1]== ord('l')) sol.add(keys_sym[0][2]== ord('a')) sol.add(keys_sym[0][3]== ord('g')) sol.add(keys_sym[0][4]== ord('{')) sol.add(keys_sym[2][7]== ord('}')) sol.add(keys_sym[(index+0)//8][(index+0)%8]== ord('r')) sol.add(keys_sym[(index+1)//8][(index+1)%8]== ord('e')) sol.add(keys_sym[(index+2)//8][(index+2)%8]== ord('c')) sol.add(keys_sym[(index+3)//8][(index+3)%8]== ord('o')) sol.add(keys_sym[(index+4)//8][(index+4)%8]== ord('v')) sol.add(keys_sym[(index+5)//8][(index+5)%8]== ord('e')) sol.add(keys_sym[(index+6)//8][(index+6)%8]== ord('r')) for i in range(8): a = enc(p[i*64:i*64+64], key_sym, 64) for j in range(64): sol.add(a[j] == c[i*64+j]) if sol.check() == sat: m = sol.model() key = '' for i in range(3): a = [m.eval(x).as_long() for x in keys_sym[i]] key += ''.join(chr(i) for i in a) key = key.encode() print(key) if MD5(key)[:5] == '3fe04': print("[+]",key) exit()from tqdm import *table = "qwertyuiopasdfghjklzxcvbnm"for index in range(0,24-6): attack2(index) |
得到 b'flag{ynrdertqrecoveryoe}'
,还不是正确答案,不过看到 recover 的位置,最后还剩 3 个字节,而且还有一个 e
在最后,忙猜是一个 key
,于是再加三条约束
123 |
sol.add(keys_sym[2][4]== ord('k'))sol.add(keys_sym[2][5]== ord('e'))sol.add(keys_sym[2][6]== ord('y')) |
跑一下脚本,bingo!
12 |
b'flag{hardertorecoverkey}'[+] b'flag{hardertorecoverkey}' |
估摸着出题人的预期应该不是这么做,可以赛后看一下官方 wp 学习一下。
SecureAgg
交互题,看到 handle
12345678910111213141516171819202122232425262728293031 |
def handle(self): signal.alarm(180) self.send(BANNER) self.send(b"Generating parameters...") agg=AggServer(114) agg.genKeys() self.send(agg.system_info().encode()) try: for i in range(ROUNDS): self.send(f'#Round {i+1}'.encode()) enc_list=agg.get_enc_list() self.send(f"enc_list={enc_list}".encode()) key=agg.aggregate() message=''.join(random.sample(string.ascii_letters,16)) aes_key=sha256(str(key).encode()).digest()[:16] aes=AES.new(aes_key,AES.MODE_CBC,iv=bytes(range(16))) enc=aes.encrypt(pad(message.encode(),16)) self.send(f"enc={b64encode(enc)}".encode()) inp=self.recv(b"input your message: ").decode() if inp==message: self.send(b'Correct!') else: self.send(b'Error!') exit(0) agg.update() self.send(flag) except: self.send(b'wtf?') self.close() |
看一下题目流程,一共二十轮,每一轮
-
题目会给出一个
enc_list
, -
然后使用
agg.aggregate()
生成一个密钥key
-
下面会用这个
key
作为 AES 密钥加密一段随机字符串,要求我们解密这个随机字符串
轮与轮之间都是相互独立的,没啥操作空间,就是很单纯的要根据 enc_list
来恢复 key
定位到 enc_list
和 key
相关的代码,在AggServer.py
1234567891011 |
def get_enc_list(self): enc_list=[] for u in self.users: enc_data=u.get_enc_data(self.M) enc_list.append(enc_data) return enc_list...def aggregate(self): self.key=sum([ u.data for u in self.users])%self.M return self.key |
所以 enc_list
就是每个用户的 enc_data
组成列表,key
就是每个用户的 data
之和。
我们继续看到 enc_data
相关的代码,在User.py
12345678 |
def get_enc_data(self,M): enc=(114*self.data+514)%M for id,k in self.agreement_keys.items(): if id>self.id: enc+=PRG(k).randbits(self.mbits)%M elif id<self.id: enc-=PRG(k).randbits(self.mbits)%M return enc |
其中 PRG 就是一个 LCG 伪随机数生成器,k 就是每个用户的自己的私钥和其他用户公钥生成的类似 DH 交换密钥的值
1234567 |
def agree(self,users,p): self.agreement_keys={} for u in users: if u.id!=self.id: u_pub=u.pub k=pow(u_pub,self.priv,p) self.agreement_keys.update({u.id:k}) |
id 是每个用户的id值(0-7)。所以思路来了:将 enc_data 求和减去 514*8 ,再在模 M 下除以 114 就可以了
举个例子, 在 get_enc_data
中有用户 A 和 B,A 的 id 小于 B,所以 A 的 enc 会减一个 $PRG((g^{b})^a)$,但是 B 的 id 大约 A,所以 B 的 enc 会加一个 $PRG((g^a)^b)$,是相等的,所以全加在一起的话就都消掉了。
123456789101112131415161718192021222324252627282930 |
from pwn import *from Crypto.Util.number import *from hashlib import sha256from Crypto.Cipher import AESfrom Crypto.Util.Padding import pad,unpadfrom base64 import b64encode,b64decodecontext.log_level = 'debug'def dec(c,key): aes_key=sha256(str(key).encode()).digest()[:16] aes=AES.new(aes_key,AES.MODE_CBC,iv=bytes(range(16))) message=unpad(aes.decrypt(b64decode(c))) return messagesh = remote("nepctf.1cepeak.cn",31045)sh.recvutil("M=")M = int(sh.recvutil("\n")[:-1])for _ in range(20): sh.recvutil("enc_list=") pubkeys = eval(sh.recvutil("]")) sh.recvutil("enc=b'") c = sh.recvutil("'")[:-1] key = (sum(pubkeys)-514*8)*inverse(114,M) % M sh.recvutil("input your message: ") sh.sendline(dec(c,key))sh.interactive() |
simple_des
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
from operator import addfrom typing import Listfrom functools import reducefrom gmpy2 import *from Crypto.Util.number import *_IP = [57, 49, 41, 33, 25, 17, 9, 1,59, 51, 43, 35, 27, 19, 11, 3,61, 53, 45, 37, 29, 21, 13, 5,63, 55, 47, 39, 31, 23, 15, 7,56, 48, 40, 32, 24, 16, 8, 0,58, 50, 42, 34, 26, 18, 10, 2,60, 52, 44, 36, 28, 20, 12, 4,62, 54, 46, 38, 30, 22, 14, 6]def IP(plain: List[int]) -> List[int]: return [plain[x] for x in _IP]__pc1 = [56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 60, 52, 44, 36, 28, 20, 12, 4, 27, 19, 11, 3]__pc2 = [13, 16, 10, 23, 0, 4, 2, 27, 14, 5, 20, 9,22, 18, 11, 3, 25, 7,15, 6, 26, 19, 12, 1,40, 51, 30, 36, 46, 54,29, 39, 50, 44, 32, 47,43, 48, 38, 55, 33, 52,45, 41, 49, 35, 28, 31]ROTATIONS = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]def PC_1(key: List[int]) -> List[int]: return [key[x] for x in __pc1]def PC_2(key: List[int]) -> List[int]: return [key[x] for x in __pc2]def get_sub_key(key: List[int]) -> List[List[int]]: key = PC_1(key) L, R = key[:28], key[28:] sub_keys = [] for i in range(16): for j in range(ROTATIONS[i]): L.append(L.pop(0)) R.append(R.pop(0)) combined = L + R if i == 0: print(f"combined = {combined}") sub_key = PC_2(combined) sub_keys.append(sub_key) print('LL=',L) print('Rr=',R) return sub_keys__ep = [31, 0, 1, 2, 3, 4, 3, 4, 5, 6, 7, 8, 7, 8, 9, 10, 11, 12,11, 12, 13, 14, 15, 16,15, 16, 17, 18, 19, 20,19, 20, 21, 22, 23, 24,23, 24, 25, 26, 27, 28,27, 28, 29, 30, 31, 0]__p = [15, 6, 19, 20, 28, 11, 27, 16,0, 14, 22, 25, 4, 17, 30, 9,1, 7, 23, 13, 31, 26, 2, 8,18, 12, 29, 5, 21, 10, 3, 24]def EP(data: List[int]) -> List[int]: return [data[x] for x in __ep]def P(data: List[int]) -> List[int]: return [data[x] for x in __p]__s_box = [[[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7],[ 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8],[ 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0],[15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13]],[[15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10],[ 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5],[ 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15],[13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9]],[[10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8],[13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1],[13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7],[ 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12]],[[ 7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15],[13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9],[10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4],[ 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14]],[[ 2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9],[14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6],[ 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14],[11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3]],[[12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11],[10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8],[ 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6],[ 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13]],[[ 4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1],[13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6],[ 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2],[ 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12]],[[13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7],[ 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2],[ 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8],[ 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11]]]def S_box(data: List[int]) -> List[int]: output = [] for i in range(0, 48, 6): row = data[i] * 2 + data[i + 5] col = reduce(add, [data[i + j] * (2 ** (4 - j)) for j in range(1, 5)]) output += [int(x) for x in format(__s_box[i // 6][row][col], '04b')] return outputdef encrypt(plain: List[int], sub_keys: List[List[int]]) -> List[int]: plain = IP(plain) L, R = plain[:32], plain[32:] for i in range(16): prev_L = L L = R expanded_R = EP(R) xor_result = [a ^ b for a, b in zip(expanded_R, sub_keys[i])] substituted = S_box(xor_result) permuted = P(substituted) R = [a ^ b for a, b in zip(permuted, prev_L)] cipher = R + L cipher = [cipher[x] for x in [39, 7, 47, 15, 55, 23, 63, 31, 38, 6, 46, 14, 54, 22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29, 36, 4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27, 34, 2, 42, 10, 50, 18, 58, 26, 33, 1, 41, 9, 49, 17, 57, 25, 32, 0, 40, 8, 48, 16, 56, 24]] return cipherdef bitxor(plain1: List[int], plain2: List[List[int]]) -> List[int]: return [int(i) for i in bin(int(''.join(str(i) for i in plain1),2)^int(''.join(str(i) for i in plain2),2))[2:].zfill(64)]#key的字母表为abcdefghijklmnopqrstuvwsyzfrom secret import flag, keyt=[]z=[[0]*64]from operator import addkey = reduce(add, [list(map(int, bin(key_byte)[2:].zfill(8))) for key_byte in key])for i in range(0,3): pt = reduce(add, [list(map(int, bin(flag_byte)[2:].zfill(8))) for flag_byte in flag[ 8*i:8*(i+1) ]]) ct = encrypt(pt, get_sub_key(bitxor(z[i],key))) z.append(pt) t += ctprint(t)'''i=0情况下的LL,RrLL= [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]Rr= [0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0]t=[0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0]''' |
题目实现了一个经典的 DES ,不过除了第一组,后面两组加密时密钥会先和前一组的明文异或。题目还给出了第一组加密时的最后一轮轮密钥( L 部分少了 9 比特,可以爆)
拿出当初祥哥的板子 https://blog.soreatu.com/posts/intended-solution-to-crypto-problems-in-nctf-2019/ ,爆一下 L 少的 9 比特就可以了
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283 |
from base64 import b64decodefrom itertools import productfrom DES import * # https://github.com/soreatu/Cryptography/blob/master/DES.pyfrom typing import Listguess_8bit = list(product(range(2), repeat=8))not_in_PC2 = [9,18,22,25,35,38,43,54]def re_PC2(sbkey): # 48-bit -> 56-bit res = [0]*56 for i in range(len(sbkey)): res[PC_2_table[i]-1] = sbkey[i] return res # okdef guess_CiDi10(sbkey, t): res = re_PC2(sbkey) for i in range(8): res[not_in_PC2[i]-1] = guess_8bit[t][i] return res # okdef guess_allsbkey(roundkey, r, t): sbkey = [[]]*16 sbkey[r] = roundkey CiDi = guess_CiDi10(roundkey, t) Ci, Di = CiDi[:28], CiDi[28:] for i in range(r+1,r+16): Ci, Di = LR(Ci, Di, i%16) sbkey[i%16] = PC_2(Ci+Di) if i%16 == 0: combined = Ci+Di return combined,sbkey # okdef long_des_enc(c, k): assert len(c) % 8 == 0 res = b'' for i in range(0,len(c),8): res += DES_enc(c[i:i+8], k) return resdef try_des(cipher, roundkey): for t in range(256): combined,allkey = guess_allsbkey(roundkey, 15, t) plain = long_des_enc(cipher, allkey[::-1]) if plain.startswith(b'Nep'): print(combined,plain) exit()def PC_2(key: List[int]) -> List[int]: return [key[x] for x in __pc2]__pc2 = [ 13, 16, 10, 23, 0, 4, 2, 27, 14, 5, 20, 9, 22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1, 40, 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47, 43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31]from Crypto.Util.number import *tt=[0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0]t = tt[:64]t = ''.join(str(i) for i in t)t= int(t,2)t = long_to_bytes(t)LL= [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]Rr= [0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0]from tqdm import *for i in range(2**9-1,2**7,-1): tmp = list(bin(i)[2:].rjust(9,'0')) L = LL + [ int(u) for u in tmp] R = Rr combined = L+R sub_key = PC_2(combined) try_des(t, sub_key)# [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0] b'NepCTF{N' |
得到第一组明文:NepCTF{N
,顺便输出了第一轮的轮密钥 combined
。
然后拿着第一组的明文 NepCTF{N
,做一个密钥拓展那边的 PC_1 置换
,再左右两边各自向左循环移位一下,再异或输出的第一轮的轮密钥 combined
,就可以解密第二组密文了,以此类推。
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849 |
from functools import reducefrom operator import addfrom Crypto.Util.number import *def attack(p,c): combined = [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0] p = bin(p)[2:].rjust(64,'0') p = [int(i) for i in p] # 对明文 PC_1 置换 P = PC_1(p) # 对明文左右两部分进行第一轮的循环移位 Ci, Di = LR(P[:28], P[28:], 0) P = Ci+Di CiDi = [] for i in range(56): CiDi.append(P[i]^combined[i]) # 生成所有轮密钥 Ci, Di = CiDi[:28], CiDi[28:] sbkey = [[]]*16 sbkey[0] = PC_2(Ci+Di) r = 1 for i in range(r,r+16): Ci, Di = LR(Ci, Di, i%16) sbkey[i%16] = PC_2(Ci+Di)# 解密密文 c = ''.join(str(i) for i in c) c= int(c,2) c = long_to_bytes(c) return long_des_enc(c, sbkey[::-1])tt=[0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0]flag = b"NepCTF{N"for i in range(2): t = tt[64*i+64:64*i+128] p = flag[i*8:i*8+8] flag += attack(bytes_to_long(p),t)print(flag)# b'NepCTF{Nep_d0wn_ddddd1s}' |
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可联系QQ 643713081,也可以邮件至 [email protected] - source:Van1sh的小屋
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论