PatriotCTF 2024 Crypto Writeup

Author: dawn1ight

About: PatriotCTF is a beginner-friendly capture-the-flag competition hosted by GMU’s cybersecurity club, MasonCC. All are welcome to participate, including students and security professionals. Challenges will range from beginner to expert, so there should be something for everyone. This is a jeopardy-style CTF, meaning there will be various challenges from the different categories described below.

Challenges:https://pctf.competitivecyber.club/challenges

References:


Bigger is Better

N = 0xa0d9f425fe1246c25b8c3708b9f6d7747dd5b5e7f79719831c5cbe19fb7bab66ed62719b3fc6090120d2cfe1410583190cd650c32a4151550732b0fc97130e5f02aa26cb829600b6ab452b5b11373ec69d4eaae6c392d92da8bcbea85344af9d4699e36fdca075d33f58049fd0a9f6919f3003512a261a00985dc3d9843a822974df30b81732a91ce706c44bde5ff48491a45a5fa8d5d73bba5022af803ab7bd85250e71fc0254fcf078d21eaa5d38724014a85f679e8a7a1aad6ed22602465f90e6dd8ef95df287628832850af7e3628ad09ff90a6dbdf7a0e6d74f508d2a6235d4eae5a828ac95558bbdf72f39af5641dfe3edb0cdaab362805d926106e2af
e = 0x5af5dbe4af4005564908a094e0eabb0a921b7482483a753e2a4d560700cb2b2dc9399b608334e05140f54d90fcbef70cec097e3f75395d0c4799d9ec3e670aca41da0892a7b3d038acb7a518be1ced8d5224354ce39e465450c12be653639a8215afb1ba70b1f8f71fc1a0549853998e2337604fca7edac67dd1e7ddeb897308ebf26ade781710e6a2fe4c533a584566ea42068d0452c1b1ecef00a781b6d31fbab893de0c9e46fce69c71cefad3119e8ceebdab25726a96aaf02a7c4a6a38d2f75f413f89064fef14fbd5762599ca8eb3737122374c5e34a7422ea1b3d7c43a110d3209e1c5e23e4eece9e964da2c447c9e5e1c8a6038dc52d699f9324fd6b9
c = 0x731ceb0ac8f10c8ff82450b61b414c4f7265ccf9f73b8e238cc7265f83c635575a9381aa625044bde7b34ad7cce901fe7512c934b7f6729584d2a77c47e8422c8c0fe2d3dd12aceda8ef904ad5896b971f8b79048e3e2f99f600bf6bac6cad32f922899c00fdc2d21fcf3d0093216bfc5829f02c08ba5e534379cc9118c347763567251c0fe57c92efe0a96c8595bac2c759837211aac914ea3b62aae096ebb8cb384c481b086e660f0c6249c9574289fe91b683609154c066de7a94eafa749c9e92d83a9d473cc88accd9d4c5754ccdbc5aa77ba9a790bc512404a81fc566df42b652a55b9b8ffb189f734d1c007b6cbdb67e14399182016843e27e6d4e5fca

大加密指数e,经典wiener攻击

