网易首页 > 网易号 > 正文 申请入驻

Pwn2Own 比赛使用的 VirtualBox NAT 网卡模拟组件的漏洞分析

0
分享至

虚拟化软件VirtualBox是一个非常有趣的目Header。模拟硬件设备和将数据安全地传递到真实硬件的复杂和难度非常大。正应了那句话:哪里有复杂性,哪里就有漏洞。

对于 Pwn2Own,以仿真组件为目Header是一个比较好的选择。在我看来,网络硬件仿真似乎是正确的途径。我从一个默认组件开始:.NET 格式的 NAT 仿真代码在/src/VBox/Devices/Network/DrvNAT.cpp中。

在审计代码的过程中,我发现了一些引起我注意的代码:

static DECLCALLBACK(void) drvNATSendWorker(PDRVNAT pThis, PPDMSCATTERGATHER pSgBuf)

#if 0 /* Assertion happens often to me after resuming a VM -- no time to investigate this now. */

Assert(pThis->enmLinkState == PDMNETWORKLINKSTATE_UP);

#endif

if (pThis->enmLinkState == PDMNETWORKLINKSTATE_UP)

struct mbuf *m = (struct mbuf *)pSgBuf->pvAllocator;

if (m)

* A normal frame.

pSgBuf->pvAllocator = NULL;

slirp_input(pThis->pNATState, m, pSgBuf->cbUsed);

else

* GSO frame, need to segment it.

/** @todo Make the NAT engine grok large frames? Could be more efficient... */

#if 0 /* this is for testing PDMNetGsoCarveSegmentQD. */

uint8_t abHdrScratch[256];

#endif

uint8_t const *pbFrame = (uint8_t const *)pSgBuf->aSegs[0].pvSeg;

PCPDMNETWORKGSO pGso = (PCPDMNETWORKGSO)pSgBuf->pvUser;

uint32_t const cSegs = PDMNetGsoCalcSegmentCount(pGso, pSgBuf->cbUsed); Assert(cSegs > 1);

for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++)

size_t cbSeg;

void *pvSeg;

m = slirp_ext_m_get(pThis->pNATState, pGso->cbHdrsTotal + pGso->cbMaxSeg, &pvSeg, &cbSeg);

if (!m)

break;

#if 1

uint32_t cbPayload, cbHdrs;

uint32_t offPayload = PDMNetGsoCarveSegment(pGso, pbFrame, pSgBuf->cbUsed,

iSeg, cSegs, (uint8_t *)pvSeg, &cbHdrs, &cbPayload);

memcpy((uint8_t *)pvSeg + cbHdrs, pbFrame + offPayload, cbPayload);

slirp_input(pThis->pNATState, m, cbPayload + cbHdrs);

#else

用于从guest向网络发送数据包的函数,包含用于通用分段卸载 (GSO)帧的单独代码路径,并调用memcpy用于合并数据片段。

在审计了各种代码路径并为所有限制因素编写了一个简单的基于 Python 的约束求解器之后,当使 VirtIO 的半虚拟化网络设备时,发现了一个重大问题。

完全模拟设备的另一种方法是使用半虚拟化。与完全虚拟化不同,在完全虚拟化中,guest完全不知道自己是guest,半虚拟化让guest安装驱动程序,这些驱动程序知道它们在guest机器中运行,以便与主机一起以更快、更多的速度传输数据。

VirtIO 是一个可用于开发半虚拟化驱动程序的接口。有一个这样的驱动程序叫virtio-net,它与 Linux 源代码一起提供并用于网络通信。

https://github.com/torvalds/linux/blob/master/drivers/net/virtio_net.c

VirtualBox 与许多其他虚拟化软件一样,支持将其作为网络适配器:

适配器类型选项图

VirtIO 网络通过使用ring形缓冲区在guest和主机(在这种情况下称为 Virtqueues 或 VQueues)之间传输数据来工作。但是,与 e1000 漏洞不同的是,VirtIO 不使用带有头尾寄存器的单个ring进行传输,而是使用三个独立的数组:

◼一个 Descriptor 数组,每个描述符包含以下数据:

·Address - 正在传输的数据的物理地址。

·Length – 地址处数据的长度。

·Flags – 确定 Next 字段是否正在使用以及缓冲区是读取还是写入的Header。

·Next – 在有链接时使用。

◼可用ring– 一个数组,其中包含正在使用且可由主机读取的描述符数组的索引。

◼一个已使用的ring– 主机已读取的 Descriptor 数组中的索引数组。

结构如下所示:

当guest希望向网络发送数据包时,它会在描述符表中添加一个条目,将这个描述符的索引添加到可用ring中,然后增加可用索引指针:

完成此操作后,guest通过将 VQueue 索引写入队列通知寄存器来通知主机。这会触发主机开始处理可用ring中的描述符。处理完描述符后,将其添加到已用ring中,并增加已用索引:

接下来,需要一些 GSO 的背景知识。要了解对 GSO 的需求,重要的是要了解它为网卡解决的问题。

最初,在计算传输层校验和或将它们分割成更小的以太网数据包时,CPU 将处理所有繁重的工作。由于此过程在处理大量传出网络流量时可能非常缓慢,因此硬件制造商开始为这些操作实施卸载,从而减轻操作系统的压力。

对于分段,这意味着操作系统不必通过网络堆栈传递许多小得多的数据包,操作系统只需传递一个数据包一次。

这种优化可以应用于其他协议,TCP 和 UDP 之外的协议,无需硬件支持,方法是将分段延迟到网络驱动程序接收消息之前,会创建 GSO。

由于 VirtIO 是半虚拟化设备,驱动程序知道它在guest机器中,因此可以在guest和主机之间应用 GSO。GSO 在 VirtIO 中通过在网络缓冲区的开头添加上下文描述符头来实现,可以在以下结构中看到此Header头:

struct VNetHdr

uint8_t u8Flags;

uint8_t u8GSOType;

uint16_t u16HdrLen;

uint16_t u16GSOSize;

uint16_t u16CSumStart;

uint16_t u16CSumOffset;

VirtIO 头可以被认为是与 e1000漏洞中的上下文描述符类似的概念。

收到此Header头后,将验证参数vnetR3ReadHeader某些级别的有效性。然后函数vnetR3SetupGsoCtx用于填充 VirtualBox 在所有网络设备上使用的标准HeaderGSO 结构:

typedef struct PDMNETWORKGSO

/** The type of segmentation offloading we're performing (PDMNETWORKGSOTYPE). */

uint8_t u8Type;

/** The total header size. */

uint8_t cbHdrsTotal;

/** The max segment size (MSS) to apply. */

uint16_t cbMaxSeg;

/** Offset of the first header (IPv4 / IPv6). 0 if not not needed. */

uint8_t offHdr1;

/** Offset of the second header (TCP / UDP). 0 if not not needed. */

uint8_t offHdr2;

/** The header size used for segmentation (equal to offHdr2 in UFO). */

uint8_t cbHdrsSeg;

/** Unused. */

uint8_t u8Unused;

} PDMNETWORKGSO;

构建完成后,VirtIO 代码就会创建一个 scatter-gatherer 来从各种描述符组装数据帧:

/* Assemble a complete frame. */

for (unsigned int i = 1; i < elem.cOut && uSize > 0; i++)

unsigned int cbSegment = RT_MIN(uSize, elem.aSegsOut[i].cb);

PDMDevHlpPhysRead(pDevIns, elem.aSegsOut[i].addr,

((uint8_t*)pSgBuf->aSegs[0].pvSeg) + uOffset,

cbSegment);

uOffset += cbSegment;

uSize -= cbSegment;

数据帧与新的 GSO 结构一起传递给 NAT 代码,现在达到了最初引起我兴趣的地方。

1.CVE-2021-2145 – Oracle VirtualBox NAT 整数下溢权限提升漏洞

