From 622956727262d064f1944acb358d38cfdb3be4d0 Mon Sep 17 00:00:00 2001 From: zefie Date: Mon, 12 Jul 2021 19:48:35 -0400 Subject: [PATCH] full encrypted convo to splash --- hacktv_updsrv/ServiceDeps/splash.html | 21 -- .../upd/{diskmap.txt => update.txt} | 0 .../ServiceVault/htv-update/updatemenu.txt | 16 ++ .../wtv-1800/offer-open-isp-suggest.js | 17 +- .../ServiceVault/wtv-1800/preregister.js | 5 + hacktv_updsrv/ServiceVault/wtv-1800/test.js | 9 + .../wtv-head-waiter/finalize-security.js | 23 +++ .../wtv-head-waiter/login-stage-two.js | 60 +++++- .../ServiceVault/wtv-head-waiter/login.js | 90 ++++----- .../ServiceVault/wtv-home/splash.txt | 24 +++ hacktv_updsrv/ServiceVault/wtv-log/log.js | 6 +- hacktv_updsrv/app.js | 163 ++++++++++++--- hacktv_updsrv/package-lock.json | 19 -- hacktv_updsrv/package.json | 3 +- hacktv_updsrv/wtvsec.js | 189 +++++++++++------- 15 files changed, 426 insertions(+), 219 deletions(-) delete mode 100644 hacktv_updsrv/ServiceDeps/splash.html rename hacktv_updsrv/ServiceVault/htv-update/upd/{diskmap.txt => update.txt} (100%) create mode 100644 hacktv_updsrv/ServiceVault/htv-update/updatemenu.txt create mode 100644 hacktv_updsrv/ServiceVault/wtv-1800/test.js create mode 100644 hacktv_updsrv/ServiceVault/wtv-head-waiter/finalize-security.js create mode 100644 hacktv_updsrv/ServiceVault/wtv-home/splash.txt diff --git a/hacktv_updsrv/ServiceDeps/splash.html b/hacktv_updsrv/ServiceDeps/splash.html deleted file mode 100644 index 0c7ebd70..00000000 --- a/hacktv_updsrv/ServiceDeps/splash.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - -
- -
- -


-


-


- - -

