实验内容
此漏洞主要利用了linux内核IPSEC框架(自linux2.6开始支持)中的一个内存越界漏洞,CVE编号为CVE-2017-7184。
IPSEC协议简介
IPSEC是一个协议组合,它包含AH、ESP、IKE协议,提供对数据包的认证和加密功能。
为了帮助更好的理解漏洞成因,下面有几个概念需要简单介绍一下
1、SA(Security Associstion)
SA由spi、ip、安全协议标识(AH或ESP)这三个参数唯一确定。SA定义了ipsec双方的ip地址、ipsec协议、加密算法、密钥、模式、抗重放窗口等。
2、AH(Authentication Header)
AH为ip包提供数据完整性校验和身份认证功能,提供抗重放能力,验证算法由SA指定。
3、ESP(Encapsulating security payload)
ESP为ip数据包提供完整性检查、认证和加密。
实验工具
qemu虚拟机:
QEMU是一款开源的模拟器及虚拟机监管器。QEMU主要提供两种功能给用户使用。一是作为用户态模拟器,利用动态代码翻译机制来执行不同于主机架构的代码。二是作为虚拟机监管器,模拟全系统,利用其他VMM来使用硬件提供的虚拟化支持,创建接近于主机性能的虚拟机。
gdb调试器:
GDB是一个由GNU开源组织发布的、UNIX/LINUX操作系统下的、基于命令行的、功能强大的程序调试工具。
实验步骤
步骤一、漏洞分析
1、补丁分析
static inline int xfrm_replay_verify_len(struct xfrm_replay_state_esn *replay_es if (nla_len(rp) < ulen || xfrm_replay_state_esn_len(replay_esn) != ulen) return -EINVAL; if (up->replay_window > up->bmp_len * sizeof(__u32) * 8) return -EINVAL; return 0; }
从补丁可以看到代码添加了对replay_window
和bmp_len
的大小检测,当replay_window
> bmp_len
的时候退出。
而这两个成员都来自同一个结构体:
struct xfrm_replay_state_esn { unsigned int bmp_len; __u32 oseq; __u32 seq; __u32 oseq_hi; __u32 seq_hi; __u32 replay_window; __u32 bmp[0]; };
bmp_len
决定整个结构体的具体大小. 而replay_window
则决定了bmp数组的索引范围。
通过上述信息可以得知:如果没有对replay_window
和bmp_len
的大小进行检测,则可能出现索引到结构体之外的情况。
2、成因分析
问题出在xfrm_replay_state_esn
结构体的replay_window
和bmp_len
上
当我们发送一个XFRM_MSG_NEWAE
类型的消息时,即可调用xfrm_new_ae
函数来更新一个已存在的 SA。
这里有检测函数如下所示:
static inline int xfrm_replay_verify_len( struct xfrm_replay_state_esn *replay_esn, struct nlattr *rp) { struct xfrm_replay_state_esn *up; int ulen; if (!replay_esn || !rp) return 0; up = nla_data(rp); ulen = xfrm_replay_state_esn_len(up); if (nla_len(rp) < ulen || xfrm_replay_state_esn_len(replay_esn) != ulen) return -EINVAL; return 0; }
可以看到,这个函数没有对replay_window
做任何检测,所以这个时候我们就可以控制replay_window
超过bmp_len
。这样,在后面流程的读写操作中,很可能就会触发越界。
触发越界的函数有两个,xfrm_replay_check
和xfrm_replay_advance
,其中xfrm_replay_advance
有越界写操作:
static void xfrm_replay_advance_esn(struct xfrm_state *x, __be32 net_seq) { unsigned int bitnr, nr, i; int wrap; u32 diff, pos, seq, seq_hi; struct xfrm_replay_state_esn *replay_esn = x->replay_esn; if (!replay_esn->replay_window) return; seq = ntohl(net_seq); pos = (replay_esn->seq - 1) % replay_esn->replay_window; seq_hi = xfrm_replay_seqhi(x, net_seq); wrap = seq_hi - replay_esn->seq_hi; if ((!wrap && seq > replay_esn->seq) || wrap > 0) { if (likely(!wrap)) diff = seq - replay_esn->seq; else diff = ~replay_esn->seq + seq + 1; if (diff < replay_esn->replay_window) { for (i = 1; i < diff; i++) { bitnr = (pos + i) % replay_esn->replay_window; nr = bitnr >> 5; bitnr = bitnr & 0x1F; replay_esn->bmp[nr] &= ~(1U << bitnr); } } else { nr = (replay_esn->replay_window - 1) >> 5; for (i = 0; i <= nr; i++) replay_esn->bmp[i] = 0; } bitnr = (pos + diff) % replay_esn->replay_window; replay_esn->seq = seq; if (unlikely(wrap > 0)) replay_esn->seq_hi++; } else { diff = replay_esn->seq - seq; if (pos >= diff) bitnr = (pos - diff) % replay_esn->replay_window; else bitnr = replay_esn->replay_window - (diff - pos); } nr = bitnr >> 5; bitnr = bitnr & 0x1F; replay_esn->bmp[nr] |= (1U << bitnr); if (xfrm_aevent_is_on(xs_net(x))) x->repl->notify(x, XFRM_REPLAY_UPDATE); }
这里我们可以看到,有多处都能触发越界写操作。
其中 net_seq,replay_esn->seq,replay_esn->replay_window,replay_esn->seq_hi 是我们可控的变量,所以我们可以通过控制这些变量让程序执行到我们想要到的流程,从而达到越界的目的。
这个函数在xfrm_input
中被调用,如果采用IPPROTO_AH
协议的话, 调用链如下所示:
xfrm4_ah_rcv -> xfrm4_rcv -> xfrm4_rcv_spi -> xfrm_input
这里通过如下伪代码测试是否能够成功触发越界写:
/* 用于向内核发送信息, 然后生成/更新xfrm_state结构体的套接字 */ xfrm_state_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_XFRM); /* 用于接收IPPROTO_AH信息, 触发xfrm_input的接收套接字 */ recvfd = socket(AF_INET, SOCK_RAW, IPPROTO_AH); /* 用于发送自定义数据包的发送套接字 */ sendfd = socket(AF_INET, SOCK_RAW, IPPROTO_AH); /* 触发漏洞 */ trigger_oob(...);
步骤二、漏洞调试过程
1、开启qemu虚拟机以及远程调试
首先开启桌面上的xshell,新建连接,连接信息如下:
ip:172.16.12.2
,user:ichunqiu
,password:ichunqiu
进入目标机后,使用su
命令切换至root
权限,然后进入/home/ichunqiu
目录下
复制当前会话窗口,右键->复制SSH渠道,新窗口为了运行qemu虚拟机系统
为了方便理解,新窗口简称为虚拟机
,旧窗口命名为宿主机
在虚拟机窗口中输入如下命令开启qemu虚拟机:
qemu-system-x86_64 -kernel bzImage -hda wheezy.img -m 512M -nographic -append "console=ttyS0 root=/dev/sda" -net user,hostfwd=tcp::10011-:22 -net nic -s
过程需要几分钟,启动后使用root
用户登陆,密码为ichunqiu
回到宿主机窗口,进入/home/ichunqiu/Tools
目录下,运行./upload setcap
和./upload exp
root@localhost’s password:ichunqiu
之后在虚拟机窗口中,进入/tmp
目录下,执行命令
./setcap cap_net_raw,cap_net_admin=eip ./exp su nobody
回到宿主机窗口,进入/home/ichunqiu/linux-4.12
目录下,输入命令:gdb vmlinux
然后启动远程调试,输入命令:target remote:1234
2、调试分析
首先我们确定xfrm_alloc_replay_state_esn
的创建,因为我们传入了XFRMA_REPLAY_ESN_VAL
,所以最终会进入函数xfrm_alloc_replay_state_esn
,创建xfrm_replay_state_esn
结构体
因此,我们需要在以下四个函数处下断点,执行命令如下:
b xfrm_alloc_replay_state_esn b xfrm_init_replay b xfrm_replay_verify_len b xfrm_replay_advance_esn
之后,输入命令c
,使虚拟机系统继续运行,来到虚拟机窗口中,输入命令./exp 123
来运行exp
回到宿主机窗口,此时gdb会第一次中断到xfrm_replay_state_esn
结构体生成的函数
继续输入命令c
,gdb会中断到SA结构体的检测函数处
继续输入命令n
,单步运行到第743行处,输入命令x replay_esn
和x/128 0xffff88001a6e7e00
(此值可能会变化,需要根据实际情况填写)
可以看到,这个时候replay_window
和bmp_len
的大小是一样的,因为这个时候刚创建结构体,并没有绕过验证传入一个大于 bmp_len
的replay_window
。
另外根据我们前面的分析,在add_sa
的过程中会有两次检测,这两次检测都无法绕过,所以在add_sa
的整个过程中,xfrm_replay_state_esn
结构体并不会出错。
继续输入命令c
,第三次中断来到调用xfrm_replay_verify_len
函数的位置
输入命令si
,进入这个xfrm_replay_verify_len
函数里
通过对这个函数的调试分析可以知道,没有对replay_window
和bmp_len
的大小进行检测,导致我们可以传入一个比bmp_len
大的replay_window
通过上面的分析我们可以知道,内核的流程为:xfrm_add_sa -> xfrm_new_ae -> xfrm_replay_advance_esn
,所以我们这里直接定位到xfrm_replay_advance_esn
,因为如果成功更改了replay_window
,在这个函数中一定是已经更新过后的xfrm_replay_state_esn
。
我们继续输入命令c
,中断到如下位置
这里便是溢出点,通过输入命令n
,来到第513行处
然后输入命令p replay_esn
来查看xfrm_replay_state_esn
结构体地址,并且通过这个地址来查看replay_window
和bmp_len
的值
这里可以看到,replay_window
的值0xc01
已经远远大于bmp_len的值0x24
了。
继续执行命令n
,来到第536行的位置
接下来在net/xfrm/xfrm_replay.c:541
位置处下断点,执行命令b net/xfrm/xfrm_replay.c:541
,然后输入命令c
使程序运行到该位置。
这个时候已经清零完成,通过之前查看内存的命令,来看下内存现在是什么情况
可以看到,已经把xfrm_replay_state_esn
结构体之后的固定位给清零了。
至此,cred
结构体已经通过喷射到了xfrm_replay_state_esn
结构体之后的位置,通过清零操作可以将其中的关键设置为0,从而拥有该cred
的线程已经为root
,再通过该线程去修改主进程的权限,从而达到提取的目的。
思考
1、造成漏洞的点在哪儿?
2、造成越界访问的那些参数是可控的,如何控制?
3、还有其他的利用思路吗?
来源:freebuf.com 2018-04-10 14:28:32 by: ellenZ
请登录后发表评论
注册