rea-reverse

文件类型识别与十六进制查看器

把任意文件拖进来——没有扩展名的、几个 GB 的都行——按魔数判断它是什么格式,找出里面嵌着哪些文件头,看每个数据块的熵怎么变化、能提取出哪些字符串,再用十六进制视图逐字节核对。

文件不会离开你的浏览器. 文件由一个没有网络权限的 Web Worker 分块读取,不会上传到任何地方。 如何自己验证

把任意文件拖到这里

或

不认识的扩展名、CTF 附件、固件 dump、压缩包、可执行文件、图片都行。不限大小,文件会分块读取。

这个工具不做什么

  • 只读文件签名和文件头,不解包、不解压、不解密,也不运行任何东西。匹配上只说明这些字节“自称”是某种格式,损坏的文件照样可能匹配。
  • Protocol Buffers、裸 C 结构体、Brotli 流、加密数据这类没有签名的格式,无法单凭字节认出来;熵很高也只能说明数据被压缩或加密过。

没有扩展名的文件、CTF 里一个叫 chall 的附件、从 flash 芯片里读出来的固件、厂商支持页面上下载的 .bin——光看名字什么都判断不了,直接双击更不是第一步该做的事。你真正需要的,是 file 和 xxd 能告诉你的那些信息,再顺便看看里面还埋着什么,然后才去挑真正能打开它的工具。

这个页面在浏览器里把这件事做完。文件按块读取,所以 2 GB 的磁盘镜像和 2 KB 的 .pyc 处理方式一样,而且不会上传。

能看到什么

它是什么文件。 先看偏移 0 处的签名,以及某些格式固定使用的偏移:tar 在 257,ISO 9660 在 0x8001,ext2/3/4 在 0x438,GPT 头在 512,DMG 的 koly 尾部在距文件末尾 512 字节处。每条结果都会给出匹配到的字节和它所在的偏移、一个可信度,以及文件头里读出的信息:

  • 高:签名匹配,并且结构校验也对得上。例如 ELF 的 class 和 version 字节、tar 头校验和、U-Boot 头的 CRC32、SquashFS 的块大小与 block_log 一致、Java class 版本号在合理范围内。
  • 中:签名匹配,但没法进一步校验,或者校验没通过(U-Boot CRC 不对、声明的大小比文件还大、PNG 不以 IEND 块结尾、gzip 压缩流没到结尾就断了)。
  • 低:签名很短或很常见,比如 MP3 帧同步、zlib 头、第 510 字节处的 55 AA。只能当线索。

