2024熵密杯wp

渗透测试中心發表於2024-10-07

第一部分:初始謎題

這一部分算是開胃菜,形式也更像平時見到的CTF題目,三個題目都是python加密的,做出其中任意一個就可以進入第二部分,也就是一個更類似真實情境的大型密碼滲透系統。

但每個初始謎題都是有分數的,所以就算開了第二部分也當然要接著做。

每個題目也都有前三血的加成,一血5%,二血3%,三血1%,在最後排名的時候會先根據分數再根據解題時間,所以血量分其實很重要,但是手速實在不太夠

然後就是他每個初始謎題下發的附件不僅包含加密用的.py檔案,還有一個.exe檔案,開啟例項並輸入ip和埠,之後題目就會下發加密資料,與他進行正確互動後就能拿到flag了。

初始謎題一(300 pts)

題目:

from sympy import Mod, Integer
from sympy.core.numbers import mod_inverse

# 模數
N_HEX = "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123"
MODULUS = Integer(int(N_HEX, 16))
MSG_PREFIX = "CryptoCup message:"


# 加密函式
def encrypt_message(message, key):
# 新增字首
message_with_prefix = MSG_PREFIX + message
message_bytes = message_with_prefix.encode('utf-8')
message_len = len(message_bytes)
num_blocks = (message_len + 15) // 16
blocks = [message_bytes[i * 16:(i + 1) * 16] for i in range(num_blocks)]

# 進行0填充
blocks[-1] = blocks[-1].ljust(16, b'\x00')

encrypted_blocks = []

k = key

# 加密每個分組
for block in blocks:
block_int = int.from_bytes(block, byteorder='big')
encrypted_block_int = Mod(block_int * k, MODULUS)
encrypted_blocks.append(encrypted_block_int)
k += 1 # 金鑰自增1

# 將加密後的分組連線成最終的密文
encrypted_message = b''.join(
int(block_int).to_bytes(32, byteorder='big') for block_int in encrypted_blocks
)

return encrypted_message


# 解密函式
def decrypt_message(encrypted_message, key):
num_blocks = len(encrypted_message) // 32
blocks = [encrypted_message[i * 32:(i + 1) * 32] for i in range(num_blocks)]

decrypted_blocks = []

k = key

# 解密每個分組
for block in blocks:
block_int = int.from_bytes(block, byteorder='big')
key_inv = mod_inverse(k, MODULUS)
decrypted_block_int = Mod(block_int * key_inv, MODULUS)
decrypted_blocks.append(decrypted_block_int)
k += 1 # 金鑰自增1

# 將解密後的分組連線成最終的明文
decrypted_message = b''.join(
int(block_int).to_bytes(16, byteorder='big') for block_int in decrypted_blocks
)

# 去除字首
if decrypted_message.startswith(MSG_PREFIX.encode('utf-8')):
decrypted_message = decrypted_message[len(MSG_PREFIX):]

return decrypted_message.rstrip(b'\x00').decode('utf-8')


# 測試
initial_key = Integer(0x123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0)
message = "Hello, this is a test message."
print("Original Message:", message)

# 加密
encrypted_message = encrypt_message(message, initial_key)
print("Encrypted Message (hex):", encrypted_message.hex())

# 解密
decrypted_message = decrypt_message(encrypted_message, initial_key)
print("Decrypted Message:", decrypted_message)

題目加密流程大概如下:

  • 有一個未知的initial_key,與一個未知的message
  • 對於這個message,題目會在他前面填上一個固定的字首”CryptoCup message:”,並在最後補充上”\x00”使得整個訊息長為16的倍數
  • 將填充了前字尾的訊息按16位元組為一組分組
  • 從第一個分組開始,將該分組訊息轉化為整數,記為mi,並計算:
2024熵密杯wp

其中ki是key在對應分組的值(key每個分組之後會自增一)

  • 將所有ci轉成32位元組,並連線在一起得到密文

靶機只會傳送encrypted_message,要傳送給他message來拿到flag。這個可以說是相當輕鬆了,由於有一個已知的字首,並且他超過了16位元組,因此就有第一個分組對應的明文和密文,所以就可以直接求出key來。

exp:

from Crypto.Util.number import *

N_HEX = "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123"
MODULUS = int(N_HEX, 16)
MSG_PREFIX = b"CryptoCup message:"

