黑客24小时在线接单网站

黑客在线接单,网站入侵,渗透测试,渗透网站,入侵网站

一个有关 V8 漏洞的细节分析 (一)

当开发人员决定使用新Torque重新实现两种语言CodeStubAssembler(CSA)函数时,V8中就会出现漏洞。这两个函数用于在JavaScript中创建新的FixedArray和FixedDoubleArray虽然新的实现乍一看是有效的,但它们缺少一个关键部分:更大长度检查,以确保新数组的长度不会超过预定义的上限。

对于普通人来说,这种缺失的长度检查看起来很正常,但对于攻击者来说,它是可以使用的TurboFan的typer访问权限中一个非常强大的使用原语:数组,其长度字段远大于其容量。这个原语为攻击者提供了V8堆叠的越界访问原语容易导致代码执行。

实现漏洞的过程

如果要继续,需要建造V8版本8.5.51(提交64cadfcf4a56c0b3b9d3b5cc00905483850d6559),建议使用完整的符号进行构建(修改)args.gn并添加symbol_level = 2行)。

在x64.release您可以使用以下命令用零编译器优化编译发行版本:

  • find.-typef-execgrep'\-O3'-l{}";"-execsed-i's/\-O3/\-O0/'{}";"-ls
  • 如果您想继续阅读本博客中的一些代码示例,我仍然建议您构建一个普通的发行版本(使用编译器优化)。如果没有优化,一些示例将需要很长时间才能运行。

    从上面链接bugtracker获得概念证明。

    2017年之前的V8

    在2017年之前,很多JavaScript内置函数(Array.prototype.concat,Array.prototype.map等)都是用JavaScript虽然使用了这些函数TurboFan(V8推测性优化编译器稍后会详细说明)。为了更大限度地发挥性能,它们的运行速度不如使用本机代码快。

    对于最常见的内置函数,开发人员将以手写汇编的形式编写非常优化的版本。可行的原因是ECMAScript这些内置函数在规范(点击查看示例)中的描述非常详细。但它有一个很大的缺点:V8对于大量的平台和系统结构,这意味着V8开发人员必须为每个系统结构编写和重写这些优化的所有内置函数。ECMAScript随着标准的不断发展和新语言函数的不断标准化,维护这些手写程序集变得非常繁琐,容易出错。

    遇到这个问题后,开发人员开始寻找更好的解决方案。TurboFan引入V8找到解决方案。

    CODESTUBASSEMBLER

    TurboFan跨平台中间带来跨平台中间表示(IR),V8团队决定在TurboFan构建一个新的前端,他们称之为CodeStubAssembler。 CodeStubAssembler定义了一种可移植的汇编语言,开发人员可以使用该语言来实现优化的内置函数。最重要的是,可移植汇编语言的跨平台性质意味着开发人员只需要编写一个内置函数。所有支持平台和系统结构的实际机器代码都由TurboFan编译,你可以在这里阅读CSA更多信息。

    虽然这是一个很大的改进,但仍然存在一些问题。CodeStubAssembler语言编写的更佳代码需要开发人员积累大量的专业知识。即使你掌握了所有这些知识,仍然有许多非常规的漏洞容易导致安全漏洞,这导致了V8团队最终写了一个名字Torque的新组件。

    Torque

    Torque是基于CodeStubAssembler构建的语言前端与语言前端相似TypeScript语法、强大的类型系统和强大的漏洞检测函数使它成为V8开发人员编写内置函数的理想选择。Torque编译器使用CodeStubAssembler将Torque将代码转换为有效的汇编代码,大大降低了安全漏洞的数量,您可以在这里阅读更多信息Torque的信息。

    漏洞发生的原因

    由于Torque还是比较新的,所以还是需要重新实现很多CSA代码。它包括用于处理创新FixedArray和FixedDoubleArray对象的CSA它们是代码V8中的“快”数组(“快”数组有连续的数组储备,“慢”基于字典的数组有后备存储)。

    漏洞利用

    开发人员将CodeStubAssembler :: AllocateFixedArray函数重新实现为两个Torque宏,一个用于FixedArray对象,另一个用于FixedDoubleArray对象:

    若将上述函数与CodeStubAssembler :: AllocateFixedArray对比变体,会发现缺乏更大长度检查。

    NewFixedArray应确保新的返回FixedArray的长度小于FixedArray :: kMaxLength,即0x7fffffd或134217725。

    同样,NewFixedDoubleArray应该根据FixedDoubleArray :: kMaxLength(为0x3fffffe或67108862)检查数组的长度。

    在我们研究如何使用缺失的长度检查之前,让我们先了解一下Sergey是如何Trigger这个漏洞,因为它不像创造一个大于kMaxLength新数组这么简单。

    V8中的数组

    现在,我们需要了解更多V8中数组的表示。

    内存中的数组

    让我们以数组[1、2、3、4]为例,并在内存中查看。您可以通过操作带进行操作--allow-natives-syntax标志的V8,创建数组并执行数组并执行数组并创建数组并执行数组并创建数组并创建数组并执行数组并创建数组并创建数组并创建数组并执行数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并执行数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并执行并创建数组并创建数组并执行并创建数组并创建数组并创建数组并执行并创建数组并创建数组并创建数组并创建数组并执行并创建数组并创建数组并创建数组并创建数组并创建数组并执行并创建数组并执行并创建数组并创建数组并创建数组并创建数组并执行并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并执行并执行并创建数组并创建数组并创建数组并执行并创建数组并创建数组并创建数组并创建数组并执行并创建数组并创建数组并创建数组并创建数组并创建数组并执行并创建数组并创建数组并创建并创建数组并创建并创建数组并创建并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建数组并创建bugPrint(array)为了实现这以实现此目的,并使用它GDB查看内存中的地址。

    在V8实际上,当数组分配数组时分配了两个对象。请注意,每个字段的长度为4字节/ 32:

    *** Array对象是实际数组,包括四个重要字段和其他不重要字段。

                     
    • 映射指针:这决定了数组“形状”,具体来说,它确定了数组存储的元素以及其后备存储对象的类型。在这种情况下,我们的数组存储整数,后备存储区域是FixedArray。
    •                
    • 属性指针:指向存储数组可能具有的任何属性的对象。在这种情况下,除了长度,数组没有任何属性,长度存储在内联中 *** Array对象本身。
    •                
    • 元素指针:指向存储数组元素的对象。这也被称为储备,在这种情况下,储备指向FixedArray对象,稍后会详细介绍。
    •                
    • 数组长度:这是数组长度。在研究人员发布的概念证明中,这是他将长度字段覆盖为0x24242424,然后允许他越界读写。

    *** Array对象的元素指针指向后备存储,这是一个FixedArray对象,有两件关键的事要记住:

                     
    • 不需要考虑FixedArray您可以将其覆盖为任何值,但仍不能读取或写入边界。
    •                
    • 每个索引都存储在数组元素中,内存中值的表示取决于数组“元素种类”,而数组的“元素种类”这取决于原始 *** Array对象的映射。在这种情况下,这些值是一个小整数,它们是31位整数,更低设置为零。 1表示1 << 1 = 2,2表示2 << 1 = 4,按此类推。

    元素种类

    V8中数组也有“元素种类”你可以在这里找到所有元素类型的列表,但所有表的基本思想都是一样的:在V8每次创建数组时,都会用元素类型型,定义数组中包含的元素类型。最常见的三种元素如下:

    PACKED_ *** I_ELEMENTS:数组被压缩,仅包括Smis(31位小整数,第32位设置为0)。

    PACKED_DOUBLE_ELEMENTS:双精度(64位浮点值)与上述相同。

    PACKED_ELEMENTS:和上面一样,但是数组只包含参考。这意味着它可以包含任何类型的元素(整数、双精度、对象等)。

    数组也可以在元素类型之间转换,但转换只能针对更通用的元素类型,而不是更具体的元素类型。例如,它PACKED_ *** I_ELEMENTS类型的数组可以转换为HOLEY_ *** I_ELEMENTS但不能转换为类型HOLEY_ *** I_ELEMENTS类型,即填充现有孔的数组中的所有孔都不会导致压缩元素类型的变体。

    下图显示了最常见元素类型的转换格:

    事实上,我们只关心两件与元素类型相关的事情:

                     
    • *** I_ELEMENTS和DOUBLE_ELEMENTS类型的数组将其元素存储在连续的数组后备存储中,作为它们在内存中的实际表示形式。例如,数组[1.1、1.1、1.1]会将0x3ff199999999999a存储在内存中三个元素的连续数组中(0x3ff199999999999a是1.1的IEEE-754表示形式)。另一方面,PACKED_ELEMENTS类型数组将正确存储HeapNumber对象的三个连续引用包括1.1的IEEE-754表达形式。还有基于字典备份存储的元素类型,但这不是本文的重点。
    •                
    • 因为 *** I_ELEMENTS和DOUBLE_ELEMENTS类数组的元素大小不同(Smis它们是31位整数,而双精度是64位浮点型值),所以它们也有所不同kMaxLength值。

    概念验证

    Sergey提供了两个概念证明:之一个为我们提供了一个类型的数组HOLEY_ *** I_ELEMENTS类型,长度为FixedArray :: kMaxLength 3,第二个为我们提供了类型为的数组HOLEY_DOUBLE_ELEMENTS类型,长度为FixedDoubleArray :: kMaxLength 1。他只用第二个概念来构建最终的越界访问原语。

    使用两种概念证明Array.prototype.concat首先得到一个数组,它的大小正好小于相应的元素类型kMaxLength值。此操作完成后,将使用Array.prototype.splice在数组中添加更多的元素会导致其长度增加kMaxLength以上。之所以可行,是因为Array.prototype.splice间接使用新的快速路径Torque函数,如果原始数组不够大,将分配一个新的数组。出于好奇,实现这一目标的函数调用链可能如下:

    你可能想知道为什么你不能创建一个大小FixedArray::kMaxLength并使用大数组。让我们尝试一下(使用优化发行版等待的时间会更短):

    这不仅需要一些时间,还需要收到OOM(内存不足)漏洞!造成这种漏洞的原因是数组分配不会一次完成。AllocateRawFixedArray调用很多,每个调用都分配一个稍大的数组。你可以通过AllocateRawFixedArray在上面设置断点,然后分配上述数组GDB我不完全确定为什么。V8但是很多分配都会导致这样做V8内存很快就耗尽了。

    我的另一个想法是改用它FixedDoubleArray :: kMaxLength,因为它要小得多(使用优化发行版):

    这真的很有效,因为它会回到一个新的HOLEY_DOUBLE_ELEMENTS类型数组的长度设置为FixedDoubleArray :: kMaxLength 1可以用它代替array .prototype.concat。我相信这样做的原因是分配的大小0x3fffffd数组所需的分配量足够小,不会导致引擎进入OOM。

    然而,这种 *** 有两个缺点:分配和填充庞大的数组需要很多时间,因此在利用漏洞方面并不理想。另一个问题是尝试在有限的内存环境(如旧手机)中使用这种 *** Trigger漏洞可能导致发动机运行OOM。

    另一方面,Sergey之一个概念证明,它在我的电脑上花了大约2秒钟,而且内存效率很高。具体分析过程如下。

    之一个概念证明

    之一个概念证明如下,请确保你使用优化的发行版版本来运行它,否则将需要很长时间才能完成:

    让我们一步一步地分析,在[1]的位置创建一个大小0x80000使用1填充的数组。这个大小的数组需要分配大量的内存,但很少使发动机成为OOM条件。因为数组最初是空的,它得到的是HOLEY_ *** I_ELEMENTS即使将其填充为1,该元素的类型也会被保留。

    我们稍后会回到[2],但在[3]中使用0xff元素创造了新的args在[1]处创建的数组中,每个元素都被设置为数组。这使得数组args数组总共为0xff * 0x80000 = 0x7f80000个元素。在[4]处,另一个尺寸是0x7fffc的数组压入args这使其总数为数组0x7f80000 0x7fffc = 0x7fffffc个元素,0x7fffffc仅比FixedDoubleArray :: kMaxLength = 0x7fffffd小1。

    在[5],Array.prototype.concat.apply将args数组中的每个元素连接到空数组[],你可以在此处阅读有关Function.prototype.apply()更多关于如何工作的信息,但会args视为参数组,并将每个元素连接到最终数组。我们知道元素的总数是0x7fffffc,因此,最终的数组将有这么多元素。这种连接发生得更快(在我的设备上大约需要2秒),尽管它比我之前演示的简单创建数组要快得多。

    最后,在[6]处,Array.prototype.splice向这个数组添加了四个额外的元素,这意味着它的长度现在是0x8000000,即FixedArray :: kMaxLength 3。

    唯一需要解释的是[2],将属性添加到原始数组中。要理解这一点,你必须首先理解几乎所有的东西V8内置函数的约定是有快路径和慢路径。Array.prototype.concat在这种情况下,使用慢路径的简单 *** 是添加到要连接的数组中,快路径具有以下代码:

    可以看出,快速路径检查确保最终数组的长度不超过kMaxLength值。由于FixedDoubleArray::kMaxLength是FixedArray::kMaxLength因此,上述概念的一半将永远不会通过此检查。试着不使用它array.prop = 1运行代码;看看会发生什么!

    另一方面,慢路径(Slow_ArrayConcat)没有长度检查(但如果长度超过)FixedArray :: kMaxLength,它仍然会崩溃,产生致命的OOM漏洞,因为它调用的函数之一仍然会检查长度。这就是为什么研究人员使用慢路径,因为它可以绕过快路径中的检查。

    第二个概念证明(之一部分)

    虽然之一个概念证明了漏洞,可以用来使用(在第二个概念证明中,你只需要稍微修改一下trigger函数),但它需要几秒钟才能完成,这可能不理想。Sergey选择使用HOLEY_DOUBLE_ELEMENTS因为类数组。FixedDoubleArray::kMaxLength值明显小于它FixedArray变体会导致更快的速度Trigger。如果你理解了之一个概念证明,那么以下注释版本的第二个概念证明的之一部分就很容易理解:

    此时,giant_array的长度为0x3ffffff,即FixedDoubleArray :: kMaxLength 1。现在的问题是,我们如何在漏洞利用程序中使用这个数组?我们没有有用的原语,所以我们需要找到引擎的其他部分,这取决于数组的长度kMaxLength值。

    对于大多数研究人员来说,漏洞本身真的很容易找到,因为你只需要新的函数Torque实现与旧实现进行比较。虽然你知道如何使用它,但你需要比较它V8对自己有更深入的了解。Sergey采用的利用利用途径V8编译器的推测优化TurboFan,它需要自己介绍。

    本文翻译自:https://www.elttam.com/blog/simple-bugs-with-complex-exploits/#content

       
    • 评论列表:
    •  惑心鸽屿
       发布于 2022-06-01 03:30:46  回复该评论
    • 类型的转换格:事实上,我们只关心两件与元素类型相关的事情:                SMI_ELEMENTS和DOUBLE_ELEMENTS类型的数组将其元素存储在连续的

    发表评论:

    Powered By

    Copyright Your WebSite.Some Rights Reserved.