from RSAwienerHacker.RSAwienerHacker import hack_RSA
from Crypto.Util.number import long_to_bytes
import hashlib
n = 0xa0d9f425fe1246c25b8c3708b9f6d7747dd5b5e7f79719831c5cbe19fb7bab66ed62719b3fc6090120d2cfe1410583190cd650c32a4151550732b0fc97130e5f02aa26cb829600b6ab452b5b11373ec69d4eaae6c392d92da8bcbea85344af9d4699e36fdca075d33f58049fd0a9f6919f3003512a261a00985dc3d9843a822974df30b81732a91ce706c44bde5ff48491a45a5fa8d5d73bba5022af803ab7bd85250e71fc0254fcf078d21eaa5d38724014a85f679e8a7a1aad6ed22602465f90e6dd8ef95df287628832850af7e3628ad09ff90a6dbdf7a0e6d74f508d2a6235d4eae5a828ac95558bbdf72f39af5641dfe3edb0cdaab362805d926106e2af
e = 0x5af5dbe4af4005564908a094e0eabb0a921b7482483a753e2a4d560700cb2b2dc9399b608334e05140f54d90fcbef70cec097e3f75395d0c4799d9ec3e670aca41da0892a7b3d038acb7a518be1ced8d5224354ce39e465450c12be653639a8215afb1ba70b1f8f71fc1a0549853998e2337604fca7edac67dd1e7ddeb897308ebf26ade781710e6a2fe4c533a584566ea42068d0452c1b1ecef00a781b6d31fbab893de0c9e46fce69c71cefad3119e8ceebdab25726a96aaf02a7c4a6a38d2f75f413f89064fef14fbd5762599ca8eb3737122374c5e34a7422ea1b3d7c43a110d3209e1c5e23e4eece9e964da2c447c9e5e1c8a6038dc52d699f9324fd6b9
c = 0x731ceb0ac8f10c8ff82450b61b414c4f7265ccf9f73b8e238cc7265f83c635575a9381aa625044bde7b34ad7cce901fe7512c934b7f6729584d2a77c47e8422c8c0fe2d3dd12aceda8ef904ad5896b971f8b79048e3e2f99f600bf6bac6cad32f922899c00fdc2d21fcf3d0093216bfc5829f02c08ba5e534379cc9118c347763567251c0fe57c92efe0a96c8595bac2c759837211aac914ea3b62aae096ebb8cb384c481b086e660f0c6249c9574289fe91b683609154c066de7a94eafa749c9e92d83a9d473cc88accd9d4c5754ccdbc5aa77ba9a790bc512404a81fc566df42b652a55b9b8ffb189f734d1c007b6cbdb67e14399182016843e27e6d4e5fca
d = hack_RSA(e,n)
m = pow(c,d,n)
print(long_to_bytes(m))

idk cipher


import base64
"""
********************************************
* *
* *
********************************************
"""
# WARNING: This is a secret key. Do not expose it.
srt_key = 'secretkey' # // TODO: change the placeholder
usr_input = input("\t:"*10)
if len(usr_input) <= 1:
raise ValueError("PT must be greater than 1")
if len(usr_input) % 2 != 0:
raise ValueError("PT can only be an even number")
if not usr_input.isalnum():
raise ValueError("Only alphabets and numbers supported")
# WARNING: Reversing input might expose sensitive information.
rsv_input = usr_input[::-1]
output_arr = []
for i in range(int(len(usr_input) / 2)):
c1 = ord(usr_input[i])
c2 = ord(rsv_input[i])
enc_p1 = chr(c1 ^ ord(srt_key[i % len(srt_key)]))
enc_p2 = chr(c2 ^ ord(srt_key[i % len(srt_key)]))
output_arr.append(enc_p1)
output_arr.append(enc_p2)
# WARNING: Encoded text should not be decoded without proper authorization.
encoded_val = ''.join(output_arr)
b64_enc_val = base64.b64encode(encoded_val.encode())
R = "R"*20
E = "E"*5
EXCLAMATION = "!"*5
print(f"ULTRA SUPE{R} SECUR{E} Encoded Cipher Text{EXCLAMATION}:", b64_enc_val.decode())

字符串加密,对加密脚本逆向可得解密脚本

import base64

srt_key = 'secretkey'

# 假设这是从上面的加密脚本中得到的Base64编码的字符串
b64_enc_val = "QRVWUFdWEUpdXEVGCF8DVEoYEEIBBlEAE0dQAURFD1I="

# 解码Base64字符串
encoded_val = base64.b64decode(b64_enc_val).decode()

# 解密过程
decoded_arr1 = []
decoded_arr2 = []
for i in range(0, len(encoded_val), 2):
c1 = ord(encoded_val[i])
c2 = ord(encoded_val[i + 1])

