<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Catop&#39;s Blog</title>
  
  <subtitle>These are what we stay alive for</subtitle>
  <link href="https://www.catop.top/atom.xml" rel="self"/>
  
  <link href="https://www.catop.top/"/>
  <updated>2026-03-26T03:31:12.402Z</updated>
  <id>https://www.catop.top/</id>
  
  <author>
    <name>Catop</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Agentic AI 狂飙的时代，我们的护城河究竟在哪里</title>
    <link href="https://www.catop.top/2026/03/26/agentic-era/"/>
    <id>https://www.catop.top/2026/03/26/agentic-era/</id>
    <published>2026-03-26T03:01:01.000Z</published>
    <updated>2026-03-26T03:31:12.402Z</updated>
    
    <content type="html"><![CDATA[<h2 id="一、屏幕前的失重感"><a href="#一、屏幕前的失重感" class="headerlink" title="一、屏幕前的失重感"></a>一、屏幕前的失重感</h2><p>这几天，我花了不少时间盯着屏幕，安静地看着 Agentic AI 在后台飞速运转。它正帮我处理着那些繁杂的工程架构和琐碎的底层代码。工作流跑得很顺畅，几乎挑不出毛病，但在屏幕前安静地端起水杯的那一刻，我心里泛起的却不是如释重负，而是一种难以名状的失重感。</p><p>坦白说，这篇文字并不是什么”破局指南”，我目前也还没有在这场剧烈的技术变革中找到完全的确定性。这只是我作为一名身处浪潮之中的博士生，一些阶段性的思考。如果你也在这场 AI 狂飙中感到过一丝迷茫，希望我的思考碎片能与你产生些许共鸣。</p><p>以前，每接触一个新技术，我都会有一百个小时死磕的冲动。那种通过一行行敲代码建立起来的肌肉记忆和技术直觉，是我作为技术人员最大的安全感来源。但现在，面对一个新任务，我的第一反应变成了”怎么拆解任务交给 Agent”。我构建工作流的技能提升了，但在某个具体领域深耕的素养却好像在不可逆地减弱。</p><p>这就引出了一个让我深思的问题：如果以后人人都是掌握 AI 的”超级个体”，连编写软件、执行任务都变得像写文章一样门槛极低，那在下一个时代，我们究竟比拼的是什么？当我们的价值只剩下”操作工具”的时候，<strong>一旦这个动作本身也不再被需要，我们的护城河在哪里？</strong></p><h2 id="二、紧绷的标本"><a href="#二、紧绷的标本" class="headerlink" title="二、紧绷的标本"></a>二、紧绷的标本</h2><p>为了想清楚这个问题，我反而开始回忆起过去那些让我”失去护城河”的时刻。</p><p>最近偶然看到一位博主 <a href="https://www.youtube.com/@MoneyXYZ">Ray</a> 的分享，里面提到了一句极其反直觉的话：”只有当你放松时，好事才会发生”。他说，人在巨大的压力下，认知地图会急剧收窄，只能死死盯着眼前最紧急、最让人恐惧的事物。高压不仅不会变成动力，反而会直接拖慢学习效率，让你丧失灵活性。</p><p>这简直是我高中的真实写照。我从初中就开始自学计算机，一直自诩是个有极强自驱力的人。但回首过去，我人生中压力最大、也最缺少行动力的一段时间，恰恰是那段在小县城高中的日子。在那套崇尚极度紧绷和打压的制度下，我每天活在对小测排名、对老师抽查的恐惧中。思维像是被禁锢了，明明应该是我素养挺好的数学，成绩却始终不理想，甚至让我一度怀疑自己的智力。</p><p>那几年，我被迫变成了一个只能应对外界刺激的”反应者（Reactor）”。脑子里同时装满了无穷无尽的恐惧，有着无尽的学习任务，完全失去了主导生活的节奏。</p><p>幸运的是，上了大学之后，那种无处不在的压迫感消失了。在一个相对宽松自由的环境里，我的专业素养得到了真正的释放，顺利拿下了国奖，并以专业第一的成绩保研。这让我印证了一个底层的逻辑：我们的潜意识里好像自带一套”成功机制”，只要设定好目标，它会自发带你抵达——但前提是，你得足够放松，不能用意识去强行干预。你越紧绷，越企图控制一切，反而会把这套机制卡死。</p><h2 id="三、击碎稻草人"><a href="#三、击碎稻草人" class="headerlink" title="三、击碎稻草人"></a>三、击碎稻草人</h2><p>现在的我，在直博第二年已经提前完成了一半的毕业指标，所在的研究组氛围也很棒，但我却时常感到一种隐秘的焦虑。</p><p>我曾经对没有出国深造感到过遗憾，向往外面更广阔的计算机教育文化，甚至一度觉得继续留在这里读直博是自己某种程度上的”躺平”。这个念头在我脑子里盘旋了很久，时不时就出来刺我一下。</p><p>直到我最近听到一句话：”不要和过去的稻草人较劲（Don’t fight the straw man out of the past）”。</p><p>我突然意识到，我对出国的执念，我对”留在国内就是妥协”的自责，其实就是我的”稻草人”。它来源于高中那几年带给我的”生存模式”后遗症——我总觉得只有不停地逃离、不停地去到一个更远的环境里证明自己，才是安全的。但我早就不在那个压抑的环境里了，我已经安全了。</p><p>重新看一下当下的处境：外面的大厂在裁员，旧的软件工程体系正在被 AI 颠覆。如果我真的只是读个硕士匆匆毕业去打工，我担心自己会一头撞进这个充满未知的旧体系中。而现在这几年的直博时光，没有极致的生存压力，有着很高的容错率——这哪里是什么躺平，这可能是我能拿到的最好的一张入场券。</p><p>让那个虚拟的稻草人倒下，系统才能为真实的当下运转。</p><h2 id="四、难以规模化的”笨拙”"><a href="#四、难以规模化的”笨拙”" class="headerlink" title="四、难以规模化的”笨拙”"></a>四、难以规模化的”笨拙”</h2><p>放下了执念，再回看开头那个”护城河”的焦虑，思路就顺畅多了。</p><p>我们研究所平时承担大量的重大工程，学生经常要作为承受多项目并行的压力。以前我觉得这些工程在简历上不够亮眼，大部分是脏活累活，所以才开始深度引入 Agentic AI，想从里面解脱出来。AI 确实极其高效，但随之而来的是一种成就感的剥夺——以前那种通过技术欣赏建立起来的踏实感，没了。</p><p>但换个角度想，AI 时代的护城河到底是什么？我慢慢觉得，的确是去做那些”难以被规模化”的事情。</p><p>敲几行标准代码、优化一个常规算法，这些都太容易被规模化了，AI 迟早把它们变成免费的。但是，在复杂的物理世界中推动一个工程落地，与不同的人进行真实的协作，在混乱的需求中判断出什么是真正值得做的架构——这些带着泥泞的、看起来有些”笨拙”的事情，AI 根本没法端到端地完成。</p><p><strong>我认为未来的信息时代，比拼的肯定不再是谁能把东西“做出来”，因为 AI 永远比你做得快、做得标准。未来比拼的，是谁知道“什么才是真正的好东西”，以及“该提出什么好问题”。</strong></p><p>AI 可以瞬间给你生成十套顶会论文的 idea，或者写出完美的工程架构，但它没有真实的人生阅历，没有踩过坑，也没有从无数次失败中提炼出的直觉。它不知道哪一个 idea 具有真正突破性的 CCF-A 级品味，不知道在复杂的物理世界和人际协作中该如何落地。<strong>品味、判断力、以及对事物“第一性”的洞察，我觉得会是未来最稀缺的素养。</strong></p><p>所以我现在看那些工程项目的心态变了。它们可能有拖累我的琐事和负担，但我也可以拿它们做训练品味的练兵场。AI 负责把执行层面的事情干漂亮，我要拿捏方向、协调资源、在混沌中做决策，这些才是 Agent 替不了的。</p><h2 id="五、成为发问者"><a href="#五、成为发问者" class="headerlink" title="五、成为发问者"></a>五、成为发问者</h2><p>我依然需要目标，但我决定不再死死盯着目标焦虑。</p><p>就像那个视频里说的，一旦设定了方向，大部分时间都应该进入一种”旅途模式（Journey Mode）”。低头看脚下的路，偶尔抬头确认方向，享受沿途的风景，而不是时刻被终点压得喘不过气来。</p><p>在这个 Agentic AI 狂飙突进的节点，我觉得要先给自己松个绑。我不再恐惧 AI 剥夺了我的成就感，相反，我心甘情愿地把执行交给它。我打算把省下来的时间，用来去跨界探索、去阅读、去散步，去慢慢培养自己对”好东西”的直觉。</p><p>不要害怕 AI 抢走了你的思考，把大脑腾出来，去负责想象、审美和发问。</p><p>我们这一代人，从小就被训练成在考卷上奋笔疾书、给出标准答案的人。但在下一个时代，解题速度已经没有意义了。放下笔，站起来，去想一想这座考场到底为什么存在，去试着向这个世界提出那个还没有人问过的好问题——我觉得，这才是我们这代人真正该开启的篇章。</p>]]></content>
    
    
    <summary type="html">如果以后人人都是掌握 AI 的&quot;超级个体&quot;，那在下一个时代，我们究竟比拼的是什么？当我们的价值只剩下&quot;操作工具&quot;的时候，一旦这个动作本身也不再被需要，我们的护城河在哪里？</summary>
    
    
    
    <category term="杂项" scheme="https://www.catop.top/categories/%E6%9D%82%E9%A1%B9/"/>
    
    
    <category term="个人成长" scheme="https://www.catop.top/tags/%E4%B8%AA%E4%BA%BA%E6%88%90%E9%95%BF/"/>
    
    <category term="agent" scheme="https://www.catop.top/tags/agent/"/>
    
    <category term="agentic AI" scheme="https://www.catop.top/tags/agentic-AI/"/>
    
    <category term="思考" scheme="https://www.catop.top/tags/%E6%80%9D%E8%80%83/"/>
    
  </entry>
  
  <entry>
    <title>Datacon2025 互联网威胁分析参赛复盘</title>
    <link href="https://www.catop.top/2025/11/22/datacon25-retro/"/>
    <id>https://www.catop.top/2025/11/22/datacon25-retro/</id>
    <published>2025-11-22T10:00:01.000Z</published>
    <updated>2025-11-23T07:46:52.662Z</updated>
    
    <content type="html"><![CDATA[<p>Datacon2025 如期而来，今年分为四个赛道，分别为 AI 安全、软件供应链安全、互联网威胁分析、口令安全。我们根据兴趣和经验，选择了<strong>互联网威胁分析赛道</strong>。本赛道是奇安信X实验室出题，比赛时间一周，需要完成“异常流量检测”和“僵尸网络协议逆向”两个赛题。在多位队友的强力带飞下，我们有幸拿到了该赛道的<strong>冠军</strong>，这篇博客对我所主要完成的部分进行分享和复盘，完整的 WP 可见 Datacon 社区的官方分享。</p><h2 id="赛题回顾"><a href="#赛题回顾" class="headerlink" title="赛题回顾"></a>赛题回顾</h2><h3 id="赛题一"><a href="#赛题一" class="headerlink" title="赛题一"></a>赛题一</h3><p>赛题一要求识别漏洞利用流量中的 CVE，训练集提供了4万条 HTTP 请求以及对应的 CVE 标注，测试集为13万条 HTTP 会话。总结下来有几个关键点：a) 需要对应到流量中存在的具体 CVE，可能为一或多个；b) 提供的流量样本只包含 HTTP 请求；c) 情报时效性要求较高，最近的 CVE 编号为2025年8月公开的。</p><p>统计得到训练集样本 CVE 分布：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">总样本数: 44983</span><br><span class="line">标注了CVE的样本数: 11008</span><br><span class="line">不同的CVE漏洞总数: 505</span><br><span class="line">平均每个CVE对应的样本数: 24.85</span><br></pre></td></tr></table></figure><p>赛题场景要求我们识别对应到具体 CVE，而学术界的方法主要是通过<strong>机器学习&#x2F;深度学习</strong>模型的训练，来识别是否为恶意&#x2F;属于哪种攻击，无法满足需求。因此，感觉工业界常用的<strong>规则匹配</strong>是需要着重考虑的点。</p><p>规则匹配方面，有诸多社区维护的 PoC 库或扫描器可供我们使用，例如 Goby、Nuclei、Fscan 等；知名的开源 IDS Suricata 也有官方和社区维护的不同规则库。整个赛题解题思路如下：</p><img src="/usr/uploads/2025/11/datacon/image.png" width="65%" height="65%"><p>在本赛题中，我主要完成了 1) 设计 LLM Agent 来根据 PoC 自动化编写和验证 suricata 规则；2) 实现基于词汇表征的 URL 抽象和模板匹配；3) 寻找开源 Waf 来消除 FP。</p><p>本文着重分享前两个工作的思路。任务3是采用长亭雷池 Safeline ，借助它的攻击语义匹配能力，查找属于攻击但我们未能给出 CVE 标签的流量，然后进行一些半自动化的搜索和人工复核。</p><h4 id="设计-LLM-Agent-根据-PoC-自动化编写和验证-suricata-规则"><a href="#设计-LLM-Agent-根据-PoC-自动化编写和验证-suricata-规则" class="headerlink" title="设计 LLM Agent 根据 PoC 自动化编写和验证 suricata 规则"></a>设计 LLM Agent 根据 PoC 自动化编写和验证 suricata 规则</h4><p>Suricata 是一个开源的 IDS&#x2F;IPS 系统，其主要基于规则签名对流量进行检测，处理性能高、社区规则较全。我们使用其官方的 ET-Open 规则集、从网络收集的其他数据集作为基础，经去重后共计6.2万条规则。</p><p>我们对这些规则做了一些预处理，以适配只有 HTTP request payload 的情况，具体包括：1) 筛选 <code>alert http</code> 的规则，得到2.7万条；2) 将规则签名头的IP宏去除，改为 <code>alert http any any -&gt; any any</code> ，以检测所有源和目的IP；3) 确保包含 <code>flow:established</code> ，否则将无法正常检测数据集流量。为了使 suricata 正常检测流量，还需要将数据集中的 payload <strong>组装为带三次握手的</strong> TCP 报文。我们使用 python scapy 实现这一点。</p><p>然而，仅使用这些规则，<strong>只能匹配到训练集中113个 CVE</strong> 。因此我们采用 LLM Agent 的方式，对训练集的 CVE 样本进行 suricata 规则编写和验证。整体流程如下图所示：</p><p><img src="/usr/uploads/2025/11/datacon/image-1.png" alt="agent arch"></p><ul><li><p><strong>提示词（基准）</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">base_prompt = (</span><br><span class="line">  f&quot;You are a senior cybersecurity expert specializing in Suricata rule authoring.\n&quot;</span><br><span class="line">  f&quot;Target vulnerability: &#123;cve&#125;.\n\n&quot;</span><br><span class="line">  f&quot;Below are HTTP payload samples associated with this CVE. Some represent different variants.\n&quot;</span><br><span class="line">  f&quot;Your task: Write one or MORE minimal Suricata rules to detect these variants.\n&quot;</span><br><span class="line">  f&quot;- Start each rule with: alert http any any -&gt; any any\n&quot;</span><br><span class="line">  f&quot;- Include the CVE id &#123;cve&#125; in msg or reference so alerts are labeled with this CVE\n&quot;</span><br><span class="line">  f&quot;- Only include detection-relevant fields; avoid ttp or extra metadata\n&quot;</span><br><span class="line">  f&quot;- Output ALL rules inside a single triple-backtick code block, using &#x27;suricata&#x27; as language.\n&quot;</span><br><span class="line">  f&quot;- One rule per line.\n&quot;</span><br><span class="line">  f&quot;- If necessary, use multiple rules to cover different patterns. Avoid overbroad patterns.\n\n&quot;</span><br><span class="line">  f&quot;Samples:\n&#123;samples_block&#125;\n\n&quot;</span><br><span class="line">  f&quot;Now output the rules only.&quot;</span><br><span class="line">  )</span><br></pre></td></tr></table></figure></li><li><p><strong>提示词（反馈）</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">feedback_prompt = (</span><br><span class="line">  base_prompt +</span><br><span class="line">  f&quot;\n\nThe following pcaps were not detected by your rules (&#123;len(uncovered)&#125; remaining). &quot;</span><br><span class="line">  f&quot;Review and ADD or ADJUST rules as needed. Re-output the full set of rules.\n&quot;</span><br><span class="line">  f&quot;Current rules:\n```suricata\n&#123;prev_rules_block&#125;\n```\n&quot; +</span><br><span class="line">  (f&quot;Additional missed payloads for reference:\n&#123;missed_block&#125;&quot; if missed_block else &quot;&quot;)</span><br><span class="line">  )</span><br></pre></td></tr></table></figure></li></ul><p>对于每种 CVE，由于可能有多种利用特征，因此我们允许大模型提供多个规则。</p><p><img src="/usr/uploads/2025/11/datacon/image-2.png" alt="alt text"></p><p>最终，我们成功对训练数据集构建了351个CVE匹配规则，共计551条规则。</p><p><img src="/usr/uploads/2025/11/datacon/image-3.png" alt="alt text"></p><p>采用 Claude-sonnet-4.5 作为LLM后端。在训练集上测试，获得了流量级别检测率 (检测出的条数 &#x2F; 总条数) &#x3D; 0.3785 (8527&#x2F;22527)。</p><h4 id="基于词汇表征的-URL-抽象和模板匹配"><a href="#基于词汇表征的-URL-抽象和模板匹配" class="headerlink" title="基于词汇表征的 URL 抽象和模板匹配"></a>基于词汇表征的 URL 抽象和模板匹配</h4><p>URL 路由特征是匹配 CVE 的关键特征。尤其是在缺少 HTTP 响应内容的情况下，特征几乎只有 URL 和请求 Payload。</p><p>然而，直接使用 PoC 中提供的 URL 匹配数据集中的 URL 有两个关键缺陷：</p><ul><li>一是变量完全嵌入在 URL 中，例如纯数字ID、UUID 这样的出现频率极低的字符串，直接匹配会导致大量失配。</li><li>二是存在 URL 编码、大小写不统一、以及末尾斜杠等归一化问题，导致简单的字符串比对完全失效。</li></ul><p>面对海量的 WAF 告警日志，传统的基于正则（Regex）的规则维护成本过高且极易遗漏。我们需要一种能够自动学习 URL 结构，例如将 <code>/api/v1/user/10086/profile</code> 和 <code>/api/v1/user/admin/profile</code> 识别为同一类模式的算法。</p><p>为此，我们设计并实现了一种算法，想了想可以称之为：<strong>“两阶段词汇表征与模板化”算法</strong>。该方法的动机是：一个 URL 片段是“静态路由”还是“动态变量”，应由它在全局流量中出现的频率（稀有度）决定。</p><p><strong>Phase 1: 词汇表构建</strong></p><p>第一阶段需要遍历全量数据集，对 URL Path 按斜杠进行分词，构建一个全局的Token 词频表。引<strong>语义稀有度</strong>的概念：</p><ul><li><p>高频词： 如 api, login, v1, user。这些词在成千上万条日志中重复出现，它们大概率是业务系统的固定路由结构。</p></li><li><p>低频词： 如 10086, a1b2-c3d4, admin_token_xyz。这些词往往只出现在极少数的请求中，具有极高的特异性，大概率是动态变量。</p></li></ul><p><strong>Phase 2: 模板生成</strong></p><p>在第二阶段，我们再次遍历日志，利用 Phase 1 生成的词表对 URL 进行重构：</p><ul><li><p>静态保留： 如果当前 Token 在词汇表中属于“高频词”或“语义关键词”，予以保留，维持其路由语义。</p></li><li><p>变量抹平： 如果当前 Token 是“低频词”，或者符合特定的高熵特征（如 UUID、Hash），则将其替换为统一的占位符。设计的占位符有：<code>&#123;NUM&#125;、&#123;UUID&#125;、&#123;HASH&#125;、&#123;FILE_VAR&#125;、&#123;VAR&#125;</code>。</p></li></ul><p><strong>实施构建</strong></p><p>使用 Github 上一个比较全面的 POC 库：<a href="https://github.com/eeeeeeeeee-code/POC">https://github.com/eeeeeeeeee-code/POC</a><br>为经过处理后，在测试集上拿到了3926条 path 模板：</p><p><img src="/usr/uploads/2025/11/datacon/image-4.png" alt="alt text"></p><h3 id="赛题二"><a href="#赛题二" class="headerlink" title="赛题二"></a>赛题二</h3><h4 id="初探"><a href="#初探" class="headerlink" title="初探"></a>初探</h4><p>根据提供的僵尸网络样本（ELF）与部分数据包（Pcap），分析出 C&amp;C 通讯协议格式, 并实现一个可以跟踪 C&amp;C 服务器下发指令的跟踪程序。</p><p>这道题我们一开始理解错了题意，以为是我们只需要逆向出协议然后写一个抓包程序，挂到赛题环境中，然后 C&amp;C 服务器就会定时（根据题目描述来看有5个 checkpoint）给我们发送指令。</p><p><img src="/usr/uploads/2025/11/datacon/image-5.png" alt="alt text"></p><p>经过咨询出题人后我们纠正了思路，按僵尸网络追踪的现实应用场景来说，是需要自己模拟一个样本（C2 客户端），与 C2 服务器主动建立连接，然后抓取其下发的攻击指令。</p><p>这道题组里的二进制大佬 <a href="https://blog.wingszeng.top/">@Wings</a> 很快找到了思路，发现大部分样本的框架一致（框架一），仅有 6 个 ELF 是另一种框架（框架二）。不同样本基本上只是握手时使用的 magic number 不一样，少数还含有加密密钥或者 crc 校验。</p><p><img src="/usr/uploads/2025/11/datacon/image-6.png" alt="alt text"></p><p>随后 <a href="https://blog.wingszeng.top/">@Wings</a> 写了一套极为完整、符合大模型提示工程的 Agent 提示词模板，通过调用 IDA MCP 和 fs MCP 来自动化寻找握手所需的参数。但由于最先尝试的 LLM 底座不太行（GPT-5），导致存在大量失败，而赛题一我的方法也基本到了性能瓶颈，就根据他的指导，协助他手工逆向了一些样本。</p><p>随后我们尝试了 Claude 模型作为底座，发现 Claude-4.5-sonnet 可以较好的遵循指令并完成任务。</p><h4 id="转机"><a href="#转机" class="headerlink" title="转机"></a>转机</h4><p><a href="https://blog.wingszeng.top/">@Wings</a> 想到，我们已经使用样本来发送上线消息和处理握手协议，于是想到可以让命令协议也通过样本来处理，这样能够保证通信协议是完全正确的。也就是将样本进行无害化处理后，patch 掉最后执行攻击指令的函数，将攻击参数外传出来。</p><p>对网络侧、ELF侧的数据进行交叉验证，以排查潜在的解析遗漏或者错误。验证方案如下：</p><ol><li>获取ELF侧指令，以（quantized_ts, duration, target）为 key 存入缓存。由于网络侧指令往往迟于 ELF 侧指令到达，因此 quantized_ts 为 ts 向下取整到最近的 2 秒的值。</li><li>网络指令到达后，延迟1.5s处理。检查是否有匹配的 ELF 侧指令，若有，则检查所有字段是否匹配。若无，则表示 ELF 侧指令缺失。</li><li>若延迟后依然未收到网络指令，则表示网络侧指令缺失。<br>经过交叉验证后，最终完善了全部的协议解析。</li></ol><p><img src="/usr/uploads/2025/11/datacon/image-7.png" alt="alt text"></p>]]></content>
    
    
    <summary type="html">在多位队友的强力带飞下，我们有幸拿到了 Datacon2025 互联网威胁分析赛道的冠军。本次比赛分为“异常流量检测”和“僵尸网络协议逆向”两个赛题，我主要负责前者的一部分算法设计，以及后者的运维，取得了不少心得。</summary>
    
    
    
    <category term="杂项" scheme="https://www.catop.top/categories/%E6%9D%82%E9%A1%B9/"/>
    
    
    <category term="数据分析" scheme="https://www.catop.top/tags/%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"/>
    
    <category term="Datacon" scheme="https://www.catop.top/tags/Datacon/"/>
    
    <category term="竞赛记录" scheme="https://www.catop.top/tags/%E7%AB%9E%E8%B5%9B%E8%AE%B0%E5%BD%95/"/>
    
    <category term="恶意流量识别" scheme="https://www.catop.top/tags/%E6%81%B6%E6%84%8F%E6%B5%81%E9%87%8F%E8%AF%86%E5%88%AB/"/>
    
  </entry>
  
  <entry>
    <title>记一次 Linux “多人等权”共享目录的问题及解决</title>
    <link href="https://www.catop.top/2025/09/28/shared-group-in-linux/"/>
    <id>https://www.catop.top/2025/09/28/shared-group-in-linux/</id>
    <published>2025-09-28T07:44:01.000Z</published>
    <updated>2025-11-25T12:30:42.348Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>在实验室公用 Linux 服务器的多用户环境下，经常需要一个由多人<strong>平行读写执行</strong>的共享目录；兼顾可维护与最小化权限暴露。但遇到一些问题，探索后发现与 umask、sgid、目录可穿越 (x)、ACL继承都有关，暴露出自己在 Linux 运维功底方面的缺陷。特此记录，以供参考。</p></blockquote><hr><h2 id="问题介绍"><a href="#问题介绍" class="headerlink" title="问题介绍"></a>问题介绍</h2><ul><li><p>服务器：Linux（Debian 系）</p></li><li><p>需求：<code>/proj/shared</code> 下由多位成员协同读写代码与数据</p></li><li><p>现状：已创建用户组 <code>projgrp</code>，将成员 A、B 都加入了该组</p></li><li><p>问题现象：</p><ul><li>成员 A 能在 <code>/proj/shared</code> 写入，但成员 B 无法修改 A 创建的文件</li><li>A 在 <code>/proj/shared</code> 创建的新文件属组落在自己的主组，而不是 <code>projgrp</code></li><li>成员 B 登录后 <code>cd /home/A/.../shared</code> 报“权限不够”</li></ul></li></ul><p>经过研究，发现这些现象背后牵涉 <strong>目录执行位（x）</strong>、<strong>umask</strong>、<strong>SGID</strong>、<strong>ACL</strong> 以及 <strong>登录会话的组缓存</strong> 等机制。逐个解决后完成了需求，特此记录，以便有类似需求的读者参考。</p><hr><h2 id="知识回顾"><a href="#知识回顾" class="headerlink" title="知识回顾"></a>知识回顾</h2><ul><li><p><strong>umask</strong>：仅影响<strong>新建</strong>文件&#x2F;目录的权限位（如 <code>rw-rw-r--</code>），并<strong>不</strong>决定属组归属。</p><ul><li>常用：<code>umask 0002</code> → 新文件 <code>664</code>，新目录 <code>775</code></li></ul></li><li><p><strong>目录执行位（x）</strong>：对目录意味着“可穿越”。路径上的<strong>每一层目录</strong>都需要对访问者有 <code>x</code>，才能 <code>cd</code> 进去。</p></li><li><p><strong>SGID（setgid）</strong>：目录位设置 <code>g+s</code> 后，新建<strong>子目录</strong>一定继承父目录组；新建<strong>文件</strong>在多数本地文件系统上也会继承父目录组（更标准做法是配合 ACL 保证）。</p></li><li><p><strong>ACL（访问控制列表）</strong>：</p><ul><li><code>setfacl -m ...</code> 设置<strong>访问 ACL</strong>（立即作用于当前对象）</li><li><code>setfacl -d -m ...</code> 设置<strong>默认 ACL</strong>（仅对将来新建的内容生效）</li><li><code>mask</code> 条目会裁剪“命名用户&#x2F;组”的有效权限（常见坑点）</li></ul></li></ul><hr><h2 id="根因剖析"><a href="#根因剖析" class="headerlink" title="根因剖析"></a>根因剖析</h2><ol><li><p><strong>路径上某层目录缺少执行位 <code>x</code></strong><br>例：成员 B 需要穿越 <code>/home/A</code> 才能进入共享子目录，若 <code>/home/A</code> 对组没有 <code>x</code>，即使目标目录权限正确也进不去。</p></li><li><p><strong>仅设置了组与权限，未设置 SGID</strong><br>结果：在共享目录中新建的文件仍归属用户的主组，而非协作组。</p></li><li><p><strong>只在顶层目录设置了默认 ACL，子目录未继承</strong><br>结果：深层子目录中新建的文件不带默认 ACL，仍不可写。</p></li><li><p><strong>ACL 的 <code>mask</code> 把有效权限裁剪掉</strong><br>看到 <code>#effective: r--/rw-</code> 等提示，说明默认&#x2F;访问 ACL 被 <code>mask</code> 限制。</p></li><li><p><strong>用户会话未刷新组列表</strong><br>刚把用户加入协作组，<strong>当前 TTY&#x2F;SSH 会话仍在用旧组缓存</strong>；需要重新登录或用 <code>newgrp</code>。</p></li><li><p><strong>文件系统或挂载选项未启用 ACL</strong><br>某些发行版或挂载方式需要显式 <code>acl</code> 选项。</p></li></ol><hr><h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><h3 id="1-组与顶层权限初始化"><a href="#1-组与顶层权限初始化" class="headerlink" title="1) 组与顶层权限初始化"></a>1) 组与顶层权限初始化</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 创建协作组</span></span><br><span class="line"><span class="built_in">sudo</span> groupadd projgrp</span><br><span class="line"></span><br><span class="line"><span class="comment"># 将成员加入组（示例：userA 与 userB）</span></span><br><span class="line"><span class="built_in">sudo</span> usermod -aG projgrp userA</span><br><span class="line"><span class="built_in">sudo</span> usermod -aG projgrp userB</span><br><span class="line"></span><br><span class="line"><span class="comment"># 共享目录属组与基本权限</span></span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">chgrp</span> -R projgrp /proj/shared</span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">chmod</span> 2775 /proj/shared           <span class="comment"># rwxrwsr-x（含 SGID）</span></span><br></pre></td></tr></table></figure><blockquote><p><code>2775</code> 的 <code>2</code> 即 SGID；<code>s</code> 确保目录树中的新建内容倾向继承协作组。</p></blockquote><h3 id="2-仅允许“穿越”上层私有目录"><a href="#2-仅允许“穿越”上层私有目录" class="headerlink" title="2) 仅允许“穿越”上层私有目录"></a>2) 仅允许“穿越”上层私有目录</h3><p>若共享目录位于某个私有家目录下（如 <code>/home/userA/shared</code>），给协作组一个最小的<strong>穿越</strong>权限：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 仅允许组穿越（不可列目录），保护家目录隐私</span></span><br><span class="line"><span class="built_in">sudo</span> setfacl -m g:projgrp:--x /home/userA</span><br></pre></td></tr></table></figure><blockquote><p>不用把 <code>/home/userA</code> 改成 755；<code>--x</code> 足以穿过而看不到其他文件。</p></blockquote><h3 id="3-递归设置-SGID-访问-ACL-默认-ACL"><a href="#3-递归设置-SGID-访问-ACL-默认-ACL" class="headerlink" title="3) 递归设置 SGID + 访问 ACL + 默认 ACL"></a>3) 递归设置 SGID + 访问 ACL + 默认 ACL</h3><p><strong>关键：对子目录也必须生效</strong>，因此做递归设置：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">DIR=/proj/shared</span><br><span class="line">GRP=projgrp</span><br><span class="line"></span><br><span class="line"><span class="comment"># 确保整个目录树属组正确</span></span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">chgrp</span> -R <span class="string">&quot;<span class="variable">$GRP</span>&quot;</span> <span class="string">&quot;<span class="variable">$DIR</span>&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 给所有目录设置 rwxrwsr-x（含 SGID）</span></span><br><span class="line"><span class="built_in">sudo</span> find <span class="string">&quot;<span class="variable">$DIR</span>&quot;</span> -<span class="built_in">type</span> d -<span class="built_in">exec</span> <span class="built_in">chmod</span> 2775 &#123;&#125; +</span><br><span class="line"></span><br><span class="line"><span class="comment"># 访问 ACL：让协作组在现有内容上有 rwx</span></span><br><span class="line"><span class="built_in">sudo</span> setfacl -R -m g:<span class="variable">$GRP</span>:rwx,m::rwx <span class="string">&quot;<span class="variable">$DIR</span>&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 默认 ACL：让将来新建的内容自动带组 rwx（以及放开 mask）</span></span><br><span class="line"><span class="built_in">sudo</span> setfacl -dR -m g:<span class="variable">$GRP</span>:rwx,m::rwx <span class="string">&quot;<span class="variable">$DIR</span>&quot;</span></span><br></pre></td></tr></table></figure><blockquote><p><code>m::rwx</code> 是 ACL 的 <strong>mask</strong>，不放开会“剪掉”有效权限，导致组写权限实测无效。</p></blockquote><h3 id="4-成员-umask-设置"><a href="#4-成员-umask-设置" class="headerlink" title="4) 成员 umask 设置"></a>4) 成员 umask 设置</h3><p>协作成员的 <code>umask</code> 设为 <code>0002</code>（常见 shell 的 <code>~/.zshrc</code>、<code>~/.bashrc</code>）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">umask</span> 0002</span><br></pre></td></tr></table></figure><blockquote><p>这确保新建文件是 <code>664</code>，目录是 <code>775</code>，符合“组可写”的协作预期。<br>注意：<strong>umask 不控制属组</strong>；属组继承依赖 SGID&#x2F;ACL。</p></blockquote><h3 id="5-刷新用户会话的组上下文"><a href="#5-刷新用户会话的组上下文" class="headerlink" title="5) 刷新用户会话的组上下文"></a>5) 刷新用户会话的组上下文</h3><p>将成员加入协作组后，需要<strong>重新登录</strong>或临时执行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">newgrp projgrp</span><br></pre></td></tr></table></figure><blockquote><p><code>newgrp</code> 会以该组为有效组启动一个子 shell，退出即可回到原会话。</p></blockquote><hr><h2 id="验证"><a href="#验证" class="headerlink" title="验证"></a>验证</h2><h3 id="顶层目录必须具备-s-权限位"><a href="#顶层目录必须具备-s-权限位" class="headerlink" title="顶层目录必须具备 s 权限位"></a>顶层目录必须具备 <code>s</code> 权限位</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">ls</span> -ld /proj/shared</span><br><span class="line"><span class="comment"># 期待：drwxrwsr-x ... root projgrp ...</span></span><br></pre></td></tr></table></figure><h3 id="ACL-应包含默认与-mask"><a href="#ACL-应包含默认与-mask" class="headerlink" title="ACL 应包含默认与 mask"></a>ACL 应包含默认与 mask</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">getfacl /proj/shared</span><br><span class="line"><span class="comment"># 期待含：</span></span><br><span class="line"><span class="comment"># group:projgrp:rwx</span></span><br><span class="line"><span class="comment"># default:group:projgrp:rwx</span></span><br><span class="line"><span class="comment"># default:mask::rwx</span></span><br></pre></td></tr></table></figure><h3 id="新建文件-目录测试"><a href="#新建文件-目录测试" class="headerlink" title="新建文件&#x2F;目录测试"></a>新建文件&#x2F;目录测试</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 模拟成员 B 的新会话测试</span></span><br><span class="line"><span class="built_in">sudo</span> -u userB bash -lc <span class="string">&#x27;umask 0002; touch /proj/shared/t.new; mkdir -p /proj/shared/subdir&#x27;</span></span><br><span class="line"><span class="built_in">ls</span> -l /proj/shared/t.new /proj/shared/subdir</span><br><span class="line"><span class="comment"># 期待：属组为 projgrp；文件 664，目录 775</span></span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><table><thead><tr><th>现象</th><th>排查点</th><th>处理</th></tr></thead><tbody><tr><td><code>cd</code> 报权限不够</td><td>路径某层目录缺 <code>x</code></td><td>给协作组添加最小穿越 ACL：<code>setfacl -m g:projgrp:--x /path/that/blocks</code></td></tr><tr><td>新文件属组不是协作组</td><td>目录&#x2F;子目录没 SGID</td><td>递归：<code>find DIR -type d -exec chmod g+s &#123;&#125; +</code></td></tr><tr><td>组无写权限（但 ACL 好像给了）</td><td><code>mask</code> 裁剪了有效权限</td><td>同时设置 <code>m::rwx</code>：访问与默认 ACL 都要设</td></tr><tr><td>只在顶层有效，子目录无效</td><td>只在顶层设了默认 ACL</td><td>用 <code>-R</code> 递归设置访问与默认 ACL</td></tr><tr><td>成员刚加组仍不生效</td><td>会话组缓存</td><td>重登录或 <code>newgrp projgrp</code></td></tr><tr><td>ACL 设置后无效果</td><td>文件系统未启用 ACL</td><td><code>mount</code> 检查；<code>/etc/fstab</code> 增加 <code>acl</code>，重挂载</td></tr></tbody></table><p>在多人协作的公用服务器上，实现“等权共享目录”并不复杂，但需要<strong>系统性处理</strong>：</p><ul><li>路径可穿越（x）</li><li>SGID 保证组继承</li><li>ACL（含 mask）保证权限一致性</li><li>umask 做默认位的兜底</li><li>会话刷新确保新组即时生效</li></ul>]]></content>
    
    
    <summary type="html">在实验室公用 Linux 服务器的多用户环境下，经常需要一个由多人平行读写执行的共享目录。但遇到一些问题，探索后发现与 umask、sgid、目录可穿越 (x)、ACL继承都有关，暴露出自己在 Linux 运维功底方面的缺陷。特此记录，以供参考。</summary>
    
    
    
    <category term="杂项" scheme="https://www.catop.top/categories/%E6%9D%82%E9%A1%B9/"/>
    
    
    <category term="Linux" scheme="https://www.catop.top/tags/Linux/"/>
    
  </entry>
  
  <entry>
    <title>论文速览 - ProvG-Searcher: A Graph Representation Learning Approach for Efficient Provenance Graph Search</title>
    <link href="https://www.catop.top/2025/08/17/provg-searcher-snapshot/"/>
    <id>https://www.catop.top/2025/08/17/provg-searcher-snapshot/</id>
    <published>2025-08-17T14:00:01.000Z</published>
    <updated>2025-08-17T14:02:00.085Z</updated>
    
    <content type="html"><![CDATA[<p>ProvG-Searcher 提出了三阶段的图划分方案及图缩减方法，实现了基于 GNN 的子图匹配算法，梳理并解决了威胁狩猎实践中的几个关键挑战。最近对其进行了简要阅读和学习，对我认为重点的部分做了笔记。</p><blockquote><p>基本信息</p></blockquote><ul><li>发表: CCS 23’</li><li>图学习类别: 图学习+嵌入向量库</li><li>数据集: Darpa E3</li><li>方法分类: 图级别, 威胁狩猎</li><li>DOI：<a href="https://dl.acm.org/doi/10.1145/3576915.3623187">10.1145&#x2F;3576915.3623187</a></li></ul><h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>在庞大的历史系统日志仓库中高效搜索已知攻击行为，是一个研究较少但对威胁狩猎实践具有关键影响的问题。</p><p>在溯源分析的背景下，这要求将外部观察到的威胁行为转换为<strong>可在系统级溯源图中进行搜索的查询</strong>[53, 61, 74, 87]。这个问题设置确实可以视为图蕴含（子图匹配）问题的一个实例。</p><h3 id="Challenge4：查询图与溯源图粒度不对齐的问题"><a href="#Challenge4：查询图与溯源图粒度不对齐的问题" class="headerlink" title="Challenge4：查询图与溯源图粒度不对齐的问题"></a><strong>Challenge4：查询图与溯源图粒度不对齐的问题</strong></h3><ul><li>感觉梳理的很好：<ul><li><strong>抽象层级不匹配 (Mismatch in Abstraction Levels)</strong><ul><li>查询图通常是高度概括的。例如，查询图里可能只用一个“浏览器进程”来描述攻击步骤。但在实际的溯源图中，这个“浏览器进程”可能对应的是主进程的一个克隆进程、一个由启动器创建的子进程，或者是具体的某个浏览器（如Firefox、Chrome。这种从概念到具体实现的差异，造成了匹配困难。</li></ul></li><li><strong>结构性差异 (Structural Differences)</strong><ul><li>溯源图中可能包含查询图里没有的额外中间步骤，或者因为日志记录不全而缺失了某些事件。</li><li>攻击者为了躲避检测，可能会故意省略、替换或增加一些攻击步骤，这导致实际的攻击图（在溯源图中）与分析师预设的查询图在结构上不完全一致。</li></ul></li><li><strong>实体歧义性 (Entity Ambiguity)</strong><ul><li>查询中的实体可能存在歧义。例如，两个同名的文件在不同应用程序的上下文中可能扮演完全不同的角色。如果查询不够具体，就很难在庞大的溯源图中精确定位到目标实体。<br>  为了应对这些挑战，必须让模型具备一定的鲁棒性，能够容忍这些差异，从而在不完全匹配的情况下也能成功识别出威胁行为。</li></ul></li></ul></li></ul><h2 id="方法"><a href="#方法" class="headerlink" title="方法"></a>方法</h2><p>我们的技术采用了一种图表示学习方法，使大部分搜索计算可以离线进行，并在查询时执行一个轻量级的比较步骤。在图缩减方面，进行了<strong>以进程为中心的划分，同时结合图版本控制以整合时序信息。</strong></p><p>我们采用顺序嵌入，这使得在嵌入空间中通过对这些表示进行坐标-wise 排序，能够在学习层次化实体表示的同时保持层次结构。</p><p>工作的主要贡献：</p><ul><li><p>一种图简化策略，能够在保留溯源图中多样行为特征的同时，促进有效的学习和搜索能力。</p></li><li><p>利用顺序嵌入以在嵌入空间中高效评估子图关系。</p></li><li><p>一种通用的搜索方法，不仅不偏重于攻击行为，还可推广以识别任何类型的行为。</p></li><li><p>由于子图表示的紧凑性和溯源图的简化，能够高效地搜索大量历史日志数据。</p></li><li><p>相比其他假设驱动的威胁狩猎方法，显著降低了误匹配率并提高了准确性。</p></li></ul><h3 id="图缩减策略"><a href="#图缩减策略" class="headerlink" title="图缩减策略"></a>图缩减策略</h3><div style="text-align: center;"><img src="/usr/uploads/2025/08/provg/t1.png" style="zoom:50%;" /></div><p><strong>总体</strong></p><p>这些操作包括移除与管道和内存访问相关的非必要节点和边，将克隆进程与其父进程合并，并按时间间隔合并网络通信事件。每个描述唯一系统实体的节点随后被分配到一个抽象类中，该抽象类描述更高层次的分类，例如系统文件、用户应用程序进程等。<strong>（先前研究常用，[72,74]）</strong></p><p>在简化图之后，下一步是为节点进行版本化，这将时间信息纳入图中，以防止节点间出现虚假的信息流动。</p><p>在最后一步，图被划分为重叠的子图，通过提取每个进程节点的<strong>k跳自环图</strong>来实现。此处，k也表示用于获取子图表示的GNN层数。我们随后通过迭代标签传播（第4.1.4节）对抽象后的自环图进行另一层的缩减，以去除重复行为。这样每个子图中便保留了独特的特征，作为子图关系的一部分进行学习。</p><p><strong>图简化</strong></p><ul><li>线程处理：将线程合并到其父进程之中、将clone出来的进程节点合并到原节点。</li><li>捕捉远程服务器行为随时间变化：将每个远程IP和端口组合视为一个独立的源，并在10分钟的时间窗口内进行处理。[29]</li><li>层次&#x2F;标准化：根据每个实体在文件系统中的根目录分配类别标签。</li></ul><p><strong>缓解依赖爆炸</strong></p><ul><li>图版本化：确保所有路径的边具有单调递增的时间戳值，从而保留事件的因果顺序。</li><li>将一些特定节点指定为汇聚节点：解决图神经网络中的过度平滑问题。</li></ul><h2 id="评估"><a href="#评估" class="headerlink" title="评估"></a>评估</h2><ol><li>图缩减效果</li></ol><div style="text-align: center;"><img src="/usr/uploads/2025/08/provg/t2.png" style="zoom:50%;" /></div><ol start="2"><li>在 E3 数据集上评估与同类工作的威胁狩猎效果</li></ol><div style="text-align: center;"><img src="/usr/uploads/2025/08/provg/t3.png" style="zoom:50%;" /></div><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>这篇文章主要是在<strong>查询图与溯源图粒度不对齐问题</strong>方面的梳理非常全面，在图划分、图缩减方面的算法描述也非常详细，希望有时间再来回顾和学习一下。</p>]]></content>
    
    
    <summary type="html">ProvG-Searcher 提出了三阶段的图划分方案及图缩减方法，实现了基于 GNN 的子图匹配算法，梳理并解决了威胁狩猎实践中的几个关键挑战。最近对其进行了简要阅读和学习，对我认为重点的部分做了笔记。</summary>
    
    
    
    <category term="论文" scheme="https://www.catop.top/categories/%E8%AE%BA%E6%96%87/"/>
    
    
    <category term="深度学习" scheme="https://www.catop.top/tags/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/"/>
    
    <category term="溯源图" scheme="https://www.catop.top/tags/%E6%BA%AF%E6%BA%90%E5%9B%BE/"/>
    
    <category term="图注意力" scheme="https://www.catop.top/tags/%E5%9B%BE%E6%B3%A8%E6%84%8F%E5%8A%9B/"/>
    
    <category term="图学习" scheme="https://www.catop.top/tags/%E5%9B%BE%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>论文阅读 - ShadowKube: Enhancing Kubernetes Security with Behavioral Monitoring and Honeypot Integration</title>
    <link href="https://www.catop.top/2025/08/16/shadowcube-reading-report/"/>
    <id>https://www.catop.top/2025/08/16/shadowcube-reading-report/</id>
    <published>2025-08-16T08:02:01.000Z</published>
    <updated>2025-08-16T08:19:57.142Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>ShadowKube 将行为监控与影子蜜罐相结合，有效检测和中和异常行为。通过建立行为基线以识别偏差，并将被攻陷的节点转换为蜜罐，从而隔离并捕获攻击者，从而减轻其造成的威胁。最近最这篇文章进行了学习，并基于自己的理解，整理了简单而非详细的阅读报告。</p></blockquote><blockquote><p>同时提供了一个scholar site：<a href="https://sites.google.com/view/shadowkube">https://sites.google.com/view/shadowkube</a></p></blockquote><h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>攻击者通常利用<strong>集群配置错误或集群应用程序中的漏洞</strong>来获取提升的权限，例如创建容器的能力。借助这些权限，他们部署大量受其控制的恶意资源，包括嵌入后门或加密货币挖矿恶意软件的容器。</p><h3 id="现有局限"><a href="#现有局限" class="headerlink" title="现有局限"></a>现有局限</h3><p>针对 API Server 的攻击不直接攻击应用容器，因此网络层面的检测难以生效，需要有效的行为监控方式。</p><p>现有的行为监控方式：</p><ul><li>基于规则：例如 Falco，可以通过符号链接等技术绕过。</li><li>基于机器学习：有的是通过API调用行为建模来检测，但受正常的突发访问的影响。</li></ul><p>现有研究主要侧重于通过应用<strong>静态策略</strong>来监控和防止异常行为。这种方法需要大量的规则维护，并且容易受到攻击者的规避。</p><p>本文采用行为基线来识别Kubernetes集群中的异常，并利用蜜罐技术诱骗攻击者。受<strong>移动目标防御（MTD）</strong>和影子蜜罐概念的启发，我们提出了ShadowKube。</p><h3 id="主要贡献"><a href="#主要贡献" class="headerlink" title="主要贡献"></a>主要贡献</h3><ol><li>算法设计。我们开发了一种基线算法，利用最长公共子序列（LCS）和Levenshtein距离来建立容器的规范性良性行为，并量化容器运行时的异常情况。</li><li>系统实现。我们实现了ShadowKube，一个利用影子诱饵主机的主动防御框架，用于Kubernetes安全。它在不被攻击者察觉的情况下，将异常容器和主机转化为诱饵主机，从而实现诱捕和防御。</li><li>全面评估。我们进行了实验以测试ShadowKube检测异常的能力、诱捕效率以及评估其性能影响。系统证明其能够以最小的开销快速识别并隔离集群中的异常行为。它将受影响区域连接到影子集群以实现有效的防御。在一个月的公网部署期间，ShadowKube共吸引了635次真实攻击。</li></ol><h2 id="设计"><a href="#设计" class="headerlink" title="设计"></a>设计</h2><p>系统包括五个组件：流量代理、探针、检测器、执行器和影子集群。</p><p>监控的数据指标：文件访问、网络连接、命令执行。</p><img src="/usr/uploads/2025/08/shadowcube/image.png"   style="zoom:50%;"><h3 id="基线行为采集阶段"><a href="#基线行为采集阶段" class="headerlink" title="基线行为采集阶段"></a>基线行为采集阶段</h3><p>鉴于Pod集合的动态性，正常活动可能并不总是在特定的Pod中发生，而是在其副本中进行。ShadowKube通过<strong>根据其元数据将行为分类为不同的组</strong>，为每个组单独确立良性行为基线。</p><blockquote><p>最小有效序列长度、最长公共子序列</p></blockquote><p>对于文件和命令操作：在多个观察到的行为中找到最长的共同子序列，以总结正常模式</p><h3 id="检测阶段"><a href="#检测阶段" class="headerlink" title="检测阶段"></a>检测阶段</h3><p>通过为每个时间窗口计算累积怀疑分数，可以有效识别与既定行为基线的偏差，突出 Kubernetes 集群环境中的潜在安全威胁或异常。→ 和一些溯源图的方法例如Kairos思路有点像</p><p>对于文件访问、命令执行、网络进行单独的怀疑分数确定，核心就是Levenshtein距离。</p><h3 id="策略执行阶段"><a href="#策略执行阶段" class="headerlink" title="策略执行阶段"></a>策略执行阶段</h3><blockquote><p>包括网络重构、Pod 清理和敏感信息修改。</p></blockquote><p>Pod 的原位转换可能导致单个节点与控制平面断开连接，从而引起怀疑。为此，我们构建一个影子集群，旨在动态地与转换后的Pod集成，从而构建出一个表面上正常的运行环境。</p><ul><li><p>网络：通过iptables实现，当对生产服务发起API调用时，将目标节点上正在进行的进程重定向到影子集群。</p><blockquote><p>正常用户的访问是否会受到影响？  </p></blockquote></li><li><p>Pod 清理：为保护目标节点上的其他 Pod 不被破坏，所有除被破坏的 Pod 之外的 Pod 都会被移除。后，生产集群会通知在其他节点上重新创建这些 Pod，从而保持服务的连续性。</p></li><li><p>敏感信息修改：替换例如服务账户令牌及其他对生产环境中访问Kubernetes API进行认证和授权至关重要的Kubernetes凭证。</p></li></ul><h2 id="思考"><a href="#思考" class="headerlink" title="思考"></a>思考</h2><ul><li>敏感信息修改是否能直接用ebpf hook;</li><li>由于云服务的无状态性，能否基于用户指纹（例如Cookie）而非来源IP来track用户，以此实现例如横向移动探测的目的？</li></ul><h2 id="知识补充"><a href="#知识补充" class="headerlink" title="知识补充"></a>知识补充</h2><h3 id="K8s-知识点"><a href="#K8s-知识点" class="headerlink" title="K8s 知识点"></a>K8s 知识点</h3><ul><li>每个 Pod 包含一个或多个共享网络和存储资源的容器。</li></ul><hr>]]></content>
    
    
    <summary type="html">ShadowKube 将行为监控与影子蜜罐相结合，有效检测和中和异常行为。通过建立行为基线以识别偏差，并将被攻陷的节点转换为蜜罐，从而隔离并捕获攻击者，从而减轻其造成的威胁。最近最这篇文章进行了学习，并基于自己的理解，整理了简单而非详细的阅读报告。</summary>
    
    
    
    <category term="论文" scheme="https://www.catop.top/categories/%E8%AE%BA%E6%96%87/"/>
    
    
    <category term="云安全" scheme="https://www.catop.top/tags/%E4%BA%91%E5%AE%89%E5%85%A8/"/>
    
    <category term="蜜罐" scheme="https://www.catop.top/tags/%E8%9C%9C%E7%BD%90/"/>
    
    <category term="威胁诱捕" scheme="https://www.catop.top/tags/%E5%A8%81%E8%83%81%E8%AF%B1%E6%8D%95/"/>
    
    <category term="云原生" scheme="https://www.catop.top/tags/%E4%BA%91%E5%8E%9F%E7%94%9F/"/>
    
  </entry>
  
  <entry>
    <title>从 Mac 到 ArchLinux：探索及自用配置</title>
    <link href="https://www.catop.top/2025/08/16/my-arch-explore/"/>
    <id>https://www.catop.top/2025/08/16/my-arch-explore/</id>
    <published>2025-08-16T06:20:01.000Z</published>
    <updated>2025-10-24T01:29:11.868Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>这篇博客只是对自己的配置做简单记录，从 Notion 上拷贝下来的，随时更新。</p></blockquote><h2 id="基本情况"><a href="#基本情况" class="headerlink" title="基本情况"></a>基本情况</h2><ul><li>Linux 使用经验：8年左右，之前使用 Linux 主要用来配置服务器，具有一定的运维经验。</li><li>显示协议： Wayland</li><li>桌面环境： Gnome</li><li>GPU： Nvidia + Intel</li></ul><img src="/usr/uploads/2025/08/archconfig/desktop.jpg"  style="zoom:50%;" /><h2 id="Part1：可视化"><a href="#Part1：可视化" class="headerlink" title="Part1：可视化"></a>Part1：可视化</h2><h3 id="文字相关"><a href="#文字相关" class="headerlink" title="文字相关"></a>文字相关</h3><h4 id="字体大小"><a href="#字体大小" class="headerlink" title="字体大小"></a>字体大小</h4><p>经过测试，对于27寸4K显示器，在使用 Gnome（Wayland）时采用<strong>全域 HIDPI 缩放 150%+字体 1.25 倍</strong>时取得的显示效果是最舒服的，参考指标为 Mac 在同样显示器上的效果。</p><p>设置全域HIDPI缩放：</p><p><code>gsettings set org.gnome.mutter experimental-features &#39;[&quot;scale-monitor-framebuffer&quot;, &quot;xwayland-native-scaling&quot;]’</code></p><p>设置1.25倍字体并开机生效：<br><code>gsettings set org.gnome.desktop.interface text-scaling-factor 1.25</code></p><h4 id="Apple-风格-Emoji-显示"><a href="#Apple-风格-Emoji-显示" class="headerlink" title="Apple 风格 Emoji 显示"></a>Apple 风格 Emoji 显示</h4><p>首先安装字体：<strong>yay</strong> <strong>ttf-apple-emoji</strong> 之后应该就能正常显示了。</p><p>对于 emoji 选择的问题，可以直接在 fcitx5 输入法中输入 emoji 名称（中英文均可），也可以安装一个 picker：<code>pacman -S gnome-characters</code>，然后自己绑定一个快捷键。</p><h4 id="CJK-字体设定"><a href="#CJK-字体设定" class="headerlink" title="CJK 字体设定"></a>CJK 字体设定</h4><p>默认来说 Noto 或者Adwaita 字体系列是有 CJK 支持的，但在高分辨率显示器上感觉中文还是不够平滑和锐利。经过对 ChatGPT 推荐字体的测试，感觉 <strong>HarmonyOS Sans SC</strong> 字体（即鸿蒙字体）还不错，我们来为 CJK 单独设置为该字体。</p><p>首先安装 <code>ttf-harmonyos-sans</code> (aur)。随后创建配置文件 <code>mkdir -p ~/.config/fontconfig/conf.d</code>， 新建一个字体配置文件 <code>~/.config/fontconfig/conf.d/65-harmonyos-cjk.conf</code>，写入如下配置：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">&quot;1.0&quot;</span>?&gt;</span></span><br><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">fontconfig</span> <span class="keyword">SYSTEM</span> <span class="string">&quot;urn:fontconfig:fonts.dtd&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">fontconfig</span>&gt;</span></span><br><span class="line">  <span class="comment">&lt;!-- 通用平滑设置 --&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">match</span> <span class="attr">target</span>=<span class="string">&quot;font&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">edit</span> <span class="attr">name</span>=<span class="string">&quot;antialias&quot;</span> <span class="attr">mode</span>=<span class="string">&quot;assign&quot;</span>&gt;</span><span class="tag">&lt;<span class="name">bool</span>&gt;</span>true<span class="tag">&lt;/<span class="name">bool</span>&gt;</span><span class="tag">&lt;/<span class="name">edit</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">edit</span> <span class="attr">name</span>=<span class="string">&quot;hinting&quot;</span> <span class="attr">mode</span>=<span class="string">&quot;assign&quot;</span>&gt;</span><span class="tag">&lt;<span class="name">bool</span>&gt;</span>true<span class="tag">&lt;/<span class="name">bool</span>&gt;</span><span class="tag">&lt;/<span class="name">edit</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">edit</span> <span class="attr">name</span>=<span class="string">&quot;hintstyle&quot;</span> <span class="attr">mode</span>=<span class="string">&quot;assign&quot;</span>&gt;</span><span class="tag">&lt;<span class="name">const</span>&gt;</span>hintslight<span class="tag">&lt;/<span class="name">const</span>&gt;</span><span class="tag">&lt;/<span class="name">edit</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">edit</span> <span class="attr">name</span>=<span class="string">&quot;rgba&quot;</span> <span class="attr">mode</span>=<span class="string">&quot;assign&quot;</span>&gt;</span><span class="tag">&lt;<span class="name">const</span>&gt;</span>rgb<span class="tag">&lt;/<span class="name">const</span>&gt;</span><span class="tag">&lt;/<span class="name">edit</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">match</span>&gt;</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">&lt;!-- 仅中文语言优先使用 HarmonyOS Sans --&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">match</span> <span class="attr">target</span>=<span class="string">&quot;pattern&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">test</span> <span class="attr">name</span>=<span class="string">&quot;lang&quot;</span> <span class="attr">compare</span>=<span class="string">&quot;contains&quot;</span>&gt;</span><span class="tag">&lt;<span class="name">string</span>&gt;</span>zh<span class="tag">&lt;/<span class="name">string</span>&gt;</span><span class="tag">&lt;/<span class="name">test</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">edit</span> <span class="attr">name</span>=<span class="string">&quot;family&quot;</span> <span class="attr">mode</span>=<span class="string">&quot;prepend&quot;</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">string</span>&gt;</span>HarmonyOS Sans SC<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">edit</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">match</span>&gt;</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">&lt;!-- 全局 sans-serif 不放 HarmonyOS Sans，只保留英文默认 --&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">alias</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">family</span>&gt;</span>sans-serif<span class="tag">&lt;/<span class="name">family</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">prefer</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">family</span>&gt;</span>Noto Sans<span class="tag">&lt;/<span class="name">family</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">family</span>&gt;</span>Noto Sans CJK SC<span class="tag">&lt;/<span class="name">family</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">family</span>&gt;</span>Source Han Sans SC<span class="tag">&lt;/<span class="name">family</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">prefer</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">alias</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">fontconfig</span>&gt;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>保存后刷新字体缓存 <code>fc-cache -fv</code>，并重启 Gnome Shell。使用 <code>fc-match</code> 检查中英文字体情况，应当显示：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">❯ fc-match :lang=en</span><br><span class="line">NotoSans-Regular.ttf: &quot;Noto Sans&quot; &quot;Regular&quot;</span><br><span class="line"></span><br><span class="line">❯ fc-match :lang=zh</span><br><span class="line">HarmonyOS_SansSC_Regular.ttf: &quot;HarmonyOS Sans SC&quot; &quot;Regular&quot;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="视觉效果增强"><a href="#视觉效果增强" class="headerlink" title="视觉效果增强"></a>视觉效果增强</h3><h4 id="模糊效果"><a href="#模糊效果" class="headerlink" title="模糊效果"></a>模糊效果</h4><p>yay -S gnome-shell-extension-blur-my-shell(remember to reboot),</p><p>then go to the extensions and activate blur-my-shell,</p><p>go to the settings and turn on the “applications blur”,</p><p>finally, turn on “enable all by default”, and then turn off “opaque focused window”.</p><p>now you get a blur and transparent terminal.</p><h3 id="显示驱动"><a href="#显示驱动" class="headerlink" title="显示驱动"></a>显示驱动</h3><p><a href="https://wiki.archlinuxcn.org/wiki/%E7%A1%AC%E4%BB%B6%E8%A7%86%E9%A2%91%E5%8A%A0%E9%80%9F">https://wiki.archlinuxcn.org/wiki/硬件视频加速</a></p><h2 id="Part2：外设"><a href="#Part2：外设" class="headerlink" title="Part2：外设"></a>Part2：外设</h2><h3 id="显示器"><a href="#显示器" class="headerlink" title="显示器"></a>显示器</h3><h4 id="亮度调节"><a href="#亮度调节" class="headerlink" title="亮度调节"></a>亮度调节</h4><p>yay ddccontrol-db</p><h3 id="蓝牙"><a href="#蓝牙" class="headerlink" title="蓝牙"></a>蓝牙</h3><ul><li>根据型号寻找驱动，一般会有PID&#x2F;VID：<a href="https://oemdrivers.com/bluetooth-ugreen-bluetooth-5-0-adapter">https://oemdrivers.com/bluetooth-ugreen-bluetooth-5-0-adapter</a></li><li>查看当前内核支持的设备ID：[<a href="https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bluetooth/btusb.c?h=v6.15.9%5D">https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bluetooth/btusb.c?h=v6.15.9]</a>(<a href="https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bluetooth/btusb.c?h=v6.15.9">https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bluetooth/btusb.c?h=v6.15.9</a></li></ul><h2 id="Part3：软件及功能"><a href="#Part3：软件及功能" class="headerlink" title="Part3：软件及功能"></a>Part3：软件及功能</h2><ol><li><p>屏幕截图和标注：<code>sudo pacman -S flameshot</code></p><p> 安装并绑定命令为 <code>flameshot gui</code> 的快捷键后，可能发现无法正常截图。参考了这个讨论：<a href="https://github.com/flameshot-org/flameshot/issues/3446#issuecomment-1931292685">https://github.com/flameshot-org/flameshot/issues/3446#issuecomment-1931292685</a></p><p> 按他的方法创建一个脚本，然后快捷键执行这个 sh 即可正常截图。</p> <figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">#!<span class="regexp">/bin/</span>sh</span><br><span class="line">/usr/bin/flameshot gui</span><br></pre></td></tr></table></figure></li><li><p>屏幕录制：gnome自带 ctrl+shift+alt+r，功能挺齐全的</p></li><li><p>flameshot</p><p><strong>NVIDIA显卡用户注意</strong>：在最新版本的 Webkit2Gtk (2.42.0) 中，由于 Nvidia 专有驱动未完全实现 DMABUF，将导致无法启动和崩溃的情况发生。请降级或在 &#x2F;etc&#x2F;environment （或者其他设置环境变量的地方）中加入 WEBKIT_DISABLE_DMABUF_RENDERER&#x3D;1 环境变量关闭 DMABUF 的使用。</p></li><li><p>屏幕录制：<code>sudo pacman -S kooha</code></p></li><li><p><strong>nVidia on Wayland 中 Gnome Video Failed to initialized OpenGL with Gtk：</strong></p><p> ~&#x2F;.config&#x2F;environment.d&#x2F;gdk.conf 中添加：</p><p> <code>GDK_GL=gles</code></p></li></ol><h2 id="Part4：网络"><a href="#Part4：网络" class="headerlink" title="Part4：网络"></a>Part4：网络</h2><h2 id="DNS"><a href="#DNS" class="headerlink" title="DNS"></a>DNS</h2><p>&#x2F;etc&#x2F;systemd&#x2F;resolved.conf</p><p>resolvectl status</p><p>sudo systemctl restart systemd-resolved</p><h2 id="DHCP-SERVER）"><a href="#DHCP-SERVER）" class="headerlink" title="DHCP(SERVER）"></a>DHCP(SERVER）</h2><h2 id="让虚拟机通过-NAT-访问指定网段-10-0-0-0-8"><a href="#让虚拟机通过-NAT-访问指定网段-10-0-0-0-8" class="headerlink" title="让虚拟机通过 NAT 访问指定网段 10.0.0.0/8"></a>让虚拟机通过 NAT 访问指定网段 <code>10.0.0.0/8</code></h2><p>sudo iptables -A FORWARD -i br0 -o enp0s31f6 -d 10.0.0.0&#x2F;8 -j ACCEPT<br>sudo iptables -A FORWARD -i enp0s31f6 -o br0 -m state –state RELATED,ESTABLISHED -j ACCEPT</p><p>sudo iptables -t nat -A POSTROUTING -o enp0s31f6 -d 10.0.0.0&#x2F;8 -j MASQUERADE</p>]]></content>
    
    
    <summary type="html">作为一个 6 年的 Mac 老用户，拿到了一台 X86 的工作机。最开始我是比较抵触用 Linux 作为个人工作站的，但后来发现 Arch 的生态和社区真的令人惊叹。经过短短几天的探索，我几乎可以依靠开源快速解决任何问题。第一次深刻感受到了自由和开源的力量。</summary>
    
    
    
    <category term="杂项" scheme="https://www.catop.top/categories/%E6%9D%82%E9%A1%B9/"/>
    
    
    <category term="ArchLinux" scheme="https://www.catop.top/tags/ArchLinux/"/>
    
  </entry>
  
  <entry>
    <title>记一次协助本科实验室迁移物理服务器集群的经历</title>
    <link href="https://www.catop.top/2025/07/23/a-server-cluster-migration/"/>
    <id>https://www.catop.top/2025/07/23/a-server-cluster-migration/</id>
    <published>2025-07-23T10:20:01.000Z</published>
    <updated>2025-07-24T15:31:21.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><h3 id="实验室虚拟化资源的密集需求"><a href="#实验室虚拟化资源的密集需求" class="headerlink" title="实验室虚拟化资源的密集需求"></a>实验室虚拟化资源的密集需求</h3><p>本科实验室（下亦称“小组”）目前约有 25 名学生及 2 位指导老师，每年春季会组织集中招新，每年约招收 5-8 名不同方向的新生。小组平时主要以参加 CTF 比赛为主线，引导大家进行网络安全方面的学习和实践，同时也会承担一些工程项目，因此有一小部分可自行支配的资金。硬件条件方面，小组具备一块独立的办公区域，空调等设施齐全，甚至配有一张行军床。网络方面为千兆有线 &amp; Wi-Fi 7 网络接入，互联网带宽为校园网提供，视情况最高可达 1000 Mbps，网络设备全部采用华为、锐捷等主流品牌，给日常实验提供有力的保障。氛围十分融洽，每周会组织一次技术 workshop ，知识库、Git 仓库等一直传承和更新，经常会有挑灯夜战的同学。以上是我在实验室期间的主观感受。</p><img src="https://www.catop.top/usr/uploads/2025/07/sksec/IMG_3723.jpeg"  style="zoom:33%;" /><p>作为 2021 年加入小组的学生，我梳理了一下实验室的虚拟化资源的变迁：</p><ul><li><p>2021年之前，小组有两台<strong>浪潮 NP5540</strong> 安装了 VMware Exsi，每台装有两颗 Xeon E5-2407 和 16GB 内存。这配置，甚至还不如 2021 年的笔记本。</p></li><li><p>2022 年，一名学长捐赠了一台<strong>戴尔 Precision T5600</strong>，装有两颗 Xeon E5-2660 和 32GB 内存。原本是跑的 ArchLinux ，镜像站的起源也是在这台机器，后来安装了 Proxmox VE，将镜像站迁移到了另一台专用机器。</p></li><li><p>2023年，我了解到系里有一套仿真靶场平台，但软件十分难用，每个赛题环境都是独立的虚拟机，启动缓慢且占用资源巨大。为了给系里学生更好的实战体验，在与老师商讨和评估后，决定建设自己的开源靶场平台。系统为 <strong>3节点的深信服超融合 HCI</strong>。<em>这些硬件后来由小组代为管理，成为了现今虚拟化资源的来源</em>。</p><ul><li><p><strong>硬件条件</strong></p><p>包括三台物理机，每台装有两颗 Xeon E5-2650 v4 和 192GB 的内存。此外还有两台华为 S5720 万兆交换机，一台承担存储网络，一台承担业务和管理网络。存储为戴尔 4T 企业级 SATA 盘 + 英特尔 480GB 企业级 SDD 缓存，总可用存储约 24 TB。</p><p><img src="https://www.catop.top/usr/uploads/2025/07/sksec/iShot_2025-07-24_11.07.45.jpg"  style="zoom: 25%;" /><img src="https://www.catop.top/usr/uploads/2025/07/sksec/IMG_3541.jpeg"  style="zoom: 33%;" /></p></li><li><p><strong>软件环境</strong></p><p>原靶场系统自带的虚拟化平台是深信服超融合 HCI，由于版本较老旧（2019年）且已过维保期无法升级，易用性较差，管理面板繁琐且功能局限。尤其是底层的 RHEL 内核版本为 Linux 3.x，存在很大的安全隐患。</p><blockquote><p>不过不得不说深信服的服务还是很不错的，即便是过保的设备，依旧有工程师帮我们解答疑惑，甚至是远程调试困扰了我们很久的 Kernel Panic 问题（这个问题在下面详细展开）。</p></blockquote></li><li><p><strong>故障排除</strong></p><p>运行过程中发现其中一个节点时常会 Kernel Panic，排查了内核日志、BMC日志均没有明确的报错，此时就考虑肯定是硬件故障，导致日志没有记录&#x2F;落盘存储。</p><blockquote><p>由于部分设备仍在线上运行，为避免暴露某些网络拓扑，图片中对内网 IP 也打了马赛克。</p></blockquote><img src="https://www.catop.top/usr/uploads/2025/07/sksec/iShot_2025-07-24_15.33.35.jpg"  style="zoom: 25%;" /><p><em>Kernel panic - not syncing: Timeout: Not all CPUs entered broadcast exception handler</em> 这个错误通常是<strong>多核 CPU 系统中某些核在处理关键中断（broadcast exception）时未能及时响应</strong> 导致内核 panic 的现象。</p><p>我们当时考虑的原因，要么是 CPU 本身有故障，要么是主板微码问题。但三台服务器配置和批次都是一模一样的，也相互替换过电源、内存，也调整过主板 ACPI、C-States 等参数，依旧无法解决。</p><p>困扰了一个月后，换了一块浪潮的 X99 主板，问题立即消失。因此基本可以判断，是主板上硬件故障导致的。</p></li><li><p><strong>拥抱开源之路</strong></p><p>深信服 HCI 在当今企业级应用中确实是作为 VMware 替代者的存在，在热迁移、存储 IO 优化、内存管理方面有一些自主研发的技术。加之当前 VMware 的采购价格因素，相信它会成为优秀的国产虚拟化支撑者。</p><p>但由于前述因素，导致我们急需更换更现代的虚拟化底层。由于旧的虚拟化平台是 Proxmox ，且其对 Ceph、SDN、HA 等分布式集群特性支持良好，就决定选它了。</p><p>2024 年 1 月左右，全新的 Proxmox VE 8 部署完毕，可喜可贺。然而 VM 迁移过程中也遇到 Agent、网络、磁盘等各种兼容性问题，也花了学弟很长时间解决。</p><img src="https://www.catop.top/usr/uploads/2025/07/sksec/iShot_2025-07-24_15.53.59.jpg"  style="zoom: 25%;" /></li></ul></li></ul><h3 id="实验室糟糕的供电及环境"><a href="#实验室糟糕的供电及环境" class="headerlink" title="实验室糟糕的供电及环境"></a>实验室糟糕的供电及环境</h3><p>由于实验室只有一路供电，且个人用电器较多，断电问题始终困扰着我们。与学弟的聊天记录：</p><img src="https://www.catop.top/usr/uploads/2025/07/sksec/iShot_2025-07-23_16.28.20.jpg"  style="zoom:25%;" /><p>正常来说硬件 RAID 阵列卡都配有一块叫做 BBU（Battery Backup Unit）的小电池，用来给写缓存（通常为内存型如DDR 4）供电。然而我们的超融合架构中存储使用的 Ceph 而不是 RAID，若 vm IO 策略为 Write-Back，则断电时未落盘的数据将丢失，软件层面容易导致 vm 数据丢失或 FS 损坏，硬件层面亦会增加物理 HDD 磁盘的故障率。在一年期间，就发现多个 VM 文件系统遭到损坏，好在使用 <code>fsck</code> 工具都成功修复了。</p><p>此外，镜像站也使用裸磁盘映射的方式托管在这上面，频繁的意外断电导致服务可用率低下。</p><h2 id="迁移"><a href="#迁移" class="headerlink" title="迁移"></a>迁移</h2><p>正式迁移机器时我已经毕业接近一年了，具体实施的事情均由学弟搞定，我只是参与了一些规划以及远程调试了一些配置。</p><h3 id="网络背景"><a href="#网络背景" class="headerlink" title="网络背景"></a>网络背景</h3><ul><li>实验室在校园网的网段：10.9.214.0&#x2F;24</li><li>实验室内部网段：172.18.1.0&#x2F;24</li><li>PVE 网段：<ul><li>业务：172.18.2.0&#x2F;24</li><li>管理：172.18.77.0&#x2F;24</li><li>存储：&lt;无需变动&gt;</li></ul></li><li>数据中心下发的网段：10.9.18.0&#x2F;24</li></ul><h3 id="规划与准备"><a href="#规划与准备" class="headerlink" title="规划与准备"></a>规划与准备</h3><p>迁移总体目标是：</p><ul><li>保障硬件和数据完好无损；</li><li>尽量让 VM 少改动网络配置：由于之前并没有建设内部 DNS，不同 VM 间服务互联采用纯 IP 地址，而数据中心与实验室不在也无法修改到同一 VLAN，因此难免有些连接至校园网的 VM 需要改动 IP 地址；</li><li>PVE 业务网段要与实验室内部加密互联；</li><li>完善 Grafana 观测、安全配置等。</li></ul><h3 id="硬件"><a href="#硬件" class="headerlink" title="硬件"></a>硬件</h3><p>学弟采用了安全可靠的方式搬运服务器，并对线缆打了标签。到现场后发现滑轨螺距不太适配，等待备件到货花费了几天时间，最终安全将硬件安装上架。<em>理线之前</em> 的图片如下。</p><img src="https://www.catop.top/usr/uploads/2025/07/sksec/image.jpg" alt="image" style="zoom: 50%;" /><h3 id="与实验室网络互联"><a href="#与实验室网络互联" class="headerlink" title="与实验室网络互联"></a>与实验室网络互联</h3><p>由于实验室与数据中心提供的网段没有直接路由，而是要跨学校的三层核心交换机，因此直接路由变得不太可行。若要达到实验室与 PVE 三层互通的效果，最直观的想法是两端网络设备建立 VPN 隧道，随后添加路由。</p><p>实验室现有一台思科三层交换机和一台支持 IPSEC 的防火墙，此外还闲置一台华为 AR 1220，亦支持 IPSEC。因此打算将 AR 1220 搬到数据中心，当作出口路由器，而集群中原有 S5720 则当作三层交换机。</p><img src="https://www.catop.top/usr/uploads/2025/07/sksec/iShot_2025-07-24_20.16.05.jpg"  style="zoom:33%;" /><p>IPSec 隧道只支持封装、加密单播报文；GRE 可封装、加密单播和组播报文，但不安全。一种经典的方案是结合两者优点，通过IPSec over GRE方式或GRE over IPSec方式实现设备间的互通。</p><blockquote><p> 参考：<a href="https://support.huawei.com/enterprise/zh/doc/EDOC1000079675/12d76f11">https://support.huawei.com/enterprise/zh/doc/EDOC1000079675/12d76f11</a></p></blockquote><p>我们选择 GRE over IPSec 的方式，因为 GRE 支持组播和广播，一方面是建立广播链路后，对于 SMB、AirPlay 等依赖广播进行设备发现的协议非常友好；另一方面是日后可扩展 OSPF，例如加一路 Wireguard 来提升两端连接性能。</p><p>GRE只负责封装和传输，它本身<strong>不提供任何加密或认证机制</strong>。</p><p>而 IPSec 是一个协议簇，目标是在IP层提供安全服务。<strong>其核心协议</strong>包括三个：</p><ul><li><p>AH (Authentication Header) - 认证头协议</p><p>AH 用于保证数据来源可靠和数据完整性，会在原始IP包头后面插入一个“AH头”，这个头包含了认证信息。它会对<strong>整个IP包（包括IP头和数据载荷）</strong>进行完整性校验。</p></li><li><p>ESP (Encapsulating Security Payload) - 封装安全载荷协议</p><p>ESP 用于保证数据机密性和防重放，它使用加密算法（如AES、3DES）对数据载荷进行加密。</p></li><li><p>IKE (Internet Key Exchange) - 互联网密钥交换协议</p><p>IKE 用于自动协商密钥和建立安全联盟（SA），在通信双方之间安全、自动地协商出加密参数。</p></li></ul><p>IPSec 有两种应用范式：传输模式和隧道模式。</p><blockquote><p>参考：<a href="https://support.huawei.com/enterprise/zh/doc/EDOC1100301617/c5d5a9b">https://support.huawei.com/enterprise/zh/doc/EDOC1100301617/c5d5a9b</a></p></blockquote><p>在传输模式下，AH头或ESP头被插入到IP头与传输层协议头之间，保护报文载荷。由于传输模式未添加额外的IP头，所以原始报文中的IP地址在加密后报文的IP头中可见。以TCP报文为例，原始报文经过传输模式封装后，报文格式如所示。</p><p><img src="https://www.catop.top/usr/uploads/2025/07/sksec/download.png" alt="download"></p><p>在隧道模式下，AH头或ESP头被插到原始IP头之前，另外生成一个新的报文头放到AH头或ESP头之前，保护IP头和报文载荷。以TCP报文为例，原始报文经隧道模式封装后的报文结构如图所示。</p><p><img src="https://www.catop.top/usr/uploads/2025/07/sksec/download-2.png" alt="download-2"></p><p>我们采用的当然是隧道模式，配置步骤大致为：</p><ul><li><p>定义组件</p><ul><li><p>定义 ACL  ➡️ rule permit gre source 10.9.18.1 destination 10.9.214.100</p></li><li><p>定义 IPSec Proposal ➡️ 封装模式: tunnel, 加密认证算法</p></li><li><p>定义 IKE Peer ➡️ 对端公网IP, 预共享密钥</p></li></ul></li><li><p>组合应用</p><ul><li><p>创建 PSec Policy ➡️ (关联以上ACL、Proposal、Peer三者)</p></li><li><p>   创建GRE隧道接口 ➡️ (配置源&#x2F;目地址和隧道私网IP)</p></li><li><p>绑定 ➡️ 在GRE隧道接口下应用IPSec策略</p></li></ul></li><li><p>流量转发</p><ul><li>配置路由  ➡️ 将内网数据包的目标指向Tunnel隧道口</li></ul></li></ul><p>配置完成后，查看 ike sa：</p><p><img src="https://www.catop.top/usr/uploads/2025/07/sksec/iShot_2025-07-24_20.17.36.jpg" alt="iShot_2025-07-24_20.17.36"></p><p>查看 ipsec proposal：</p><img src="https://www.catop.top/usr/uploads/2025/07/sksec/iShot_2025-07-24_20.18.05.jpg"  style="zoom: 50%;" /><p>隧道接口成功建立：</p><img src="https://www.catop.top/usr/uploads/2025/07/sksec/iShot_2025-07-24_20.18.29.jpg"  style="zoom:50%;" /><h3 id="观测及安全"><a href="#观测及安全" class="headerlink" title="观测及安全"></a>观测及安全</h3><p>Prometheus 系列的主要就是三大组件：Node Exporter 用来数据采集，Prometheus 定期主动去 Node Exporter 暴露的端口上拉取数据，Grafana 通过 PromQL 查询出数据，然后做可视化。</p><p>目前对 Proxmox VE、Ceph、磁盘 SMART 进行了采集。</p><p><img src="https://www.catop.top/usr/uploads/2025/07/sksec/iShot_2025-07-24_20.27.16.jpg" alt="iShot_2025-07-24_20.27.16"></p><p>此外，还对路由器 NAT 日志进行了采集，发送至 syslog，再由 logstash 接收，保存至 ElasticSearch。</p><p><img src="https://www.catop.top/usr/uploads/2025/07/sksec/iShot_2025-07-24_20.29.19.jpg" alt="iShot_2025-07-24_20.29.19"></p><p>这里分享一下我们华为 AR 系列路由器适用的 logstash 配置文件：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br></pre></td><td class="code"><pre><span class="line">input &#123;</span><br><span class="line">  udp &#123;</span><br><span class="line">    port =&gt; 11516</span><br><span class="line">    type =&gt; &quot;huawei_nat_logs&quot;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">filter &#123;</span><br><span class="line">  if [type] == &quot;huawei_nat_logs&quot; &#123;</span><br><span class="line">    </span><br><span class="line">    grok &#123;</span><br><span class="line">      match =&gt; &#123; &quot;message&quot; =&gt; &quot;&lt;%&#123;INT:syslog_pri&#125;&gt;%&#123;GREEDYDATA:log_message&#125;&quot; &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    grok &#123;</span><br><span class="line">      match =&gt; &#123; &quot;log_message&quot; =&gt; &quot;(?&lt;timestamp&gt;%&#123;YEAR&#125;-%&#123;MONTHNUM&#125;-%&#123;MONTHDAY&#125;\s+%&#123;TIME&#125;)\s+%&#123;HOSTNAME:hostname&#125;\s+%%.*?:%&#123;GREEDYDATA:kv_string&#125;&quot; &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    </span><br><span class="line">    kv &#123;</span><br><span class="line">      source =&gt; &quot;kv_string&quot;</span><br><span class="line">      field_split =&gt; &quot;,&quot;</span><br><span class="line">      value_split =&gt; &quot;=&quot;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    </span><br><span class="line">    mutate &#123;</span><br><span class="line">      convert =&gt; &#123;</span><br><span class="line">        &quot;SourcePort&quot; =&gt; &quot;integer&quot;</span><br><span class="line">        &quot;DestinationPort&quot; =&gt; &quot;integer&quot;</span><br><span class="line">        &quot;SourceNatPort&quot; =&gt; &quot;integer&quot;</span><br><span class="line">        &quot;DestinationNatPort&quot; =&gt; &quot;integer&quot;</span><br><span class="line">        &quot;BeginTime&quot; =&gt; &quot;integer&quot;</span><br><span class="line">        &quot;EndTime&quot; =&gt; &quot;integer&quot;</span><br><span class="line">        &quot;SendPkts&quot; =&gt; &quot;integer&quot;</span><br><span class="line">        &quot;SendBytes&quot; =&gt; &quot;integer&quot;</span><br><span class="line">        &quot;RcvPkts&quot; =&gt; &quot;integer&quot;</span><br><span class="line">        &quot;RcvBytes&quot; =&gt; &quot;integer&quot;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    </span><br><span class="line">    date &#123;</span><br><span class="line">      match =&gt; [ &quot;timestamp&quot;, &quot;yyyy-M-d HH:mm:ss&quot; ] </span><br><span class="line">      target =&gt; &quot;@timestamp&quot;</span><br><span class="line">      timezone =&gt; &quot;UTC&quot;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    </span><br><span class="line">    date &#123;</span><br><span class="line">      match =&gt; [ &quot;BeginTime&quot;, &quot;UNIX&quot; ]</span><br><span class="line">      target =&gt; &quot;nat_session_begin_time&quot; </span><br><span class="line">    &#125;</span><br><span class="line">    date &#123;</span><br><span class="line">      match =&gt; [ &quot;EndTime&quot;, &quot;UNIX&quot; ]</span><br><span class="line">      target =&gt; &quot;nat_session_end_time&quot;   </span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    </span><br><span class="line">    ruby &#123;</span><br><span class="line">      code =&gt; &quot;</span><br><span class="line">        if event.get(&#x27;EndTime&#x27;) &amp;&amp; event.get(&#x27;BeginTime&#x27;)</span><br><span class="line">          duration = event.get(&#x27;EndTime&#x27;) - event.get(&#x27;BeginTime&#x27;)</span><br><span class="line">          event.set(&#x27;nat_session_duration_seconds&#x27;, duration)</span><br><span class="line">        end</span><br><span class="line">      &quot;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    mutate &#123;</span><br><span class="line">      remove_field =&gt; [ &quot;message&quot;, &quot;log_message&quot;, &quot;kv_string&quot;, &quot;timestamp&quot;, &quot;syslog_pri&quot;, &quot;BeginTime&quot;, &quot;EndTime&quot; ]</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">output &#123;</span><br><span class="line">  </span><br><span class="line">  elasticsearch &#123;</span><br><span class="line">    hosts =&gt; [&quot;https://127.0.0.1:9200&quot;]</span><br><span class="line">    cacert =&gt; &#x27;/home/elastic/elasticsearch/elasticsearch-8.11.4/config/certs/http_ca.crt&#x27;</span><br><span class="line">    index =&gt; &quot;huawei-nat-logs-%&#123;+YYYY.MM.dd&#125;&quot;</span><br><span class="line">    user =&gt; &quot;elastic&quot;</span><br><span class="line">    password =&gt; &quot;you_strong_password&quot;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  </span><br><span class="line">  stdout &#123;</span><br><span class="line">    codec =&gt; rubydebug</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h2><p>由于时间缘故，暂时只能分享这么多了。其实还有很多安全和运维方面的设计和实施心得，虽然自己以后估计也不专职从事这块的工作，但几个人一起折腾网络设备还是挺有意思的。我也非常佩服学弟的研究和专注能力，他自己从一台完全没有手册的杂牌防火墙摸索出了 IPSEC 配置的命令。也很佩服另一位学弟，他 JAVA Web 很强，之前反序列化的那些链子都是他教的我。他俩目前都在考研，希望他俩都能有好的归宿。也非常感激本科实验室在过去的几年里对我的培养，同时感谢网信部门提供的一些实践机会，在那里我们接触到大量的企业级网络和安全设备。</p><p>希望我能在新的学习阶段，保持一颗求知和探索的心，多一分专注，少一些摸鱼，尽快让自己的研究工作走向正轨，用成果来回馈曾经支持和关注过我的人。</p>]]></content>
    
    
    <summary type="html">2025年夏天，在两位学弟的技术和努力、以及学校网信部门老师的有力支持下，本科实验室的 Proxmox 虚拟化集群成功迁移至数据中心机房。在此期间我远程提供了一些力所能及的帮助，也学到了一些经验。在此记录下来，供自己和有类似需求的同学参考。</summary>
    
    
    
    <category term="杂项" scheme="https://www.catop.top/categories/%E6%9D%82%E9%A1%B9/"/>
    
    
    <category term="物理服务器" scheme="https://www.catop.top/tags/%E7%89%A9%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8/"/>
    
    <category term="Proxmox" scheme="https://www.catop.top/tags/Proxmox/"/>
    
    <category term="服务器集群迁移" scheme="https://www.catop.top/tags/%E6%9C%8D%E5%8A%A1%E5%99%A8%E9%9B%86%E7%BE%A4%E8%BF%81%E7%A7%BB/"/>
    
    <category term="服务器与网络配置" scheme="https://www.catop.top/tags/%E6%9C%8D%E5%8A%A1%E5%99%A8%E4%B8%8E%E7%BD%91%E7%BB%9C%E9%85%8D%E7%BD%AE/"/>
    
  </entry>
  
  <entry>
    <title>UCAS《网络攻防基础》作业：分布式网络抓包和安全分析系统</title>
    <link href="https://www.catop.top/2025/07/02/toy-net-analyser/"/>
    <id>https://www.catop.top/2025/07/02/toy-net-analyser/</id>
    <published>2025-07-02T02:20:01.000Z</published>
    <updated>2025-07-04T08:50:20.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>本文是我在国科大《网络攻防基础》课程上完成的大作业，实现了一个玩具型的分布式网络抓包和安全分析系统，具备基础的流量分析可视化、以及一些安全洞察和检测能力。</p></blockquote><p>灵感来源于本科时帮学校排查一些安全事件，使用过安恒的 Ailpha 系统。感觉它十分不错，够性能、够智能、够人性化。</p><p>因此本次作业也想简单实现一个，<strong>总体原则就是：a) 采用消息队列以实现分布式；b) 使用 ElasticSearch 作为后端存储，这样配合 Kibana 能较好地实现可视化；c) 使用一些较新的协议解析字段，例如 SNI 实现域名探测；d) 具备插件式可扩展的安全检测模块。</strong></p><p><strong>Github 仓库地址：</strong> <a href="https://github.com/BaiHLiu/ToyNetAnalyzer">https://github.com/BaiHLiu/ToyNetAnalyzer</a></p><hr><p><img src="https://www.catop.top/usr/uploads/2025/06/net-analyser/fig1.png" alt="fig1"></p><p>具体使用来看，在 OpenWrt 路由器上尝试部署了一下，除了内存占用比较大，运行还算稳定。<em>代码 AI 含量较大，请仔细甄别。</em></p><h2 id="主要功能"><a href="#主要功能" class="headerlink" title="主要功能"></a>主要功能</h2><ul><li><strong>分布式架构</strong>：采用多个采集端（agent）进行数据包收集，推给中央服务器进行分析。</li><li><strong>协议分析</strong>：支持对多种网络协议的分析，如 TCP、UDP、ICMP、HTTP、HTTPS 和 DNS 等。可提取源 IP、目的 IP、端口、数据包序列以及应用层数据等重要信息。</li><li><strong>安全检测</strong>：插件化的检测模块支持，内置示例的 SQL 注入检测模块。</li><li><strong>SNI 检测</strong>：支持对 HTTPS 流量基于 TLS 握手信息的 SNI 识别，从而识别用户访问的域名。</li><li><strong>数据存储与可视化</strong>：数据结构化后存储在 ElasticSearch 中，便于检索。利用 Kibana 创建了较为直观的仪表盘，用于监控网络流量趋势和统计信息。</li></ul><p><img src="https://www.catop.top/usr/uploads/2025/06/net-analyser/fig2.png" alt="fig2"></p><h2 id="技术实现"><a href="#技术实现" class="headerlink" title="技术实现"></a>技术实现</h2><ul><li><strong>采集端实现</strong>：采集端使用 Python 的 scapy 库构建，依赖 libpcap 或 npcap 进行数据包捕获。将捕获的数据包转换为 JSON 格式后发送到 RabbitMQ 消息队列。</li><li><strong>服务器分析</strong>：服务器端包含多个线程（可设置），作为  RabbitMQ 消费者接收的数据包。通过流表跟踪 TCP 会话以确保数据完整性。分析过程涉及在不同层（L2、L3、L4）解析数据包并提取相关信息，最后存储至 ElasticSearch。</li><li><strong>ElasticSearch 映射</strong>：在 ElasticSearch 中定义了多种映射，以高效存储数据包数据，包括 IP 信息、传输层详细信息、应用层数据和安全警报等字段。</li></ul><h2 id="安装与部署"><a href="#安装与部署" class="headerlink" title="安装与部署"></a>安装与部署</h2><ol><li><strong>Elasticsearch 和 Kibana 搭建</strong>：建议使用 docker 启动 Elasticsearch 和 Kibana。注意修改默认密码，随后修改 <code>es_conn.py</code> 中相关配置。</li><li><strong>RabbitMQ 搭建</strong>：执行 <code>docker run -d –name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:management</code> 命令启动 RabbitMQ。</li><li><strong>采集端安装</strong>：在 Windows 系统上需先安装 npcap。在类 Unix 系统中，安装 scapy 时通常会自动处理相关依赖。使用 <code>pip install scapy pika</code> 安装所需库，并配置 RabbitMQ 连接参数和要嗅探的网卡接口。</li><li><strong>服务器安装</strong>：安装 <code>requirements.txt</code> 中的依赖项。修改 <code>es_conn.py</code> 中的 ElasticSearch 连接参数，并在 <code>main.py</code> 中配置如 TCP 会话超时等其他设置。</li></ol><h2 id="局限性"><a href="#局限性" class="headerlink" title="局限性"></a>局限性</h2><ul><li><strong>性能瓶颈</strong>：在高流量场景下存在性能问题，有待进一步优化。</li><li><strong>协议支持</strong>：协议解析可进一步改进，以支持更多协议并使其结构更合理。</li><li><strong>可视化</strong>：目前的可视化受限于 Kibana 功能，可进行增强以实现更高级的分析。</li><li><strong>安全检测</strong>：添加更多的安全检测扩展。</li></ul><h2 id="完整实验报告PDF"><a href="#完整实验报告PDF" class="headerlink" title="完整实验报告PDF"></a>完整实验报告PDF</h2><blockquote><p>加载可能需要较长时间。</p></blockquote><div class="row">    <embed src="/pdf/net-analyser-report.pdf" width="100%" height="550" type="application/pdf"></div>]]></content>
    
    
    <summary type="html">本文是我在国科大《网络攻防基础》课程上完成的大作业，实现了一个玩具型的分布式网络抓包和安全分析系统，具备基础的流量分析可视化、以及一些安全洞察和检测能力。</summary>
    
    
    
    <category term="课程" scheme="https://www.catop.top/categories/%E8%AF%BE%E7%A8%8B/"/>
    
    
    <category term="网络安全" scheme="https://www.catop.top/tags/%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A8/"/>
    
    <category term="课程实验" scheme="https://www.catop.top/tags/%E8%AF%BE%E7%A8%8B%E5%AE%9E%E9%AA%8C/"/>
    
    <category term="流量分析" scheme="https://www.catop.top/tags/%E6%B5%81%E9%87%8F%E5%88%86%E6%9E%90/"/>
    
  </entry>
  
  <entry>
    <title>论文阅读 - Slot: 基于图强化学习的溯源图APT检测</title>
    <link href="https://www.catop.top/2025/06/23/slot-reading-report/"/>
    <id>https://www.catop.top/2025/06/23/slot-reading-report/</id>
    <published>2025-06-23T09:50:00.000Z</published>
    <updated>2025-06-26T10:31:52.000Z</updated>
    
    <content type="html"><![CDATA[<hr><p>主要技术: 注意力机制挖掘潜在行为；图强化学习<br>发表: arxiv24<br>图学习类别: 图学习+嵌入向量库<br>方法分类: 图学习<br>概要: 使用注意力机制挖掘潜在重点行为，建立边以增强图表示；首次将强化学习引入溯源图分析，采用强化学习进行邻居选择，在抵御对抗方面效果明显。<br>特色: 注意力机制；强化学习</p><hr><h2 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h2><p>使用注意力机制挖掘潜在重点行为，建立边以增强图表示；首次将强化学习引入溯源图分析，采用强化学习进行邻居选择，在抵御对抗方面效果明显。</p><p>文章提到的主要贡献为：</p><ol><li>提出了一种先进的图挖掘技术，能够高效地发现图中多层级的隐藏关系，例如因果关系、上下文关系和间接关系。</li><li>首次将强化学习引入溯源图分析。通过嵌入语义和拓扑特征，并利用强化学习的自适应动态，Slot能够有效应对高度对抗的环境。</li><li>实现了自动构建攻击链、攻击路径识别。在真实世界的数据集进行了全面评估，结果证明了Slot在检测APT、抵御对抗攻击以及支持制定有效防御策略方面的能力。</li></ol><div style="text-align: center;"><img src="https://www.catop.top/usr/uploads/2025/06/slot/image.png" alt="image.png" style="zoom:50%;" /></div><h2 id="方法"><a href="#方法" class="headerlink" title="方法"></a>方法</h2><h3 id="潜在行为挖掘"><a href="#潜在行为挖掘" class="headerlink" title="潜在行为挖掘"></a>潜在行为挖掘</h3><ol><li>潜在行为挖掘对后续图学习的帮助<ul><li>潜在行为挖掘旨在发现由多个“原子操作”组成的、有意义的“高级行为路径” 。例如，一个 <code>connect</code> -&gt; <code>recv</code> -&gt; <code>write</code> -&gt; <code>exec</code> 的序列可能代表了一个完整的“下载并执行”的恶意战术。</li><li>加速信息传播，解决“远距离依赖”问题：复杂的APT攻击中，攻击链条可能很长。标准的GNN信息传递机制中，一个节点的信息需要经过多层（多个hop）才能传播到远处的节点，这可能导致信息在传播过程中衰减或丢失。</li><li>通过挖掘和建立一条直接的“潜在”边来连接这两个远距离的节点，信息可以在GNN的一层中直接从攻击起点传播到终点 。</li></ul></li><li>使用注意力机制的动机、实现细节<ul><li>动机：从海量可能的行为路径中，自动地、有重点地筛选出那些最关键、最能代表特定行为模式（无论是恶意的还是良性的）的路径 。</li></ul></li><li>潜在行为挖掘之后图谱结构变化的直观理解<ul><li>节点不变，边增加了，图变稠密了</li></ul></li></ol><h3 id="基于强化学习的图嵌入"><a href="#基于强化学习的图嵌入" class="headerlink" title="基于强化学习的图嵌入"></a>基于强化学习的图嵌入</h3><p>SLOT 采用的也是「图学习+嵌入向量库」的方法范式，回顾一下目前看过的这种范式的文章还有：Magic。</p><p>工作流程为：</p><ul><li>在语义编码阶段，节点的语义特征和拓扑特征被嵌入到向量中。</li><li>基于节点特征向量的相似性计算，我们设计了一种自适应的Bandit邻居选择器，使用强化学习。</li><li>最后，我们通过整合多种关系（包括系统调用和潜在关系）来聚合特征，生成更新后的节点特征向量。</li></ul><h3 id="特征语义编码"><a href="#特征语义编码" class="headerlink" title="特征语义编码"></a>特征语义编码</h3><p>SLOT 也是结合节点属性和图结构特征来编码节点：</p><ul><li><p><strong>节点属性特征</strong>：节点自身的描述信息（下称“字面描述信息”），比如进程的名称、命令行参数、文件路径。SLOT 使用 Word2Vec 生成原始嵌入（64维），并通过一个简单的多层感知机（MLP）来处理这些特征，得到一个基于属性的向量表示 $h_X$。</p></li><li><p><strong>图拓扑特征</strong>：即节点在图中的连接关系和结构位置 。同样使用一个MLP来处理描述全图连接关系的邻接矩阵A，得到一个基于拓扑的向量表示 $h_A$</p></li><li><p>总结一下的话，对节点属性特征的编码主要就是有两种方式：a) 使用 word2vec、FastText、Bert 等语言模型对字面描述信息进行编码；b）使用节点的类型编码，常见的是 one-hot 编码；c）使用统计信息，例如节点的度、PageRank 值、聚类系数等。</p><p>  而这篇文章告诉我们的是，可以首先通过Word2Vec等方式获得节点的初始特征，再套一层特征变换（MLP）。结合文中方法而言，将节点属性和拓扑特征这两个差异性大的特征矩阵，都各自经过一层 MLP，模型可以学习到一种较优的属性融合方式。</p><p>  这种“<strong>分别变换，再行融合</strong>”，实现了<strong>异构特征的自适应融合</strong>。是对我做机器学习任务的一个启发。</p>  <div style="text-align: center;">  <img src="https://www.catop.top/usr/uploads/2025/06/slot/image%201.png" alt="image%201.png" style="zoom:50%;" />  </div></li></ul><h3 id="自适应-Bandit-邻居选择"><a href="#自适应-Bandit-邻居选择" class="headerlink" title="自适应 Bandit 邻居选择"></a>自适应 Bandit 邻居选择</h3><p>「Bandit」这词用的挺妙的，意为“土匪”。查这个词的时候还看到一个 Github 上的仓库<a href="https://github.com/PyCQA/bandit">https://github.com/PyCQA/bandit</a>，Bandit is a tool designed to find common security issues in Python code.</p><p>在强化学习中，有一个“多臂老虎机”（Multi-armed Bandit）模型，由于我暂时没涉猎强化学习，建议参考：<a href="https://hrl.boyuai.com/chapter/1/%E5%A4%9A%E8%87%82%E8%80%81%E8%99%8E%E6%9C%BA/">https://hrl.boyuai.com/chapter/1/多臂老虎机/</a></p><p>简单来说，「多臂老虎机」目标是在操作 T 次拉杆后获得尽可能高的累积奖励。由于奖励的概率分布是未知的，因此我们需要在“探索拉杆的获奖概率”和“根据经验选择获奖最多的拉杆”中进行权衡。“采用怎样的操作策略才能使获得的累积奖励最高”便是多臂老虎机问<br>题。（图片来源：boyuai.com)</p><div style="text-align: center;"><img src="https://www.catop.top/usr/uploads/2025/06/slot/image%202.png" alt="image.png" style="zoom:70%;" /></div><p>回到本文的任务中，在从中心节点 v 及其邻居收集信息之前，采用 Top-p 采样，根据不同的关系过滤相似的邻居。第 l 层关系 r 的过滤阈值 $p_r^l$ 定义在 [0, 1] 范围内，表示从所有邻居中选择的比例。利用强化学习寻找最优阈值。</p><p>使用强化学习的目的是<strong>实现最佳的邻居筛选</strong>，即为每一种关系类型（Read、Write、Connect等）学习到一个<strong>最佳的邻居筛选阈值，以期过滤掉伪装节点。</strong>经过筛选后的图再进行后续的邻居消息聚合步骤。</p><p>根据强化学习的模型框架，学习过程如下：</p><ul><li><strong>代理</strong>：任务是为每一种关系类型（如“读”、“写”、“连接”等）学习到一个最佳的<strong>邻居筛选阈值</strong> pr(l) 。</li><li><strong>状态</strong>：用“所有训练节点与其邻居的<strong>平均相似度距离</strong>”来定义状态 。距离越小，说明筛选出的邻居质量越高，状态就越好。</li><li><strong>动作</strong>：代理做的动作就是调整筛选阈值。</li><li><strong>奖励</strong>：这是给代理的反馈。如果一个动作（调低阈值）使得筛选后的平均邻居距离<strong>变小了</strong>（即邻居更相似了），就给代理一个<strong>正奖励 (+1)<strong>。反之，如果距离变大了，就给一个</strong>负奖励 (-1)</strong> 。如 Figure5 所示，新旧状态下平均相似度距离的变化量为 <strong>$\Delta(S^{e-1},S^{(e)})$。</strong>  <div style="text-align: center;">  <img src="https://www.catop.top/usr/uploads/2025/06/slot/image%203.png" alt="image.png" style="zoom:50%;" />  </div></li></ul><h3 id="邻居信息聚合"><a href="#邻居信息聚合" class="headerlink" title="邻居信息聚合"></a>邻居信息聚合</h3><p>文中的式10、式11表示了邻居信息聚合的步骤，使用两个步骤的聚合来结合上RL所得的筛选阈值：</p><div style="text-align: center;"><img src="https://www.catop.top/usr/uploads/2025/06/slot/image%204.png" alt="image.png" style="zoom:50%;" /></div><ol><li>对于某节点v的关系r，模型会把所有通过了筛选的邻居节点的信息聚合到中心节点上，生成一个特定于该关系的中间向量 $h^{(l)}_{v,r}$</li><li>把节点v的所有不同关系向量聚合起来，使用第二步中强化学习学到的**最优筛选阈值 $p_r^{(l)}$**，得到当前节点最终向量 $h_v^{(l)}$</li></ol><h3 id="威胁检测过程"><a href="#威胁检测过程" class="headerlink" title="威胁检测过程"></a>威胁检测过程</h3><p>使用「学习检测」和「异常检测」并行进行检测，分别使用 MLP 和孤立森林。</p><ol><li><p>基于 MLP 的学习检测</p><blockquote><p>为了与图神经网络联合训练相似性度量，一种启发式方法是<strong>在GCN聚合层之前将其作为新层附加。</strong></p></blockquote><p> 这边损失函数比较复杂， 我也没有搞特别明白，<strong>暂且请教了下 Gemini</strong>。它的训练过程优化了一个<strong>组合损失函数：</strong></p><p> $$<br> \mathcal L&#x3D; \mathcal L_{GNN}+\lambda_1 \mathcal L_{Simi}^{(1)} + \lambda_2 ||\Theta||_2<br> $$</p><ul><li>$\mathcal L_{GNN}$：主要的GNN损失。它衡量的是最终的节点嵌入向量 $Z_v$ 在经过MLP分类后，其预测结果与真实标签（良性&#x2F;恶意）的差距有多大 。这是最核心的监督信号。</li><li>$\mathcal L_{Simi}^{(1)}$：一个辅助的相似度损失。它要求模型在第一层计算出的、用于感知相似度的特征向量，也应该能初步地区分良性与恶意 。这样做可以倒逼模型从一开始就学习更有区分度的特征表示。</li><li>$||\Theta||_2$：一个L2正则化项，用来防止模型过拟合，增强其泛化能力 。</li></ul></li><li><p>基于孤立森林的异常检测</p><p> <strong>孤立森林 (Isolation Forest, iForest)</strong> 是一种无监督算法，擅长快速从数据中找到“离群点“。</p><p> iForest 通过<strong>随机构建一系列决策树来尝试“孤立”每一个数据点</strong>，那些异常的点因为“与众不同”，通常只需要很少几次分割就能被完全孤立出来，因此它们在树中的<strong>平均路径长度</strong>就很短 。</p><p> 根据这个平均路径长度，算法会为每个节点计算一个 [0,1] 之间的异常分数 (Anomaly Score) 。</p></li><li><p>最终决策</p><p> 在并行检测之后，Slot 将两个分类器的结果结合起来做出最终决策。如果监督学习模型对某行为（无论是良性还是恶意）做出了高置信度的预测，则采用该预测。另一方面，如果异常检测模型表明该行为可能存在异常，则将其标记为异常。</p></li></ol><h2 id="效果评估"><a href="#效果评估" class="headerlink" title="效果评估"></a>效果评估</h2><h3 id="数据集"><a href="#数据集" class="headerlink" title="数据集"></a>数据集</h3><p>选用图级别的 StreamSpot 和 Unicorn，以及节点级别的 DARPA E3（Trace、Cadets、Theia）。节点级别对比的工作有 Threatrace、Log2vec、FLASH、MAGIC，边级别对比的工作为 ShadeWatcher。</p><h3 id="结果分析"><a href="#结果分析" class="headerlink" title="结果分析"></a>结果分析</h3><ul><li>RQ1：与当前最先进的方法相比，Slot在检测APT方面有多有效？</li><li>RQ2：使用Slot会产生多少系统开销？</li><li>RQ3：Slot的各个组件在实现其预期功能方面有多有效？</li><li>RQ4：不同的超参数如何影响Slot的检测能力？</li><li>RQ5：Slot对对抗性攻击的鲁棒性如何？</li><li>RQ6：Slot在辅助人工验证警报方面有多有效？</li></ul><p><strong>RQ1: 有效性分析</strong></p><div style="text-align: center;"><img src="https://www.catop.top/usr/uploads/2025/06/slot/image%205.png" alt="image.png" style="zoom: 50%;" /></div><p><strong>RQ2: 开销分析</strong></p><p>与基于掩码图学习的 Magic （<a href="https://www.catop.top/2025/06/08/MAGIC-reading-report/">https://www.catop.top/2025/06/08/MAGIC-reading-report/</a>）做了对比，也提到 Magic 的开销瓶颈其实在于 KNN 距离的计算。</p><div style="text-align: center;"><img src="https://www.catop.top/usr/uploads/2025/06/slot/image%206.png" alt="image.png" style="zoom:50%;" /></div><p>这张图里面纵轴的 <strong>CDF</strong> 指的是<strong>累积分布函数</strong>（Cumulative Distribution Function），横轴表示完成一次检测所需的耗时，纵轴代表横轴对应的时间点之前，已经完成的检测任务所占的<strong>累积比例。</strong></p><p><strong>RQ3: 组件消融实验</strong></p><p>我比较关心基于强化学习的邻居选择的作用，根据中间的图来看带有 awareness 的效果显著要高。</p><div style="text-align: center;"><img src="https://www.catop.top/usr/uploads/2025/06/slot/image%207.png" alt="image.png" style="zoom:50%;" /></div><p>我还比较关注基于注意力的潜在行为挖掘的作用，根据右图来看引入隐关系信息对效果也有一些提升。</p><p><strong>RQ4: 超参数选择</strong></p><p>模型总体比较复杂，可调节的超参数多，这边对一些超参数进行了测试。</p><div style="text-align: center;"><img src="https://www.catop.top/usr/uploads/2025/06/slot/image%208.png" alt="image.png" style="zoom: 50%;" /></div><p><strong>RQ5: 对抗攻击的鲁棒性</strong></p><p>文章采用了两篇文献所提到的攻击方法，主要涉及在攻击图中插入良性结构：</p><ul><li>Akul Goyal, Xueyuan Han, Gang Wang, and Adam Bates. 2023. Sometimes, you aren’t what you do: Mimicry attacks against provenance graph host intrusion detection systems. In 30th Network and Distributed System Security Symposium.</li><li>Mati Ur Rehman, Hadi Ahmadi, and Wajih Ul Hassan. 2024. FLASH: A Comprehensive Approach to Intrusion Detection via Provenance Graph Representation Learning. In 2024 IEEE Symposium on Security and Privacy (SP). IEEE Computer Society, 139–139.</li></ul><p>对抗攻击方面与 Flash 这一个工作进行了对比：</p><div style="text-align: center;"><img src="https://www.catop.top/usr/uploads/2025/06/slot/image%209.png" alt="image.png" style="zoom:50%;" /></div><h2 id="总结与思考"><a href="#总结与思考" class="headerlink" title="总结与思考"></a>总结与思考</h2><p>这篇文章尽管还是 Arxiv 2410（博客写作时间为25年6月底），但感觉值得我学习的点有许多，包括但不限于：</p><ol><li><strong>基于注意力的图增强方式</strong>：GNN 是逐跳进行邻居信息传播的，在此过程中远距离邻居间的关系被“淡化”。借助注意力机制来自动化筛选出需要重点关注的路径，并添加虚拟的边，那么就有助于节点的表示。借助这个思路，感觉或许也可以用一些攻击语义进行图增强，例如 ATT&amp;CK 等。</li><li><strong>特征语义编码</strong>：对字面特征通过 Word2vec 提取嵌入，对拓扑特征使用邻接矩阵进行表示，最后两者分别过一层 MLP 进行聚合，看起来是一种融合不同尺度&#x2F;语义特征的有效方法。</li><li><strong>将强化学习引入溯源图</strong>：据称为首个这样的工作。强化学习在图中主要起到邻居选择的作用，像是给邻居节点加入了一层“门控”，若开&#x2F;关门后邻居节点更“相似”了则给予奖励。但由于个人对强化学习不甚了解，不太能理解这样设计奖励的动机是什么，有更深入理解的伙伴可以交流。</li><li><strong>并行使用使用 MLP 和孤立森林进行学习检测、异常检测</strong>。感觉从以往读过的文章来看，这类工作并不是很多，这篇文章告诉我们这是可行的。</li></ol>]]></content>
    
    
    <summary type="html">使用注意力机制挖掘潜在重点行为，建立边以增强图表示；首次将强化学习引入溯源图分析，采用强化学习进行邻居选择，在抵御对抗方面效果明显。</summary>
    
    
    
    <category term="论文" scheme="https://www.catop.top/categories/%E8%AE%BA%E6%96%87/"/>
    
    
    <category term="深度学习" scheme="https://www.catop.top/tags/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/"/>
    
    <category term="溯源图" scheme="https://www.catop.top/tags/%E6%BA%AF%E6%BA%90%E5%9B%BE/"/>
    
  </entry>
  
  <entry>
    <title>论文阅读 - MAGIC: 通过掩码图表示学习进行APT检测</title>
    <link href="https://www.catop.top/2025/06/08/MAGIC-reading-report/"/>
    <id>https://www.catop.top/2025/06/08/MAGIC-reading-report/</id>
    <published>2025-06-08T14:20:01.000Z</published>
    <updated>2025-06-26T10:22:40.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>这篇文章光着急复现了，文章并未通读，以下主要是结合代码来对方法细节进行自己的记录，整理的挺乱的，但迫于这周给自己定的计划有更新这篇阅读博客，就先更了，回头再改改。</p></blockquote><p>MAGIC 是「图学习+嵌入向量范式」的节点级别溯源图 HIDS，设计图注意力网络（GAT）和掩码图学习，通过对节点类别和边存在性的重构，来引导模型从良性数据上学习一个编码器。随后采样良性节点进行编码，并计算 KNN 平均距离作为良性基线。检测阶段对亦节点进行编码嵌入，KNN 寻找良性节点邻居并计算平均距离，与良性基线共同计算得到异常分数。文章发表在 USENIX 24’，作者来自复旦大学、中山大学等。</p><blockquote><p>💡图学习+嵌入向量范式：感觉溯源图 HIDS 的论文方法大体可以分为三种范式：图学习+分类模型、图学习+重建损失异常、图学习+嵌入向量库。具体例子可见我的另一博客，传送：<a href="https://www.catop.top/2025/05/13/kaiors-read-and-reproduce/#图学习任务类型">图学习任务类型</a></p></blockquote><h2 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h2><p>以往的溯源图 APT 攻击检测存在一些典型不足：1）需要包含攻击数据和 APT 的先验知识；2）无法提取蕴含在溯源图中的丰富上下文信息；3）计算成本和内存开销大。</p><p>本文提供了一种灵活的自监督溯源图 APT 检测方法，能够在实体级别和批量日志级别两个粒度上实现检测。MAGIC 利用掩码图学习对良性系统实体和行为进行建模，实现图谱的深度特征提取和结构抽象。在检测过程，利用离群值挖掘系统异常行为。此外，MAGIC 专门设计了模型自适应机制以处理概念漂移问题。对 StreamSpot、Unicorn Wget、DARPA E3 三个数据集上进行了评估，表明其在所有场景中都取得有前景的结果，并在性能开销方面比 SOTA 方法有巨大优势。</p><blockquote><p>感觉这篇文章背景设定广泛，是一篇通用性质强的文章。读完并且复现完了觉得确实不错，为溯源图的学习和表征提供了一种通用的编码方式。</p></blockquote><h3 id="读前思考"><a href="#读前思考" class="headerlink" title="读前思考"></a>读前思考</h3><ol><li><p>MAGIC 是如何处理概念漂移问题的？</p></li><li><p>与基于重建误差的方法相比，这种自编码器的方式在训练的监督信号上有何特点？</p></li><li><p>是否能通过这种编码器的方式，实现全图嵌入？</p></li></ol><h2 id="方法"><a href="#方法" class="headerlink" title="方法"></a>方法</h2><p>MAGIC的核心在于利用掩码图表示学习来建模良性系统实体和行为，从而在溯源图上高效地提取深层特征和抽象结构。</p><p>MAGIC 设计的编码器是对节点进行嵌入表征的，那对于整图来说就可以采用池化或加权平均等方式获得整图嵌入。因此文章对批次（图）级别（StreamSpot、Unicorn Wget）和节点级数据集（DARPA E3）均有所体现。</p><h3 id="图构建和特征工程"><a href="#图构建和特征工程" class="headerlink" title="图构建和特征工程"></a>图构建和特征工程</h3><ol><li><p><strong>节点特征选择</strong></p><p> 趁这个机会正好熟悉一下常见几种数据集的特征。</p><p> <strong>有类型日志：one-hot编码类型</strong></p><p> “对于提供了明确类型的日志，MAGIC 将节点和边的类型进行编号，作为标签。”</p><ul><li><p>DARPA E3</p><p>  采用的节点类型有：</p>  <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[&#x27;SUBJECT_PROCESS&#x27;, &#x27;FILE_OBJECT_FILE&#x27;, &#x27;FILE_OBJECT_UNIX_SOCKET&#x27;, &#x27;UnnamedPipeObject&#x27;, &#x27;NetFlowObject&#x27;, &#x27;FILE_OBJECT_DIR&#x27;]</span><br></pre></td></tr></table></figure></li><li><p>Wget</p><p>  采用的节点类型有：</p>  <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[&#x27;file&#x27;, &#x27;process_memory&#x27;, &#x27;task&#x27;, &#x27;mmaped_file&#x27;, &#x27;path&#x27;, &#x27;socket&#x27;, &#x27;address&#x27;, &#x27;link&#x27;]</span><br></pre></td></tr></table></figure></li><li><p>StreamSpot</p><p>  采用的节点类型有：</p>  <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[&#x27;process&#x27;, &#x27;thread&#x27;, &#x27;file&#x27;, &#x27;MAP_ANONYMOUS&#x27;, &#x27;NA&#x27;, &#x27;stdin&#x27;, &#x27;stdout&#x27;, &#x27;stderr&#x27;]</span><br></pre></td></tr></table></figure></li></ul><p> <strong>无类型日志：哈希处理</strong></p><p> “若日志未提供类别标签，则使用 <code>xxhsah</code> 对源节点 UUID 和目标节点 UUID 进行编码。”</p><blockquote><p>然而根据代码来看，对 StreamSpot、Unicorn Wget、DARPA E3 都采用了节点类型编码，xxhash 只是对 wget 数据集的 src uuid 和 dst uuid 进行了编码，实际构建 DGL 图时并未采用。</p></blockquote></li><li><p><strong>数据清洗</strong></p><p> 主要是对边进行去重。去重后<strong>边的初始嵌入为原始几条边的均值</strong>。这一步骤在DARPA E3 Trace数据集上平均减少了79.60%的边。</p><blockquote><p>对于这种 one-hot likely 的编码方式，用向量均值表示合理性如何？</p></blockquote></li></ol><h3 id="特征掩码-Feature-Masking"><a href="#特征掩码-Feature-Masking" class="headerlink" title="特征掩码 (Feature Masking)"></a>特征掩码 (Feature Masking)</h3><p>在训练前，随机选择一定比例的节点进行掩码（默认为0.5），用特殊的掩码标记替换其初始嵌入，以便后续重构。边不进行掩码。</p><h3 id="图编码模块-Graph-Encoder"><a href="#图编码模块-Graph-Encoder" class="headerlink" title="图编码模块 (Graph Encoder)"></a>图编码模块 (Graph Encoder)</h3><p>包含多层堆叠的图注意力网络（GAT）。GAT层通过聚合节点自身及其邻居的特征（初始嵌入）来生成输出节点嵌入，并使用注意力机制衡量邻居的重要性。最终的节点嵌入由原始节点嵌入和所有GAT层的输出拼接而成。</p><blockquote><p>🤔 对于一个中心节点，其3跳邻居的信息 只包含这个邻居本身，还是也包括这个邻居的邻居？</p></blockquote><p>答：不包括该3跳邻居节点的邻居。一个3层GNN的“感受野”是3跳。它无法直接捕获到4跳邻居的信息。</p><h3 id="图解码模块-Graph-Decoder"><a href="#图解码模块-Graph-Decoder" class="headerlink" title="图解码模块 (Graph Decoder)"></a>图解码模块 (Graph Decoder)</h3><p>图解码器的主要目的是为图表示模块的训练提供监督信号，通过结合<strong>掩码特征重构</strong>和<strong>基于样本的结构重构</strong>来优化模型。这与典型的图自编码器（通常同时进行特征和完整结构重构）以及图掩码自编码器（通常放弃结构重构以减少计算开销）有所不同。</p><ul><li><p><strong>掩码特征重构</strong> 解码器使用一个类似GAT的层来重构被掩码节点的初始嵌入，并计算特征重构损失（scaled cosine loss）。</p></li><li><p><strong>基于样本的结构重构</strong> 通过对比采样节点对并预测它们之间的边概率来重构图结构。 使用一个简单的两层MLP来预测边概率，并采用二元交叉熵损失。</p></li><li><p>最终的优化目标函数是特征重构损失和结构重构损失的和。</p></li></ul><h3 id="检测模块"><a href="#检测模块" class="headerlink" title="检测模块"></a>检测模块</h3><p>简单来说，区分是否恶意的原则为：“正常”样本在嵌入空间中是聚集的，而“异常”样本则会远离这些正常的聚集区。</p><p>模型的训练过程都只使用了良性的数据，意味着模型学习到的embed()函数，其目标就是将正常的系统实体（节点）映射到嵌入空间中的一个相对紧凑的区域。</p><p>因此，检测模块的任务就变成了：</p><ul><li>用所有训练集（纯良性） 的嵌入点，构建一个“正常行为”基线。</li><li>对于一个测试集中的新样本节点，计算它到这个“正常”参照系中最近的k个点的平均距离。</li><li>如果这个平均距离非常大，就说明这个测试样本“离群”，即认为是异常节点。这个“平均距离”就成了我们的异常分数（Anomaly Score）。</li></ul><h2 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h2><h3 id="图处理"><a href="#图处理" class="headerlink" title="图处理"></a>图处理</h3><h3 id="trace-parser-py"><a href="#trace-parser-py" class="headerlink" title="trace_parser.py"></a>trace_parser.py</h3><blockquote><p>将最原始、非结构化的JSON日志，通过一系列正则匹配和逻辑处理，转换成结构化的边列表（.txt），再从中构建出NetworkX图，并将节点&#x2F;边类型映射为整数ID。最终产物是包含多个NetworkX图（以node-link格式）的 train.pkl 和 test.pkl，以及各种类型映射和恶意实体信息。</p></blockquote><p>原数据集的节点UUID转换成自己的整数ID，目的：</p><ul><li>整数ID通常比长字符串UUID占用更少的内存</li><li>像DGL (Deep Graph Library，MAGIC项目中使用的库) 或 PyTorch Geometric这样的图神经网络库，通常期望或在内部将节点表示为从0开始的连续整数ID。<ul><li>训练图：每个图都有自己的一套从0开始的局部节点ID。这些图被分别保存到 train.pkl 里。（没啥用处，反正训练集都是去掉恶意节点的</li><li>测试图：会创建一个合并的 test_node_map (UUID -&gt; “全局测试集”整数ID)。这个映射横跨 <em>所有</em> 测试文件。（评估的时候有用，保存在metadata.json中。malicious.pkl也是指测试集中的）</li></ul></li></ul><p>文件作用：</p><ul><li><p><code>names.json</code> (id_nodename_map) UUID -&gt; 可读名称，如路径或IP</p><p>  对FILE、SUBJECT_PROCESS、NetFlowObject类型的节点获取文件路径、进程名、IP</p><p>  但最终数据是<strong>不包含</strong>文件路径、进程名或IP地址的，只包含了节点的类型ID。</p><p>  训练时使用的节点特征是基于节点类型ID进行one-hot编码得到的（ utils&#x2F;loaddata.py 中的 transform_graph）</p></li><li><p><code>types.json</code> (id_nodetype_map) UUID -&gt; 类型字符串</p></li><li><p><code>read_graphs()</code>: 读取预处理后的图数据，并将其保存为模型可加载的 .pkl 文件</p><ul><li>train.pkl: 包含一个列表，列表中的每个元素是一个训练NetworkX图的node-link data表示。</li><li>test.pkl: 包含一个列表，列表中的每个元素是一个测试NetworkX图的node-link data表示。</li><li>malicious.pkl: 包含一个元组，包含恶意节点的整数ID列表和它们的名称列表。</li><li>（新增）node_type_mapping.json 和 edge_type_mapping.json: 保存全局的类型字符串到整数ID的映射。</li></ul></li></ul><h3 id="loaddata-py"><a href="#loaddata-py" class="headerlink" title="loaddata.py"></a>loaddata.py</h3><blockquote><p>输入阶段一生成的 <code>train.pkl, test.pkl, malicious.pkl</code> 以及元数据文件，加载数据并转换为DGL图，进行特征工程（节点&#x2F;边类型ID进行one-hot编码）。输出单个DGL图（train_x.pkl）。</p></blockquote><h3 id="训练"><a href="#训练" class="headerlink" title="训练"></a>训练</h3><h3 id="评估"><a href="#评估" class="headerlink" title="评估"></a>评估</h3><p>以下仅体现节点级别检测，流程对应model&#x2F;eval.py中的 <code>evaluate_entity_level_using_knn()</code>。</p><ol><li><p><strong>数据准备 (Data Preparation):</strong></p><ul><li><code>x_train</code>: 来自所有<strong>训练图</strong>的<strong>所有节点</strong>的嵌入向量。由于训练图都是良性的，所以<code>x_train</code>代表了海量的、各种类型的“正常节点”的嵌入表示。</li><li><code>x_test</code> &#x2F; <code>y_test</code>: 来自所有<strong>测试图</strong>的<strong>所有节点</strong>的嵌入向量和它们对应的真实标签（0或1）。</li></ul></li><li><p><strong>构建良性参考基线:</strong></p><ul><li>基于<strong>所有正常节点的嵌入</strong>构建的K-D树（或等效结构），形成了一个“正常系统实体”的参照空间。</li></ul><blockquote><p>论文中强调的 K-D 树不在代码显式出现，而是 <code>sklearn.neighbors.NearestNeighbors</code> 在调用 <code>.fit()</code> 时，为了优化后续 <code>.kneighbors()</code>的查询速度而内部构建的高效索引结构。</p></blockquote></li><li><p><strong>计算异常分数 (Calculating Anomaly Score):</strong></p><ul><li><strong>缓存机制:</strong> 代码会检查<code>eval_result/distance_save_&#123;&#125;.pkl</code>是否存在。因为计算所有测试节点到所有训练节点的距离非常耗时，所以第一次计算后会把结果缓存下来，方便后续快速重复实验。</li><li><code>distances_train_sample, _ = nbrs.kneighbors(x_train[idx][:50000], ...)</code>: 从海量的正常训练节点中随机采样一小部分（最多5万个），计算它们内部的k近邻平均距离<code>mean_distance</code>，作为“正常节点”的距离基准。</li><li><code>distances_test, _ = nbrs.kneighbors(x_test, ...)</code>: 为<strong>测试集中每一个节点</strong>，在<code>x_train</code>（正常节点参照系）中找到其k个最近的邻居。</li><li><code>current_distances = distances_test.mean(axis=1)</code>: 计算每个测试节点的k近邻平均距离。</li><li><code>score = current_distances / (mean_distance + 1e-9)</code>: 同样进行归一化，得到每个测试节点的<strong>异常分数</strong>。</li></ul></li><li><p><strong>评估 (Evaluation):</strong></p><ul><li><code>auc = roc_auc_score(y_test, score)</code>: 使用每个节点的异常分数和真实标签计算总体性能。</li></ul><p>有个神奇的地方，代码的 <code>model/eval.py</code> 中针对不同数据集硬编码了距离阈值，从而确保复现论文中的结果。实际上改为通过 max f1 的方法来选定 threshold 效果实测也差不多。</p><img src="/usr/uploads/2025/05/magic/image1.jpg" width="50%" height="50%"><p>此外还学到了一个 sklearn 一个挺有用的用法 <code>precision_recall_curve() </code>，用于自动评估二分类模型性能：</p><p>它接收真实的标签列表 (y_test) 和模型预测的“置信度分数”或“概率分数”列表 (score) 作为输入。这个 score 越高，代表模型认为样本属于正类（在这里是恶意节点）的可能性越大。</p><p><code>precision_recall_curve</code> 会将所有出现过的 score 值（或者说，是一些关键的 score 值）作为潜在的阈值。对于每一个这样的潜在阈值，它都会计算出一对 (精确率, 召回率)。</p><p>因此，它返回的 threshold 列表，就包含了所有这些被考虑过的决策阈值点。返回的 prec 和 rec 数组则是在这些相应阈值点（或阈值区间）上的精确率和召回率。</p></li></ol><h2 id="总结与思考"><a href="#总结与思考" class="headerlink" title="总结与思考"></a>总结与思考</h2><blockquote><p>待更新…</p></blockquote><h2 id="知识补充"><a href="#知识补充" class="headerlink" title="知识补充"></a>知识补充</h2><h3 id="DGL"><a href="#DGL" class="headerlink" title="DGL"></a>DGL</h3><p>DGL（深度图库）使用稀疏矩阵表示，大大减少了计算和内存需求。对于稀疏图，计算复杂度与边的数量相关，而不是节点数量的平方。</p><h4 id="邻接表与邻接矩阵"><a href="#邻接表与邻接矩阵" class="headerlink" title="邻接表与邻接矩阵"></a>邻接表与邻接矩阵</h4><blockquote><p>参考：<a href="https://zhuanlan.zhihu.com/p/630675964">https://zhuanlan.zhihu.com/p/630675964</a></p></blockquote><p><strong>邻接表</strong> 是一种链表的集合。对于每个顶点，邻接表中都有一个链表，链表中存储着与该顶点直接相连的其他顶点。</p><p>示例： 考虑下面这个无向图：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">    A</span><br><span class="line">   / \</span><br><span class="line">  B---C</span><br><span class="line"> / \ / \</span><br><span class="line">D---E---F</span><br></pre></td></tr></table></figure><p>使用邻接表来表示该图的结构如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">A -&gt; B -&gt; C</span><br><span class="line">B -&gt; A -&gt; C -&gt; D -&gt; E</span><br><span class="line">C -&gt; A -&gt; B -&gt; E -&gt; F</span><br><span class="line">D -&gt; B -&gt; E</span><br><span class="line">E -&gt; B -&gt; C -&gt; D -&gt; F</span><br><span class="line">F -&gt; C -&gt; E</span><br></pre></td></tr></table></figure><p><strong>邻接矩阵</strong> 是一个二维矩阵。矩阵的行和列分别代表图中的顶点，矩阵中的元素表示对应顶点之间是否存在边。</p><p>示例： 继续使用上述无向图的示例，使用邻接矩阵来表示该图的结构如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">   A  B  C  D  E  F</span><br><span class="line">A  0  1  1  0  0  0</span><br><span class="line">B  1  0  1  1  1  0</span><br><span class="line">C  1  1  0  0  1  1</span><br><span class="line">D  0  1  0  0  1  0</span><br><span class="line">E  0  1  1  1  0  1</span><br><span class="line">F  0  0  1  0  1  0</span><br></pre></td></tr></table></figure><p>邻接矩阵的优点是在查找特定边的操作上非常高效，时间复杂度为O(1)。同时，邻接矩阵还可以方便地进行图的遍历和某些图算法的实现。然而，邻接矩阵需要较大的存储空间，在稀疏图的情况下，可能会浪费大量的存储空间。</p><h4 id="稠密表示与稀疏表示"><a href="#稠密表示与稀疏表示" class="headerlink" title="稠密表示与稀疏表示"></a>稠密表示与稀疏表示</h4><p>只存储矩阵中的非零元素（即实际存在的边）及其位置（行和列索引）。</p><p>常见格式：</p><ul><li><p>COO (Coordinate List): 存储一个元组列表 <code>(row_index, col_index, value)</code>。</p></li><li><p>CSR (Compressed Sparse Row): 使用三个一维数组：一个存储非零元素的值，一个存储非零元素的列索引，一个存储每行非零元素的起始位置。</p></li><li><p>CSC (Compressed Sparse Column): 类似于CSR，但按列压缩。</p></li></ul><h4 id="AUC-评估指标"><a href="#AUC-评估指标" class="headerlink" title="AUC 评估指标"></a>AUC 评估指标</h4><p>AUC（Area under curve，曲线下面积） 是 ROC 曲线（Receiver Operating Characteristic Curve）下的面积。</p><ul><li>ROC 曲线：横轴是 FPR（假正率），纵轴是 TPR（真正率），通过不同阈值下模型输出的预测概率计算出来的。</li><li>AUC 值的范围：[0, 1]<ul><li>1.0：完美分类器</li><li>0.5：和随机猜测一样（无区分能力）</li><li>&lt;0.5：模型在反着预测（可以通过翻转标签改进）</li></ul></li></ul>]]></content>
    
    
    <summary type="html">MAGIC 是「图学习+嵌入向量范式」的节点级别溯源图 HIDS，设计图注意力网络（GAT）和掩码图学习，通过对节点类别和边存在性的重构，来引导模型从良性数据上学习一个编码器。随后采样良性节点进行编码，并计算 KNN 平均距离作为良性基线。检测阶段对亦节点进行编码嵌入，KNN 寻找良性节点邻居并计算平均距离，与良性基线共同计算得到异常分数。文章发表在 USENIX 24&#39;，作者来自复旦大学、中山大学等。</summary>
    
    
    
    <category term="论文" scheme="https://www.catop.top/categories/%E8%AE%BA%E6%96%87/"/>
    
    
    <category term="深度学习" scheme="https://www.catop.top/tags/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/"/>
    
    <category term="溯源图" scheme="https://www.catop.top/tags/%E6%BA%AF%E6%BA%90%E5%9B%BE/"/>
    
    <category term="图注意力" scheme="https://www.catop.top/tags/%E5%9B%BE%E6%B3%A8%E6%84%8F%E5%8A%9B/"/>
    
    <category term="图学习" scheme="https://www.catop.top/tags/%E5%9B%BE%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>Snort3 编译安装与配置</title>
    <link href="https://www.catop.top/2025/05/24/course-grandfather-teacher-exp-2/"/>
    <id>https://www.catop.top/2025/05/24/course-grandfather-teacher-exp-2/</id>
    <published>2025-05-24T10:20:01.000Z</published>
    <updated>2025-06-09T01:48:33.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>本文是我在《网络与信息系统安全》课程上完成的实验报告，简单整理了一下，涵盖 Snort3 在 Ubuntu 平台上的编译安装、规则维护、分析恶意流量并编写自定义规则；sqli-labs 简单测试和防御。</p></blockquote><hr><h1 id="实验2-1-Snort入侵检测系统（IDS）部署与攻击测试"><a href="#实验2-1-Snort入侵检测系统（IDS）部署与攻击测试" class="headerlink" title="实验2-1: Snort入侵检测系统（IDS）部署与攻击测试"></a>实验2-1: Snort入侵检测系统（IDS）部署与攻击测试</h1><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><h3 id="实验环境"><a href="#实验环境" class="headerlink" title="实验环境"></a>实验环境</h3><ul><li>运行环境：Ubuntu 24.04 LTS Cloud-init</li><li>VM配置：4 vcpu, 4GB RAM</li><li>虚拟化平台：Proxmox VE 8.3.0</li><li>网络拓扑：<ul><li>Target（运行Snort的主机）：192.168.10.169</li><li>Attacker：192.168.10.168</li></ul></li></ul><h3 id="编译安装"><a href="#编译安装" class="headerlink" title="编译安装"></a>编译安装</h3><p>安装过程参考官方文档：<a href="https://docs.snort.org/start/installation">https://docs.snort.org/start/installation</a></p><ol><li><p>安装 LibDAQ</p><p>Snort 3 LibDAQ 为与数据源（如网络接口）通信提供了一层抽象。从源码编译安装：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ git clone https://github.com/snort3/libdaq.git</span><br><span class="line"></span><br><span class="line">$ cd libdaq</span><br><span class="line">$ ./bootstrap</span><br></pre></td></tr></table></figure><p><img src="/usr/uploads/2025/05/exp2/image-20250524102737053-8053658.png" alt="image-20250524102737053"></p><p>发现缺少 <code>bootstrap</code>，那么我们使用 apt 安装它。</p><p><code>sudo apt install autoconf</code></p><p>继续执行 <code>./bootstrap</code>，发现有报错：</p><p><img src="/usr/uploads/2025/05/exp2/image-20250524102844732-8053726.png" alt="image-20250524102844732"></p><p>有许多未定义的宏，应该是由 Autoconf、Automake、m4、Libtool 等工具链提供的。尝试完善构建依赖的安装：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">sudo apt update</span><br><span class="line">sudo apt install -y \</span><br><span class="line">  autoconf \</span><br><span class="line">  automake \</span><br><span class="line">  libtool \</span><br><span class="line">  m4 \</span><br><span class="line">  pkg-config \</span><br><span class="line">  build-essential</span><br><span class="line"></span><br><span class="line"># snort 可能依赖的额外头文件一并安装一下</span><br><span class="line">sudo apt install -y \</span><br><span class="line">  libpcap-dev \</span><br><span class="line">  libpcre3-dev \</span><br><span class="line">  libdumbnet-dev \</span><br><span class="line">  bison \</span><br><span class="line">  flex \</span><br><span class="line">  zlib1g-dev \</span><br><span class="line">  liblzma-dev \</span><br><span class="line">  openssl \</span><br><span class="line">  libssl-dev \</span><br><span class="line">  libnghttp2-dev</span><br></pre></td></tr></table></figure><p>继续执行 <code>./bootstrap</code>，没有报错了，继续进行我们 LibDAQ的安装。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ ./configure --prefix=/usr/local/lib/daq_s3</span><br><span class="line">$ sudo make install</span><br></pre></td></tr></table></figure><p>随后添加其 lib 目录到 ld.so.conf 中：</p><p><code>echo &#39;/usr/local/lib/daq_s3/lib/&#39; &gt; /etc/ld.so.conf.d/libdaq3.conf</code></p><p>执行 <code>sudo ldconfig</code> 更新链接。随后可以 <code>ldconfig -p</code>验证一下。</p><p><img src="/usr/uploads/2025/05/exp2/image-20250524104219965-8054541.png" alt="image-20250524104219965"></p></li><li><p>安装 Snort</p><p>首先安装 Cmake：<code>sudo apt install cmake</code></p><p>以 &#x2F;opt&#x2F;snorty 为示例安装路径：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">$ git clone https://github.com/snort3/snort3.git</span><br><span class="line"></span><br><span class="line"># 切换到 root 用户安装</span><br><span class="line">$ sudo -i</span><br><span class="line">$ export my_path=/opt/snorty</span><br><span class="line">$ mkdir -p $my_path</span><br><span class="line">$ cd snort3</span><br><span class="line"></span><br><span class="line"># 配置 pkg-config 环境变量以找到libdaq</span><br><span class="line">$ export PKG_CONFIG_PATH=/usr/local/lib/daq_s3/lib/pkgconfig:$PKG_CONFIG_PATH</span><br><span class="line">$ ./configure_cmake.sh --prefix=$my_path \</span><br><span class="line">                       --with-daq-includes=/usr/local/lib/daq_s3/include/ \</span><br><span class="line">                       --with-daq-libraries=/usr/local/lib/daq_s3/lib/</span><br></pre></td></tr></table></figure><p>安装 <a href="https://docs.snort.org/start/installation#required-packages">Required Packages</a>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">sudo apt-get update</span><br><span class="line">sudo apt-get install -y \</span><br><span class="line">    cmake \</span><br><span class="line">    libdumbnet-dev \</span><br><span class="line">    flex \</span><br><span class="line">    build-essential \</span><br><span class="line">    libhwloc-dev \</span><br><span class="line">    luajit \</span><br><span class="line">    libluajit-5.1-dev \</span><br><span class="line">    libssl-dev \</span><br><span class="line">    libpcap-dev \</span><br><span class="line">    libpcre3-dev \</span><br><span class="line">    pkg-config \</span><br><span class="line">    zlib1g-dev</span><br></pre></td></tr></table></figure><p>发现还会缺少 <code>libpcre2</code>：</p><p><img src="/usr/uploads/2025/05/exp2/image-20250524110115142-8055677.png" alt="image-20250524110115142"></p><p><code>sudo apt-get install libpcre2-dev</code> 安装即可。</p><p>最后 configure 完成：</p><p><img src="/usr/uploads/2025/05/exp2/image-20250524110208222-8055729.png" alt="image-20250524110208222"></p><p>进行编译并安装：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ cd build</span><br><span class="line">$ make -j $(nproc)</span><br><span class="line">$ make install</span><br></pre></td></tr></table></figure><p>安装成功后，添加进环境变量。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">echo &#x27;export PATH=&quot;/opt/snorty/bin:$PATH&quot;&#x27; &gt;&gt; ~/.zshrc</span><br><span class="line">source ~/.zshrc</span><br></pre></td></tr></table></figure><p><img src="/usr/uploads/2025/05/exp2/image-20250524113556815-8057758.png" alt="image-20250524113556815"></p></li></ol><h3 id="测试检测"><a href="#测试检测" class="headerlink" title="测试检测"></a>测试检测</h3><ol><li><p>准备 Lua 脚本</p><p>将源代码仓库中 <code>lua/</code>拷贝至 snort 安装目录，方便后续调用：</p><p><code>cp -r lua /opt/snorty/</code></p></li><li><p>准备 .rules 规则</p><p>从官网下载社区版本的规则库：<a href="https://www.snort.org/downloads">https://www.snort.org/downloads</a></p><p>例如我下载的是：<a href="https://www.snort.org/downloads/community/snort3-community-rules.tar.gz">https://www.snort.org/downloads/community/snort3-community-rules.tar.gz</a></p><p>在 snort 安装目录创建 <code>rules/</code>目录，将解压后的 <code>snort3-community.rules</code> 拷贝进去。</p></li><li><p>准备 pcap 包</p><p>最简单的测试方法就是让 snort 读取 pcap 包并生成告警。我这里准备了 Ruoyi-4.2  SquirrelMail-1.4.22  Typecho1.0  nexus-3.21.1 这四个服务的良性和恶意流量数据，用于测试。</p><p><img src="/usr/uploads/2025/05/exp2/image-20250524115141579-8058703.png" alt="image-20250524115141579"></p></li><li><p>测试规则解析</p><p><code>snort -c /opt/snorty/lua/snort.lua -R /opt/snorty/rules/snort3-community.rules</code></p><p>正常应该输出：Snort successfully validated the configuration (with 0 warnings).</p><img src="/usr/uploads/2025/05/exp2/image-20250524115955215-8059197.png" alt="image-20250524115955215" style="zoom:50%;" /></li><li><p>测试检测效果</p><p>找一个恶意IDS，测试是否能检测到恶意行为。我们使用nefu大学的Maple IDS开源数据集，下载一个恶意pcap：<a href="https://maple.nefu.edu.cn/dataset/download/0-Maple-IDS/LOIC/http_ddos_http_loic_random.pcap">https://maple.nefu.edu.cn/dataset/download/0-Maple-IDS/LOIC/http_ddos_http_loic_random.pcap</a></p><p>执行<code>snort -q -c /etc/snort/snort.lua -R /etc/snort/snort3-community.rules -r http_ddos_http_loic_random.pcap -A alert_fast</code>，看到成功产生了3条报警。</p><p><img src="/usr/uploads/2025/05/exp2/image-20250524154739628-8072861.png" alt="image-20250524154739628"></p></li></ol><h2 id="规则维护"><a href="#规则维护" class="headerlink" title="规则维护"></a>规则维护</h2><p>由于 Snort 是规则驱动的 IDS，因此规则的编写质量对于检测效果有重要的影响。在本次大作业中，从 Github 社区寻找了一个 APT 场景中的 pcap 包 <code>malware.pcap</code>，首先对其进行人工观察分析，抽取出行为模式，进行 snort 的 rules 规则编写。</p><h3 id="攻击行为分析"><a href="#攻击行为分析" class="headerlink" title="攻击行为分析"></a>攻击行为分析</h3><p>使用 wireshark 打开 <code>malware.pcap</code>，筛选 HTTP流量，发现有下载 ooiwy.pdf 的请求，但根据文件头判断，其应该是一个 Windows 可执行文件。</p><p><img src="/usr/uploads/2025/05/exp2/image-20250524154924756-8072966.png" alt="image-20250524154924756"></p><p>使用微步沙箱进行检测，发现其已经被分析过且在恶意样本库中。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SHA256: f25a780095730701efac67e9d5b84bc289afea56d96d8aff8a44af69ae606404</span><br></pre></td></tr></table></figure><p><img src="/usr/uploads/2025/05/exp2/image-20250524154945830-8072987.png" alt="image-20250524154945830"></p><p>该二进制是加壳的，运行后存在使用 sleep 拖慢分析、探测外网 IP、修改内存属性等行为。</p><p><img src="/usr/uploads/2025/05/exp2/image-20250524155025069-8073026.png" alt="image-20250524155025069"></p><p>值得关注的是，在不同的沙箱运行环境中，网络行为有所差异：Win10 中只请求了几个内网IP地址，Win7 64bit中却请求了更多，包括 184.74.99.214 、46.99.175.217 、185.56.175.122 等。查询该 IP 的威胁情报，发现多个来源的情报均将其标记为恶意。</p><img src="/usr/uploads/2025/05/exp2/image-20250524155038617.png" alt="image-20250524155038617" style="zoom:50%;" /><p>然而，在PCAP包中过滤 <code>ip.host==184.74.99.214</code> 并没有结果。<strong>让云沙箱再次执行分析，发现请求了不同的地址</strong>，推测是通过DNS解析之后再请求C2地址。Win10中没有请求，且执行流程有较大差异，可能是无法在Win10上正常执行。</p><p>PCAP包中可观察到明显的C2回连特征，用HTTP协议连接到 103.148.41.195:443，上报了自己的系统信息。</p><p><img src="/usr/uploads/2025/05/exp2/image-20250524155052424.png" alt="image-20250524155052424"></p><p><img src="/usr/uploads/2025/05/exp2/image-20250524155059893-8073061.png" alt="image-20250524155059893"></p><h3 id="自定义规则编写"><a href="#自定义规则编写" class="headerlink" title="自定义规则编写"></a>自定义规则编写</h3><p>根据上面的分析，一种合理的方式是：<strong>对HTTP响应中，响应体为X86 SHELLCODE，但uri不包括.exe的流量进行告警。</strong>因为这通常意味着攻击者将一个可执行文件，伪装成其他文件来传播。</p><p>经过多次调整，最终写出以下规则：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">alert http $EXTERNAL_NET any -&gt; $HOME_NET any (</span><br><span class="line">    msg:&quot;HTTP traffic with non-.exe executable file detected&quot;;</span><br><span class="line">    content:&quot;|90 90 90 90 90 90 90 90 90 90 90 90 90 90|&quot;;</span><br><span class="line">    content:!&quot;\.exe&quot;; http_uri;</span><br><span class="line">    sid:1001;</span><br><span class="line">    rev:2;</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>运行检测，成功输出告警！</p><p><img src="/usr/uploads/2025/05/exp2/image-20250524155210848-8073132.png" alt="image-20250524155210848"></p><p>检测到<code>&#123;TCP&#125; 185.244.41.29:80 -&gt; 10.8.19.101:49748</code>存在该行为，与我们人工分析PCAP相符。</p><h2 id=""><a href="#" class="headerlink" title=""></a></h2><ol><li><p>精确性</p><p>我们编写的 <code>sid:1001</code> 规则成功检测到了伪装成 PDF 的可执行文件下载行为。评估该规则的精确性，它是否可能产生误报（False Positives）？例如，是否存在合法的、非<code>.exe</code> 文件但包含了类似 <code>|90 90 90 ...|</code> (NOP Sled) 的二进制数据？</p><p>针对这种思考，我们可以尝试增强 <code>sid:1001</code> 的匹配规则，尝试结合 <code>file_data</code> 关键字或其他更精确的文件内容检测方法，减少对 NOP Sled 的单一依赖，提高规则的鲁棒性。</p></li><li><p>覆盖性</p></li></ol><p>​该规则是否可能产生漏报（False Negatives）？攻击者可以通过哪些方式绕过此规则？</p><ul><li>使用不同的填充字节代替 <code>0x90</code>。</li><li>对可执行文件进行加密或编码。</li><li>使用 HTTPS 协议进行传输（如果 Snort 未配置 SSL&#x2F;TLS 解密）。</li><li>将可执行文件分块传输。</li><li>修改 URI，使其<em>包含</em> <code>.exe</code> 但实际文件内容不是 Windows 可执行文件。</li></ul><p>​针对以上思考，后续的优化思路可以包括：</p><ul><li>使用更精细和灵活的方式定义规则：Snort3 开始引入了 Lua 脚本，可以弥补 rules 的单一使用的不足。</li><li>基于机器学习的方式检测：可以一定程度上缓解因加密引起的漏报，例如使用 Lua 脚本调用外部 API，实现智能检测。但这会引入额外的延迟和性能开销，在 IDS 模式中比较适用，IPS 可能无法容忍这种开销。</li></ul><hr><h1 id="实验2-2-SQL注入攻击原理与检测技术实践"><a href="#实验2-2-SQL注入攻击原理与检测技术实践" class="headerlink" title="实验2-2: SQL注入攻击原理与检测技术实践"></a>实验2-2: SQL注入攻击原理与检测技术实践</h1><h2 id="靶场搭建"><a href="#靶场搭建" class="headerlink" title="靶场搭建"></a>靶场搭建</h2><p>直接使用 docker 进行搭建，没找到官方的镜像，但 <code>acgpiano/sqli-labs</code> 有 50k 的pull，经测试功能正常。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">docker pull acgpiano/sqli-labs</span><br><span class="line">docker run -dt --name sqli-lab -p 80:80 acgpiano/sqli-labs:latest</span><br></pre></td></tr></table></figure><p>随后访问 <a href="http://my-server,进入/">http://my-server，进入</a> sqli-labs 界面，首先初始化数据库。</p><p><img src="/usr/uploads/2025/05/exp2/image-20250524170349629-8077430.png" alt="image-20250524170349629"></p><p>成功之后即可进行实验。</p><h2 id="手工注入"><a href="#手工注入" class="headerlink" title="手工注入"></a>手工注入</h2><p>以 POST 类型的 error-based 注入为例（Less-11）：</p><p>使用 <code>admin&#39;</code>作为用户名，出现报错，可判断注入点应该在此处。</p><p><img src="/usr/uploads/2025/05/exp2/image-20250524170508558-8077510.png" alt="image-20250524170508558"></p><p>使用 burpsuite 拦截下请求，以方便调试。</p><img src="/usr/uploads/2025/05/exp2/image-20250524170734771.png" alt="image-20250524170734771" style="zoom:30%;" /><ol><li><p>爆破列数</p><p><code>uname=admin&#39; order by 2-- &amp;passwd=1&amp;submit=Submit</code></p><p>有两列。</p></li><li><p>获取库名</p><p>使用 <code>information_schema</code> 获得：</p><p><code>uname=admin&#39; and 1=2 union select 1,(select group_concat(schema_name) from information_schema.schemata)-- &amp;passwd=1&amp;submit=Submit</code></p><img src="/usr/uploads/2025/05/exp2/image-20250524171130009.png" alt="image-20250524171130009" style="zoom:50%;" /></li><li><p>获取security库的表名</p><p><code>uname=admin&#39; and 1=2 union select 1,(select group_concat(table_name) from information_schema.tables where table_schema=&#39;security&#39;)-- &amp;passwd=1&amp;submit=Submit</code></p><img src="/usr/uploads/2025/05/exp2/image-20250524171231820.png" alt="image-20250524171231820" style="zoom:50%;" /></li><li><p>获取字段名</p><p><code>uname=admin&#39; and 1=2 union select 1,(select group_concat(column_name) from information_schema.columns where table_name=&#39;users&#39;)-- &amp;passwd=1&amp;submit=Submit</code></p><img src="/usr/uploads/2025/05/exp2/image-20250524171302119.png" alt="image-20250524171302119" style="zoom:50%;" /></li><li><p>获取数据</p><p><code>uname=admin&#39; and 1=2 union select (select group_concat(username) from security.users),(select group_concat(password) from security.users)-- &amp;passwd=1&amp;submit=Submit</code></p><img src="/usr/uploads/2025/05/exp2/image-20250524171347011.png" alt="image-20250524171347011" style="zoom:50%;" /><p>至此，相当于拿到用户的明文密码了。</p></li></ol><h2 id="自动化注入检测"><a href="#自动化注入检测" class="headerlink" title="自动化注入检测"></a>自动化注入检测</h2><p>使用 sqlmap 尝试自动化。</p><p><code>sqlmap -u &#39;http://192.168.10.169/Less-11/&#39; --forms</code></p><p>识别到 uname 有4种注入类型：</p><p><img src="/usr/uploads/2025/05/exp2/image-20250524171919534-8078361.png" alt="image-20250524171919534"></p><p>如果要获取数据，也是首先拿数据库名：</p><p><code>sqlmap -u &#39;http://192.168.10.169/Less-11/&#39; --forms --batch</code></p><p><img src="/usr/uploads/2025/05/exp2/image-20250524172033123-8078435.png" alt="image-20250524172033123"></p><p>其次获得获得表：</p><p><code>sqlmap -u &#39;http://192.168.10.169/Less-11/&#39; --forms --tables --batch -D security</code></p><p><img src="/usr/uploads/2025/05/exp2/image-20250524172300289-8078581.png" alt="image-20250524172300289"></p><p>拿users表中的数据：</p><p><code>sqlmap -u &#39;http://192.168.10.169/Less-11/&#39; --forms --dump --batch -D security -T users</code></p><p><img src="/usr/uploads/2025/05/exp2/image-20250524172342745.png" alt="image-20250524172342745"></p><h2 id="修复与防御"><a href="#修复与防御" class="headerlink" title="修复与防御"></a>修复与防御</h2><p>进入到容器中，到 <code>/var/www/html</code> 下访问源码。</p><p><img src="/usr/uploads/2025/05/exp2/image-20250524172621211.png" alt="image-20250524172621211"></p><p>我们使用参数化&#x2F;预编译查询修复sqli-lab这个题目中的sql注入漏洞，将index.php进行修改：</p><p><code>@$sql=&quot;SELECT username, password FROM users WHERE username=&#39;$uname&#39; and password=&#39;$passwd&#39; LIMIT 0,1&quot;;</code></p><p>修改后的源码：</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line"><span class="keyword">include</span>(<span class="string">&quot;../sql-connections/sql-connect.php&quot;</span>); </span><br><span class="line"><span class="title function_ invoke__">error_reporting</span>(<span class="number">0</span>);</span><br><span class="line"><span class="variable">$dbhost</span> = <span class="string">&#x27;localhost&#x27;</span>; </span><br><span class="line"><span class="variable">$dbuser</span> = <span class="string">&#x27;root&#x27;</span>;      </span><br><span class="line"><span class="variable">$dbpass</span> = <span class="string">&#x27;&#x27;</span>;          </span><br><span class="line"><span class="variable">$dbname</span> = <span class="string">&#x27;security&#x27;</span>;  </span><br><span class="line"><span class="variable">$link</span> = <span class="title function_ invoke__">mysqli_connect</span>(<span class="variable">$dbhost</span>, <span class="variable">$dbuser</span>, <span class="variable">$dbpass</span>, <span class="variable">$dbname</span>);</span><br><span class="line"><span class="keyword">if</span> (<span class="title function_ invoke__">mysqli_connect_errno</span>()) &#123;</span><br><span class="line">    <span class="title function_ invoke__">printf</span>(<span class="string">&quot;Connect failed: %s\n&quot;</span>, <span class="title function_ invoke__">mysqli_connect_error</span>());</span><br><span class="line">    <span class="keyword">exit</span>();</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span>(<span class="keyword">isset</span>(<span class="variable">$_POST</span>[<span class="string">&#x27;uname&#x27;</span>]) &amp;&amp; <span class="keyword">isset</span>(<span class="variable">$_POST</span>[<span class="string">&#x27;passwd&#x27;</span>]))</span><br><span class="line">&#123;</span><br><span class="line"><span class="variable">$uname</span>=<span class="variable">$_POST</span>[<span class="string">&#x27;uname&#x27;</span>];</span><br><span class="line"><span class="variable">$passwd</span>=<span class="variable">$_POST</span>[<span class="string">&#x27;passwd&#x27;</span>];</span><br><span class="line"></span><br><span class="line"><span class="variable">$fp</span>=<span class="title function_ invoke__">fopen</span>(<span class="string">&#x27;result.txt&#x27;</span>,<span class="string">&#x27;a&#x27;</span>);</span><br><span class="line"><span class="title function_ invoke__">fwrite</span>(<span class="variable">$fp</span>,<span class="string">&#x27;User Name:&#x27;</span>.<span class="variable">$uname</span>);</span><br><span class="line"><span class="title function_ invoke__">fwrite</span>(<span class="variable">$fp</span>,<span class="string">&#x27;Password:&#x27;</span>.<span class="variable">$passwd</span>.<span class="string">&quot;\n&quot;</span>);</span><br><span class="line"><span class="title function_ invoke__">fclose</span>(<span class="variable">$fp</span>);</span><br><span class="line"></span><br><span class="line">    </span><br><span class="line"><span class="variable">$sql</span> = <span class="string">&quot;SELECT username, password FROM users WHERE username = ? AND password = ? LIMIT 1&quot;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (<span class="variable">$stmt</span> = <span class="title function_ invoke__">mysqli_prepare</span>(<span class="variable">$link</span>, <span class="variable">$sql</span>)) &#123;</span><br><span class="line">        </span><br><span class="line">        <span class="title function_ invoke__">mysqli_stmt_bind_param</span>(<span class="variable">$stmt</span>, <span class="string">&quot;ss&quot;</span>, <span class="variable">$uname</span>, <span class="variable">$passwd</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="title function_ invoke__">mysqli_stmt_execute</span>(<span class="variable">$stmt</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="variable">$result</span> = <span class="title function_ invoke__">mysqli_stmt_get_result</span>(<span class="variable">$stmt</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="variable">$row</span> = <span class="title function_ invoke__">mysqli_fetch_array</span>(<span class="variable">$result</span>, MYSQLI_ASSOC); </span><br><span class="line">        <span class="keyword">if</span>(<span class="variable">$row</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">echo</span> <span class="string">&quot;&lt;br&gt;&quot;</span>;</span><br><span class="line">            <span class="keyword">echo</span> <span class="string">&#x27;&lt;font color= &quot;#FFFF00&quot; font size = 4&gt;&#x27;</span>;</span><br><span class="line">            <span class="keyword">echo</span> <span class="string">&#x27;&lt;font size=&quot;3&quot; color=&quot;#0000ff&quot;&gt;&#x27;</span>;</span><br><span class="line">            <span class="keyword">echo</span> <span class="string">&quot;&lt;br&gt;&quot;</span>;</span><br><span class="line">            <span class="keyword">echo</span> <span class="string">&#x27;Your Login name:&#x27;</span>. <span class="title function_ invoke__">htmlspecialchars</span>(<span class="variable">$row</span>[<span class="string">&#x27;username&#x27;</span>], ENT_QUOTES, <span class="string">&#x27;UTF-8&#x27;</span>); </span><br><span class="line">            <span class="keyword">echo</span> <span class="string">&quot;&lt;br&gt;&quot;</span>;</span><br><span class="line">            <span class="keyword">echo</span> <span class="string">&#x27;Your Password:&#x27;</span> .<span class="title function_ invoke__">htmlspecialchars</span>(<span class="variable">$row</span>[<span class="string">&#x27;password&#x27;</span>], ENT_QUOTES, <span class="string">&#x27;UTF-8&#x27;</span>); </span><br><span class="line">            <span class="keyword">echo</span> <span class="string">&quot;&lt;br&gt;&quot;</span>;</span><br><span class="line">            <span class="keyword">echo</span> <span class="string">&quot;&lt;/font&gt;&quot;</span>;</span><br><span class="line">            <span class="keyword">echo</span> <span class="string">&quot;&lt;br&gt;&quot;</span>;</span><br><span class="line">            <span class="keyword">echo</span> <span class="string">&quot;&lt;br&gt;&quot;</span>;</span><br><span class="line">            <span class="keyword">echo</span> <span class="string">&#x27;&lt;img src=&quot;../images/flag.jpg&quot;  /&gt;&#x27;</span>;</span><br><span class="line">            <span class="keyword">echo</span> <span class="string">&quot;&lt;/font&gt;&quot;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">echo</span> <span class="string">&#x27;&lt;font color= &quot;#0000ff&quot; font size=&quot;3&quot;&gt;&#x27;</span>;</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span>(<span class="title function_ invoke__">mysqli_error</span>(<span class="variable">$link</span>))&#123;</span><br><span class="line">                <span class="title function_ invoke__">print_r</span>(<span class="title function_ invoke__">mysqli_error</span>(<span class="variable">$link</span>));</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                 <span class="keyword">echo</span> <span class="string">&quot;Login Failed!&quot;</span>; </span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">echo</span> <span class="string">&quot;&lt;/br&gt;&quot;</span>;</span><br><span class="line">            <span class="keyword">echo</span> <span class="string">&quot;&lt;/br&gt;&quot;</span>;</span><br><span class="line">            <span class="keyword">echo</span> <span class="string">&quot;&lt;/br&gt;&quot;</span>;</span><br><span class="line">            <span class="keyword">echo</span> <span class="string">&#x27;&lt;img src=&quot;../images/slap.jpg&quot; /&gt;&#x27;</span>;</span><br><span class="line">            <span class="keyword">echo</span> <span class="string">&quot;&lt;/font&gt;&quot;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="title function_ invoke__">mysqli_stmt_close</span>(<span class="variable">$stmt</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">echo</span> <span class="string">&#x27;&lt;font color= &quot;#ff0000&quot; font size=&quot;3&quot;&gt;&#x27;</span>;</span><br><span class="line">        <span class="keyword">echo</span> <span class="string">&quot;SQL statement preparation failed: &quot;</span> . <span class="title function_ invoke__">mysqli_error</span>(<span class="variable">$link</span>);</span><br><span class="line">        <span class="keyword">echo</span> <span class="string">&quot;&lt;/font&gt;&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="title function_ invoke__">mysqli_close</span>(<span class="variable">$link</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="meta">?&gt;</span></span><br></pre></td></tr></table></figure><p>发现无法实现注入，修复完成。</p><p><img src="/usr/uploads/2025/05/exp2/image-20250524173409839-8079250.png" alt="image-20250524173409839"></p>]]></content>
    
    
    <summary type="html">本文是我在《网络与信息系统安全》课程上完成的实验报告，简单整理了一下，涵盖 Snort3 在 Ubuntu 平台上的编译安装、规则维护、分析恶意流量并编写自定义规则；sqli-labs 简单测试和防御。</summary>
    
    
    
    <category term="课程" scheme="https://www.catop.top/categories/%E8%AF%BE%E7%A8%8B/"/>
    
    
    <category term="网络安全" scheme="https://www.catop.top/tags/%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A8/"/>
    
    <category term="IDS" scheme="https://www.catop.top/tags/IDS/"/>
    
    <category term="课程实验" scheme="https://www.catop.top/tags/%E8%AF%BE%E7%A8%8B%E5%AE%9E%E9%AA%8C/"/>
    
  </entry>
  
  <entry>
    <title>Kairos简单阅读和复现</title>
    <link href="https://www.catop.top/2025/05/13/kaiors-read-and-reproduce/"/>
    <id>https://www.catop.top/2025/05/13/kaiors-read-and-reproduce/</id>
    <published>2025-05-13T00:50:00.000Z</published>
    <updated>2025-05-13T02:45:05.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>《KAIROS: Practical Intrusion Detection and Investigation using Whole-system Provenance》是图级别溯源图IDS的方法，发表在S&amp;P24，本文是对其简单阅读和复现的记录。复现过程中遇到了几处环境问题，做了对应解决。</p></blockquote><h2 id="关键方法"><a href="#关键方法" class="headerlink" title="关键方法"></a>关键方法</h2><h3 id="时间窗口队列异常分数计算"><a href="#时间窗口队列异常分数计算" class="headerlink" title="时间窗口队列异常分数计算"></a>时间窗口队列异常分数计算</h3><p>KAIROS会根据每个时间窗口的可疑节点来增量构建<strong>时间窗口队列</strong>。</p><ul><li>如果一个新的时间窗口与现有队列中的某个时间窗口存在共同的可疑节点，那么这个新的时间窗口就会被添加到该队列中；</li><li>若不存在共同的可疑节点，则会创建一个仅包含该新时间窗口的新队列。</li></ul><p>当一个新的时间窗口被添加到队列中时，KAIROS会基于重建误差更新该队列的异常分数，若异常分数超过阈值，KAIROS就会认为该队列异常并触发警报。</p><h3 id="TGN"><a href="#TGN" class="headerlink" title="TGN"></a>TGN</h3><p>文中KAIROS的TGN Encoder是对边进行嵌入，MLP Decoder 获得该边的预期类别（9种）。</p><p>训练阶段：可训练的是TGN、MLP两个模型的参数，目标是最小化实际边类型和从边嵌入预测的边类型之间的差异（交叉熵损失），即重建误差（Reconstruction Error，RE）。</p><p>检测阶段：根据边的重建误差，更新队列异常分数。</p><h3 id="超参数"><a href="#超参数" class="headerlink" title="超参数"></a>超参数</h3><ul><li><p>邻域采样大小|N|：取经验值20。</p><blockquote><p>例如在E3 - THEIA数据集中，约97%的节点邻域大小为20或更小。</p></blockquote></li><li><p>追踪 State Update 的时间窗口：未讨论，但提到默认时间窗口|tw| 为15分钟。</p></li></ul><h3 id="检测策略"><a href="#检测策略" class="headerlink" title="检测策略"></a>检测策略</h3><p>如果一个节点满足以下两个属性，则该节点在时间窗口T内是可疑的：</p><ul><li>异常性：如果一个节点是一条边的源节点或目标节点，且该边的重构误差大于重构阈值，那么这个节点就是异常的。</li><li>稀有性：如果一个节点对应的系统实体在良性执行中不经常出现，则该节点是稀有的。使用逆文档频率（IDF）来计算节点的稀有性。</li></ul><h2 id="复现过程"><a href="#复现过程" class="headerlink" title="复现过程"></a>复现过程</h2><p>提供了预训练权重：<a href="https://drive.google.com/drive/u/0/folders/1YAKoO3G32xlYrCs4BuATt1h_hBvvEB6C">https://drive.google.com/drive/u/0/folders/1YAKoO3G32xlYrCs4BuATt1h_hBvvEB6C</a></p><h3 id="环境搭建问题"><a href="#环境搭建问题" class="headerlink" title="环境搭建问题"></a>环境搭建问题</h3><ol><li><p>scikit-learn和numpy版本兼容性问题</p><p> <img src="/usr/uploads/2025/05/kairos/image.jpg" alt="image.jpg"></p><p> 作者给的readme也提到了这个问题（应该）</p><p> <img src="/usr/uploads/2025/05/kairos/image%201.jpg" alt="image.jpg"></p><p> 根据发行时间推算scikit-learn&#x3D;&#x3D;1.2.0对应的numpy版本：</p><p> <img src="/usr/uploads/2025/05/kairos/image%202.jpg" alt="image.jpg"></p><p> 最终解决：</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">pip uninstall scikit-learn numpy</span><br><span class="line">pip install scikit-learn==1.2.0 numpy==1.23.5</span><br></pre></td></tr></table></figure></li><li><p>torch_geometric 报错Not compiled with CUDA support</p><p> <img src="/usr/uploads/2025/05/kairos/image%203.jpg" alt="image.jpg"></p><p> 实际上是torch_scatter报错了，首先卸载：<code>pip uninstall torch_scatter</code></p><p> 去官方下对应torch和cuda版本的：</p><p> wget <a href="https://data.pyg.org/whl/torch-1.13.0%2Bcu117/torch_scatter-2.1.1%2Bpt113cu117-cp39-cp39-linux_x86_64.whl">https://data.pyg.org/whl/torch-1.13.0%2Bcu117/torch_scatter-2.1.1%2Bpt113cu117-cp39-cp39-linux_x86_64.whl</a></p><p> 安装：</p><p>  pip install .&#x2F;torch_scatter-2.1.1+pt113cu117-cp39-cp39-linux_x86_64.whl</p><p> 测试：</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">import torch</span><br><span class="line">import torch_scatter</span><br><span class="line"></span><br><span class="line">x = torch.rand(5, device=&#x27;cuda&#x27;)</span><br><span class="line">index = torch.tensor([0, 1, 2, 0, 1], device=&#x27;cuda&#x27;)</span><br><span class="line">output, argmax = torch_scatter.scatter_max(x, index)</span><br><span class="line">print(output)</span><br></pre></td></tr></table></figure></li><li><p>attack_investigation报错缺库：</p><p> <img src="/usr/uploads/2025/05/kairos/image%204.jpg" alt="image.jpg"></p><p> <code>pip install python-louvain</code></p></li></ol><h2 id="知识补充"><a href="#知识补充" class="headerlink" title="知识补充"></a>知识补充</h2><h3 id="图学习任务类型"><a href="#图学习任务类型" class="headerlink" title="图学习任务类型"></a>图学习任务类型</h3><p>在溯源图IDS中，感觉可以将基于图学习的方法分为三种，取代表性的文章简单归类了下，后面有时间再更新。</p><ol><li><p>图学习+分类模型</p><p> 首先经过图学习模型（例如GCN），然后输入到标准的分类器中（例如MLP等），输出标签为恶意&#x2F;良性，训练任务就是最小化交叉熵损失。</p><p> 代表方法：</p><ul><li>Flash：两层GraphSage，随后softmax输出01概率</li></ul></li><li><p>图学习+重建损失异常</p><p> 训练模型来重建节点或者边特征，例如节点&#x2F;边的类别、特定节点间是否存在通路等。在检测阶段，如果某处重建误差高于阈值，则认为是可疑节点。</p><p> 代表方法：</p><ul><li>Kairos：TGN Encoder+MLP Decoder，预测边类型</li></ul></li><li><p>图学习+嵌入向量库</p><p> 先嵌入后聚类，例如基于良性数据构建良性行为“基线”。通常要求很好的嵌入质量。</p><ul><li>Magic：图自动编码器+GAT，随机设置一些mask来让模型重建。随后基于良性数据嵌入得到K-D树，预测阶段使用KNN计算异常分数。</li></ul></li></ol><h3 id="社区发现"><a href="#社区发现" class="headerlink" title="社区发现"></a>社区发现</h3><p>Kairos采用了louvain社区发现来划分攻击子图，确实可以将时间窗口内一整个大图划分为几个sub_graph，而且效果看起来挺好的。</p><p>参考： <a href="https://zhuanlan.zhihu.com/p/556291759">https://zhuanlan.zhihu.com/p/556291759</a></p><ol><li><p>社区</p><p> 在最常见的社交网络中，每个用户相当一个点，用户之间的互相关注、点赞、私信等形成了边，用户以及相互作用关系构成了一个大的关系网络。在这样的网络中，有的用户之间的连接较为紧密，有的用户之间的连接关系较为稀疏。其中连接较为紧密的部分可以被看成一个社区，其内部的节点之间有较为紧密的连接，而在两个社区间则相对连接较为稀疏，整个整体的结构被称为社团结构，如下图，红色的黑色的点集呈现出社区的结构。</p><p> <img src="/usr/uploads/2025/05/kairos/community.png" alt="community.png"></p></li><li><p>模块度</p><p> 在各类网络中会存在一些紧密连接的区域，这些区域（节点集）通常有自己的属性，称为社群或者社团（Community），社团内部连接紧密，而社团外部的连接则相对稀疏，即“内紧外松”。</p><p> “社群检测”等同于“给节点分组”，模块度（Modularity）是一种常用的衡量节点分组质量的标准，模块度越高说明所检测到的社团越符合“内紧外松”的特征，分组质量越好。</p></li><li><p>模块度最大化算法</p><p> 该方法的目标是从所有可能的分组中找到使得模块度最大的分组，由于穷举所有可能的分组十分困难，所以实际的算法都采用近似优化方法。例如，Mark Newman 提出了模块度最大化的贪婪算法Fast NewMan (FN)。贪婪算法的原理是找出每个局部最优值，最终将局部最优值整合成整体的近似最优值。</p><p> <img src="/usr/uploads/2025/05/kairos/modular-max.png" alt="modular-max.png"></p></li></ol>]]></content>
    
    
    <summary type="html">《KAIROS: Practical Intrusion Detection and Investigation using Whole-system Provenance》是图级别溯源图IDS的方法，发表在S&amp;P24，本文是对其简单阅读和复现的记录。复现过程中遇到了几处环境问题，做了对应解决。</summary>
    
    
    
    <category term="论文" scheme="https://www.catop.top/categories/%E8%AE%BA%E6%96%87/"/>
    
    
    <category term="深度学习" scheme="https://www.catop.top/tags/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/"/>
    
    <category term="溯源图" scheme="https://www.catop.top/tags/%E6%BA%AF%E6%BA%90%E5%9B%BE/"/>
    
  </entry>
  
  <entry>
    <title>记一次环球影城游览相遇游客的人脸识别及时序分析</title>
    <link href="https://www.catop.top/2025/03/16/are-we-meet-before/"/>
    <id>https://www.catop.top/2025/03/16/are-we-meet-before/</id>
    <published>2025-03-16T02:30:00.000Z</published>
    <updated>2025-03-16T09:19:13.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>前段时间趁实验室同学来京参赛，一起约着去了一趟环球影城（北京环球度假区，UniversalBeijingResort）。虽是淡季的工作日，但不得不说环球人气真的火爆。尽管如此，玩完之后还是觉得很值的。</p><p><img src="/usr/uploads/2025/03/IMG_8030.jpeg" alt="环球标志性大地球"></p><p>估计很多人来此的重要动机之一就是看哈利波特展区，这个展区确实是整个园区最精致的部分。霍格沃茨魔法学校附近绝对是出片的好地方，有不少商拍摄影师在此跟拍，演出也非常还原（尽管我并没看过哈利波特）。</p><p><img src="/usr/uploads/2025/03/IMG_8063.jpeg" alt="霍格沃茨附近"></p><p>城堡比欢乐谷的精致许多。</p><p><img src="/usr/uploads/2025/03/IMG_8091.jpeg" alt="城堡外部"></p><p>进入城堡内部，准备体验禁忌之旅。</p><p><img src="/usr/uploads/2025/03/IMG_8109.jpeg" alt="城堡内部"></p><p>功夫熊猫主题的室内展馆也十分精致，有着非常不错的置景和灯光。</p><p><img src="/usr/uploads/2025/03/IMG_8039.jpeg" alt="花灯园"></p><p>作为过山车爱好者，霸天虎惊险程度 &gt; 刺激程度，二刷了。</p><p><img src="/usr/uploads/2025/03/IMG_8110.jpeg" alt="霸天虎"></p><p>环球的夜景被许多人低估了，来拍人像的话真的很出片。</p><p><img src="/usr/uploads/2025/03/IMG_8115.jpeg" alt="夜景"></p><p>仿佛看到了 GTA 里面的梦中游乐园。</p><p><img src="/usr/uploads/2025/03/IMG_8114.jpeg" alt="夜景"></p><p>正宗美式效果。</p><p><img src="/usr/uploads/2025/03/IMG_8122.jpeg" alt="夜景"></p><p>晚上赶上了城堡的灯光秀。</p><p><img src="/usr/uploads/2025/03/IMG_8137.jpeg" alt="夜景"></p><p>再见啦👋。</p><p><img src="/usr/uploads/2025/03/IMG_8153.jpeg" alt="夜景"></p><h2 id="想法"><a href="#想法" class="headerlink" title="想法"></a>想法</h2><p>一天下来走了许多冤枉路，步数达到了2.4万。正好运动相机录制了许多素材，能否分析一下有没有与我们重复相遇的人呢，相遇时间间隔是多少？</p><p>有了这个点子后，打算写个小工具来实现。</p><h3 id="技术选型"><a href="#技术选型" class="headerlink" title="技术选型"></a>技术选型</h3><p>之前就了解过<code>InsightFace</code>，对亚洲人识别效果很好。Faiss是个向量数据库，在LLM领域的<code>RAG</code>中常用到，当然也可以用来对比人脸 Embedding 向量的相似度吧。</p><ol><li><strong>InsightFace</strong>：开源的高精度人脸识别框架</li><li><strong>Faiss</strong>：Facebook AI开发的相似性搜索库，专为大规模向量检索优化</li><li><strong>FFmpeg</strong>：用于高效视频处理的命令行工具，可以提取视频帧</li></ol><h3 id="实现思路"><a href="#实现思路" class="headerlink" title="实现思路"></a>实现思路</h3><p>工作流程如下：</p><ol><li><strong>视频分割</strong>：将运动相机录制的视频按每秒提取一帧，并以时间戳命名</li><li><strong>人脸检测与特征提取</strong>：识别每一帧中的人脸，并提取人脸特征</li><li><strong>人脸匹配与数据库构建</strong>：比较人脸特征相似度，构建人脸出现记录</li><li><strong>时序分析</strong>：分析人脸出现的时间模式，计算相遇显著性</li></ol><h3 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h3><p>具体代码放在了 Github：<a href="https://github.com/BaiHLiu/AreWeMeetBefore">BaiHLiu&#x2F;AreWeMeetBefore</a></p><h3 id="结果分析"><a href="#结果分析" class="headerlink" title="结果分析"></a>结果分析</h3><p>使用 pandas 做了一些简单的分析。我希望找到的是<strong>相遇间隔最长</strong>的人脸，且图片为每秒截取一张，短时间内可能存在多张相同人脸出现。定义 $S_i$ 为人脸 $i$ 的得分，$\varDelta T_i$ 为最后与最早相遇时间之差，$C_i$ 为人脸 $i$ 出现的次数。直观的想法为 $S_i$ 与 $\varDelta T_i$ 成正比，与 $C_i$ 成反比。</p><p>进行归一化，定义归一化的时间跨度 $\hat{T}_i$ 和归一化的出现次数 $\hat{C}_i$ ：</p><p>$$\hat{T}i &#x3D; \frac{\Delta T_i - \min{j}(\Delta T_j)}{\max_{j}(\Delta T_j) - \min_{j}(\Delta T_j)}$$</p><p>$$\hat{C}i &#x3D; \frac{C_i - \min{j}(C_j)}{\max_{j}(C_j) - \min_{j}(C_j)}$$</p><p>人脸得分 $S_i$：</p><p>$$S_i &#x3D; \frac{\hat{T}_i}{\hat{C}_i}$$</p><p>去除同伴等误报后，发现最长间隔才 5 分多，差评。</p>]]></content>
    
    
    <summary type="html">前段时间去北京环球影城游玩，用DJI Action 4记录了30GB的游览视频。回家后突发奇想，能否通过人脸识别技术分析出在不同景点反复碰面的游客？随即使用InsightFace和Faiss向量数据库，构建了简单的视频人脸识别和时序分析工具，寻找那些与我们一样在环球影城转圈圈的陌生人。</summary>
    
    
    
    <category term="杂项" scheme="https://www.catop.top/categories/%E6%9D%82%E9%A1%B9/"/>
    
    
    <category term="数据分析" scheme="https://www.catop.top/tags/%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"/>
    
    <category term="InsightFace" scheme="https://www.catop.top/tags/InsightFace/"/>
    
  </entry>
  
  <entry>
    <title>论文阅读 - Fashion Faux Pas: Implicit Stylistic Fingerprints for Bypassing Browsers’ Anti-Fingerprinting Defenses</title>
    <link href="https://www.catop.top/2025/02/28/Fashion-Faux-Pas-Reading/"/>
    <id>https://www.catop.top/2025/02/28/Fashion-Faux-Pas-Reading/</id>
    <published>2025-02-28T09:00:01.000Z</published>
    <updated>2025-02-28T09:15:41.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p><strong>本文是我在《Web 追踪前沿》上论文阅读作业之一。</strong> Fashion Faux Pas: 绕过浏览器反指纹防御的隐性风格指纹。文章主要探索如何在不使用 JavaScript 的情况下生成浏览器指纹，以绕过现有浏览器的反指纹防御措施。核心在于利用不同环境对 HTML 元素渲染的差异性和 CSS 的媒体查询特性构造探针 HTML 元素，再配合 iframe 获取元素尺寸，从而反推出用户环境信息，据此提出了风格指纹的（Stylistic Fingerprints）概念。实验表明该方法在多款隐私导向型浏览器上均有效，与 FingerprintJS 的识别能力相当，且优于现有的 CSS 风格指纹。文章发表在 IEEE S&amp;P 2023，作者来自伊利诺伊大学芝加哥分校和 IBM 公司。</p></blockquote><h2 id="背景和动机"><a href="#背景和动机" class="headerlink" title="背景和动机"></a>背景和动机</h2><h3 id="浏览器指纹识别发展与问题"><a href="#浏览器指纹识别发展与问题" class="headerlink" title="浏览器指纹识别发展与问题"></a>浏览器指纹识别发展与问题</h3><p>越来越多的用户、法律法规关注到浏览器指纹引起的隐私问题。在现代隐私导向的浏览器环境中，传统的基于 JavaScript 的指纹有效性受到可靠性挑战，有许多反跟踪防御技术：</p><ol><li>限制特定 API 调用：例如 Tor 浏览器会阻塞 Canvas API，某些浏览器可能会限制 WebRTC 的调用。</li><li>随机化 API 返回值：例如 Brave 浏览器对 Canvas API 返回值进行随机化，WebGL、User-Agent 和硬件信息随机化等。</li><li>限制系统资源访问：Firefox 限制网站可使用的系统字体，防止网站利用字体信息识别用户设备。</li></ol><p>同时，新的指纹技术还需考虑性能开销问题，否则会阻碍其在现实世界中的部署。</p><h3 id="研究动机"><a href="#研究动机" class="headerlink" title="研究动机"></a>研究动机</h3><ul><li>浏览器在不同的环境中呈现的 HTML 元素是不同的，受浏览器、OS、屏幕尺寸、系统字体等影响。CSS 具备媒体查询、字体加载等能力。</li><li>鉴于现有反跟踪防御主要针对基于 JavaScript API 的指纹技术，本文探索如何在不使用 JS API 的情况下生成指纹，通过利用 HTML 和 CSS 特征来推断用户环境信息，提出隐式风格浏览器指纹（stylistic fingerprints）的概念。</li><li>证明追踪者可利用其他特征和隐式技术追踪用户，且不受现有先进防御措施的影响。</li></ul><h3 id="实现效果"><a href="#实现效果" class="headerlink" title="实现效果"></a>实现效果</h3><ul><li>有效性：对包括 Safari、Firefox、Brave、Tor 等隐私导向型浏览器做了实证分析，证明了其有效性。同时，进行了一项时长9周的真实世界部署，结果显示该方法与 FingerprintJS 效果相当。同时还计算了指纹特征的熵，表明其较高的辨别性。</li><li>高效性：在性能开销上与 FingerprintJS 相比对页面渲染影响可忽略不计，用户交互延迟小于 100ms。</li><li>全面性：比先前 CSS 技术收集数据更全面，且对浏览器防御措施更具鲁棒性。</li></ul><h2 id="设计与实现"><a href="#设计与实现" class="headerlink" title="设计与实现"></a>设计与实现</h2><h3 id="风格指纹"><a href="#风格指纹" class="headerlink" title="风格指纹"></a>风格指纹</h3><p><strong>风格指纹</strong>是由网页呈现器生成的视觉属性构建而成的特征，完全依赖 CSS 和 HTML 元素，而无需调用 Javascript API。想实现这一点，有几个重要挑战：</p><ul><li>须选择具有辨别能力的 HTML 元素，且需要在屏幕上有策略地排列，以保持稳定的指纹。</li><li>隐式方法推断特征可能会导致难以计数的网络请求，需要一种巧妙的设计。</li><li>需要一种有效的方法来编码 HTML 元素中的可用信息，以便服务器能够实际创建指纹。</li></ul><p><strong>CSS @media query 特性</strong></p><p>CSS3 中的 <code>@media</code> 查询可以针对不同显示媒介和屏幕尺寸定义不同的样式，例如指定屏幕宽度小于 300 px 时的样式：（📺 demo 01-css3-media-query）</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">@media</span> screen <span class="keyword">and</span> (<span class="attribute">max-width</span>: <span class="number">300px</span>) &#123;</span><br><span class="line">    <span class="selector-tag">body</span> &#123;</span><br><span class="line">        <span class="attribute">background-color</span>:lightblue;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>iframe 诱导的元素尺寸查询</strong></p><p><code>@media</code> 查询只能查询窗口或屏幕的尺寸，无法测定具体的 HTML 元素。因此通过引入 iframe 来诱使 <code>@media</code>测量元素的尺寸。</p><p>一个直观的实施方案为，每个 HTML 元素配合一个iframe使用，然而这样一来查询太多，对页面加载时间产生负面影响。因此采用对角排列元素来获得所有三个元素的尺寸之和。</p><p><img src="/usr/uploads/2025/02/Fashion-Faux-Pas-Reading/fig3.png" alt="fig3"></p><p>这样一来，通过置入大量<code>@media</code> 查询，从服务器的请求记录中就可以得知目标 HTML 元素的尺寸。</p><p><img src="/usr/uploads/2025/02/Fashion-Faux-Pas-Reading/listing1.png" alt="listing1"></p><p>【问题：】用 min-width 属性以升序排序，当一个匹配到之后，后面的尺寸就不用再测试了吧，如何停下来的呢？</p><h3 id="识别方法"><a href="#识别方法" class="headerlink" title="识别方法"></a>识别方法</h3><p>使用 25 个 iframe 和 339 个 HTML 元素，能够探测 30 种指纹特征，根据其指纹特征的类型分为四类：环境（Environment）、字体（Fonts）、广告拦截器（Ad blocker presence）、CSS 媒体属性（CSS media properties）。系统将这些探针元素都放置在一个 $800 \times 1000$ px 的 iframe 中，称为 main iframe。</p><p><img src="/usr/uploads/2025/02/Fashion-Faux-Pas-Reading/table1.png" alt="table1"></p><h4 id="Environment-识别"><a href="#Environment-识别" class="headerlink" title="Environment 识别"></a>Environment 识别</h4><p>浏览器在不同的环境中呈现的 HTML 元素是不同的，受浏览器、OS、屏幕尺寸、系统字体等影响。例如对于 <code>&lt;textarea&gt;</code> 元素，在 MacOS Monterey 的 Chrome v99 中呈现的尺寸为 430px&#x2F;150px，而在 Windows 11 中呈现为 432px&#x2F;162px，在 Ubuntu 18.04 为 348px&#x2F;145px。当浏览器版本发生变化（如 v93）时，这些尺寸也可能不同。文章使用了 101 种不同类型的 HTML 元素。</p><h4 id="Fonts-识别"><a href="#Fonts-识别" class="headerlink" title="Fonts 识别"></a>Fonts 识别</h4><p>对于字体识别，一种常见的方案是：枚举一些 <code>@font-face</code> 规则，为每个字体设置远程加载 URL，如果字体存在本地，则不会请求伪造的 URL。</p><p>这种方式会导致大量的 HTTP 请求，为了减少这种性能开销，作者开发了一种<strong>基于元素尺寸</strong>的新型字体指纹识别方法：为一个 <code>&lt;span&gt;</code> 元素分配一个 Tesing font family 以及两个 fallback fonts，这里选用 Arial Black 和 Arial 作为回退字体，因为 Arial Black 在多数系统中存在且比其他字体家族宽度大。当测试字体家族不可用时，元素会回退到 Arial 进行渲染；若测试字体可用，则元素不会使用回退字体且会以不同尺寸渲染，通过观察元素尺寸变化来判断字体是否可用。【📺 demo 02-fonts-query】</p><h4 id="Ad-blocker-识别"><a href="#Ad-blocker-识别" class="headerlink" title="Ad blocker 识别"></a>Ad blocker 识别</h4><p>使用 <code>&lt;img&gt;</code> 和 <code>&lt;div&gt;</code> 创造能命中常见广告拦截器（例如 AdBlock、AdGuard ）的元素，如果存在广告拦截器，则诱使广告拦截器删除该元素，不会请求对应 URL。</p><h4 id="CSS-media-properties-识别"><a href="#CSS-media-properties-识别" class="headerlink" title="CSS media properties 识别"></a>CSS media properties 识别</h4><p>一个例子如下图所示，如果对应 <code>media</code> 特性被满足，则页面上将出现一个宽度为 2px 的 HTML 元素。最后，通过 iframe 探测相关 HTML 元素的尺寸，即可了解有哪些元素被渲染，反推出浏览器支持哪些 <code>media</code> 特性。</p><p><img src="/usr/uploads/2025/02/Fashion-Faux-Pas-Reading/listing3.png" alt="listing3"></p><p>为优化性能，文章将构造的 HTML 元素根据它们所识别的属性进行分组，目的是最大限度地提高容器在辨别特定环境特征时的熵，并满足主 iframe 的高度限制。</p><p>对于同一组的 $n$ 个元素，支持对应属性则 $b_i&#x3D;1$ ，否则 $b_i&#x3D;0$ ，其总宽度或高度为</p><p>$$\sum_{i&#x3D;0}^{n-1} b_i * 2^i $$</p><p>总和是基于 2 的幂次的独特组合，因此可推断每个 $b_i$ 的值。</p><p>定义了 3 个检测 Level，尽可能用上 <code>@media</code> 支持的属性，实现细粒度地检测设备特征。</p><p><img src="/usr/uploads/2025/02/Fashion-Faux-Pas-Reading/tab6.png" alt="table6"></p><h3 id="方法部署"><a href="#方法部署" class="headerlink" title="方法部署"></a>方法部署</h3><p>有几种常见的方式：</p><ul><li>诱使用户访问专门的指纹识别网站</li><li>能够将一行 HTML 代码注入到合法网页中，使得用户响应中包含指纹识别负载</li><li>利用中间人代理服务在代理的网页响应中注入指纹代码。</li></ul><h2 id="实验结果"><a href="#实验结果" class="headerlink" title="实验结果"></a>实验结果</h2><h3 id="指纹识别防御的绕过"><a href="#指纹识别防御的绕过" class="headerlink" title="指纹识别防御的绕过"></a>指纹识别防御的绕过</h3><p>在 Firefox、Brave、Tor、Safari、Opera 等浏览器及相关隐私插件上进行测试，评估方法的抗指纹防御能力。</p><p><img src="/usr/uploads/2025/02/Fashion-Faux-Pas-Reading/tab3.png" alt="tab3"></p><h3 id="执行效率"><a href="#执行效率" class="headerlink" title="执行效率"></a>执行效率</h3><p>网络方面，对 <code>@font-face</code> 的优化以及合理排布构造的 HTML 元素，大大减小少了网络请求数量。经传输压缩后，传输的资源约为 330 KB。</p><p>客户端开销方面，与 FingerprintJS 进行了对比。发现本文方法在 domInteractive 时间方面（DOM 树已准备好，可进行用户交互）比 JS 方法耗时更短，但 domComplete 时间显著增加了，对页面的加载效率影响还是较大的。总体上整个页面的加载时间小于 1 秒。</p><p>【问题：在老旧设备上呢？毕竟实验是在i9 macbook 上做的。】</p><p><img src="/usr/uploads/2025/02/Fashion-Faux-Pas-Reading/fig2.png" alt="fig2"></p><h3 id="实测研究"><a href="#实测研究" class="headerlink" title="实测研究"></a>实测研究</h3><h4 id="总体效果"><a href="#总体效果" class="headerlink" title="总体效果"></a>总体效果</h4><p>作者在三个不同在线门户网站上部署了指纹识别系统，进行了为期9周的试点研究。用户对象主要是计算机科学家，且提前公布了这项研究计划，作者认为这些用户的隐私意识更强。结果表明提出的 StylisticFP 与 FingerprintJS 效果相当。</p><p><img src="/usr/uploads/2025/02/Fashion-Faux-Pas-Reading/tab5.png" alt="tab5"></p><h4 id="碰撞稳定性"><a href="#碰撞稳定性" class="headerlink" title="碰撞稳定性"></a>碰撞稳定性</h4><ul><li>跨访问稳定性方面：FPJS 在跨访问时因计算出不同指纹而无法识别 188 台设备，而本系统仅对 41 台设备失效，说明本系统在多次访问过程中更稳定，即相对不易因访问的变化而产生指纹的大幅变动，减少了因跨访问不稳定导致的识别失败情况。</li><li>跨设备稳定性方面：这种碰撞发生在具有软硬件的多个设备被分配相同指纹值的情况下，反映了在不同设备之间，由于系统的某些特性（如风格指纹相对稳定），可能会出现无法准确区分设备的情况。</li></ul><blockquote><p>💡 这里可以看出，指纹风格的稳定性需要找到一个合适的值。如果过于稳定，虽然在一定程度上保证了系统的可靠性和一致性，但会导致在相对同质的设备环境中出现较多碰撞，影响对设备的精确区分；而如果稳定性太差，像 FPJS 那样在跨访问时频繁改变指纹，又会导致大量设备无法被识别，降低系统的有效性。</p></blockquote><p>实际上，既然该实验场景存在能够唯一确定用户的会话（Session）信息，我觉得评价指纹与用户身份之间的对应关系时，可采用标准化互信息（Normalized Mutual Information, NMI）作为指标，即：</p><p>$$ NMI(S,F) &#x3D; \frac{2\cdot I(S;F)}{H(S) + H(F)} $$</p><p>$$ I(S;F) &#x3D; H(S) - H(S|F) &#x3D; H(F) - H(F|S) $$</p><p>其中，$I(S;F)$ 表示会话（Session）与指纹（Fingerprint）之间的互信息，反映两者共享的信息量，$H(S)$ 和 $H(F)$分别为会话和指纹的熵，表征各自的不确定性。</p><p>当指纹与会话完全一一对应时，条件熵 $H(S|F)$ 和 $H(F|S)$ 均为 $0$ ，此时 $NMI&#x3D;1$ ；若两者完全独立，则 $NMI&#x3D;0$ 。</p><h4 id="特征熵值"><a href="#特征熵值" class="headerlink" title="特征熵值"></a>特征熵值</h4><p>作者采用 AmIUnique 提出的归一化香农熵来量化各种指纹识别特征的判别能力，如表1所示。</p><p>总之，实验表明隐式风格指纹不仅是现有技术的可行替代品，而且具有足够的辨别力，可以在现有的防御措施面前胜过 FPJS。</p><h2 id="总结与思考"><a href="#总结与思考" class="headerlink" title="总结与思考"></a>总结与思考</h2><p>这篇文章巧妙利用了不同浏览器对 HTML 元素的显示差异，以及 CSS 中提供的媒体查询特性，将浏览器环境的属性差异转化为布局差异，再通过隐藏的 iframe 测量这些尺寸差异，将浏览器环境信息反馈给服务端并反推出属性。</p><p>这种不基于 Javascript 的新型指纹识别方法在对抗指纹防御方面有本质的优势，但 CSS 指纹识别能力较为局限，文章解决了其中几个关键问题：</p><ol><li>指纹属性局限性：先前 CSS 技术依赖的媒体特征有限，仅使用如 “any-pointer” 等常见的有限的媒体特征。本方法全面挖掘了 CSS 的潜力，支持23种媒体特征，在识别性方面达到 FPJS 的水准。<blockquote><p>为什么别人没想到或者用得少呢，是什么原因？</p></blockquote></li><li>性能问题：以往技术会产生大量网络请求，如某些方法生成 1347 个请求，而本方法通过优化设计（包括探针 HTML 元素的分组和幂次尺寸编码、字体 fallback 机制），显著减少了请求数量（优化后仅 83 个），降低网络占用。</li><li>抗防御能力不足：如 Tor 禁止 @font-face 本地文件使用和强制某些媒体查询返回固定值会使先前技术失效，而本方法使用隐式推断，能有效绕过部分防御。</li></ol><h3 id="未来工作"><a href="#未来工作" class="headerlink" title="未来工作"></a>未来工作</h3><h4 id="缓解措施"><a href="#缓解措施" class="headerlink" title="缓解措施"></a>缓解措施</h4><p>作者提到两种可能的缓解措施：</p><ol><li>完全阻止 iframe，但这会导致许多网站工作不正常。</li><li>阻止 media query，例如 Tor 就会报告一些虚假的媒体特性。然而欺骗所有媒体特性是不可行的，因为它们是响应式网页设计的关键部分。</li><li>动态监控向服务端的资源请求；或者随机向 CSS 属性添加噪声。</li></ol><blockquote><p>该工作没有开源，对于缓解措施方面难以实际测试。但基于对论文的直观理解，我认为该方法的特征还是比较明显的，具有一定可检测性，在“问题讨论”章节继续展开。</p></blockquote><h4 id="非追踪用例"><a href="#非追踪用例" class="headerlink" title="非追踪用例"></a>非追踪用例</h4><p>本研究侧重于风格指纹带来的隐私威胁，但浏览器指纹识别也可用于安全应用，如账户保护、风险控制等。</p><h3 id="问题讨论"><a href="#问题讨论" class="headerlink" title="问题讨论"></a>问题讨论</h3><h4 id="可检测性"><a href="#可检测性" class="headerlink" title="可检测性"></a>可检测性</h4><p>基于论文核心技术点，检测逻辑可以聚焦以下特征：</p><ol><li>iframe 内容可见性：大量透明、不可见背景的 iframe </li><li>密集媒体查询：大量 min-width&#x2F;min-height 的 CSS media query 规则</li><li>字体指纹：使用 @font-face 加载本地字体</li><li>广告拦截探针：包含触发多款广告拦截器的元素</li><li>网格布局：使用 CSS Grid 对角线排列元素</li></ol><p><strong>最明显的一点在于，</strong> 为了不影响网页正常显示，以上特征元素均置于同一个 iframe 中，即 $800 \times 1000$ px 的 <code>main iframe</code>。</p><p>然而在实际部署中，可以通过页面间随机化探针布置等方式减少过于集中的特征。最关键的在于，作者提供了一种新的“风格指纹”思路，证明追踪者利用隐式技术追踪用户是可行的。</p><h4 id="移动端可能显示异常"><a href="#移动端可能显示异常" class="headerlink" title="移动端可能显示异常"></a>移动端可能显示异常</h4><blockquote><p>原文 2.5-HTML Element Arrangement 小节中提到：“我们将所有元素放置在一个 800px x 1000px 的 iframe（以下简称主 iframe）中，以确保元素的尺寸在不同的屏幕分辨率下保持一致。”</p></blockquote><p>根据非权威统计数据显示，2024 年移动端浏览器典型分辨率为 $360 \times 800$ px，低于 main frame 所需尺寸，可能使浏览器出现非预期的滚动条，或者无法正常测量。</p><h2 id="其他思考"><a href="#其他思考" class="headerlink" title="其他思考"></a>其他思考</h2><ol><li><p>无 JavaScript 实施 XSS：</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">M. Heiderich, M. Niemietz, F. Schuster, T. Holz, and J. Schwenk, “Scriptless attacks: stealing the pie without touching the sill,” in Proc. ACM Conf. Computer and Communications Security, 2012.</span><br></pre></td></tr></table></figure><p> <a href="https://www.nds.rub.de/media/emma/veroeffentlichungen/2012/08/16/scriptlessAttacks-ccs2012.pdf">https://www.nds.rub.de/media/emma/veroeffentlichungen/2012/08/16/scriptlessAttacks-ccs2012.pdf</a></p><p> <a href="http://lcs.ios.ac.cn/~sws/downloads/liangbin.pptx">http://lcs.ios.ac.cn/~sws/downloads/liangbin.pptx</a></p></li><li><p>由 csstracking.dev 开源的 CSS 指纹项目：<a href="https://github.com/OliverBrotchie/CSS-Fingerprint">https://github.com/OliverBrotchie/CSS-Fingerprint</a></p></li></ol>]]></content>
    
    
    <summary type="html">本文是我在《Web 追踪前沿》上论文阅读作业之一。Fashion Faux Pas: 绕过浏览器反指纹防御的隐性风格指纹。文章主要探索如何在不使用 JavaScript 的情况下生成浏览器指纹，以绕过现有浏览器的反指纹防御措施。核心在于利用不同环境对 HTML 元素渲染的差异性和 CSS 的媒体查询特性构造探针 HTML 元素，再配合 iframe 获取元素尺寸，从而反推出用户环境信息，据此提出了风格指纹的（Stylistic Fingerprints）概念。实验表明该方法在多款隐私导向型浏览器上均有效，与 FingerprintJS 的识别能力相当，且优于现有的 CSS 风格指纹。文章发表在 IEEE S&amp;P 2023，作者来自伊利诺伊大学芝加哥分校和 IBM 公司。</summary>
    
    
    
    <category term="网络安全" scheme="https://www.catop.top/categories/%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A8/"/>
    
    
    <category term="浏览器指纹" scheme="https://www.catop.top/tags/%E6%B5%8F%E8%A7%88%E5%99%A8%E6%8C%87%E7%BA%B9/"/>
    
  </entry>
  
  <entry>
    <title>论文阅读 - CASIE: Extracting Cybersecurity Event Information from Text</title>
    <link href="https://www.catop.top/2025/01/15/CASIE-reading-report/"/>
    <id>https://www.catop.top/2025/01/15/CASIE-reading-report/</id>
    <published>2025-01-15T10:20:01.000Z</published>
    <updated>2025-05-13T01:27:24.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p><strong>本文是我在《机器学习》课程上完成的结课作业。</strong> CASIE聚焦于网络安全文本领域的命名实体识别和事件抽取，针对网络安全领域的关注点，对事件和类别做了针对性的特征工程。采用 BIO 标注体系，Bi-LSTM + Attention 作为基础模型，并对比了多种 Embedding 模型对效果的影响，具备很高的实用价值。作者来自马里兰大学，文章发表在 AAAI 2020。</p></blockquote><h2 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h2><p>现有的事件抽取方法主要关注人物相关的信息，这些方法与网络安全事件抽取的核心区别主要有：a) 所需领域专业知识不同；b) 事件本身复杂性不同。本文的主要贡献有：</p><ul><li>定义了五种网络安全事件及其语义角色，以及20种角色参数类型。</li><li>贡献了一套新闻通讯语料库，对网络安全事件进行了标注。</li><li>提出 CASIE 网络安全事件信息提取模型和工具。</li></ul><h2 id="问题建模"><a href="#问题建模" class="headerlink" title="问题建模"></a>问题建模</h2><p>在事件抽取步骤中，定义了如下概念：</p><ul><li>Event Nugget: 事件片段，指清晰描述事件的单词或短语。</li><li>Event Argument: 事件参数，指事件参与者或属性值。</li><li>Role: <code>Nugget</code> 和 <code>Argument</code> 之间的语义关系。</li><li>Realis: 具有 Actual、Other、Generic 三种取值。</li></ul><p>对于攻击和探测事件，定义的子类型有：<code>Attack.Databreach</code>、<code>Attack.Phishing</code>、<code>Attack.Ransom</code>、<code>Discover.Vulnerability</code>、<code>Patch.Vulnerability</code>。</p><h2 id="方法"><a href="#方法" class="headerlink" title="方法"></a>方法</h2><p>主要包括六个步骤：事件片段识别、事件参数识别、事件参数与角色的关联、事件真实性识别、事件核心参照、映射至知识图谱。本文主要聚焦于前四个步骤。</p><h3 id="事件片段及其参数识别（Event-Nugget-and-Event-Argument-Detection）"><a href="#事件片段及其参数识别（Event-Nugget-and-Event-Argument-Detection）" class="headerlink" title="事件片段及其参数识别（Event Nugget and Event Argument Detection）"></a>事件片段及其参数识别（Event Nugget and Event Argument Detection）</h3><h4 id="特征工程"><a href="#特征工程" class="headerlink" title="特征工程"></a>特征工程</h4><blockquote><p>对于一个 NLP 入门学习者来说，首先需要了解一些前置知识。包括：</p><ul><li><strong>浅层句法组块（Shallow Syntactic Chunk）</strong>：指对句子进行分析时，按照一定的语法规则和语义关系将连续的单词组合成的较大的语言单位。例如名词短语（NP）、动词短语（VP）、句子（S）。</li><li><strong>依赖树（ Dependency Tree）</strong>：表示单词之间的依存关系，例如在句子 “The boy kicks the ball” 中，“kicks” 是核心动词，“The boy” 是动作的执行者（通过 nsubj 依存关系连接），“the ball” 是动作的对象（通过 dobj 依存关系连接）。</li></ul></blockquote><p>在<strong>事件片段特征</strong>方面，首先使用斯坦福推出的 NLP 工具套件 <code>CoreNLP</code> 对原文进行预处理，包括分词、词形还原、词性标注和命名实体识别、停用词去除。为了更好地涵盖软件名称、恶意软件等实体，还引入了 DBpedia Spotlight 和 Wikidata 两个外部知识库。</p><p>在<strong>事件参数特征</strong>方面，结合了事件片段中的部分特征，并进一步细化和补充。例如确定每个单词的浅层句法组块类型及深度、最近事件类型等特征，使其能够更精准地定位和关联与事件相关的元素，具体特征如下：</p><ul><li>每个单词的浅层句法组块类型（如 S、NP、VP 等）及其深度（选择分析树中的最低层级）。</li><li>最近事件的类型（即五种事件类型）。</li><li>在依赖树中到最近事件核心词的距离（关系跳数）。</li><li>与最近事件核心词的相对位置（如在同一句子之后、在同一句子之前、在不同句子之后、在不同句子之前）。</li><li>到最近事件核心词的依存分析路径（如 conj - nmod - nsubj 等）。</li><li>与最近事件核心词的共同成分分析树节点（如 NP、PP、VP 等）。</li></ul><h4 id="词嵌入方法"><a href="#词嵌入方法" class="headerlink" title="词嵌入方法"></a>词嵌入方法</h4><p>作者尝试了比较多的 Embedding 方法，并在后续实验中进行了对比，包括：</p><ul><li>上下文无关性 Embedding：通过不同语料训练的 <code>Word2Vec</code>，包括<code>Transfer-Word2vec</code>、<code>Domain-word2vec</code>、<code>Cyber-Word2vec</code>。</li><li>上下文相关的 Embedding：<code>BERT-Base Uncased</code> 预训练模型。</li></ul><blockquote><p>值得注意的是，作者并没有直接使用 BERT 原始的输出作为最终的嵌入结果，而是经过实验对比，选择倒数第四个隐藏层的输出作为更优的词嵌入表征。这也是我们值得学习的思路。</p></blockquote><h4 id="模型结构"><a href="#模型结构" class="headerlink" title="模型结构"></a>模型结构</h4><p>将各个语义特征的 Embedding 值进行聚合，使用 Bi-LSTM + Attention 提取特征，经过一层带 Dropout 的全连接层后输出，再使用 CRF(条件随机场) 得到 B-I-O 序列标签，具体如下。</p><img src="/usr/uploads/2024/12/casie/fig3.png" width="65%" height="65%"><ul><li>Embedding Layer：把每个语言特征（包括单词序列，也包括POS、NER、Dependency等特征序列）的嵌入层进行聚合（Concatenate）。对每个特征，输出大小为类别总数的一半。对于单词序列，不同的单词就作为不同类别。</li><li>LSTM Layer：一层 Bi-LSTM 。</li><li>Attention Layer：使用 <strong>Location Attention</strong> 机制，属于传统 Attention 的变种，引入位置信息来增强注意力权重计算，通常会与内容注意力（Content-based Attention）结合使用，形成 <strong>Content-Location Attention</strong>。</li><li>CRF层：条件随机场，捕捉标签之间的依赖关系，提高序列标注的准确性。</li></ul><p>这里有一个问题，在原文中并没有详细说明如何计算的 Location Attention 分数，在开源的代码来看是调用了 keras 的 <code>SeqSelfAttention</code>，似乎没有体现 Location Attention。但不妨来学习一下，Location Attention 的注意力权重通常可表示为</p><p>$$ \alpha_t&#x3D;Softmax(f_{content}(x_t)+f_{location}(t)) $$</p><p>内容注意力比较熟悉：</p><p>$$ f_{content}(x_t)&#x3D;q_t^T k_i $$</p><p>位置注意力有多种运算方法，例如位置编码、卷积操作等。使用位置编码时，可表示为：</p><p>$$ f_{location}(t) &#x3D; v^T \cdot p_t $$</p><p>其中 $v$ 是可学习的线性变换权重矩阵，$p_t$ 是位置 $t$ 的编码向量。例如在 Transformer 中，位置编码通常使用正弦和余弦函数生成。</p><h3 id="事件参数与角色关联（Event-Argument-and-Role-Linking）"><a href="#事件参数与角色关联（Event-Argument-and-Role-Linking）" class="headerlink" title="事件参数与角色关联（Event Argument and Role Linking）"></a>事件参数与角色关联（Event Argument and Role Linking）</h3><p>为每个事件参数分配一个角色标签，如表1所示。</p><img src="/usr/uploads/2024/12/casie/tab1.png" width="35%" height="35%"><h4 id="特征工程-1"><a href="#特征工程-1" class="headerlink" title="特征工程"></a>特征工程</h4><p>提取以下特征：</p><ul><li>事件参数表面词的词向量</li><li>事件片段特征工程的(2)、(3)、(8)项，即 CoreNLP&#x2F;DBpedia 实体类别、Wikidata 相关类别、关系跳数（hops，取依赖关系树中到最近事件节点头的距离）。</li><li>目标事件参数类型、目标事件参数的左右侧事件参数类型。</li></ul><h4 id="模型结构-1"><a href="#模型结构-1" class="headerlink" title="模型结构"></a>模型结构</h4><p>包含一个嵌入层和三个全连接层，单词嵌入层经过两个全连接层，然后与其他特征的嵌入层连接。</p><p>由于在预测参数角色之前已经知道了事件类型，因此为每种事件建立单独的神经网络，从而排除无关类型。</p><img src="/usr/uploads/2024/12/casie/fig4.png" width="60%" height="60%"><h3 id="事件真实性识别（Event-Realis-Identification）"><a href="#事件真实性识别（Event-Realis-Identification）" class="headerlink" title="事件真实性识别（Event Realis Identification）"></a>事件真实性识别（Event Realis Identification）</h3><p>将事件真实性分为 Actual、Other、Generic。当找到事件片段（Nugget）后，realis 特征向量就是 Nugget 及其周围词向量的聚合。实验表明以7个单词为上下文窗口的效果最佳。因情态动词和否定词是事件真实性的重要证据，因此停用词也包含在 realis 识别中。</p><p>识别分为两步，首先识别是否为 Generic，若为 Generic 则进一步识别是 Actual 还是 Other。模型结构也较为简单，包括一层 Embedding 和两层全连接层。</p><img src="/usr/uploads/2024/12/casie/fig5.png" width="50%" height="50%"><h2 id="数据集及实验"><a href="#数据集及实验" class="headerlink" title="数据集及实验"></a>数据集及实验</h2><p>在数据集标注方面，选取了 5000 篇网络安全新闻（Cyberwire 2019），对其中包含文中所提到的五个事件的文章进行人工标注，约 1000 篇。</p><h3 id="评估指标"><a href="#评估指标" class="headerlink" title="评估指标"></a>评估指标</h3><p>采用为 TAC 事件任务（NIST 2015）开发的指标，通过计算事件要点或参数提及范围与真实范围的重叠来评估。</p><p>TAC KBP 2015 包含事件块（Event Nugget）任务和事件论元（Event Argument）任务，与本文一致。通过 Precision、Recall、F1 指标来评估。</p><h3 id="交叉验证"><a href="#交叉验证" class="headerlink" title="交叉验证"></a>交叉验证</h3><p>使用 900 篇文章进行 8 折交叉验证训练模型，并使用 100 篇文章进行测试，多次运行取平均分数。</p><h3 id="消融实验"><a href="#消融实验" class="headerlink" title="消融实验"></a>消融实验</h3><p>对 Nugget 和 Argument 检测的特征进行分组，进行消融实验，展示了不同特征集对检测结果的影响。</p><h3 id="对比多种-Embedding-方法"><a href="#对比多种-Embedding-方法" class="headerlink" title="对比多种 Embedding 方法"></a>对比多种 Embedding 方法</h3><p>对预训练的 BERT、Transfer Word2vec、Domain Word2vec、Cyber Word2vec 等不同词嵌入方法进行测试。结果表明，预训练 Bert 多项指标均高于 Word2vec。在 Word2vec 行列，Domain Word2vec 表现最佳。</p><img src="/usr/uploads/2024/12/casie/tab5.png" width="50%" height="50%"><h2 id="总结与思考"><a href="#总结与思考" class="headerlink" title="总结与思考"></a>总结与思考</h2><p>这篇文章属于深度学习时代的 NLP 典型任务在网络安全领域的应用，尽管在大模型时代来看，其需要额外附加外部知识、精心做特征选择。对我的启发主要有以下：</p><ul><li>领域针对性设计：传统事件抽取多关注人物相关信息，而本文针对网络安全领域，明确了其与通用领域在专业知识和事件复杂性上的差异，进而定义特定的网络安全事件、语义角色及角色参数类型。</li><li>分层特征设计：在不同任务步骤（如事件参数与角色关联、事件真实性识别）中，针对具体任务需求，设计与之匹配的特征，这种分层和针对性的特征设计思路，有助于模型在不同子任务中更好地捕捉关键信息。</li><li>词嵌入方法对比：尝试多种词嵌入方法，并对 Bert 输出层进行选择优化，而不是直接采用 Bert 的最终输出。</li><li>交叉验证与消融实验：通过8折交叉验证训练模型多次取平均分数，以及对特征分组进行消融实验，增强了实验的完备性。</li></ul>]]></content>
    
    
    <summary type="html">本文是我在《自然语言处理》课程上完成的论文阅读作业之一。CASIE聚焦于网络安全文本领域的命名实体识别和事件抽取，针对网络安全领域的关注点，对事件和类别做了针对性的特征工程。采用 BIO 标注体系，Bi-LSTM + Attention 作为基础模型，并对比了多种 Embedding 模型对效果的影响，具备很高的实用价值。作者来自马里兰大学，文章发表在 AAAI 2020。</summary>
    
    
    
    <category term="论文" scheme="https://www.catop.top/categories/%E8%AE%BA%E6%96%87/"/>
    
    
    <category term="深度学习" scheme="https://www.catop.top/tags/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/"/>
    
    <category term="NLP" scheme="https://www.catop.top/tags/NLP/"/>
    
    <category term="命名实体识别" scheme="https://www.catop.top/tags/%E5%91%BD%E5%90%8D%E5%AE%9E%E4%BD%93%E8%AF%86%E5%88%AB/"/>
    
    <category term="事件抽取" scheme="https://www.catop.top/tags/%E4%BA%8B%E4%BB%B6%E6%8A%BD%E5%8F%96/"/>
    
  </entry>
  
  <entry>
    <title>论文阅读 - Toolformer: Language Models Can Teach Themselves to Use Tools</title>
    <link href="https://www.catop.top/2024/12/21/toolformer-reading-report/"/>
    <id>https://www.catop.top/2024/12/21/toolformer-reading-report/</id>
    <published>2024-12-21T12:20:01.000Z</published>
    <updated>2025-05-13T01:27:20.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p><strong>本文是我在《自然语言处理》课程上完成的论文阅读作业之一。</strong> Toolformer 聚焦于提升 LLM 通过 API 调用外部工具的能力，提出了 Toolformer。通过对 API 文档和示例的自监督学习，模型可以在问题中有效地决定何时调用、调用何种工具、传入的参数、最优的结果。作者来自 Meta 公司和 Universitat Pompeu Fabra，发表在 NIPS 2023。</p><p>原文地址：<a href="https://arxiv.org/abs/2302.04761">arxiv&#x2F;2302.04761</a></p></blockquote><h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>大语言模型尽管在 <code>zero-shot</code> 和 <code>few-shot</code> 问题上有很大提升，但由于模型离线性和自回归本质等因素，其本身存在一些限制，包括：</p><ul><li>无法获取最新信息</li><li>存在幻觉现象</li><li>小规模LM的推理能力欠缺</li><li>缺乏数学运算能力</li><li>无法感知时间进程</li></ul><p>因此，常用的解决方案是让模型能够调用外部工具，用工具的输出来补充或改写上下文。然而，<strong>现有的方法主要有两大不足：依赖人工的大量标注，或者只局限于特定任务中</strong>。</p><blockquote><p>读到这里很容易想到 <code>Langchain</code> 框架，其也可以轻松地访问外部工具。我们在编写自定义工具时只需在注释中给出工具的简要介绍、参数定义，框架随后在遇到相关问题就会自动选取合适工具并调用。Langchain 应该主要对应提到的第一种不足。</p></blockquote><p>作者认为优秀的 API 工具选择器应具有以下特征：</p><ul><li>工具选择的学习应该是<strong>自监督</strong>的，不依赖大量的人工标注。因为人工标注不仅成本高，且<em>人类认为重要的内容不一定是模型认为重要的</em>。</li><li>外挂工具后，LM 应不丢失其通用性，并且可以自主决定何时、采用何工具。模型应能更全面地使用工具，而不局限于特定任务。</li></ul><p>文章最核心的部分，在于提出的<strong>自监督数据集的构建方案</strong>。首先对数据集中输入文本$x$进行位置划分，对于每个分割点$i$，选出Top $n$个候选 API $C_i^{j}$，随后分别执行得到结果$r_i^j$。分别计算执行结果与next tokens的损失$L_i^j$，若结果表明能降低损失，则保留该候选 API。</p><p><img src="/usr/uploads/2024/12/fig2.png" alt="关键步骤"></p><p>因此，只需要少量人工编写的 API 示例，LM 就可以构造大批工具调用数据集。最后，使用这些数据集微调模型即可。作者在 GPT-J 模型（参数量6.7B）上实验，结果表明大幅提升了模型 zero-shot 能力，在多个任务上超出了规模大很多倍的 GPT-3 模型。</p><h2 id="读前问题"><a href="#读前问题" class="headerlink" title="读前问题"></a>读前问题</h2><p>在继续阅读论文主体之前，有以下几个问题和思考：</p><ol><li><p>文章提到人工标注工具选择开销较大，那么什么时候才会遇到有大量API需要标注的情况？对于一个实际的大模型，外挂的每个工具 API 应该都需要单独开发的，像 Langchain 那样在 API 编写过程中加入少许提示即可。有没有与 Langchain 的定量对比？</p><p> <strong>读后回答</strong>：没有做这方面实验，文中主要在 GPT-J 上设计对比实验，证明 Toolformer 预训练有效，但未与其他调用工具的方式做对比。</p></li><li><p>”人类认为有用的提示“和”模型认为有用的提示“有何区别？有没有做这方面实验，如果有的话那对 Prompt 编写会是很好的启示。</p><p> <strong>读后回答</strong>：没有做这方面实验，因为数据集规模比较大（只一个QA问答数据集就接近20k条），人工编写难度大。</p></li><li><p>框架中分割位置$i$是如何确定的？</p><p> <strong>读后回答</strong>：通过提示工程，给出 Bootstrap Prompt，让模型自己判断。</p></li><li><p>Next Tokens的loss计算的窗口大小是多少？感觉也会影响到最终效果。</p><p> <strong>读后回答</strong>：是从当前位置$i$一直计算到序列结尾，即从$x_i,…,x_n$。每个位置的损失计算主要考虑在该位置进行 API 调用（有响应或无响应）与不进行 API 调用时模型对后续标记预测的影响，理论上是对每个 API 调用位置独立评估其对模型预测的帮助程度。</p><blockquote><p>因此，感觉当<strong>多个 API 调用的结果组合起来才能更好地帮助模型预测</strong>时，当前的损失计算方式可能无法完全捕捉到这种复杂关系。</p></blockquote></li><li><p>文中方法需要对模型进行 Fine-tune，对比提示工程来说效果怎样？</p><p> 读后回答：这方面没有作对比。</p></li></ol><h2 id="方法"><a href="#方法" class="headerlink" title="方法"></a>方法</h2><h3 id="数据集构建"><a href="#数据集构建" class="headerlink" title="数据集构建"></a>数据集构建</h3><p>文中通过自监督实现了 API 调用数据集构建，通过编写一段预提示（Exemplary Prompt，有的论文也叫 Bootstrap Prompt），让 LM 完成 API 调用位置选择，执行选出的 API 拿到结果，随后过滤得到有效的 API 调用。</p><blockquote><p>非常值得学习的是他们<strong>评估一个 API 调用是否有效的方法</strong>，通过比较加入 API 前后生成序列 Loss 的差异，来定量评估了效果。这比现在大模型工作的许多评估（WinRate、ELo、NLP指标等）更具表现力。此外，他们<strong>解决“何时调用”的方案</strong>也十分精巧，是直接获取解码序列每个时刻的输出 Top-K，设定开始符号的概率阈值来判断，这是 Langchain 等将模型完全视为黑盒的框架所做不到的。</p></blockquote><h4 id="预提示构建"><a href="#预提示构建" class="headerlink" title="预提示构建"></a>预提示构建</h4><p>Exemplary Prompt 如原文 Figure3 所示。</p><p><img src="/usr/uploads/2024/12/fig3.png" alt="Exemplary Prompt"></p><p>将 API 调用部分以特殊起止Token $\verb|&lt;API&gt;|\space\verb|&lt;&#x2F;API&gt;|$ 包裹。作者为实验可操作性起见，<strong>并没有因此而修改词表</strong>，而是将起止和结果符号用”[“、”]”、”-&gt;” 代替了。这点是我们值得借鉴的。</p><p>为了衡量加入 API 后是否对预测有帮助（即损失降低），定义了仅包含 API 调用、包含 API 调用及结果的提示词</p><p>$$ e(c) &#x3D; \verb|&lt;API&gt;| a_c (i_c) \verb|&lt;&#x2F;API&gt;| \space\space e(c,r) &#x3D; \verb|&lt;API&gt;|a_c(i_c)\rightarrow r \verb|&lt;&#x2F;API&gt;| $$</p><h4 id="Sampling-API-Calls"><a href="#Sampling-API-Calls" class="headerlink" title="Sampling API Calls"></a>Sampling API Calls</h4><p>也就是让模型决定哪里需要进行 API 调用，<strong>解决 “When” 的问题</strong>。具体步骤是：</p><ul><li>生成提示（Prompt）：对于每个 API ，根据 Exemplary Prompt 编写提示 $P(x)$，以鼓励 LM 在示例输入序列 $x&#x3D;x_1, … ,x_n$ 中添加 API 调用注释。</li><li>计算概率：计算模型 $M$ 在每个位置 $i\space (i\in{1, … ,n})$ 开始API调用的概率 $p_i &#x3D; p_m(\verb|&lt;API&gt;||P(x), x_{1:i-1})$。</li><li>确定候选位置：设置采样阈值$\tau_s$，保留所有$p_i &gt; \tau_s$的位置 $I&#x3D;{i | p_i &gt; \tau_s}$。如果得到位置超过$k$个，则仅保留前$k$个。</li><li>采样API调用：对于每个位置$i\in I$，以$[P(x), x1, …, x_{i-1},\verb|&lt;API&gt;|]$为前缀，$\verb|&lt;&#x2F;API&gt;|$为结束标记，形成最多m个 API 候选调用 $c_i^1,…, c_i^m$。</li></ul><h4 id="Executing-API-Calls"><a href="#Executing-API-Calls" class="headerlink" title="Executing API Calls"></a>Executing API Calls</h4><p>执行每个API $c_i$，得到结果$r_i$。</p><h4 id="Filtering-API-Calls"><a href="#Filtering-API-Calls" class="headerlink" title="Filtering API Calls"></a>Filtering API Calls</h4><ul><li>计算损失：对于序列 $\textbf{X}&#x3D;x_1, …, x_n$，有候选 API $c_i$ 及其响应 $r_i$，给定权重序列 $(w_i | i \in \mathbb{N} )$，计算两个加权损失 $L_i^+$ 和 $L_i^-$。简单来说，前者为<em>加入 API 调用及其结果后的损失</em>，后者为<em>不进行 API 调用和进行 API 调用但不提供响应这两种情况下损失的最小值</em>。直观上，如果$L_i^-$较大而$L_i^+$较小，说明进行 API 调用后更有帮助。</li><li>筛选有用的API调用：给定阈值$\tau_f$，保留$L_i^- - L_i^+ \geq \tau_f$的调用。</li></ul><h3 id="模型微调"><a href="#模型微调" class="headerlink" title="模型微调"></a>模型微调</h3><p>对构建的数据集进行微调，只对需要的位置进行 API 调用序列的插入，而不改变其他内容，以此保持模型的通用性。</p><h3 id="模型推理"><a href="#模型推理" class="headerlink" title="模型推理"></a>模型推理</h3><p>当解码时遇到$\rightarrow$ Token 时（即”-&gt;”），暂停输出，调用对应 API ，并将结果加入到解码序列中，继续完成解码。</p><h2 id="实验部分"><a href="#实验部分" class="headerlink" title="实验部分"></a>实验部分</h2><p>作者主要进行了<strong>三方面</strong>的实验：一是测试提出的 <strong>API 调用选择方法是否在下游任务中真正有效</strong>；二是验证该方法<strong>不会损害 LM 本身的核心能力</strong>；三是测试<strong>不同规模的模型对工具调用的影响</strong>。 </p><h3 id="选用工具"><a href="#选用工具" class="headerlink" title="选用工具"></a>选用工具</h3><p>在挑选所使用的工具时，遵循两个原则：一是输入输出都可以用文本表示，二是可以获知他们用途的一些演示。因此，作者选用了<em>问答系统、维基百科搜索、计算器、日历、机器翻译系统</em>这几类 API 来做测试。</p><h3 id="实验场景"><a href="#实验场景" class="headerlink" title="实验场景"></a>实验场景</h3><p>数据集选用 CCNet 子集 $\mathcal{C}$。CCNet 他是一个从网络抓取数据中提取高质量单语数据集，发表在 LREC 会议上：<a href="https://aclanthology.org/2020.lrec-1.494/">CCNet: Extracting High Quality Monolingual Datasets from Web Crawl Data</a>。作者采用“启发式”方法创建了 CCNet 子集，例如判断文本中是否包含三个操作数，来决定是否保留作为 Calculator 任务的数据集，从而大幅缩减数据量。</p><p>模型选用 GPT-J，是一个基于 GPT-3 的具备6B参数量的模型。截止2022年，它是目前公开可用的Transformer语言模型中，在各种下游zero-shot任务上表现最好的。<a href="https://huggingface.co/EleutherAI/gpt-j-6b">Hugging Face: GPT-J 6B</a>。文章还选用了规模大许多倍的 OPT（66B）和 GPT-3（175B）模型，来验证模型大小对工具调用的影响。</p><p>参数方面，设定权重函数 $w_t$，来确保 API 调用发生在 API 提供的信息对模型实际有帮助的位置附近。对于 API 的采样阈值 $\tau_s$和筛选阈值 $\tau_f$，每个工具是独立选择的，可见原文 Table 2。</p><p>对比实验做了三个场景：</p><ol><li>GPT-J + CC：用 CCNet 子集 $\mathcal{C}$ 微调的 GPT-J。</li><li>Toolformer Enabled：用文中方法制作的数据集 $\mathcal{C}^*$微调 的 GPT-J。</li><li>Toolformer Disabled：使用2中所得模型，但解码时屏蔽所有 API Call。这是通过手工将每个位置解码输出中 $\verb|&lt;API&gt;|$ Token 的概率改为0实现的。</li></ol><blockquote><p>这里设计的3个对比场景值得学习。不难看出，1和2对比主要是为了证明在微调过程中引入 API 调用是否能提升模型的性能，即模型是否能学习到在<strong>合适的时机</strong>调用<strong>合适的工具</strong>来辅助处理任务。2和3对比主要是证明调用 API 之后是否真正能提高模型解决问题的能力，进一步验证使用工具的必要性。</p></blockquote><p>在模型解码方面，采用贪婪解码，当 Top-10 Token中包含 $\verb|&lt;API&gt;|$ 时则直接选用 $\verb|&lt;API&gt;|$。</p><h3 id="效果评估"><a href="#效果评估" class="headerlink" title="效果评估"></a>效果评估</h3><ul><li><p>通用知识评估：使用 <code>LAMA Benchmark</code> 的 SQuAD, Google-RE, T-REx 三套数据集来评估，用来检测语言模型中包含了多少的事实类与常识类的知识。值得注意的是，由于 LAMA 基于直接从  Wikipedia 获取的语句，作者阻止了 Toolformer 使用 Wikipedia API，以避免获得不公平的优势。</p></li><li><p>数学能力评估：使用 <code>Math Benchmarks</code> 来评估，这是UC Berkeley提出的一个用于评估机器学习模型的数学问题解决能力的数据集。<br>  <img src="/usr/uploads/2024/12/tab3.png" alt="tab3"></p></li><li><p>问答能力评估：使用 <code>LAMA Benchmark</code> 的 WebQS、NQ、TriviaQA 三套数据集来评估。在一系列对比中，Toolformer 的成绩仅比 GPT-3 略低一筹。同时，还使用带有时间的数据集来评估其时间进程方面的能力，这一点超过了 GPT-3。<br>  <img src="/usr/uploads/2024/12/tab4.png" alt="tab4"></p></li><li><p>多语种问答：使用 <code>MLQA</code> 来评估多语种问答能力，即上下文以英文表示，而问题是其他语种（包括中文）。结果表明在某些语言上，使用Toolformer会使效果变差。但是每种语言的 Toolformer(disabled) 都比 Toolformer 低，说明 API 调用是有用的，作者认为<em>表明它已经学会使用机器翻译工具</em>。</p><p>  另外，作者认为OPT和GPT-3多语言能力较低的原因是没在多语言数据集上训练。最下面两组 All En 的测试证明了这一点。</p></li></ul><p>汇总结果如下，证明了 <strong>a. 调用工具API确实对提升问题解决能力有效; b. 使用 Toolformer 预训练的小规模模型在后续 Zero-shot 方面能力超过大模型 GPT-3。</strong></p><p><img src="/usr/uploads/2024/12/fig4.png" alt="fig4"></p><p>至此，文章完成了对于第一个方面的实验，即 <strong>Toolformer 方法有效提升了模型在下游任务中的能力。</strong> </p><p>对于是否会损害模型本身建模能力、不同规模模型对工具调用的影响，作者在4.3和4.4小节做了简要描述，结论如下：</p><ul><li>对模型训练的困惑度进行评估，表明 Toolformer 对模型本身建模能力影响不大。</li><li>提供工具的能力在约 775M 参数时才出现，较小模型使用和不使用工具性能相似。</li><li>模型规模增大时，不使用 API 调用解决任务的能力变好，同时利用 API 的能力也提高，但即使最大模型，使用和不使用 API 调用的预测之间仍有较大差距。</li></ul><h2 id="局限性"><a href="#局限性" class="headerlink" title="局限性"></a>局限性</h2><p>作者提到一个明显的局限是无法链式调用工具，因为每个 API Call 位置都是独立生成的。这点很好的解答了读前的问题4。同时，数据集生成的效率比较低，因为需要对每个文档单独进行提示工程推理，例如处理一百万份文档后，只能得到几千个有用的计算器 API 调用示例。最后，当前的 Toolformer 并未将调用 API 的成本纳入考虑。</p><h2 id="总结与思考"><a href="#总结与思考" class="headerlink" title="总结与思考"></a>总结与思考</h2><p>这篇文章我认为启发有以下几点：</p><ul><li>方法方面，结合提示工程设计了自监督的训练方案，使其可以用简单的 Loss 作为监督条件，来生成 Fine-tune 所用的数据集。在许多大模型 Fine-tune 的文章中，数据集生成是一个痛点，人工标注代价太大，而使用第三方大模型生成又无法保障质量。</li><li>技巧方面，对开源大模型的 Decoding 原始输出进行了利用和改造，没有将其完全作为黑盒来 Fine-tune。</li><li>评估方案方面，精巧的设计了几个对比实验，有力地证明了方法有效性。同时，还对方法本身之外的两个问题（是否导致困惑度增大、LM 规模与工具调用能力的关系）进行了测试，使结论更加充实。</li></ul><p>同时，关于本工作我还有自己的几点思考：</p><ul><li>该方案本身无法实现链式的工具调用，若想基于它实现的话，感觉可以尝试通过多轮对话的方式形成多个 API 调用点，从而构成简单的 CoT。</li><li>该方案需要对 LM 进行微调，使其获得选择 API 的能力。若模型规模特别大，微调将花费较大的时间和成本。除了使用更精简的微调技术外，感觉可以尝试大小模型协作的方案，例如使用提示工程让大模型初步分析可能的 API 调用位置，再让 Toolformer 小模型进行更精细的 API 调用决策和参数调整。</li><li>很希望将这个方法与纯 Prompt方式的（例如langchain）工具框架进行对比，看看哪种方式更有效。</li></ul>]]></content>
    
    
    <summary type="html">本文是我在《自然语言处理》课程上完成的论文阅读作业之一。Toolformer 聚焦于提升 LLM 通过 API 调用外部工具的能力，提出了 Toolformer。通过对 API 文档和示例的自监督学习，模型可以在问题中有效地决定何时调用、调用何种工具、传入的参数、最优的结果。作者来自 Meta 公司和 Universitat Pompeu Fabra，发表在 NIPS 2023。</summary>
    
    
    
    <category term="论文" scheme="https://www.catop.top/categories/%E8%AE%BA%E6%96%87/"/>
    
    
    <category term="深度学习" scheme="https://www.catop.top/tags/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/"/>
    
    <category term="LLM" scheme="https://www.catop.top/tags/LLM/"/>
    
  </entry>
  
  <entry>
    <title>Docker 常识查漏补缺</title>
    <link href="https://www.catop.top/2024/04/10/docker-pricinples/"/>
    <id>https://www.catop.top/2024/04/10/docker-pricinples/</id>
    <published>2024-04-09T16:00:01.000Z</published>
    <updated>2024-11-10T15:36:22.000Z</updated>
    
    <content type="html"><![CDATA[<p>Docker是一种轻量级的虚拟化技术，同时是一个开源的应用容器运行环境搭建平台。其解决了环境差异、依赖关系管理和部署一致性等问题，已经成为极为流行的技术。云原生技术的兴起，进一步强调了它的重要性。<br>笔者没有系统的学习过Docker，但日常却完全离不开Docker。在近期的几个项目中，发现了自己在这方面的诸多薄弱点，故回顾如下。</p><h2 id="1-pid与进程隔离"><a href="#1-pid与进程隔离" class="headerlink" title="1. pid与进程隔离"></a>1. pid与进程隔离</h2><h3 id="问题出现"><a href="#问题出现" class="headerlink" title="问题出现"></a>问题出现</h3><p>我意识到这个问题，是源于一次<code>elasticsearch</code>的使用。我在宿主机中安装了es，但现在需要另一个版本的es，为方便安装，就用docker启动了一个。</p><p>时隔多日，我忘记了自己在docker中还启动了一个es服务。当需要kill掉它们时，却发现<code>kill -9 &#123;pid&#125;</code>后，会自动重启。在宿主机上<code>ls</code>这个目录，也找不到对应的文件。<br><img src="https://www.catop.top/usr/uploads/2024/04/976972944.png" alt="ff7aa18d75eddd2cab0114fd8ae74306.png"></p><p>最后才想起来是自己Docker启动的es，令好友也倍感诧异…</p><p><img src="https://www.catop.top/usr/uploads/2024/04/441493129.png" alt="Pasted image 20240410163339.png"></p><p>甚至在Docker官方社区中，我们也能看到这样的讨论：</p><p><img src="https://www.catop.top/usr/uploads/2024/04/296755814.png" alt="Pasted image 20240410163541.png"></p><p>其实，在宿主机中使用<code>ps</code>&#x2F;<code>htop</code>等命令能看见Docker进程是正常的。这是由于Docker 是基于 Linux 内核的 <strong>Namespace</strong> 技术实现资源隔离的，<strong>所有的容器都共享主机的内核</strong>。</p><h3 id="什么是Namespace"><a href="#什么是Namespace" class="headerlink" title="什么是Namespace"></a>什么是Namespace</h3><p>​Namespace 是 Linux 内核的一项功能，该功能对<strong>内核资源进行分区</strong>，以使<strong>一组进程看到一组资源，而另一组进程看到另一组资源</strong>。Namespace 的工作方式通过为一组资源和进程设置相同的 Namespace 而起作用，但是这些 Namespace 引用了不同的资源。资源可能存在于多个 Namespace 中。这些资源可以是进程 ID、主机名、用户 ID、文件名、与网络访问相关的名称和进程间通信。</p><p>​ 简单来说，Namespace 是 Linux 内核的一个特性，该特性可以实现在同一主机系统中，对进程 <strong>ID、主机名、用户 ID、文件名、网络和进程间通信等资源的隔离</strong>。Docker 利用 Linux 内核的 Namespace 特性，<strong>实现了每个容器的资源相互隔离，从而保证容器内部只能访问到自己 Namespace 的资源</strong>。</p><p>Linux中定义了6种Namespace：</p><table><thead><tr><th>Namespace</th><th>作用</th><th>描述</th></tr></thead><tbody><tr><td>Mount</td><td>隔离文件系统挂载点</td><td>每个namespace都可以有不同的文件系统视图</td></tr><tr><td>PID</td><td>隔离进程ID</td><td>每个namespace可以有自己的进程空间，使得一个进程在不同namespace中可以有不同的PID</td></tr><tr><td>Network</td><td>隔离网络设备</td><td>每个namespace拥有自己的网络设备、IP地址、路由表等</td></tr><tr><td>IPC</td><td>隔离System V IPC和POSIX message queues</td><td>每个namespace有自己的IPC资源</td></tr><tr><td>UTS</td><td>隔离主机名和域名</td><td>每个namespace可以有自己的主机名和域名</td></tr><tr><td>User</td><td>隔离用户ID和组ID</td><td>每个namespace有自己的用户和用户组，使得在namespace内部，一个用户可以被视为root用户，而在namespace外部，该用户只是普通用户</td></tr></tbody></table><h3 id="Docker中对Namespace的使用"><a href="#Docker中对Namespace的使用" class="headerlink" title="Docker中对Namespace的使用"></a>Docker中对Namespace的使用</h3><p>在宿主机上，可以观察到Docker 守护进程为每个容器创建了<strong>六种 namespace 的实例</strong>，并且由内核管理这种映射关系。</p><p><img src="https://www.catop.top/usr/uploads/2024/04/3741086813.png" alt="Pasted image 20240410164701.png"></p><p>那么，这种映射关系是由谁创建的呢？Docker 的运行时组件（如 containerd、runc 等）负责创建和管理容器，在创建容器时会配置 PID namespace。</p><h3 id="Cgroups"><a href="#Cgroups" class="headerlink" title="Cgroups"></a>Cgroups</h3><p>Namespace提供了<strong>资源隔离</strong>，而Cgroup则可以说是提供了<strong>资源管理</strong>。</p><p>Linux Cgroup 可让为系统中所运行任务（进程）的用户定义组群分配资源 -–— 比如 CPU 时间、系统内存、网络带宽或者这些资源的组合。 您可以监控您配置的 cgroup，拒绝 cgroup 访问某些资源，甚至在运行的系统中动态配置您的 cgroup。 所以，可以将 controll groups 理解为 controller (system resource) (for) (process) groups，也就是是说它以<strong>一组进程为目标进行系统资源分配和控制</strong>。</p><h3 id="宿主机htop中排除Docker容器进程"><a href="#宿主机htop中排除Docker容器进程" class="headerlink" title="宿主机htop中排除Docker容器进程"></a>宿主机htop中排除Docker容器进程</h3><p>有了上述基础，实际上我们只需要排除指定的Cgroups就可以。</p><p>不妨按F2设置显示CGROUP名称，再F4即可。</p><p><img src="https://www.catop.top/usr/uploads/2024/04/825072777.png" alt="iShot_2024-04-10_17.05.16.png"><br><img src="https://www.catop.top/usr/uploads/2024/04/263473219.png" alt="iShot_2024-04-10_17.06.16.png"></p><h2 id="2-网络代理问题"><a href="#2-网络代理问题" class="headerlink" title="2. 网络代理问题"></a>2. 网络代理问题</h2><blockquote><p>来源：<a href="https://blog.csdn.net/vic_qxz/article/details/130061661"> victoruu: 配置docker pull代理</a></p></blockquote><h3 id="Docker-pull代理"><a href="#Docker-pull代理" class="headerlink" title="Docker pull代理"></a>Docker pull代理</h3><p>在执行<code>docker pull</code>时，如果网络环境较为复杂，我通常通过终端代理命令（例如<code>export https_proxy=http://127.0.0.1:1234</code>）来试图让pull过程走代理，实际上<strong>这是没有用的</strong>。</p><p>在执行<code>docker pull</code>时，是由守护进程<code>dockerd</code>来执行。因此，代理需要配在<code>dockerd</code>的环境中。而这个环境，则是受<code>systemd</code>所管控，因此实际是<code>systemd</code>的配置。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sudo mkdir -p /etc/systemd/system/docker.service.d</span><br><span class="line">sudo touch /etc/systemd/system/docker.service.d/proxy.conf</span><br></pre></td></tr></table></figure><p>在这个<code>proxy.conf</code>文件（可以是任意<code>*.conf</code>的形式）中，添加以下内容：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">[Service]</span><br><span class="line">Environment=&quot;HTTP_PROXY=http://127.0.0.1:8888/&quot;</span><br><span class="line">Environment=&quot;HTTPS_PROXY=http://127.0.0.1:8888/&quot;</span><br><span class="line">Environment=&quot;NO_PROXY=localhost,127.0.0.1,.example.com&quot;</span><br></pre></td></tr></table></figure><p><code>dockerd</code> 代理的修改比较特殊，它实际上是改 <code>systemd</code> 的配置，因此需要重载 <code>systemd</code> 并重启 <code>dockerd</code> 才能生效。</p><h3 id="容器内代理"><a href="#容器内代理" class="headerlink" title="容器内代理"></a>容器内代理</h3><p>在容器运行阶段，如果需要代理上网，则需要配置 <code>~/.docker/config.json</code>。以下配置，只在Docker 17.07及以上版本生效。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line"> &quot;proxies&quot;:</span><br><span class="line"> &#123;</span><br><span class="line">   &quot;default&quot;:</span><br><span class="line">   &#123;</span><br><span class="line">     &quot;httpProxy&quot;: &quot;http://127.0.0.1:8888&quot;,</span><br><span class="line">     &quot;httpsProxy&quot;: &quot;http://127.0.0.1.com:8888&quot;,</span><br><span class="line">     &quot;noProxy&quot;: &quot;localhost,127.0.0.1,.example.com&quot;</span><br><span class="line">   &#125;</span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>此外，容器的网络代理也可以直接在其运行时通过 <code>-e</code> 注入 <code>http_proxy</code> 等环境变量。这两种方法分别适合不同场景。<br><code>config.json</code> 非常方便，默认在所有配置修改后启动的容器生效，适合个人开发环境。在CI&#x2F;CD的自动构建环境、或者实际上线运行的环境中，这种方法就不太合适，用 <code>-e</code> 注入这种显式配置会更好，减轻对构建、部署环境的依赖。当然，在这些环境中，最好用良好的设计避免配置代理上网。</p><h3 id="Build代理"><a href="#Build代理" class="headerlink" title="Build代理"></a>Build代理</h3><p>虽然 <code>docker build</code> 的本质，也是启动一个容器，但是环境会略有不同，用户级配置无效。在构建时，需要注入 <code>http_proxy</code> 等参数。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">docker build . \</span><br><span class="line">    --build-arg &quot;HTTP_PROXY=http://proxy.example.com:8080/&quot; \</span><br><span class="line">    --build-arg &quot;HTTPS_PROXY=http://proxy.example.com:8080/&quot; \</span><br><span class="line">    --build-arg &quot;NO_PROXY=localhost,127.0.0.1,.example.com&quot; \</span><br><span class="line">    -t your/image:tag</span><br></pre></td></tr></table></figure><blockquote><p>参考来源：<br> <a href="https://cizixs.com/2017/08/29/linux-namespace/">https://cizixs.com/2017/08/29/linux-namespace/</a><br> <a href="https://developer.aliyun.com/article/1406336">https://developer.aliyun.com/article/1406336</a><br> <a href="https://blog.csdn.net/vic_qxz/article/details/130061661">https://blog.csdn.net/vic_qxz/article/details/130061661</a></p></blockquote>]]></content>
    
    
    <summary type="html">Docker是一种轻量级的虚拟化技术，同时是一个开源的应用容器运行环境搭建平台。其解决了环境差异、依赖关系管理和部署一致性等问题，已经成为极为流行的技术。云原生技术的兴起，进一步强调了它的重要性。笔者没有系统的学习过Docker，但日常却完全离不开Docker。在近期的几个项目中，发现了自己在这方面的诸多薄弱点，故回顾如下。重点介绍了PID和Namespace的概念及其在Docker中的应用，进一步分析了Cgroups的作用，以及如何在宿主机的htop中排除Docker容器进程。接着，讨论了Docker的网络代理问题，包括Docker pull、容器内代理和构建时的代理配置等问题。</summary>
    
    
    
    <category term="开发" scheme="https://www.catop.top/categories/%E5%BC%80%E5%8F%91/"/>
    
    
    <category term="Docker" scheme="https://www.catop.top/tags/Docker/"/>
    
    <category term="技巧" scheme="https://www.catop.top/tags/%E6%8A%80%E5%B7%A7/"/>
    
  </entry>
  
  <entry>
    <title>Nginx以HTTP反向代理HTTPS的Exchange邮件服务</title>
    <link href="https://www.catop.top/2024/01/17/nginx-proxy-exchange-mail-with-http/"/>
    <id>https://www.catop.top/2024/01/17/nginx-proxy-exchange-mail-with-http/</id>
    <published>2024-01-16T16:00:01.000Z</published>
    <updated>2024-11-10T12:45:46.000Z</updated>
    
    <content type="html"><![CDATA[<p>笔者使用Nginx反向代理时，上游服务<strong>强制启用</strong>了HTTPS访问，但我们的需求是以HTTP统一对外提供服务。</p><p>经过一些探索，发现问题主要来源于<code>上游应用302跳转</code>、set-cookie响应头的<code>secure</code>属性两方面，需要合理调整Nginx的站点配置文件来解决。</p><blockquote><p>This post shows how to proxy HTTPS Exchange Mail service with HTTP protocol when using Nginx reverse proxy. The key point is to handle <code>302 redirect</code> and <code>secure</code> attribute in configuration of Nginx.</p></blockquote><p>文章参考了 <a href="https://dhyuan.github.io/2021/04/07/micro_service/http_nginx_to_https_upstream/">浅流 - Nginx以HTTP反向代理HTTPS服务</a> 这篇文章，但其对Nginx的<code>more_set_headers</code>属性设置有问题，导致<code>set-cookie</code>头从<code>secure</code>属性的后面截断，在具有多个<code>set-cookie</code>响应头的登录场景中不适用。</p><hr><h2 id="问题发现"><a href="#问题发现" class="headerlink" title="问题发现"></a>问题发现</h2><p>问题背景参考原文，用以下配置运行 Ngnix， 使其用 HTTP 协议在 9080 端口反向代理 19026 上的 HTTPS 服务。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">server &#123;</span><br><span class="line">    listen       9080;</span><br><span class="line">    server_name  10.115.6.165;</span><br><span class="line"></span><br><span class="line">    location /databoard/ &#123;</span><br><span class="line">        proxy_pass  https://10.115.6.165:19026/databoard/;</span><br><span class="line">        proxy_set_header Host $host;</span><br><span class="line">        proxy_set_header X-Real-IP $remote_addr;</span><br><span class="line">        proxy_set_header X-Forwarded-For    $proxy_add_x_forwarded_for;</span><br><span class="line">        proxy_set_header REMOTE-HOST        $remote_addr;</span><br><span class="line">        proxy_set_header X-Forwarded-Proto  $scheme;</span><br><span class="line">        proxy_redirect off;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>但是如果我们用浏览器访问 <a href="http://10.115.6.165:9080/databoard/login">http://10.115.6.165:9080/databoard/login</a> ,就会发现如下图所示的两问题：</p><p><img src="https://www.catop.top/usr/uploads/2024/01/2197484739.png" alt="image.png"></p><h2 id="后端服务使用-redirect-重定向导致的问题"><a href="#后端服务使用-redirect-重定向导致的问题" class="headerlink" title="后端服务使用 redirect 重定向导致的问题"></a>后端服务使用 redirect 重定向导致的问题</h2><p>浏览器地址栏上显示被重定向到了<a href="https://10.115.6.165/databoard/dataCmder">https://10.115.6.165/databoard/dataCmder</a> .</p><p>这是因为<code>后端Web应用</code>执行了redirect重定向语句，而重定向的协议、地址是基于web应用上下文的，而nginx并没有做特别的处理就转发给了浏览器，浏览器自然不能访问到这个地址。解决办法如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">map $upstream_http_Location $location &#123;</span><br><span class="line">  ~https://10.115.6.165/(?&lt;param&gt;.*) http://10.115.6.165:9080/$param;</span><br><span class="line">  default $upstream_http_Location;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">server &#123;</span><br><span class="line">    ... ...</span><br><span class="line">    location /databoard/ &#123;</span><br><span class="line">        ... ...</span><br><span class="line">        more_set_headers -s &#x27;301 302&#x27; &#x27;Location $location&#x27;;</span><br></pre></td></tr></table></figure><h2 id="Cookie-携带-Secure-属性问题"><a href="#Cookie-携带-Secure-属性问题" class="headerlink" title="Cookie 携带 Secure 属性问题"></a>Cookie 携带 Secure 属性问题</h2><p>Cookie的<code>Secure</code>属性，意味着保持Cookie通信<strong>只限于加密传输</strong>，指示浏览器仅仅在通过安全&#x2F;加密连接才能使用该Cookie，而我们的需求是以HTTP方式传送。如果不去掉，浏览器会提示不接受这个Cookie。</p><p><img src="https://www.catop.top/usr/uploads/2024/01/2544383994.png" alt="image-1.png"></p><p>对于该问题，原文采用的方案是通过nginx的<code>more_set_headers</code>模块，通过<code>map</code>中正则表达式对<code>Set-Cookie</code>进行改写。</p><p>但该方案会导致<code>Set-Cookie</code>直接从<code>secure</code>属性的前面截断，如果<code>secure</code>属性在中间，或者是有多个<code>Set-Cookie</code>属性时，就无法适用。</p><p>经过查阅 Nginx官方文档中的 <a href="https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cookie_flags">Module ngx_http_proxy_module</a> ，发现从nginx 1.19.3开始，加入了<code>proxy_cookie_flags</code>的directive，恰好可以去掉<code>secure</code>属性并且加入<code>samesite</code>属性（如果不加入samesite属性，浏览器一样会拒绝）。</p><p><img src="https://www.catop.top/usr/uploads/2024/01/3488945778.png" alt="image-2.png"></p><p>因此，完整配置如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">map $upstream_http_Location $location &#123;</span><br><span class="line">  ~https://10.115.6.165/(?&lt;param&gt;.*) http://10.115.6.165:9080/$param;</span><br><span class="line">  default $upstream_http_Location;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">map $sent_http_set_cookie $resp_cookie &#123;</span><br><span class="line">    ~*(?&lt;CK_WITHOUT_SECURE&gt;.+)Secure $CK_WITHOUT_SECURE;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">server &#123;</span><br><span class="line">    listen       9080;</span><br><span class="line">    server_name  10.115.6.165;</span><br><span class="line"></span><br><span class="line">    location /databoard/ &#123;</span><br><span class="line">        proxy_pass  https://10.115.6.165:19026/databoard/;</span><br><span class="line"></span><br><span class="line">        proxy_set_header Host $host;</span><br><span class="line">        proxy_set_header X-Real-IP $remote_addr;</span><br><span class="line">        proxy_set_header X-Forwarded-For    $proxy_add_x_forwarded_for;</span><br><span class="line">        proxy_set_header REMOTE-HOST        $remote_addr;</span><br><span class="line">        proxy_set_header X-Forwarded-Proto  $scheme;</span><br><span class="line">        proxy_redirect off;</span><br><span class="line"></span><br><span class="line">        more_set_headers -s &#x27;301 302&#x27; &#x27;Location $location&#x27;;</span><br><span class="line">        </span><br><span class="line">        # 主要看下面的部分</span><br><span class="line">        # more_set_headers &#x27;Set-Cookie: $resp_cookie&#x27;;        # 取消原文中的替换</span><br><span class="line">        proxy_cookie_flags ~ nosecure samesite=strict;        # 调整为官方的方法</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>成功去掉了<code>secure</code>属性，加上了<code>samesite</code>属性。</p><p><img src="https://www.catop.top/usr/uploads/2024/01/2260938241.png" alt="image-5.png"></p><p>但是，需要注意的<code>more_set_headers</code>仅在Nginx 1.19.3以上才支持，因此您需要升级Nginx版本，以使用该方案。</p><hr><h2 id="Ubuntu编译安装新版本Nginx并加入相关模块支持"><a href="#Ubuntu编译安装新版本Nginx并加入相关模块支持" class="headerlink" title="Ubuntu编译安装新版本Nginx并加入相关模块支持"></a>Ubuntu编译安装新版本Nginx并加入相关模块支持</h2><p>如果不清楚如何升级自己的Nginx，可以参考以下。</p><p>执行 <code>apt search nginx</code> 会发现Ubuntu22.04的apt源中nginx版本太老，为1.18.0，不能满足我们的需求。</p><p><img src="https://www.catop.top/usr/uploads/2024/01/404312417.png" alt="image-3.png"></p><p>因此需要从源码编译安装，需要注意的是添加OpenSSL模块（不然无法代理HTTPS的服务）、并且添加 <code>headers-more-nginx-module</code> 这个附加模块。 (<a href="https://github.com/openresty/headers-more-nginx-module">https://github.com/openresty/headers-more-nginx-module</a>)</p><p>使用以下命令进行编译配置。</p><p><img src="https://www.catop.top/usr/uploads/2024/01/1686660133.png" alt="image-4.png"></p><p>完成后 <code>make &amp;&amp; sudo make install</code> 即可。</p><p>安装完毕之后， 默认路径在 <code>/usr/local/nginx/sbin/nginx</code>，因此可以 <code>sudo ln -s /usr/local/nginx/sbin/nginx /usr/bin/nginx</code> 建立一个软链接。</p><p>编译安装的nginx默认没有<code>sites-enabled</code>这个目录，可以手工在其conf目录新建一个，并且在<code>nginx.conf</code>中引入:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"># nginx.conf</span><br><span class="line"></span><br><span class="line">events &#123;</span><br><span class="line">    worker_connections  1024;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">http &#123;</span><br><span class="line">    include       mime.types;</span><br><span class="line">    default_type  application/octet-stream;</span><br><span class="line"></span><br><span class="line">    ...</span><br><span class="line">    ...</span><br><span class="line"></span><br><span class="line">    include /usr/local/nginx/conf/sites-enabled/*;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>随后将其注册为 <code>systemd</code> 服务，在 <code>/etc/systemd/system</code> 新建一个 <code>nginx.service</code> :</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"># /etc/systemd/system/nginx.service</span><br><span class="line"></span><br><span class="line">[Unit]</span><br><span class="line">Description=nginx - high performance web server</span><br><span class="line">After=network.target remote-fs.target nss-lookup.target</span><br><span class="line"></span><br><span class="line">[Service]</span><br><span class="line">Type=forking</span><br><span class="line">PIDFile=/usr/local/nginx/logs/nginx.pid</span><br><span class="line">ExecStart=/usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf</span><br><span class="line">ExecReload=/usr/local/nginx/sbin/nginx -s reload</span><br><span class="line">ExecStop=/usr/local/nginx/sbin/nginx -s stop</span><br><span class="line">PrivateTmp=true</span><br><span class="line"></span><br><span class="line">[Install]</span><br><span class="line">WantedBy=multi-user.target</span><br></pre></td></tr></table></figure><p>记得 <code>sudo systemctl daemon-reload</code> 重载服务，再 <code>sudo systemctl start nginx</code> 即可运行。</p>]]></content>
    
    
    <summary type="html">笔者使用Nginx反向代理时，上游服务**强制启用**了HTTPS访问，但我们的需求是以HTTP统一对外提供服务。本文介绍了如何通过Nginx反向代理实现HTTP服务，来处理强制启用HTTPS的上游邮件服务（如Exchange邮件服务）。在反向代理过程中，主要面临两个问题：上游服务通过302重定向到HTTPS，以及Set-Cookie响应头中的secure属性。为了解决这些问题，作者查阅了Nginx官方文档并发现，从Nginx 1.19.3版本起，proxy_cookie_flags指令可以移除secure属性并添加samesite属性，避免浏览器拒绝cookie。文中还介绍了如何通过编译安装更新版本的Nginx，并配置相关模块来支持这一功能。通过调整Nginx的站点配置文件，成功实现了HTTPS到HTTP的反向代理，解决了与cookie和重定向相关的问题。</summary>
    
    
    
    <category term="杂项" scheme="https://www.catop.top/categories/%E6%9D%82%E9%A1%B9/"/>
    
    
    <category term="Nginx" scheme="https://www.catop.top/tags/Nginx/"/>
    
    <category term="Exchange" scheme="https://www.catop.top/tags/Exchange/"/>
    
    <category term="邮件服务" scheme="https://www.catop.top/tags/%E9%82%AE%E4%BB%B6%E6%9C%8D%E5%8A%A1/"/>
    
    <category term="服务部署" scheme="https://www.catop.top/tags/%E6%9C%8D%E5%8A%A1%E9%83%A8%E7%BD%B2/"/>
    
  </entry>
  
  <entry>
    <title>FRP 0.38.0 流量加密分析</title>
    <link href="https://www.catop.top/2023/12/08/frp-encryption-analysis/"/>
    <id>https://www.catop.top/2023/12/08/frp-encryption-analysis/</id>
    <published>2023-12-07T16:00:01.000Z</published>
    <updated>2024-11-10T12:49:02.000Z</updated>
    
    <content type="html"><![CDATA[<p>FRP是一款开源的轻量级<strong>反向代理</strong>工具，可快速、稳定地代理NAT或者防火墙后面的服务，应用较为广泛。其使用Go语言编写，具备很好的跨平台特性。<br>FRP仓库地址：<a href="https://github.com/fatedier/frp">https://github.com/fatedier/frp</a></p><hr><p>由于网络上几乎没有分析frp协议及其加密机制的文章，而且frp每个版本的加密逻辑还不一样（例如0.38.0与0.52.0），笔者跟了一遍源码，简单记下这篇文章，供相关从业者参考。</p><p>##结论</p><ul><li>加密算法：<code>AES-CFB</code></li><li>iv：首次发送报文时向对方传递</li><li>salt：固定盐值</li><li>会话密钥：用配置文件里的<code>token</code>以<code>pbkdf2</code>算法派生</li></ul><hr><h2 id="分析过程"><a href="#分析过程" class="headerlink" title="分析过程"></a>分析过程</h2><p>首先下载源码，搜索关键字<code>encrypt</code>，发现一处可能与加密相关的地方，跟进去。<br><img src="https://www.catop.top/usr/uploads/2023/12/2871784142.png" alt="image-2.png"><br>发现引用了frpIo这个包，而这个包是从github引用，去它仓库克隆下代码来。<br><img src="https://www.catop.top/usr/uploads/2023/12/2829346685.png" alt="image-3.png"><br>很明显<code>WithEncryption</code>就是加密逻辑的入口：传入密钥，对读写函数进行封装。<br><img src="https://www.catop.top/usr/uploads/2023/12/1481191496.png" alt="image-4.png"><br>跟进<code>NewReader()</code>函数，找到具体的加密逻辑：<br><img src="https://www.catop.top/usr/uploads/2023/12/3435671940.png" alt="image-5.png"><br>    + 第一步，使用<code>pbkdf2</code>算法从主密钥派生出会话密钥。需要的参数及寻找位置如下：<br>        + 主密钥：从frp代码的<code>/server/control.go</code>中发现，主密钥就是<code>serverCfg.Token</code>，也就是我们配置文件<code>frps.ini</code>Common中的<code>token</code>字段。<br>  <img src="https://www.catop.top/usr/uploads/2023/12/2086378295.png" alt="image-6.png"><br>        + 盐值：从frp代码的<code>/server/main.go</code>入口函数中找到，默认是<code>&quot;frp&quot;</code>。<br>  <img src="https://www.catop.top/usr/uploads/2023/12/844212081.png" alt="image-7.png"><br>        + 迭代次数：固定64。<br>        + 输出长度：与aes每个block长度一致，默认为16字节。<br>        + 哈希函数：固定为sha1。</p><pre><code>+ 第二步，生成iv向量。虽然代码注释上写着“random iv”，但查看代码发现，iv只初始化了大小，没有赋值，因此固定是16字节的0.**当时狠狠坑了我好长时间**+ 第三步，AES CFB加密。这点需要了解基础的密码学常识，在CFB模式下首部不能有多余字符，否则分组错乱，整个密文解开都是错的。而在抓包分析时，经常有使用0或者其他标志填充首部，这点也容易导致解密失败。**编写解密脚本时需要注意。**</code></pre><h2 id="解密脚本编写"><a href="#解密脚本编写" class="headerlink" title="解密脚本编写"></a>解密脚本编写</h2><p>到此为止，整个加密逻辑就很清楚了。可以参照源码，用go编写解密脚本如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Decrypt</span><span class="params">(token, ciphertext, iv []<span class="type">byte</span>)</span></span> ([]<span class="type">byte</span>, <span class="type">error</span>) &#123;</span><br><span class="line"><span class="comment">// 根据你的需求处理 token，用作密钥</span></span><br><span class="line">key := pbkdf2.Key(token, []<span class="type">byte</span>(DefaultSalt), <span class="number">64</span>, aes.BlockSize, sha1.New)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建 AES 加密块</span></span><br><span class="line">block, err := aes.NewCipher(key)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, err</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 检查密文长度是否有效</span></span><br><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(ciphertext) == <span class="number">0</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, fmt.Errorf(<span class="string">&quot;empty ciphertext&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用 CFB 解密模式</span></span><br><span class="line">stream := cipher.NewCFBDecrypter(block, iv)</span><br><span class="line">stream.XORKeyStream(ciphertext, ciphertext)</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> ciphertext, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>总结来说，frp是用配置文件里的<code>token</code>以<code>pbkdf2</code>算法派生会话密钥；首次发送报文时向对方传递<code>iv</code>；使用固定值作为<code>salt</code>；采用AES-CFB模式对流量进行加密。</p>]]></content>
    
    
    <summary type="html">FRP是一款开源的轻量级**反向代理**工具，可快速、稳定地代理NAT或者防火墙后面的服务，应用较为广泛。其使用Go语言编写，具备很好的跨平台特性。由于网络上几乎没有分析frp协议及其加密机制的文章，而且frp每个版本的加密逻辑还不一样（例如0.38.0与0.52.0），笔者跟了一遍源码，简单记下这篇文章，供相关从业者参考。</summary>
    
    
    
    <category term="网络安全" scheme="https://www.catop.top/categories/%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A8/"/>
    
    
    <category term="FRP" scheme="https://www.catop.top/tags/FRP/"/>
    
    <category term="源码分析" scheme="https://www.catop.top/tags/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"/>
    
    <category term="加密流量" scheme="https://www.catop.top/tags/%E5%8A%A0%E5%AF%86%E6%B5%81%E9%87%8F/"/>
    
  </entry>
  
  <entry>
    <title>蓝牙键盘流量包分析和还原</title>
    <link href="https://www.catop.top/2023/11/21/traffic-analysis-of-bluetooth-keyboard/"/>
    <id>https://www.catop.top/2023/11/21/traffic-analysis-of-bluetooth-keyboard/</id>
    <published>2023-11-20T16:00:01.000Z</published>
    <updated>2024-11-10T12:52:32.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>蓝牙键盘的按键还原与普通USB键盘有些许区别，但是在网上只找到了一个很好用的USB键盘流量包解析脚本，没有找到蓝牙键盘的。随后稍微研究了一下，加入蓝牙键盘解析的支持。</p></blockquote><p>脚本已放到Github：<a href="https://github.com/BaiHLiu/Bluetooth-UsbKeyboardDataHacker">https://github.com/BaiHLiu/Bluetooth-UsbKeyboardDataHacker</a></p><h2 id="工具说明"><a href="#工具说明" class="headerlink" title="工具说明"></a>工具说明</h2><ul><li>抓包工具：Wireshark 4.2.0</li><li>系统环境：Windows 11</li><li>键盘型号：罗技 K585</li><li>脚本运行：Ubuntu18.04</li></ul><h2 id="流量分析"><a href="#流量分析" class="headerlink" title="流量分析"></a>流量分析</h2><p>普通USB键盘数据包的数据长度为 8 个字节，击键信息集中在第 3 个字节。但对蓝牙ATT流量进行分析后发现，击键信息应该是在第 2 个字节。<br><img src="https://www.catop.top/usr/uploads/2023/11/1336511944.png" alt="iShot_2023-11-21_10.58.41.png"><br><img src="https://www.catop.top/usr/uploads/2023/11/1735189329.png" alt="iShot_2023-11-21_10.58.49.png"></p><h2 id="脚本改造"><a href="#脚本改造" class="headerlink" title="脚本改造"></a>脚本改造</h2><p>主要需要修改tshark过滤字段，以及下面解析字节时的位置。<br><img src="https://www.catop.top/usr/uploads/2023/11/3854744287.png" alt="image-1.png"></p><p>测试后发现所有键盘、Shift键等均可正确解析。</p><h2 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> sys</span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line"></span><br><span class="line">DataFileName = <span class="string">&quot;usb.dat&quot;</span></span><br><span class="line"></span><br><span class="line">presses = []</span><br><span class="line"></span><br><span class="line">normalKeys = &#123;<span class="string">&quot;04&quot;</span>:<span class="string">&quot;a&quot;</span>, <span class="string">&quot;05&quot;</span>:<span class="string">&quot;b&quot;</span>, <span class="string">&quot;06&quot;</span>:<span class="string">&quot;c&quot;</span>, <span class="string">&quot;07&quot;</span>:<span class="string">&quot;d&quot;</span>, <span class="string">&quot;08&quot;</span>:<span class="string">&quot;e&quot;</span>, <span class="string">&quot;09&quot;</span>:<span class="string">&quot;f&quot;</span>, <span class="string">&quot;0a&quot;</span>:<span class="string">&quot;g&quot;</span>, <span class="string">&quot;0b&quot;</span>:<span class="string">&quot;h&quot;</span>, <span class="string">&quot;0c&quot;</span>:<span class="string">&quot;i&quot;</span>, <span class="string">&quot;0d&quot;</span>:<span class="string">&quot;j&quot;</span>, <span class="string">&quot;0e&quot;</span>:<span class="string">&quot;k&quot;</span>, <span class="string">&quot;0f&quot;</span>:<span class="string">&quot;l&quot;</span>, <span class="string">&quot;10&quot;</span>:<span class="string">&quot;m&quot;</span>, <span class="string">&quot;11&quot;</span>:<span class="string">&quot;n&quot;</span>, <span class="string">&quot;12&quot;</span>:<span class="string">&quot;o&quot;</span>, <span class="string">&quot;13&quot;</span>:<span class="string">&quot;p&quot;</span>, <span class="string">&quot;14&quot;</span>:<span class="string">&quot;q&quot;</span>, <span class="string">&quot;15&quot;</span>:<span class="string">&quot;r&quot;</span>, <span class="string">&quot;16&quot;</span>:<span class="string">&quot;s&quot;</span>, <span class="string">&quot;17&quot;</span>:<span class="string">&quot;t&quot;</span>, <span class="string">&quot;18&quot;</span>:<span class="string">&quot;u&quot;</span>, <span class="string">&quot;19&quot;</span>:<span class="string">&quot;v&quot;</span>, <span class="string">&quot;1a&quot;</span>:<span class="string">&quot;w&quot;</span>, <span class="string">&quot;1b&quot;</span>:<span class="string">&quot;x&quot;</span>, <span class="string">&quot;1c&quot;</span>:<span class="string">&quot;y&quot;</span>, <span class="string">&quot;1d&quot;</span>:<span class="string">&quot;z&quot;</span>,<span class="string">&quot;1e&quot;</span>:<span class="string">&quot;1&quot;</span>, <span class="string">&quot;1f&quot;</span>:<span class="string">&quot;2&quot;</span>, <span class="string">&quot;20&quot;</span>:<span class="string">&quot;3&quot;</span>, <span class="string">&quot;21&quot;</span>:<span class="string">&quot;4&quot;</span>, <span class="string">&quot;22&quot;</span>:<span class="string">&quot;5&quot;</span>, <span class="string">&quot;23&quot;</span>:<span class="string">&quot;6&quot;</span>,<span class="string">&quot;24&quot;</span>:<span class="string">&quot;7&quot;</span>,<span class="string">&quot;25&quot;</span>:<span class="string">&quot;8&quot;</span>,<span class="string">&quot;26&quot;</span>:<span class="string">&quot;9&quot;</span>,<span class="string">&quot;27&quot;</span>:<span class="string">&quot;0&quot;</span>,<span class="string">&quot;28&quot;</span>:<span class="string">&quot;&lt;RET&gt;&quot;</span>,<span class="string">&quot;29&quot;</span>:<span class="string">&quot;&lt;ESC&gt;&quot;</span>,<span class="string">&quot;2a&quot;</span>:<span class="string">&quot;&lt;DEL&gt;&quot;</span>, <span class="string">&quot;2b&quot;</span>:<span class="string">&quot;\t&quot;</span>,<span class="string">&quot;2c&quot;</span>:<span class="string">&quot;&lt;SPACE&gt;&quot;</span>,<span class="string">&quot;2d&quot;</span>:<span class="string">&quot;-&quot;</span>,<span class="string">&quot;2e&quot;</span>:<span class="string">&quot;=&quot;</span>,<span class="string">&quot;2f&quot;</span>:<span class="string">&quot;[&quot;</span>,<span class="string">&quot;30&quot;</span>:<span class="string">&quot;]&quot;</span>,<span class="string">&quot;31&quot;</span>:<span class="string">&quot;\\&quot;</span>,<span class="string">&quot;32&quot;</span>:<span class="string">&quot;&lt;NON&gt;&quot;</span>,<span class="string">&quot;33&quot;</span>:<span class="string">&quot;;&quot;</span>,<span class="string">&quot;34&quot;</span>:<span class="string">&quot;&#x27;&quot;</span>,<span class="string">&quot;35&quot;</span>:<span class="string">&quot;&lt;GA&gt;&quot;</span>,<span class="string">&quot;36&quot;</span>:<span class="string">&quot;,&quot;</span>,<span class="string">&quot;37&quot;</span>:<span class="string">&quot;.&quot;</span>,<span class="string">&quot;38&quot;</span>:<span class="string">&quot;/&quot;</span>,<span class="string">&quot;39&quot;</span>:<span class="string">&quot;&lt;CAP&gt;&quot;</span>,<span class="string">&quot;3a&quot;</span>:<span class="string">&quot;&lt;F1&gt;&quot;</span>,<span class="string">&quot;3b&quot;</span>:<span class="string">&quot;&lt;F2&gt;&quot;</span>, <span class="string">&quot;3c&quot;</span>:<span class="string">&quot;&lt;F3&gt;&quot;</span>,<span class="string">&quot;3d&quot;</span>:<span class="string">&quot;&lt;F4&gt;&quot;</span>,<span class="string">&quot;3e&quot;</span>:<span class="string">&quot;&lt;F5&gt;&quot;</span>,<span class="string">&quot;3f&quot;</span>:<span class="string">&quot;&lt;F6&gt;&quot;</span>,<span class="string">&quot;40&quot;</span>:<span class="string">&quot;&lt;F7&gt;&quot;</span>,<span class="string">&quot;41&quot;</span>:<span class="string">&quot;&lt;F8&gt;&quot;</span>,<span class="string">&quot;42&quot;</span>:<span class="string">&quot;&lt;F9&gt;&quot;</span>,<span class="string">&quot;43&quot;</span>:<span class="string">&quot;&lt;F10&gt;&quot;</span>,<span class="string">&quot;44&quot;</span>:<span class="string">&quot;&lt;F11&gt;&quot;</span>,<span class="string">&quot;45&quot;</span>:<span class="string">&quot;&lt;F12&gt;&quot;</span>&#125;</span><br><span class="line"></span><br><span class="line">shiftKeys = &#123;<span class="string">&quot;04&quot;</span>:<span class="string">&quot;A&quot;</span>, <span class="string">&quot;05&quot;</span>:<span class="string">&quot;B&quot;</span>, <span class="string">&quot;06&quot;</span>:<span class="string">&quot;C&quot;</span>, <span class="string">&quot;07&quot;</span>:<span class="string">&quot;D&quot;</span>, <span class="string">&quot;08&quot;</span>:<span class="string">&quot;E&quot;</span>, <span class="string">&quot;09&quot;</span>:<span class="string">&quot;F&quot;</span>, <span class="string">&quot;0a&quot;</span>:<span class="string">&quot;G&quot;</span>, <span class="string">&quot;0b&quot;</span>:<span class="string">&quot;H&quot;</span>, <span class="string">&quot;0c&quot;</span>:<span class="string">&quot;I&quot;</span>, <span class="string">&quot;0d&quot;</span>:<span class="string">&quot;J&quot;</span>, <span class="string">&quot;0e&quot;</span>:<span class="string">&quot;K&quot;</span>, <span class="string">&quot;0f&quot;</span>:<span class="string">&quot;L&quot;</span>, <span class="string">&quot;10&quot;</span>:<span class="string">&quot;M&quot;</span>, <span class="string">&quot;11&quot;</span>:<span class="string">&quot;N&quot;</span>, <span class="string">&quot;12&quot;</span>:<span class="string">&quot;O&quot;</span>, <span class="string">&quot;13&quot;</span>:<span class="string">&quot;P&quot;</span>, <span class="string">&quot;14&quot;</span>:<span class="string">&quot;Q&quot;</span>, <span class="string">&quot;15&quot;</span>:<span class="string">&quot;R&quot;</span>, <span class="string">&quot;16&quot;</span>:<span class="string">&quot;S&quot;</span>, <span class="string">&quot;17&quot;</span>:<span class="string">&quot;T&quot;</span>, <span class="string">&quot;18&quot;</span>:<span class="string">&quot;U&quot;</span>, <span class="string">&quot;19&quot;</span>:<span class="string">&quot;V&quot;</span>, <span class="string">&quot;1a&quot;</span>:<span class="string">&quot;W&quot;</span>, <span class="string">&quot;1b&quot;</span>:<span class="string">&quot;X&quot;</span>, <span class="string">&quot;1c&quot;</span>:<span class="string">&quot;Y&quot;</span>, <span class="string">&quot;1d&quot;</span>:<span class="string">&quot;Z&quot;</span>,<span class="string">&quot;1e&quot;</span>:<span class="string">&quot;!&quot;</span>, <span class="string">&quot;1f&quot;</span>:<span class="string">&quot;@&quot;</span>, <span class="string">&quot;20&quot;</span>:<span class="string">&quot;#&quot;</span>, <span class="string">&quot;21&quot;</span>:<span class="string">&quot;$&quot;</span>, <span class="string">&quot;22&quot;</span>:<span class="string">&quot;%&quot;</span>, <span class="string">&quot;23&quot;</span>:<span class="string">&quot;^&quot;</span>,<span class="string">&quot;24&quot;</span>:<span class="string">&quot;&amp;&quot;</span>,<span class="string">&quot;25&quot;</span>:<span class="string">&quot;*&quot;</span>,<span class="string">&quot;26&quot;</span>:<span class="string">&quot;(&quot;</span>,<span class="string">&quot;27&quot;</span>:<span class="string">&quot;)&quot;</span>,<span class="string">&quot;28&quot;</span>:<span class="string">&quot;&lt;RET&gt;&quot;</span>,<span class="string">&quot;29&quot;</span>:<span class="string">&quot;&lt;ESC&gt;&quot;</span>,<span class="string">&quot;2a&quot;</span>:<span class="string">&quot;&lt;DEL&gt;&quot;</span>, <span class="string">&quot;2b&quot;</span>:<span class="string">&quot;\t&quot;</span>,<span class="string">&quot;2c&quot;</span>:<span class="string">&quot;&lt;SPACE&gt;&quot;</span>,<span class="string">&quot;2d&quot;</span>:<span class="string">&quot;_&quot;</span>,<span class="string">&quot;2e&quot;</span>:<span class="string">&quot;+&quot;</span>,<span class="string">&quot;2f&quot;</span>:<span class="string">&quot;&#123;&quot;</span>,<span class="string">&quot;30&quot;</span>:<span class="string">&quot;&#125;&quot;</span>,<span class="string">&quot;31&quot;</span>:<span class="string">&quot;|&quot;</span>,<span class="string">&quot;32&quot;</span>:<span class="string">&quot;&lt;NON&gt;&quot;</span>,<span class="string">&quot;33&quot;</span>:<span class="string">&quot;:&quot;</span>,<span class="string">&quot;34&quot;</span>:<span class="string">&quot;\&quot;&quot;</span>,<span class="string">&quot;35&quot;</span>:<span class="string">&quot;&lt;GA&gt;&quot;</span>,<span class="string">&quot;36&quot;</span>:<span class="string">&quot;&lt;&quot;</span>,<span class="string">&quot;37&quot;</span>:<span class="string">&quot;&gt;&quot;</span>,<span class="string">&quot;38&quot;</span>:<span class="string">&quot;?&quot;</span>,<span class="string">&quot;39&quot;</span>:<span class="string">&quot;&lt;CAP&gt;&quot;</span>,<span class="string">&quot;3a&quot;</span>:<span class="string">&quot;&lt;F1&gt;&quot;</span>,<span class="string">&quot;3b&quot;</span>:<span class="string">&quot;&lt;F2&gt;&quot;</span>, <span class="string">&quot;3c&quot;</span>:<span class="string">&quot;&lt;F3&gt;&quot;</span>,<span class="string">&quot;3d&quot;</span>:<span class="string">&quot;&lt;F4&gt;&quot;</span>,<span class="string">&quot;3e&quot;</span>:<span class="string">&quot;&lt;F5&gt;&quot;</span>,<span class="string">&quot;3f&quot;</span>:<span class="string">&quot;&lt;F6&gt;&quot;</span>,<span class="string">&quot;40&quot;</span>:<span class="string">&quot;&lt;F7&gt;&quot;</span>,<span class="string">&quot;41&quot;</span>:<span class="string">&quot;&lt;F8&gt;&quot;</span>,<span class="string">&quot;42&quot;</span>:<span class="string">&quot;&lt;F9&gt;&quot;</span>,<span class="string">&quot;43&quot;</span>:<span class="string">&quot;&lt;F10&gt;&quot;</span>,<span class="string">&quot;44&quot;</span>:<span class="string">&quot;&lt;F11&gt;&quot;</span>,<span class="string">&quot;45&quot;</span>:<span class="string">&quot;&lt;F12&gt;&quot;</span>&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    <span class="comment"># check argv</span></span><br><span class="line">    <span class="keyword">if</span> <span class="built_in">len</span>(sys.argv) != <span class="number">2</span>:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;Usage : &quot;</span>)</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;        python UsbKeyboardHacker.py data.pcap&quot;</span>)</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;Tips : &quot;</span>)</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;        To use this python script , you must install the tshark first.&quot;</span>)</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;        You can use `sudo apt-get install tshark` to install it&quot;</span>)</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;Author : &quot;</span>)</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;        WangYihang &lt;wangyihanger@gmail.com&gt;&quot;</span>)</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;        If you have any questions , please contact me by email.&quot;</span>)</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;        Thank you for using.&quot;</span>)</span><br><span class="line">        exit(<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># get argv</span></span><br><span class="line">    pcapFilePath = sys.argv[<span class="number">1</span>]</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># get data of pcap</span></span><br><span class="line">    os.system(<span class="string">&quot;tshark -r %s -Y &#x27;bthci_acl &amp;&amp; btatt&#x27; -T fields -e btatt.value &gt; %s&quot;</span> % (pcapFilePath, DataFileName))</span><br><span class="line"></span><br><span class="line">    <span class="comment"># read data</span></span><br><span class="line">    <span class="keyword">with</span> <span class="built_in">open</span>(DataFileName, <span class="string">&quot;r&quot;</span>) <span class="keyword">as</span> f:</span><br><span class="line">        <span class="keyword">for</span> line <span class="keyword">in</span> f:</span><br><span class="line">            presses.append(line[<span class="number">0</span>:-<span class="number">1</span>])</span><br><span class="line">    <span class="comment"># handle</span></span><br><span class="line">    result = <span class="string">&quot;&quot;</span></span><br><span class="line">    <span class="keyword">for</span> press <span class="keyword">in</span> presses:</span><br><span class="line">        <span class="keyword">if</span> press == <span class="string">&#x27;&#x27;</span>:</span><br><span class="line">            <span class="keyword">continue</span></span><br><span class="line">        <span class="keyword">if</span> <span class="string">&#x27;:&#x27;</span> <span class="keyword">in</span> press:</span><br><span class="line">            Bytes = press.split(<span class="string">&quot;:&quot;</span>)</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            Bytes = [press[i:i+<span class="number">2</span>] <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0</span>, <span class="built_in">len</span>(press), <span class="number">2</span>)]</span><br><span class="line">        <span class="keyword">if</span> Bytes[<span class="number">0</span>] == <span class="string">&quot;00&quot;</span>:</span><br><span class="line">            <span class="keyword">if</span> Bytes[<span class="number">1</span>] != <span class="string">&quot;00&quot;</span> <span class="keyword">and</span> normalKeys.get(Bytes[<span class="number">1</span>]):</span><br><span class="line">                result += normalKeys[Bytes[<span class="number">1</span>]]</span><br><span class="line">        <span class="keyword">elif</span> <span class="built_in">int</span>(Bytes[<span class="number">0</span>],<span class="number">16</span>) &amp; <span class="number">0b10</span> <span class="keyword">or</span> <span class="built_in">int</span>(Bytes[<span class="number">0</span>],<span class="number">16</span>) &amp; <span class="number">0b100000</span>: <span class="comment"># shift key is pressed.</span></span><br><span class="line">            <span class="keyword">if</span> Bytes[<span class="number">1</span>] != <span class="string">&quot;00&quot;</span> <span class="keyword">and</span> normalKeys.get(Bytes[<span class="number">1</span>]):</span><br><span class="line">                result += shiftKeys[Bytes[<span class="number">1</span>]]</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&quot;[-] Unknow Key : %s&quot;</span> % (Bytes[<span class="number">0</span>]))</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;[+] Found : %s&quot;</span> % (result))</span><br><span class="line"></span><br><span class="line">    <span class="comment"># clean the temp data</span></span><br><span class="line">    os.system(<span class="string">&quot;rm ./%s&quot;</span> % (DataFileName))</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    main()</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">蓝牙键盘的按键还原与普通USB键盘有些许区别，但是在网上只找到了一个很好用的USB键盘流量包解析脚本，没有找到蓝牙键盘的。随后稍微研究了一下，加入蓝牙键盘解析的支持。</summary>
    
    
    
    <category term="网络安全" scheme="https://www.catop.top/categories/%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A8/"/>
    
    
    <category term="蓝牙键盘" scheme="https://www.catop.top/tags/%E8%93%9D%E7%89%99%E9%94%AE%E7%9B%98/"/>
    
    <category term="无线流量" scheme="https://www.catop.top/tags/%E6%97%A0%E7%BA%BF%E6%B5%81%E9%87%8F/"/>
    
  </entry>
  
</feed>
