
简介这是一款专为Android平台开发的dat文件批量转换工具面向逆向分析人员、微信数据恢复从业者及移动安全研究者解决微信缓存.dat图片无法直接查看的核心痛点。工具支持将dat文件无损还原为jpg、png、gif等常见图像格式并扩展支持pdf、mp4、docx、zip、jar、java等49类文档与可执行文件采用通用字节流识别与魔数匹配算法具备较强泛用性。压缩包共1395个文件含496个flat资源文件、194个dex字节码、191个class类文件、155个jar依赖库及大量xml配置与json元数据完整涵盖APK安装包、Gradle构建脚本、Java源码与assets资源包体大小13.32MB结构清晰便于二次开发与算法调优。已有1679人学习下载用户可直接部署APK快速处理缓存文件亦可深入源码理解dat封装逻辑、复用核心转换模块或适配其他App缓存格式。 dat 转 img 这个小工具起因是我在手机文件管理器里第一次看到一堆.dat文件时的困惑。目录在Android/data/com.tencent.mm/下面几百个十六进制文件名后缀全是.dat系统看图软件打不开第三方工具也提示“文件格式不支持”。后来才知道这些不是坏文件而是被异或加密过的图片缓存。这个背景下我做了一个 Android 端的 dat 转 img 小工具包含可直接安装的 APK以及完整的 Android 源码。它能在手机上直接扫描目录里的.dat文件自动识别加密密钥批量转换并导出为 jpg / png 图片到相册。这篇文章我会把加密原理、密钥探测算法、APK 工程结构、实机踩坑过程都拆开讲清楚。不管你是想恢复自己手机里的图片缓存还是打算自己写一个类似的批量文件转换工具都可以拿这篇文章当参考。特别说明一下这里的 img 指的就是 image 图片格式不是智能盒子或路由器刷机用的固件镜像.img两者只是同名场景完全不同别弄混了。1. dat文件的真实身份它不坏只是被“锁”了一层1.1 微信为什么要把图片缓存成.dat微信的本地图片缓存并不是以.jpg或.png直接存储的而是以.dat后缀的加密文件存在。路径一般在/storage/emulated/0/Android/data/com.tencent.mm/MicroMsg/下的某个长字符串目录中不同版本可能略有差异部分旧版本在/storage/emulated/0/Tencent/MicroMsg/下。你只要用文件管理器一搜就能看到大量这样的文件。微信这样做的目的我猜测有两层一是防止其他应用直接扫描到原始图片避免隐私泄漏二是把图片文件头打乱之后即使手机拿去维修一般用户也无法直接提取完整图片算是一种轻量级的“防君子不防小人”措施。注意这里不是强加密它用的是非常简单的异或变换不是为了对抗安全分析只是避免“普通用户拿根数据线就能把聊天图片翻出来”。从技术角度看微信的.dat文件保持了和原图片完全相同的文件体积。异或操作只是逐字节修改内容不增加任何校验信息、不打包、不压缩所以文件大小和原来的图片一模一样。这一点也成了后续判断“这个文件是不是图片缓存”的线索之一。1.2 异或加密的基本模型异或XOR是一种二进制位运算规则就一句话相同为0不同为1。文件加密场景下最常见的用法是用一个固定字节 key对原文件的每个字节做original[i] XOR key得到加密字节stored[i]。解密就更简单了对加密字节再做一次同样的异或stored[i] XOR key就还原成original[i]。为什么微信这种亿级用户的应用会选这么简单的加密因为速度快、体积零开销、CPU 开销几乎可以忽略。移动设备上每天产生的图片缓存是海量的用 AES 之类的对称加密虽然安全性高但单张图片加密耗时会增加几十毫秒且需要管理密钥。用单字节异或本质上就是一个常数级的位变换微信缓存图片时几乎无感知。用一个生活类比这就像一本日记每个汉字都被替换成“按某个固定偏移量往后移动 n 位”的字。外人看到满篇乱码但只要知道偏移量 n或者能通过几个常见词反推出 n整本日记就能完整读出来。dat 文件就是那本“被换过字”的日记密钥就是那个偏移量。1.3 为什么看图软件打不开它所有看图软件、系统相册识别图片格式靠的不是扩展名而是文件头“魔数”。JPEG 文件的前两个字节固定是0xFF 0xD8PNG 的前八个字节固定是0x89 0x50 0x4E 0x47GIF 前六个字节固定是0x47 0x49 0x46 0x38。解析器在读文件时会先读取这些魔数匹配到对应格式才按该格式解码。微信的.dat文件因为每个字节都被异或了文件头的魔数位置变成了完全不同的值。看图软件读到陌生的文件头直接判定“格式不支持”于是就有了你看到的那句冷冰冰的提示。实际内容本身没有损坏一旦按正确的密钥还原图片依然能打开连 EXIF 信息都还在。把这个逻辑理清楚之后整个工具的核心就浮出水面了拿到 dat 文件找到那个被使用的单字节密钥逐字节异或还原再按正确的图片格式写出文件。听起来简单但落到 Android 平台上有一堆细节值得展开。2. 密钥探测算法让文件头自己“报出”密钥2.1 常见图片格式的魔数签名首先需要建立一张“签名对照表”。我们要探测的就是这个文件异或前的真实格式是什么。常见图片格式的头部字节如下格式头部魔数十六进制对应的 ASCII 文本JPEGFF D8 FF E0 或 FF D8 FF E1\xFF\xD8\xFF\xE?PNG89 50 4E 47 0D 0A 1A 0A\x89PNG\r\n\x1a\nGIF47 49 46 38 39 61GIF89aBMP42 4DBM这里每一行头部第一个字节和第二个字节是关键。因为单字节异或变换是逐字节的原文件头的第 N 字节异或 key 之后就得到 dat 文件第 N 字节。也就是说对于加密后的文件存在这样的关系dat[0] 0xFF XOR key如果原图是 JPEGdat[1] 0xD8 XOR key如果原图是 JPEG。2.2 从两个字节反推单字节密钥我们可以反过来算假设原图是 JPEG那么key dat[0] XOR 0xFF同时key dat[1] XOR 0xD8。如果这两个 key 相同那基本可以认定原格式是 JPEG。如果不相同就换 PNG、GIF、BMP 的头部签名再试。注意这里不需要暴力遍历 0 到 255 的全部 key。只要拿第一个字节和一个已知魔数异或就能直接算出一个候选 key然后用文件头后面几个字节来验证这个候选 key 是否正确。整个探测过程只做几次异或运算时间复杂度是 O(1)。2.3 校验条件怎么避免“假阳性”只靠前两个字节来判断是远远不够的。一些非图片文件、甚至随机数据前两个字节也可能碰巧满足某一种签名条件。我在第一版工具里只校验了两个字节结果把几个聊天语音文件误判成图片转换出来全是花屏。改进思路很简单增加校验长度。JPEG 的头部前 4 个字节通常为FF D8 FF E0或FF D8 FF E1那就要用这 4 个字节去同时匹配同一个 keyPNG 直接校验前 8 字节89 50 4E 47 0D 0A 1A 0AGIF 校验前 6 字节47 49 46 38 39 61。这样误判概率会指数级下降。2.4 核心探测代码我用的完整探测代码大致是这样Kotlinobject DatDecoder { private data class Signature(val ext: String, val header: ByteArray) private val signatures listOf( Signature(jpg, byteArrayOf(0xFF.toByte(), 0xD8.toByte(), 0xFF.toByte(), 0xE0.toByte())), Signature(jpg, byteArrayOf(0xFF.toByte(), 0xD8.toByte(), 0xFF.toByte(), 0xE1.toByte())), Signature(png, byteArrayOf(0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A)), Signature(gif, byteArrayOf(0x47, 0x49, 0x46, 0x38, 0x39, 0x61)) ) fun detect(input: File): Result? { if (input.length() 16) return null val head ByteArray(8) FileInputStream(input).use { it.readFully(head) } for (sig in signatures) { var key (head[0].toInt() xor sig.header[0].toInt()) and 0xFF var matched true for (i in sig.header.indices) { val k (head[i].toInt() xor sig.header[i].toInt()) and 0xFF if (k ! key) { matched false break } } if (matched) return Result(sig.ext, key) } return null } }这段代码最关键的一点是key 一旦确定后面整段流程都是一样的所以探测只做一次。探测通过之后解码过程就是纯粹的流式异或性能上有保障。3. 工具的整体设计从“能跑”到“好用”3.1 功能边界做减法这个工具的核心需求非常明确手机端扫描某些目录下的.dat文件识别出真正是图片的转换后导出到相册。为了不做成一锅粥我第一版限定了以下功能边界只处理.dat后缀文件其他文件一概不碰。只识别 jpg、png、gif 三种常见图片格式其他类型提示“无法识别”。转换结果输出到系统相册的Pictures/DatConverter目录不直接删除原文件。支持单文件转换和目录批量转换。转换前提供缩略图预览避免“全部转完才发现都是垃圾数据”。这个功能列表看起来简单但实际开发时每一条都对应一些系统适配问题后面会展开。3.2 技术选型为什么不用第三方库我用的是原生 Kotlin Android 官方库没有引入图片加载框架、网络库、数据库。原因有三第一这个工具不需要联网完全不涉及任何服务器第二图片解码和转换自己写并不难异或解码几十行代码就搞定引入 Glide 反而会增大 APK 体积第三这类小工具的生命周期可能很长依赖越少以后维护越省心。界面方面用了最简单的 RecyclerView 列表加一个底部按钮UI 层不做花哨设计。整个工程只有一个 Activity、一个扫描逻辑模块、一个解码核心类。真机实测APK 体积可以控制在 3MB 以内。3.3 模块划分代码结构分成四个部分职责非常清晰Scanner负责目录遍历拿到所有.dat文件列表。Decoder负责密钥探测和异或还原是整个工具的技术核心。Exporter负责把解码后的字节流写入相册或指定目录处理 Android 不同版本的存储权限。MainActivity负责界面展示、进度更新、结果统计和错误提示。为什么这样拆因为 Android 上最常变的其实是“输出”这一层。Scanner 和 Decoder 相对稳定Exporter 却要面对 Android 6、Android 10、Android 11 三套不同的权限行为本文还有配套的精品资源点击获取