首先将取一个android手机QQ应用的一个版本取apk到电脑,使用jadx-gui或者任意的反编译软件进行反编译,拿到反编译后的代码这是准备阶段。

首先需要取到QQ内使用的Context上下文信息,这个步奏通常是反编译一个应用进行二次开发的第一步奏,我定位到了如下包中的一个类:“xxx.xxx.xxx.xxx.InjectUtils”能够分析定位到一个名字为injectExtraDexes的方法,在这个方法中能够Hook到application信息,QQ源码如下:

    public static synchronized String injectExtraDexes(Application application, boolean z) {String injectExtraDexesOat;synchronized (InjectUtils.class) {if (QLog.isColorLevel()) {QLog.i(TAG, 2, "Runtime : " + (isRuntimeART() ? "ART" : "Dalvik") + " ; sdk :" + VERSION.SDK_INT);}try {if (VERSION.SDK_INT >= 21) {injectExtraDexesOat = injectExtraDexesOat(application, z);} else {injectExtraDexesOat = injectExtraDexManual(application, z);}} catch (Throwable th) {QLog.e(TAG, 1, "", th);injectExtraDexesOat = null;}}return injectExtraDexesOat;}

当我们能够取到application的时候自然能够获取到QQ应用的包信息以及版本信息,就可以在代码中进行版本的适配,同时可以获取到父类的ClassLoader方便后续的Hook操作,本博客针对的是QQ 6.7.0v的一个能在网上找到的较为老的一个版本,可以更为方便的做代码分析样例来使用。新版本的QQ不清楚是否在技术层面支持了Xposed框架的检测以及各项功能接口代码的保护,还有可能对非正常人为的报错异常信息进行上传而封号的处理。

之后通过定位查找关键字,查找对应消息页面的反编译代码定位到一个叫做MessageHandlerUtils的工具类,相关代码如下:

    public static boolean a(QQAppInterface qQAppInterface, MessageRecord messageRecord, boolean z) {if (messageRecord == null || (messageRecord.msg == null && messageRecord.msgData == null)) {if (QLog.isColorLevel()) {QLog.w("Q.msg.MessageHandlerUtils", 2, "---------------msgFilter message [before filter] is null !");}return true;}StringBuilder stringBuilder;long currentTimeMillis = System.currentTimeMillis();if (QLog.isColorLevel()) {StringBuilder stringBuilder2 = new StringBuilder(256);stringBuilder2.append("---------------msgFilter istroop: ").append(messageRecord.istroop).append(" shmsgseq: ").append(messageRecord.shmsgseq).append(" friendUin: ").append(messageRecord.frienduin).append(" senderUin: ").append(messageRecord.senderuin).append(" msgType: ").append(messageRecord.msgtype).append(" time:").append(messageRecord.time).append(" msgContent: ").append(messageRecord.getLogColorContent()).append(" isNormalMsg: ").append(z);stringBuilder = stringBuilder2;} else {stringBuilder = null;}List<MessageRecord> b = qQAppInterface.a().b(messageRecord.frienduin, messageRecord.istroop);if (messageRecord.istroop == 1 || messageRecord.istroop == 1026 || messageRecord.istroop == 3000) {if (b != null && b.size() > 0) {for (MessageRecord a : b) {if (MsgProxyUtils.a(a, messageRecord, false, z)) {if (QLog.isColorLevel() && stringBuilder != null) {stringBuilder.append(" filterType: troop msg isNormalMsg=" + z);QLog.w("Q.msg.MessageHandlerUtils", 2, stringBuilder.toString());}MsgAutoMonitorUtil.a().h(System.currentTimeMillis() - currentTimeMillis);return true;}}}if (qQAppInterface.a().f(messageRecord)) {return true;}} else if (MsgProxyUtils.c(messageRecord.istroop)) {if (b != null && b.size() > 0) {for (MessageRecord a2 : b) {if (MsgProxyUtils.a(a2, messageRecord, z)) {if (QLog.isColorLevel() && stringBuilder != null) {stringBuilder.append(" filterType: " + messageRecord.istroop);QLog.w("Q.msg.MessageHandlerUtils", 2, stringBuilder.toString());}MsgAutoMonitorUtil.a().h(System.currentTimeMillis() - currentTimeMillis);return true;}}}if (qQAppInterface.a().f(messageRecord)) {return true;}} else if (messageRecord.istroop == 7220) {if (b != null && b.size() > 0) {for (MessageRecord a22 : b) {if (MsgProxyUtils.a(a22, messageRecord, true)) {if (QLog.isColorLevel() && stringBuilder != null) {stringBuilder.append(" filterType: other");QLog.w("Q.msg.MessageHandlerUtils", 2, stringBuilder.toString());}MsgAutoMonitorUtil.a().h(System.currentTimeMillis() - currentTimeMillis);return true;}}}} else if (b != null && b.size() > 0) {for (MessageRecord a222 : b) {if (a222.time == messageRecord.time && a222.msg.equals(messageRecord.msg)) {if (QLog.isColorLevel() && stringBuilder != null) {stringBuilder.append(" filterType: other");QLog.w("Q.msg.MessageHandlerUtils", 2, stringBuilder.toString());}MsgAutoMonitorUtil.a().h(System.currentTimeMillis() - currentTimeMillis);return true;}}}if (QLog.isColorLevel() && stringBuilder != null) {QLog.d("Q.msg.MessageHandlerUtils", 2, stringBuilder.toString());}MsgAutoMonitorUtil.a().h(System.currentTimeMillis() - currentTimeMillis);return false;}

