본문 바로가기
hacking & security/system

Jerryscirpt Code Execution Vulnerability

by 탁종민 2021. 1. 7.

자바스크립트 엔진을 대략적으로 공부하고 싶어서 동기를 부여할 겸 조금 취약하다고 할 수 있는 Jerryscript를 타겟으로 겸사겸사 분석을 진행한적이 있다. 여차저차 처음으로 오픈소스 소프트웨어에서 취약점을 발견하게 되었고 공부도 많이 되었었다. 취약점을 찾긴 했지만 웹 브라우저에서 사용되는건 아니어서 크게 영향력이 있을것 같진 않았고 그래서 그냥 Jerryscript Git 페이지에 제보를 해줬다.

 

github.com/jerryscript-project/jerryscript/issues/2146

 

misuse of type in ecma_gc_mark_property function which can lead to memory corruption · Issue #2146 · jerryscript-project/jerry

in ecma_gc_mark function JERRY_ASSERT (prop_iter_p->types[0] == ECMA_PROPERTY_TYPE_HASHMAP || ECMA_PROPERTY_IS_PROPERTY_PAIR (prop_iter_p)); ecma_gc_mark_property ((ecma_property_pair_t *) prop_...

github.com

영어를 정말 못할 때 쓴 거라 영어 실력이 형편없다. 물론 지금도 잘하는 건 아니다.

 

전형적인 Type-Confusion 버그였는데 Mark and Sweep 알고리즘을 구현하는 과정에서 Type 핸들링을 제대로 해주지 않아 발생했던 취약점이다.

우선 Jerryscript의 프로퍼티를 다루는 방식에 대한 설명이 필요한데 Jerryscript는 프로퍼티들을 2개씩 pair로 ecma_property_pair_t를 이용해 관리한다. 즉 아래 코드에서 values[0], values[1] 에 각각 별개의 필드값을 가지는 식이다.

 

- ecma-globals.h

typedef struct
{
  ecma_property_header_t header; /**< header of the property */
  ecma_property_value_t values[ECMA_PROPERTY_PAIR_ITEM_COUNT]; /**< property value slots */
  jmem_cpointer_t names_cp[ECMA_PROPERTY_PAIR_ITEM_COUNT]; /**< property name slots */
} ecma_property_pair_t;

이때 만약 Object의 프로퍼티가 array라면 ecma_property_pair_t가 아니라 ecma_property_hashmap_t 라는 전용 property 관리 오브젝트를 사용한다. ecma_property_pair_t와 ecma_property_hashmap_t는 똑같이 ecma_property_header_t를 사용하는 property 관리 오브젝트이다.

 

typedef struct
{
  ecma_property_header_t header; /**< header of the property */
  uint32_t max_property_count; /**< maximum property count (power of 2) */
  uint32_t null_count; /**< number of NULLs in the map */
  uint32_t unused_count; /**< number of unused entires in the map */

  /*
   * The hash is followed by max_property_count ecma_cpointer_t
   * compressed pointers and (max_property_count + 7) / 8 bytes
   * which stores a flag for each compressed pointer.
   *
   * If the compressed pointer is equal to ECMA_NULL_POINTER
   *   - flag is cleared if the entry is NULL
   *   - flag is set if the entry is deleted
   *
   * If the compressed pointer is not equal to ECMA_NULL_POINTER
   *   - flag is cleared if the first entry of a property pair is referenced
   *   - flag is set if the second entry of a property pair is referenced
   */
} ecma_property_hashmap_t;

 

Jerryscript에서는 array의 구현을 위해 hash map을 사용하는데 hash map 생성과정을 보면 헤더 구조체 ecma_property_header_t를 설정할 때 각 0,1 번째 types을

  hashmap_p->header.types[0] = ECMA_PROPERTY_TYPE_HASHMAP;

  hashmap_p->header.types[1] = ECMA_PROPERTY_TYPE_DELETED;

으로 설정해 이 오브적트가 ecma_property_pair_t 가 아니라 ecma_property_hashmap_t 오브젝트 임을 표시한다.

 

반면에 배열의 크기가 0x10000 ( LIT_STRING_HASH_LIMIT ) 보다 크면 types[1]에 shift_counter 값을 저장해 크기에 대한 shift 값을 저장한다.

  hashmap_p->header.types[1] = shift_counter; 

 

 

 

- ecma-property-hashmap.c

ecma_property_hashmap_create (ecma_object_t *object_p){
  ...
  
  ecma_property_hashmap_t *hashmap_p = (ecma_property_hashmap_t *) jmem_heap_alloc_block_null_on_error (total_size);

  ...
  
  hashmap_p->header.types[0] = ECMA_PROPERTY_TYPE_HASHMAP;
  hashmap_p->header.types[1] = ECMA_PROPERTY_TYPE_DELETED;

  ...
  
  uint8_t shift_counter = 0;

  if (max_property_count <= LIT_STRING_HASH_LIMIT)
  {
    hashmap_p->header.types[1] = 0;
  }
  else
  {
    while (max_property_count > LIT_STRING_HASH_LIMIT)
    {
      shift_counter++;
      max_property_count >>= 1;
    }
  }

  hashmap_p->header.types[1] = shift_counter;
  ...
}

 

문제는 Mark and Sweep 과정에서 이에 대한 핸들링을 제대로 다루지 못해 발생한다. ecma_gc_mark_property 함수는 Mark and Sweep과정에서 Property들이 가비지 컬렉팅을 당하게 하지 않기 위해 Marking을 담당하는 함수이다.

즉  ecma_property_pair_t, ecma_property_hashmap_t 오브젝트를 담당하며 Marking을 한다.

아까 봤듯이 ecma_property_hashmap_t->header.types[1] 에는 shift_counter값이 들어갈 수 있지만 ecma_gc_mark_propety 함수의 코드에서는 이를 전혀 핸들링 하지 못하고 switch { case ECMA_PROPERTY_TYPE_NAMEDDATA: } 블록을 실행하게 되고 Type-Confusion이 발생하게 된다.

static void
ecma_gc_mark_property (ecma_property_pair_t *property_pair_p, /**< property pair */
                       uint32_t index) /**< property index */
{
  uint8_t property = property_pair_p->header.types[index];

  switch (ECMA_PROPERTY_GET_TYPE (property))
  {
    case ECMA_PROPERTY_TYPE_NAMEDDATA:
    {
      if (ECMA_PROPERTY_GET_NAME_TYPE (property) == ECMA_STRING_CONTAINER_MAGIC_STRING
          && property_pair_p->names_cp[index] >= LIT_NEED_MARK_MAGIC_STRING__COUNT)
      {
        break;
      }

      ecma_value_t value = property_pair_p->values[index].value;

      if (ecma_is_value_object (value))
      {
        ecma_object_t *value_obj_p = ecma_get_object_from_value (value);

        ecma_gc_set_object_visited (value_obj_p);
      }
      break;
    }

  ...
	
  }
}

 

자바스크립트 엔진에서 Type-Confusion 같은 경우는 굉장히 exploit하기 쉬운 편이어서 무난하게 exploit 할 수 있었다.

https://github.com/zbvs/opensource-research/tree/master/jerryscript/bug_1

 

GitHub - zbvs/opensource-research

Contribute to zbvs/opensource-research development by creating an account on GitHub.

github.com

 


I wanted to study Javascript Engine so I choose Jerryscript as my target because I wanted to motivate myself and it looks like vulnerable and looks like I'm able to find something. As a result I could find vulnerability for the first time in the opensource project and it was really beneficial study for me. I just reported it to Jerryscript Git page because I thought Jerryscript is not used by Web Browser so doesn't have any big effect.

 

github.com/jerryscript-project/jerryscript/issues/2146

 

misuse of type in ecma_gc_mark_property function which can lead to memory corruption · Issue #2146 · jerryscript-project/jerry

in ecma_gc_mark function JERRY_ASSERT (prop_iter_p->types[0] == ECMA_PROPERTY_TYPE_HASHMAP || ECMA_PROPERTY_IS_PROPERTY_PAIR (prop_iter_p)); ecma_gc_mark_property ((ecma_property_pair_t *) prop_...

github.com


It was a typical Type Confusion bug. They didn't handle the type of object appropriately in the Mark and Sweep code for Jerryscript.
To explain the bug, First I need to explain how Jerryscript handles Javascript property. Jerryscript makes pair for every 2 properties with ecma_property_pair_t object. For example, as we can see below, values[0] and values[1] would be each property field value.

 

- ecma-globals.h

typedef struct
{
  ecma_property_header_t header; /**< header of the property */
  ecma_property_value_t values[ECMA_PROPERTY_PAIR_ITEM_COUNT]; /**< property value slots */
  jmem_cpointer_t names_cp[ECMA_PROPERTY_PAIR_ITEM_COUNT]; /**< property name slots */
} ecma_property_pair_t;

But if the property of a Object is an array then Object doesn't use ecma_property_pair_t for the array but use ecma_property_hashmap_t. ecma_property_pair_t and ecma_property_hashmap_t both are property managing object with header field of ecma_property_header_t type.

 

typedef struct
{
  ecma_property_header_t header; /**< header of the property */
  uint32_t max_property_count; /**< maximum property count (power of 2) */
  uint32_t null_count; /**< number of NULLs in the map */
  uint32_t unused_count; /**< number of unused entires in the map */

  /*
   * The hash is followed by max_property_count ecma_cpointer_t
   * compressed pointers and (max_property_count + 7) / 8 bytes
   * which stores a flag for each compressed pointer.
   *
   * If the compressed pointer is equal to ECMA_NULL_POINTER
   *   - flag is cleared if the entry is NULL
   *   - flag is set if the entry is deleted
   *
   * If the compressed pointer is not equal to ECMA_NULL_POINTER
   *   - flag is cleared if the first entry of a property pair is referenced
   *   - flag is set if the second entry of a property pair is referenced
   */
} ecma_property_hashmap_t;

Jerryscript use hash map to implement an array. When it creats hash map, it sets 0, 1 index types of header structure ecma_property_header_t as 

  hashmap_p->header.types[0] = ECMA_PROPERTY_TYPE_HASHMAP;

  hashmap_p->header.types[1] = ECMA_PROPERTY_TYPE_DELETED;

to describe it's not ecma_property_pair_t but it is ecma_property_hashmap_t object.

 

On the other hand, if the size of array is bigger than 0x10000 ( LIT_STRING_HASH_LIMIT ) it sets shift_counter value at types[1] as a shift value for size.

  hashmap_p->header.types[1] = shift_counter; 

 

- ecma-property-hashmap.c

ecma_property_hashmap_create (ecma_object_t *object_p){
  ...
  
  ecma_property_hashmap_t *hashmap_p = (ecma_property_hashmap_t *) jmem_heap_alloc_block_null_on_error (total_size);

  ...
  
  hashmap_p->header.types[0] = ECMA_PROPERTY_TYPE_HASHMAP;
  hashmap_p->header.types[1] = ECMA_PROPERTY_TYPE_DELETED;

  ...
  
  uint8_t shift_counter = 0;

  if (max_property_count <= LIT_STRING_HASH_LIMIT)
  {
    hashmap_p->header.types[1] = 0;
  }
  else
  {
    while (max_property_count > LIT_STRING_HASH_LIMIT)
    {
      shift_counter++;
      max_property_count >>= 1;
    }
  }

  hashmap_p->header.types[1] = shift_counter;
  ...
}

The problem was, Mark and Sweep didn't handle this special case appropriately. ecma_gc_mark_propety is a function that prevents garbage collecting an object of property field by marking. This function inspects ecma_property_pair_t, ecma_property_hashmap_t objects and marks them.

As we saw, there can be shift_counter value in ecma_property_hashmap_t->header.types[1] but ecma_gc_mark_propety function's code doesn't handle it at all and executes switch { case ECMA_PROPERTY_TYPE_NAMEDDATA: } block code which leads to Type-Confusion.

static void
ecma_gc_mark_property (ecma_property_pair_t *property_pair_p, /**< property pair */
                       uint32_t index) /**< property index */
{
  uint8_t property = property_pair_p->header.types[index];

  switch (ECMA_PROPERTY_GET_TYPE (property))
  {
    case ECMA_PROPERTY_TYPE_NAMEDDATA:
    {
      if (ECMA_PROPERTY_GET_NAME_TYPE (property) == ECMA_STRING_CONTAINER_MAGIC_STRING
          && property_pair_p->names_cp[index] >= LIT_NEED_MARK_MAGIC_STRING__COUNT)
      {
        break;
      }

      ecma_value_t value = property_pair_p->values[index].value;

      if (ecma_is_value_object (value))
      {
        ecma_object_t *value_obj_p = ecma_get_object_from_value (value);

        ecma_gc_set_object_visited (value_obj_p);
      }
      break;
    }

  ...
	
  }
}

 

Usually, Type-Confusion in Javascript Engine is easy to exploit and actually it was too.

https://github.com/zbvs/opensource-research/tree/master/jerryscript/bug_1

 

GitHub - zbvs/opensource-research

Contribute to zbvs/opensource-research development by creating an account on GitHub.

github.com

 

 

'hacking & security > system' 카테고리의 다른 글

Game and Messenger Hooking  (0) 2021.01.07

댓글