1 “色情”图片的消失
自从微信增加了消息提现功能之后,我的好奇心就增加了好几倍!
我们身边就有这么一个搞笑的小伙,就叫他胖子吧,他经常在朋友圈、微信群里说段子,大部分都是自嘲式的,配图也恰到好处,他的乐观、执着深深地感动了我们。
前不久他在微信群里发了一张聊天截图,肯定有什么好玩的,但很快就撤了,之后又反复发又撤,引发大家的好奇心,最后我终于通过其他渠道看到了那张图,治好了我毕业综合症,开心了好几天。
最近发生了一起“消息撤回事件”,主角热爱生活,有梦想,具体来说,他试图用JPEG保存自己的生活。是的,他到处拍照,即使没有风景也能玩得开心!他给我发了一张“性感”的照片,当我正要挑剔的时候,他撤回了!这是我的感受:rm -rf /*。
2 真相只有一个
微信的消息撤回功能虽然弥补了输入法不够智能的缺点,但却毁掉了生活中的一些乐趣。作为程序员的我实在是受不了了,非要把丢失的“情色”图片找出来。之前听说过框架里有微信防撤回插件,更加坚定了我的信心。
因为我正好在PC和手机上同时登录了微信,所以我先从PC开始。
2.1 个人电脑
一番查找后,我发现存储位置是[X]:\\[USER]\\ \[]\Data。里面有一堆后缀为.dat的文件,大小不一。最新的一个有100多KB。我猜这应该就是我要找的图片。
于是我把后缀改成.jpg试了一下,还是不行。果断在++上装了HEX-,秒变成功!为了搞清楚如何加密图片,我随便找了一张图片,发到微信,在微信存放图片的位置找到最新的.dat文件,++打开后是这样的(加密后的图片):
在微信客户端里右键另存为,将图片保存到本地++打开就是这样的(原图):
可以明显看到原图的63 63...变成了32 32....猜测是图片中每个字节都经过了变换,为了找规律,找到原图的00,对应加密后图片的51,所以大胆猜测是:`
def encode(b): return b + 0x51
但是 0x63 + 0x51 不等于 0x32,我们把十六进制数转为二进制看看:
0110 0011 ? 0101 0001 -------------- 0011 0010
两个 1 相加,是没有进位的,所以这不是加法,而是半加,也就是异或!赶紧写代码测试一下:
def _decode_pc_dat(self, datfile):
magic = 0x51
with open(datfile, 'rb') as f:
buf = bytearray(f.read())
imgfile = re.sub(r'.dat$', '.jpg', datfile)
with open(imgfile, 'wb') as f:
newbuf = bytearray(map(lambda b: b ^ magic, list(buf)))
f.write(str(newbuf))
成功找到“燕”的图片。
2.2 移动端()
通过逐个检查微信路径下的文件,我们最终找到了可疑文件:////。该路径下文件名类似.data.10,大小约2MB,如果将后缀改为.jpg,则可以打开。不过可疑之处是,一张2MB的图片马赛克密度这么高,最多只有10KB左右。而且每个文件都在2MB左右,人为干预的痕迹明显。
能打开图片说明文件内容没有被修改过,文件头文件脚都很正规,难道是微信工程师把多张图片关联到一起了?!像这样:
图片1的头部 图片1的尾部 图片2的头部 图片2的尾部 ...
由于 JPEG 文件以 ff d8 ff e0 开头,以 ff d9 结尾,因此搜索:
在一张图片的二进制中发现多个文件头,且在文件头之前有文件尾标记,与猜测一致。此时解密方法可以写成:
def _decode_android_dat(self, datfile):
with open(datfile, 'rb') as f:
buf = f.read()
last_index = 0
for i, m in enumerate(re.finditer(b'\xff\xd8\xff\xe0\x00\x10\x4a\x46', buf)):
if m.start() == 0:
continue
imgfile = '%s_%d.jpg' % (datfile, i)
with open(imgfile, 'wb') as f:
f.write(buf[last_index: m.start()])
last_index = m.start()
3 神奇的0x51
对于PC端的图片加密,工程师选择了一个魔法数字0x51来对每个字节进行异或,为什么选择0x51呢?
>>> chr(0x51)
'Q'
0x51对应的代码就是“QQ”中的Q!
最后给大家提供一个解密微信图片和找回被撤回图片的工具:。
參考