格基

线性无关

给定一组线性无关的向量,回到原点的唯一方法是沿着原向量移动。其他任何向量的组合都无法让你回到原点。

维数

记作 $||v||$ ,计算方式:

Gram Schmidt(两两垂直,线性无关)

求正交基的算法,sage可以直接调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
try:
from sage.all import vector, matrix
except ImportError:
print("SageMath is required")
exit()

V = [
vector([4, 1, 3, -1]),
vector([2, 1, -3, 4]),
vector([1, 0, -2, 7]),
vector([6, 2, 9, -5])
]

# SageMath 内置 Gram-Schmidt
U = matrix(V).gram_schmidt()[0]

for u in U:
print(u)

print(float(U[3][1]))

每一个向量大小唯一这个限制是 标准 ,即标准正交基.

RSA

费马分解

两个质数的差很小的时候可以用.

文件格式

1
2
3
4
5
6
7
8
9
10
公钥格式:
- PKCS#1: -----BEGIN RSA PUBLIC KEY-----
- X.509 SubjectPublicKeyInfo: -----BEGIN PUBLIC KEY-----

私钥格式:
- PKCS#1: -----BEGIN RSA PRIVATE KEY-----
- PKCS#8: -----BEGIN PRIVATE KEY-----

证书格式:
- X.509: -----BEGIN CERTIFICATE-----

pem

PKCS#1格式的RSA:

1
2
3
4
5
6
7
8
9
10
11
12
from Crypto.PublicKey import RSA

with open("privacy_enhanced_mail_1f696c053d76a78c2c531bb013a92d4a (1).pem", "r") as f:
key = RSA.importKey(f.read())

print(f"n = {key.n}")
print(f"e = {key.e}")
print(f"d = {key.d}")
print(f"p = {key.p}")
print(f"q = {key.q}")
print(f"u = {key.u}")
# u = p^{-1} mod q加速运算的

der

x509 RSA编码的der文件

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
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives import hashes

with open("a.der", "rb") as f:
cert = x509.load_der_x509_certificate(f.read(), default_backend())

print("Subject:", cert.subject.rfc4514_string())
print("Issuer:", cert.issuer.rfc4514_string())
print("Serial Number:", cert.serial_number)
print("Version:", cert.version.value)
print("Not Valid Before:", cert.not_valid_before_utc)
print("Not Valid After:", cert.not_valid_after_utc)
print("Signature Algorithm:", cert.signature_algorithm_oid._name)
print("Public Key Algorithm:", cert.public_key().__class__.__name__)

pub = cert.public_key()
nums = pub.public_numbers()
print("Modulus (decimal):", nums.n)
print("Exponent:", nums.e)

for ext in cert.extensions:
print("Extension:", ext.oid._name, "Critical:", ext.critical, "Value:", ext.value)

sig = cert.signature
print("Signature (hex):", sig.hex())
print("TBS Fingerprint (SHA256):", cert.tbs_certificate_bytes.hex())
print("Fingerprint (SHA256):", cert.fingerprint(hashes.SHA256()).hex())

ssh-rsa

pub文件,格式为OpenSSH.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import base64

s=open("bruce_rsa.pub").read().split()
x=base64.b64decode(s[1])
i=0
def get():
global i
l=int.from_bytes(x[i:i+4],"big");i+=4
v=x[i:i+l];i+=l
return v

t=get().decode()
e=get()
n=get()

print("type:",t)
print("e:",int.from_bytes(e,"big"))
print("n:",int.from_bytes(n,"big"))
print("comment:",s[2])
print("key bytes:",len(x))

WEB

JWT

JWT有对称加密和非对称加密两种形式,对称加密用相同的密钥,非对称加密用私钥加密公钥验证签名,所以可以用公钥去套对称加密的信息…

JSON序列化漏洞

典中典之序列化,典中典之注入…

1
2
3
4
5
6
7
8
9
10
body = '{' \
+ '"admin": "' + "False" \
+ '", "username": "' + str(username) \
+ '"}'
这个因为有字符串拼接所以有风险

encoded = jwt.encode({'username': username, 'admin': False}, PRIVATE_KEY, algorithm='RS256')
这个是没有风险的(被字典当成字段了)

payload= 'test", "admin": "True"'

