【原理简介】
- 首先,微信DAT文件是将图片数据的每个字节经过和密钥做异或运算来得到的。
- 异或运算法则:如果a、b两个值不相同,则异或结果为1。如果a、b两个值相同,异或结果为0。
- 异或运算有个可交换的特点,即:如果a和b异或运算后得到了c,那么在a、b、c三者中,任意两者进行异或运算会得到它们中的第三者。
基于上面三条来梳理一下工具编写的前提条件,就变成了下面表格描述的这样。问题变得简单了,我们只需要通过循环,将图片的文件头数据的第1、2字节与DAT数据的第1、2字节相应做异或运算,那么就可以得到两个值,如果这两个值相等,那么就是我们要找的密码。然后用得到的密码与DAT的所有字节做异或运算,便可以还原出原始的图片文件。
图片 | 密码 | DAT |
可有限枚举,已知文件头数据
jpg: [0xFF, 0xD8, 0xFF]
png: [0x89, 0x50, 0x4E]
gif: [0x47, 0x49, 0x46]
...
| 未知 | 已知 |
【工具编写】
基于上述原理,代码实现起来并不难。我用JS写了两个版本,一个Electron版本的,一个是Web版本的。相对而言,Electron打包后体积较大,对于一个功能单一的小工具来讲太鸡肋;而Web版本比较轻量,而且可以挂到网上,随时随地方便地使用,性价比更优。
如下图所示,工具的界面设计比较简单,Web版本只有一个主区域,可以将DAT文件拖拽进去还原出图片,因为文件头枚举数量很少,所以速度很快,基本是瞬间的;下方设置了两个按钮“全部删除”和“全部下载”,可以快速清空主体区域和下载所有还原出来的图片。
代码逻辑并不复杂,这里贴两处比较关键的代码片段(Web版),文后会附上Web版的下载地址,另外也做了个视频(两个版本的工具展示+代码解释),可以视自己口味选择下载和观看。
[JavaScript] 纯文本查看 复制代码 // 拖拽添加文件drop (e) {
const fileList = e.dataTransfer.files
if (!fileList) return
// 取文件后缀名,必须是dat文件
let taskList = []
let indexList = []
for (let i = 0; i < fileList.length; i++) {
const file = fileList[i]
if (!/.dat$/i.test(file.name)) {
return this.$toast('请选择DAT文件!')
}
let fStat = fs.statSync(file.path)
if (fStat.isDirectory()) {
return this.$toast('请选择DAT文件!')
}
taskList.push(this.getFileInfo(file.path))
indexList.push(i)
}
Promise.all(taskList).then((infos) => {
if (!infos || !infos.length) return
infos.forEach((v, i) => {
let file = fileList[indexList[i]]
this.filesList.push({
name: file.name, path: file.path, size: file.size, ...v
})
})
})
}
[JavaScript] 纯文本查看 复制代码 // 获取单个文件的信息(包括密码和数据解密)getFileInfo (filePath) {
let result = {name: '', type: '未知', password: '', data: '', url: ''}
let _arr = path.basename(filePath).split('.')
result.name = _arr.slice(0, _arr.length - 1).join('.')
return new Promise((resolve, reject) => {
if (!fs.existsSync(filePath)) {
reject('文件不存在')
}
fs.readFile(filePath, (err, data) => {
if (err || !data) reject('读取文件错误')
result.data = data
for (let key in this.fileHeaderMarks) {
let val = this.fileHeaderMarks[key] || []
let pwd1 = (val[0] || 0) ^ data[0]
let pwd2 = (val[1] || 0) ^ data[1]
if (pwd1 === pwd2) {
result.type = key
result.password = pwd1
break
}
}
if (result.password) {
let mimeType = this.fileMimeType[result.type] || `image/${result.type}`
result.data = result.data.map(v => (v ^ result.password))
result.url = `data:${mimeType};base64,${result.data.toString('base64')}`
}
resolve(result)
})
})
}
视频展示:https://www.bilibili.com/video/BV1f841187tm/
Web版工具源码:https://pan.baidu.com/s/1_KIqd0h_3T2vcR87GlUJYg?pwd=3m4h 提取码: 3m4h |