覆盖的格式包括:

  • 可执行文件:ELF(架构、32/64 位、字节序、类型、OS/ABI)、PE(PE32 还是 PE32+、机器类型、EXE 还是 DLL、子系统;带 CLI 头的标为 .NET 程序集)、Mach-O(CPU、文件类型),以及通用 Mach-O 里每个切片的架构。CA FE BA BE 会根据后面 4 个字节区分是通用 Mach-O 还是 Java class,和 file(1) 的规则相同。
  • 字节码:Java class(带 Java 版本)、DEX/ODEX/VDEX/ART、WebAssembly 模块和组件、.pyc(根据魔数读出 Python 版本,从 2.x 到 3.15 开发版;预发布版的魔数会单独标出)、Lua 5.1–5.5 和 LuaJIT、Hermes(React Native)及其字节码版本、V8 代码缓存(bytenode 的 .jsc)、Erlang BEAM、Dart kernel。
  • 归档与压缩:ZIP、gzip(原文件名和操作系统)、xz、Zstandard、bzip2、7z、RAR 4 和 5、LZ4、lzip、LZMA、Unix compress、CAB、ar 和 .deb、RPM、XAR(.pkg)、CRX、tar(POSIX 和 GNU)、cpio(newc、CRC、odc、二进制)、Electron ASAR。
  • 基于 ZIP 的格式,根据中央目录里的文件名区分:APK、AAB、APKS、XAPK、AAR、IPA、JAR、WAR、DOCX、XLSX、PPTX、EPUB、ODT/ODS/ODP、MSIX、VSIX、NuGet、Python wheel 和 egg、3MF、KMZ、Fiddler SAZ、Firefox XPI 及其他 WebExtension 扩展包。
  • 固件与文件系统:SquashFS(版本、压缩算法、块大小、镜像大小,也认厂商改过字节序的魔数)、U-Boot uImage(名称、操作系统、架构、镜像类型、压缩算法、头 CRC)、Broadcom TRX、UBI 和 UBIFS、两种字节序的 JFFS2、cramfs、romfs、设备树、Android boot.img 和 vendor_boot.img、Android sparse 镜像(带展开后的大小)、OTA payload.bin、DTBO、ext2/3/4、FAT、NTFS、exFAT、GPT 和 MBR、ISO 9660、DMG、qcow、VMDK、VHDX、VDI、WIM、LUKS、Intel HEX 和 S-record。
  • 图片与音视频:PNG、JPEG、GIF、BMP、WebP、TIFF 及基于它的 RAW 格式、ICO/CUR、PSD、QOI、ICNS、JPEG XL、JPEG 2000、HEIF/AVIF,按 ftyp brand 区分的 MP4/MOV/M4A/3GP,WAV(编码、声道、采样率)、AVI、Ogg(及其中的编码)、FLAC、MP3、AAC、Matroska 和 WebM、FLV、MIDI、AIFF、MPEG-TS 和 PS、ASF。
  • 数据、密钥与文本:PDF、OLE2 复合文档、RTF、PostScript、二进制和 XML 格式的 plist、SQLite 及其 WAL、pcap 和 pcapng、Windows 快捷方式、注册表配置单元、事件日志和 minidump、字体、Git pack、Android 二进制 XML 和 resources.arsc、DER 编码的 X.509 证书、DER 或 BER 编码的 CMS / PKCS#7 SignedData(被签名的内容是带描述文件字段的 plist 时报为 iOS 描述文件)、按标签识别的 PEM 块、OpenSSH 密钥、Java keystore,以及按内容识别的文本格式:HAR、source map、JSON、HTML、SVG、XML、带 shebang 的脚本、十六进制文本形式的 EVM 字节码。

Protocol Buffers 认不出来。 序列化后的 protobuf 消息和 FlatBuffer、裸 C 结构体、Brotli 流以及任何加密数据一样,没有魔数:第一个字节只是第一个字段的 tag。工具会直接说明这一点,不会瞎猜;这时能依靠的只有下面的熵图和字符串。

用其他工具打开。 如果本站有对应格式的工具,结果里会给出链接:APK/AAB 分析器、处理 ELF/PE/Mach-O 的原生二进制概览、.NET 元数据查看器、WASM 转 WAT、ASAR 解包、HAR / SAZ 接口归纳、source map 还原、EVM 选择器提取,描述文件和 IPA 则链接到 iOS 描述文件与 IPA 查看器。页面之间不会传递文件,需要在那边重新选择同一个文件。

十六进制视图。 整个文件的偏移、十六进制和 ASCII 三栏。只读取当前可见的行,所以滚动几个 GB 的镜像和滚动小文件一样轻。用来识别文件的那几个字节会高亮。可以按十六进制(0x8001)或十进制跳转到任意偏移;点击一个字节选中它,拖动或按住 Shift 点击扩展选区。下方面板会把选区开头的字节分别按 8/16/32/64 位整数(有符号和无符号、大小端)、32/64 位浮点数和 Unix 时间戳解读,并把选区按 ASCII、UTF-8 和 UTF-16LE 显示成文本。

内嵌签名。 类似 binwalk 的签名扫描:逐字节扫一遍文件,列出每个出现已知文件头并通过校验的偏移,比如固件里的 gzip 流、uImage 内核后面的 SquashFS 根文件系统、PDF 里的 PNG。落在声明了长度的容器(SquashFS、uImage、TRX、设备树、cramfs、DEX、SQLite)内部的命中会标为嵌套。重复出现的结构(JFFS2 节点、UBI 擦除块、cpio 和 ZIP 成员)每段只报告一次。最多保留 1,000 个命中。

