鸿蒙PC平台 Goby 网络资产测绘工具适配实战:基于 Electron 壳方案的真实 TCP 扫描与漏洞检测引擎开发
Goby 是一款网络资产测绘与安全扫描工具,能够对目标网络进行端口探测、服务指纹识别和常见漏洞检测。本文记录在鸿蒙 PC 平台上,基于 Electron 壳方案从零构建该工具的完整流程,帮助开发者理解网络扫描类应用在鸿蒙平台的适配要点。
欢迎加入开源鸿蒙 PC 社区:https://harmonypc.csdn.net/
欢迎在 PC 社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper
AtomGit 仓库地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_goby
一、技术架构分析
1.1 原始架构(Go 语言版)
- 技术栈:Go + Web 前端
- 核心逻辑:SYN 半开放扫描 + 内置指纹库 + 数百条 PoC
- 部署方式:独立安装包
1.2 目标架构(鸿蒙 Electron)
- 技术栈:Electron + HTML/CSS/JavaScript + 鸿蒙 web_engine 模块
- 核心逻辑:纯 Node.js 内置模块(net/http/dns/os)实现 TCP 全连接扫描 + HTTP 指纹 + 8 条漏洞规则
- 部署方式:HAP 包(鸿蒙应用)
1.3 架构差异
| 对比项 | Go 原版 | 鸿蒙适配版 |
|---|---|---|
| 扫描引擎 | Go net 包 | Node.js net 模块 |
| 端口探测 | SYN 半开放扫描 | TCP 全连接(net.Socket) |
| 指纹识别 | 内置指纹库 | HTTP GET + 标题/Server 头提取 |
| 漏洞检测 | 数百条 PoC | 8 条核心规则 |
| 前端框架 | React | HTML/CSS/Vanilla JavaScript |
| 外部依赖 | go mod | 零依赖(纯 Node.js 内置模块) |
| 部署方式 | 独立安装包 | HAP 包(鸿蒙应用) |
二、环境准备
2.1 开发环境要求
- 操作系统:Windows 10/11
- 开发工具:DevEco Studio(鸿蒙官方 IDE)
- HarmonyOS SDK:API 15+
- Node.js:v20+(Electron 依赖)
2.2 项目结构
ohos_hap/
├── electron-apps/
│ └── Goby/ # Goby Electron 应用源码
│ ├── main.js # 主进程(窗口管理 + IPC + 扫描调度)
│ ├── scanner.js # 扫描引擎(TCP 探测 + HTTP 指纹 + 漏洞检测)
│ ├── renderer.js # 渲染进程(UI 交互 + 看板渲染)
│ ├── index.html # HTML 布局(IDE 风格)
│ ├── package.json # 项目配置
│ └── styles/
│ └── goby.css # Catppuccin Mocha 深色主题
├── web_engine/ # 鸿蒙 web_engine 模块
│ └── src/main/resources/
│ └── resfile/resources/app/ # 部署目录
│ ├── main.js
│ ├── scanner.js
│ ├── renderer.js
│ ├── index.html
│ ├── package.json
│ └── styles/goby.css
└── build-profile.json5 # 鸿蒙构建配置
三、核心适配流程
3.1 第一步:实现扫描引擎
文件:electron-apps/Goby/scanner.js
扫描引擎是整个应用的核心,基于 Node.js 内置 net 模块实现真实 TCP connect 扫描,覆盖 30 个常见端口:
// scanner.js - 端口及服务映射
var PORT_MAP = {
21: 'FTP', 22: 'SSH', 23: 'Telnet', 25: 'SMTP', 53: 'DNS',
80: 'HTTP', 110: 'POP3', 111: 'RPCBind', 135: 'MSRPC', 139: 'NetBIOS',
143: 'IMAP', 443: 'HTTPS', 445: 'SMB', 993: 'IMAPS', 995: 'POP3S',
1433: 'MSSQL', 1521: 'Oracle', 2049: 'NFS', 3306: 'MySQL',
3389: 'RDP', 5432: 'PostgreSQL', 5900: 'VNC', 5901: 'VNC',
6379: 'Redis', 8080: 'HTTP-Proxy', 8443: 'HTTPS-Alt',
8888: 'HTTP-Alt', 9090: 'WebConsole', 27017: 'MongoDB',
9200: 'Elasticsearch', 11211: 'Memcached'
};
TCP 端口探测使用独立的 net.Socket 进行真实连接,带 done 防重入标志:
// scanner.js - TCP 端口探测
function probePort(host, port, timeout) {
return new Promise(function(resolve) {
var sock = new net.Socket();
var done = false;
sock.setTimeout(timeout || 2000);
sock.connect(port, host, function() {
if (done) return;
done = true;
sock.destroy();
resolve({ port: port, open: true });
});
sock.on('timeout', function() {
if (done) return;
done = true;
sock.destroy();
resolve({ port: port, open: false });
});
sock.on('error', function() {
if (done) return;
done = true;
sock.destroy();
resolve({ port: port, open: false });
});
});
}
关键要点:
- 零外部依赖:仅使用 Node.js 内置的 net/http/dns/os 模块
- done 防重入:每个 Socket 的 timeout/error/connect 回调都检查 done 标志,防止重复 resolve
- 2 秒超时:单个端口探测超时 2 秒,避免长时间阻塞
3.2 第二步:实现扫描调度与并发控制
文件:electron-apps/Goby/scanner.js
扫描循环使用独立的 dispatchIdx 调度指针和 tryFinish 统一收尾,避免异步回调中的索引竞态:
// scanner.js - 分批扫描,控制并发
var dispatchIdx = 0;
var activeCount = 0;
var allDone = false;
function tryFinish() {
if (activeCount === 0 && (dispatchIdx >= targets.length || allDone)) {
if (!allDone) {
allDone = true;
self.runVulnChecks().then(function() {
self.finishScan();
});
}
}
}
function scanNextHost(host) {
if (allDone || !self.running) {
activeCount--;
tryFinish();
return;
}
// ... 扫描逻辑 ...
activeCount--;
var next = dispatchIdx++;
if (next < targets.length && self.running) {
setTimeout(function() { scanNextHost(targets[next]); }, 50);
}
tryFinish();
}
关键要点:
- dispatchIdx 独立调度指针:与 activeCount 分离,避免双重递增导致跳过主机
- tryFinish 统一收尾:所有完成判断集中在一处,避免多处 emit done
- 主机间 50ms 间隔 + 默认并发 10:防止鸿蒙平台大量 TCP 连接触发 XComponent 崩溃
- running 标志检查:scanNextHost 入口检查 self.running,确保 stop 后不再调度新主机
3.3 第三步:实现漏洞检测引擎
文件:electron-apps/Goby/scanner.js
内置 8 条漏洞检测规则,全部基于真实协议交互:
| 规则 ID | 名称 | 端口 | 检测方法 | 等级 |
|---|---|---|---|---|
| GOBY-001 | Redis 未授权访问 | 6379 | TCP 发送 INFO 命令,检查 redis_version | 高危 |
| GOBY-002 | Memcached 未授权访问 | 11211 | TCP 发送 stats 命令,检查 STAT version | 高危 |
| GOBY-003 | Elasticsearch 未授权访问 | 9200 | HTTP GET /,解析 JSON version.number | 高危 |
| GOBY-004 | MongoDB 未授权访问 | 27017 | 发送 Wire Protocol OP_QUERY 报文 | 高危 |
| GOBY-005 | HTTP 安全头缺失 | 80 | 检查 4 个安全响应头 | 中危 |
| GOBY-006 | FTP 匿名登录 | 21 | FTP 握手发送 USER/PASS anonymous | 高危 |
| GOBY-007 | Telnet 暴露 | 23 | TCP 连接检查 login/username 关键字 | 中危 |
| GOBY-008 | RDP 暴露 | 3389 | 发送 X.224 Connection Request | 中危 |
以 Redis 检测为例:
// scanner.js - GOBY-001 Redis 未授权访问
{ id: 'GOBY-001', name: 'Redis 未授权访问', port: 6379, protocol: 'tcp',
check: function(host, port, cb) {
var sock = new net.Socket();
sock.setTimeout(3000);
sock.connect(port, host, function() {
sock.write('INFO\r\n');
});
var data = '';
sock.on('data', function(chunk) { data += chunk.toString(); });
sock.on('timeout', function() { sock.destroy(); cb(false); });
sock.on('error', function() { cb(false); });
sock.on('end', function() {
cb(data.indexOf('redis_version') >= 0);
});
}
}
漏洞检测使用 Promise.race 设置 30 秒整体超时兜底,防止任何规则回调丢失导致扫描卡住:
// scanner.js - 漏洞检测超时兜底
var timeoutPromise = new Promise(function(resolve) {
setTimeout(function() {
self.log('WARN', '漏洞检测超时(30s),强制结束');
resolve();
}, 30000);
});
return Promise.race([
Promise.all(checks).then(function() {
self.emit('progress', { phase: 'vulncheck', total: self.assets.length, scanned: self.assets.length });
}),
timeoutPromise
]);
关键要点:
- 8 条规则全部使用真实 TCP/HTTP 交互,零模拟数据
- GOBY-006/007/008 使用 done 防重入标志,防止 close 事件导致双重回调
- GOBY-004 MongoDB 需要构造 60 字节 Wire Protocol 报文(MsgHeader + OP_QUERY + BSON)
- Promise.race 超时兜底确保扫描不会永远卡住
3.4 第四步:创建主进程与 IPC 通道
文件:electron-apps/Goby/main.js
// main.js - 主进程核心结构
var electron = require('electron');
var app = electron.app;
var BrowserWindow = electron.BrowserWindow;
var ipcMain = electron.ipcMain;
var scannerModule = require('./scanner');
app.disableHardwareAcceleration();
var mainWindow = null;
var scanner = null;
function createWindow() {
var display = screen.getPrimaryDisplay();
mainWindow = new BrowserWindow({
width: Math.floor(display.workAreaSize.width * 0.9),
height: Math.floor(display.workAreaSize.height * 0.85),
frame: true,
transparent: false,
resizable: true,
webPreferences: { nodeIntegration: true, contextIsolation: false }
});
mainWindow.loadFile('index.html');
}
app.whenReady().then(createWindow);
主进程提供 5 个 IPC 通道:
// 启动扫描
ipcMain.handle('scan:start', function(event, targetInput, ports, concurrency) {
if (scanner && scanner.running) {
return { success: false, error: '扫描任务正在运行中' };
}
// 清理旧扫描引擎,防止事件泄漏
if (scanner) {
scanner.removeAllListeners();
scanner = null;
}
scanner = new scannerModule.GobyScanner();
// ... 挂载事件监听 ...
setImmediate(function() { scanner.scan(targetInput, portList, conc); });
return { success: true };
});
// 停止扫描
ipcMain.handle('scan:stop', function() {
if (!scanner || !scanner.running) {
return { success: false, error: '当前无运行中的扫描任务' };
}
scanner.running = false;
scanner.endTime = Date.now();
scanner.scanLog.push({ time: new Date().toISOString().substring(11, 23), level: 'WARN', msg: '用户手动停止扫描' });
var result = { assets: scanner.assets, vulns: scanner.vulns, log: scanner.scanLog, duration: scanner.endTime - scanner.startTime, summary: scanner.getSummary(), stopped: true };
scanner.removeAllListeners();
scanner = null;
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('scan:done', result);
}
return { success: true };
});
其余 IPC 通道包括:scan:getNetworkInfo(获取本机网卡信息)、scan:getLastResult(获取上次扫描结果)、scan:exportReport(导出报告)。
关键要点:
- 所有 IPC 通道统一返回 { success: true, … } 或 { success: false, error: … } 格式
- scan:start 创建新 scanner 前必须 removeAllListeners 清理旧实例,防止事件泄漏
- scan:stop 直接构建结果并清理,不再通过 emit 触发事件链,减少 IPC 发送次数
- frame: true + transparent: false 防止 XComponent 崩溃
3.5 第五步:设计 IDE 风格页面布局
文件:electron-apps/Goby/index.html
页面采用 IDE 风格布局,包含工具栏、预设标签栏、侧边面板和中央标签页:
<body>
<!-- 顶部工具栏 -->
<div id="toolbar">
<div class="toolbar-left">
<span class="app-logo">G</span>
<span class="app-title">Goby</span>
<span class="app-subtitle">Security Scanner</span>
</div>
<div class="toolbar-center">
<div id="targetInput" class="target-input-wrap">
<span class="input-icon">🎯</span>
<input type="text" id="txtTarget" placeholder="输入目标:IP / CIDR / 范围" spellcheck="false">
</div>
<button id="btnScan" class="tb-btn primary" title="开始扫描">▶ 扫描</button>
<button id="btnStop" class="tb-btn danger" title="停止扫描" disabled>⏹ 停止</button>
<button id="btnExport" class="tb-btn" title="导出报告">📄 导出</button>
</div>
<div class="toolbar-right">
<span id="statusText" class="status-text">就绪</span>
</div>
</div>
<!-- 快速测试预设(内联标签,无绝对定位) -->
<div id="presetBar" class="preset-bar">
<span class="preset-label">快测:</span>
<span class="preset-tag" data-value="127.0.0.1">🏠 127.0.0.1 <em>本机回环</em></span>
<span class="preset-tag" data-value="scanme.nmap.org">🎯 scanme.nmap.org <em>Nmap测试</em></span>
<span class="preset-tag" data-value="example.com">🌍 example.com <em>安全头检测</em></span>
</div>
<!-- 主体区域 -->
<div id="mainArea">
<div id="sidebar"><!-- 网络信息 + 端口配置 --></div>
<div id="content"><!-- 标签页:资产/漏洞/看板/原始输出 --></div>
</div>
</body>
关键要点:
- 工具栏三组布局:左侧 Logo、中间操作区、右侧状态文字
- 预设标签栏使用自然文档流的内联标签,不使用 position:absolute 下拉框
- 并发数下拉框使用自定义 div 实现,不使用原生 select(鸿蒙平台会崩溃)
- 所有交互元素避免 position:fixed + z-index + transform/animation 组合
3.6 第六步:实现渲染进程交互
文件:electron-apps/Goby/renderer.js
通知系统改为状态栏文字更新,不使用浮动层弹窗:
// renderer.js - 状态栏通知(替代 DOM 弹窗)
function notify(msg, type) {
var st = el('statusText');
if (!st) return;
st.textContent = msg;
st.className = 'status-text' + (type === 'error' ? ' error' : type === 'success' ? ' success' : '');
}
数据看板在页面加载时初始化零状态,扫描完成后自动更新:
// renderer.js - 看板渲染
function renderDashboard(summary) {
var overviewHtml = '<div class="overview-grid">';
overviewHtml += '<div class="overview-item"><div class="overview-num">' + summary.totalAssets + '</div><div class="overview-label">活跃资产</div></div>';
overviewHtml += '<div class="overview-item"><div class="overview-num danger">' + summary.totalVulns + '</div><div class="overview-label">发现漏洞</div></div>';
overviewHtml += '<div class="overview-item"><div class="overview-num">' + (summary.duration / 1000).toFixed(1) + 's</div><div class="overview-label">扫描耗时</div></div>';
overviewHtml += '<div class="overview-item"><div class="overview-num">' + summary.totalPorts + '</div><div class="overview-label">探测总数</div></div>';
overviewHtml += '</div>';
el('dashAssetOverview').innerHTML = overviewHtml;
renderBarChart(el('dashPortChart'), summary.portStats, 'port');
renderBarChart(el('dashServiceChart'), summary.serviceStats, 'service');
}
扫描进行中点击预设标签时,状态栏提示用户先停止:
// renderer.js - 预设标签点击保护
document.querySelectorAll('.preset-tag').forEach(function(tag) {
tag.addEventListener('click', function() {
var val = this.getAttribute('data-value');
if (val) {
if (isScanning) {
notify('扫描进行中,请先点"停止"再切换目标', 'error');
return;
}
el('txtTarget').value = val;
}
});
});
关键要点:
- notify 函数直接更新状态栏文字和颜色,零浮动层
- 看板使用 renderBarChart 渲染纯 CSS 柱状图,不依赖外部图表库
- 预设标签在扫描进行中禁止切换,防止用户误操作
四、部署与测试
4.1 同步文件到鸿蒙项目
每次修改代码后,需要将 electron-apps/Goby/ 下的文件同步到鸿蒙部署目录:
# 同步脚本
$src = "electron-apps\Goby"
$dst = "web_engine\src\main\resources\resfile\resources\app"
"package.json","main.js","scanner.js","index.html","renderer.js","styles\goby.css" | ForEach-Object {
Copy-Item "$src\$_" "$dst\$_" -Force
Write-Host "OK: $_"
}
部署路径:web_engine/src/main/resources/resfile/resources/app/
注意:每次修改代码后都需要同步,否则构建的 HAP 包不会包含最新代码。
4.2 在鸿蒙设备上测试
- 连接鸿蒙设备(HUAWEI MateBook Pro 或启动模拟器)
- 点击 Run > Run ‘entry’
- 安装完成后,应用自动启动
- 点击预设标签栏的 127.0.0.1 或 scanme.nmap.org
- 点击 ▶ 扫描 开始测试



