Android如何获取QQ与微信的聊天记录并保存到数据库详解

前言

提前说明下:(该方法只适用于监控自己拥有的微信或者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中。(这个减号一定要带着!)

另外,如果手机是双卡双待,那么会有两个IMEI号,默认选择 IMEI1,如果不行,可以尝试一下字符串‘1234567890ABCDEF'。早期的微信会去判定你的IMEI,如果为空 默认选择这个字符串。

拿到密码,就可以打开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);
 }
}

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • Android破解微信获取聊天记录和通讯录信息(静态方式)

    一.猜想数据存放路径 微信现在是老少皆宜,大街小巷都在使用,已经替代了传统的短信聊天方式了,只要涉及到聊天就肯定有隐私消息,那么本文就来讲解如何获取微信的聊天记录以及通讯录信息. 首先我们在没有网络的时候,打开微信同样可以查看聊天记录,说明微信会把聊天记录保存到本地,那么这么多信息肯定会保存在数据库中,所以我们可以去查看微信的databases目录看看内容: 可惜的是,我们在这个里面并没有发现一些有用的数据,所以这时候就了解到了微信因为把重要信息的数据库存在其他目录下面,我们可以直接把微信的整个

  • 详解Android 获取手机中微信聊天记录方法

    首先我们要知道,微信的聊天记录一般是不提供给我们获取的,所以一般情况下我们手机没root的话就拿不到了.就算是root后的手机,想要获取微信的EnMicroMsg.db文件并且解密它.打开它也有点难度. 下面我们就来演示怎么从安卓设备的手机中拿到微信的数据文件吧~ 实验软件 :Android Studio实验设备:Root过的真机一部一.拿到数据库文件EnMicroMsg.db 一步步来,打开Android Studio的File Explorer:Tools –> Android –> An

  • Android如何获取QQ与微信的聊天记录并保存到数据库详解

    前言 提前说明下:(该方法只适用于监控自己拥有的微信或者QQ ,无法监控或者盗取其他人的聊天记录.本文只写了如何获取聊天记录,服务器落地程序并不复杂,不做赘述.写的仓促,有错别字还请见谅.) 为了获取黑产群的动态,有同事潜伏在大量的黑产群(QQ 微信)中,干起了无间道的工作.随着黑产群数量的激增,同事希望能自动获取黑产群的聊天信息,并交付风控引擎进行风险评估.于是,我接到了这么一个工作-- 分析了一通需求说明,总结一下: 能够自动获取微信和 QQ群的聊天记录 只要文字记录,图片和表情包,语音之类

  • 微信小程序获取用户信息并保存登录状态详解

    前言 微信小程序的运行环境不是在浏览器下运行的.所以不能以cookie来维护登录态.下面我就来说说我根据官方给出的方法来写出的维护登录态的方法吧. 一.登录态维护 官方的文档地址:https://mp.weixin.qq.com/debug/wxadoc/dev/api/api-login.html#wxloginobject 通过 wx.login() 获取到用户登录态之后,需要维护登录态.开发者要注意不应该直接把 session_key.openid 等字段作为用户的标识或者 session

  • 微信小程序 本地数据存储实例详解

    微信小程序 本地数据存储实例详解 前言 如果您在看此文章之前有过其他程序的开发经验,那一定会知道一般例如安卓或者苹果的原生APP都提供了本地的存储功能,甚至可以使用sqlite数据库来做存储.可是微信的小程序框架基于微信本身,其实际运行环境只是在浏览器里面,所以不会提供那么丰富的数据存储实力.但html5开始已经可以在浏览器里面存储数据,好在微信的小程序给这个功能封装好了,这样我们可以使用数据存储. 每个微信小程序都可以有自己的本地缓存,可以通过 wx.setStorage(wx.setStor

  • 微信小程序支付及退款流程详解

    首先说明一下,微信小程序支付的主要逻辑集中在后端,前端只需携带支付所需的数据请求后端接口然后根据返回结果做相应成功失败处理即可.我在后端使用的是php,当然在这篇博客里我不打算贴一堆代码来说明支付的具体实现,而主要会侧重于整个支付的流程和一些细节方面的东西.所以使用其他后端语言的朋友有需要也是可以看一下的.很多时候开发的需求和相应问题的解决真的要跳出语言语法层面,去从系统和流程的角度考虑.好的,也不说什么废话了.进入正题. 一. 支付 支付主要分为几个步骤: 前端携带支付需要的数据(商品id,购

  • django 微信网页授权认证api的步骤详解

    微信网页授权认证 根据微信官方文档,网页授权需要四个步骤, - 用户同意授权-获取code - 通过code 获取网页授权access_token - 通过code 获取网页授权access_token - 刷新token - 拉去用户信息scope为snsapi_userinfo -检验授权凭证 access_token是否有效 1 授权 url="https://open.weixin.qq.com/connect/oauth2/authorize?appid={0}&redirec

  • 使用 Python 实现微信群友统计器的思路详解

    基于微信可以做很多有意思的练手项目,看了这张速查表你就会发现,可以做的事情超过你的想象. 有一次我想要统计微信群里哪些同学在北京,但发现直接问是很难得到准确结果的-- 这时候不如运用 wxpy 这个库抓取所有群友的地区信息,很快就可以得到想要的结果,甚至还精确到了区. 下面来分享一下这个微信群友统计器的实现思路,你可以基于它去拓展更多实用功能. 实现思路 解决复杂问题最好的办法就是把问题简单化,拆解成若干个小问题,然后逐个击破.问题的拆解思路如下: ▍1. 如何通过 wxpy 库找到指定微信群?

  • iOS开发微信收款到账语音提醒功能思路详解

    一.背景 为了解决小商户老板们在频繁交易中不方便核对.确认到账的痛点,产品MM提出了新版本需要支持收款到账语音提醒功能.这篇文章总结了开发过程中遇到的坑和一些小技巧. 二.技术方案 后台唤醒App 收款到账语音提醒需要收款方在收到款后,播放一段TTS合成语音播报金额,微信在前台时可以通过模板消息将需要播报的金额带下来,再请求TTS数据并播放,但是app在挂起或者被kill掉的情况下要如何请求语音数据并播放呢? iOS提供了两种方式唤醒处于挂起或已经被kill掉的app.分别是Silent Not

  • Android启动内置APK和动态发送接收自定义广播实例详解

    Android启动内置APK和动态发送接收自定义广播实例详解 工作中遇到这样一个需求,需要为按键添加一个亲情号,提供一个接口启动内置的APK,思考再三决定更改Framework,利用广播机制去实现. 一.代码动态自主启动内置APK 我们都知道Android系统为我们提供了很多服务管理类,PackageManager主要是管理应用程序包,通过它就可以获取应用程序信息并构建Intent,启动对应的应用.除此之外Android还未我们提供了一些对应的类来管理相关的xml文件,比如说可以通过Packag

  • 微信小程序 滚动选择器(时间日期)详解及实例代码

    微信小程序  滚动选择器(时间日期)详解 微信小程序自己封装了很多控件,用起来确实很方便,如果这是Android里面,还需要自己去定义,不废话,效果图: 一起来看看怎么实现的呢?看完你应该就该说,尼玛,这就行啦-. 这个效果呢,要用到picker组件,动画从底部弹起的滚动选择器,现支持三种选择器,通过mode来区分,分别是普通选择器,时间选择器,日期选择器,默认是普通选择器. 看下相应的属性: 具体的来看看代码,布局: <view class="section" > <

  • 微信小程序网络请求wx.request详解及实例

    微信小程序网络请求wx.request详解及实例 如果说小程序API里面最重要一个接口是哪一个?那么首推wx.request().相当于在小程序内请发起一个https请求(本地调试模式下支持HTTP).HTTP协议中共定义了八种方法或者叫"动作"来表明对Request-URI指定的资源的不同操作方式. GET:向特定的资源发出请求. POST:向指定资源提交数据进行处理请求.数据被包含在请求体中. PUT:向指定资源位置上传其最新内容. DELETE:请求服务器删除Request-UR

随机推荐