Xposed的配置与简单使用

Xposed是什么

官方对此的解释是这样的:
“Xposed是一个适用于Android的框架。基于这个框架开发的模块可以改变系统和app应用的行为,而不需要修改APK。这是一个很棒的特性,意味着Xposed模块可以不经过任何修改,安装在各种不同的ROM上。Xposed模块可以很容易的开启和关闭。你只需要激活或者禁用Xposed模块,然后重启手机即可。”

Hook是什么

Hook 英文就是「挂钩」的意思.那我们如何使用这个「挂钩」呢?在我们的应用程序种,包括应用触发事件和后台逻辑处理,都是是根据事件流程一步步地向下执行。而「挂钩」的意思,就是在事件传送到终点前截获并监控事件的传输,像个钩子钩上事件一样,并且能够在钩上事件时,处理一些自己特定的事件。
hook
如何理解,例如我们有一个方法:

1
2
3
private int getResult(int i, int i2) {
return i+i2;
}

正常情况下,i=1,i2=1,我们能得到2,但是根据上图,现在我们知道利用Hook现在能做些什么了,通过Hook我们可以对参数i,i2进行修改,i=1,i2=2,最终我们得到的结果是3.这就是挂钩的作用。

关于Android中的Hook机制,大致有两个方式:

要 root 权限,直接Hook系统,可以干掉所有的App。

免root权限,但是只能 Hook 自身,对系统其它 App 无能为力。

Xposed接入

创建工程

这个工程就是我们的插件工程,也是我们需要激活的Xposed模块工程。在我们的群控项目中,我们的hotB就是我们的模块工程。什么是模块工程,可以理解我们使用Hook挂钩自定义消息体的工程。这个app安装后可以在Xposed app的模块中找到。
set

导入api

倒入api比较简单:直接在build.gradle中添加依赖即可:

1
2
3
/* Xposed */
provided 'de.robv.android.xposed:api:82'
provided 'de.robv.android.xposed:api:82:sources'

注意:这里是provided,因为Xposed里已有该jar包内容,再次打包进去会冲突,并导致handleLoadPackage没有回调

修改AndroidManifest.xml文件

在标签里面加三个meta-data

1
2
3
4
5
6
<!-- 作为xposed模块 -->
<meta-data android:name="xposedmodule" android:value="true" />
<!-- Xposed的模块描述 显示在xposed模块列表那里第二行-->
<meta-data android:name="xposeddescription" android:value="HotAX" />
<!-- XposedBridgeApi的最低版本号 -->
<meta-data android:name="xposedminversion" android:value="53" />

编写hook类

创建一个类,实现IXposedHookLoadPackage接口,重写handleLoadPackage方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class XposedModule implements IXposedHookLoadPackage {

@Override
public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
if (!loadPackageParam.packageName.startsWith("com.hotax.autowechat")) {
if (loadPackageParam.packageName.startsWith("com.tencent.mm")) {
XposedHelpers.findAndHookMethod(Application.class, "attach", Context.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {

}

@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {

}
});
}
}
}
}

创建xposed_init文件

在main/assets目录下创建xposed_init文件,不要后缀名。这个就是模块的入口,只有一行代码,就是说明入口类,这个类就是我们上边的类,在这里我们能打印出所有package。

1
com.hotax.autowechat.xposed.XposedModule。

至此,Xposed模块开发的接入就全部完成了。

Hook实现

hook变量

hook前

1
2
3
4
5
6
7
8
9
private static  int staticPrivateInt = 100;
public static int staticPublicInt = 100;
private static String staticPrivateString = "staticPrivateString";
private static String staticPublicString = "staticPublicString";

public int publicInt = 200;
private int privateInt = 300;
private String privateString = "privateString";
private String publicString = "publicString";

hook

获取Class:

final Class<?> clazz = XposedHelpers.findClass(“全路径类名”, loadPackageParam.classLoader);

1
2
3
4
5
6
XposedHelpers.setStaticIntField(clazz, "staticPrivateInt", 99);
XposedHelpers.setStaticIntField(clazz, "staticPublicInt", 99);
XposedHelpers.setStaticObjectField(clazz, "staticPrivateString", "hook_staticPrivateString");
XposedHelpers.setStaticObjectField(clazz, "staticPublicString", "hook_staticPublicString");

