前言:
最近一直在研究xposed微信,然后研究了很多微信的源码,发现了微信聊天数据库的原理,这里献上分析成果.
之前有写过一个文章
Android破解微信数据库获取聊天记录 https://www.jianshu.com/p/0cc9e7a95461
但是这个只能是用电脑端sqlcipher数据库查看,而且只能看普通文本信息,所以花了一段时间,写了一个手机版本的出来了,这样更加方便查看微信聊天记录了.
使用前提:
1.手机必须Root
2.手机必须Root
3.手机必须Root
成果:可以获取到微信联系人列表和联系人头像
Screenshot_2018-08-04-18-11-23-100_com.wechatutil.png开始分析:
我们在断网的情况下,聊天记录也是可以看到的,所以微信的聊天数据库肯定是存在本地文件的,所以我们只要找到微信存储的数据库位置就能通过数据库查看聊天记录了,但是微信本地数据库肯定是做了加密的,所以只要找到微信数据库的解密方法就能查看数据库了,这个之前写过具体,这里不再多说
Android破解微信数据库获取聊天记录 https://www.jianshu.com/p/0cc9e7a95461
但是因为上面的方法,聊天记录是只能电脑端查看的,而且只能看文本内容,查看也不方便,所以现在我们的新需求就是要把数据获取到,显示在手机应用上面,但是因为Android是沙箱隔离机制,我们获取不了微信下面的数据,所以手机必须要Root之后获取超级su权限才能拿到微信的数据.
一、请求APP管理员ROOT权限以及微信目录下面的读写权限
新建一个WechatApplication类,然后每次APP启动获取读写权限
public class WechatApplication extends Application {
private static WechatApplication sInstance;
public static WechatApplication getInstance() {
return sInstance;
}
@Override
public void onCreate() {
super.onCreate();
sInstance = this;
execRootCmd("chmod 777 -R " + RootUtils.WX_ROOT_PATH);
}
/**
* 执行linux指令
*
* @param paramString
*/
public void execRootCmd(String paramString) {
try {
Process localProcess = Runtime.getRuntime().exec("su");
Object localObject = localProcess.getOutputStream();
DataOutputStream localDataOutputStream = new DataOutputStream((OutputStream) localObject);
String str = String.valueOf(paramString);
localObject = str + "\n";
localDataOutputStream.writeBytes((String) localObject);
localDataOutputStream.flush();
localDataOutputStream.writeBytes("exit\n");
localDataOutputStream.flush();
localProcess.waitFor();
} catch (Exception localException) {
localException.printStackTrace();
}
}
}
每次打开APP准备读取数据库之前都需要执行一次该命令。先通过这个命令,使得当前app获取到root权限,然后再通过chmod命令来修改微信的data目录的读写权限,因为我们需要操作读取微信的数据库文件,所以必须要有微信文件的操作权限。
二、复制微信数据库到自己应用目录下面
public static final String WX_ROOT_PATH = "/data/data/com.tencent.mm/";
public static final String WX_SP_UIN_PATH = WX_ROOT_PATH + "shared_prefs/auth_info_key_prefs.xml";
public static final String WX_DB_DIR_PATH = WX_ROOT_PATH + "MicroMsg";
public static final String WX_DB_FILE_NAME = "EnMicroMsg.db";
public static final String mCurrApkPath = "/data/data/" + "com.wechatutils.chatrecord" + "/";
private static final String COPY_WX_DATA_DB = "wx_data.db";
public static final String copyFilePath = mCurrApkPath + COPY_WX_DATA_DB;
public static void weChatFileUtil() {
File wxDataDir = new File(WX_DB_DIR_PATH);
searchFile(wxDataDir, WX_DB_FILE_NAME);
}
/**
* 递归查询微信本地数据库文件
* @param file 目录
* @param fileName 需要查找的文件名称
*/
public static void searchFile(File file, String fileName) {
if (file.isDirectory()) {
File[] files = file.listFiles();
if (files != null) {
for (File childFile : files) {
searchFile(childFile, fileName);
}
}
} else {
if (fileName.equals(file.getName())) {
copyFile(file.getAbsolutePath(),copyFilePath);
}
}
}
三、解密微信数据库获取数据
compile 'net.zetetic:android-database-sqlcipher:3.5.9@aar'
导入SQLCipher数据库,微信数据库也是用的这个,然后再获取密码,密码获取的方式不再细说,我们这里只说数据库使用
/**
* 连接数据库
*
* @param
*/
public static List<WechatBean> openWxDb(Context context, String mDbPassword) {
List<WechatBean> weChatDataList=new ArrayList<>();
File copyWxDataDb = new File(copyFilePath);
SQLiteDatabase.loadLibs(context);
SQLiteDatabaseHook hook = new SQLiteDatabaseHook() {
@Override
public void preKey(net.sqlcipher.database.SQLiteDatabase sqLiteDatabase) {
}
@Override
public void postKey(net.sqlcipher.database.SQLiteDatabase sqLiteDatabase) {
sqLiteDatabase.rawExecSQL("PRAGMA cipher_migrate;"); //兼容2.0的数据库
}
};
try {
//打开数据库连接
// SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(copyWxDataDb, mDbPassword, null, hook);
SQLiteDatabase db = SQLiteDatabase.openDatabase(copyWxDataDb.getAbsolutePath(), mDbPassword, null,1, hook);
//查询所有联系人(verifyFlag!=0:公众号等类型,群里面非好友的类型为4,未知类型2)
Cursor c1 = db.rawQuery("select * from rcontact where verifyFlag = 0 and type != 4 and type != 2 and nickname != '' limit 20, 9999", null);
String defile = PreferenceUtil.getString(DB_FILE, "").replace("/storage/emulated/0/tencent/MicroMsg/","");
while (c1.moveToNext()) {
WechatBean wechatBean = new WechatBean();
String userName = c1.getString(c1.getColumnIndex("username"));
String alias = c1.getString(c1.getColumnIndex("alias"));
String nickName = c1.getString(c1.getColumnIndex("nickname"));
wechatBean.setUsername(userName);
//微信用户头像解密
String wechatUserAvatarImage = decryptionWechatUserAvatarImage(userName, defile);
if(TextUtils.isEmpty(alias)){
alias=userName;
}
wechatBean.setAvatarImage(wechatUserAvatarImage);
wechatBean.setAlias(alias);
wechatBean.setUsername(userName);
wechatBean.setNickname(nickName);
weChatDataList.add(wechatBean);
}
c1.close();
db.close();
} catch (Exception e) {
Log.d(TAG, "读取数据库信息失败" + e.toString());
e.printStackTrace();
}
Log.d(TAG, "openWxDb: ========"+weChatDataList.size());
return weChatDataList;
}
这个就是查询数据库,然后根据你需要的数据返回,有些具体不懂的,可以看我之前写的上一篇文章
public class WechatBean implements Serializable {
private int id;
private String username;
private String alias;
private String avatarImage;
private String nickname;
private int type;
public String getAvatarImage() {
return avatarImage;
}
public void setAvatarImage(String avatarImage) {
this.avatarImage = avatarImage;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getAlias() {
return alias;
}
public void setAlias(String alias) {
this.alias = alias;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
}
四、解密微信联系人的头像
这个就是微信所有联系人返回的数据,然后就自己可以写个列表显示,这里Adapter那些代码就不贴出来了,这个比较简单可以自己写,下面开始分析头像,这里数据库的头像是没有返回的,然后我用Xpose打印微信的日志,获取到了微信的头像是在/data/user/0/com.tencent.mm/MicroMsg/xxxxxx用户信息 /avatar/下面的目录
image.png image.png但是分析这个图片发现这里面每个都是目录下面的图片,根本不知道,那个文件的图片对应哪个用户头像,
没办法,只能继续Xposed微信是怎么获取的,这里就直接说方案吧,微信的用户头像是根据数据库的username这个字段进行md5加密,然后生成字符串,再截取前面两个字段的文件目录生成的
public static String decryptionWechatUserAvatarImage(String userName,String defile) {
//根据微信的 WechatBean的userName然后使用md5加密,成字符串,再截取前面两个字段的文件目录
String decryptionWechatMd5 = decryptionWechatMd5(userName.getBytes());
//decryptionWechatMd5 5f39b18498a4107de947dc9b1e5d29b2
String decryptionWechatSubString = decryptionWechatSubString(decryptionWechatMd5);
//decryptionWechatSubString 5f/39/
String imagePath = "/data/user/0/com.tencent.mm/MicroMsg/" + defile + "/avatar/" + decryptionWechatSubString + "user_" + decryptionWechatMd5+".png";
// /data/user/0/com.tencent.mm/MicroMsg/1306e8eb3f168108d6f138fd6dbc511e/avatar/5f/39/user_5f39b18498a4107de947dc9b1e5d29b2.png
//这个就是当前用户的头像地址
return imagePath;
};
/**
*
*微信Md5解密
*/
public static final String decryptionWechatMd5(byte[] bArr) {
char[] cArr = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
try {
MessageDigest instance = MessageDigest.getInstance("MD5");
instance.update(bArr);
byte[] digest = instance.digest();
int length = digest.length;
char[] cArr2 = new char[(length * 2)];
int i = 0;
int i2 = 0;
while (i < length) {
byte b = digest[i];
int i3 = i2 + 1;
cArr2[i2] = cArr[(b >>> 4) & 15];
int i4 = i3 + 1;
cArr2[i3] = cArr[b & 15];
i++;
i2 = i4;
}
return new String(cArr2);
} catch (Exception e) {
return null;
}
}
public static String decryptionWechatSubString(String str) {
if (!TextUtils.isEmpty(str) && str.length() > 4) {
return str.substring(0, 2) + "/" + str.substring(2, 4) + "/";
}
return null;
}
这里就是每个用户头像地址返回了,这就是获取微信所有联系人以及头像的逻辑,描叙的不够详细,请大家原谅,如果有什么问题可以在下面回复。
五、后续
这里暂时就写这么多,明天后续还会写上查询单个联系人所有聊天记录,包含语音,图片。
网友评论