本文主要介绍通过静态分析微信发送文件的逻辑,并通过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[5];
objArr[0] = Long.valueOf(file.length());
objArr[1] = Integer.valueOf(0);
objArr[2] = Integer.valueOf(aVar.dRl() ? 1 : 0);
objArr[3] = Integer.valueOf(om);
objArr[4] = 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";
}
}
测试发送成功,效果如下
对于发送文件的分析思路大体就是这样,而发送图片与短视频的分析过程与此类似,这里就不再赘述,有兴趣的自己研究下吧。