利用 Xposed 快速實現(xiàn)一個簡易微信機(jī)器人

目標(biāo)

當(dāng)前微信網(wǎng)頁版限制越來越多九杂,考慮嘗試在手機(jī)上實現(xiàn)類似機(jī)器人的功能。本文目的是利用 Xposed 快速實現(xiàn)簡易機(jī)器人功能足画,包括獲取好友發(fā)來的消息嚷狞,以及回復(fù)消息块促。后續(xù)可以增加智能回復(fù),比如接入圖靈機(jī)器人床未,或者自己自定義實現(xiàn)一些功能竭翠。

快速實現(xiàn)

項目框架的搭建

WechatSpellbook - 站在"巨人"的肩膀上

WechatSpellbook 是微信巫師作者在微信巫師的基礎(chǔ)提取出來的通用微信 Xposed 插件框架。它提供了友好的的 API薇搁,提供自動分析微信內(nèi)部結(jié)構(gòu)特征的API(忽略微信版本差異)斋扰,對 hook 微信出現(xiàn)的常見問題都做了優(yōu)化,總之就是使用它會更容易對微信 hook啃洋,感謝作者的貢獻(xiàn)传货,項目的集成和詳細(xì)介紹參見wiki,以下步驟的實現(xiàn)都是基于這個框架的宏娄。
以下源碼均基于微信 6.6.6 版本问裕,由于使用了 WechatSpellbook 框架動態(tài)匹配的原理,大部分微信版本均可自動適配绝编。

獲得好友發(fā)來的消息

實現(xiàn)機(jī)器人功能的首要步驟就是獲得好友發(fā)來的消息僻澎,獲得消息之后才能回復(fù)吧,才能叫“機(jī)器人”吧十饥。
使用了 WechatSpellbook,獲取消息是很容易的祖乳,參見api逗堵,當(dāng)新消息存入數(shù)據(jù)庫后回調(diào),具體代碼:

object WechatMessageHook : IMessageStorageHook {
    override fun onMessageStorageInserted(msgId: Long, msgObject: Any) {
        XposedBridge.log("onMessageStorageInserted msgId=$msgId,msgObject=$msgObject")
        // 這些都是消息的屬性眷昆,內(nèi)容蜒秤,發(fā)送人汁咏,類型等
        val field_content = XposedHelpers.getObjectField(msgObject, "field_content") as String?
        val field_talker = XposedHelpers.getObjectField(msgObject, "field_talker") as String?
        val field_type = (XposedHelpers.getObjectField(msgObject, "field_type") as Int).toInt()
        val field_isSend = (XposedHelpers.getObjectField(msgObject, "field_isSend") as Int).toInt()
        XposedBridge.log("field_content=$field_content,field_talker=$field_talker," +
                "field_type=$field_type,field_isSend=$field_isSend")
        if (field_isSend == 1) {// 代表自己發(fā)出的,不處理
            return
        }
        // 做其他事情
    }
}

其中字段名含義如下:

  • field_content: 消息內(nèi)容
  • field_talker: 發(fā)送者
  • field_type: 消息類型
  • field_isSend: 是誰發(fā)出的作媚,我自己發(fā)出為1
    這步到此就完成了攘滩,下一步是機(jī)器人怎么將消息回復(fù)給好友。

機(jī)器人回復(fù)消息

機(jī)器人回復(fù)消息需要找到發(fā)送消息出去這個 API纸泡,然后 hook 它漂问,在我們的代碼里調(diào)用就行了。

利用 Monitor 的 Method Profiling 功能分析

首先在模擬器中打開微信聊天窗口女揭,打開 Monitor蚤假,選中微信進(jìn)程,點擊Start Method Profiling吧兔,然后在聊天窗口隨便發(fā)送一條消息磷仰,然后回來點擊Stop Method Profiling,會生成分析文件境蔼。分析步驟如下:

  1. 先搜索 click灶平,點擊發(fā)送按鈕,肯定是觸發(fā)了點擊事件的嘛箍土,先找找看


    image.png
  2. 發(fā)現(xiàn)調(diào)用了 ChatFooter$3.onClick() 方法逢享,單從名字上來看,應(yīng)該就是這里了涮帘,點進(jìn)去拼苍,看這個函數(shù)調(diào)用了哪里
    image.png
  3. 它調(diào)用了 chatting.o.FZ 方法,注意參數(shù)是 String调缨,返回值是 Boolean疮鲫,大膽猜測一下,這個字符串就是消息文本弦叶,返回值應(yīng)該是發(fā)送是否成功俊犯。驗證一下,直接 Hook 這個函數(shù)伤哺,運行發(fā)現(xiàn)猜測是真的燕侠,這里比較簡單就不貼代碼了。
  4. 分析到這里立莉,已經(jīng)知道了chatting.o.FZ 方法就是發(fā)送消息的绢彤,參數(shù)就是消息文本,但是有個很重要的地方忽略了蜓耻,為什么沒有接收者參數(shù)茫舶?,微信內(nèi)部聯(lián)系人 ID 一般是以 wx_idxxx 開頭的刹淌,接收者 id 設(shè)置在哪饶氏,怎么設(shè)置 hook讥耗,現(xiàn)在就差這個問題了。
    到這里已經(jīng)知道了發(fā)送消息的 API疹启,hook 掉就可以搞事情了古程,但是缺少接收者這個重要參數(shù)的設(shè)置,分析下源碼吧喊崖。