c = bytes.fromhex("a7ea042608ffce5be79a19ee45533506819e85f8d9250fccef5a89731151fd7a76d83aa85c47ba1357a86d0e9763470fb608cd54d0927125f500353e156a01da759fa814e96fa41a888eea3a9cf9b062923ed70774add490c7ed7f83d6b47e711e7b3c8a960dcc2838e577459bb6f2769d0917e1fd57db0829633b77652c2180")
C = [c[32*i:32*i+32] for i in range(len(c)//32)]

msg = b""
key = bytes_to_long(C[0]) * inverse(bytes_to_long(MSG_PREFIX[:16]), MODULUS) % MODULUS
for i in range(len(C)):
msg += long_to_bytes(bytes_to_long(C[i]) * inverse(key,MODULUS) % MODULUS)
key += 1
print(msg)


#CryptoCup message:dHyNBCgxEq4prNBbxjDOiOgmvviuAgfx\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\

傳送message回去之後就會拿到flag,以及一個登入Gitea的帳號密碼:

驗證透過
flag{OYLXbASQsEc5SVkhBj7kTiSBc4AM5ZkR}
gitea賬號:giteauser2024
gitea口令:S(*HD^WY63y89TY71
提示:gitea賬號和口令用於登入第二環節的gitea伺服器,請注意儲存!

後面兩個初始謎題也都是給一個拿分的flag,以及一個賬號密碼作為開第二部分的鑰匙,所以後面兩個初始謎題就不寫這個了

初始謎題二(300 pts)

題目:

import binascii
from gmssl import sm3


# 讀取HMAC key檔案
def read_hmac_key(file_path):
with open(file_path, 'rb') as f:
hmac_key = f.read().strip()
return hmac_key


# 生成token
def generate_token(hmac_key, counter):
# 如果HMAC_KEY長度不足32位元組,則在末尾補0,超過64位元組則截斷
if len(hmac_key) < 32:
hmac_key = hmac_key.ljust(32, b'\x00')
elif len(hmac_key) > 32:
hmac_key = hmac_key[:32]

# 將計數器轉換為位元組表示
counter_bytes = counter.to_bytes((counter.bit_length() + 7) // 8, 'big')
# print("counter_bytes:", binascii.hexlify(counter_bytes))

tobe_hashed = bytearray(hmac_key + counter_bytes)

# print("tobe_hashed:", binascii.hexlify(tobe_hashed))

# 使用SM3演算法計算雜湊值
sm3_hash = sm3.sm3_hash(tobe_hashed)

# 將SM3的雜湊值轉換為十六進位制字串作為token
token = sm3_hash

return token


current_counter = 0


def verify_token(hmac_key, counter, token):
# 生成token
generated_token = generate_token(hmac_key, counter)
global current_counter
# 比較生成的token和輸入的token是否相同
if generated_token == token:
if counter & 0xFFFFFFFF > current_counter:
current_counter = counter & 0xFFFFFFFF
print("current_counter: ", hex(current_counter))
return "Success"
else:
return "Error: counter must be increasing"
else:
return "Error: token not match"


# 假設HMAC key檔案路徑
hmac_key_file = 'hmac_key.txt'
# 假設計數器值
counter = 0x12345678

# 讀取HMAC key
hmac_key = read_hmac_key(hmac_key_file)

# 生成token
token = generate_token(hmac_key, counter)
print("Generated token:", token)
print(verify_token(hmac_key, counter, token))

題目內容很簡單:

  • 讀取一個未知的hmac_key,並生成一個隨機的counter
  • 將hmac_key控制在32位元組(不足則填充”\x00”,超出則截斷)
  • 將hmac_key與counter拼接起來進行SM3雜湊

然後下發的資料有:

  • SM3得到的雜湊值
  • counter值

我們需要完成的事情是:

  • 找到一個新的counter,使得新counter的低32位比原來的counter大
  • 計算出hmac_key與新counter拼接後的SM3雜湊值
  • 傳送新counter和這個雜湊值就能拿到flag

看明白題意就會知道這是一個基於SM3的雜湊長度擴充套件攻擊,由於控制了hmac_key為32位元組,並且counter只有4位元組,而SM3的分組長度是64位元組,所以說我們拿到的雜湊值是隻有一個分組的。而按照SM3的填充規則,這個分組雜湊的完整分組其實是下面這部分內容的part1 + part2:(單引號代表位元組串,雙引號代表位元串)

#448 bits
part1 = 'hmac_key'(32 bytes) + 'counter'(4 bytes) + "1" + "00...0"

#64 bits
part2 = bin(8*(len(hmac_key + counter)))[2:].zfill(64)

這兩部分拼起來就得到了完整的第一個分組。

SM3的雜湊長度擴充套件攻擊基於其Merkle Damgard結構,我們可以用一個已知分組的雜湊值,去繼續迭代計算更長的含有該分組訊息的雜湊值,而不需要知道這個分組對應的明文是什麼。所以我們完全可以構造下面這樣的counter:

New_counter = 'counter'(4 bytes) + "1" + "00...0" + bin(8*(len(hmac_key + counter)))[2:].zfill(64) + '\xff\xff\xff\xff'

那麼hmac_key拼接上這個counter後,其用於SM3雜湊的訊息就會按64位元組分為兩組,而第一組是和靶機傳送的訊息完全一樣的,因此我們就可以利用雜湊長度擴充套件攻擊迭代計算整個訊息的雜湊值了,具體實現程式碼是賽前那天晚上在github上隨便找的:

KKrias/length-extension-attack-for-SM3 (github.com)

稍微對著題意改一改就好。

exp:

def zero_fill(a,n):
if len(a)<n:
a="0"*(n-len(a))+a
return a
def cycle_shift_left( B, n):
n=n%32
return ((B << n) ^ (B >> (32 - n)))%(2**32)

def T(j):
if j>=0 and j<=15:
return int("79cc4519",16)
elif j>=16 and j<=63:
return int("7a879d8a",16)

def FF(X,Y,Z,j):
if j>=0 and j<=15:
return X^Y^Z
elif j>=16 and j<=63:
return (X&Y)|(X&Z)|(Y&Z)
def GG(X,Y,Z,j):
if j >= 0 and j <= 15:
return X ^ Y ^ Z
elif j >= 16 and j <= 63:
return (X & Y) | (~X & Z)

def P0(x):
return x^(cycle_shift_left(x,9))^cycle_shift_left(x,17)
def P1(x):
return x^(cycle_shift_left(x,15))^cycle_shift_left(x,23)

def Message_extension(a): #a的數一定要滿足512bit,不夠要補零!! ,承接的是字串
W1 = [] # W0-15
W2=[] # W' 0-63
#print("a訊息擴充套件的a:",a)
for i in range(int(len(a) / 8)):
W1.append(int(a[8 * i:8 * i + 8],16))
#print("W1的前16個",a[8 * i:8 * i + 8])
for j in range(16,68):
temp=P1(W1[j-16] ^ W1[j-9] ^ cycle_shift_left(W1[j-3],15)) ^cycle_shift_left(W1[j-13],7)^W1[j-6]
#print("訊息擴充套件:",hex(temp))
W1.append(temp)

for j in range(0,64):
W2.append(W1[j]^W1[j+4])

W1.append(W2)
return W1

def CF(V,Bi): #V是字串
Bi=zero_fill(Bi,128)
W=[]
W=Message_extension(Bi) #訊息擴充套件完的訊息字
#print("W:",W)
A=int(V[0:8],16)
#print("A:", hex(A))
B = int(V[8:16], 16)
C = int(V[16:24], 16)
D = int(V[24:32], 16)
E = int(V[32:40], 16)
F = int(V[40:48], 16)
G = int(V[48:56], 16)
H = int(V[56:64], 16)
for j in range(0,64):
temp=(cycle_shift_left(A,12) + E +cycle_shift_left(T(j),j)) %(2**32)
SS1=cycle_shift_left(temp,7)
SS2=SS1 ^ cycle_shift_left(A,12)
TT1=(FF(A,B,C,j) +D +SS2 +W[-1][j] ) %(2**32)
TT2=(GG(E,F,G,j)+H+SS1+W[j])%(2**32)
D=C
C=cycle_shift_left(B,9)
B=A
A=TT1
H=G
G=cycle_shift_left(F,19)
F=E
E=P0(TT2)
#print("B:", hex(B))
t1=zero_fill(hex(A^int(V[0:8],16))[2:],8)
t2 = zero_fill(hex(B ^ int(V[8:16], 16))[2:], 8)
t3 = zero_fill(hex(C ^ int(V[16:24], 16))[2:], 8)
t4 = zero_fill(hex(D ^ int(V[24:32], 16))[2:], 8)
t5 = zero_fill(hex(E ^ int(V[32:40], 16))[2:], 8)
t6 = zero_fill(hex(F ^ int(V[40:48], 16))[2:], 8)
t7 = zero_fill(hex(G ^ int(V[48:56], 16))[2:], 8)
t8 = zero_fill(hex(H ^ int(V[56:64], 16))[2:], 8)
t=t1+t2+t3+t4+t5+t6+t7+t8
return t

def SM3(plaintext):
Vtemp=IV
a=(len(plaintext)*4+1 ) % 512
#print(a)
k=0
B=[]
if a<=448:
k=448-a
elif a>448:
k=512-a+448
#print(k)
m=plaintext+"8"+"0"*int((k+1)/4-1)+zero_fill(str(hex(len(plaintext)*4))[2:],16)
#print(m)
block_len=int((len(plaintext)*4 + k + 65) / 512)
#print(block_len)
for i in range(0,block_len):
B.append(m[128*i:128*i+128]) #分組
#print("B:",B)
for i in range(0,block_len):
Vtemp=CF(Vtemp,B[i])

return Vtemp

def SM3_len_ex_ak(num_block,IV,plaintext):
Vtemp=IV
a=(len(plaintext)*4+1 ) % 512
#print(a)
k=0
B=[]
if a<=448:
k=448-a
elif a>448:
k=512-a+448
#print(k)
m=plaintext+"8"+"0"*int((k+1)/4-1)+zero_fill(str(hex(len(plaintext)*4+num_block*512))[2:],16)
#print(m)
block_len=int((len(plaintext)*4 + k + 65) / 512)
#print(block_len)
for i in range(0,block_len):
B.append(m[128*i:128*i+128]) #分組
#print("B:",B)
for i in range(0,block_len):
Vtemp=CF(Vtemp,B[i])

return Vtemp

IV="7380166f4914b2b9172442d7da8a0600a96f30bc163138aae38dee4db0fb0e4e"


#############################################################################
IV2="c2427b818b1fb3b9e72e0ec8c60d101a17865842506e6b0052278a0c156d9e7a"
num_block=1
counter = "51f18456"
New_Counter = hex(int((bin(int(counter,16))[2:].zfill(32) + "1") + "0"*(448 - 32*8 - 1 - 4*8) + bin(36*8)[2:].zfill(64) , 2))[2:] + "ffffffff"

print(New_Counter)
print(SM3_len_ex_ak(1,IV2,"FFFFFFFF"))

#flag{3WhlSlIw4tSOhbY52j6CMrUCAYSLfrS9}


初始謎題三(300 pts)

題目:

import sympy as sp
import random

# 設定引數
n = 16 # 向量長度
q = 251 # 模數

# 生成隨機噪聲向量e
e = sp.Matrix(sp.randMatrix(n, 1, min=0, max=1)) # 噪聲向量

# 生成隨機n維私鑰向量s和n*n矩陣A
s = sp.Matrix(sp.randMatrix(n, 1, min=0, max=q - 1)) # 私鑰向量
Temp = sp.Matrix(sp.randMatrix(n, n, min=0, max=q - 1)) # 中間變數矩陣Temp
A = Temp.inv_mod(q) # 計算矩陣Temp在模 q 下的逆矩陣作為A

# 計算n維公鑰向量b
b = (A * s + e) % q # 公鑰向量b = A * s + e


# 加密函式
def encrypt(message, A, b):
m_bin = bin(message)[2:].zfill(n) # 將訊息轉換為16位元的二進位制字串
m = sp.Matrix([int(bit) for bit in m_bin]) # 轉換為SymPy矩陣
x = sp.Matrix(sp.randMatrix(n, n, min=0, max=q // (n * 4))) # 隨機產生一個n*n的矩陣x
e1 = sp.Matrix(sp.randMatrix(n, 1, min=0, max=1)) # 隨機產生一個n維噪聲向量e
c1 = (x * A) % q # 密文部分c1 = x * A
c2 = (x * b + e1 + m * (q // 2)) % q # 密文部分c2 = x * b + e1 + m * q/2
return c1, c2


# 解密函式
def decrypt(c1, c2, s):
m_dec = (c2 - c1 * s) % q
m_rec = m_dec.applyfunc(lambda x: round(2 * x / q) % 2) # 還原訊息
m_bin = ''.join([str(bit) for bit in m_rec]) # 將SymPy矩陣轉換為二進位制字串
m_rec_int = int(m_bin, 2) # 將二進位制字串轉換為整數
return m_rec_int


# 測試加解密
message = random.randint(0, 2 ** n - 1) # 要加密的訊息,隨機生成一個16位元整數
c1, c2 = encrypt(message, A, b) # 加密

print("原始訊息: ", message)
print("公鑰A=sp.", A)
print("公鑰b=sp.", b)
print("密文c1=sp.", c1)
print("密文c2=sp.", c2)

decrypted_message = decrypt(c1, c2, s)
print("解密後的訊息: ", decrypted_message) # 輸出解密

題目名字叫lwe,具體來說給了一些如下資料:

  • 隨機生成16維的01向量e
  • 隨機生成16維的向量s以及16x16的可逆矩陣A,並計算:
    b=As+e
  • 將m轉化為位元串,並進一步變為長度為16的01向量(也就是說m本身也只有2位元組)
2024熵密杯wp

  • 給出A、b、c1、c2,要求還原message併傳送給他

雖然說題目叫lwe,似乎也可以透過lwe的方法求出s來,但是很顯眼的一點是維數僅僅為16,實在太小了,只需要瓊劇2^16其中就一定有正確的e、e1了。

然而再仔細看發現有更離譜的一點,既然A、c1都給好了並且A可逆,那麼x直接求就好了,然後就可以輕鬆得到:

2024熵密杯wp


而由於e1也是01向量,他對向量t的大小影響可以忽略不計,所以t中大於等於q/2的位置就是m中為1的位置,否則就是0。

exp:

A = Matrix(ZZ,[[139, 63, 18, 202, 166, 185, 85, 108, 58, 90, 211, 248, 240, 44, 137, 39], [5, 230, 89, 226, 139, 24, 233, 20, 12, 108, 127, 11, 52, 64, 188, 156], [80, 61, 105, 3, 165, 96, 154, 40, 62, 103, 157, 75, 190, 101, 31, 239], [193, 100, 124, 216, 248, 95, 241, 196, 67, 192, 217, 114, 171, 248, 219, 169], [116, 71, 221, 105, 167, 153, 22, 124, 178, 45, 7, 183, 125, 8, 127, 123], [182, 162, 164, 184, 27, 148, 206, 73, 217, 86, 187, 137, 82, 150, 99, 65], [106, 60, 153, 91, 213, 41, 188, 92, 121, 246, 164, 223, 199, 85, 161, 25], [93, 97, 145, 31, 48, 36, 7, 110, 56, 47, 108, 79, 233, 186, 93, 181], [195, 98, 47, 147, 49, 40, 158, 89, 218, 8, 23, 118, 170, 19, 50, 17], [127, 95, 37, 48, 230, 244, 130, 37, 75, 125, 103, 154, 148, 218, 227, 178], [162, 235, 129, 44, 204, 228, 221, 130, 239, 36, 57, 38, 41, 74, 61, 155], [246, 11, 11, 97, 218, 57, 209, 72, 229, 27, 250, 73, 19, 64, 25, 62], [60, 162, 1, 110, 191, 130, 120, 227, 214, 98, 165, 245, 28, 55, 94, 190], [129, 212, 185, 156, 119, 239, 83, 221, 4, 174, 65, 218, 32, 211, 213, 223], [80, 218, 135, 245, 238, 127, 55, 68, 113, 145, 110, 59, 50, 177, 159, 146], [68, 239, 36, 166, 206, 23, 59, 126, 67, 152, 99, 189, 133, 113, 243, 198]])
b = Matrix(ZZ,[[88], [74], [219], [244], [81], [109], [81], [216], [125], [218], [170], [56], [152], [229], [204], [45]])
c1 = Matrix(ZZ,[[173, 2, 67, 11, 40, 80, 187, 38, 16, 226, 243, 79, 117, 127, 100, 113], [208, 231, 211, 196, 2, 146, 35, 2, 221, 119, 12, 25, 208, 152, 83, 201], [154, 43, 180, 76, 235, 5, 179, 196, 206, 171, 98, 145, 92, 144, 247, 98], [121, 145, 123, 232, 87, 78, 181, 145, 79, 166, 112, 169, 208, 102, 201, 63], [204, 141, 165, 225, 213, 137, 40, 43, 229, 151, 72, 237, 58, 15, 2, 31], [35, 114, 241, 31, 122, 123, 164, 231, 197, 89, 41, 236, 128, 22, 152, 82], [141, 133, 235, 79, 43, 120, 209, 231, 58, 85, 3, 44, 73, 245, 227, 62], [28, 158, 71, 41, 152, 32, 91, 200, 163, 46, 19, 121, 23, 209, 25, 55], [156, 17, 218, 146, 231, 242, 91, 76, 217, 57, 100, 212, 243, 87, 62, 159], [100, 111, 107, 62, 106, 72, 51, 79, 223, 93, 86, 145, 192, 21, 218, 243], [196, 250, 248, 166, 155, 39, 7, 93, 103, 54, 168, 188, 190, 104, 183, 64], [16, 131, 148, 193, 19, 149, 179, 212, 109, 170, 201, 168, 165, 167, 68, 25], [30, 222, 171, 32, 141, 105, 232, 104, 198, 53, 50, 157, 206, 165, 200, 42], [90, 149, 148, 112, 142, 228, 231, 119, 235, 248, 233, 9, 242, 102, 241, 93], [150, 32, 78, 183, 68, 249, 80, 165, 95, 229, 211, 0, 75, 14, 172, 139], [175, 69, 15, 100, 113, 63, 123, 71, 24, 250, 135, 232, 53, 32, 81, 117]])
c2 = Matrix(ZZ,[[18], [67], [187], [237], [99], [127], [128], [23], [83], [66], [64], [69], [7], [214], [43], [156]])

p = 251
A = Matrix(Zmod(p), A)
c1 = Matrix(Zmod(p), c1)
b = vector(b.T)
c2 = vector(c2.T)
x = c1*A^(-1)
t = c2 - x*b
m = ""
for i in t:
if(i >= p // 2):
m += "1"
else:
m += "0"
print(hex(int(m,2)))


#21c4


第二部分:大型密碼系統

這一部分共有4個題目和一個最終挑戰,題目之間是有順序關係的,也就是要先做出某些題目,才能得到後續題目的附件、資料、登入密碼之類的相關資訊,具體來說這次挑戰的先後順序是:

  • flag1和flag3可以同時挑戰
  • 做出flag1可以開啟flag2
  • 做出flag3可以開啟flag4
  • 全部完成後可以開啟最終挑戰


flag1(600 pts)

題目:

passwordEncryptorV2.c:

#include <stdio.h>
#include <string.h>
#include <openssl/sha.h>

#define ROUND 16

//S-Box 16x16
int sBox[16] =
{
2, 10, 4, 12,
1, 3, 9, 14,
7, 11, 8, 6,
5, 0, 15, 13
};


// 將十六進位制字串轉換為 unsigned char 陣列
void hex_to_bytes(const char* hex_str, unsigned char* bytes, size_t bytes_len) {
size_t hex_len = strlen(hex_str);
if (hex_len % 2 != 0 || hex_len / 2 > bytes_len) {
fprintf(stderr, "Invalid hex string length.\n");
return;
}

for (size_t i = 0; i < hex_len / 2; i++) {
sscanf(hex_str + 2 * i, "%2hhx", &bytes[i]);
}
}


// 派生輪金鑰
void derive_round_key(unsigned int key, unsigned char *round_key, int length) {

unsigned int tmp = key;
for(int i = 0; i < length / 16; i++)
{
memcpy(round_key + i * 16, &tmp, 4); tmp++;
memcpy(round_key + i * 16 + 4, &tmp, 4); tmp++;
memcpy(round_key + i * 16 + 8, &tmp, 4); tmp++;
memcpy(round_key + i * 16 + 12, &tmp, 4); tmp++;
}
}


// 位元逆序
void reverseBits(unsigned char* state) {
unsigned char temp[16];
for (int i = 0; i < 16; i++) {
unsigned char byte = 0;
for (int j = 0; j < 8; j++) {
byte |= ((state[i] >> j) & 1) << (7 - j);
}
temp[15 - i] = byte;
}
for (int i = 0; i < 16; i++) {
state[i] = temp[i];
}
}


void sBoxTransform(unsigned char* state) {
for (int i = 0; i < 16; i++) {
int lo = sBox[state[i] & 0xF];
int hi = sBox[state[i] >> 4];
state[i] = (hi << 4) | lo;
}
}


void leftShiftBytes(unsigned char* state) {
unsigned char temp[16];
for (int i = 0; i < 16; i += 4) {
temp[i + 0] = state[i + 2] >> 5 | (state[i + 1] << 3);
temp[i + 1] = state[i + 3] >> 5 | (state[i + 2] << 3);
temp[i + 2] = state[i + 0] >> 5 | (state[i + 3] << 3);
temp[i + 3] = state[i + 1] >> 5 | (state[i + 0] << 3);
}
for (int i = 0; i < 16; i++)
{
state[i] = temp[i];
}
}


// 輪金鑰加
void addRoundKey(unsigned char* state, unsigned char* roundKey, unsigned int round) {
for (int i = 0; i < 16; i++) {
for (int j = 0; j < 8; j++) {
state[i] ^= ((roundKey[i + round * 16] >> j) & 1) << j;
}
}
}

// 加密函式
void encrypt(unsigned char* password, unsigned int key, unsigned char* ciphertext) {
unsigned char roundKeys[16 * ROUND] = {}; //

// 生成輪金鑰
derive_round_key(key, roundKeys, 16 * ROUND);

// 初始狀態為16位元組的口令
unsigned char state[16]; // 初始狀態為16位元組的密碼
memcpy(state, password, 16); // 初始狀態為密碼的初始值

// 迭代加密過程
for (int round = 0; round < ROUND; round++)
{
reverseBits(state);
sBoxTransform(state);
leftShiftBytes(state);
addRoundKey(state, roundKeys, round);
}

memcpy(ciphertext, state, 16);
}

void main() {
unsigned char password[] = "pwd:xxxxxxxxxxxx"; // 口令明文固定以pwd:開頭,16位元組的口令
unsigned int key = 0xF0FFFFFF; // 4位元組的金鑰
unsigned char ciphertext[16]; // 16位元組的狀態

printf("Password: \n");
printf("%s\n", password);

encrypt(password, key, ciphertext);

// 輸出加密後的結果
printf("Encrypted password:\n");
for (int i = 0; i < 16; i++) {
printf("%02X", ciphertext[i]);
}
printf("\n");
}


題目基於一個對稱加密,給出了其具體實現步驟。連線靶機之後會給出密文,要求求出password,來解壓帶密碼的協同簽名原始碼檔案壓縮包,壓縮包內含有本題的flag值以及flag2的原始碼。

可以看出在有key的情況下,解密就是把整個加密過程逆一下,這一部分交給學長很快就寫好了。

然而學長發現對於靶機給出的密文,用題目給定的0xF0FFFFFF當作key是解不出他要求的”pwd:”開頭的password的,所以我猜測這個key只是個示例,實際上要用這個已知的開頭來爆破4位元組的key。4位元組對於c來說似乎也不算很大,因此簡單修改下解密部分就開爆了。但是,實際效果並不是很理想,如果要爆破完所有解空間的話,差不多需要2^16秒,這對於僅僅6h的比賽來說太長了,所以要考慮一些最佳化。而比起仔細檢視程式碼來說,最簡單的最佳化當然是直接用多程序來做。

可是我只用過python的多程序,並且考慮到python本身的速度,為了用個多程序把整個求解程式碼轉成python實在是不太划算。可是比賽不出網,要查詢資料不僅需要申請,時間也只限10min,還會對整個隊伍的成績產生影響,更不划算。所以想來想去也只能三個人都多開點視窗,然後從不同的位置開爆。

也算是一種多程序了。

然而這樣做有意想不到的效果——我讓學弟倒著爆破的那個視窗過了一段時間真的跑出了結果,這個題也就順利解掉了。

實際上最後一輪提示中有提到,因為某些原因,key首位元組一定是F,所以倒著爆才更加快;此外還有一些其他地方可以減少耗時。

這裡就不仔細研究產生這些最佳化的原因了,多程序肯定是最有力的XD,做出來就行。

exp:(header.h就是題目加密原始碼裡的函式)

#include "header.h"

void print(unsigned char* m) {
for (int i = 0; i < 16; i++) {
printf("%02X", m[i]);
}
printf("\n");
}

int sBox_inv[16] =
{
13, 4, 0, 5, 2, 12, 11, 8, 10, 6, 1, 9, 3, 15, 7, 14
};

void rightShiftBytes(unsigned char* state) {
unsigned char temp[16];
for (int i = 0; i < 16; i += 4) {
temp[i + 0] = state[i + 2] << 5 | (state[i + 3] >> 3);
temp[i + 1] = state[i + 3] << 5 | (state[i + 0] >> 3);
temp[i + 2] = state[i + 0] << 5 | (state[i + 1] >> 3);
temp[i + 3] = state[i + 1] << 5 | (state[i + 2] >> 3);
}
for (int i = 0; i < 16; i++) {
state[i] = temp[i];
}
}

void decrypt(unsigned char* password, unsigned int key, unsigned char* ciphertext) {
unsigned char roundKeys[16 * ROUND] = {};
derive_round_key(key, roundKeys, 16 * ROUND);
unsigned char state[16];
memcpy(state, ciphertext, 16);
for (int round = ROUND - 1; round >= 0; round--) {
addRoundKey(state, roundKeys, round);
rightShiftBytes(state);
sBoxTransform(state, sBox_inv);
reverseBits(state);
}
memcpy(password, state, 16);
}

int main() {
// cipher = "B17164A27E035012107D6F7B0454D51D"
// cipher = "99F2980AAB4BE8640D8F322147CBA409"

unsigned char password[] = "pwd:xxxxxxxxxxxx"; // 口令明文固定以pwd:開頭,16位元組的口令
unsigned char ciphertext[16]; // 16位元組的狀態
hex_to_bytes("99F2980AAB4BE8640D8F322147CBA409", ciphertext, 16);



for (unsigned int key = 0; key < 0xFFFFFFFF; key++) {
if ((key & 0xFFFF) == 0) printf("%d\n", key);
decrypt(password, key, ciphertext);
if (password[0] == 112 && password[1] == 119 && password[2] == 100 && password[3] == 58) {
print(password);
}
}

return 0;
}


flag2(900 pts)

題目:

co-signing_client.js:

const form = ref({
password: "",
msgdigest: "",
})

const k1: any = ref("");

const submit = () => {
isform.value.validate((valid: boolean) => {
if (valid) {

loading.value = true;
let smPassword = ref("");
smPassword.value = sm3(form.value.password);
// 客戶端透過使用者口令、訊息摘要和使用者私鑰d1,計算客戶端協同簽名值 p1x, p1y, q1x, q1y, r1, s1
var { str_e, str_p1x, str_p1y, str_q1x, str_q1y, str_r1, str_s1, errMessage } = clientSign1(smPassword.value, form.value.msgdigest);
if (errMessage) {
ElMessage.error(errMessage)
loading.value = false;
return
}
let data = {
q1x: str_q1x,
q1y: str_q1y,
e: str_e,
r1: str_r1,
s1: str_s1,
p1x: str_p1x,
p1y: str_p1y
}
// 客戶端將 e, p1x, p1y, q1x, q1y, r1, s1傳送給服務端
// 服務端用服務端私鑰d2計算服務端協同簽名值 s2, s3, r 傳送給客戶端
sign_param_send(data).then((res: any) => {
// 客戶端透過s2, s3, r,計算協同簽名值 s
let str_s: any = clientSign2(smPassword.value, res.s2, res.s3, res.r);
if (str_s.errMessage) {
ElMessage.error(errMessage)
loading.value = false;
return
}
ElMessage.success("協同簽名成功");
signature_send({ client_sign: str_s }).then((res: any) => {
qmz.value = str_s;
loading.value = false;
}).then((err: any) => {
loading.value = false;
})
}).catch((err: any) => {
loading.value = false;
})
}
})
}
const clientSign1: any = (str_d1: any, str_e: any) => {
let d1 = new BN(str_d1, 16);
// console.log("e",str_e)

let e = new BN(str_e, 16);
// console.log("e",e)
const sm2: any = new elliptic.curve.short({
p: 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF',
a: 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC',
b: '28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93',
n: 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123',
g: [
'32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7',
'BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0'
]
} as any);

let n = new BN(sm2.n.toString(16), 16);
let G = sm2.g;

// generate random k1
const randomBytes = cryptoRandomStringAsync({ length: 64 });
k1.value = new BN(randomBytes as any, 16);
while(k1.value.mod(n).isZero()){
const randomBytes = cryptoRandomStringAsync({ length: 64 });
k1.value = new BN(randomBytes as any, 16);
}
k1.value = k1.value.mod(n);

// d1 = d1 mod n
d1 = d1.mod(n);
if (d1.isZero()) {
let errMessage = "d1=0,簽名失敗"
return { errMessage }
}

//P1 = ((d1)^(-1)) * G
let tmp1 = d1.invm(n);
let P1 = G.mul(tmp1);

//Q1 = k1*G = (x, y)
let Q1 = G.mul(k1.value);
let x = new BN(Q1.getX().toString(16), 16);

//r1 = x mod n
let r1 = x.mod(n);
if (r1.isZero()) {
let errMessage = "r1=0,簽名失敗"
return { errMessage }
}

//s1 = k1^(-1) * (e + d1^(-1) * r1) mod n
tmp1 = d1.invm(n);
let tmp2 = tmp1.mul(r1).mod(n);
let tmp3 = tmp2.add(e).mod(n);
tmp1 = k1.value.invm(n);
let s1 = tmp1.mul(tmp3).mod(n);
if (s1.isZero()) {
let errMessage = "s1=0,簽名失敗"
return { errMessage }
}

str_e = e.toString(16);
// console.log("str_e",str_e)
let str_p1x = P1.getX().toString(16);
let str_p1y = P1.getY().toString(16);
let str_q1x = Q1.getX().toString(16);
let str_q1y = Q1.getY().toString(16);
let str_r1 = r1.toString(16);
let str_s1 = s1.toString(16);
return { str_e, str_p1x, str_p1y, str_q1x, str_q1y, str_r1, str_s1 }
}
const clientSign2 = (str_d1: any, str_s2: any, str_s3: any, str_r: any) => {
const sm2 = new elliptic.curve.short({
p: 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF',
a: 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC',
b: '28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93',
n: 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123',
g: [
'32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7',
'BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0'
]
} as any);

let d1 = new BN(str_d1, 16);
let n = new BN(sm2.n.toString(16), 16);
let s2 = new BN(str_s2, 16);
let s3 = new BN(str_s3, 16);
let r = new BN(str_r, 16);
//s = d1*k1*s2 + d1*s3 -r mod n
let tmp1 = d1.mul(k1.value).mod(n);
let tmp2 = tmp1.mul(s2).mod(n);
let tmp3 = d1.mul(s3).mod(n);
tmp1 = tmp2.add(tmp3).mod(n);
let s = tmp1.sub(r).mod(n);
if (s.isZero()) {
let errMessage = "s=0,簽名失敗"
return { errMessage }
}
if (s.add(r).mod(n).isZero()) {
let errMessage = "s=n-r,簽名失敗"
return { errMessage }
}
let str_s = s.toString(16);
if (str_s[0] == '-') {
s = s.add(n).mod(n);
str_s = s.toString(16);
}
return str_s;
}

co-signing_client.c:

#include <stdio.h>
#include <stdlib.h>
#include <openssl/ec.h>
#include <openssl/rand.h>

#define SM2LEN 32

int error() {
printf("Error.\n");
return 0;
}

int error_partial_verify() {
printf("Error partial verify.\n");
return 0;
}

void print_flag2(const BIGNUM *d2) {
char *hex_str = BN_bn2hex(d2);
for (int i = 0; hex_str[i] != '\0'; i++) {
if (hex_str[i] >= 'A' && hex_str[i] <= 'F') {
hex_str[i] += 32;
}
}
printf("flag2{%s}\n", hex_str);
}

typedef struct {
char s2[SM2LEN * 2 + 1];
char s3[SM2LEN * 2 + 1];
char r[SM2LEN * 2 + 1];
int success;
} Result;

// 協同簽名服務端簽名演算法
Result server(char* str_e,char* str_p1x,char* str_p1y,char* str_q1x,char* str_q1y,char* str_r1,char* str_s1){
Result res = {"", "", "", 0};

int rv = 1;
BIGNUM *e,*a,*b,*p,*n,*x,*y;
BIGNUM *d2,*r1,*s1,*p1x,*p1y,*q1x,*q1y;
BIGNUM *u1,*u2,*xprime,*yprime,*k2,*k3,*x1,*y1,*r,*s2,*s3,*s,*tmp1,*tmp2,*tmp3;
EC_GROUP* group;
EC_POINT *generator,*G,*P,*P1,*Q1,*TMP;

BN_CTX* bn_ctx = BN_CTX_new();
BN_CTX_start(bn_ctx);
if (!bn_ctx)
{ error(); return res; }
e = BN_CTX_get(bn_ctx);
a = BN_CTX_get(bn_ctx);
b = BN_CTX_get(bn_ctx);
p = BN_CTX_get(bn_ctx);
n = BN_CTX_get(bn_ctx);
d2 = BN_CTX_get(bn_ctx);
x = BN_CTX_get(bn_ctx);
y = BN_CTX_get(bn_ctx);
p1x = BN_CTX_get(bn_ctx);
p1y = BN_CTX_get(bn_ctx);
q1x = BN_CTX_get(bn_ctx);
q1y = BN_CTX_get(bn_ctx);
r1 = BN_CTX_get(bn_ctx);
s1 = BN_CTX_get(bn_ctx);
u1 = BN_CTX_get(bn_ctx);
u2 = BN_CTX_get(bn_ctx);
xprime = BN_CTX_get(bn_ctx);
yprime = BN_CTX_get(bn_ctx);
k2 = BN_CTX_get(bn_ctx);
k3 = BN_CTX_get(bn_ctx);
x1 = BN_CTX_get(bn_ctx);
y1 = BN_CTX_get(bn_ctx);
r = BN_CTX_get(bn_ctx);
s2 = BN_CTX_get(bn_ctx);
s3 = BN_CTX_get(bn_ctx);
s = BN_CTX_get(bn_ctx);
tmp1 = BN_CTX_get(bn_ctx);
tmp2 = BN_CTX_get(bn_ctx);
tmp3 = BN_CTX_get(bn_ctx);

if (
!BN_hex2bn(&e, str_e) ||
!BN_hex2bn(&p1x, str_p1x) ||
!BN_hex2bn(&p1y, str_p1y) ||
!BN_hex2bn(&q1x, str_q1x) ||
!BN_hex2bn(&q1y, str_q1y) ||
!BN_hex2bn(&r1, str_r1) ||
!BN_hex2bn(&s1, str_s1) ||
!BN_hex2bn(&a, "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC") ||
!BN_hex2bn(&b, "28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93") ||
!BN_hex2bn(&p, "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF") ||
!BN_hex2bn(&n, "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123") ||
// d2 = ds (server key)
!BN_hex2bn(&d2, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX") ||
!BN_hex2bn(&x, "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7") ||
!BN_hex2bn(&y, "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0") ||
!BN_rand_range(k2,n) ||
!BN_copy(k3, k2)
)
{ error(); return res; }

// generate k2 in [1, n-1]
while(BN_is_zero(k2)){
if (
!BN_rand_range(k2,n) ||
!BN_copy(k3, k2)
)
{ error(); return res; }
}

group = EC_GROUP_new_curve_GFp(p, a, b, bn_ctx);
generator = EC_POINT_new(group);
if (!generator)
{ error(); return res; }
if (1 != EC_POINT_set_affine_coordinates_GFp(group, generator, x, y, bn_ctx))
{ error(); return res; }
if (1 != EC_GROUP_set_generator(group, generator, n, NULL))
{ error(); return res; }

G = EC_POINT_new(group);
P = EC_POINT_new(group);
P1 = EC_POINT_new(group);
Q1 = EC_POINT_new(group);
TMP = EC_POINT_new(group);

// if r1=0 or s1=0, error
if (BN_is_zero(r1) || BN_is_zero(s1))
{ error(); return res; }

// set P1 = (p1x, p1y)
if (1 != EC_POINT_set_affine_coordinates_GFp(group, P1, p1x, p1y, bn_ctx))
{ error(); return res; }

// set Q1 = (q1x, q1y)
if (1 != EC_POINT_set_affine_coordinates_GFp(group, Q1, q1x, q1y, bn_ctx))
{ error(); return res; }

//u1 = e * (s1^(-1)) mod n, u2 = r1 * (s1^(-1)) mod n
if (!BN_mod_inverse(tmp1, s1, n, bn_ctx) ||
!BN_mod_mul(u1, e, tmp1, n, bn_ctx) ||
!BN_mod_mul(u2, r1, tmp1, n, bn_ctx) ||
!BN_mod(u1, u1, n, bn_ctx) ||
!BN_mod(u2, u2, n, bn_ctx)
)
{ error(); return res; }

//u1*G + u2*P1 = (x', y')
if (!EC_POINT_mul(group, TMP, u1, P1, u2, bn_ctx))
{ error(); return res; }

if (!EC_POINT_get_affine_coordinates_GFp(group, TMP, xprime, yprime, bn_ctx))
{ error(); return res; }

//verify r1 = x' mod n
if (!BN_mod(xprime, xprime, n, bn_ctx))
{ error(); return res; }

if(BN_cmp(r1,xprime))
{ error_partial_verify(); return res; }

//k2*G + k3*Q1 = (x1, y1)
if (!EC_POINT_mul(group, TMP, k2, Q1, k3, bn_ctx))
{ error(); return res; }

if (!EC_POINT_get_affine_coordinates_GFp(group, TMP, x1, y1, bn_ctx))
{ error(); return res; }

//r=(e+x1) mod n
if (!BN_mod_add(r, e, x1, n, bn_ctx))
{ error(); return res; }

if (BN_is_zero(r))
{ error(); return res; }
strncpy(res.r, BN_bn2hex(r), 2*SM2LEN+1);

//s2 = d2 * k3 mod n, s3 = d2 * (r+k2) mod n
if (!BN_mod_mul(s2, d2, k3, n, bn_ctx) ||
!BN_mod_add(tmp1, r, k2, n, bn_ctx) ||
!BN_mod_mul(s3, d2, tmp1, n, bn_ctx) ||
!BN_mod(s2, s2, n, bn_ctx) ||
!BN_mod(s3, s3, n, bn_ctx)
)
{ error(); return res; }
printf("s2: %s\n",BN_bn2hex(s2));
printf("s3: %s\n",BN_bn2hex(s3));
strncpy(res.s2, BN_bn2hex(s2), 2*SM2LEN+1);
strncpy(res.s3, BN_bn2hex(s3), 2*SM2LEN+1);

// flag2 的格式如下:flag2{xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx},大括號中的內容為 16 進位制格式(字母小寫)的 d2。
print_flag2(d2);

rv = 0;
BN_CTX_free(bn_ctx);

return rv;
}

// 計算公鑰P
int getPublicKey(char *str_d2, char *str_p1x, char *str_p1y) {
int rv = 1;
BIGNUM *negone, *a, *b, *p, *n, *x, *y;
BIGNUM *d2, *p1x, *p1y, *px, *py;
BIGNUM *tmp1, *tmp2;
EC_GROUP *group;
EC_POINT *generator, *G, *P, *P1;

BN_CTX *bn_ctx = BN_CTX_new();
BN_CTX_start(bn_ctx);
if (!bn_ctx) {
error();
return 1;
}

negone = BN_CTX_get(bn_ctx);
a = BN_CTX_get(bn_ctx);
b = BN_CTX_get(bn_ctx);
p = BN_CTX_get(bn_ctx);
n = BN_CTX_get(bn_ctx);
d2 = BN_CTX_get(bn_ctx);
x = BN_CTX_get(bn_ctx);
y = BN_CTX_get(bn_ctx);
p1x = BN_CTX_get(bn_ctx);
p1y = BN_CTX_get(bn_ctx);
px = BN_CTX_get(bn_ctx);
py = BN_CTX_get(bn_ctx);
tmp1 = BN_CTX_get(bn_ctx);
tmp2 = BN_CTX_get(bn_ctx);

if (
!BN_hex2bn(&d2, str_d2) ||
!BN_hex2bn(&p1x, str_p1x) ||
!BN_hex2bn(&p1y, str_p1y) ||
!BN_hex2bn(&a, "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC") ||
!BN_hex2bn(&b, "28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93") ||
!BN_hex2bn(&p, "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF") ||
!BN_hex2bn(&n, "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123") ||
!BN_hex2bn(&x, "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7") ||
!BN_hex2bn(&y, "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0")
) {
error();
return 1;
}
group = EC_GROUP_new_curve_GFp(p, a, b, bn_ctx);
generator = EC_POINT_new(group);
if (!generator) {
error();
return 1;
}
if (1 != EC_POINT_set_affine_coordinates_GFp(group, generator, x, y, bn_ctx)) {
error();
return 1;
}
if (1 != EC_GROUP_set_generator(group, generator, n, NULL)) {
error();
return 1;
}

G = EC_POINT_new(group);
P = EC_POINT_new(group);
P1 = EC_POINT_new(group);

// set P1 = (p1x, p1y)
if (1 != EC_POINT_set_affine_coordinates_GFp(group, P1, p1x, p1y, bn_ctx)) {
error();
return 1;
}

//P = ((d2)^(-1)) * P1 - G
if (!BN_zero(tmp1) ||
!BN_one(tmp2) ||
!BN_mod_sub(negone, tmp1, tmp2, n, bn_ctx)
) {
error();
return 1;
}
if (!BN_mod_inverse(tmp1, d2, n, bn_ctx) || !EC_POINT_mul(group, P, negone, P1, tmp1, bn_ctx)) {
error();
return 1;
}

if (!EC_POINT_get_affine_coordinates_GFp(group, P, px, py, bn_ctx)) {
error();
return 1;
}
printf("Px: %s\n", BN_bn2hex(px));
printf("Py: %s\n", BN_bn2hex(py));

rv = 0;
BN_CTX_free(bn_ctx);

return rv;
}

int main(int argc, char *argv[]) {
int rv = 1;
if (server(argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7])) {
error();
return rv;
}

rv = 0;
return rv;
}

這個題目程式碼特別特別的長,具體細節可以慢慢讀。

.js檔案是互動部分,梳理一下主要互動流程是:

使用者輸入口令和訊息摘要,併傳送給伺服器
使用者本地計算出如下資料,這些資料可以在傳送包的負載裡找到:
e, p1x, p1y, q1x, q1y, r1, s1

伺服器接收到資料後,進行協同簽名,併傳送以下資料返回:
   s2, s3, r
我們需要計算出伺服器的私鑰d2,d2就是flag2的值

而.c檔案則是告訴我們協同簽名流程,這些資料主要有以下一些關係(運算均在模n下,n是曲線階):

使用SM2的標準曲線,引數及生成元G均已知,伺服器私鑰為d2,並有以下P點座標:
2024熵密杯wp

使用使用者傳送來的p1x, p1y, q1x, q1y這幾個資料設定點P1、Q1
使用使用者傳送來的e、r1、s1計算u1、u2:
2024熵密杯wp
計算中間點T(x’,y’),驗證r1=x’:
2024熵密杯wp
生成隨機數k2、k3,並計算:
2024熵密杯wp
計算r:
2024熵密杯wp
計算s2、s3:
2024熵密杯wp

返回r、s2、s3

整個步驟就是看註釋一步步梳理出來的,我們的目的是算出d2來,而s2、s3中一共有三個變數d2、k2、k3,並不足以求出所有未知數,所以可能需要利用r再構造一個等式才行。

然而這個題藏了個相當陰的地方,仔細觀察可以發現一行程式碼:

BN_copy(k3, k2)

這也就是說k3=k2,因此未知數實際上就只有兩個,所以很輕鬆就可以拿到d2了XD。

exp:

from Crypto.Util.number import *

a = int("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", 16)
b = int("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", 16)
p = int("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", 16)
n = int("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", 16)
x = int("32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16)
y = int("BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16)

E = EllipticCurve(Zmod(p),[a,b])
G = E(x,y)


################################################################################# res
e = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
p1x = "3e8eda67c5f1b70ac1950f615c2c4e0b0fe2544823ac96cb127ba318d96b4f5"
p1y = "ab1bbde72e7d1ef42e0c9d18d44a10e7250a0dfea98194f2d8d591b355fc636"
q1x = "bc44ec67a42c1613d9cf99f7bd2d1d859ab94823ba6cfb1836e8083e23bbd41e"
q1y = "faef1f853c095d6de79ba9ad9a2026d742042116b38b1c672ae67c7c7e9e762d"
r1 = "bc44ec67a42c1613d9cf99f7bd2d1d859ab94823ba6cfb1836e8083e23bbd41e"
s1 = "6c1bfef8bacf4f9c8bc4703c66458715475e50d17ba84f666372b4f4c364e16f"
r = "C987C22813DD2D0537433FF583C84B047E0313DCA072E187ACBB5A638D4E2BC0"
s2 = "E1E08110628EEB528DC26AA117AFEF8613B1D22EBFD77A9F42524CEFEB57F676"
s3 = "758CBCCFADFB5078DB26DF382A179C9AFDE1D0617D92EC5496F67380162235B6"

tt = [e,p1x,p1y,q1x,q1y,r1,s1,r,s2,s3]
e,p1x,p1y,q1x,q1y,r1,s1,r,s2,s3 = [int(i,16) for i in tt]
P1 = E(p1x,p1y)
Q1 = E(q1x,q1y)
u1 = e * inverse(s1, n) % n
u2 = r1 * inverse(s1, n) % n
T = u1*G + u2*P1
x_, y_ = T.xy()
assert r1 == x_
x1 = r - e

d2 = (s3-s2)*inverse(r,n) % n
print(hex(d2))

#flag2{a61bdbacbad62b141284a6955b14a27df01c09984e23785ec75b5e5c79e18f62}



flag3(500 pts)

題目:

login.go:

package controllers

import (
"crypto/ecdsa"
"encoding/hex"
"encoding/pem"
"fmt"
jwtgo "github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
"github.com/tjfoc/gmsm/sm2"
"github.com/tjfoc/gmsm/x509"
"http_svr/config"
"http_svr/models"
"http_svr/utils"
"math/big"
"net/http"
"time"
)

// 載入證書
func loadCertificate(certPEM string) (*x509.Certificate, error) {
//certPEM := "-----BEGIN CERTIFICATE-----\nMIIBQDCB6KADAgECAgECMAoGCCqBHM9VAYN1MBIxEDAOBgNVBAoTB1Jvb3QgQ0Ew\nHhcNMjQwNzI0MDkyMTI5WhcNMjUwNzI0MDkyMTI5WjAaMRgwFgYDVQQKEw9NeSBP\ncmdhbml6YXRpb24wWTATBgcqhkjOPQIBBggqgRzPVQGCLQNCAASlPepwTvt5c4rF\nEsg1Mqs+Tyx/BwRkwyWqDyZd/gBFKp7veuoZnGK11c24xPOqR/eQZNW7ugsZW6eb\nLyXSsE9ooycwJTAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEw\nCgYIKoEcz1UBg3UDRwAwRAIgG4/snkgUCW819OotUWUfMOo0BzHX8KeTTUSLpIjy\nEO4CIEq6X7h3nVNeFzdtLWdy5+1MeNwsWawHU5YzITsNtqOe\n-----END CERTIFICATE-----\n"
block, _ := pem.Decode([]byte(certPEM))
if block == nil || block.Type != "CERTIFICATE" {
return nil, fmt.Errorf("無效的證書格式")
}

return x509.ParseCertificate(block.Bytes)
}

// 驗證證書
func validateCertificate(cert *x509.Certificate, rootCert *x509.Certificate) error {
// 檢查頒發者
if cert.Issuer.CommonName != rootCert.Subject.CommonName {
return fmt.Errorf("證書校驗失敗")
}
// 檢查頒發者組織
if len(cert.Issuer.Organization) != 1 || cert.Issuer.Organization[0] != rootCert.Subject.Organization[0] {
return fmt.Errorf("證書校驗失敗")
}
// 檢查頒發者國家
if len(cert.Issuer.Country) != 1 || cert.Issuer.Country[0] != rootCert.Subject.Country[0] {
return fmt.Errorf("證書校驗失敗")
}

// 檢查有效日期
if time.Now().Before(cert.NotBefore) || time.Now().After(cert.NotAfter) {
return fmt.Errorf("證書校驗失敗")
}

// 檢查組織
if len(cert.Subject.Organization) != 1 || cert.Subject.Organization[0] != "ShangMiBei" {
return fmt.Errorf("證書校驗失敗")
}

// 檢查組織單元
if len(cert.Subject.OrganizationalUnit) != 1 || cert.Subject.OrganizationalUnit[0] != "ShangMiBei2024" {
return fmt.Errorf("證書校驗失敗")
}

// 檢查國家
if len(cert.Subject.Country) != 1 || cert.Subject.Country[0] != "CN" {
return fmt.Errorf("證書校驗失敗")
}

// 建立證書鏈
roots := x509.NewCertPool()
roots.AddCert(rootCert)

opts := x509.VerifyOptions{
Roots: roots,
CurrentTime: time.Now(),
}

// 驗證證書鏈
if _, err := cert.Verify(opts); err != nil {
return fmt.Errorf("證書鏈校驗失敗: %v", err)
}

return nil
}

type SM2Signature struct {
R, S *big.Int
}

// 驗證簽名
func validateSignature(message, signature string, publicKey *sm2.PublicKey) (bool, error) {
//rawSignatureHex, err := base64.StdEncoding.DecodeString(base64EncodedSignature)
hexSignature, err := hex.DecodeString(signature)
if err != nil {
return false, fmt.Errorf("invalid signature format")
}

isValid := publicKey.Verify([]byte(message), hexSignature)
if isValid {
return true, nil
} else {
return false, fmt.Errorf("signature is invalid")
}
}

// Login 登入
func Login(c *gin.Context, conf config.Config) {
// 解析請求引數
var req models.LoginReq
if err := c.ShouldBind(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

// 校驗使用者名稱是否已註冊過
if _, exists := models.Users[req.Username]; !exists {
c.JSON(http.StatusBadRequest, gin.H{"error": "username not exists"})
return
}

// 校驗隨機字串是否過期
randomStr, exists := conf.Cache.Get(req.Username)
if !exists {
c.JSON(http.StatusBadRequest, gin.H{"error": "random string has expired"})
return
}

// 校驗證書
cert, err := loadCertificate(req.Cert)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := validateCertificate(cert, models.RootCert); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

// 判斷是否挑戰成功(隨機字串的簽名能否用證書中的公鑰驗簽過)
ecdsaPubKey, ok := cert.PublicKey.(*ecdsa.PublicKey)
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": "public key in cert is not sm2"})
return
}
sm2PubKey := sm2.PublicKey{
Curve: ecdsaPubKey.Curve,
X: ecdsaPubKey.X,
Y: ecdsaPubKey.Y,
}
isValid, err := validateSignature(randomStr.(string), req.Signature, &sm2PubKey)
if isValid {
//c.JSON(http.StatusOK, gin.H{"msg": "success", "flag3": config.Flag3, "download_url": config.DownloadUrl})
generateToken2(c, req.Username, conf)
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
}

// 生成令牌
func generateToken2(c *gin.Context, username string, conf config.Config) {
j := &utils.JWT{
SigningKey: []byte(conf.SignKey),
}
claims := utils.CustomClaims{
Name: username,
StandardClaims: jwtgo.StandardClaims{
NotBefore: time.Now().Unix() - conf.NotBeforeTime, // 簽名生效時間
ExpiresAt: time.Now().Unix() + conf.ExpiresTime, // 過期時間
Issuer: conf.Issuer, // 簽名的發行者
},
}

token, err := j.CreateToken(claims)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"code": 5091,
"msg": "登入失敗,系統有誤",
})
return
}

// 將當前使用者對應的快取中的隨機字串刪除
conf.Cache.Delete(username)

isAdmin := false
if username == "shangmibeiadmin" {
isAdmin = true
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"msg": "登入成功",
"token": token,
"is_admin": isAdmin,
})
return
}

資料庫管理系統管理員證書.cer:

-----BEGIN CERTIFICATE-----
MIICXjCCAgWgAwIBAgIIatKGfgnOvYYwCgYIKoEcz1UBg3UwNjELMAkGA1UEBhMC
Q04xEzARBgNVBAoTClNoYW5nTWlCZWkxEjAQBgNVBAMTCVNoYW5nTWlDQTAeFw0y
NDA4MDUwNzUyMTdaFw0yNTEwMTAxMjAxMDFaMFUxEzARBgNVBAoTClNoYW5nTWlC
ZWkxFzAVBgNVBAsTDlNoYW5nTWlCZWkyMDI0MRgwFgYDVQQDEw9zaGFuZ21pYmVp
YWRtaW4xCzAJBgNVBAYTAkNOMFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEiHG2
LM9gsuJXiyo+0yDDZEVP1+3Qh+47g65eMeoUXoi0eUiGPvhehh4RaWacpVrQKJXQ
qzCqkR4n1B+7ZymwXqOB3TCB2jAOBgNVHQ8BAf8EBAMCA4gwHQYDVR0lBBYwFAYI
KwYBBQUHAwIGCCsGAQUFBwMBMA8GA1UdDgQIBAYBAgMEBQYwDwYDVR0jBAgwBoAE
AQIDBDAuBgNVHREEJzAlgQtnaXRAZ2l0LmNvbYcEfwAAAYcQIAFIYAAAIAEAAAAA
AAAAaDBXBgNVHR8EUDBOMCWgI6Ahhh9odHRwOi8vY3JsMS5leGFtcGxlLmNvbS9j
YTEuY3JsMCWgI6Ahhh9odHRwOi8vY3JsMi5leGFtcGxlLmNvbS9jYTEuY3JsMAoG
CCqBHM9VAYN1A0cAMEQCIEU8qEYGqgRTJPGI8YLRrpR7x3M2HzZOt377PwsnivGW
AiA67pgq6qfrhKsWc/B2VUqi2t+ZlK+iAM6D+Ai7NoqYSw==
-----END CERTIFICATE----

題目連線上之後有一個簡易的網站,由於復現不了所以只能大致描述一下它的功能:

  • 有一個登入介面,可以輸入使用者名稱、私鑰以及公鑰檔案,如果能透過login.go中的所有check就能成功登入
  • 還有一個註冊介面,可以輸入使用者名稱和裸公鑰,如果裸公鑰格式正確,伺服器就會用根證書發放一個完整公鑰檔案給你

我們的目標是用“shangmibeiadmin”成功登入,就可以拿到flag3的值以及flag4的原始碼。

已知的這個證書檔案是個公鑰檔案,檢視一下發現這個證書的使用者就是“shangmibeiadmin”,所以如果我們能知道他的私鑰的話就可以直接登入了。結合這個題只有500分這個事實,我第一反應是私鑰相當小,可以直接爆出來,但是用mitm爆了2^50無果,所以只能從其他部分入手。

用gmssl這個工具可以比較輕鬆的生成一對公私鑰證書,我們只需要把公鑰裡的裸公鑰拆出來,然後自己隨便生成個使用者名稱就可以註冊一個使用者,並得到伺服器頒發的公鑰證書。

這裡需要注意一下不能直接註冊“shangmibeiadmin”,它會顯示已註冊

然後檢視login.go可以發現他似乎根本沒檢驗證書持有者是不是和使用者名稱一樣,所以按理來說接下來的步驟很簡單,我們只需要在使用者名稱一欄輸入“shangmibeiadmin”,然後輸入剛才我們生成的公私鑰證書中的私鑰,再輸入剛才伺服器下發的證書就可以成功登入。

然而我們實在是不熟悉gmssl乃至openssl這些工具,並且不出網,不能自由查詢怎麼使用,所以只能一直用help來看有什麼引數可以用。我們遇到的最大問題是:gmssl必須要一個密碼,才能生成sm2私鑰檔案,而這個私鑰檔案是用這個密碼加密過的,但是我們怎麼找都找不到怎麼解密這個私鑰檔案並解析他。

這裡花了很長很長時間,最後離比賽結束不到一小時的時候想了一個笨辦法出來——直接去原始碼c檔案裡面加幾行列印私鑰d的檔案,並重新編譯一下再用這個工具:

image-20240906160617551

這個方法很笨但是確實有效,由於腦子有點混亂,也想不太清楚d具體該怎麼拼,就用從前往後和從後往前兩種順序得到兩個d,並用是否滿足P=dG這個式子來進行核驗,最後好歹是把自己生成的私鑰d搞出來了:

from Crypto.Util.number import *
from tqdm import *

a = int("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", 16)
b = int("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", 16)
p = int("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", 16)
n = int("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", 16)
x = int("32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16)
y = int("BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16)

E = EllipticCurve(Zmod(p),[a,b])
G = E(x,y)

t = "3059301306072a8648ce3d020106082a811ccf5501822d03420004ed7a7dce0e4e2e4b779f76b4ec407b8987ba5c3beba5cd454604e587fce0a17160b29510b2beb36e36470fba3ed6bd436049a0b588e931c71df6cf0b0d0e6407"
x1 = int(t[-128:-64], 16)
y1 = int(t[-64:], 16)

P = E(x1,y1)

dd = [12437958772606967559,9879664919779981675,172814172046494727,15816591967453487196]

d = (dd[3] << (64*3)) + (dd[2] << (64*2)) + (dd[1] << (64*1)) + (dd[0] << (64*0))
print(d)
print(hex(d))
print(d*G == P)

之後按剛才的方式就可以登入上網站拿到flag3以及flag4的原始碼。



flag4(1000 pts)

題目:

SM4加密解密程式碼.py:

from gmssl.sm4 import CryptSM4, SM4_ENCRYPT, SM4_DECRYPT

MULTIPLIER = 6364136223846793005
ADDEND = 1
MASK = 0xffffffffffffffff
ITERATIONS = 1000

# 從檔案中讀取seed
def read_seed(file_path):
with open(file_path, 'r') as file:
seed = int(file.read().strip(), 16)
print("seed:", hex(seed))
return seed

global_seed = read_seed('seed.txt')

def genRandom():
global global_seed
# print("global_seed", hex(global_seed))
for _ in range(ITERATIONS):
global_seed = (global_seed * MULTIPLIER + ADDEND) & MASK
return (global_seed >> 32) & 0xffffffff

# 16進位制字串轉bytes
def HexStringToBytes(hex_str):
return bytes.fromhex(hex_str)

# bytes轉16進位制字串
def BytesToHexString(byte_seq):
return byte_seq.hex()

def genSM4KeyOrIV():
return HexStringToBytes(''.join(f'{genRandom():08x}' for _ in range(4)))

def SM4Encrypt(data_bytes, key_bytes, iv_bytes):
sm4 = CryptSM4()
sm4.set_key(key_bytes, SM4_ENCRYPT)
return sm4.crypt_cbc(iv_bytes, data_bytes)

def SM4Decrypt(cipher_bytes, key_bytes, iv_bytes):
sm4 = CryptSM4()
sm4.set_key(key_bytes, SM4_DECRYPT)
return sm4.crypt_cbc(iv_bytes, cipher_bytes)


print("############ SM4 Cryptographic Services Start... ###################")

iv_bytes = genSM4KeyOrIV()
print("iv hex:", BytesToHexString(iv_bytes))

key_bytes = genSM4KeyOrIV()
print("key hex:", BytesToHexString(key_bytes))

# 從test.pcapng讀取資料並加密
with open('test.pcapng', 'rb') as f1:
plain1_bytes = f1.read()
cipher1_bytes = SM4Encrypt(plain1_bytes,key_bytes,iv_bytes)

# 寫密文資料到cipherText.dat
with open('cipherText.dat', 'wb') as f2:
f2.write(cipher1_bytes)

# 從cipherText.dat讀密文資料
with open('cipherText.dat', 'rb') as f3:
cipher2_bytes = f3.read()
plain2_bytes = SM4Decrypt(cipher2_bytes,key_bytes,iv_bytes)

# 解密密文並將明文寫入到plainText.pcapng(含flag4)
with open('plainText.pcapng', 'wb') as f4:
f4.write(plain2_bytes)

總經理協同簽名流量包加密使用的iv.txt:

90fc5cf2e2f47488a257fd51e0ae615

終於是一個python加密了,倍感親切。題目主要流程是:

  • 讀取seed.txt檔案得到初始seed
  • 用genSM4KeyOrIV函式連續生成16位元組的iv和key
  • 讀取一個流量包檔案,並用iv、key對流量包檔案進行SM4加密
  • 給出密文檔案以及iv,要求還原流量包

有古怪的地方只可能在genSM4KeyOrIV函式里,檢視一下發現其是連續呼叫四次genRandom函式並拼接而成,而genRandom函式是:

def genRandom():
global global_seed
# print("global_seed", hex(global_seed))
for _ in range(ITERATIONS):
global_seed = (global_seed * MULTIPLIER + ADDEND) & MASK
return (global_seed >> 32) & 0xffffffff

可以看出這是一個LCG過程,其會返回seed迭代一千次之後的高32位。

我們知道IV,也就是我們知道連續四次迭代一千次之後的seed高位,這就變成了一個簡單的HNP問題。由於LCG迭代過程可以寫為如下矩陣乘法:

2024熵密杯wp


所以一千次迭代也就是:

2024熵密杯wp


對於題目來說是已知高32位,那麼以IV的第一個分組和第二個分組為例,式子就可以寫成:


2024熵密杯wp


所以對IV所有連續的兩組用第一行對應的線性等式,就可以把問題轉化成規約低32位的HNP問題了,得到所有低位之後就可以向後迭代得到key,從而恢復流量包。

exp:

get xl:

c = "90fc5cf2e2f47488a257fd51e0ae615b"

MULTIPLIER = 6364136223846793005
ADDEND = 1
MASK = 0xffffffffffffffff + 1
ITERATIONS = 1000

t1,t2,t3,t4 = c[:8],c[8:16],c[16:24],c[24:32]
res = [t1,t2,t3,t4]
t = [int(i,16) for i in res]

##################################################
M = Matrix(Zmod(MASK),[
[MULTIPLIER,1],
[0,1]
])
Mn = M^ITERATIONS
a,b = Mn[0]
a,b = int(a),int(b)

nums = 4
L = Matrix(ZZ,2*nums,2*nums)
for i in range(nums+1):
L[i,i] = 1
for i in range(nums-1):
L[i,nums+i+1] = a
L[i+1,nums+i+1] = -1
c = a*2^32*t[i] - 2^32*t[i+1] + b
L[nums,nums+i+1] = c
L[nums,nums] = 2^32
for i in range(nums-1):
L[-i-1,-i-1] = MASK
L[:,-(nums-1):] *= MASK

res = L.LLL()[0][:4]
print(res)

decrypt:

from gmssl.sm4 import CryptSM4, SM4_ENCRYPT, SM4_DECRYPT

MULTIPLIER = 6364136223846793005
ADDEND = 1
MASK = 0xffffffffffffffff
ITERATIONS = 1000

global_seed = 0 # TODO
iv_high = 0xe0ae615b
iv_low = 187714221
iv_last = (iv_high << 32) + iv_low
global_seed = iv_last

def genRandom():
global global_seed
# print("global_seed", hex(global_seed))
for _ in range(ITERATIONS):
global_seed = (global_seed * MULTIPLIER + ADDEND) & MASK
return (global_seed >> 32) & 0xffffffff

# 16進位制字串轉bytes
def HexStringToBytes(hex_str):
return bytes.fromhex(hex_str)

# bytes轉16進位制字串
def BytesToHexString(byte_seq):
return byte_seq.hex()

def genSM4KeyOrIV():
return HexStringToBytes(''.join(f'{genRandom():08x}' for _ in range(4)))

def SM4Encrypt(data_bytes, key_bytes, iv_bytes):
sm4 = CryptSM4()
sm4.set_key(key_bytes, SM4_ENCRYPT)
return sm4.crypt_cbc(iv_bytes, data_bytes)

def SM4Decrypt(cipher_bytes, key_bytes, iv_bytes):
sm4 = CryptSM4()
sm4.set_key(key_bytes, SM4_DECRYPT)
return sm4.crypt_cbc(iv_bytes, cipher_bytes)

iv_bytes = HexStringToBytes("90fc5cf2e2f47488a257fd51e0ae615b")
key_bytes = genSM4KeyOrIV()
print(key_bytes)

with open("總經理協同簽名流量包(加密後的檔案).dat", "rb") as fp:
cipher_bytes = fp.read()
plain_bytes = SM4Decrypt(cipher_bytes, key_bytes, iv_bytes)

with open("plainText.pcapng", "wb") as fp:
fp.write(plain_bytes)

然後就可以在流量包裡找到flag4。

2024熵密杯wp


最終挑戰 *

在比賽還是不到半分鐘的時候,我們隊才驚險地交上flag4,完全沒有時間看最終挑戰了,因此只能賽後復現一下。

flag4的流量包跟蹤TCP流,可以看到裡面有以下內容:

image-20240906162944644

除了flag4外,剩下的資料很顯然是和flag2的協同簽名有關的,而相比於flag2來說,這裡多給了一個client_sign欄位的值,再回頭看看.js檔案可以發現這是clientSign2函式的返回值,其流程為:

在clientSign1的過程裡會生成一個隨機數k1,滿足:
2024熵密杯wp

  • 傳入未知的使用者私鑰d1,以及已知的s2、s3、r
計算s:
2024熵密杯wp


可以看出s1、s的生成等式其實分別就是關於d1、k1的兩個變數的方程,所以就可以解出d1了。而我們的目的是偽造一個簽名,解出d1之後走一遍協同簽名的流程就好了,自然也就沒有難度。

沒有互動部分了,但可以用d1聯絡的兩個點來檢驗d1的正確性

exp:

from Crypto.Util.number import *

a = int("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", 16)
b = int("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", 16)
p = int("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", 16)
n = int("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", 16)
x = int("32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16)
y = int("BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16)

E = EllipticCurve(Zmod(p),[a,b])
G = E(x,y)


################################################################################# res
q1x = "125fd6eb66351ca49073a6e55be1fa40cfd6662f80452a6bcea3b25bd69b6b26"
q1y = "79a9748598cc2886b09fa856b9806b8789b8a719f6a969e2f08da35ea997bc5d"
e = "eaf0adee014bd35a12180bbc99292e3acf895203aa97f8dbbb760da04da844f6"
r1 = "125fd6eb66351ca49073a6e55be1fa40cfd6662f80452a6bcea3b25bd69b6b26"
s1 = "47baaef61c7a3c4c239fc2634ec25a2059d937026c6e0b72df1463fbba5b3a05"
p1x = "4c84b1cf8e9255c9385c07c2bf3426a9497d49e2b33c328ab02c4aed8b021bad"
p1y = "8a3e40da9d3423f27be30eebb2e4e11999e565be0def197fe1bcf4f6b724b471"

r = "8A6BB033033E79683E81FE36D6394262D451A3DB9D1A0C489D51543D22E67BC4"
s2 = "B54A6668F644EC08D925552D45F66E348762B460693E7A68CBB0FDF38327DB45"
s3 = "B50FAE013594F79192898FF7FC0A84D931B1EC56EF9174159023ACF1C708180D"

s = "cb524f49515c9a7387210ddcdbf1f32aad1c8806f01a362c62a5d6a5466da158"


tt = [e,p1x,p1y,q1x,q1y,r1,s1,r,s2,s3,s]
e,p1x,p1y,q1x,q1y,r1,s1,r,s2,s3,s = [int(i,16) for i in tt]
P1 = E(p1x,p1y)
Q1 = E(q1x,q1y)

################################################################################# solve d1
PR.<k1,d1> = PolynomialRing(Zmod(n))
f1 = (s1*k1 - e)*d1 - r1
f2 = d1*k1*s2 + d1*s3 - r - s
res = f1.sylvester_matrix(f2, k1).det().univariate_polynomial().monic().roots()
d1 = int(res[1][0])

print(d1*P1 == G)

或者

from sage.all import *

a = 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC
b = 0x28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93
p = 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF
n = 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123
x = 0x32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7
y = 0xBC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0
E = EllipticCurve(GF(p), [a, b])
G = E(x, y)

s = 0xcb524f49515c9a7387210ddcdbf1f32aad1c8806f01a362c62a5d6a5466da158

r = 0x8A6BB033033E79683E81FE36D6394262D451A3DB9D1A0C489D51543D22E67BC4
s2 = 0xB54A6668F644EC08D925552D45F66E348762B460693E7A68CBB0FDF38327DB45
s3 = 0xB50FAE013594F79192898FF7FC0A84D931B1EC56EF9174159023ACF1C708180D

e = 0xeaf0adee014bd35a12180bbc99292e3acf895203aa97f8dbbb760da04da844f6
r1 = 0x125fd6eb66351ca49073a6e55be1fa40cfd6662f80452a6bcea3b25bd69b6b26
s1 = 0x47baaef61c7a3c4c239fc2634ec25a2059d937026c6e0b72df1463fbba5b3a05

d2 = ZZ((s3 - s2) * inverse_mod(r, n) % n)

'''
s1*k1-e = d1^(-1) * r1
r1 = d1*(s1*k1-e)
r1 = d1*k1 * s1 - d1*e
s = d1*k1*s2 + d1*s3 -r
s*s1 = d1*k1*s1 * s2 + d1*s3*s1 - r*s1
s*s1 = (r1+d1*e)*s2 + d1 * s3*s1 - r*s1
'''
R = PolynomialRing(GF(n), 'x')
x = R.gens()[0]
f = (r1 + x*e)*s2 + x*s3*s1 - r*s1 - s*s1
ans = f.roots()

d1 = 90919127323695568397119051689582862352296983775157729258730148362152821090405
d2 = 75133153874808200698750375741973887146735262423059242244009334005845482114914
e = 0x9e810778a6b177c6aa1799365977adfbeef605c19b5ea917527d1541c1339019

k1 = 233
P = inverse_mod(d1, n) * G
Q = k1*G
r1 = ZZ(Q.xy()[0])
s1 = ZZ(inverse_mod(k1, n) * (e + inverse_mod(d1, n) * r1) % n)

k2 = 17
k3 = 71
R = k2*G + k3*Q
x1 = ZZ(R.xy()[0])
r = ZZ((e + x1) % n)
s2 = ZZ(d2 * k3 % n)
s3 = ZZ(d2 * (r+k2) % n)

s = (d1*k1*s2 + d1*s3 - r) % n
print(s)
print(hex(r)[2:])
print(hex(s)[2:])




來源: https://tangcuxiaojikuai.xyz/post/6452f9a0.html

相關文章