碎碎念

对于写WP个人的见解

对于一些比赛,虽然有写wp,但是其实本人是想着一个方向或者多个方向复现完再发的,但是这比较困难,因为每个比赛的每个方向题目都是有简单、有困难的。简单的题目还好,可能一下子就复现完了,但是剩下的比较困难的题目就很难受了可能会因为知识储备不足而无法自己独立做出。虽然说是有wp可以看,但是看wp的目的是能学到东西,而不是利用脚本一把梭一下。

所以在复现比赛题目的时候往往对自己要求比较高,而遇到难的短时间复现不了,时间一长好像就把题目鸽了(老毛病了)QAQ。这就导致我写的wp感觉还是比较少的。并且一些比赛题目是用AI一把梭的(AI一把梭的根本学不到东西),对于这种情况我是更希望复现完了再发wp的。

对于CTF中使用AI的一些碎碎念

高中物理老师说过,考试是考试,考试要有考试的技巧;平时刷题就要踏踏实实的刷题;考试过后对于试卷的题目也是需要踏踏实实的。

而CTF比赛其实某种程度上也相当于考试吧,而大部分CTF比赛都允许使用AI的,这就导致在比赛中使用AI就相当于一个考试的技巧,黑猫白猫抓到老鼠的都是好猫——能解出题目的AI就是好AI(哈哈哈哈)。这就导致了大部分人遇到题目就会直接把题目附件之类的直接丢给AI一把梭(包括我自己)。

所以这个暑假就期间就已经开始反思了,就觉得AI一把梭是真学不到东西,赛后也是一鸽再鸽,所以干脆在平时的CTF比赛中减少AI一把梭的次数,并且拿到题目附件的时候并不是第一时间丢给AI,而是认认真真的看懂代码,对于代码中不明白的部分再去询问AI(这样总比直接复制粘贴丢给AI能学到东西),只有实在没思路的题目还是需要AI一把梭的(毕竟CTF是个竞赛,还是个团队协作的比赛,都能用AI一把梭的但是你不会,也只能先使用AI一把梭把flag梭出来再说了)。

Crypto

hinting

  • 题目附件如下:
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
import secrets
from Crypto.Util.number import bytes_to_long
import sys

FLAG = b"There was an actual flag here once"
FLAG = sys.argv[1].encode() if len(sys.argv) > 1 else FLAG
BS = 1024
FLAG = bytes_to_long(FLAG)
set_random_seed(secrets.randbelow(int(2**64)))
p, q = random_prime(2 ** (BS), 2 ** (BS - 1)), random_prime(2**BS, 2 ** (BS - 1))
n = p * q
e = 0x10001
c = pow(FLAG, e, n) # RSA加密

print(f"n = {n:x}")
print(f"e = {e:x}")
print(f"c = {int(c):x}")