熵图。 把文件分成大约 1,024 个块(每块至少 1 KiB),画出每块的香农熵。贴着 0 的是填充或擦除过的 flash,代码和文本一般在 4.5 到 6.5 之间,接近 8 的平台则是压缩或加密数据。点击图表可以跳到十六进制视图里对应的块。

字符串。 长度不少于 6 个字符(可在 2 到 64 之间调整)的可打印 ASCII 和 UTF-16LE 串,每条都带偏移,点偏移就能在十六进制视图里看到它。保留前 20,000 条并可筛选;大文件找到这么多条后就停止扫描,并说明扫到了哪里。

原理

页面把文件交给一个 Web Worker。Worker 先用 Blob.slice 读出前 64 KiB,跑一遍签名表:每一项包含魔数、魔数所在的偏移,以及格式允许时对周围头字段的校验;匹配上的再去读详细信息。遇到 ZIP 时,Worker 从文件末尾读出中央目录,根据里面列出的条目判断具体格式。如果没有任何二进制签名匹配,就把前 8 KiB 当文本来判断。

接着 Worker 再按 4 MiB 一块把整个文件读一遍,同时做内嵌签名扫描、计算各块的熵并提取字符串,边扫边报告进度。签名表上建了一个两字节的索引,所以扫描时每个字节只需查一次表。熵和字符串的代码与原生二进制概览用的是同一套。无论文件多大,内存峰值基本就是一块的大小。

我们拿 macOS 自带的 file 5.41 和 binwalk 3.1.0 做了对比,样本是专门准备的 98 个文件:用 zip、tar、gzip、xz、zstd、7zz、bzip2、lz4、cpio 和 mksquashfs(gzip、xz、lz4、zstd 四种压缩)生成的归档,CPython 3.11、3.12、3.13、3.14 编译出的 .pyc,一个 SQLite 数据库,用 sips 转换的图片,ffmpeg 生成的音视频,openssl 生成的证书,hdiutil 和 pkgbuild 生成的 DMG、ISO 和 .pkg,按各自打包工具的目录结构做的 ZIP 类安装包,以及本站自己的 ELF、PE、Mach-O、APK、AAB、WASM、ASAR 和 HAR 测试文件。每个样本的首个结果都和我们生成它时的格式一致。file 对其中 90 个给出了相同的格式;剩下 8 个是 file 5.41 没覆盖到的情况:4 个 .pyc(Python 3.11 到 3.14)、ASAR 归档和 MPEG-TS 它都报成 data,PEM 私钥报成 ASCII text,UDZO 格式的 DMG 报成 zlib compressed data(它不看 koly 尾部)。整个样本集上 binwalk 一共报告了 53 个偏移,其中 51 个这里也能找到:偏移 0 的由识别结果覆盖,其余由内嵌扫描找到。另外 2 个是 binwalk 在 TIFF 和 JPEG 2000 图片的色彩配置里标出的 “Copyright text” 字符串,在这里会出现在字符串列表中。在一个人为拼接了 gzip 文件、SquashFS 镜像和 PNG 的样本上,两个工具报告的三个偏移完全一致;这里还会列出 SquashFS 镜像内部的 xz 压缩块,并标为嵌套。

大文件方面,我们测了一个 2.5 GB 的稀疏镜像,在偏移 0 放了 gzip 头、1 GiB 处放了 SquashFS 镜像、2 GiB 处放了 PNG:在 Node.js 22 里完整扫描用时 10.4 秒,常驻内存峰值 133 MiB,三个文件头都在正确的偏移被找到。

文件如何保持私密

文件不会上传:

  • 识别、内嵌扫描、熵和字符串都在你设备上的 Web Worker 里完成;十六进制视图在页面里直接按块读取文件。
  • 从文件里取出的一切内容(名称、标签、字符串、字节)都以纯文本显示,不会被当作 HTML 解析或执行。
  • 唯一的统计事件 filetype_identify 只包含结果状态、首个结果的粗粒度类别和可信度(例如 archive 和 high)、大小区间和耗时区间。不会包含文件名、格式名称、字节、字符串或偏移。完整列表见隐私页。
  • 工具页不展示广告。

