腾讯QQ是中国网民使用最多的聊天工具,为保护网民个人隐私不被外泄,腾讯公司的技术团队对用户的聊天记录msg2.0.db文件(2009年以后版本的QQ)做了非常严密的密码保护功能。
网上提供的方法也对新的版本没有效果:
------------------------------------------------------------------
Msg2.0.db 破解步骤(研究学习用途)-Alex
2010-01-11 12:00
步骤简短,看不懂的,本人不提供支持。本人原创。纯研究,用于黑客或商业用途,请别联系我。----Alex
msg2.0.db,这个东西真麻烦,不象之前的msgex那么容易破解。下面简短陈述。看的懂得拿走,看不懂的自己想。
如果你连msg2.0.db是什么都不知道,那就别往下看了。你肯定看不懂。更浪费时间。
1.需要:7-zip,DBcompresser或者Paradox (Paradox用起来麻烦需要library,推荐DBC)
2 用7—zip释放你需要破解得msg2.0.db。 得到buddy,discuss,group,mobile,system这5个文件夹。以及lastmsginfo.dat , Matrix.dat,seqbase.dat 这3个加密的dat文件。
3 需要你自己的msg2.0 并且释放。得到buddy,discuss,group,mobile,system这5个文件夹。以及lastmsginfo.dat , Matrix.dat,seqbase.dat 这3个加密的dat文件。注:buddy是重点,其余的文件夹缺失,没有关系。
4 打开需破解的buddy文件夹,里面就是很多qq号码组成的文件夹。再任意打开其中一个看到content.dat,index.dat,info.dat 三个dat文件。 将自己的info.dat 复制到需破解buddy文件夹下。根据号码复制自己需要的。当然,你也可以全部复制。
5 把7-zip 释放的3个你自己的dat文件,既lastmsginfo.dat , Matrix.dat,seqbase.dat 覆盖到需破解的msq2.0文件夹中。(却一不可)
6 用DBcompresse组合文件,重命名为msg2.0.db,默认储存路径为C:\gainover 。放入user\XXXX里面。(DOS可以直接combine,这个我没研究,看到有人研究过)
7 用复制合成的msg2.0 登陆自己的号码,就能直接看到其中记录。目标人一般在你的黑名单或已删除联系人中。
8 破解完毕。享受吧。该哭的哭,该笑的笑。
如果你懂java.可以直接破解dat,算法已有。还有,别以为这些buddy里面的dat很容易破解。Passware Kit 没有破出来,想破壳的人...绕道吧...
感谢朋友DiLi,提供 qq2009 msg2.0.db VB结构。
以下是msq2.0.db 分析图。如果需要查看内容的话,用IStorage::OpenStream函数就可以打开,这里不多说。
// ClassTest.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "objidl.h"
#include "comdef.h"
void PrintDB(IEnumSTATSTG *pEnum,IStorage *pStore,int Depth)
{
STATSTG statstg;
while(NOERROR == pEnum->Next(1,&statstg,NULL))
{
for(int i=0;i<Depth;i++)printf("\t|");
wprintf(_T("---%s\n"),statstg.pwcsName);
if(statstg.type == STGTY_STORAGE)
{
IStorage *pStore1=NULL;
HRESULT hr;
hr=pStore->OpenStorage(statstg.pwcsName,NULL,
STGM_READ|STGM_DIRECT|STGM_SHARE_EXCLUSIVE,
NULL,
0,&pStore1);
if(hr != S_OK || pStore1==NULL)
{
wprintf(_T("open %s error\n"),statstg.pwcsName);
continue;
}
IEnumSTATSTG *pEnum1=NULL;
pStore1->EnumElements(0,NULL,0,&pEnum1);
PrintDB(pEnum1,pStore1,Depth+1);
pEnum1->Release();
pStore1->Release();
}
}
}
int _tmain(int argc, _TCHAR* argv[])
{
wchar_t szFileName[]=_T("E:\\下载保存\\XXXX\\msg2.0.db"//换成你的QQ);
::CoInitialize(NULL);
IStorage *pStore=NULL;
IStream *pStream=NULL;
HRESULT hr=E_FAIL;
hr=::StgOpenStorage(_bstr_t(szFileName),NULL,STGM_READ|STGM_DIRECT|STGM_SHARE_EXCLUSIVE,NULL,0,&pStore);
if(hr != S_OK || pStore==NULL)
{
printf("open failed\n");
return 0;
}
IEnumSTATSTG *pEnum=NULL;
hr = pStore->EnumElements(0,NULL,0,&pEnum );
if(hr != S_OK || pStore==NULL)
{
printf("enumerate failed\n");
return 0;
}
PrintDB(pEnum,pStore,0);
pEnum-> Release();
pStore->Release();
return 0;
}
输出结果
---buddy
|---8362xx
| |---info.dat
| |---index.dat
| |---content.dat
|---169329xx
| |---info.dat
| |---index.dat
| |---content.dat
---group
|---2048838xx
| |---info.dat
| |---index.dat
| |---content.dat
|---2049667xx
| |---info.dat
| |---index.dat
| |---content.dat
---mobile
---system
|---1
| |---index.dat
| |---content.dat
|---3
| |---index.dat
| |---content.dat
|---4
| |---index.dat
| |---content.dat
---discuss
---Matrix.dat
---seqbase.dat
---lastmsginfo.dat
以下是JAVA。
package com;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* Decry LoginUinList.dat
* @author rmb
* 2008-08-07
*/
public class LoginUinListDecry {
FileOutputStream fw = null;
FileInputStream fr = null;
boolean isFileEnd = false;
int[] ascii = ;
/**
* init Decry,Encry file handle
* @return
*/
public int initFile(){
int result = -1;
try {
fw = new FileOutputStream((new File("LoginUinList_Decry.txt")));
fr = new FileInputStream((new File("LoginUinList.dat")));
result = 1;
} catch (FileNotFoundException e) {
result = -1;
e.printStackTrace();
}
return result;
}
/**
* Judge file effectiveness
* @return
*/
public int isEffectHead(){
int result = -1;
try{
if(fr!=null){
byte[] byte_head = new byte[13];
fr.read(byte_head);
if(byte_head[0] == 0x51 && byte_head[1] == 0x41 ){ //&& byte_head[4] == 0x0E
result = 1;
}
if(result>0){
fr.read(new byte[6]);
}
}
}
catch(Exception e){
result = -1;
e.printStackTrace();
}
return result;
}
/**
* decry LoginUinList data
* @param key
* @param orig_Data
* @return
*/
public byte[] decryData(byte key,byte[] encry_Data){
/* mov dl, byte ptr [ecx+esi]
* not dl
* xor dl, al
* mov byte ptr [ecx+esi], dl
*/
byte single_Data;
for(int i=0;i<encry_Data.length;i++){
single_Data = encry_Data[i];
single_Data = (byte)(~single_Data);
single_Data = (byte)(single_Data^key);
encry_Data[i] = single_Data;
}
return encry_Data;
}
public byte[] getEncryData(byte encryDataLength){
byte[] encryData = new byte[encryDataLength];
int read_num = 0;
try {
read_num = fr.read(encryData);
if(read_num!=encryData.length){
isFileEnd = true;
}
return encryData;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public byte[] getKeyField(int length){
int read_num = 0;
if(fr!=null){
byte[] keyField = new byte[length];
try {
read_num = fr.read(keyField);
if(read_num!=keyField.length){
isFileEnd = true;
}
return keyField;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
return null;
}
public byte getKey(byte[] KeyField){
byte key;
key = KeyField[1];
return key;
}
public byte getKeyDataValue(byte[] KeyField){
byte key;
key = KeyField[0];
return key;
}
public boolean isNeedDecry(byte key){
int int_key = (int)key;
if(int_key<5){
return false;
}
return true;
}
public boolean isNeedJump(byte[] KeyField){
if(KeyField[0]==0x09){
return true;
}
return false;
}
public byte[] trunAround(byte[] DateValue){
byte[] temp = new byte[DateValue.length];
for(int i=0;i<temp.length;i++){
temp[i] = DateValue[DateValue.length-i-1];
}
return temp;
}
public int writeData(byte[] decryData){
try{
fw.write(decryData);
return 1;
}
catch(Exception e){
e.printStackTrace();
}
return -1;
}
public int writeDataString(byte[] decryData){
try{
int height = 0;
int down = 0;
for(int i=0;i<decryData.length;i++){
height = ((decryData[i]&240)>>4);
if(height<0){
height = 0;
}
down = (decryData[i]&15);
if(down<0){
down = 0;
}
fw.write(ascii[height]);
fw.write(ascii[down]);
}
return 1;
}
catch(Exception e){
e.printStackTrace();
}
return -1;
}
public void fileFlush(){
try {
fw.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
public void jump(){
try {
fr.read(new byte[8]);
} catch (IOException e) {
e.printStackTrace();
}
}
public int run(){
int result = -1;
byte[] keyField;
byte key;
byte[] decryData;
byte[] encryData;
if(initFile()>0){
if(isEffectHead()>0){
while(!isFileEnd){
result=-1;
keyField = getKeyField(3);
if(keyField==null) break;
if(isNeedJump(keyField)){
jump();
writeData(new byte[]);//block line
continue;
}
key = getKey(keyField);
encryData = getEncryData(key);
if(encryData==null) break;
decryData = decryData(key,encryData);
writeData(decryData);
writeData(new byte[]);//:
keyField = getKeyField(4);
if(keyField==null) break;
key = getKeyDataValue(keyField);
encryData = getEncryData(key);
if(encryData==null) break;
if(isNeedDecry(key)){
decryData = decryData(key,encryData);
decryData = trunAround(decryData);
}
else{
decryData = trunAround(encryData);
}
writeDataString(decryData);
writeData(new byte[]);//break line
fileFlush();
result=1;
}
}
destroy();
}
return result;
}
/**
* destroy
* @return
*/
public int destroy(){
int result = -1;
try{
if(fw!=null){
fw.close();
}
if(fr!=null){
fr.close();
}
result = 1;
}
catch(Exception e){
result = -1;
e.printStackTrace();
}
return result;
}
public static void main(String[] args) {
LoginUinListDecry ListDecry = new LoginUinListDecry();
if(ListDecry.run()>0){
System.out.println("Decry LoginUinList.dat Success");
}
else{
System.out.println("Decry LoginUinList.dat Fail");
}
}
7-zip 请去官网下载。
下载地址 : http://www.7-zip.org/
DB压缩器 :
下载地址 : http://www.rayfile.com/zh-cn/files/41470597-ee3f-11de-884b-0014221b798a/
--------------------------------------------------------------
2009版本以后的聊天记录,其用户登陆后本地是不保留完整密码文件的,需要本地密钥文件和腾讯服务器上的密钥同时使用才可以成功登陆的,但本地QQ号如在登陆后选择QQ号自动登陆选项后,或曾经勾选过些项,在登陆时会在本地生成密码的哈希文件,在用户系统的software注册表文件中找到生成密码的哈希文件,目前国外已有软件可以通过此方法进行解密QQ的聊天记录文件,碟科数据恢复中心的技术人员对其测试版软件做过测试,可以成功破解2009和2010版本的聊天记录文件。
虽然其功能还有一些局限性,但能作为目前唯一可以破解QQ聊天记录的软件,也是一个很大的进步了。