// C2 模块前端逻辑 - 完整实现
// 支持: xterm 终端、文件管理、监听器/会话/任务/事件/Payload/Profile 管理
(function() {
'use strict';
// C2 模块命名空间
const C2 = {
currentPage: '',
listeners: [],
sessions: [],
tasks: [],
tasksPage: 1,
tasksPageSize: 10,
tasksTotal: 0,
tasksPendingQueuedCount: null,
events: [],
eventsPage: 1,
eventsPageSize: 10,
eventsTotal: 0,
profiles: [],
selectedSessionId: null,
selectedListenerId: null,
eventSource: null,
// xterm 相关
terminalInstance: null,
terminalFitAddon: null,
terminalResizeObserver: null,
terminalContainer: null,
terminalSessionId: null,
terminalHistory: {},
terminalLogs: {},
terminalBusy: false,
terminalQueue: [],
// 文件管理
currentPath: '.',
implantPwd: null,
fileList: [],
fileUploadBusy: false,
// 任务轮询
taskPollInterval: null,
};
// API 基础路径
const API_BASE = '/api/c2';
window.__c2DownloadPayload = function(filename) {
const url = `${API_BASE}/payloads/${filename}/download`;
const fetchFn = (typeof apiFetch === 'function') ? apiFetch : fetch;
fetchFn(url).then(resp => {
if (!resp.ok) throw new Error('download failed: ' + resp.status);
return resp.blob();
}).then(blob => {
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = filename;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(a.href);
}).catch(err => {
if (window.showToast) window.showToast(err.message, 'error');
});
};
function c2t(key, opts) {
try {
if (typeof window.t === 'function') return window.t(key, opts || {});
} catch (e) {}
return key;
}
function listenerTypeLabel(type) {
if (!type) return '';
const k = 'c2.listeners.typeLabels.' + String(type).toLowerCase();
const tr = c2t(k);
if (tr !== k) return tr;
return String(type).replace(/_/g, ' ');
}
function sessionStatusLabel(status) {
const s = String(status || '').toLowerCase();
if (!s) return '';
const k = 'c2.sessions.' + s;
const tr = c2t(k);
if (tr !== k) return tr;
return status;
}
function taskStatusLabel(status) {
const s = String(status || '').toLowerCase();
if (!s) return '';
const k = 'c2.tasks.' + s;
const tr = c2t(k);
if (tr !== k) return tr;
return status;
}
function formatTaskCommand(task) {
if (!task) return '';
const type = String(task.taskType || '').toLowerCase();
const p = task.payload;
if (!p || typeof p !== 'object' || Object.keys(p).length === 0) {
if (type === 'pwd' || type === 'ps' || type === 'screenshot') return type;
return '';
}
switch (type) {
case 'shell':
case 'exec':
return p.command != null ? String(p.command) : '';
case 'ls':
case 'cd':
return p.path != null ? String(p.path) : '';
case 'download':
return p.remote_path != null ? String(p.remote_path) : '';
case 'upload':
if (p.remote_path) return String(p.remote_path);
if (p.file_id) return 'file:' + String(p.file_id);
return '';
case 'kill_proc':
return p.pid != null ? 'pid:' + String(p.pid) : '';
case 'sleep':
let sleepStr = p.seconds != null ? 'sleep ' + p.seconds + 's' : '';
if (p.jitter != null) sleepStr += (sleepStr ? ', ' : '') + 'jitter ' + p.jitter + '%';
return sleepStr;
case 'port_fwd':
return [p.action, p.remote_host, p.remote_port, p.local_port].filter(v => v != null && v !== '').join(':');
case 'socks_start':
case 'socks_stop':
return p.port != null ? 'port:' + String(p.port) : type;
case 'load_assembly':
if (p.args) return String(p.args);
if (p.file_id) return 'file:' + String(p.file_id);
return '';
case 'persist':
return p.method != null ? String(p.method) : '';
default:
try { return JSON.stringify(p); } catch (e) { return ''; }
}
}
function truncateCommand(cmd, maxLen) {
if (!cmd) return '';
const s = String(cmd);
if (!maxLen || s.length <= maxLen) return s;
return s.substring(0, maxLen - 1) + '\u2026';
}
// ============================================================================
// 工具函数
// ============================================================================
function apiRequest(method, url, data) {
const options = {
method: method,
headers: { 'Content-Type': 'application/json' }
};
if (data && (method === 'POST' || method === 'PUT' || method === 'PATCH' || method === 'DELETE')) {
options.body = JSON.stringify(data);
}
if (typeof apiFetch === 'function') {
return apiFetch(url, options).then(r => r.json());
}
return fetch(url, options).then(r => r.json());
}
function showToast(message, type = 'info') {
if (window.showToast) {
window.showToast(message, type);
return;
}
const container = document.getElementById('c2-toast-container') || (() => {
const div = document.createElement('div');
div.id = 'c2-toast-container';
div.style.cssText = 'position:fixed;top:20px;right:20px;z-index:10100;display:flex;flex-direction:column;gap:8px;';
document.body.appendChild(div);
return div;
})();
container.style.zIndex = '10100';
const toast = document.createElement('div');
const colors = { error: '#e53e3e', success: '#38a169', info: '#3182ce', warn: '#d69e2e' };
toast.style.cssText = `background:${colors[type] || colors.info};color:#fff;padding:10px 18px;border-radius:6px;font-size:0.875rem;box-shadow:0 4px 12px rgba(0,0,0,0.2);opacity:0;transition:opacity .3s;max-width:400px;word-break:break-word;`;
toast.textContent = message;
container.appendChild(toast);
requestAnimationFrame(() => { toast.style.opacity = '1'; });
setTimeout(() => {
toast.style.opacity = '0';
setTimeout(() => toast.remove(), 300);
}, 3500);
}
function formatTime(dateStr) {
if (!dateStr) return '-';
return new Date(dateStr).toLocaleString();
}
function formatDuration(ms) {
if (!ms || ms <= 0) return '-';
if (ms < 1000) return c2t('c2.fmt.durationMs', { n: ms });
if (ms < 60000) return c2t('c2.fmt.durationSec', { n: (ms / 1000).toFixed(1) });
return c2t('c2.fmt.durationMin', { n: (ms / 60000).toFixed(1) });
}
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
/** 任务列表操作按钮(查看/取消/删除)— 事件委托 */
function bindC2TaskActionDelegation() {
if (document.documentElement.dataset.c2TaskActionsBound === '1') return;
document.documentElement.dataset.c2TaskActionsBound = '1';
document.addEventListener('click', function(e) {
const btn = e.target.closest('[data-c2-task-action]');
if (!btn) return;
e.preventDefault();
e.stopPropagation();
const action = btn.getAttribute('data-c2-task-action');
const id = btn.getAttribute('data-task-id');
if (!id) return;
if (action === 'view') C2.viewTask(id);
else if (action === 'cancel') C2.cancelTask(id);
else if (action === 'delete') C2.deleteTaskById(id);
});
}
bindC2TaskActionDelegation();
/** 监听器表单:Malleable Profile 下拉选项 HTML(value / 文本已转义) */
function listenerProfileSelectHtml(selectedProfileId) {
const sel = selectedProfileId ? String(selectedProfileId) : '';
let opts = `${escapeHtml(c2t('c2.listeners.malleableProfileNone'))} `;
for (const p of (C2.profiles || [])) {
if (!p) continue;
const pid = p.id || p.ID;
if (!pid) continue;
const idEsc = escapeHtml(String(pid));
const nameEsc = escapeHtml(p.name || pid);
const selected = sel && String(pid) === sel ? ' selected' : '';
opts += `${nameEsc} `;
}
return opts;
}
function listenerResolvedProfileId(l) {
if (!l) return '';
const v = l.profileId != null && l.profileId !== '' ? l.profileId : l.profile_id;
return v != null ? String(v).trim() : '';
}
/** 监听器卡片展示用 Profile 名称(依赖 C2.profiles,由 loadListeners 一并拉取) */
function listenerProfileDisplayName(l) {
const pid = listenerResolvedProfileId(l);
if (!pid) return '';
const list = C2.profiles || [];
for (let i = 0; i < list.length; i++) {
const p = list[i];
if (p && (p.id === pid || p.ID === pid)) return String(p.name || p.id || pid).trim() || pid;
}
return pid.length > 18 ? pid.substring(0, 16) + '…' : pid;
}
function listenerTypeVisualClass(type) {
const t = String(type || '').toLowerCase();
if (t === 'https_beacon') return 'c2-ltype-mark--https';
if (t === 'http_beacon') return 'c2-ltype-mark--http';
if (t === 'tcp_reverse') return 'c2-ltype-mark--tcp';
if (t === 'websocket') return 'c2-ltype-mark--ws';
return 'c2-ltype-mark--def';
}
function listenerTypeShortLabel(type) {
const t = String(type || '').toLowerCase();
if (t === 'https_beacon') return 'HTTPS';
if (t === 'http_beacon') return 'HTTP';
if (t === 'tcp_reverse') return 'TCP';
if (t === 'websocket') return 'WS';
return '?';
}
function listenerCardStatusPillLabel(status) {
const s = String(status || '').toLowerCase();
if (s === 'running') return c2t('c2.listeners.running');
if (s === 'stopped') return c2t('c2.listeners.stopped');
if (s === 'error') return c2t('c2.listeners.statusError');
return c2t('c2.listeners.stopped');
}
/** 避免 i18n 插值把日期里的「/」转成 /,与 formatTime 拼接后整体转义 */
function formatListenerStartedHtml(dateStr) {
if (!dateStr) return '';
const prefix = c2t('c2.listeners.startedAtPrefix');
const time = formatTime(dateStr);
return '
' + escapeHtml(prefix) + ' ' + escapeHtml(time) + '
';
}
function copyToClipboard(text) {
if (navigator.clipboard) {
navigator.clipboard.writeText(text).then(() => showToast(c2t('c2.clipboardCopied'), 'success'));
} else {
const ta = document.createElement('textarea');
ta.value = text;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
document.body.removeChild(ta);
showToast(c2t('c2.clipboardCopied'), 'success');
}
}
// ============================================================================
// 页面初始化
// ============================================================================
C2.init = function() {
const pageId = window.currentPageId || '';
if (pageId.startsWith('c2')) {
C2.connectEventStream();
}
switch(pageId) {
case 'c2':
case 'c2-listeners':
C2.loadListeners();
break;
case 'c2-sessions':
C2.loadSessions();
C2.ensureListenersLoaded();
break;
case 'c2-tasks':
C2.loadTasks();
break;
case 'c2-payloads':
C2.loadListenersForPayload();
break;
case 'c2-events':
C2.loadEvents();
break;
case 'c2-profiles':
C2.loadProfiles();
break;
}
};
// ============================================================================
// 监听器管理
// ============================================================================
C2.ensureListenersLoaded = function() {
if (C2.listeners && C2.listeners.length > 0) {
return Promise.resolve(C2.listeners);
}
return apiRequest('GET', `${API_BASE}/listeners`).then(function(data) {
C2.listeners = (data && data.listeners) || [];
return C2.listeners;
});
};
C2.loadListeners = function() {
Promise.all([
apiRequest('GET', `${API_BASE}/listeners`),
apiRequest('GET', `${API_BASE}/profiles`).catch(function() { return {}; })
]).then(function(results) {
var ldata = results[0];
var pdata = results[1];
C2.listeners = (ldata && ldata.listeners) || [];
if (pdata && pdata.profiles && !pdata.error) {
C2.profiles = pdata.profiles;
}
C2.renderListeners();
C2.updateDashboardStats();
});
};
/** 拉取 Profile 列表(监听器表单用);失败时置空列表不阻断弹窗 */
C2.ensureProfilesLoaded = function() {
return apiRequest('GET', `${API_BASE}/profiles`).then(data => {
if (data && data.error) {
C2.profiles = [];
return C2.profiles;
}
C2.profiles = (data && data.profiles) || [];
return C2.profiles;
});
};
C2.renderListeners = function() {
const container = document.getElementById('c2-listener-grid');
if (!container) return;
if (C2.listeners.length === 0) {
container.innerHTML = `
${escapeHtml(c2t('c2.listeners.emptyTitle'))}
${escapeHtml(c2t('c2.listeners.emptyHint'))}
${escapeHtml(c2t('c2.listeners.headerCreateBtn'))}
`;
return;
}
container.innerHTML = C2.listeners.map(function(l) {
const st = String(l.status || 'stopped').toLowerCase();
const stUi = st === 'running' || st === 'stopped' || st === 'error' ? st : 'stopped';
const profilePid = listenerResolvedProfileId(l);
const profileName = listenerProfileDisplayName(l);
const profileBadge = profilePid
? '' + escapeHtml(profileName) + '
'
: '';
const cb = C2.getListenerCallbackHost(l);
const cbRow = cb
? '' + escapeHtml(c2t('c2.listeners.callbackShort')) + ' ' + escapeHtml(cb) + '
'
: '';
const remarkRow = l.remark ? '' : '';
const startedHtml = formatListenerStartedHtml(l.startedAt);
const pillLabel = escapeHtml(listenerCardStatusPillLabel(st));
const typeMark = escapeHtml(listenerTypeShortLabel(l.type));
const typeVis = listenerTypeVisualClass(l.type);
const fullType = escapeHtml(listenerTypeLabel(l.type));
const bindVal = escapeHtml(String(l.bindHost)) + ':' + escapeHtml(String(l.bindPort));
return `
${typeMark}
${escapeHtml(l.name)}
${pillLabel}
${escapeHtml(l.id)}
${escapeHtml(c2t('c2.listeners.bindEndpoint'))}
${bindVal}
${cbRow}
${profileBadge}
${remarkRow}
${startedHtml}
${l.status === 'stopped'
? `▶ ${escapeHtml(c2t('c2.listeners.start'))} `
: `⏹ ${escapeHtml(c2t('c2.listeners.stop'))} `
}
${escapeHtml(c2t('c2.listeners.edit'))}
${escapeHtml(c2t('c2.listeners.delete'))}
`;
}).join('');
};
C2.getListenerCallbackHost = function(l) {
if (!l) return '';
try {
var raw = l.configJson != null ? l.configJson : '{}';
var j = typeof raw === 'string' ? JSON.parse(raw || '{}') : (raw || {});
return String(j.callback_host || '').trim();
} catch (e) {
return '';
}
};
C2.showCreateListenerModal = function() {
const modal = document.getElementById('c2-modal');
const content = document.getElementById('c2-modal-content');
if (!content || !modal) return;
modal.style.display = 'flex';
content.innerHTML = `
${escapeHtml(c2t('c2.listeners.loadingProfiles'))}
`;
C2.ensureProfilesLoaded().then(() => {
const profileOpts = listenerProfileSelectHtml('');
const emptyProfHintCreate = (C2.profiles && C2.profiles.length > 0)
? ''
: `${escapeHtml(c2t('c2.listeners.malleableProfileEmptyListHint'))}
`;
content.innerHTML = `
${escapeHtml(c2t('c2.listeners.remark'))}
`;
C2.syncListenerProfileRowForType();
}).catch(() => {
showToast(c2t('c2.listeners.toastProfilesLoadFailed'), 'error');
C2.closeModal();
});
};
/** 非 HTTP/HTTPS Beacon 时隐藏 Profile 行(避免误以为 TCP 等也会用) */
C2.syncListenerProfileRowForType = function() {
const typeEl = document.getElementById('c2-listener-type');
const row = document.getElementById('c2-listener-profile-group');
if (!typeEl || !row) return;
const t = String(typeEl.value || '').toLowerCase();
const show = t === 'http_beacon' || t === 'https_beacon';
row.style.display = show ? '' : 'none';
if (!show) {
const sel = document.getElementById('c2-listener-profile-id');
if (sel) sel.value = '';
}
};
C2.createListener = function() {
const name = document.getElementById('c2-listener-name')?.value.trim();
const type = document.getElementById('c2-listener-type')?.value;
const bindHost = document.getElementById('c2-listener-host')?.value || '127.0.0.1';
const bindPort = parseInt(document.getElementById('c2-listener-port')?.value);
const callbackHost = document.getElementById('c2-listener-callback-host')?.value?.trim() || '';
const remark = document.getElementById('c2-listener-remark')?.value;
if (!name || !type || !bindPort) {
showToast(c2t('c2.listeners.toastFillRequired'), 'error');
return;
}
const profileId = (document.getElementById('c2-listener-profile-id')?.value || '').trim();
apiRequest('POST', `${API_BASE}/listeners`, {
name, type, bind_host: bindHost, bind_port: bindPort, remark,
callback_host: callbackHost,
profile_id: profileId
}).then(data => {
if (data.error) {
showToast(data.error, 'error');
} else {
showToast(c2t('c2.listeners.toastCreated'), 'success');
C2.closeModal();
C2.loadListeners();
}
});
};
C2.startListener = function(id) {
apiRequest('POST', `${API_BASE}/listeners/${id}/start`, {}).then(data => {
if (data.error) showToast(data.error, 'error');
else {
showToast(c2t('c2.listeners.toastStarted'), 'success');
C2.loadListeners();
}
});
};
C2.stopListener = function(id) {
apiRequest('POST', `${API_BASE}/listeners/${id}/stop`, {}).then(data => {
if (data.error) showToast(data.error, 'error');
else {
showToast(c2t('c2.listeners.toastStopped'), 'success');
C2.loadListeners();
}
});
};
C2.deleteListener = function(id) {
if (!confirm(c2t('c2.listeners.confirmDelete'))) return;
apiRequest('DELETE', `${API_BASE}/listeners/${id}`, {}).then(data => {
showToast(c2t('c2.listeners.toastDeleted'), 'success');
C2.loadListeners();
});
};
C2.editListener = function(id) {
const l = C2.listeners.find(x => x.id === id);
if (!l) return;
const cbHost = C2.getListenerCallbackHost(l);
const modal = document.getElementById('c2-modal');
const content = document.getElementById('c2-modal-content');
if (!content || !modal) return;
modal.style.display = 'flex';
content.innerHTML = `
${escapeHtml(c2t('c2.listeners.loadingProfiles'))}
`;
C2.ensureProfilesLoaded().then(() => {
const resolvedPid = listenerResolvedProfileId(l);
const profileOpts = listenerProfileSelectHtml(resolvedPid);
const lt = String(l.type || '').toLowerCase();
const httpHint = (lt === 'http_beacon' || lt === 'https_beacon')
? ''
: `${escapeHtml(c2t('c2.listeners.malleableProfileNonHttpHint'))}
`;
const emptyProfHint = (C2.profiles && C2.profiles.length > 0)
? ''
: `${escapeHtml(c2t('c2.listeners.malleableProfileEmptyListHint'))}
`;
content.innerHTML = `
`;
}).catch(() => {
showToast(c2t('c2.listeners.toastProfilesLoadFailed'), 'error');
C2.closeModal();
});
};
C2.saveListener = function(id) {
const name = document.getElementById('c2-listener-name')?.value.trim();
const bindHost = document.getElementById('c2-listener-host')?.value;
const bindPort = parseInt(document.getElementById('c2-listener-port')?.value);
const callbackHost = document.getElementById('c2-listener-callback-host')?.value?.trim() ?? '';
const remark = document.getElementById('c2-listener-remark')?.value;
const profileEl = document.getElementById('c2-listener-profile-id');
const profileId = profileEl ? String(profileEl.value || '').trim() : '';
apiRequest('PUT', `${API_BASE}/listeners/${id}`, {
name, bind_host: bindHost, bind_port: bindPort, remark,
callback_host: callbackHost,
profile_id: profileId
}).then(data => {
if (data.error) showToast(data.error, 'error');
else {
showToast(c2t('c2.listeners.toastUpdated'), 'success');
C2.closeModal();
C2.loadListeners();
}
});
};
// ============================================================================
// 会话管理
// ============================================================================
C2.loadSessions = function() {
return apiRequest('GET', `${API_BASE}/sessions`).then(data => {
C2.sessions = data.sessions || [];
C2.renderSessions();
C2.updateDashboardStats();
});
};
C2.renderSessions = function() {
const list = document.getElementById('c2-session-list');
const main = document.getElementById('c2-session-main');
if (!list) return;
if (C2.sessions.length === 0) {
list.innerHTML = `
${escapeHtml(c2t('c2.sessions.emptyTitle'))}
${escapeHtml(c2t('c2.sessions.emptyHint'))}
`;
if (main) main.innerHTML = '';
return;
}
list.innerHTML = C2.sessions.map(s => `
${escapeHtml(s.username)} · ${s.os}/${s.arch}
${s.isAdmin ? '' + escapeHtml(c2t('c2.sessions.rootBadge')) + ' ' : ''}
${s.internalIp || '-'} · PID ${s.pid}
`).join('');
if (C2.selectedSessionId && !C2.sessions.find(s => s.id === C2.selectedSessionId)) {
C2.selectedSessionId = null;
}
if (!C2.selectedSessionId && C2.sessions.length > 0) {
C2.selectSession(C2.sessions[0].id);
}
};
C2.selectSession = function(id) {
C2.selectedSessionId = id;
C2.implantPwd = null;
C2.currentPath = '.';
C2.renderSessions();
C2.renderSessionDetail(id);
};
C2.renderSessionDetail = function(id) {
const container = document.getElementById('c2-session-main');
if (!container) return;
const s = C2.sessions.find(x => x.id === id);
if (!s) return;
const adminVal = s.isAdmin ? c2t('c2.sessions.adminYes') : c2t('c2.sessions.adminNo');
const sleepLine = c2t('c2.sessions.infoSleepLine', { sec: s.sleepSeconds, jitter: s.jitterPercent });
container.innerHTML = `
${escapeHtml(c2t('c2.sessions.terminal'))}
${escapeHtml(c2t('c2.sessions.files'))}
${escapeHtml(c2t('c2.sessions.tasks'))}
${escapeHtml(c2t('c2.sessions.info'))}
${escapeHtml(c2t('c2.sessions.clearTerminal'))}
${escapeHtml(c2t('common.copy'))}
${escapeHtml(c2t('c2.sessions.termStatusReady'))}
⬆ ${escapeHtml(c2t('c2.files.parent'))}
${escapeHtml(c2t('c2.files.refresh'))}
📤 ${escapeHtml(c2t('c2.files.upload'))}
/
${escapeHtml(c2t('c2.sessions.infoSessionId'))}: ${s.id}
${escapeHtml(c2t('c2.sessions.infoImplantUuid'))}: ${s.implantUuid}
${escapeHtml(c2t('c2.sessions.infoHostname'))}: ${escapeHtml(s.hostname)}
${escapeHtml(c2t('c2.sessions.infoUsername'))}: ${escapeHtml(s.username)}
${escapeHtml(c2t('c2.sessions.infoOs'))}: ${s.os}
${escapeHtml(c2t('c2.sessions.infoArch'))}: ${s.arch}
${escapeHtml(c2t('c2.sessions.infoPid'))}: ${s.pid}
${escapeHtml(c2t('c2.sessions.infoProcess'))}: ${escapeHtml(s.processName || '-')}
${escapeHtml(c2t('c2.sessions.infoAdmin'))}: ${escapeHtml(adminVal)}
${escapeHtml(c2t('c2.sessions.infoInternalIp'))}: ${s.internalIp || '-'}
${escapeHtml(c2t('c2.sessions.infoSleep'))}: ${escapeHtml(sleepLine)}
${escapeHtml(c2t('c2.sessions.infoFirstSeen'))}: ${formatTime(s.firstSeenAt)}
${escapeHtml(c2t('c2.sessions.infoLastCheckin'))}: ${formatTime(s.lastCheckIn)}
${escapeHtml(c2t('c2.sessions.infoNote'))}: ${escapeHtml(s.note || '-')}
`;
var isCurlBeacon = s.implantUuid && s.implantUuid.startsWith('curl_');
if (isCurlBeacon) {
var termContainer = container.querySelector('#c2-terminal-container');
if (termContainer) {
termContainer.innerHTML =
'' +
'
📡
' +
'
' + escapeHtml(c2t('c2.sessions.curlBeaconTitle')) + '
' +
'
' + c2t('c2.sessions.curlBeaconBody').split('\n').map(function (ln) { return escapeHtml(ln); }).join(' ') + '
' +
'
';
}
}
setTimeout(() => {
if (!isCurlBeacon) C2.initTerminal();
C2.loadFileList(s.id, '.');
C2.loadSessionTasks(s.id);
C2.updateFileUploadButton(s);
C2.ensureListenersLoaded().then(function() {
C2.updateFileUploadButton(s);
});
}, 50);
};
C2.switchTab = function(tab) {
document.querySelectorAll('.c2-session-tab').forEach(el => el.classList.remove('active'));
document.querySelectorAll('.c2-tab-panel').forEach(el => el.style.display = 'none');
const tabEl = document.querySelector(`.c2-session-tab[data-tab="${tab}"]`);
if (tabEl) tabEl.classList.add('active');
const panel = document.getElementById(`c2-tab-${tab}`);
if (panel) panel.style.display = 'block';
if (tab === 'terminal') {
setTimeout(function () {
C2.fitTerminal();
if (C2.terminalInstance) C2.terminalInstance.focus();
}, 50);
}
};
C2.setSessionSleep = function(id) {
const sleep = prompt(c2t('c2.sessions.promptSleepSeconds'), '5');
if (!sleep) return;
const jitter = prompt(c2t('c2.sessions.promptJitterPercent'), '0') || '0';
apiRequest('PUT', `${API_BASE}/sessions/${id}/sleep`, {
sleep_seconds: parseInt(sleep),
jitter_percent: parseInt(jitter)
}).then(data => {
if (data.error) showToast(data.error, 'error');
else showToast(c2t('c2.sessions.toastSleepUpdated'), 'success');
});
};
C2.killSession = function(id) {
if (!confirm(c2t('c2.sessions.confirmExitSession'))) return;
apiRequest('POST', `${API_BASE}/tasks`, {
session_id: id,
task_type: 'exit',
payload: {}
}).then(data => {
showToast(c2t('c2.sessions.toastExitSent'), 'success');
});
};
C2.deleteSessionRecord = function(id) {
if (!confirm(c2t('c2.sessions.confirmDeleteSession'))) return;
apiRequest('DELETE', `${API_BASE}/sessions/${id}`, {}).then(data => {
if (data.error) {
showToast(data.error, 'error');
return;
}
showToast(c2t('c2.sessions.toastSessionDeleted'), 'success');
if (C2.selectedSessionId === id) C2.selectedSessionId = null;
C2.loadSessions();
});
};
// ============================================================================
// xterm 终端
// ============================================================================
C2.serializeTerminalBuffer = function(term) {
if (!term || !term.buffer || !term.buffer.active) return '';
const buf = term.buffer.active;
const lines = [];
for (let i = 0; i < buf.length; i++) {
const line = buf.getLine(i);
if (line) lines.push(line.translateToString(true));
}
return lines.join('\n');
};
C2.pushTerminalHistory = function(cmd) {
const sid = C2.selectedSessionId;
if (!sid || !cmd) return;
if (!C2.terminalHistory[sid]) C2.terminalHistory[sid] = [];
const hist = C2.terminalHistory[sid];
if (hist.length === 0 || hist[hist.length - 1] !== cmd) {
hist.push(cmd);
if (hist.length > 200) hist.shift();
}
};
C2.finishTerminalCommand = function(term, status) {
C2.terminalBusy = false;
const statusEl = document.getElementById('c2-terminal-status');
if (status === 'err' && statusEl) {
statusEl.textContent = c2t('c2.sessions.termStatusErr');
} else if (status === 'timeout' && statusEl) {
statusEl.textContent = c2t('c2.sessions.termStatusTimeout');
} else if (statusEl && C2.terminalQueue.length === 0) {
statusEl.textContent = c2t('c2.sessions.termStatusReady');
}
if (C2.terminalQueue.length > 0) {
const next = C2.terminalQueue.shift();
C2.runTerminalCommand(next, term);
return;
}
term.write('$ ');
if (statusEl && status !== 'err' && status !== 'timeout') {
statusEl.textContent = c2t('c2.sessions.termStatusReady');
}
};
C2.runTerminalCommand = function(cmd, term) {
if (!C2.selectedSessionId) {
term.writeln('\x1b[31m' + c2t('c2.sessions.termNoSession') + '\x1b[0m');
term.write('$ ');
return;
}
C2.terminalBusy = true;
C2.pushTerminalHistory(cmd);
const statusEl = document.getElementById('c2-terminal-status');
if (statusEl) statusEl.textContent = c2t('c2.sessions.termStatusExec');
apiRequest('POST', `${API_BASE}/tasks`, {
session_id: C2.selectedSessionId,
task_type: 'shell',
payload: { command: cmd, timeout_seconds: 60 }
}).then(data => {
if (data.error) {
term.writeln(`\x1b[31mError: ${data.error}\x1b[0m`);
C2.finishTerminalCommand(term, 'err');
} else {
C2.waitForTaskResult(data.task?.id || data.task_id, term);
}
}).catch(function () {
term.writeln('\x1b[31mError: request failed\x1b[0m');
C2.finishTerminalCommand(term, 'err');
});
};
C2.executeInTerminal = function(cmd, term) {
if (!cmd) {
term.write('$ ');
return;
}
if (C2.terminalBusy) {
C2.terminalQueue.push(cmd);
term.writeln('\x1b[33m' + c2t('c2.sessions.termQueued') + '\x1b[0m');
return;
}
C2.runTerminalCommand(cmd, term);
};
C2.waitForTaskResult = function(taskId, term) {
let attempts = 0;
const maxAttempts = 60;
let delay = 500;
const maxDelay = 5000;
const check = () => {
if (++attempts > maxAttempts) {
term.writeln('\x1b[33m' + c2t('c2.sessions.termWaitTimeout') + '\x1b[0m');
C2.finishTerminalCommand(term, 'timeout');
return;
}
apiRequest('GET', `${API_BASE}/tasks/${taskId}`).then(data => {
const task = data.task;
if (task && (task.status === 'success' || task.status === 'failed')) {
if (task.resultText) {
const lines = task.resultText.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
lines.forEach(line => term.writeln(line));
}
if (task.error) {
term.writeln(`\x1b[31m${task.error}\x1b[0m`);
}
C2.finishTerminalCommand(term, task.status === 'failed' ? 'err' : 'ready');
} else {
delay = Math.min(delay * 1.5, maxDelay);
setTimeout(check, delay);
}
}).catch(function () {
C2.finishTerminalCommand(term, 'err');
});
};
check();
};
C2.initTerminal = function() {
const container = document.getElementById('c2-terminal-container');
if (!container || typeof Terminal === 'undefined') return;
if (C2.terminalInstance && C2.terminalSessionId) {
C2.terminalLogs[C2.terminalSessionId] = C2.serializeTerminalBuffer(C2.terminalInstance);
}
if (C2.terminalInstance) {
C2.terminalInstance.dispose();
}
const sessionId = C2.selectedSessionId || '_none';
C2.terminalSessionId = sessionId;
C2.terminalQueue = [];
C2.terminalBusy = false;
const term = new Terminal({
cursorBlink: true,
cursorStyle: 'block',
fontSize: 14,
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
lineHeight: 1.3,
scrollback: 5000,
theme: {
background: '#0d1117',
foreground: '#e6edf3',
cursor: '#58a6ff',
cursorAccent: '#0d1117',
selection: 'rgba(88, 166, 255, 0.3)'
}
});
if (typeof FitAddon !== 'undefined') {
const FitCtor = FitAddon.FitAddon || FitAddon;
C2.terminalFitAddon = new FitCtor();
term.loadAddon(C2.terminalFitAddon);
}
term.open(container);
try {
if (C2.terminalFitAddon) C2.terminalFitAddon.fit();
} catch (e) {}
let lineBuffer = '';
let cursorIndex = 0;
let historyIndex = -1;
let lastPasteAt = 0;
let lastPasteText = '';
const prompt = '$ ';
function redrawInputLine() {
term.write('\x1b[2K\r' + prompt + lineBuffer);
const tail = lineBuffer.length - cursorIndex;
if (tail > 0) term.write('\x1b[' + tail + 'D');
}
function resetInputLine() {
lineBuffer = '';
cursorIndex = 0;
historyIndex = -1;
term.write('\x1b[2K\r' + prompt);
}
function insertPlainText(text) {
const safe = String(text).replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '');
if (!safe) return;
lineBuffer = lineBuffer.slice(0, cursorIndex) + safe + lineBuffer.slice(cursorIndex);
cursorIndex += safe.length;
redrawInputLine();
}
function deleteWordBeforeCursor() {
if (cursorIndex === 0) return;
let start = cursorIndex;
while (start > 0 && /\s/.test(lineBuffer[start - 1])) start--;
while (start > 0 && !/\s/.test(lineBuffer[start - 1])) start--;
lineBuffer = lineBuffer.slice(0, start) + lineBuffer.slice(cursorIndex);
cursorIndex = start;
redrawInputLine();
}
function moveWordLeft() {
if (cursorIndex === 0) return;
let pos = cursorIndex;
while (pos > 0 && /\s/.test(lineBuffer[pos - 1])) pos--;
while (pos > 0 && !/\s/.test(lineBuffer[pos - 1])) pos--;
const delta = cursorIndex - pos;
if (delta > 0) {
term.write('\x1b[' + delta + 'D');
cursorIndex = pos;
}
}
function moveWordRight() {
if (cursorIndex >= lineBuffer.length) return;
let pos = cursorIndex;
while (pos < lineBuffer.length && /\s/.test(lineBuffer[pos])) pos++;
while (pos < lineBuffer.length && !/\s/.test(lineBuffer[pos])) pos++;
const delta = pos - cursorIndex;
if (delta > 0) {
term.write('\x1b[' + delta + 'C');
cursorIndex = pos;
}
}
function showHistoryEntry(entry) {
lineBuffer = entry || '';
cursorIndex = lineBuffer.length;
term.write('\x1b[2K\r' + prompt + lineBuffer);
}
function submitCurrentLine() {
if (C2.terminalBusy) {
term.writeln('');
term.writeln('\x1b[33m' + c2t('c2.sessions.termWaitFinish') + '\x1b[0m');
term.write(prompt + lineBuffer);
const tail = lineBuffer.length - cursorIndex;
if (tail > 0) term.write('\x1b[' + tail + 'D');
return;
}
term.writeln('');
const cmd = lineBuffer.trim();
lineBuffer = '';
cursorIndex = 0;
historyIndex = -1;
if (cmd) {
C2.executeInTerminal(cmd, term);
} else {
term.write(prompt);
}
}
function handlePasteText(text) {
const now = Date.now();
if (text === lastPasteText && now - lastPasteAt < 80) return;
lastPasteAt = now;
lastPasteText = text;
const normalized = String(text).replace(/\r\n/g, '\n').replace(/\r/g, '\n');
if (normalized.indexOf('\n') === -1) {
insertPlainText(normalized);
return;
}
const endsWithNewline = normalized.endsWith('\n');
const parts = normalized.split('\n');
const tail = parts.pop() || '';
parts.forEach(function (part) {
insertPlainText(part);
submitCurrentLine();
});
if (tail) insertPlainText(tail);
else if (endsWithNewline && parts.length === 0) submitCurrentLine();
}
const savedLog = C2.terminalLogs[sessionId];
if (savedLog) {
term.write(String(savedLog).replace(/\r\n/g, '\n').replace(/\r/g, '\n').replace(/\n/g, '\r\n'));
if (!savedLog.endsWith('\n')) term.write('\r\n');
} else {
term.writeln('\x1b[36m' + c2t('c2.sessions.terminalWelcome') + '\x1b[0m');
term.writeln('');
}
term.write(prompt);
term.onData(function (e) {
if (e === '\x0c') {
term.clear();
resetInputLine();
C2.terminalLogs[sessionId] = '';
return;
}
if (e === '\x03') {
if (C2.terminalBusy) {
term.writeln('');
term.writeln('\x1b[33m^C (' + c2t('c2.sessions.termCtrlC') + ')\x1b[0m');
}
resetInputLine();
return;
}
if (e === '\x16') {
if (navigator.clipboard && navigator.clipboard.readText) {
navigator.clipboard.readText().then(handlePasteText).catch(function () {});
}
return;
}
if (e.length > 1 && e.indexOf('\x1b') !== 0) {
handlePasteText(e);
return;
}
if (e === '\x1b[D' || e === '\x1bOD') {
if (cursorIndex > 0) {
cursorIndex--;
term.write('\x1b[D');
}
return;
}
if (e === '\x1b[C' || e === '\x1bOC') {
if (cursorIndex < lineBuffer.length) {
cursorIndex++;
term.write('\x1b[C');
}
return;
}
if (e === '\x1b[1;3D' || e === '\x1bb') {
moveWordLeft();
return;
}
if (e === '\x1b[1;3C' || e === '\x1bf') {
moveWordRight();
return;
}
if (e === '\x1b[A' || e === '\x1bOA') {
const hist = C2.terminalHistory[sessionId] || [];
if (hist.length === 0) return;
historyIndex = historyIndex < 0 ? hist.length - 1 : Math.max(0, historyIndex - 1);
showHistoryEntry(hist[historyIndex]);
return;
}
if (e === '\x1b[B' || e === '\x1bOB') {
const hist = C2.terminalHistory[sessionId] || [];
if (hist.length === 0) return;
historyIndex = historyIndex < 0 ? -1 : Math.min(hist.length - 1, historyIndex + 1);
if (historyIndex < 0) showHistoryEntry('');
else showHistoryEntry(hist[historyIndex]);
return;
}
if (e === '\x1b[H' || e === '\x1bOH' || e === '\x01') {
if (cursorIndex > 0) {
term.write('\x1b[' + cursorIndex + 'D');
cursorIndex = 0;
}
return;
}
if (e === '\x1b[F' || e === '\x1bOF' || e === '\x05') {
const move = lineBuffer.length - cursorIndex;
if (move > 0) {
term.write('\x1b[' + move + 'C');
cursorIndex = lineBuffer.length;
}
return;
}
if (e === '\x1b[3~') {
if (cursorIndex < lineBuffer.length) {
lineBuffer = lineBuffer.slice(0, cursorIndex) + lineBuffer.slice(cursorIndex + 1);
redrawInputLine();
}
return;
}
if (e === '\x15') {
resetInputLine();
return;
}
if (e === '\x0b') {
lineBuffer = lineBuffer.slice(0, cursorIndex);
redrawInputLine();
return;
}
if (e === '\x17') {
deleteWordBeforeCursor();
return;
}
if (e === '\x1b\x7f') {
deleteWordBeforeCursor();
return;
}
const code = e.charCodeAt(0);
if (code === 13 || code === 10) {
submitCurrentLine();
} else if (code === 127 || code === 8) {
if (cursorIndex > 0) {
lineBuffer = lineBuffer.slice(0, cursorIndex - 1) + lineBuffer.slice(cursorIndex);
cursorIndex--;
redrawInputLine();
}
} else if (e.length === 1 && code >= 32) {
historyIndex = -1;
lineBuffer = lineBuffer.slice(0, cursorIndex) + e + lineBuffer.slice(cursorIndex);
cursorIndex++;
if (cursorIndex === lineBuffer.length) {
term.write(e);
} else {
redrawInputLine();
}
}
});
const onTerminalPaste = function (ev) {
const text = ev.clipboardData && ev.clipboardData.getData('text');
if (!text) return;
ev.preventDefault();
handlePasteText(text);
};
if (term.element) {
term.element.addEventListener('paste', onTerminalPaste);
}
term.attachCustomKeyEventHandler(function (ev) {
if (ev.type !== 'keydown') return true;
if ((ev.ctrlKey || ev.metaKey) && !ev.shiftKey && (ev.key === 'c' || ev.key === 'C')) {
if (term.getSelection()) return true;
}
const isPaste = (ev.ctrlKey || ev.metaKey) && !ev.shiftKey && !ev.altKey
&& (ev.key === 'v' || ev.key === 'V');
if (isPaste && navigator.clipboard && navigator.clipboard.readText) {
ev.preventDefault();
navigator.clipboard.readText().then(handlePasteText).catch(function () {});
return false;
}
if (ev.shiftKey && ev.key === 'Insert' && navigator.clipboard && navigator.clipboard.readText) {
ev.preventDefault();
navigator.clipboard.readText().then(handlePasteText).catch(function () {});
return false;
}
return true;
});
container.addEventListener('click', function () {
term.focus();
});
container.setAttribute('tabindex', '0');
C2.terminalInstance = term;
if (C2.terminalResizeObserver) {
C2.terminalResizeObserver.disconnect();
}
C2.terminalResizeObserver = new ResizeObserver(function () {
C2.fitTerminal();
});
C2.terminalResizeObserver.observe(container);
setTimeout(function () {
try {
if (C2.terminalFitAddon) C2.terminalFitAddon.fit();
term.focus();
} catch (e) {}
}, 100);
};
C2.fitTerminal = function() {
if (C2.terminalFitAddon && C2.terminalInstance) {
try {
C2.terminalFitAddon.fit();
} catch (e) {}
}
};
C2.clearTerminal = function() {
if (C2.terminalInstance) {
C2.terminalInstance.clear();
C2.terminalInstance.writeln('\x1b[36m' + c2t('c2.sessions.termCleared') + '\x1b[0m');
C2.terminalInstance.write('$ ');
if (C2.terminalSessionId) {
C2.terminalLogs[C2.terminalSessionId] = C2.serializeTerminalBuffer(C2.terminalInstance);
}
}
C2.terminalQueue = [];
};
C2.copyTerminal = function() {
if (!C2.terminalInstance) return;
const text = C2.terminalInstance.getSelection();
if (text) copyToClipboard(text);
else showToast(c2t('c2.sessions.termNoSelection'), 'warning');
};
// ============================================================================
// 文件管理
// ============================================================================
C2.normalizeFilePath = function(path) {
var p = path == null ? '.' : String(path).trim();
if (!p || p === '/') return '.';
p = p.replace(/\\/g, '/').replace(/\/+/g, '/').replace(/\/+$/, '');
return p || '.';
};
C2.joinFilePath = function(base, name) {
var b = C2.normalizeFilePath(base);
var n = String(name || '').trim().replace(/\\/g, '/').replace(/^\/+/, '');
if (!n) return b;
if (b === '.' || b === '/') return n;
return b + '/' + n;
};
/** 将相对浏览路径解析为 implant 工作目录下的绝对路径 */
C2.resolvePathAgainstPwd = function(pwd, rel) {
var base = String(pwd || '').trim().replace(/\\/g, '/').replace(/\/+$/, '');
if (!base) base = '/';
if (!base.startsWith('/')) base = '/' + base;
var parts = String(rel || '.').replace(/\\/g, '/').split('/');
var stack = base === '/' ? [] : base.split('/').filter(Boolean);
for (var i = 0; i < parts.length; i++) {
var p = parts[i];
if (!p || p === '.') continue;
if (p === '..') {
if (stack.length) stack.pop();
} else {
stack.push(p);
}
}
return '/' + stack.join('/');
};
C2.resolveRemotePath = function(browsePath, filename) {
var joined = C2.joinFilePath(browsePath || '.', filename);
if (!C2.implantPwd) return joined;
return C2.resolvePathAgainstPwd(C2.implantPwd, joined);
};
C2.updateFileBreadcrumb = function(browsePath) {
var breadcrumb = document.getElementById('c2-current-path');
if (!breadcrumb) return;
var rel = C2.normalizeFilePath(browsePath || '.');
if (C2.implantPwd) {
breadcrumb.textContent = C2.resolvePathAgainstPwd(C2.implantPwd, rel);
breadcrumb.title = rel;
} else {
breadcrumb.textContent = rel;
breadcrumb.title = '';
}
};
C2.parseLsLine = function(line) {
var trimmed = String(line || '').trim();
if (!trimmed || /^total\s+\d+/i.test(trimmed)) return null;
// Beacon 结构化输出:type\tmode\tsize\tname
var beaconParts = trimmed.split('\t');
if (beaconParts.length >= 4) {
var bName = beaconParts.slice(3).join('\t').trim();
var bMode = beaconParts[1].trim();
var bType = beaconParts[0].trim();
if (bName && bName !== '.' && bName !== '..') {
return {
mode: bMode || bType,
size: beaconParts[2].trim(),
name: bName,
isDir: bType.charAt(0) === 'd' || bMode.charAt(0) === 'd'
};
}
return null;
}
// 原生 ls -l 输出
var m = trimmed.match(/^(\S+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.+)$/);
if (!m) return null;
var name = m[9].trim();
var arrow = name.indexOf(' -> ');
if (arrow > 0) name = name.slice(0, arrow).trim();
if (!name || name === '.' || name === '..') return null;
return {
mode: m[1],
size: m[5],
name: name,
isDir: m[1].charAt(0) === 'd'
};
};
C2.isDownloadShellError = function(text) {
var lower = String(text || '').toLowerCase();
return lower.indexOf('c2_download_err:') >= 0 ||
lower.indexOf('no such file') >= 0 ||
lower.indexOf('permission denied') >= 0 ||
lower.indexOf('is a directory') >= 0 ||
lower.indexOf('cannot open') >= 0 ||
lower.indexOf('not a regular file') >= 0;
};
C2.refreshImplantPwd = function(sessionId, callback) {
if (!sessionId) {
if (callback) callback();
return;
}
apiRequest('POST', `${API_BASE}/tasks`, {
session_id: sessionId,
task_type: 'pwd',
payload: {}
}).then(function(data) {
if (data.error) {
if (callback) callback();
return;
}
var taskId = data.task && data.task.id ? data.task.id : data.task_id;
if (!taskId) {
if (callback) callback();
return;
}
C2.waitForImplantPwd(taskId, callback);
}).catch(function() {
if (callback) callback();
});
};
C2.waitForImplantPwd = function(taskId, callback) {
var attempts = 0;
var poll = function() {
if (++attempts > 30) {
if (callback) callback();
return;
}
apiRequest('GET', `${API_BASE}/tasks/${taskId}`).then(function(data) {
var task = data.task;
if (task && task.status === 'success' && task.resultText) {
C2.implantPwd = String(task.resultText).trim().split('\n').pop().trim();
C2.updateFileBreadcrumb(C2.currentPath);
if (callback) callback();
} else if (task && task.status === 'failed') {
if (callback) callback();
} else {
setTimeout(poll, 300);
}
});
};
poll();
};
C2.getParentFilePath = function(path) {
var p = C2.normalizeFilePath(path);
if (p === '.' || p === '/') return '.';
var idx = p.lastIndexOf('/');
if (idx < 0) return '.';
var parent = p.slice(0, idx);
return parent || '.';
};
C2.goToParentDirectory = function() {
var parent = C2.getParentFilePath(C2.currentPath || '.');
C2.loadFileList(null, parent);
};
C2.openDirectory = function(name) {
var next = C2.joinFilePath(C2.currentPath || '.', name);
C2.loadFileList(null, next);
};
C2.loadFileList = function(sessionId, path) {
// 兼容误传:仅传路径时(如旧版 loadFileList('..'))自动纠正
if (sessionId && path == null && typeof sessionId === 'string' &&
(sessionId === '..' || sessionId === '.' || sessionId.indexOf('/') >= 0)) {
path = sessionId;
sessionId = null;
}
if (!sessionId) sessionId = C2.selectedSessionId;
if (!sessionId) return;
if (!path) path = C2.currentPath || '.';
path = C2.normalizeFilePath(path);
const container = document.getElementById('c2-file-list');
const breadcrumb = document.getElementById('c2-current-path');
if (container) container.innerHTML = '' + escapeHtml(c2t('c2.files.loading')) + '
';
apiRequest('POST', `${API_BASE}/tasks`, {
session_id: sessionId,
task_type: 'ls',
payload: { path: path }
}).then(data => {
if (data.error) {
if (container) container.innerHTML = `${data.error}
`;
return;
}
C2.waitForFileList(data.task?.id || data.task_id, sessionId, path);
});
};
C2.waitForFileList = function(taskId, sessionId, path) {
let attempts = 0;
const container = document.getElementById('c2-file-list');
const check = () => {
if (++attempts > 60) {
if (container) container.innerHTML = '' + escapeHtml(c2t('c2.files.timeout')) + '
';
return;
}
apiRequest('GET', `${API_BASE}/tasks/${taskId}`).then(data => {
const task = data.task;
if (task && task.status === 'success') {
C2.currentPath = path;
C2.updateFileBreadcrumb(path);
C2.renderFileList(task.resultText || '');
C2.refreshImplantPwd(sessionId);
} else if (task && task.status === 'failed') {
if (container) container.innerHTML = `${escapeHtml(task.error || c2t('c2.files.failed'))}
`;
} else {
setTimeout(check, 500);
}
});
};
check();
};
C2.renderFileList = function(output) {
const container = document.getElementById('c2-file-list');
if (!container) return;
const entries = output.split('\n')
.map(C2.parseLsLine)
.filter(function(entry) { return entry != null; });
if (entries.length === 0) {
container.innerHTML = '' + escapeHtml(c2t('c2.files.emptyDir')) + '
';
return;
}
container.innerHTML = `
${escapeHtml(c2t('c2.files.colName'))}
${escapeHtml(c2t('c2.files.colSize'))}
${escapeHtml(c2t('c2.files.colMode'))}
${escapeHtml(c2t('c2.files.colActions'))}
${entries.map(function(entry) {
return `
${entry.isDir ? '📁' : '📄'}
${escapeHtml(entry.name)}
${escapeHtml(entry.size)}
${escapeHtml(entry.mode)}
${entry.isDir
? `${escapeHtml(c2t('c2.files.open'))} `
: `${escapeHtml(c2t('c2.files.download'))} `
}
`;
}).join('')}
`;
};
C2.refreshFiles = function() {
C2.loadFileList(null, C2.currentPath);
};
C2.sessionTransport = function(session) {
if (!session || !session.metadata) return '';
return String(session.metadata.transport || '').toLowerCase();
};
C2.sessionSupportsUpload = function(session) {
if (!session) {
return { supported: false, reasonKey: 'c2.files.uploadUnsupported' };
}
if (session.implantUuid && String(session.implantUuid).indexOf('curl_') === 0) {
return { supported: false, reasonKey: 'c2.files.uploadCurlBeacon' };
}
var transport = C2.sessionTransport(session);
// 编译 Beacon:HTTP/HTTPS/TCP(CSB1) 均走二进制/结构化协议,支持 upload
if (transport === 'tcp_beacon' || transport === 'http_beacon' || transport === 'https_beacon') {
return { supported: true, reasonKey: '' };
}
// 经典 TCP 反弹 Shell(bash/nc,metadata.transport=tcp_reverse)
if (transport === 'tcp_reverse' || (session.hostname && String(session.hostname).indexOf('tcp_') === 0)) {
return { supported: false, reasonKey: 'c2.files.uploadTcpShell' };
}
return { supported: true, reasonKey: '' };
};
C2.updateFileUploadButton = function(session) {
if (!session && C2.selectedSessionId) {
session = C2.sessions.find(function(s) { return s.id === C2.selectedSessionId; });
}
var btn = document.getElementById('c2-file-upload-btn');
if (!btn) return;
var cap = C2.sessionSupportsUpload(session);
btn.disabled = !cap.supported || !!C2.fileUploadBusy;
btn.title = cap.supported ? c2t('c2.files.upload') : c2t(cap.reasonKey);
if (!cap.supported) {
btn.classList.add('is-disabled');
} else {
btn.classList.remove('is-disabled');
}
var hint = document.getElementById('c2-file-upload-hint');
if (hint) {
if (!cap.supported) {
hint.hidden = false;
hint.textContent = c2t(cap.reasonKey);
} else {
hint.hidden = true;
hint.textContent = '';
}
}
};
C2.setFileUploadProgress = function(visible, percent, filename) {
var row = document.getElementById('c2-file-upload-progress');
if (!row) return;
if (!visible) {
row.hidden = true;
return;
}
row.hidden = false;
var fill = document.getElementById('c2-file-upload-progress-fill');
var label = document.getElementById('c2-file-upload-progress-label');
if (fill) fill.style.width = Math.max(0, Math.min(100, percent || 0)) + '%';
if (label) {
label.textContent = c2t('c2.files.uploading', { name: filename || '', percent: percent || 0 });
}
};
C2.openFileUploadPicker = function() {
if (!C2.selectedSessionId || C2.fileUploadBusy) return;
var session = C2.sessions.find(function(s) { return s.id === C2.selectedSessionId; });
var cap = C2.sessionSupportsUpload(session);
if (!cap.supported) {
showToast(c2t(cap.reasonKey), 'warn');
return;
}
var inp = document.getElementById('c2-file-upload-input');
if (inp) inp.click();
};
C2.onC2FileUploadPick = function(ev) {
var input = ev && ev.target;
var file = input && input.files && input.files[0];
if (!file) return;
if (input) input.value = '';
C2.uploadFileToImplant(file);
};
C2.uploadFileToImplant = function(file) {
if (!C2.selectedSessionId || C2.fileUploadBusy || !file) return;
var sessionId = C2.selectedSessionId;
var remotePath = C2.resolveRemotePath(C2.currentPath || '.', file.name);
var uploadUrl = API_BASE + '/files/upload';
C2.fileUploadBusy = true;
C2.updateFileUploadButton();
C2.setFileUploadProgress(true, 0, file.name);
var form = new FormData();
form.append('session_id', sessionId);
form.append('remote_path', remotePath);
form.append('file', file);
var uploadPromise;
if (typeof apiUploadWithProgress === 'function') {
uploadPromise = apiUploadWithProgress(uploadUrl, form, {
onProgress: function(p) {
C2.setFileUploadProgress(true, Math.min(p.percent || 0, 50), file.name);
}
});
} else if (typeof apiFetch === 'function') {
uploadPromise = apiFetch(uploadUrl, { method: 'POST', body: form });
} else {
uploadPromise = fetch(uploadUrl, { method: 'POST', body: form });
}
uploadPromise.then(function(res) {
if (!res.ok) {
return res.text().then(function(text) {
throw new Error(text || c2t('c2.files.failed'));
});
}
return res.json();
}).then(function(uploadData) {
var fileId = uploadData && uploadData.file_id;
if (!fileId) throw new Error(c2t('c2.files.failed'));
C2.setFileUploadProgress(true, 55, file.name);
return apiRequest('POST', API_BASE + '/tasks', {
session_id: sessionId,
task_type: 'upload',
payload: { remote_path: remotePath, file_id: fileId }
});
}).then(function(taskData) {
if (taskData && taskData.error) throw new Error(taskData.error);
var taskId = taskData.task && taskData.task.id ? taskData.task.id : taskData.task_id;
if (!taskId) {
showToast(c2t('c2.files.uploadQueued'), 'success');
C2.fileUploadBusy = false;
C2.setFileUploadProgress(false);
C2.updateFileUploadButton();
return;
}
if (taskData.task && taskData.task.approvalStatus === 'pending') {
showToast(c2t('c2.files.uploadPendingApproval'), 'info');
}
C2.waitForFileUpload(taskId, file.name);
}).catch(function(err) {
showToast((err && err.message) || c2t('c2.files.failed'), 'error');
C2.fileUploadBusy = false;
C2.setFileUploadProgress(false);
C2.updateFileUploadButton();
});
};
C2.waitForFileUpload = function(taskId, filename) {
var attempts = 0;
var check = function() {
if (++attempts > 120) {
showToast(c2t('c2.files.timeout'), 'error');
C2.fileUploadBusy = false;
C2.setFileUploadProgress(false);
C2.updateFileUploadButton();
return;
}
apiRequest('GET', API_BASE + '/tasks/' + taskId).then(function(data) {
var task = data.task;
if (task && task.approvalStatus === 'pending' && task.status === 'queued') {
C2.setFileUploadProgress(true, 60, filename);
setTimeout(check, 1000);
return;
}
if (task && task.status === 'success') {
C2.setFileUploadProgress(true, 100, filename);
showToast(c2t('c2.files.uploadOk'), 'success');
C2.fileUploadBusy = false;
setTimeout(function() { C2.setFileUploadProgress(false); }, 400);
C2.updateFileUploadButton();
C2.refreshFiles();
} else if (task && task.status === 'failed') {
showToast(task.error || task.resultText || c2t('c2.files.failed'), 'error');
C2.fileUploadBusy = false;
C2.setFileUploadProgress(false);
C2.updateFileUploadButton();
} else {
var pct = 60 + Math.min(35, Math.floor(attempts / 3));
C2.setFileUploadProgress(true, pct, filename);
setTimeout(check, 500);
}
}).catch(function() {
C2.fileUploadBusy = false;
C2.setFileUploadProgress(false);
C2.updateFileUploadButton();
});
};
check();
};
C2.saveDownloadBlob = function(blob, filename) {
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = filename;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(a.href);
};
C2.saveDownloadContent = function(content, filename) {
const text = String(content || '');
if (C2.isDownloadShellError(text)) {
throw new Error(text.trim() || c2t('c2.files.failed'));
}
const b64 = text.replace(/\s/g, '');
let bytes;
try {
if (/^[A-Za-z0-9+/=]+$/.test(b64) && b64.length > 0) {
const binary = atob(b64);
bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
} else {
bytes = new TextEncoder().encode(text);
}
} catch (e) {
bytes = new TextEncoder().encode(text);
}
C2.saveDownloadBlob(new Blob([bytes], { type: 'application/octet-stream' }), filename);
};
C2.fetchTaskResultFile = function(taskId, filename) {
const url = `${API_BASE}/tasks/${taskId}/result-file`;
const fetchFn = (typeof apiFetch === 'function') ? apiFetch : fetch;
fetchFn(url).then(resp => {
if (!resp.ok) throw new Error('download failed: ' + resp.status);
return resp.blob();
}).then(blob => {
C2.saveDownloadBlob(blob, filename);
}).catch(err => {
showToast((err && err.message) || c2t('c2.files.failed'), 'error');
});
};
C2.waitForFileDownload = function(taskId, filename) {
let attempts = 0;
const check = () => {
if (++attempts > 120) {
showToast(c2t('c2.files.timeout'), 'error');
return;
}
apiRequest('GET', `${API_BASE}/tasks/${taskId}`).then(data => {
const task = data.task;
if (task && task.status === 'success') {
if (task.resultBlobPath) {
C2.fetchTaskResultFile(taskId, filename);
} else if (task.resultText != null) {
try {
C2.saveDownloadContent(task.resultText, filename);
showToast(c2t('c2.files.downloadOk'), 'success');
} catch (err) {
showToast((err && err.message) || c2t('c2.files.failed'), 'error');
}
} else {
C2.saveDownloadBlob(new Blob([], { type: 'application/octet-stream' }), filename);
showToast(c2t('c2.files.downloadOk'), 'success');
}
} else if (task && task.status === 'failed') {
showToast(task.error || task.resultText || c2t('c2.files.failed'), 'error');
} else {
setTimeout(check, 500);
}
});
};
check();
};
C2.downloadFile = function(filename) {
if (!C2.selectedSessionId) return;
const remotePath = C2.resolveRemotePath(C2.currentPath || '.', filename);
apiRequest('POST', `${API_BASE}/tasks`, {
session_id: C2.selectedSessionId,
task_type: 'download',
payload: { remote_path: remotePath }
}).then(data => {
if (data.error) {
showToast(data.error, 'error');
return;
}
const taskId = data.task?.id || data.task_id;
if (!taskId) {
showToast(c2t('c2.payloads.toastDownloadQueued'), 'success');
return;
}
C2.waitForFileDownload(taskId, filename);
});
};
// ============================================================================
// 任务管理
// ============================================================================
C2.loadTasks = function(page) {
const p = page != null ? page : (C2.tasksPage || 1);
C2.tasksPage = p;
const ps = C2.tasksPageSize || 10;
apiRequest('GET', `${API_BASE}/tasks?page=${encodeURIComponent(String(p))}&page_size=${encodeURIComponent(String(ps))}`).then(data => {
if (data.error) {
showToast(String(data.error), 'error');
return;
}
C2.tasks = data.tasks || [];
C2.tasksTotal = typeof data.total === 'number' ? data.total : (C2.tasks.length || 0);
if (typeof data.pending_queued_count === 'number') {
C2.tasksPendingQueuedCount = data.pending_queued_count;
}
const maxPage = Math.max(1, Math.ceil(C2.tasksTotal / ps));
if (p > maxPage) {
C2.loadTasks(maxPage);
return;
}
C2.renderTasks();
C2.renderTasksPagination();
C2.syncTasksToolbar();
C2.updateDashboardStats();
}).catch(err => {
showToast(err.message || String(err), 'error');
});
};
C2.goTasksPage = function(targetPage) {
const totalPages = Math.max(1, Math.ceil((C2.tasksTotal || 0) / (C2.tasksPageSize || 10)));
if (targetPage < 1 || targetPage > totalPages) return;
C2.loadTasks(targetPage);
const list = document.getElementById('c2-task-list');
if (list) list.scrollIntoView({ behavior: 'smooth', block: 'start' });
};
C2.changeTasksPageSize = function() {
const sel = document.getElementById('c2-tasks-page-size-pagination');
if (!sel) return;
const n = parseInt(sel.value, 10);
if (n > 0) {
C2.tasksPageSize = n;
C2.loadTasks(1);
}
};
C2.renderTasksPagination = function() {
const paginationContainer = document.getElementById('c2-tasks-pagination');
if (!paginationContainer) return;
const total = C2.tasksTotal || 0;
const currentPage = C2.tasksPage || 1;
const pageSize = C2.tasksPageSize || 10;
const totalPages = Math.max(1, Math.ceil(total / pageSize));
if (total === 0) {
paginationContainer.innerHTML = '';
return;
}
const start = total === 0 ? 0 : (currentPage - 1) * pageSize + 1;
const end = Math.min(currentPage * pageSize, total);
let html = '';
paginationContainer.innerHTML = html;
if (typeof applyTranslations === 'function') applyTranslations(paginationContainer);
};
C2.collectCheckedTaskIds = function() {
return Array.from(document.querySelectorAll('.c2-task-row-check:checked')).map(cb => cb.getAttribute('data-id')).filter(Boolean);
};
C2.syncTasksToolbar = function() {
const batchBtn = document.getElementById('c2-tasks-batch-delete');
const ids = C2.collectCheckedTaskIds();
if (batchBtn) batchBtn.disabled = ids.length === 0;
const all = document.querySelectorAll('.c2-task-row-check');
const selAll = document.getElementById('c2-tasks-select-all');
if (selAll && all.length) {
const nChecked = document.querySelectorAll('.c2-task-row-check:checked').length;
selAll.checked = nChecked === all.length;
selAll.indeterminate = nChecked > 0 && nChecked < all.length;
} else if (selAll) {
selAll.checked = false;
selAll.indeterminate = false;
}
};
C2.onTasksSelectAll = function(checked) {
document.querySelectorAll('.c2-task-row-check').forEach(cb => { cb.checked = checked; });
C2.syncTasksToolbar();
};
C2.deleteTaskById = function(id) {
if (!id) return;
if (!confirm(c2t('c2.tasks.confirmDeleteOne'))) return;
apiRequest('DELETE', `${API_BASE}/tasks`, { ids: [id] }).then(data => {
if (data.error) {
showToast(String(data.error), 'error');
return;
}
showToast(c2t('c2.tasks.toastDeleted', { n: data.deleted != null ? data.deleted : 1 }), 'success');
C2.loadTasks(C2.tasksPage || 1);
}).catch(err => showToast(err.message || String(err), 'error'));
};
C2.deleteSelectedTasks = function() {
const ids = C2.collectCheckedTaskIds();
if (!ids.length) {
showToast(c2t('c2.tasks.toastSelectFirst'), 'warn');
return;
}
if (!confirm(c2t('c2.tasks.confirmBatchDelete', { n: ids.length }))) return;
apiRequest('DELETE', `${API_BASE}/tasks`, { ids }).then(data => {
if (data.error) {
showToast(String(data.error), 'error');
return;
}
const deleted = data.deleted != null ? data.deleted : ids.length;
showToast(c2t('c2.tasks.toastDeleted', { n: deleted }), 'success');
C2.loadTasks(C2.tasksPage || 1);
}).catch(err => showToast(err.message || String(err), 'error'));
};
C2.loadSessionTasks = function(sessionId) {
apiRequest('GET', `${API_BASE}/tasks?session_id=${encodeURIComponent(sessionId)}&limit=50`).then(data => {
const container = document.getElementById('c2-session-tasks-list');
const tasks = data.tasks || [];
if (typeof data.pending_queued_count === 'number') {
C2.tasksPendingQueuedCount = data.pending_queued_count;
C2.updateDashboardStats();
}
if (!container) return;
if (tasks.length === 0) {
container.innerHTML = '' + escapeHtml(c2t('c2.tasks.emptySession')) + '
';
return;
}
container.innerHTML = tasks.map(t => {
const rawId = t.id || '';
const cmd = formatTaskCommand(t);
const cmdShort = truncateCommand(cmd, 40);
return `
${escapeHtml(t.taskType || '')}
${cmdShort ? `${escapeHtml(cmdShort)} ` : ''}
${escapeHtml(taskStatusLabel(t.status))} | ${formatDuration(t.durationMs)}
${escapeHtml(c2t('c2.tasks.view'))}
`;
}).join('');
});
};
C2.renderTasks = function() {
const container = document.getElementById('c2-task-list');
if (!container) return;
const selAll = document.getElementById('c2-tasks-select-all');
if (selAll) {
selAll.checked = false;
selAll.indeterminate = false;
}
if (C2.tasks.length === 0) {
container.innerHTML = '' + escapeHtml(c2t('c2.tasks.emptyAll')) + '
';
if (selAll) selAll.disabled = true;
C2.syncTasksToolbar();
return;
}
if (selAll) selAll.disabled = false;
const delTitle = escapeHtml(c2t('c2.tasks.deleteOne'));
container.innerHTML = `
`;
C2.syncTasksToolbar();
if (typeof applyTranslations === 'function') applyTranslations(container);
};
C2.viewTask = function(id) {
const modal = document.getElementById('c2-modal');
const content = document.getElementById('c2-modal-content');
if (!content) return;
const renderTaskModal = function(t) {
if (!t || !modal) return;
const cmd = formatTaskCommand(t);
const hasPayload = t.payload && typeof t.payload === 'object' && Object.keys(t.payload).length > 0;
const modalBox = modal.querySelector('.c2-modal');
if (modalBox) modalBox.classList.add('c2-modal--wide');
content.innerHTML = `
${escapeHtml(c2t('c2.tasks.labelId'))}
${escapeHtml(t.id || '-')}
${escapeHtml(c2t('c2.tasks.labelSession'))}
${escapeHtml(t.sessionId || '-')}
${escapeHtml(c2t('c2.tasks.labelType'))}
${escapeHtml(t.taskType || '-')}
${escapeHtml(c2t('c2.tasks.labelDuration'))}
${formatDuration(t.durationMs)}
${escapeHtml(c2t('c2.tasks.labelCreated'))}
${formatTime(t.createdAt) || '-'}
${escapeHtml(c2t('c2.tasks.labelSent'))}
${formatTime(t.sentAt) || '-'}
${escapeHtml(c2t('c2.tasks.labelCompleted'))}
${formatTime(t.completedAt) || '-'}
${cmd ? `
` : ''}
${hasPayload && !cmd ? `
${escapeHtml(JSON.stringify(t.payload, null, 2))}
` : ''}
${t.error ? `
` : ''}
${t.resultText ? `
${escapeHtml(t.resultText)}
` : ''}
`;
modal.style.display = 'flex';
};
const local = C2.tasks.find(x => x.id === id);
if (local) {
renderTaskModal(local);
return;
}
apiRequest('GET', `${API_BASE}/tasks/${encodeURIComponent(id)}`).then(data => {
if (data.error) {
showToast(String(data.error), 'error');
return;
}
if (data.task) renderTaskModal(data.task);
else showToast(c2t('c2.tasks.emptyAll'), 'warn');
}).catch(err => showToast(err.message || String(err), 'error'));
};
C2.cancelTask = function(id) {
apiRequest('POST', `${API_BASE}/tasks/${encodeURIComponent(id)}/cancel`, {}).then(data => {
if (data.error) showToast(String(data.error), 'error');
else {
showToast(c2t('c2.tasks.toastCancelled'), 'success');
C2.loadTasks(C2.tasksPage || 1);
}
}).catch(err => showToast(err.message || String(err), 'error'));
};
// ============================================================================
// Payload 生成
// ============================================================================
C2.loadListenersForPayload = function() {
apiRequest('GET', `${API_BASE}/listeners`).then(data => {
if (data.error) {
showToast(data.error, 'error');
return;
}
C2.listeners = data.listeners || [];
C2.renderPayloadPage();
}).catch(err => {
showToast(c2t('c2.payloads.toastLoadListenersFail', { msg: err.message || '' }), 'error');
});
};
var onelinerKindsByListenerType = {
'tcp_reverse': [
{ value: 'bash', label: 'Bash (/dev/tcp)' },
{ value: 'nc', label: 'Netcat (-e)' },
{ value: 'nc_mkfifo', label: 'Netcat (mkfifo)' },
{ value: 'python', label: 'Python' },
{ value: 'perl', label: 'Perl' },
{ value: 'powershell', label: 'PowerShell' }
],
'http_beacon': [
{ value: 'curl_beacon', label: 'Curl Beacon (HTTP)' }
],
'https_beacon': [
{ value: 'curl_beacon', label: 'Curl Beacon (HTTP)' }
],
'websocket': [
{ value: 'curl_beacon', label: 'Curl Beacon (HTTP)' }
]
};
C2.updateOnelinerKinds = function() {
var listenerSelect = document.getElementById('c2-payload-listener');
var kindSelect = document.getElementById('c2-payload-kind');
if (!listenerSelect || !kindSelect) return;
var listenerId = listenerSelect.value;
var listener = (C2.listeners || []).find(function(l) { return l.id === listenerId; });
var ltype = listener ? listener.type : '';
var kinds = onelinerKindsByListenerType[ltype] || [];
if (kinds.length === 0) {
kindSelect.innerHTML = '' + escapeHtml(c2t('c2.payloads.noKindOption')) + ' ';
} else {
kindSelect.innerHTML = kinds.map(function(k) {
return '' + k.label + ' ';
}).join('');
}
};
C2.updateLoopbackBuildHint = function() {
const sel = document.getElementById('c2-build-listener');
const hint = document.getElementById('c2-build-loopback-hint');
if (!hint) return;
const override = document.getElementById('c2-build-host') && String(document.getElementById('c2-build-host').value || '').trim();
if (override) {
hint.style.display = 'none';
return;
}
const id = sel && sel.value;
if (!id) {
hint.style.display = 'none';
return;
}
const l = (C2.listeners || []).find(function(x) { return x.id === id; });
const h = (l && l.bindHost ? String(l.bindHost) : '').toLowerCase().trim();
if (h === '127.0.0.1' || h === 'localhost' || h === '::1') {
hint.textContent = c2t('c2.payloads.loopbackBeaconWarning');
hint.style.display = 'block';
} else {
hint.style.display = 'none';
}
};
C2.renderPayloadPage = function() {
const optionsHtml = C2.listeners.length > 0
? C2.listeners.map(l =>
`${escapeHtml(l.name)} (${l.type} ${l.bindHost}:${l.bindPort}) `
).join('')
: '' + escapeHtml(c2t('c2.payloads.noListenersOption')) + ' ';
const listenerSelect = document.getElementById('c2-payload-listener');
if (listenerSelect) {
listenerSelect.innerHTML = optionsHtml;
listenerSelect.removeEventListener('change', C2.updateOnelinerKinds);
listenerSelect.addEventListener('change', C2.updateOnelinerKinds);
}
const buildSelect = document.getElementById('c2-build-listener');
if (buildSelect) {
const listeners = C2.listeners || [];
let buildOptionsHtml;
if (listeners.length > 0) {
buildOptionsHtml = listeners.map(l =>
`${escapeHtml(l.name)} (${l.type} ${l.bindHost}:${l.bindPort}) `
).join('');
} else {
buildOptionsHtml = '' + escapeHtml(c2t('c2.payloads.noListenersOption')) + ' ';
}
buildSelect.innerHTML = buildOptionsHtml;
buildSelect.removeEventListener('change', C2.updateLoopbackBuildHint);
buildSelect.addEventListener('change', C2.updateLoopbackBuildHint);
C2.updateLoopbackBuildHint();
}
const buildHostInput = document.getElementById('c2-build-host');
if (buildHostInput) {
buildHostInput.removeEventListener('input', C2.updateLoopbackBuildHint);
buildHostInput.addEventListener('input', C2.updateLoopbackBuildHint);
}
C2.updateOnelinerKinds();
const buildBtn = document.getElementById('c2-build-btn');
if (buildBtn && !buildBtn.disabled) buildBtn.textContent = c2t('c2.payloads.buildBeaconBtn');
const genBtn = document.getElementById('c2-generate-oneliner-btn');
if (genBtn) genBtn.textContent = c2t('c2.payloads.generateOnelinerBtn');
};
C2.generateOneliner = function() {
const listenerId = document.getElementById('c2-payload-listener')?.value;
const kind = document.getElementById('c2-payload-kind')?.value || 'bash';
const host = document.getElementById('c2-payload-host')?.value;
if (!listenerId) {
showToast(c2t('c2.payloads.toastPickListener'), 'error');
return;
}
apiRequest('POST', `${API_BASE}/payloads/oneliner`, {
listener_id: listenerId,
kind: kind,
host: host
}).then(data => {
if (data.error) {
showToast(data.error, 'error');
} else {
const output = document.getElementById('c2-oneliner-output');
if (output) {
output.textContent = data.oneliner;
output.style.display = 'block';
}
}
}).catch(err => {
showToast(c2t('c2.payloads.toastOnelinerFail', { msg: err.message || '' }), 'error');
});
};
C2.copyOneliner = function() {
const el = document.getElementById('c2-oneliner-output');
if (el && el.textContent) copyToClipboard(el.textContent);
};
C2.buildBeacon = function() {
const listenerId = document.getElementById('c2-build-listener')?.value;
const os = document.getElementById('c2-build-os')?.value || 'linux';
const arch = document.getElementById('c2-build-arch')?.value || 'amd64';
const host = document.getElementById('c2-build-host')?.value;
if (!listenerId) {
showToast(c2t('c2.payloads.toastPickListener'), 'error');
return;
}
const btn = document.getElementById('c2-build-btn');
if (btn) {
btn.disabled = true;
btn.textContent = c2t('c2.payloads.building');
}
apiRequest('POST', `${API_BASE}/payloads/build`, {
listener_id: listenerId,
os: os,
arch: arch,
host: host
}).then(data => {
if (btn) {
btn.disabled = false;
btn.textContent = c2t('c2.payloads.buildBeaconBtn');
}
if (data.error) {
showToast(data.error, 'error');
} else {
showToast(c2t('c2.payloads.toastBuildSuccess', { bytes: data.payload?.size_bytes }), 'success');
const result = document.getElementById('c2-build-result');
if (result) {
result.innerHTML = `
✓ ${escapeHtml(c2t('c2.payloads.buildSuccessTitle'))}
${escapeHtml(c2t('c2.payloads.buildMetaOsArch', { os: data.payload?.os, arch: data.payload?.arch }))}
${escapeHtml(c2t('c2.payloads.buildSize', { bytes: data.payload?.size_bytes }))}
${escapeHtml(c2t('c2.payloads.download'))}
`;
}
}
}).catch(err => {
if (btn) {
btn.disabled = false;
btn.textContent = c2t('c2.payloads.buildBeaconBtn');
}
showToast(c2t('c2.payloads.toastBuildFail', { msg: err.message || '' }), 'error');
});
};
// ============================================================================
// 事件审计
// ============================================================================
C2.loadEvents = function(page) {
const p = page != null ? page : (C2.eventsPage || 1);
C2.eventsPage = p;
const ps = C2.eventsPageSize || 10;
apiRequest('GET', `${API_BASE}/events?page=${encodeURIComponent(String(p))}&page_size=${encodeURIComponent(String(ps))}`).then(data => {
if (data.error) {
showToast(String(data.error), 'error');
return;
}
C2.events = data.events || [];
C2.eventsTotal = typeof data.total === 'number' ? data.total : (C2.events.length || 0);
const maxPage = Math.max(1, Math.ceil(C2.eventsTotal / ps));
if (p > maxPage) {
C2.loadEvents(maxPage);
return;
}
C2.renderEvents();
C2.renderEventsPagination();
C2.syncEventsToolbar();
}).catch(err => {
showToast(err.message || String(err), 'error');
});
};
C2.goEventsPage = function(targetPage) {
const totalPages = Math.max(1, Math.ceil((C2.eventsTotal || 0) / (C2.eventsPageSize || 10)));
if (targetPage < 1 || targetPage > totalPages) return;
C2.loadEvents(targetPage);
const list = document.getElementById('c2-event-list');
if (list) list.scrollIntoView({ behavior: 'smooth', block: 'start' });
};
C2.changeEventsPageSize = function() {
const sel = document.getElementById('c2-events-page-size-pagination');
if (!sel) return;
const n = parseInt(sel.value, 10);
if (n > 0) {
C2.eventsPageSize = n;
C2.loadEvents(1);
}
};
C2.renderEventsPagination = function() {
const paginationContainer = document.getElementById('c2-events-pagination');
if (!paginationContainer) return;
const total = C2.eventsTotal || 0;
const currentPage = C2.eventsPage || 1;
const pageSize = C2.eventsPageSize || 10;
const totalPages = Math.max(1, Math.ceil(total / pageSize));
if (total === 0) {
paginationContainer.innerHTML = '';
return;
}
const start = total === 0 ? 0 : (currentPage - 1) * pageSize + 1;
const end = Math.min(currentPage * pageSize, total);
let html = '';
paginationContainer.innerHTML = html;
if (typeof applyTranslations === 'function') applyTranslations(paginationContainer);
};
C2.collectCheckedEventIds = function() {
return Array.from(document.querySelectorAll('.c2-event-check:checked')).map(cb => cb.getAttribute('data-id')).filter(Boolean);
};
C2.syncEventsToolbar = function() {
const batchBtn = document.getElementById('c2-events-batch-delete');
const ids = C2.collectCheckedEventIds();
if (batchBtn) batchBtn.disabled = ids.length === 0;
const all = document.querySelectorAll('.c2-event-check');
const selAll = document.getElementById('c2-events-select-all');
if (selAll && all.length) {
const nChecked = document.querySelectorAll('.c2-event-check:checked').length;
selAll.checked = nChecked === all.length;
selAll.indeterminate = nChecked > 0 && nChecked < all.length;
} else if (selAll) {
selAll.checked = false;
selAll.indeterminate = false;
}
};
C2.onEventsSelectAll = function(checked) {
document.querySelectorAll('.c2-event-check').forEach(cb => { cb.checked = checked; });
C2.syncEventsToolbar();
};
C2.deleteEventById = function(id) {
if (!id) return;
if (!confirm(c2t('c2.events.confirmDeleteOne'))) return;
apiRequest('DELETE', `${API_BASE}/events`, { ids: [id] }).then(data => {
if (data.error) {
showToast(String(data.error), 'error');
return;
}
showToast(c2t('c2.events.toastDeleted', { n: data.deleted != null ? data.deleted : 1 }), 'success');
C2.loadEvents(C2.eventsPage || 1);
}).catch(err => showToast(err.message || String(err), 'error'));
};
C2.deleteSelectedEvents = function() {
const ids = C2.collectCheckedEventIds();
if (!ids.length) {
showToast(c2t('c2.events.toastSelectFirst'), 'warn');
return;
}
if (!confirm(c2t('c2.events.confirmBatchDelete', { n: ids.length }))) return;
apiRequest('DELETE', `${API_BASE}/events`, { ids }).then(data => {
if (data.error) {
showToast(String(data.error), 'error');
return;
}
const deleted = data.deleted != null ? data.deleted : ids.length;
showToast(c2t('c2.events.toastDeleted', { n: deleted }), 'success');
C2.loadEvents(C2.eventsPage || 1);
}).catch(err => showToast(err.message || String(err), 'error'));
};
C2.renderEvents = function() {
const container = document.getElementById('c2-event-list');
if (!container) return;
const selAll = document.getElementById('c2-events-select-all');
if (selAll) {
selAll.checked = false;
selAll.indeterminate = false;
}
if (C2.events.length === 0) {
container.innerHTML = '' + escapeHtml(c2t('c2.events.empty')) + '
';
if (selAll) selAll.disabled = true;
C2.syncEventsToolbar();
return;
}
if (selAll) selAll.disabled = false;
const delTitle = escapeHtml(c2t('c2.events.deleteOne'));
container.innerHTML = C2.events.map(e => {
const eid = escapeHtml(e.id || '');
return `
${escapeHtml(e.message)}
${formatTime(e.createdAt)} · ${escapeHtml(e.category || '')}${e.sessionId ? ' · ' + escapeHtml(String(e.sessionId).substring(0, 8)) : ''}
🗑
`;
}).join('');
C2.syncEventsToolbar();
if (typeof applyTranslations === 'function') applyTranslations(container);
};
C2.connectEventStream = function() {
if (C2.eventSource) C2.eventSource.close();
let streamUrl = `${API_BASE}/events/stream`;
if (typeof authToken !== 'undefined' && authToken) {
streamUrl += `?token=${encodeURIComponent(authToken)}`;
}
C2.eventSource = new EventSource(streamUrl);
C2.eventSource.onmessage = (e) => {
try {
const event = JSON.parse(e.data);
C2.onEvent(event);
} catch (err) {}
};
C2.eventSource.onerror = () => {
setTimeout(() => C2.connectEventStream(), 5000);
};
};
C2.onEvent = function(event) {
if (window.currentPageId === 'c2-events' && (C2.eventsPage || 1) === 1) {
C2.loadEvents(1);
}
const msg = event.message || '';
const sessionOnline = event.category === 'session' && (
msg.includes('上线') || msg.includes('新会话') || /new session/i.test(msg)
);
if (event.level === 'critical' || sessionOnline) {
showToast(`[${event.category}] ${event.message}`, event.level === 'critical' ? 'error' : 'info');
}
C2.updateDashboardStats();
};
// ============================================================================
// Profile 管理
// ============================================================================
C2.loadProfiles = function() {
apiRequest('GET', `${API_BASE}/profiles`).then(data => {
C2.profiles = data.profiles || [];
C2.renderProfiles();
});
};
C2.renderProfiles = function() {
const container = document.getElementById('c2-profile-list');
if (!container) return;
if (C2.profiles.length === 0) {
container.innerHTML = '' + escapeHtml(c2t('c2.profiles.empty')) + '
';
return;
}
const defVal = c2t('c2.profiles.defaultValue');
container.innerHTML = C2.profiles.map(p => `
UA: ${escapeHtml(p.userAgent || defVal)}
URIs: ${escapeHtml((p.uris || []).join(', ') || defVal)}
Jitter: ${p.jitterMinMs || 0}ms – ${p.jitterMaxMs || 0}ms
`).join('');
};
C2.showCreateProfileModal = function() {
const modal = document.getElementById('c2-modal');
const content = document.getElementById('c2-modal-content');
if (!content) return;
content.innerHTML = `
`;
modal.style.display = 'flex';
};
C2.createProfile = function() {
const name = document.getElementById('c2-profile-name')?.value.trim();
if (!name) {
showToast(c2t('c2.profiles.toastNameRequired'), 'error');
return;
}
const userAgent = document.getElementById('c2-profile-ua')?.value.trim() || '';
const urisRaw = document.getElementById('c2-profile-uris')?.value.trim() || '';
const uris = urisRaw.split('\n').map(u => u.trim()).filter(u => u);
const jitterMinMs = parseInt(document.getElementById('c2-profile-jmin')?.value) || 100;
const jitterMaxMs = parseInt(document.getElementById('c2-profile-jmax')?.value) || 500;
let responseHeaders = {};
const headersRaw = document.getElementById('c2-profile-headers')?.value.trim();
if (headersRaw) {
try { responseHeaders = JSON.parse(headersRaw); }
catch (e) { showToast(c2t('c2.profiles.toastInvalidHeadersJson'), 'error'); return; }
}
apiRequest('POST', `${API_BASE}/profiles`, {
name,
user_agent: userAgent,
uris,
jitter_min_ms: jitterMinMs,
jitter_max_ms: jitterMaxMs,
response_headers: responseHeaders
}).then(data => {
if (data.error) {
showToast(data.error, 'error');
} else {
showToast(c2t('c2.profiles.toastCreated'), 'success');
C2.closeModal();
C2.loadProfiles();
}
});
};
C2.deleteProfile = function(id) {
if (!confirm(c2t('c2.profiles.confirmDelete'))) return;
apiRequest('DELETE', `${API_BASE}/profiles/${id}`, {}).then(data => {
showToast(c2t('c2.profiles.toastDeleted'), 'success');
C2.loadProfiles();
});
};
// ============================================================================
// 仪表盘
// ============================================================================
C2.updateDashboardStats = function() {
const runningListeners = C2.listeners.filter(l => l.status === 'running').length;
const activeSessions = C2.sessions.filter(s => s.status === 'active').length;
const pendingTasks = typeof C2.tasksPendingQueuedCount === 'number'
? C2.tasksPendingQueuedCount
: C2.tasks.filter(t => t.status === 'queued' || t.status === 'pending').length;
const elListeners = document.getElementById('c2-stat-listeners');
const elSessions = document.getElementById('c2-stat-sessions');
const elPending = document.getElementById('c2-stat-pending');
if (elListeners) elListeners.textContent = runningListeners;
if (elSessions) elSessions.textContent = activeSessions;
if (elPending) elPending.textContent = pendingTasks;
};
// ============================================================================
// 模态框
// ============================================================================
C2.copyTaskBlock = function(elementId) {
const el = document.getElementById(elementId);
if (el && el.textContent) copyToClipboard(el.textContent);
};
C2.closeModal = function() {
const modal = document.getElementById('c2-modal');
if (modal) {
modal.style.display = 'none';
const modalBox = modal.querySelector('.c2-modal');
if (modalBox) modalBox.classList.remove('c2-modal--wide');
}
};
// ============================================================================
// 暴露到全局
// ============================================================================
window.C2 = C2;
// 页面切换监听
window.addEventListener('pageChanged', function(e) {
if (e.detail?.pageId?.startsWith('c2')) {
C2.init();
}
});
// DOM 加载完成后初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
if (window.currentPageId?.startsWith('c2')) C2.init();
});
} else {
if (window.currentPageId?.startsWith('c2')) C2.init();
}
document.addEventListener('languagechange', function () {
try {
if (!window.currentPageId || !String(window.currentPageId).startsWith('c2')) return;
if (typeof applyTranslations === 'function') applyTranslations(document);
C2.init();
if (C2.selectedSessionId && (window.currentPageId === 'c2-sessions')) {
C2.renderSessions();
C2.renderSessionDetail(C2.selectedSessionId);
}
} catch (e) {
console.warn('languagechange C2 refresh failed', e);
}
});
})();