pp, qq = (
p.digits(base=7, padto=int(log(2 ** (BS + 1), 7)) + 2),
q.digits(base=7, padto=int(log(2 ** (BS + 1), 7)) + 2),
) #pp,qq分别为p、q的7进制位,小索引号对应的是低位
V = vector([(pp[i] + qq[i]) % 7 for i in range(len(pp))]) # 相同位相加然后模7
print(V)# 输出结果
"""
n = 2666fab0a6dae7095642f0cb67602776badc8ede2629a54866017fc3840394f8157f0cc38e9f02b48733f1dbae4514d3d808d9a824b2933170e153dfcfedb0148f8a89311575df58c2c12ee11fae2510073956221ec472ae3f1bbd685011c350955502a2b0b1160e1fa299a050c0ee89c161c0fa55e32fb3806f2970287702f7d566e155fac71a4202c8b15d27c0dac2a566ad955d0ef7df73af86dc4e8f7e0048ccdb6ea551477c99bd6545b7dda2886c86796f20cbba8f2ddd173e2ab93bb00e5993c8adac5921d8586553b7a6086f7ff8c43f571d251bf5931ebb858bd64366cd92efa2fa7d9c4ea6a91049696454066046ef6dd4e6d9516439bcbadc266b
e = 10001
c = 230e6605e4a59cda037ec9d08830137e93bcec65af7ca9d93c17fccc45b7e7908909134e9f6410ab9b76b53c2402455e6b7cc0b1e7ee2c0921b2ebc5c6407b323fef3b905197ef4224886d2ba0b98b277f2cf267fbefae9e2067e2c8a4be0915b2665d53c1cf725b4f50ff4e7b66743656f36bee57b93fa07bd3d5fcaf7b596d48e876b26499b436c703581d10d85de024a803710f188733766c3be06dd6eeb7a0acff37a4656cb9d4c0053a96b2c61480b6da6792095d47d3a7e7e0beb00acba38d5f430228e54f691dce6e63a936500acede51afbba16ea8b0b7042373787667dfc5694d4ed048a9127565ca2050986997cd312a54616d63611d56ab4dbbf0
(1, 0, 3, 4, 1, 1, 1, 1, 5, 6, 0, 1, 3, 2, 0, 5, 6, 2, 4, 6, 0, 6, 4, 2, 3, 1, 6, 5, 1, 6, 0, 2, 2, 2, 2, 6, 0, 5, 6, 2, 5, 0, 6, 5, 2, 5, 5, 0, 0, 2, 3, 2, 5, 2, 0, 4, 2, 2, 1, 0, 6, 2, 3, 5, 3, 6, 5, 5, 3, 6, 1, 4, 4, 2, 2, 6, 6, 3, 5, 6, 3, 6, 6, 5, 6, 1, 1, 6, 5, 4, 4, 2, 1, 3, 0, 5, 4, 4, 0, 6, 3, 2, 1, 0, 1, 2, 0, 6, 3, 5, 4, 2, 2, 1, 5, 3, 0, 1, 3, 5, 2, 2, 3, 6, 5, 5, 2, 0, 6, 0, 6, 1, 0, 2, 2, 4, 0, 3, 1, 4, 1, 4, 6, 5, 4, 6, 3, 3, 1, 3, 0, 3, 4, 5, 0, 0, 0, 4, 1, 6, 5, 3, 0, 0, 1, 4, 3, 4, 0, 3, 4, 1, 6, 3, 6, 0, 0, 3, 6, 2, 2, 3, 4, 4, 1, 5, 4, 5, 1, 5, 3, 2, 0, 0, 1, 4, 3, 0, 4, 2, 1, 3, 0, 2, 6, 1, 5, 6, 3, 4, 1, 3, 2, 5, 3, 5, 5, 5, 1, 5, 2, 2, 1, 2, 6, 6, 3, 1, 2, 5, 6, 5, 2, 3, 4, 2, 3, 4, 2, 6, 6, 0, 6, 1, 5, 3, 1, 4, 5, 0, 4, 3, 1, 0, 1, 1, 1, 0, 4, 6, 0, 4, 2, 0, 0, 2, 0, 4, 3, 2, 3, 0, 4, 5, 6, 2, 4, 6, 6, 0, 0, 4, 1, 1, 3, 3, 6, 1, 6, 2, 4, 4, 3, 5, 3, 2, 4, 3, 4, 1, 4, 1, 6, 6, 1, 0, 4, 2, 3, 6, 3, 5, 4, 2, 6, 2, 2, 1, 6, 2, 1, 2, 4, 4, 0, 0, 4, 5, 2, 3, 1, 4, 1, 1, 5, 6, 0, 0, 5, 0, 4, 6, 1, 5, 2, 0, 0, 4, 6, 2, 3, 3, 2, 2, 5, 2, 0, 1, 2, 1, 0, 5, 6, 2, 3, 0, 0)
"""
  • 这题其实也是类似于p异或q的题型,都是通过剪枝搜索,但是应该如何搜索,应该从多项式乘法入手。目前已知pq的七进制位,将它们俩个用多项式表示如下:

p=an7n+an17n1+...+a171+a070q=bn7n+bn17n1+...+b171+b070pq=a0q+a171q+.....+an7nqp = a_n*7^{n} + a_{n-1}*7^{n-1} + ...+a_{1}*7^{1}+a_0*7^{0} \\q = b_n*7^{n} + b_{n-1}*7^{n-1} + ...+b_{1}*7^{1}+b_0*7^{0} \\p*q=a_0*q+a_1*7^{1}*q+.....+a_n*7^{n}*q

  • 这时展开来可以发现:

pq=a0b0+a0b171+a1b071+........p*q=a_0*b_0+a_0b_1*7^1+a_1b_0*7^{1}+........

  • 此时我们可以发现,由a_0*b_0其实就可以确定n的最低1位,而已知a_0、b_0、a_1、b_1就可以已知最低的2位,以此类推知道多少位就能已知多少位一直到最高位。这其实就类似于p^q的低位搜索,只不过将二进制换成了七进制。所以正常DFS剪枝搜索即可。(猜想:可能可以从高位和低位进行中间相遇)
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 libnum
n = 0x2666fab0a6dae7095642f0cb67602776badc8ede2629a54866017fc3840394f8157f0cc38e9f02b48733f1dbae4514d3d808d9a824b2933170e153dfcfedb0148f8a89311575df58c2c12ee11fae2510073956221ec472ae3f1bbd685011c350955502a2b0b1160e1fa299a050c0ee89c161c0fa55e32fb3806f2970287702f7d566e155fac71a4202c8b15d27c0dac2a566ad955d0ef7df73af86dc4e8f7e0048ccdb6ea551477c99bd6545b7dda2886c86796f20cbba8f2ddd173e2ab93bb00e5993c8adac5921d8586553b7a6086f7ff8c43f571d251bf5931ebb858bd64366cd92efa2fa7d9c4ea6a91049696454066046ef6dd4e6d9516439bcbadc266b
e = 0x10001
c = 0x230e6605e4a59cda037ec9d08830137e93bcec65af7ca9d93c17fccc45b7e7908909134e9f6410ab9b76b53c2402455e6b7cc0b1e7ee2c0921b2ebc5c6407b323fef3b905197ef4224886d2ba0b98b277f2cf267fbefae9e2067e2c8a4be0915b2665d53c1cf725b4f50ff4e7b66743656f36bee57b93fa07bd3d5fcaf7b596d48e876b26499b436c703581d10d85de024a803710f188733766c3be06dd6eeb7a0acff37a4656cb9d4c0053a96b2c61480b6da6792095d47d3a7e7e0beb00acba38d5f430228e54f691dce6e63a936500acede51afbba16ea8b0b7042373787667dfc5694d4ed048a9127565ca2050986997cd312a54616d63611d56ab4dbbf0
hint = (1, 0, 3, 4, 1, 1, 1, 1, 5, 6, 0, 1, 3, 2, 0, 5, 6, 2, 4, 6, 0, 6, 4, 2, 3, 1, 6, 5, 1, 6, 0, 2, 2, 2, 2, 6, 0, 5, 6, 2, 5, 0, 6, 5, 2, 5, 5, 0, 0, 2, 3, 2, 5, 2, 0, 4, 2, 2, 1, 0, 6, 2, 3, 5, 3, 6, 5, 5, 3, 6, 1, 4, 4, 2, 2, 6, 6, 3, 5, 6, 3, 6, 6, 5, 6, 1, 1, 6, 5, 4, 4, 2, 1, 3, 0, 5, 4, 4, 0, 6, 3, 2, 1, 0, 1, 2, 0, 6, 3, 5, 4, 2, 2, 1, 5, 3, 0, 1, 3, 5, 2, 2, 3, 6, 5, 5, 2, 0, 6, 0, 6, 1, 0, 2, 2, 4, 0, 3, 1, 4, 1, 4, 6, 5, 4, 6, 3, 3, 1, 3, 0, 3, 4, 5, 0, 0, 0, 4, 1, 6, 5, 3, 0, 0, 1, 4, 3, 4, 0, 3, 4, 1, 6, 3, 6, 0, 0, 3, 6, 2, 2, 3, 4, 4, 1, 5, 4, 5, 1, 5, 3, 2, 0, 0, 1, 4, 3, 0, 4, 2, 1, 3, 0, 2, 6, 1, 5, 6, 3, 4, 1, 3, 2, 5, 3, 5, 5, 5, 1, 5, 2, 2, 1, 2, 6, 6, 3, 1, 2, 5, 6, 5, 2, 3, 4, 2, 3, 4, 2, 6, 6, 0, 6, 1, 5, 3, 1, 4, 5, 0, 4, 3, 1, 0, 1, 1, 1, 0, 4, 6, 0, 4, 2, 0, 0, 2, 0, 4, 3, 2, 3, 0, 4, 5, 6, 2, 4, 6, 6, 0, 0, 4, 1, 1, 3, 3, 6, 1, 6, 2, 4, 4, 3, 5, 3, 2, 4, 3, 4, 1, 4, 1, 6, 6, 1, 0, 4, 2, 3, 6, 3, 5, 4, 2, 6, 2, 2, 1, 6, 2, 1, 2, 4, 4, 0, 0, 4, 5, 2, 3, 1, 4, 1, 1, 5, 6, 0, 0, 5, 0, 4, 6, 1, 5, 2, 0, 0, 4, 6, 2, 3, 3, 2, 2, 5, 2, 0, 1, 2, 1, 0, 5, 6, 2, 3, 0, 0)
x0 = {0:0,1:6,2:5,3:4,4:3,5:2,6:1}
x1 = {0:1,1:0,2:6,3:5,4:4,5:3,6:2}
x2 = {0:2,1:1,2:0,3:6,4:5,5:4,6:3}
x3 = {0:3,1:2,2:1,3:0,4:6,5:5,6:4}
x4 = {0:4,1:3,2:2,3:1,4:0,5:6,6:5}
x5 = {0:5,1:4,2:3,3:2,4:1,5:0,6:6}
x6 = {0:6,1:5,2:4,3:3,4:2,5:1,6:0}
print(len(x0))
search = {0:x0,1:x1,2:x2,3:x3,4:x4,5:x5,6:x6}
n_list = n.digits(base=7)
print(n_list)
candidate = []
# 1*7^0 + 0*7^1 + ... +
#print(bit)
def find(p,q):
l = len(p)
if l < len(hint):
count = hint[l]
p_ = int(p,7)
q_ = int(q,7)
n_ = p_*q_
nn_list = n_list[0:l]
if n_==n:
candidate.append(p_)
#print('--------')
if Integer(n_).digits(base=7)[0:l] == nn_list:
if l>= len(hint):
return
h = hint[l]

