randompath 发表于 2019-8-27 16:29

Xposed微信之发送文件

本帖最后由 randompath 于 2019-8-27 16:34 编辑


本文主要介绍通过静态分析微信发送文件的逻辑,并通过xposed实现文件自动发送。分析的微信版本为当前最新版(v7.0.6),分析工具jadx+AS。
## 分析过程
### 选择文件
点击文件,使用hierachyViewer查看选择文件界面是com.tencent.mm.pluginsdk.ui.tools.NewFileExplorerUI类。我们知道安卓Activity间发送数据主要是通过Intent,所以在NewFileExplorerUI中查找`new Intent`,找到以下代码
```
      newFileExplorerUI.wOI.a(new com.tencent.mm.ui.widget.b.c.a.b() {
            public final void bEL() {
                AppMethodBeat.i(28179);
                Intent intent = new Intent();
                intent.setClass(NewFileExplorerUI.this.getContext(), NewFileExplorerUI.class);
                intent.putExtra("explorer_mode", 1);
                intent.putStringArrayListExtra("selected_file_lst", NewFileExplorerUI.this.wOF.dxa());
                intent.putStringArrayListExtra("key_select_video_list", NewFileExplorerUI.this.wOF.dxc());
                intent.putStringArrayListExtra("CropImage_OutputPath_List", NewFileExplorerUI.this.wOF.dxb());
                intent.putExtra("GalleryUI_ToUser", NewFileExplorerUI.this.toUserName);
                NewFileExplorerUI.this.startActivityForResult(intent, 0);
                AppMethodBeat.o(28179);
            }
      });
      newFileExplorerUI.wOI.Nc(R.string.u5).a(new com.tencent.mm.pluginsdk.ui.applet.q.a() {
            public final void a(boolean z, String str, int i) {
                AppMethodBeat.i(28180);
                NewFileExplorerUI.this.hideVKB();
                if (z) {
                  Intent intent = new Intent();
                  intent.putStringArrayListExtra("selected_file_lst", NewFileExplorerUI.this.wOF.dxa());
                  intent.putStringArrayListExtra("key_select_video_list", NewFileExplorerUI.this.wOF.dxc());
                  intent.putStringArrayListExtra("CropImage_OutputPath_List", NewFileExplorerUI.this.wOF.dxb());
                  intent.putExtra("GalleryUI_ToUser", NewFileExplorerUI.this.toUserName);
                  intent.putExtra("with_text_content", str);
                  NewFileExplorerUI.this.setResult(-1, intent);
                  NewFileExplorerUI.this.finish();
                }
                AppMethodBeat.o(28180);
            }
      }).gQP.show();
```
猜测这两个匿名内部类至少有一个是发送button的回调,而且其回调方法的逻辑很相似。发送时如果选择的文件中有图片,会把文件与图片分开发送,所以我们猜测
* `selected_file_lst`是选择的待上传文件路径
* `CropImage_OutputPath_List`是选择的图片路径
* `with_text_content` 是发送文件时捎带的留言

### 发送文件
在源码中搜索`selected_file_lst`关键字, 定位到类`com/tencent/mm/ui/chatting/p`。
在文件最后找到发送代码
```
   static /* synthetic */ void a(p pVar, com.tencent.mm.ui.chatting.d.a aVar, int i, Intent intent) {
      AppMethodBeat.i(156138);
      if (i == -1 && intent != null) {
            int om;
            String str;
            ((aa) aVar.aU(aa.class)).g(217, i, intent);
            ArrayList stringArrayListExtra = intent.getStringArrayListExtra("selected_file_lst");
            if (aVar.dRl()) {
                om = com.tencent.mm.model.n.om(pVar.AiD);
            } else {
                om = 0;
            }
            Iterator it = stringArrayListExtra.iterator();
            while (it.hasNext()) {
                // 准备文件对象
                String str2 = (String) it.next();
                WXFileObject wXFileObject = new WXFileObject();
                // 文件路径
                wXFileObject.setFilePath(str2);
                WXMediaMessage wXMediaMessage = new WXMediaMessage();
                wXMediaMessage.mediaObject = wXFileObject;
                File file = new File(str2);
                // 文件名称
                wXMediaMessage.title = file.getName();
                // 文件大小
                wXMediaMessage.description = bo.hw(file.length());
                // 发送文件
                com.tencent.mm.pluginsdk.model.app.l.a(wXMediaMessage, "", "", pVar.AiD, 4, null);
                int lastIndexOf = file.getName().lastIndexOf(".");
                str = "";
                if (lastIndexOf >= 0 && lastIndexOf < file.getName().length() - 1) {
                  str = file.getName().substring(lastIndexOf + 1);
                }
                h hVar = h.rdc;
                Object[] objArr = new Object;
                objArr = Long.valueOf(file.length());
                objArr = Integer.valueOf(0);
                objArr = Integer.valueOf(aVar.dRl() ? 1 : 0);
                objArr = Integer.valueOf(om);
                objArr = str;
                hVar.e(14986, objArr);
            }
            str = intent.getStringExtra("with_text_content");
            if (!bo.isNullOrNil(str)) {
                com.tencent.mm.plugin.messenger.a.g.bWU().ft(str, pVar.AiD);
            }
      }
      AppMethodBeat.o(156138);
    }
```
在准备文件对象时只有`bo.hw(file.length())`的含义不明,跟进去发现这个方法将文件大小转为字符串,简单翻译后代码如下
```
private String getFileDesc(long filesize) {
      long j = filesize;
      if ((j >> 30) > 0) {
            // GB
            return (((double) Math.round((((double) j) * 10.0d) / 1.073741824E9d)) / 10.0d) + " GB";
      }
      if ((j >> 20) > 0) {
            // MB
            return (((double) Math.round((((double) j) * 10.0d) / 1048576.0d)) / 10.0d) + " MB";
      }
      if ((j >> 9) <= 0) {
            return j + " B";
      }
      return (((double) Math.round((((double) j) * 10.0d) / 1024.0d)) / 10.0d) + " KB";

    }
```