在这个方法中就可以很轻易的拿到传到这个方法的参数信息,然后再根据getObjectField的方法拿到该对象的实例,我不知道该不该这样理解或是这样子表达,然后再上诉的QQ反编译的代码中,能够看到这个方法参数的实例MessageRecord.istroop/.friendui/.msg等等的信息,这样你单独在这里先停止下面的代码分析和处理,在这里单独的对上述得到的信息打Log,在控制台看看你到底得到的信息是什么,测试一下在往下分析也来得及。

经过log验证,你会发现你在QQ中接收到的消息就是 msg 其它的分别是qq号,群判断等...这些东西都能够在QQ的数据库中存储,你可以将QQ的数据库拿出来没用可视化工具查看QQ的数据库结构信息,分析消息部分每个字段代表的具体含义,但是我不清楚QQ数据库有没有KEY,微信数据库是有KEY的,但是能够通过Hook的方式拿到KEY,我想即便有密码也大同小异,我记得微信数据库密码是两个字段分别进行MD5和某一种加密方式的拼接在取其前几位的字段,这步掠过也可以的,因为这些东西是进行设置QQ机器人的各项回复依据,例如群消息不回复等...

然后你能够拿到好友对你发送的消息了,能够拿到这条消息的对应各项参数信息了,那你就能能够进行验证分析是否跟你本地配置的文件是否匹配,这决定了是否进行回复和回复那些消息内容甚至可以延时多少时间进行回复等操作...当然Hook的代码是依附于QQ的源码的,类似于一个病毒细菌一样,嵌入在了QQ的体内,所以的配置的东西在QQ内是无法获取的,不知道你们能不能明白,举个例子,你写的一个APP是Demo1,那么你写的本地界面设置的参数信息保存是保存在你Demo1的进程中,保存在了你Demo1对应的内容提供者的路径文件夹下,那么你无权去访问QQ的数据库SP它的进程。我不知道这样的比喻是否恰当但是却是常识性的东西,那么该怎么办?开始初学的时候我确实煞费苦心,但是其实Xposed框架早就准备了这样的一个共享的SP文件用来存储小量信息,我简称为XSP(XSharedPreferences),这篇博客主要讲解干货原理分析,所以设置本地的配置就不写了,先写死的东西,比如我用我的大号进行测试,然后小号给大号发送消息会自动回复,回复特别内容会回复特别的内容。