当 NAT 代码收到 GSO 帧时,它会获取完整的以太网数据包并将其作为mbuf消息传递给Slirp(用于 TCP/IP 仿真的库)。为了做到这一点,VirtualBox 分配了一条mbuf新消息并将数据包复制过去。分配函数获取一个大小并从三个不同的存储桶中选择一个最大的分配大小:

  1. MCLBYTES(0x800 字节)

  2. MJUM9BYTES(0x2400 字节)

  3. MJUM16BYTES(0x4000 字节)

struct mbuf *slirp_ext_m_get(PNATState pData, size_t cbMin, void **ppvBuf, size_t *pcbBuf)

struct mbuf *m;

int size = MCLBYTES;

LogFlowFunc(("ENTER: cbMin:%d, ppvBuf:%p, pcbBuf:%p\n", cbMin, ppvBuf, pcbBuf));

if (cbMin < MCLBYTES)

size = MCLBYTES;

else if (cbMin < MJUM9BYTES)

size = MJUM9BYTES;

else if (cbMin < MJUM16BYTES)

size = MJUM16BYTES;

else

AssertMsgFailed(("Unsupported size"));

m = m_getjcl(pData, M_NOWAIT, MT_HEADER, M_PKTHDR, size);

如果提供的大小大于MJUM16BYTES,就会触发断言。不幸的是,此断言仅在使用RT_STRICT宏时才编译,而在发布版本中不这样。这意味着在命中此断言后将继续执行,从而为分配选择大小为 0x800 的存储桶。由于实际数据量较大,这会导致用户数据复制到mbuf.

/** @def AssertMsgFailed

* An assertion failed print a message and a hit breakpoint.

* @param a printf argument list (in parenthesis).

#ifdef RT_STRICT

# define AssertMsgFailed(a) \

do { \

RTAssertMsg1Weak((const char *)0, __LINE__, __FILE__, RT_GCC_EXTENSION __PRETTY_FUNCTION__); \

RTAssertMsg2Weak a; \

RTAssertPanic(); \

} while (0)

#else

# define AssertMsgFailed(a) do { } while (0)

#endif

2.CVE-2021-2310 - 基于 Oracle VirtualBox NAT 堆的缓冲区溢出权限提升漏洞

在整个代码中,使用了一个调用的函数PDMNetGsoIsValid来验证guest提供的 GSO 参数是否有效。但是无论何时使用它,它都会放在断言中。例如:

DECLINLINE(uint32_t) PDMNetGsoCalcSegmentCount(PCPDMNETWORKGSO pGso, size_t cbFrame)

size_t cbPayload;

Assert(PDMNetGsoIsValid(pGso, sizeof(*pGso), cbFrame));

cbPayload = cbFrame - pGso->cbHdrsSeg;

return (uint32_t)((cbPayload + pGso->cbMaxSeg - 1) / pGso->cbMaxSeg);

如前所述,像这样的断言不会在发布版本中编译。这会导致允许无效的 GSO 参数;给定的大小可能会导致错误计算slirp_ext_m_get,使其小于for 循环中的memcpy复制总量。在我的PoC中,我用于计算pGso->cbHdrsTotal + pGso->cbMaxSeg的参数cbMin导致分配了 0x4000 字节,但计算cbPayload导致了memcpy对 0x4065 字节的调用,从而使分配的区域溢出。

3.CVE-2021-2442 - Oracle VirtualBox NAT UDP Header头越界漏洞

另一种卸载机制也很脆弱:校验和卸载。校验和卸载可应用于在其消息头中有校验和的各种协议。模拟时,VirtualBox 支持 TCP 和 UDP。

为了访问此功能,GSO 帧需要u8Flags设置成员的第一位以指示需要校验和卸载。在 VirtualBox 的情况下,必须始终设置此位,因为它无法在不执行校验和卸载的情况下处理 GSO。当 VirtualBox 使用 GSO 处理 UDP 数据包时,它可能会在某些情况下在PDMNetGsoCarveSegmentQD函数中结束:

case PDMNETWORKGSOTYPE_IPV4_UDP:

if (iSeg == 0)

pdmNetGsoUpdateUdpHdrUfo(RTNetIPv4PseudoChecksum((PRTNETIPV4)&pbFrame[pGso->offHdr1]),

pbSegHdrs, pbFrame, pGso->offHdr2);

该函数pdmNetGsoUpdateUdpHdrUfo使用offHdr2来指示 UDP 报头在数据包结构中的位置。最终这会调用一个名为RTNetUDPChecksum的函数:

RTDECL(uint16_t) RTNetUDPChecksum(uint32_t u32Sum, PCRTNETUDP pUdpHdr)

bool fOdd;

u32Sum = rtNetIPv4AddUDPChecksum(pUdpHdr, u32Sum);

fOdd = false;

u32Sum = rtNetIPv4AddDataChecksum(pUdpHdr + 1, RT_BE2H_U16(pUdpHdr->uh_ulen) - sizeof(*pUdpHdr), u32Sum, &fOdd);

return rtNetIPv4FinalizeChecksum(u32Sum);

这就是漏洞所在。在此函数中,该uh_ulen属性是完全可信的,无需任何验证,这会导致大小超出缓冲区范围,或者因减去sizeof(*pUdpHdr)而导致整数下溢。

rtNetIPv4AddDataChecksum 接收大小值和数据包头指针并继续计算校验和:

/* iterate the data. */