AES

Oracle

oracle的意思是”神谕”,意思是只提供部分反馈的黑盒.
你可以向神祈祷获得hint,但是不能直接找到答案,这样…
所以大概意思是flag要一点一点poke出来.

CRIME(CTRIME)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from Crypto.Cipher import AES
from Crypto.Util import Counter
import os
import zlib

KEY = ?
FLAG = ?

@chal.route('/ctrime/encrypt/<plaintext>/')
def encrypt(plaintext):
plaintext = bytes.fromhex(plaintext)

iv = int.from_bytes(os.urandom(16), 'big')
cipher = AES.new(KEY, AES.MODE_CTR, counter=Counter.new(128, initial_value=iv))
encrypted = cipher.encrypt(zlib.compress(plaintext + FLAG.encode()))

return {"ciphertext": encrypted.hex()}

这个题会把你的输入和FLAG放一起压缩然后加密输出出来.
CRIME是侧信道攻击,把重要信息和hacker可以掌握的信息结合在一起压缩的攻击场景,通过枚举字符,去poke压缩后字符串的长度变化来检测字符有没有poke出来.
而CTR密钥流有一个很重要的性质就是输出的密文流长度随意,而且长度和明文保持一致.

Triple DES

  1. 弱密码攻击.
    选择弱密码会造成加密的加密变成解密.

  2. 奇偶校验位攻击
    鉴于某些python库在实现DES的时候并不会检查奇偶校验位,所以可以构造密码相同但是奇偶校验位不同的key,造成”key不一样但是密文一样”的攻击,比如
    01010101010101010000000000000000 .

元数据

ECB Oracle

为了防止线下断网比赛的时候不会用request,这个wp留个档.

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

import string
import time
import requests

BASE_URL = "https://aes.cryptohack.org/ecb_oracle/encrypt/{}/"
session = requests.Session()


def encrypt(plaintext_bytes: bytes) -> bytes:
"""带网络重试与错误检查的加密请求函数"""
hex_str = plaintext_bytes.hex()
url = BASE_URL.format(hex_str)

# 简单重试机制,防止网络抖动或临时 502/429
for attempt in range(5):
try:
res = session.get(url, timeout=10)
if res.status_code == 200:
data = res.json()
if "ciphertext" in data:
return bytes.fromhex(data["ciphertext"])
# 如果触发频控,稍作等待
time.sleep(0.5)
except Exception:
time.sleep(1)

raise RuntimeError(f"请求失败,URL: {url},响应内容: {res.text[:100]}")


def pwn():
flag = b""
print("[+] 开始爆破 Flag: ", end="", flush=True)

for i in range(64):
# 计算常规下的块索引与填充长度
rem = i % 16
pad_len = 15 - rem
block_idx = i // 16

# 【核心修复】:如果 pad_len 为 0,说明当前填充刚好为空
# 此时改用 16 字节填充,并将目标块索引后移一块 (block_idx + 1)
if pad_len == 0:
padding = b"A" * 16
target_block_idx = block_idx + 1
else:
padding = b"A" * pad_len
target_block_idx = block_idx

# 1. 获取目标密文块
target_ct = encrypt(padding)
target_block = target_ct[
target_block_idx * 16 : (target_block_idx + 1) * 16
]

found = False
candidate_chars = string.printable.encode()

# 2. 遍历候选字符
for char in candidate_chars:
test_payload = padding + flag + bytes([char])
test_ct = encrypt(test_payload)
test_block = test_ct[
target_block_idx * 16 : (target_block_idx + 1) * 16
]

# 3. 比对目标块
if test_block == target_block:
flag += bytes([char])
print(chr(char), end="", flush=True)
found = True
break

if not found:
print("\n[!] 未查找到匹配字符,爆破结束。")
break

if flag.endswith(b"}"):
break

print(f"\n\n[+] 成功获取 Flag: {flag.decode(errors='ignore')}")


if __name__ == "__main__":
pwn()

md5 collision

重点学习socket和json用法

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
import json
import socket

def interact_with_server():
# 服务器信息
host = "socket.cryptohack.org"
port = 13389