XposedHelpers.setStaticIntField(clazz, "publicInt", 99);

hook后结果打印

1
2
3
4
5
6
7
8
D/HookDemo: staticPrivateInt = 99
D/HookDemo: staticPublicInt = 99
D/HookDemo: staticPrivateString = hook_staticPrivateString
D/HookDemo: staticPublicString = hook_staticPublicString
D/HookDemo: publicInt = 200
D/HookDemo: privateInt = 300
D/HookDemo: privateString = privateString
D/HookDemo: publicString = publicString

结论:XposedHelpers.setStaticObjectField方法只能修改静态变量,非静态变量不能使用此方式修改。否则会报java.lang.NullPointerException:异常;那非静态变量如何修改呢:例如我们目标程序里有两个方法

1
2
3
4
5
6
7
public void publicMethod(String value){    
Log.d(Tag, "参数打印-publicMethod:" + value);
}

private void privateMethod(String value){
Log.d(Tag, "参数打印-privateMethod:" + value);
}

hook非静态变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
XposedHelpers.findAndHookMethod(clazz, "publicFunc", String.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
param.args[0] = "before--------------publicMethod";
XposedHelpers.setIntField(param.thisObject, "publicInt", 199);
XposedHelpers.setIntField(param.thisObject, "privateInt", 199);
XposedHelpers.setObjectField(param.thisObject, "privateString", "hook_privateString");
XposedHelpers.setObjectField(param.thisObject, "publicString", "hook_publicString");


XposedHelpers.setIntField(param.thisObject, "staticPrivateInt", 199);
XposedHelpers.setIntField(param.thisObject, "staticPublicInt", 199);
XposedHelpers.setObjectField(param.thisObject, "staticPrivateString", "hook_-setObjectField-staticPrivateString");
XposedHelpers.setObjectField(param.thisObject, "staticPublicString", "hook_-setObjectField-staticPublicString");

}
});

hook后结果打印

1
2
3
4
5
6
7
8
9
参数打印-publicMethod:before--------------publicMethod
D/HookDemo: staticPrivateInt = 199
D/HookDemo: staticPublicInt = 199
D/HookDemo: staticPrivateString = hook_-setObjectField-staticPrivateString
D/HookDemo: staticPublicString = hook_-setObjectField-staticPublicString
D/HookDemo: publicInt = 199
D/HookDemo: privateInt = 199
D/HookDemo: privateString = hook_privateString
D/HookDemo: publicString = hook_publicString

结论:非静态变量的修改只能通过反射某类中的某个方法(方法类型不限)中修改,且静态变量也可以这么修改。

hook方法

hook构造方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public HookDemo(String value){
Log.d(Tag, "参数打印-带参构造方法:" + value);
}

-------------------------------------------------------------------

//Hook有参构造函数,修改参数
XposedHelpers.findAndHookConstructor(clazz, String.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
param.args[0] = "参数打印-带参构造方法:我是修改后的参数";
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log("HOOK-After:" +param.args[0]);
}
});

-------------------------------------------------------------------

D/HookDemo: 参数打印-带参构造方法:我是修改后的参数
D/HookDemo: HOOK-After:参数打印-带参构造方法:我是修改后的参数
hook带返回值的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public String getHook(String value){
return value;
}

-------------------------------------------------------------------

1、
XposedHelpers.findAndHookMethod(clazz, "getHook", String.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
param.args[0] = "返回值:before--------------gethook";
}
});

执行结果:
D/HookDemo: getHook 返回值 = 返回值:before--------------gethook

-------------------------------------------------------------------

2、
XposedHelpers.findAndHookMethod(clazz, "getHook", String.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
// param.args[0] = "返回值:before--------------gethook";
}

@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
param.setResult("返回值:after--------------gethook");
}
});
执行结果:
D/HookDemo: getHook 返回值 = 返回值:after--------------gethook
hook静态方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
private static String staticPrivateMethod(String value){
Log.d("HookDemo", "静态函数--" + value);
return "静态函数--返回:"+value;
}

