fix challenge exchange and tickets

This commit is contained in:
zefie
2021-07-11 18:28:53 -04:00
parent 5662357981
commit e3c5c15a7c
14 changed files with 485 additions and 217 deletions

5
README.md Normal file
View File

@@ -0,0 +1,5 @@
# wtv minisrv node.js
The ***wtv minisrv***, or "***hacktv_updsrv***" project is an ambitious node.js project to have a mini WebTV server that supports wtv-encryption for advanced level access.
This open source server is not yet ready for public use, but is available for anyone wanting to try to help advance it.

View File

@@ -1,5 +1,4 @@
ANDY........z.
.`.6[......8(&.S.....}...lIa.;...b..c..d....e...f..g%.c*I.h..i!...j).k.B.lH.m.=C5*@.n
ANDY...........r`.{....}..8..".,....}...lIa.;...b..c..d....e...f..g%.c*I.h..i!...j).k.B.lH.m.=C5*@.n
.1].oQ.t..Ip.().{..Isetdtr..(C.);.Idoelay..1>..z..1....rC...}M.q.(..r..R.lIs|...w(...P)e..P;..r..0.t0.u.,~..v.,lIw;..1.v.){.i(.!..D).b;..'.P='.D.o.i(.-.>....j....IUx..y[.za.A..N8.B.=..C.C
.UzN.D..A..y~..:...NC2<..y3.N....}.a....e{2.g.$.
...=.v.[.A{.D..d
@@ -27,6 +26,9 @@ Z....&.;2
.i.*...F*.*I.G..H..hrJ..K.........#.].....?rycoun.Qy.8"..tM.=Iq..n..
.nex..seq.Len.....T.qC.==.f(..=..;R..<....Pu.....=IL...u,..[z..]Z.K.,@....s..c..rMG.-....y.|..E..-7.r.%.]..qe$...W.mQ00.E.:.%.as..J..I........!r.r.r..r.r.r .1....ma.i..?.?.*..=@rA.4.DL.[C5.T.T...lINp.ti.ck~.le#
.!.7. g..v.O*..Q......+<C9..PU...qc..A...rog3..s.iJ.fullp.op....S.......r..E....,C2.....o.Q.+L...s.:'.&!z...?.....R.)..~...+.su{cc.., in.@.]0A..(..-..)./C3<..:. ..?...:.. #..d.;.@i.i........1Y..X. u...i(Z.[PP.]&.8V...2..0...3..)|~././. >./.z./._ 7_..../.. 6..c./.. 5 ..........Z.. ..7.........lIS:..L^.Premp<..to 6pl.......a$cVO.}.,=.c...[C8n...SD...%s .@........f.bf.f.f.f.A/.Nj.j...c.......WebTV...8....0.Wai.A...+.1...a.IS..n.swee.].=.\....-...M.....V
O.(..(...yI. ....v..4.4....*....rn...k@..L(..T..U...IV..r..Sw.tv_6......S.....base.ts.f .. 77 (.ANI=(E.et;))^Eani.X..0.P.E..ldIl.....k..W....5...'......O...#.*.vi.c..9=39==s33.`
.<32...n..Io.di.=SS ....[.`]...>a[.0...@j...)..6..8,.4G.66.V.o@J 7Y.../P,..Y.5w.......
....4..g.....:.*.V.P*...G...5.qh .J..h..4....=..d....;..$.69;....;.>J...Y...J. 1a.v.:A....._...q:.Q.6;..o.5.Y.Q...h.......?.a...<......Z.." h. "+..U..{.....l..,s?..r;..1C.47..7.... .\.. .zb~!..P.8.i..P.X)....Y......,*IZ@.*Ia9c. ..lIy.q...(.=S18006.13819..6.=;Sa .mis..6..@...:.:.:.H.8.w..BUGd.....a1.....`.....i.........,..B...c.\.b...c..d..$.fcJ..g0.h..=.@,.U@-.@..@/.@0...BW.A.*C64..+P.I1..`...o.U....u.... for~..%d:%02...........l..p..r.;.S.S1. |.D.@S.D...e...e.\.\.\..\.L.A.......>.".^...S-a*.-...,..(...\.....;.<c=X.....
O.(..(...yI. ....v..4.4....*....rn ..k@..L(..Tx.bg..af....ebU..V*..W..X..Y.SW..windowsi.z;..u.........r.O.(@......ub.5.badQ(sVx. .(%d) for. Open...`.R8..$.Kr12<y.....q..SERROR. 0.......pue.od)....u:@..0..".u<..3..u>#..o..t10..L0o..u1.d.<.J.m.i.j.S.-..-.1I...t.s...a...`..o.)....e.......,1.]...!..*...]...%.br..&! P.#. =y#mQ. x....."..Y...... ..........T..).,`+..r1.ZyS.{A.,se..tsf /4 7.7 (ANI=(...et)).uan.i..w.+.#.....I)l....k_.Z..g................n{amt.rvic...9=39==33\o.
.<32..na..IoA.i.=S |....[C6.]...0..9.....p. ).NN.=C6..G.5G....V.{.....7e.6Jt.8t.4J...j..
.u.f.
.W.4..9....+...8...*...5RK.h ...h..;
..1..6....=C5 9-...6..3...K.U=^.4..;..:[..&.w.....3.w..@o..:.o.I.:..~......,.o.,.........~.I5....>....?...;.Q.{.ev..nlIUa..b..c..d...f..b..g.....!r.
r..y.........O.~........

View File

@@ -0,0 +1,21 @@
<!--- *=* Copyright 1996-99 WebTV Networks, Inc. All rights reserved. --->
<html>
<head>
<display nooptions nostatus skipback clearback fontsize=medium>
</head>
<body bgcolor="#000000" text="#449944">
<bgsound src="file://ROM/Sounds/Splash.mid">
<center>
<spacer type=block height=98 width=21>
<br>
<img src="file://rom/images/splash_logo_msn.gif">
<br><br><br>
<p><br>
<p><br>
</center>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -1,29 +1,30 @@
var ssid = initial_headers['wtv-client-serial-number'] || null;
if (ssid != null && !sec_session[ssid]) {
sec_session[ssid] = new WTVNetworkSecurity();
sec_session[ssid].IssueChallenge();
if (socket_session_data[socket.id].ssid != null && !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].IssueChallenge();
sec_session[socket_session_data[socket.id].ssid].set_incarnation(initial_headers['wtv-incarnation']);
}
headers = `200 OK
Connection: Keep-Alive
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: ` + sec_session[ssid].challenge_key.toString(CryptoJS.enc.Base64) + `
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-client-time-zone: GMT -0000
wtv-client-date: `+strftime("%a, %d %b %Y %H:%M:%S", new Date(new Date().toUTCString()))+` GMT
wtv-boot-url: wtv-head-waiter:/login?
Location: wtv-head-waiter:/login?
wtv-visit: wtv-head-waiter:/login?`;
wtv-visit: wtv-head-waiter:/login?
wtv-client-time-zone: GMT -0000
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 = getWTVROMType(initial_headers);
var romtype = socket_session_data[socket.id].romtype;
switch (romtype) {
case "US-LC2-disk-0MB-8MB":
data = fs.readFileSync(__dirname + "/ServiceVault/wtv-1800/LC2/artemis_18006138199.tok");
data = fs.readFileSync(__dirname + "/ServiceDeps/LC2/artemis_18006138199.tok").buffer;
//data = fs.readFileSync(__dirname + "/ServiceDeps/LC2/LC2.tok").buffer;
break;
default:

View File

@@ -1,4 +1,5 @@
headers = `200 OK
Connection: Keep-Alive
wtv-open-isp-disabled: false
wtv-visit: wtv-1800:/offer-open-isp-suggest?`;
wtv-visit: wtv-1800:/offer-open-isp-suggest?
Content-type: text/html`;

View File

@@ -1,17 +1,45 @@
var ssid = initial_headers['wtv-client-serial-number'] || null;
var initialChallenge, challenge_response, challenge_header = '';
var challenge_response, challenge_header = '';
var gourl;
if (ssid !== null) {
if (sec_session[ssid].ticket) {
challenge_header = "wtv-ticket: "+sec_session[ssid].ticket;
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].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 {
gourl = "wtv-head-waiter:/login?reissue_challenge=true";
}
} else {
gourl = "wtv-head-waiter:/login?no_response=true";
}
}
}
}
if (gourl) {
headers = `200 OK
wtv-ticket: `+sec_session[ssid].ticket+`
Connection: Keep-Alive
wtv-open-isp-disabled: false
wtv-visit: `+gourl+`
Content-type: text/html`;
data = '';
} else {
headers = `200 OK
Connection: Keep-Alive
wtv-encrypted: true
wtv-ticket: `+sec_session[socket_session_data[socket.id].ssid].ticket_b64+`
Content-Type: text/html`;
data = sec_session[ssid].EncryptKey1('hehe! stage two! and its encrypted!');
data = "hehe! stage two! <a href='wtv-head-waiter:/finalize-security'>test</a>";
}

View File

@@ -1,43 +1,63 @@
var ssid = initial_headers['wtv-client-serial-number'] || null;
var initialChallenge, challenge_response, challenge_header = '';
var gourl = "wtv-head-waiter:/login?reissue_challenge=true";
var gourl = "wtv-head-waiter:/login-stage-two?";
if (query['reissue_challenge']) {
gourl = "client:activ";
}
if (ssid !== null) {
if (sec_session[ssid].ticket_b64 == null) {
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']) {
DecodeTicket(initial_headers['wtv-ticket']);
sec_session[ssid].ticket_b64 = initial_headers['wtv-ticket'];
challenge_header = "wtv-ticket: "+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'];
}
} else {
challenge_response = sec_session[ssid].challenge_response;
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 "+ssid);
sec_session[ssid].PrepareTicket();
challenge_header = "wtv-ticket: "+sec_session[ssid].ticket_b64;
var gourl = "wtv-head-waiter:/login-stage-two?";
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-whatever: meh";
gourl = "wtv-1800:/preregister?";
challenge_header = "wtv-challenge: "+sec_session[socket_session_data[socket.id].ssid].IssueChallenge();
}
} else {
if (sec_session[ssid].challenge_b64 == null) {
challenge_header = "wtv-whatever: meh";
gourl = "wtv-1800:/preregister?";
} else {
challenge_header = "wtv-challenge: "+sec_session[ssid].challenge_b64;
}
challenge_header = "wtv-challenge: "+sec_session[socket_session_data[socket.id].ssid].IssueChallenge();
}
}
} else {
challenge_header = "wtv-ticket: "+sec_session[ssid].ticket_b64;
}
}
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 {
headers = `200 OK
Connection: Keep-Alive
@@ -49,9 +69,6 @@ wtv-log-url: wtv-log:/log
wtv-relogin-url: wtv-1800:/preregister?relogin=true
wtv-reconnect-url: wtv-1800:/preregister?reconnect=true
wtv-visit: `+gourl+`
Content-length: 0
Content-type: text/html`;
data = '';
}

View File

@@ -7,45 +7,17 @@ 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;
var secure_mode = true;
var pubip = "192.168.11.8";
var port = 1615;
var sec_session = new Array();
function getWTVIncarnation(headers, ssid = null) {
var incarnation = null;
headers.some(function (v) {
if (v.substring(0, 15) === "wtv-incarnation") {
incarnation = v.split(': ')[1].replace("\r", "");
return incarnation != null;
}
});
if (ssid != null && incarnation != null) {
if (sec_session[ssid] != null) {
sec_session[ssid].set_incarnation(incarnation);
if (zdebug) console.log(" * Updated wtv-incarnation for " + ssid + " to " + incarnation + " ...");
}
}
return incarnation;
}
function getWTVROMType(headers, ssid = null) {
var romtype = null;
headers.some(function (v) {
if (v.substring(0, 19) === "wtv-client-rom-type") {
romtype = v.split(': ')[1].replace("\r", "");
return romtype != null;
}
});
return romtype;
}
var socket_session_data = new Array();
function getPublicIP() {
var options = {
@@ -71,12 +43,14 @@ function doErrorPage(code) {
headers = "HTTP/1.1 404 Not Found\r\n";
headers += "Content-Type: text/html\r\n";
break;
case 500:
case 400:
data = "An internal server error has occured.";
headers = "HTTP/1.1 500 HackTV has ran into a technical problem.\r\n";
headers = "400 HackTV ran into a technical problem.\r\n";
headers += "Content-Type: text/html\r\n";
break;
default:
// what we send when we did not detect a wtv-url.
// e.g. when a pc browser connects
data = "Hello, stranger!";
headers = "HTTP/1.1 200 OK\r\n";
headers += "Content-Type: text/html\r\n";
@@ -85,7 +59,7 @@ function doErrorPage(code) {
return new Array(headers, data);
}
function processPath(path, initial_headers = new Array(), query = new Array()) {
function processPath(socket, path, initial_headers = new Array(), query = new Array()) {
var headers, data = null;
var request_is_direct_file = false;
try {
@@ -100,29 +74,29 @@ function processPath(path, initial_headers = new Array(), query = new Array()) {
if (request_is_direct_file) {
// file exists, read it and return it
console.log(" * Found " + path + " to handle request");
console.log(" * Found " + path + " to handle request (Direct File Mode)");
var contype = mime.lookup(path);
data = fs.readFileSync(path);
headers = "200 OK\r\n"
data = fs.readFileSync(path).buffer;
headers = "200 OK\n"
headers += "Content-Type: " + contype;
} else if (fs.existsSync(path + ".txt")) {
// raw text format, entire payload expected (headers and content)
console.log(" * Found " + path + ".txt to handle request");
console.log(" * Found " + path + ".txt to handle request (Raw TXT Mode)");
var fdat = fs.readFileSync(path + ".txt").toString();
headers = fdat.split("\r\n\r\n")[0];
data = fdat.split("\r\n\r\n")[1];
headers = fdat.split("\n\n")[0];
data = fdat.split("\n\n")[1];
} 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.
// any query args are in an array named 'query'
console.log(" * Found " + path + ".js to handle request");
console.log(" * Found " + path + ".js to handle request (JS Interpreter mode)");
var fdat = fs.readFileSync(path + ".js").toString();
eval(fdat);
} else if (fs.existsSync(path + ".html")) {
// Standard HTML with no headers, WTV Style
console.log(" * Found " + path + ".html to handle request");
console.log(" * Found " + path + ".html to handle request (HTML Mode)");
data = fs.readFileSync(path + ".html").toString();
headers = "200 OK\r\n"
headers = "200 OK\n"
headers += "Content-Type: text/html"
} else {
var errpage = doErrorPage(404);
@@ -137,123 +111,289 @@ function processPath(path, initial_headers = new Array(), query = new Array()) {
if (typeof headers !== "string") {
headers = headers.toString();
}
if (headers.indexOf("\r") === -1) {
headers = headers.replace("\n", "\r\n");
}
} else {
var errpage = doErrorPage(500);
var errpage = doErrorPage(400);
headers = errpage[0];
data = errpage[1];
}
if (data === null) {
data = '';
}
if (typeof data !== "string") {
data = data.toString();
}
} catch (e) {
var errpage = doErrorPage(500);
var errpage = doErrorPage(400);
headers = errpage[0];
data = errpage[1] + "<br><br><pre>" + e.toString() + "</pre>";
data = errpage[1] + "<br><br>The interpreter said:<br><pre>" + e.toString() + "</pre>";
console.log(e);
}
if (headers.toLowerCase().indexOf("content-length") === -1) {
headers += "\r\nContent-Length: " + data.length;
if (typeof data.length !== 'undefined') {
headers += "\nContent-Length: " + data.length;
} else if (typeof data.byteLength !== 'undefined') {
headers += "\nContent-Length: " + data.byteLength;
}
}
return new Array(headers, data);
}
function processURL(initial_headers, socket) {
function processURL(socket, initial_headers) {
if (initial_headers === null) {
return;
}
var shortURL, headers, data = "";
var query = new Array();
if (initial_headers['request_url'].indexOf('?') >= 0) {
shortURL = initial_headers['request_url'].split('?')[0];
var qraw = initial_headers['request_url'].split('?')[1];
if (qraw.length > 0) {
qraw = qraw.split("&");
for (let i = 0; i < qraw.length; i++) {
query[qraw[i].split("=")[0]] = qraw[i].split("=")[1];
if (initial_headers['request_url']) {
if (initial_headers['request_url'].indexOf('?') >= 0) {
shortURL = initial_headers['request_url'].split('?')[0];
var qraw = initial_headers['request_url'].split('?')[1];
if (qraw.length > 0) {
qraw = qraw.split("&");
for (let i = 0; i < qraw.length; i++) {
query[qraw[i].split("=")[0]] = qraw[i].split("=")[1];
}
if (zdebug) {
console.log("URL Request has query arguments:")
console.log(query);
}
}
if (zdebug) {
console.log("URL Request has query arguments:")
console.log(query);
} else {
shortURL = initial_headers['request_url'];
}
if (shortURL.indexOf(':/') >= 0) {
var ssid = socket_session_data[socket.id].ssid;
if (ssid == null) {
ssid = initial_headers['wtv-client-serial-number'];
}
}
} else {
shortURL = initial_headers['request_url'];
}
if (ssid != null) {
console.log(" * Request for " + initial_headers['request_url'] + " from WebTV SSID " + ssid);
} else {
console.log(" * Request for " + initial_headers['request_url']);
}
// assume webtv since there is a :/ in the GET
var urlToPath = __dirname + "/ServiceVault/" + shortURL.split(':/')[0] + "/" + shortURL.split(':/')[1];
if (zdebug) console.log(initial_headers);
var result = processPath(socket, urlToPath, initial_headers, query);
if (shortURL.indexOf(':/') >= 0) {
var ssid = initial_headers['wtv-client-serial-number'];
if (ssid != null) {
console.log(" * Request for " + initial_headers['request_url'] + " from WebTV SSID " + ssid);
} else {
console.log(" * Request for " + initial_headers['request_url']);
}
// assume webtv since there is a :/ in the GET
var urlToPath = __dirname + "/ServiceVault/" + shortURL.split(':/')[0] + "/" + shortURL.split(':/')[1];
if (zdebug) console.log(initial_headers);
var result = processPath(urlToPath, initial_headers, query);
if (result[0] == null) {
var errpage = doErrorPage(404);
headers = errpage[0];
data = errpage[1];
} else {
headers = result[0];
data = result[1];
}
} else {
switch (shortURL) {
default:
var errpage = doErrorPage(200);
if (result[0] == null) {
var errpage = doErrorPage(404);
headers = errpage[0];
data = errpage[1];
break;
} else {
headers = result[0];
data = result[1];
}
} else {
switch (shortURL) {
default:
var errpage = doErrorPage(200);
headers = errpage[0];
data = errpage[1];
break;
}
}
} else {
var errpage = doErrorPage(400);
headers = errpage[0];
data = errpage[1];
}
// headers to object
if (typeof headers != 'object') {
var headers_obj = {};
var inc_headers = 1;
headers.split('\n').forEach(function (d) {
if (d.length > 0) {
if (d.indexOf(":") > 0 && !/^([0-9]{3} )/.test(d.substring(0, 4))) {
var d = d.split(':');
var header_name = d[0];
d.shift();
if (headers_obj[header_name] != null) {
header_name = header_name + "_" + inc_headers;
inc_headers++;
}
headers_obj[header_name] = d.join(':').replace("\r", "");
if (headers_obj[header_name].substring(0, 1) == " ") {
headers_obj[header_name] = headers_obj[header_name].substring(1);
}
} else if (/^([0-9]{3} )/.test(d.substring(0, 4))) {
headers_obj['http_response'] = d.replace("\r", "");
}
}
});
} else {
header_obj = headers;
}
var toClient = headers + "\r\n\r\n" + data;
console.log(headers);
socket.write(toClient);
socket.destroy();
// 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 ...")
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;
}
headers = "";
console.log(headers_obj);
Object.keys(headers_obj).forEach(function (k) {
if (k == "http_response") {
headers += headers_obj[k] + "\r\n";
} else {
if (k.indexOf('_') >= 0) {
var j = k.split('_')[0];
headers += j + ": " + headers_obj[k] + "\n";
} else {
headers += k + ": " + headers_obj[k] + "\n";
}
}
});
var toClient = null;
if (typeof data == 'string') {
toClient = headers + "\n" + data;
socket.write(toClient);
} else if (typeof data == 'object') {
socket.write(new Uint8Array(concatArrayBuffer(Buffer.from(headers + "\r\n"), data)));
}
if (headers_obj['Connection']) {
if (headers_obj['Connection'].toLowerCase() == "close") {
socket.destroy();
}
}
}
function concatArrayBuffer(buffer1, buffer2) {
var tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength);
tmp.set(new Uint8Array(buffer1), 0);
tmp.set(new Uint8Array(buffer2), buffer1.byteLength);
return tmp.buffer;
}
var server = net.createServer(function (socket) {
socket.setEncoding("utf8"); //set data encoding (either 'ascii', 'utf8', or 'base64')
function moveObjectElement(currentKey, afterKey, obj) {
var result = {};
var val = obj[currentKey];
delete obj[currentKey];
var next = -1;
var i = 0;
if (typeof afterKey == 'undefined' || afterKey == null) afterKey = '';
Object.keys(obj).forEach(function (k) {
var v = obj[k];
if ((afterKey == '' && i == 0) || next == 1) {
result[currentKey] = val;
next = 0;
}
if (k == afterKey) { next = 1; }
result[k] = v;
++i;
});
if (next == 1) {
result[currentKey] = val;
}
if (next !== -1) return result; else return obj;
}
socket.on('data', function (data) {
var url = "";
var headers = new Array();
if (typeof data === "string") {
data.split('\n').forEach(function (d) {
if (d != "") {
if (d == "SECURE ON") {
headers['secure'] = true;
}
if (d.indexOf(": ") > 0) {
headers[d.split(': ')[0]] = (d.split(': ')[1]).replace("\r","");
} else if (/^(GET |PUT |POST)$/.test(d.substring(0, 4))) {
headers['request'] = d.replace("\r", "");
headers['request_url'] = (d.split(' ')[1]).replace("\r", "");
function headersAreStandard(string) {
// the test will see the binary compressed/enrypted data as ASCII, so a generic "isAscii"
// 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);
if (zdebug) console.log("request is ascii: " + test);
if (zdebug) console.log("request as follows: " + string);
return test;
}
function processHeaders(socket, data_b64, returnHeadersBeforeSecure = false) {
var url = "";
var data = CryptoJS.enc.Latin1.stringify(CryptoJS.enc.Base64.parse(data_b64));
var headers = new Array();
if (typeof data === "string") {
if (data.length > 1) {
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;
}
if (d.indexOf(":") > 0 && d.indexOf(":/") == -1) {
headers[d.split(':')[0]] = (d.split(':')[1]).replace("\r", "");
if (headers[d.split(':')[0]].substring(0, 1) == " ") {
headers[d.split(':')[0]] = headers[d.split(':')[0]].substring(1);
}
} else if (/^(GET |PUT |POST)$/.test(d.substring(0, 4))) {
headers['request'] = d.replace("\r", "");
headers['request_url'] = (d.split(' ')[1]).replace("\r", "");
}
}
});
} 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 (headers['wtv-client-rom-type'] != null) {
socket_session_data[socket.id].romtype = headers['wtv-client-rom-type'];
}
if (headers['wtv-client-serial-number'] != null) {
socket_session_data[socket.id].ssid = headers['wtv-client-serial-number'];
}
if (headers['wtv-incarnation'] != null) {
if (sec_session[socket_session_data[socket.id].ssid]) {
sec_session[socket_session_data[socket.id].ssid].set_incarnation(headers['wtv-incarnation']);
}
});
console.log(headers);
}
if (returnHeadersBeforeSecure) {
return headers;
}
if (headers['secure'] === true) {
// assume we have an ssid if we are this far
sec_session[headers['wtv-client-serial-number']].SecureOn();
if (!headers['request_url']) {
headers['request_url'] = "wtv-head-waiter:/login-stage-two?";
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));
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);
}
}
}
processURL(headers,this);
return headers;
} else {
// socket error, terminate it.
socket.destroy();
}
}
return null;
}
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.on('data', function (data_b64) {
processURL(this, processHeaders(this, data_b64));
});
socket.on('error', (err, socket) => {
console.log('client socket error:', err);
});
socket.on('end', function () {
secure_mode = false;
socket_session_data[socket.id] = null;
});
});
server.listen(port, '0.0.0.0');

View File

@@ -4,23 +4,25 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"arc4": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/arc4/-/arc4-3.4.0.tgz",
"integrity": "sha512-zFQmSbTwSTrICYV8kldrD3YreCtlLIKEZk+lmlNSlpUKU/cF9o746WFEy9eNqLrqSyUgAAjB3gnD9UXdK23beA==",
"requires": {
"lodash": "4.17.4"
}
"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",
"integrity": "sha512-bzHZN8Pn+gS7DQA6n+iUmBfl0hO5DJq++QP3U6uTucDtk/0iGpXd/Gg7CGR0p8tJhofJyaKoWBuJI4eAO00BBg=="
},
"lodash": {
"version": "4.17.4",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
"integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
"endianness": {
"version": "8.0.2",
"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",
@@ -39,6 +41,15 @@
"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"
}
}
}
}

View File

@@ -7,9 +7,10 @@
"name": ""
},
"dependencies": {
"arc4": "^3.4.0",
"crypto-js": "^4.0.0",
"endianness": "^8.0.2",
"mime-types": "^2.1.31",
"strftime": "^0.10.0"
"strftime": "^0.10.0",
"string-to-arraybuffer": "^1.0.2"
}
}