# 两个MD5碰撞的文档(具有相同MD5但内容不同)
collision1 = "17dbf4b2a89278122d443a98facc9718ae3882a6261bdbd43eb67a8e27c039165ae303c34c6146d8cfc3db54c995e85a107f819ae8b8edf797e5826f5167965de2d1b0c5efba670ae561559d23b15532c89819d614bf8021ddbbf4c454615d2e4f49deb629977e759770b1cf269d7f84f300acbf649523a15b906e84051690e9"
collision2 = "17dbf4b2a89278122d443a98facc9718ae388226261bdbd43eb67a8e27c039165ae303c34c6146d8cfc3db54c915e95a107f819ae8b8edf797e582ef5167965de2d1b0c5efba670ae561559d23b15532c898195614bf8021ddbbf4c454615d2e4f49deb629977e759770b1cf261d7f84f300acbf649523a15b906e04051690e9"

# 创建socket连接
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, port))

print("Connected to server")

# 读取初始提示
initial_response = sock.recv(4096).decode()
print(f"Initial response: {initial_response}")

# 发送第一个文档
message1 = json.dumps({"document": collision1})
sock.sendall(message1.encode())
response1 = sock.recv(4096).decode()
print(f"Response 1: {response1}")

# 发送第二个文档(MD5碰撞)
message2 = json.dumps({"document": collision2})
sock.sendall(message2.encode())
response2 = sock.recv(4096).decode()
print(f"Response 2: {response2}")

# 尝试解析响应
try:
# 可能有多个JSON对象
responses = response2.strip().split('\n')
for resp in responses:
if resp:
parsed = json.loads(resp)
if "error" in parsed:
print(f"Error message: {parsed['error']}")
if "flag" in parsed['error'].lower():
print(f"FLAG FOUND: {parsed['error']}")
elif "success" in parsed:
print(f"Success: {parsed['success']}")
except:
print(f"Raw response 2: {response2}")

sock.close()

if __name__ == "__main__":
interact_with_server()

signature

学学json的序列化

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
import json
import socket

def interact_with_server():
# 服务器信息
host = "socket.cryptohack.org"
port = 13374

# 创建socket连接
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, port))

print("Connected to server")

# 读取初始提示
initial_response = sock.recv(4096).decode()
print(f"Initial response: {initial_response}")

# 发送第一个文档
message1 = json.dumps({"option":"get_secret" })
sock.sendall(message1.encode())
response1 = json.loads(sock.recv(4096).decode())
print(f"Response 1: {response1}")
st=response1['secret']
# 发送第二个文档(MD5碰撞)
message2 = json.dumps({"option": "sign","msg":st})
sock.sendall(message2.encode())
response2 = sock.recv(4096).decode()
print(f"Response 2: {response2}")

sock.close()

if __name__ == "__main__":
interact_with_server()

gotta go fast

用pwn做交互

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
#!/usr/bin/env python3
import json
import socket
import time
import hashlib
from Crypto.Util.number import long_to_bytes
from pwn import remote

# 服务器信息
HOST = 'socket.cryptohack.org'
PORT = 13372

def generate_key(timestamp):
key = long_to_bytes(timestamp)
return hashlib.sha256(key).digest()

def decrypt(ciphertext_hex, timestamp):
key = generate_key(timestamp)
ciphertext = bytes.fromhex(ciphertext_hex)
plaintext = b''
for i in range(len(ciphertext)):
plaintext += bytes([ciphertext[i] ^ key[i]])
return plaintext

def solve():
# 连接到服务器
conn = remote(HOST, PORT)

# 记录连接时间
start_time = int(time.time())

# 等待提示信息
print(conn.recvline().decode().strip())

# 请求加密的flag
payload = json.dumps({"option": "get_flag"})
conn.sendline(payload.encode())

# 接收响应
response = conn.recvline().decode().strip()
print(f"Server response: {response}")

# 解析加密的flag
encrypted_flag = json.loads(response)["encrypted_flag"]

# 记录收到响应的时间
end_time = int(time.time())

# 枚举时间范围(前后各多几秒以防网络延迟)
time_range_start = start_time - 5
time_range_end = end_time + 5

print(f"Searching timestamps from {time_range_start} to {time_range_end}")