-------------------------------------------------------------------

1、
XposedHelpers.findAndHookMethod(clazz, "staticPrivateMethod", String.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
param.args[0] = "before---";
}
});

执行结果:
D/HookDemo: 静态函数--before---
D/HookDemo: 静态函数--返回:before---

-------------------------------------------------------------------
2、
XposedHelpers.findAndHookMethod(clazz, "staticPrivateMethod", String.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
param.args[0] = "before---";
}

@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
param.setResult("静态函数--返回:after---");
}
});
执行结果:
D/HookDemo: 静态函数--before---
D/HookDemo: 静态函数--返回:after---

-------------------------------------------------------------------
3、
Object object = XposedHelpers.callStaticMethod(clazz, "staticPrivateMethod", "callStaticMethod 静态函数调用");
Log.d("HookDemo", "xposed-:staticPrivateFunc:"+object);

执行结果:
D/HookDemo: 静态函数--callStaticMethod 静态函数调用
D/HookDemo: xposed-:staticPrivateFunc:静态函数--返回:callStaticMethod 静态函数调用

结论:静态方法的修改和其他方法hook方式一样,但是静态方法可以直接通过callStaticMethod进行参数的修改。

Hook复杂的参数
1
2
3
4
5
6
7
8
9
10
11
12
public void paramsClass(ClasName cn, String value,Intent intent,int key,Map map,List list){  
}
-------------------------------------------------------------------
Class cnClazz = loadPackageParam.classLoader.loadClass("com.xxxx. ClasName");
Class cnMap = XposedHelpers.findClass("java.util.Map", loadPackageParam.classLoader);
Class cnArrayList = XposedHelpers.findClass("java.util.ArrayList", loadPackageParam.classLoader);

XposedHelpers.findAndHookMethod(clazz, "paramsClass", cnClazz, String.class, Intent.class,int.class,Map.class,ArrayList.class new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
}
});
Hook内部类中的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class HookDemo {
private String Tag = "HookDemo";
class InnerClass{
public int innerPublicInt = 10;
private int innerPrivateInt = 20;
public InnerClass(){
}
public void InnerFunc(String value){
}
}
}
-------------------------------------------------------------------
final Class<?> clazz1 = XposedHelpers.findClass("com.xxxxxx.HookDemo$InnerClass", loadPackageParam.classLoader);
XposedHelpers.findAndHookMethod(clazz1, "InnerMethod", String.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
}
});

注:hook内部类中的函数和变量,跟以上的hook方式没有区别,唯一的区别是,XposedHelpers.findClass中的路径名不同。

Hook匿名内部类中的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
anonymousInner(new ClasName() {
@Override
public void clasNameMethod(String value) {
Log.d(Tag, "匿名类参数修改:get:" + value);
}
});
-------------------------------------------------------------------
XposedHelpers.findAndHookMethod("com.xxxxxx.HookDemo$1", clazz.getClassLoader(),
"clasNameMethod",String.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
param.args[0] = "匿名类-参数修改"; }
});
-------------------------------------------------------------------
执行结果:
D/HookDemo: 匿名类参数修改:get:匿名类-参数修改
Hook替换函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void repleaceMethod(){
getHook("sfdsfs-afsafsf");
}
-------------------------------------------------------------------
XposedHelpers.findAndHookMethod(clazz, "repleaceMethod", new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable {
try {
// 直接替换原来要执行的逻辑代码,目标方法就不在执行了
} catch (Throwable t) {
XposedBridge.log(t);
}

}
});

替换资源

IXposedHookZygoteInit

Hook the initialization of Zygote process(es), from which all the apps are forked.

Implement this interface in your module’s main class in order to be notified when Android is starting up. In IXposedHookZygoteInit, you can modify objects and place hooks that should be applied for every app. Only the Android framework/system classes are available at that point in time. Use null as class loader for XposedHelpers.findAndHookMethod(String, ClassLoader, String, Object…) and its variants.

If you want to hook one/multiple specific apps, use IXposedHookLoadPackage instead.

替换系统框架资源(对所有app 起作用)需要实现 IXposedHookZygoteInit接口的 initZygote 方法,并在该方法中调用Resources.setSystemWideReplacement(…) 方法替换资源:

