In this paper, synchronization to: www.jianshu.com/p/8d0d0b52b…

The target

At present, wechat web page version is more and more limited, so consider trying to achieve robot-like functions on mobile phones. The purpose of this article is to use Xposed to quickly achieve simple robot functions, including access to friends sent a message, and reply to the message. Intelligent reply can be added later, such as access to Turing robots, or their own custom implementation of some functions.

Rapid implementation

Set up the project framework

WechatSpellbook – Stand on the shoulders of giants

WechatSpellbook is a universal Xposed plug-in framework extracted from the wechat wizard author on the basis of wechat wizard. It provides a friendly API to automatically analyze the internal structural features of wechat (ignoring the differences between wechat versions), and optimizes the common problems of Hook wechat. In short, it will be easier to use hook wechat. Thanks for the author’s contribution, the project integration and detailed introduction can be found in Wiki. The implementation of the following steps is based on this framework. The following source code is based on wechat version 6.6.6, due to the use of WechatSpellbook framework dynamic matching principle, most of the wechat version can be automatically adapted.

Get messages from friends

The first step to achieve the robot function is to get the message sent by friends, after getting the message to reply, can be called “robot” bar. With WechatSpellbook, it is very easy to get messages. See the API to call back when a new message is stored in the database.

object WechatMessageHook : IMessageStorageHook { override fun onMessageStorageInserted(msgId: Long, msgObject: Any) {xposedbridge. log("onMessageStorageInserted msgId=$msgId,msgObject=$msgObject") Types of val field_content = XposedHelpers. GetObjectField (msgObject, "field_content") as a 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) {if (field_isSend == 1) {Copy the code

The meanings of the fields are as follows:

  • Field_content: message content
  • Field_talker: sender
  • Field_type: message type
  • Field_isSend: Who sent it, I sent it 1 so that’s all there is to it, the next step is how the robot sends the message back to the friend.

Robot replies to messages

To reply to a message, the robot needs to find the SENDING message API, hook it, and call it in our code.

Analysis using the Method Profiling feature of Monitor

First, open the wechat chat window in the simulator, open Monitor, select the wechat process, click Start Method Profiling, then send a random message in the chat window, and then click Stop Method Profiling to generate the analysis file. The analysis steps are as follows:

  1. Search for click, click the send button, must have triggered the click event, look for it
    image.png
  2. Found that calledChatFooter$3.onClick()The method, just by its name, should be right there, click on it, and see where this function is called
    image.png
  3. It calls forchatting.o.FZMethod, notice that the argument is String, and the return value is Boolean. Just to take a wild guess, the String is the text of the message, and the return value should be sent successfully or not. Verify, Hook this function directly, run to find that the guess is true, here is relatively simple not to paste the code.
  4. So far, we knowchatting.o.FZThe method is the one that sends the message, the argument is the text of the message, but there’s an important omission,Why is there no receiver parameter?The internal contact ID of wechat generally starts with WX_IDxxx. Where is the recipient ID set and how to set hook? Now there is only this problem.

    Here already know the API to send messages, hook off can do things, but the lack of receiver this important parameter Settings, analyze the source code.

Decompile to view source code analysis

Chatting. O.fiz method (chatting.

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

Then analyzed the yOg. YRO. Dt method, which is com. Tencent. Mm. UI. Chatting. The method of class b, look at the source code:

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(); / / omit}Copy the code

You can see in azvar.setContent (Xf); SetContent () is a method of the parent class of az, com.tencent. Mm.

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; }Copy the code

This class contains not only the text of the message, but also the receiver field_talker, the sending time field_createTime, etc. Bold guess, this class is a wrapper class for the message, containing all the attributes of the message. In this case, the field of interest is the receiver field_talker. As long as you know where the Ed method is called to hook off, you can do whatever you want. However, there are many places to call this through AS lookup, and it is impossible to determine where the specific message is called and how to do. Xposed analysis with the help of com.0700.mm.g.c.c. Ed () method, which is to set the receiver field_talker method, as long as the hook method, and then print out the call stack to see where exactly is the callback.

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 () // print stack log("set field_talker end")}})Copy the code

Print result:

image.png



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}); // omit a lot of code}Copy the code

You can see that the constructor of this class instantiates CG azVar = new az(); And calls the Ed () method. Str2 is the text content, the last few do not know, I guess this class is to send a message, from the source code is difficult to analyze, hook off to see. Hook com. Tencent. Mm. Modelmulti i. constructor printing parameters, see whether and sending a message. I won’t post the code and screenshots here, the conclusion is relevant. Hook the constructor of this class to send a message.

Hook key points found

  1. com.tencent.mm.ui.chatting.o.FZ(String)Method that takes the message text and can be called to send a message but not to set the receiver
  2. com.tencent.mm.modelmulti.i()Constructor where the 0th argument is the receiver ID and the first argument is the message text

Call the first API to send the text of the message, hook the second API to modify the receiver ID, and then you can happily send the message

Problems with key points

The problem with hook is that when you hook the second API, you don’t know who the receiver of the message is, so it’s not easy to set up.

Problem solving

Since I can hook these two apis, can I just call the first API and put the receiver ID in front of the text message, and then parse the receiver ID in the text message and assign it to the 0th parameter when HOOK the second API? New message text = receiver ID + delimiter + Real message text delimiter can use special characters that the user will not enter, such as \ T, etc

Code implementation

Source code here, key places are annotated, interested can star