From f9e65fc96887478bfcb00a90f2eb25800a5e7fc6 Mon Sep 17 00:00:00 2001 From: echicken <echicken@bbs.electronicchicken.com> Date: Thu, 31 Dec 2015 02:52:09 -0500 Subject: [PATCH] De-obfuscate crappy RLogin client. Meh. --- mods/websocket-rlogin-service.js | 346 ++++++++++++++++++++++++++++++- 1 file changed, 340 insertions(+), 6 deletions(-) diff --git a/mods/websocket-rlogin-service.js b/mods/websocket-rlogin-service.js index 9ba685899b..7d653ec550 100644 --- a/mods/websocket-rlogin-service.js +++ b/mods/websocket-rlogin-service.js @@ -17,10 +17,342 @@ function getSession(un) { return session; } -// Obfuscated lazy port of an unfinished rlogin client I made quite some time -// ago. It does what it needs to do. Unless it doesn't - in which case, -// replace it with something better. -var RLogin=function(e){var n=this;const t=24,i=13,s=17,o=19,c=10;var r=[],p=[],u={connected:!1,cooked:!0,suspendInput:!1,suspendOutput:!1,watchForClientEscape:!0,clientHasEscaped:!1},d={rows:24,columns:80,pixelsX:640,pixelsY:480,clientEscape:"~"},f={DOT:n.disconnect,EOT:n.disconnect,SUB:function(){u.suspendInput=u.suspendInput?!1:!0,u.suspendOutput=u.suspendInput},EOM:function(){u.suspendInput=u.suspendInput?!1:!0,u.suspendOutput=!1}};this.__defineGetter__("connected",function(){return u.connected}),this.__defineSetter__("connected",function(e){"boolean"!=typeof e||e||n.disconnect()}),this.__defineGetter__("rows",function(){return d.rows}),this.__defineSetter__("rows",function(e){if(!("number"==typeof e&&e>0))throw"RLogin: Invalid 'rows' setting "+e;d.rows=e}),this.__defineGetter__("columns",function(){return d.columns}),this.__defineSetter__("columns",function(e){if(!("number"==typeof e&&e>0))throw"RLogin: Invalid 'columns' setting "+e;d.columns=e}),this.__defineGetter__("pixelsX",function(){return d.pixelsX}),this.__defineSetter__("pixelsX",function(e){if(!("number"==typeof e&&e>0))throw"RLogin: Invalid 'pixelsX' setting "+e;d.pixelsX=e}),this.__defineGetter__("pixelsY",function(){return d.pixelsY}),this.__defineSetter__("pixelsY",function(e){if(!("number"==typeof e&&e>0))throw"RLogin: Invalid 'pixelsY' setting "+e;d.pixelsY=e}),this.__defineGetter__("clientEscape",function(){return d.clientEscape}),this.__defineSetter__("clientEscape",function(e){if("string"!=typeof e||1!=e.length)throw"RLogin: Invalid 'clientEscape' setting "+e;d.clientEscape=e});var a=new Socket,l=function(){if(!(a.nread<1)){for(var e=[];a.nread>0;)e.push(a.recvBin(1));if(!u.connected)if(0==e[0]){if(u.connected=!0,!(e.length>1))return;e=e.slice(1)}else n.disconnect();u.suspendOutput||(r=r.concat(e))}};this.send=function(e){if(!u.connected)throw"RLogin.send: not connected.";if(u.suspendInput)throw"RLogin.send: input has been suspended.";"string"==typeof e&&(e=e.split("").map(function(e){return ascii(e)}));for(var n=[],r=0;r<e.length;r++)u.watchForClientEscape&&e[r]==d.clientEscape.charCodeAt(0)?(u.watchForClientEscape=!1,u.clientHasEscaped=!0):u.clientHasEscaped?(u.clientHasEscaped=!1,"undefined"!=typeof f[e[r]]&&f[e[r]]()):!u.cooked||e[r]!=s&&e[r]!=o?((r>0&&e[r-1]==i&&e[r]==c||e[r]==t)&&(u.watchForClientEscape=!0),n.push(e[r])):u.suspendOutput==(e[r]==o);p=p.concat(n)},this.receive=function(){return r.splice(0,r.length)},this.addClientEscape=function(e,n){if("string"!=typeof e&&"number"!=typeof e||"string"==typeof e&&e.length>1||"function"!=typeof n)throw"RLogin.addClientEscape: invalid arguments.";f[e.charCodeAt(0)]=n},this.connect=function(){if("number"!=typeof e.port||"string"!=typeof e.host)throw"RLogin: invalid host or port argument.";if("string"!=typeof e.clientUsername)throw"RLogin: invalid clientUsername argument.";if("string"!=typeof e.serverUsername)throw"RLogin: invalid serverUsername argument.";if("string"!=typeof e.terminalType)throw"RLogin: invalid terminalType argument.";if("number"!=typeof e.terminalSpeed)throw"RLogin: invalid terminalSpeed argument.";if(!a.connect(e.host,e.port))throw"RLogin: Unable to connect to server.";for(a.sendBin(0,1),a.send(e.clientUsername),a.sendBin(0,1),a.send(e.serverUsername),a.sendBin(0,1),a.send(e.terminalType+"/"+e.terminalSpeed),a.sendBin(0,1);a.is_connected&&a.nread<1;)mswait(5);l()},this.cycle=function(){if(l(),!(u.suspendInput||p.length<1))for(;p.length>0;)a.sendBin(p.shift(),1)},this.disconnect=function(){a.close(),u.connected=!1}}; +var RLoginClient = function(options) { + + var self = this; + + const CAN = 0x18, + CR = 0x0D, + DC1 = 0x11, + DC3 = 0x13, + DOT = 0x2E, + EOM = 0x19, + EOT = 0x04, + LF = 0x0A, + SUB = 0x1A, + DISCARD = 0x02, + RAW = 0x10, + COOKED = 0x20, + WINDOW = 0x80; + + var serverBuffer = []; // From server + var clientBuffer = []; // From client + + var state = { + connected : false, + cooked : true, + suspendInput : false, + suspendOutput : false, + watchForClientEscape : true, + clientHasEscaped : false + }; + + var properties = { + rows : 24, + columns : 80, + pixelsX : 640, + pixelsY : 480, + clientEscape : '~' + }; + + // As suggested by RFC1282 + var clientEscapes = { + DOT : self.disconnect, + EOT : self.disconnect, + SUB : function() { + state.suspendInput = (state.suspendInput) ? false : true; + state.suspendOutput = state.suspendInput; + }, + EOM : function() { + state.suspendInput = (state.suspendInput) ? false : true; + state.suspendOutput = false; + } + }; + + this.__defineGetter__('connected', function () { return state.connected; }); + + this.__defineSetter__( + 'connected', + function (value) { + if (typeof value === 'boolean' && !value) self.disconnect(); + } + ); + + this.__defineGetter__('rows', function () { return properties.rows; }); + + this.__defineSetter__( + 'rows', + function(value) { + if (typeof value === 'number' && value > 0) { + properties.rows = value; + } else { + throw 'RLogin: Invalid \'rows\' setting ' + value; + } + } + ); + + this.__defineGetter__( + 'columns', + function () { return properties.columns; } + ); + + this.__defineSetter__( + 'columns', + function (value) { + if (typeof value === 'number' && value > 0) { + properties.columns = value; + } else { + throw 'RLogin: Invalid \'columns\' setting ' + value; + } + } + ); + + this.__defineGetter__( + 'pixelsX', + function () { return properties.pixelsX; } + ); + + this.__defineSetter__( + 'pixelsX', + function (value) { + if (typeof value === 'number' && value > 0) { + properties.pixelsX = value; + } else { + throw 'RLogin: Invalid \'pixelsX\' setting ' + value; + } + } + ); + + this.__defineGetter__( + 'pixelsY', + function () { return properties.pixelsY; } + ); + + this.__defineSetter__( + 'pixelsY', + function (value) { + if (typeof value === 'number' && value > 0) { + properties.pixelsY = value; + } else { + throw 'RLogin: Invalid \'pixelsY\' setting ' + value; + } + } + ); + + this.__defineGetter__( + 'clientEscape', + function() { return properties.clientEscape; } + ); + + this.__defineSetter__( + 'clientEscape', + function (value) { + if (typeof value === 'string' && value.length === 1) { + properties.clientEscape = value; + } else { + throw 'RLogin: Invalid \'clientEscape\' setting ' + value; + } + } + ); + + var handle = new Socket(); + + function getServerData() { + + if (handle.nread < 1) return; + + var data = []; + while (handle.nread > 0) { + data.push(handle.recvBin(1)); + } + + if (!state.connected) { + if (data[0] === 0) { + state.connected = true; + if (data.length > 1) { + data = data.slice(1); + } else { + return; + } + } else { + self.disconnect(); + } + } + + // If I could tell if the TCP urgent-data pointer had been set, + // I would uncomment (and complete) this block. We'll settle + // for a partial implementation for the time being. + // We would need something to tell us if urgent data was sent, + // eg. var lookingForControlCode = urgentDataPointerIsSet(); + /* + var temp = []; + for (var d = 0; d < data.length; d++) { + if (!lookingForControlCode) { + temp.push(data[d]); + continue; + } + switch (data[d]) { + case DISCARD: + temp = []; + // We found our control code + lookingForControlCode = false; + break; + case RAW: + state.cooked = false; + lookingForControlCode = false; + break; + case COOKED: + state.cooked = true; + lookingForControlCode = false; + break; + case WINDOW: + self.sendWCCS(); + lookingForControlCode = false; + break; + default: + temp.push(data[d]); + break; + } + } + if (!state.suspendOutput) self.emit('data', new Buffer(temp)); + */ + if (!state.suspendOutput) serverBuffer = serverBuffer.concat(data); + } + + // Send a Window Change Control Sequence + // this.sendWCCS = function() { + // var magicCookie = [0xFF, 0xFF, 0x73, 0x73]; + // var rcxy = new Buffer(8); + // rcxy.writeUInt16LE(properties.rows, 0); + // rcxy.writeUInt16LE(properties.columns, 2); + // rcxy.writeUInt16LE(properties.pixelsX, 4); + // rcxy.writeUInt16LE(properties.pixelsY, 6); + // if(state.connected) + // handle.write(Buffer.concat([magicCookie, rcxy])); + // } + + // Send 'data' (String or Buffer) to the rlogin server + this.send = function (data) { + + if (!state.connected) throw 'RLogin.send: not connected.'; + if (state.suspendInput) throw 'RLogin.send: input has been suspended.'; + + if (typeof data === 'string') { + data = data.split('').map(function (d) { return ascii(d); }); + } + + var temp = []; + for (var d = 0; d < data.length; d++) { + if (state.watchForClientEscape && + data[d] == properties.clientEscape.charCodeAt(0) + ) { + state.watchForClientEscape = false; + state.clientHasEscaped = true; + continue; + } + if (state.clientHasEscaped) { + state.clientHasEscaped = false; + if (typeof clientEscapes[data[d]] !== 'undefined') { + clientEscapes[data[d]](); + } + continue; + } + if (state.cooked && (data[d] === DC1 || data[d] === DC3)) { + state.suspendOutput = (data[d] === DC3); + continue; + } + if ((d > 0 && data[d - 1] === CR && data[d] === LF) || + data[d] == CAN + ) { + state.watchForClientEscape = true; + } + temp.push(data[d]); + } + clientBuffer = clientBuffer.concat(temp); + + } + + this.receive = function () { + return serverBuffer.splice(0, serverBuffer.length); + } + + /* If 'ch' is found in client input immediately after the + 'this.clientEscape' character when: + - this is the first input after connection establishment or + - these are the first characters on a new line or + - these are the first characters after a line-cancel character + then the function 'callback' will be called. Use this to allow + client input to trigger a particular action. */ + this.addClientEscape = function (ch, callback) { + if( (typeof ch !== 'string' && typeof ch !== 'number') || + (typeof ch === 'string' && ch.length > 1) || + typeof callback !== 'function' + ) { + throw 'RLogin.addClientEscape: invalid arguments.'; + } + clientEscapes[ch.charCodeAt(0)] = callback; + } + + this.connect = function () { + + if (typeof options.port !== 'number' || + typeof options.host != 'string' + ) { + throw 'RLogin: invalid host or port argument.'; + } + + if (typeof options.clientUsername !== 'string') { + throw 'RLogin: invalid clientUsername argument.'; + } + + if (typeof options.serverUsername !== 'string') { + throw 'RLogin: invalid serverUsername argument.'; + } + + if (typeof options.terminalType !== 'string') { + throw 'RLogin: invalid terminalType argument.'; + } + + if (typeof options.terminalSpeed !== 'number') { + throw 'RLogin: invalid terminalSpeed argument.'; + } + + if (handle.connect(options.host, options.port)) { + handle.sendBin(0, 1); + handle.send(options.clientUsername); + handle.sendBin(0, 1); + handle.send(options.serverUsername); + handle.sendBin(0, 1); + handle.send(options.terminalType + '/' + options.terminalSpeed); + handle.sendBin(0, 1); + while (handle.is_connected && handle.nread < 1) { + mswait(5); + } + getServerData(); + } else { + throw 'RLogin: Unable to connect to server.'; + } + + } + + this.cycle = function () { + + getServerData(); + + if (state.suspendInput || clientBuffer.length < 1) return; + + while (clientBuffer.length > 0) { + handle.sendBin(clientBuffer.shift(), 1); + } + + } + + this.disconnect = function () { + handle.close(); + state.connected = false; + } + +} try { @@ -65,7 +397,7 @@ try { var ini = f.iniGetObject('BBS'); f.close(); - rlogin = new RLogin( + rlogin = new RLoginClient( { host : system.inet_addr, port : ini.RLoginPort, clientUsername : usr.security.password, @@ -90,11 +422,13 @@ try { rlogin.send(data); } + mswait(5); + } } catch (err) { - log(err); + log(LOG_ERR, err); } finally { rlogin.disconnect(); -- GitLab