跳转到内容
林术
返回

飞书聊天记录解密:一次完整的逆向工程实战

一句话总结:不破解密码,不撬锁。让飞书自己开锁,自己把数据搬出来。


问题是什么?

想象一下这个场景:

你想导出自己的飞书聊天记录——也许是为了备份,也许是为了做数据分析。你找到了飞书存在电脑上的数据文件:

Linux:  ~/.config/LarkShell/sdk_storage/xxxx/messages.db
macOS:  ~/Library/Containers/com.bytedance.macos.feishu/.../messages.db

你试着用数据库工具打开它,屏幕上只有一片乱码。

文件是加密的。

这就是我们要解决的问题:怎么拿到属于自己的聊天记录?

接下来,我会完整还原这次破解的过程——包括两次失败和最终的成功。读完之后,你不仅知道怎么做,更能理解整个逆向工程的思路。


第一部分:背景知识

在开始破解之前,先认识 5 个概念。它们会在后面反复出现,现在花两分钟搞懂,后面就不会一头雾水。

1. SQLite —— 一个文件就是一个数据库

一句话定义: 世界上用得最多的数据库,一个文件就能存一张张表格。

生活类比: 把它想象成一个 Excel 文件——打开就能看到行和列,只不过用程序来读写。

和飞书的关系: 飞书把你的聊天记录存在一个叫 messages.db 的 SQLite 文件里,每条消息就是表格里的一行:

发送人时间内容
张三10:01你好
李四10:02在吗
张三10:03明天见

2. SQLCipher —— 给数据库加了一把锁

一句话定义: SQLite + AES-256 加密,必须有密码才能打开。

生活类比: 如果 SQLite 是一个透明的保险箱(谁都能看见里面的东西),SQLCipher 就是给这个保险箱装了一把密码锁

和飞书的关系: 飞书用 SQLCipher 加密了 messages.db。没有密码,打开只能看到乱码。而且飞书还魔改了锁的参数——标准锁孔的直径是 36mm,飞书改成了 99mm。这意味着就算你拿到了密码,用普通钥匙(标准 SQLCipher 工具)也插不进去。

3. Strip —— 把门牌号全撕了

一句话定义: 把程序里所有函数的名字删掉,让人看不懂代码结构。

生活类比: 一栋办公楼,每间办公室门口都有门牌——“101-财务部""102-人事部""103-密码保管室”。Strip 就是把所有门牌号全撕了。楼里还是有人在工作,但你站在走廊里,不知道哪间是哪间。

Strip 前(有门牌号)Strip 后(门牌号撕了)
101 - 财务部??? - 有人在工作
102 - 人事部??? - 有人在工作
103 - 密码保管室 ← 直接找??? - 有人在工作 ← 哪间?
104 - 技术部??? - 有人在工作

和飞书的关系: 飞书发布前执行了 strip,116MB 的核心文件 liblark.so 里几万个函数全变成匿名代码块。我们想找的 sqlite3_keysqlite3_exec 这些关键函数,名字全被删了。

4. Frida —— 能钻进程序内部的间谍工具

一句话定义: 一个动态注入工具,能钻进正在运行的程序,偷看、偷听、甚至指挥它做事。

生活类比: 飞书是一家正在营业的餐厅,你进不了后厨。Frida 相当于在后厨安插了一个卧底:

能力保险箱类比实际操作
偷看看保险箱的密码是什么监控函数参数
偷听听什么时候开了保险箱拦截函数调用
指挥让管理员多开一次保险箱调用程序内部函数

和飞书的关系: Frida 是我们最终成功的关键工具。它不需要源代码、不需要重新编译、不需要暂停飞书,直接对正在运行的飞书动手。

5. Protobuf —— 数据的压缩包装

一句话定义: Google 发明的数据序列化格式,把结构化数据压缩成紧凑的二进制。

生活类比: 你寄快递时,会把东西打包压缩。Protobuf 就是数据的”真空压缩袋”——体积小、传输快,但拆开之前看不出里面是什么。

和飞书的关系: 飞书的聊天内容不是直接存文字,而是先用 Protobuf 打包再存进数据库。所以即使数据库解密了,消息内容还需要再”拆包”一次。


第二部分:破解过程

这是整篇文章的核心。我会按真实的时间线展示整个过程,包括失败的尝试。逆向工程的本质就是不断试错——每次失败都会带来新的线索。

第一回合:LD_PRELOAD 拦截 —— 失败

思路

密码是飞书在运行时传给 SQLCipher 的。如果我们能拦截这个传递过程,就能偷看密码。

Linux 有一个机制叫 LD_PRELOAD:在程序启动前,先加载一个我们写的假函数库。当飞书调用 sqlite3_key(传密码的函数)时,实际上会先调用我们的假版本——我们记录下密码,再转发给真版本。

保险箱类比: 保险箱管理员每次开锁前要先登记密码。我们在登记处安插了一个人,偷偷抄一份密码。

结果:失败

飞书根本不走我们的”登记处”。

原因

LD_PRELOAD 只能拦截动态链接的函数——就是那些飞书在运行时从外部库加载的函数。但飞书把 SQLCipher 的代码直接编译进了自己的程序(静态链接),根本不需要从外面加载。

