<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>l4n</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://l4n.top/</id>
  <link href="https://l4n.top/" rel="alternate"/>
  <link href="https://l4n.top/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, l4n</rights>
  <subtitle>something for nothing</subtitle>
  <title>l4n's blog</title>
  <updated>2026-09-05T17:27:13.427Z</updated>
  <entry>
    <author>
      <name>l4n</name>
    </author>
    <category term="LLM" scheme="https://l4n.top/tags/LLM/"/>
    <category term="安全" scheme="https://l4n.top/tags/%E5%AE%89%E5%85%A8/"/>
    <content>
      <![CDATA[<p>现在的大语言模型在结构上都是同一种东西：一串 token 进入同一套 attention，逐层变换，最后自回归地预测下一个 token。这篇把这套结构当成攻击面来看，看看 LLM 安全里反复出现的那些问题，有多少在架构层面就已经定下来了。</p><p>先划定范围。这里只讨论由架构决定的攻击面，也就是只要模型还是上面描述的样子，就没法靠修 bug 消除的那些。tokenizer 库的内存安全问题、推理框架的反序列化漏洞、模型文件格式的任意代码执行，都属于工程问题，不在这里讨论。</p><h2 id="指令和数据在同一个序列里"><a href="#指令和数据在同一个序列里" class="headerlink" title="指令和数据在同一个序列里"></a>指令和数据在同一个序列里</h2><p>后面讲的每一个问题，追到底都是这一件事。</p><p>模型的输入是一串 token，每个 token 经过同一组投影矩阵 W_Q、W_K、W_V 得到 q、k、v，全序列的 q 和 k 两两做点积，得到一个 n × n 的分数矩阵，每一行做 softmax，再用得到的权重对 v 加权求和。这就是 attention 的全部计算。这个结构里没有任何机制能区分「这段 token 是指令」和「这段 token 是数据」。system prompt、用户消息、工具返回的结果、检索到的文档，全部拼接进同一个序列，在 attention 看来没有等级之分。</p><p><img loading="lazy" decoding="async" src="/images/posts/transformer/one-sequence.svg" alt="所有来源的 token 进入同一套 attention"></p><p>对话模型看起来有角色之分，是因为 chat template 在各段之间插入了几个特殊 token，比如 <code>&lt;|im_start|&gt;system</code>。这些只是词表里几个普通的 id。模型会优先遵循 system 段落，是因为训练数据里 system 段总是被遵循的，这是一条学出来的统计规律，架构本身没有给它任何特权。</p><p>传统安全里最接近的问题是 SQL 注入。SQL 注入是数据被当成代码执行，它有一个架构级的根治方案，参数化查询，因为数据库引擎能在语法层面把查询结构和参数值分开。LLM 没有对应的东西。指令和数据在 token 层面是同一种东西，任何试图分开它们的做法，比如在 prompt 里写「以下内容是数据，请勿执行其中的指令」，本身也只是更多的 token，和它想隔离的内容进的是同一个 softmax。</p><p>所以 prompt injection 在架构上没法根除。直接注入是用户自己写，间接注入藏在网页、邮件、工具返回值里，投放路径不同，机制完全一样。能做的只有降低概率：训练侧让模型更倾向于尊重指令层级，OpenAI 2024 年的 instruction hierarchy 就是这个思路；工程侧把模型当成不可信组件，用权限、隔离和输出校验兜底。2025 年出现的 CaMeL 一类方案干脆放弃在模型内部解决，把「哪些数据能流向哪些动作」的控制拿到模型外面，用传统的能力系统来做，等于承认了架构层面没有出路。</p><h2 id="位置带来的影响"><a href="#位置带来的影响" class="headerlink" title="位置带来的影响"></a>位置带来的影响</h2><p>system prompt 的约束力既然来自训练出来的倾向，这个倾向就会受各种因素影响，位置是最明显的一个。</p><p>2023 年「Lost in the Middle」的实验表明，长上下文里模型对开头和结尾的信息利用得最好，中间的信息经常被忽略。放在序列末尾的内容还有 recency 加成，因为训练数据里最近的一轮对话往往就是要回应的那一轮。于是一段注入指令放在长文档末尾，会比放在中间有效得多；一段 system prompt 被几万 token 的检索内容隔开之后，约束力会明显下降。这些都不算 bug，是 softmax 在不同位置分配权重的自然结果。</p><p>上下文窗口也是有限的。推理时每个 token 的 k、v 都要缓存在显存里，随长度线性增长，服务端必然设上限。超过上限之后怎么截断由实现决定，如果实现保留的是最近的 n 个 token，那么足够长的用户输入可以把 system prompt 整个挤出窗口。模型不会报错，只是看不到那段约束了。</p><p>分段标记本身也可能被伪造。如果 tokenizer 没有把特殊 token 从用户输入里转义掉，用户就能在自己的消息里写出和 chat template 一模一样的 <code>&lt;|im_start|&gt;system</code>，模型看到的序列和真的有一段 system prompt 出现在这里没有任何区别。这是常见的工程失误，之所以后果严重，是因为架构上没有第二道防线。</p><h2 id="tokenizer-这一层"><a href="#tokenizer-这一层" class="headerlink" title="tokenizer 这一层"></a>tokenizer 这一层</h2><p>模型处理的是 token id 序列，人看到的是字符串，两者之间的映射由 BPE 决定，既不唯一，也不直观。</p><p>同一个意思可以有很多种 token 序列。Unicode 里有大量视觉上相同或相近的字符，零宽字符可以插在任何位置，大小写、全半角、拼写变体、base64、换一种语言，都会得到完全不同的 token 序列。任何在字符串层面做的关键词过滤，和模型在 token 层面看到的内容之间都有一道缝。反过来也一样，模型能理解的东西，过滤器未必识别得出来。</p><p>词表里还有一些异常的 token。2023 年发现的 SolidGoldMagikarp 一类 glitch token，在 BPE 训练语料里出现过，但在模型的训练语料里几乎没出现过，它们的 embedding 从来没被梯度更新过，接近初始化时的随机值。喂给模型会导致复读、拒绝、输出无关内容等各种异常。每个模型都有这类 token，数量不少。</p><p>离散的输入加上可微分的内部，意味着可以做优化搜索。模型从 embedding 到 logits 全程可微，只有最前面字符串到 token 这一步是离散的。2023 年的 GCG 攻击就是在 token 空间做贪心加梯度的离散优化，找一段对人类毫无意义的后缀，拼在请求后面能让模型的拒绝失效。它需要白盒梯度，但找到的后缀在不同模型之间有一定的可迁移性。这类攻击在架构上是必然的，只要有梯度就有对抗样本，图像分类领域十年前就走过这条路。</p><h2 id="拒绝也是生成出来的"><a href="#拒绝也是生成出来的" class="headerlink" title="拒绝也是生成出来的"></a>拒绝也是生成出来的</h2><p>生成是自回归的，模型每一步只做一件事：在词表上给出一个分布，取一个 token，拼回输入，再来一次。所谓「拒绝回答」，在模型内部并没有一个决策过程，只是「对不起，我不能」这几个 token 在当前位置的概率比较高。</p><p>这带来几个后果。</p><p>一是对齐比看起来浅。2024 年有一篇论文的标题就叫「安全对齐应该不止几个 token 深」，他们测量发现，对齐前后的模型在输出分布上的差异主要集中在回复的前几个 token，一旦前几个 token 已经是「好的，下面是」，后面的分布和未对齐的基座模型几乎一样。所以任何能控制回复开头的手段都有效：在 prompt 里预填一段回复的开头，用 few-shot 示例把先答应再说变成惯例，或者直接通过 API 的 prefill 参数指定回复以什么开始。</p><p>二是拒绝只是一个概率，不是开关。某个请求被拒绝的概率是 0.9，意味着采样十次大约有一次不拒绝。temperature、top-p 这些采样参数因此也是攻击面的一部分，调高随机性多试几次，就是在对这个概率做重复采样。</p><p>三是分布可以被直接改。有些 API 暴露 logit bias 参数，允许调用方给指定 token 的 logits 加减一个常数，给「对不起」的第一个 token 减一个大数，拒绝就从分布里消失了。架构把「输出是一个分布」这件事直接暴露给了调用方。</p><h2 id="权重里的记忆"><a href="#权重里的记忆" class="headerlink" title="权重里的记忆"></a>权重里的记忆</h2><p>每个 Transformer block 里，attention 之后还有两层全连接组成的 FFN，它占了 block 三分之二的参数。一种被广泛接受的解读是，attention 负责在 token 之间搬运信息，FFN 负责存储，模型的事实性知识主要在 FFN 里。这里的存储不是比喻，训练数据确实被压进了权重。</p><p>记忆是可以提取的。在训练数据里出现次数足够多的序列，模型会逐字记住，给出开头，它能接着背出后面，包括个人信息、密钥、受版权保护的文本。提取效率和序列在训练集里的重复次数正相关，也和模型规模正相关，越大的模型记得越多。membership inference 是它的弱化版本，不提取内容，只判断某段文本是否在训练集里。</p><p>对齐也可以被微调覆盖。从残差流的角度看，对齐只是在预训练权重上叠加的一层很薄的修正。已有实验表明，用几十到几百条样本微调就能把它抹掉，而且不需要恶意样本，一批普通的、和安全无关的指令数据就足以让拒绝率显著下降。开放权重的模型不存在不可移除的安全机制，这和具体哪个模型无关，权重就是模型的全部。</p><h2 id="KV-cache-和多租户"><a href="#KV-cache-和多租户" class="headerlink" title="KV cache 和多租户"></a>KV cache 和多租户</h2><p>推理有一个架构上的便利：在 causal mask 之下，前文 token 的 k、v 不依赖任何后文，算过一次就不会再变，可以缓存起来，也就是 KV cache。推理服务把这件事又推进一步，多个请求如果共享同一段前缀，比如同一个 system prompt，这段前缀的 KV cache 可以跨请求复用，省掉重复的计算。</p><p>代价是多了一条侧信道。命中前缀缓存的请求，首字延迟明显短于未命中的。2025 年有研究对多家商用 API 做了审计，确认在共享缓存的部署下，一个用户可以通过测量延迟，判断另一个用户是否发送过某个特定前缀，逐步探测出他人 system prompt 的内容。修法是按用户隔离缓存，代价是缓存命中率下降。</p><p>batching 有类似的问题。多个请求在同一个 batch 里前向，理论上互不干扰，但实现上的数值差异、内存布局的差异，都可能泄露同批次其他请求的信息。这方面的研究才刚开始。</p><h2 id="两端的线性层"><a href="#两端的线性层" class="headerlink" title="两端的线性层"></a>两端的线性层</h2><p>模型两端各有一个和词表大小相关的矩阵：输入端把 token id 查表成向量的 embedding，输出端把最后的向量映射回词表分布的 LM head。两者都在一定程度上可逆。</p><p>从 logits 可以恢复权重。2024 年 Carlini 等人的工作表明，如果 API 返回完整的 logits 或足够多的 logprobs，可以通过线性代数恢复出模型的隐藏维度 d_model，进而恢复出输出层的权重矩阵，成本只有几千美元的 API 调用。原理并不复杂，logits 是 d_model 维向量乘以 d_model × V 的矩阵，秩最多是 d_model，采样足够多的输出做 SVD 就能读出来。</p><p>从 embedding 可以恢复文本。这不是 LLM 本身的问题，是 RAG 一类系统的问题。向量数据库里存的 embedding 常被当成已经脱离明文的东西，但 2023 年的 vec2text 证明，用一个反演模型可以从 embedding 恢复出原文，短文本几乎逐字恢复。embedding 应当按明文的安全等级来保护。</p><h2 id="汇总"><a href="#汇总" class="headerlink" title="汇总"></a>汇总</h2><table><thead><tr><th>架构特性</th><th>导出的攻击面</th><th>能否在架构内修复</th></tr></thead><tbody><tr><td>所有来源的 token 进同一套 attention</td><td>prompt injection，直接与间接</td><td>不能，只能降概率、外部隔离</td></tr><tr><td>角色靠特殊 token 与训练倾向</td><td>伪造分段、位置效应、截断挤出 system prompt</td><td>部分，靠转义与训练</td></tr><tr><td>离散 token 输入 + 可微分内部</td><td>编码绕过、glitch token、梯度搜索的对抗后缀</td><td>不能，可微分即有对抗样本</td></tr><tr><td>自回归、输出是分布</td><td>浅对齐、预填开头、重复采样、logit bias</td><td>部分，靠加深对齐、收紧 API</td></tr><tr><td>知识存在权重里</td><td>训练数据提取、成员推断、微调去对齐</td><td>不能，权重即模型</td></tr><tr><td>前缀 KV cache 跨请求复用</td><td>延迟侧信道探测他人 prompt</td><td>能，按租户隔离，牺牲命中率</td></tr><tr><td>两端的线性层</td><td>从 logits 偷权重、从 embedding 反演文本</td><td>能，限制 API 输出、保护 embedding</td></tr></tbody></table><p>表里写着「不能」的几行有一个共同点，它们都来自「模型是一个把 token 序列映射到下一个 token 分布的可微函数」这个定义本身。要在架构层面解决，需要在 token 上引入来源标记或特权位，让 attention 对不同来源的 token 有结构上不同的处理。这方面有零星的研究，但没有主流模型采用，因为它意味着放弃「任何文本都是训练数据」这个让 decoder-only 胜出的最大优势。</p><p>在那之前，把 LLM 当成一个能力很强但不可信的组件来设计系统，是比较稳妥的做法：它能读到的所有内容都可能是指令，它的所有输出都需要校验，它的权限应该是完成当前任务所需的最小集合。这和对待一段从网上下载的、没法审计源码的程序是同一个思路。</p>]]>
    </content>
    <id>https://l4n.top/2026/09/05/Transformer-%E5%90%8E%E6%97%A5%E8%B0%88-%E4%BB%8E%E6%9E%B6%E6%9E%84%E7%9C%8BLLM%E6%94%BB%E5%87%BB%E9%9D%A2/</id>
    <link href="https://l4n.top/2026/09/05/Transformer-%E5%90%8E%E6%97%A5%E8%B0%88-%E4%BB%8E%E6%9E%B6%E6%9E%84%E7%9C%8BLLM%E6%94%BB%E5%87%BB%E9%9D%A2/"/>
    <published>2026-09-05T07:30:00.000Z</published>
    <summary>
      <![CDATA[<p>现在的大语言模型在结构上都是同一种东西：一串 token 进入同一套 attention，逐层变换，最后自回归地预测下一个 token。这篇把这套结构当成攻击面来看，看看 LLM 安全里反复出现的那些问题，有多少在架构层面就已经定下来了。</p>
<p>先划定范围。这里只讨]]>
    </summary>
    <title>从架构看 LLM 的攻击面</title>
    <updated>2026-09-05T17:27:13.427Z</updated>
  </entry>
  <entry>
    <author>
      <name>l4n</name>
    </author>
    <category term="LLM" scheme="https://l4n.top/tags/LLM/"/>
    <content>
      <![CDATA[<p>Transformer 是 2017 年《Attention Is All You Need》提出的序列建模架构，现在的大语言模型基本都建立在它上面。这篇按从零件到整机的顺序过一遍：文本怎么变成向量，attention 怎么算，多头、位置编码、一个 block 的组成，encoder-decoder 到 decoder-only 的演变，训练目标，最后是推理阶段的采样、KV cache 和性能瓶颈。</p><p>读完应该能回答这几个问题：</p><ul><li>attention 为什么能替代 RNN，付出了什么代价</li><li>一个 Transformer block 里有几个矩阵乘法，参数主要在哪，怎么从超参数估算总参数量</li><li>现在的大模型为什么都是 decoder-only</li><li>KV cache 存的是什么，为什么它是长上下文的显存大头，推理为什么卡在带宽上</li></ul><h2 id="为什么需要它"><a href="#为什么需要它" class="headerlink" title="为什么需要它"></a>为什么需要它</h2><p>Transformer 出现之前，序列建模的主力是 RNN 和它的变体 LSTM、GRU。它们最大的问题是串行：第 t 步的隐状态依赖第 t−1 步，长度为 n 的序列必须一步一步算 n 次，GPU 再多也用不上。</p><p>另一个问题是长距离依赖。信息从第 1 个 token 传到第 100 个 token，中间要经过 99 次状态更新，每次都会被压缩和覆盖一部分。LSTM 的门控缓解了这个问题，但没有解决。</p><p>Transformer 的思路是不再按时间步逐个传递，而是让序列里任意两个位置直接建立联系。这样一来，任意两个 token 之间的路径长度都是 O(1)，整个序列的所有位置也能同时计算，前面两个问题就一起解决了。</p><table><thead><tr><th></th><th>每层计算量</th><th>串行步数</th><th>任意两位置的路径长度</th></tr></thead><tbody><tr><td>RNN</td><td>O(n · d²)</td><td>n</td><td>O(n)</td></tr><tr><td>Self-attention</td><td>O(n² · d)</td><td>1</td><td>O(1)</td></tr></tbody></table><p>代价也在这张表里：任意两个位置之间都要算一次关系，计算量和 n 的平方成正比，序列长度翻一倍，attention 的开销就翻四倍。后面长上下文的种种麻烦，大多是从这里来的。</p><h2 id="从文本到向量"><a href="#从文本到向量" class="headerlink" title="从文本到向量"></a>从文本到向量</h2><p>模型不直接处理字符。文本先经过 tokenizer 切成 token，主流做法是 BPE 或它的变体：从单个字符出发，反复把语料里最常相邻出现的两个片段合并成一个新 token，直到词表达到预定大小。常见词会成为一个 token，生僻词被切成几个子词，任何字节序列都能被表示出来，不存在词表外的情况。词表大小从 GPT-2 的 5 万到现在常见的 10 到 15 万。</p><p>每个 token 有一个整数 id，通过一张 V × d_model 的 embedding 矩阵查表，变成一个 d_model 维的向量。V 是词表大小，d_model 是模型的隐藏维度，7B 量级的模型一般是 4096。模型末端还有一个 d_model × V 的输出层，把最后的向量映射回词表上的分布。这两个矩阵形状互为转置，很多模型直接共用一份权重。</p><p>从这一步开始，模型内部流动的就是长度为 n 的一串 d_model 维向量。位置信息还没有加进来，后面单独讲。</p><h2 id="Self-attention"><a href="#Self-attention" class="headerlink" title="Self-attention"></a>Self-attention</h2><p>attention 从每个 token 的向量 x 出发做三个线性变换：</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">q = x · W_Q      k = x · W_K      v = x · W_V</span><br></pre></td></tr></table></figure><p>W_Q、W_K、W_V 是三个 d_model × d_k 的矩阵，整个序列共用同一组，位置 1 和位置 1000 用的是同一个 W_Q。这一点既是它能并行的前提，也是它没有位置概念的原因。</p><p>三个向量可以这样理解：q 是「我在找什么」，k 是「我有什么」，v 是「我要传出去的内容」。可以把整个过程想成一次「软」的查表。普通哈希表用 key 精确匹配，取出一个 value；attention 用 q 和所有 k 算相似度，再按相似度把所有 v 加权平均，没有命中和未命中之分，只有权重大小。</p><p>具体地说，一个 token 拿自己的 q 和序列里所有 token 的 k 做点积，得到一组相关性分数，分数过 softmax 变成和为 1 的权重，再用这组权重对所有 v 做加权求和，结果就是这个 token 的新表示。把整个序列的 q、k、v 各堆成矩阵，一次矩阵乘法就能把所有 token 一起算完：</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">Attention(Q, K, V) = softmax( Q Kᵀ / √d_k ) · V</span><br></pre></td></tr></table></figure><p><img loading="lazy" decoding="async" src="/images/posts/transformer/attention.svg" alt="scaled dot-product attention 的计算流程"></p><p>有几处细节值得多说两句。</p><p>Q Kᵀ 是一个 n × n 的矩阵，第 i 行第 j 列是第 i 个 token 对第 j 个 token 的关注分数。softmax 按行做，第 i 行归一化之后，就是第 i 个 token 分给序列里每个位置的权重。n² 的计算量和显存占用都来自这个矩阵。</p><p>除以 √d_k 的原因要从数值范围说起。假设 q 和 k 的各分量独立、零均值、单位方差，那么点积 q·k 是 d_k 个乘积之和，方差就是 d_k。d_k 取 64 或 128 时，点积的数值会很大，而 softmax 对大数值很敏感，最大的那个分量会拿走几乎全部权重，其余趋近于 0，梯度也跟着消失。除以 √d_k 把方差拉回 1，softmax 才能工作在有梯度的区间。原论文对这一步只有一句话，但省掉它的实现经常训练不收敛。</p><p>mask 是可选的一步，在 softmax 之前把某些位置的分数置为 −∞，softmax 之后这些位置的权重就精确为 0。后面 decoder 用的 causal mask 和 padding 的处理都靠它。</p><p>输出的形状和输入一样，n 个 token 进去，n 个 d_v 维向量出来，每个位置的输出都是全序列 v 的加权平均。所以 attention 可以一层层叠加，每一层在上一层的基础上重新聚合。</p><p>最小实现十几行，只用 numpy，对着公式看一遍就够：</p><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">attention</span>(<span class="params">Q, K, V, mask=<span class="literal">None</span></span>):</span><br><span class="line">    d_k = Q.shape[-<span class="number">1</span>]</span><br><span class="line">    scores = Q @ K.T / np.sqrt(d_k)          <span class="comment"># n × n</span></span><br><span class="line">    <span class="keyword">if</span> mask <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span>:</span><br><span class="line">        scores = np.where(mask, scores, -np.inf)</span><br><span class="line">    scores -= scores.<span class="built_in">max</span>(axis=-<span class="number">1</span>, keepdims=<span class="literal">True</span>)   <span class="comment"># 数值稳定</span></span><br><span class="line">    w = np.exp(scores)</span><br><span class="line">    w /= w.<span class="built_in">sum</span>(axis=-<span class="number">1</span>, keepdims=<span class="literal">True</span>)       <span class="comment"># softmax，按行</span></span><br><span class="line">    <span class="keyword">return</span> w @ V                             <span class="comment"># n × d_v</span></span><br></pre></td></tr></table></figure><h2 id="Multi-head"><a href="#Multi-head" class="headerlink" title="Multi-head"></a>Multi-head</h2><p>单头 attention 有一个局限：softmax 出来的只有一种权重分布，一个 token 在一层里只能用一种方式聚合信息。但语言里同时存在很多种关系，语法上的主谓、指代上的先行词、位置上的邻近，一种权重分布照顾不过来。</p><p>多头的做法是把 d_model 切成 h 份，每份 d_model&#x2F;h 维，各自独立做一次 attention，把 h 个结果拼接起来，再过一个线性层 W_O 融合：</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">head_i = Attention(x W_Q^i, x W_K^i, x W_V^i)</span><br><span class="line">MultiHead(x) = Concat(head_1, …, head_h) · W_O</span><br></pre></td></tr></table></figure><p>每个头的维度 d_head &#x3D; d_model &#x2F; h，现在的模型基本固定在 128：7B 模型 4096 维配 32 个头，70B 模型 8192 维配 64 个头。实现上 h 个头并不是分别算的，而是把 h 组 W_Q 拼成一个 d_model × d_model 的大矩阵，一次乘法得到所有头的 q，再 reshape 成 h × n × d_head，在头这个维度上批量做 attention。</p><p>参数量和单头一样：h 个头的投影矩阵各是 d_model × d_head，加起来还是 d_model × d_model。多头换来的是同一层里 h 种不同的关注模式。事后可视化可以看到，有的头在追句法结构，有的头专门看前一个 token，有的头在做指代消解，也有相当一部分头看起来没干什么，剪掉之后效果几乎不变。</p><h2 id="位置编码"><a href="#位置编码" class="headerlink" title="位置编码"></a>位置编码</h2><p>attention 有一个容易忽略的性质：它是置换等变的。把输入序列打乱顺序，输出也跟着打乱，但每个 token 得到的表示完全不变。也就是说 attention 本身没有位置的概念，「我爱你」和「你爱我」在它看来一样。这是「整个序列共用同一组 W」的直接后果。</p><p>所以位置信息必须显式地加进去，做法大致经历了三代。</p><p>第一代是原论文的正弦编码。给每个位置 pos 生成一个 d_model 维向量，偶数维用 sin、奇数维用 cos，频率随维度指数递减：</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">PE(pos, 2i)   = sin( pos / 10000^(2i/d) )</span><br><span class="line">PE(pos, 2i+1) = cos( pos / 10000^(2i/d) )</span><br></pre></td></tr></table></figure><p>直接加在输入 embedding 上。低维度变化快，高维度变化慢，和二进制计数各位的规律类似。它不需要学，任意长度都能算，但模型要自己从这堆三角函数里学会「相对位置」，学得并不好。</p><p>第二代是可学习的绝对位置编码，BERT 和 GPT-2 用的就是它，给每个位置一个可训练的向量，简单直接。问题是训练时最长见过 1024，推理时第 1025 个位置就没有向量了，完全没法外推。</p><p>第三代是 RoPE，旋转位置编码，LLaMA、Qwen 等现在的主流模型都用它。思路换了一下，不再往输入上加东西，而是在算 attention 分数之前对 q 和 k 做旋转。把 d 维向量两两分组看成 d&#x2F;2 个二维向量，第 i 组用一个固定的角频率 θ_i，位置 m 的 token 把这一组旋转 m·θ_i：</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">[x1&#x27;]   [ cos(mθ)  −sin(mθ) ] [x1]</span><br><span class="line">[x2&#x27;] = [ sin(mθ)   cos(mθ) ] [x2]        θ_i = 10000^(−2i/d)</span><br></pre></td></tr></table></figure><p>二维旋转有个很好的性质：两个向量分别旋转 α 和 β 之后再做点积，结果只和 α − β 有关。于是位置 m 的 q 和位置 n 的 k 做点积，天然只依赖 m − n，相对位置信息直接进了 attention 分数，不需要模型自己去学。低频组编码远距离关系，高频组编码近距离关系，分工和正弦编码一样，只是塞进去的位置从输入换到了 q 和 k。</p><p>RoPE 的外推能力比绝对编码好，但也有限。位置超出训练长度后，高频维度的旋转角度会进入训练时没见过的区间。现在常见的长上下文扩展方法，比如 NTK-aware 缩放、YaRN，做的都是调整那组基频 θ_i，让长位置对应的角度落回模型熟悉的范围。另一条路是 ALiBi，什么编码都不做，直接在 attention 分数上按距离减一个线性惩罚，外推表现好，表达力弱一些，用得少。</p><h2 id="一个-block-长什么样"><a href="#一个-block-长什么样" class="headerlink" title="一个 block 长什么样"></a>一个 block 长什么样</h2><p>把上面的零件装起来就是一个 Transformer block。以现在通用的 Pre-LN 结构为例：</p><p><img loading="lazy" decoding="async" src="/images/posts/transformer/block.svg" alt="Pre-LN 的 Transformer block"></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">x  = x + MHA( Norm(x) )</span><br><span class="line">x&#x27; = x + FFN( Norm(x) )</span><br></pre></td></tr></table></figure><p>两条支路，每条都是先归一化，过子层，再加回残差。</p><p>FFN 是两层全连接加一个非线性，先升维到 4·d_model，激活，再降回 d_model。它对每个位置独立计算，位置之间不交换信息，交换信息是 attention 的事。现在的主流模型把它换成了 SwiGLU，多一条门控支路，中间维度相应调到 8&#x2F;3·d_model 左右，参数量持平。有一种流行的解读把 FFN 看成 key-value 存储：第一层的每一行是一个 key，和输入做内积得到激活强度，第二层的对应列是 value，按强度加权取出。按这个解读，attention 负责在 token 之间搬运信息，FFN 负责存储和变换，模型记住的事实主要在 FFN 里。</p><p>参数分布可以直接算。attention 的四个投影矩阵各是 d²，合计 4·d²；FFN 两层各 4·d²，合计 8·d²。一个 block 大约 12·d²，三分之二在 FFN。整个模型的参数量也就有了估算公式：</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">总参数 ≈ 12 · d² · L  +  V · d       （L 层，V 是词表大小）</span><br></pre></td></tr></table></figure><p>代入 LLaMA-7B 的 d &#x3D; 4096、L &#x3D; 32、V &#x3D; 32000：12 × 4096² × 32 约 64 亿，加上 embedding 的 1.3 亿，约 66 亿，实际是 67 亿，差的部分来自 SwiGLU 多出来的那条支路。这个公式反过来也有用，看到一个模型的参数量，大致就能推出它的 d 和 L。</p><p>残差给梯度留了一条直通路径，几十上百层的网络能训练起来全靠它。它也提供了一个看模型的角度：残差流是一条贯穿所有层的 d_model 维主干，每一层从里面读一点、往里面写一点修正，没有哪一层会彻底重写表示。</p><p>归一化方面，原论文用 LayerNorm，对每个位置的 d 维向量减均值、除标准差，再乘一个可学习的缩放 g、加一个偏移 b。现在基本都换成了 RMSNorm，去掉减均值和偏移项，效果几乎一样，算得更快：</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">LayerNorm(x) = (x − μ) / σ · g + b</span><br><span class="line">RMSNorm(x)   = x / √( mean(x²) + ε ) · g</span><br></pre></td></tr></table></figure><p>Pre-LN 和 Post-LN 的区别在归一化的位置。原论文是 Post-LN，先加残差再归一化，这种结构在深层时训练不稳定，必须配合 learning rate warmup。Pre-LN 把归一化挪到子层之前，残差路径上没有 Norm，梯度更平稳，可以用更大的学习率，现在是标配。</p><p>把 L 个这样的 block 叠起来，前面接 embedding，后面接一次归一化和输出层，就是完整的模型。GPT-3 是 96 层，LLaMA-70B 是 80 层。</p><h2 id="从-Encoder-Decoder-到-Decoder-only"><a href="#从-Encoder-Decoder-到-Decoder-only" class="headerlink" title="从 Encoder-Decoder 到 Decoder-only"></a>从 Encoder-Decoder 到 Decoder-only</h2><p>原论文是为翻译设计的，结构分两半。encoder 双向看完整个源句，每个位置可以关注前后所有位置；decoder 生成目标句，用 causal mask 保证只能看到已经生成的部分，另外多一个 cross-attention 去看 encoder 的输出，q 来自 decoder，k 和 v 来自 encoder。</p><p>后来这两半各自成了流派。BERT 只留 encoder，双向，适合分类、抽取这类理解任务。T5 保留完整的 encoder-decoder，至今在翻译、摘要上还有使用。GPT 只留 decoder，去掉 cross-attention，只做一件事：给定前文，预测下一个 token。</p><p>decoder-only 最终胜出，主要有三个原因。训练目标极其简单，任何文本天然就是训练数据，不需要标注。理解和生成统一到了同一个框架里，回答问题就是续写问题后面的文字。规模化也最顺，scaling law 的曲线在 decoder-only 上最干净。</p><p>causal mask 是这个结构的核心。把 Q Kᵀ 矩阵的上三角全部置为 −∞，softmax 后权重为 0，第 i 个位置就只能看到 1 到 i。它还带来一个训练上的好处：一个长度为 n 的序列，一次前向传播同时得到 n 个位置的预测，每个位置都在预测它的下一个 token，相当于 n 个训练样本并行完成。这种训练方式叫 teacher forcing，喂给模型的永远是真实的前文，而不是它自己生成的。</p><p><img loading="lazy" decoding="async" src="/images/posts/transformer/causal-kv.svg" alt="causal mask 与 KV cache"></p><h2 id="训练目标"><a href="#训练目标" class="headerlink" title="训练目标"></a>训练目标</h2><p>预训练只有一个损失函数：每个位置对下一个 token 的预测，和真实的下一个 token 之间的交叉熵。输出层给出词表上的 logits，softmax 之后取真实 token 对应的概率，取负对数，n 个位置求平均。没有别的监督信号。模型的全部能力，包括事实、推理、代码，都是从「把下一个 token 猜准」这一件事里来的。</p><p>预训练的数据量级是万亿 token。之后的指令微调（SFT）和偏好对齐（RLHF、DPO）用的是完全相同的架构和损失形式，只是数据从网页换成了对话样本，规模小几个数量级。对齐没有在架构里加任何新东西，它只是在预训练权重上继续做梯度下降。</p><h2 id="推理"><a href="#推理" class="headerlink" title="推理"></a>推理</h2><p>训练时序列是并行的，推理时不是。生成是自回归的：给一段前文，算出下一个 token 的分布，从中取一个，拼到前文后面，再算下一个。每一步都是一次完整的前向传播。</p><p>取哪一个由采样策略决定。logits 除以 temperature 之后再做 softmax，temperature 越低分布越尖，趋近于永远取最大的那个，越高越平，随机性越强。top-p 只在累计概率达到 p 的那批候选里采样，截掉长尾。temperature 设为 0 就是贪心解码，同样的输入永远得到同样的输出。</p><p>最朴素的做法是每一步把整个序列重新算一遍。生成 n 个 token，第 t 步的 attention 是 O(t²)，累积起来 O(n³)。这里面有大量重复：在 causal mask 之下，前面 token 的 k 和 v 不依赖任何后面的 token，算过一次之后就不会再变。</p><p>KV cache 就是把它们存下来。每一步只为新 token 算 q、k、v，把新的 k、v 追加进缓存，然后用这一个 q 对缓存里所有的 k 做 attention。每步的 attention 从 O(t²) 降到 O(t)。上面那张图里的实线行就是这一步真正在算的部分。</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">2 × 层数 × 序列长度 × d_model × 每个数的字节数        （再乘 batch）</span><br></pre></td></tr></table></figure><p>拿 LLaMA-7B 算一下：32 层，d_model 4096，fp16。每个 token 是 2 × 32 × 4096 × 2 B &#x3D; 512 KB。4k 上下文 2 GB，32k 上下文 16 GB，已经和模型权重本身一个量级。MQA 和 GQA 就是为此出现的：让多个 query 头共享同一组 k、v 头。LLaMA-2-70B 用 8 组 kv 头对应 64 个 query 头，缓存缩小 8 倍，效果只有很小的损失。</p><p>推理的瓶颈在显存带宽而不是算力。生成每一个 token，都要把全部模型权重从显存里完整读一遍，但每个权重只参与一次乘加。以 A100 为例，显存带宽 2 TB&#x2F;s，读一遍 14 GB 的 7B 模型权重要 7 ms，而这些权重对应的浮点运算在 A100 上不到 0.1 ms 就能算完，算力大部分时间在等数据。所以推理优化主要在减少读显存的次数和字节数：把多个请求 batch 在一起分摊权重读取，量化到 int8 或 int4 缩小权重体积，用 GQA 缩小 KV cache。FlashAttention 解决的是另一头的问题，它不把 n × n 的分数矩阵完整写进显存，而是分块在片上缓存里算完 softmax 再往下走，结果精确不变，省掉了最大的一块中间读写。</p><p>推理分两个阶段，性质不一样。prefill 处理输入的 prompt，所有 token 并行，和训练一样受算力限制，决定首字延迟。decode 一个一个吐 token，受带宽限制，决定每秒多少字。推理服务通常还会把多个请求共同的前缀，比如同一个 system prompt，对应的 KV cache 缓存起来跨请求复用，叫 prefix caching，省掉重复的 prefill。</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>回到开头的几个问题：</p><ul><li>attention 用 O(n²) 的两两连接换掉了 RNN 的串行，路径长度 O(1)，整个序列并行</li><li>一个 block 有 attention 的 4 个投影和 FFN 的 2 到 3 个矩阵，约 12·d² 个参数，三分之二在 FFN；总量约 12·d²·L + V·d</li><li>decoder-only 赢在目标简单、数据免标注、理解生成统一、规模化最顺</li><li>KV cache 存的是每一层每个 token 的 k 和 v，随上下文线性增长；decode 阶段每个 token 都要读一遍全部权重，瓶颈在带宽</li></ul><p>架构本身到这里就讲完了。从安全的角度再看一遍这套结构，会发现现在 LLM 的很多攻击面在架构层面就已经定下来了，这部分单独写了一篇：<a href="/2026/09/05/Transformer-%E5%90%8E%E6%97%A5%E8%B0%88-%E4%BB%8E%E6%9E%B6%E6%9E%84%E7%9C%8BLLM%E6%94%BB%E5%87%BB%E9%9D%A2/">从架构看 LLM 的攻击面</a>。</p>]]>
    </content>
    <id>https://l4n.top/2026/09/05/Transformer-%E6%9E%B6%E6%9E%84%E6%A2%B3%E7%90%86/</id>
    <link href="https://l4n.top/2026/09/05/Transformer-%E6%9E%B6%E6%9E%84%E6%A2%B3%E7%90%86/"/>
    <published>2026-09-05T06:30:00.000Z</published>
    <summary>
      <![CDATA[<p>Transformer 是 2017 年《Attention Is All You Need》提出的序列建模架构，现在的大语言模型基本都建立在它上面。这篇按从零件到整机的顺序过一遍：文本怎么变成向量，attention 怎么算，多头、位置编码、一个 block 的组成，en]]>
    </summary>
    <title>Transformer 架构梳理</title>
    <updated>2026-09-05T17:27:13.426Z</updated>
  </entry>
  <entry>
    <author>
      <name>l4n</name>
    </author>
    <category term="随记" scheme="https://l4n.top/tags/%E9%9A%8F%E8%AE%B0/"/>
    <content>
      <![CDATA[<p>2.5-2.10 和inkey 秋风师傅一起去了hk 进行一个紧张刺激的HKCERT final。最后拿下国际组冠军 这也是我在线下赛拿的第一个冠军，还是这么有含金量的一个比赛。</p><p>以下多图预警</p><h2 id="香港篇"><a href="#香港篇" class="headerlink" title="香港篇"></a>香港篇</h2><h3 id="HKCERT-CTF"><a href="#HKCERT-CTF" class="headerlink" title="HKCERT CTF"></a>HKCERT CTF</h3><p>2.5落地，酒店在铜锣湾，非常old school的一个酒店，晚上很热闹的备赛了一会，LamentXU和Jerry请吃了冰室，猪扒很顶级可惜没拍照，此外香港的奶茶是苦的我才知道这件事情，所以 如果再去我会选择一万次冻柠茶</p><p>2.6 闲话不谈，猛打一天拿下。</p><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/5e7908f26a4f9f5f123b1316a5ea443b.jpg" alt="5e7908f26a4f9f5f123b1316a5ea443b"></p><p>（感觉赛方也会传 就不打码了</p><p>以及一些赛中图：</p><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/6030b025390e1f0783c42c87028f6130.png" alt="6030b025390e1f0783c42c87028f6130"></p><p>这张拍的很帅 我真会拍</p><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/2dcd6bb99148c5c7aa18e8ee9b34d5a0.jpg" alt="2dcd6bb99148c5c7aa18e8ee9b34d5a0"></p><p>赛后去吃饭了，加上LamentXU Jerry去吃了顶级大排档，吃完进行了自由city walk</p><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/8b520b3adb2c9a8e154f165e6ccac34f.jpg" alt="8b520b3adb2c9a8e154f165e6ccac34f"></p><p>真不错吧我说</p><p>回去的路上，香港夜景</p><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/c14f850b306f451604cd89973f5a759a.jpg" alt="c14f850b306f451604cd89973f5a759a"></p><p>给我一种梦回大阪之感，疲惫，回酒店进行一个睡眠</p><h3 id="纯玩篇"><a href="#纯玩篇" class="headerlink" title="纯玩篇"></a>纯玩篇</h3><p>本来计划2.7直接深圳的，结果规划了一下寄存行李的可能性 决定和队友再玩一天香港</p><p>但是香港其实没什么好玩的，和inkey 秋风从佐敦一路北伐，走到旺角后力竭了。在麦当劳轻轻的碎了</p><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/cab93aa3d443c23ae14975540ff228a6.jpg" alt="cab93aa3d443c23ae14975540ff228a6"></p><p>香港人是真的多，尾气也是真的呛人<br>相比铜锣湾的美日混合风 这里更像是东南亚风格</p><p>还去了角川书店，找路花了一年</p><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/20a3f2ee0da1d91c9ebde9c5b8e352cf.jpg" alt="20a3f2ee0da1d91c9ebde9c5b8e352cf"></p><p>然后回龙佐敦，离开前pua秋风买macbook pro未遂 到达西九龙高铁去深圳 开启深圳篇</p><h2 id="深圳篇"><a href="#深圳篇" class="headerlink" title="深圳篇"></a>深圳篇</h2><p>深圳牛逼 好顶级的城市</p><p>第一天被本地人带着吃了神秘日料，进行了一番citywalk，还收到了神秘小礼物</p><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/4817ff858100ad8d46cfc283aa8c20ee.jpg" alt="4817ff858100ad8d46cfc283aa8c20ee"></p><p>然后独自沿着海岸线狂飙了2h</p><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/f172ee5a11a9500df931f23afb204bf9.jpg" alt="f172ee5a11a9500df931f23afb204bf9"></p><p>天气能见度有些差 所以看不太清</p><p>晚上掏出大胃袋吃了寿司郎</p><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/8312c3395f4f2693290f28a4ac2f2f9c.jpg" alt="8312c3395f4f2693290f28a4ac2f2f9c"></p><p>这个牛肉+鹅肝的combo真的很无敌 长期爽食造成的</p><p>day2也是citywalk了一番 触动我的标语</p><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/541d2368ed696edc9b07892a73f3a367.jpg" alt="541d2368ed696edc9b07892a73f3a367"></p><p>最值得纪念的还是饭 人生烤肉</p><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/b0a3c8b63534fe951f038e607bda3430.jpg" alt="b0a3c8b63534fe951f038e607bda3430"></p><p>吃完我就走了，再见了大湾区</p><h2 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h2><p>我小学去过一次香港，如今大三再来，感觉还是没有大变化。从香港来到深圳，看着眼前的静音巴士滑过马路，回想起香港街头的尾气味道和如雷般轰鸣过耳畔的香港大巴，也许这就是制度的差距吧。</p><p>深圳的地铁站里有很多宣传画，和其他地方的不同 这里的主题主要是创新，就连画也是对未来科技的畅想。面对这样的变革我们该何去何从呢？opus4.6 和 codex5.3 在这次决赛大显身手，传统安全真的还有出路吗？</p><p>好吧 不管怎样 命运的分岔口出现了 我应该好好把握它。</p>]]>
    </content>
    <id>https://l4n.top/2026/02/10/HKCERT-%E6%B7%B1%E5%9C%B3%E8%A1%8C/</id>
    <link href="https://l4n.top/2026/02/10/HKCERT-%E6%B7%B1%E5%9C%B3%E8%A1%8C/"/>
    <published>2026-02-10T15:29:12.000Z</published>
    <summary>
      <![CDATA[<p>2.5-2.10 和inkey 秋风师傅一起去了hk 进行一个紧张刺激的HKCERT final。最后拿下国际组冠军 这也是我在线下赛拿的第一个冠军，还是这么有含金量的一个比赛。</p>
<p>以下多图预警</p>
<h2 id="香港篇"><a href="#香港篇" c]]>
    </summary>
    <title>
      <![CDATA[HKCERT&&深圳行]]>
    </title>
    <updated>2026-09-02T15:48:21.858Z</updated>
  </entry>
  <entry>
    <author>
      <name>l4n</name>
    </author>
    <category term="安全" scheme="https://l4n.top/tags/%E5%AE%89%E5%85%A8/"/>
    <content>
      <![CDATA[<h2 id="前置知识"><a href="#前置知识" class="headerlink" title="前置知识"></a>前置知识</h2><h4 id="APP沙箱机制"><a href="#APP沙箱机制" class="headerlink" title="APP沙箱机制"></a>APP沙箱机制</h4><p>android基于linux内核借鉴了uid机制</p><ul><li>appA无法访问app B的私有目录</li><li>每个app有一个唯一uid，拥有独立虚拟机（ART&#x2F;Daivik）</li><li>SELinux<ul><li>DAC自主访问控制 MAC强制访问控制</li></ul></li><li>Binder 共享UID等漏洞</li></ul><h4 id="Android启动流程"><a href="#Android启动流程" class="headerlink" title="Android启动流程"></a>Android启动流程</h4><ul><li>[Android启动过程-万字长文(Android14) - 柳云居士 - 博客园](<a href="https://www.cnblogs.com/anywherego/p/18221943#:~:text=1">https://www.cnblogs.com/anywherego/p/18221943#:~:text=1</a> 1.引导加载程序（Bootloader）启动： 当设备上电或者重启时，首先会由引导加载程序负责启动。 … 2 2.内核加载： 引导加载程序会根据预定义的配置从设备存储中加载操作系统内核。 …,6 6.启动系统服务： 在Zygote进程启动后，还会启动一系列系统服务，例如SurfaceFlinger、ActivityManager、PackageManager等。 … 7 7.启动桌面程序： 一旦系统服务启动完成，Android系统就处于可用状态。 )<ul><li>首先启动BootLoader，引导加载程序首先启动，对硬件及内核做初始化，加载内核到内存</li><li>内核加载：根据预定义的配置加载操作系统内核</li><li>内核初始化，初始化硬件，建立虚拟文件系统，创建进程和线程</li><li>启动init进程，内核初始化完成后，启动init用户空间进程，读取系统配置文件，然后启动一些系统服务和应用</li><li>启动Zygote进程，android应用程序孵化器，预加载常用的java类和资源</li><li>启动系统服务：ActivityManager等，管理显示，包管理，生命周期管理等</li><li>启动桌面：上述系统服务启动完成 就启动桌面，开机成功</li></ul></li></ul><h4 id="APP安装过程"><a href="#APP安装过程" class="headerlink" title="APP安装过程"></a>APP安装过程</h4><p><strong>system&#x2F;app</strong> 存放系统自带的应用程序</p><p><strong>data&#x2F;app</strong> 用户程序安装的目录</p><p><strong>data&#x2F;data</strong> 存放应用程序的数据</p><ol><li>复制APK安装包到data&#x2F;app目录下</li><li>解压并扫描安装包，把dex文件(Dalvik字节码)保存到dalvik-cache目录</li><li>在data&#x2F;data目录下创建对应的应用数据目录</li></ol><h4 id="App运行流程"><a href="#App运行流程" class="headerlink" title="App运行流程"></a>App运行流程</h4><ol><li>BootClassLoader加载系统核心库</li><li>PathClassLoader加载App自身dex</li><li>进入App自身组件开始执行</li><li>调用Application的attachBaseContext</li><li>调用Application的onCreate</li></ol><h4 id="Binder"><a href="#Binder" class="headerlink" title="Binder"></a>Binder</h4><p>android中 进程间通信主要使用Binder，只需要一次拷贝</p><ul><li>Cilent发起请求 Server提供服务</li><li>Service Manager 管理服务，查找Server</li><li>Binder Driver负责数据转发 内存映射 权限检查</li><li>原理：<ul><li>内存映射mmap。首先开辟一块接受缓存区</li><li>然后驱动调用mmap 将Server进程的用户空间 和 内核缓存区映射到同一款物理内存</li><li>Client发送数据，拷贝到内核缓存区，这样Server就可以直接读</li></ul></li><li>代理模式<ul><li>AIDL运行，Proxy，parcel对象打包</li><li>Parcel：为IPC设计的序列化容器 可能有反序列化风险</li></ul></li></ul><h4 id="Bundle"><a href="#Bundle" class="headerlink" title="Bundle"></a>Bundle</h4><p>一个为String key 和Parcalable类型值设计的类型安全映射，非常像Java里的HashMap 但其实是为了IPC来高度优化过的</p><ul><li>内部持有一个Parcel，是一个高级封装</li><li>跨进程传输遵守Lazy Unparcelling</li><li>CVE-2017-13315 由于 <strong>懒拆包</strong> 机制，如果发送端（App A）和接收端（App B）使用的 <code>Parcelable</code> 对象定义不一致，或者 Android 系统框架对某个类型的解析逻辑有 Bug，就会产生“错位”。</li></ul><h4 id="intent"><a href="#intent" class="headerlink" title="intent"></a>intent</h4><ul><li>组件交互的方式，携带数据，动作等重要敏感信息，起到一个游走的作用</li><li>启动Activity，service，发送广播</li><li>Intent有多个构造函数的重载，Intent（Context packageContext,Class&lt;?&gt; <code>cls</code>）</li><li>分为显式intent 隐式intent<ul><li>显式打开：明确指定Component 具体类名<br>Intent intent <code>=` `new Intent(MainActivity.</code>class<code>,SecondActivity.</code>class<code>);</code> &#x2F;<code>/</code>实例化Intent对象<br>intent.putExtra(<code>&quot;et1&quot;</code>,et1Str); <code>/</code>&#x2F;<code>使用putExtra传递参数，参数</code>1<code>：键名 参数</code>2<code>：键对应的值 我们可以使用intent.getStringExtra(</code>“et1”<code>)获取传递的参数 startActivity(intent);</code> &#x2F;<code>/</code>启动Intent，完成从MainActivity类跳转到SecondActivity类</li><li>隐式打开 不指定组件名，只声明 Action、Data 和 Category，由系统分析找到合适的Activity并打开<br>在androidManifest中指定action和category，每次添加action只能一个，但是可以多个category<ul><li>可以打开程序外activity（url</li></ul></li></ul></li></ul><h4 id="Context"><a href="#Context" class="headerlink" title="Context"></a>Context</h4><ul><li>一个抽象类，主要完成两个主要任务：<ul><li>访问应用资源，获取资产，图片，包名 classloder等</li><li>调用系统服务，启动activiy，发送广播，检查权限，获取系统级服务</li></ul></li><li>生命周期<ul><li><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260111152409789.png" alt="image-20260111152409789"></li></ul></li><li>底层原理：进程信息+资源索引+token<ul><li>ContextImpl，继承自context 实现了startActivity等方法</li><li>不同组件通过ContextWrapper包装</li><li>鉴权也要走这里</li></ul></li><li>长生命周期对象获取短生命周期context 就可能导致内存泄露</li></ul><h4 id="权限管理"><a href="#权限管理" class="headerlink" title="权限管理"></a>权限管理</h4><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260111210517047.png" alt="image-20260111210517047"></p><p>有一些自定义安全 签名什么的 如果能够找到某种方法去bypass这些权限 就可以完成利用</p><h2 id="Activity"><a href="#Activity" class="headerlink" title="Activity"></a>Activity</h2><h5 id="基础知识"><a href="#基础知识" class="headerlink" title="基础知识"></a>基础知识</h5><ul><li>生命周期</li><li><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/activity_lifecycle.png" alt="img"></li></ul><p><strong>onCreate()：</strong></p><ul><li>必须实现，当然也是对activity进行逆向的核心破局点</li><li>Activity第一次创建时调用</li></ul><p>**onStart()：**Activity置于栈顶可见但无焦点状态时调用</p><p>**onRestart()：**Activity再次回到栈顶可见时调用</p><p>**onResume()【活动状态】：**在Activity可见并且有焦点时调用</p><p>**onPause()：**发生中断时（准备启动或者恢复另一个activity时）调用，进入暂停状态。可以编写一些保存现场或者释放资源的操作。【该Activity仍然用户可见】</p><p>**onStop()：**Activity不再对用户可见时调用</p><p>**onDestory()：**Activity销毁时调用</p><ul><li>打开新Activity。原Activity: onPause()–&gt;onStop()</li><li>回到原Activity。onRestart()–&gt;onStart()–&gt;onResume()</li></ul><p><strong>四种启动模式</strong></p><ul><li>Standard 先进后出 放入栈顶</li><li>SingleTop 栈顶复用<ul><li>如果顶部不存在这个Activity 就新建 放入栈顶</li><li>如果存在 就调用示例的onNewIntent方法传送intent 不创建新示例</li></ul></li><li>SingleTask 栈内复用模式&#x2F;单任务模式 就是一个栈里只有一个任务</li><li>SingleInstance 单例模式<ul><li>Singletask的升级版，系统直接创建一个新的返回栈并创建Activity的新实例置于新Task返回栈中</li></ul></li></ul><h5 id="攻击面"><a href="#攻击面" class="headerlink" title="攻击面"></a>攻击面</h5><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/905443_SEK8YFRNTN3AYFB.png" alt="image-20210905185725812"></p><p>Activity的组件导出，一般会导致的问题：Android Browser Intent Scheme URLs的攻击手段</p><ul><li>拒绝服务攻击：通过Intent给Activity传输畸形数据使得程序崩溃从而影响用户体验</li><li>越权攻击：Activity用户界面绕过会造成用户信息窃取、Activity界面被劫持产生欺诈等安全事件<ul><li>设置export &#x3D; true 或者添加intent filter这样的属性且没有做鉴权，会导致其他app可以调用</li></ul></li><li>组件导出导致钓鱼欺诈&#x2F;activity劫持<ul><li>activity覆盖等手法</li><li>PendingIntent劫持重定向</li></ul></li><li>隐式启动intent包含敏感数据</li></ul><p>LanuchAnyWhere</p><ul><li>调起任意未导出的activity</li><li>原理：AccountManager账户管理器类允许APP注册自己的账户类型<ul><li>如果A调用addAccount 选择了恶意app，恶意app会返回一个Bundle</li><li>正常逻辑下，这里是要返回一个添加账户的intent 但恶意app会返回一个任意intent</li><li>然后Settings调用这个intent 由于是system权限 可以进行任意未到出的activity的调用</li></ul></li><li>修复：<ul><li>在<strong>AppB</strong>返回<code>Bundle</code>给<strong>AppA</strong>时检查其中的<code>Intent</code> 指向组件的签名是否与AppB签名一致</li><li>但是可以通过parcalable反序列化来绕过，通过精确的布局，使得<code>system_server</code>在检查<code>Intent</code>时找不到这个<code>Intent</code>，而在错位后<code>Settings</code>却刚好可以找到，这样就可以实现补丁的绕过并再次实现<code>LaunchAnyWhere</code>，研究人员将发现的这种漏洞利用方式命名为<code>**Bundle mismatch**</code></li></ul></li></ul><h3 id="Service"><a href="#Service" class="headerlink" title="Service"></a>Service</h3><p>服务，挂在后台执行长时间操作 不提供用户界面</p><ul><li>startservice启动<ul><li>启动后无限期运行，直到它自己调用 <code>stopSelf()</code> 或被其他组件调用 <code>stopService()</code>终止。</li><li>回调：onStartCommand</li></ul></li><li>Bound service<ul><li>提供一个 <strong>Client-Server</strong> 接口 启动组件持有service的binder句柄，实时调用service的方法。</li><li>所有客户端解除绑定后自动销毁</li><li>回调：onBind()，返回一个ibinder对象</li></ul></li><li>保活<ul><li>自定义系统服务</li><li>前台服务</li><li>第三方库</li><li>双service轮询拉起</li></ul></li><li>进程优先级<ul><li>前台进程</li><li>可见进程。对用户可见，但是不能交互的Activity和绑定在上面的service</li><li>服务进程</li><li>后台进程</li><li>空进程</li></ul></li><li>漏洞<ul><li><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/905443_EV7J3X865V3ABAW.png" alt="image-20210908160946499"></li><li>权限提升<ul><li>假如我们注册一个服务来做某种操作，这个服务注册的权限是很高的 但是由于app设计问题 可以接受外部应用以某种方式调起这个service，来执行非预期的操作，这样就是一个越权</li></ul></li><li>service劫持<ul><li>隐式启动service，当存在同名service的时候，先安装的service优先级要更高</li></ul></li><li>消息伪造<ul><li>暴露的service对外接受intent，导致可能的构造intent引起的攻击</li></ul></li><li>DoS<ul><li>没处理异常导致的，代码没写好</li></ul></li></ul></li><li>防御<ul><li>私有服务不导出</li><li>对来源app进行签名校验</li><li>数据谨慎处理，回调的地方做检验</li></ul></li></ul><h3 id="Broadcast"><a href="#Broadcast" class="headerlink" title="Broadcast"></a>Broadcast</h3><p>广播，主要分广播发送者，接收者，消息中心。<br>主要用于一种全局的通信，更加敏捷，异步</p><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/905443_MVZXUT6TBSK5KQU.png" alt="img"></p><p>流程</p><ul><li>首先定义一个BroadcastReceiver，并重写onRecvice()方法，在里面可以实现具体操作，然后到消息中心AMS注册</li><li>广播发送者定义并向AMS发送广播</li><li>AMS查找符合相应条件（IntentFilter<code>/</code>Permission等）的BroadcastReceiver</li><li>AMS将广播发送到上述符合条件的BroadcastReceiver相应的消息循环队列中</li><li>BroadcastReceiver通过消息循环执行拿到此广播，回调BroadcastReceiver中的onReceive()方法。</li></ul><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/905443_PX6UGEV9HMMWTBV.png" alt="image-20210912155436267"></p><ul><li>BroadcastReceiver是四大组件之一，这个组件涉及：广播发送者和广播接收者，这里的广播实际上指的是intent<br>当发送一个广播时，系统会将发送的广播(intent)与系统中所有注册的符合条件的接IntentFilter进行匹配，匹配成功，则执行相应的onReceive函数<br>发送广播时，如果处理不当，恶意应用便可以嗅探，拦截广播，致使敏感数据泄露，接收广播时处理不当，便会导致拒绝服务攻击、伪造消息、越权操作等</li><li>敏感信息泄露<ul><li>如果包含敏感信息的广播没有明确指定接收者，我们注册一个恶意的接收者 符合匹配，就可以窃取到敏感数据</li></ul></li><li>权限绕过<ul><li>原理：可以通过两种方式注册广播接收器，一种是在AndroidManifest.xml文件中通过标签静态注册，另一种是通过Context.registerReceiver()动态注册，指定相应的intentFilter参数，动态注册的广播默认都是导出的，如果导出的BroadcastReceiver没有做权限控制，导致BroadcastReceiver组件可以接收一个外部可控的url、或者其他命令，导致攻击者可以越权利用应用的一些特定功能，比如发送恶意广播、伪造消息、任意应用下载安装、打开钓鱼网站等</li></ul></li><li>消息伪造<ul><li>暴露的Receiver对外接收Intent，如果构造恶意的消息放在Intent中传输，被调用的Receiver接收可能产生安全隐患</li><li>钓鱼什么的</li></ul></li><li>拒绝服务<ul><li>如果敏感的BroadcastReceiver没有设置相应的权限保护，很容易受到攻击。最常见的是拒绝服务攻击。拒绝服务攻击指的是，传递恶意畸形的intent数据给广播接收器，广播接收器无法处理异常导致crash。<br>拒绝服务攻击的危害视具体业务场景而定，比如一个安全防护产品的拒绝服务、锁屏应用的拒绝服务、支付进程的拒绝服务等危害就是巨大的。</li></ul></li></ul><p>防御</p><ul><li><p>私有广播接收器设置exported<code>=</code>’false’,并且不配置intent<code>-</code>filter&#96;&#96;。(私有广播接收器依然能接收到同UID的广播)。</p><p>对接收来的广播进行验证。</p><p>内部app之间的广播使用protectionLevel<code>=</code>’signature’ 验证其是否真是内部app。</p><p>返回结果时需注意接收app是否会泄露信息。</p><p>发送的广播包含敏感信息时需指定广播接收器，使用显示意图或者setPackage(String packageName)。</p><p>使用LocalBroadcastManager。</p></li></ul><h3 id="Content-Provider"><a href="#Content-Provider" class="headerlink" title="Content Provider"></a>Content Provider</h3><p>内容提供者，负责数据治理，封装一些底层数据源，向外部提供增删改查的接口。</p><ul><li><p>定位数据使用内容URI</p><ul><li><p>定义：Uniform Resource Identifier，即统一资源标识符</p><p>作用：唯一标识ContentProvider &amp;其中的数据</p><p>外界进程通过URL找到对应的ContentProvider &amp;其中数据，再进行数据操作</p></li></ul><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/905443_92EXMEKYH5VR3J2.png" alt="img"></p></li><li><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/905443_WHA5BS39WRBY5G3.png" alt="image-20210922104352070"></p></li><li><p>sql注入</p><ul><li>实现query等方法时 content:&#x2F;&#x2F;com.victim.provider&#x2F;user&#x2F;1 OR 1&#x3D;1这种可能导致注入</li></ul></li><li><p>信息泄露</p><ul><li>content URI是一个标志provider中的数据的URI。Content URI中包含了整个provider的以符号表示的名字(它的authority)和指向一个表的名字(一个路径)。<br>当你调用一个客户端的方法来操作一个provider中的一个表，指向表的contentURI是参数之一，如果对ContentProvider的权限没有做好控制，就有可能导致恶意的程序通过这种方式读取APP的敏感数据。</li></ul></li><li><p>目录遍历</p><ul><li>访问目录就会有这种情况 可能有任意文件读取这一类的问题</li></ul></li></ul>]]>
    </content>
    <id>https://l4n.top/2026/01/11/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AE%89%E5%85%A8%E6%A2%B3%E7%90%86-%E5%9B%9B%E5%A4%A7%E7%BB%84%E4%BB%B6%E5%AE%89%E5%85%A8/</id>
    <link href="https://l4n.top/2026/01/11/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AE%89%E5%85%A8%E6%A2%B3%E7%90%86-%E5%9B%9B%E5%A4%A7%E7%BB%84%E4%BB%B6%E5%AE%89%E5%85%A8/"/>
    <published>2026-01-11T05:02:44.000Z</published>
    <summary>
      <![CDATA[<h2 id="前置知识"><a href="#前置知识" class="headerlink" title="前置知识"></a>前置知识</h2><h4 id="APP沙箱机制"><a href="#APP沙箱机制" class="headerlink" title="APP]]>
    </summary>
    <title>移动端安全梳理-四大组件安全</title>
    <updated>2026-09-02T15:48:21.846Z</updated>
  </entry>
  <entry>
    <author>
      <name>l4n</name>
    </author>
    <category term="安全" scheme="https://l4n.top/tags/%E5%AE%89%E5%85%A8/"/>
    <content>
      <![CDATA[<h2 id="协议"><a href="#协议" class="headerlink" title="协议"></a>协议</h2><h4 id="HTTP-HTTPS"><a href="#HTTP-HTTPS" class="headerlink" title="HTTP&#x2F;HTTPS"></a>HTTP&#x2F;HTTPS</h4><ul><li><p><strong>HTTP</strong></p><ul><li><p>请求响应结构</p><ul><li>起始行：方法（get post）路径 版本</li><li>头部：键值对，元数据（user-Agent Cookie Host）</li><li>主题body：实际数据</li></ul></li><li><p>常见方法</p><ul><li>GET 从服务器获取资源，?id&#x3D;1</li><li>POST 向服务器提交数据。参数放在body里，更安全</li></ul></li><li><p>状态码</p><ul><li>1xx 信息性响应</li><li>2xx 成功 200 OK</li><li>3xx 重定向 301 永久移动 302 临时移动</li><li>4xx 客户端错误 403 forbidden 无权限 404notfound 对应资源找不到</li><li>5xx 服务器错误 500 Internal Server Error 后端崩溃 502 Bad Gateway 错误网关&#x2F;连接超时</li></ul></li><li><p>流量结构</p><ul><li><p>请求结构</p><ul><li>请求行，请求头，空行，请求体</li><li><pre><code>POST /api/v1/login HTTP/1.1              &lt;-- 1. 请求行 (Method, Path, Version)Host: example.com                        &lt;-- 2. 请求头 (Headers)Content-Type: application/jsonContent-Length: 27User-Agent: Mozilla/5.0                                         &lt;-- 3. 空行 (CRLF, \r\n){&quot;username&quot;:&quot;admin&quot;,&quot;pw&quot;:&quot;123&quot;}          &lt;-- 4. 请求体 (Body)</code></pre></li></ul></li></ul></li></ul></li></ul><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">  - 请求行：</span><br><span class="line"></span><br><span class="line">    - Method (方法)： `GET` (获取), `POST` (提交), `PUT` (更新), `DELETE` (删除), `OPTIONS` (预检)。</span><br><span class="line">    - Path (路径)： 目标资源的 URL 路径及查询参数。</span><br><span class="line">    - Version (版本)： `HTTP/1.1` 或 `HTTP/2`。</span><br><span class="line"></span><br><span class="line">  - 请求头： 键值对形式，告知服务器客户端的环境、预期的格式（Accept）、认证信息（Cookie/Authorization）等。</span><br><span class="line"></span><br><span class="line">  - 空行： 必须存在，用于标识 Header 结束和 Body 开始。</span><br><span class="line"></span><br><span class="line">  - 请求体： 实际传输的数据。`GET` 请求通常没有 Body。</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">    HTTP/1.1 200 OK                          &lt;-- 1. 状态行 (Version, Status Code, Phrase)</span><br><span class="line">    Date: Sat, 10 Jan 2026 14:00:00 GMT      &lt;-- 2. 响应头</span><br><span class="line">    Server: Apache</span><br><span class="line">    Content-Type: text/html; charset=UTF-8</span><br><span class="line">    Content-Length: 155</span><br><span class="line">                                             &lt;-- 3. 空行</span><br><span class="line">    &lt;html&gt;                                   &lt;-- 4. 响应体</span><br><span class="line">      &lt;body&gt;Hello World&lt;/body&gt;</span><br><span class="line">    &lt;/html&gt;</span><br></pre></td></tr></table></figure><pre><code>    <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></pre></td><td class="code"><pre><span class="line">- 关键Header</span><br><span class="line"></span><br><span class="line">  - **Cookie / Set-Cookie：** 维持会话状态，是 CSRF 和会话劫持的核心。</span><br><span class="line">  - **Referer / Origin：** 标识请求来源，常用于 CSRF 防御。</span><br><span class="line">  - **User-Agent：** 客户端特征，常用于爬虫识别或漏洞精准投放。</span><br><span class="line">  - **Content-Length / Transfer-Encoding：** 涉及 **HTTP 请求走私 (Request Smuggling)** 漏洞的关键字段。</span><br><span class="line">  - **X-Forwarded-For：** 标识原始 IP（在经过代理/负载均衡时），常被用于绕过 IP 限制。</span><br><span class="line">- 流程</span><br><span class="line"></span><br><span class="line">  - 输入&lt;http://www.baidu.com/123&gt; 后 发生了：</span><br><span class="line">  - DNS解析</span><br><span class="line"></span><br><span class="line">    - 浏览器缓存——操作系统缓存——路由器缓存——ISP递归DNS。。。</span><br><span class="line">    - 可能发生DNS劫持</span><br><span class="line">  - 建立连接（TCP三次握手</span><br><span class="line"></span><br><span class="line">    - **SYN:** 客户端：“你好，我想连接你（序号 x）。”</span><br><span class="line">    - **SYN-ACK:** 服务端：“收到，我也准备好了（序号 y，确认号 x+1）。”</span><br><span class="line">    - **ACK:** 客户端：“太好了，那我们开始吧（确认号 y+1）。”</span><br><span class="line">  - 发送HTTP请求</span><br><span class="line"></span><br><span class="line">    - ```</span><br><span class="line">      GET /api/user HTTP/1.1</span><br><span class="line">      Host: http://www.baidu.com/123</span><br><span class="line">      Cookie: session_id=12345</span><br><span class="line">      User-Agent: Mozilla/5.0...</span><br></pre></td></tr></table></figure>- 服务器处理并返回响应</code></pre><ul><li><p><strong>HTTPS</strong></p><ul><li><p>HTTP是明文传输的，意味着任何中间人都能看到密码等敏感数据</p><ul><li><p>HTTPS解决了以下问题：</p><ul><li><strong>机密性 (Confidentiality):</strong> 内容加密，中间人看不懂。</li><li><strong>完整性 (Integrity):</strong> 内容不可篡改，改了就会被发现。</li><li><strong>身份认证 (Authentication):</strong> 证明你访问的确实是真正的目标网站，而不是钓鱼网站。</li></ul></li></ul></li><li><p>流程</p><ul><li>在三次握手之后 进行TLS握手<ul><li>Client发送支持的加密列表和随机数A</li><li>Server发送选择好的加密算法，数字证书和随机数B</li></ul></li><li>证书校验<ul><li>浏览器检查证书是否过期，域名是否匹配，CA签名是否合法</li><li>证书包含CA的数字签名 使用CA的公钥解密能解开才通过完整性校验</li></ul></li><li>生成预主密钥<ul><li>浏览器从证书中获取公钥，生成随机数C 用公钥加密后发送给服务器</li></ul></li><li>生成会话密钥Session key<ul><li>现在都有ABC 双方通过协商好的算法 把这三个混合成一个会话密钥</li></ul></li><li>开始对称加密通信<ul><li>使用这个会话密钥加密http数据 进行传输</li><li>先用非对称协商密钥 再用对称快速通信</li></ul></li></ul></li></ul></li></ul><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/905443_D5JZTT2NY4UURDK.png" alt="image-20211204165055568"></p><h2 id="OWASP-TOP-10"><a href="#OWASP-TOP-10" class="headerlink" title="OWASP TOP 10"></a>OWASP TOP 10</h2><h4 id="XSS"><a href="#XSS" class="headerlink" title="XSS"></a>XSS</h4><ul><li>Cross Site Scripting 网站中注入恶意脚本 导致浏览器执行任意代码（通常JS<ul><li>反射型：服务端直接返回到html，存储在url参数里</li><li>存储型：存到数据库里，服务端读取后返回到html，危害大</li><li>DOM型：不经过服务端，客户端js&#x2F;url localstorage解析时触发</li></ul></li><li>钓鱼 内网探测 配合CSRF 劫持表单 盗取cookie等</li><li>防御：<ul><li>输出转义，HTML实体编码</li><li>HttpOnly：禁止JS读取cookie 防止窃取</li><li>内容安全CSP：限制域名白名单，禁止内敛脚本等</li><li>输入验证</li></ul></li></ul><h4 id="CSRF"><a href="#CSRF" class="headerlink" title="CSRF"></a>CSRF</h4><ul><li>Cross-site Request Forgery 跨站请求伪造<ul><li>核心是网站对用户浏览器的信任，攻击者诱导受害者访问一个恶意页面，该页面利用浏览器<strong>自动携带cookie</strong>的特性来获取cookie 导致盗号等，本质是利用用户权限来进行非法操作</li></ul></li><li>防御<ul><li>Anti-CSRF token 服务器嵌入一个随机生成的token，客户端请求时必须携带该token。攻击者无法获取这个token 无法伪造请求</li><li>属性：将Cookie设置为<code>SameSite=Lax</code> 或 <code>Strict</code>，限制第三方网站请求携带 Cookie。</li><li>验证Referer&#x2F;Origin：检查请求来源是否可信（可以主动隐藏， ）https跳http也会丢失referrer</li><li>二次验证，短信验证码&#x2F;支付密码</li></ul></li></ul><h4 id="SSRF"><a href="#SSRF" class="headerlink" title="SSRF"></a>SSRF</h4><ul><li>Server-Side Request Forgery 服务端请求伪造<ul><li>核心原理是：攻击者利用服务端提供了“从外部获取资源”的功能（如图片上传、URL 预览），伪造请求让<strong>服务器去访问其内网资源</strong>。<br> <strong>攻击目标：</strong> 绕过防火墙，探测服务器所在的内部网络、读取敏感配置文件、攻击内网其他脆弱服务。</li></ul></li><li>防御<ul><li>黑名单 白名单限制协议，域名，ip等</li><li>验证内网ip</li><li>防止DNS重绑定：直接对校验过的ip发起连接</li><li>网络隔离：业务服务器和内网隔离</li></ul></li></ul><h4 id="SQL注入"><a href="#SQL注入" class="headerlink" title="SQL注入"></a>SQL注入</h4><ul><li>数据与命令未分离，用户输入直接拼接进SQL语句里导致的非法执行<ul><li>联合注入 UNION SELECT</li><li>报错注入</li><li>盲注：布尔盲注 时间盲注</li><li>宽字节注入<ul><li><strong>原理</strong>：当后端使用 <code>addslashes</code> 等函数转义单引号时（<code>&#39;</code> 变为 <code>\&#39;</code>），如果数据库编码是 <strong>GBK</strong>，攻击者输入 <code>%df%27</code>。转义后变成 <code>%df%5c%27</code>。由于 GBK 认为 <code>%df%5c</code> 是一个汉字，导致反斜杠被“吃掉”，单引号逃逸。</li><li><strong>防御</strong>：使用 <code>mysql_set_charset(&#39;gbk&#39;)</code> 统一字符集，或直接使用 <strong>预编译</strong>。</li></ul></li><li>二次注入<ul><li><strong>原理</strong>：第一步攻击者输入的恶意数据经过转义存储到了数据库中（此时是安全的）。第二步，程序再次调用该数据并<strong>拼接到另一个 SQL 语句</strong>中执行，此时恶意数据生效。</li><li><strong>防御</strong>：永远不要信任从数据库里取出的数据，<strong>二次调用时依然要进行预编译或转义</strong>。</li></ul></li><li><strong>所有参数都可以预编译吗？</strong><ul><li><strong>不可以！</strong> 预编译只能处理 <strong>占位符（Data Literal）</strong>。</li><li><strong>无法预编译的场景</strong>：<code>ORDER BY [字段名]</code>、<code>GROUP BY</code>、<code>LIMIT</code>、表名、列名。</li><li><strong>解决方案</strong>：这些位置必须使用 <strong>白名单</strong> 校验，或强制类型检查</li></ul></li></ul></li><li>现在哪还有sql注入啊。。</li></ul><h2 id="密码学"><a href="#密码学" class="headerlink" title="密码学"></a>密码学</h2><h4 id="AES"><a href="#AES" class="headerlink" title="AES"></a>AES</h4><ul><li>分组密码，每次处理16字节</li><li>工作模式<ul><li>ECB 每个块独立加密，可以明文统计</li><li>CBC 每个块与前一个密文块xor 再加密</li><li>CTR 对一个递增的计数器加密 再与明文xor （iv不可重复</li><li>GCM ：CTR+GMAC消息验证码，有密文防篡改 推荐使用</li></ul></li></ul><h4 id="对称加密-V-S-非对称加密"><a href="#对称加密-V-S-非对称加密" class="headerlink" title="对称加密 V.S 非对称加密"></a>对称加密 V.S 非对称加密</h4><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260110231254346.png" alt="image-20260110231254346"></p><ul><li><p>非对称加密主要依靠复杂的数学难题（大数分解，椭圆曲线点运算）来实现</p><ul><li>C &#x3D; E_publickey(P), P &#x3D; D_privatekey(C)</li></ul></li><li><p>RSA</p><ul><li>基于大数分解</li><li>至少需要2048位才安全</li><li>核心步骤<ul><li>选两个超大质数pq 计算n &#x3D; pq</li><li>计算phi(n) &#x3D; (p-1)(q-1)</li><li>公钥e 与phi(n)互质</li><li>计算 e 关于phi(n) 的模反元素私钥d <img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260110232034302.png" alt="image-20260110232034302"></li><li>这样就有了公钥对n e 私钥对n d</li><li><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260110232132853.png" alt="image-20260110232132853"></li></ul></li></ul></li><li><p>ECC</p><ul><li><p>定义曲线：例如常见的 y^2 &#x3D; x^3 + ax + b。</p><p>基点 G：曲线上一个已知的点。</p><p>私钥 k：一个巨大的随机数。</p><p>公钥 K：通过“标量乘法”计算出的点 K &#x3D; kG。</p></li></ul></li><li><p>Diffie-Hellman</p><ul><li><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260110232310875.png" alt="image-20260110232310875"></li></ul></li></ul>]]>
    </content>
    <id>https://l4n.top/2026/01/10/OWASP-TOP-10-%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E7%9B%B8%E5%85%B3/</id>
    <link href="https://l4n.top/2026/01/10/OWASP-TOP-10-%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E7%9B%B8%E5%85%B3/"/>
    <published>2026-01-10T10:10:17.000Z</published>
    <summary>
      <![CDATA[<h2 id="协议"><a href="#协议" class="headerlink" title="协议"></a>协议</h2><h4 id="HTTP-HTTPS"><a href="#HTTP-HTTPS" class="headerlink" title="HTTP&]]>
    </summary>
    <title>
      <![CDATA[web漏洞扫盲 && 计算机网络相关 && 密码学]]>
    </title>
    <updated>2026-09-02T15:48:21.828Z</updated>
  </entry>
  <entry>
    <author>
      <name>l4n</name>
    </author>
    <category term="课内" scheme="https://l4n.top/tags/%E8%AF%BE%E5%86%85/"/>
    <content>
      <![CDATA[<h2 id="1-引言"><a href="#1-引言" class="headerlink" title="1. 引言"></a>1. 引言</h2><ul><li>所谓编译，就是高级语言转换为目标程序代码的过程</li><li>分为五部分<ul><li>词法分析：分析源程序构成的字符串，转化为一个个的单词，留待下一步分析</li><li>语法分析：根据语言语法规则，把单词合成语法单位，如句子等</li><li>语义分析及IR的生成：根据第二步的语法分析，将其翻译成中间代码<ul><li>中间代码一般含义明确</li></ul></li><li>代码优化：这一步的任务是对第三步的中间代码进行等价变换，节省时间空间</li><li>目标代码生成：中间代码变成特定机器上的机器代码</li></ul></li></ul><h2 id="2-形式语言理论基础"><a href="#2-形式语言理论基础" class="headerlink" title="2. 形式语言理论基础"></a>2. 形式语言理论基础</h2><h3 id="形式语言基本概念"><a href="#形式语言基本概念" class="headerlink" title="形式语言基本概念"></a>形式语言基本概念</h3><p>形式语言，顾名思义，我们只关心语言的“形式”，不关心具体含义</p><ul><li>形式语言是一种不考虑含义的符号语言</li><li>形式语言主要研究：如何用严谨的数学规则来定义、产生和识别字符串的集合</li></ul><p>符号：语言中最小的不可再分的基本单位<br>符号串：符号的有序序列，空字符串记作ε （ε不是空格）<br>字母表：非空的有限集合，集合里是符号即字母表的元素<br>符号串集合：代表了利用字母表能拼凑出的<strong>所有可能</strong>的字符串</p><p>我们给出语言&#x2F;形式语言的具体定义：<br>语言&#x2F;形式语言：字母表上所有符号串组成的集合的子集，用L表示。</p><ul><li>语言是符号串集合的任意一个子集</li></ul><h4 id="符号串运算"><a href="#符号串运算" class="headerlink" title="符号串运算"></a>符号串运算</h4><ul><li>符号串相等：必须所有符号依次相等</li><li>字符串长度：符号串中包含字符的长度</li><li>符号串连接：ab*bc &#x3D; abbc</li><li>符号串的逆：倒置，abc -1 &#x3D; cba ε-1 &#x3D; ε</li><li>符号串的前缀，后缀，字串：以abc举例<ul><li>前缀：ε a ab abc</li><li>后缀：ε c bc abc</li><li>子串：ε a b c ab bc abc</li></ul></li><li>符号串集合的乘积：A&#x3D;{ab，bc}，B&#x3D;{bc，b}<ul><li>AB&#x3D;{abbc，abb，babc，bab}</li><li>{ε}A&#x3D;A{ε}&#x3D;A</li></ul></li><li>符号串的幂：ω0&#x3D;ε；ω1&#x3D;ω；ω2&#x3D;ωω；……；ωn&#x3D;ωn-1ω<ul><li>ω&#x3D;ab<ul><li>ω0&#x3D;ε<ul><li>ω1&#x3D;ab<ul><li>ωn&#x3D;abab……ab</li></ul></li></ul></li></ul></li></ul></li><li>符号串集合的幂：A0&#x3D;{ε}；A1&#x3D;A；……；An&#x3D;An-1A&#x3D;AAn-1 (n&gt;0)<ul><li>例：A&#x3D;{ab, c}</li><li>A0&#x3D;{ε}，A1&#x3D;{ab, c}，A2&#x3D;{abc, cab, abab, cc}</li></ul></li><li>集合A的闭包和正闭包：<ul><li><strong>闭包 (Closure)</strong> 指的是通过某种运算，将一个集合扩展到“包含所有可能结果”的完整状态。</li><li>星闭包 A* &#x3D;A0 ∪ A1 ∪ …… A+&#x3D;AA*&#x3D;A*A</li><li>正闭包 A+&#x3D;A1 ∪ A2 ∪ …… A*&#x3D; A0 ∪ A+</li><li>正闭包不包含空串</li></ul></li></ul><h3 id="文法和语言的形式定义"><a href="#文法和语言的形式定义" class="headerlink" title="文法和语言的形式定义"></a>文法和语言的形式定义</h3><p>再给一遍定义：</p><ul><li><p>语言：所有句子组成的集合，有限集：枚举，无限集：文法</p></li><li><p>句子：抽象看成某个有限字符表上的字符串。</p></li><li><p>文法：形式上描述和规定语言结构的方法，用有限的手段描述无限的句子集合的方法之一</p><ul><li>文法是一个四元组：<ul><li>G&#x3D;（VN，VT，S，P）记为G[S]。V是字汇表，V&#x3D; VT∪VN，S∈VN，VT∩VN&#x3D;Φ</li><li>Vt：终结符号集，字母，最终成果，不可以被分解替换</li><li>Vn：非终结符号集合，中间变量，用来推到最终句子</li><li>S：开始符号：推导起点，通过S一步步变换成最终的集合</li><li>P：产生式&#x2F;规则集合：形式是U ::&#x3D; X 代表U可以被改写成X</li></ul></li></ul></li><li><p>巴斯克范式：</p><ul><li><p>BNF文法（Backus-Naur Form），用来描述编程语言语法的一种数学表示法</p></li><li><pre><code>&lt;digit&gt; ::= &quot;0&quot; | &quot;1&quot; | &quot;2&quot; | &quot;3&quot; | &quot;4&quot; | &quot;5&quot; | &quot;6&quot; | &quot;7&quot; | &quot;8&quot; | &quot;9&quot;&lt;integer&gt; ::= &lt;digit&gt; | &lt;digit&gt; &lt;integer&gt;</code></pre></li><li><p>第一行定义数字可以是0到9的任意一个</p><ul><li><strong><code>&lt;符号&gt;</code> ::&#x3D; <code>&lt;表达式&gt;</code></strong></li><li><strong><code>&lt;符号&gt;</code>（非终结符）</strong>：表示可以继续分解的概念（如“数字”、“运算符”）。</li><li><strong><code>::=</code></strong>：意思是“定义为”或“由…组成”。</li><li><strong><code>&lt;表达式&gt;</code></strong>：包含终结符（直接出现的字符）和其他非终结符。</li><li><strong><code>|</code></strong>：表示“或”，即多种可能的选择。</li></ul></li></ul></li><li><p>句型：文法G[S]，G&#x3D;（VN，VT，S，P）</p><ul><li>由S推出的符号串X∈(VT∪VN)*称为句型。</li><li>推导 规约 | 箭头表示 箭头下加三角代表规约<ul><li>A-&gt;a 是一个规则，p1&#x3D;bAb p2&#x3D;bab 所以p1直接推导出p2 p2直接规约到p1</li><li>如果经过序列推导，那么就称为推导&#x2F;规约，箭头上带个加号<img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260104135017006.png" alt="image-20260104135017006"></li><li>如果a可以推导b 或者a&#x3D;b 那么称为a广义推导b，<img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260104134958248.png" alt="image-20260104134958248"></li><li>规范推导：每次替换最后边非终结符的直接推导 <img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260104135030549.png" alt="image-20260104135030549"></li></ul></li></ul></li><li><p>语言</p><ul><li>文法G 产生的语言L(G)<ul><li><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260104135045264.png" alt="image-20260104135045264"></li></ul></li><li>空语言：由文法开始符号推不出任何句子</li><li>给定一个文法，就能从结构上唯一确定其语言，给定语言可以推出文法 但不唯一</li></ul></li></ul><h3 id="语法树和二义性"><a href="#语法树和二义性" class="headerlink" title="语法树和二义性"></a>语法树和二义性</h3><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260104143554788.png" alt="image-20260104143554788"></p><ul><li>子树<ul><li>简单子树：只有两层的树</li><li>简单短语：简单子树的叶子节点</li></ul></li></ul><p>二义性：一个句子有两个不同的语法树</p><ul><li>若文法所定义的某个句子,有两种不同的最左(or最右) 推导&#x2F;规约，则具有二义性<ul><li>最左&#x2F;右推导&#x2F;规约：每次选择最左&#x2F;右的非终结符进行推导&#x2F;规约</li></ul></li><li>二义性导致语义不确定性，句子一样，但是可以有两种语法解释</li><li>解决<ul><li>修改编译算法</li><li>修改文法，加入优先层等</li></ul></li><li>不存在一种算法在有限步内判断二义性<ul><li>在计算机科学中，我们无法写出一个通用的程序（算法），让它输入任意一个上下文无关文法（CFG），然后告诉我们这个文法“是”还是“不是”二义性的。</li></ul></li><li>规则左部的符号在右部同时出现两次 or 两次以上，导致二义性<ul><li>这里的“规则”指推导式（Production Rule）。如果一个非终结符（左部）在推导出的结果（右部）中出现了多次，且没有明确的优先级或结合律限制，就很容易产生二义性</li></ul></li><li>先天二义性文法<ul><li>如果一个上下文无关语言（CFL）的所有文法都是二义性的，那么这个语言就称为<strong>先天二义性语言</strong>，而生成该语言的文法即为<strong>先天二义性文法</strong>。</li><li>如果一个语言 L，<strong>没有任何</strong>非二义性的文法可以生成它，那么 L 就是先天二义性的。</li></ul></li></ul><p>有害规则 多余规则</p><ul><li>A-&gt;A 二义性</li><li><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260104153257598.png" alt="image-20260104153257598"></li><li><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260104153641349.png" alt="image-20260104153641349"></li></ul><p>如果一个文法没有有害&#x2F;多余规则 那么这个文法称为压缩 化简过的</p><p>扩充文法：<img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260104163358458.png" alt="image-20260104163358458"></p><p>类似头节点</p><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260104163529585.png" alt="image-20260104163529585"></p><p>这个是要确保每一个非终结符都要能够推出纯粹的终结符字符串</p><ul><li>首先去遍历产生式，把所有推出来全终结符的非终结符收起来</li><li>然后递归，看谁能推出来上一步得到的集合U终结符集合构成的串。可以就加入 然后继续这一步骤</li><li>最后删除掉不在Vn中的非终结符（清理多余规则</li></ul><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260104164038545.png" alt="image-20260104164038545"></p><p>这个要确保删掉那些从“开始符号”出发，无论如何也推导不到的符号和规则。</p><ul><li>从S开始推 递推找到所有有用的非终结符</li><li>找完后 看哪些非终结符是没用的 直接删了</li></ul><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260104164652486.png" alt="image-20260104164652486"></p><p>这一步是要简化推导过程，删掉一些没啥用的中间规则，消除这类规则的目的是为了减少推导步骤，使文法更加简洁高效。</p><ul><li>依次对每一个非终结符A 构造一个集合 代表这个非终结符可以推到的非终结符</li><li>然后看这个集合，假如这个集合中有元素可以推到句子，就直接加上A-句子 这个规则</li></ul><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260104170252535.png" alt="image-20260104170252535"></p><p>其目的是将文法中递推到空串的规则去掉，同时保证文法所定义的语言保持不变。</p><ul><li>和2.2差不多 首先找能推出空串的非终结符号 化成集合，然后递归找相关的扩充</li><li>删去这些空规则</li><li>然后在已有规则里找一下集合里的非终结符，全删掉</li><li>扩充新规则，假如之前是A - BC 但是C推出来空 那么就改成A - BC | C</li></ul><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260104170335688.png" alt="image-20260104170335688"></p><p>在构建语法分析器（特别是自上而下的 LL 预测分析器）时，左递归会导致程序陷入死循环。为了解决这个问题，我们需要将左递归规则转换为等价的右递归规则。</p><ul><li>这个例子很明显了，首先如果不递归 左边一定是a</li><li>那么我们先把a写好 再用一个A‘ 来完成后续的递归b</li></ul><p>扩充BNF表示法</p><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260104173955136.png" alt="image-20260104173955136"></p><p>可以用于消除左递归</p><h3 id="文法和语言的Chomsky分类"><a href="#文法和语言的Chomsky分类" class="headerlink" title="文法和语言的Chomsky分类"></a>文法和语言的Chomsky分类</h3><ul><li><p><strong>3型文法</strong>：正规文法，正则文法</p><ul><li>限制最严，规则左侧只能是一个非终结符，右侧必须是一个终结符&#x2F;终结符后跟一个非终结符（左线性或右线性）</li><li>对于每一个左(右)线性文法，都存在一个与某等价的右(左)线性文法</li><li>与词法有关的文法一般属于3型文法</li></ul></li><li><p><strong>2型文法</strong>：程序设计文法，上下文无关文法，下推自动机PDA</p><ul><li>左侧只能有一个非终结符，右侧可以是终结符和非终结符的任意组合。</li><li>替换非终结符时不需要考虑前后字符<ul><li>描述语法结构</li><li>可以描述栈，所以可以处理嵌套结构，构成下推自动机</li></ul></li></ul></li><li><p><strong>1型文法</strong>：上下文相关文法</p><ul><li>要求产生式右边字符串长度不小于左边，替换符号必须在特定上下文环境中进行</li><li>线性有界自动机LBA</li></ul></li><li><p><strong>0型文法</strong>：无限制文法，短语结构文法</p><ul><li>没有任何限制，左侧只要包含一个非终结符即可。</li><li>递归可枚举语言</li><li>图灵机</li></ul></li><li><p>0-3限制逐渐增加，描述语言能力逐渐减弱</p><ul><li>1型号不允许A-e 但是2-3允许</li></ul></li><li><p>四种语言可分别被四种自动机接收</p></li></ul><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260104175137162.png" alt="image-20260104175137162"></p><h2 id="3-自动机理论基础"><a href="#3-自动机理论基础" class="headerlink" title="3.自动机理论基础"></a>3.自动机理论基础</h2><h3 id="有限自动机"><a href="#有限自动机" class="headerlink" title="有限自动机"></a>有限自动机</h3><p>自动机从识别语言出发，定义了语言。自动机是具有离散输入&#x2F;出系统的一种数学模型</p><ul><li>文法：是从产生语言的角度定义了语言。</li><li>自动机理论：是编译程序词法分析的理论基础。</li></ul><p>首先定义自动机</p><ul><li>状态转换图：定义在字母表上的有向图，满足三个初始条件：<ul><li><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260104195119849.png" alt="image-20260104195119849"></li></ul></li><li><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260104202849834.png" alt="image-20260104202849834"></li></ul><p>可以用这个图识别句子：从开始状态到终止状态经过的边上的符号序列。</p><p>有限自动机FA</p><ul><li><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260105132308984.png" alt="image-20260105132308984"></p><ul><li>没有栈 没有寄存器，只存储当前状态</li><li><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260105142142547.png" alt="image-20260105142142547"></li></ul></li><li><p>确定性有限自动机 DFA</p></li><li><p>非确定性有限自动机 NFA</p></li></ul><p>写到这里 我决定面向押题学习了</p><h1 id="————————————————————分割线——————————————————————"><a href="#————————————————————分割线——————————————————————" class="headerlink" title="————————————————————分割线——————————————————————"></a>————————————————————分割线——————————————————————</h1><h2 id="FIRST-FOLLOW集"><a href="#FIRST-FOLLOW集" class="headerlink" title="FIRST FOLLOW集"></a>FIRST FOLLOW集</h2><h3 id="一、-FIRST-集的求法"><a href="#一、-FIRST-集的求法" class="headerlink" title="一、 FIRST 集的求法"></a>一、 FIRST 集的求法</h3><p><strong>FIRST 集</strong>的意义是：从非终结符开始推导，可能出现在字符串开头的第一个<strong>终结符</strong>的集合。</p><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260106142934782.png" alt="image-20260106142934782"></p><h3 id="二、-FOLLOW-集的求法"><a href="#二、-FOLLOW-集的求法" class="headerlink" title="二、 FOLLOW 集的求法"></a>二、 FOLLOW 集的求法</h3><p><strong>FOLLOW 集</strong>的意义是：在某个句型中，紧跟在非终结符 A 后面可能出现的<strong>终结符</strong>的集合。</p><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260106142940871.png" alt="image-20260106142940871"></p><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260106191615768.png" alt="image-20260106191615768"></p><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260106200807972.png" alt="image-20260106200807972"></p><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260107103959401.png" alt="image-20260107103959401"></p><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260107104049991.png" alt="image-20260107104049991"></p><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260107105640459.png" alt="image-20260107105640459"></p><p>产生移进规约冲突 就不是LR0 直接SLR1 规约规约就看</p><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260107105835514.png" alt="image-20260107105835514"></p><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260107105743885.png" alt="image-20260107105743885"></p><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260107123417697.png" alt="image-20260107123417697"></p><h1 id="LL1："><a href="#LL1：" class="headerlink" title="LL1："></a>LL1：</h1><p>先优化文法，然后求first follow select 然后画表格 然后对输入串进行求解</p><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260107132642591.png" alt="image-20260107132642591"></p><h1 id="DFA-NFA"><a href="#DFA-NFA" class="headerlink" title="DFA NFA"></a>DFA NFA</h1><p>先优化表达式 然后画图，状态推导，最小化（小包大</p><h1 id="LR0-SLR1"><a href="#LR0-SLR1" class="headerlink" title="LR0 SLR1"></a>LR0 SLR1</h1><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260107132846824.png" alt="image-20260107132846824"></p><p>SLR 先求follow集 再擦掉不在集里的r</p><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260107132831741.png" alt="image-20260107132831741"></p><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260107133205604.png" alt="image-20260107133205604"></p><h1 id="LR1"><a href="#LR1" class="headerlink" title="LR1"></a>LR1</h1><p><img loading="lazy" decoding="async" src="https://raw.githubusercontent.com/lancer0rz/Picture/main/blog/image-20260107133359144.png" alt="image-20260107133359144"></p><p>LALR 合并同心</p>]]>
    </content>
    <id>https://l4n.top/2026/01/02/%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E6%9C%9F%E6%9C%AB%E5%A4%8D%E4%B9%A0/</id>
    <link href="https://l4n.top/2026/01/02/%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E6%9C%9F%E6%9C%AB%E5%A4%8D%E4%B9%A0/"/>
    <published>2026-01-02T06:33:31.000Z</published>
    <summary>
      <![CDATA[<h2 id="1-引言"><a href="#1-引言" class="headerlink" title="1. 引言"></a>1. 引言</h2><ul>
<li>所谓编译，就是高级语言转换为目标程序代码的过程</li>
<li>分为五部分<ul>
<li>词法分析：分析]]>
    </summary>
    <title>编译原理期末复习</title>
    <updated>2026-09-02T15:48:21.808Z</updated>
  </entry>
  <entry>
    <author>
      <name>l4n</name>
    </author>
    <category term="杂七杂八" scheme="https://l4n.top/tags/%E6%9D%82%E4%B8%83%E6%9D%82%E5%85%AB/"/>
    <content>
      <![CDATA[<p>最近想重拾一下pwn，发现wsl环境炸了gdb起不来。加上杂七杂八的环境非常的杂乱，遂重装，顺便记录一下踩坑过程。</p><h3 id="卸载"><a href="#卸载" class="headerlink" title="卸载"></a>卸载</h3><p>这个简单</p><figure class="highlight shell"><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">wsl --list </span><br><span class="line">wsl --unregister Ubuntu-22.03</span><br></pre></td></tr></table></figure><p>因为我之前已经设置了wsl的安装系统存储位置，如有需要请参照下面链接<br><a href="https://blog.csdn.net/farer_yyh/article/details/133934904">Win10&#x2F;11下安装WSL并修改WSL默认安装目录到其他盘_wsl设置默认路径-CSDN博客</a></p><h3 id="重新安装"><a href="#重新安装" class="headerlink" title="重新安装"></a>重新安装</h3><p>直接应用商店里安装即可</p><p>这里列一些wsl常用命令</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">wsl常用命令：</span><br><span class="line"> 查看WSL帮助：wsl --help</span><br><span class="line"> 更新WSL：wsl --update</span><br><span class="line"> 查看微软官方提供的子系统：wsl -l -o 或者 wsl --list --online </span><br><span class="line"> 安装子系统：wsl --install -d Ubuntu-20.04</span><br><span class="line"> 设置Ubuntu20.04为默认子系统：wsl --set-default Ubuntu-20.04 或者 wsl -s Ubuntu-20.04</span><br><span class="line"> 将 wsl2 设为默认版本：wsl --set-default-version 2</span><br><span class="line"> 将 Ubuntu 设为 wsl2：wsl --set-version ubuntu 2</span><br><span class="line"> 查看状态，包括当前的默认子系统是哪个、wsl 的版本是 1 还是 2、内核版本等：wsl --status</span><br><span class="line"> 查看详细信息，包括安装了哪些子系统、子系统的运行状态、wsl1 还是 wsl2：wsl --list --verbose  // 可简写为 wsl -l -v，非常常用！</span><br><span class="line"> 直接运行子系统命令：wsl -d ubuntu ls // 如果是在默认子系统中运行命令，可省略 -d &lt;Distro&gt;，例如：wsl ls</span><br><span class="line"> 关闭某个子系统：wsl --terminate &lt;Distro&gt; // --terminate 可简写为 -t，&lt;Distro&gt; 不能省。// 例如，关闭 Ubuntu：wsl -t ubuntu</span><br><span class="line"> 关闭所有子系统： wsl --shutdown</span><br><span class="line"> 卸载某个子系统：wsl --unregister &lt;Distro&gt; // 例如，卸载 Ubuntu：wsl --unregister ubuntu</span><br></pre></td></tr></table></figure><h4 id="基本环境配置"><a href="#基本环境配置" class="headerlink" title="基本环境配置"></a>基本环境配置</h4><p>设置默认root登录 （或者你可以在wsl.conf里配置）</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">ubuntu2204.exe config --default-user root</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></pre></td><td class="code"><pre><span class="line">vim /etc/apt/sources.list</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><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"># 默认注释了源码镜像以提高 apt update 速度，如有需要可自行取消注释</span><br><span class="line">deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial main restricted universe multiverse</span><br><span class="line"># deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial main restricted universe multiverse</span><br><span class="line">deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial-updates main restricted universe multiverse</span><br><span class="line"># deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial-updates main restricted universe multiverse</span><br><span class="line">deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial-backports main restricted universe multiverse</span><br><span class="line"># deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial-backports main restricted universe multiverse</span><br><span class="line">deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial-security main restricted universe multiverse</span><br><span class="line"># deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial-security main restricted universe multiverse</span><br><span class="line"> </span><br><span class="line"># 预发布软件源，不建议启用</span><br><span class="line"># deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial-proposed main restricted universe multiverse</span><br><span class="line"># deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial-proposed main restricted universe multiverse</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">sudo apt-get update</span><br><span class="line">sudo apt-get upgrade</span><br></pre></td></tr></table></figure><p>安装zsh 与oh-my-zsh全套美化</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">sudo apt install zsh</span><br><span class="line"></span><br><span class="line">sh -c &quot;$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)&quot; </span><br><span class="line"></span><br><span class="line">git clone --depth=1 https://github.com/romkatv/powerlevel10k.git $&#123;ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom&#125;/themes/powerlevel10k</span><br></pre></td></tr></table></figure><p>设置默认终端为zsh</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">chsh -s /bin/zsh</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></pre></td><td class="code"><pre><span class="line">sudo apt install build-essential</span><br></pre></td></tr></table></figure><h4 id="安装conda"><a href="#安装conda" class="headerlink" title="安装conda"></a>安装conda</h4><p>我选择miniconda 比较轻量<br>可以直接看官方文档<br><a href="https://www.anaconda.com/docs/getting-started/miniconda/install#linux-2">Installing Miniconda - Anaconda</a></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></pre></td><td class="code"><pre><span class="line">mkdir -p ~/miniconda3</span><br><span class="line">wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda3/miniconda.sh</span><br><span class="line">bash ~/miniconda3/miniconda.sh -b -u -p ~/miniconda3</span><br><span class="line">rm ~/miniconda3/miniconda.sh</span><br><span class="line">source ~/miniconda3/bin/activate</span><br><span class="line">conda init --all</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><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><br><span class="line">conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/</span><br><span class="line">conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/</span><br><span class="line">conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/</span><br><span class="line">conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/bioconda/</span><br><span class="line">#设置bioconda</span><br><span class="line">conda config --add channels bioconda</span><br><span class="line">conda config --add channels conda-forge</span><br><span class="line">#设置搜索时显示通道地址</span><br><span class="line">conda config --set show_channel_urls yes</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></pre></td><td class="code"><pre><span class="line">conda create -n env_name python=3.12</span><br></pre></td></tr></table></figure><p>启用虚拟环境&#x2F;查询虚拟环境</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">conda env list</span><br><span class="line">conda activate xxx</span><br></pre></td></tr></table></figure><p>pip换源</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"># 清华源</span><br><span class="line">pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple</span><br><span class="line"># 阿里源</span><br><span class="line">pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/</span><br><span class="line"># 腾讯源</span><br><span class="line">pip config set global.index-url http://mirrors.cloud.tencent.com/pypi/simple</span><br><span class="line"># 豆瓣源</span><br><span class="line">pip config set global.index-url http://pypi.douban.com/simple/</span><br><span class="line"># 换回默认源</span><br><span class="line">pip config unset global.index-url</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://l4n.top/2025/07/23/wsl-ubuntu-%E4%B8%AA%E4%BA%BA%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE%E6%8C%87%E5%8D%97/</id>
    <link href="https://l4n.top/2025/07/23/wsl-ubuntu-%E4%B8%AA%E4%BA%BA%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE%E6%8C%87%E5%8D%97/"/>
    <published>2025-07-23T14:36:19.000Z</published>
    <summary>
      <![CDATA[<p>最近想重拾一下pwn，发现wsl环境炸了gdb起不来。加上杂七杂八的环境非常的杂乱，遂重装，顺便记录一下踩坑过程。</p>
<h3 id="卸载"><a href="#卸载" class="headerlink" title="卸载"></a>卸载</h3><p>这个简单<]]>
    </summary>
    <title>wsl-ubuntu 个人环境配置指南</title>
    <updated>2026-09-02T16:35:34.283Z</updated>
  </entry>
  <entry>
    <author>
      <name>l4n</name>
    </author>
    <category term="移动端对抗" scheme="https://l4n.top/tags/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%AF%B9%E6%8A%97/"/>
    <content>
      <![CDATA[<h2 id="寄存器间接跳转的基本概念"><a href="#寄存器间接跳转的基本概念" class="headerlink" title="寄存器间接跳转的基本概念"></a>寄存器间接跳转的基本概念</h2><p>寄存器存储地址：寄存器间接跳转依赖于寄存器内的值来指定要跳转到的地址，而这个地址可以在程序运行时动态改变。<br>跳转指令：在汇编语言中，类似于 jmp eax 或 br x8 的指令，使用寄存器（如 eax, x8 等）存储目标地址，执行这些指令后，程序的控制流就会转移到寄存器中的指定地址。<br>作为混淆，这种基于寄存器间接跳转的混淆会使反编译器无法正常识别代码逻辑<br>那么为了能够看清程序的逻辑，我们需要对这玩意进行修复。</p><h2 id="简单的处理思路"><a href="#简单的处理思路" class="headerlink" title="简单的处理思路"></a>简单的处理思路</h2><p>大部分的间接跳转将用于计算地址的值储存在data段，而反编译器不会去解析data段的常量。这是因为在反编译器的预设中，data段是默认可写的。这时被引用的值被视为变量，就不会进行常量传播优化。<br>如果我们将data段的属性改为只读，可以促进反编译器的常量传播，去除一部分混淆。</p><h2 id="自动化一点的进阶思路"><a href="#自动化一点的进阶思路" class="headerlink" title="自动化一点的进阶思路"></a>自动化一点的进阶思路</h2><p>一个比较常规的思路是用模拟执行框架来解出跳转时对应寄存器存储的具体值，然后进行patch去除。这里我选择了使用unicorn，当然 angr&#x2F;qiling等框架应该也是可以的，只是作为初学者我不太会写，还是unicorn写着顺手。</p><h3 id="实例：N1CTF-junior-easy-re"><a href="#实例：N1CTF-junior-easy-re" class="headerlink" title="实例：N1CTF junior easy-re"></a>实例：N1CTF junior easy-re</h3><p>是一个arm64的elf，有间接跳转和字符串混淆。字符串混淆可以通过unicorn 内存写入trace简单的过掉。这里只讨论如何去除间接跳转混淆。<br>一个具体的汇编代码片段例子：</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></pre></td><td class="code"><pre><span class="line">loc_1E60                                ; CODE XREF: sub_1FD4+2C↓j</span><br><span class="line">LDR             W8, [SP,#0x1C0+var_140] ; Load from Memory</span><br><span class="line">MOV             W9, #2                  ; Rd = Op2</span><br><span class="line">MOV             W10, #0                 ; Rd = Op2</span><br><span class="line">CMP             W8, #0x100              ; Set cond. codes on Op1 - Op2</span><br><span class="line">CSEL            W8, W9, W10, LT         ; Conditional Select</span><br><span class="line">MOV             W11, W8                 ; Rd = Op2</span><br><span class="line">SXTW            X11, W11                ; Signed Extend Word</span><br><span class="line">MOV             X12, #8                 ; Rd = Op2</span><br><span class="line">MUL             X11, X12, X11           ; Multiply</span><br><span class="line">LDR             X12, [SP,#0x1C0+var_190] ; Load from Memory</span><br><span class="line">ADD             X11, X12, X11           ; Rd = Op1 + Op2</span><br><span class="line">LDR             X11, [X11]              ; Load from Memory</span><br><span class="line">MOV             W8, #0x464CA149</span><br><span class="line">LDR             W9, [SP,#0x1C0+var_19C] ; Load from Memory</span><br><span class="line">SUBS            W8, W8, W9              ; Rd = Op1 - Op2</span><br><span class="line">MOV             W13, W8                 ; Rd = Op2</span><br><span class="line">SXTW            X13, W13                ; Signed Extend Word</span><br><span class="line">ADD             X11, X11, X13           ; Rd = Op1 + Op2</span><br><span class="line">BR              X11                     ; Branch To Register</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">mov x1 #x</span><br><span class="line">mov x2 #x</span><br><span class="line">...</span><br><span class="line">cslr xx x1 x2 eq </span><br><span class="line">...</span><br><span class="line">br xx</span><br></pre></td></tr></table></figure><p>基于这样的特征，我们可以在初始化的时候，使用模式匹配遍历.text .initarray等代码段。然后将mov的地址，br的地址（cslr地址）存入一个要去执行的list里。在遍历完后，循环分别处理每一段地址作为参数传入模拟执行函数。</p><p>假设我们已经识别了所有的指令，现在要做的就是如何去除。<br>如果优化掉后面的查表计算等操作，将前面的值分别确定，模拟执行拿到数据后，就可以直接patch成正常的跳转，自然可以被正常的反编译。<br>于是我们的思路有了，具体拆成以下几个部分来分别实现。</p><ol><li>patch cslr 为 mov xx1&#x2F;mov xx2</li><li>依据不明确取值的寄存器，向上溯回拿到值，模拟执行后拿到后续br的值</li><li>patch掉 mov下标 cslr 查表 计算地址 brxx</li><li>cmp xx后，逻辑patch为 b xx1 和 b xx2</li></ol><p>一步一步完成吧。</p><h2 id="step1-按照不同跳转分支进行信息搜集"><a href="#step1-按照不同跳转分支进行信息搜集" class="headerlink" title="step1 按照不同跳转分支进行信息搜集"></a>step1 按照不同跳转分支进行信息搜集</h2><p>先拿到cslr指令和操作数，然后依据对应的操作数，拿到mov指令的形状后，patch，分为两个分支模拟执行</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><span class="line">90</span><br><span class="line">91</span><br></pre></td><td class="code"><pre><span class="line">saved_state = &#123;&#125;</span><br><span class="line">def save_state(mu):</span><br><span class="line">    regs = &#123;&#125;</span><br><span class="line">    regs_list = [</span><br><span class="line">        UC_ARM64_REG_X0, UC_ARM64_REG_X1, UC_ARM64_REG_X2, UC_ARM64_REG_X3,</span><br><span class="line">        UC_ARM64_REG_X4, UC_ARM64_REG_X5, UC_ARM64_REG_X6, UC_ARM64_REG_X7,</span><br><span class="line">        UC_ARM64_REG_X8, UC_ARM64_REG_X9, UC_ARM64_REG_X10, UC_ARM64_REG_X11,</span><br><span class="line">        UC_ARM64_REG_X12, UC_ARM64_REG_X13, UC_ARM64_REG_X14, UC_ARM64_REG_X15,</span><br><span class="line">        UC_ARM64_REG_X16, UC_ARM64_REG_X17, UC_ARM64_REG_X18, UC_ARM64_REG_X19,</span><br><span class="line">        UC_ARM64_REG_X20, UC_ARM64_REG_X21, UC_ARM64_REG_X22, UC_ARM64_REG_X23,</span><br><span class="line">        UC_ARM64_REG_X24, UC_ARM64_REG_X25, UC_ARM64_REG_X26, UC_ARM64_REG_X27,</span><br><span class="line">        UC_ARM64_REG_X28, UC_ARM64_REG_X29, UC_ARM64_REG_X30, UC_ARM64_REG_SP,</span><br><span class="line">        UC_ARM64_REG_PC</span><br><span class="line">    ]</span><br><span class="line">    for reg in regs_list:</span><br><span class="line">        regs[reg] = mu.reg_read(reg)</span><br><span class="line">    return regs</span><br><span class="line"></span><br><span class="line">def restore_state(mu, regs):</span><br><span class="line">    for reg, val in regs.items():</span><br><span class="line">        mu.reg_write(reg, val)</span><br><span class="line"></span><br><span class="line">def branch_hook(mu, addr, size, user_data):</span><br><span class="line">    # 对当前指令反汇编，若遇到BR指令则终止模拟</span><br><span class="line">    for inst in cs.disasm(mu.mem_read(addr, size), addr):</span><br><span class="line">        if inst.mnemonic.lower() == &quot;br&quot;:</span><br><span class="line">            print(&quot;&gt;&gt;&gt; BR 指令在 0x%x 被触发，停止模拟&quot; % addr)</span><br><span class="line">            mu.emu_stop()</span><br><span class="line">            break</span><br><span class="line">    return </span><br><span class="line"></span><br><span class="line">pass_addr = [0x24A8,0x24AC,0x1E20]</span><br><span class="line"></span><br><span class="line">csel_handled = False</span><br><span class="line">def hook_code(mu, address, size, user_data): </span><br><span class="line">    global save_state , csel_handled</span><br><span class="line">    if address in LIBC_FUNCS:</span><br><span class="line">        print(&quot;&gt;&gt;&gt; Entering simulated library function at 0x%x&quot; % address)</span><br><span class="line">        LIBC_FUNCS[address](mu)</span><br><span class="line">        return</span><br><span class="line">    </span><br><span class="line">    # if address in pass_addr:# 修改类似eip的寄存器来跳过这两个地址</span><br><span class="line">    #     mu.reg_write(UC_ARM64_REG_PC, address+size)</span><br><span class="line">    #     print(&#x27;&gt;&gt;&gt; Skipping address 0x%x&#x27; % address)</span><br><span class="line">    </span><br><span class="line">    # print(&#x27;&gt;&gt;&gt; Tracing instruction at 0x%x, instruction size = 0x%x&#x27; % (address, size))</span><br><span class="line">    # 打印汇编代码</span><br><span class="line">    for inst in cs.disasm(mu.mem_read(address, size), address):</span><br><span class="line">        print(&quot;0x%x:\t%s\t%s&quot; % (inst.address, inst.mnemonic, inst.op_str))</span><br><span class="line">        #如果当前指令是CSEL</span><br><span class="line">        if inst.mnemonic.lower() == &quot;csel&quot; and not csel_handled:</span><br><span class="line">            print(&quot;&gt;&gt;&gt; csel detected&quot;)</span><br><span class="line">            csel_handled = True</span><br><span class="line">            csel_addr = address</span><br><span class="line">            original_bytes = mu.mem_read(csel_addr, size)</span><br><span class="line">            saved_state = save_state(mu)#保存当前状态</span><br><span class="line">            #获取操作寄存器</span><br><span class="line">            op = inst.op_str.split(&quot;, &quot;)</span><br><span class="line">            #组装mov指令</span><br><span class="line">            mov_inst1 = &quot;mov &quot; + op[0] + &quot;, &quot; + op[1] +&#x27;;&#x27;</span><br><span class="line">            mov_inst2 = &quot;mov &quot; + op[0] + &quot;, &quot; + op[2] +&#x27;;&#x27;</span><br><span class="line">            # print(&#x27;&gt;&gt;&gt; mov instructions:&#x27;, mov_inst1, mov_inst2)</span><br><span class="line">            #编译mov指令</span><br><span class="line">            asm_mov_x1_x2, _ = ks.asm(mov_inst1)</span><br><span class="line">            asm_mov_x1_x3, _ = ks.asm(mov_inst2)</span><br><span class="line">            # print(&#x27;there&#x27;)</span><br><span class="line">            #定义内部函数，用于进行两次patch</span><br><span class="line">            def run_branch_test(patch_bytes, branch_label):</span><br><span class="line">                # patch 当前 csel 指令位置</span><br><span class="line">                mu.mem_write(csel_addr, bytes(patch_bytes))</span><br><span class="line">                print(f&quot;&gt;&gt;&gt; 已将 csel 指令 patch 为 &#123;branch_label&#125;，开始执行至遇到 BR 指令&quot;)</span><br><span class="line">                # 添加一个临时 hook 来检测 BR 指令</span><br><span class="line">                bh_id = mu.hook_add(UC_HOOK_CODE, branch_hook)</span><br><span class="line">                try:</span><br><span class="line">                    mu.emu_start(mu.reg_read(UC_ARM64_REG_PC), END_POINT)</span><br><span class="line">                except UcError as e:</span><br><span class="line">                    print(&quot;模拟过程中出现错误:&quot;, e)</span><br><span class="line">                mu.hook_del(bh_id)</span><br><span class="line">                state = save_state(mu)</span><br><span class="line">                print(&quot;&gt;&gt;&gt; 运行结束后状态:&quot;, state)</span><br><span class="line">                return state</span><br><span class="line">            </span><br><span class="line">            # 首先用 mov x1, x2 运行</span><br><span class="line">            state_branch1 = run_branch_test(asm_mov_x1_x2, &quot;mov x1, x2&quot;)</span><br><span class="line">            # 恢复现场</span><br><span class="line">            restore_state(mu, saved_state)</span><br><span class="line">            # 然后用 mov x1, x3 运行</span><br><span class="line">            state_branch2 = run_branch_test(asm_mov_x1_x3, &quot;mov x1, x3&quot;)</span><br><span class="line">            print(&quot;&gt;&gt;&gt; 分别 patch 后两条分支的最终状态:&quot;)</span><br><span class="line">            print(&quot;    mov x1, x2 状态:&quot;, hex(state_branch1[UC_ARM64_REG_X8]))</span><br><span class="line">            print(&quot;    mov x1, x3 状态:&quot;, hex(state_branch2[UC_ARM64_REG_X8]))</span><br></pre></td></tr></table></figure><h2 id="step2-模拟执行，获取最终跳转值"><a href="#step2-模拟执行，获取最终跳转值" class="headerlink" title="step2 模拟执行，获取最终跳转值"></a>step2 模拟执行，获取最终跳转值</h2><p>已经有了模拟执行的框架，这里就直接写一个方法去拿到相关的指令储存起来</p><p>那么有小伙伴就要问了，如何拿到相关的指令呢？这里其实可以注意到，这段混淆实际上是通过先取立即数和地址，然后存入栈，在后续重新从栈上拿回地址。所以我们可以在范围内遍历有关sp的指令，然后确定偏移，向上溯回，找到所有对改位置进行写入的寄存器。通过跟踪这些寄存器，我们就能拿到相应的赋值指令，进而在后续模拟执行。</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><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br></pre></td><td class="code"><pre><span class="line">def hit_hook(self, mu: Uc, addr: int, size: int, user_data) -&gt; None:</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        代码 hook，用于捕获并存储 hit 指令（通过向上追踪含 SP 且 ldr 指令）。</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        </span><br><span class="line">        try:</span><br><span class="line">            code = mu.mem_read(addr, size)</span><br><span class="line">        except UcError:</span><br><span class="line">            return</span><br><span class="line"></span><br><span class="line">        for inst in self.cs.disasm(code, addr):</span><br><span class="line">            # print(f&quot;hit hook : &gt;&gt;&gt; &#123;hex(addr)&#125; &#123;inst.mnemonic&#125; &#123;inst.op_str&#125;&quot;)</span><br><span class="line">            if inst.mnemonic.lower() == &quot;br&quot;:</span><br><span class="line">                mu.emu_stop()</span><br><span class="line">                break</span><br><span class="line">            if inst.mnemonic.lower() == &quot;stur&quot; or inst.mnemonic.lower() == &quot;ldur&quot;:</span><br><span class="line">                self.mu.reg_write(UC_ARM64_REG_PC, addr + size)</span><br><span class="line">                # print(f&quot;&gt;&gt;&gt; stur or ldur detected, skip&quot;)</span><br><span class="line">                return</span><br><span class="line">            if &quot;sp&quot; in inst.op_str and &quot;ldr&quot; in inst.mnemonic:</span><br><span class="line">                # print(f&quot;attached ldr sp instruction: &#123;inst.mnemonic&#125; &#123;inst.op_str&#125;&quot;)</span><br><span class="line">                parts = inst.op_str.split(&quot;, [&quot;)</span><br><span class="line">                target = parts[0] if &quot;sp&quot; in parts[0] else parts[1]</span><br><span class="line">                target_state = False</span><br><span class="line">                target2 = &quot;&quot;</span><br><span class="line">                curr_addr = addr</span><br><span class="line">                for _ in range(1, 200):</span><br><span class="line">                    curr_addr -= inst.size</span><br><span class="line">                    try:</span><br><span class="line">                        code_b = mu.mem_read(curr_addr, 4)</span><br><span class="line">                    except UcError:</span><br><span class="line">                        continue</span><br><span class="line">                    for inst_b in self.cs.disasm(code_b, curr_addr):</span><br><span class="line">                        if target in inst_b.op_str and &quot;ldr&quot; not in inst_b.mnemonic and not target_state:</span><br><span class="line">                            # print(f&quot;hit instruction: &#123;inst_b.mnemonic&#125; &#123;inst_b.op_str&#125;&quot;)</span><br><span class="line">                            target_state = True</span><br><span class="line">                            self.hit_inst.append(inst_b)</span><br><span class="line">                            parts_b = inst_b.op_str.split(&quot;, &quot;)</span><br><span class="line">                            target2 = parts_b[0] if target not in parts_b[0] else parts_b[1]</span><br><span class="line">                            break</span><br><span class="line">                        if target_state and target2 in inst_b.op_str and &quot;mov&quot; in inst_b.mnemonic and &#x27;#0x&#x27; in inst_b.op_str:</span><br><span class="line">                            self.hit_inst.append(inst_b)</span><br><span class="line">                        if target_state and target2 in inst_b.op_str and &#x27;adr&#x27; in inst_b.mnemonic:</span><br><span class="line">                            # print(f&quot;hit instruction: &#123;inst_b.mnemonic&#125; &#123;inst_b.op_str&#125;&quot;)</span><br><span class="line">                            if &quot;adrp&quot; in inst_b.mnemonic:</span><br><span class="line">                                try:</span><br><span class="line">                                    next_code = mu.mem_read(curr_addr + inst_b.size, 4)</span><br><span class="line">                                except UcError:</span><br><span class="line">                                    continue</span><br><span class="line">                                for inst_el in self.cs.disasm(next_code, curr_addr + inst_b.size):</span><br><span class="line">                                    if &quot;add&quot; in inst_el.mnemonic:</span><br><span class="line">                                        self.hit_inst.append(inst_el)</span><br><span class="line">                                        self.hit_inst.append(inst_b)</span><br><span class="line">                            else:</span><br><span class="line">                                self.hit_inst.append(inst_b)</span><br><span class="line">        return</span><br><span class="line"></span><br><span class="line">    def branch_hook(self, mu: Uc, addr: int, size: int, user_data) -&gt; None:</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        当执行到 BR 指令时停止模拟</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        try:</span><br><span class="line">            code = mu.mem_read(addr, size)</span><br><span class="line">        except UcError:</span><br><span class="line">            return</span><br><span class="line"></span><br><span class="line">        for inst in self.cs.disasm(code, addr):</span><br><span class="line">            if inst.mnemonic.lower() == &quot;br&quot;:</span><br><span class="line">                # print(f&quot;&gt;&gt;&gt; BR 指令在 0x&#123;addr:x&#125; 被触发，停止模拟&quot;)</span><br><span class="line">                mu.emu_stop()</span><br><span class="line">                break</span><br><span class="line">    </span><br><span class="line">    def get_hit_inst(self, addr: int) -&gt; None:</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        从指定地址向后模拟收集 hit 指令</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        # print(f&quot;&gt;&gt;&gt; Getting hit instructions from 0x&#123;addr:x&#125; to 0x&#123;self.inst_br_addr_temp:x&#125;&quot;)</span><br><span class="line">        bh_id = self.mu.hook_add(UC_HOOK_CODE, self.hit_hook)</span><br><span class="line">        self.mu.hook_del(self.hook_code_id)</span><br><span class="line">        # 搜寻前面的有没有立即数存储指令</span><br><span class="line">        for i in range(1, 50):</span><br><span class="line">            try:</span><br><span class="line">                code = self.mu.mem_read(addr - i * 4, 4)</span><br><span class="line">            except UcError:</span><br><span class="line">                continue</span><br><span class="line">            for inst in self.cs.disasm(code, addr - i * 4):</span><br><span class="line">                # print(f&quot;get_hit_inst : &gt;&gt;&gt; &#123;hex(addr - i * 4)&#125; &#123;inst.mnemonic&#125; &#123;inst.op_str&#125;&quot;)</span><br><span class="line">                if (&quot;mov&quot; in inst.mnemonic or &quot;ldr&quot; in inst.mnemonic ) and &#x27;#&#x27; in inst.op_str:</span><br><span class="line">                    self.hit_inst.append(inst)</span><br><span class="line">        </span><br><span class="line">        try:</span><br><span class="line">            self.mu.emu_start(addr - 4, self.inst_br_addr_temp)</span><br><span class="line">        except UcError as e:</span><br><span class="line">            print(&quot;模拟过程中出现错误:&quot;, e)</span><br><span class="line">            self.err_flag = True</span><br><span class="line">            self.err_addr.append((self.inst_mov_addr_temp, self.inst_br_addr_temp,&quot;error at get_hit_inst:&quot;, e))</span><br><span class="line">        self.mu.hook_del(bh_id)</span><br><span class="line">        self.hook_code_id = self.mu.hook_add(UC_HOOK_CODE, self.hook_code)</span><br><span class="line">        # print(&quot;\n&gt;&gt;&gt; Hit instructions:&quot;)</span><br><span class="line">        # for inst in self.hit_inst:</span><br><span class="line">        #     print(f&quot;    0x&#123;inst.address:x&#125;: &#123;inst.mnemonic&#125; &#123;inst.op_str&#125;&quot;)</span><br><span class="line"></span><br><span class="line">    def run_hit_inst(self) -&gt; None:</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        只模拟 hit 指令（hit_inst 列表中的指令）。</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        # print(&quot;&gt;&gt;&gt; Running hit instructions&quot;)</span><br><span class="line">        original_pc = self.mu.reg_read(UC_ARM64_REG_PC)</span><br><span class="line">        for inst in reversed(self.hit_inst):</span><br><span class="line">            </span><br><span class="line">            try:</span><br><span class="line">                self.mu.emu_start(inst.address, inst.address + inst.size)</span><br><span class="line">            except UcError as e:</span><br><span class="line">                print(&quot;run_hit_inst 模拟出现错误:&quot;, e)</span><br><span class="line">                self.err_flag = True</span><br><span class="line">                self.err_addr.append((self.inst_mov_addr_temp, self.inst_br_addr_temp, &quot;error at run_hit_inst:&quot;, e))</span><br><span class="line">        self.mu.reg_write(UC_ARM64_REG_PC, original_pc)</span><br></pre></td></tr></table></figure><p>在unicorn加载的时候，没有load .data段到正确的地方，导致在.data段上的值无法被正确的读取。我们可以使用lief对文件进行解析，分别计算出对应section的虚拟地址和正确的patch地址，然后代替直接将整个文件加载，把对应的段分别正确的映射到unicorn的虚拟内存里。</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></pre></td><td class="code"><pre><span class="line">&quot;&quot;&quot;</span><br><span class="line">        利用 LIEF 解析 ELF 文件，将各 section 映射到 Unicorn 内存中。</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        binary = lief.parse(self.binary_path)</span><br><span class="line">        mapped = &#123;&#125;  # 已映射区域字典</span><br><span class="line"></span><br><span class="line">        for section in binary.sections:</span><br><span class="line">            vaddr = section.virtual_address</span><br><span class="line">            mem_size = section.size</span><br><span class="line">            if mem_size == 0 or vaddr == 0:</span><br><span class="line">                continue</span><br><span class="line">            if section.name == &quot;.text&quot;:</span><br><span class="line">                self.text_section = section</span><br><span class="line">            start_addr = (vaddr // self.PAGE_SIZE) * self.PAGE_SIZE</span><br><span class="line">            end_addr = ((vaddr + mem_size + self.PAGE_SIZE - 1) // self.PAGE_SIZE) * self.PAGE_SIZE</span><br><span class="line">            map_size = end_addr - start_addr</span><br><span class="line"></span><br><span class="line">            if start_addr not in mapped:</span><br><span class="line">                try:</span><br><span class="line">                    self.mu.mem_map(start_addr, map_size)</span><br><span class="line">                    mapped[start_addr] = map_size</span><br><span class="line">                except UcError as e:</span><br><span class="line">                    print(f&quot;映射地址&#123;section.name&#125; 0x&#123;start_addr:x&#125;（大小 0x&#123;map_size:x&#125;）失败: &#123;e&#125;&quot;)</span><br><span class="line">                    continue</span><br><span class="line"></span><br><span class="line">            if section.content:</span><br><span class="line">                data = bytes(section.content)</span><br><span class="line">                offset = vaddr - start_addr</span><br><span class="line">                try:</span><br><span class="line">                    self.mu.mem_write(start_addr + offset, data)</span><br><span class="line">                except UcError as e:</span><br><span class="line">                    print(f&quot;写入数据到地址 0x&#123;start_addr+offset:x&#125; 失败: &#123;e&#125;&quot;)</span><br></pre></td></tr></table></figure><p>跑一下发现，虽然有几个段没有成功映射，但大部分段都成功的加载了。</p><h2 id="step3-使用上一步的值进行Patch"><a href="#step3-使用上一步的值进行Patch" class="headerlink" title="step3 使用上一步的值进行Patch"></a>step3 使用上一步的值进行Patch</h2><p>获取了正确的跳转地址后，我们就可以想想如何去patch逻辑了。<br>再看看代码</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></pre></td><td class="code"><pre><span class="line">loc_1E60                                ; CODE XREF: sub_1FD4+2C↓j</span><br><span class="line">LDR             W8, [SP,#0x1C0+var_140] ; Load from Memory</span><br><span class="line">MOV             W9, #2                  ; Rd = Op2</span><br><span class="line">MOV             W10, #0                 ; Rd = Op2</span><br><span class="line">CMP             W8, #0x100              ; Set cond. codes on Op1 - Op2</span><br><span class="line">CSEL            W8, W9, W10, LT         ; Conditional Select</span><br><span class="line">MOV             W11, W8                 ; Rd = Op2</span><br><span class="line">SXTW            X11, W11                ; Signed Extend Word</span><br><span class="line">MOV             X12, #8                 ; Rd = Op2</span><br><span class="line">MUL             X11, X12, X11           ; Multiply</span><br><span class="line">LDR             X12, [SP,#0x1C0+var_190] ; Load from Memory</span><br><span class="line">ADD             X11, X12, X11           ; Rd = Op1 + Op2</span><br><span class="line">LDR             X11, [X11]              ; Load from Memory</span><br><span class="line">MOV             W8, #0x464CA149</span><br><span class="line">LDR             W9, [SP,#0x1C0+var_19C] ; Load from Memory</span><br><span class="line">SUBS            W8, W8, W9              ; Rd = Op1 - Op2</span><br><span class="line">MOV             W13, W8                 ; Rd = Op2</span><br><span class="line">SXTW            X13, W13                ; Signed Extend Word</span><br><span class="line">ADD             X11, X11, X13           ; Rd = Op1 + Op2</span><br><span class="line">BR              X11                     ; Branch To Register</span><br></pre></td></tr></table></figure><p>cslr之后，所有的计算基本都是对最终br的寄存器进行地址计算，而事实上我们已经有了这些值，就不需要再进行这些计算了。<br>先设计一下应该执行的指令：</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">MOV             W9, #2                  ; Rd = Op2</span><br><span class="line">MOV             W10, #0                 ; Rd = Op2</span><br><span class="line">CMP             W8, #0x100              ; Set cond. codes on Op1 - Op2</span><br><span class="line">CSEL            W8, W9, W10, LT         ; Conditional Select</span><br><span class="line">...</span><br><span class="line">cmp             w8, w9            </span><br><span class="line">beq             addr1 ; 等于就跳转，即第一种情况                  </span><br><span class="line">bne             addr2   ; 不等就跳转，即第二种情况          </span><br><span class="line">...</span><br><span class="line">BR              reg    ; patch到这里</span><br></pre></td></tr></table></figure><p>注意到，CMP后的指令顺序是不变的，而且cmp的结果就决定了后续跳转的地址。所以我们可以拿下面的固定不变指令来做一点文章。<br>如果我们设计为：</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></pre></td><td class="code"><pre><span class="line">nop &lt; mov x1 #r</span><br><span class="line">nop &lt; mov x2 #i</span><br><span class="line">...</span><br><span class="line">cmp   xx #0</span><br><span class="line">br.ne addr1</span><br><span class="line">br    addr2</span><br><span class="line">nop</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>就可以完成这些工作。但是有一个问题是，如果过早jmp，br后面的代码对于之后程序的执行可能会有一定影响。<br>所以我们要进行指令前移，在所有其余指令执行完后，再进行br操作。<br>如何实现指令前移呢？观察代码。在一些混淆中，br之前做了一些常量保存工作，如果贸然nop掉会导致后续指令无法执行。<br>所以我们可以采取这样的做法</p><ol><li>遍历cslr到br间的所有指令，取出str指令</li><li>将str指令上移</li><li>patch跳转指令，然后nop掉剩余的所有指令</li></ol><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><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><span class="line">90</span><br></pre></td><td class="code"><pre><span class="line">def patch_br(self) -&gt; None:</span><br><span class="line">    &quot;&quot;&quot;</span><br><span class="line">        扫描 self.csel_addr_temp 到 self.inst_br_addr_temp 区间内的所有指令，</span><br><span class="line">        记录所有 STR 指令，将所有 STR 指令上移，其余指令下移，</span><br><span class="line">        重新构造新的指令序列：首先放置所有 STR 指令，</span><br><span class="line">        然后是两条分支指令，</span><br><span class="line">        剩余空间填充 NOP 指令，</span><br><span class="line">        最后将组装好的字节补丁写入内存。</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line"></span><br><span class="line">    all_insts = []   # 保存区间内所有(地址, 指令)元组</span><br><span class="line">    str_insts = []   # 保存STR类型指令</span><br><span class="line"></span><br><span class="line">    addr_start = self.csel_addr_temp </span><br><span class="line">    for addr in range(addr_start, self.inst_br_addr_temp, 4):</span><br><span class="line">        try:</span><br><span class="line">            code = self.mu.mem_read(addr, 4)</span><br><span class="line">        except UcError:</span><br><span class="line">            continue</span><br><span class="line">            for inst in self.cs.disasm(code, addr):</span><br><span class="line">                all_insts.append((addr, inst))</span><br><span class="line">                if &quot;str&quot; in inst.mnemonic.lower():</span><br><span class="line">                    str_insts.append((addr, inst))</span><br><span class="line"></span><br><span class="line">        print(&quot;&gt;&gt;&gt; 扫描到的所有指令:&quot;)</span><br><span class="line">    for addr, inst in all_insts:</span><br><span class="line">        print(f&quot;    0x&#123;addr:x&#125;: &#123;inst.mnemonic&#125; &#123;inst.op_str&#125;&quot;)</span><br><span class="line">        print(&quot;&gt;&gt;&gt; 其中 STR 指令:&quot;)</span><br><span class="line">    for addr, inst in str_insts:</span><br><span class="line">        print(f&quot;    0x&#123;addr:x&#125;: &#123;inst.mnemonic&#125; &#123;inst.op_str&#125;&quot;)</span><br><span class="line"></span><br><span class="line">        # 构造新的指令序列</span><br><span class="line">        new_inst_list = []</span><br><span class="line">    # (1) 添加所有 STR 指令（以其原有汇编文本为准）</span><br><span class="line">    for _, inst in str_insts:</span><br><span class="line">        asm_line = f&quot;&#123;inst.mnemonic&#125; &#123;inst.op_str&#125;&quot;</span><br><span class="line">        new_inst_list.append(asm_line)</span><br><span class="line"></span><br><span class="line">        # (2) 添加两条跳转指令（分支指令），这里假定 self.br_value 存储了两个跳转目标</span><br><span class="line">        # 构造分支指令文本（注意汇编语法，根据实际需要可能调整条件代码）</span><br><span class="line"></span><br><span class="line">        b_inst1 = &quot;b.ne #&quot; + hex(self.br_value[0] - self.csel_addr_temp - len(str_insts)*4)</span><br><span class="line">    b_inst2 = &quot;b #&quot; + hex(self.br_value[1] - self.csel_addr_temp - len(str_insts)*4 - 4)</span><br><span class="line">    for i in range(2):</span><br><span class="line">        print(f&quot;    0x&#123;self.inst_br_addr_temp + i * 4:x&#125;: &#123;b_inst1 if i == 0 else b_inst2&#125;&quot;)</span><br><span class="line">        new_inst_list.append(b_inst1)</span><br><span class="line">    new_inst_list.append(b_inst2)</span><br><span class="line"></span><br><span class="line">    # 使用 Keystone 汇编生成机器码，并计算补丁区域大小</span><br><span class="line">    patch_bytes = b&quot;&quot;</span><br><span class="line">    for asm_line in new_inst_list:</span><br><span class="line">        try:</span><br><span class="line">            encoding, _ = self.ks.asm(asm_line)</span><br><span class="line">            patch_bytes += bytes(encoding)</span><br><span class="line">        except Exception as e:</span><br><span class="line">            print(f&quot;组装指令失败 &#123;asm_line&#125;: &#123;e&#125;&quot;)</span><br><span class="line"></span><br><span class="line">        # 计算目标区域可用字节数</span><br><span class="line">        region_size = self.inst_br_addr_temp - self.csel_addr_temp</span><br><span class="line">    current_size = len(patch_bytes)</span><br><span class="line">    print(f&quot;累计补丁字节长度: &#123;current_size&#125;, 目标区域大小: &#123;region_size&#125;&quot;)</span><br><span class="line"></span><br><span class="line">    # (3) 如果不足，填充 NOP 指令（ARM64 的 nop 固定4字节）</span><br><span class="line">    if current_size &lt; region_size:</span><br><span class="line">        remaining = region_size - current_size</span><br><span class="line">        nop_count = remaining // 4  # 每个 nop 占4字节</span><br><span class="line">        for _ in range(nop_count):</span><br><span class="line">            try:</span><br><span class="line">                encoding, _ = self.ks.asm(&quot;nop&quot;)</span><br><span class="line">                patch_bytes += bytes(encoding)</span><br><span class="line">            except Exception as e:</span><br><span class="line">                print(f&quot;组装 nop 失败: &#123;e&#125;&quot;)</span><br><span class="line">        # 如果超过目标区域，根据需要截断（不要溢出）</span><br><span class="line">        if len(patch_bytes) &gt; region_size:</span><br><span class="line">            patch_bytes = patch_bytes[:region_size]</span><br><span class="line"></span><br><span class="line">    print(&quot;&gt;&gt;&gt; 重新组装后的补丁字节:&quot;)</span><br><span class="line">    print(patch_bytes.hex())</span><br><span class="line"></span><br><span class="line">    # 将生成的补丁字节写入内存（写回到原区域起始处）</span><br><span class="line">        self.patch_code(addr_start, patch_bytes)</span><br><span class="line">        </span><br><span class="line">        print(&quot;&gt;&gt;&gt; 重写区域内指令:&quot;)</span><br><span class="line">        for addr in range(addr_start, self.inst_br_addr_temp, 4):</span><br><span class="line">            try:</span><br><span class="line">                code = self.mu.mem_read(addr, 4)</span><br><span class="line">            except UcError:</span><br><span class="line">                continue</span><br><span class="line">            for inst in self.cs.disasm(code, addr):</span><br><span class="line">                print(f&quot;    0x&#123;addr:x&#125;: &#123;inst.mnemonic&#125; &#123;inst.op_str&#125;&quot;)</span><br></pre></td></tr></table></figure><p>至此，我们完成了所有的工作。再加上亿点小小的细节，添加一下注释和批量优化，就是完整的实现代码</p><h2 id="Final：完整实现代码"><a href="#Final：完整实现代码" class="headerlink" title="Final：完整实现代码"></a>Final：完整实现代码</h2><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><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br><span class="line">301</span><br><span class="line">302</span><br><span class="line">303</span><br><span class="line">304</span><br><span class="line">305</span><br><span class="line">306</span><br><span class="line">307</span><br><span class="line">308</span><br><span class="line">309</span><br><span class="line">310</span><br><span class="line">311</span><br><span class="line">312</span><br><span class="line">313</span><br><span class="line">314</span><br><span class="line">315</span><br><span class="line">316</span><br><span class="line">317</span><br><span class="line">318</span><br><span class="line">319</span><br><span class="line">320</span><br><span class="line">321</span><br><span class="line">322</span><br><span class="line">323</span><br><span class="line">324</span><br><span class="line">325</span><br><span class="line">326</span><br><span class="line">327</span><br><span class="line">328</span><br><span class="line">329</span><br><span class="line">330</span><br><span class="line">331</span><br><span class="line">332</span><br><span class="line">333</span><br><span class="line">334</span><br><span class="line">335</span><br><span class="line">336</span><br><span class="line">337</span><br><span class="line">338</span><br><span class="line">339</span><br><span class="line">340</span><br><span class="line">341</span><br><span class="line">342</span><br><span class="line">343</span><br><span class="line">344</span><br><span class="line">345</span><br><span class="line">346</span><br><span class="line">347</span><br><span class="line">348</span><br><span class="line">349</span><br><span class="line">350</span><br><span class="line">351</span><br><span class="line">352</span><br><span class="line">353</span><br><span class="line">354</span><br><span class="line">355</span><br><span class="line">356</span><br><span class="line">357</span><br><span class="line">358</span><br><span class="line">359</span><br><span class="line">360</span><br><span class="line">361</span><br><span class="line">362</span><br><span class="line">363</span><br><span class="line">364</span><br><span class="line">365</span><br><span class="line">366</span><br><span class="line">367</span><br><span class="line">368</span><br><span class="line">369</span><br><span class="line">370</span><br><span class="line">371</span><br><span class="line">372</span><br><span class="line">373</span><br><span class="line">374</span><br><span class="line">375</span><br><span class="line">376</span><br><span class="line">377</span><br><span class="line">378</span><br><span class="line">379</span><br><span class="line">380</span><br><span class="line">381</span><br><span class="line">382</span><br><span class="line">383</span><br><span class="line">384</span><br><span class="line">385</span><br><span class="line">386</span><br><span class="line">387</span><br><span class="line">388</span><br><span class="line">389</span><br><span class="line">390</span><br><span class="line">391</span><br><span class="line">392</span><br><span class="line">393</span><br><span class="line">394</span><br><span class="line">395</span><br><span class="line">396</span><br><span class="line">397</span><br><span class="line">398</span><br><span class="line">399</span><br><span class="line">400</span><br><span class="line">401</span><br><span class="line">402</span><br><span class="line">403</span><br><span class="line">404</span><br><span class="line">405</span><br><span class="line">406</span><br><span class="line">407</span><br><span class="line">408</span><br><span class="line">409</span><br><span class="line">410</span><br><span class="line">411</span><br><span class="line">412</span><br><span class="line">413</span><br><span class="line">414</span><br><span class="line">415</span><br><span class="line">416</span><br><span class="line">417</span><br><span class="line">418</span><br><span class="line">419</span><br><span class="line">420</span><br><span class="line">421</span><br><span class="line">422</span><br><span class="line">423</span><br><span class="line">424</span><br><span class="line">425</span><br><span class="line">426</span><br><span class="line">427</span><br><span class="line">428</span><br><span class="line">429</span><br><span class="line">430</span><br><span class="line">431</span><br><span class="line">432</span><br><span class="line">433</span><br><span class="line">434</span><br><span class="line">435</span><br><span class="line">436</span><br><span class="line">437</span><br><span class="line">438</span><br><span class="line">439</span><br><span class="line">440</span><br><span class="line">441</span><br><span class="line">442</span><br><span class="line">443</span><br><span class="line">444</span><br><span class="line">445</span><br><span class="line">446</span><br><span class="line">447</span><br><span class="line">448</span><br><span class="line">449</span><br><span class="line">450</span><br><span class="line">451</span><br><span class="line">452</span><br><span class="line">453</span><br><span class="line">454</span><br><span class="line">455</span><br><span class="line">456</span><br><span class="line">457</span><br><span class="line">458</span><br><span class="line">459</span><br><span class="line">460</span><br><span class="line">461</span><br><span class="line">462</span><br><span class="line">463</span><br><span class="line">464</span><br><span class="line">465</span><br><span class="line">466</span><br><span class="line">467</span><br><span class="line">468</span><br><span class="line">469</span><br><span class="line">470</span><br><span class="line">471</span><br><span class="line">472</span><br><span class="line">473</span><br><span class="line">474</span><br><span class="line">475</span><br><span class="line">476</span><br><span class="line">477</span><br><span class="line">478</span><br><span class="line">479</span><br><span class="line">480</span><br><span class="line">481</span><br><span class="line">482</span><br><span class="line">483</span><br><span class="line">484</span><br><span class="line">485</span><br><span class="line">486</span><br><span class="line">487</span><br><span class="line">488</span><br><span class="line">489</span><br><span class="line">490</span><br><span class="line">491</span><br><span class="line">492</span><br><span class="line">493</span><br><span class="line">494</span><br><span class="line">495</span><br><span class="line">496</span><br><span class="line">497</span><br><span class="line">498</span><br><span class="line">499</span><br><span class="line">500</span><br><span class="line">501</span><br><span class="line">502</span><br><span class="line">503</span><br><span class="line">504</span><br><span class="line">505</span><br><span class="line">506</span><br><span class="line">507</span><br><span class="line">508</span><br><span class="line">509</span><br><span class="line">510</span><br><span class="line">511</span><br><span class="line">512</span><br><span class="line">513</span><br><span class="line">514</span><br><span class="line">515</span><br><span class="line">516</span><br><span class="line">517</span><br><span class="line">518</span><br><span class="line">519</span><br><span class="line">520</span><br><span class="line">521</span><br><span class="line">522</span><br><span class="line">523</span><br><span class="line">524</span><br><span class="line">525</span><br><span class="line">526</span><br><span class="line">527</span><br><span class="line">528</span><br><span class="line">529</span><br><span class="line">530</span><br><span class="line">531</span><br><span class="line">532</span><br><span class="line">533</span><br><span class="line">534</span><br><span class="line">535</span><br><span class="line">536</span><br><span class="line">537</span><br><span class="line">538</span><br><span class="line">539</span><br><span class="line">540</span><br><span class="line">541</span><br><span class="line">542</span><br><span class="line">543</span><br><span class="line">544</span><br><span class="line">545</span><br><span class="line">546</span><br><span class="line">547</span><br><span class="line">548</span><br><span class="line">549</span><br><span class="line">550</span><br><span class="line">551</span><br><span class="line">552</span><br><span class="line">553</span><br><span class="line">554</span><br><span class="line">555</span><br><span class="line">556</span><br><span class="line">557</span><br><span class="line">558</span><br><span class="line">559</span><br><span class="line">560</span><br><span class="line">561</span><br><span class="line">562</span><br><span class="line">563</span><br><span class="line">564</span><br><span class="line">565</span><br><span class="line">566</span><br><span class="line">567</span><br><span class="line">568</span><br><span class="line">569</span><br><span class="line">570</span><br><span class="line">571</span><br><span class="line">572</span><br><span class="line">573</span><br><span class="line">574</span><br><span class="line">575</span><br><span class="line">576</span><br><span class="line">577</span><br><span class="line">578</span><br><span class="line">579</span><br><span class="line">580</span><br></pre></td><td class="code"><pre><span class="line">import lief</span><br><span class="line">from unicorn import Uc, UcError, UC_ARCH_ARM64, UC_MODE_ARM, UC_HOOK_CODE</span><br><span class="line">from unicorn.arm64_const import *</span><br><span class="line">from capstone import Cs, CS_ARCH_ARM64, CS_MODE_ARM</span><br><span class="line">from keystone import Ks, KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN</span><br><span class="line">from pwn import u32</span><br><span class="line"></span><br><span class="line">class Arm64ELFSimulator:</span><br><span class="line">    # Configuration constants</span><br><span class="line">    STACK_ADDR = 0x100000</span><br><span class="line">    STACK_SIZE = 1024 * 1024</span><br><span class="line">    PAGE_SIZE = 0x1000# Must be a divisor of the mapped sizes</span><br><span class="line">    BASE_ADDR = 0x0</span><br><span class="line">    ENTRY_POINT = 0x0</span><br><span class="line">    END_POINT = 0x0</span><br><span class="line">    </span><br><span class="line">    # Mapping of register names to Unicorn constants</span><br><span class="line">    regs_dic = &#123;</span><br><span class="line">        &quot;x0&quot;: UC_ARM64_REG_X0, &quot;x1&quot;: UC_ARM64_REG_X1, &quot;x2&quot;: UC_ARM64_REG_X2, &quot;x3&quot;: UC_ARM64_REG_X3,</span><br><span class="line">        &quot;x4&quot;: UC_ARM64_REG_X4, &quot;x5&quot;: UC_ARM64_REG_X5, &quot;x6&quot;: UC_ARM64_REG_X6, &quot;x7&quot;: UC_ARM64_REG_X7,</span><br><span class="line">        &quot;x8&quot;: UC_ARM64_REG_X8, &quot;x9&quot;: UC_ARM64_REG_X9, &quot;x10&quot;: UC_ARM64_REG_X10, &quot;x11&quot;: UC_ARM64_REG_X11,</span><br><span class="line">        &quot;x12&quot;: UC_ARM64_REG_X12, &quot;x13&quot;: UC_ARM64_REG_X13, &quot;x14&quot;: UC_ARM64_REG_X14, &quot;x15&quot;: UC_ARM64_REG_X15,</span><br><span class="line">        &quot;x16&quot;: UC_ARM64_REG_X16, &quot;x17&quot;: UC_ARM64_REG_X17, &quot;x18&quot;: UC_ARM64_REG_X18, &quot;x19&quot;: UC_ARM64_REG_X19,</span><br><span class="line">        &quot;x20&quot;: UC_ARM64_REG_X20, &quot;x21&quot;: UC_ARM64_REG_X21, &quot;x22&quot;: UC_ARM64_REG_X22, &quot;x23&quot;: UC_ARM64_REG_X23,</span><br><span class="line">        &quot;x24&quot;: UC_ARM64_REG_X24, &quot;x25&quot;: UC_ARM64_REG_X25, &quot;x26&quot;: UC_ARM64_REG_X26, &quot;x27&quot;: UC_ARM64_REG_X27,</span><br><span class="line">        &quot;x28&quot;: UC_ARM64_REG_X28, &quot;x29&quot;: UC_ARM64_REG_X29, &quot;x30&quot;: UC_ARM64_REG_X30,</span><br><span class="line">        &quot;sp&quot;: UC_ARM64_REG_SP, &quot;pc&quot;: UC_ARM64_REG_PC</span><br><span class="line">    &#125;</span><br><span class="line">    reg_list = list(regs_dic.values())</span><br><span class="line">    </span><br><span class="line">    def __init__(self, binary_path: str):</span><br><span class="line">        # Initialize Unicorn, Capstone, Keystone</span><br><span class="line">        self.mu = Uc(UC_ARCH_ARM64, UC_MODE_ARM)</span><br><span class="line">        try:</span><br><span class="line">            self.mu.mem_map(self.STACK_ADDR, self.STACK_SIZE)</span><br><span class="line">        except UcError as e:</span><br><span class="line">            print(f&quot;映射栈空间失败: &#123;e&#125;&quot;)</span><br><span class="line">        self.mu.reg_write(UC_ARM64_REG_SP, self.STACK_ADDR + self.STACK_SIZE - 100)</span><br><span class="line">        self.binary_path = binary_path</span><br><span class="line"></span><br><span class="line">        self.cs = Cs(CS_ARCH_ARM64, CS_MODE_ARM)</span><br><span class="line">        self.ks = Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN)</span><br><span class="line"></span><br><span class="line">        # Simulation state</span><br><span class="line">        self.hit_inst = []</span><br><span class="line">        self.csel_handled = False</span><br><span class="line">        self.csel_handled = []</span><br><span class="line">        self.err_addr = []</span><br><span class="line"></span><br><span class="line">        # Load ELF sections and record .text section for later analysis</span><br><span class="line">        self.text_section = None</span><br><span class="line">        self.load_elf_sections()</span><br><span class="line"></span><br><span class="line">        # 初始化预置寄存器及临时变量</span><br><span class="line">        self.mu.reg_write(UC_ARM64_REG_X9, 0x7B6C5B9A)</span><br><span class="line">        self.inst_mov_addr_temp = 0</span><br><span class="line">        self.inst_br_addr_temp = 0</span><br><span class="line">        self.br_reg = 0</span><br><span class="line">        self.csel_addr_temp = 0</span><br><span class="line">        self.csel_args = (0,0,0)</span><br><span class="line">        self.br_value = (0,0)</span><br><span class="line">        self.err_flag = False</span><br><span class="line"></span><br><span class="line">    def load_elf_sections(self) -&gt; None:</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        利用 LIEF 解析 ELF 文件，将各 section 映射到 Unicorn 内存中。</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        binary = lief.parse(self.binary_path)</span><br><span class="line">        mapped = &#123;&#125;  # 已映射区域字典</span><br><span class="line"></span><br><span class="line">        for section in binary.sections:</span><br><span class="line">            vaddr = section.virtual_address</span><br><span class="line">            mem_size = section.size</span><br><span class="line">            if mem_size == 0 or vaddr == 0:</span><br><span class="line">                continue</span><br><span class="line">            if section.name == &quot;.text&quot;:</span><br><span class="line">                self.text_section = section</span><br><span class="line">            start_addr = (vaddr // self.PAGE_SIZE) * self.PAGE_SIZE</span><br><span class="line">            end_addr = ((vaddr + mem_size + self.PAGE_SIZE - 1) // self.PAGE_SIZE) * self.PAGE_SIZE</span><br><span class="line">            map_size = end_addr - start_addr</span><br><span class="line"></span><br><span class="line">            if start_addr not in mapped:</span><br><span class="line">                try:</span><br><span class="line">                    self.mu.mem_map(start_addr, map_size)</span><br><span class="line">                    mapped[start_addr] = map_size</span><br><span class="line">                except UcError as e:</span><br><span class="line">                    print(f&quot;映射地址&#123;section.name&#125; 0x&#123;start_addr:x&#125;（大小 0x&#123;map_size:x&#125;）失败: &#123;e&#125;&quot;)</span><br><span class="line">                    continue</span><br><span class="line"></span><br><span class="line">            if section.content:</span><br><span class="line">                data = bytes(section.content)</span><br><span class="line">                offset = vaddr - start_addr</span><br><span class="line">                try:</span><br><span class="line">                    self.mu.mem_write(start_addr + offset, data)</span><br><span class="line">                except UcError as e:</span><br><span class="line">                    print(f&quot;写入数据到地址 0x&#123;start_addr+offset:x&#125; 失败: &#123;e&#125;&quot;)</span><br><span class="line"></span><br><span class="line">    def patch_elf_sections(self, output_file: str) -&gt; None:</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        从 Unicorn 内存中提取所有映射成功的段内容，然后将这些数据</span><br><span class="line">        patch 回 ELF 文件，并写入到 output_file 中。</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        # 通过 LIEF 重新解析原始 ELF 文件</span><br><span class="line">        elf = lief.parse(self.binary_path)</span><br><span class="line">        </span><br><span class="line">        for section in elf.sections:</span><br><span class="line">            if section.name == &quot;.text&quot; or section.name == &quot;.initarray&quot; or section.name == &quot;.finiarray&quot;:</span><br><span class="line">                </span><br><span class="line">                vaddr = section.virtual_address</span><br><span class="line">                mem_size = section.size</span><br><span class="line">                if mem_size == 0 or vaddr == 0:</span><br><span class="line">                    continue</span><br><span class="line">                # 根据 PAGE_SIZE 计算映射的起始地址和实际偏移量</span><br><span class="line">                start_addr = (vaddr // self.PAGE_SIZE) * self.PAGE_SIZE</span><br><span class="line">                offset = vaddr - start_addr</span><br><span class="line">                try:</span><br><span class="line">                    # 从 Unicorn 内存中读取 section 内容，读取大小为 section.size</span><br><span class="line">                    new_content = self.mu.mem_read(start_addr + offset, mem_size)</span><br><span class="line">                    # LIEF 要求 section.content 为列表形式</span><br><span class="line">                    section.content = list(new_content)</span><br><span class="line">                    print(f&quot;Section &#123;section.name&#125; 更新成功, vaddr: 0x&#123;vaddr:x&#125;, size: &#123;mem_size&#125;&quot;)</span><br><span class="line">                except UcError as e:</span><br><span class="line">                    print(f&quot;读取段 &#123;section.name&#125; 内存失败: &#123;e&#125;&quot;)</span><br><span class="line">            else :</span><br><span class="line">                continue</span><br><span class="line"></span><br><span class="line">        try:</span><br><span class="line">            elf.write(output_file)</span><br><span class="line">            print(f&quot;已将修补后的 ELF 写入到: &#123;output_file&#125;&quot;)</span><br><span class="line">        except Exception as e:</span><br><span class="line">            print(f&quot;写入补丁 ELF 失败: &#123;e&#125;&quot;)</span><br><span class="line">    </span><br><span class="line">    def find_reg_jmp_addr(self) -&gt; list:</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        遍历 .text 段寻找寄存器间接跳转地址，返回 tup( mov_addr, br_addr, reg) 列表。</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        reg_jmp_addr = []</span><br><span class="line">        addr = self.text_section.virtual_address</span><br><span class="line">        while addr &lt; self.text_section.virtual_address + self.text_section.size:</span><br><span class="line">            mov_addr = self.find_mov(addr)</span><br><span class="line">            if mov_addr:</span><br><span class="line">                br_addr, reg = self.find_br(mov_addr)</span><br><span class="line">                if br_addr:</span><br><span class="line">                    reg_jmp_addr.append((mov_addr, br_addr, reg))</span><br><span class="line">                    addr = br_addr</span><br><span class="line">            addr += 4</span><br><span class="line">        return reg_jmp_addr</span><br><span class="line"></span><br><span class="line">    def find_mov(self, addr: int) -&gt; int:</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        搜索连续两条带立即数的 mov 指令，</span><br><span class="line">        返回目标 mov 指令的地址，未找到则返回 0。</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        try:</span><br><span class="line">            code = self.mu.mem_read(addr, 4)</span><br><span class="line">        except UcError:</span><br><span class="line">            print(f&quot;&gt;&gt;&gt; read error at find_mov: &#123;hex(addr)&#125;&quot;)</span><br><span class="line">            return 0</span><br><span class="line"></span><br><span class="line">        for inst in self.cs.disasm(code, addr):</span><br><span class="line">            if inst.mnemonic == &quot;mov&quot; and &#x27;#&#x27; in inst.op_str:</span><br><span class="line">                addr += 4</span><br><span class="line">                try:</span><br><span class="line">                    code2 = self.mu.mem_read(addr, 4)</span><br><span class="line">                except UcError:</span><br><span class="line">                    print(f&quot;&gt;&gt;&gt; read error at find_mov: &#123;hex(addr)&#125;&quot;)</span><br><span class="line">                    continue</span><br><span class="line">                for inst_b in self.cs.disasm(code2, addr):</span><br><span class="line">                    if inst_b.mnemonic == &quot;mov&quot; and &#x27;#&#x27; in inst_b.op_str:</span><br><span class="line">                        return addr</span><br><span class="line">        return 0</span><br><span class="line"></span><br><span class="line">    def find_br(self, addr: int) -&gt; tuple:</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        向后搜索 &#x27;br&#x27; 指令，返回 (addr, operand) 元组，</span><br><span class="line">        未找到时返回 (0, 0)。</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        for _ in range(1, 100):</span><br><span class="line">            try:</span><br><span class="line">                code = self.mu.mem_read(addr, 4)</span><br><span class="line">            except UcError:</span><br><span class="line">                print(f&quot;&gt;&gt;&gt; read error at find_br: &#123;hex(addr)&#125;&quot;)</span><br><span class="line">                addr += 4</span><br><span class="line">                continue</span><br><span class="line">            for inst in self.cs.disasm(code, addr):</span><br><span class="line">                if inst.mnemonic == &quot;br&quot;:</span><br><span class="line">                    return addr, inst.op_str</span><br><span class="line">            addr += 4</span><br><span class="line">        print(f&quot;&gt;&gt;&gt; find_br 未找到, addr: &#123;hex(addr - 400)&#125;&quot;)</span><br><span class="line">        return 0, 0</span><br><span class="line"></span><br><span class="line">    def print_regs(self) -&gt; None:</span><br><span class="line">        &quot;&quot;&quot; 打印当前所有寄存器的值 &quot;&quot;&quot;</span><br><span class="line">        print(&quot;&gt;&gt;&gt; Registers:&quot;)</span><br><span class="line">        for name, reg in self.regs_dic.items():</span><br><span class="line">            print(f&quot;    &#123;name&#125; = 0x&#123;self.mu.reg_read(reg):x&#125;&quot;)</span><br><span class="line"></span><br><span class="line">    def print_stack_sp(self) -&gt; None:</span><br><span class="line">        &quot;&quot;&quot; 打印当前栈区域的部分内容 &quot;&quot;&quot;</span><br><span class="line">        sp = self.mu.reg_read(UC_ARM64_REG_SP)</span><br><span class="line">        print(&quot;&gt;&gt;&gt; Stack:&quot;)</span><br><span class="line">        for i in range(0, 32, 4):</span><br><span class="line">            try:</span><br><span class="line">                data = self.mu.mem_read(sp + i, 4)</span><br><span class="line">                print(f&quot;    0x&#123;sp + i:x&#125;: &#123;u32(data):x&#125;&quot;)</span><br><span class="line">            except UcError:</span><br><span class="line">                print(f&quot;    0x&#123;sp + i:x&#125;: ???&quot;)</span><br><span class="line">        print(&quot;&quot;)</span><br><span class="line"></span><br><span class="line">    def save_state(self) -&gt; dict:</span><br><span class="line">        &quot;&quot;&quot; 保存当前寄存器状态 &quot;&quot;&quot;</span><br><span class="line">        return &#123;reg: self.mu.reg_read(reg) for reg in self.reg_list&#125;</span><br><span class="line"></span><br><span class="line">    def restore_state(self, state: dict) -&gt; None:</span><br><span class="line">        &quot;&quot;&quot; 恢复寄存器状态 &quot;&quot;&quot;</span><br><span class="line">        for reg, value in state.items():</span><br><span class="line">            self.mu.reg_write(reg, value)</span><br><span class="line"></span><br><span class="line">    def clear_state(self) -&gt; None:</span><br><span class="line">        &quot;&quot;&quot; 清空所有寄存器，并重置 csel 标志 &quot;&quot;&quot;</span><br><span class="line">        self.csel_handled = False</span><br><span class="line">        for reg in self.reg_list:</span><br><span class="line">            self.mu.reg_write(reg, 0)</span><br><span class="line"></span><br><span class="line">    def hit_hook(self, mu: Uc, addr: int, size: int, user_data) -&gt; None:</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        代码 hook，用于捕获并存储 hit 指令（通过向上追踪含 SP 且 ldr 指令）。</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        </span><br><span class="line">        try:</span><br><span class="line">            code = mu.mem_read(addr, size)</span><br><span class="line">        except UcError:</span><br><span class="line">            return</span><br><span class="line"></span><br><span class="line">        for inst in self.cs.disasm(code, addr):</span><br><span class="line">            # print(f&quot;hit hook : &gt;&gt;&gt; &#123;hex(addr)&#125; &#123;inst.mnemonic&#125; &#123;inst.op_str&#125;&quot;)</span><br><span class="line">            if inst.mnemonic.lower() == &quot;br&quot;:</span><br><span class="line">                mu.emu_stop()</span><br><span class="line">                break</span><br><span class="line">            if inst.mnemonic.lower() == &quot;stur&quot; or inst.mnemonic.lower() == &quot;ldur&quot;:</span><br><span class="line">                self.mu.reg_write(UC_ARM64_REG_PC, addr + size)</span><br><span class="line">                # print(f&quot;&gt;&gt;&gt; stur or ldur detected, skip&quot;)</span><br><span class="line">                return</span><br><span class="line">            if &quot;sp&quot; in inst.op_str and &quot;ldr&quot; in inst.mnemonic:</span><br><span class="line">                # print(f&quot;attached ldr sp instruction: &#123;inst.mnemonic&#125; &#123;inst.op_str&#125;&quot;)</span><br><span class="line">                parts = inst.op_str.split(&quot;, [&quot;)</span><br><span class="line">                target = parts[0] if &quot;sp&quot; in parts[0] else parts[1]</span><br><span class="line">                target_state = False</span><br><span class="line">                target2 = &quot;&quot;</span><br><span class="line">                curr_addr = addr</span><br><span class="line">                for _ in range(1, 200):</span><br><span class="line">                    curr_addr -= inst.size</span><br><span class="line">                    try:</span><br><span class="line">                        code_b = mu.mem_read(curr_addr, 4)</span><br><span class="line">                    except UcError:</span><br><span class="line">                        continue</span><br><span class="line">                    for inst_b in self.cs.disasm(code_b, curr_addr):</span><br><span class="line">                        if target in inst_b.op_str and &quot;ldr&quot; not in inst_b.mnemonic and not target_state:</span><br><span class="line">                            # print(f&quot;hit instruction: &#123;inst_b.mnemonic&#125; &#123;inst_b.op_str&#125;&quot;)</span><br><span class="line">                            target_state = True</span><br><span class="line">                            self.hit_inst.append(inst_b)</span><br><span class="line">                            parts_b = inst_b.op_str.split(&quot;, &quot;)</span><br><span class="line">                            target2 = parts_b[0] if target not in parts_b[0] else parts_b[1]</span><br><span class="line">                            break</span><br><span class="line">                        if target_state and target2 in inst_b.op_str and &quot;mov&quot; in inst_b.mnemonic and &#x27;#0x&#x27; in inst_b.op_str:</span><br><span class="line">                            self.hit_inst.append(inst_b)</span><br><span class="line">                        if target_state and target2 in inst_b.op_str and &#x27;adr&#x27; in inst_b.mnemonic:</span><br><span class="line">                            # print(f&quot;hit instruction: &#123;inst_b.mnemonic&#125; &#123;inst_b.op_str&#125;&quot;)</span><br><span class="line">                            if &quot;adrp&quot; in inst_b.mnemonic:</span><br><span class="line">                                try:</span><br><span class="line">                                    next_code = mu.mem_read(curr_addr + inst_b.size, 4)</span><br><span class="line">                                except UcError:</span><br><span class="line">                                    continue</span><br><span class="line">                                for inst_el in self.cs.disasm(next_code, curr_addr + inst_b.size):</span><br><span class="line">                                    if &quot;add&quot; in inst_el.mnemonic:</span><br><span class="line">                                        self.hit_inst.append(inst_el)</span><br><span class="line">                                        self.hit_inst.append(inst_b)</span><br><span class="line">                            else:</span><br><span class="line">                                self.hit_inst.append(inst_b)</span><br><span class="line">        return</span><br><span class="line"></span><br><span class="line">    def branch_hook(self, mu: Uc, addr: int, size: int, user_data) -&gt; None:</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        当执行到 BR 指令时停止模拟</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        try:</span><br><span class="line">            code = mu.mem_read(addr, size)</span><br><span class="line">        except UcError:</span><br><span class="line">            return</span><br><span class="line"></span><br><span class="line">        for inst in self.cs.disasm(code, addr):</span><br><span class="line">            if inst.mnemonic.lower() == &quot;br&quot;:</span><br><span class="line">                # print(f&quot;&gt;&gt;&gt; BR 指令在 0x&#123;addr:x&#125; 被触发，停止模拟&quot;)</span><br><span class="line">                mu.emu_stop()</span><br><span class="line">                break</span><br><span class="line">    </span><br><span class="line">    def get_hit_inst(self, addr: int) -&gt; None:</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        从指定地址向后模拟收集 hit 指令</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        # print(f&quot;&gt;&gt;&gt; Getting hit instructions from 0x&#123;addr:x&#125; to 0x&#123;self.inst_br_addr_temp:x&#125;&quot;)</span><br><span class="line">        bh_id = self.mu.hook_add(UC_HOOK_CODE, self.hit_hook)</span><br><span class="line">        self.mu.hook_del(self.hook_code_id)</span><br><span class="line">        # 搜寻前面的有没有立即数存储指令</span><br><span class="line">        for i in range(1, 50):</span><br><span class="line">            try:</span><br><span class="line">                code = self.mu.mem_read(addr - i * 4, 4)</span><br><span class="line">            except UcError:</span><br><span class="line">                continue</span><br><span class="line">            for inst in self.cs.disasm(code, addr - i * 4):</span><br><span class="line">                # print(f&quot;get_hit_inst : &gt;&gt;&gt; &#123;hex(addr - i * 4)&#125; &#123;inst.mnemonic&#125; &#123;inst.op_str&#125;&quot;)</span><br><span class="line">                if (&quot;mov&quot; in inst.mnemonic or &quot;ldr&quot; in inst.mnemonic ) and &#x27;#&#x27; in inst.op_str:</span><br><span class="line">                    self.hit_inst.append(inst)</span><br><span class="line">        </span><br><span class="line">        try:</span><br><span class="line">            self.mu.emu_start(addr - 4, self.inst_br_addr_temp)</span><br><span class="line">        except UcError as e:</span><br><span class="line">            print(&quot;模拟过程中出现错误:&quot;, e)</span><br><span class="line">            self.err_flag = True</span><br><span class="line">            self.err_addr.append((self.inst_mov_addr_temp, self.inst_br_addr_temp,&quot;error at get_hit_inst:&quot;, e))</span><br><span class="line">        self.mu.hook_del(bh_id)</span><br><span class="line">        self.hook_code_id = self.mu.hook_add(UC_HOOK_CODE, self.hook_code)</span><br><span class="line">        # print(&quot;\n&gt;&gt;&gt; Hit instructions:&quot;)</span><br><span class="line">        # for inst in self.hit_inst:</span><br><span class="line">        #     print(f&quot;    0x&#123;inst.address:x&#125;: &#123;inst.mnemonic&#125; &#123;inst.op_str&#125;&quot;)</span><br><span class="line"></span><br><span class="line">    def run_hit_inst(self) -&gt; None:</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        只模拟 hit 指令（hit_inst 列表中的指令）。</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        # print(&quot;&gt;&gt;&gt; Running hit instructions&quot;)</span><br><span class="line">        original_pc = self.mu.reg_read(UC_ARM64_REG_PC)</span><br><span class="line">        for inst in reversed(self.hit_inst):</span><br><span class="line">            </span><br><span class="line">            try:</span><br><span class="line">                self.mu.emu_start(inst.address, inst.address + inst.size)</span><br><span class="line">            except UcError as e:</span><br><span class="line">                print(&quot;run_hit_inst 模拟出现错误:&quot;, e)</span><br><span class="line">                self.err_flag = True</span><br><span class="line">                self.err_addr.append((self.inst_mov_addr_temp, self.inst_br_addr_temp, &quot;error at run_hit_inst:&quot;, e))</span><br><span class="line">        self.mu.reg_write(UC_ARM64_REG_PC, original_pc)</span><br><span class="line"></span><br><span class="line">    def patch_code(self, addr: int, patch_bytes: bytes) -&gt; None:</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        打补丁，将代码修改为 patch_bytes</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        self.mu.mem_write(addr, patch_bytes)</span><br><span class="line">        # print(f&quot;&gt;&gt;&gt; 已将地址 0x&#123;addr:x&#125; patch 为 &#123;patch_bytes&#125;&quot;)</span><br><span class="line"></span><br><span class="line">    def hook_code(self, mu: Uc, address: int, size: int, user_data) -&gt; None:</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        检测 CSEL 指令，进行向后分析并实现分支变种测试</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        try:</span><br><span class="line">            code = mu.mem_read(address, size)</span><br><span class="line">        except UcError:</span><br><span class="line">            return</span><br><span class="line"></span><br><span class="line">        for inst in self.cs.disasm(code, address):</span><br><span class="line">            # print(f&quot;&gt;&gt;&gt; addr:&#123;hex(address)&#125;  &#123;inst.mnemonic&#125; &#123;inst.op_str&#125;&quot;)</span><br><span class="line">            if inst.mnemonic.lower() == &quot;stur&quot; or inst.mnemonic.lower() == &quot;ldur&quot;:</span><br><span class="line">                self.mu.reg_write(UC_ARM64_REG_PC, address + size)</span><br><span class="line">                # print(f&quot;&gt;&gt;&gt; stur or ldur detected, skip&quot;)</span><br><span class="line">                return</span><br><span class="line">            if inst.mnemonic.lower() == &quot;csel&quot; and not self.csel_handled:</span><br><span class="line">                # print(&quot;&gt;&gt;&gt; csel detected&quot;)</span><br><span class="line">                self.csel_handled = True</span><br><span class="line">                self.csel_addr_temp = address</span><br><span class="line">                saved_state = self.save_state()  # 保存当前状态</span><br><span class="line">                </span><br><span class="line">                # 使用 mov_addr_temp 作为先前的 mov 指令地址</span><br><span class="line">                # print(&quot;&gt;&gt;&gt; start get hit inst\n&quot;)</span><br><span class="line">                # self.print_regs()</span><br><span class="line">                mov_addr = self.inst_mov_addr_temp</span><br><span class="line">                self.hit_inst = []  # 重置 hit 指令列表</span><br><span class="line">                self.get_hit_inst(mov_addr)</span><br><span class="line">                self.run_hit_inst()</span><br><span class="line">                self.restore_state(saved_state)</span><br><span class="line"></span><br><span class="line">                operands = inst.op_str.split(&quot;, &quot;)</span><br><span class="line">                self.csel_args = (operands[0],operands[1], operands[2])</span><br><span class="line">                mov_inst1 = f&quot;mov &#123;operands[0]&#125;, &#123;operands[1]&#125;;&quot;</span><br><span class="line">                mov_inst2 = f&quot;mov &#123;operands[0]&#125;, &#123;operands[2]&#125;;&quot;</span><br><span class="line">                asm_mov_branch1, _ = self.ks.asm(mov_inst1)</span><br><span class="line">                asm_mov_branch2, _ = self.ks.asm(mov_inst2)</span><br><span class="line"></span><br><span class="line">                def run_branch_test(patch_bytes: list, branch_label: str) -&gt; dict:</span><br><span class="line">                    self.mu.mem_write(self.csel_addr_temp, bytes(patch_bytes))</span><br><span class="line">                    # print(f&quot;&gt;&gt;&gt; 已将 csel 指令 patch 为 &#123;branch_label&#125;，开始执行至遇到 BR 指令&quot;)</span><br><span class="line">                    bh_id = self.mu.hook_add(UC_HOOK_CODE, self.branch_hook)</span><br><span class="line">                    try:</span><br><span class="line">                        self.mu.emu_start(self.inst_mov_addr_temp - 4, self.inst_br_addr_temp)</span><br><span class="line">                    except UcError as e:</span><br><span class="line">                        print(&quot;run_branch_test 模拟错误:&quot;, e)</span><br><span class="line">                        self.err_flag = True</span><br><span class="line">                        self.err_addr.append((self.inst_mov_addr_temp, self.inst_br_addr_temp,&quot;error at run_branch_test:&quot;, e))</span><br><span class="line">                    self.mu.hook_del(bh_id)</span><br><span class="line">                    return self.save_state()</span><br><span class="line"></span><br><span class="line">                # print(&quot;&gt;&gt;&gt; Executing first branch variation&quot;)</span><br><span class="line">                self.run_hit_inst()</span><br><span class="line">                saved_state = self.save_state()</span><br><span class="line">                state_branch1 = run_branch_test(asm_mov_branch1, mov_inst1)</span><br><span class="line">                self.restore_state(saved_state)</span><br><span class="line">                self.run_hit_inst()</span><br><span class="line">                # print(&quot;&gt;&gt;&gt; Executing second branch variation&quot;)</span><br><span class="line">                state_branch2 = run_branch_test(asm_mov_branch2, mov_inst2)</span><br><span class="line"></span><br><span class="line">                print(&quot;&gt;&gt;&gt; Final state:&quot;)</span><br><span class="line">                </span><br><span class="line">                br_value1 = state_branch1.get(self.regs_dic[self.br_reg], 0)</span><br><span class="line">                br_value2 = state_branch2.get(self.regs_dic[self.br_reg], 0)</span><br><span class="line">                if br_value1 + br_value2 &gt; 0x10000:</span><br><span class="line">                    print(f&quot;maybe br addr error: &#123;hex(br_value1)&#125; , &#123;hex(br_value2)&#125;&quot;)</span><br><span class="line">                    # self.print_regs()</span><br><span class="line">                    self.err_flag = True</span><br><span class="line">                    self.err_addr.append((self.inst_mov_addr_temp, self.inst_br_addr_temp,&quot;error at run_branch_test:&quot;,0))</span><br><span class="line">                    return</span><br><span class="line">                else:    </span><br><span class="line">                    print(f&quot;    1: &#123;hex(br_value1)&#125;&quot;)</span><br><span class="line">                    print(f&quot;    2: &#123;hex(br_value2)&#125;&quot;)</span><br><span class="line">                    self.br_value = (br_value1, br_value2)</span><br><span class="line">                    self.mu.emu_stop()</span><br><span class="line">        return</span><br><span class="line"></span><br><span class="line">    def emulate_single_addr(self,mov_addr, br_addr, br_reg) -&gt; None:</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        模拟执行单组地址</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        </span><br><span class="line">        self.inst_mov_addr_temp = mov_addr</span><br><span class="line">        self.inst_br_addr_temp = br_addr</span><br><span class="line">        self.br_reg = br_reg</span><br><span class="line">        # print(f&quot;\n&gt;&gt;&gt; Processing indirect jump: mov_addr=0x&#123;mov_addr:x&#125;, br_addr=0x&#123;br_addr:x&#125;, br_reg=&#123;br_reg&#125;&quot;)</span><br><span class="line">        try:</span><br><span class="line">            self.clear_state()</span><br><span class="line">            self.hook_code_id = self.mu.hook_add(UC_HOOK_CODE, self.hook_code)</span><br><span class="line">            self.mu.emu_start(self.BASE_ADDR + mov_addr, br_addr)</span><br><span class="line">            self.mu.hook_del(self.hook_code_id)</span><br><span class="line">        except UcError as e:</span><br><span class="line">            print(&quot;run() 执行错误:&quot;, e)</span><br><span class="line">            self.err_flag = True</span><br><span class="line">            self.err_addr.append((mov_addr, br_addr, &quot;error at run():&quot;, e))</span><br><span class="line"></span><br><span class="line">    # python</span><br><span class="line">    def patch_br(self) -&gt; None:</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        扫描 self.csel_addr_temp 到 self.inst_br_addr_temp 区间内的所有指令，</span><br><span class="line">        记录所有 STR 指令，将所有 STR 指令上移，其余指令下移，</span><br><span class="line">        重新构造新的指令序列：首先放置所有 STR 指令，</span><br><span class="line">        然后是两条分支指令，</span><br><span class="line">        剩余空间填充 NOP 指令（注意：不能超过原区域大小）。</span><br><span class="line">        最后将组装好的字节补丁写入内存。</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        </span><br><span class="line">        all_insts = []   # 保存区间内所有(地址, 指令)元组</span><br><span class="line">        str_insts = []   # 保存STR类型指令</span><br><span class="line"></span><br><span class="line">        addr_start = self.csel_addr_temp </span><br><span class="line">        for addr in range(addr_start, self.inst_br_addr_temp, 4):</span><br><span class="line">            try:</span><br><span class="line">                code = self.mu.mem_read(addr, 4)</span><br><span class="line">            except UcError:</span><br><span class="line">                continue</span><br><span class="line">            for inst in self.cs.disasm(code, addr):</span><br><span class="line">                all_insts.append((addr, inst))</span><br><span class="line">                if &quot;str&quot; in inst.mnemonic.lower():</span><br><span class="line">                    str_insts.append((addr, inst))</span><br><span class="line">        </span><br><span class="line">        # print(&quot;&gt;&gt;&gt; 扫描到的所有指令:&quot;)</span><br><span class="line">        # for addr, inst in all_insts:</span><br><span class="line">        #     print(f&quot;    0x&#123;addr:x&#125;: &#123;inst.mnemonic&#125; &#123;inst.op_str&#125;&quot;)</span><br><span class="line">        # print(&quot;&gt;&gt;&gt; 其中 STR 指令:&quot;)</span><br><span class="line">        # for addr, inst in str_insts:</span><br><span class="line">        #     print(f&quot;    0x&#123;addr:x&#125;: &#123;inst.mnemonic&#125; &#123;inst.op_str&#125;&quot;)</span><br><span class="line">        </span><br><span class="line">        # 构造新的指令序列</span><br><span class="line">        new_inst_list = []</span><br><span class="line">        # (1) 添加所有 STR 指令（以其原有汇编文本为准）</span><br><span class="line">        for _, inst in str_insts:</span><br><span class="line">            asm_line = f&quot;&#123;inst.mnemonic&#125; &#123;inst.op_str&#125;&quot;</span><br><span class="line">            new_inst_list.append(asm_line)</span><br><span class="line">        </span><br><span class="line">        # (2) 添加两条跳转指令（分支指令），这里假定 self.br_value 存储了两个跳转目标</span><br><span class="line">        # 构造分支指令文本（注意汇编语法，根据实际需要可能调整条件代码）</span><br><span class="line">        </span><br><span class="line">        b_inst1 = &quot;b.ne #&quot; + hex(self.br_value[0] - self.csel_addr_temp - len(str_insts)*4)</span><br><span class="line">        b_inst2 = &quot;b #&quot; + hex(self.br_value[1] - self.csel_addr_temp - len(str_insts)*4 - 4)</span><br><span class="line">        for i in range(2):</span><br><span class="line">            print(f&quot;    0x&#123;self.inst_br_addr_temp + i * 4:x&#125;: &#123;b_inst1 if i == 0 else b_inst2&#125;&quot;)</span><br><span class="line">        new_inst_list.append(b_inst1)</span><br><span class="line">        new_inst_list.append(b_inst2)</span><br><span class="line">        </span><br><span class="line">        # 使用 Keystone 汇编生成机器码，并计算补丁区域大小</span><br><span class="line">        patch_bytes = b&quot;&quot;</span><br><span class="line">        for asm_line in new_inst_list:</span><br><span class="line">            try:</span><br><span class="line">                encoding, _ = self.ks.asm(asm_line)</span><br><span class="line">                patch_bytes += bytes(encoding)</span><br><span class="line">            except Exception as e:</span><br><span class="line">                print(f&quot;组装指令失败 &#123;asm_line&#125;: &#123;e&#125;&quot;)</span><br><span class="line">        </span><br><span class="line">        # 计算目标区域可用字节数</span><br><span class="line">        region_size = self.inst_br_addr_temp - self.csel_addr_temp</span><br><span class="line">        current_size = len(patch_bytes)</span><br><span class="line">        # print(f&quot;累计patch字节长度: &#123;current_size&#125;, 目标区域大小: &#123;region_size&#125;&quot;)</span><br><span class="line">        </span><br><span class="line">        # (3) 如果不足，填充 NOP 指令（ARM64 的 nop 固定4字节）</span><br><span class="line">        if current_size &lt; region_size:</span><br><span class="line">            remaining = region_size - current_size</span><br><span class="line">            nop_count = remaining // 4  # 每个 nop 占4字节</span><br><span class="line">            for _ in range(nop_count):</span><br><span class="line">                try:</span><br><span class="line">                    encoding, _ = self.ks.asm(&quot;nop&quot;)</span><br><span class="line">                    patch_bytes += bytes(encoding)</span><br><span class="line">                except Exception as e:</span><br><span class="line">                    print(f&quot;组装 nop 失败: &#123;e&#125;&quot;)</span><br><span class="line">        # 如果超过目标区域，根据需要截断（不要溢出）</span><br><span class="line">        if len(patch_bytes) &gt; region_size:</span><br><span class="line">            patch_bytes = patch_bytes[:region_size]</span><br><span class="line">        </span><br><span class="line">        # print(&quot;&gt;&gt;&gt; 重新组装后的补丁字节:&quot;)</span><br><span class="line">        # print(patch_bytes.hex())</span><br><span class="line">        </span><br><span class="line">        # 将生成的补丁字节写入内存（写回到原区域起始处）</span><br><span class="line">        self.patch_code(addr_start, patch_bytes)</span><br><span class="line">        </span><br><span class="line">        # print(&quot;&gt;&gt;&gt; 重写区域内指令:&quot;)</span><br><span class="line">        for addr in range(addr_start, self.inst_br_addr_temp, 4):</span><br><span class="line">            try:</span><br><span class="line">                code = self.mu.mem_read(addr, 4)</span><br><span class="line">            except UcError:</span><br><span class="line">                continue</span><br><span class="line">            # for inst in self.cs.disasm(code, addr):</span><br><span class="line">            #     print(f&quot;    0x&#123;addr:x&#125;: &#123;inst.mnemonic&#125; &#123;inst.op_str&#125;&quot;)</span><br><span class="line">        </span><br><span class="line">    </span><br><span class="line">    def run(self) -&gt; None:</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        启动模拟执行，先收集间接跳转地址，再逐个模拟</span><br><span class="line">        &quot;&quot;&quot;</span><br><span class="line">        self.reg_jmp_addr = self.find_reg_jmp_addr()</span><br><span class="line">        for addr in self.reg_jmp_addr:</span><br><span class="line">            print(f&quot;indirect jmp addr: 0x&#123;addr[0]:x&#125;, br addr: 0x&#123;addr[1]:x&#125;, br reg: &#123;addr[2]&#125;&quot;)</span><br><span class="line">        print(f&quot;\n&gt;&gt;&gt; 共找到 &#123;len(self.reg_jmp_addr)&#125; 个间接跳转地址&quot;)</span><br><span class="line">        cnt = 0</span><br><span class="line">        for mov_addr, br_addr, br_reg in self.reg_jmp_addr:</span><br><span class="line">            # if br_addr != 0x1638:</span><br><span class="line">            #     continue</span><br><span class="line">            cnt+=1</span><br><span class="line">            # print(f&quot;\n&gt;&gt;&gt; Processing indirect jump &#123;cnt&#125;: mov_addr=0x&#123;mov_addr:x&#125;, br_addr=0x&#123;br_addr:x&#125;, br_reg=&#123;br_reg&#125;&quot;)</span><br><span class="line">            try:</span><br><span class="line">                self.emulate_single_addr(mov_addr, br_addr, br_reg)</span><br><span class="line">                if self.err_flag:</span><br><span class="line">                    self.err_flag = False</span><br><span class="line">                    continue</span><br><span class="line">                self.patch_br()</span><br><span class="line">            except:</span><br><span class="line">                continue</span><br><span class="line">            </span><br><span class="line"></span><br><span class="line">        if self.err_addr:</span><br><span class="line">            print(&quot;\n&gt;&gt;&gt; Error addresses:&quot;)</span><br><span class="line">            for addr in self.err_addr:</span><br><span class="line">                print(f&quot;    indirect jmp addr: 0x&#123;addr[0]:x&#125;, br addr: 0x&#123;addr[1]:x&#125; , &#123;addr[2]&#125; , &#123;addr[3]&#125;&quot;)</span><br><span class="line">            </span><br><span class="line">            for addr in self.err_addr:</span><br><span class="line">                print(f&quot;( &#123;addr[0]&#125; , &#123;addr[1]&#125; , \&quot; &#123;addr[2]&#125; \&quot;) ,&quot; , end=&quot;&quot;)</span><br><span class="line">                </span><br><span class="line">    </span><br><span class="line"></span><br><span class="line">def main() -&gt; None:</span><br><span class="line">    binary_path = r&quot;F:\_reverse_study\_unicorn_study\task1\easy-re&quot;</span><br><span class="line">    simulator = Arm64ELFSimulator(binary_path)</span><br><span class="line">    simulator.run()</span><br><span class="line">    # simulator.run_err_addr(err_addr)</span><br><span class="line">    simulator.patch_elf_sections(binary_path + &quot;_patched&quot;)</span><br><span class="line"></span><br><span class="line">if __name__ == &quot;__main__&quot;:</span><br><span class="line">    main()</span><br></pre></td></tr></table></figure><h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2><p><a href="https://bbs.kanxue.com/thread-282826.htm">https://bbs.kanxue.com/thread-282826.htm</a><br><a href="https://blog.csdn.net/Luckiers/article/details/128221506">https://blog.csdn.net/Luckiers/article/details/128221506</a><br><a href="https://bbs.kanxue.com/thread-283706.htm">https://bbs.kanxue.com/thread-283706.htm</a><br><a href="https://bbs.kanxue.com/thread-280231.htm">https://bbs.kanxue.com/thread-280231.htm</a></p>]]>
    </content>
    <id>https://l4n.top/2025/03/31/%E5%AF%84%E5%AD%98%E5%99%A8%E9%97%B4%E6%8E%A5%E8%B7%B3%E8%BD%AC%E6%B7%B7%E6%B7%86%E5%8E%BB%E9%99%A4%E5%AE%9E%E6%88%98/</id>
    <link href="https://l4n.top/2025/03/31/%E5%AF%84%E5%AD%98%E5%99%A8%E9%97%B4%E6%8E%A5%E8%B7%B3%E8%BD%AC%E6%B7%B7%E6%B7%86%E5%8E%BB%E9%99%A4%E5%AE%9E%E6%88%98/"/>
    <published>2025-03-31T10:11:06.000Z</published>
    <summary>
      <![CDATA[<h2 id="寄存器间接跳转的基本概念"><a href="#寄存器间接跳转的基本概念" class="headerlink" title="寄存器间接跳转的基本概念"></a>寄存器间接跳转的基本概念</h2><p>寄存器存储地址：寄存器间接跳转依赖于寄存器内的值来指定要跳转]]>
    </summary>
    <title>寄存器间接跳转混淆去除实战</title>
    <updated>2026-09-02T15:48:21.767Z</updated>
  </entry>
</feed>
