内容简介:我司有关部门为了获取黑产群的动态,有同事潜伏在大量的黑产群(QQ群、微信群)中,干起了无间道的工作。随着黑产群数量的激增,同事希望能自动获取黑产群的聊天信息,并交付风控引擎进行风险评估。于是,这个工作就交给我了,是时候表现一波了……
1、引言
特别说明: 本文内容仅用于即时通讯技术研究和学习之用,请勿用于非法用途。如本文内容有不妥之处,请联系 JackJiang 进行处理!
我司有关部门为了获取黑产群的动态,有同事潜伏在大量的黑产群(QQ群、微信群)中,干起了无间道的工作。随着黑产群数量的激增,同事希望能自动获取黑产群的聊天信息,并交付风控引擎进行风险评估。于是,这个工作就交给我了,是时候表现一波了……
针对同事的需求,分析了一通,总结一下:
1)能够自动获取微信和 QQ群的聊天记录;
2)只要文字记录,图片和表情包,语音之类的不要;
3)后台自动运行,非实时获取记录。
( 注: 本文读取聊天记录的方法只适用于监控自己拥有的微信或者QQ ,无法监控或者盗取其他人的聊天记录。本文只写了如何获取聊天记录,服务器落地程序并不复杂,不做赘述。写的仓促,有错别字还请见谅。)
学习交流:
- 即时通讯开发交流3群: 185926912 [推荐]
- 移动端IM开发入门文章:《 新手入门一篇就够:从零开发移动端IM 》
(本文同步发布于: http://www.52im.net/thread-1992-1-1.html )
2、相关文章
即时通讯网之前整理过微信本地数据库的读取和样本,如有兴趣可请往阅读:
《 微信本地数据库破解版(含iOS、Android),仅供学习研究 [附件下载] 》
3、准备工作
参阅很多相关的文章之后,对这个需求有了大致的想法,开始着手准备:
1)需要一个有root权限的Android手机,我用的是红米5(强调必须已被ROOT);
2)android的开发环境(就是Android Studio那一套啦);
3)android相关的开发经验(我是个PHP,第一次写Android程序,踩了不少坑)。
4、获取微信聊天记录过程分享
4.1 着手准备
微信的聊天记录保存在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”中。(这个减号一定要带着!)
另外: 即时通讯网之前整理过微信本地数据库的样本,如有兴趣可请往下载:《 微信本地数据库破解版(含iOS、Android),仅供学习研究 [附件下载] 》。
注意: 如果手机是双卡双待,那么会有两个IMEI号,默认选择 IMEI1,如果不行,可以尝试一下字符串‘1234567890ABCDEF’。早期的微信会去判定你的IMEI,如果为空 默认选择这个字符串。
拿到密码,就可以打开EnMicroMsg.db了。微信聊天记录,包括个人、群组的所有记录全部存在message这张表里(如下图所示),就像下面这两张截图里展示的一样。
(为了方便截图,此图截自《 微信本地数据库破解版(含iOS、Android),仅供学习研究 [附件下载] 》中的样本)
(为了方便截图,此图截自《 微信本地数据库破解版(含iOS、Android),仅供学习研究 [附件下载] 》中的样本)
4.2 代码实现
第一步, 不可能直接去访问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 = newSQLiteDatabaseHook() { publicvoidpreKey(SQLiteDatabase database) { } publicvoidpostKey(SQLiteDatabase database) { database.rawExecSQL("PRAGMA cipher_migrate;");//很重要 } }; SQLiteDatabase db = openDatabase(newPath, password, null, NO_LOCALIZED_COLLATORS, hook); longnow = System.currentTimeMillis(); Log.e("readWxDatabases", "读取微信数据库:"+ now); intcount = 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")); inttype = c.getInt(c.getColumnIndex("type")); String talker = c.getString(c.getColumnIndex("talker")); longtime = 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什么的)。
5、获取QQ聊天记录过程分享
5.1 说明
QQ的聊天记录有点麻烦,他的文件保存在:“/data/data/com.tencent.mobileqq/databases/你的QQ号码.db”。
这个文件是不加密的,可以直接打开。QQ中群组的聊天记录是单独建表存放的,所有的QQ群信息存放在TroopInfoV2表里,需要对字段troopuin求MD5,然后找到他的聊天记录表:mr_troop_" + troopuinMD5 +"_New。
但是!(看到“但是”就没好事。。。)
问题来了,它的内容是加密的,而且加密方法还很复杂:根据手机IMEI循环逐位异或。具体的我不举例子了,太麻烦,直接看文章最后的解密方法。
5.2 代码实现
第一步, 还是拷贝数据库文件:
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 = newSQLiteDatabaseHook() { publicvoidpreKey(SQLiteDatabase database) {} publicvoidpostKey(SQLiteDatabase database) { database.rawExecSQL("PRAGMA cipher_migrate;"); } }; MessageDecode mDecode = newMessageDecode(imid); HashMap<String, String> troopInfo = newHashMap<String, String>(); try{ SQLiteDatabase db = openDatabase(newPath,password,null, NO_LOCALIZED_COLLATORS,hook); longnow = 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(); inttroopCount = troopInfo.size(); Iterator<String> it = troopInfo.keySet().iterator(); JSONObject json = newJSONObject(); //遍历所有的表 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 = newJSONObject(); 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()); }
然后你就可以把信息发到服务器落地了(同样跟微信的记录上传一样,通过你自已写的代码发送到你的服务端就可以了)。
6、题外话:一些注意点
这里还有几个需要注意的地方。
1) 最新安卓系统很难写个死循环直接跑了,所以我们需要使用Intent,来开始Service,再通过Service调用AlarmManager,就像下面的代码这样:
publicclassMainActivity extendsAppCompatActivity { privateIntent intent; @Override protectedvoidonCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity\_main); intent = newIntent(this, LongRunningService.class); startService(intent); } @Override protectedvoidonDestroy() { super.onDestroy(); stopService(intent); } }
然后再创建一个LongRunningService,在其中调用AlarmManager:
AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE); intMinutes = 60*1000; //此处规定执行的间隔时间 longtriggerAtTime = SystemClock.elapsedRealtime() + Minutes; Intent intent1 = newIntent(this, AlarmReceiver.class);//注入要执行的类 PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent1, 0); manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pendingIntent); returnsuper.onStartCommand(intent, flags, startId);
在AlarmReceiver中调用我们的方法:
//微信部分 postWXMsg.readWXDatabase(); //QQ部分 postQQMsg.readQQDatabase(); //再次开启LongRunningService这个服务,即可实现定时循环。 Intent intentNext = newIntent(context, LongRunningService.class); context.startService(intentNext);
2) 安卓不允许在主线程里进行网络连接,可以直接用 retrofit2 来发送数据(或者最简单的方法就是用AsyncTask了)。
3) 项目需要授权网络连接(就是在AndroidManifast.xml里加上网络权限申请就是了);
4) 项目需要引入的包:
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'
5) 如果复制文件时失败,校验文件路径不存在,多半是因为授权问题。需要对数据库文件授权 全用户rwx权限;
6) 如果服务端使用 MySql 数据库的话,数据库编码请用utf8mb4编码,用来支持Emoji表情。。
7、我的部分源码
(因为种种原因,我不太好直接把源码贴上来,现把几个实用方法分享出来,可以直接使用。)
复制文件的方法:
/** * 复制单个文件 * * @param oldPath String 原文件路径 如:c:/fqf.txt * @param newPath String 复制后路径 如:f:/fqf.txt * @return boolean */ publicstaticbooleancopyFile(String oldPath, String newPath) { deleteFolderFile(newPath, true); Log.e("copyFile", "time_1:"+ System.currentTimeMillis()); InputStream inStream = null; FileOutputStream fs = null; try{ intbytesum = 0; intbyteread = 0; File oldfile = newFile(oldPath); Boolean flag = oldfile.exists(); Log.e("copyFile", "flag:"+flag ); if(oldfile.exists()) { //文件存在时 inStream = newFileInputStream(oldPath); //读入原文件 fs = newFileOutputStream(newPath); byte[] buffer = newbyte[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(); } } returntrue; } /** * 删除单个文件 * * @param filepath * @param deleteThisPath */ publicstaticvoiddeleteFolderFile(String filepath, booleandeleteThisPath) { if(!TextUtils.isEmpty(filepath)) { try{ File file = newFile(filepath); if(file.isDirectory()) { //处理目录 File files[] = file.listFiles(); for(inti = 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方法:
publicclassMD5Until { publicstaticcharHEX_DIGITS[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; //将字符串转化为位 publicstaticString toHexString(byte[] b){ StringBuilder stringBuilder = newStringBuilder(b.length * 2); for(inti = 0; i < b.length; i++) { stringBuilder.append(HEX_DIGITS[(b[i] & 0xf0) >>> 4]); stringBuilder.append(HEX_DIGITS[b[i] & 0x0f]); } returnstringBuilder.toString(); } publicstaticString md5(String string){ try{ MessageDigest digest = java.security.MessageDigest.getInstance("MD5"); digest.update(string.getBytes()); bytemessageDigest[] = digest.digest(); returntoHexString(messageDigest); }catch(NoSuchAlgorithmException e){ e.printStackTrace(); } return""; } }
QQ信息解密方法:
public class MessageDecode { public String imeiID; public intimeiLen; public MessageDecode(String imeiID) { this.imeiID = imeiID; this.imeiLen = imeiID.length(); } public boolean isChinese(bytech) { intres = ch & 0x80; if(res != 0) returntrue; returnfalse; } public String timeDecode(String time) { String datetime = "1970-01-01 08:00:00"; SimpleDateFormat sdFormat = newSimpleDateFormat("yyyy-MM-dd HH:mm:ss"); try{ longsecond = Long.parseLong(time); Date dt = newDate(second * 1000); datetime = sdFormat.format(dt); } catch(NumberFormatException e) { e.printStackTrace(); } returndatetime; } public String nameDecode(String name) { bytenbyte[] = name.getBytes(); byteibyte[] = imeiID.getBytes(); bytexorName[] = newbyte[nbyte.length]; intindex = 0; for(inti = 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) { byteubyte[] = uin.getBytes(); byteibyte[] = imeiID.getBytes(); bytexorMsg[] = newbyte[ubyte.length]; intindex = 0; for(inti = 0; i < ubyte.length; i++) { xorMsg[i] = (byte)(ubyte[i] ^ ibyte[index % imeiLen]); index++; } returnnewString(xorMsg); } public String msgDecode(byte[] msg) { byteibyte[] = imeiID.getBytes(); bytexorMsg[] = newbyte[msg.length]; intindex = 0; for(int i = 0; i < msg.length; i++) { xorMsg[i] = (byte)(msg[i] ^ ibyte[index % imeiLen]); index++; } return new String(xorMsg); } }
附录:有关微信、QQ的技术文章汇总
《 腾讯技术分享:腾讯是如何大幅降低带宽和网络流量的(图片压缩篇) 》
《 腾讯技术分享:腾讯是如何大幅降低带宽和网络流量的(音视频技术篇) 》
《 微信团队分享:微信移动端的全文检索多音字问题解决方案 》
《 腾讯技术分享:Android版手机QQ的缓存监控与优化实践 》
《 微信团队分享:iOS版微信的高性能通用key-value组件技术实践 》
《 微信团队分享:iOS版微信是如何防止特殊字符导致的炸群、APP崩溃的? 》
《 腾讯技术分享:Android手Q的线程死锁监控系统技术实践 》
《 微信团队原创分享:iOS版微信的内存监控系统技术实践 》
《 让互联网更快:新一代QUIC协议在腾讯的技术实践分享 》
《 微信团队分享:微信每日亿次实时音视频聊天背后的技术解密 》
《 QQ音乐团队分享:Android中的图片压缩技术详解(上篇) 》
《 QQ音乐团队分享:Android中的图片压缩技术详解(下篇) 》
《 腾讯团队分享:手机QQ中的人脸识别酷炫动画效果实现详解 》
《 腾讯团队分享 :一次手Q聊天界面中图片显示bug的追踪过程分享 》
《 微信团队分享:微信Android版小视频编码填过的那些坑 》
《 微信团队披露:微信界面卡死超级bug“15。。。。”的来龙去脉 》
《 QQ 18年:解密8亿月活的QQ后台服务接口隔离技术 》
《 月活8.89亿的超级IM微信是如何进行Android端兼容测试的 》
《 一篇文章get微信开源移动端数据库组件WCDB的一切! 》
《 微信客户端团队负责人技术访谈:如何着手客户端性能监控和优化 》
《 微信团队原创分享:Android版微信的臃肿之困与模块化实践之路 》
《 微信后台团队:微信后台异步消息队列的优化升级实践分享 》
《 微信团队原创分享:微信客户端 SQLite 数据库损坏修复实践 》
《 腾讯原创分享(一):如何大幅提升移动网络下手机QQ的图片传输速度和成功率 》
《 腾讯原创分享(二):如何大幅压缩移动网络下APP的流量消耗(下篇) 》
《 腾讯原创分享(三):如何大幅压缩移动网络下APP的流量消耗(上篇) 》
《 微信Mars:微信内部正在使用的网络层封装库,即将开源 》
《 如约而至:微信自用的移动端IM网络层跨平台组件库Mars已正式开源 》
《 开源libco库:单机千万连接、支撑微信8亿用户的后台框架基石 [源码下载] 》
《 微信新一代通信安全解决方案:基于TLS1.3的MMTLS详解 》
《 微信团队原创分享:Android版微信后台保活实战分享(进程保活篇) 》
《 微信团队原创分享:Android版微信后台保活实战分享(网络保活篇) 》
《 Android版微信从300KB到30MB的技术演进(PPT讲稿) [附件下载] 》
《 微信团队原创分享:Android版微信从300KB到30MB的技术演进 》
《 微信技术总监谈架构:微信之道——大道至简(演讲全文) 》
《 微信技术总监谈架构:微信之道——大道至简(PPT讲稿) [附件下载] 》
《 如何解读《微信技术总监谈架构:微信之道——大道至简》 》
《 微信海量用户背后的后台系统存储架构(视频+PPT) [附件下载] 》
《 微信异步化改造实践:8亿月活、单机千万连接背后的后台解决方案 》
《 一份微信后台技术架构的总结性笔记 》
《 架构之道:3个 程序员 成就微信朋友圈日均10亿发布量[有视频] 》
《 快速裂变:见证微信强大后台架构从0到1的演进历程(一) 》
《 快速裂变:见证微信强大后台架构从0到1的演进历程(二) 》
《 微信团队原创分享:Android内存泄漏监控和优化技巧总结 》
《 微信团队原创Android资源混淆工具:AndResGuard [有源码] 》
《 微信“红包照片”背后的技术难题 》
《 移动端IM实践:Android版微信如何大幅提升交互性能(一) 》
《 移动端IM实践:Android版微信如何大幅提升交互性能(二) 》
《 移动端IM实践:实现Android版微信的智能心跳机制 》
《 移动端IM实践:WhatsApp、Line、微信的心跳策略分析 》
《 移动端IM实践:谷歌消息推送服务(GCM)研究(来自微信) 》
《 移动端IM实践:iOS版微信的多设备字体适配方案探讨 》
《 信鸽团队原创:一起走过 iOS10 上消息推送(APNS)的坑 》
《 IPv6技术详解:基本概念、应用现状、技术实践(上篇) 》
《 IPv6技术详解:基本概念、应用现状、技术实践(下篇) 》
《 腾讯TEG团队原创:基于MySQL的分布式数据库TDSQL十年锻造经验分享 》
《 微信多媒体团队访谈:音视频开发的学习、微信的音视频技术和挑战等 》
《 了解iOS消息推送一文就够:史上最全iOS Push技术详解 》
《 腾讯资深架构师干货总结:一文读懂大型分布式系统设计的方方面面 》
《 微信多媒体团队梁俊斌访谈:聊一聊我所了解的音视频技术 》
《 腾讯音视频实验室:使用AI黑科技实现超低码率的高清实时视频聊天 》
《 腾讯技术分享:微信小程序音视频与WebRTC互通的技术思路和实践 》
《 手把手教你读取Android版微信和手Q的聊天记录(仅作技术研究学习) 》
>> 更多同类文章 ……
(本文同步发布于: http://www.52im.net/thread-1992-1-1.html )
以上所述就是小编给大家介绍的《手把手教你读取Android版微信和手Q的聊天记录(仅作技术研究学习)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 小白解密安卓机上微信聊天记录
- python可视化文本分析(1)—分析QQ班群聊天记录宏观
- React 折腾记 - (7) 基于React+Antd封装聊天记录(用到memo,lazy, Suspense)这些
- Golang入坑笔记 – 整理了和她的9万条微信聊天记录,每天通过邮件分享给自己
- Telegram 支持删除聊天双方设备中的消息记录
- 3.64亿条中国用户聊天信息泄露到网上,涉及微信支付记录
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Android编程权威指南
[美] Bill Phillips、[美] Brian Hardy / 王明发 / 人民邮电出版社 / 2014-4 / CNY 99.00元
权威、全面、实用、易懂,是本书最大的特色。本书根据美国大名鼎鼎的Big Nerd Ranch训练营的Android培训讲义编写而成,已经为微软、谷歌、Facebook等行业巨头培养了众多专业人才。作者巧妙地把Android开发所需的庞杂知识、行业实践、编程规范等融入一本书中,通过精心编排的应用示例、循序渐进的内容组织,以及循循善诱的语言,深入地讲解了Android开发的方方面面。如果学完一章之后仍......一起来看看 《Android编程权威指南》 这本书的介绍吧!