关于QQ聊天记录文件格式的分析(1)

网络杂谈 CN-P5 2155℃ 0评论

QQ的消息实际上是存放在本地的,位于"QQ安装目录QQ号码MsgEx.db"内。关于QQ消息文件格式的文章,网上有不少,但是没有一篇是完整并且可重现。结合QQ聊天记录察看器 5.1,我做了一些研究,重现了读取并显示历史消息的完整过程。

一个很好的学习QQ相关算法的实例,是它的Linux版本LumaQQ

首先,MsgEx.db文件的大致结构可以参考QQ聊天记录查看器 5.3 华军版
IStorage的详细介绍可以在MSDN中查到,CHM就是使用了这个格式。为了方便的操作这个COM接口,我们可以直接使用Decompiling CHM (help) files with C#中提供的RelatedObjects.Storage.dll

消息的加密密码存放在Matrix.db中,提取出来之后就可以解密实际存放消息文本的Data.msj文件了
(值得注意的是,QQ使用的数据加密算法并不是上面帖子里提到的Blowfish,而是TEA算法,可以参考QQ的TEA填充算法C#实现

QQ分若干种消息类型,诸如双人消息、群消息和系统公告等,格式有一些差异。

具体的细节,看看代码就清楚了。一个简单的QQ消息类的实现如下:

namespace Van.Utility.QQMsg
{
    public enum QQMsgType
     {
         BIM, C2C, Group, Sys, Mobile, TempSession //Disc
     }

    class QQMsgMgr
     {
        private static readonly int s_MsgTypeNum = (int)QQMsgType.TempSession + 1;
        private static readonly string[] s_MsgName = new string[] {
            "BIMMsg", "C2CMsg", "GroupMsg", "SysMsg", "MobileMsg", "TempSessionMsg"
         };
        private IStorageWrapper m_Storage;
        private byte[] m_Password;

        private List<string>[] m_MsgList = new List<string>[s_MsgTypeNum];

        public void Open(string QQID)
         {
             Open(QQID, null);
         }
        public void Open(string QQID, string QQPath)
         {
            if (QQPath == null)
             {
                using (Microsoft.Win32.RegistryKey reg = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SoftwareTencentQQ"))
                 {
                     QQPath = reg.GetValue("Install") as string;
                 }
                if (QQPath == null) return;
             }

            for (int i = 0; i < m_MsgList.Length; ++i)
             {
                 m_MsgList[i] = new List<string>();
             }
             m_Storage = null;
             m_Password = null;
            m_Storage = new IStorageWrapper(QQPath + QQID + @"MsgEx.db");
             m_Password = QQMsgMgr.GetGlobalPass(m_Storage, QQID);

            if (m_Password == null) m_Storage = null;

            foreach (IBaseStorageWrapper.FileObjects.FileObject fileObject in m_Storage.foCollection)
             {
                if (fileObject.FileType == 1)
                 {
                    for (int i = 0; i < m_MsgList.Length; ++i)
                     {
                        if (fileObject.FilePath == s_MsgName[i])
                         {
                             m_MsgList[i].Add(fileObject.FileName);
                         }
                     }
                 }
             }
         }

        public void OutputMsg()
         {
            for (int i = 0; i < s_MsgTypeNum; ++i)
             {
                 OutputMsg((QQMsgType)i);
             }
         }
        public void OutputMsg(QQMsgType type)
         {
            if (m_Storage == null) return;
            if (m_Password == null) return;

            int typeI
ndex = (int)type;
            if (typeIndex < 0 || typeIndex >= s_MsgTypeNum)
             {
                throw new ArgumentException("Invalid QQMsgType", "type");
             }

            string filePath = s_MsgName[typeIndex] + "";
            Directory.CreateDirectory(filePath);

            foreach (string QQID in m_MsgList[typeIndex])
             {
                string fileName = filePath + QQID + ".msj";
                 OutputMsg(type, QQID, fileName);
             }
         }
        public void OutputMsg(QQMsgType type, string QQID)
         {
            if (m_Storage == null) return;
            if (m_Password == null) return;

            int typeIndex = (int)type;
            if (typeIndex < 0 || typeIndex >= s_MsgTypeNum)
             {
                throw new ArgumentException("Invalid QQMsgType", "type");
             }
            string filePath = s_MsgName[typeIndex] + "";
            Directory.CreateDirectory(filePath);

            string fileName = filePath + QQID + ".msj";
             OutputMsg(type, QQID, fileName);
         }

        private void OutputMsg(QQMsgType type, string QQID, string fileName)
         {
            string msgPath = s_MsgName[(int)type] + QQID;
            IList<byte[]> msgList = QQMsgMgr.DecryptMsg(m_Storage, msgPath, m_Password);
            Encoding encoding = Encoding.GetEncoding(936);

            using (FileStream fs = new FileStream(fileName, FileMode.Create))
             {
                using (StreamWriter sw = new StreamWriter(fs))
                 {
                    for (int i = 0; i < msgList.Count; ++i)
                     {
                        using (MemoryStream ms = new MemoryStream(msgList[i]))
                         {
                            using (BinaryReader br = new BinaryReader(ms, Encoding.GetEncoding(936)))
                             {
#if false
                                 fs.Write(msgList[i], 0, msgList[i].Length);
#else
                                                                                          ———-来源:http://bbs.eyuyan.com/dispbbs.asp?boardid=124&Id=163452

转载请注明:黑白的自留地 » 关于QQ聊天记录文件格式的分析(1)

喜欢 (0)
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
(4)个小伙伴在吐槽
  1. 麻烦问一下,怎么恢复聊天记录啊?谢谢~~~
    ESTHERQ2008-10-11 23:48 Reply
  2. 我也想知道啊 有没有简单明了的办法 别太复杂的 谢谢
    夏松じ☆ve2009-06-13 16:48 Reply
  3. 只要你备份了“QQ安装目录你的QQ号码MsgEx.db”,下次上qq把这个文件复制到新机子的“QQ安装目录你的QQ号码MsgEx.db”就可以保存聊天记录。如果你没有备份这个文件就没法恢复了。
    rzsky2009-06-20 16:57 Reply
  4. 我的QQ聊天记录文件貌似是msg2.0.db
    凌雪飘香12011-10-11 15:13 Reply