前一篇文章已经详细介绍了如何使用Xposed框架编写第一个微信插件:摇骰子和猜拳作弊器 本文继续来介绍如何使用Xposed框架编写第二个微信插件,可以将本地小视频发布到朋友圈的功能。在这之前我们还是要有老套路,准备工作要做好,这里还是使用微信6.3.9版本进行操作,准备工作:
1、使用apktool工具进行反编译,微信没有做加固防护,所以这个版本的微信包反编译是没有任何问题的。
2、借助于可视化反编译工具Jadx打开微信包,后续几乎重要分析都是借助这个工具来操作的。
做好上面这两步之后,加上我们在之前的那个编写插件的基础之上,我们本次操作就应该非常简单了,还记得之前的插件的突破口是啥吗?看过文章的同学应该了解通过分析界面的控件来获取到id值,然后全局搜索得到的突破口,那么本文其实可能不需要这个方式了,而是另外一种方式,下面来详细介绍一下。在这之前我们先来看看微信正常的发布小视频到朋友圈的方式,会跳转到这个发布页面:
那么我们又要开始大胆的猜想了:
首先这个页面有的元素:标题,小视频,地理位置等信息,而这些信息应该会在请求发布的时候携带到服务器上。这个有点类似于小文件的上传功能。所以这个视频的文件是如何得到的。那么可以得到的假设:这个页面是一个Activity页面,可能从其他页面跳转过来的,同时会把这些元素信息通过intent携带过来,而小视频是个文件,所以应该会携带文件的名称。
有了这猜想之后,咋们就可以开始操作了,首先得到这个页面的activity名称,这个比较简单了,直接使用一个命令即可:adb shell dumpsys activity top
看到这个页面的名称是SightUploadUI,我们借助Jadx反编译微信之后,找到这个类:
我们直接看onCreate方法中有没有对intent参数解析操作,或者我们可以在这个类中全局搜一下getIntent字段,也可以快速得到解析的地方:
看到第一个字段Kdescription,从字段的名称来看应该是描述信息,而从下面的代码setText调用更可以确认了这个就是标题信息。我们继续查找:
又发现了一个字段KSightDraftEntrance,这块代码就有点多了,他是一个boolean类型,所以先不管了,因为后面即使是尝试的话也就两次操作,一次false一次true。不碍事的!可是到这里我们在也搜不到其他字段了,但是这个就和我们的预期不一样了,还差几个元素信息呢?最重要的视频文件路径没有,所以这个就要想起在onCreate方法中有一个ae类初始化的时候把当前activity传递进去了,那么可能他内部继续进行了参数解析,我们可以进去查看一下:
果然在他内部还有三个字段解析,分别是:KSightThumbPath,KSightPath,sight_md5;而从字段命名上来看猜想这个应该就是和视频信息相关的字段了。这里只要有Android开发经验的同学应该可以猜想:KSightPath字段是短视频路径,KSightThumbPath是短视频的默认封面图,sight_md5是短视频的校验值。到这里其实我们已经感觉快成功了,得到了这五个参数,那么我们可以直接尝试了操作了:
在本地存放一个短视频,封面图片,然后计算短视频的md5码,最后通过intent来启动这个页面即可。先不管后面的上传过程了,咋们可以先试验能成功跳转到这个页面展示本地小视频功能。
可惜到这里我们有一个问题,就是怎么获取这个启动页面的activity呢?也就是用哪个activity来启动他呢?有的同学可能这么干?直接简单明了的编写一个小程序,然后用小程序的activity启动这个页面。这个猜想是可以的,不过我没尝试,因为我想微信做了activity启动安全防护的,不可能在其他应用中可以启动微信中的任意一个页面。所以这里我就没费那劲了。而是想到用微信自己的页面来启动他,那么如何获取到微信的一个其他页面呢?这个也简单。咋们可以打开一个聊天页面,继续使用adb shell dumpsys activity top 命令查看页面:
好了,就是这个LauncherUI页面了,那么知道这个页面下面怎么获取这个对象呢?这时候就需要借助Xposed框架进行Hook了,代码如下:
看到了吧,代码很简单的,我们hook页面的onResume方法,因为这时候页面已经初始化完成了是整个Activity生命周期中的比较晚的一个方法了,所以拦截他就可以了。然后在拦截回调用使用MethodHookParam的thisObject属性就可以得到这个方法所属的对象了,也就是LauncherUI类型了。
好了既然现在微信启动页面也有了,下面就简单了,直接构造上面的五个参数得到intent直接启动:
代码很简单,咋们直接运行模块,然后重启设备生效,然后打开微信界面瞬间看到效果了:
果然跳转到这个页面了,也就是说我们的猜想对了,下面我们点击发送,会发现发送失败了:
原因可能有两个:
1、没有弄对视频文件的MD5码
2、视频格式不符合服务器接受的要求:视频的长度和视频的大小
关于第二个原因,其实网上有答案,就是微信这个发布的小视频长度不能超过15s,大小不能超过1M。所以这里我就把本地视频做成了符合这两个标准的,再次操作依然是这样的失败效果。那么就有可能猜想是视频的MD5码校验出问题了,上面看到代码中我传入的MD5码是aaa,我是为了方便没去弄。但是这里就必须写了。获取文件的MD5码这里就不多解释了,不过可惜的是,MD5弄成文件的还是失败。那么这时候就猜想他或许不是真正意义上的MD5值了,可能加上了他自己的一个算法了。所以又来了一个问题,如何得到这个算法呢?
这时候就需要跟踪代码看看其他页面跳转到这个页面携带过来的MD5码是什么呢?我们可以这么干就是全局搜索那五个字段中的任意一个即可,这里在Jadx中全局搜索:sight_md5
我们点击进入查看方法:
继续查找这个方法在哪被调用了:
咋们继续点击进入查看:
这里看到了倒数第二个参数就是那个MD5码值,我们在全局搜一下这个变量在哪里被使用到了:
看到这里有赋值的地方,点击进入查看:
然后查看这个kbVar变量,在上面的代码中:
这里我们可以先看看这个kb类的定义:
这里的aFL就是那个MD5码值了,我们继续上面的那个a方法查看哪些地方调用了,不过查找是没有效果的,因为这个方法可能是抽象的,所以咋们得找到他抽象定义的地方,在上面就是一个抽象类c:
然后进入c类查看抽象方法a:
然后查找a方法调用的地方:
继续查看这个方法的调用地方:
这时候我们多看一下,这个方法所属的类是个单例:
那么继续查看这个g方法被调用的地方,或者全局搜一下jJA这个变量的使用也可以的:
又回到了刚刚的那个MainSightContainerView类了,这里看到了赋值的地方了,而且是给aFL字段赋值的,这个就是上面看到kb类中的字段值,这里依然调用了一个方法计算MD5码值,而且传入的参数是视频路径:
这里首先判断当前视频文件是否存在,然后在进行文件操作:
真正加密算法是在a方法中,这里也可以看到因为计算文件的MD5码是耗时的,所以这里做了一个优化,只会计算文件的前100KB数据:
哎,到这里终于真相大白了,看到他的确是用了MD5算法,只是在后面自己又高了一个简单的算法。所以这里我们为了简单,可以直接把这三个方法拷贝到我们的Xposed模块代码中:
然后在把之前的intent中的sight_md5字段值替换一下:
这时候咋们在之前的拦截的onResume方法中再次调用,然后重启设备生效,点击发送:
哈哈,到这里可以看到,发送成功啦啦,好兴奋呀。终于实现了这个功能。以后可以尽情的装逼了。
注意:在上面我们定位一个方法在哪些地方被调用,有时候可能找不到,但是不代表这个方法真的没有被调用,而是因为这个方法是抽象的,直接跟踪可能没有效果,这时候就需要去抽象方法的定义地方去全局搜索就可以了。
但是到这里我们是否就结束了本次操作了,其实并没有,因为有的同学在上面的实践中会发现,有时候微信会打不开,一打开就闪退,其实这个原因是我们虽然拦截了LauncherUI页面的onResume方法,但是这个页面比较独特的是微信中的首页也是他,所以这就有可能出现你刚刚要打开微信页面,有些初始化操作没做完,而这时候你就立马跳转到SightUploadUI页面去发布视频会出现问题。所以这里就存在一个发布视频的触发时机,为了更好的体验效果,我们决定做到更人性化,就是添加一个菜单可以点击的时候再去触发发布视频逻辑。那么又来了一个问题就是如何在微信中添加一个我们自己想要的菜单?这个我觉得比上面那个还简单点,我们准备在聊天界面中选中一条消息之后弹出的菜单中加一项子菜单:
就是在这里,我们加一项,有的同学觉得这个可能会比较麻烦,其实很简单,我们只要找到这个菜单定义的地方即可。直接看看步骤:想得到这个菜单定义地方很简单,咋们先去反编译之后的values/strings.xml文件中找到这个字符串的定义:
得到他的id值是ne,然后在Jadx中全局搜索:R.string.ne
注意:这里可能有的同学会好奇,在之前一篇文章中不是得去public.xml中找到ne对应的id整型值,然后全局搜索吗?这里可能和微信做了资源混淆工作有关,开始的时候通过整型值死活没找到,最后无意间用了这种方式找到了。所以以后我们可以先用标准方案去public.xml中找到id对应的整型值,如果没找到,在使用这种方式进行查找即可。
上面找到这个字符串定义的地方,直接点击进入即可:
这里可以看到了,使用了系统提供的ContextMenu类进行菜单定义的,这里就需要你对这个类了解了。后面添加菜单就必须用add方法来进行添加了,不过这个方法还是比较简单的,参数都比较好理解主要是菜单组的id值,菜单自身的id值,菜单名称,然后在设置点击事件即可。下面我们继续看这个方法在哪里被调用了:
不过这个方法跟踪没有结果,猜想他可能是一个抽象方法,所以就去他定义的地方进行查看y类:
果然是一个抽象方法,这里在跟踪就可以了:
点击查找结果:
继续看这段代码之前的方法和类定义:
这里有一个变量fHr,也就是菜单创建的回调接口,在往上面查看:
到这里就看明白了,在ChattingUI有一个内部静态类a,在这个类内部开始创建菜单,然后定义一个fHr变量代表菜单创建的回调接口类型,然后在onCreateContextMenu回调方法中进行子菜单添加工作。
好了上面就分析了菜单的创建代码,下面咋们就开始操练了,还是得借助Xposed进行拦截了,这次拦截哪个呢?咋们可以拦截ChattingUI这个类的静态内部类a,然后我们自己在定义一个创建菜单的接口,去替换fHr变量的值,最后我们只要在我们的回调接口中操作即可:
这里拦截代码也是比较简单的,主要是定义我们自己的回调接口,然后在替换fHr值即可,再来看看接口定义:
这里才是最关键的代码了,在onCreateContextMenu回调方法中创建一个菜单,但是这里有一个问题就是怎么获取到菜单组的id值,这个我们还得回到开始的那个添加菜单代码:
看到,这里他是先通过view的tag得到dd对象,然后在调用position属性即可,那么我们操作也就简单了,继续使用反射机制就可以得到这个值了。代码如上。
代码编写完之后再次运行之后,重启设备生效,打开一个聊天室然后选中一条消息:
哈哈看到这个菜单选项了,咋们点击之后就可以跳转到发布页面了:
好了到这里我们就完成了本文提到的如何将本地小视频发布到朋友圈功能实现,下面来总结一下本文的实现步骤以及能够学习到的逆向技巧:
1、首先猜想微信发布视频的页面中的几个重要元素信息:标题,视频信息,地理位置等,然后这些信息可能在其他页面通过intent传递过来的,那么应该不可能传递整个视频数据,而是视频路径。
2、带着猜想就去实践,使用命令找到发布视频的页面activity名称,然后去jadx中找到这个类分析intent中的字段,果然能够得到五个重要的参数信息:Kdescription,KSightDraftEntrance,KSightThumbPath,KSightPath,sight_md5。
3、然后有了这五个字段再次猜想每个字段的含义,然后就直接做了一个简单的实验,在本地存放视频和封面图,然后在代码中构造一个intent,启动即可。
4、但是在启动页面的时候发现有一个问题就是微信应该做了页面启动的安全检查,有些页面只能在应用中其他页面启动,所以这里还需要得到微信中的一个页面。这里就用了聊天界面,依然使用adb命令获取聊天页面类名称,然后借助Xposed进行这个页面的onResume方法拦截,然后在拦截之后启动发布视频页面。
5、实验之后发现既然可以直接调用起来,说明上面的第一步猜想对了,那几个字段我们也猜对了,但是这时候发现点击发送的时候出现了失败现象。然后分析失败的原因有两个:一个是微信服务器对发布的视频做了时长和大小限制,还有一个原因是视频的MD5码计算错了。我们通过修改本地视频的大小和时长之后再次实验发现还是失败,所以可以猜想应该是视频的MD5码计算错误了,微信自己有一个算法,所以得找到这个算法逻辑。
6、下面就是常规路线借助Jadx的查找方法调用功能进行跟踪,在这个过程中学到一个技巧就是如果发现一个方法没有被调用有可能是因为这个方法是抽象的,具体得去抽象类中定义的地方继续跟踪才有结果。
7、最终跟踪到了MD5码的算法,我们为了简单,直接把那几个方法拷贝出来改一下直接使用,计算视频的MD5码,再次实验之后发现发送成功了。
8、在最后发现一个问题,就是微信的很多页面都叫做LauncherUI,所以如果拦截这个方法的onResume方法然后就发送视频的话会出现问题,导致微信启动失败。所以这里就想弄一个事件来控制发送操作。
9、在聊天页面中选中一条消息之后可以弹出一个菜单选项,决定在这里添加一项来触发发送操作,这里定位到菜单的创建过程中,用到了前一篇文章中提到的查找资源id方法,但是这里需要注意的是可能微信自己做了资源混淆策略导致这个方法查找id值是失败的,最后直接使用R.string.xxx这种方式找到了。
学习到的技巧:
1、新的逆向突破口,快速定位页面,使用adb shell dumpsys activity top命令即可。
2、使用Jadx进行方法跟踪时候如果发现没有结果,可能这个方法是抽象的,需要找到这个抽象方法最原始的定义的地方继续跟踪即可。
3、微信可能做了资源混淆(或者以后遇到做了资源混淆的apk)的时候,如果发现通过public.xml中的id值查找不到结果,可以直接使用R.xxx.xxx进行查找id值。
1、其实本文还可以做一个效果,就是上面在看到聊天界面选中一个消息的时候弹出一个我们自己定义的菜单,可以获取到这个消息的类型(视频,文字,图片,表情等),以及具体信息,直接发送分享。而这个就需要解析选中之后的消息内容了,当然这个是在本文的项目代码中已经做了。这里我就不做分析了。
2、因为本文使用了微信6.3.9版本,所以这里进行拦截的方法有:
com.tencent.mm.ui.LauncherUI的onResume方法。
com.tencent.mm.ui.chatting.ChattingUI.a的构造方法,替换fHr变量值。
对于每一个版本混淆之后的类名会发生变化,所以不要一味的用本文提到的代码去实践,要先看懂所有的逆向流程,具体版本具体分析才是王道。
严重声明
本文的目的只有一个,分享更多逆向知识以及逆向技巧,没有任何商业目的操作,如果有人利用本文知识实现任何商业目的带来的一切法律责任将由操作者本身负责。与本文和作者没有任何关系。也由衷的希望每位读者能够秉着技术学习的态度阅读。
本文就详细介绍完了利用Xposed框架实现微信发送本地小视频的功能,对于这个功能个人认为还是有用的,但是对于有些人可能并没有那么用,因为现在在朋友圈中发布视频的人会很少,因为即使发布了由于流量的限制不会有什么效果的。然后就是其实微信对于小视频做了还是有很多限制的,而这些限制都是在服务端进行的,比如视频的校验,时长,大小等。这也就粉碎了小编想发布一个几G的电影到朋友圈的梦想。最后当然还是希望每位读者能够从本文学习到更多的逆向技巧,小编没写这样逆向文章就会很累,感觉自己被掏空了一样,所以大家看完一定要记得多多点赞啦,如果有打赏就更好了!