如何自己验证

  1. 在选择文件之前,打开浏览器开发者工具,切到 Network(网络) 面板。识别一个文件,滚动十六进制视图,再改一下字符串的最短长度。你只会看到页面资源和 worker 脚本 /_astro/filetype-worker-*.js 的请求,没有任何请求携带你的文件。
  2. 或者先打开页面,再断网(关掉 Wi-Fi,或者在 Network 面板勾选 Offline),然后识别文件,依然可以正常工作。
  3. worker 运行在内容安全策略 default-src 'none'; script-src 'self'; connect-src 'none' 之下,浏览器不允许它发出任何网络请求。

局限

  • 签名只是一种声明。 被截断或损坏的文件开头照样是正确的字节,有些格式也只能靠很短的特征认出来。只有 PNG(看结尾是不是 IEND 块)和不超过 8 MiB 的 gzip(试解压到压缩流结尾)会检查结尾是否缺失;截断的 ZIP 因为中央目录没了也能看出来。其他格式不检查。每条结果的可信度和备注说明了到底校验了多少。
  • 不解包。 不会提取、解密或执行任何东西;除了对不超过 8 MiB 的 gzip 做上面那次结尾检查(解压结果直接丢弃),也不解压任何东西。内嵌扫描只告诉你东西从哪里开始;要切出来,请用 binwalk -e、unblob 或 dd,Binwalk 3 使用指南里有完整步骤。
  • ZIP 细分依赖中央目录:ZIP64 归档和超过 1 GiB 的 ZIP 文件只会报成普通 ZIP,并附上说明。
  • 文本格式只根据前 8 KiB 判断,所以一个到后面才看得出是 HAR 的 JSON 文件会被报成 JSON。
  • 内嵌命中可能是巧合,尤其是在压缩数据里,任何字节组合都可能出现。每个扫描签名都要求至少 4 个字节或通过头部校验,这让误报很少,但不是不可能。
  • V8 启动快照(snapshot_blob.bin)没有稳定的签名,识别不了;能识别的只有 V8 代码缓存。
  • 字符串只包括可打印 ASCII(0x20–0x7e)的单字节串和 UTF-16LE 串,和 strings、strings -e l 找到的一样。

常见问题

为什么我的 .pyc 带着“预发布版”的备注?

前两个字节的魔数在每个 CPython 版本的开发周期里都会变。工具把整个区间都映射到对应版本,只要不是该版本的最终值就加上这条备注,意思是文件由 alpha、beta 或 rc 版本的解释器写出。

文件明明是 APK,结果却显示 ZIP。

APK 和其他 ZIP 文件的区别,是中央目录里有 AndroidManifest.xml,并且有 classes.dex 或 resources.arsc。如果中央目录读不出来(ZIP64、文件损坏或大于 1 GiB),结果就停留在 ZIP,并附上说明。

熵等于 8 能说明什么?

只能说明这些字节看起来是随机的。压缩数据、加密数据和随机密钥都会到这个值。固件镜像通常是混合的:bootloader 和填充部分熵很低,从压缩内核和根文件系统开始出现接近 8 的平台,而内嵌扫描一般也正是在这些位置找到它们的文件头。

和 file、binwalk 有什么区别?

file 根据文件开头判断格式,binwalk 扫描内嵌签名并负责提取。这个页面不用安装任何东西就能完成前两件事,还多了十六进制视图、熵图和字符串,但什么都不提取。签名表是我们自己整理的,覆盖范围和两者都不完全相同,上面的对比列出了结果不一致的地方。

合规

请只识别你自己生成的文件、开源软件、CTF 题目或你获得授权评估的文件。尊重所分析软件的许可证(包括其中限制逆向工程的条款)以及适用的法律。参见免责声明。

相关教程