找寻微信撤回的图片

找寻微信撤回的图片

zhangxiaoyangzhangxiaoyang

(欢迎关注 微信订阅号 AND 知乎专栏:Yang的后花园)

1 消失的“艳”图

自从微信添加撤回消息功能后,我的好奇心翻了好几番啊!

我们身边有一个逗比,暂且称他为胖胖,经常在朋友圈和微信群里说单口相声,内容以自黑为主,配图恰到好处。他乐观、奉献的精神深深的打动了我们。

前不久他在微信群里放出了一张聊天截图,肯定有料,但奈何他迅速撤回了。然后他在发送和撤回之间迭代,大家的好奇心被撩的不要不要的。最终通过其他渠道终于看到了那张图,治好了我的毕业综合症,心情美丽了好几天。

最近还发生了一起“消息撤回事件”,主人公爱生活,有梦想。具体来说是,力争把生活存成JPEG。对,无处不拍照,无景也能high!他丢过来一张“艳”图,当我准备鸡蛋里挑骨头的时候,他撤回了!心情是这样纸滴:rm -rf /*。

2 真相只有一个

微信的消息撤回功能算是治疗输入法不够智能的一剂良药,但是却毁了生活中的一些小乐子啊。作为一名程序员,我忍无可忍了,柯南附体,必须找到那张消失的“艳”图。之前有听说Xposed框架里的防止微信撤回插件,更加坚定了我的信心。

因为当时刚好PC和手机同时登陆了微信,所以先从PC这里入手。

2.1 PC端

经过一番调研,把储存的位置锁定到了[X]:\Users\[USER]\Documents\WeChat Files\[WECHAT_USER]\Data。里面有一堆以.dat为后缀的文件,他们大小不一,其中最新的一个文件100多KB,我猜这应该就是要找的图片。

于是把后缀改为.jpg试试看,结果失败。果断给Notepad++装上HEX-Editor插件,秒变UltraEdit啊!为了找到图片的加密方法,随便找了一张图,发送过来,并到微信储存图片的位置找到最新的.dat文件,Notepad++打开是这样的(加密后的图片):

在微信的客户端里右键另存为,把图片储存到本地,Notepad++打开是这样的(原始图片):

可以很明显的看到,原始图片的63 63 ...变成了32 32 ...。猜测图片中的每一个字节经都经过了变换。为了方便寻找规律,找到原始图片的00,其对应加密后的图片的51,于是大胆猜测变换的方法是:`

def encode(b):
    return b + 0x51

但是,0x63 + 0x51并不等于0x32。把16进制换成2进制再看一下:

   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 手机端(Android)

通过对微信路径里的文件逐一排查,最终找到了可疑的文件:/sdcard/tencent/MicroMsg/diskcache。该路径里的文件名称类似cache.data.10,大小在2MB左右。如果把后缀改为.jpg,是可以打开的。但是,疑点在于,2MB的图片,竟然马赛克浓度这么高,这充其量也就是才10几KB吧。而且,每一个文件都是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对应的ASCII码为“QQ”的Q!

最后献上微信图片解密、找回撤回的图片工具: github.com/zhangxiaoyan

参考

「您的支持是我持续写作的动力!」
2 人赞赏
萧骁
Ranchosky
文章被以下专栏收录
92 条评论
推荐阅读