CTF_Notes
[TOC]
0x01 Not Wiener
题目是变体的Boneh-Durfee attack, 需要在理解攻击原理后做调整。
1 | from Crypto.Util.number import getPrime, bytes_to_long |
由于d = phi - D
(令D为540位的随机素数),比正常的多了个负号,故要做一些调整。
列出RSA等式:
两边同时mod e:
其中
考虑以下的二元多项式:
exp:
1 | from Crypto.Util.number import long_to_bytes |
1.2 Boneh-Durfee的碎碎念
从上面的题也可以看出,Boneh-Durfee的核心就是Multivariate
Coppersmith,也即多元Coppersmith。对于RSA公钥方程:
0x02 Not Wiener(Another)
1 | from Crypto.Util.number import * |
题目嵌套了标准Boneh-Durfee attack 和线性DSA攻击。
由cry()函数得之用的RSA加密的a, 同时d较小,用标准Boneh-Durfee attack就能求出a。
接下来是求线性关系的步骤。已知:
exp:
1 | from Crypto.Util.number import * |
0x03 Or1cle
目的是拿到xenny的签名。一同操作后发现hex报错能弹源码:
1 | 1. get_signature |
观察到r s没加非0校验,和CVE-2022-21449一样,非预期直接秒
payload:
1 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 |
做出这道真不是纯运气,要不是之前辛苦复现,不然这道题拿不下来。
0x04 Or2cle
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143from Crypto.Util.number import *
from hashlib import *
import os
from Crypto.Cipher import AES
from Crypto.Util import Counter
from random import *
import socket
import threading
import base64
FLAG = b'fake_flag'
class Proof():
def S(self,n):
factors = set()
while n % 2 == 0:
factors.add(2)
n //= 2
for i in range(3, int(n**0.5) + 1, 2):
while n % i == 0:
factors.add(i)
n //= i
if n > 2:
factors.add(n)
return factors
def Y(self,n):
if n == 1:
return 1
result = n
for p in self.S(n):
result *= (1 - 1 / p)
return int(result)
def proof(self,n):
res = 0
for m in range(1, n+1):
for l in range(1, m+1):
for k in range(1, l+1):
for j in range(1, k+1):
for i in range(1, j+1):
for h in range(1, i+1):
for g in range(1, h+1):
for f in range(1, g+1):
for e in range(1, f+1):
for d in range(1, e+1):
for c in range(1, d+1):
for b in range(1, c+1):
for a in range(1, b+1):
res += b//a*(self.Y(a)) # Euler function
return res
class Harukii_Oracle:
def __init__(self,key):
self.k = key
def pad(self, plaintext):
block_size = randint(1,len(plaintext)-1)
if len(plaintext) < block_size :
return plaintext
else:
padding_length = len(plaintext) // 16
padding_byets = bytes([padding_length])
plaintext = plaintext.replace(padding_byets,b"\x00")
p = plaintext[:16]
for i in range(1 , padding_length+1):
p += padding_byets + plaintext[16*i:16*(i+1)]
return p
def encrypt(self,plaintext):
aes = AES.new(self.k,mode=AES.MODE_CTR,counter=Counter.new(128))
chipertext = aes.encrypt(self.pad(plaintext))
return chipertext
def gift(self,ciphertext):
aes = AES.new(self.k,mode=AES.MODE_CTR,counter=Counter.new(128))
plaintext = aes.decrypt(ciphertext)
padding_length = len(plaintext) // 16
padding_bytes = bytes([padding_length])
return plaintext.count(padding_bytes) == padding_length
Menu = """
1. get flag
2. gift
3. exit
"""
def task(client_socket):
client_socket.settimeout(3600)
s = str((getPrime(128)))
client_socket.send(f"s : {s}\n".encode())
client_socket.send("Give me a hash: ".encode())
hash = client_socket.recv(1024)[:-1]
hash = int(hash,16)
'''if hash != int(sha256(str(Proof.proof(s)).encode()).hexdigest(),16):
client_socket.send(": C".encode())
exit(-1)'''
key = randbytes(16)
YSGS = Harukii_Oracle(key=key)
while True:
try:
client_socket.send(Menu.encode())
choice = client_socket.recv(1024).decode().strip()
if choice == '1' :
enc_flag = base64.b64encode(YSGS.encrypt(FLAG))
client_socket.send(f"This is Your flag {enc_flag}".encode())
elif choice == '2':
c = client_socket.recv(1024)[:-1]
if YSGS.gift(c):
client_socket.send("Dec successfully".encode())
else:
client_socket.send("Dec faild".encode())
else:
exit(-1)
except Exception as e:
print(e)
def main():
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("0.0.0.0", 9999))
server.listen(100)
try:
while True:
client_sock, address = server.accept()
print(f"Accepted connection from {address[0]}:{address[1]}")
client_handler = threading.Thread(target=task, args=(client_sock,))
client_handler.start()
finally:
server.close()
if __name__ == "__main__":
main()
0x04.1 简化proof函数
拿到前几个序列的数不知道咋搞,上谷歌搜,居然在杨辉三角里找到了对应的数,勾起极大的兴趣,于是
pip install pascal
跑测试。其结果也很简单,就是
binomial(s+13,14)
。
赛后知道可以用 https://oeis.org/ 查数列,厉害厉害。
0x04.2 CTR Oracle
CTR模式中,AES加密的是Counter,然后把加密得到的Counter与明文异或得到密文。因此,我们对输入1得到的flag密文,改变其中任意一个不是padding位置的字节,在AES解密后得到的明文中,就也只有这一个位置的字节被改变了。
而我们对这一个被改变的字节,遍历256种可能取值,当且仅当解密后的明文恰为padding字节的值时,靶机会返回给我们failed,因为这个时候padding字节多出来一个。我们将这时候的这个字节值记为t。
而padding字节的值是多少,这个我们是知道的,因为我们从输入1返回的密文中,可以知道flag的长度是42,也就自然知道了有几个分组。我们把它记为r。而CTR模式又有一个很重要的性质,也就是同一分组的明文异或等于密文异或,因此对于当前这个字节,其本身的加密值是enc的话,我们就可以由:
exp:
1 | from pwn import * |
0x05 babySPN
类DES加密
1 | import random |
特么的K已知啊,直接跑题目得flag。
exp:
1 | from hashlib import sha256 |
0x06 babySPN_revenge
中间相遇 yyds
0x06.1 准备工作
1 | import random |
刚看到题用的是差分,因为一般类DES的考法也就差分/线性,翻了几篇博客没懂就没往下想了,可能官方解法是差分吧。
逆s, p 盒
1 | Pbox=[1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15, 4, 8, 12, 16] |
逆加密算法
1 | def round_func(X,r,K): |
0x06.2 中间相遇攻击
中间相遇攻击,明文 ‘0’*16 的两轮加密结果和对应的密文
1 | [0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1] |
两轮解密的结果相同。把加密结果存在一个dic 里面,然后用相同的key对密文解密,在dic 里面查,如果查到的话可能就是,再用其他密文检验以下。
中间还遇到一个毛病:key只存了12 bit(因为只需要12bit ),4096个数据,如果range 设大的话就会覆盖 :( ,要再加一个循环。先构造一个key检验一下,有结果,于是开爆,花了近7h 成功出结果 :)
exp:
1 | import random |
0x07 guess
1 | from random import * |
随机数预测板子题
exp:
1 | from randcrack import RandCrack |
0x08 babyRSA
1 | ('n:', 22034616002810077367213986975491254660059405267832839238660142745262533722936869262128002848007071492830566792423948764669542596674862348845018069988568386929972799963860498358532060076292090442666332244803634081932410319039049654417535288106255607272156125389093011075687983996658797091362429917761882582099891126882036275226319812950951273212778669525797252443533260358966791414451881952511213526139935019304078758161325320517695299473291202659147434843041548537463174719777262120734530482940790956181957828062067836118689134391817949968101908769390005724479538030093708384442926717939114194865171002974201607355261L) |
数论题。
exp:
1 | n = 2203461600281007736721398697549125466005940526783283923866014274526253372293686926212800284800707149283566792423948764669542596674862348845018069988568386929972799963860498358532060076292090442666332244803634081932410319039049654417535288106255607272156125389093011075687983996658797091362429917761882582099891126882036275226319812950951273212778669525797252443533260358966791414451881952511213526139935019304078758161325320517695299473291202659147434843041548537463174719777262120734530482940790956181957828062067836118689134391817949968101908769390005724479538030093708384442926717939114194865171002974201607355261 |
0x09 LFSR
1 | from os import urandom |
解 模2 有限域内的线性矩阵方程。
exp:
1 | from Crypto.Util.number import * |
0x0a oppen pattern
1 | from Crypto.Util.number import long_to_bytes,bytes_to_long,getPrime |
由二项式定理可将加密式展开:
又由小费马定理:
根据条件
exp:
1 | import gmpy2 |
0x0b Dangerous RSA
1 | #n: 0x52d483c27cd806550fbe0e37a61af2e7cf5e0efb723dfc81174c918a27627779b21fa3c851e9e94188eaee3d5cd6f752406a43fbecb53e80836ff1e185d3ccd7782ea846c2e91a7b0808986666e0bdadbfb7bdd65670a589a4d2478e9adcafe97c6ee23614bcb2ecc23580f4d2e3cc1ecfec25c50da4bc754dde6c8bfd8d1fc16956c74d8e9196046a01dc9f3024e11461c294f29d7421140732fedacac97b8fe50999117d27943c953f18c4ff4f8c258d839764078d4b6ef6e8591e0ff5563b31a39e6374d0d41c8c46921c25e5904a817ef8e39e5c9b71225a83269693e0b7e3218fc5e5a1e8412ba16e588b3d6ac536dce39fcdfce81eec79979ea6872793L |
低指数攻击:在e较小,m^e<n时,可直接对c开方得到m
exp:
1 | from Crypto.Util.number import * |
0x0c ChildRSA
1 | from random import choice |
这道题有意思的地方在getPrime的方式:通过随机产生素数与之相乘,达到上限后+1得到素数。而sieve_base是个包含了前10000个素数的列表,所以设
费马小定理:
代入题目条件有
又因为
exp:
1 | from random import choice |
0x0d bbbbbbrsa
1 | from base64 import b64encode as b32encode |
由题:c给的base64是逆序打印的,所以要先逆回去,然后解密得到c。由于不知道e,要在给定的范围内爆破,爆破方法是求解出原文看有没有flag,ctf,{}之类的特定的字符串。
exp:
1 | import gmpy2 as gp |
0x0e babyrsa
1 | from Crypto.Util.number import * |
p和q隔得很近,可以通过爆破得到。
至于如何爆破的……
由公式
exp:
1 | from Crypto.Util.number import * |
0x0f babyrsa
1 | import sympy |
威尔逊定理求解。
求解方程
可以设
因为
1 | import gmpy2 as gp |