简介

这个项目初步完成于2020.07.26。一开始是想找个方法导出自己的QQ聊天记录,看到了roadwide/qqmessageoutputYiyiyimu/QQ_History_Backup两个项目,于是拿来用,却发现:1、导出内容较多时,网页根本不适用;2、QQ昵称中含有表情时,解密不出来。于是fork了roadwide/qqmessageoutput,自己改改,得到了一个带GUI版本的聊天记录。

下载和使用不再赘述,请参见 https://github.com/ctem049/qqmessageoutput

QQ聊天db文件分析

QQ聊天数据储存位置为:

data\data\com.tencent.mobileqq\databases\QQ号.db
data\data\com.tencent.mobileqq\databases\slowtable_QQ号.db

root过的设备可以复制出来,使用SQLite工具(例如Navicat)打开,可以看到:

qqm_1.png

数据表含义

其中比较重要的几个表:

  1. Friends表记录了好友信息,uin为QQ号,name为QQ名字,remark为备注,还有age、gender等等。
  2. TroopInfoV2表记录了加入的群聊,troopuin为群号,troopname为群名,trooponweruin为群主QQ号等。
  3. TroopMemberInfo表记录了群聊里的成员信息,troopuin为群号,memberuin为qq号,troopnick为群名片,friendnick为qq名,join_time为入群时间。
  4. mr_friend_XXX_Newmr_troop_XXX_New表即为QQ好友或群聊的聊天记录表,XXX处实际为QQ号或群号的md5。众所周知md5是单向的hash函数,所以先得知道好友/群聊的号码,再查询表访问数据。但实际上如果没有事先得到QQ号/群号表,也可以通过www.cmd5.com直接逆向破解,因为QQ号纯数字又比较短,基本上都是免费破解的。
    聊天记录表重要的列:senderuin为发送者QQ,time为发送的时间(10位时间戳,精确到秒), msgData为内容, selfuin为自己的QQ, frienduin为好友的QQ或群号。通过判断selfuin==senderuin即可知道是不是自己发的消息。

数据解密分析

直接使用SQLite工具打开数据库可以发现,无论是QQ号(uin字段)还是聊天内容(msgData字段)都是乱码,根据前人的经验,它是采用简单的异或加密,密钥为imei或kc文件,位置:

data\data\com.tencent.mobileqq\files\imei
data\data\com.tencent.mobileqq\files\kc

打开imei文件可以看到imei=XXXXXXXX,XXXXXXXX即为密钥,密钥是循环补位的。对于不同的数据,解密方法也有所不同:

按unicode字符解密

对于uin、昵称等信息,读取出来的数据为str(字符串)类型,例如
0x01 06 03 0D 02 09 09 06 03 03读取出来为\x01\x06\x03\r\x02\t\t\x06\x03\x03,需要读取它的Unicode码0x01 06 03 0D 02 09 09 06 03 03
密码为3524300727,Unicode码为0x33 35 32 34 33 30 30 37 32 37
计算0x01 06 03 0D 02 09 09 06 03 03 XOR 0x33 35 32 34 33 30 30 37 32 37
0x32 33 31 39 31 39 39 31 31 34,解码得2319199114
对于中文字符,每一个中文字符只消耗一个字符的密钥,例如(Unicode码0x5E 41)与0x35异或得0x5E 74),实际python程序一个字符一个字符来就行。

这种解密方式看似是对的,但是遇到emoji字符时,就出现了问题,如果QQ昵称中有emoji,解出来是乱码。
比如我们得到昵称为0x01 3F 74如果按照上述解密方法,XOR 0x33 35 32得到0x32 0A 46,解不出🍁(0x01 F3 41)。
经过逆运算0x01 3F 74 XOR 0x01 F3 410xCC 35,发现是由0x33 35将33左移两位得到的:
0x33 35= 0011 0011 0011 0101
0xCC 35= 1100 1100 0011 0101
因此对于>0xFF FF的字符,需要用密钥的两个字节解密,其他的字符用一个密钥解密即可。

按字节解密

对于消息内容,读取出来为bytes(字节码)类型,解密很简单,按字节顺序与密钥异或即可。
例如0xD7 8A 9D D7 9C A6与密钥0x32 34 33 30 30 37异或得0xE5 BE AE E7 AC 91,utf-8解码即得微笑

破解分析

参照上面的一些信息,实际上在没有密钥的情况下,也是可以解出密钥的,假设密钥为15位imei,则密钥为15字节,利用前面的md5逆向,可以解出mr_friend_XXX_New中好友的QQ号,利用表中的frienduin,假设QQ为10位则可以推出前10个字节的密钥,且imei最后一位为校验位,只剩4位需要破解,仅剩10000种可能,可以暴力破解,也可结合一些消息内容推算出来。

参考

安卓QQ聊天记录导出、备份完全攻略 https://www.cnblogs.com/roadwide/p/11220211.html