鸿蒙平台 Yakit 网络安全工具适配实战:基于 Electron 壳方案的 Web Fuzzer 与漏洞扫描平台开发
Yakit 是一款开源的网络安全工具平台,集成了 Web Fuzzer、漏洞扫描、弱点爆破、Codec 编解码、Payload 管理、HTTP History 等渗透测试常用功能。本文记录将其基于 Electron 壳方案适配到鸿蒙 PC 平台的完整流程。
欢迎加入开源鸿蒙 PC 社区:https://harmonypc.csdn.net/
欢迎在 PC 社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper
AtomGit 仓库地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_yakit
一、技术架构分析
1.1 功能定位
Yakit 的核心功能是 HTTP 请求构造与模糊测试。与原版 Yakit(Electron + React + gRPC)相比,鸿蒙适配版使用纯 Node.js 内置模块实现完整功能,零外部依赖,包含 7 个功能模块:Web Fuzzer、HTTP History、漏洞扫描、弱点爆破、Payload 管理、漏洞库、Codec 编解码。
1.2 目标架构(鸿蒙 Electron)
- 技术栈:Electron + HTML/CSS/JavaScript + 鸿蒙 web_engine 模块
- 核心逻辑:主进程使用 Node.js 原生 http/https/crypto/zlib 模块实现 HTTP 请求引擎、漏洞扫描、编解码,渲染进程负责 UI 交互和语法高亮,通过 IPC 通道通信
1.3 架构设计
| 层级 | 职责 | 技术实现 |
|---|---|---|
| 主进程 | 窗口管理 + 引擎调度 + IPC | Electron BrowserWindow + YakitEngine |
| 核心引擎 | HTTP 请求 + 扫描 + 编解码 + Fuzz | Node.js http/https/crypto/zlib/events |
| IPC 通道 | 进程间通信(10 个通道) | ipcMain.handle / ipcRenderer.invoke |
| 渲染进程 | 模块导航 + UI 交互 + 语法高亮 | 原生 DOM + JavaScript |
| 样式层 | 深色主题 | Catppuccin Mocha 配色方案 |
1.4 鸿蒙平台适配要点
鸿蒙 Electron 适配层存在三大兼容约束,必须在开发中严格遵守:
- 禁止使用原生 prompt/confirm/alert 对话框,调用会导致 SubWindow 崩溃
- 禁止使用原生 select 元素,调用会触发 SubWindow 崩溃
- 禁止使用 setWindowOpenHandler 和 will-navigate API,调用会导致页面纯白
二、环境准备
2.1 开发环境要求
- 操作系统:Windows 10/11
- 开发工具:DevEco Studio(鸿蒙官方 IDE)
- HarmonyOS SDK:API 15+
- Node.js:v20+
2.2 项目结构
ohos_hap/
├── electron-apps/
│ └── Yakit/ # Yakit Electron 应用源码
│ ├── main.js # 主进程(窗口管理 + IPC + 引擎调度)
│ ├── yakit-engine.js # 核心引擎(HTTP + 扫描 + 编解码 + Fuzz)
│ ├── renderer.js # 渲染进程(UI 交互 + 语法高亮)
│ ├── index.html # HTML 布局(IDE 风格多模块)
│ ├── package.json # 项目配置
│ └── styles/
│ └── yakit.css # Catppuccin Mocha 深色主题
├── web_engine/ # 鸿蒙 web_engine 模块
│ └── src/main/resources/
│ └── resfile/resources/app/ # 部署目录
└── build-profile.json5 # 鸿蒙构建配置
三、核心适配流程
3.1 第一步:创建主进程与核心引擎
文件:electron-apps/Yakit/main.js
Yakit 的核心是 YakitEngine 引擎实例。主进程负责创建窗口、懒加载引擎、注册 IPC 通道:
var electron = require('electron');
var app = electron.app;
var BrowserWindow = electron.BrowserWindow;
var screen = electron.screen;
var ipcMain = electron.ipcMain;
var path = require('path');
var fs = require('fs');
var engineModule = require('./yakit-engine');
var mainWindow = null;
var engine = null;
function ensureEngine() {
if (!engine) {
engine = new engineModule.YakitEngine();
engine.on('request:done', function(result) {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('request:done', result);
}
});
}
return engine;
}
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),
webPreferences: { nodeIntegration: true, contextIsolation: false }
});
mainWindow.loadFile('index.html');
}
app.whenReady().then(function() { createWindow(); });
app.on('window-all-closed', function() { app.quit(); });
关键要点:
- 引擎采用懒加载模式,首次 IPC 调用时才初始化
- 引擎通过 EventEmitter 推送实时事件(如 Fuzz 进度、请求完成)
- 主进程通过 webContents.send 将引擎事件转发到渲染进程
3.2 第二步:注册 IPC 通信通道
文件:electron-apps/Yakit/main.js
Yakit 注册了 10 个 IPC 通道,覆盖所有功能模块:
// 发送 HTTP 请求
ipcMain.handle('request:send', function(event, options) {
var eng = ensureEngine();
return new Promise(function(resolve) {
eng.sendRequest(options, function(result) { resolve(result); });
});
});
// 漏洞扫描
ipcMain.handle('scan:start', function(event, targetUrl) {
var eng = ensureEngine();
if (eng.scanRunning) return { success: false, error: '扫描正在运行' };
return new Promise(function(resolve) {
eng.scanUrl(targetUrl, function(result) {
try {
var data = { vulns: result.vulns || [], timestamp: Date.now() };
fs.writeFileSync(getDataFilePath(), JSON.stringify(data, null, 2), 'utf-8');
} catch (e) { /* ignore */ }
resolve(result);
});
});
});
// Codec 编解码
ipcMain.handle('codec:exec', function(event, input, operation) {
var eng = ensureEngine();
return eng.codec(input, operation);
});
// Fuzz 测试(带实时进度推送)
ipcMain.handle('fuzz:start', function(event, options) {
var eng = ensureEngine();
if (eng.fuzzRunning) return { success: false, error: 'Fuzz 正在运行' };
return new Promise(function(resolve) {
var resultHandler = function(r) {
if (mainWindow && !mainWindow.isDestroyed()) mainWindow.webContents.send('fuzz:result', r);
};
var progressHandler = function(d) {
if (mainWindow && !mainWindow.isDestroyed()) mainWindow.webContents.send('fuzz:progress', d);
};
eng.on('fuzz:result', resultHandler);
eng.on('fuzz:progress', progressHandler);
eng.runFuzz(options, function(result) {
eng.removeListener('fuzz:result', resultHandler);
eng.removeListener('fuzz:progress', progressHandler);
resolve(result);
});
});
});
完整 IPC 通道列表:
| 通道名 | 方向 | 功能 |
|---|---|---|
| request:send | 渲染 → 主 | 发送 HTTP 请求 |
| history:get | 渲染 → 主 | 获取历史记录 |
| history:clear | 渲染 → 主 | 清空历史 |
| fuzz:start | 渲染 → 主 | 启动 Fuzz 测试 |
| fuzz:stop | 渲染 → 主 | 停止 Fuzz |
| scan:start | 渲染 → 主 | 启动漏洞扫描 |
| scan:stop | 渲染 → 主 | 停止扫描 |
| codec:exec | 渲染 → 主 | 执行编解码 |
| payload:get / payload:types | 渲染 → 主 | 获取 Payload |
| rules:get / data:getLastResult | 渲染 → 主 | 获取漏洞规则/结果 |
3.3 第三步:构建 HTTP 请求引擎
文件:electron-apps/Yakit/yakit-engine.js
HTTP 请求引擎是整个 Yakit 的核心,使用 Node.js 内置的 http/https 模块实现,支持自动解压、重定向跟随、魔数兜底检测:
var http = require('http');
var https = require('https');
var url = require('url');
var crypto = require('crypto');
var zlib = require('zlib');
var EventEmitter = require('events');
var util = require('util');
function YakitEngine() {
EventEmitter.call(this);
this.history = [];
this.maxHistory = 500;
this.fuzzRunning = false;
this.scanRunning = false;
}
util.inherits(YakitEngine, EventEmitter);
自动解压:支持 gzip、deflate、brotli 三种压缩格式,并带有 gzip 魔数兜底检测:
function decodeBody(buf, enc, callback) {
if (enc === 'gzip' || enc === 'x-gzip') {
zlib.gunzip(buf, callback);
} else if (enc === 'deflate') {
zlib.inflate(buf, callback);
} else if (enc === 'br') {
zlib.brotliDecompress(buf, callback);
} else {
callback(null, buf);
}
}
// 解压失败时,检测是否为 gzip 魔数字节
if (rawBuffer.length >= 2 && rawBuffer[0] === 0x1f && rawBuffer[1] === 0x8b) {
zlib.gunzip(rawBuffer, function(err2, decoded2) {
var body = err2 ? rawBuffer.toString('utf-8') : decoded2.toString('utf-8');
finishResult(body, totalLength, startTime, res, finalUrl, method);
});
}
重定向跟随:自动处理 301/302/303/307/308 重定向,最多跟随 5 次:
if ([301, 302, 303, 307, 308].indexOf(res.statusCode) >= 0 && res.headers.location) {
if (redirectCount >= maxRedirects) {
return finishRequest(res, startTime, currentUrl, reqOptions.method);
}
res.resume();
var nextUrl = res.headers.location;
if (nextUrl.indexOf('http') !== 0) {
var base = url.parse(currentUrl);
nextUrl = url.resolve(base.protocol + '//' + base.host, nextUrl);
}
return doRequest(nextUrl, redirectCount + 1);
}
关键要点:
- 使用 Node.js 原生 http/https 模块,避免 CORS 限制
- 自动检测 http/https 协议,选择对应的库
- rejectUnauthorized: false 允许自签名证书
- 10 秒超时保护,1MB 响应体上限
- 自动添加 User-Agent 和 Accept-Encoding 请求头
3.4 第四步:设计多模块 IDE 布局
文件:electron-apps/Yakit/index.html
Yakit 采用经典的 IDE 风格布局:顶部状态栏 + 左侧模块导航 + 右侧工作区。导航按功能分为三组:渗透测试(Web Fuzzer、HTTP History)、安全工具(漏洞扫描、弱点爆破)、数据(Payload 管理、漏洞库)。
<div id="statusBar" class="status-bar">
<div class="status-left">
<span class="status-dot online"></span>
<span id="engineStatus" class="status-label">引擎就绪</span>
<span class="status-sep">|</span>
<span class="status-label">历史:</span>
<span id="historyCount" class="status-value">0</span>
<span class="status-sep">|</span>
<span id="shortcutHint" class="status-hint">Ctrl+Enter 发送</span>
</div>
<div class="status-right">
<button id="btnCodec" class="status-btn" title="编解码工具">🔣 Codec</button>
<button id="btnPayload" class="status-btn" title="Payload 管理">📦 Payload</button>
<span class="status-mode">本地模式</span>
</div>
</div>
<div id="mainArea">
<nav id="moduleNav" class="module-nav">
<div class="nav-group">
<div class="nav-group-title">渗透测试</div>
<div class="nav-item active" data-module="fuzzer">
<span class="nav-icon">🔁</span>
<span class="nav-text">Web Fuzzer</span>
</div>
<div class="nav-item" data-module="history">
<span class="nav-icon">📜</span>
<span class="nav-text">HTTP History</span>
</div>
</div>
<div class="nav-group">
<div class="nav-group-title">安全工具</div>
<div class="nav-item" data-module="scanner">
<span class="nav-icon">🛡️</span>
<span class="nav-text">漏洞扫描</span>
</div>
<div class="nav-item" data-module="brute">
<span class="nav-icon">🔑</span>
<span class="nav-text">弱点爆破</span>
</div>
</div>
<div class="nav-group">
<div class="nav-group-title">数据</div>
<div class="nav-item" data-module="payloads">
<span class="nav-icon">📦</span>
<span class="nav-text">Payload 管理</span>
</div>
<div class="nav-item" data-module="vulns">
<span class="nav-icon">🐛</span>
<span class="nav-text">漏洞库</span>
</div>
</div>
</nav>
<div id="workspace">
<!-- 各模块面板 -->
</div>
</div>
关键要点:
- 状态栏使用 div 自然文档流,避免 position: fixed + z-index 组合触发 SubWindow 崩溃
- HTTP 方法使用自定义 div 下拉框(非原生 select),通过 stopPropagation 防止事件冒泡
- Codec 和 Payload 通过状态栏快捷按钮快速切换,无需手动导航
3.5 第五步:实现 Web Fuzzer 模块
文件:electron-apps/Yakit/renderer.js
Web Fuzzer 是 Yakit 的核心模块,支持自定义 URL、请求方法、请求体,发送 HTTP 请求并展示响应结果。
发送请求:
el('btnSendFuzz').addEventListener('click', function() {
var urlVal = el('fuzzUrl').value.trim();
if (!urlVal) { notify('请输入 URL', 'error'); return; }
if (!/^https?:\/\//i.test(urlVal)) { urlVal = 'http://' + urlVal; el('fuzzUrl').value = urlVal; }
var method = el('fuzzMethodSelect').getAttribute('data-value') || 'GET';
var body = el('fuzzBody').value;
notify('发送中...', '');
ipcRenderer.invoke('request:send', { url: urlVal, method: method, body: body, timeout: 15000 }).then(function(result) {
if (result.success) {
notify('请求成功 ' + result.statusCode, 'success');
renderFuzzResponse(result);
updateHistoryCount();
} else {
notify('请求失败: ' + result.error, 'error');
}
});
});
响应体自动语法高亮:根据 Content-Type 自动检测内容类型,分别使用不同的语法高亮策略,带有 try/catch 降级保护:
function renderFuzzResponse(result) {
var body = result.body || '';
var bodyPane = el('fuzzRespBody').querySelector('.resp-content');
if (!body) {
bodyPane.textContent = '(空响应)';
} else {
try {
var ct = '';
if (result.headers) ct = result.headers['content-type'] || '';
var trimmed = body.trim();
if (ct.indexOf('json') >= 0 || (trimmed.charAt(0) === '{' || trimmed.charAt(0) === '[')) {
bodyPane.innerHTML = highlightJSON(body);
} else if (ct.indexOf('html') >= 0 || (trimmed.charAt(0) === '<' && /<\/?[a-zA-Z]/.test(trimmed))) {
bodyPane.innerHTML = highlightHTML(body);
} else if (ct.indexOf('xml') >= 0 || (trimmed.charAt(0) === '<' && trimmed.indexOf('<?xml') >= 0)) {
bodyPane.innerHTML = highlightXML(body);
} else {
bodyPane.innerHTML = nl2br(escapeHtml(formatBody(body, result.headers)));
}
} catch (e) {
bodyPane.innerHTML = nl2br(escapeHtml(body));
}
}
}
FuzzTag 支持:URL 和 Body 输入框支持 FuzzTag 语法,点击标签即可插入到当前焦点所在的输入框:
var lastFocusedInput = el('fuzzUrl');
[el('fuzzUrl'), el('fuzzBody')].forEach(function(inp) {
inp.addEventListener('focus', function() { lastFocusedInput = inp; });
});
document.querySelectorAll('.hint-tag').forEach(function(tag) {
tag.addEventListener('click', function() {
var insert = this.getAttribute('data-insert');
var target = lastFocusedInput || el('fuzzUrl');
var pos = target.selectionStart || target.value.length;
var val = target.value;
target.value = val.substring(0, pos) + insert + val.substring(pos);
target.focus();
target.selectionStart = target.selectionEnd = pos + insert.length;
});
});
预设目标:提供 5 个一键可测目标,点击自动填充 URL、方法、Body 并发送请求:
<div class="preset-bar">
<span class="preset-label">快捷目标:</span>
<span class="preset-target" data-url="http://example.com" data-method="GET">example.com</span>
<span class="preset-target" data-url="http://httpbin.org/get" data-method="GET">httpbin GET</span>
<span class="preset-target" data-url="http://httpbin.org/post" data-method="POST" data-body="name=yakit&test=hello">httpbin POST</span>
<span class="preset-target" data-url="http://html5test.com" data-method="GET">html5test</span>
<span class="preset-target" data-url="https://www.baidu.com" data-method="GET">baidu.com</span>
</div>
3.6 第六步:实现漏洞扫描与弱点爆破
漏洞扫描:引擎对目标 URL 发送 GET 请求,然后使用 5 条检测规则逐一检查响应。每条规则包含 ID、名称、严重等级和 check 函数,返回 found 和 detail 两个字段:
var VULN_RULES = [
{ id: 'YAKIT-001', name: '安全响应头缺失', severity: 'low',
check: function(r) {
var missing = [];
['x-content-type-options', 'x-frame-options',
'strict-transport-security', 'content-security-policy'].forEach(function(h) {
if (!r.headers[h]) missing.push(h);
});
return missing.length > 0 ? { found: true, detail: '缺失: ' + missing.join(', ') } : { found: false };
}
},
{ id: 'YAKIT-002', name: '服务器信息泄露', severity: 'info',
check: function(r) {
var s = r.headers['server'] || r.headers['x-powered-by'];
return s ? { found: true, detail: '暴露: ' + s } : { found: false };
}
},
{ id: 'YAKIT-003', name: '目录列表暴露', severity: 'medium',
check: function(r) {
return (r.body.indexOf('Index of /') >= 0 || r.body.indexOf('Directory listing') >= 0)
? { found: true, detail: '检测到目录列表' } : { found: false };
}
},
{ id: 'YAKIT-004', name: 'SQL 错误信息泄露', severity: 'high',
check: function(r) {
var patterns = [/SQL syntax.*MySQL/i, /ORA-\d{5}/i, /Microsoft SQL Server/i];
for (var i = 0; i < patterns.length; i++) {
if (patterns[i].test(r.body)) return { found: true, detail: 'SQL 错误: ' + patterns[i].source };
}
return { found: false };
}
},
{ id: 'YAKIT-005', name: '敏感路径暴露', severity: 'medium',
check: function(r) {
var paths = ['/admin', '/phpmyadmin', '/.env', '/wp-admin', '/.git/config'];
var found = [];
paths.forEach(function(p) { if (r.url.indexOf(p) >= 0 && r.statusCode === 200) found.push(p); });
return found.length > 0 ? { found: true, detail: found.join(', ') } : { found: false };
}
}
];
关键要点:
- 扫描采用单次 GET 请求 + 多规则并行检测模式,一次请求完成所有规则检查
- 5 条规则覆盖安全头缺失、信息泄露、目录暴露、SQL 错误、敏感路径等常见问题
- 扫描结果自动持久化到本地 JSON 文件,重启后可恢复
弱点爆破:对目标 URL 的指定参数进行 Payload 遍历,自动检测反射型漏洞,内置 4 类 Payload:
var FUZZ_PAYLOADS = {
'weak-password': ['admin', '123456', 'password', 'admin123', 'root', 'test', 'guest', 'info', 'mysql', 'oracle'],
'xss': ['<script>alert(1)</script>', '"><img src=x onerror=alert(1)>', '<svg/onload=alert(1)>', '{{7*7}}', 'javascript:alert(1)'],
'sqli': ["' OR '1'='1", "1' UNION SELECT NULL--", "admin'--", "' AND 1=1--", "1; DROP TABLE users--"],
'traversal': ['../../../etc/passwd', '..\\..\\..\\windows\\system32\\config\\sam', '%2e%2e%2f%2e%2e%2fetc%2fpasswd', '/etc/passwd%00']
};
YakitEngine.prototype.runFuzz = function(options, cb) {
var self = this;
var payloads = FUZZ_PAYLOADS[options.payloadType] || [];
if (payloads.length === 0) return cb({ success: false, error: '无可用 Payload' });
self.fuzzRunning = true;
var results = [];
var completed = 0;
var total = payloads.length;
payloads.forEach(function(payload, idx) {
setTimeout(function() {
if (!self.fuzzRunning) return;
var fuzzUrl = options.url + (options.url.indexOf('?') >= 0 ? '&' : '?') +
options.param + '=' + encodeURIComponent(payload);
self.sendRequest({ url: fuzzUrl, method: 'GET', timeout: 15000 }, function(result) {
var fuzzResult = {
payload: payload, statusCode: result.statusCode || 0,
bodySize: result.bodySize || 0, reflected: false
};
if (result.body && result.body.indexOf(payload) >= 0) fuzzResult.reflected = true;
results.push(fuzzResult);
completed++;
self.emit('fuzz:result', fuzzResult);
self.emit('fuzz:progress', { completed: completed, total: total });
if (completed >= total) {
self.fuzzRunning = false;
cb({ success: true, results: results });
}
});
}, idx * 200);
});
};
关键要点:
- 爆破使用 setTimeout 间隔 200ms 逐个发送,避免并发请求过多
- 通过 EventEmitter 实时推送单条结果和进度到渲染进程
- 反射检测:检查响应体中是否包含 Payload 原文
3.7 第七步:实现 Codec 编解码模块
文件:electron-apps/Yakit/yakit-engine.js
编解码模块使用 Node.js 的 crypto 模块和原生 API 实现 11 种编解码算法:
YakitEngine.prototype.codec = function(input, operation) {
try {
switch (operation) {
case 'base64-encode': return { success: true, output: Buffer.from(input).toString('base64') };
case 'base64-decode': return { success: true, output: Buffer.from(input, 'base64').toString('utf-8') };
case 'url-encode': return { success: true, output: encodeURIComponent(input) };
case 'url-decode': return { success: true, output: decodeURIComponent(input) };
case 'hex-encode': return { success: true, output: Buffer.from(input).toString('hex') };
case 'hex-decode': return { success: true, output: Buffer.from(input, 'hex').toString('utf-8') };
case 'html-encode': return { success: true, output: input.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"') };
case 'html-decode': return { success: true, output: input.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"') };
case 'md5': return { success: true, output: crypto.createHash('md5').update(input).digest('hex') };
case 'sha1': return { success: true, output: crypto.createHash('sha1').update(input).digest('hex') };
case 'sha256': return { success: true, output: crypto.createHash('sha256').update(input).digest('hex') };
default: return { success: false, error: '未知操作: ' + operation };
}
} catch (e) {
return { success: false, error: e.message };
}
};
Codec 界面提供 11 个按钮,点击即执行对应操作,结果实时显示在输出区域:
<div class="codec-actions">
<button class="codec-btn" data-op="base64-encode">Base64 编码</button>
<button class="codec-btn" data-op="base64-decode">Base64 解码</button>
<button class="codec-btn" data-op="url-encode">URL 编码</button>
<button class="codec-btn" data-op="url-decode">URL 解码</button>
<button class="codec-btn" data-op="hex-encode">Hex 编码</button>
<button class="codec-btn" data-op="hex-decode">Hex 解码</button>
<button class="codec-btn" data-op="html-encode">HTML 编码</button>
<button class="codec-btn" data-op="html-decode">HTML 解码</button>
<button class="codec-btn" data-op="md5">MD5</button>
<button class="codec-btn" data-op="sha1">SHA1</button>
<button class="codec-btn" data-op="sha256">SHA256</button>
</div>
3.8 第八步:实现语法高亮系统
文件:electron-apps/Yakit/renderer.js
由于不能使用外部依赖,语法高亮完全通过手写正则和字符串处理实现,支持 HTML、JSON、XML、HTTP Headers 四种高亮器,所有高亮函数均带有 try/catch 降级保护。
HTML 高亮使用字符级状态机,逐段解析注释、标签、属性、文本四类 token:
function highlightHTML(html) {
var formatted = prettyPrintHTML(html);
var out = '';
var i = 0;
var len = formatted.length;
while (i < len) {
if (formatted.substr(i, 4) === '<!--') {
// 注释 → hl-cmt(灰色)
var endCmt = formatted.indexOf('-->', i + 4);
if (endCmt === -1) endCmt = len - 3;
out += '<span class="hl-cmt">' + escapeHtmlStr(formatted.substring(i, endCmt + 3)) + '</span>';
i = endCmt + 3;
} else if (formatted.charAt(i) === '<' && /[a-zA-Z\/]/.test(formatted.charAt(i + 1))) {
// 标签 → hl-kw(括号灰色)+ hl-tag(标签名蓝色)+ 属性高亮
// ... 解析标签名和属性
} else {
// 文本 → hl-txt
}
}
return out.replace(/\n/g, '<br>');
}
JSON 高亮通过正则判断当前字符串是 key(后面跟冒号)还是 value,分别着不同颜色:
function highlightJSON(body) {
var formatted;
try { formatted = JSON.stringify(JSON.parse(body), null, 2); } catch (e) { formatted = body; }
var out = '';
var i = 0;
var len = formatted.length;
var inStr = false;
while (i < len) {
var ch = formatted.charAt(i);
if (inStr) {
if (ch === '\\' && i + 1 < len) { out += escapeHtmlStr(formatted.substr(i, 2)); i += 2; continue; }
if (ch === '"') { out += '"</span>'; inStr = false; i++; continue; }
out += escapeHtmlStr(ch); i++;
} else {
if (ch === '"') {
var isKey = !!formatted.substring(i).match(/^"([^"]*)"\s*:/);
if (isKey) {
out += '<span class="hl-key">"'; // key → 绿色
} else {
out += '<span class="hl-str">"'; // string → 黄色
}
inStr = true; i++;
} else if (/[0-9\-]/.test(ch)) {
// number → 橙色
} else if (/^(true|false|null)/.test(formatted.substring(i))) {
// boolean/null → 橙色
}
}
}
return out.replace(/\n/g, '<br>');
}
HTML 自动格式化:服务器返回的 HTML 经常是压缩的单行格式,prettyPrintHTML 函数通过三步处理将其转为带缩进的可读格式:标签独占一行 → 清理多余空行 → 根据嵌套深度添加缩进:
function prettyPrintHTML(html) {
var voidTags = ['area','base','br','col','embed','hr','img','input','link','meta','param','source','track','wbr'];
var s = html.replace(/<\/?([a-zA-Z][a-zA-Z0-9]*)[^>]*\/?>/g, function(m) { return '\n' + m + '\n'; });
s = s.replace(/\n{2,}/g, '\n').trim();
var lines = s.split('\n');
var level = 0;
var out = [];
for (var i = 0; i < lines.length; i++) {
var line = lines[i].trim();
if (!line) continue;
var isClose = /^<\//.test(line);
if (isClose) level = Math.max(0, level - 1);
out.push(repeatStr(' ', level) + line);
if (!isClose) level++;
}
return out.join('\n');
}
关键要点:
- 所有高亮函数使用 try/catch 保护,高亮失败降级为纯文本
- HTML 高亮中的属性解析使用手动 for 循环查找空白位置,避免 String.search() 的参数陷阱
- 高亮颜色通过 CSS 变量控制:hl-tag(蓝色)、hl-key(绿色)、hl-str(黄色)、hl-num(橙色)、hl-kw(灰色)、hl-cmt(灰色)
3.9 第九步:Catppuccin Mocha 深色主题
文件:electron-apps/Yakit/styles/yakit.css
Yakit 采用 Catppuccin Mocha 配色方案,与 ES Agent 保持一致的视觉风格:
:root {
--bg-primary: #1e1e2e;
--bg-secondary: #181825;
--bg-tertiary: #11111b;
--bg-surface: #313244;
--bg-hover: #45475a;
--text-primary: #cdd6f4;
--text-secondary: #a6adc8;
--text-muted: #6c7086;
--accent: #89b4fa;
--accent-hover: #74c7ec;
--success: #a6e3a1;
--warning: #f9e2af;
--error: #f38ba8;
--info: #94e2d5;
--border: #45475a;
--border-light: #585b70;
--nav-width: 200px;
--status-height: 36px;
--font-mono: 'Consolas', 'Monaco', 'Courier New', monospace;
--font-ui: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
漏洞严重等级配色:
.sev-high { background: rgba(243, 139, 168, 0.2); color: var(--error); }
.sev-medium { background: rgba(249, 226, 175, 0.2); color: var(--warning); }
.sev-low { background: rgba(148, 226, 213, 0.2); color: var(--info); }
.sev-info { background: rgba(166, 227, 161, 0.2); color: var(--success); }
语法高亮配色:
.hl-tag { color: #89b4fa; }
.hl-key { color: #a6e3a1; }
.hl-str { color: #f9e2af; }
.hl-num { color: #fab387; }
.hl-kw { color: #6c7086; }
.hl-cmt { color: #585b70; font-style: italic; }
.hl-txt { color: var(--text-primary); }
四、部署到鸿蒙平台
4.1 文件同步
将 Electron 应用文件复制到鸿蒙 web_engine 模块的部署目录:
$src = "electron-apps\Yakit"
$dest = "web_engine\src\main\resources\resfile\resources\app"
Copy-Item "$src\main.js" "$dest\main.js" -Force
Copy-Item "$src\yakit-engine.js" "$dest\yakit-engine.js" -Force
Copy-Item "$src\renderer.js" "$dest\renderer.js" -Force
Copy-Item "$src\index.html" "$dest\index.html" -Force
Copy-Item "$src\package.json" "$dest\package.json" -Force
Copy-Item "$src\styles\yakit.css" "$dest\styles\yakit.css" -Force
4.2 构建 HAP 包
在 DevEco Studio 中:
- 打开项目根目录
- 点击 Build > Build Hap(s)/APP(s)
- 选择 Build Hap(s)
- 等待构建完成
4.3 真机测试
- 连接鸿蒙设备(HUAWEI MateBook Pro)
- 点击 Run > Run ‘electron’
- 安装完成后,应用自动启动
- 点击 Web Fuzzer 预设目标验证 HTTP 请求功能
- 切换到漏洞扫描模块,点击预设目标验证扫描功能
- 切换到 Codec 模块,验证编解码功能




五、常见问题 FAQ
Q1:为什么 HTTP 方法使用自定义 div 下拉框而不是原生 select?
问题现象:最初使用原生 select 元素,在鸿蒙真机上点击后应用直接崩溃
根本原因:鸿蒙 Electron 适配层对原生 select 元素的处理会触发 SubWindow 创建,导致崩溃
解决方案:使用自定义 div 下拉框(method-select),通过 stopPropagation 防止事件冒泡
document.querySelectorAll('.method-select').forEach(function(sel) {
var display = sel.querySelector('.method-display');
display.addEventListener('click', function(e) {
e.stopPropagation();
sel.classList.toggle('open');
});
sel.querySelectorAll('.method-option').forEach(function(opt) {
opt.addEventListener('click', function(e) {
e.stopPropagation();
sel.querySelectorAll('.method-option').forEach(function(o) { o.classList.remove('selected'); });
this.classList.add('selected');
sel.setAttribute('data-value', this.getAttribute('data-value'));
sel.classList.remove('open');
});
});
});
Q2:为什么响应体渲染使用 div 而不是 pre 元素?
问题现象:使用 pre 元素 + innerHTML 设置高亮 HTML 时,在鸿蒙真机上显示异常
根本原因:鸿蒙 Electron 适配层中,pre 元素与 innerHTML 存在兼容性问题
解决方案:使用 div 元素配合 white-space: normal 和 br 换行
.resp-content {
padding: 12px;
font-family: var(--font-mono);
font-size: 12px;
line-height: 1.6;
white-space: normal;
word-wrap: break-word;
tab-size: 4;
-moz-tab-size: 4;
color: var(--text-primary);
flex: 1;
}
Q3:为什么请求要通过主进程 IPC 而不是直接用 fetch?
问题现象:渲染进程直接使用 fetch 发送请求是否可行?
根本原因:鸿蒙 Electron 壳应用中,渲染进程的 fetch/XMLHttpRequest 受 CORS 策略限制,无法直接访问外部 API
解决方案:通过 IPC 通道委托主进程使用 Node.js 原生 http/https 模块发送请求
const result = await ipcRenderer.invoke('request:send', {
url: urlVal, method: method, body: body, timeout: 15000
});
优势:
- 主进程不受 CORS 限制
- 支持 http 和 https 协议自动切换
- 支持自签名证书(rejectUnauthorized: false)
- 支持 gzip/deflate/brotli 自动解压
Q4:语法高亮函数为什么需要 try/catch 保护?
问题现象:某些特殊字符组合导致高亮函数抛出异常,整个响应面板无法渲染
根本原因:手写的正则和字符串处理无法覆盖所有边界情况
解决方案:所有高亮函数使用 try/catch 包裹,失败时降级为纯文本显示
try {
bodyPane.innerHTML = highlightJSON(body);
} catch (e) {
bodyPane.innerHTML = nl2br(escapeHtml(body));
}
Q5:快捷键有哪些?
Yakit 支持以下快捷键:
- Ctrl + Enter:在当前模块执行主操作(发送请求/开始扫描/开始爆破)
document.addEventListener('keydown', function(e) {
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
e.preventDefault();
if (currentModule === 'fuzzer') {
el('btnSendFuzz').click();
} else if (currentModule === 'scanner') {
el('btnStartScan').click();
} else if (currentModule === 'brute') {
el('btnStartBrute').click();
}
}
});
Q6:如何同步文件到鸿蒙项目?
问题现象:修改了 electron-apps/Yakit/ 下的文件,但构建后没有生效
根本原因:文件没有同步到鸿蒙 web_engine 模块的部署目录
解决方案:使用 PowerShell 脚本同步文件
$src = "electron-apps\Yakit"
$dest = "web_engine\src\main\resources\resfile\resources\app"
Copy-Item "$src\main.js" "$dest\main.js" -Force
Copy-Item "$src\yakit-engine.js" "$dest\yakit-engine.js" -Force
Copy-Item "$src\renderer.js" "$dest\renderer.js" -Force
Copy-Item "$src\index.html" "$dest\index.html" -Force
Copy-Item "$src\package.json" "$dest\package.json" -Force
Copy-Item "$src\styles\yakit.css" "$dest\styles\yakit.css" -Force
部署路径:web_engine/src/main/resources/resfile/resources/app/
注意:每次修改代码后都需要同步,否则构建的 HAP 包不会包含最新代码。
更多推荐




所有评论(0)