`com.tencent.mm.pluginsdk.model.app.l.a(wXMediaMessage, "", "", pVar.AiD, 4, null);` 就是发送文件的调用。发送文件需要两个必要参数:文件对象与接收者的微信id,所以猜测`pVar.AiD`就是接收方的微信id,对于发送对象可以通过反射构建。分析到这里就可以了,接下来试着编写发送文件的代码
```
public class AttachSendingHook {
    Class IMediaObject;
    Class mediaMsgSenderClz, fileObjectClz, mediaMessageClz;

    public AttachSendingHook(XC_LoadPackage.LoadPackageParam lpparam) {
      ClassLoader cl = lpparam.classLoader;
      IMediaObject = XposedHelpers.findClass("com.tencent.mm.opensdk.modelmsg.WXMediaMessage$IMediaObject", cl);
      fileObjectClz = XposedHelpers.findClass("com.tencent.mm.opensdk.modelmsg.WXFileObject", cl);
      mediaMessageClz = XposedHelpers.findClass("com.tencent.mm.opensdk.modelmsg.WXMediaMessage", cl);
      mediaMsgSenderClz = XposedHelpers.findClass("com.tencent.mm.pluginsdk.model.app.l", cl);
    }

    public void sendAttach(String recvr, String filePath) {
      File file = new File(filePath);
      if (file.exists() && file.isFile()) {
            debug("向 %s 发送文件: %s", recvr, filePath);
            Object fileObject = XposedHelpers.newInstance(fileObjectClz);
            XposedHelpers.callMethod(fileObject, "setFilePath", filePath);
            Object mediaObject = XposedHelpers.newInstance(mediaMessageClz);
            XposedHelpers.setObjectField(mediaObject, "mediaObject", IMediaObject.cast(fileObject));
            XposedHelpers.setObjectField(mediaObject, "title", file.getName());
            XposedHelpers.setObjectField(mediaObject, "description", getFileDesc(file.length()));
            // com.tencent.mm.pluginsdk.model.app.l.a(wXMediaMessage, "", "", pVar.xVs, 4, null);
            XposedHelpers.callStaticMethod(mediaMsgSenderClz, "a", mediaObject, "", "", recvr, 4, null);
      } else {
            error("文件不存在,无法发送 %s", filePath);
      }
    }

    private String getFileDesc(long filesize) {
      long j = filesize;
      if ((j >> 30) > 0) {
            // GB
            return (((double) Math.round((((double) j) * 10.0d) / 1.073741824E9d)) / 10.0d) + " GB";
      }
      if ((j >> 20) > 0) {
            // MB
            return (((double) Math.round((((double) j) * 10.0d) / 1048576.0d)) / 10.0d) + " MB";
      }
      if ((j >> 9) <= 0) {
            return j + " B";
      }
      return (((double) Math.round((((double) j) * 10.0d) / 1024.0d)) / 10.0d) + " KB";

    }
}
```
测试发送成功,效果如下
![自动发送文件效果](http://www.wisedream.net/res/img/tricks/wx_sendfile.png)

对于发送文件的分析思路大体就是这样,而发送图片与短视频的分析过程与此类似,这里就不再赘述,有兴趣的自己研究下吧。

我根号四你 发表于 2020-3-9 18:47

楼主你好,你能把发送图片的部分补上嘛?我根据你的思路 发现 WXFileObject 之外 还有一个 WXImageObject 对象,有一个 setImagePath 方法,我把 WXFileObject 修改为 WXImageObject 测试后,发现无法把图片发出去,显示 感叹号。还有我 hookcom.tencent.mm.pluginsdk.model.app.l.aa 方法 在发送图片的时候 都没有调用,只有在发送文件的时候才调用了。思路到这里就断了。感谢&#128591;,希望楼主回复我。

randompath 发表于 2019-8-29 13:29

快没时间了 发表于 2019-8-29 10:04
楼主能弄过发送语音吗,还有楼主有研究过修过Xposed包名吗

发语音没做过,不过我可以跟你说下思路,你可以从录音的相关api入手,在录音结束后肯定有发送音频文件的接口,直接调这个接口就行了。微信的语音文件是slk,如果要发送mp3需要先编码。简单的改包名只要改manifest文件后重打包就行了,但如果原包有签名检测就麻烦了。

XiaoYuGan0103 发表于 2019-8-27 17:31

楼主真棒

lizhipei78 发表于 2019-8-27 17:56

微信不是直接可以发文件的吗?

丶那年如此年少o 发表于 2019-8-27 18:06

楼主,视频也可以调用这个接口发吗?

BlueTears_ 发表于 2019-8-27 18:32

感谢楼主分享

airborne 发表于 2019-8-27 19:04

感谢楼主分享,抽空好好研究一下

qwe124040 发表于 2019-8-27 19:16

学习,。感谢分享

icehappin 发表于 2019-8-27 20:51

感谢楼主分享,抽空好好研究

dxlulu 发表于 2019-8-27 20:53

学习,。感谢分享

简讯丶 发表于 2019-8-27 23:12

好厉害的样子
页: [1] 2 3 4
查看完整版本: Xposed微信之发送文件