BUUCTF 每日打卡 2021-5-12
引言
求 wp 的时候,大佬们告诉我 2021 红帽杯的 crypto 都是原题。。。 一共有三份 wp 其中两份的思路是一样(也就是原题的解答),另外一份是和我同级的 Pheonix dl 当时的解答(感谢 Pheonix dl!)花了一小时解出来的
[2021 红帽杯]primegame
原题加密代码如下: 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#!/usr/bin/env python3
from decimal import *
import math
import random
import struct
from flag import flag
primes = [2]
for i in range(3, 100):
f = True
for j in primes:
if i * i < j:
break
if i % j == 0:
f = False
break
if f:
primes.append(i)
# Random shuffle the primes
# Now you cannot know the order
seed = struct.unpack('<i', flag[5:9])[0]
random.seed(seed)
random.shuffle(primes)
# Use ln function
# Now you cannot know the key itself
getcontext().prec = 100
keys = []
for i in range(len(flag)):
keys.append(Decimal(primes[i]).ln())
# Sum values
# Now you cannot know the flag
sum_ = Decimal(0.0)
for i, c in enumerate(flag):
sum_ += c * Decimal(keys[i])
ct = math.floor(sum_ * 2 ** 256)
print(ct)
对比红帽杯的加密代码: 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#!/usr/bin/env python3
from decimal import *
import math
import random
import struct
from flag import flag
assert (len(flag) == 48)
msg1 = flag[:24]
msg2 = flag[24:]
primes = [2]
# 获取 [2,90] 的素数
for i in range(3, 90):
f = True
for j in primes:
if i * i < j: # i^2 >= j
break
if i % j == 0:
f = False
break
if f:
primes.append(i)
getcontext().prec = 100 # 保留 100 位小数
keys = []
for i in range(len(msg1)): # len(primes) = 24
keys.append(Decimal(primes[i]).ln())
sum_ = Decimal(0.0)
for i, c in enumerate(msg1):
sum_ += c * Decimal(keys[i])
ct = math.floor(sum_ * 2 ** 256)
print(ct)
# 597952043660446249020184773232983974017780255881942379044454676980646417087515453
sum_ = Decimal(0.0)
for i, c in enumerate(msg2):
sum_ += c * Decimal(keys[i])
ct = math.floor(sum_ * 2 ** 256)
print(ct)
# 425985475047781336789963300910446852783032712598571885345660550546372063410589918
首先,这可以转化成背包加密问题 背包加密是对密文的二进制字符串进行加密 至于为什么这道题是背包加密,就我个人的理解的话,大多数加密问题都可以转化成背包问题解决(例如 RSA 中的 CopperSmith 攻击法),给我灵感的是 2 ** 256
也就是 \(2^{2^{8}}\),联想到字符 8 位二进制(虽然没什么根据就是了) 最后也没有思路就是了。。。
wp 的解法也都是转化为对两段分别进行背包问题求解
Peonix dl 解法
dl 的手稿: 首先,将 flag 前24位转化为 24*8 位的二进制 因为原来的 keys 是直接对每位 flag 进行操作,而现在是要对每位二进制进行操作,所以要重新构造 keys 代码如下:
1 | from decimal import * |
需要注意的是这里的二进制字符串是倒序的,之后解密需要倒回来 如手稿中所写的,由于对 keys 向下取整了,所以答案会偏小,所以需要调整 c(据说调整花了大部分时间,这也是这个方案的一个缺陷) 然后接下来就是构造矩阵,用 LLL 算法解出最短向量得到结果了 这里注意最后结果是一个最后一位是 0,其他都是 -1 或 1 的向量,有把 -1 替换成 0 和 1 替换成 0 并且 -1 替换成 1 两种情况,需要分别尝试 怎么构造矩阵参照背包加密问题,至于 LLL 算法,曾经在研究 cryptohack 的时候 dl 尝试复现过,但是非常慢(当然我也试过,但是失败了),用 sagemath 封装好的速度会比较快 最后把结果倒序之后 long_to_bytes 就行了 结果为:
我个人认为难点在于构造 keys 以及想到去构造 keys ,还有就是存在需要调整 c 这个缺陷 至于其他部分,想到背包问题就是顺理成章的了
原题解法
在原题中,将 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
68import math
from decimal import *
import random
import struct
getcontext().prec = int(100)
primes = [2]
for i in range(3, 100):
f = True
for j in primes:
if i * i < j:
break
if i % j == 0:
f = False
break
if f:
primes.append(i)
keys = []
for i in range(len(primes)):
keys.append(Decimal(int(primes[i])).ln())
arr = []
for v in keys:
arr.append(int(v * int(16) ** int(64)))
ct = 737384863737803670841307970259513146291422299366557325168325233349136771464845311
#ct = 425985475047781336789963300910446852783032712598571885345660550546372063410589918
def encrypt(res):
h = Decimal(int(0))
for i in range(len(keys)):
h += res[i] * keys[i]
ct = int(h * int(16)**int(64))
return ct
def f(N):
ln = len(arr)
A = Matrix(ZZ, ln + 1, ln + 1)
for i in range(ln):
A[i, i] = 1
A[i, ln] = arr[i] // N
A[ln, i] = 64
A[ln, ln] = ct // N
res = A.LLL()
for i in range(ln + 1):
flag = True
for j in range(ln):
if -64 <= res[i][j] < 64:
continue
flag = False
break
if flag:
vec = [int(v + 64) for v in res[i][:-1]]
ret = encrypt(vec)
if ret == ct:
print(N, bytes(vec))
else:
print("NO", ret, bytes(vec))
for i in range(2, 10000):
print(i)
f(i)
结语
事实上,类似的问题也可以用类似的方法解,如原题 wp 中提到的 cryptohack 中的一题 太晚了明天再说 分析 wp 究竟能学到什么呢?只能说长点见识吧,我见的还是太少了 太弱小了,因为 我们没有力量~ 希望继续坚持