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)
对于发送文件的分析思路大体就是这样,而发送图片与短视频的分析过程与此类似,这里就不再赘述,有兴趣的自己研究下吧。 楼主你好,你能把发送图片的部分补上嘛?我根据你的思路 发现 WXFileObject 之外 还有一个 WXImageObject 对象,有一个 setImagePath 方法,我把 WXFileObject 修改为 WXImageObject 测试后,发现无法把图片发出去,显示 感叹号。还有我 hookcom.tencent.mm.pluginsdk.model.app.l.aa 方法 在发送图片的时候 都没有调用,只有在发送文件的时候才调用了。思路到这里就断了。感谢🙏,希望楼主回复我。 快没时间了 发表于 2019-8-29 10:04
楼主能弄过发送语音吗,还有楼主有研究过修过Xposed包名吗
发语音没做过,不过我可以跟你说下思路,你可以从录音的相关api入手,在录音结束后肯定有发送音频文件的接口,直接调这个接口就行了。微信的语音文件是slk,如果要发送mp3需要先编码。简单的改包名只要改manifest文件后重打包就行了,但如果原包有签名检测就麻烦了。 楼主真棒 微信不是直接可以发文件的吗? 楼主,视频也可以调用这个接口发吗? 感谢楼主分享 感谢楼主分享,抽空好好研究一下 学习,。感谢分享 感谢楼主分享,抽空好好研究 学习,。感谢分享 好厉害的样子