然后下面会遇到一个问题是如何查找到send发送消息的接口?我的办法是哪一步实行的发送动作,将消息发送出去了,就从哪里入手查找发送的功能代码,最后定位到了ChatActiviryFacade类,查找到了一个类似于发送消息的方法:

    public static long[] a(QQAppInterface qQAppInterface, Context context, SessionInfo sessionInfo, String str, ArrayList arrayList, SendMsgParams sendMsgParams) {if (QLog.isColorLevel()) {QLog.d("SendMsgBtn", 2, " sendMessage start currenttime:" + System.currentTimeMillis());}if (str == null || str.length() == 0 || sendMsgParams == null || sessionInfo == null || TextUtils.isEmpty(sessionInfo.f1386a)) {return null;}long[] a = a(qQAppInterface, sessionInfo, str, arrayList, sendMsgParams);ThreadManager.a(new ljr(sendMsgParams, qQAppInterface, sessionInfo), 8, null, true);if (!QLog.isColorLevel()) {return a;}QLog.d("SendMsgBtn", 2, " sendMessage end currenttime:" + System.currentTimeMillis());return a;}

整个类反编译后有4000多行的代码,你如何分析查找到关键处,是尤为重要的,我也只是一枚渣渣只能够凭借感觉和关键字还有一些运气成分去碰几个好像还挺像那么回事的代码片儿,然后用Hook去取这个方法的参数或是返回值去验证是否判断正确,有时候你hook不到信息,很有可能是你没有拿到一个正确的Context上下文对象,而如何能找到对应的Context也显得尤为重要。

那么你找到了关键的发送方法了,那么之后呢?当然是寻找对应参数的实例,然后使用callStaticMethod方法调用此方法,就能够将消息发送出去啦~到这里可以松一口气了,因为之前的代码大多是试探,或是根据自己的感觉,在或者根据页面的对应相关逻辑分析查找关键字,来一步一步的分析定位,或者发现这是一条死胡同,就赶紧撤回继续去找,很费时间也有一些看运气的成分,毕竟这是大厂的APP由几十人共同研发而成的代码,想一想把他攻克下来也是很有自豪感和成就感的,那好像就能松口气继续研究下去了。

回归正传查找定位这几个参数的实例的过程这里就省略了,直接上最后的Xposed代码,首先新建工程将Xposed的jar包以及准备工作完成,我再之前的博客里总结的基础的准备阶段,这里不明白的可以找我之前的博客,网上查也会查到一堆这里也不过多的详述了:

        findAndHookMethod(InjectUtils, loadPackageParam.classLoader, "injectExtraDexes",Application.class, boolean.class, new XC_MethodHook() {@Overrideprotected void afterHookedMethod(MethodHookParam param) throws Throwable {final Application application = (Application) param.args[0];PackageInfo packageInfo = application.getPackageManager().getPackageInfo(application.getPackageName(), 0);String QqVersion = packageInfo.versionName;Log.e("HookQQ", QqVersion);Common.initVersion(QqVersion);MessageUtil messageUtil = new MessageUtil(application.getClassLoader(), application);messageUtil.initAutoReply();}});