类比: 我们在大楼门口设了关卡,但保险箱管理员住在楼里面,从来不走大门。

收获

虽然失败了,但我们确认了一个重要信息:SQLCipher 是静态编译进 liblark.so。这意味着所有加密相关的代码都在这个文件里,后面要在这里面找。


第二回合:Frida 偷看密码 —— 偷到了,但没用

思路

LD_PRELOAD 拦不住,那就换 Frida。Frida 直接钻进飞书进程内部,不管函数是动态的还是静态的,都能拦截。

保险箱类比: 大门关卡没用?那就直接派间谍混进办公室,趴在保险箱旁边偷看密码。

难点:函数名被 Strip 了

Frida 拦截函数需要知道函数的地址。正常情况下,我们可以通过函数名 sqlite3_key 查到地址。但飞书做了 strip,函数名全删了。

怎么办?

解法:搜索调试文字

门牌号撕了,但房间里的东西没删

飞书程序员在加密函数里写了一行调试日志:"sqlite3_key: db=%p"。函数名可以删,但这句文字还留在程序里:

flowchart LR
    A["🔍 在 liblark.so 中搜索<br>sqlite3_key: db=%p"] -->|找到!字节 91,014,420| B["📎 反汇编工具查看<br>谁引用了这段文字"]
    B -->|字节 78,476,446| C["✅ 定位到 sqlite3_key<br>附近找到 sqlite3_exec"]

类比: 门牌号撕了,但透过窗户看到桌上写着”密码保管室”四个字。

结果:成功偷看到密码

Frida 拦截到飞书调用加密函数时传入的密码:

飞书:用密码 "x'307435c4...'" 打开 messages.db
Frida:偷听到了!密码是 "x'307435c4...'"

但是:密码没用

拿到密码后,我们用标准 SQLCipher 工具尝试解密——全部失败

原因是飞书魔改了加密参数

标准 SQLCipher:     reserve_sz = 36
飞书的 SQLCipher:   reserve_sz = 99  ← 不一样

类比: 密码拿到了,但保险箱的锁孔是定制的。我们的钥匙形状对了,但尺寸不对,插不进去。只有飞书自己造的那把钥匙才能用。

收获

虽然密码不能直接用,但这一轮我们掌握了两个关键能力:

  1. 能在 strip 后的程序里找到目标函数
  2. Frida 确实能钻进飞书进程并拦截函数调用

第三回合:让飞书自己导出 —— 成功

思路转变

前两次失败给了我们启发:

问题的根源是我们想自己开锁,但锁是定制的

换个思路:不自己开锁,让飞书帮我们开

飞书正在运行,数据库已经被它自己打开了——它有钥匙,而且钥匙正插在锁上。我们只需要让飞书多做一步:把保险箱里的东西复制一份到一个没上锁的新保险箱里。

原理:三行 SQL

这三行 SQL 是整个解密的核心:

第一行:放一个空保险箱

ATTACH DATABASE '/tmp/明文.db' AS pt KEY '';

在飞书已经打开的加密保险箱旁边,放一个没上锁的空保险箱。

第二行:搬东西

SELECT sqlcipher_export('pt');

让飞书把加密保险箱里的所有东西,原封不动搬到旁边那个没锁的空保险箱里。这一步是飞书自己的代码在执行,它有钥匙,能读取加密数据,然后写入不加密的新数据库。

第三行:搬完收工

DETACH DATABASE pt;

搬完了,把新保险箱的连接断开。

执行完之后:

flowchart LR
    subgraph before["执行前"]
        A["🔒 messages.db<br>加密(打不开)"]
    end
    subgraph after["执行后"]
        B["🔒 messages.db<br>加密(没变)"]
        C["🔓 明文.db<br>不加密(随便看)✅"]
    end
    before -->|"三行 SQL"| after

实现过程

知道了原理,接下来的问题是:怎么让飞书执行这三行 SQL?

答案还是 Frida。分两步:

第一步:收集数据库的”遥控器”

飞书每打开一个数据库,系统就会给它一个”句柄”——你可以把它理解成遥控器:拿着这个遥控器,才能对这个数据库发号施令。Frida 拦截飞书的 sqlite3_execsqlite3_prepare_v2 函数,每次飞书操作数据库时,就偷偷记下它用的是哪个遥控器。

第二步:拿着遥控器执行导出

收集到遥控器后,Frida 用飞书自己的 sqlite3_exec 函数,拿着遥控器,执行那三行 SQL。飞书以为是自己在操作,乖乖执行。

flowchart TD
    A["Frida 拦截飞书的数据库操作"] --> B["收集所有数据库的'遥控器'"]
    B --> C["用飞书自己的 sqlite3_exec 函数"]
    C --> D["执行 ATTACH → sqlcipher_export → DETACH"]
    D --> E["明文数据库生成在硬盘上 ✅"]

    style E fill:#4CAF50,color:#fff

踩过的坑

实现过程并非一帆风顺,这里记录几个典型的坑:

结果:成功

