如何做到微信机器人不封号_利用 Xposed 快速实现一个简易微信机器人
本文同步至:github
目標
當前微信網頁版限制越來越多,考慮嘗試在手機上實現類似機器人的功能。本文目的是利用 Xposed 快速實現簡易機器人功能,包括獲取好友發來的消息,以及回復消息。后續可以增加智能回復,比如接入圖靈機器人,或者自己自定義實現一些功能。
快速實現
項目框架的搭建
WechatSpellbook - 站在"巨人"的肩膀上
WechatSpellbook 是微信巫師作者在微信巫師的基礎提取出來的通用微信 Xposed 插件框架。它提供了友好的的 API,提供自動分析微信內部結構特征的API(忽略微信版本差異),對 hook 微信出現的常見問題都做了優化,總之就是使用它會更容易對微信 hook,感謝作者的貢獻,項目的集成和詳細介紹參見wiki,以下步驟的實現都是基于這個框架的。
以下源碼均基于微信 6.6.6 版本,由于使用了 WechatSpellbook 框架動態匹配的原理,大部分微信版本均可自動適配。
獲得好友發來的消息
實現機器人功能的首要步驟就是獲得好友發來的消息,獲得消息之后才能回復吧,才能叫“機器人”吧。
使用了 WechatSpellbook,獲取消息是很容易的,參見api,當新消息存入數據庫后回調,具體代碼:
object WechatMessageHook : IMessageStorageHook {
override fun onMessageStorageInserted(msgId: Long, msgObject: Any) {
XposedBridge.log("onMessageStorageInserted msgId=$msgId,msgObject=$msgObject")
// 這些都是消息的屬性,內容,發送人,類型等
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) {// 代表自己發出的,不處理
return
}
// 做其他事情
}
}
其中字段名含義如下:
field_content: 消息內容
field_talker: 發送者
field_type: 消息類型
field_isSend: 是誰發出的,我自己發出為1
這步到此就完成了,下一步是機器人怎么將消息回復給好友。
機器人回復消息
機器人回復消息需要找到發送消息出去這個 API,然后 hook 它,在我們的代碼里調用就行了。
利用 Monitor 的 Method Profiling 功能分析
首先在模擬器中打開微信聊天窗口,打開 Monitor,選中微信進程,點擊Start Method Profiling,然后在聊天窗口隨便發送一條消息,然后回來點擊Stop Method Profiling,會生成分析文件。分析步驟如下:
先搜索 click,點擊發送按鈕,肯定是觸發了點擊事件的嘛,先找找看
發現調用了 ChatFooter$3.onClick() 方法,單從名字上來看,應該就是這里了,點進去,看這個函數調用了哪里
它調用了 chatting.o.FZ 方法,注意參數是 String,返回值是 Boolean,大膽猜測一下,這個字符串就是消息文本,返回值應該是發送是否成功。驗證一下,直接 Hook 這個函數,運行發現猜測是真的,這里比較簡單就不貼代碼了。
分析到這里,已經知道了chatting.o.FZ 方法就是發送消息的,參數就是消息文本,但是有個很重要的地方忽略了,為什么沒有接收者參數?,微信內部聯系人 ID 一般是以 wx_idxxx 開頭的,接收者 id 設置在哪,怎么設置 hook,現在就差這個問題了。
到這里已經知道了發送消息的 API,hook 掉就可以搞事情了,但是缺少接收者這個重要參數的設置,分析下源碼吧。
反編譯查看源碼分析
反編譯之后分析 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);這里將發送的消息文本放在放在了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,發送時間field_createTime等,大膽猜想,這個類就是消息的包裝類,包含消息所有的屬性,這里關注的字段是接收者 field_talker,只要知道在哪里調用了ed方法 hook 掉就可以為所欲為了。
但是,通過 AS 查找調用這個的地方有很多,根本無法判斷具體發消息是哪里調用了,怎么辦。
借助 Xposed 分析com.tencent.mm.g.c.cg.ed()方法,也就是設置接收者 field_talker 的方法,只要 hook 這個方法,然后打印出調用堆棧看看到底是哪里回調了。
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() // 打印調用堆棧
log("set field_talker end")
}
})
打印結果:
可以看到函數調用鏈,關鍵點在com.tencent.mm.modelmulti.i.,看下這個方法的源碼:
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});
// 省略很多代碼
}
可以看到這個類的構造方法實例化了cg azVar = new az();,并調用了ed()方法。分析下這個構造函數,很有意思的是:參數 str 就是微信 id,str2是文本內容,后幾個不知道,大膽猜測下這個類就是去發送消息的,從源碼很難分析,hook 掉看看。
hook com.tencent.mm.modelmulti.i的構造方法打印參數,看下是否和發送消息有關。這里就不貼代碼和截圖了,結論是有關。那可以 hook 這個類的構造方法發送消息啊。
找到的 hook 關鍵點
com.tencent.mm.ui.chatting.o.FZ(String) 方法,參數是消息文本,調用該方法可以發消息,但是無法設置接收者
com.tencent.mm.modelmulti.i()構造方法,第0個參數是接收者 id,第1個參數是消息文本
機器人回復消息思路:調用第一個 API 發送消息文本,hook 第二個 API 修改接收者 id,然后就可以愉快的發消息了
關鍵點存在的問題
上述 hook 思路存在的問題:當 hook 第二個API 時,不知道該條消息的接收者是誰,不太好設置。
問題解決方法
既然我能 hook 這兩個 API,那么我可不可以直接在調用第一個 API 的時候,將接收者 id 放在文本消息前面,然后在 hook 第二個 API 時將文本消息中的接收者 id 解析出來賦值給第0個參數。
新消息文本 = 接收者ID + 分隔符號 + 真實消息文本
分割符號可以采用特殊字符,用戶不會輸入的字符,比如 \t 等
代碼實現
源碼在這里,關鍵地方都有注釋,有興趣可以 star
效果圖
總結
以上是生活随笔為你收集整理的如何做到微信机器人不封号_利用 Xposed 快速实现一个简易微信机器人的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 大公司比较习惯问及的97道问题附答案
- 下一篇: 《你一年的8760小时》读后感