# 使用相同的密钥对字符进行异或操作
dec_p1 = chr(c1 ^ ord(srt_key[i // 2 % len(srt_key)]))
dec_p2 = chr(c2 ^ ord(srt_key[i // 2 % len(srt_key)]))

# 添加解密后的字符到数组中
decoded_arr1.append(dec_p1)
decoded_arr2.append(dec_p2)

first_half = decoded_arr1
second_half = decoded_arr2[::-1] # 反转第二部分

# 合并两部分得到最终解密结果
usr_input = ''.join(first_half + second_half)

print("Decrypted Input:", usr_input)

High Roller

#! /usr/bin/python3.10
from Crypto.Util.number import *
from Crypto.PublicKey import RSA
import random
import time

random.seed(int(time.time()))
p, q = getPrime(512, random.randbytes), getPrime(512, random.randbytes)
n = p*q
e = getPrime(512)
phi = (p-1)*(q-1)

assert GCD(e, phi) == 1
d = pow(e, -1, phi)

key = RSA.construct((n, e, d, p, q))
with open("public_key.pem", "wb") as f:
f.write(key.publickey().export_key("PEM"))

with open("private_key.pem", "wb") as f:
f.write(key.export_key("PEM"))
-----BEGIN PUBLIC KEY-----
MIHdMA0GCSqGSIb3DQEBAQUAA4HLADCBxwKBgQCMdauT2revYJrutp7eqQfrMkse
TqfgRdLlMddaVRxiG04qJneVtpzkeLQTZqniJWx5YsUwMDeISeQjmVkr2a+Ob9S8
+xsqVQ0XTW3xPjwKaZhW8jXAlX13ClhAxk1FvPbl6ASsPGUMX6gRSXArRYFx3Kev
C9xng/ZKEhsC5FzBBwJBALKsZCm9FGHXvyJChFDt7vDZUCyU1jbOgS9EhNz+HrrU
K9OCgOoZGfcjIHAcrM+w4AdF48NQELqttmKlcko6ock=
-----END PUBLIC KEY-----

openssl读取公钥

openssl rsa -pubin -in public.pem

e = 9357885447383373532894895505085381556066479232870333782284357317530689434635519527644215046975239651802146048650000941858355721661518511867620441456288201
n = 98634120039089098694716541094010585689286433311349526663366541706397717606400616707193452046001090589663396231287196347120718771479123852243352063594083947086372781079441835003204477521976780690108126553882967223715412003897334440698277808850595475155858935951484345749354296015842014107721137899755292901639

提取出了n,e,但分解不出来

注意到random.seed(int(time.time()))

The seed was used is int(time.time()) and converted into .pem file so we can use command to find time

给了公钥和p,q的生成方法,是用时间作种子,求利用random来求的。根据题目文件的时间向前可以爆破出来。

stat -c '%n %y' public_key.pem

看这里复习一下stat命令 https://blog.csdn.net/u012294618/article/details/72630092

from Crypto.Util.number import *
from Crypto.PublicKey import RSA
import random
import time

pubkey = RSA.import_key(open("public_key.pem", "r").read())
c = bytes_to_long(open("flag.enc", "rb").read())
time_i = int(time.mktime((2024, 9, 22, 9, 9, 18, 0, 0, 0)))

for i in range(time_i, 1, -1):
random.seed(i)
p, q = getPrime(512, random.randbytes), getPrime(512, random.randbytes)
if p*q == pubkey.n:
print(i)
break

d = pow(pubkey.e, -1, (p-1)*(q-1))
print(long_to_bytes(pow(c, d, pubkey.n)))

Textbook Schnorr right??

from sage.all import *
import secrets
import hashlib
import sys
import signal
import re

class EllipticCurveCrypto:
"""
Elliptic Curve Cryptography using the secp256k1 curve.
"""

def __init__(self):
self.p = [<REDACTED>]
self.q = [<REDACTED>]
self.K = GF(self.p)
self.a = self.K([<REDACTED>])
self.b = self.K([<REDACTED>])
self.curve = EllipticCurve(self.K, [self.a, self.b])
self.generator = self.curve(
[<REDACTED>],
[<REDACTED>],
)
# Generate private and public keys
self.private_key = secrets.randbelow(self.q)
self.public_key = self.private_key * self.generator
print(f"Public Key: {self.public_key}")

@staticmethod
def bytes_to_int(b):
return int.from_bytes(b, byteorder='big')

@staticmethod
def int_to_bytes(i):
return i.to_bytes((i.bit_length() + 7) // 8, byteorder='big')

def compute_hash(self, target):
hash_int = int(hashlib.sha256(str(target).encode()).hexdigest(), 16)
return hash_int % self.q

def sign(self, message):
r = secrets.randbelow(self.q)
R = r * self.generator
R_int = int(R.xy()[0] + R.xy()[1])
h = self.compute_hash(R_int | message)
s = (r + self.private_key * h) % self.q
return s, R

def verify(self, message, signature):
s, R = signature
R_int = int(R.xy()[0] + R.xy()[1])
h = self.compute_hash(R_int | message)
left_side = s * self.generator
right_side = R + h * self.public_key
return left_side == right_side

class TimeoutInput:

class TimeoutError(Exception):
"""Custom exception for input timeout."""
pass

@staticmethod
def timeout_handler(signum, frame):
raise TimeoutInput.TimeoutError("Input timed out!")

@staticmethod
def get_input(prompt, timeout=10):
"""
Get user input with a timeout.
:param prompt: The prompt to display to the user.
:param timeout: The time limit for input in seconds.
:return: The user's input as a string.
"""
# Set the signal for a timeout
signal.signal(signal.SIGALRM, TimeoutInput.timeout_handler)
signal.alarm(timeout)
try:
user_input = input(prompt)
signal.alarm(0) # Cancel the timer
return user_input
except TimeoutInput.TimeoutError:
print("No input received within the time limit.")
sys.exit(0)

def display_flag():
"""Read and display the flag from the 'flag.txt' file."""
try:
with open('flag.txt', 'r') as f:
flag = f.read().strip()
print(f"Congratulations! Your flag is: {flag}")
except FileNotFoundError:
print("Flag file not found.")

def verify_message(separator, words, signature, ecc_instance):
"""
Verify the provided signature for the message constructed from words and separator.
"""
message_bytes = separator.join(word.encode() for word in words) + separator
message_int = int.from_bytes(message_bytes, byteorder='big')
if ecc_instance.verify(message_int, signature):
display_flag()
sys.exit(0)
else:
print("Verification failed.")
sys.exit(0)


def main():
# Initialize the elliptic curve cryptography instance
ecc = EllipticCurveCrypto()

# Test the signing and verification process
test_message = ecc.bytes_to_int("test".encode())
s_test, R_test = ecc.sign(test_message)
assert ecc.verify(test_message, (s_test, R_test))

# Regular expression patterns for input validation
separator_pattern = re.compile(r'^[0-9a-fA-F]{2}$')
word_pattern = re.compile(r'^[0-9a-fA-F]{6}$')

# Get separator input
separator_hex = TimeoutInput.get_input("Enter separator as a hex value (2 digits): ")
if not separator_pattern.match(separator_hex):
print("Invalid separator format.")
sys.exit(0)
separator = bytes.fromhex(separator_hex)

# Get words input
words_hex = []
for i in range(8):
prompt = f"Enter 3-letter word {i + 1} as a hex value (6 digits): "
word_hex = TimeoutInput.get_input(prompt)
if not word_pattern.match(word_hex):
print("Invalid word format.")
sys.exit(0)
words_hex.append(word_hex)

try:
words = [bytes.fromhex(word_hex).decode('ascii') for word_hex in words_hex]
except ValueError:
print("Invalid ASCII encoding in words.")
sys.exit(0)

if any(len(word) != 3 for word in words):
print("Words must be 3-letter ASCII words.")
sys.exit(0)

# Get signature components
try:
hex_x_R = input("Enter the x-coordinate of signature R (in hex): ")
x_R = int(hex_x_R, 16)
hex_y_R = input("Enter the y-coordinate of signature R (in hex): ")
y_R = int(hex_y_R, 16)
hex_signature_s = input("Enter signature s (in hex): ")
signature_s = int(hex_signature_s, 16)
except ValueError:
print("Invalid input for signature components.")
sys.exit(0)

if not (0 < signature_s < ecc.q):
print("Illegal value of s.")
sys.exit(0)

# Construct the point R
try:
R = ecc.curve(x_R, y_R)
except ValueError:
print("Point R does not lie on the curve.")
sys.exit(0)

# Verify the message and signature
verify_message(separator, words, (signature_s, R), ecc)

if __name__ == "__main__":
main()

这个题用了schnorr-digital-signature数字签名算法

它是将经典的schnorr数字签名算法移到secp256k1椭圆曲线上

# 经典Schnorr签名算法
import hashlib
import random

# 选择一个大素数 p 和它的大素因子 q
p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000001 # 一个大素数
q = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 # 大素因子

# 选择一个原根 g
g = 2 # 通常选择 2 作为原根

# 生成签名者的私钥 a 和公钥 A
a = random.randint(1, q - 1) # 私钥 a
A = pow(g, a, p) # 公钥 A

# 哈希函数 H
def hash_message(message, x):
combined = f"{message}{x}".encode()
h = hashlib.sha256(combined).hexdigest()
return int(h, 16) % q

# 签名函数
def sign(message, a, g, p, q):
r = random.randint(1, q - 1) # 随机数 r
X = pow(g, r, p) # 计算 X
e = hash_message(message, X) # 计算 e
s = (r + a * e) % q # 计算 s
return (e, s)

# 验证函数
def verify(message, signature, A, g, p, q):
e, s = signature
X_prime = (pow(A, e, p) * pow(g, s, p)) % p # 计算 X'
e_prime = hash_message(message, X_prime) # 计算 e'
return e_prime == e

# 示例消息
message = "Hello, Schnorr!"

# 生成签名
signature = sign(message, a, g, p, q)

# 验证签名
is_valid = verify(message, signature, A, g, p, q)
print(f"Signature is {'valid' if is_valid else 'invalid'}")
# 椭圆曲线上的Schnorr签名
from Crypto.Random import get_random_bytes
from Crypto.Hash import SHA256
from sage.all import *

# 定义 secp256k1 椭圆曲线参数
p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
q = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
curve = EllipticCurve(GF(p), [0, 7])
G = curve(0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8)

# 生成私钥
private_key = Integer(get_random_bytes(32).hex(), base=16) % q

# 计算公钥
public_key = private_key * G

def hash_message(message, x):
combined = f"{message}{x}".encode()
h = SHA256.new(combined).hexdigest()
return int(h, 16) % q

def sign(message, private_key, G, q):
# 选择随机数 r
r = Integer(get_random_bytes(32).hex(), base=16) % q

# 计算点 R
R = r * G

# 计算挑战值 e
e = hash_message(message, R.xy()[0])

# 计算签名分量 s
s = (r + e * private_key) % q

return (R.xy()[0], s)

def verify(message, signature, public_key, G, q):
x_R, s = signature

# 计算挑战值 e
e = hash_message(message, x_R)

# 计算验证点 U
U = s * G - e * public_key

# 检查验证点的 x 坐标是否等于签名中的 x_R
return int(U.xy()[0]) == x_R

# 示例消息
message = "Hello, Schnorr!"

# 生成签名
signature = sign(message, private_key, G, q)

# 验证签名
is_valid = verify(message, signature, public_key, G, q)
print(f"Signature is {'valid' if is_valid else 'invalid'}")

可以看出题目给出的Schnorr签名算法问题出在compute_hash函数上

正常的算法中e = H(M||X) where H() is the hash function

而题目给出的是e = H(M|X)

所以当输入为全1时得到的hash为固定值

separator = "FF"
payload = "7F7F7F"
io.sendlineafter(b"Enter separator as a hex value (2 digits): ", separator.encode().strip())
io.sendlineafter("Enter 3-letter word 1 as a hex value (6 digits): ",payload.encode().strip())
io.sendlineafter("Enter 3-letter word 2 as a hex value (6 digits): ",payload.encode().strip())
io.sendlineafter("Enter 3-letter word 3 as a hex value (6 digits): ",payload.encode().strip())
io.sendlineafter("Enter 3-letter word 4 as a hex value (6 digits): ",payload.encode().strip())
io.sendlineafter("Enter 3-letter word 5 as a hex value (6 digits): ",payload.encode().strip())
io.sendlineafter("Enter 3-letter word 6 as a hex value (6 digits): ",payload.encode().strip())
io.sendlineafter("Enter 3-letter word 7 as a hex value (6 digits): ",payload.encode().strip())
io.sendlineafter("Enter 3-letter word 8 as a hex value (6 digits): ",payload.encode().strip())

TARGET = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
MESSAGE_OR_MASK = int("7F7F7FFF" * 8, 16)

def compute_hash(target):
hash_int = int(hashlib.sha256(str(target).encode()).hexdigest(), 16)
return hash_int % n

def verify_sig(s, R_test):
R_test_str = str(R_test).strip("()")
R_test_parts = [part.strip() for part in R_test_str.split(":")]
xR = R_test_parts[0]
yR = R_test_parts[1]
sendxR = hex(xR)[2:]
sendyR = hex(yR)[2:]
send_s = hex(s)[2:]
print(f"{sendxR = }")
print(f"{sendyR = }")
print(f"{send_s = }")

io.sendlineafter(b"Enter the x-coordinate of signature R (in hex):", sendxR)
io.sendlineafter(b"Enter the y-coordinate of signature R (in hex):", sendyR)
io.sendlineafter(b"EEnter signature s (in hex): ", send_s)
io.interactive()

from tqdm import*
def bruforce_signature(P, G):
print("Brute-forcing to find a valid signature...")
h = compute_hash(TARGET)
hP = h*P
s = secrets.randbelow(n)
sG = s*G
with tqdm(total=0, unit=' iterations', unit_scale=True) as pbar:

while True:
R_test = sG - hP
R_test_binary = int(R_test.xy()[0] + R_test.xy()[1])
if (R_test_binary | MESSAGE_OR_MASK) == TARGET:
assert s*G - h*P == R_test
print("found")
verify_sig(s, R_test)
break
s = (s*2) %n
sG = 2*sG
pbar.update(int(1))
bruforce_signature(P, G)

Hard to Implement

#!/usr/bin/python3

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from Crypto.Random import get_random_bytes
from external import *
import socketserver, signal

listen = 1337
attempts = 1500
flag = getflag()

def encrypt(key,plaintext):
cipher = AES.new(key, AES.MODE_ECB)
pt = pad(plaintext + flag.encode(), 16)
return cipher.encrypt(pt).hex()

def serve(req):
key = get_random_bytes(16)
tries = 0
req.sendall(b"Thank you for using our secure communications channel.\nThis channel uses top-shelf military-grade encryption.\nIf you are not the intended recepient, you can't read our secret.")
while tries < attempts:
req.sendall(b'\n('+str.encode(str(tries))+b'/'+str.encode(str(attempts))+b') ')
req.sendall(b'Send challenge > ')
try:
ct = encrypt(key, req.recv(4096).strip(b'\n'))
req.sendall(b"Response > " + ct.encode() + b'\n')
except Exception as e:
req.sendall(b"An error occured!\n")
tries += 1
req.sendall(b'\nMax attempts exceeded, have a good day.\n')

class incoming(socketserver.BaseRequestHandler):
def handle(self):
signal.alarm(1500)
req = self.request
serve(req)

def main():
socketserver.TCPServer.allow_reuse_address = True
server = ReusableTCPServer(("0.0.0.0", listen), incoming)
server.serve_forever()

if __name__ == "__main__":
main()

AES ECB的padding oracle: 块密码 | Lazzaro (lazzzaro.github.io)

1.由于是ECB的模式,所以当我们输入十五个’0’后,服务会将十五个’0’+flag加密,而此时第一组就是十五个’0’和flag的第一个字符。即,返回的明文的第一组是’0’*15 + flag[0]的密文。

2.我们遍历0-255,发送’0’*15+chr(i),看返回的密文是不是和最初获得的密文的第一组一致,如果一致,那么此时的chr(i)就是flag的第一位。

3.有了第一位我们就可以发送’0’*14+flag[0]过去,此时返回的第一组密文就是’0’*14+flag[0]+flag[1]的密文了,我们继续用第2步的方法就可以恢复flag[1]了。

4.如此循环往复,逐位爆破flag。

exp: [Patriot CTF 2024]-CSDN博客

from pwn import *
from base64 import *
import string

def oracle(data):
r.sendlineafter(b'Send challenge > ', data)
r.recvuntil(b"Response > ")
s=r.recvline().strip().decode()
return bytes.fromhex(s)

r=remote('chal.competitivecyber.club', 6001)
flag=b''

for i in range(32):
padding=b'A'*(32-1-i)
test=oracle(padding) #尾部漏出flag 1个字符
for c in string.printable:
if oracle(padding+flag+c.encode())[:32]==test[:32]: #pad+已知的flag+猜
flag=flag+c.encode()
print(flag, i)
break