Xposed第五課(微信篇) 聊天機(jī)器人

經(jīng)過了前段日子基于微信的學(xué)習(xí)饲宿,今天終于折騰除了一點干貨,微信聊天機(jī)器人迈勋。先上個圖

一對一聊天 群聊
device-2018-06-12-220825.gif
device-2018-06-12-221655.png

為了完成這個炬灭,人都蒙蔽了,不多說了開始分析把靡菇。

首先進(jìn)行如下圖的操作

DDMS 軌跡
QQ截圖20180612223149.png
QQ截圖20180612223157.png

左圖選中要記錄的進(jìn)程點擊左圖右上角那個圖標(biāo)進(jìn)行軌跡錄制之后重归,再次點擊這個停止錄制,就有了右邊的圖厦凤,我是從點擊微信發(fā)送按鈕開始錄制的鼻吮,所以在有圖下面輸入過濾關(guān)鍵字 onClick

public final boolean FZ(String str) {
        mS(false);
        ctQ();
        return this.yOg.yRO.dt(str, 0);
    }

從這個開始入手進(jìn)入 dt(String str, int i)

public final boolean dt(String str, int i) {
        ...此處省略...
        this.ejx.cuJ().post(new y$1(this, Xf, i));
        this.ejx.mZ(true);
        return true;
    }

接下來是關(guān)鍵點了,后面關(guān)聯(lián)的地方有多處泳唠,我一一列出來
首先是 y$1

package com.tencent.mm.ui.chatting.b;

import android.database.Cursor;
import com.tencent.mm.ac.l;
import com.tencent.mm.ai.a;
import com.tencent.mm.compatible.e.n;
import com.tencent.mm.modelmulti.i;
import com.tencent.mm.plugin.appbrand.jsapi.audio.d;
import com.tencent.mm.plugin.bbom.h;
import com.tencent.mm.plugin.report.service.g;
import com.tencent.mm.pluginsdk.ui.chat.ChatFooter;
import com.tencent.mm.sdk.platformtools.an;
import com.tencent.mm.sdk.platformtools.bh;
import com.tencent.mm.sdk.platformtools.w;
import com.tencent.mm.storage.aw;
import com.tencent.mm.storage.ax;
import com.tencent.mm.storage.bj;
import com.tencent.mm.storage.x;
import com.tencent.mm.z.au;
import com.tencent.mm.z.br;
import com.tencent.mm.z.q;
import com.tencent.mm.z.s;
import java.util.LinkedList;

class y$1 implements Runnable {
    final /* synthetic */ String fNk;
    final /* synthetic */ int ra;
    final /* synthetic */ y yXJ;

    y$1(y yVar, String str, int i) {
        this.yXJ = yVar;
        this.fNk = str;
        this.ra = i;
    }

    public final void run() {
        g.vX(20);
        int i = (this.yXJ.ejx.cuz().field_username.equals("medianote") && (q.GG() & 16384) == 0) ? 1 : 0;
        if (i != 0) {
            this.yXJ.ejx.cuM();
            au.Dv().a(new a(this.yXJ.ejx.cuz().field_username, this.fNk), 0);
            return;
        }
        String cuB;
        if (this.yXJ.ejx.cuP().getCount() == 0 && x.XM(this.yXJ.ejx.ctS())) {
            br.ID().c(10076, new Object[]{Integer.valueOf(1)});
        }
        String ctS = this.yXJ.ejx.ctS();
        int hC = s.hC(ctS);
        String str = this.fNk;
        q qVar = this.yXJ.yRE;
        if (qVar.ejx.cuC()) {
            w.i("MicroMsg.ChattingUI.LbsImp", "[oneliang]encrypt:" + qVar.ejx.wG() + ",raw:" + qVar.ejx.cuB());
            cuB = bh.oB(qVar.ejx.wG()) ? qVar.ejx.cuB() : qVar.ejx.wG();
        } else {
            cuB = ctS;
        }
        if (bh.oB(cuB)) {
            w.w("MicroMsg.ChattingUI.TextImp", "tempUser is null");
            return;
        }
        ChatFooter cuS = this.yXJ.ejx.cuS();
        int i2 = this.ra;
        int i3 = cuS.vST.vTU.containsKey(ctS) ? ((LinkedList) cuS.vST.vTU.get(ctS)).size() > 0 ? d.CTRL_INDEX : i2 : i2;
        l iVar = new i(cuB, str, hC, i3, this.yXJ.ejx.cuS().fu(ctS, str));
        q qVar2 = this.yXJ.yRE;
        if (qVar2.ejx.cuC()) {
            aw awVar;
            cuB = qVar2.klH;
            ax SB = com.tencent.mm.bb.d.SB();
            String wG = qVar2.ejx.wG();
            Cursor b = SB.fOK.b("SELECT * FROM " + SB.getTableName() + " where sayhiencryptuser=? and isSend=0 and flag=0" + " ORDER BY createtime desc LIMIT 1", new String[]{wG}, 2);
            if (b == null) {
                awVar = null;
            } else if (b.moveToFirst()) {
                awVar = new aw();
                awVar.c(b);
                b.close();
            } else {
                b.close();
                awVar = null;
            }
            if (!(awVar == null || bh.oB(awVar.field_ticket))) {
                cuB = awVar.field_ticket;
            }
            if (bh.oB(cuB)) {
                awVar = com.tencent.mm.bb.d.SB().YM(qVar2.ejx.wG());
                if (!(awVar == null || bh.oB(awVar.field_ticket))) {
                    cuB = awVar.field_ticket;
                }
            }
            if (cuB != null) {
                iVar.gJj = new h(cuB);
            }
        }
        au.Dv().a(iVar, 0);
        if (s.hy(ctS)) {
            au.Dv().a(new com.tencent.mm.plugin.setting.model.i(com.tencent.mm.compatible.e.q.zI(), this.fNk + " key " + bj.cnt() + " local key " + bj.cns() + "NetType:" + an.getNetTypeString(this.yXJ.ejx.cuH().getContext().getApplicationContext()) + " hasNeon: " + n.zj() + " isArmv6: " + n.zl() + " isArmv7: " + n.zk()), 0);
        }
    }
}