View File

@@ -1,19 +1,18 @@
const CryptoJS = require('crypto-js');
const rc4 = require('arc4');
const endianness = require('endianness');
class WTVNetworkSecurity {
initial_shared_key = null;
current_shared_key = null;
challenge_key = null;
challenge_response = null;
challenge_b64 = null;
ticket_b64 = null;
incarnation = 1;
session_key1 = null;
session_key2 = null;
hRC4_Key1 = null;
hRC4_Key2 = null;
zdebug = false;
zdebug = true;
constructor(wtv_initial_key = CryptoJS.lib.WordArray.random(8), wtv_incarnation = 1) {
var initial_key = wtv_initial_key;
@@ -31,6 +30,7 @@ class WTVNetworkSecurity {
set_incarnation(wtv_incarnation) {
this.incarnation = wtv_incarnation;
this.SecureOn();
}
increment_incarnation() {
@@ -45,11 +45,12 @@ class WTVNetworkSecurity {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.NoPadding
});
this.ticket_b64 = this.current_shared_key.concat(ticket_data_enc.ciphertext.toString(CryptoJS.enc.Base64));
this.ticket_b64 = this.current_shared_key.concat(ticket_data_enc.ciphertext).toString(CryptoJS.enc.Base64);
} catch (e) {
console.log("Error encrypting ticket: " + e.toString());
return null;
}
return this.ticket_b64;
}
DecodeTicket(ticket_b64) {
@@ -156,22 +157,54 @@ class WTVNetworkSecurity {
// get the expected response for when client sends it
this.challenge_response = this.ProcessChallenge(challenge_b64);
this.challenge_key = this.current_shared_key;
this.challenge_b64 = challenge_b64;
this.current_shared_key = new_shared_key;
return challenge_b64;
}
SecureOn() {
var buf = Buffer.allocUnsafe(4);
buf.writeUIntLE(this.incarnation, 0, 4);
var bigbuf = buf.readUIntBE(0, 4);
var md5_digest_key1 = CryptoJS.MD5(this.session_key1.concat(CryptoJS.lib.WordArray.create(bigbuf).concat(this.session_key1)));
wordToByteArray(word, length) {
var ba = [],
i,
xFF = 0xFF;
if (length > 0)
ba.push(word >>> 24);
if (length > 1)
ba.push((word >>> 16) & xFF);
if (length > 2)
ba.push((word >>> 8) & xFF);
if (length > 3)
ba.push(word & xFF);
buf = Buffer.allocUnsafe(4);
buf.writeUIntLE(this.incarnation, 0, 4);
bigbuf = buf.readUIntBE(0, 4);
var md5_digest_key2 = CryptoJS.MD5(this.session_key2.concat(CryptoJS.lib.WordArray.create(bigbuf).concat(this.session_key2)));
return ba;
}
wordArrayToUint8Array(wordArray, length = 0) {
if (wordArray.hasOwnProperty("sigBytes") && wordArray.hasOwnProperty("words")) {
length = wordArray.sigBytes;
wordArray = wordArray.words;
}
var result = [],
bytes,
i = 0;
while (length > 0) {
bytes = this.wordToByteArray(wordArray[i], Math.min(4, length));
length -= bytes.length;
result.push(bytes);
i++;
}
return new Uint8Array([].concat.apply([], result));
}
SecureOn() {
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;
@@ -179,34 +212,42 @@ class WTVNetworkSecurity {
}
EncryptKey1(data) {
return this.Encrypt(this.hRC4_Key1, data);
return Buffer.from(this.wordArrayToUint8Array(this.Encrypt(this.hRC4_Key1, CryptoJS.lib.WordArray.create(data)).ciphertext));
}
EncryptKey2(data) {
return this.Encrypt(this.hRC4_Key2, data);
return Buffer.from(this.wordArrayToUint8Array(this.Encrypt(this.hRC4_Key2, CryptoJS.lib.WordArray.create(data)).ciphertext));
}
Encrypt(context, data) {
if (key != null) {
return CryptoJS.RC4.encrypt(data, key);
} else {
throw ("Invalid RC4 encryption key");
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());
}
}
DecryptKey1(data) {
return this.Decrypt(this.hRC4_Key1, data);
return Buffer.from(this.wordArrayToUint8Array(this.Decrypt(this.hRC4_Key1, data)));
}
DecryptKey2(data) {
return this.Decrypt(this.hRC4_Key2, data);
return Buffer.from(this.wordArrayToUint8Array(this.Decrypt(this.hRC4_Key2, data)));
}
Decrypt(key, data) {
if (key != null) {
return CryptoJS.RC4.decrypt(data, key);
} else {
throw ("Invalid RC4 encryption key");
try {
return CryptoJS.RC4.decrypt(data, key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.NoPadding
});
} catch (e) {
throw ("Invalid RC4 encryption key: " + e.toString());
}
}