先别急着把改好的包写回去
这次遇到的问题,表面上很像一个文件处理题:把手机剪辑应用里的草稿取出来,解开,改几处内容,再重新封装,最后放回手机。前半段确实可以这样描述。草稿主文件在应用私有目录里,普通的设备调试通道不能直接读取,所以先通过设备自带的备份路径取得副本,再在电脑上离线解密。到了这一步,文件结构、加解密过程和重新封装都已经能够自检通过。
但我没有因此立刻把改好的整包写回手机。因为“文件能被重新封装”和“应用能安全接收这份文件”,根本是两件事。前者只说明离线处理没有破坏格式,后者还要回答:写入时会不会清空原数据?应用会不会重建自己的索引?原有草稿、登录状态和其他用户数据还能不能保留?
这件事最容易让人产生一种错觉:既然改动只发生在一个草稿文件里,恢复时就应该只影响这一个草稿。可应用看到的可能不是一个孤立文件,而是一整份用户数据目录。文件层面的“只改一个字段”,不等于应用层面的“只影响一个对象”。
如果不先处理这个边界,最坏的结果不是这次修改没有生效,而是一次看似正常的恢复,把现有草稿和登录状态一起带回旧快照。那时再去追究是哪一个文件出了问题,已经没有多少补救空间了。
表面是改文件,真正是改应用状态
我一开始把“写回”理解成读取和解密的反方向:从应用私有区取出文件,改好,再沿原路返回。后来发现,真正需要研究的不是路径,而是状态变化。
手机剪辑应用的草稿并不只是一个 JSON 文件。它还和应用内部的索引、目录结构、其他资源以及当前运行状态连在一起。非 root 条件下,普通调试通道既不能直接写入私有目录,也不能假设应用会自动发现一个被放进去的文件。即使文件本身完全正确,应用没有建立对应的索引,打开时也可能看不到它;反过来,恢复机制如果直接处理整个应用目录,就可能把当前状态一起替换掉。
于是,写回问题必须拆成至少 3 条边界:能不能读出,能不能在离线环境里修改,能不能通过应用认可的方式导入。它们分别属于访问、内容和生命周期。前两条已经跑通,不代表第 3 条自然成立。
我后来把每条路线都换成同一个问题来问:入口是谁提供的?它写入哪个范围?它会创建新对象,还是覆盖旧对象?失败后能不能回到原状?这样一来,讨论就不会被“这个包看起来没问题”带偏。
把读、改、写、导入拆开
拆分之后,第一条边界很快明确了。草稿主文件在应用私有目录,非 root 不能用普通调试命令直接读取。设备备份机制可以提供一份副本,副本再经过离线处理,能够得到明文草稿。这个过程解决的是“怎样拿到可研究的材料”,不是“怎样把结果送回去”。
第二条边界也有了比较硬的证据。离线重新封装时,先做不改内容的往返测试,确认解密和重新加密可以逐字节对应;再只改一个草稿字段,确认重新解密后只有目标成员发生变化;最后故意破坏一个比特,确认完整性校验会拒绝它。这些检查证明了改包工具没有把结构弄坏。
不过,离线自检的价值正在于它边界清楚。它证明的是“包在离线世界里自洽”,不是“真机上的恢复动作安全”。自检通过只能让文件进入下一关,不能替写回动作做安全背书。
因此第三条边界必须单独验证。备份恢复路径的行为很关键:它不是把一个文件合并进现有目录,而是先清空这个应用的用户数据,再释放整包数据。虽然可以只选择这个应用进行恢复,不牵连其他应用,但对这个应用本身来说仍然是“清空后覆盖”,不是增量合并。
这就是为什么我坚持要用专门制造的 throwaway 数据做验证。真草稿里包含的不只是视频编辑结果,还有用户不希望丢失的现状。只要恢复动作的覆盖粒度没有被证明,拿真草稿试就是把验证成本转嫁给用户。
为什么我把整包恢复放到最后
整包恢复并非完全不可行。研究结果显示,恢复前确实有完整性校验;但这条校验关注的是备份包是否和校验信息一致,使用的是备份口令派生出的校验机制,并没有把设备身份绑定到这份包上。因此,在掌握正确口令的前提下,离线修改后重新计算校验信息,技术上可以让改过的包通过完整性检查。
这解决了“改过的包会不会被校验拦住”,却没有解决“恢复会不会覆盖现有状态”。两件事不能混在一起。一个包能被系统接受,不代表它适合直接覆盖正在使用的数据。
**我没有把整包恢复当作首选,是因为它的技术可行性和数据安全性不是同一个判断。**它可以是最后的备选路径,但必须先做完整快照,停止应用运行,使用 throwaway 草稿验证恢复后的可见性,再向用户复述清空和覆盖的代价,获得明确同意后才可能触碰真实数据。
更重要的是,恢复前后的时间差也会制造风险。备份完成后,如果用户又新建了草稿、修改了设置,或者登录状态发生变化,那么恢复的旧快照即使完全合法,也可能把这些新增状态带回过去。如果不把这个风险写进流程,用户很容易只看到“恢复成功”,却忽略了成功恢复的其实是旧世界。
导入通道带来的新路径,也可能只是静态幻觉
为了绕开清空和覆盖,我把注意力转向应用自己的导入能力。反编译结果里确实发现了一个草稿导入路径:它可以接收普通压缩包,包内只放特定目录结构和草稿 JSON,应用进程再用自己的权限把它写入私有目录,并建立新的草稿索引。从设计上看,这正好符合“新建对象而不是覆盖旧对象”的安全方向。
这条发现很有价值,因为它把问题从“非 root 能不能写私有目录”改成了“应用有没有愿意替我们写进去的入口”。应用自己的进程当然可以写自己的沙箱,用户不必取得 root,也不必让外部工具直接碰私有目录。优先寻找能新建对象的应用入口,是处理高风险写回时更稳的顺序。
但这里还有一个不能跳过的陷阱:代码里存在导入器,不等于当前版本的手机界面提供了可操作的导入入口。真机验证时,改好的测试包已经放到公共下载目录,随后逐层检查菜单、草稿管理和传输相关页面,结果没有找到本地 zip 导入选项。包没有机会进入导入器,更谈不上被拒绝;真正的阻塞发生在入口之前。
所以最终结论不是“导入包格式错误”,也不是“需要登录或付费”。测试使用的是已有登录状态,应用里也没有出现相关拦截。准确结论是:导入逻辑在应用内部存在,但移动端没有把它暴露成可用的本地入口,路线因此是架构性 BLOCKED。测试过程中只清理了自己推送的测试文件,没有导入、删除或改动现有草稿。
下次我会怎么做
下次再遇到类似需求,我会先画出 3 条边界:可读、可写、可导入。先确认数据怎样安全拿到副本,再确认离线修改是否可逆,最后单独寻找由应用自己完成写入的入口。任何一条没有真实证据,都不把它写成“已经打通”。
具体流程会从只读检查开始:确认应用识别方式不依赖易变的版本号,确认当前草稿数量和登录状态,保存原始快照,并把所有实验对象标成 throwaway。接着做离线往返、自检和篡改拒绝测试,证明内容处理没有引入额外变量。然后优先找“新建草稿”的入口,验证入口是否真实存在、包是否能被送达、导入后是否出现新的 UUID 草稿。
只有在导入路径明确不存在,且确实需要继续研究恢复时,我才会把整包恢复列为候选。那时先记录它的覆盖范围、清空动作和时间差风险,再用可丢弃数据做最小验证。任何真实恢复前,都要把可能丢失的内容和回滚条件说清楚,不能用“文件格式正确”代替用户同意。
这件事让我重新意识到,工具真正改变的不是速度,而是我们能不能把判断过程留下来。一个流程成熟的标志,不是它能在实验室里跑通一次,而是下一次遇到同样的边界时,知道哪一步可以继续,哪一步必须停下。对手机草稿来说,最安全的入口往往不是最接近文件的那个,而是最尊重应用对象生命周期的那个。