From e1eba0477560d786afc930f329d20fc4332eaa03 Mon Sep 17 00:00:00 2001 From: c4ch3c4d3 Date: Tue, 18 Aug 2026 21:43:05 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20attacks=20workflow=20=E2=80=94=20playbo?= =?UTF-8?q?ok=20steps=20+=20auto-targeting=20deauth=20panel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Evil WPA/Open pages now show a state-aware playbook (current step highlighted, contextual hint) and the Deauth Targeting panel auto-fills the live attack SSID and refreshes clients automatically. Enterprise page gains a playbook hint card. --- .../remote_access/pager-webui/www/js/views.js | 160 +++++++++++++----- 1 file changed, 121 insertions(+), 39 deletions(-) diff --git a/payload/user/remote_access/pager-webui/www/js/views.js b/payload/user/remote_access/pager-webui/www/js/views.js index 3f7e856..0088ef7 100644 --- a/payload/user/remote_access/pager-webui/www/js/views.js +++ b/payload/user/remote_access/pager-webui/www/js/views.js @@ -1394,11 +1394,41 @@ function attackLauncher(kind, opts) { } let deauthCard = null; + const ssidRef = { current: '' }; if (opts.deauth) { - deauthCard = deauthPanel(); + deauthCard = deauthPanel(ssidRef); box.appendChild(deauthCard); } + if (opts.playbook) { + const pb = h('div', { class: 'pineap-title-card' }); + pb.appendChild(h('div', { class: 'pineap-card-title' }, 'Playbook')); + const pbBody = h('div', { style: 'font-size:13px;line-height:1.8' }); + pb.appendChild(pbBody); + box.appendChild(pb); + const steps = opts.playbook.steps; + const stepEls = {}; + const stepList = h('ol', { style: 'margin:0;padding-left:20px' }); + steps.forEach((s) => { + const li = h('li', { style: 'margin-top:4px' }, h('span', { text: s })); + stepEls[s] = li; + stepList.appendChild(li); + }); + pbBody.appendChild(stepList); + const pbHint = h('div', { class: 'muted', style: 'font-size:12px;margin-top:6px' }); + pbBody.appendChild(pbHint); + const pbTick = (status, which) => { + const activeStep = opts.playbook.currentStep(status, which); + steps.forEach((s) => { + stepEls[s].style.color = s === activeStep ? 'var(--primary)' : ''; + stepEls[s].style.fontWeight = s === activeStep ? '600' : ''; + }); + pbHint.textContent = opts.playbook.hint(status, which) || ''; + }; + pbTick(null, null); + box.__pbTick = pbTick; + } + function load() { PagerAPI.get('/api/attacks/status').then((r) => { const s = r.data || {}; @@ -1416,6 +1446,11 @@ function attackLauncher(kind, opts) { text: 'Configured but interface not live yet — radio reload may still be in progress.' })); } capIface = (which && (which.band === '5' || which.band === '6')) ? 'wlan1mon' : 'wlan0mon'; + if (ssidRef) { + ssidRef.current = which && which.ssid ? which.ssid : ''; + if (ssidRef.tick) ssidRef.tick(); + } + if (box.__pbTick) box.__pbTick(s, which); if (opts.handshakes) { hsBox.innerHTML = ''; hsBox.appendChild(h('div', { class: 'pineap-card-title', text: 'Handshakes Captured (' + (s.handshakes || 0) + ')' })); @@ -1525,7 +1560,7 @@ views.harness = (root) => { return { destroy: () => {} }; }; -function deauthPanel() { +function deauthPanel(ssidRef) { const wrap = h('div', { class: 'pineap-title-card' }); wrap.appendChild(h('div', { class: 'pineap-card-title' }, 'Deauth Targeting')); const body = h('div', { style: 'font-size:13px' }); @@ -1533,46 +1568,58 @@ function deauthPanel() { const ssidBox = h('input', { placeholder: 'SSID to find clients for' }); const apSel = h('select', {}); const clTable = h('div', {}); - body.appendChild(h('div', { class: 'row' }, ssidBox, - h('div', {}, btn('Find', () => { - const q = ssidBox.value.trim(); - if (!q) return; - PagerAPI.get('/api/attacks/clients?ssid=' + encodeURIComponent(q)).then((r) => { - const d = r.data || {}; - apSel.innerHTML = ''; - (d.aps || []).forEach((a) => apSel.appendChild(h('option', { - value: (a.bssid || '') + '|' + (a.channel || 1), - text: (a.ssid || q) + ' — ' + (a.bssid || '?') + ' ch' + (a.channel || 1) + let lastLookup = ''; + function lookup(q) { + if (!q || q === lastLookup) return; + lastLookup = q; + PagerAPI.get('/api/attacks/clients?ssid=' + encodeURIComponent(q)).then((r) => { + const d = r.data || {}; + apSel.innerHTML = ''; + (d.aps || []).forEach((a) => apSel.appendChild(h('option', { + value: (a.bssid || '') + '|' + (a.channel || 1), + text: (a.ssid || q) + ' — ' + (a.bssid || '?') + ' ch' + (a.channel || 1) + }))); + if (!(d.aps || []).length) apSel.appendChild(h('option', { value: '|1', text: 'No APs found — check SSID' })); + clTable.innerHTML = ''; + const cl = (d.clients || []).slice(0, 30); + if (!cl.length) { + clTable.appendChild(h('div', { class: 'empty', text: 'No devices in recon yet.' })); + return; + } + clTable.appendChild(table( + [{ key: 'mac', label: 'MAC' }, { key: 'freq', label: 'Freq (MHz)' }, + { key: 'signal', label: 'Signal' }, { key: 'packets', label: 'Packets' }, + { key: '_deauth', label: '' }], + cl.map((c) => { + const mac = c.mac || c.client_mac || ''; + return { mac: mac || '—', freq: c.freq || '', signal: c.signal || '', + packets: c.packets || '', _deauth: btn('Deauth', () => { + const parts = apSel.value.split('|'); + if (!mac || !parts[0]) { App.toast('Pick an AP and device first', 'error'); return; } + PagerAPI.post('/api/attacks/deauth', { + bssid: parts[0], client: mac, channel: parseInt(parts[1], 10) + }).then(() => App.toast('Deauth frames sent')) + .catch((e) => App.toast(e.message || 'Deauth failed', 'error')); + }, 'danger') }; }))); - if (!(d.aps || []).length) apSel.appendChild(h('option', { value: '|1', text: 'No APs found — check SSID' })); - clTable.innerHTML = ''; - const cl = (d.clients || []).slice(0, 30); - if (!cl.length) { - clTable.appendChild(h('div', { class: 'empty', text: 'No devices in recon yet.' })); - return; - } - clTable.appendChild(table( - [{ key: 'mac', label: 'MAC' }, { key: 'freq', label: 'Freq (MHz)' }, - { key: 'signal', label: 'Signal' }, { key: 'packets', label: 'Packets' }, - { key: '_deauth', label: '' }], - cl.map((c) => { - const mac = c.mac || c.client_mac || ''; - return { mac: mac || '—', freq: c.freq || '', signal: c.signal || '', - packets: c.packets || '', _deauth: btn('Deauth', () => { - const parts = apSel.value.split('|'); - if (!mac || !parts[0]) { App.toast('Pick an AP and device first', 'error'); return; } - PagerAPI.post('/api/attacks/deauth', { - bssid: parts[0], client: mac, channel: parseInt(parts[1], 10) - }).then(() => App.toast('Deauth frames sent')) - .catch((e) => App.toast(e.message || 'Deauth failed', 'error')); - }, 'danger') }; - }))); - }).catch(() => App.toast('Lookup failed', 'error')); - })))); + }).catch(() => App.toast('Lookup failed', 'error')); + } + body.appendChild(h('div', { class: 'row' }, ssidBox, + h('div', {}, btn('Find', () => lookup(ssidBox.value.trim()))))); body.appendChild(apSel); body.appendChild(clTable); body.appendChild(h('div', { class: 'muted', style: 'font-size:12px;margin-top:4px', text: 'Only deauth targets you are authorized to test.' })); + if (ssidRef) { + ssidRef.tick = () => { + const liveSsid = ssidRef.current && ssidRef.current.trim(); + if (liveSsid && ssidBox.value.trim() !== liveSsid) { + ssidBox.value = liveSsid; + lastLookup = ''; + lookup(liveSsid); + } + }; + } return wrap; } @@ -1583,14 +1630,42 @@ views.attacks_wpa = attackLauncher('wpa', { encodings: EVIL_ENC, handshakes: true, export: true, - deauth: true + deauth: true, + playbook: { + steps: ['Deploy the evil twin', + 'Wait for a client to associate', + 'Deauth the target client to force the 4-way', + 'Export .hc22000 and crack with hashcat'], + currentStep: (s, w) => { + if (!w || !w.enabled) return 'Deploy the evil twin'; + if (!s || !(s.handshakes > 0)) return 'Wait for a client to associate'; + return 'Export .hc22000 and crack with hashcat'; + }, + hint: (s, w) => { + if (!w || !w.enabled) return '1. Set the target SSID and passphrase, pick a channel, Deploy. 2. When the target client is near, use Deauth Targeting below. 3. Captured handshakes appear above — Export and run the hashcat command.'; + if (s && s.handshakes > 0) return 'Handshake captured! Export .hc22000 and run hashcat -m 22000.'; + return 'AP is live on ' + (w.ssid || 'the target') + '. Watch the handshakes list — use Deauth Targeting to nudge the client.'; + } + } }); views.attacks_open = attackLauncher('open', { title: 'Evil Open', subtitle: 'Open network evil twin', bssid: true, - country: true + country: true, + playbook: { + steps: ['Deploy the open AP', + 'Wait for clients to associate', + 'Watch connected clients under PineAP → Clients'], + currentStep: (s, w) => { + if (!w || !w.enabled) return 'Deploy the open AP'; + return 'Wait for clients to associate'; + }, + hint: (s, w) => !w || !w.enabled + ? 'Set the SSID (optionally spoof a BSSID), pick a channel, Deploy.' + : 'Open AP is live on ' + (w.ssid || 'the target') + ' — clients that join appear in the Clients list.' + } }); views.attacks_enterprise = (root) => { @@ -1628,6 +1703,13 @@ views.attacks_enterprise = (root) => { const status = attackStatusCard(); box.appendChild(status.card); + const pb = h('div', { class: 'pineap-title-card' }); + pb.appendChild(h('div', { class: 'pineap-card-title' }, 'Playbook')); + const pbHint = h('div', { style: 'font-size:13px;line-height:1.8', text: + '1. Deploy the enterprise twin (5 GHz). 2. When a client attempts 802.1X, its identity and MSCHAPv2 challenge/response land in the tables below. 3. Crack offline: hashcat -m 5500 on the challenge data.' }); + pb.appendChild(pbHint); + box.appendChild(pb); + const pApe = h('div', { class: 'pineap-title-card' }); pApe.appendChild(h('div', { class: 'pineap-card-title' }, 'PineAPE')); const pApeBody = h('div', {});