[Android] How to get the chat history of QQ and WeChat and save it to the database

640?wxfrom=5&wx_lazy=1

Reprinted from: http://www.jb51.net/article/138090.htm

Author: Liu Lingqi


foreword

Explain in advance: (This method is only suitable for monitoring WeChat or QQ owned by yourself, and cannot monitor or steal other people's chat records. This article only describes how to obtain chat records. The server landing procedure is not complicated, so I won't go into details. Write I'm in a hurry, sorry for any typos.)

In order to obtain the dynamics of the black production group, some colleagues lurked in a large number of black production groups (QQ WeChat) and did infernal work. With the surge in the number of black production groups, colleagues hope to automatically obtain the chat information of black production groups and deliver risk control engines for risk assessment. So, I got this job...

After analyzing the requirements, we can summarize:

  • Can automatically obtain the chat records of WeChat and QQ groups

  • As long as text records, pictures and emoticons, voice and the like are not required

  • Automatic running in the background, non-real-time acquisition of records

Ready to work

After reading many related articles, I got a general idea of ​​this requirement and started to prepare:

  • A mobile phone with root privileges, I am using Redmi 5 (emphasis on the need to have ROOT)

  • android development environment

  • Android-related development experience (I am a PHP, the first time I wrote an ANDROID program, and I stepped on a lot of pits)

Get WeChat chat history

illustrate:

WeChat chat history is saved in "/data/data/com.tencent.mm/MicroMsg/c5fb89d4729f72c345711cb*/EnMicroMsg.db"

This file is an encrypted database file and needs to be opened with sqlcipher. The password is: the first seven digits of MD5 (IMEI + WeChat UIN of the mobile phone). The name of the garbled folder where the file is located is also an encrypted MD5 value: MD5('mm'+WeChat UIN). The WeChat UIN is stored in the WeChat folder /data/data/com.tencent.mmshared_prefs/system_config_prefs.xml. (Be sure to bring this minus sign!)

640?wx_fmt=png

In addition, if the mobile phone is dual-SIM dual-standby, there will be two IMEI numbers, and IMEI1 is selected by default. If not, you can try the string '1234567890ABCDEF'. The early WeChat will determine your IMEI. If it is empty, this string is selected by default.

640?wx_fmt=png

Get the password, you can open EnMicroMsg.db. All WeChat chat records, including individuals and groups, are all stored in the message table.

Code

In the first step, it is impossible to access EnMicroMsg.db directly. Without permission, and to avoid conflicts with WeChat itself, so choose to copy this file to your own project:

 
  

oldPath="/data/data/com.tencent.mm/MicroMsg/c5fb89d4729f72c345711cb**\***/EnMicroMsg.db"; newPath="/data/data/com.your-project/EnMicroMsg.db"; copyFile( oldPath, newPath);//See part of the source code for the code

The second step is to get the password of the file:

 
  

String password = (MD5Until.md5("IMEI+微信UIN").substring(0, 7).toLowerCase());

The third step is to open the file and execute SQL:

 
  

SQLiteDatabase.loadLibs(context); SQLiteDatabaseHook hook = new SQLiteDatabaseHook() { public void preKey(SQLiteDatabase database) { } public void postKey(SQLiteDatabase database) {  database.rawExecSQL("PRAGMA cipher_migrate;");//很重要 } }; SQLiteDatabase db = openDatabase(newPath, password, null, NO_LOCALIZED_COLLATORS, hook); long now = System.currentTimeMillis(); Log.e("readWxDatabases", "读取微信数据库:" + now); int count = 0; if (msgId != "0") {  String sql = "select * from message";  Log.e("sql", sql);  Cursor c = db.rawQuery(sql, null);  while (c.moveToNext()) {   long _id = c.getLong(c.getColumnIndex("msgId"));   String content = c.getString(c.getColumnIndex("content"));   int type = c.getInt(c.getColumnIndex("type"));   String talker = c.getString(c.getColumnIndex("talker"));   long time = c.getLong(c.getColumnIndex("createTime"));   JSONObject tmpJson = handleJson(_id, content, type, talker, time);   returnJson.put("data" + count, tmpJson);   count++;  }  c.close();  db.close();  Log.e("readWxDatanases", "读取结束:" + System.currentTimeMillis() + ",count:" + count); }

At this point, you can get the chat records of WeChat, and then you can directly send the sorted JSON to the server through a POST request. (Can't help but complain: It took 30 minutes to write the server landing program, and it took three or four days to write the above, not including building the development environment, downloading the SDK, and tossing ADB)

Get QQ chat history

illustrate

QQ chat records are a bit troublesome. His file is saved in /data/data/com.tencent.mobileqq/databases/your QQ number.db

This file is not encrypted and can be opened directly. The chat records of groups in QQ are stored in a separate table. All QQ group information is stored in the TroopInfoV2 table. You need to find MD5 for the field troopuin, and then find his chat record table: mr_troop_" + troopuinMD5 + "_New.

but! ! !

The problem is that its content is encrypted, and the encryption method is still very complicated: cyclically exclusive OR bit by bit according to the IMEI of the mobile phone. I won't give an example for specifics, it's too troublesome, just look at the decryption method at the end of the article.

Code

The first step is to copy the database files.

 
  

final String QQ_old_path = "/data/data/com.tencent.mobileqq/databases/QQ号.db"; final String QQ_new_path = "/data/data/com.android.saurfang/QQ号.db"; DataHelp.copyFile(QQ_old_path,QQ_new_path);

The second step, open and read the content

 
  

SQLiteDatabase.loadLibs(context); String password = ""; SQLiteDatabaseHook hook = new SQLiteDatabaseHook() { public void preKey(SQLiteDatabase database) {} public void postKey(SQLiteDatabase database) {  database.rawExecSQL("PRAGMA cipher_migrate;"); } }; MessageDecode mDecode = new MessageDecode(imid); HashMap<String, String> troopInfo = new HashMap<String, String>(); try{ SQLiteDatabase db = openDatabase(newPath,password,null, NO_LOCALIZED_COLLATORS,hook); long now = System.currentTimeMillis(); Log.e("readQQDatabases","读取QQ数据库:"+now); //读取所有的群信息 String sql = "select troopuin,troopname from TroopInfoV2 where _id"; Log.e("sql",sql); Cursor c = db.rawQuery(sql,null); while (c.moveToNext()){  String troopuin = c.getString(c.getColumnIndex("troopuin")); String troopname = c.getString(c.getColumnIndex("troopname"));  String name = mDecode.nameDecode(troopname);  String uin = mDecode.uinDecode(troopuin);  Log.e("readQQDatanases","读取结束:"+name);  troopInfo.put(uin, name); } c.close(); int troopCount = troopInfo.size(); Iterator<String> it = troopInfo.keySet().iterator(); JSONObject json = new JSONObject(); //遍历所有的表 while(troopCount > 0) {  try{   while(it.hasNext()) {    String troopuin = (String)it.next();    String troopname = troopInfo.get(troopuin);    if(troopuin.length() < 8)     continue;    String troopuinMD5 = getMD5(troopuin);    String troopMsgSql = "select _id,msgData, senderuin, time from mr_troop_" + troopuinMD5 +"_New";    Log.e("sql",troopMsgSql);    Cursor cc = db.rawQuery(troopMsgSql,null);   JSONObject tmp = new JSONObject();    while(cc.moveToNext()) {     long _id = cc.getLong(cc.getColumnIndex("_id"));     byte[] msgByte = cc.getBlob(cc.getColumnIndex("msgData"));     String ss = mDecode.msgDecode(msgByte);     //图片不保留     if(ss.indexOf("jpg") != -1 || ss.indexOf("gif") != -1       || ss.indexOf("png") != -1 )      continue;     String time = cc.getString(cc.getColumnIndex("time"));     String senderuin = cc.getString(cc.getColumnIndex("senderuin"));     senderuin = mDecode.uinDecode(senderuin);     JSONObject tmpJson = handleQQJson(_id,ss,senderuin,time);     tmp.put(String.valueOf(_id),tmpJson);    }    troopCount--;    cc.close();   }  } catch (Exception e) {   Log.e("e","readWxDatabases"+e.toString());  } } db.close();}catch (Exception e){ Log.e("e","readWxDatabases"+e.toString()); }

Then you can send the information to the server and land.

follow-up

Here are a few more points to note:

The latest Android system is difficult to write an infinite loop and run directly, so we need to use Intent to start the Service, and then call AlarmManager through the Service.

 
  

public class MainActivity extends AppCompatActivity { private Intent intent; @Override protected void onCreate(Bundle savedInstanceState) {  super.onCreate(savedInstanceState);  setContentView(R.layout.activity\_main);  intent = new Intent(this, LongRunningService.class);  startService(intent); } @Override protected void onDestroy() {  super.onDestroy();  stopService(intent); } }

Then create a LongRunningService and call AlarmManager in it.

 
  

AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE); int Minutes = 60*1000; //此处规定执行的间隔时间 long triggerAtTime = SystemClock.elapsedRealtime() + Minutes; Intent intent1 = new Intent(this, AlarmReceiver.class);//注入要执行的类 PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent1, 0); manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pendingIntent); return super.onStartCommand(intent, flags, startId);

在AlarmReceiver中调用我们的方法。

 
  

Log.e("saurfang","测试定时任务----BEGIN"); //微信部分 postWXMsg.readWXDatabase(); //QQ部分 postQQMsg.readQQDatabase(); Log.e("saurfang","测试定时任务----END"); //再次开启LongRunningService这个服务,即可实现定时循环。 Intent intentNext = new Intent(context, LongRunningService.class); context.startService(intentNext);

  • 安卓不允许在主线程里进行网络连接,可以直接用 retrofit2 来发送数据。

  • 项目需要授权网络连接

  • 项目需要引入的包

 
  

implementation files('libs/sqlcipher.jar') implementation files('libs/sqlcipher-javadoc.jar') implementation 'com.squareup.retrofit2:retrofit:2.0.0' implementation 'com.squareup.retrofit2:converter-gson:2.0.0'

如果复制文件时失败,校验文件路径不存在,多半是因为授权问题。需要对数据库文件授权 全用户rwx权限

部分源码

(因为种种原因,我不太好直接把源码贴上来。)

复制文件的方法

 
  

/**  * 复制单个文件  *  * @param oldPath String 原文件路径 如:c:/fqf.txt  * @param newPath String 复制后路径 如:f:/fqf.txt  * @return boolean  */ public static boolean copyFile(String oldPath, String newPath) {  deleteFolderFile(newPath, true);  Log.e("copyFile", "time_1:" + System.currentTimeMillis());  InputStream inStream = null;  FileOutputStream fs = null;  try {   int bytesum = 0;   int byteread = 0;   File oldfile = new File(oldPath);   Boolean flag = oldfile.exists();   Log.e("copyFile", "flag:" +flag );   if (oldfile.exists()) { //文件存在时    inStream = new FileInputStream(oldPath); //读入原文件    fs = new FileOutputStream(newPath);    byte[] buffer = new byte[2048];    while ((byteread = inStream.read(buffer)) != -1) {     bytesum += byteread; //字节数 文件大小     fs.write(buffer, 0, byteread);    }    Log.e("copyFile", "time_2:" + System.currentTimeMillis());   }  } catch (Exception e) {   System.out.println("复制单个文件操作出错");   e.printStackTrace();  } finally {   try {    if (inStream != null) {     inStream.close();    }    if (fs != null) {     fs.close();    }   } catch (IOException e) {    e.printStackTrace();   }  }  return true; } /**  * 删除单个文件  *  * @param filepath  * @param deleteThisPath  */ public static void deleteFolderFile(String filepath, boolean deleteThisPath) {  if (!TextUtils.isEmpty(filepath)) {   try {    File file = new File(filepath);    if (file.isDirectory()) {     //处理目录     File files[] = file.listFiles();     for (int i = 0; i < file.length(); i++) {      deleteFolderFile(files[i].getAbsolutePath(), true);     }    }    if (deleteThisPath) {     if (!file.isDirectory()) {      //删除文件      file.delete();     } else {      //删除目录      if (file.listFiles().length == 0) {       file.delete();      }     }    }   } catch (Exception e) {    e.printStackTrace();   }  } }

MD5方法

 
  

public class MD5Until { public static char HEX_DIGITS[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',   'A', 'B', 'C', 'D', 'E', 'F'}; //将字符串转化为位 public static String toHexString(byte[] b){  StringBuilder stringBuilder = new StringBuilder(b.length * 2);  for (int i = 0; i < b.length; i++) {   stringBuilder.append(HEX_DIGITS[(b[i] & 0xf0) >>> 4]);   stringBuilder.append(HEX_DIGITS[b[i] & 0x0f]);  }  return stringBuilder.toString(); } public static String md5(String string){  try {   MessageDigest digest = java.security.MessageDigest.getInstance("MD5");   digest.update(string.getBytes());   byte messageDigest[] = digest.digest();   return toHexString(messageDigest);  }catch (NoSuchAlgorithmException e){   e.printStackTrace();  }  return ""; } }

QQ信息解密方法

 
  

public class MessageDecode { public String imeiID; public int imeiLen; public MessageDecode(String imeiID) {  this.imeiID = imeiID;  this.imeiLen = imeiID.length(); } public boolean isChinese(byte ch) {  int res = ch & 0x80;  if(res != 0)   return true;  return false; } public String timeDecode(String time) {  String datetime = "1970-01-01 08:00:00";  SimpleDateFormat sdFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  try {   long second = Long.parseLong(time);   Date dt = new Date(second * 1000);   datetime = sdFormat.format(dt);  } catch (NumberFormatException e) {   e.printStackTrace();  }  return datetime; } public String nameDecode(String name) {  byte nbyte[] = name.getBytes();  byte ibyte[] = imeiID.getBytes();  byte xorName[] = new byte[nbyte.length];  int index = 0;  for(int i = 0; i < nbyte.length; i++) {   if(isChinese(nbyte[i])){    xorName[i] = nbyte[i];    i++;    xorName[i] = nbyte[i];    i++;    xorName[i] = (byte)(nbyte[i] ^ ibyte[index % imeiLen]);    index++;   } else {    xorName[i] = (byte)(nbyte[i] ^ ibyte[index % imeiLen]);    index++;   }  }  return new String(xorName); } public String uinDecode(String uin) {  byte ubyte[] = uin.getBytes();  byte ibyte[] = imeiID.getBytes();  byte xorMsg[] = new byte[ubyte.length];  int index = 0;  for(int i = 0; i < ubyte.length; i++) {   xorMsg[i] = (byte)(ubyte[i] ^ ibyte[index % imeiLen]);   index++;  }  return new String(xorMsg); } public String msgDecode(byte[] msg) {  byte ibyte[] = imeiID.getBytes();  byte xorMsg[] = new byte[msg.length];  int index = 0;  for(int i = 0; i < msg.length; i++) {   xorMsg[i] = (byte)(msg[i] ^ ibyte[index % imeiLen]);   index++;  }  return new String(xorMsg); } }


640?wx_fmt=jpeg


640?wx_fmt=png

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326047449&siteId=291194637