1
2
3
4
5
6
7
8
public class Tutorial2  implements IXposedHookZygoteInit{

@Override
public void initZygote(StartupParam arg0) throws Throwable {
XResources.setSystemWideReplacement("android", "bool", "config_unplugTurnsOnScreen", false);
}

}

IXposedHookInitPackageResources

替换app应用资源需要实现 IXposedHookInitPackageResources 类的
andleInitPackageResources方法,并在该方法中调用res.setReplacement(…)方法替换资源,注意在该方法中不要使用XResources.setSystemWideReplacement 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public void handleInitPackageResources(XC_InitPackageResources.InitPackageResourcesParam resparam) throws Throwable {

if (resparam.packageName.equals("com.example.xposedhooktarget")) {
//getClassInfo(clazz);
resparam.res.setReplacement("com.xxxxxx.xposedhooktarget", "string", "action_test", "HkTargetTest");
resparam.res.setReplacement("com.example.xposedhooktarget", "color", "colorAccent", R.color.colorPrimary);
resparam.res.setReplacement("2131165219", "HkTargetTest-action_text");
resparam.res.setReplacement("com.xxxxxxx.xposedhooktarget", "mipmap", "test_icon", new XResources.DrawableLoader() {
@Override
public Drawable newDrawable(XResources res, int id) throws Throwable {
Drawable dg = res.getDrawable(R.mipmap.ic_launcher);
return dg;
}
});

}
}

还可以替换动画文件等等。

修改布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
resparam.res.hookLayout("com.xxxxxx.xposedhooktarget", "layout", "activity_main", new XC_LayoutInflated() {
@Override
public void handleLayoutInflated(LayoutInflatedParam liparam) throws Throwable {
//增加一个TextView
FrameLayout contentview = (FrameLayout) liparam.view;
if (contentview != null) {
TextView textView = new TextView(contentview.getContext());
textView.setText("修改布局");
contentview.addView(textView);
}
//修改@+id= bt_text1 的TextView的文案
TextView bt_text1 = (TextView) liparam.view.findViewById(
liparam.res.getIdentifier("bt_text1", "id", "com.xxxxxx.xposedhooktarget"));
bt_text1.setText("修改布局文本");
}
});

群控

需求:被动添加好友。

要求:采用静默的无感知模式。

实现:思路,采用hook添加好友协议的模式,入口在db的hook基础上进行监听验证好友的请求,因为不论什么信息,都会首先会存储与微信的db中。

前提:已经hook了界面的跳转,和打开了微信的log信息。详细点击.

过程:在前提下,我们可以得到了三个关于被动添加好友的信息:

1、好友通过验证的界面名称和传递的参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
E/hotWeixinHook: ActivityHook; activity=com.tencent.mm.plugin.profile.ui.SayHiWithSnsPermissionUI@a3b6b62, onCreate; getIntent()=Intent
{ cmp=com.tencent.mm/.plugin.profile.ui.SayHiWithSnsPermissionUI (has extras), extra=bundle{
sayhi_with_sns_perm_send_verify=true,
room_name=null,
source_from_user_name=null,
sayhi_with_sns_perm_add_remark=true,
source_from_nick_name=null,
Contact_Nick=大王,
Contact_User=v1_841761d9ad2fee00bafee3e6c65ec45aef0c541d5c3bacfc2d24acd30fe184f31cd28a41808180cfff4ebd0325d4a1a1@stranger,
Contact_Scene=15,
Contact_RemarkName=,
=false,
AntispamTicket=v2_6e521f51ae39720c3154a79ba26c689efab115f39b314795f803a2889d76a013e05b3569cdd1f5328da35803ed134968@stranger

} }, bundle=bundle{}

