[漏洞分析] CVE-2023-0179内核提权详细分析

渗透技巧 1年前 (2023) admin
1,099 0 0

‍漏洞简介

漏洞编号: CVE-2023-0179

漏洞产品: linux kernel – netfilter

利用条件: CAP_NET_ADMIN

利用效果: 本地提权

环境搭建

ubuntu 22.04 + linux-6.1.6

下载编译源码,然后使用下面的 config 编译内核.

https://github.com/TurtleARM/CVE-2023-0179-PoC/blob/master/config

编译命令

make
make modules_install
make install
update-grub

编译完后编辑虚拟机的 .vmx 文件,让其启动时开启 debugStub.

debugStub.listen.guest64 = "TRUE"
debugStub.port.guest64 = "42040"
debugStub.listen.guest64.remote = "TRUE"

然后在调试机上 gdb 连接即可调试内核

target remote host-ip:42040

漏洞分析

漏洞根因是长度计算有误,导致整数溢出,进而导致内存溢出和越界访问。

漏洞函数为 nft_payload_copy_vlan,代码如下:

#define VLAN_HLEN    4
#define VLAN_ETH_HLEN    18
static bool nft_payload_copy_vlan(u32 *d, const struct sk_buff *skb, u8
offset, u8 len)

{
    int mac_off = skb_mac_header(skb) - skb->data;
    u8 *vlanh, *dst_u8 = (u8 *) d;
    struct vlan_ethhdr veth;
    u8 vlan_hlen = 0;

    if ((skb->protocol == htons(ETH_P_8021AD) ||       <===== [0] 数据包类型检查
         skb->protocol == htons(ETH_P_8021Q)) &&
        offset >= VLAN_ETH_HLEN && offset < VLAN_ETH_HLEN + VLAN_HLEN)
        vlan_hlen += VLAN_HLEN;

    vlanh = (u8 *) &veth;

    if (offset < VLAN_ETH_HLEN + vlan_hlen) {
        u8 ethlen = len;

        if (vlan_hlen &&
            skb_copy_bits(skb, mac_off, &veth, VLAN_ETH_HLEN) < 0)
            return false;
        else if (!nft_payload_rebuild_vlan_hdr(skb, mac_off, &veth))
            return false;

        if (offset + len > VLAN_ETH_HLEN + vlan_hlen) <===== [1]
            ethlen -= offset + len - VLAN_ETH_HLEN + vlan_hlen;   <===== [2] 计算 ethlen

        memcpy(dst_u8, vlanh + offset - vlan_hlen, ethlen);     <===== [3] 从 veth 栈地址拷贝 ethlen 数据到 dst_u8

nft_payload_copy_vlan 的 offset 和 len 由用户态控制:

static int nft_payload_init(const struct nft_ctx *ctx,
                const struct nft_expr *expr,
                const struct nlattr * const tb[])

{
    struct nft_payload *priv = nft_expr_priv(expr);

    // 从 netlink 消息里面提取 base, offset, len, dreg
    priv->base   = ntohl(nla_get_be32(tb[NFTA_PAYLOAD_BASE]));
    priv->offset = ntohl(nla_get_be32(tb[NFTA_PAYLOAD_OFFSET]));
    priv->len    = ntohl(nla_get_be32(tb[NFTA_PAYLOAD_LEN]));

    return nft_parse_register_store(ctx, tb[NFTA_PAYLOAD_DREG],
                    &priv->dreg, NULL, NFT_DATA_VALUE,
                    priv->len);
}

nft_payload_copy_vlan 函数中 [2] 的运算有误,会导致 ethlen 整数溢出,变成负数,然后在 [3] 处内存拷贝的时候就会溢出或者越界读。

比如当 offset = 19 ,len = 4 时,经过运算后 ethlen 为 -5 , 由于 ethlen 的类型为 u8,所以 ethlen 的值为 251.

之后当执行 [3] 处代码时,会从 vlanh 拷贝 251 字节的数据到 dst_u8 中,由于 dst_u8 和 vlanh 的大小都小于 251 字节,因此会导致溢出和越界读

memcpy 内存拷贝的参数如下:

  1. vlanh 是当前函数局部变量 struct vlan_ethhdr veth 的地址, veth 的大小为18字节,因此整数溢出后会越界拷贝栈里面的其他数据(包括返回地址、栈中的其他局部变量等)。

  2. dst_u8 在 nft_payload_eval 函数传入 &regs->data[priv->dreg],实际是 nft_do_chain 里面的 regs 结构体的地址,结构体大小为 80 字节,位于栈上

nft_do_chain 函数的局部变量布局如下:

unsigned int
nft_do_chain(struct nft_pktinfo *pkt, void *priv)
{
    const struct nft_chain *chain = priv, *basechain = chain;
    const struct nft_rule_dp *rule, *last_rule;
    const struct net *net = nft_net(pkt);
    const struct nft_expr *expr, *last;
    struct nft_regs regs = {};
    unsigned int stackptr = 0;
    struct nft_jumpstack jumpstack[NFT_JUMP_STACK_SIZE];

struct nft_regs 的结构体类型定义如下,由 20 个 u32 组成,大小为 80 字节.

#define NFT_REG32_NUM        20
struct nft_regs {
    union {
        u32          data[NFT_REG32_NUM];
        struct nft_verdict  verdict;
    };
};

‍紧挨着 regs 的是 jumpstack 数组, struct nft_jumpstack 的结构体定义如下:

gef➤  ptype struct nft_jumpstack
type = struct nft_jumpstack {

    const struct nft_chain *chain;
    const struct nft_rule_dp *rule;
    const struct nft_rule_dp *last_rule;
}

因此控制 priv->dreg 的值,可以控制溢出到 jumpstack 数组的字节数,由于数组中存在指针,通过劫持这些指针,伪造对象,最后可以实现ROP。

漏洞利用

信息泄露

用户态可以下发 expression 对 struct nft_regs 结构体进行读写,通过控制 priv->dreg 让 dst_u8 指向 regs->data 的开头,然后触发漏洞,就能越界将 251 字节的栈数据拷贝到 regs 结构体里面,然后再利用 expression 把数据读出来,就可以进行信息泄露。

具体做法:

  1. 控制 priv->dreg 为 NFT_REG32_00 即 nft_regs 的开头,触发漏洞,内核会越界把 251 字节的栈数据拷贝到 nft_regs 里面。

  2. 然后利用 dynset 把寄存器里面的数据加载到 set里面.

  3. 然后通过 nft list 命令导出数据到用户态.

  4. 最终泄露出内核基地址和 regs 的地址 (栈地址).

[漏洞分析] CVE-2023-0179内核提权详细分析

ROP

通过设置 nft_payload 的 dreg 字段,让其指向 nft_regs 的最后一项,这样触发溢出时就可以修改挨在它后面的 jumpstack 数组。

228  nft_do_chain(struct nft_pktinfo *pkt, void *priv)
229  {
230      const struct nft_chain *chain = priv, *basechain = chain;
231      const struct nft_rule_dp *rule, *last_rule;
232      const struct net *net = nft_net(pkt);
233      const struct nft_expr *expr, *last;
234      struct nft_regs regs = {};
235      unsigned int stackptr = 0;
236      struct nft_jumpstack jumpstack[NFT_JUMP_STACK_SIZE];  // 溢出目标
237      bool genbit = READ_ONCE(net->nft.gencursor);
238      struct nft_rule_blob *blob;
239      struct nft_traceinfo info;

在 nft_payload_copy_vlan 函数的 memcpy 处下断点,触发漏洞时可以发现 memcpy 源数据 (src ~ src + size)包括如下内容:

  • nft_payload_copy_vlan 函数的 veth 结构体以及其后面的内容.

  • nft_do_chain 函数的 nft_regs 结构体 jumpstack 的前 0x10 字节

调试时的上下文:

────── registers ────
$rax   : 0xfffffffb    
$rbx   : 0xffffc90000003ae0
$rcx   : 0x0000000000001e  →  0x0000000000001e
$rdx   : 0x000000000000fb  →  0x000000000000fb
$rsp   : 0xffffc90000003a00
$rbp   : 0xffffc90000003a70
$rsi   : 0xffffc90000003a45
$rdi   : 0xffffc90000003af8
$rip   : 0xffffffff81c47799  →  <nft_payload_eval+857> rep movs QWORD PTR es:[rdi], QWORD PTR ds:[rsi]
$r8    : 0x00000000000013  →  0x00000000000013
$r9    : 0x00000000000004  →  0x00000000000004
$r10   : 0x00000000000017  →  0x00000000000017
$r11   : 0x00000000000004  →  0x00000000000004
$r12   : 0x00000000000004  →  0x00000000000004
$r13   : 0xffff888103714600
$r14   : 0xffffc90000003af0
$r15   : 0xfffffffffffffff2
$eflags: [zero carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x10 $ss: 0x18 $ds: 0x00 $es: 0x00 $fs: 0x00 $gs: 0x00 
───────────── code:x86:64 ────
   0xffffffff81c47791 <nft_payload_eval+849> sub    rsi, rcx
   0xffffffff81c47794 <nft_payload_eval+852> add    ecx, edx
   0xffffffff81c47796 <nft_payload_eval+854> shr    ecx, 0x3
●→ 0xffffffff81c47799 <nft_payload_eval+857> rep    movs QWORD PTR es:[rdi], QWORD PTR ds:[rsi]
───────────── source:net/netfilter/n[...].c+67 ────
     64            if (offset + len > VLAN_ETH_HLEN + vlan_hlen)
     65                ethlen -= offset + len - VLAN_ETH_HLEN + vlan_hlen;
     66     
 →   67            memcpy(dst_u8, vlanh + offset - vlan_hlen, ethlen);
───────────── trace ────
[#00xffffffff81c47799 → nft_payload_copy_vlan(len=<optimized out>, offset=<optimized out>, skb=0xffff888103714600, d=0xffffc90000003af0)
[#10xffffffff81c47799 → nft_payload_eval(expr=<optimized out>, regs=0xffffc90000003ae0, pkt=<optimized out>)
[#20xffffffff81c2c6e1 → expr_call_ops_eval(pkt=0xffffc90000003d10, regs=0xffffc90000003ae0, expr=0xffff8881062541f0)
[#30xffffffff81c2c6e1 → nft_do_chain(pkt=0xffffc90000003d10, priv=0xffff88816b53b950)
[#40xffffffff81c43254 → nft_do_chain_netdev(priv=0xffff88816b53b950, skb=<optimized out>, 

相关的变量的地址如下:

  • memcpy 拷贝的数据源(src + size): [0xffffc90000003a45, 0xffffc90000003b40]

  • nft_do_chain 函数的 regs0xffffc90000003ae0

  • nft_do_chain 函数的 jumpstack 起始地址 0xffffc90000003b30 ,结束地址 0xffffc90000003cb0 .

设置 dreg 为 NFT_REG32_15 ,触发漏洞时 memcpy 的源地址范围为 [nft_payload_copy_vlan 的 vlan_ethhdr, nft_do_chain 的 jumpstack ] ,即图中的 [0x3a45, 0x3b40].

[漏洞分析] CVE-2023-0179内核提权详细分析

溢出的目的地址范围包括 &regs.data[19] ~ &jumpstack[10]

[漏洞分析] CVE-2023-0179内核提权详细分析

因此我们只能溢出 jumpstack,且溢出的数据中前面一部分字节的数据不可控,struct nft_jumpstack 的结构体定义如下。

gef➤  ptype /o struct nft_jumpstack
/* offset      |    size */  type = struct nft_jumpstack {

/*      0      |       8 */    const struct nft_chain *chain;
/*      8      |       8 */    const struct nft_rule_dp *rule;
/*     16      |       8 */    const struct nft_rule_dp *last_rule;

                               /* total size (bytes):   24 */
                             }

ntf_rule_dp 的 data字段中保存的是 struct nft_expr 数组

#define nft_rule_expr_first(rule)    (struct nft_expr *)&rule->data[0]
gef➤  ptype /o struct nft_rule_dp 
/* offset      |    size */  type = struct nft_rule_dp {

/*      0: 0   |       8 */    u64 is_last : 1;
/*      0: 1   |       8 */    u64 dlen : 12;
/*      1: 5   |       8 */    u64 handle : 42;
/* XXX  1-bit hole       */
/* XXX  1-byte hole      */
/*      8      |       0 */    unsigned char data[];

                               /* total size (bytes):    8 */
                             }

struct nft_expr 里面有函数指针表,由于通过泄露获取了 regs 的地址,且 regs 中的内容可以通过 netfilter 的其他表达式设置。

因此我们可以在 regs 伪造 rule, expr, 以及 expr->ops->eval 函数指针,然后触发函数指针调用执行 rop

[漏洞分析] CVE-2023-0179内核提权详细分析

溢出 jumpstack 前

gef➤  p 0xffffc90000003ae0+4*20
$34 = 0xffffc90000003b30
gef➤  x/40xg 0xffffc90000003b30 # jumpstack
0xffffc90000003b30:    0xffff8881a16fadfa  0xffff8881002459f0
0xffffc90000003b40:    0xffff8881002459f0  0xffff888171c97480
0xffffc90000003b50:    0xffff8881002458d0  0xffff8881002458d0
0xffffc90000003b60:    0xffff888171c97c80  0xffff888100245150
0xffffc90000003b70:    0xffff888100245150  0xffff888171c97900
0xffffc90000003b80:    0xffff888100245390  0xffff888100245390
0xffffc90000003b90:    0xffff888171c97800  0xffff888100245e70
0xffffc90000003ba0:    0xffff888100245e70  0xffff888171c97280
0xffffc90000003bb0:    0xffff888100245630  0xffff888100245630
0xffffc90000003bc0:    0xffff888171c97680  0xffff888100245ab0
0xffffc90000003bd0:    0xffff888100245ab0  0xffff888171c97b80
0xffffc90000003be0:    0xffff888100245330  0xffff888100245330
0xffffc90000003bf0:    0xffff888100b2ba80  0xffff888100b2b360
0xffffc90000003c00:    0xc88d406900000080  0xffff888100b2ba80
0xffffc90000003c10:    0x00000000000323c0  0xfa00000000000000
0xffffc90000003c20:    0x00ffff8881a16fad  0x00000000ffff7661
0xffffc90000003c30:    0x0000000000000000  0xffffc90000003c48
0xffffc90000003c40:    0xffffffff81ef1eab  0xffffc90000003ca0
0xffffc90000003c50:    0xffffffff81109efe  0xffffc90000003ca0
0xffffc90000003c60:    0xffffffff8118bffb  0x0000000000000000

溢出后在 0xffffc90000003be0 伪造一个 rule 以及其中的 expr,并将 ops->eval 函数指针改到了 0xffffffff81134571 ,之后内核使用该函数指针时就会进入 ROP 中执行。

gef➤  x/40xg 0xffffc90000003b30 # jumpstack
0xffffc90000003b30:    0x1026449d9e6ff393  0x08ffff88811db15a
0xffffc90000003b40:    0xe0ffff88811db158  0x00ffffc90000003a
0xffffc90000003b50:    0x10ffff88811db15c  0xe0ffffc90000003d
0xffffc90000003b60:    0xe1ffffc90000003c  0x00ffffffff81c2c6
0xffffc90000003b70:    0x00ffff88815863fc  0x50ffff88810324c0
0xffffc90000003b80:    0x01ffff88810d98fd  0x0000000008000000
0xffffc90000003b90:    0x00ffff88810d9921  0x00ffff88811db15c
0xffffc90000003ba0:    0x00ffffc90000003b  0xb200000000000000
0xffffc90000003bb0:    0x000000000100001d  0xe000000000000000
0xffffc90000003bc0:    0xabffffc90000003a  0xffffffffff81ef1e
0xffffc90000003bd0:    0x0000000000ffffff  0x41ffff88810d9921


// 0xffffc90000003be0: overwrite'd jumpstack[7].rule = 0xffffc90000003be8
// 0xffffc90000003be8 处为 fake struct nft_rule_dp
0xffffc90000003be0:    0xffffc90000003be8  0xffffffffffffffff 

// fake struct nft_expr->ops=0xffffc90000003bf8, ops->eval=0xffffffff81134571
0xffffc90000003bf0:    0xffffc90000003bf8  0xffffffff81134571
0xffffc90000003c00:    0x4141414141414141  0x4141414141414141
0xffffc90000003c10:    0x4141414141414141  0x9300008105414141
0xffffc90000003c20:    0x00ffff88819e6ff3  0x8601000406000801
0xffffc90000003c30:    0x0000000000000000  0xffffc90000003c48
0xffffc90000003c40:    0xffffffff81ef1eab  0xffffc90000003ca0
0xffffc90000003c50:    0xffffffff81109efe  0xffffc90000003ca0
0xffffc90000003c60:    0xffffffff8118bffb  0x0000000000000000

[漏洞分析] CVE-2023-0179内核提权详细分析

因此完整的利用步骤:

  1. 利用信息泄露获取内核镜像基地址和 regs 的地址。

  2. 利用 immediate expr 在 regs 里面布置数据,包括 rop 链、伪造的 ruleexpr、和 expr->ops->eval

  3. 设置 dreg 为 NFT_REG32_15 ,触发漏洞,溢出修改 jumpstack 中的 rule 指针指向伪造的 struct nft_rule_dp 结构体,同时伪造 expr->ops->eval

  4. nft_do_chain 执行 expr->ops->eval 时跳转到 rop gadget 中执行

  5. rop 修改 modprobe_path提权。

漏洞补丁

static bool
nft_payload_copy_vlan(u32 *d, const struct sk_buff *skb, u8 offset, u8 len)
{
    ...
    if (offset + len > VLAN_ETH_HLEN + vlan_hlen)
-        ethlen -= offset + len - VLAN_ETH_HLEN + vlan_hlen;
+        ethlen -= offset + len - VLAN_ETH_HLEN - vlan_hlen;
    ...
}

参考链接

  • https://www.openwall.com/lists/oss-security/2023/01/13/2

  • https://github.com/TurtleARM/CVE-2023-0179-PoC


原文始发于微信公众号(华为安全应急响应中心):[漏洞分析] CVE-2023-0179内核提权详细分析

版权声明:admin 发表于 2023年2月25日 下午6:09。
转载请注明:[漏洞分析] CVE-2023-0179内核提权详细分析 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...