這個類就是 消息處理的過程 調(diào)用關(guān)鍵方法
au.Dv().a(l lVar, int i)

我在分析到這個地方被卡了很久

這個au.Dv().a(l lVar, int i) 有三種形式

以下面的方式調(diào)用可以實現(xiàn)自動回復(fù)狈网,自能自己看到宙搬,對方看不到消息

au.Dv().a(new a(this.yXJ.ejx.cuz().field_username, this.fNk), 0);

以下面的方式調(diào)用可以實現(xiàn)自動回復(fù)笨腥,自能自己看到,對方也能看到消息

       l iVar = new i(cuB, str, hC, i3, this.yXJ.ejx.cuS().fu(ctS, str));
       
au.Dv().a(iVar, 0);

以下面的方式暫未試驗勇垛,有興趣的小伙伴可以自己嘗試調(diào)用脖母,如果方便的話可以以反饋給我

            au.Dv().a(new com.tencent.mm.plugin.setting.model.i(com.tencent.mm.compatible.e.q.zI(), this.fNk + " key " + bj.cnt() + " local key " + bj.cns() + "NetType:" + an.getNetTypeString(this.yXJ.ejx.cuH().getContext().getApplicationContext()) + " hasNeon: " + n.zj() + " isArmv6: " + n.zl() + " isArmv7: " + n.zk()), 0);

接下來就是給上面的方法拼接參數(shù),等會給的代碼里面會提到闲孤。這里就不多重復(fù)了

到此我們能夠回復(fù)了谆级,那么我們回復(fù)的時機(jī)是什么呢烤礁?
于是我們需要找到什么時候收到消息,然后進(jìn)行調(diào)用上面的代碼

重復(fù)DDMS的操作進(jìn)行接收消息通知的篩選 如下圖

QQ截圖20180612232854.png

看到篩選后關(guān)鍵類com.tencent.mm.booter.notification.b$1

package com.tencent.mm.booter.notification;

import android.os.Looper;
import android.os.Message;
import com.tencent.mm.sdk.platformtools.ac;
import com.tencent.mm.sdk.platformtools.af;
import com.tencent.mm.sdk.platformtools.w;

class b$1 extends af {
    final /* synthetic */ b fEw;

    b$1(b bVar, Looper looper) {
        this.fEw = bVar;
        super(looper);
    }

    public final void handleMessage(Message message) {
        super.handleMessage(message);
        ac.getContext().getSharedPreferences("notify_prep", 0).edit().putBoolean("longNoopIntervalFlag", true).apply();
        String string = message.getData().getString("notification.show.talker");
        String string2 = message.getData().getString("notification.show.message.content");
        int i = message.getData().getInt("notification.show.message.type");
        int i2 = message.getData().getInt("notification.show.tipsflag");
        w.i("MicroMsg.MMNotification", "notify need to deal: %s", new Object[]{string});
        try {
            if (message.what == 1) {
                b.a(this.fEw, string, string2, i, i2, true);
            } else {
                b.a(this.fEw, string, string2, i, i2, false);
            }
        } catch (Throwable e) {
            w.printErrStackTrace("MicroMsg.MMNotification", e, "showNotifiHandler", new Object[0]);
        }
    }
}

看著這關(guān)鍵字貌似挺匹配的肥照,于是進(jìn)行hook發(fā)現(xiàn)確實接收的時候會執(zhí)行這里

結(jié)合上面的得到如下代碼