2、验证的详细信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
MicroMsg.FMessageProvider; setDigest, 
fmsgType = 1, fmsgScene = 30,
fmsgContent =
<msg
fromusername="aibhyi1314521" encryptusername="v1_4bb4022da882fa9b480543b3a0e48fcc76b5530d371c9defe34a653497843e38@stranger"
fromnickname="忘记一切"
content="我是忘记一切"
fullpy="wangjiyiqie"
shortpy="WJYQ"
imagestatus="3"
scene="30" country="CN" province="Hebei" city="Baoding"
sign="带走一盏渔火,让他温暖我的双眼。留下一段真情让他停泊在枫桥边" percard="1" sex="1" alias="wuluqi0606" weibo="" albumflag="0" albumstyle="0" albumbgimgid="" snsflag="17"
snsbgimgid="http://shmmsns.qpic.cn/mmsns/CttmTaYSYkS6fvUyDJs0icQ6oRUqibtDIuUylCkHeRHfk4jF7nToTbSYAm5H0z84qh9fHACkovW1c/0"
snsbgobjectid="12854304281324228767" mhash="3140af8c3cd25f8db0eaa8815cc2cd95" mfullhash="3140af8c3cd25f8db0eaa8815cc2cd95"
bigheadimgurl="http://wx.qlogo.cn/mmhead/ver_1/qKwkYHXZa7iahrsJ2DV7NhuZnrWft9icLTiculKSTJiaJVQvpMPV1dyKH9HuqM3F9picnfZ6dd1ujxxCADH0tHburXhYQBsSP0ZQbD8USaqzXh7c/0" smallheadimgurl="http://wx.qlogo.cn/mmhead/ver_1/qKwkYHXZa7iahrsJ2DV7NhuZnrWft9icLTiculKSTJiaJVQvpMPV1dyKH9HuqM3F9picnfZ6dd1ujxxCADH0tHburXhYQBsSP0ZQbD8USaqzXh7c/96" ticket="v2_e57f9519b15cab7743fe81feced778340c7cb4a2c6cbbb361fdd606bbf815478db4e967568ece4fbf27519726dda64492f68992a4e750ef8e4e6e72724f075e8@stranger"
opcode="2" googlecontact="" qrticket="" chatroomusername="" sourceusername="" sourcenickname="">
<brandlist count="0" ver="685330891"></brandlist>
</msg>,
isSend = false

3、存储数据库名称。

MicroMsg.SDK.MAutoStorage; fmessage_conversation:get with primary key

4、根据1得到的界面,我们可以查看微信的逆向代码。我是用的jadx-0.7.2,不要用jadx低版本,因为低版本的逆向结果的代码中,不含有我们需要hook的具体类名和方法名。因为微信的包比较大,我们最好把逆向代码导出来,使用的时候用AS打开即可。

