解密QQ的MsgEx.db消息文件格式

您所在的位置:网站首页 查看qq消息记录 解密QQ的MsgEx.db消息文件格式

解密QQ的MsgEx.db消息文件格式

2023-03-23 19:03| 来源: 网络整理| 查看: 265

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[] m_MsgList = new List[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(@"Software/Tencent/QQ"))    {     QQPath = reg.GetValue("Install") as string;    }    if (QQPath == null) return ;   }   for (int i = 0; i < m_MsgList.Length; ++i)   {    m_MsgList = new List();   }   m_Storage = null;   m_Password = null;   m_Storage = new IStorageWrapper(QQPath + QQID + @"/MsgEx.db");   m_Password = QQMsgMgr.GetGlobalPass(m_Storage);   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)      {       m_MsgList.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 typeIndex = (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))      {       using (BinaryReader br = new BinaryReader(ms, Encoding.GetEncoding(936)))       { #if false        fs.Write(msgList, 0, msgList.Length); #else        int ticks = br.ReadInt32();        DateTime time = new DateTime(1970, 1, 1) + new TimeSpan(0, 0, ticks);        switch (type)        {        case QQMsgType.BIM:        case QQMsgType.C2C:        case QQMsgType.Mobile:         ms.Seek(1, SeekOrigin.Current);         break;        case QQMsgType.Group:         ms.Seek(8, SeekOrigin.Current);         break;        case QQMsgType.Sys:         ms.Seek(4, SeekOrigin.Current);         break;        case QQMsgType.TempSession:  //?         ms.Seek(9, SeekOrigin.Current);         break;        }        if (type == QQMsgType.TempSession)        {         int gLen = br.ReadInt32();         string groupName = encoding.GetString(br.ReadBytes(gLen));         if (groupName.Length > 0) sw.WriteLine("{0}", groupName);        }        int nLen = br.ReadInt32();        string id = encoding.GetString(br.ReadBytes(nLen));        sw.WriteLine("{0}: {1}", id, time.ToString());        int cLen = br.ReadInt32();        string msg = encoding.GetString(br.ReadBytes(cLen));        msg.Replace("/n", Environment.NewLine);        sw.WriteLine(msg);        sw.WriteLine(); #endif       }      }     }    }   }  }  public void OutputFileList()  {   if (m_Storage == null) return ;   Dictionary dic = new Dictionary();   foreach (IBaseStorageWrapper.FileObjects.FileObject fileObject in m_Storage.foCollection)   {    if (fileObject.FileType == 2 && fileObject.FileName == "Index.msj")    {     dic[fileObject.FilePath] = fileObject.Length / 4;    }   }   for (int i = 0; i < m_MsgList.Length; ++i)   {    Console.WriteLine("{0}", s_MsgName);    foreach (string ID in m_MsgList)    {     Console.WriteLine("/t{0}: {1}", ID, dic[s_MsgName + ID]);    }   }  }  private static IBaseStorageWrapper.FileObjects.FileObject GetStorageFileObject(IStorageWrapper iw, string path, string fileName)  {   foreach (IBaseStorageWrapper.FileObjects.FileObject fileObject in iw.foCollection)   {    if (fileObject.CanRead)    {     if (fileObject.FilePath == path && fileObject.FileName == fileName) return fileObject;    }   }   return null;  }  private static byte[] Decrypt(byte[] src, byte[] pass, long offset)  {   RedQ.QQCrypt decryptor = new RedQ.QQCrypt();   return decryptor.QQ_Decrypt(src, pass, offset);  }  private static IList < byte[] > DecryptMsg(IStorageWrapper iw, string path, byte[] pass)  {   List < byte[] > msgList = new List < byte[] > ();   int num = 0;   int[] pos = null;   int[] len = null;   using (IBaseStorageWrapper.FileObjects.FileObject fileObject = GetStorageFileObject(iw, path, "Index.msj"))   {    if (fileObject == null) return msgList;    int fileLen = (int)fileObject.Length;    num = fileLen / 4;    pos = new int[num + 1];    using (BinaryReader br = new BinaryReader(fileObject))    {     for (int i = 0; i < num; ++i)     {      pos = br.ReadInt32();     }    }   }   using (IBaseStorageWrapper.FileObjects.FileObject fileObject = GetStorageFileObject(iw, path, "Data.msj"))   {    if (fileObject != null)    {     int fileLen = (int)fileObject.Length;     len = new int[num];     pos[num] = fileLen;     for (int i = 0; i < num; ++i)     {      len = pos[i + 1] - pos;     }     using (BinaryReader br = new BinaryReader(fileObject))     {      for (int i = 0; i < num; ++i)      {       fileObject.Seek(pos, SeekOrigin.Begin);       byte[] data = br.ReadBytes(len);       byte[] msg = Decrypt(data, pass, 0);       msgList.Add(msg);      }     }    }   }   return msgList;  }  private static byte[] GetGlobalPass(IStorageWrapper iw)  {   System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create();   string QQID = "254614441";   byte[] dataID = new byte[QQID.Length];   for (int i = 0; i < QQID.Length; ++i) dataID = (byte)(QQID);   byte[] hashID = md5.ComputeHash(dataID);   IBaseStorageWrapper.FileObjects.FileObject fileObject = GetStorageFileObject(iw, "Matrix", "Matrix.db");   if (fileObject != null)   {    using (BinaryReader br = new BinaryReader(fileObject))    {     byte[] data = br.ReadBytes((int)fileObject.Length);     long len = data.Length;     if (len < 6 || data[0] != 0x51 || data[1] != 0x44) return null;     if (len >= 32768) return null;     bool bl = false;     int i = 6;     while (i < len)     {      bl = false;      byte type = data[i++];      if (i + 2 > len) break;      int len1 = data + data[i + 1] * 256;      byte xor1 = (byte)(data ^ data[i + 1]);      i += 2;      if (i + len1 > len) break;      for (int j = 0; j < len1; ++j) data[i + j] = (byte)(~(data[i + j] ^ xor1));      if (len1 == 3 && data == 0x43 && data[i + 1] == 0x52 && data[i + 2] == 0x4B)      {       bl = true;      }      i += len1;      if (type > 7) break;      if (i + 4 > len) break;      int len2 = data + data[i + 1] * 256 + data[i + 2] * 256 * 256 + data[i + 3] * 256 * 256 * 256;      byte xor2 = (byte)(data ^ data[i + 1]);      i += 4;      if (i + len2 > len) break;      if (type == 6 || type == 7)      {       for (int j = 0; j < len2; ++j) data[i + j] = (byte)(~(data[i + j] ^ xor2));      }      if (bl && len2 == 0x20)      {       byte[] dataT = new byte[len2];       for (int j = 0; j < len2; ++j) dataT[j] = data[i + j];       return Decrypt(dataT, hashID, 0);      }      i += len2;     }     if (i != len) return null;    }   }   return null;  } } } 利用这个类,你就可以方便的导出QQ中的历史消息了。 从上面的分析可以看到,查看本地的历史消息是不需要你的QQ密码的,加密密钥来源于你的QQ号码的MD5散列。所以为了保证安全,最好不要在公共电脑或者别人的电脑上使用QQ并记录历史消息。在个人电脑上,最好将历史消息加密。 本文纯属技术交流,不是为了破解别人消息的,大家慎用,高手可以跟帖讨论。原文:http: //blog.csdn.net/vbvan/archive/2007/12/14/1937440.aspx



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3