CVE-2023-0179提权利用

风雨同行  金牌会员 | 2024-1-5 06:25:05 | 来自手机 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 866|帖子 866|积分 2598

前言

CVE-2023-0179-Nftables整型溢出中,分析了漏洞的成因,接下来分析漏洞的利用。
漏洞利用

根据漏洞成因可以知道,payload_eval_copy_vlan函数存在整型溢出,导致我们将vlan头部结构拷贝到寄存器(NFT_REG32_00-NFT_REG32_15),而该变量时存在与栈上的,因此可以覆盖栈上的其余变量的。
[img=720,649.547]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202311211526618.png[/img]
可以发现regs变量是无法覆盖到返回地址。
[img=720,380.36775106082035]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202311211526619.png[/img]
因此我们需要观察源码,jumpstack变量是在regs变量下方
[img=720,709.7142857142857]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202311211526621.png[/img]
我们可以通过溢出regs变量覆盖到jumpstack变量。
[img=720,416.7238689547582]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202311211526622.png[/img]
那么接下来需要观察一下nft_jumpstack结构体中存在哪些变量
【----帮助网安学习,以下所有学习资料免费领!加vx:yj009991,备注 “博客园” 获取!】
 ① 网安学习成长路径思维导图
 ② 60+网安经典常用工具包
 ③ 100+SRC漏洞分析报告
 ④ 150+网安攻防实战技术电子书
 ⑤ 最权威CISSP 认证考试指南+题库
 ⑥ 超1800页CTF实战技巧手册
 ⑦ 最新网安大厂面试题合集(含答案)
 ⑧ APP客户端安全检测指南(安卓+IOS)
  1. struct nft_jumpstack {
  2.     const struct nft_chain *chain;
  3.     const struct nft_rule_dp *rule;
  4.     const struct nft_rule_dp *last_rule;
  5. };
复制代码

  • chain:用于指定在哪个流程进行hook
  • rule:以什么样的规则处理数据包
  • last_rule:规则可能不止一条,因此last_rule用于指向最后一条规则