5、
我们看下点击发送按钮等源代码:(比较长,但是不贴一下,没法说啊)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
class C305316 implements OnMenuItemClickListener {
C305316() {
}

public final boolean onMenuItemClick(MenuItem menuItem) {
final C1138k c8233f;
SayHiWithSnsPermissionUI sayHiWithSnsPermissionUI;
Context context;
if (SayHiWithSnsPermissionUI.this.f97358pqM) {
int i;
List linkedList = new LinkedList();
linkedList.add(SayHiWithSnsPermissionUI.this.userName);
List linkedList2 = new LinkedList();
linkedList2.add(Integer.valueOf(SayHiWithSnsPermissionUI.this.f97352pnn));
String g = SayHiWithSnsPermissionUI.m63520g(SayHiWithSnsPermissionUI.this);
Map hashMap = new HashMap();
if (SayHiWithSnsPermissionUI.this.f97357pqL.f28645zEk) {
i = 1;
} else {
i = 0;
}
hashMap.put(SayHiWithSnsPermissionUI.this.userName, Integer.valueOf(i));
C6159x.m11666d("MicroMsg.SayHiWithSnsPermissionUI", "select sns permission, %s", Integer.valueOf(i));
if (C10044x.m20844Xg(SayHiWithSnsPermissionUI.this.userName)) {
c8233f = new C8233f(SayHiWithSnsPermissionUI.this.userName, g, SayHiWithSnsPermissionUI.this.getIntent().getStringExtra(C32536a.f104548xML));
C32688as.m69428CN().mo13442a(c8233f, 0);
sayHiWithSnsPermissionUI = SayHiWithSnsPermissionUI.this;
context = SayHiWithSnsPermissionUI.this.mController.f28409xRr;
SayHiWithSnsPermissionUI.this.getString(C1085R.C1081l.f8616dGZ);
sayHiWithSnsPermissionUI.f97348inI = C36199h.m82220a(context, SayHiWithSnsPermissionUI.this.getString(C1085R.C1081l.f10212eKs), true, new OnCancelListener() {
public final void onCancel(DialogInterface dialogInterface) {
C32688as.m69428CN().mo13446c(c8233f);
}
});
} else {
final C1138k c31589o = new C31589o(2, linkedList, linkedList2, g, "", hashMap, SayHiWithSnsPermissionUI.this.chatroomName);
String stringExtra = SayHiWithSnsPermissionUI.this.getIntent().getStringExtra("source_from_user_name");
String stringExtra2 = SayHiWithSnsPermissionUI.this.getIntent().getStringExtra("source_from_nick_name");
if (!C6135bi.m11498oN(stringExtra)) {
c31589o.mo40569fj(stringExtra, stringExtra2);
}
C32688as.m69428CN().mo13442a(c31589o, 0);
SayHiWithSnsPermissionUI sayHiWithSnsPermissionUI2 = SayHiWithSnsPermissionUI.this;
context = SayHiWithSnsPermissionUI.this.mController.f28409xRr;
SayHiWithSnsPermissionUI.this.getString(C1085R.C1081l.f8616dGZ);
sayHiWithSnsPermissionUI2.f97348inI = C36199h.m82220a(context, SayHiWithSnsPermissionUI.this.getString(C1085R.C1081l.f10212eKs), true, new OnCancelListener() {
public final void onCancel(DialogInterface dialogInterface) {
C32688as.m69428CN().mo13446c(c31589o);
}
});
}
} else if (SayHiWithSnsPermissionUI.this.f97359pqN) {
String stringExtra3 = SayHiWithSnsPermissionUI.this.getIntent().getStringExtra("Verify_ticket");
if (C10044x.m20844Xg(SayHiWithSnsPermissionUI.this.userName)) {
c8233f = new C8234g(SayHiWithSnsPermissionUI.this.userName, stringExtra3);
C32688as.m69428CN().mo13442a(c8233f, 0);
sayHiWithSnsPermissionUI = SayHiWithSnsPermissionUI.this;
context = SayHiWithSnsPermissionUI.this.mController.f28409xRr;
SayHiWithSnsPermissionUI.this.getString(C1085R.C1081l.f8616dGZ);
sayHiWithSnsPermissionUI.f97348inI = C36199h.m82220a(context, SayHiWithSnsPermissionUI.this.getString(C1085R.C1081l.f9343dUY), true, new OnCancelListener() {
public final void onCancel(DialogInterface dialogInterface) {
C32688as.m69428CN().mo13446c(c8233f);
}
});
} else {
c8233f = new C31589o(SayHiWithSnsPermissionUI.this.userName, stringExtra3, SayHiWithSnsPermissionUI.this.f97352pnn);
C32688as.m69428CN().mo13442a(c8233f, 0);
sayHiWithSnsPermissionUI = SayHiWithSnsPermissionUI.this;
context = SayHiWithSnsPermissionUI.this.mController.f28409xRr;
SayHiWithSnsPermissionUI.this.getString(C1085R.C1081l.f8616dGZ);
sayHiWithSnsPermissionUI.f97348inI = C36199h.m82220a(context, SayHiWithSnsPermissionUI.this.getString(C1085R.C1081l.f9343dUY), true, new OnCancelListener() {
public final void onCancel(DialogInterface dialogInterface) {
C32688as.m69428CN().mo13446c(c8233f);
}
});
}
}
return false;
}
}

经过一系列判断,我们最终最后定位到通过协议的代码C32688as.m69428CN().mo13442a(c8233f, 0);为啥啊,因为不管是if和else if的的条件我们都能够拿到,怎么拿到,我们可以通过XposedHelpers.getObjectField(param.thisObject, “方法变量”);获得到f97359pqN和f97358pqM的值;然后我们看C32688as这个类:

1
2
3
4
5
6
7
8
/* renamed from: com.tencent.mm.y.as */
public final class C32688as {
/* renamed from: CN */
public static C7656n m69428CN() {
C1693g.m4270Dr();
return C1693g.m4268Dp().f13865gRu;
}
}

再看C7656n调用类:好吧只看mo13442a这一个方法就行了,这个就是通过好友的开始方法了;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* renamed from: com.tencent.mm.ad.n */
public final class C7656n implements C1125e {
/* renamed from: a */
public final boolean mo13442a(C1138k c1138k, int i) {
boolean z = c1138k != null || i >= 0;
Assert.assertTrue(z);
String str = "worker thread has not been set";
if (this.f30978hoG != null) {
z = true;
} else {
z = false;
}
Assert.assertTrue(str, z);
if (!m15236e(c1138k)) {
return false;
}
m15231b(c1138k, i);
return true;
}
}

我们在看请求的参数:一个类C31589o,好吧,看出来了,就是一个model类。

1
2
3
/* renamed from: com.tencent.mm.pluginsdk.model.o */
public class C31589o extends C1138k implements C1865k {
}

综上:我们可以得到最终的静默实现好友验证请求通过了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@Override
public void onUpdate(Object database, String path, String table, ContentValues values, String whereClause, String[] whereArgs, int conflictAlgorithm) {
if (DBConstants.MicroMsg.Fmessage_Conversation.TABLE_NAME.equals(table)) {

Integer isNew = values.getAsInteger(DBConstants.MicroMsg.Fmessage_Conversation.COLUMN_isNew);
String talker = values.getAsString(DBConstants.MicroMsg.Fmessage_Conversation.COLUMN_talker);
Log.e(WeixinHook.TAG, "DBEventReceiveFriendsVerify; parseRequestContent;isNew."+isNew);

// isNew == 1:新好友请求
if (isNew != null && isNew == 1) {
try {
String fmsgContent = values.getAsString(DBConstants.MicroMsg.Fmessage_Conversation.COLUMN_fmsgContent);
Log.e(WeixinHook.TAG, "DBEventReceiveFriendsVerify; parseRequestContent;fmsgContent."+fmsgContent);

JSONObject jsonObject = new XmlToJson.Builder(fmsgContent).build().toJson();
JSONObject appMsg = jsonObject.getJSONObject("msg");
if (fmsgContent == null) {
Log.e(WeixinHook.TAG, "DBEventReceiveFriendsVerify; parseRequestContent; appMsg is null, just return.");
return;
}
Log.e(WeixinHook.TAG, "DBEventReceiveFriendsVerify; &wxid:=" + appMsg.getString("fromusername") + "&ticket:" + appMsg.getString("ticket") + "&scene:" + appMsg.getInt("scene"));
int state = Utils.getFCState(talker);
Log.e(WeixinHook.TAG, "DBEventReceiveFriendsVerify; state1:"+state);
if(state == 1){
report(appMsg.getString("fromusername").toString(),
appMsg.getString("fromnickname").toString(),
appMsg.getString("content").toString()
);
}else{
final Object instance_y_as_cn = XposedHelpers.callStaticMethod(
XposedHelpers.findClass("com.tencent.mm.y.as", mWeixinHook.getClassLoader()),
"CN"
);

String username = appMsg.getString("fromusername").toString();
String ticket = appMsg.getString("ticket").toString();
int scene = appMsg.getInt("scene");
Class<?> class_model_o = XposedHelpers.findClass("com.tencent.mm.pluginsdk.model.o", mWeixinHook.getClassLoader());
Object request = XposedHelpers.newInstance(class_model_o, username, ticket, scene);

Object result = XposedHelpers.callMethod(instance_y_as_cn, "a", request, 0);
Log.e(WeixinHook.TAG, "DBEventReceiveFriendsVerify; result; " + result);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

测试一下,搞定。

参考资源

https://api.xposed.info/reference/packages.html

https://bintray.com/rovo89/de.robv.android.xposed/api

http://www.infoq.com/cn/articles/android-in-depth-xposed

https://blog.csdn.net/u012417380/article/details/55254369?locationNum=13&fps=1

https://www.cnblogs.com/gordon0918/p/6732100.html