反編譯查看源碼分析

反編譯之后分析 chatting.o.FZ 方法源碼:

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

然后分析yOg.yRO.dt方法挣磨,它是com.tencent.mm.ui.chatting.b類的方法,看下源碼:

    public final boolean dt(String str, int i) {
        int i2 = 0;
        String Xf = bh.Xf(str);
        if (Xf == null || Xf.length() == 0) {
            w.e("MicroMsg.ChattingUI.TextImp", "doSendMessage null");
            return false;
        }
        x xVar = this.yXC;
        if (!ah.oB(Xf)) {
            az azVar = new az();
            azVar.setContent(Xf);
            azVar.eW(1);
            xVar.aB(azVar);
        }
        bt btVar = new bt();
        // 省略
    }

可以看到在azVar.setContent(Xf);這里將發(fā)送的消息文本放在放在了az這個類中贷祈,setContent() 是 az 的父類com.tencent.mm.g.c.cg的方法趋急,看下這個類的源碼:

    // 截取了幾個方法
    public final void av(long j) {
        this.field_createTime = j;
        this.eRw = true;
    }

    public final long wQ() {
        return this.field_createTime;
    }

    public final void ed(String str) {
        this.field_talker = str;
        this.feh = true;
    }

    public final String wR() {
        return this.field_talker;
    }

    public final void setContent(String str) {
        this.field_content = str;
        this.eRE = true;
    }

只截取了幾個方法,可以看到這個類不僅僅包含消息文本势誊,還包含了接受者field_talker呜达,發(fā)送時間field_createTime等,大膽猜想粟耻,這個類就是消息的包裝類查近,包含消息所有的屬性,這里關(guān)注的字段是接收者 field_talker挤忙,只要知道在哪里調(diào)用了ed方法 hook 掉就可以為所欲為了霜威。
但是,通過 AS 查找調(diào)用這個的地方有很多册烈,根本無法判斷具體發(fā)消息是哪里調(diào)用了戈泼,怎么辦。
借助 Xposed 分析com.tencent.mm.g.c.cg.ed()方法赏僧,也就是設(shè)置接收者 field_talker 的方法大猛,只要 hook 這個方法,然后打印出調(diào)用堆椀砹悖看看到底是哪里回調(diào)了挽绩。

        val clz = XposedHelpers.findClass("com.tencent.mm.g.c.cg", WechatGlobal.wxLoader)
        XposedHelpers.findAndHookMethod(clz, "ed", String::class.java, object : XC_MethodHook() {
            override fun beforeHookedMethod(param: MethodHookParam?) {
                log("set field_talker start")
                LogUtil.logStackTraces() // 打印調(diào)用堆棧
                log("set field_talker end")
            }
        })

打印結(jié)果:

image.png

