十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
主要思路
邹平ssl适用于网站、小程序/APP、API接口等需要进行数据传输应用场景,ssl证书未来市场广阔!成为创新互联公司的ssl证书销售渠道,可以享受市场价格4-6折优惠!如果有意向欢迎电话联系或者加微信:18982081108(备注:SSL证书合作)期待与您的合作!
从UI获取文本信息是最为简单的方法,于是应该优先逆向UI代码部分。
逆向微信apk
首先解包微信apk,用dex2jar反编译classes.dex,然后用JD-GUI查看jar源码。当然,能看到的源码都是经过高度混淆的。但是,继承自安卓重要组件(如Activity、Service等)的类名无法被混淆,于是还是能从中看到点东西。
首先定位到微信APP package。我们知道这个是 com.tencent.mm。
在 com.tencent.mm
中,我们找到一个 ui
包,有点意思。
展开 com.tencent.mm.ui
,发现多个未被混淆的类,其中发现 MMBaseActivity直接继承自 Activity
, MMFragmentActivity
继承自 ActionBarActivity
, MMActivity
继承自 MMFragmentActivity
,并且 MMActivity
是微信中大多数Activity的父类:
public class MMFragmentActivity
extends ActionBarActivity
implements SwipeBackLayout.a, b.a {
...
}
public abstract class MMActivity
extends MMFragmentActivity {
...
}
public class MMBaseActivity
extends Activity {
...
}
现在需要找出朋友圈的Activity,为此要用Xposed hook MMActivity。
创建一个Xposed模块
参考 [TUTORIAL]Xposed module devlopment,创建一个Xposed项目。
简单Xposed模块的基本思想是:hook某个APP中的某个方法,从而达到读写数据的目的。
小编尝试hook com.tencent.mm.ui.MMActivity.setContentView这个方法,并打印出这个Activity下的全部TextView内容。那么首先需要遍历这个Activity下的所有TextView,遍历ViewGroup的方法参考了SO的以下代码:
private void getAllTextViews(final View v) {if (v instanceof ViewGroup) {
ViewGroup vg = (ViewGroup) v;
for (int i = 0; i vg.getChildCount(); i++) {View child = vg.getChildAt(i);
getAllTextViews(child);
}
} else if (v instanceof TextView ) {
dealWithTextView((TextView)v); //dealWithTextView(TextView tv)方法:打印TextView中的显示文本}
}
Hook MMActivity.setContentView
的关键代码如下:
findAndHookMethod("com.tencent.mm.ui.MMActivity", lpparam.classLoader, "setContentView", View.class, new XC_MethodHook() {...
});
在findAndHookMethod方法中,第一个参数为完整类名,第三个参数为需要hook的方法名,其后若干个参数分别对应该方法的各形参类型。在这里, Activity.setContentView(View view)方法只有一个类型为 View
的形参,因此传入一个 View.class
。
现在,期望的结果是运行时可以从Log中读取到每个Activity中的所有的TextView的显示内容。
但是,因为View中的数据并不一定在 setContentView()时就加载完毕,因此小编的实验结果是,log中啥都没有。
意外的收获
当切换到朋友圈页面时,Xposed模块报了一个异常,异常源从 com.tencent.mm.plugin.sns.ui.SnsTimeLineUI这个类捕捉到。从类名上看,这个很有可能是朋友圈首页的UI类。展开这个类,发现更多有趣的东西:
这个类下有个子类 a
(被混淆过的类名),该子类下有个名为 gyO的 ListView
类的实例。我们知道, ListView
是显示列表类的UI组件,有可能就是用来展示朋友圈的列表。
顺藤摸瓜
那么,我们先要获得一个 SnsTimeLineUI.a.gyO的实例。但是在这之前,要先获得一个 com.tencent.mm.plugin.sns.ui.SnsTimeLineUI.a的实例。继续搜索,发现 com.tencent.mm.plugin.sns.ui.SnsTimeLineUI有一个名为 gLZ
的 SnsTimeLineUI.a
实例,那么我们先取得这个实例。
经过测试, com.tencent.mm.plugin.sns.ui.SnsTimeLineUI.a(boolean, boolean, String, boolean)这个方法在每次初始化微信界面的时候都会被调用。因此我们将hook这个方法,并从中取得 gLZ。
findAndHookMethod("com.tencent.mm.plugin.sns.ui.SnsTimeLineUI", lpparam.classLoader, "a", boolean.class, boolean.class, String.class, boolean.class, new XC_MethodHook() {@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {XposedBridge.log("Hooked. ");
Object currentObject = param.thisObject;
for (Field field : currentObject.getClass().getDeclaredFields()) { //遍历类成员field.setAccessible(true);
Object value = field.get(currentObject);
if (field.getName().equals("gLZ")) {
XposedBridge.log("Child A found.");
childA = value;
//这里获得了gLZ
...
}
}
}
});
现在取得了 SnsTimeLineUI.a
的一个实例 gLZ
,需要取得这个类下的 ListView
类型的 gyO
属性。
private void dealWithA() throws Throwable{if (childA == null) {
return;
}
for (Field field : childA.getClass().getDeclaredFields()) { //遍历属性field.setAccessible(true);
Object value = field.get(childA);
if (field.getName().equals("gyO")) { //取得了gyOViewGroup vg = (ListView)value;
for (int i = 0; i vg.getChildCount(); i++) { //遍历这个ListView的每一个子View...
View child = vg.getChildAt(i);
getAllTextViews(child); //这里调用上文的getAllTextViews()方法,每一个子View里的所有TextView的文本...
}
}
}
}
现在已经可以将朋友圈页面中的全部文字信息打印出来了。我们需要根据TextView的子类名判断这些文字是朋友圈内容、好友昵称、点赞或评论等。
private void dealWithTextView(TextView v) {String className = v.getClass().getName();String text = ((TextView)v).getText().toString().trim().replaceAll("\n", " ");if (!v.isShown())
return;
if (text.equals(""))
return;
if (className.equals("com.tencent.mm.plugin.sns.ui.AsyncTextView")) {//好友昵称
...
}
else if (className.equals("com.tencent.mm.plugin.sns.ui.SnsTextView")) {//朋友圈文字内容
...
}
else if (className.equals("com.tencent.mm.plugin.sns.ui.MaskTextView")) {if (!text.contains(":")) {
//点赞
...
} else {
//评论
...
}
}
}
自此,我们已经从微信APP里取得了朋友圈数据。当然,这部分抓取代码需要定时执行。因为从 ListView中抓到的数据只有当前显示在屏幕上的可见部分,为此需要每隔很短一段时间再次执行,让用户在下滑加载的过程中抓取更多数据。
剩下的就是数据分类处理和格式化输出到文件,受本文篇幅所限不再赘述,详细实现可参考作者GitHub上的源码。
程序运行原理
给页面文字添加span标签,设置id="text-xx"唯一属性,使用contenteditable="true",开启该元素的编辑模式,用jQuery属性.click()判断点击,用.text()返回此元素的文本内容,并用正则进行判断内容是否合法,然后通过AJAX POST给php处理,php对传入的参数进行过滤,然后读取模版文件,替换模版文件对应内容,保存为新文件并记录操作,最后返回数据给前端,前端处理数据并更新页面。
可以通过代码实现。
修改Scrapy项目中的文件,需要获取的数据是朋友圈和发布日期,因此在这里定义好日期和动态两个属性,修改实现爬虫逻辑的主文。
首先要导入模块,尤其是要主要将WeixinMomentItem类导入进来,之后修改start_requests方法,修改parse方法,对导航数据包进行解析。
定义parse_moment函数,来抽取朋友圈和聊天信息数据,返回的数据以JSON加载的,用JSON去提取数据,之后就可以在命令行中进行程序运行了,在命令行中输入scrapy crawl moment -o moment.json,之后可以得到微信聊天信息和朋友圈的数据。
申请应用AppKey申请方法:访问友推网站后台,登录后进入应用列表,添加需要集成友推组件的App,如下图,添加成功后可获取应用的AppKey。如何把app分享到微信2申请社交平台appkey集成前您需要为您的应用在各大社交网站的开放平台申请账号并通过审核,否则只能调用系统的分享菜单,无法跟踪分享的回调事件及统计平台如何把app分享到微信3引用youtui库项目将youtui-lib项目库和应用工程放在同一个目录下在PackageExplorer中右键点击工程的根目录,选择Properties(属性),然后点击,在Android选项点击Add添加youtui-lib如何把app分享到微信4注册需要分享的平台1.配置各分享平台key,该配置文件为youtui_sdk.xml,配置完放入工程的assets文件夹。2.如果需要分享到哪个平台就将该平台的Enable属性设置为true.3.如果需要将某个平台排列到前面,只需要改变它在youtui_sdk.xml文件中的位置即可。5各平台需要注意的事项:新浪微博:新浪微博需要验证应用签名,请一定要在新浪开放平台管理中心应用信息-基本信息-Android签名包名信息配置该信息每次包名变化或者使用的.keystore变化都会导致应用签名变化,请重新到新浪微博开放平台设置。运行Demo时如果重新编译,因为使用的.keystore文件变化也会导致应用签名变化,导致Demo新浪微博分享无法正常工作,请运行Demo时使用Demo工程包中提供的debug.keystore替换C:\Users\Administrator.android中的debug.keystore文件微信和朋友圈:微信和朋友圈也需要验证应用签名,请在微信开放平台管理中心修改应用-开发信息配置。QQ和QQ空间:QQ和QQ空间使用的是腾讯开放平台api,请在腾讯开放平台申请账号和注册应用,请不要使用QQ互联(用于网站账号登录)的配置,虽然都是腾讯的,但是两个平台并不通用,除了需要在youtui_sdk.xml配置信息,还需要在manifest文件中的android:scheme中的tencent后的一串数字换成自己应用的appid。最后一行配置说明:选用调用系统分享菜单功能在AndroidManifest.xml注册权限在AndroidManifest.xml注册需要的Activity微信和朋友圈回调设置如果需要分享微信和朋友圈,必需建一个应用包名+.wxapi的包,在该包下建WXEntryActivity.java,将该类继承cn.bidaround.youtui.wxapi.WXEntryActivity即可(里面不用写代码)publicclassWXEntryActivityextendscn.bidaround.youtui.wxapi.WXEntryActivity{}如图:(将com.xingxinglangtuoche替换成你应用的package名,微信回调会使用到)如何把app分享到微信初始化友推开发者请在自己的程序开始,最好是在MainActivity的onCreate方法调用YtTemplate.init(this)初始化友推sdk,这样友推sdk才能进行后续调用(否则分享等操作会出现空指针异常),例如:protectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);YtTemplate.init(this);/初始化友推/initView();}应用退出时:在您项目的出口Activity的onDestroy方法的第一行插入下面的代码YtTemplate.release(this);此方法用于释放内存,统计用户使用情况,一旦调用了release,就必须重新调用init才能使用友推的功能,否则会出现空指针异常;创建ShareData实例,调用该实例的set方法设置需要分享的数据:创建ShareData实例,调用该实例的set方法设置自己需要分享的数据,关于该实例具体内容见下文,如果只是分享应用则只需要设置setIsAppShare(true)就可以分享应用在友推后台填写的信息和下载链接。ShareData包含的字段:isAppSharetextimagePathimageUrldescriptiontitletarget_url判断是否为分享应用待分享的文字,短信要小于70个字符,微博要小于140个字符,如果需要分享链接,最好将链接url放在最后待分享的本地图片地址,分享图片的话需要在本地和网络图片中选一,如果都有则优先分享本地图片待分享网络图片url,分享图片的话需要在本地和网络图片中选一,如果都有则优先分享本地图片待分享内容的描述待分享内容的标题待分享内容的跳转链接通过创建该类实例,调用实例的set方法设置这些参数,例如:ShareDatashareData=newShareData();shareData.isAppShare=false;//设置为true则分享的信息从友推后台填写的应用信息中读取,可动态更新,后面的值不用设置。shareData.setDescription("友推积分组件");shareData.setTitle("友推分享");shareData.setText("通过友推积分组件,开发者几行代码就可以为应用添加分享送积分功能,并提供详尽的后台统计数据,除了本身具备的分享功能外,开发者也可将积分功能单独集成在已有分享组件的app上,");shareData.setTarget_url("");shareData.setImageUrl("");shareData.setImagePath("");各个平台分享数据的限制和注意事项:因为各个平台的分享限制,请分享时尽量分享图片+链接,依靠链接来了解信息1)微信朋友圈微信分享为linkcard形式,超出的文字部分不会显示2)新浪微博很低版本的新浪微博不支持发多类型微博,进行图文分享时只会分享图片,新浪微博分享消息最长为140字3)QQ、QQ空间QQ分享的消息最长40字,分享的标题最长30字,多余的部分将被忽略,description将被忽略QQ空间分享的消息最长200字,分享的标题最长600字,多余的部分将被忽略,description将被忽略4)腾讯微博只有image(imagePath或imageUrl)和text被分享,其他字段忽略,腾讯微博分享消息最长为140字5)人人网只有image(imagePath或imageUrl)和text被分享,其他字段忽略6)短信只有text被分享,其他字段忽略7)邮件只有text被分享,其他字段忽略如何把app分享到微信调用友推分享推荐组件为应用添加一个分享推荐按钮,如:如何把app分享到微信在分享按钮事件中调用youtui的组件即可,示例代码:publicvoidonClick(Viewv){if(v.getId()==R.id.popup_bt){/调用友推分享推荐组件,YouTuiViewType类的常量为分享样式参数,目前支持白色列表和黑色网格两种//创建分享的模板,第一个参数为activity,第二个参数为分享窗口样式,第三个参数为是否需要积分/YtTemplateblackTemp=newYtTemplate(this,YouTuiViewType.BLACK_POPUP,false);//黑色网格样式不需要积分活动/YtTemplateblackTemp=newYtTemplate(this,YouTuiViewType.WHITE_LIST,ture);///白色列表样式需要积分活动ShareDatashareData=newShareData();shareData.isAppShare=false;//设置为true则分享的信息从友推后台填写的应用信息中读取,可动态更新后面的值不用设置。shareData.setDescription("友推积分组件");shareData.setTitle("友推分享");shareData.setText("通过友推积分组件,开发者几行代码就可以为应用添加分享送积分功能,并提供详尽的后台统计数据,除了本身具备的分享功能外,开发者也可将积分功能单独集成在已有分享组件的app上,快来试试吧");shareData.setTarget_url("");shareData.setImageUrl("");shareData.setImagePath("");blackTemp.setShareData(shareData);//设置默认的分享数据;shareData设置参看4.6//**如果要为某个平台设置不一样的分享信息。则单独设置*///blackTemp.addData(YtPlatform.PLATFORM_QQ,shareData);//调出分享窗口blackTemp.show();//如果需要自定义分享事件,可以创建监听事件,然后在回调中处理YtShareListenerlistener1=newYtShareListener(){@OverridepublicvoidonSuccess(ErrorInfoarg0){}@OverridepublicvoidonPreShare(){}@OverridepublicvoidonError(ErrorInfoarg0){}@OverridepublicvoidonCancel(){}};//给新浪微博添加分享监听blackTemp.addListener(YtPlatform.PLATFORM_SINAWEIBO,listener1);//给QQ添加分享监听//blackTemp.addListener(YtPlatform.PLATFORM_QQ,listener2);}