@Override
    public void autoRepeat() {

        final Class<?> afClass = XposedHelpers.findClass("com.tencent.mm.sdk.platformtools.af", mClassLoader);
//        //獲取收到消息的標(biāo)記通知欄
        Class<?> b$1Class = XposedHelpers.findClass("com.tencent.mm.booter.notification.b$1", mClassLoader);
        XposedHelpers.findAndHookMethod(b$1Class, "handleMessage", Message.class, new XC_MethodHook() {
            @Override
            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                super.beforeHookedMethod(param);
                Message message = (Message) param.args[0];
                final String string = message.getData().getString("notification.show.talker");
                String string2 = message.getData().getString("notification.show.message.content");
                int i = message.getData().getInt("notification.show.message.type");
                int i2 = message.getData().getInt("notification.show.tipsflag");
                LogUtils.i(string, string2, i, i2, afClass);
                Class<?> gClass = XposedHelpers.findClass("com.tencent.mm.kernel.g", mClassLoader);
                Object g = XposedHelpers.callStaticMethod(gClass, "Ea");
                Object filedA = XposedHelpers.getObjectField(g, "fVR");

                Class<?> oClass = XposedHelpers.findClass("com.tencent.mm.ac.o", mClassLoader);
                Class<?> lClass = XposedHelpers.findClass("com.tencent.mm.ac.l", mClassLoader);
                Method methodA = XposedHelpers.findMethodExact(oClass, "a", lClass, int.class);
                Object o = XposedHelpers.callStaticMethod(oClass, "a", filedA);

                //這里只能自己看到回復(fù)消息 對方看不到
//                Class<?> aClass = XposedHelpers.findClass("com.tencent.mm.ai.a", mClassLoader);
//                Object a = XposedHelpers.newInstance(aClass, new Class[]{String.class, String.class}, string, "haha");
//                Object[] p = new Object[]{a, 0};

                //調(diào)用這里可以實現(xiàn)自動回復(fù) 并發(fā)送到對方
                Class<?> iClass = XposedHelpers.findClass("com.tencent.mm.modelmulti.i", mClassLoader);
                Object io = XposedHelpers.newInstance(iClass, new Class[]{String.class, String.class, int.class, int.class, Object.class}, string, "haha", 1, 1, new HashMap<String, String>() {{
                    put(string, string);
                }});
                Object[] pp = new Object[]{io, 0};
//                LogUtils.i(gClass, g, filedA, oClass, lClass, methodA, o, aClass, a, p, iClass, io, pp);
                LogUtils.i(gClass, g, filedA, oClass, lClass, methodA, o, iClass, io, pp);
                try {
//                    XposedBridge.invokeOriginalMethod(methodA, o, p);

                    XposedBridge.invokeOriginalMethod(methodA, o, pp);
                } catch (Exception e) {
                    e.printStackTrace();
                    LogUtils.e(e.getLocalizedMessage());
                }
                LogUtils.i("send ok");
            }
        });

    }

令我感到頭痛的是在進(jìn)行匹配參數(shù)脚仔,因為有些關(guān)聯(lián)的引用不是很明顯,找的時候有很多誤區(qū)舆绎。
根據(jù)以上內(nèi)容今后可以擴(kuò)展聊天機(jī)器人鲤脏,群助手,以及方便學(xué)習(xí)微信搶紅包的流程吕朵。哈哈

以上為本課內(nèi)容猎醇,我所有的內(nèi)容都是以學(xué)習(xí)為主,請各位小伙伴勿要用來違法努溃,造成的一切違法后果與本人無任何關(guān)系

垃圾代碼已上傳

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末硫嘶,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子梧税,更是在濱河造成了極大的恐慌沦疾,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贡蓖,死亡現(xiàn)場離奇詭異曹鸠,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)斥铺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進(jìn)店門彻桃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人晾蜘,你說我怎么就攤上這事邻眷。” “怎么了剔交?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵肆饶,是天一觀的道長。 經(jīng)常有香客問我岖常,道長驯镊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任竭鞍,我火速辦了婚禮板惑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘偎快。我一直安慰自己冯乘,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布晒夹。 她就那樣靜靜地躺著裆馒,像睡著了一般姊氓。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上喷好,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天翔横,我揣著相機(jī)與錄音,去河邊找鬼梗搅。 笑死棕孙,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的些膨。 我是一名探鬼主播蟀俊,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼订雾!你這毒婦竟也來了肢预?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤洼哎,失蹤者是張志新(化名)和其女友劉穎烫映,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體噩峦,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡锭沟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了识补。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片族淮。...
    茶點故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖凭涂,靈堂內(nèi)的尸體忽然破棺而出祝辣,到底是詐尸還是另有隱情,我是刑警寧澤切油,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布蝙斜,位于F島的核電站,受9級特大地震影響澎胡,放射性物質(zhì)發(fā)生泄漏孕荠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一攻谁、第九天 我趴在偏房一處隱蔽的房頂上張望稚伍。 院中可真熱鬧,春花似錦巢株、人聲如沸槐瑞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽困檩。三九已至,卻和暖如春那槽,著一層夾襖步出監(jiān)牢的瞬間悼沿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工骚灸, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留糟趾,地道東北人。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓甚牲,卻偏偏與公主長得像义郑,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子丈钙,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,543評論 2 349