欢迎光临
我们一直在努力

鸿蒙平台 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" datamodule="fuzzer">
<span class="nav-icon">🔁</span>
<span class="nav-text">Web Fuzzer</span>
</div>
<div class="nav-item" datamodule="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" datamodule="scanner">
<span class="nav-icon">🛡️</span>
<span class="nav-text">漏洞扫描</span>
</div>
<div class="nav-item" datamodule="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" datamodule="payloads">
<span class="nav-icon">📦</span>
<span class="nav-text">Payload 管理</span>
</div>
<div class="nav-item" datamodule="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" dataurl="http://example.com" datamethod="GET">example.com</span>
<span class="preset-target" dataurl="http://httpbin.org/get" datamethod="GET">httpbin GET</span>
<span class="preset-target" dataurl="http://httpbin.org/post" datamethod="POST" databody="name=yakit&test=hello">httpbin POST</span>
<span class="preset-target" dataurl="http://html5test.com" datamethod="GET">html5test</span>
<span class="preset-target" dataurl="https://www.baidu.com" datamethod="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,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;') };
case 'html-decode': return { success: true, output: input.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&quot;/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" dataop="base64-encode">Base64 编码</button>
<button class="codec-btn" dataop="base64-decode">Base64 解码</button>
<button class="codec-btn" dataop="url-encode">URL 编码</button>
<button class="codec-btn" dataop="url-decode">URL 解码</button>
<button class="codec-btn" dataop="hex-encode">Hex 编码</button>
<button class="codec-btn" dataop="hex-decode">Hex 解码</button>
<button class="codec-btn" dataop="html-encode">HTML 编码</button>
<button class="codec-btn" dataop="html-decode">HTML 解码</button>
<button class="codec-btn" dataop="md5">MD5</button>
<button class="codec-btn" dataop="sha1">SHA1</button>
<button class="codec-btn" dataop="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 {
bgprimary: #1e1e2e;
bgsecondary: #181825;
bgtertiary: #11111b;
bgsurface: #313244;
bghover: #45475a;
textprimary: #cdd6f4;
textsecondary: #a6adc8;
textmuted: #6c7086;
accent: #89b4fa;
accenthover: #74c7ec;
success: #a6e3a1;
warning: #f9e2af;
error: #f38ba8;
info: #94e2d5;
border: #45475a;
borderlight: #585b70;
navwidth: 200px;
statusheight: 36px;
fontmono: 'Consolas', 'Monaco', 'Courier New', monospace;
fontui: applesystem, BlinkMacSystemFont, 'Segoe UI', sansserif;
}

漏洞严重等级配色:

.sevhigh { background: rgba(243, 139, 168, 0.2); color: var(error); }
.sevmedium { background: rgba(249, 226, 175, 0.2); color: var(warning); }
.sevlow { background: rgba(148, 226, 213, 0.2); color: var(info); }
.sevinfo { background: rgba(166, 227, 161, 0.2); color: var(success); }

语法高亮配色:

.hltag { color: #89b4fa; }
.hlkey { color: #a6e3a1; }
.hlstr { color: #f9e2af; }
.hlnum { color: #fab387; }
.hlkw { color: #6c7086; }
.hlcmt { color: #585b70; fontstyle: italic; }
.hltxt { color: var(textprimary); }

四、部署到鸿蒙平台

4.1 文件同步

将 Electron 应用文件复制到鸿蒙 web_engine 模块的部署目录:

$src = "electron-apps\\Yakit"
$dest = "web_engine\\src\\main\\resources\\resfile\\resources\\app"

CopyItem "$src\\main.js" "$dest\\main.js" Force
CopyItem "$src\\yakit-engine.js" "$dest\\yakit-engine.js" Force
CopyItem "$src\\renderer.js" "$dest\\renderer.js" Force
CopyItem "$src\\index.html" "$dest\\index.html" Force
CopyItem "$src\\package.json" "$dest\\package.json" Force
CopyItem "$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 换行

    .respcontent {
    padding: 12px;
    fontfamily: var(fontmono);
    fontsize: 12px;
    lineheight: 1.6;
    whitespace: normal;
    wordwrap: breakword;
    tabsize: 4;
    moztabsize: 4;
    color: var(textprimary);
    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"

    CopyItem "$src\\main.js" "$dest\\main.js" Force
    CopyItem "$src\\yakit-engine.js" "$dest\\yakit-engine.js" Force
    CopyItem "$src\\renderer.js" "$dest\\renderer.js" Force
    CopyItem "$src\\index.html" "$dest\\index.html" Force
    CopyItem "$src\\package.json" "$dest\\package.json" Force
    CopyItem "$src\\styles\\yakit.css" "$dest\\styles\\yakit.css" Force

    部署路径:web_engine/src/main/resources/resfile/resources/app/

    注意:每次修改代码后都需要同步,否则构建的 HAP 包不会包含最新代码。

    赞(0)
    未经允许不得转载:171主机测评 » 鸿蒙平台 Yakit 网络安全工具适配实战:基于 Electron 壳方案的 Web Fuzzer 与漏洞扫描平台开发
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址