linux 内存越界判断_Linux内存越界漏洞分析(CVE-2017-7184)

在2017年的PWN2OWN大赛中,有参赛团队成功演示了基于Linux内核IPSEC框架的一个内存越界漏洞的利用,此漏洞的CVE编号为CVE-2017-7184。安全研究人员可利用此漏洞实现代码执行、权限提升等操作,从而获取目标机器的root权限。

Linux内核应用范围甚广,我们经常使用的Android、CentOS、Ubuntu等都使用了Linux内核,且本次利用的IPSEC框架自Linux2.6就开始支持,所以漏洞影响较广,建议及时更新内核至最新版本。此漏洞主要利用了linux内核IPSEC框架(自linux2.6开始支持)中的一个内存越界漏洞,CVE编号为CVE-2017-7184。

实验工具qemu虚拟机:QEMU是一款开源的模拟器及虚拟机监管器。QEMU主要提供两种功能给用户使用。一是作为用户态模拟器,利用动态代码翻译机制来执行不同于主机架构的代码。二是作为虚拟机监管器,模拟全系统,利用其他VMM来使用硬件提供的虚拟化支持,创建接近于主机性能的虚拟机。

gdb调试器:GDB是一个由GNU开源组织发布的、UNIX/LINUX操作系统下的、基于命令行的、功能强大的程序调试工具。

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数据包提供完整性检查、认证和加密。

步骤一、漏洞分析

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权限,再通过该线程去修改主进程的权限,从而达到提取的目的。

总结经过本次实验的实际操作和调试,我们清楚的了解和学习到CVE-2017-7184的漏洞成因和利用思路。并且学习了Linux内核下的堆溢出漏洞该如何去利用和Linux 下权限检测内存分配等机制。

进入实验环境访问如下地址:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值