print(Integer(n_).digits(base=7))
if h==0:
s = search[int(h)]
for i in range(len(s)):
#print('sou')
find(str(i)+p,str(s[i])+q)
if h==1:
s = search[int(h)]
for i in range(len(s)):
#print('sou')
find(str(i)+p,str(s[i])+q)
if h==2:
s = search[int(h)]
for i in range(len(s)):
#print('sou')
find(str(i)+p,str(s[i])+q)
if h==3:
s = search[int(h)]
for i in range(len(s)):
#print('sou')
find(str(i)+p,str(s[i])+q)
if h==4:
s = search[int(h)]
for i in range(len(s)):
#print('sou')
find(str(i)+p,str(s[i])+q)
if h==5:
s = search[int(h)]
for i in range(len(s)):
#print('sou')
find(str(i)+p,str(s[i])+q)
if h==6:
s = search[int(h)]
for i in range(len(s)):
#print('sou')
find(str(i)+p,str(s[i])+q)
# 0+1 = 1
# 1+0 = 1
# 6+2 5+3 4+4 3+5 2+6


for i in range(7):
find(str(i),str(x1[i]))
print(candidate)
for i in candidate:
q = n//i
phi = (i-1)*(q-1)
d = inverse_mod(e,phi)
m = pow(c,d,n)
print(libnum.n2s(int(m)))
# b'GPNCTF{wOw_faCTORING_wi7H_H1NT5_15_FUn}'

