<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>SQLCipher on 落日余晖</title>
    <link>https://is-i7.cn/tags/sqlcipher/</link>
    <description>Recent content in SQLCipher on 落日余晖</description>
    <generator>Hugo</generator>
    <language>zh-cn</language>
    <lastBuildDate>Mon, 07 Sep 2026 14:10:00 +0800</lastBuildDate>
    <atom:link href="https://is-i7.cn/tags/sqlcipher/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>飞书撤回消息逆向分析:原文是怎么被抹掉的</title>
      <link>https://is-i7.cn/posts/feishu-recall-reverse-engineering/</link>
      <pubDate>Mon, 07 Sep 2026 14:10:00 +0800</pubDate>
      <guid>https://is-i7.cn/posts/feishu-recall-reverse-engineering/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;分析对象:飞书 macOS 客户端 &lt;code&gt;/Applications/Lark.app&lt;/code&gt;(v131.0.6778.268 起分析,143.0.7499.203 复验,Electron/Chromium 内核)。
方法:静态逆向(strings / 反汇编 / asar 解包)+ 真实撤回的落盘观察 + 前端 asar 补丁注入的实测迭代。
本文讲&lt;strong&gt;原理与逆向过程&lt;/strong&gt;;拿着结论去动手改客户端,见下一篇&lt;a href=&#34;https://is-i7.cn/posts/feishu-anti-recall-patch-guide/&#34;&gt;《飞书防撤回补丁复现指南》&lt;/a&gt;。&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;tldr撤回是怎么实现的&#34;&gt;TL;DR:撤回是怎么实现的&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;撤回 = 服务端下发一条带撤回标记的消息资源更新,客户端把本地 SQLite 行打上 &lt;code&gt;is_recalled=1&lt;/code&gt; 等标记列,前端按标记切换渲染成「撤回了一条消息」提示。而原始 content 会被一并抹掉&lt;/strong&gt;(实测:绕过渲染门之后,被撤回的消息渲染成空气泡;DB 加载与撤回推送两路拿到的 content 均为空)。所以单纯改前端的布尔分支只能得到空气泡——&lt;strong&gt;防撤回必须「渲染即存档、撤回时还原」&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;完整链路:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;发起方&lt;/strong&gt;:前端菜单 → JSB 命令 &lt;code&gt;RECALL_GROUP_MESSAGE&lt;/code&gt; / &lt;code&gt;RECALL_MESSAGE&lt;/code&gt; → Rust 核心 &lt;code&gt;liblark.dylib&lt;/code&gt;(&lt;code&gt;lark-message&lt;/code&gt; 模块)→ 长连接(pipe/websocket protobuf,非 REST)→ 服务端。日志串:&lt;code&gt;recall_message callback for id:&lt;/code&gt; / &lt;code&gt;recall message success: id=&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;接收方&lt;/strong&gt;:服务端推送更新后的 Message protobuf(字段含 &lt;code&gt;is_recalled / recaller_identity / recall_type / recall_user_id&lt;/code&gt;)→ &lt;code&gt;lark-message/src/logic/dependencies/processor.rs&lt;/code&gt; 校验(&lt;code&gt;chat recall message validate failed, recaller not found&lt;/code&gt; 等)→ 更新本地库 → 通知前端。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;本地库&lt;/strong&gt;:SQLCipher 加密的 SQLite,&lt;code&gt;messages&lt;/code&gt; 表同一行内:&lt;code&gt;content BLOB&lt;/code&gt;(原始内容)+ &lt;code&gt;is_recalled BOOLEAN&lt;/code&gt; + &lt;code&gt;recaller_id BIGINT&lt;/code&gt; + &lt;code&gt;recaller_identity INTEGER&lt;/code&gt; + &lt;code&gt;recall_type INTEGER&lt;/code&gt; + &lt;code&gt;recall_user_id BIGINT&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;前端渲染&lt;/strong&gt;(messenger asar,已提取实证代码):
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;let&lt;/span&gt;{isRecalled&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt;r,isDeleted&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt;a,recallerIdentity&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt;n}&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;t;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; r &lt;span style=&#34;color:#ff79c6&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;lt;&lt;/span&gt;E recallerIdentity&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;{n}&lt;span style=&#34;color:#ff79c6&#34;&gt;/&amp;gt;&lt;/span&gt;   &lt;span style=&#34;color:#6272a4&#34;&gt;// &amp;#34;X 撤回了一条消息&amp;#34; 提示条
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;&lt;/span&gt;         &lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; a &lt;span style=&#34;color:#ff79c6&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;lt;&lt;/span&gt;S.A&lt;span style=&#34;color:#ff79c6&#34;&gt;/&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;lt;&lt;/span&gt;N {...e}&lt;span style=&#34;color:#ff79c6&#34;&gt;/&amp;gt;&lt;/span&gt;     &lt;span style=&#34;color:#6272a4&#34;&gt;// 正常消息
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;以及 &lt;code&gt;function L(e){return !e.isRecalled &amp;amp;&amp;amp; !!e.content.filePath}&lt;/code&gt; —— 撤回消息对象上 &lt;code&gt;content&lt;/code&gt; 依然存在,只是被布尔位挡住(但值已被掏空)。&lt;/li&gt;
&lt;li&gt;通知/红点同步收尾:&lt;code&gt;do not push notice to oneself when is recalled&lt;/code&gt;(自己撤回的不推通知)、thread 根消息标记 &lt;code&gt;root_message_is_recalled&lt;/code&gt;、回复计数修正 &lt;code&gt;update reply count for msg has recalled&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;一客户端架构&#34;&gt;一、客户端架构&lt;/h2&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;层&lt;/th&gt;
          &lt;th&gt;位置&lt;/th&gt;
          &lt;th&gt;作用&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;Electron 主进程&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;Contents/MacOS/Feishu&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;壳、窗口、升级&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;渲染进程&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;Lark Helper (Renderer)&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;加载 &lt;code&gt;webcontent/*.asar&lt;/code&gt;(messenger.asar = 聊天 UI)&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;aha-service 后台进程&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;Lark Helper --type=backend&lt;/code&gt;(加载 liblark)&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;Rust 核心&lt;/strong&gt;:同步、存储、JSB 命令&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;核心库&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;Libraries/liblark.dylib&lt;/code&gt;(84MB)&lt;/td&gt;
          &lt;td&gt;全部业务逻辑,Rust 编写,字符串含 &lt;code&gt;lark-message/src/*.rs&lt;/code&gt; 等源码路径&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;DB 层&lt;/td&gt;
          &lt;td&gt;liblark 内静态链接 SQLCipher 4&lt;/td&gt;
          &lt;td&gt;本地库全加密&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;前后端通信:JS Bridge 命令表(全大写,如 &lt;code&gt;GET_CHAT_MESSAGES&lt;/code&gt;、&lt;code&gt;RECALL_GROUP_MESSAGE&lt;/code&gt;、&lt;code&gt;HANDLE_PIPE_PACKET&lt;/code&gt;、&lt;code&gt;HANDLE_WEBSOCKET_PACKET&lt;/code&gt;);服务端长连接推送走 pipe/websocket 包,protobuf 编码。&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