while (cbData > 1)

u32Sum += *pw;

pw++;

cbData -= 2;

从开发的角度来看,将大量越界数据添加在一起似乎不是特别有趣。但是,如果攻击者能够为连续的 UDP 数据包重新分配相同的堆位置,并且每次添加两个字节的 UDP 大小参数,则可以计算每个校验和的差异并泄露越界数据。

最重要的是,还可以利用此漏洞对网络中的其他虚拟机造成拒绝服务:

https://twitter.com/i/status/1421859745380638727

卸载支持在现代网络设备中很常见,因此虚拟化软件模拟设备也很自然地做到这一点。虽然大多数公共研究都集中在其主要组件上,例如环形缓冲区,卸载模块并没有受到过多的关注。

参考及来源:https://www.sentinelone.com/labs/gsoh-no-hunting-for-vulnerabilities-in-virtualbox-network-offloads/

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
为啥社会失去了消费力?看看这张图你就知道!

为啥社会失去了消费力?看看这张图你就知道!

我不叫阿哏
2024-06-19 15:15:24
中国海警的立威之战,让菲律宾和美国不敢启动《共同防御条约》?

中国海警的立威之战,让菲律宾和美国不敢启动《共同防御条约》?

战域笔墨
2024-06-19 23:42:55
“人民”警察网暴人民,丧尽天良!

“人民”警察网暴人民,丧尽天良!

观风者
2024-06-14 10:06:53
枢密院十号:连乌克兰都不要的,台湾还当个宝……

枢密院十号:连乌克兰都不要的,台湾还当个宝……

环球网资讯
2024-06-19 23:58:21
曝陈晓陈妍希婚姻破裂!两人去年5月最后合体,同框细节见端倪!

曝陈晓陈妍希婚姻破裂!两人去年5月最后合体,同框细节见端倪!

古希腊掌管月桂的神
2024-06-19 13:07:08
俄罗斯加快从克里米亚撤离,军人家庭和博物馆展品下架撤离

俄罗斯加快从克里米亚撤离,军人家庭和博物馆展品下架撤离

亡海中的彼岸花
2024-06-16 16:28:59
正义不仅迟到还开了个玩笑?实地走访提灯定损村,提灯定损房还在

正义不仅迟到还开了个玩笑?实地走访提灯定损村,提灯定损房还在

三月柳
2024-06-19 11:11:38
国足出战18强赛最强11人敲定,王大雷+韦世豪无缘,颜骏凌进首发

国足出战18强赛最强11人敲定,王大雷+韦世豪无缘,颜骏凌进首发

罗掌柜体育
2024-06-19 16:30:37
刚刚,中国总理访澳国宴菜单曝光!因为朴素,遭到巨大争议

