欢迎来到 黑吧安全网 聚焦网络安全前沿资讯,精华内容,交流技术心得!

kernel pwn(0):入门&ret2usr

来源:本站整理 作者:佚名 时间:2019-03-07 TAG: 我要投稿


 
一、序言
从栈、格式化串,再到堆,当这些基本的攻击面都过下来以后,对于劫持流程拿shell的过程就应该非常熟悉了,然而传统的exploit拿到的shell的权限并非最高的,而kernel pwn的核心目标就是拿到一个具有root权限的shell,其意义就在于提权。
 
二、kernel pwn简介
1.一般背景:内核pwn的背景一般都是Linux内核模块所出现的漏洞的利用。众所周知,Linux的内核被设置成可扩展的,我们可以自己开发内核模块,如各种驱动程序;其后缀常常是ko。由于这些内核模块运行时的权限是root权限,因此我们将有机会借此拿到root权限的shell。
2.题目形式:不同于用户态的pwn,kernel pwn不再是用python远程链接打payload拿shell,而是给你一个环境包,下载后qemu本地起系统,flag文件就在这个虚拟系统里面,权限是root,因此那flag的过程也是选手在自己的环境里完成,exploit往往也是C编写。
3.流程与攻击面:内核pwn的攻击面其实仍然是用户态的那些传统攻击面,各种堆栈幺蛾子等等。流程上就是C程序exp调用内核模块利用其漏洞提权,只是提权后要“着陆”回用户态拿shell。提权代码是commit_creds(prepare_kernel_cred(0))。详解请参考ctf-wiki。
 
三、Linux内核模块的若干知识
1.fop结构体:
内核模块程序的结构中包括一些callback回调表,对应的函数存在一个file_operations(fop)结构体中,这也是对我们pwn手来说最重要的结构体;结构体中实现了的回调函数就会静态初始化上函数地址,而未实现的函数,值为NULL。
例如:
Events
User functions
Kernel functions
Load
insmod
module_init()
Open
fopen
file_operations: open
Close
fread
file_operations: read
Write
fwrite
file_operations: write
Close
fclose
file_operations: release
Remove
rmmod
module_exit()
#include
#include
#include
MODULE_LICENSE("Dual BSD/GPL");
static int hello_init(void)
{
    printk(" Hello world!n");
    return 0;
}
static void hello_exit(void)
{
    printk(" Bye, cruel worldn");
}
module_init(hello_init);
module_exit(hello_exit);
struct file_operations module_fops =
{
    read: module_read,
    write: module_write,
    open: module_open,
    release: module_release
};
其中,module_init/module_exit是在载入/卸载这个驱动时自动运行;而fop结构体实现了如上四个callback,冒号右侧的函数名是由开发者自己起的,在驱动程序载入内核后,其他用户程序程序就可以借助文件方式(后面将提到)像进行系统调用一样调用这些函数实现所需功能。
完整的fop请自行百度。
2.proc_create创建文件
这个涉及Linux文件系统的一些知识,挺恶心的,有兴趣自己去看,笔者就简单咕一下。
很多内核pwn题都会用像proc_create这种函数创建一个文件,qemu起系统后在proc下可以看到对应的文件名;就从应用的角度来说,笔者认为可以这么草率地理解:相当于这个驱动给自个儿创建了一个内核中的映像,映射成了所创建的这个文件,其他用户程序在调用前面我们所说的fop中实现的函数时,就是借助声明这个文件来区分是哪个驱动的函数。
比如说一个驱动在init中执行了proc_create(“core”, 0x1B6LL, 0LL, &core_fops),文件名是“core”,而且在回调中实现了ioctl,那么其他用户程序就可以先fopen这个core获取文件指针fd,然后执行ioctl(fd,,)来进行具体操作,其他的fop中的回调接口函数也类似。
3.数据的通信
众所周知,一个进程的在用户态和内核态是对应了完全不搭边儿的两个栈的,用户栈和内核栈既然相互隔离,在系统调用或者调用驱动、内核模块函数时就不能通过栈传参了,而要通过寄存器,像拷贝这样的操作也要借助具体的函数:copy_to_user/copy_from_user,啥意思不多说自己顾名思义。像打印这种也由prinf变成了printk,具体还有哪些自行百度吧。
 
四、用户态与内核态的切换
众所周知,当发生系统调用、异常或外中断时,会切入内核态,此时会保存现场如各种寄存器什么的,我们关注的是系统调用这种情形。
当发生系统调用时,进入内核态之前,首先通过swapgs指令将gs寄存器值与某个特定位置切换(显然回来的时候也一样)、然后把用户栈顶esp存到独占变量同时也将独占变量里存的内核栈顶给esp(显然回来的时候也一样)、最后push各寄存器值(由上一条知是存在内核栈里了),这样保存现场的工作就完成了。
系统调用执行完了以后就得回用户态,首先swapgs恢复gs,然后执行iretq恢复用户空间,此处需要注意的是:iretq需要给出用户空间的一些信息(CS, eflags/rflags, esp/rsp 等),这些信息在哪的?就是内核栈!想想当时的push啊!
 
五、提权
kernel中有两个内核态函数是用来改变进程权限的:
int commit_creds(struct cred *new)
struct cred prepare_kernel_cred(struct task_struct daemon)
很明显第二个将根据daemon这个权限描述符来返回一个cred结构体(用于记录进程权限),然后commit_creds就会为进程赋上这个结构体,进程就有了对应的权限。提权代码:
commit_creds(prepare_kernel_cred(0))
 
六、漏洞利用
最常见的无非就是利用驱动程序或内核模块的漏洞劫持控制流执行上述提权函数,然后正确的着陆回用户态get root shell;具体的几种常见攻击手段、保护绕过等等,将在此后的文章中,借助具体的ctf pwn题目深入探讨;今天我们先借强网杯2018的core讲一下ret2usr。

[1] [2] [3] [4] [5]  下一页

【声明】:黑吧安全网(http://www.myhack58.com)登载此文出于传递更多信息之目的,并不代表本站赞同其观点和对其真实性负责,仅适于网络安全技术爱好者学习研究使用,学习中请遵循国家相关法律法规。如有问题请联系我们,联系邮箱admin@myhack58.com,我们会在最短的时间内进行处理。
  • 最新更新
    • 相关阅读
      • 本类热门
        • 最近下载