这部分是hook的主入口,之前会进行各程序进程包的剔除,寻找关键部分,然后进入到MessageUtils消息处理工具类中:

    public void initAutoReply() {sApplication = callStaticMethod(findClass(BaseApplicationImpl, classLoader), "getApplication");findAndHookMethod(MessageHandlerUtils, classLoader, "a",QQAppInterface,MessageRecord,boolean.class, new XC_MethodHook() {@Overrideprotected void afterHookedMethod(MethodHookParam param) throws Throwable {//                        if (!XPreferenceUtil.getMasterSwitch()) return;final String frienduin = getObjectField(param.args[1], "frienduin").toString();final String selfuin = getObjectField(param.args[1], "selfuin").toString();final String senderuin = getObjectField(param.args[1], "senderuin").toString();final int istroop = (int) getObjectField(param.args[1], "istroop");final String msg = getObjectField(param.args[1], "msg").toString();boolean isread = (boolean) getObjectField(param.args[1], "isread");Log.e("HookQQ","frienduin---" + frienduin);Log.e("HookQQ","selfuin---" + selfuin);Log.e("HookQQ","senderuin---" + senderuin);//                        String[] whiteList = XPreferenceUtil.getWhiteList();// 白名单 好友列表的数组 先写死// 查询内容为 qq号 !String[] whiteList = {"xxx","xxx","xxx"};if (whiteList.length != 0 && !senderuin.equals(selfuin)) {
//                            if (XPreferenceUtil.getNoReplyTroop() && istroop == 1) return;// 上面是群消息不接收与回复if(istroop == 1)return;iteratorWhiteList(frienduin, selfuin, msg, senderuin, istroop, isread, whiteList);}}});}

执行发送白名单的方法为iteratorWhiteList执行对字符串拆分然后遍历取相关信息与设置的白名单信息做比较,匹配后进入send方法:

    private void iteratorWhiteList(String frienduin, String selfuin, String msg, String senderuin, int istroop, boolean isread, String[] list) {for (String s : list) {if (frienduin.equals(s) && !isread && !frienduin.equals(selfuin)) {Log.e("HookQQ", "iteratorWhiteList is running...");ArrayList<Map<String, String>> keyReply = getKeyReply();if (keyReply != null && keyReply.size() != 0) {for (Map<String, String> stringMap: keyReply){if (stringMap.get("content").trim().equals(msg)) {send(frienduin, selfuin, istroop, stringMap.get("reply_content"));return;}}}send(frienduin, selfuin, istroop, "统一回复!!!");}}}

发送send方法如下:

    private void send(String frienduin, String selfuin, int istroop, String s) {Object qqAppInterface = callMethod(sApplication, "getAppRuntime", selfuin);Object sessionInfo = newInstance(findClass(SessionInfo, classLoader));Object sendMsgParams = newInstance(findClass(SendMsgParams, classLoader));// 嗯 很暴躁!XposedUtil.setField(sessionInfo, "a", frienduin, String.class);XposedUtil.setField(sessionInfo, "a", istroop, int.class);callStaticMethod(XposedHelpers.findClass(ChatActivityFacade, classLoader), "a",qqAppInterface, mContext, sessionInfo, s, new ArrayList<>(), sendMsgParams);}

    以上的代码只是部分,只是以分析和逻辑处理代码的分享,Hook的QQ具体包名类名,更多的都没有给出,然后接下来是测试验证好使的截图(回复谁的消息,关键字匹配回复等都是写死的,之前已经说过了,这里可以写的更为灵活,可以用socket或是Http去请求后台,在后台进行更为详细的判断之后再返回给前端具体的消息,后台也可以根据需求绑定图灵机器人的相关API接口,在回调给前台这样...反正随便你设计...最好的最灵活的方式肯定是这样了):

点个赞评论下呗~么么哒~

逆向分析QQ消息自动回复机器人设计相关推荐

  1. 教你一招!如何用 Python 实现 QQ 消息自动回复?

    作者:il_持之以恒_li https://blog.csdn.net/qq_45404396/article/details/112750110 前言 近段时间,看了一下运用python实现app自 ...

  2. 如何用 Python 实现 QQ 消息自动回复?

    作者:il_持之以恒_li https://blog.csdn.net/qq_45404396/article/details/112750110 前言 近段时间,看了一下运用python实现app自 ...

  3. 逆向学习QQ机器人——辅助资料

    在看了川川菜鸟的博客之后,基本上仿造了一个qq机器人,但是对其中原理并不是很清楚,对插件的写法也不是很清楚,在此逆向分析代码. 以下为原博主链接: 手把手教你python制作一个完整qq机器人_pyt ...

  4. python实现微信自动回复机器人+查看别人撤回的消息(部署到云服务器)

    python实现微信自动回复机器人+查看别人撤回的消息(部署到云服务器) 声明:仅供技术交流,请勿用于非法用途,如有其它非法用途造成损失,和本博客无关 目录 python实现微信自动回复机器人+查看别 ...

  5. python自动回复qq消息_基于python使用qqbot接入qq做一个简单的文字消息自动回复

    qqbot是一个免费开源的基于smartqq的python插件,如果默认安装有pip,则可以直接在命令行下执行:pip install qqbot安装qqbot,安装成功后可以在命令行输入qqbot ...

  6. python 微信自动回复机器人_python实现微信自动回复机器人+查看别人撤回的消息(部署到云服务器)...

    前言首先你的微信号能够登录网页版微信,才能打造你的专属个人微信号机器人,点击跳转网页版微信登录页面 类似的文章网上也都有,其实我也是受到别的文章的一些启发,因为不是每个人都想实现同样的功能的,直接套用 ...

  7. 逆向与分析-WebBrowserPassView消息分析

    逆向与分析-WebBrowserPassView消息分析 这个的源头是之前我写的一个博客: http://blog.csdn.net/u013761036/article/details/730427 ...

  8. 用 Python 自动回复 QQ 消息,附源码!

    前言 近段时间,看了一下运用python实现app自动化的视频,觉得那上面的讲的不错,于是就用所学的知识做了一个程序,实现自动回复QQ消息. 准备工作 1.1 安装client模块 打开命令窗口,输入 ...

  9. python自动化:实现自动回复QQ消息

    python自动化:实现自动回复QQ消息 前言 近段时间,看了一下运用python实现app自动化的视频,觉得那上面的讲的不错,于是就用所学的知识做了一个程序,实现自动回复QQ消息. 文章目录 pyt ...

  10. python qq消息接收存储_用 Python 自动回复 QQ 消息,附源码!

    前言 近段时间,看了一下运用python实现app自动化的视频,觉得那上面的讲的不错,于是就用所学的知识做了一个程序,实现自动回复QQ消息. 1. 准备工作 1.1 安装client模块 打开命令窗口 ...

最新文章

  1. ORACLE 数据的逻辑组成
  2. 用MATLAB画桌子,怎样用matlab编写桌子的动态变化图
  3. hbase shell
  4. linux ftp做yum源,在RedHat5下架设yum源服务器(FTP)
  5. ccna综合实验实训总结_实验室设备搬迁工作顺利展开
  6. 前端学习(2793):完成联系我们页面和地图
  7. C语言bound函数,C/C++-STL中lower_bound与upper_bound的用法以及cmp函数
  8. Redis数据类型--散列类型
  9. x86汇编语言——处理器架构
  10. 128x64液晶驱动(添加详细)
  11. php.ini 是否设置路由,php – 如何在路由INI文件中为Zend Framework中的子域编写路由链?...
  12. intouch负值显示0_17、定位的盒子居中显示
  13. vue $slot基本用法
  14. Maker工作室_激光雕刻机使用方法
  15. 海森矩阵介绍及其在机器学习、深度学习中的理解
  16. python 相关系数_Python计算皮尔逊 pearson相关系数
  17. js实现pc打开摄像头,拍照,下载
  18. 前端SPA(single page web application单页面应用not水疗)
  19. SQL SERVER 2016安装部署
  20. 桌面支持--skype登陆不上

热门文章

  1. linux查看占用负载的程序,Linux中查看负载
  2. 小柏实战学习Liunx(图文教程二十二)
  3. 智能营销获客引流-入门-宁波慧客科技有限公司
  4. 解决UmengSDK社会化分享过程中微信,QQ,新浪微博分享不成功的问题
  5. 《程序员》5月刊精彩内容预告
  6. 1.6 Go语言适合做什么
  7. 雅虎邮箱 转发设置_如何在Yahoo Mail中设置外出答复
  8. duilib设计器 DuiEditor简易教程 (DuiDesigner) (一)
  9. 数字图像处理(1): 数字图像处理领域应用——电磁波谱 可见光
  10. Min GW 安装教程(转载)