Python查看Android QQ本地消息记录数据库

Python查看Android QQ本地消息记录数据库

需求

  • 随着电子产品的更新换代,每隔一段时间我们就会更换手中的电子设备,早期版本的QQ不支持备份聊天记录,Android 版本的不同也给QQ数据迁移带来了一定的麻烦。能不能通过提取QQ的数据文件来获取到以往设备中的消息记录?

调研

  • Android QQ的聊天记录存储于/data/data/com.tencent.mobileqq/databases目录下,其中QQ号.db文件即为该QQ号的聊天记录数据库,获得该文件即有机会调取出相应的聊天记录。

  • 本文仅适用于 Android 设备。

  • 本文仅用于技术研究和学习之用,切勿用于非法用途。

前期准备

  • 数据库获取

    首先需要获得上面提到的 QQ号.db 文件。由于该文件位于/data分区下,Android 默认不可读写。如果手机已root可直接复制到电脑上。如果手机没有root并且不想root,现在的系统大多提供了数据备份与恢复这一功能,我们可以使用这一工具备份整个QQ的数据,然后将压缩包导出至电脑上,提取出其中的QQ号.db文件。

    直接打开这个 .db 文件是不是就可以看到消息记录了呢?如果有那么简单自然就不会有这篇文章了。

    我们使用 SQLite Expert 打开这个数据库,可以看到,这个 database 包含很多张 table ,其中有许多名为mr_friend_***mr_troop_***的数据表。查看这些 table ,我们可以发现里面存有诸如 msgId、msgUid、msgData、msgtype、senderuin、selfuin、time 等字段。通过这些字段以及其中的内容,我们基本可以确定,这就是 QQ 存放消息记录的数据表。表名mr_friend_***应当代表与某好友的聊天记录,mr_troop_***应当代表某QQ群的聊天记录。

  • 数据表分析

    mr_friend_***_New为例,此类数据表一共26个字段,其中extStrfrienduinselfuinsenderuin均为 TEXT 文本类型,msgData为 BLOB 二进制类型,且以上5个字段内容均为乱码,猜测应当是被加密了。其余21个字段均为 INTEGER 类型,无乱码未加密。现在要做的就是解密这5个加密字段的内容,重点是msgData字段。

  • 数据表解密

    互联网是很发达的,在本文之前很多年早已有人破解出了密钥,在此就不用多费时间去亲自推敲了:表名mr_friend_***_New中的***为QQ号/群号的32位大写MD5值,表中用于加密5字段的密钥为本设备的IMEI号,将加密数据与该IMEI号进行逐位异或即可解密。

    如果手机背面的进网许可标签没有撕掉,直接把手机翻过来就能看到IMEI号(有些设备贴在电池内侧);也可以直接在手机里查询,打开拨号盘,输入*#06#,会显示14位长度的MEID和15位长度的IMEI,如果手机有两个卡槽则还有第2个15位的IMEI。

代码实现

  • 仅查询

    import sqlite3
    import time
    import hashlib
    
    IMEID = '****************' # 可以是15位IMEI也可以是14位MEID
    
    conn = sqlite3.connect('*********.db') # QQ号
    c = conn.cursor()
    
    cu = conn.cursor()
    
    # 获取表名,保存在tab_name列表
    cu.execute("select name from sqlite_master where type='table'")
    tab_name = cu.fetchall()
    tab_name = [line[0] for line in tab_name]
    
    num = input("请输入QQ号:")
    hl = hashlib.md5()
    hl.update(num.encode(encoding='utf-8'))
    
    for line in tab_name:
      if "mr_friend_"+hl.hexdigest().upper() in line: # 查找对应QQ号的聊天记录,如果是群聊记录则需将mr_friend改成mr_troop
        cursor = c.execute('SELECT senderuin,time,msgData FROM '+line+' WHERE msgtype=-1000;') # msgtype=-1000代表文本类型
    
        def decrypt_msg(encrypted_msg): # 解密单条数据
          if type(encrypted_msg) is bytes:
            "" # bytes类型无需转码
          elif type(encrypted_msg) is int:
            if 1000000000<encrypted_msg<10000000000:
              timeArray = time.localtime(encrypted_msg)
              encrypted_msg = time.strftime("%Y-%m-%d %H:%M:%S", timeArray) # 时间戳需要转换为标准时间
            return encrypted_msg
          else:
            encrypted_msg = bytes(encrypted_msg, encoding = "utf8")  
          msg = []
          for i in range(len(encrypted_msg)):
            msg.append(encrypted_msg[i] ^ IMEID[i%15].encode()[0]) # 如果前面用的14位MEID此处也对应换成i%14
          return bytes(msg).decode()
    
        for row in cursor:
          decrypted_msgs = []
          for item in row:
            decrypted_msgs.append(decrypt_msg(item))
          print(decrypted_msgs)
    

注意事项

  • 若仅查询消息记录筛选senderuintimemsgData三个字段即可。

  • 若单独通过mr_troop_***查询群聊记录,无法查出群成员昵称,需结合表 TroopMemberCardInfo/TroopMemberInfo 进行显示。

  • 若仅查询QQ聊天记录中的图片、视频、语音等文件,直接检索内部存储 /sdcard/tencent/MobileQQ 文件夹即可。

  • 若发现一些较早的聊天记录缺失,则需结合 slowtable_QQ号.db 进行查询,QQ默认将超出一定数量限制的聊天记录转存至该文件中。

  • 在撰写完本文后偶然发现一位大佬编写的源码与GUI,还比较全面,一并分享于此,供大家研究参考:怎样导出手机中的QQ聊天记录?

  • 本文所述方法理论上可用于查看曾在本机登录过的他人消息记录,若如此,请务必征得其本人同意。

  • 本文仅用于技术研究和学习之用,切勿用于非法用途。

参考资料

posted @ 2021-02-18 13:35  Lolipop  阅读(84)  评论(0编辑  收藏  举报