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 架构设计

层级职责技术实现
主进程窗口管理 + 引擎调度 + IPCElectron BrowserWindow + YakitEngine
核心引擎HTTP 请求 + 扫描 + 编解码 + FuzzNode.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,'&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" 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 中:

  1. 打开项目根目录
  2. 点击 Build > Build Hap(s)/APP(s)
  3. 选择 Build Hap(s)
  4. 等待构建完成

4.3 真机测试

  1. 连接鸿蒙设备(HUAWEI MateBook Pro)
  2. 点击 Run > Run ‘electron’
  3. 安装完成后,应用自动启动
  4. 点击 Web Fuzzer 预设目标验证 HTTP 请求功能
  5. 切换到漏洞扫描模块,点击预设目标验证扫描功能
  6. 切换到 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 包不会包含最新代码。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