刚刚,中国总理访澳国宴菜单曝光!因为朴素,遭到巨大争议

澳洲红领巾
2024-06-19 13:39:32
黄奇帆谈趋势:到2024年底,我国极有可能出现的6个新趋势!

黄奇帆谈趋势:到2024年底,我国极有可能出现的6个新趋势!

校长侃财
2024-06-19 12:01:46
9岁女孩在小区荡椅上玩耍跌落死亡,设备公司被判担责60%赔107万

9岁女孩在小区荡椅上玩耍跌落死亡,设备公司被判担责60%赔107万

澎湃新闻
2024-06-19 17:54:26
FIFA排名第18!日本队大概率将跻身世界杯第二档球队

FIFA排名第18!日本队大概率将跻身世界杯第二档球队

直播吧
2024-06-19 20:34:16
王源最新露面“烂脸”严重!坑包明显,疑留疤痕,网友劝尽快治疗

王源最新露面“烂脸”严重!坑包明显,疑留疤痕,网友劝尽快治疗

山野下
2024-06-18 16:40:57
对姜萍的质疑上升到老师王闰秋,私下喜好全被扒,知乎被迫清空

对姜萍的质疑上升到老师王闰秋,私下喜好全被扒,知乎被迫清空

王二哥老搞笑
2024-06-19 19:49:13
中国女排巴黎奥运名单公布:朱婷领衔,张常宁、李盈莹在列

中国女排巴黎奥运名单公布:朱婷领衔,张常宁、李盈莹在列

懂球帝
2024-06-19 17:12:16
费迪南德:福登的情况之前在曼联见过,贝隆就是因为基恩才离开

费迪南德:福登的情况之前在曼联见过,贝隆就是因为基恩才离开

直播吧
2024-06-19 20:13:13
事态升级!党政媒体点评南方医科大学:校规之上应该有人性良知

事态升级!党政媒体点评南方医科大学:校规之上应该有人性良知

芒果的爱pMgf
2024-06-19 23:15:28
45岁冯坤亮相抽签仪式!抽到法国队时她笑了:我对中国女排有信心

45岁冯坤亮相抽签仪式!抽到法国队时她笑了:我对中国女排有信心

体坛纪录片
2024-06-19 22:07:52
2012年三对高校教师夫妻玩“换妻”游戏,内容不堪入目,结局如何

2012年三对高校教师夫妻玩“换妻”游戏,内容不堪入目,结局如何

阿胡
2024-06-19 14:36:12
令人泪目!山西8岁男孩:反正穷光蛋,长大捡破烂吧,能活着就好

令人泪目!山西8岁男孩:反正穷光蛋,长大捡破烂吧,能活着就好

贾文彬的史书
2024-06-18 23:36:34
2024-06-20 09:02:44
嘶吼RoarTalk
嘶吼RoarTalk
不一样的互联网安全新视界
7438文章数 10509关注度
往期回顾 全部

科技要闻

美国AI圈震动! “OpenAI宫斗”核心人物苏茨克维官宣创业

头条要闻

德对华最强硬部长将访华 专家:或向中方传递三层意思

头条要闻

德对华最强硬部长将访华 专家:或向中方传递三层意思

体育要闻

欧洲杯最大的混子,非他莫属

娱乐要闻

黄一鸣“杀疯了” 直播间卖大葱养孩子

财经要闻

茅台大跌,谁的锅?

汽车要闻

双肾格栅变化大/内饰焕新 新一代宝马X3官图发布

态度原创

游戏
艺术
亲子
本地
公开课

《龙腾世纪影障守护者》等级上限50级 技能树分三种

艺术要闻

穿越时空的艺术:《马可·波罗》AI沉浸影片探索人类文明

亲子要闻

女儿自信给包上密码锁,下一秒被爸爸轻松“解码”

本地新闻

中式沙拉宇宙的天花板,它必须有姓名

公开课

近视只是视力差?小心并发症

无障碍浏览 进入关怀版