91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

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

發布時間:2020-09-18 11:35:26 來源:腳本之家 閱讀:480 作者:劉伶已 欄目:移動開發

前言

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

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

分析了一通需求說明,總結一下:

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

準備工作

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

  • 一個有root權限的手機,我用的是紅米5(強調必須要有ROOT)
  • android的開發環境
  • android相關的開發經驗(我是個PHP,第一次寫ANDROID程序,踩了不少坑)

獲取微信聊天記錄

說明:

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

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

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

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

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

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

代碼實現

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

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

第三步,打開文件,執行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);
 }

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

獲取QQ聊天記錄

說明

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

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

但是!!!

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

代碼實現

第一步,還是拷貝數據庫文件。

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

第二步,打開并讀取內容

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

然后你就可以把信息發到服務器落地了。

后續

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

最新安卓系統很難寫個死循環直接跑了,所以我們需要使用Intent,來開始Service,再通過Service調用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);
 }
}

然后再創建一個LongRunningService,在其中調用AlarmManager。

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

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對億速云的支持。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

天长市| 苍山县| 兰西县| 元朗区| 东明县| 乐陵市| 石林| 柞水县| 武威市| 祁阳县| 稷山县| 梓潼县| 嘉定区| 中江县| 台湾省| 宝坻区| 弋阳县| 双柏县| 新宾| 泰州市| 万全县| 洱源县| 无极县| 白河县| 晋宁县| 宝清县| 松原市| 济源市| 广平县| 山阴县| 贺州市| 五原县| 永川市| 子长县| 拉萨市| 正安县| 胶州市| 安远县| 麻江县| 贵阳市| 孙吴县|