微信数据解密用了 794 次断点,钉钉我只写了三行代码

钉钉本地数据库是加密的,但 v2 的密钥就写在目录名上。我实测解开 468 个会话;真正的硬仗是 1MB 的 WAL,以及一次连续 7 天的假阴性。

2026 年 7 月 29 日晚上,项目部的宿舍里,我刚把微信 4.1 那场硬仗的战报收进抽屉,转向这条数据链路的第二块阵地:钉钉。

目标很朴素:项目工作群的通知和文件,每天自动读、自动按日期归档,躺着能查。前提只有一个,读得到钉钉存在我自己电脑上的本地数据库。库是加密的。

我是野生码农,39 岁,程序员。这是"AI 解密攻防"系列的第二篇。上一篇微信,8 轮断点、794 次命中,硬抠出来的。轮到钉钉,我以为怎么也得再熬一晚。

结果像坐过山车。

一、排雷:五条路,先死三条半

程序员的老习惯,先侦察再动手。我把能想到的路全列出来,一条条试:

路线结果
官方 API要企业管理员建应用,我在群里只是普通成员,出局
模拟鼠标点界面微信时代就否掉了,脆弱、丢人
本机 MCP 服务空欢喜,只会开会议
公开解密资料前两篇针对 v3,我机器上是 v2,版本对不上
第三份资料明确支持 v2,成了

中间那条最戏剧。AI 翻钉钉目录时发现,钉钉居然在本机 8440 端口跑着一个标准的 MCP 服务。MCP 是让 AI 按标准协议调用外部工具的接口规范,发现它,等于发现官方在本地留了扇门。

我以为中了头奖。

连上去列工具:创建会议、查会议状态、查会议 ID。只会开会议。消息?没有。文件?没有。

头奖是张过期的彩票。

接着是版本低谷。网上能查到的解密资料,前两篇全是针对 v3 的,我电脑上的账号目录后缀是 _v2。版本对不上,照抄就是死路。

二、反转:钥匙和锁,挂在同一扇门上

反转来自第三份资料,它明确支持 v2,而且给出了密钥算法:v2 的密钥就是账号 UID 处理后的结果,而 UID 就写在目录名上。

全部密钥逻辑,三行:

import hashlib
from Crypto.Cipher import AES

def make_key(uid: str) -> bytes:
    # uid 就是账号目录名里 "_v2" 前面的那一段
    return hashlib.md5(uid.encode()).hexdigest()[:16].encode()

cipher = AES.new(make_key(uid), AES.MODE_ECB)

MD5 就是把任意字符串压成 32 位十六进制指纹的哈希函数。钉钉把你的账号 ID 做了个 MD5,取前 16 位,就是加密你全部聊天记录的钥匙。

钥匙和锁,挂在同一扇门上。

解密动作也朴素。数据库按 4096 字节分页,逐页做 AES-128-ECB 解密,不足一页的尾部原样保留。ECB 是最简单的分组加密模式,每页各解各的,页与页不串联:

def decrypt_pages(data: bytes, cipher) -> bytes:
    out = bytearray(data)
    full = len(data) // 4096 * 4096
    for off in range(0, full, 4096):
        out[off:off + 4096] = cipher.decrypt(bytes(out[off:off + 4096]))
    return bytes(out)

前一天,微信 4.1 是 8 轮断点、794 次命中硬抠出来的。同是大厂出品,防盗门和挂锁的差距。

但微信那一晚给我留下一个条件反射:解密成没成,不能看程序报不报错,要看钢印。微信的钢印是 HMAC,钉钉这边更直接,看文件头:

if not plain.startswith(b"SQLite format 3\x00"):
    sys.exit("解密失败(首 16 字节不是 SQLite 头)")

SQLite format 3 是每个 SQLite 数据库出生就带的魔数,相当于文件的胎记。解对了它才出现,解错了满篇乱码。钢印对上,库应声而开:468 个会话

程序不报错≠成功,数学验证才算数。这条是微信篇花一下午买的,这次三秒钟用上了。