nft_jumpstack结构体在nft_do_chain函数的作用如下,当状态寄存器被设置为JUMP条件时,意味着需要跳转到其他chain进行处理,因此需要先保存当前chain的状态,这里与函数调用时保存栈时的处理一样,估计因此才命名为jumpstack。并且使用一个全局变量stackptr用于确定保存的chain的先后顺序。在保存完之后,就跳转到目的chain,目的chain则是存储在regs.verdict.chain中。
  1.     ...
  2.     switch (regs.verdict.code) {
  3.     case NFT_JUMP:
  4.         if (WARN_ON_ONCE(stackptr >= NFT_JUMP_STACK_SIZE))
  5.             return NF_DROP;
  6.         jumpstack[stackptr].chain = chain;
  7.         jumpstack[stackptr].rule = nft_rule_next(rule);
  8.         jumpstack[stackptr].last_rule = last_rule;
  9.         stackptr++;
  10.     case NFT_GOTO:
  11.         chain = regs.verdict.chain;
  12.         goto do_chain;
  13.     ...
复制代码
还原chain的过程如下,通过递减stackptr来取出存储在jumpstack变量中存储的chain、rule、lastrule,然后就会跳转到next_rule对还原的rule,进行rule的解析,这里需要注意的是在遍历rule的时候,循环是通过rule < last_rule进行遍历的,因此我们在后续伪造last_rule的时候需要大于rule,否则是无法进入循环内部的。
  1.    next_rule:
  2.        regs.verdict.code = NFT_CONTINUE;
  3.        for (; rule < last_rule; rule = nft_rule_next(rule)) {
  4.            nft_rule_dp_for_each_expr(expr, last, rule) {
  5.                if (expr->ops == &nft_cmp_fast_ops)
  6.                    nft_cmp_fast_eval(expr, &regs);
  7.                else if (expr->ops == &nft_cmp16_fast_ops)
  8.                    nft_cmp16_fast_eval(expr, &regs);
  9.                else if (expr->ops == &nft_bitwise_fast_ops)
  10.                    nft_bitwise_fast_eval(expr, &regs);
  11.                else if (expr->ops != &nft_payload_fast_ops ||
  12.                     !nft_payload_fast_eval(expr, &regs, pkt))
  13.                    expr_call_ops_eval(expr, &regs, pkt);
  14.                if (regs.verdict.code != NFT_CONTINUE)
  15.                    break;
  16.            }
  17.    ...
  18.    if (stackptr > 0) {
  19.            stackptr--;
  20.            chain = jumpstack[stackptr].chain;
  21.            rule = jumpstack[stackptr].rule;
  22.            last_rule = jumpstack[stackptr].last_rule;
  23.            goto next_rule;
  24.        }
  25.    ...
复制代码
紧接着来看一下nft_rule_dp结构体,可以发现第一个八个字节是一些标志位组成的,而后续的八个字节则是用于存储nft_expr结构体的指针。
  1. struct nft_rule_dp {
  2.     u64             is_last:1,
  3.                     dlen:12,
  4.                     handle:42;  /* for tracing */
  5.     unsigned char           data[]
  6.         __attribute__((aligned(__alignof__(struct nft_expr))));
  7. };
复制代码
然后可以看到nft_expr结构体里存储了函数指针,如果我们能够篡改该函数指针就可以劫持程序流程。
  1. struct nft_expr {
  2.     const struct nft_expr_ops   *ops;
  3.     unsigned char           data[]
  4.         __attribute__((aligned(__alignof__(u64))));
  5. };
复制代码
然后在这篇文章https://www.ctfiot.com/100156.html学习到了一个小技巧。使用ptype /o struct xxx就可以看到具体的结构体信息与偏移。
[img=720,616.6588235294117]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202311211526623.png[/img]
因此构造的流程如下,首先我们通过漏洞溢出到nft_jumpstack结构体,并且修改rule变量为可控内容的地址同时需要将lastrule的值篡改为比rule更大的值,原因上述已经说过。紧接着在可控内容中伪造一个nft_rule_dp结构体,第一个八字节是填充位,而第二个八字节是需要伪造的函数表指针,同样的我们也将该指针篡改为可控内容的地址,然后再该地址处伪造nft_expr,并且将ops变量指向我们想要执行的函数即可。
[img=720,709.0909090909091]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202311211526624.png[/img]
通过上述分析已经知道了该如何通过漏洞完成程序流程的劫持,接下来需要分析如果伪造上述几个结构体。
首先在nft_payload_copy_vlan函数中,漏洞点是将vlan头的数据拷贝到指定的寄存器里面,而vlan头的地址是低于寄存器的地址,这就会导致在拷贝完vlan头后会将寄存器中的值也进行拷贝的操作,而寄存器的值我们是能人为控制的,因此就可以完成伪造的操作。
[img=720,416.87116564417175]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202311211526625.png[/img]
可以看到我们对NFT_REG32_00的赋值会覆盖到jumpstack[7].rule的值,完成了对jumpstack结构体的篡改,这里我们可以通过NFT_REG32_00 - NFT_REG32_15进行赋值,紧接着查看jumpstack哪个值是被赋值。就可以知道哪个jumpstack可以被篡改。
[img=720,694.6701846965699]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202311211526626.png[/img]
由于我们可以控制regs变量的值,我们可以首先泄露regs的地址,然后在regs上伪造rule即可。然后expr重新指向为jumpstack即可,这里采用了一个小技巧就是将last_rule设置为一个函数地址,由于函数地址的值是大于regs变量的地址值的,因此我们可以节约八个字节。
[img=720,430.18232819074336]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202311211526627.png[/img]
但是这里有个问题就是我们只能控制八个字节的函数指针,因此是无法构造一个完整的ROP链的,而内核并不存在像用户态下有one_gadget可以只利用八个字节就能完成利用,因此在这里必须使用栈迁移,迁移的目的是一段可以控制的内存,那么这里选用的目的自然就是regs了。那么该如何找栈迁移的gadget呢?,这里我首先采用的使用利用vmlinux-to-elf将bzImage的符号表提取出来,然后寻找对应的gadget,gadget类型如下

  • mov rsp,xxx
  • push xxx;pop rsp
  • add rsp,xxx
  • xchg rsp,xxx
上述指令都可以修改rsp寄存器,完成栈迁移的效果。
首先通过vmlinux-to-elf ./bzImage ./vmlinux去提取出符号表
[img=720,490.44222539229673]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202311211526628.png[/img]
然后通过ropper进行gadget的提取,ropper --file ./vmlinux --nocolor > g
最后这在搜索gadget,cat g | grep 'add rsp.*ret',但是通过尝试发现下述的地址都没办法使用,因为下述地址都不具备可执行的权限。
[img=720,134.97650130548303]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202311211526629.png[/img]
然后尝试了搜索上述所有的gadget,我都没有找到可以用的gadget,唯一比较接近的gadget是pop rsi的,但是无法控制rsi的寄存器,其实这里一开始我使用的镜像是自己编译的,这里搜索的gadget是需要控制rdi寄存器的,经过多次尝试无果后才使用了作者的config文件重新编译发现还是不可行。
[img=720,238.67586206896553]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202311211526630.png[/img]
其实我们在编译内核文件时是存在vmlinux文件的,但是那个文件十分的大,使用ropper工具无法分析,就在我准备放弃的时候,想到使用objdump工具进行gadget的提取
使用objdump -d -M intel vmlinux > ./gadget.txt

  • -d是dump代码
  • -M是指定汇编代码的格式
objdump提取的速度非常快,提取代码如下,但是它没有ropper搜索gadget那么方便,但是会全的多
[img=720,216.2345276872964]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202311211526631.png[/img]
这里我首先尝试了搜索栈迁移的gadget,cat gadget.txt | grep -E 'add rsp.*'
[img=720,601.8300653594771]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202311211526632.png[/img]
可以发现有非常多的匹配的gadget,接着我们在gdb中验证可以使用的gadget,通常在栈进行还原的时候会用到add rsp,xxx,因此都是有效的gadget,然后就是计算栈顶与resg函数地址的差值找到相应的栈迁移gadget即可。
[img=720,207.26306465899026]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202311211526633.png[/img]
接下就是考虑如何进行提权的利用了,虽然我们可以控制regs但是可控的范围也只有0x40是不足于采用commit_creds(prepare_kernel_cred(0))设置root凭证然后返回到用户空间执行后门的。那么相当的一个办法就是通过覆盖modprobe_path进行提权。这里我找了下列gadget进行modprobe_path的覆盖,将rdi设置为modprobe_path,rax设置为覆盖后的路径即可。
  1.         0xffffffff810d1e6b: mov qword ptr [rdi], rax; ret;
  2.         0xffffffff81004165: pop rdi; pop rbp; ret
复制代码
最后就是覆盖完modprobe_path该如何返回到用户态,因为modprobe_path的提权需要在用户态下执行非法文件头的文件,这里作者采用的是将栈还原,通过在rbp中的地址值覆盖会rsp中即可,采用下述gadget
  1.         0xffffffff810b47f0: mov rsp, rbp; pop rbp; ret;
复制代码
[img=720,248.03632236095345]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202311211526634.png[/img]
但是在我的环境下直接返回不行,这是因为在返回到nf_hook_slow函数时,有对状态码的一个检验,而在上述覆盖modprobe_path时,我们设置了rax值,就导致无法将状态码设置成合法值。那分支就会跳转到default,导致报错。在尝试搜索了gadget之后,可以将rax设置为0,但是这回进入到NF_DROP分支 中,但是此时skb变量也被我们破坏了,无法正常执行。
  1. int nf_hook_slow(struct sk_buff *skb, struct nf_hook_state *state,
  2.          const struct nf_hook_entries *e, unsigned int s)
  3. {
  4.     unsigned int verdict;
  5.     int ret;
  6.     for (; s < e->num_hook_entries; s++) {
  7.         verdict = nf_hook_entry_hookfn(&e->hooks[s], skb, state);
  8.         switch (verdict & NF_VERDICT_MASK) {
  9.         case NF_ACCEPT:
  10.             break;
  11.         case NF_DROP:
  12.             kfree_skb_reason(skb,
  13.                      SKB_DROP_REASON_NETFILTER_DROP);
  14.             ret = NF_DROP_GETERR(verdict);
  15.             if (ret == 0)
  16.                 ret = -EPERM;
  17.             return ret;
  18.         case NF_QUEUE:
  19.             ret = nf_queue(skb, state, s, verdict);
  20.             if (ret == 1)
  21.                 continue;
  22.             return ret;
  23.         default:
  24.             /* Implicit handling for NF_STOLEN, as well as any other
  25.              * non conventional verdicts.
  26.              */
  27.             return 0;
  28.         }
  29.     }
  30.     return 1;
  31. }
复制代码
在尝试很久之后,最终放弃正常返回的这个选项,然后我在rbp中搜索是否有合适的返回地址。最后在rbp中我找到了一个do_softirq函数
[img=720,123.82165605095541]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202311211526635.png[/img]
该函数是一个软中断处理的函数,当时我就猜想,如果这个函数返回了,应该不会影响程序的执行。

尝试运行之后,发现还是有内核异常,顿时有点失望。
[img=720,342.84127718648773]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202311211526637.png[/img]
但是在操控命令行的时候是能够正常输入命令的,说明我们成功返回到用户态了。
[img=720,399.1218441273326]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202311211526638.png[/img]
最后就是查看是否将新用户写入到/etc/passwd中了,最终完成写入。完结撒花!。
[img=720,502.2413793103448]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202311211526639.png[/img]
完整exp可以参考
https://github.com/h0pe-ay/Vulnerability-Reproduction/blob/master/CVE-2023-0179(nftables)/poc.c
更多网安技能的在线实操练习,请点击这里>>
  

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

风雨同行

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表