最终成功导出了 15 个数据库,共 448 条消息。


最后一步:拆开 Protobuf 包装

数据库解密了,用工具打开,能看到表格了——但消息内容列还是一堆二进制乱码。

这不是加密,而是包装。 飞书用 Protobuf 把消息内容”真空压缩”了。需要再拆一次包:

flowchart LR
    A["0a 12 08 e6 b5 8b ...
    (二进制)"] -->|Protobuf 解码| B["测试
    (可读文本)"]

对于卡片消息(比如科技简报),结构更复杂一些——Protobuf 里面嵌套了 JSON:

flowchart LR
    A["二进制数据"] -->|"Protobuf 解码"| B["JSON 字符串"]
    B -->|"解析 JSON"| C["提取 text/content 字段"]

导出工具会自动完成这个解码过程。


第三部分:扩展知识与方法论

如果飞书加强了保护怎么办?

我们能找到函数,是因为飞书程序员留了调试文字 "sqlite3_key: db=%p"。如果飞书在未来版本中把调试文字也删了呢?

别担心,还有很多办法:

方法一:搜功能性字符串

调试文字是可选的,删了程序照样运行。但有些字符串是程序运行必需的——比如 SQL 语句 "PRAGMA key""SELECT sqlcipher_export"。删了这些,程序自己就崩了。

方法二:特征码匹配

SQLCipher 是开源的。我们可以自己编译一份,看看函数编译成机器指令后长什么样,然后去飞书的文件里搜索相似的指令模式。

类比:门牌号撕了、桌上的纸条也没了,但房间的格局和公开的建筑图纸对得上。

方法三:行为追踪

不找函数名,直接观察行为。用 strace / ltrace 监控飞书打开了哪些文件、调用了哪些系统函数,顺藤摸瓜找到加密相关的代码。

类比:盯着走廊看谁进出了保管室,跟着他就行。

方法四:导入函数分析

飞书虽然删了自己的函数名,但它调用的系统函数名字还在(mallocmmapopen 等)。通过分析谁调用了密码学相关的系统函数,可以反推目标函数。

方法五:IDA Pro 签名匹配

专业逆向工具 IDA Pro 能自动识别已知开源库的函数签名,即使被 strip 了也能自动标注。

方法六:二进制对比

对比飞书不同版本的二进制文件,观察哪些函数在版本间没有变化——这些很可能是稳定的第三方库函数(比如 SQLCipher)。

定位函数的方法对比

方法难度前提条件说明
搜调试字符串调试文字未被删除我们用的方法,最简单
搜功能性字符串⭐⭐运行必需的字符串不能删
特征码匹配⭐⭐⭐有开源代码可对比对比编译结果
行为追踪⭐⭐⭐能运行程序监控运行时行为
导入函数分析⭐⭐⭐⭐需要逆向经验从系统调用反推
IDA Pro 签名匹配⭐⭐⭐⭐需要专业工具自动识别已知库
二进制对比⭐⭐⭐有多个版本diff 找稳定函数

逆向工具对比

工具方式特点适用场景
GDB断点调试暂停程序逐步执行精确分析某个函数的行为
Frida动态注入不暂停程序,实时监控拦截函数、修改行为(本文方案)
IDA Pro静态反编译不运行程序就能分析代码分析程序逻辑和结构
LD_PRELOAD替换动态库函数简单但局限大只能拦截动态链接的函数
objdump反汇编把机器码翻译成汇编快速查看函数指令

方案对比:我们尝试过的路

方案结果失败/成功原因
翻配置文件找密码❌ 失败密码只在内存中,不存在任何文件里
LD_PRELOAD 拦截加密函数❌ 失败SQLCipher 是静态编译的,拦不住
Frida 偷看密码 + 标准工具解密❌ 失败飞书魔改了 reserve_sz=99,标准工具不兼容
Frida + 让飞书自己导出成功绕过一切兼容性问题,用飞书自己的代码操作

安全领域有一句话:没有破解不了的软件,只有不值得花时间破解的软件。


附录

完整流程图

flowchart TD
    A["飞书正在运行,数据库已被飞书打开<br>(保险箱是开着的)"]
    B["Frida 钻进飞书 backend 进程<br>(间谍混进了办公室)"]
    C["搜索调试文字,定位函数地址<br>(透过窗户找到密码保管室)"]
    D["Hook 函数,收集数据库的遥控器<br>(记下每个保险箱的遥控器)"]
    E["用飞书自己的函数执行三行 SQL<br>ATTACH → sqlcipher_export → DETACH<br>(让飞书把东西搬到没锁的保险箱)"]
    F["Protobuf 解码(拆开真空压缩袋)<br>→ 可读的聊天记录 ✅"]

    A --> B --> C --> D --> E --> F

    style F fill:#4CAF50,color:#fff

一键导出使用方法

# 1. 安装依赖
pip3 install frida frida-tools protobuf

# 2. 启动飞书桌面客户端并登录

# 3. 运行导出工具(需要 root 权限)
sudo python3 feishu_export.py

# 4. 导出结果在 export_output/ 目录

局限性


分享到:

上一篇
你好,世界