三、真正的硬仗:1MB 的 WAL

库开了,我以为收工了。拉到最新消息一看,时间是旧的。

原因很快查明:最新消息不在数据库本体里,在 WAL 里。WAL 是 SQLite 的预写日志,相当于主库的暂存账本,新数据先记进账本,攒够一批才誊进主库。我这个账本有 1MB,最近的聊天全在里面。

麻烦在校验。WAL 里每一帧,也就是每一笔账,都盖着 CRC 校验的骑缝章,而且章是对着密文盖的。你把密文翻成明文,章就全花了,数据库一看章不对,整本账作废,当没看见。

没有现成答案。AI 按 SQLite 公开的 WAL 格式,把校验链从头到尾逐帧重算。WAL 文件头里有个魔数,0x377F0682 还是 0x377F0683,差一个字节,决定校验和按小端还是大端算。核心是两个 32 位累加器 s1、s2,每 8 字节滚动一次,帧头接帧数据一路滚到底:

def _wal_cksum(data, s1, s2, little):
    fmt = "<" if little else ">"
    for i in range(0, len(data), 8):
        x0, x1 = struct.unpack_from(fmt + "II", data, i)
        s1 = (s1 + x0 + s2) & 0xFFFFFFFF
        s2 = (s2 + x1 + s1) & 0xFFFFFFFF
    return s1, s2

每一帧的流程:读帧头和 4096 字节页数据,页数据 ECB 解密,帧头前 8 字节滚进校验链,明文页滚进校验链,把新的 s1、s2 盖回帧头的校验位,明文写回。一帧出错,链就断,sqlite 会整本丢弃 WAL。

盖章通过。库里最新消息刷新到当天早上 7 点 20 分。活数据,到手。

四、找文件路径翻了四层,最后在任务表里抄了家

消息到手,还差文件。群里发的文件,本机下载过的那些,落盘路径记在哪?我翻了四层。

明文配置文件,没有。解密的配置库,568 行键值对逐条扫,没有。浏览器内核存储,没有。最后在文件传输任务表里,一次抄了家:每次"下载/另存为"的文件名、大小、落盘路径,全量历史都在。客户端自己的记账习惯,比配置项诚实。

查到路径,归档逻辑就一句话:任务表里状态完成且本地文件还在的,复制归档;从没下载过的,记账待补,不硬拉。

五、管道成型:每天早上 06:52 的闹钟

整条管道最后拼成四步:只读复制数据库快照、解密、增量读消息、归档文件。

增量这块多说两句。钉钉的消息表按 tbmsg_000tbmsg_127 拆成 128 个分片,群消息落在哪几片,先全库扫一遍记下来;之后每次只查水位线以上的新消息,水位线就是上次读到的最大消息 ID,存在一个本地 JSON 里。发送人 uid 自动解析,群昵称优先,其次全局昵称。

战果:群消息 426 条全量回填,2025 年 2 月到 2026 年 7 月,按天排好;群里发过的 380 个文件,149 个当场归档,剩下 231 个我从没下载过,记账待补。Windows 计划任务每天早上 06:52 自动跑。

躺着能查了。我以为这事翻篇了。

六、第二幕:连续 7 天的假阴性

8 月 10 日,我发现归档停在 7 月 31 日。

三层验证定位:解密库里最新消息 7-31 15:13;源库文件 8-3 之后一周没动过;钉钉桌面客户端根本没在运行。

根因一句话:解密管线只读本地库,本地库只在客户端登录时同步。客户端没登录,管道每天 06:52 忠实地解密一份过期数据库,然后报"无新消息"。技术上句句属实,业务上是连续 7 天的假阴性

设计缺陷也很清楚:我只校验了"解密成功",也就是管道活着;从不校验"源库新鲜度",也就是数据活着。

[ok] 报的是工具活着,不是数据活着。

整改两条。我侧,每天上班先开钉钉客户端;工具侧,加一条源库超过 24 小时没更新就告警。原则沉淀下来:管道活性检查必须配套数据时效检查,报 ok 的前提是"源数据是新的"。