image-20250716093455090

restricted oracle

  • 从这题了解到了CBC块加密模式有一个攻击为padding Oracle攻击,稍微改了一下这题的代码,使得爆破次数没有限制,从而学习了一下padding Oracle攻击。基本版的padding Oracle攻击的学习过程写在了这篇博客中:块加密工作模式 | iyheart的博客
  • 题目附件如下:
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
from Crypto.Cipher import AES
import sys
from hashlib import sha512
import random
import os
import secrets
import string
MAX_TRIES = lambda x: len(x)*40 # 严格限制了爆破的次数
TEXT_FILE = "text.txt"
def getText(n=10)->str:
lines = []
with open(TEXT_FILE, "r") as f:
lines = f.readlines()
#print(len(lines), "lines loaded from text file.")
out = ""
for _ in range(n):
line =secrets.choice(lines)
line =line.split(" ",1)[1]
out += line.strip()
#out = "abc"*100 + "bcd"*100+"A" # 题目给出了密文的形式,应该就是用于减少爆破次数的.
out = "".join(filter(lambda x: x in string.ascii_letters,out))
return out

class PadServer:

def __init__(self,text):
self.queries = [0]
self.key = os.urandom(16)
self.cipher = AES.new(self.key, AES.MODE_CBC)
self.chall = text.encode("utf-8")
for i in range(random.randint(0,4)):
self.chall += chr(ord("A")+(os.urandom(1)[0] % 26)).encode()# 明文 = getText(n) 的输出(仅包含英文字母)+ 0~4 个随机大写字母(来自 'A'-'Z')



self.iv = os.urandom(16)
#print(f"Padding is : {self.pad(self.chall).hex()}")
def cutq(self):
self.queries+=[0]
def get_chall(self):
aes = AES.new(self.key, AES.MODE_CBC, self.iv)
return self.iv+aes.encrypt(self.pad(self.chall))
def pad(self, s):
padbit = 16 - len(s) % 16
padding = bytes([padbit] * padbit)
return s + padding

def unpad(self, s):
padbit = s[-1]
padding = s[-padbit:]
if set(padding) == {padbit}:
return s[:-s[-1]]
else:
return None
def decrypt(self, ciphertext):
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
plaintext = cipher.decrypt(ciphertext)

return plaintext

def oracle(self, ciphertext):
self.queries[-1] += 1
plaintext = self.decrypt(ciphertext)
#print("oracle request decrypts to ", plaintext.hex())
if self.unpad(plaintext) == None:
return False
else:
return True

def getFlag():
with open("/flag", "r") as f:
return f.read().strip()
def xor(a:bytes,b:bytes)->bytes:

ml = max(len(a),len(b))
a = a.ljust(ml, b'\x00')
b = b.ljust(ml, b'\x00')
return bytes(x ^ y for x, y in zip(a, b))

if __name__ == "__main__":
print("Welcome to the Pad Server!")
FLAG =getFlag()
text =getText()
oracle =PadServer(text)
MAX_TRIES = MAX_TRIES(text)
tries = 0
print(xor(sha512(text[:-3].encode("utf-8")).digest(),FLAG.encode()).hex())