- - diff --git a/hacktv_updsrv/ServiceVault/htv-update/upd/diskmap.txt b/hacktv_updsrv/ServiceVault/htv-update/upd/update.txt similarity index 100% rename from hacktv_updsrv/ServiceVault/htv-update/upd/diskmap.txt rename to hacktv_updsrv/ServiceVault/htv-update/upd/update.txt diff --git a/hacktv_updsrv/ServiceVault/htv-update/updatemenu.txt b/hacktv_updsrv/ServiceVault/htv-update/updatemenu.txt new file mode 100644 index 00000000..e2aea456 --- /dev/null +++ b/hacktv_updsrv/ServiceVault/htv-update/updatemenu.txt @@ -0,0 +1,16 @@ +200 OK +Connection: Keep-Alive +wtv-expire-all: wtv-home:/splash +Content-type: text/html + + + + + + + +Check for HackTV Updates
+Unlock Full Client (Options, Goto, etc)
+Splash test + + \ No newline at end of file diff --git a/hacktv_updsrv/ServiceVault/wtv-1800/offer-open-isp-suggest.js b/hacktv_updsrv/ServiceVault/wtv-1800/offer-open-isp-suggest.js index aa7738c4..b61b6fe0 100644 --- a/hacktv_updsrv/ServiceVault/wtv-1800/offer-open-isp-suggest.js +++ b/hacktv_updsrv/ServiceVault/wtv-1800/offer-open-isp-suggest.js @@ -6,12 +6,13 @@ if (socket_session_data[socket.id].ssid != null && !sec_session[socket_session_d headers = `200 OK Connection: Close -wtv-initial-key: ` + sec_session[socket_session_data[socket.id].ssid].challenge_key.toString(CryptoJS.enc.Base64) + ` -Content-Type: text/tellyscript +wtv-initial-key: ` + issueWTVInitialKey(socket) + ` +Content-Type: text/html wtv-service: reset -wtv-service: name=wtv-1800 host=` + pubip + ` port=1615 connections=1 -wtv-service: name=wtv-head-waiter host=` + pubip + ` port=1615 flags=0x04 flags=0x00000001 connections=1 -wtv-service: name=htv-update host=` + pubip + ` port=1615 flags=0x04 +wtv-service: name=wtv-* host=` + pubip + ` port=`+port+` flags=0x00000007 +wtv-service: name=wtv-head-waiter host=` + pubip + ` port=`+port+` flags=0x04 flags=0x00000001 connections=1 +wtv-service: name=wtv-flashrom host=` + pubip + ` port=`+port+` flags=0x00000040 +wtv-service: name=htv-update host=` + pubip + ` port=`+port+` flags=0x04 wtv-boot-url: wtv-head-waiter:/login? wtv-visit: wtv-head-waiter:/login? wtv-client-time-zone: GMT -0000 @@ -19,12 +20,12 @@ wtv-client-time-dst-rule: GMT wtv-client-date: `+strftime("%a, %d %b %Y %H:%M:%S", new Date(new Date().toUTCString()))+` GMT`; +/* var romtype = socket_session_data[socket.id].romtype; switch (romtype) { case "US-LC2-disk-0MB-8MB": - data = fs.readFileSync(__dirname + "/ServiceDeps/LC2/artemis_18006138199.tok").buffer; - //data = fs.readFileSync(__dirname + "/ServiceDeps/LC2/LC2.tok").buffer; + data = getFile("LC2/artemis_18004653537.tok",true); break; default: @@ -32,3 +33,5 @@ switch (romtype) { break; } +*/ +data=''; \ No newline at end of file diff --git a/hacktv_updsrv/ServiceVault/wtv-1800/preregister.js b/hacktv_updsrv/ServiceVault/wtv-1800/preregister.js index 10f048f6..ea2b814b 100644 --- a/hacktv_updsrv/ServiceVault/wtv-1800/preregister.js +++ b/hacktv_updsrv/ServiceVault/wtv-1800/preregister.js @@ -1,3 +1,8 @@ +var gourl = "wtv-1800:/offer-open-isp-suggest?"; +if (initial_headers['wtv-ticket']) { + gourl = "wtv-head-waiter:/login-stage-two?"; +} + headers = `200 OK Connection: Keep-Alive wtv-open-isp-disabled: false diff --git a/hacktv_updsrv/ServiceVault/wtv-1800/test.js b/hacktv_updsrv/ServiceVault/wtv-1800/test.js new file mode 100644 index 00000000..2b1f464b --- /dev/null +++ b/hacktv_updsrv/ServiceVault/wtv-1800/test.js @@ -0,0 +1,9 @@ +var wtv = new WTVNetworkSecurity(); +var test = CryptoJS.enc.Utf8.parse("this is a test"); +var test2 = wtv.wordArrayToUint8Array(test); +var test3 = CryptoJS.lib.WordArray.create(test2); +headers = `200 OK +Connection: Close +Content-type: text/plain` + +data = test3.toString(CryptoJS.enc.Utf8); \ No newline at end of file diff --git a/hacktv_updsrv/ServiceVault/wtv-head-waiter/finalize-security.js b/hacktv_updsrv/ServiceVault/wtv-head-waiter/finalize-security.js new file mode 100644 index 00000000..5bb623cb --- /dev/null +++ b/hacktv_updsrv/ServiceVault/wtv-head-waiter/finalize-security.js @@ -0,0 +1,23 @@ +var challenge_response, challenge_header = ''; +var gourl; + +if (socket_session_data[socket.id].ssid !== null) { + if (initial_headers['wtv-ticket']) { + if (initial_headers['wtv-ticket'].length > 8) { + DecodeTicket(initial_headers['wtv-ticket']); + sec_session[socket_session_data[socket.id].ssid].ticket_b64 = initial_headers['wtv-ticket']; + //socket_session_data[socket.id].secure == true; + } + } else if (sec_session[socket_session_data[socket.id].ssid].ticket_b64 == null) { + // TODO: client should have a ticket and send it back by now, if not we should handle this correctly + } +} + +headers = `200 OK +Connection: Keep-Alive +wtv-encrypted: true +wtv-ticket: `+sec_session[socket_session_data[socket.id].ssid].ticket_b64+` +wtv-expire-all: htv- +wtv-visit: wtv-home:/splash? +Content-Type: text/html +`; \ No newline at end of file diff --git a/hacktv_updsrv/ServiceVault/wtv-head-waiter/login-stage-two.js b/hacktv_updsrv/ServiceVault/wtv-head-waiter/login-stage-two.js index afef9f9c..bdbecf76 100644 --- a/hacktv_updsrv/ServiceVault/wtv-head-waiter/login-stage-two.js +++ b/hacktv_updsrv/ServiceVault/wtv-head-waiter/login-stage-two.js @@ -5,18 +5,21 @@ if (socket_session_data[socket.id].ssid !== null) { if (sec_session[socket_session_data[socket.id].ssid].ticket_b64 == null) { if (initial_headers['wtv-ticket']) { if (initial_headers['wtv-ticket'].length > 8) { - DecodeTicket(initial_headers['wtv-ticket']); + sec_session[socket_session_data[socket.id].ssid].DecodeTicket(initial_headers['wtv-ticket']); sec_session[socket_session_data[socket.id].ssid].ticket_b64 = initial_headers['wtv-ticket']; - socket_session_data[socket.id].secure == true; + //socket_session_data[socket.id].secure = true; } } else { challenge_response = sec_session[socket_session_data[socket.id].ssid].challenge_response; var client_challenge_response = initial_headers['wtv-challenge-response'] || null; if (challenge_response && client_challenge_response) { - if (challenge_response.toString(CryptoJS.enc.Base64).substring(0,85) == client_challenge_response.substring(0,85)) { + //if (challenge_response.toString(CryptoJS.enc.Base64).substring(0,85) == client_challenge_response.substring(0,85)) { + if (challenge_response.toString(CryptoJS.enc.Base64) == client_challenge_response) { console.log(" * wtv-challenge-response success for "+socket_session_data[socket.id].ssid); + if (zdebug) console.log("Response Expected:",challenge_response.toString(CryptoJS.enc.Base64)); + if (zdebug) console.log("Response Received:",client_challenge_response) sec_session[socket_session_data[socket.id].ssid].PrepareTicket(); - socket_session_data[socket.id].secure == true; + //socket_session_data[socket.id].secure = true; } else { gourl = "wtv-head-waiter:/login?reissue_challenge=true"; } @@ -35,11 +38,58 @@ wtv-visit: `+gourl+` Content-type: text/html`; data = ''; } else { + var nickname = 'HackTVUsr_'+Math.floor(Math.random() * 100000); headers = `200 OK Connection: Keep-Alive wtv-encrypted: true wtv-ticket: `+sec_session[socket_session_data[socket.id].ssid].ticket_b64+` +wtv-client-time-zone: GMT -0000 +wtv-client-date: `+strftime("%a, %d %b %Y %H:%M:%S", new Date(new Date().toUTCString()))+` GMT +wtv-country: US +wtv-language-header: en-US,en +wtv-tv-zipcode: 90210 +wtv-visit: client:closeallpanels +wtv-messagewatch-checktimeoffset: off +wtv-expire-all: client:closeallpanels +wtv-input-timeout: 14400 +wtv-connection-timeout: 90 +wtv-fader-timeout: 900 +wtv-ssl-log-url: wtv-log:/log +wtv-smartcard-inserted-message: Contacting service +user-id: 1`+Math.floor(Math.random() * 1000000000000000000)+` +wtv-transition-override: off +wtv-bypass-proxy: true +wtv-allow-dsc: true +wtv-messenger-enable: 0 +wtv-noback-all: wtv- +wtv-service: reset +wtv-service: name=wtv-1800 host=` + pubip + ` port=` + port + ` connections=1 +wtv-service: name=wtv-head-waiter host=` + pubip + ` port=` + port + ` flags=0x04 flags=0x00000001 connections=1 +wtv-service: name=htv-update host=` + pubip + ` port=` + port + ` connections=3 +wtv-service: name=wtv-log host=` + pubip + ` port=` + port + ` connections=1 +wtv-service: name=wtv-home host=` + pubip + ` port=` + port + ` flags=0x00000010 +wtv-boot-url: wtv-1800:/preregister +wtv-user-name: `+nickname+` +wtv-human-name: `+nickname+` +wtv-irc-nick: `+nickname+` +wtv-home-url: htv-update:/update? +wtv-domain: wtv.zefie.com +wtv-inactive-timeout: 0 +wtv-connection-timeout: 90 +wtv-show-time-enabled: true +wtv-fader-timeout: 900 +wtv-tourist-enabled: true +wtv-boot-url: wtv-head-waiter:/login +wtv-connection-timeout: 180 +wtv-ssl-timeout: 240 +wtv-login-timeout: 7200 +wtv-open-isp-disabled: false +wtv-log-url: wtv-log:/log +wtv-demo-mode: 0 +wtv-wink-deferrer-retries: 3 +wtv-offline-mail-enable: true +wtv-visit: wtv-head-waiter:/finalize-security? Content-Type: text/html`; - data = "hehe! stage two! test"; +data = ''; } \ No newline at end of file diff --git a/hacktv_updsrv/ServiceVault/wtv-head-waiter/login.js b/hacktv_updsrv/ServiceVault/wtv-head-waiter/login.js index db8cb13b..fd40b586 100644 --- a/hacktv_updsrv/ServiceVault/wtv-head-waiter/login.js +++ b/hacktv_updsrv/ServiceVault/wtv-head-waiter/login.js @@ -1,74 +1,58 @@ -var initialChallenge, challenge_response, challenge_header = ''; -var gourl = "wtv-head-waiter:/login-stage-two?"; +var challenge_response, challenge_header = ''; if (socket_session_data[socket.id].ssid !== null) { - if (sec_session[socket_session_data[socket.id].ssid].ticket_b64 == null) { - if (initial_headers['wtv-ticket']) { + if (initial_headers['wtv-ticket']) { + if (sec_session[socket_session_data[socket.id].ssid].ticket_b64 == null) { if (initial_headers['wtv-ticket'].length > 8) { - DecodeTicket(initial_headers['wtv-ticket']); + sec_session[socket_session_data[socket.id].ssid].DecodeTicket(initial_headers['wtv-ticket']); sec_session[socket_session_data[socket.id].ssid].ticket_b64 = initial_headers['wtv-ticket']; + //socket_session_data[socket.id].secure = true; + } + } + } else { + challenge_response = sec_session[socket_session_data[socket.id].ssid].challenge_response; + var client_challenge_response = initial_headers['wtv-challenge-response'] || null; + if (challenge_response && client_challenge_response) { + if (challenge_response.toString(CryptoJS.enc.Base64).substring(0,85) == client_challenge_response.substring(0,85)) { + console.log(" * wtv-challenge-response success for "+socket_session_data[socket.id].ssid); + sec_session[socket_session_data[socket.id].ssid].PrepareTicket(); + //socket_session_data[socket.id].secure = true; + } else { + challenge_header = "wtv-challenge: "+issueWTVChallenge(socket); } } else { - challenge_response = sec_session[socket_session_data[socket.id].ssid].challenge_response; - var client_challenge_response = initial_headers['wtv-challenge-response'] || null; - if (challenge_response && client_challenge_response) { - if (challenge_response.toString(CryptoJS.enc.Base64).substring(0,85) == client_challenge_response.substring(0,85)) { - console.log(" * wtv-challenge-response success for "+socket_session_data[socket.id].ssid); - sec_session[socket_session_data[socket.id].ssid].PrepareTicket(); - } else { - challenge_header = "wtv-challenge: "+sec_session[socket_session_data[socket.id].ssid].IssueChallenge(); - } - } else { - challenge_header = "wtv-challenge: "+sec_session[socket_session_data[socket.id].ssid].IssueChallenge(); - } + challenge_header = "wtv-challenge: "+issueWTVChallenge(socket); } } } -if (sec_session[socket_session_data[socket.id].ssid].ticket_b64) { - headers = `200 OK -Connection: Keep-Alive -wtv-encrypted: true -wtv-ticket: `+sec_session[socket_session_data[socket.id].ssid].ticket_b64+` -wtv-client-time-zone: GMT -0000 -wtv-client-date: `+strftime("%a, %d %b %Y %H:%M:%S", new Date(new Date().toUTCString()))+` GMT -wtv-country: US -wtv-language-header: en-US,en -wtv-visit: client:closeallpanels -wtv-expire-all: client:closeallpanels -wtv-noback-all: wtv- -wtv-service: reset -wtv-service: name=wtv-1800 host=` + pubip + ` port=1615 connections=1 -wtv-service: name=wtv-head-waiter host=` + pubip + ` port=1615 flags=0x04 flags=0x00000001 connections=1 -wtv-service: name=htv-update host=` + pubip + ` port=1615 flags=0x04 -wtv-boot-url: wtv-head-waiter:/login? -wtv-input-timeout: 14400 -wtv-connection-timeout: 90 -wtv-fader-timeout: 900 -wtv-ssl-log-url: wtv-log:/log -wtv-bypass-proxy: true -wtv-allow-dsc: true -wtv-messenger-enable: 0 -wtv-nameserver: 1.1.1.1 -wtv-phone-log-url: wtv-log:/log -wtv-visit: wtv-head-waiter:/login-stage-two? -Content-type: text/html` - -data = ''; -//data = fs.readFileSync(__dirname + "/ServiceDeps/splash.html"); - -} else { +if (initial_headers) { + var cookiedata = {}; + Object.keys(initial_headers).forEach(function (k) { + switch (k) { + case "wtv-capability-flags": + case "wtv-system-version": + case "wtv-client-rom-type": + case "wtv-client-bootrom-version": + case "wtv-system-chipversion": + case "wtv-system-sysconfig": + case "wtv-system-cpuspeed": + cookiedata[k] = initial_headers[k]; + break; + } + }); + cookie_dat[socket_session_data[socket.id].ssid] = CryptoJS.enc.Utf8.parse(JSON.stringify(cookiedata)).toString(CryptoJS.enc.Base64); +} headers = `200 OK Connection: Keep-Alive Expires: Wed, 09 Oct 1991 22:00:00 GMT wtv-expire-all: wtv-head-waiter: -wtv-service: name=wtv-log host=` + pubip + ` port=1615 connections=1 +wtv-service: name=wtv-log host=` + pubip + ` port=`+port+` connections=1 wtv-log-url: wtv-log:/log `+challenge_header+` wtv-relogin-url: wtv-1800:/preregister?relogin=true wtv-reconnect-url: wtv-1800:/preregister?reconnect=true -wtv-visit: `+gourl+` +wtv-visit: wtv-head-waiter:/login-stage-two? Content-type: text/html`; data = ''; -} \ No newline at end of file diff --git a/hacktv_updsrv/ServiceVault/wtv-home/splash.txt b/hacktv_updsrv/ServiceVault/wtv-home/splash.txt new file mode 100644 index 00000000..ca62b258 --- /dev/null +++ b/hacktv_updsrv/ServiceVault/wtv-home/splash.txt @@ -0,0 +1,24 @@ +200 OK +Connection: Keep-Alive +wtv-expire-all: htv- +Content-type: text/html + + + + + + + + + +
+ +
+ +
+
+ + diff --git a/hacktv_updsrv/ServiceVault/wtv-log/log.js b/hacktv_updsrv/ServiceVault/wtv-log/log.js index ef504672..9a515fec 100644 --- a/hacktv_updsrv/ServiceVault/wtv-log/log.js +++ b/hacktv_updsrv/ServiceVault/wtv-log/log.js @@ -1,10 +1,8 @@ -console.log(initial_headers); +// dummy page, we could handle the logs here. headers = `200 OK Connection: Keep-Alive -Expires: `+strftime("%a, %d %b %Y %H:%M:%S", new Date((new Date().toUTCString()) + 10))+` GMT -Content-length: 0 -Content-type: text/html`; +Content-length: 0`; data = ''; diff --git a/hacktv_updsrv/app.js b/hacktv_updsrv/app.js index e21f5ff6..0f9deb22 100644 --- a/hacktv_updsrv/app.js +++ b/hacktv_updsrv/app.js @@ -7,7 +7,6 @@ const strftime = require('strftime'); const net = require('net'); const CryptoJS = require('crypto-js'); const mime = require('mime-types'); -var str2ab = require('string-to-arraybuffer') var WTVNetworkSecurity = require('./wtvsec.js'); var zdebug = true; @@ -17,8 +16,14 @@ var pubip = "192.168.11.8"; var port = 1615; var sec_session = new Array(); +var cookie_dat = new Array(); +var socket_buffer = new Array(); var socket_session_data = new Array(); +var overrides = new Array(); +//overrides['initial_key'] = "CC5rWmRUE0o="; +//overrides['challenge'] = "0kjyqIYAu0ziFBbSERN6DGaZ6S0fT+DBUCtpHCJ4lpuM7CbXdAm+x83BIDoJYztd1Z+5KFZ7ghmb3LJCT/6mhWUYkqqKOyfPRW8ZIdbICK/CV+Kxm8EUjRXZSk/97tsmFpH3hcCJ7C2TBw+TX38uQQ=="; + function getPublicIP() { var options = { host: 'www.planeptune.org', @@ -35,6 +40,39 @@ function getPublicIP() { }); } +function getFile(path, deps = false) { + var dir = null; + if (deps) dir = __dirname + "/ServiceDeps/"; + else dir = __dirname + "/ServiceVault/"; + if (fs.lstatSync(dir + path).isFile()) { + return fs.readFileSync(dir + path, { + encoding: null, + flags: 'r' + }); + } + return null; +} + +function issueWTVInitialKey(socket) { + if (overrides['initial_key']) { + sec_session[socket_session_data[socket.id].ssid].initial_shared_key = CryptoJS.enc.Base64.parse(overrides['initial_key']); + sec_session[socket_session_data[socket.id].ssid].current_shared_key = CryptoJS.enc.Base64.parse(overrides['initial_key']); + sec_session[socket_session_data[socket.id].ssid].challenge_key = CryptoJS.enc.Base64.parse(overrides['initial_key']); + return overrides['initial_key']; + } else { + return sec_session[socket_session_data[socket.id].ssid].challenge_key.toString(CryptoJS.enc.Base64); + } +} + +function issueWTVChallenge(socket) { + if (overrides['challenge']) { + sec_session[socket_session_data[socket.id].ssid].challenge_response = sec_session[socket_session_data[socket.id].ssid].ProcessChallenge(overrides['challenge']); + return overrides['challenge']; + } else { + return sec_session[socket_session_data[socket.id].ssid].IssueChallenge(); + } +} + function doErrorPage(code) { var headers, data = null; switch (code) { @@ -62,6 +100,7 @@ function doErrorPage(code) { function processPath(socket, path, initial_headers = new Array(), query = new Array()) { var headers, data = null; var request_is_direct_file = false; + path = path.replace(/\\/g, "/"); try { try { // try to see if the exact request exists @@ -83,8 +122,17 @@ function processPath(socket, path, initial_headers = new Array(), query = new Ar // raw text format, entire payload expected (headers and content) console.log(" * Found " + path + ".txt to handle request (Raw TXT Mode)"); var fdat = fs.readFileSync(path + ".txt").toString(); - headers = fdat.split("\n\n")[0]; - data = fdat.split("\n\n")[1]; + if (fdat.indexOf("\n\n") > 0) { + var fdata = fdat.split("\n\n"); + headers = fdata[0]; + fdata.shift(); + data = fdata.join("\n"); + } else if (fdat.indexOf("\r\n\r\n") > 0) { + var fdata = fdat.split("\r\n\r\n"); + headers = fdata[0].replace(/\r/g, ""); + fdata.shift(); + data = fdata.join("\r\n"); + } } else if (fs.existsSync(path + ".js")) { // js scripting, process with vars, must set 'headers' and 'data' appropriately. // loaded script will have r/w access to any JavaScript vars this function does. @@ -164,10 +212,14 @@ function processURL(socket, initial_headers) { if (ssid == null) { ssid = initial_headers['wtv-client-serial-number']; } + var reqverb = "Request"; + if (initial_headers['encrypted'] || initial_headers['secure']) { + reqverb = "Encrypted " + reqverb; + } if (ssid != null) { - console.log(" * Request for " + initial_headers['request_url'] + " from WebTV SSID " + ssid); + console.log(" * "+reqverb+" for " + initial_headers['request_url'] + " from WebTV SSID " + ssid); } else { - console.log(" * Request for " + initial_headers['request_url']); + console.log(" * "+reqverb+" for " + initial_headers['request_url']); } // assume webtv since there is a :/ in the GET var urlToPath = __dirname + "/ServiceVault/" + shortURL.split(':/')[0] + "/" + shortURL.split(':/')[1]; @@ -226,11 +278,22 @@ function processURL(socket, initial_headers) { // set wtv-encrypted and put it near the top of the headers (unknown if needed) if (socket_session_data[socket.id].secure == true) { - console.log(" * encrypting response to client ...") + var clen = null; + if (typeof data.length !== 'undefined') { + clen = data.length; + } else if (typeof data.byteLength !== 'undefined') { + clen = data.byteLength; + } headers_obj['wtv-encrypted'] = true; headers_obj = moveObjectElement('wtv-encrypted', 'Connection', headers_obj); - var enc_data = sec_session[socket_session_data[socket.id].ssid].EncryptKey2(data); - var test = data; + if (clen > 0) { + console.log(" * Encrypting response to client ...") + if (typeof (data) === 'string') { + data = CryptoJS.enc.Utf8.parse(data); + } + var enc_data = sec_session[socket_session_data[socket.id].ssid].Encrypt(1,data); + data = enc_data; + } } headers = ""; @@ -252,7 +315,12 @@ function processURL(socket, initial_headers) { toClient = headers + "\n" + data; socket.write(toClient); } else if (typeof data == 'object') { - socket.write(new Uint8Array(concatArrayBuffer(Buffer.from(headers + "\r\n"), data))); + if (socket_session_data[socket.id].secure_headers == true) { + var enc_headers = sec_session[socket_session_data[socket.id].ssid].Encrypt(1,headers+"\n"); + socket.write(new Uint8Array(concatArrayBuffer(enc_headers, data))); + } else { + socket.write(new Uint8Array(concatArrayBuffer(Buffer.from(headers + "\n"), data))); + } } if (headers_obj['Connection']) { if (headers_obj['Connection'].toLowerCase() == "close") { @@ -297,28 +365,28 @@ function headersAreStandard(string) { // is not suffuicent. This checks for characters expected in unecrypted headers, and returns // true only if every character in the string matches the regex. Once we know the string is binary // we can better process it with the raw base64 data in processHeaders() below. - var test = /^([A-Za-z0-9\+\/\=\-\.\ \;\:\?\&\r\n\(\)\%\<\>\_]{8,})$/.test(string); + var test = /^([A-Za-z0-9\+\/\=\-\.\,\ \;\:\?\&\r\n\(\)\%\<\>\_]{8,})$/.test(string); if (zdebug) console.log("request is ascii: " + test); - if (zdebug) console.log("request as follows: " + string); + if (zdebug) console.log("request is SECURE ON: " + /^SECURE ON/.test(string)); return test; } -function processHeaders(socket, data_b64, returnHeadersBeforeSecure = false) { +function processHeaders(socket, data_hex, returnHeadersBeforeSecure = false, encryptedRequest = false) { var url = ""; - var data = CryptoJS.enc.Latin1.stringify(CryptoJS.enc.Base64.parse(data_b64)); + var data = CryptoJS.enc.Latin1.stringify(CryptoJS.enc.Hex.parse(data_hex)); var headers = new Array(); if (typeof data === "string") { if (data.length > 1) { + data = data.split("\r\n\r\n")[0]; if (headersAreStandard(data)) { - data = data.split("\r\n\r\n")[0]; data.split('\n').forEach(function (d) { if (d.length > 0) { if (/^SECURE ON/.test(d)) { - console.log(data); secure_mode = true; headers['secure'] = true; socket_session_data[socket.id].secure = true; + socket_session_data[socket.id].secure_headers = true; } if (d.indexOf(":") > 0 && d.indexOf(":/") == -1) { headers[d.split(':')[0]] = (d.split(':')[1]).replace("\r", ""); @@ -332,10 +400,25 @@ function processHeaders(socket, data_b64, returnHeadersBeforeSecure = false) { } }); } else { - // failed the headersAreStandard test, so we think this is a binary blob - var encdata = CryptoJS.enc.Base64.parse(data_b64); - var decdata = sec_session[socket_session_data[socket.id].ssid].DecryptKey1(encdata); - var test = decdata; + if (!encryptedRequest) { + // failed the headersAreStandard test, so we think this is a binary blob + if (socket_session_data[socket.id].secure != true) { + // first time so reroll sessions + sec_session[socket_session_data[socket.id].ssid].SecureOn(); + socket_session_data[socket.id].secure = true; + } + var enc_data = CryptoJS.enc.Hex.parse(data_hex.substring(header_length * 2)); + if (enc_data.sigBytes > 0) { + var dec_data = CryptoJS.lib.WordArray.create(sec_session[socket_session_data[socket.id].ssid].Decrypt(0,enc_data)); + var dec_data_text = dec_data.toString(CryptoJS.enc.Latin1); + var secure_headers = processHeaders(socket, dec_data.toString(CryptoJS.enc.Hex), true, true); + headers['encrypted'] = true; + console.log("Encrypted Request (Decrypted):", dec_data.toString(CryptoJS.enc.Latin1)); + Object.keys(secure_headers).forEach(function (k, v) { + headers[k] = secure_headers[k]; + }); + } + } } if (headers['wtv-client-rom-type'] != null) { socket_session_data[socket.id].romtype = headers['wtv-client-rom-type']; @@ -354,16 +437,23 @@ function processHeaders(socket, data_b64, returnHeadersBeforeSecure = false) { } if (headers['secure'] === true) { - // assume we have an ssid if we are this far - sec_session[headers['wtv-client-serial-number']].SecureOn(); + if (!sec_session[socket_session_data[socket.id].ssid]) { + sec_session[socket_session_data[socket.id].ssid] = new WTVNetworkSecurity(); + sec_session[socket_session_data[socket.id].ssid].DecodeTicket(headers['wtv-ticket']); + sec_session[socket_session_data[socket.id].ssid].ticket_b64 = headers['wtv-ticket']; + sec_session[socket_session_data[socket.id].ssid].SecureOn(); + } if (!headers['request_url']) { var header_length = data.length + 4; - var enc_data = CryptoJS.enc.Hex.parse(CryptoJS.enc.Base64.parse(data_b64).toString(CryptoJS.enc.Hex).substring(header_length * 2)); - console.log(enc_data.toString(CryptoJS.enc.Hex)); + var enc_data = CryptoJS.enc.Hex.parse(data_hex.substring(header_length * 2)); if (enc_data.sigBytes > 0) { - var dec_data = sec_session[socket_session_data[socket.id].ssid].DecryptKey1(enc_data); - console.log(dec_data); - console.log(headers); + var dec_data = CryptoJS.lib.WordArray.create(sec_session[socket_session_data[socket.id].ssid].Decrypt(0,enc_data)) + //var dec_data_text = dec_data.toString(CryptoJS.enc.Latin1); + var secure_headers = processHeaders(socket, dec_data.toString(CryptoJS.enc.Hex), true); + console.log("Encrypted Request (Decrypted):", secure_headers.toString(CryptoJS.enc.Latin1)); + Object.keys(secure_headers).forEach(function (k,v) { + headers[k] = secure_headers[k]; + }); } } } @@ -377,20 +467,31 @@ function processHeaders(socket, data_b64, returnHeadersBeforeSecure = false) { } var server = net.createServer(function (socket) { - socket.binaryType = 'arraybuffer'; socket.id = Math.floor(Math.random() * 1000); socket_session_data[socket.id] = []; - socket.setEncoding('base64'); //set data encoding (either 'ascii', 'utf8', or 'base64') + socket.setEncoding('hex'); //set data encoding (either 'ascii', 'utf8', or 'base64') - socket.on('data', function (data_b64) { - processURL(this, processHeaders(this, data_b64)); + socket.on('data', function (data_hex) { + socket.setTimeout(300); + if (socket_buffer[socket.id]) { + socket_buffer[socket.id].concat(CryptoJS.enc.Hex.parse(data_hex)); + } else { + socket_buffer[socket.id] = CryptoJS.enc.Hex.parse(data_hex); + } + }); + + socket.on('timeout', function () { + socket.setTimeout(0); + processURL(this, processHeaders(this, socket_buffer[socket.id].toString(CryptoJS.enc.Hex))); + socket_buffer[socket.id] = null; }); socket.on('error', (err, socket) => { console.log('client socket error:', err); }); - socket.on('end', function () { + socket.on('end', function () { + socket_buffer[socket.id] = null; secure_mode = false; socket_session_data[socket.id] = null; }); diff --git a/hacktv_updsrv/package-lock.json b/hacktv_updsrv/package-lock.json index 2684127c..7af7441a 100644 --- a/hacktv_updsrv/package-lock.json +++ b/hacktv_updsrv/package-lock.json @@ -4,11 +4,6 @@ "lockfileVersion": 1, "requires": true, "dependencies": { - "atob-lite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/atob-lite/-/atob-lite-2.0.0.tgz", - "integrity": "sha1-D+9a1G8b16hQLGVyfwNn1e5D1pY=" - }, "crypto-js": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.0.0.tgz", @@ -19,11 +14,6 @@ "resolved": "https://registry.npmjs.org/endianness/-/endianness-8.0.2.tgz", "integrity": "sha512-IU+77+jJ7lpw2qZ3NUuqBZFy3GuioNgXUdsL1L9tooDNTaw0TgOnwNuc+8Ns+haDaTifK97QLzmOANJtI/rGvw==" }, - "is-base64": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-base64/-/is-base64-0.1.0.tgz", - "integrity": "sha512-WRRyllsGXJM7ZN7gPTCCQ/6wNPTRDwiWdPK66l5sJzcU/oOzcIcRRf0Rux8bkpox/1yjt0F6VJRsQOIG2qz5sg==" - }, "mime-db": { "version": "1.48.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz", @@ -41,15 +31,6 @@ "version": "0.10.0", "resolved": "https://registry.npmjs.org/strftime/-/strftime-0.10.0.tgz", "integrity": "sha1-s/D6QZKVICpaKJ9ta+n0kJphcZM=" - }, - "string-to-arraybuffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-to-arraybuffer/-/string-to-arraybuffer-1.0.2.tgz", - "integrity": "sha512-DaGZidzi93dwjQen5I2osxR9ERS/R7B1PFyufNMnzhj+fmlDQAc1DSDIJVJhgI8Oq221efIMbABUBdPHDRt43Q==", - "requires": { - "atob-lite": "^2.0.0", - "is-base64": "^0.1.0" - } } } } diff --git a/hacktv_updsrv/package.json b/hacktv_updsrv/package.json index adc4c846..26eb241a 100644 --- a/hacktv_updsrv/package.json +++ b/hacktv_updsrv/package.json @@ -10,7 +10,6 @@ "crypto-js": "^4.0.0", "endianness": "^8.0.2", "mime-types": "^2.1.31", - "strftime": "^0.10.0", - "string-to-arraybuffer": "^1.0.2" + "strftime": "^0.10.0" } } diff --git a/hacktv_updsrv/wtvsec.js b/hacktv_updsrv/wtvsec.js index 74597edc..d95bb684 100644 --- a/hacktv_updsrv/wtvsec.js +++ b/hacktv_updsrv/wtvsec.js @@ -1,17 +1,22 @@ const CryptoJS = require('crypto-js'); const endianness = require('endianness'); +var crypto = require('crypto'); class WTVNetworkSecurity { initial_shared_key = null; current_shared_key = null; challenge_key = null; + challenge_signed_key = null; + challenge_raw = null; challenge_response = null; ticket_b64 = null; - incarnation = 1; + incarnation = 0; session_key1 = null; session_key2 = null; hRC4_Key1 = null; hRC4_Key2 = null; + RC4Session = new Array(); + zdebug = true; constructor(wtv_initial_key = CryptoJS.lib.WordArray.random(8), wtv_incarnation = 1) { @@ -22,30 +27,39 @@ class WTVNetworkSecurity { if (initial_key.sigBytes === 8) { this.incarnation = wtv_incarnation; this.initial_shared_key = initial_key; - this.current_shared_key = initial_key; + this.current_shared_key = initial_key; } else { throw ("Invalid initial key length"); } } set_incarnation(wtv_incarnation) { - this.incarnation = wtv_incarnation; - this.SecureOn(); + if (this.incarnation != wtv_incarnation) { + this.incarnation = wtv_incarnation; + this.SecureOn(); + } } increment_incarnation() { - this.incarnation = this.incarnation + 1; + this.set_incarnation(parseInt(this.incarnation) + 1); + } + + DuplicateWordArray(wa) { + return CryptoJS.lib.WordArray.create(this.wordArrayToUint8Array(wa).buffer); } PrepareTicket() { // store last challenge response in ticket - var ticket_data = this.challenge_response; + var ticket_data = this.challenge_raw; try { var ticket_data_enc = CryptoJS.DES.encrypt(ticket_data, this.current_shared_key, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.NoPadding }); - this.ticket_b64 = this.current_shared_key.concat(ticket_data_enc.ciphertext).toString(CryptoJS.enc.Base64); + // create a copy of WordArray since concat modifies the original + var current_shared_key = this.DuplicateWordArray(this.current_shared_key); + var challenge_signed_key = this.DuplicateWordArray(this.challenge_signed_key); + this.ticket_b64 = current_shared_key.concat(challenge_signed_key.concat(ticket_data_enc.ciphertext)).toString(CryptoJS.enc.Base64); } catch (e) { console.log("Error encrypting ticket: " + e.toString()); return null; @@ -54,8 +68,10 @@ class WTVNetworkSecurity { } DecodeTicket(ticket_b64) { - var ticket_hex = CryptoJS.enc.Base64.parse(ticket_b64); - var ticket_key = CryptoJS.enc.Hex.parse(ticket_hex.substring(0, this.current_shared_key.sigBytes)); + var ticket_hex = CryptoJS.enc.Base64.parse(ticket_b64).toString(CryptoJS.enc.Hex); + var ticket_key = CryptoJS.enc.Hex.parse(ticket_hex.substring(0,16)); + var challenge_key = CryptoJS.enc.Hex.parse(ticket_hex.substring(16, 32)); + var challenge_enc = CryptoJS.enc.Hex.parse(ticket_hex.substring(32)); var ticket_dec = CryptoJS.DES.decrypt( { ciphertext: challenge_enc @@ -63,38 +79,39 @@ class WTVNetworkSecurity { ticket_key, { mode: CryptoJS.mode.ECB, - padding: CryptoJS.pad.ZeroPadding + padding: CryptoJS.pad.NoPadding } ); - this.ProcessChallenge(ticket_dec); + this.ProcessChallenge(ticket_dec.toString(CryptoJS.enc.Base64), challenge_key); + console.log(" * Decoded session from wtv-ticket"); } - ProcessChallenge(wtv_challenge) { + ProcessChallenge(wtv_challenge, key = this.current_shared_key) { var challenge_raw = CryptoJS.enc.Base64.parse(wtv_challenge); if (challenge_raw.sigBytes > 8) { var challenge_raw_hex = challenge_raw.toString(CryptoJS.enc.Hex); + var challenge_id_hex = challenge_raw_hex.substring(0, (8 * 2)); var challenge_enc_hex = challenge_raw_hex.substring((8*2)); var challenge_enc = CryptoJS.enc.Hex.parse(challenge_enc_hex); - - var challenge_decrypted = CryptoJS.DES.decrypt( { ciphertext: challenge_enc }, - this.current_shared_key, + key, { mode: CryptoJS.mode.ECB, - padding: CryptoJS.pad.ZeroPadding + padding: CryptoJS.pad.NoPadding } ); var challenge_dec_hex = challenge_decrypted.toString(CryptoJS.enc.Hex); var challenge_md5_challenge = CryptoJS.MD5(CryptoJS.enc.Hex.parse(challenge_dec_hex.substring(0, (80 * 2)))); - - if (challenge_dec_hex.substring((80 * 2), (96 * 2)) == challenge_md5_challenge.toString(CryptoJS.enc.Hex)) { + var test = challenge_dec_hex.substring((80 * 2), (96 * 2)); + var test2 = challenge_md5_challenge.toString(CryptoJS.enc.Hex); + if (test == test2) { this.current_shared_key = CryptoJS.enc.Hex.parse(challenge_dec_hex.substring((72*2), (80*2))); var challenge_echo = CryptoJS.enc.Hex.parse(challenge_dec_hex.substr(0, (40*2))); @@ -102,15 +119,15 @@ class WTVNetworkSecurity { this.session_key1 = CryptoJS.enc.Hex.parse(challenge_dec_hex.substring((40*2), (56*2))); this.session_key2 = CryptoJS.enc.Hex.parse(challenge_dec_hex.substring((56*2), (72*2))); - var echo_encrypted = CryptoJS.DES.encrypt(CryptoJS.MD5(challenge_echo).concat(challenge_echo), this.current_shared_key, { + var echo_encrypted = CryptoJS.DES.encrypt(CryptoJS.MD5(challenge_echo).concat(challenge_echo).concat(CryptoJS.enc.Utf8.parse("\x08".repeat(8))), this.current_shared_key, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.NoPadding }); // Last bytes is just extra padding - var challenge_response = CryptoJS.enc.Hex.parse(challenge_raw_hex.substr(0, (8 * 2))).concat(echo_encrypted.ciphertext.concat(CryptoJS.enc.Utf8.parse("\x00".repeat(8)))); - - + this.challenge_raw = challenge_raw; + this.challenge_key = this.current_shared_key; + var challenge_response = CryptoJS.enc.Hex.parse(challenge_raw_hex.substr(0, (8 * 2))).concat(echo_encrypted.ciphertext); return challenge_response; } else { throw ("Couldn't solve challenge"); @@ -134,16 +151,19 @@ class WTVNetworkSecurity { */ - var random_id_question_mark = CryptoJS.lib.WordArray.random(8); + var challenge_id = CryptoJS.lib.WordArray.random(8); var echo_me = CryptoJS.lib.WordArray.random(40); this.session_key1 = CryptoJS.lib.WordArray.random(16); this.session_key2 = CryptoJS.lib.WordArray.random(16); var new_shared_key = CryptoJS.lib.WordArray.random(8); - - var challenge_puzzle = echo_me.concat(this.session_key1.concat(this.session_key2.concat(new_shared_key))); - var challenge_secret = challenge_puzzle.concat(CryptoJS.MD5(challenge_puzzle).concat(CryptoJS.enc.Hex.parse("\x00".repeat(8)))); + var session_key1 = this.DuplicateWordArray(this.session_key1); + var session_key2 = this.DuplicateWordArray(this.session_key2); + + var challenge_puzzle = echo_me.concat(session_key1.concat(session_key2.concat(new_shared_key))); + var challenge_secret = challenge_puzzle.concat(CryptoJS.MD5(challenge_puzzle).concat(CryptoJS.enc.Hex.parse("\x08".repeat(8)))); + // Shhhh!! var challenge_secreted = CryptoJS.DES.encrypt(challenge_secret, this.current_shared_key, { mode: CryptoJS.mode.ECB, @@ -151,14 +171,11 @@ class WTVNetworkSecurity { }); - var challenge = random_id_question_mark.concat(challenge_secreted.ciphertext); + var challenge = challenge_id.concat(challenge_secreted.ciphertext); var challenge_b64 = challenge.toString(CryptoJS.enc.Base64); - // get the expected response for when client sends it + this.challenge_signed_key = this.current_shared_key; this.challenge_response = this.ProcessChallenge(challenge_b64); - this.challenge_key = this.current_shared_key; - - this.current_shared_key = new_shared_key; return challenge_b64; } @@ -196,59 +213,77 @@ class WTVNetworkSecurity { return new Uint8Array([].concat.apply([], result)); } - SecureOn() { + + SecureOn(rc4session = null) { + console.log("Generating RC4 sessions with wtv-incarnation: " + this.incarnation); + var buf = new Uint8Array([0xff & this.incarnation, 0xff & (this.incarnation >> 8), 0xff & (this.incarnation >> 16), 0xff & (this.incarnation >> 24)]); - endianness(buf, 2); - var md5_digest_key1 = CryptoJS.MD5(this.session_key1.concat(CryptoJS.lib.WordArray.create(buf).concat(this.session_key1))); - - var next_incarnation = this.incarnation + 1; - buf = new Uint8Array([0xff & next_incarnation, 0xff & (next_incarnation >> 8), 0xff & (next_incarnation >> 16), 0xff & (next_incarnation >> 24)]); - endianness(buf, 2); - var md5_digest_key2 = CryptoJS.MD5(this.session_key2.concat(CryptoJS.lib.WordArray.create(buf).concat(this.session_key2))); - - - this.hRC4_Key1 = md5_digest_key1; - this.hRC4_Key2 = md5_digest_key2; - } - - EncryptKey1(data) { - return Buffer.from(this.wordArrayToUint8Array(this.Encrypt(this.hRC4_Key1, CryptoJS.lib.WordArray.create(data)).ciphertext)); - } - - EncryptKey2(data) { - return Buffer.from(this.wordArrayToUint8Array(this.Encrypt(this.hRC4_Key2, CryptoJS.lib.WordArray.create(data)).ciphertext)); - } - - Encrypt(key, data) { - try { - if (key != null) { - return CryptoJS.RC4.encrypt(data, key, { - mode: CryptoJS.mode.ECB, - padding: CryptoJS.pad.NoPadding - }); - } - } catch (e) { - throw ("Invalid RC4 encryption key: " + e.toString()); + endianness(buf, 4); + this.hRC4_Key1 = CryptoJS.MD5(this.DuplicateWordArray(this.session_key1).concat(CryptoJS.lib.WordArray.create(buf).concat(this.DuplicateWordArray(this.session_key1)))); + this.hRC4_Key2 = CryptoJS.MD5(this.DuplicateWordArray(this.session_key2).concat(CryptoJS.lib.WordArray.create(buf).concat(this.DuplicateWordArray(this.session_key2)))); + switch (rc4session) { + case 0: + this.RC4Session[0] = crypto.createCipheriv('rc4', Buffer.from(this.wordArrayToUint8Array(this.hRC4_Key1)),''); + break; + case 1: + this.RC4Session[1] = crypto.createDecipheriv('rc4', Buffer.from(this.wordArrayToUint8Array(this.hRC4_Key1)),''); + break; + case 2: + this.RC4Session[2] = crypto.createCipheriv('rc4', Buffer.from(this.wordArrayToUint8Array(this.hRC4_Key2)),''); + break; + case 3: + this.RC4Session[3] = crypto.createDecipheriv('rc4', Buffer.from(this.wordArrayToUint8Array(this.hRC4_Key2)),''); + break; + default: + this.RC4Session[0] = crypto.createCipheriv('rc4', Buffer.from(this.wordArrayToUint8Array(this.hRC4_Key1)), ''); + this.RC4Session[1] = crypto.createDecipheriv('rc4', Buffer.from(this.wordArrayToUint8Array(this.hRC4_Key1)), ''); + this.RC4Session[2] = crypto.createCipheriv('rc4', Buffer.from(this.wordArrayToUint8Array(this.hRC4_Key2)), ''); + this.RC4Session[3] = crypto.createDecipheriv('rc4', Buffer.from(this.wordArrayToUint8Array(this.hRC4_Key2)), ''); + break; } } - DecryptKey1(data) { - return Buffer.from(this.wordArrayToUint8Array(this.Decrypt(this.hRC4_Key1, data))); + + NewRC4Session(num) { + this.SecureOn(num); } - DecryptKey2(data) { - return Buffer.from(this.wordArrayToUint8Array(this.Decrypt(this.hRC4_Key2, data))); - } - - Decrypt(key, data) { - try { - return CryptoJS.RC4.decrypt(data, key, { - mode: CryptoJS.mode.ECB, - padding: CryptoJS.pad.NoPadding - }); - } catch (e) { - throw ("Invalid RC4 encryption key: " + e.toString()); + Encrypt(keynum, data) { + var session_id; + switch (keynum) { + case 0: + session_id = 0; + break; + case 1: + session_id = 2 + break; + default: + throw ("Invalid key option (0 or 1 only)"); + break; } + if (!this.RC4Session[session_id]) { + this.NewRC4Session(session_id); + } + return this.RC4Session[session_id].update(Buffer.from(this.wordArrayToUint8Array(data))); + } + + Decrypt(keynum, data) { + var session_id; + switch (keynum) { + case 0: + session_id = 1; + break; + case 1: + session_id = 3; + break; + default: + throw ("Invalid key option (0 or 1 only)"); + break; + } + if (!this.RC4Session[session_id]) { + this.NewRC4Session(session_id); + } + return this.RC4Session[session_id].update(Buffer.from(this.wordArrayToUint8Array(data))); } Test() {