for timestamp in range(time_range_start, time_range_end + 1):
try:
decrypted = decrypt(encrypted_flag, timestamp)
# 检查是否解密成功(flag通常以crypto{开头)
if decrypted.startswith(b'crypto{'):
print(f"\nFound flag with timestamp {timestamp}!")
print(f"Flag: {decrypted.decode()}")
return decrypted.decode()
except:
continue

print("Flag not found in the time range. Trying wider range...")

# 如果上面的范围没找到,尝试更大的范围
for timestamp in range(time_range_start - 60, time_range_end + 60):
try:
decrypted = decrypt(encrypted_flag, timestamp)
if decrypted.startswith(b'crypto{'):
print(f"\nFound flag with timestamp {timestamp}!")
print(f"Flag: {decrypted.decode()}")
return decrypted.decode()
except:
continue

print("Flag not found. Please try again.")
conn.close()
return None

if __name__ == "__main__":
solve()

Stream of Consciousness

因为CTR状态被定死了,所以 密文=固定位置噪音^明文.
所以彼此抑或就能消掉加密的影响.
但是抵消掉之后变成了明文的彼此抑或,所以需要猜flag在哪个位置,然后用 crypto{ 填进去.
然后自己看词,慢慢猜…
由于此题过于恶心,放出来wp.

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
tar=[0x8116216d95ddda0dcc75a238ee,
0x800c7f72fcadde18cc7db86ab8aff0b096e1f457cb2ecc47e5e6c45072eb2737573093bb9fe8221e3c96c4ff99e7e81f97,
0x820c253799aac2068378b628bdb8efe4ada9d5418726da09a3fc801b68e13c7b1f3781e9dbe9330d2f8683fe99e7ee19cf2eac6746a40e26c61b5ea671796bfbb3ce15e29eb09cf55756b6a7054115c36271fe720fda528fdb9032101c,
0x860c2472c5f8dd01883ab624b5e1b8a589b1c918cf27920be8a8c21526f9233e19789eac9ffc33182edfceeecda9e81e862f,
0x87177331d4e49500cc78b26aa5aea2aad9aec54c8b62d712f0a8c90426ed2a35573a93e9d6fc38032f9ac7b9,
0x8743203ad4e6de548075a42ff1a4a6a18bb8c450ce2cd247e5e6c45068e13f7b103d82e9d7f23b4c3f9ec0fcc3,
0x8743203ad4e6de58cc53f026bde1bcab8aa4905dd127c71ef0e0c91e61ae223d573093e9dbf4331f33d8d7b78ea8ea0fc36ca56b5aaa,
0x87443e72c0e4da159c6aae66f188f0a09cb2d54ad127950ef0a480046eeb6b3d162d9abd98e876013491c6bbcda5f21ec347e36511f1083bca0242b6306c6ef9f49453a99eb795fd123be0aa0e5800c36e7ab4,
0x8a0c3f3eccaac51d8076f73eb9a8beafd9b5d859d362fc40e9a8cc1567f82235107897e9ccfe3503339b83ff98b4e50b8d6ae4695fe04627c31346ef646567e7b18654bedbe4bdb01a62b3b64b4705916673e3720ed71b8ad1c2231657b9526ea22b3060,
0x8a113621c6a7df158773b92df1a0bea0d98cd954cb2bdb02f6f1,
0x8c162772fcaac51d8076f739b9aea7e491a8dd16,
0x8f0d3772fcaac11c8d76bb6ab8a6beab8ba49051d36c,
0x8f10733bd3aafb54847bb36ab0afa9e48ea8c3508736da47e6ed801968ae3f33127884a0d8f3224d7db683f48ca9a01ec2,
0x990b2a72d1e59200847fae6ab6aef0ab97e1c059ce2cc10eeaef801168ea6b3902319aadd6f5314c3c93cfb799afe24a9767a96d0e,
0x990b322695eb9218836ef725b7e1a4ac90afd74b8736dd06f0a8d41863e06b28123d9bacdbbb22037d92c6b79ea8a707827cb26d5de80926d85253a1742d77fbb5944fadd7aa95f21b72ece203550686237dff3103df5ec3d68c241755f74c7aa426362076a6069935307006fa42a677dff38f3d43d5cdd4fa3e040b20f248b5003378676a6b929ade5b4f6957501b210aeaa8372e29d3deb339ca861e4669,
0x990b322695eb921a8d69a333f1b2bda195ad904ccf2bc647f4e9c91e72ae233a1376,
0x990c263ed1aafb54847ba12ff1a3b5a890a4c65dc362c10fe1e680046eef3f7b3e7895a6caf7324c2f9ac2f485e7f41f8066e46c54f4123bd8525da9306577f8bd8c52adcaad9bfe48,
0x9a0b2137d0aad01b9569f738a4afbead97a69c18d72ed41eede6c75067fa6b33182a85acccb7763f388ddaf897afe64b,
0x9a0b3621d0aada1b9e69b239fde1a4ac90b2905bc630c70ee5efc5502bae23340078bfe9d3f43718359a83fa94b4e206852ead6611f00e3ad85251ae627f6bf4b3851be19eb09cf50e30b2a74b551c8f2377f32157925996cbc21e5e41f14470a165392b74ef54d828313552e64fe36e97fb863b599baa,
0x9a0b3672c1efc0068578bb2ff1b5b8ad97a69051d462c10fe5fc80046eeb6b2b162b82e9dcfa384b29dfc1f2cdb3e8188d2eab7d45a4042a8b1b46bc307f6dfaa09315,
0x9e06213ad4fac154847ff722b0b2f0a990b2c35dc362c10fe1a8d40267e7257b163692e9d6e8760e3c9cc8b78fbea7048c79ea2866e508278b1f5dbd752d6ae0b98957a5dfb09dff1936,
0xad112a22c1e5c91fdf63e27da3f2e4a9a6b3834d9271ea56b1d7c64431ba2726,
]
from Crypto.Util.number import long_to_bytes


# pos:
# [第几个字符串, 已知字符]
# 例如:
# [0, b'c'] -> tar[0] 的第 0 个字符是 c
# [0, b'r'] -> tar[0] 的第 1 个字符是 r
# [0, b'y'] -> tar[0] 的第 2 个字符是 y
pos = [
[21, b'c'],
[21, b'r'],
[21, b'y'],
[21, b'p'],
[21, b't'],
[21, b'o'],
[21, b'{'],
[20, b' '],
[4, b' '],
[16, b'a'],
[16, b'v'],
[16, b'e'],
[2, b'l'],
[2, b'y'],
[11, b'n'],
[11, b'o'],
[11, b'r'],
[11, b'e'],
[11, b' '],
[12, b'h'],
[7, b'v'],
[7, b'e'],
[7, b' '],
[5, b' '],
[4, b't'],
[6, b'h'],
[6, b'i'],
[6, b'n'],
[6, b'g'],
[15, b' '],
[15, b'h'],
[15, b'a'],
[15, b'd'],
]


# 整数 -> bytes
tar = [long_to_bytes(x) for x in tar]

# 防止长度不足
max_len = max(len(x) for x in tar)

# 用 None 表示未知字符
ans = [
[None] * len(x)
for x in tar
]


# --------------------------------------------------
# 已知明文恢复
# --------------------------------------------------

for ps, ch in pos:
ch = ch[0] # b'c' -> 99

# 已知:
# tar[ps][i] = ch
#
# 如果使用 XOR:
# key = ciphertext ^ plaintext
#
# 则:
# plaintext = ciphertext ^ key
for i in range(len(tar[ps])):

# 如果当前位置本身就是已知字符
if i == len(ans[ps]):
continue

# pos 中的字符对应的位置
# 注意:原代码的设计是按照 pos 的顺序决定 i
i = pos.index([ps, bytes([ch])])

# 求出该位置的 XOR key
key = tar[ps][i] ^ ch

# 用这个 key 解所有字符串的同一位置
for j in range(len(tar)):
if i < len(tar[j]):
ans[j][i] = tar[j][i] ^ key


# --------------------------------------------------
# 输出
# --------------------------------------------------

for i, x in enumerate(ans):
result = ''.join(
chr(c) if c is not None and 32 <= c <= 126 else '?'
for c in x
)

print(f"{i}: {result}")

Saying Hello(openssl)

用下面的命令指定tls版本,以及输出握手内容

1
openssl s_client -connect tls1.cryptohack.org:443 -tls1_2 -cipher 'ALL:@SECLEVEL=0'