Practice for Chromium with V8 Exploit

 


1. 디버깅 환경 구성

 

 


2. 어디가 취약한가?

 

GetLastElement에서 13, 15번째 줄이 취약하다.

var a = [1,  2,  3]의 length는 3이지만, a[3]out-of-bound가 된다.

SetLastElement도 마찬가지로, 28, 32번째 줄이 취약하다.

 


3. 배열(array)은 어떻게 구성되어 있나?

 

JSArray 객체는 사실상 배열로 볼 수 있으며, 4개의 필드가 중요하다.

  • Map Pointer : 배열의 형태를 결정한다. 특히, 배열에 저장되는 요소의 종류를 결정한다. 보조 저장(Backing Store)되는 객체의 종류를 결정한다. 위의 예시에 적용하면, 배열에는 float이 저장되고, FixedArray가 보조 저장된다.
  • Properties Pointer : 배열의 속성을 저장한 객체를 가리킨다.
  • Elements Pointer : 배열의 요소가 저장된 객체를 가리킨다. Elements Pointer를 Backing Store라고도 한다. 위의 예시에 적용하면, FixedArray 객체를 가리킨다.
  • Array Length : 배열의 길이이다.

포인터는 전체 주소에서 상위 4바이트가 생략된 주소이다.

 


4. 맵(Map)이란?

 

객체(배열도 객체)의 맵은 아래의 정보를 포함하는 데이터 구조이다.

  • 객체의 동적 자료형, String, Uint8Array, HeapNumber…
  • 객체의 바이트 크기
  • 객체의 속성, 저장위치
  • 배열 요소의 자료형, 예:
  • 객체의 프로토타입

프로퍼티 값은 객체 내부에 저장되지만,  프로퍼티의 이름은 보통 맵에 저장된다.

맵은 프로퍼티 값의 정확한 위치를 제공한다.

 


5. V8에서 배열(array)은 메모리에 어떻게 할당되나?

 

Element Pointer의 마지막 요소에 바로 이어서 JSArray의 Map Pointer가 이어진다.

위에서 확인한 취약점으로 생각해보면, GetLastElement()로 JSArray의 Map Pointer를 읽을 수 있다.

또한, SetLastElement()로 JSArray의 Map Pointer를 덮어 쓸 수 있다.

 


6. Float Array vs Object Array?

 

Float Arrayindex[n]의 위치에 실제값이 들어있다.

Object Arrayindex[n]의 위치에 Object Pointer가 들어있다.

 


7. 원하는 객체의 메모리 주소를 알 수 있다?

 

Object Array의 Map Pointer, Float Array의 Map Pointer를 구한다.

Object Array의 Map Pointer는 GetLastElement로 확인할 수 없다. 디버거를 통해 실측해야 한다.

Float Array를 하나 만들고, SetLastElement로 Object Array의 Map Pointer를 인자로 전달하면, SetLastElement의 취약점 때문에 Float Array의 Map Pointer가 Object Array의 Map Pointer로 변경된다.

위에서 만든 Array의 요소 하나에 주소를 확인하고자 하는 객체를 넣어 준다. 위에서 Map Pointer를 Float Array에서 Object Array로 변경했기 때문에, 타겟 객체의 주소가 Array에 쓰여진다.

다시 SetLastElement로 Object Array에서 Float Array로 Map Pointer로 변경한다. 이렇게 하면, 타겟 객체의 주소를 확인할 수 있다.

 

 


8. 원하는 메모리 주소에 저장된 데이터를 읽을 수 있다?

 

8.1. 가짜 객체 생성

 

Float Array의 Map Pointer(fl_map)을 첫 번째 인자로 하는 배열(fake_arr)을 하나 생성한다.

fake_arr[0]의 주소(0x0008)는 fake_arr의 주소(0x0028)보다 0x20만큼 앞서 있다. fake_arr[0]의 주소(0x0008)를 인자로 fakeobj()를 호출한다.

fakeobj()에서도 Float Array(arr)를 하나 생성하고, Element(arr[0])를 fake_arr[0]의 주소(0x0008)을 바꿔준다.

그리고, arr의 Map Pointer를 Object Array의 Map Pointer(obj_map)로 변경하면, arr은 Float Array가 아니라 Object Array로 형변환된다.

이제 arr[0]에 접근하면, arr[0]에 저장된 값(0x0008)이 가리키는 객체에 접근한다.

arr[0]에는 fl_map이 저장된 주소(0x0008)가 저장되어 있기 때문에, arr[0]는 Float Array Ojbect로 인식하게 된다.

이 결과, fake_arr와 fake는 아래와 같이 메모리 영역이 중복된다.

 

8.2. 주소에서 데이터 읽기

 

8.1의 결론에 이어서, fake_arr[1]을 변경하는 것은 fake의 Elements Pointer를 수정하는 결과를 가져온다.

fake가 Float Array로 인식되기 때문에 Elements Pointer를 수정(addr+0x8)하면, 원하는 주소의 데이터를 배열의 인덱스(fake[0])로 읽어올 수 있다.

8n << 32n은 fake 객체의 Array Length를 덮어쓰기 위해서다.

 


9. 원하는 메모리 주소에 데이터를 쓸 수도 있나?

 

8과 동일하게 fake_arr[1]에 타겟 주소를 쓰고, fake[0]에 원하는 값을 쓰면 된다.

 


10. Exploit Code

 

 


11. ShellCode

 

 

 


12. 포인터 태깅(Pointer tagging)?

 

V8에서 Pointer, Double, SMI(immediate Small Interger)를 구분하기 위해 사용하는 방법이다.

v8/src/objects/objects.h에 정의되어 있다.

 


13. Exploit

 

 

 


14. 관련글