七、四个判断点,谁再踩谁冤

  1. 先判版本,再选武器。目录后缀三秒定版本,比抄错答案折腾一天强。
  2. 解密成功要有钢印。文件头必须是 SQLite 魔数,最新消息时间必须刷新到当下。
  3. 别忽略 WAL。校验链要解密后逐帧重算,不然你读到的永远是旧数据。
  4. 找不到的东西,去任务表、日志表里找。客户端自己的记账,比配置项诚实。

边界还是那句,系列里每篇都说一遍:全程只在自己电脑、自己账号上操作,只读不写,产物不出本机。读懂自己的数据,天经地义。单位、群名、同事的事一个字不写,上面所有代码都是脱敏后的关键片段,流程只指向一个目的,读自己的工作数据。

有时候门根本没锁,你只需要先把"它一定锁得很死"的预设放下。当然,前提是你得先打赢过一场硬仗,才配享受这种轻松。

下一篇,印象笔记 token 自动换新。token 会过期,自动化断在半路的坑我踩了一圈,8 月 16 日发。

FAQ

问:钉钉 v2 这个算法,现在还能用吗? 我 7 月 30 日出货,到 8 月 10 日排查时管道仍在正常解密。公开资料里 v3 的密钥思路已经完全不同,钉钉大版本升级可能换算法。什么时候变、变成什么样,我不知道,不猜。

问:WAL 不解行不行? 行,脚本里留了降级:WAL 解密失败就弃用,只用主库快照,代价是数据略旧。但群消息这种场景,最新消息恰恰全在 WAL 里,不解等于白解。

问:380 个文件为什么只归档了 149 个? 149 个是我在钉钉里下载过的,落盘路径在任务表里查得到;剩下 231 个我从没下载过,本机没有文件实体。我不为凑数去拉没接收过的数据。

问:这套东西普通人用得上吗? 看消息密度和归档需求。群里一天几十条通知加文件、要按日期归档备查的,值;群只是用来吹水的,别折腾。

我是谁?csdn平台不许说,说我是广告营销!我也不说了,纯分享个技术和AI实战,给我搞这套。

WPTools 是一种文字处理组件,支持多种字符和段落属性、样式表、编号和要点。它有效管理页眉和页脚文字,并可根据需要处理列和脚注。此外,WPTools 支持书签、嵌入式图片(带文字包裹)以及内容桌。如果需要发送大量邮件或为客户创建可自定义的数据库视图,“邮件合并”功能将非常有用。WPTools 使用特殊的字段对象来动态更新文本并提取文档的特定部分,使其适用于需要数据输入的表单。如需快速简便的PDF导出,wPDF只需包含我们的产品WPDF即可。wPDF 不仅从 WPTools 导出,还能与其他产品配合使用,使您能够根据需要整合报告组件或元文件中的输出。结合 DocX 支持单元,您拥有强大的组件集,可将 RTF、DOCX、HTML、XML 转换为 PDF。WPToolsVCL控制WPTools的核心部分是完整的RTF WYSIWYG文字处理控制,其体积出乎意料地小。与类似的组件不同,WPTools 支持可编辑的页眉和页脚,采用其完美的页面布局模式。现代架构支持缩放、分屏、表情符号、表,包括桌面行内页面损坏的可能性,以及强大的类似CSS的段落样式概念。使用 WPReporter 进行报告,这是一种功能强大的附加组件,可理解为非常强大的邮件处理实现。与邮件合并相比,其中字段被数据库内容替换,WPReporter 还允许在模板中集成循环来创建列表和表。为了去除大量多分辨率的位图,WPTools 9.x embedded SVG rendering engine包含一个嵌入式SVG渲染引擎。它以正确的分辨率和可选的修改方式将工具栏和标尺图标的SVG源转换为位图,以匹配暗色主题。您也可以使用此SVG渲染引擎在TGraphicControl中显示SVG,或在TCanvas上进行渲染。(要求 Delphi XE2 或更高版本)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值