Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
zipdefrag — Presented at Recon Montreal 2018 | Kitploit
工具/GitHubGitHub/nccgroup/zipdefrag
Embedded Systems SecurityMemory ForensicsReverse EngineeringData RecoveryDigital ForensicsFirmware Analysis
GitHubnccgroup/zipdefrag

zipdefrag

Presented at Recon Montreal 2018

查看仓库
748年前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

这个转储是一个谜题

或者,为傻瓜与叛逆者准备的进阶霰弹枪解析

从前有一次

从前,亲爱的读者,在一个漆黑而暴风雨肆虐的夜晚,您忠实的作者偶然遇到了一种令人困惑且神秘的情况——在一个系统中,唯一阻碍芯片剥离(chip-off)内存分析与逆向的,是一种未知的、碎片化的专有文件系统,再加上因使用嵌入式 Java 而引入的压缩,使得现有的文件雕刻(file carving)工具效果大打折扣。

当时,也许有人通过手动拼接那些看起来能对上的块,配合一些丑陋的 Python 控制台操作和难看的临时 bash 脚本,拼凑出某种临时解决方案。它够用,但非常耗时。

尽管在硬件黑客的日常工作中,从芯片剥离分析中提取未压缩的普通数据是相当乏味的工作,但当压缩数据的碎片被以无理取闹、令人不快的方式随意丢弃得到处都是时,即使没有其他真正的提取保护措施,也会带来严重问题。

但肯定有更好的办法吧?

尤其是 Zip 文件(也就是 JAR 文件使用的基本格式)有一些有趣的地方。作为 International Journal of PoC||GTFO 长期以来的忠实读者,尤其关注 Ange Albertini 在文件格式特技方面的工作,我觉得一个 zip 文件内部有足够多关于该 zip 文件自身的数据,足以让我们出色地把它重新拼接起来。

好了,参考内容就说到这里,让我们进入技术细节。

首先,我们可能不知道文件系统的具体细节(而从我研究的视角来看,独立于我遇到问题的那个系统,我决定干脆不去关心它)。但我们对大多数文件系统的实现方式略知一二。特别是,我们知道它们往往以块(chunk)为单位写入。这些块有一个某种形式的最小尺寸,称为页(page),我们可以通过浏览转储并识别被写入的最小块大小来确定页大小。

其中一些块可能是连续的,另一些则不是,而且块何时连续并没有清晰的规律。

所有这一切都在说,我们要解决的问题是:如何对数据页重新排序,使它们能为我们提供要提取文件的合法(或足够接近的)映像。

Zip 文件的写入方式实现了一种反向层级结构。首先是压缩文件数据(包裹在描述它的本地文件头 local file header 中)。然后是中央目录(central directory,列出各本地文件头的偏移量),再然后是中央目录结束记录(end of central directory,除其他内容外,它描述了 zip 中存储的文件数量、中央目录的起始偏移量以及中央目录的大小)。

让我们把它倒过来,深入一点细节:

  • 中央目录结束记录(End of Central Directory)告诉我们:

    • EOCD 记录在 Zip 文件中的确切位置(CD 的偏移量加上 CD 的长度,位于 EOCD 之前)
    • 需要查找多少个文件(因此也就是多少条 CD 记录)。
    • 第一条 CD 记录在 Zip 文件中的精确位置。
  • 每条中央目录记录告诉我们:

    • 压缩文件数据的 CRC32
    • 时间戳
    • 大量其他元数据(压缩方法、标志、使用/需要的操作系统版本……)
    • 文件中对应 LF 块(local file chunk)的索引
    • 关键是:足以构建对应 LF 块映像的数据。
  • 每条本地文件记录告诉我们:

    • 我们转储中一个文件起始处的位置
    • 如果文件足够小,我们可以在同一页内得到整个文件;或者,得益于下一个文件头出现在 zip 文件的下一个分页块中!
    • 如果有足够多的小文件被打包进足够多的页中,我们就可以利用页的位置和已知的目录值来为这些页创建排序(并带有已知的间隙!)

综上所述,我们得以重建文件的绝大部分内容。

剧情反转——我们必须现实地处理不止一个 JAR 固件!

首先,我们需要一个步骤来区分来自不同固件的数据。原因是所有偏移量都只在其各自的 zip 文件内有效——任何冲突都会导致 zip 流不匹配和损坏,而我们非常希望尽可能多地提取出未损坏的数据。此外,我们也希望有很好的保证,例如,我们在目标固件中诊断出的任何漏洞,影响的是我们通常看到运行的那个固件,而不是某个只是被闲置在一边的其他文件。

这里需要的解决方案是 kmeans 算法(又称“劳埃德算法”)。这个视频 很好地解释了它的工作原理。SciPy 已经有一个可用的好版本,但我必须识别/修补 唯一实现了该算法的聚类/分析 crate,才能让它在 Rust 实现中正常工作。幸运的是,我不必从头编写它。

