微信数据解密用了 794 次断点,OPPO 便签我一行解密代码都没写
OPPO 便签的数据在云端,请求和响应全程加密。我没碰加密,连进它的页面,让页面替我解密,实测导出 106 条便签、234 个附件。
2026 年 7 月 30 日下午,钉钉管道刚验收完,我转向数据链路的第四块阵地:手机便签。
我是野生码农,39 岁,程序员。这是"AI 解密攻防"系列的第四篇。前面三站:微信本地库,8 轮断点、794 次命中硬抠;钉钉本地库,三行密钥代码;印象笔记,token 自动换新。我用 OPPO 手机,便签是我平时记素材、记想法的主战场。要进本地知识库,先得把它导出来。
一、排雷:加密在云端,抓包是条死路
OPPO 便签在 PC 端没有独立客户端,入口在 O+互联这个互联套件里。老习惯,先侦察再动手:
| 路线 | 结果 |
|---|---|
| 官方开放 API | 没有这个接口,出局 |
| 模拟鼠标点界面 | 微信时代就否掉了,脆弱丢人 |
| 抓包解协议 | 请求响应全程加密,硬解是深坑 |
| 连进页面让它自己解密 | 成了 |
便签窗口里看到的东西,实际是内嵌浏览器(webview)加载的一个网页应用。webview 就是程序里嵌的一个浏览器内核,数据在 OPPO 云端,请求和响应的加密由页面自己的 JS 完成。
抓包抓到的是密文。要解,得逆向页面 JS 里的加密函数,还得跟着它每次改版返工。又深又脆。
但换个角度:页面自己总能看懂这些数据,不然它没法显示。
我不解密,让页面替我解密。
二、反转:连进页面,调它自己的 API
浏览器页面就有浏览器的门。Chrome 系内核都带一套调试协议,叫 CDP,能通过 websocket 连进页面、在里面执行 JS。思路一句话:用 CDP 连进这个 webview,直接调页面里现成的 API 模块,返回来的就是解密后的明文。
不碰协议,不碰密钥,一行解密代码都不用写。对比前两站:微信 794 次断点,钉钉三行代码,这一站,零行。
三、三道坎:自杀开关、403、一个没替换的占位符
第一道坎,反调试。想用调试端口启动,原版程序检测到 --remote-debugging-port 这类启动参数,直接 process.exit(1) 自杀。硬刚没戏,但它查的是字符串,那就动字符串:把整个应用复制一份副本,约 930MB,一次性;把副本里 app.asar 文件中的参数字符串等长改掉一个字符:
OLD = b'"--remote-debugging-port"'
NEW = b'"--remote-debugging-porx"' # 等长替换,绕过 argv 前缀检查
data = open(ASAR, "rb").read()
if OLD in data:
open(ASAR, "wb").write(data.replace(OLD, NEW))
等长替换,就是新旧字符串一样长,文件结构一个字节不动。改完后程序再查 --remote-debugging-port,查无此串,放行。副本带调试端口启动,正版一个字节没动,导完一键恢复。应用升级副本会失效,重打一遍补丁就行。
第二道坎,403。调试端口起来了,websocket 一连就被拒。原因是内核校验握手时的 Origin 头,压掉就好:
ws = websocket.create_connection(ws_url, timeout=120, suppress_origin=True)
连上之后在页面列表里找便签页:类型是 webview、URL 里含 desknote 的那个,就是它。
第三道坎最戏剧性。连进页面了,API 函数在哪?这页面是 webpack 打包的,模块全封在闭包里,外面摸不到。webpack 是把一堆 JS 模块打成一个文件的构建工具。常规打法是往它暴露的全局数组里 push 一个假模块,把内部加载函数钓出来。但我按惯例找这个数组的时候愣住了:它的名字是字面量 webpackJsonp_[name]。构建工具本该把 [name] 占位符换成真实名字,它没换,原样留在了线上代码里,像施工队把模板里的占位符直接砌进了墙。
占位符没换正好,名字反而固定。push 注入,钓出加载函数,便签的 API 在编号 f220 的模块里:
var w = window["webpackJsonp_[name]"];
w.push([["zz_export"], {"zz_export": function(m, e, r){ req = r; }}, [["zz_export"]]]);
window.__noteApi = req.c["f220"].exports; // q=列表 t=详情 r=分组
API 到手,调列表接口,参数照页面自己的用法抄。又一个实测出来的边界:pageSize 填 200 报参数错误,填 50 正常。这种边界,文档里没有,不试不知道。
然后是个意外之喜:列表接口直接返回全文,标题、正文、附件元数据全在里面,连详情接口都不用调。我以为得一页页翻详情,结果接口比我想的厚道。
四、战果,和一处没打通
战果:106 条便签全量导出,创建时间从 2024 年 1 月 20 日到 2026 年 7 月 28 日;一份 JSON 原始数据加一份可读 MD,落进本地知识库。
没打通的如实写:分组接口的参数没摸清,报 system error,106 条便签的分组名没拿到。内容一条不少,只是 MD 里不显示分组名。没硬刚,记为已知限制,以后想折腾再说。
五、附件比正文麻烦:四种类型,手写是三件套
文字好办,附件麻烦。逆向下来,附件有四种类型:0 是照片;18 是手写笔画的矢量数据,每个点记坐标和笔压;19 是官方渲染好的手写批注图,一张和照片同尺寸的透明叠加层;16 是手写识别出的文字。
也就是说,一张"照片上写了字"的便签,服务器上存的是三件套:照片、笔画矢量、渲染叠加层。下载地址里要带一个会过期的 token,每次从页面现取;下载完把叠加层和照片做透明合成,插图位置照正文里的 img 标记还原;有笔画数据没渲染层的,自己按坐标画一张兜底。
这部分没有反转,纯体力活:下载加合成的脚本三百行出头,一次跑完 234 个附件,照片 77 张、笔画 53 组、叠加层 53 张、识别结果 51 份。
六、四个判断点,谁再踩谁冤
- 加密在前端 JS 里完成的,别解协议。用 CDP 连进页面,调页面自己的 API,让页面替你解密。凡"内嵌 webview + 前端加密"的应用,这套思路通用。
- 反调试别硬碰。它查的是字符串,等长改一个字符就绕过;补丁打在副本上,正版不动。
- 参数边界要实测。pageSize 200 报错、50 可用,不试不知道。
- 附件类型别假设只有图片。0、18、19、16 四种,手写是"矢量笔画加渲染叠加层加识别结果"三件套,少拿一件,图就是不完整的。
边界还是那句,系列里每篇都说一遍:全程只在自己电脑、自己账号上操作,只读不写,产物不出本机。读懂自己的数据,天经地义。便签内容是我的隐私,一条没往这篇里放;上面所有代码都是脱敏后的关键片段,流程只指向一个目的,导出我自己的便签。
微信那晚我以为每场仗都得 794 次断点那么打;钉钉告诉我,有时候钥匙就挂在门上;OPPO 这一课更彻底:有时候门都不用开,屋里的人本来就在替你干活,你只管敲门。
下一篇,TODO 清单本地库逆向,8 月 20 日发。
FAQ
问:这套方法能用在别的应用上吗? 看数据在哪、加密在哪。数据在内嵌 webview 里、加密由页面 JS 完成的,CDP 这条路通用;像微信钉钉那样加密在本地数据库层的,还是得回老战场硬解。先侦察再选武器,这条系列里每篇都一样。
问:为什么不抓包解协议,那不更硬核? 硬核不等于高效。加密的密钥和逻辑都在页面 JS 里,逆它要跟着版本升级反复返工;调页面自己的 API,加密它自己处理,我只维护一个连接。工程上选后者,不犹豫。
问:分组没打通就发出来,不丢人吗? 不丢。106 条内容一条不少,分组名没拿到是事实,写进来是规矩。这个系列数字只报实测,没打通就是没打通,哪天打通了再报。
问:普通 OPPO 用户值得折腾吗? 看量。便签几百条、要进自己的知识库长期留存的,值;就几十条备忘的,手动复制更快。另外它需要一点 Python 和浏览器调试基础,纯小白别硬上。
我是野生码农,AI 实战派。自研 AI 本地知识库系统、AI 数字员工团队、软考 AI 备考系统、量化交易系统,全程公开复盘。
这个"AI 解密攻防"系列,微信是第一战,钉钉是第二战,印象笔记是第三战,本篇 OPPO 便签是第四战,下一篇 8 月 20 日,TODO 清单本地库逆向。8 月 30 日前后还有一份月报,所有系统跑了多少活、花了多少钱,真实数据全公开,难看也放。
关注我,看真的。
野生码农 · 全网同名 · AI 数字员工实战复盘,数据只报实测
