0、声明
本文仅供安全研究与分享之用,任何人将本文的内容做其他用途(攻击、商业),作者不承担任何法律及连带责任。(说实话:也没什么商业和攻击的用途,本文只能用来装X和学习,没有什么其他用途。)
1、前言
本人还是安全路上的一个菜鸟,很多东西都是站在巨人的肩膀上进行产出的。本文是基于luoyesiqiu对7.0.10微信进行分析的基础之上对于7.0.14进行分析(这几天好像微信更新了7.0.15,有兴趣的师傅们可以试一试本文的方法),以及完善其原文没有的视频朋友圈方面的内容。(luoyesiqiu博客传送门:https://www.cnblogs.com/luoyesiqiu/)
2、环境配置
2.0 使用工具和环境
·Android Studio 162.3934792(主要是为了SDK中的工具,不装AS也行)
·JDK8
·雷电模拟器3.93
·python 3.72
·frida 12.8.20
·jadx
2.1工具介绍
2.1.1 SDK tools
对于微信这个商业的大工程来说,如果从头进行分析,那么无疑是很困难的,所以对于具体的功能点,可以通过定位具体Activity进行分析。为了定位需要用到一些在安卓APP开发过程中的工具,这里使用的是DDMS和UI automonitor。
这两个工具在Android Studio的tools下面是可以找到的。具体用法可参考:https://www.cnblogs.com/gaobig/p/5029381.html
这里还有一个小提示,对于Java环境最好不要配置最新的,很多工具并不能支持,所以在这里我改回了Java8
2.1.2 雷电模拟器
这个模拟器是个人感觉比较好用的,默认是root。当然对于不同人来说使用体验是不一样的。在分析过程中,使用的雷电模拟器版本是3.93,应该在当前来说是最新版本。
本菜鸟在请教很多大佬的时候,大佬们都建议使用可以root的真机进行调试。。。(学生党,是真的穷。)所以只能用模拟器来凑合一些,有条件的还是用用真机。
2.1.3 frida
frida是一款方便并且易用的跨平台Hook工具,使用它不仅可以Hook Java写的应用程序,而且还可以Hook原生的应用程序。
frida分客户端环境和服务端环境。在客户端我们可以编写Python代码,用于连接远程设备,提交要注入的代码到远程,接受服务端的发来的消息等。在服务端,我们需要用Javascript代码注入到目标进程,操作内存数据,给客户端发送消息等操作。我们也可以把客户端理解成控制端,服务端理解成被控端。
假如我们要用PC来对Android设备上的某个进程进行操作,那么PC就是客户端,而Android设备就是服务端。
这里对frida进行一个简单的介绍,在下一节对其配置进行详细的介绍
2.1.4 jadx
此工具主要是为了静态分析的。之前本菜鸟比较喜欢使用的是JEB,可以进行动态调试,静态分析也很支持。但是可能是电脑配置不够,如果想要分析微信的话,直接就卡死了。原因是Java堆内存不够。
后来就决定使用jadx,比较好的是jadx可以直接修改电脑给他的堆内存,有助于后续的静态分析。
2.2 Frida配置
frida的配置说简单也是简单的,但是说难也难。这里为了让其他人少踩点坑,就详细介绍一下如何进行配置。
2.2.1 服务端配置
查看服务端(就是手机或者模拟器)版本,这里是x86
在Github上下载对应的版本(一般来说模拟器都是x86,真机根据架构不同,选择的版本也不同)
解压缩之后,可以重命名
之后传入模拟器
给予其运行权限
运行frida-server
2.2.2 客户端配置
这个安装对我来说耗费的时间比较多。参考了很多网站上的配置,都没成功。后来发现归根结底还是自己不够耐心。
有几个关键点:一是python的版本,这里我使用的是3.7+的版本,所以一定要去官网上看看对于3.7+版本的Python需要什么版本的frida库;二是耐心,对于firda-tools和frida的安装,安装命令很简单:pip install xxxxxx==(version)。但是很长时间都会卡在setup.py上面。
那个时候我比较急躁,想着去找办法,网上给的建议是自己去下对应版本的egg文件,然后放在Script目录下。
其实慢慢等是可以解决的。当初我就是吃饭前敲完命令,吃完饭回来之后就装上了。
命令如下(一定要注意python的版本):
pip install frida-tools
or
pip install frida-tools == (version)
pip install frida
or
pip install frida == (version)
2.2.3 frida使用
frida代码编写有一个python框架,对于这个框架只要修改jscode代码即可
import frida
import sys
jscode ='''
Java.perform(function(){
var SnsUploadUI = Java.use('com.tencent.mm.plugin.sns.ui.SnsUploadUI');
var ai = SnsUploadUI.ai.overload("android.os.Bundle");
ai.implementation = function(bundle)
{
var ret = ai.call(this, bundle);
send("sns type = " + this.wUl.value);
return ret;
}
}
);
'''
def message(message,data):
if message["type"] == 'send':
print("[*] {0}".format(message["payload"]))
else:
print(message)
process = frida.get_remote_device().attach("com.tencent.mm")
script = process.create_script(jscode)
script.on("message",message)
script.load()
sys.stdin.read()
运行流程如下:
1、adb shell 获取root手机或模拟器 shell
2、在具体目录下运行server:
./frida-serverx86
3、adb端口转发
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043
4、运行python脚本
3、定位
环境配置完之后就到了正文部分。
因为微信是一款成熟的商业APP,其中的保护技术:混淆、反调试之类的技术肯定是很完善的,所以为了更好的完成指定功能,最好的就是先定位。
首先再模拟器中,将画面停留在发送pyq的界面
之后使用以下adb命令,将此时的activity显示在最前面
adb.exe shell dumpsys activity top
可以看到此时的Activity是com.tencent.mm.plugin.sns.ui.SnsUploadUI
但是com.tencent.mm.plugin.sns.ui.SnsUploadUI这个activity的代码还是很多的,需要进一步的定位
使用DDMS进行跟踪记录。因为由之前的adb命令可以知道跟踪的task时com.tencent.mm,那么在DDMS记录时可以选择这一个task
选中con.tencent.mm这个进程之后,点击右上角的图标,进行跟踪
当pyq发出之后,停止跟踪,可以发现很多东西
我们知道,发表Pyq是要点击“发表”这一个键的,也就是一般来说是OnClick,于是乎可以找到OnClick类似的函数。同时在上文中找到了具体的Activity是com.tencent.mm.plugin.sns.ui.SnsUploadUI,所以也可以通过这个进行搜索。在这里找到的可以函数是——onMenuItemClick
下一章对于这个函数进行具体的静态分析,以及不同类型的pyq(文字、图片、视频)进行hook。
4、静态分析与HOOK
4.0 总体分析
4.0.1 类型分析
使用jadx对微信静态分析,定位到上文提到的onMenuItemClick。因为混淆过了,所以目前来说,很难看懂。但是这并不影响。
为了更好的分析,这里再一次定位具体的功能代码,使用到的工具就是UI automonitor。首先还是再在模拟器上停留到发送文字朋友圈的界面(发送纯文本pyq算是一个隐藏功能,开启的方法就是长按右上角的照相机,之后就会进入文本pyq发送的界面,也算涨知识了)
之后可以在UI上捕捉到此时模拟器上的布局,同时可以根据具体的内容,找到其对应的Resource id
可以看到此时文字对应的Resource id是fms,无疑是混淆后的,但是不影响。之后再源码中查找哪里使用了findViewById,而参数就是fms。因为findViewById这个函数
是寻找在XML或是Layout里的名称,所以可以认为这里就是寻找文字的ID。
运气比较好,确实有这么一个调用,此时又引出了一个新的变量this.wYj
对于this.wYj进行搜索,可以看到其中对于this.wYj是否为空有一个判断。这里就是说如果this.wYj为空的话,那么内容描述(contentdesc)为空。所以这
里this.wYj相当于一个描述了
接着找到了this.wYj的一个赋值。这里对this.desc进行了赋值
接下来就可以跟踪this.desc了。往下跟之后发现,this.desc被当作参数传入的
SnsUploadUI.this.wYk.a()这个方法了
这里可以查看到接口信息如下,但是并没有什么实质性的作用
归根结底还是得看看this.wYk的赋值情况,在ai这个方法中发现了this.wYk的赋值
在这里thi.wYk跟wUl这个值有关系,所以现在目标又转到this.wUl这里。通过静态分析可以知道this.wUl有如下几个值:0、9、14
在这里可以通过frida脚本来hook,看看这个值与朋友圈有什么关系。测试脚本如下:
import frida
import sys
jscode ='''
Java.perform(function(){
var SnsUploadUI = Java.use('com.tencent.mm.plugin.sns.ui.SnsUploadUI');
var ai = SnsUploadUI.ai.overload("android.os.Bundle");
ai.implementation = function(bundle)
{
var ret = ai.call(this, bundle);
send("sns type = " + this.wUl.value);
return ret;
}
}
);
'''
def message(message,data):
if message["type"] == 'send':
print("[*] {0}".format(message["payload"]))
else:
print(message)
process = frida.get_remote_device().attach("com.tencent.mm")
script = process.create_script(jscode)
script.on("message",message)
script.load()
sys.stdin.read()
首先发送一个纯文本pyq
可以看到hook返回的值是9
再发一个纯图片动态
可以看到返回值是0
在这里有一个奇怪的现象,就是如果是发表文字动态的话(长按右上角照相机),那么hook返回的值立刻就返回到了终端上面,但是图片的话并不是,而是要选择完内容之后才返回的。所以可以猜测视频的值就是14。
这里实际测试一下
发现值确实为14
因为本菜鸟没怎么发过pyq,所以为了考虑其他可能性,还尝试了:文字+图片、文字+视频的pyq,结果返回值就是和图片、视频一致。本来还想测试图片+视频的返回值,没想到图片和视频不能同时发(憨憨一个)
小结一下:
9:纯文本动态
0:纯图片、图片+文字动态
14:纯视频、视频+文字动态
4.0.2 方法分析
找到了不同this.wUI对应的值,接下来就要找到不同类型的动态对应的方法。
还是回到ai这个函数中,可以找到一段switch case的代码。这里就是对不同类型的pyq进行不同的处理。
首先找到case 9,也就是处理纯文本动态的地方。可以看到此时用的是ag这个方法
对比case0和case14,也就是图片和视频的方法,发现都调用了MU这个方法
结合实际发动态判断类型的时候也是,如果是文字动态,那么长按摄像头就可以判断出是文字动态,但是图片和视频动态却需要当具体的文件选择之后才能判断。符合真实情况。所以猜测MU这个函数中有对图片和视频进行判断。所以跟进MU这个函数。果不其然,所以可以得到视频图片动态调用的方法是ak,视频动态调用的方法是af
总结一下:
文字动态类型值为9,对应的调用方法:com.tencent.mm.plugin.sns.ui.ag
图片动态类型值为0,对应的调用方法:com.tencent.mm.plugin.sns.ui.ak
视频动态类型值为14,对应的调用方法:com.tencent.mm.plugin.sns.ui.af
4.0.3 参数分析
通过上述的分析可以知道,pyq动态基本上都是a方法,所以回到onMenuItemClick,看看a方法的参数,可以发现这里的this.desc是第四个参数,并且还有一些诸如privated之类的参数,联想实际动态发送,猜测可能是发送动态的权限设置
仔细分析一下前后的代码,根据这些参数名有一些猜测,比如private有可能就是动态的权限设置,this.desc可能是文字描述,getAtList可能是@的好友名单吧。其他的就不是很清楚了。所以为了搞清楚这些参数的具体含义,需要继续使用frida进行hook
为了简单起见,这里就以文字动态的调用方法com.tencent.mm.plugin.sns.ui.ag来写hook代码。当每次用户发送完pyq之后,会将参数的返回值回显在终端上面。完整代码如下:
import frida
import sys
jscode ='''
Java.perform(function(){
var ag = Java.use('com.tencent.mm.plugin.sns.ui.ag');
var ag_a = ag.a.overload("int","int","org.b.d.i","java.lang.String","java.util.List","com.tencent.mm.protocal.protobuf.blw","java.util.LinkedList","int","boolean","java.util.List","com.tencent.mm.pointers.PInt","java.lang.String","int","int");
ag_a.implementation = function(isPrivate,syncFlag2,twitterAccessToken,desc,atList,location,list1,pasterLen,bool1,list2,pint1,str1,num1,num2){
var ret = ag_a.call(this,isPrivate,syncFlag2,twitterAccessToken,desc,atList,location,list1,pasterLen,bool1,list2,pint1,str1,num1,num2);
console.log("************Basic Info************");
console.log("isPrivate = " + isPrivate);
console.log("syncFlag2 = " + syncFlag2);
console.log("twitterAccessToken = " + twitterAccessToken);
console.log("desc = " + "'" + desc + "'");
if(atList.size()>0){
for(var i=0;i<atList.size();i++){
console.log("atList[" + i + "] = " + atList.get(0));
}
}
if(location != null){
if(location.CsI.value != null){
console.log("location.CsI = " + location.CsI.value)
}
if(location.CsJ.value != null){
console.log("location.CsJ = " + location.CsJ.value)
}
if(location.Dnw.value != null){
console.log("location.Dnw = " + location.Dnw.value)
}
if(location.Dnx.value != null){
console.log("location.Dnx = " + location.Dnx.value)
}
if(location.wUK.value != null){
console.log("location.wUK = " + location.wUK.value)
}
if(location.wUM.value != null){
console.log("location.wUM = " + location.wUM.value)
}
if(location.country != null){
console.log("location.country = " + location.country.value)
}
}
console.log("list1 = " + list1);
console.log("pasterLen = " + pasterLen);
console.log("bool1 = " + bool1);
if(list2 != null){
console.log("list2 = " + list2.size());
}
else{
console.log("list2 = " + list2);
}
console.log("pint1 = " + pint1.value.value);
console.log("str1 = " + str1);
console.log("num1 = " + num1);
console.log("num1 = " + num1);
return ret;
}
}
);
'''
def message(message,data):
if message["type"] == 'send':
print("[*] {0}".format(message["payload"]))
else:
print(message)
process = frida.get_remote_device().attach("com.tencent.mm")
script = process.create_script(jscode)
script.on("message",message)
script.load()
sys.stdin.read()
首先对于位置参数进行测试,发pyq的时候设置一下位置
可以看到,将地址设置在了利生写字楼(我也不知道这是哪),终端回显的信息如下:
猜测是经纬度,在在线网站上,搜索这个地址,得到基本相同的经纬度(不完全一样可能是地点还是有一点偏差)
接着对@用户参数进行测试,可以看到这里存储的@用户的微信ID(这里通过测试发现一个结论:微信在创建的时候有一个初始的微信号,都是wxid_xxxx之类的,非常难记,于是乎微信用户有一次机会修改自己的微信号,但是在这里测试发现,得到的还是初始的那个微信号,所以微信服务器应该还是按照原始的wxid进行处理的)
最后对谁可以看进行测试。总体来看公开、部分可见和部分不可见都是0,私密设为1。理论上应该有一个列表用来记录可见或不可见的人,这里就没有继续研究下去了,感兴趣的师傅可以进行一下完善
最后可以得到如下结果
privated(int):动态是否私密:0公开,1私密
desc(String):朋友圈的文本
AtList(List):@人的wxid
Location(com.tencent.mm.protocal.protobuf.blw):定位信息
4.1 文字动态分析
通过之前的分析可以知道文字动态调用的方法是com.tencent.mm.plugin.sns.ui.ag。跟进去看看能否调用,发现有Activity类型的参数。Activity类型的参数是很难构造的,所以放弃构造com.tencent.mm.plugin.sns.ui.ae类来调用a方法。
于是直接去看a方法,看能不能找到有用的东西。由于是文字动态,所以我们着重关注传入的文本,即com.tencent.mm.plugin.sns.ui.SnsUploadUI类的desc成员,在a方法中它是第4个参数:发现第四个参数str只用到了一次,就是str传给了azVar.ans
这个azVar也是在a方法中声明的,这边使用2这个参数传入az这个方法就能创建azVar。这里就不分析这个参数的含义了。在使用的时候传入同样的参数即可
在a方法的结尾有一个commit的方法,猜测有可能就是发布朋友圈的方法
跟到commit中看一下
之后可以写一段代码来测试一下能否使用脚本来发送文本pyq,以下是完整代码:
# -*- coding: UTF-8 -*-
import frida
import sys
jscode ='''
if(Java.available)
{
Java.perform(function(){
var az_class = Java.use("com.tencent.mm.plugin.sns.model.az");
var desc = "文本朋友圈";
var azInstance = az_class.$new(2);
azInstance.ans(desc);
azInstance.commit();
});
}
'''
def message(message,data):
if message["type"] == 'send':
print("[*] {0}".format(message["payload"]))
else:
print(message)
process = frida.get_remote_device().attach("com.tencent.mm")
script = process.create_script(jscode)
script.on("message",message)
script.load()
sys.stdin.read()
运行一下(这里需要刷新一下,大家都懂,那个小圈圈转啊转就是刷新了),成功用脚本发送了文字的pyq。测试结果如下:
4.2 图片动态分析
接下来分析一下图片类型的动态。思路一样,先找到图片调用的方法com.tencent.mm.plugin.sns.ui.ak。同样的ak也是有个Activity类型的参数不好构造
找到ak下面的a方法,可以发现这里有很多种a方法
找到需要相同参数的a方法后进行分析
在a方法的开头,看到利用迭代器去遍历一个列表,遍历过程中组装com.tencent.mm.plugin.sns.data.p类的数据,然后把p类放入链表linkedList2中
跟进p这个类看看,可以看到有很多成员变量,目前还不知道这些具体的含义
查看linkedList2在哪里被引用,发现在下面的第二张图片中有着”commit pic size “这样的字样,应该就是写入日志吧
对于上面的第三张图片可以发现linkedList2传入了两个地方。
①处没什么好看的,跟进之后发现对List没有进行赋值
接下来分析一下②处,跟进去之后,发现这里将list赋给this.wWX
而this.wWX在接下来也有使用,在dQ中发现了azVar的fo使用了this.wWx。同时这里还有一句话叫做commit imp time。
而这里的w就是在com.tencent.mm.plugin.sns.ui.ak中的a方法被调用。分析到这,上面的linkedList2传出去之后都终有所属了,即最终都传入了com.tencent.mm.plugin.sns.model.az类fo方法。知道图片往哪传了,可以写个frida代码测试一下。
为了测试需要,需要先将图片传入模拟器中。这里有可能有出现read-only的错误,所以为了避免这些麻烦,我直接在/data/local/tmp下面建立一个img文件,然后将图片传进去
代码的思路如下:发送文本动态代码的基础上初始化三个p类,分别传入三个本地图片路径,再将三个类实例添加到链表,再将链表传入az类的fo方法,最后调用az类的commit方法将动态发送出去。以下是完整代码:
# -*- coding: UTF-8 -*-
import frida
import sys
jscode ='''
if(Java.available)
{
Java.perform(function(){
var az_class = Java.use("com.tencent.mm.plugin.sns.model.az");
var p_class = Java.use("com.tencent.mm.plugin.sns.data.p")
var desc = "图片测试";
var likedList_class = Java.use("java.util.LinkedList");
var linkedListInstance = likedList_class.$new();
var azInstance = az_class.$new(1);
var pInstance1 = p_class.$new("/data/local/tmp/img/img1.jpg",2);
var pInstance2 = p_class.$new("/data/local/tmp/img/img2.jpg",2);
var pInstance3 = p_class.$new("/data/local/tmp/img/img3.jpg",2);
linkedListInstance.add(pInstance1);
linkedListInstance.add(pInstance2);
linkedListInstance.add(pInstance3);
azInstance.fo(linkedListInstance);
azInstance.ans(desc);
azInstance.commit();
});
}
'''
def message(message,data):
if message["type"] == 'send':
print("[*] {0}".format(message["payload"]))
else:
print(message)
process = frida.get_remote_device().attach("com.tencent.mm")
script = process.create_script(jscode)
script.on("message",message)
script.load()
sys.stdin.read()
测试结果如下:
4.3 视频动态分析
按照图片的思路,直接找到com.tencent.mm.plugin.sns.ui.af中的a方法,在审计代码之前发现几个奇怪的参数,比较明显的就是this.videoPath和this.md5,这两个应该就是视频的路径和视频的md5,(传个视频还要验证md5,醉了),对于不大明显的this.thumbPath,通过查阅资料之后知道,这是视频封面的路径
对于这三个参数的可以用frida代码来返回其具体的值,以下是返回三个参数的frida代码:
import frida
import sys
jscode ='''
Java.perform(function(){
var af = Java.use('com.tencent.mm.plugin.sns.ui.af');
var af_a = af.a.overload("int","int","org.b.d.i","java.lang.String","java.util.List","com.tencent.mm.protocal.protobuf.blw","java.util.LinkedList","int","boolean","java.util.List","com.tencent.mm.pointers.PInt","java.lang.String","int","int");
af_a.implementation = function(isPrivate,syncFlag2,twitterAccessToken,desc,atList,location,list1,pasterLen,bool1,list2,pint1,str1,num1,num2){
var ret = af_a.call(this,isPrivate,syncFlag2,twitterAccessToken,desc,atList,location,list1,pasterLen,bool1,list2,pint1,str1,num1,num2);
console.log("************Basic Info************");
console.log("videopath = " + this.videoPath.value);
console.log("md5 = " + this.md5.value);
console.log("thumbPath = " + this.thumbPath.value);
return ret;
}
}
);
'''
def message(message,data):
if message["type"] == 'send':
print("[*] {0}".format(message["payload"]))
else:
print(message)
process = frida.get_remote_device().attach("com.tencent.mm")
script = process.create_script(jscode)
script.on("message",message)
script.load()
sys.stdin.read()
得到如下结果,可见发送视频朋友圈的时候确实是需要这三个参数的。
接下来重点分析这个md5是如何获取。
首先还是在szVar这个变量中
之后根据sv这个类中,因为dBs.dBv代表的就是md5,但是这里并没有导入视频路径,所以并没有什么用
继续往下寻找,在j方法中找到相关的信息
在这里看到一句关键的句子:video path,thumb path后面对应的格式化串是stringExtra和stringExtra2。所以可以推测stringExtra对应的就是视频路径也就是this.videoPath。而这里也通过g.aCZ()方法对md5进行赋值,所以这里应该就是对md5的计算
跟进g.aCZ这个方法,它是在com.tencent.mm.vfs.g这个类中,根据这个函数的返回值进行逆向,发现返回值是取aLe每个字节的值与上255之后再加上256再转换为16进制(总感觉这段代码有点不知所云)
所以现在就是要得到aLe数组的值。通过之前的分析可以知道str这个参数代表的就是视频的路径,这里的aLe数组是通过aLe()方法得到的,参数就是视频的路径。
跟进aLe这个函数,发现程序确实对视频文件进行了MD5计算
所以到了这里可以大概知道视频的md5是如何得到的,首先就是真正的计算视频文件的MD5值,之后将获取的散列值的每个字节进行一个小小的变化运算(与上255再加上256),但是仔细一想的话,对于一个字节(8位)&255就相当于没变,再加上256(9位)就相当于加上0。所以这里比较迷,也不知道到底是要干啥。
总之算是分析好了获取MD5的方法
之后尝试使用代码发送视频朋友圈的时候发生一个问题。(这里为了测试直接使用之前代码的路径和md5)。以下是js部分代码
if(Java.available)
{
Java.perform(function(){
var az_class = Java.use("com.tencent.mm.plugin.sns.model.az");
var desc = "视频测试";
var azInstance = az_class.$new(15);
azInstance.b("","/storage/emulated/0/tencent/MicroMsg/WeiXin/wx_camera_1589198170517.mp4","","a05a5637bc6da550e5ce80a64654e882","/storage/emulated/0/tencent/MicroMsg/ce970bb1ead64d8901415bab33ba544e/videovsg_thumb_1589375986582","");
azInstance.ans(desc);
azInstance.commit();
});
}
但是结果却是这样的,既无封面图,视频也无法播放。明明之前打印出来的信息都是一样的
于是使用上一个打印信息的代码进行测试,发表的视频都是同一个,这里做了三组测试。发现一个结果,就是每次路径都会发生变化,所以md5值也会变化,所以这样视频和md5就不匹配也就无法正常发送视频
知道了错误的原因之后,就要找到某个函数,传入的参数为视频路径、封面图、md5等相关参数。但是对于模块com.tencent.mm.plugin.sns.ui.af中好像没有相关的调用,要么就是需要构造intent。此时通过jadx可以发现有一段好像是被注释掉的代码。经过查阅资料之后得知,这里并不是被注释掉,而是可能当前版本的jadx无法解析。并不是实际文件中没有
于是阅读这段代码之后发现一个奇怪的函数,同时这几个参数与猜测的基本一致,有videopath、thumbpah、md5等
于是到com.tencent.mm.plugin.sns.model.az中查看相关代码内容
高度怀疑此段代码就是与视频动态相关的,于是写个frida代码测试以下。当然需要先传进一个封面图和视频
以下是完整代码:
# -*- coding: UTF-8 -*-
import frida
import sys
jscode ='''
if(Java.available)
{
Java.perform(function(){
var az_class = Java.use("com.tencent.mm.plugin.sns.model.az");
var desc = "视频测试";
var azInstance = az_class.$new(15);
var videopath = "/data/local/tmp/video/videotest.mp4";
var thumbpath = "/data/local/tmp/video/thumb.jpg";
var g = Java.use("com.tencent.mm.vfs.g");
var md5 = g.aCZ(videopath);
console.log("md5 = " + md5);
azInstance.w(videopath,thumbpath,"video test",md5);
azInstance.ans(desc);
azInstance.commit();
});
}
'''
def message(message,data):
if message["type"] == 'send':
print("[*] {0}".format(message["payload"]))
else:
print(message)
process = frida.get_remote_device().attach("com.tencent.mm")
script = process.create_script(jscode)
script.on("message",message)
script.load()
sys.stdin.read()
可以看到,成功发送了视频pyq,并且封面图可以指定,并不是视频的0秒的图片。测试结果如下:
5 总结
第一次分析这么高度商业化的产品,难度还是很大的。安全路上永无止尽,继续向大佬们学习。
比较遗憾的是没有成功绕过视频的时长限制。可能是分析还是不够到位吧,猜测是在服务端进行了时长的处理。希望哪位大佬可以有所突破,然后告知一下,学习学习大佬的技术。