国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統(tǒng) > Android > 正文

Android如何獲取QQ與微信的聊天記錄并保存到數(shù)據(jù)庫詳解

2019-10-22 18:13:03
字體:
供稿:網(wǎng)友

前言

提前說明下:(該方法只適用于監(jiān)控自己擁有的微信或者QQ ,無法監(jiān)控或者盜取其他人的聊天記錄。本文只寫了如何獲取聊天記錄,服務(wù)器落地程序并不復(fù)雜,不做贅述。寫的倉促,有錯別字還請見諒。)

為了獲取黑產(chǎn)群的動態(tài),有同事潛伏在大量的黑產(chǎn)群(QQ 微信)中,干起了無間道的工作。隨著黑產(chǎn)群數(shù)量的激增,同事希望能自動獲取黑產(chǎn)群的聊天信息,并交付風(fēng)控引擎進(jìn)行風(fēng)險評估。于是,我接到了這么一個工作……

分析了一通需求說明,總結(jié)一下:

  • 能夠自動獲取微信和 QQ群的聊天記錄
  • 只要文字記錄,圖片和表情包,語音之類的不要
  • 后臺自動運行,非實時獲取記錄

準(zhǔn)備工作

參閱很多相關(guān)的文章之后,對這個需求有了大致的想法,開始著手準(zhǔn)備:

  • 一個有root權(quán)限的手機(jī),我用的是紅米5(強(qiáng)調(diào)必須要有ROOT)
  • android/55224.html">android的開發(fā)環(huán)境
  • android相關(guān)的開發(fā)經(jīng)驗(我是個PHP,第一次寫ANDROID程序,踩了不少坑)

獲取微信聊天記錄

說明:

微信的聊天記錄保存在"/data/data/com.tencent.mm/MicroMsg/c5fb89d4729f72c345711cb*/EnMicroMsg.db"

該文件是加密的數(shù)據(jù)庫文件,需要用到sqlcipher來打開。密碼為:MD5(手機(jī)的IMEI+微信UIN)的前七位。文件所在的那個亂碼文件夾的名稱也是一段加密MD5值:MD5('mm'+微信UIN)。微信的UIN存放在微信文件夾/data/data/com.tencent.mmshared_prefs/system_config_prefs.xml中。(這個減號一定要帶著!)

android,微信聊天記錄,微信聊天記錄數(shù)據(jù)庫,微信數(shù)據(jù)庫

另外,如果手機(jī)是雙卡雙待,那么會有兩個IMEI號,默認(rèn)選擇 IMEI1,如果不行,可以嘗試一下字符串‘1234567890ABCDEF'。早期的微信會去判定你的IMEI,如果為空 默認(rèn)選擇這個字符串。

android,微信聊天記錄,微信聊天記錄數(shù)據(jù)庫,微信數(shù)據(jù)庫

拿到密碼,就可以打開EnMicroMsg.db了。微信聊天記錄,包括個人,群組的所有記錄全部存在message這張表里。

代碼實現(xiàn)

第一步,不可能直接去訪問EnMicroMsg.db。沒有權(quán)限,還要避免和微信本身產(chǎn)生沖突,所以選擇把這個文件拷貝到自己的項目下:

oldPath ="/data/data/com.tencent.mm/MicroMsg/c5fb89d4729f72c345711cb**/***/EnMicroMsg.db";newPath ="/data/data/com.你的項目/EnMicroMsg.db";copyFile(oldPath,newPath);//代碼見 部分源碼

第二步,拿到文件的密碼:

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

第三步,打開文件,執(zhí)行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", "讀取微信數(shù)據(jù)庫:" + 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", "讀取結(jié)束:" + System.currentTimeMillis() + ",count:" + count); }

到此,就可以拿到微信的聊天記錄了,之后可以直接將整理好的JSON通過POST請求發(fā)到服務(wù)器就可以了。(忍不住吐槽:寫服務(wù)器落地程序用了30分鐘,寫上面這一坨花了三四天,還不包括搭建開發(fā)環(huán)境,下載SDK,折騰ADB什么的)

獲取QQ聊天記錄