搞定这一步后,事情就顺了。

我们可以利用许多特征来做这件事。Flags、Method 和 Version 字段都会根据用于压缩文件的 Zip 软件栈而变化。此外,头部带有时间戳,而且通常不太可能所有固件都在完全相同的时间被编译和压缩。

顺便说一句,值得注意的是,Zip 文件使用 MS-DOS 格式的时间戳, 它们是位打包的短整数,表示年-月-日和小时-分钟-两秒。 如果在将其用作分类数据之前没有将其转换为绝对标量值, 那么你很可能会把一年的差异看得和一秒的差异同等重要,这绝对不行!

我们将这些转换成一个欧几里得向量(Euclidean Vector,这是对 ℝ 中一个 n 维值数组,也就是浮点坐标的花哨说法,不过上面链接的视频可能是最直接的解释),然后聚类算法几乎为我们完成了其余所有工作,把所有解析出的头部收集到我们预期数量的桶中。

关于解析的快速附注

虽然我是用 相当粗略的 Python 脚本为这种方法做的原型,但在 PoC 中达到了大约 70-80% 的 JAR 内容恢复率后,我决定就此打住,转而用 Rust 实现一个快速版本。

Rust 有一个名为 nom 的 crate,它对于编写解析器验证器(parser-verifiers)来说绝对棒极了。这也是用 Rust 重写它的主要吸引力之一,顺便说一句。能够编写清晰且极其严格的解析器,使得这件事在某些方面比试图用 Python 处理所有这一切要容易得多(Python 通常要宽容得多,以至于有时很难确保你没有因草率处理错误失败而漏掉某个边界情况)。

如果出色的、快速且清晰的解析器让你感兴趣,去看看

  • Writing Parsers like it's 2017
  • Nom Benchmarks - 其中有人用 Rust 从头编写了一个 http 解析器,比一个非常快的 C 实现还稍快一些,并且没有缓冲区溢出。

除此之外,用 Python 运行这类分析本质上偏慢,而且它从未真正被期待成为“通往 PoC 的路径”之外的东西,这个 PoC 用于探索这种方法的可行性。

总之,说得够多了……

继续前进

重建剩余块的方法包括:首先过滤剩余页以找出高熵候选;先填补较小的间隙(尽可能多地从搜索列表中排除页,因为在最坏情况下,测试这些页的排列是一项指数级时间任务,所以快速解决较简单的情况是优先事项,并且随着进展会指数级地简化我们的问题!)。

我们还可以通过寻找因页边界而无法解析的本地文件头实例来快速取胜(我们应该能够将它匹配到一个对齐方式相同的对应头部,至少在相似对齐是唯一且不与其他损坏伪影冲突的情况下是这样)。

我们如何检查缺失页的候选?好吧,我们的中央目录里就放着文件的 CRC32 校验和!与其对整个文件计算 CRC32,可能最好的方法是:对我们已经知道的块计算 CRC32(从间隙开始前,页末尾的数据向前计算;再从间隙之后的数据(或 deflate 流之后的 DataDescriptor 块)反向计算),并由此算出每一块缺失页我们应当预期的中间 CRC32。

本质上,每当我们选择解决最简单/最快的问题,我们都会通过消除干扰项(chaff)让更困难的问题显著变简单。这也是为什么使用香农熵(Shannon entropy)直接扔掉空页或几乎为空的页——我们并不能保证只会遇到高熵的 zip 页,但即使存在离群值,从一开始就避免处理这种复杂情况也会带来巨大的速度提升。

我只是想把这该死的东西构建出来,到底怎么回事?

如果你想摆弄它,请安装 rust(推荐使用出色的 rustup nightly。然后:

root@kitploit:~
$ git clone [repo]
...
$ cd zipdefrag
...
$ cargo build --release

你可以省略 release 标志以启用调试。

构建产物将位于 /target/{debug,release}

使用 cargo doc 构建文档(这个 crate 有大量文档。我喜欢写文档。)

目前 Rust 版本默认没有终端输出;如果你想运行 CLI 测试工具,需要设置环境变量 RUST_LOG=zipdefrag,它会启用详细终端日志,展示至今的分析过程。

更多内容即将推出:

  • 一个快速、可移植的原生可执行文件(带 Python 钩子),用于解析来自未知文件系统的谜题式 zip 转储。

  • 一个演示转储

已知问题

  • 当前 Rust 实现的性能很糟糕,因为在搜索对应的 LFH 块时存在浪费行为。我会修复这个问题并吸取教训。

  • 当 JAR 中有许多文件明显大于页大小时,这种技术效果不佳。因为它依赖于大量利用 zip 文件固有的结构,数据密集的文件就是不太行。

方便的是,对于 J2ME midlet 来说,class 文件通常相当小,但打包在其中的大型二进制文件可能无法恢复。

另外,Python PoC 包含一些算术错误。

下载工具