本文为看雪论坛优秀文章
看雪论坛作者ID:PIG-007
网上一大堆教编译内核的,但很多教程看得特别迷糊。第一次编译内核时,没设置好参数,直接把虚拟机编译炸开了。所以就想着能不能先做个一键获取内核源码和相关vmlinux以及bzImage的脚本,先试试题,后期再深入探究编译内核,加入debug符号,所以就有了这个一键脚本。这个直接看我的项目就好了,我是直接拖官方的docker,然后把编译所需要的环境都重新安装了一遍,基本可以适配所有环境,安装各个版本的内核,外加调试信息也可以配置。PIG-007/kernelAll (github.com)(https://github.com/PIG-007/kernelAll)前置环境,前置知识啥的在上面已经足够了,如果还是感觉有点迷糊可以再去搜搜其他教程。看雪的钞sir师傅和csdn上的ha1vk师傅就很不错啊,还有安全客上的ERROR404师傅。钞sir师傅:Ta的论坛 (pediy.com)(https://bbs.pediy.com/user-818602.htm)ha1vk师傅:kernel- CSDN搜索(https://so.csdn.net/so/search?q=kernel&t=blog&u=seaaseesa)error404师傅:Kernel Pwn 学习之路(一) - 安全客,安全资讯平台 (anquanke.com)(https://www.anquanke.com/post/id/201043)这个系列记录新的kernel解析,旨在从源码题目编写,不同内核版本来进行各式各样的出题套路解析和exp的解析。另外内核的pwn基本都是基于某个特定版本的内核来进行模块开发,而出漏洞地方就是这个模块,我们可以借助这个模块来攻破内核,所以我们进行内核pwn的时候,最应该先学习的就是一些简单内核驱动模块的开发。
#include
#include
#include
MODULE_LICENSE("Dual BSD/GPL");
static int __init hello_init(void)
{
printk("PIG007:Hello world!\n");
return 0;
}
static void __exit hello_exit(void)
{
printk("PIG007:Bye,world\n");
}
module_init(hello_init);
module_exit(hello_exit);
1、头文件简介
module.h:包含可装载模块需要的大量符号和函数定义。另外大部分模块还包括moduleparam.h头文件,这样就可以在装载的时候向模块传递参数。而我们常常用的函数_copy_from_user则来自头文件uaccess.h。2、模块许可证
MODULE_LICENSE("Dual BSD/GPL");
这个就是模块许可证,具体有啥用不太清楚,如有大佬恳请告知。可以通过下列命令查询。grep "MODULE_LICENSE" -B 27 /usr/src/linux-headers-`uname -r`/include/linux/module.h
或者网址Linux内核许可规则 — The Linux Kernel documentation(https://www.kernel.org/doc/html/latest/translations/zh_CN/process/license-rules.html)
3、模块加载卸载
加载
一般以 __init标识声明,返回值为0表示加载成功,为负数表示加载失败。用来初始化,定义之类的。static int __init hello_init(void)
卸载
一般以 __exit标识声明,用来释放空间,清除一些东西的。static void __exit hello_exit(void)
▲其实加载和卸载有点类似于面向对象里的构造函数和析构函数。以上是一个最简单的例子,下面讲讲实际题目的编写,实际的题目一般涉及驱动的装载。由于模块装载是在内核启动时完成的(root下也可以设置再insmod装载),所以一般需要安装驱动,通过驱动来启动模块中的代码功能。而驱动类型也一般有两种,一种是字符型设备驱动,一种是globalmem虚拟设备驱动。1、字符型设备驱动
(1)安装套路
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
} __randomize_layout;
struct xxx_dev_t {
struct cdev cdev;
} xxx_dev;
static int __init xxx_init(void)
{
cdev_init(&xxx_dev.cdev, &xxx_fops);
xxx_dev.cdev.owner = THIS_MODULE;
if (xxx_major) {
register_chrdev_region(xxx_dev_no, 1, DEV_NAME);
} else {
alloc_chrdev_region(&xxx_dev_no, 1, DEV_NAME);
}
ret = cdev_add(&xxx_dev.cdev, xxx_dev_no, 1);
}
static void __exit xxx_exit(void)
{
unregister_chrdev_region(xxx_dev_no, 1);
cdev_del(&xxx_dev.cdev);
}
这样简单的驱动就安装完了,安装完了之后,我们想要使用这个驱动的话,还需要进行交互,向驱动设备传递数据,所以上面的xxx_fops,即file_operations这个结构体就起到了这个功能。有的时候安装注册设备驱动需要用到class来创建注册,原因未知:static int __init xxx_init(void)
{
buffer_var=kmalloc(100,GFP_DMA);
printk(KERN_INFO "[i] Module xxx registered");
if (alloc_chrdev_region(&dev_no, 0, 1, "xxx") < 0)
{
return -1;
}
if ((devClass = class_create(THIS_MODULE, "chardrv")) == NULL)
{
unregister_chrdev_region(dev_no, 1);
return -1;
}
if (device_create(devClass, NULL, dev_no, NULL, "xxx") == NULL)
{
printk(KERN_INFO "[i] Module xxx error");
class_destroy(devClass);
unregister_chrdev_region(dev_no, 1);
return -1;
}
cdev_init(&cdev, &xxx_fops);
if (cdev_add(&cdev, dev_no, 1) == -1)
{
device_destroy(devClass, dev_no);
class_destroy(devClass);
unregister_chrdev_region(dev_no, 1);
return -1;
}
printk(KERN_INFO "[i] : \n", MAJOR(dev_no), MINOR(dev_no));
return 0;
}
(2)交互套路
安装完成之后还需要交互,用到file_operations结构体中的成员函数,首先了解下这个结构体。
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iopoll)(struct kiocb *kiocb, bool spin);
int (*iterate) (struct file *, struct dir_context *);
int (*iterate_shared) (struct file *, struct dir_context *);
__poll_t (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
unsigned long mmap_supported_flags;
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);
#endif
ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
loff_t, size_t, unsigned int);
loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,
struct file *file_out, loff_t pos_out,
loff_t len, unsigned int remap_flags);
int (*fadvise)(struct file *, loff_t, loff_t, int);
} __randomize_layout;
ssize_t xxx_read(struct file *filp, char __user *buf, size_t count,
loff_t *f_pos)
{
...
copy_to_user(buf, ..., ...);
...
}
ssize_t xxx_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos)
{
...
copy_from_user(..., buf, ...);
...
}
long xxx_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
...
switch (cmd) {
case XXX_CMD1:
...
break;
case XXX_CMD2:
...
break;
default:
return -ENOTTY;
}
return 0;
}
然后需要file_operations结构体中的函数来重写用户空间的write,open,read等函数:static struct file_operations xxx_fops =
{
.owner = THIS_MODULE,
.write = xxx_write,
.read = xxxx_read
};
这样当用户空间打开该设备,调用该设备的write函数,就能通过.write进入到xxx_write函数中。▲这样一些常规kernel题的编写模板就总结出来了。
(3)具体的题目
原题:https://github.com/black-bunny/LinKern-x86_64-bypass-SMEP-KASLR-kptr_restric
①代码和简单的解析
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static dev_t first;
static struct cdev c_dev;
static struct class *cl;
static char *buffer_var;
static int vuln_open(struct inode *i, struct file *f)
{
printk(KERN_INFO "[i] Module vuln: open()\n");
return 0;
}
static int vuln_close(struct inode *i, struct file *f)
{
printk(KERN_INFO "[i] Module vuln: close()\n");
return 0;
}
static ssize_t vuln_read(struct file *f, char __user *buf, size_t len, loff_t *off)
{
if(strlen(buffer_var)>0) {
printk(KERN_INFO "[i] Module vuln read: %s\n", buffer_var);
kfree(buffer_var);
buffer_var=kmalloc(100,GFP_DMA);
return 0;
} else {
return 1;
}
}
static ssize_t vuln_write(struct file *f, const char __user *buf,size_t len, loff_t *off)
{
char buffer[100]={0};
if (_copy_from_user(buffer, buf, len))
return -EFAULT;
buffer[len-1]='\0';
printk("[i] Module vuln write: %s\n", buffer);
strncpy(buffer_var,buffer,len);
return len;
}
static struct file_operations pugs_fops =
{
.owner = THIS_MODULE,
.open = vuln_open,
.release = vuln_close,
.write = vuln_write,
.read = vuln_read
};
static int __init vuln_init(void)
{
buffer_var=kmalloc(100,GFP_DMA);
printk(KERN_INFO "[i] Module vuln registered");
if (alloc_chrdev_region(&first, 0, 1, "vuln") < 0)
{
return -1;
}
if ((cl = class_create(THIS_MODULE, "chardrv")) == NULL)
{
unregister_chrdev_region(first, 1);
return -1;
}
if (device_create(cl, NULL, first, NULL, "vuln") == NULL)
{
printk(KERN_INFO "[i] Module vuln error");
class_destroy(cl);
unregister_chrdev_region(first, 1);
return -1;
}
cdev_init(&c_dev, &pugs_fops);
if (cdev_add(&c_dev, first, 1) == -1)
{
device_destroy(cl, first);
class_destroy(cl);
unregister_chrdev_region(first, 1);
return -1;
}
printk(KERN_INFO "[i] : \n", MAJOR(first), MINOR(first));
return 0;
}
static void __exit vuln_exit(void)
{
unregister_chrdev_region(first, 3);
printk(KERN_INFO "Module vuln unregistered");
}
module_init(vuln_init);
module_exit(vuln_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("blackndoor");
MODULE_DESCRIPTION("Module vuln overflow");
②内核函数解析
printk
#defineKERN_EMERG "<0>"
#defineKERN_ALERT "<1>"
#defineKERN_CRIT "<2>"
#define KERN_ERR "<3>"
#define KERN_WARNING "<4>"
#define KERN_NOTICE "<5>"
#define KERN_INFO "<6>"
#define KERN_DEBUG "<7>"
kmalloc
static inline void *kmalloc(size_t size, gfp_t flags)
其中flags一般设置为GFP_KERNEL或者GFP_DMA,在堆题中一般就是GFP_KERNEL模式,如下: |– 进程上下文,可以睡眠 GFP_KERNEL
|– 进程上下文,不可以睡眠 GFP_ATOMIC
| |– 中断处理程序 GFP_ATOMIC
| |– 软中断 GFP_ATOMIC
| |– Tasklet GFP_ATOMIC
|– 用于DMA的内存,可以睡眠 GFP_DMA | GFP_KERNEL
|– 用于DMA的内存,不可以睡眠 GFP_DMA |GFP_ATOMICLinux内核空间内存申请函数kmalloc、kzalloc、vmalloc的区别【转】 - sky-heaven - 博客园 (cnblogs.com)(https://www.cnblogs.com/sky-heaven/p/7390370.html)
kfree
copy_from_user
copy_from_user(void *to, const void __user *from, unsigned long n)
copy_to_user
copy_to_user(void __user *to, const void *from, unsigned long n)
注册函数
alloc_chrdev_region(&t_dev, 0, 1, "xxx");
unregister_chrdev_region(t_dev, 1);
xxx_class = class_create(THIS_MODULE, "xxx");
device_create(xxx_class, NULL, devno, NULL, "xxx");
cdev_init(&c_dev, &pugs_fops);
cdev_add(&c_dev, t_dev, 1)
2、globalmem虚拟设备驱动
printk()函数的总结 - 深蓝工作室 - 博客园 (cnblogs.com)https://www.cnblogs.com/king-77024128/articles/2262023.htmlLinux kernel pwn notes(内核漏洞利用学习) - hac425 - 博客园 (cnblogs.com)https://www.cnblogs.com/hac425/p/9416886.htmlLinux设备驱动(二)字符设备驱动 | BruceFan's Blog (pwn4.fun)http://pwn4.fun/2016/10/21/Linux%E8%AE%BE%E5%A4%87%E9%A9%B1%E5%8A%A8%EF%BC%88%E4%BA%8C%EF%BC%89%E5%AD%97%E7%AC%A6%E8%AE%BE%E5%A4%87%E9%A9%B1%E5%8A%A8/看雪ID:PIG-007
https://bbs.pediy.com/user-home-904686.htm
*本文由看雪论坛 PIG-007 原创,转载请注明来自看雪社区