說明

QQ的聊天記錄有點麻煩。他的文件保存在/data/data/com.tencent.mobileqq/databases/你的QQ號碼.db

這個文件是不加密的,可以直接打開。QQ中群組的聊天記錄是單獨建表存放的,所有的QQ群信息存放在TroopInfoV2表里,需要對字段troopuin求MD5,然后找到他的聊天記錄表:mr_troop_" + troopuinMD5 +"_New。

但是!!!

問題來了,它的內(nèi)容是加密的,而且加密方法還很復(fù)雜:根據(jù)手機(jī)IMEI循環(huán)逐位異或。具體的我不舉例子了,太麻煩,直接看文章最后的解密方法。

代碼實現(xiàn)

第一步,還是拷貝數(shù)據(jù)庫文件。

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);

第二步,打開并讀取內(nèi)容

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數(shù)據(jù)庫:"+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","讀取結(jié)束:"+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());}

然后你就可以把信息發(fā)到服務(wù)器落地了。

后續(xù)

這里還有幾個需要注意的地方:

最新安卓系統(tǒng)很難寫個死循環(huán)直接跑了,所以我們需要使用Intent,來開始Service,再通過Service調(diào)用AlarmManager。

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); }}

然后再創(chuàng)建一個LongRunningService,在其中調(diào)用AlarmManager。

 AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE); int Minutes = 60*1000; //此處規(guī)定執(zhí)行的間隔時間 long triggerAtTime = SystemClock.elapsedRealtime() + Minutes; Intent intent1 = new Intent(this, AlarmReceiver.class);//注入要執(zhí)行的類 PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent1, 0); manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pendingIntent); return super.onStartCommand(intent, flags, startId);

在AlarmReceiver中調(diào)用我們的方法。

 Log.e("saurfang","測試定時任務(wù)----BEGIN"); //微信部分 postWXMsg.readWXDatabase(); //QQ部分 postQQMsg.readQQDatabase(); Log.e("saurfang","測試定時任務(wù)----END"); //再次開啟LongRunningService這個服務(wù),即可實現(xiàn)定時循環(huán)。 Intent intentNext = new Intent(context, LongRunningService.class); context.startService(intentNext);
  • 安卓不允許在主線程里進(jìn)行網(wǎng)絡(luò)連接,可以直接用 retrofit2 來發(fā)送數(shù)據(jù)。
  • 項目需要授權(quán)網(wǎng)絡(luò)連接
  • 項目需要引入的包
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'

如果復(fù)制文件時失敗,校驗文件路徑不存在,多半是因為授權(quán)問題。需要對數(shù)據(jù)庫文件授權(quán) 全用戶rwx權(quán)限

部分源碼

(因為種種原因,我不太好直接把源碼貼上來。)

復(fù)制文件的方法

 /**  * 復(fù)制單個文件  *  * @param oldPath String 原文件路徑 如:c:/fqf.txt  * @param newPath String 復(fù)制后路徑 如: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; //字節(jié)數(shù) 文件大小     fs.write(buffer, 0, byteread);    }    Log.e("copyFile", "time_2:" + System.currentTimeMillis());   }  } catch (Exception e) {   System.out.println("復(fù)制單個文件操作出錯");   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'}; //將字符串轉(zhuǎn)化為位 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); }}

總結(jié)

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對VEVB武林網(wǎng)的支持。


注:相關(guān)教程知識閱讀請移步到Android開發(fā)頻道。
發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 全椒县| 东阳市| 清水河县| 乌拉特前旗| 平湖市| 海林市| 万载县| 吉木萨尔县| 施甸县| 长汀县| 来安县| 玛曲县| 钦州市| 方城县| 南阳市| 桓台县| 长葛市| 南靖县| 仙桃市| 登封市| 三门峡市| 六盘水市| 钦州市| 鸡泽县| 洱源县| 日照市| 游戏| 汉中市| 饶阳县| 措美县| 临漳县| 包头市| 绥中县| 鱼台县| 大新县| 鱼台县| 康乐县| 淮滨县| 治多县| 肇源县| 承德县|