五、常见问题 FAQ
Q1:扫描停止后再扫描再停止,应用崩溃?
问题现象:扫描 → 停止 → 再扫描 → 再停止,应用报 SIGABRT 崩溃
根本原因:每次 scan:start 创建新 scanner 并挂载 5 个事件监听器,但旧 scanner 未被清理。旧 scanner 的残留 TCP 连接超时后触发事件,通过已失效的监听器发送 IPC 导致崩溃
解决方案:scan:start 开头调用 removeAllListeners 清理旧实例;scan:stop 中直接构建结果并清理
// 正确写法 - 创建前清理
if (scanner) {
scanner.removeAllListeners();
scanner = null;
}
scanner = new scannerModule.GobyScanner();
Q2:漏洞检测永远不完成,一直显示扫描中?
问题现象:端口扫描完成后,状态一直显示扫描中,看板不更新
根本原因:GOBY-008 RDP 检测规则缺少 close 事件处理。远端直接关闭连接时,data/timeout/error 均不触发,只有 close 事件但无处理,回调永远不调用,Promise.all 永远不完成
解决方案:为 GOBY-008 添加 close 事件处理 + runVulnChecks 添加 30 秒超时兜底
// 修复后 - GOBY-008 添加 close 处理
sock.on('timeout', function() { if (!done) { done = true; sock.destroy(); cb(resp.length > 0); } });
sock.on('error', function() { if (!done) { done = true; cb(false); } });
sock.on('close', function() { if (!done) { done = true; cb(false); } });
Q3:扫描完成或停止时应用崩溃?
问题现象:扫描完成或点停止后,应用崩溃报 SIGABRT
根本原因:notify 函数创建带 position:fixed + z-index:999 + CSS transform 动画的浮动通知弹窗,触发鸿蒙适配层创建 SubWindow,XComponent 超时崩溃
解决方案:将 notify 改为直接更新状态栏文字和颜色
// 错误写法(鸿蒙平台必崩)
var div = document.createElement('div');
div.className = 'goby-notification'; // position:fixed + z-index:999 + animation
document.body.appendChild(div);
// 正确写法
function notify(msg, type) {
var st = el('statusText');
st.textContent = msg;
st.className = 'status-text' + (type === 'error' ? ' error' : ' success');
}
Q4:看板扫描耗时显示 NaN?
问题现象:扫描完成后,数据看板的扫描耗时显示 “NaNs”
根本原因:scan:stop 处理中未设置 endTime,getSummary 计算 undefined - startTime = NaN
解决方案:停止时补充 endTime 设置
// 修复后
scanner.running = false;
scanner.endTime = Date.now(); // 补充 endTime
Q5:点击预设标签后扫描按钮点不了?
问题现象:点击预设下拉框中的 IP 后,扫描按钮无法点击
根本原因:预设下拉框使用 position:absolute + z-index:200,在鸿蒙平台上创建不可见的覆盖层,遮挡扫描按钮的点击事件
解决方案:移除绝对定位下拉框,改为工具栏下方的内联标签按钮行
<!-- 错误写法(绝对定位遮挡按钮) -->
<div class="target-presets-dropdown">...</div>
<!-- 正确写法(内联标签,自然文档流) -->
<div class="preset-bar">
<span class="preset-tag" data-value="127.0.0.1">🏠 127.0.0.1</span>
<span class="preset-tag" data-value="scanme.nmap.org">🎯 scanme.nmap.org</span>
</div>
Q6:如何同步文件到鸿蒙项目?
问题现象:修改了 electron-apps/Goby/ 下的文件,但构建后没有生效
根本原因:文件没有同步到鸿蒙 web_engine 模块的部署目录
解决方案:使用 PowerShell 脚本同步文件(参见 4.1 节)
部署路径:web_engine/src/main/resources/resfile/resources/app/
注意:每次修改代码后都需要同步,否则构建的 HAP 包不会包含最新代码。
六、总结
本文记录了 Goby 网络资产测绘工具在鸿蒙 PC 平台的完整适配过程,核心经验如下:
- 零依赖设计:扫描引擎仅使用 Node.js 内置的 net/http/dns 模块,无需 npm 安装任何第三方包,天然兼容鸿蒙沙盒环境
- 真实协议交互:8 条漏洞检测规则全部基于真实 TCP/HTTP 协议交互,不使用模拟数据,确保扫描结果可验证
- 并发控制策略:默认并发 10 + 主机间 50ms 调度间隔,在扫描效率与鸿蒙平台稳定性之间取得平衡
- 防重入机制:所有 Socket 回调统一使用 done 标志防重入,避免 close/timeout/error 事件交叉触发导致的双重回调
- 鸿蒙三防适配:禁用 alert/confirm/prompt、禁用原生 select、禁用 position:fixed + z-index + transform 动画组合,全面规避 XComponent 崩溃
这些经验对于在鸿蒙平台开发网络扫描类、安全检测类应用具有直接参考价值。
更多推荐



所有评论(0)