可以看到函數(shù)調(diào)用鏈,關(guān)鍵點在com.tencent.mm.modelmulti.i.<init>驾中,看下這個方法的源碼:

    public i(String str, String str2, int i, int i2, Object obj) {
        w.d("MicroMsg.NetSceneSendMsg", "dktext :%s", new Object[]{bh.cjG()});
        if (!bh.oB(str)) {
            cg azVar = new az();
            azVar.eV(1);
            azVar.ed(str);
            azVar.av(bd.in(str));
            azVar.eW(1);
            azVar.setContent(str2);
            azVar.setType(i);
            String a = a(((o) g.l(o.class)).s(azVar), obj, i2);
            if (!bh.oB(a)) {
                azVar.ej(a);
                w.d("MicroMsg.NetSceneSendMsg", "NetSceneSendMsg:MsgSource:%s", new Object[]{azVar.fnF});
        // 省略很多代碼
    }

可以看到這個類的構(gòu)造方法實例化了cg azVar = new az();唉堪,并調(diào)用了ed()方法。分析下這個構(gòu)造函數(shù)肩民,很有意思的是:參數(shù) str 就是微信 id唠亚,str2是文本內(nèi)容,后幾個不知道持痰,大膽猜測下這個類就是去發(fā)送消息的趾撵,從源碼很難分析,hook 掉看看共啃。
hook com.tencent.mm.modelmulti.i的構(gòu)造方法打印參數(shù)占调,看下是否和發(fā)送消息有關(guān)。這里就不貼代碼和截圖了移剪,結(jié)論是有關(guān)究珊。那可以 hook 這個類的構(gòu)造方法發(fā)送消息啊。

找到的 hook 關(guān)鍵點

  1. com.tencent.mm.ui.chatting.o.FZ(String) 方法纵苛,參數(shù)是消息文本剿涮,調(diào)用該方法可以發(fā)消息,但是無法設(shè)置接收者
  2. com.tencent.mm.modelmulti.i()構(gòu)造方法攻人,第0個參數(shù)是接收者 id取试,第1個參數(shù)是消息文本

機(jī)器人回復(fù)消息思路:調(diào)用第一個 API 發(fā)送消息文本,hook 第二個 API 修改接收者 id怀吻,然后就可以愉快的發(fā)消息了

關(guān)鍵點存在的問題

上述 hook 思路存在的問題:當(dāng) hook 第二個API 時瞬浓,不知道該條消息的接收者是誰,不太好設(shè)置蓬坡。

問題解決方法

既然我能 hook 這兩個 API猿棉,那么我可不可以直接在調(diào)用第一個 API 的時候,將接收者 id 放在文本消息前面屑咳,然后在 hook 第二個 API 時將文本消息中的接收者 id 解析出來賦值給第0個參數(shù)萨赁。
新消息文本 = 接收者ID + 分隔符號 + 真實消息文本
分割符號可以采用特殊字符,用戶不會輸入的字符兆龙,比如 \t 等

代碼實現(xiàn)

源碼在這里杖爽,關(guān)鍵地方都有注釋,有興趣可以 star

效果圖

image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末紫皇,一起剝皮案震驚了整個濱河市慰安,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌坝橡,老刑警劉巖泻帮,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異计寇,居然都是意外死亡锣杂,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進(jìn)店門番宁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來元莫,“玉大人,你說我怎么就攤上這事蝶押□獯溃” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長茎截。 經(jīng)常有香客問我苇侵,道長,這世上最難降的妖魔是什么企锌? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任榆浓,我火速辦了婚禮,結(jié)果婚禮上撕攒,老公的妹妹穿的比我還像新娘陡鹃。我一直安慰自己,他們只是感情好抖坪,可當(dāng)我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布萍鲸。 她就那樣靜靜地躺著,像睡著了一般擦俐。 火紅的嫁衣襯著肌膚如雪脊阴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天捌肴,我揣著相機(jī)與錄音蹬叭,去河邊找鬼。 笑死状知,一個胖子當(dāng)著我的面吹牛秽五,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播饥悴,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼坦喘,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了西设?” 一聲冷哼從身側(cè)響起瓣铣,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎贷揽,沒想到半個月后棠笑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡禽绪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年蓖救,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片印屁。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡循捺,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出雄人,到底是詐尸還是另有隱情从橘,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站恰力,受9級特大地震影響叉谜,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜牺勾,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一正罢、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧驻民,春花似錦、人聲如沸履怯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽叹洲。三九已至柠硕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間运提,已是汗流浹背蝗柔。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留民泵,地道東北人癣丧。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像栈妆,于是被迫代替她去往敵國和親胁编。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理鳞尔,服務(wù)發(fā)現(xiàn)嬉橙,斷路器,智...
    卡卡羅2017閱讀 134,651評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,074評論 25 707
  • 去年有段時間得空寥假,就把谷歌GAE的API權(quán)威指南看了一遍市框,收獲頗豐,特別是在自己幾乎獨立開發(fā)了公司的云數(shù)據(jù)中心之后...
    騎單車的勛爵閱讀 20,513評論 0 41
  • 文/云端一夢今早糕韧,一段記錄生活的文字讀一遍又一遍枫振,沒有詩意里面滿是平淡字里行間,寫滿你的笑顏最美的一天 中午兔沃,在廚...
    云端一夢l閱讀 1,073評論 32 60
  • ?曾經(jīng),我是一個默默無聞的人,沒有方向窍侧!沒有目標(biāo)县踢!真的很迷茫!總以為這樣平凡的過一生也好伟件!可是硼啤,現(xiàn)在現(xiàn)社會的壓力補(bǔ)...
    555106a62337閱讀 247評論 0 0