(一)makeshift
题目:
from secret import FLAG
flag = FLAG[::-1]
new_flag = ''
for i in range(0, len(flag), 3):
new_flag += flag[i+1]
new_flag += flag[i+2]
new_flag += flag[i]
print(new_flag)
思路:
属于很简单的流加密
首先,flag = FLAG[::-1],步长为-1,从后向前取值,对字符串进行了反转,效果如下:
然后,直接看作将flag三位为一组,进行同一个模式的乱序,m+下来,对应顺序为HTB,从len(c)-3开始,步长为-3遍历字符串,直到索引为-3,那么取出的c中THB索引为i、i+1、i+2,对应赋值即可。
脚本:
c = "!?}De!e3d_5n_nipaOw_3eTR3bt4{_THB"
m = ''
for i in range(len(c)-3, -3, -3):
m += c[i+1]
m += c[i]
m += c[i+2]
print(m)
(二)dynastic
题目:
from secret import FLAG
from random import randint
def to_identity_map(a):
return ord(a) - 0x41
def from_identity_map(a):
return chr(a % 26 + 0x41)
def encrypt(m):
c = ''
for i in range(len(m)):
ch = m[i]
if not ch.isalpha():
ech = ch
else:
chi = to_identity_map(ch)
ech = from_identity_map(chi + i)
c += ech
return c
with open('output.txt', 'w') as f:
f.write('Make sure you wrap the decrypted text with the HTB flag format :-]\n')
f.write(encrypt(FLAG))
ech = chr((ord(mch)-0x41 + i)%26 +0x41)
ord(ech) -0x41 = (ord(mch) - 0x41 + i)mod26
思路:
简单的流加密
对字符串中的字母进行了简单的移位,保留特殊字符,写个等式求逆
c = chr((ord(m) - 0x41 + i)%26 + 0x41)
ord(c) = (ord(m) - 0x41 + i)%26 + 0x41
ord(c) - 0x41 = (ord(m) - 0x41 + i)%26
ord(c) - 0x41 + k*26 = ord(m) - 0x41 +i
感觉这里不会溢出,所以不取模
ord(c) - 0x41 + k*26 - i = ord(m) - 0x41
感觉会溢出,所以取模
char((ord(c) - 0x41 -i)%26 + 0x41) = m
脚本:
def decrypt(c):
m = ''
for i in range(len(c)):
ch = c[i]
if not ch.isalpha():
mch = ch
else:
mch = chr((ord(ch)-0x41 - i) % 26 + 0x41)
m += mch
return m
(三)primary_knowledge
题目:
import math
from Crypto.Util.number import getPrime, bytes_to_long
from secret import FLAG
m = bytes_to_long(FLAG)
n = math.prod([getPrime(1024) for _ in range(2**0)])
e = 0x10001
c = pow(m, e, n)
with open('output.txt', 'w') as f:
f.write(f'{n = }\n')
f.write(f'{e = }\n')
f.write(f'{c = }\n')
思路:
搞了个骚操作来get n
n = math.prod([getPrime(1024) for _ in range(2**0)])
1. [getPrime(1024) for _ in range(2**0)] 是一个列表推导式,它会生成一个列表。range(2**0) 会生成一个包含 2**0(也就是1)个元素的序列,对于序列中的每个元素(在这里只有一个元素),都会调用 getPrime(1024)。getPrime(1024) 是一个函数调用,假设它会返回一个1024位的质数。所以这个列表推导式会生成一个包含1个1024位质数的列表。
2. math.prod() 是一个函数,它会计算一个序列中所有元素的乘积。在这里,它会计算上面生成的列表中所有元素(也就是那个1024位质数)的乘积。
所以,本质上这是一题n为单一素数的RSA,那么phi就直接等于 n-1了
脚本:
def decrypt(c,e,n):
phi = n - 1
d = inverse(e, phi)
m = pow(c, d, n)
return long_to_bytes(m)
print(decrypt(c,e,n))
(四)iced_tea
题目:
import os
from secret import FLAG
from Crypto.Util.Padding import pad
from Crypto.Util.number import bytes_to_long as b2l, long_to_bytes as l2b
from enum import Enum
class Mode(Enum):
ECB = 0x01
CBC = 0x02
class Cipher:
def __init__(self, key, iv=None):
self.BLOCK_SIZE = 64
self.KEY = [b2l(key[i:i+self.BLOCK_SIZE//16]) for i in range(0, len(key), self.BLOCK_SIZE//16)]
self.DELTA = 0x9e3779b9
self.IV = iv
if self.IV:
self.mode = Mode.CBC
else:
self.mode = Mode.ECB
def _xor(self, a, b):
return b''.join(bytes([_a ^ _b]) for _a, _b in zip(a, b))
def encrypt(self, msg):
msg = pad(msg, self.BLOCK_SIZE//8)
blocks = [msg[i:i+self.BLOCK_SIZE//8] for i in range(0, len(msg), self.BLOCK_SIZE//8)]
ct = b''
if self.mode == Mode.ECB:
for pt in blocks:
ct += self.encrypt_block(pt)
elif self.mode == Mode.CBC:
X = self.IV
for pt in blocks:
enc_block = self.encrypt_block(self._xor(X, pt))
ct += enc_block
X = enc_block
return ct
def encrypt_block(self, msg):
m0 = b2l(msg[:4])
m1 = b2l(msg[4:])
K = self.KEY
msk = (1 << (self.BLOCK_SIZE//2)) - 1
s = 0
for i in range(32):
s += self.DELTA
m0 += ((m1 << 4) + K[0]) ^ (m1 + s) ^ ((m1 >> 5) + K[1])
m0 &= msk
m1 += ((m0 << 4) + K[2]) ^ (m0 + s) ^ ((m0 >> 5) + K[3])
m1 &= msk
m = ((m0 << (self.BLOCK_SIZE//2)) + m1) & ((1 << self.BLOCK_SIZE) - 1) # m = m0 || m1
return l2b(m)
if __name__ == '__main__':
KEY = os.urandom(16)
cipher = Cipher(KEY)
ct = cipher.encrypt(FLAG)
with open('output.txt', 'w') as f:
f.write(f'Key : {KEY.hex()}\nCiphertext : {ct.hex()}')
思路:
是个带向量的,有点像AES的块加密,参照着encrypt和encrypt_block写逆。
主要矛盾在块的加密中。明文被分成等长的两部分进行交叉的运算,然后又合并。
(值得注意这步操作
# 创建一个掩码,长度为self.BLOCK_SIZE//2,用于限制m0和m1的长度 msk = (1 << (self.BLOCK_SIZE // 2)) - 1
)
m | m1 | m0 |
c | f(m0) | f(m1) |
因为在加密过程中,m0 的计算依赖于 m1,所以在解密过程中,我们需要先恢复 m1,然后再恢复 m0。
脚本:
def decrypt(self, ct):
blocks = [ct[i:i + self.BLOCK_SIZE // 8] for i in range(0, len(ct), self.BLOCK_SIZE // 8)]
pt = b''
if self.mode == Mode.ECB:
for c in blocks:
pt += self.decrypt_block(c)
elif self.mode == Mode.CBC:
X = self.IV
for c in blocks:
dec_block = self.decrypt_block(c)
pt += self._xor(X, dec_block)
X = c
print(pt)
print(len(pt))
return pt
def decrypt_block(self, ciphertext):
c = b2l(ciphertext)
msk = (1 << (self.BLOCK_SIZE // 2)) - 1
m1 = c & msk
m0 = (c >> (self.BLOCK_SIZE // 2)) & msk
K = self.KEY
s = self.DELTA * 32
for i in range(32):
m1 -= ((m0 << 4) + K[2]) ^ (m0 + s) ^ ((m0 >> 5) + K[3])
m1 &= msk
m0 -= ((m1 << 4) + K[0]) ^ (m1 + s) ^ ((m1 >> 5) + K[1])
m0 &= msk
s -= self.DELTA
original_msg = l2b((m0 << 32) + m1)
return original_msg
(五)blunt
题目:
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from Crypto.Util.number import getPrime, long_to_bytes
from hashlib import sha256
from secret import FLAG
import random
p = getPrime(32)
print(f'p = 0x{p:x}')
g = random.randint(1, p-1)
print(f'g = 0x{g:x}')
a = random.randint(1, p-1)
b = random.randint(1, p-1)
A, B = pow(g, a, p), pow(g, b, p)
print(f'A = 0x{A:x}')
print(f'B = 0x{B:x}')
C = pow(A, b, p)
assert C == pow(B, a, p)
# now use it as shared secret
hash = sha256()
hash.update(long_to_bytes(C))
key = hash.digest()[:16]
iv = b'\xc1V2\xe7\xed\xc7@8\xf9\\\xef\x80\xd7\x80L*'
cipher = AES.new(key, AES.MODE_CBC, iv)
encrypted = cipher.encrypt(pad(FLAG, 16))
print(f'ciphertext = {encrypted}')
思路:
AES终于来了,哦,原来是皮包AES。
今年Cyber Apocalypse特别爱出的,攻击共享密钥的题目。
这题是最基础的dlp,不确定的是有两个随机数(但是都不大)进行碰撞,我们照着脚本一起碰撞就好。
脚本:
p = 0xdd6cc28d
g = 0x83e21c05
A = 0xcfabb6dd
B = 0xc4a21ba9
iv = b'\xc1V2\xe7\xed\xc7@8\xf9\\\xef\x80\xd7\x80L*'
ciphertext = b'\x94\x99\x01\xd1\xad\x95\xe0\x13\xb3\xacZj{\x97|z\x1a(&\xe8\x01\xe4Y\x08\xc4\xbeN\xcd\xb2*\xe6{'
a = sympy.discrete_log(p, A, g)
b = sympy.discrete_log(p, B, g)
C = pow(A, b, p)
assert C == pow(B, a, p)
print(long_to_bytes(C))
hash =sha256()
hash.update(long_to_bytes(C))
key = hash.digest()[:16]
print(key)
print(hash.digest())
cipher = AES.new(key, AES.MODE_CBC, iv)
m = cipher.decrypt(ciphertext)
print(m)
(六)arranged
题目:
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from Crypto.Util.number import long_to_bytes
from hashlib import sha256
from secret import FLAG, p, b, priv_a, priv_b
F = GF(p)
E = EllipticCurve(F, [726, b])
G = E(926644437000604217447316655857202297402572559368538978912888106419470011487878351667380679323664062362524967242819810112524880301882054682462685841995367, 4856802955780604241403155772782614224057462426619061437325274365157616489963087648882578621484232159439344263863246191729458550632500259702851115715803253)
A = G * priv_a
B = G * priv_b
print(A)
print(B)
C = priv_a * B
assert C == priv_b * A
# now use it as shared secret
secret = C[0]
hash = sha256()
hash.update(long_to_bytes(secret))
key = hash.digest()[16:32]
iv = b'u\x8fo\x9aK\xc5\x17\xa7>[\x18\xa3\xc5\x11\x9en'
cipher = AES.new(key, AES.MODE_CBC, iv)
encrypted = cipher.encrypt(pad(FLAG, 16))
print(encrypted)
思路:
crypto到此已是本人极限。
已知椭圆曲线系数a和三点G(x1, y1), A(x2, y2), B(x3, y3)
对于G,A有
两边相减可得:
即
同理有
求最大公约数可得p,然后求b,得到椭圆曲线的完整系数
脚本:
待整理