print(oracle.get_chall().hex())
while tries < MAX_TRIES:
ciph = bytes.fromhex(input("speak to the oracle: "))
if len(ciph) % 16 != 0:
print("Ciphertext must be a multiple of 16 bytes.")
continue
print("Oracle says: ", oracle.oracle(ciph))
tries+=1


  • 首先需要接收消息,并从消息中分离数vi密文
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
from pwn import *
import string
def my_bytes(block):
print(len(block))
if len(block)==0:
return b''
else:
return bytes.fromhex(block)
def pad(padding):
return bytes([padding] * padding)
context.log_level='debug'
p = remote("newford-of-charged-unity.gpn23.ctf.kitctf.de", "443", ssl=True)
p.recvuntil(b'Welcome to the Pad Server!\n')
c1 = p.recvline()[:-1].decode()
print(c1)
iv_c2 = p.recvline()[:-1].decode()
print(iv_c2)
iv = iv_c2[:32]
c2 = iv_c2[32:]
print('c2=',c2)
print('iv=',iv)
l = len(c2)
ll = l//32
print(l)
print(l//32)
c_list = []
for i in range(ll):
c_list.append(c2[i*32:i*32+32])
print(c_list)
print(my_bytes(c_list[-1]))
  • 之后就是爆破出padding的个数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
padding = 0
for i in range(16):
block1 = my_bytes(c_list[-2])
block2 = my_bytes(c_list[-1])
payload = (block1[:i]+xor(block1[i],b'\x10')+block1[i+1:]+block2).hex().encode()
print(payload)
p.sendlineafter(b'speak to the oracle:',payload)
p.recvuntil(b'Oracle says: ')
rec = p.recvline()[:-1].decode()
print(rec)
if rec=='False':
padding=0x10-i
break
print("padding---->",padding)

image-20250716100001976

  • 尝试爆破最后一块剩余没有被padding填充的部分,会发现爆破出来的,字母目前只有大写字母和小写字母。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 破解尾块的数据
text = ''
for j in range(padding,16,1):
t = j-padding
padd = pad(padding)
padd2 = pad(j + 1)[:-1]
print(padd2)
print(f"------------第{15-j}位爆破--------------")
sleep(0.5)
for i in range(255):
payload = (block1[:15-j]+xor(block1[15-j],i.to_bytes(1,'big'))+xor(xor(block1[16-j:],text.encode()+padd),padd2)+block2).hex().encode()
print(len(payload))
p.sendlineafter(b'speak to the oracle:',payload)
p.recvuntil(b'Oracle says: ')
rec = p.recvline()[:-1].decode()
print(rec)
if rec=='True':
print("find--->",xor(i.to_bytes(1,'big'),j+1))
text = xor(i.to_bytes(1,'big'),j+1).decode() + text
print("text--->",text)
sleep(1)
break
#out = "abc"*100 + "bcd"*100+"A" # 题目给出了密文的形式,应该就是用于减少爆破次数的.

image-20250716095632989

  • 但是由于有限制次数,而题目中又给了key是大小写字母,所以我们可以构造如下列表,用于减少爆破次数
1
2
3
4
5
6
7
8
9
10
11
12
# 建立一个列表,用于爆破使用,爆破只选择对应对应索引与字母的异或值,从而减少爆破次数
latter = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
a = []
for i in range(0,17):
x = []
for j in latter:
print(j)
x.append(i^(ord(j)))
#print(x)
a.append(x)
print(a)

  • 减少次数后就可以在有限次数绕过爆破出key,在爆破的时候还需要考虑一下时间问题,明文非常长,但是靶机只有30分钟的限制。但是在爆破的过程中会发现明文是德国新闻的一些句子。

image-20250716143239889

  • 这就需要对单词进行词频分析,从而减少爆破的时间,并且还发现,每次连接发送过来的块长度不一样,有的块长度到达了60多,有的才30多或者40多,所以我们应该选择40多、30多块的来进行爆破,这样也能节省时间。这样能使得平均每块爆破时间降低到38.07秒

image-20250716144442590

  • 爆破出来text后再去掉最后的大写字母,即可得到结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import hashlib
def xor(a:bytes,b:bytes)->bytes:
ml = max(len(a),len(b))
a = a.ljust(ml, b'\x00')
b = b.ljust(ml, b'\x00')
return bytes(x ^^ y for x, y in zip(a, b))

c1 = 'wiederumhatbereitsweitereEntlassungenbeiWallStreetBankenundanderenFinanzinstitutionenzurFolgeEinwandererausDeutschlandsetzenflschlicherweiseeineLehremitdemgesellschaftlichenAbstieggleichHPIncwirdesdiesenMarktauchweiterhinadressierenerklrtderBritewarumausMcLarenSichtvielfreinMotorenUpdateinSpielbergsprichtkuriertderMinistereinenBeinbruchausundGtzeuernsichnichtBildNchstesBildDiesesBildmachtedasaustralischeMerinoschafChrisweltweitbekanntSeptemberseienalleDetailszuderAbschaltsoftwareoffengelegtwordenvergrernBildinfoausblendenBildWaserwartenSieindieserPhasevonIhrenSpielernWosinddennjetztallehindienochvoreinpaarJahren'
c2 = 'd620ed061b06427d260bda1af508da1517a84b5d93a9c466a65ca608a671bdffd941e829a0c923b69c9187dba50ffcdf81351c7f117503ba78b664b72643e626'
print(bytes.fromhex(c2))
print(xor(hashlib.sha512(c1[:-3].encode("utf-8")).digest(),bytes.fromhex(c2)))
print(len(c1)//16)
b'GPNCTF{nic3_Gu3SS1Ng_p4DdInG_iS_FuN}\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
38

image-20250716161430730

  • exp如下:
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
143
144
145
146
147
148
149
150
151
from pwn import *
import hashlib
from tqdm import tqdm
import threading
import string
# 建立一个列表,用于爆破使用,爆破只选择对应对应索引与字母的异或值,从而减少爆破次数
# 高频次出现的字母放在前面减少爆破次数和时间
latter = 'enisratdhulcgmobwfkzvpjyxqSDMBNAFEKLGHTWRZIUPCJVOYXQ'
a = []
for i in range(0,17):
x = []
for j in latter:
print(j)
x.append(i^(ord(j)))
#print(x)
a.append(x)
print(a)
text = ''
def my_bytes(block):
print(len(block))
if len(block)==0:
return b''
else:
return bytes.fromhex(block)
def pad(padding):
return bytes([padding] * padding)
def attack_other_block(pre_list,lat_list):
global text
block1 = my_bytes(pre_list)
block2 = my_bytes(lat_list)
text1 = ''
for i in a[1]:
payload = block1[:15]
payload += xor(block1[15], i.to_bytes(1, 'big'))
payload += block2
payload = payload.hex().encode() # 转换为16进制字符形式,再转换为字节形式发送
#print(len(payload))
p.sendlineafter(b'speak to the oracle:',payload)
p.recvuntil(b'Oracle says: ')
rec = p.recvline()[:-1].decode()
#print(rec)
if rec == 'True': # 判断是否能得到True的应答
#print("find--->", xor(i.to_bytes(1, 'big'), 1)) # 爆破到明文
text = xor(i.to_bytes(1, 'big'), 1).decode() + text # 这里应该需要修改为异或1
#print("text--->", text)
text1 = xor(i.to_bytes(1, 'big'), 1).decode() + text1
break
for j in range(1, 16, 1):
padd2 = pad(j + 1)[:-1] # 再计算j+1的pad
#print(padd2)
print(f"------------第{15 - j}位爆破--------------")
#print("text1--->", text1)
t = xor(block1[16 - j:], text1.encode()) # 先计算C_i-1[j]^P_n[j], 相比于前面这里去掉了padd
t2 = xor(t, padd2) # 最终计算得到C_i-1[j]^P_n[j]^(L+1)
for i in a[j+1]:
payload = block1[:15 - j] # C_i-1[15-L]前面的数据不要动
payload += xor(block1[15 - j], i.to_bytes(1, 'big')) + t2 # 组合成第C_i-1密文块
payload += block2 # 加上尾块
payload = payload.hex().encode() # 转换为16进制字符形式,再转换为字节形式发送
#print(len(payload))
p.sendlineafter(b'speak to the oracle:', payload)
p.recvuntil(b'Oracle says: ')
rec = p.recvline()[:-1].decode()
#print(rec)
if rec == 'True': # 判断是否能得到True的应答
#print("find--->", xor(i.to_bytes(1, 'big'), j + 1)) # 爆破到明文
text = xor(i.to_bytes(1, 'big'), j + 1).decode() + text # 添加到text中
print("text--->", text)
text1 = xor(i.to_bytes(1, 'big'), j + 1).decode() + text1
break
print('text-->',text)
return text1
#context.log_level='debug'
while True:
p = remote("grandforge-of-face-melting-tschunk.gpn23.ctf.kitctf.de", "443", ssl=True)
p.recvuntil(b'Welcome to the Pad Server!\n')
c1 = p.recvline()[:-1].decode()
print('c1 = ',c1)
iv_c2 = p.recvline()[:-1].decode()
print(iv_c2)
iv = iv_c2[:32]
c2 = iv_c2[32:]
print('c2=',c2)
print('iv=',iv)
l = len(c2)
ll = l//32
print(l)
print(l//32)
c_list = []
for i in range(ll):
c_list.append(c2[i*32:i*32+32])
print(c_list)
print(my_bytes(c_list[-1]))
print('block_length--->',len(c_list))
if len(c_list)<=40: # 选择块总数<=40的消息进行爆破,从而减少时间
break
p.close()
# 确定padding长度

padding = 0
for i in range(16):
block1 = my_bytes(c_list[-2])
block2 = my_bytes(c_list[-1])
payload = (block1[:i]+xor(block1[i],b'\x10')+block1[i+1:]+block2).hex().encode()
#print(payload)
p.sendlineafter(b'speak to the oracle:',payload)
p.recvuntil(b'Oracle says: ')
rec = p.recvline()[:-1].decode()
#print(rec)
if rec=='False':
padding=0x10-i
break
#print("padding---->",padding)

# 破解尾块的数据

for j in range(padding,16,1):
padd = pad(padding) # 先计算原始padd
padd2 = pad(j + 1)[:-1] # 再计算j+1的pad
#print(padd2)
print(f"------------第{15-j}位爆破--------------")
t = xor(block1[16-j:],text.encode()+padd) # 先计算C_i-1[j]^P_n[j]
t2 = xor(t,padd2) # 最终计算得到C_i-1[j]^P_n[j]^(L+1)
for i in a[j+1]:
payload = block1[:15-j] # C_i-1[15-L]前面的数据不要动
payload +=xor(block1[15-j],i.to_bytes(1,'big'))+t2 # 组合成第C_i-1密文块
payload +=block2 #加上尾块
payload = payload.hex().encode() # 转换为16进制字符形式,再转换为字节形式发送
#print(len(payload))
p.sendlineafter(b'speak to the oracle:',payload)
p.recvuntil(b'Oracle says: ')
rec = p.recvline()[:-1].decode()
#print(rec)
if rec=='True': # 判断是否能得到True的应答
#print("find--->",xor(i.to_bytes(1,'big'),j+1)) # 爆破到明文
text = xor(i.to_bytes(1,'big'),j+1).decode() + text # 添加到text中
print("text--->",text)
break




for i in tqdm(range(len(c_list)-2,-1,-1),leave='true'):
print(f'block{i}')
if i>=1:
attack_other_block(c_list[i-1],c_list[i])
elif i==0:
attack_other_block(iv,c_list[0])

print('text:',text)
print('encrypt_flag:',c1)