Android之Xposed框架完全使用指南

Xposed环境搭建

Xposed简介

Xposed是一款可以在不修改APK的情况下影响程序运行的框架,基于它可以制作出许多功能强大的模块,且在功能不冲突的情况下同时运作。在这个框架下,我们可以编写并加载自己编写的插件APP,实现对目标apk的注入拦截等。

Xposed原理

控制zygote进程,通过替换/system/bin/app_precess程序控制zygote进程,使得它在系统启动的时候会加载Xposed framework的一个jar文件即XposedBridge.jar,从而完成对zygote进程及其创建的Dalvik/ART虚拟机的劫持,并且能够允许开发者独立的替代任何class。

Xposed的安装

4.4以下的Android版本安装较为简单,只需要两步:

  1. 对需要安装xposed的手机进行root
  2. 下载并安装xposedinstall,之后授予root权限,进入app点击安装即可

从Android 5.0开始,谷歌使用ART替换Dalvik,所以Xposed安装有点麻烦,分为两个部分:xposed*.zip和XposedInstaller.apk。zip文件是框架主体,需要进入Recovery后刷入,apk文件是xposed模块管理应用的,主要用于下载,激活,是否启用模块等功能管理。步骤如下:

  1. 完成对手机的root,并刷入recovery(比如twrp)
  2. 下载对应的zip补丁包,进入recovery刷入
  3. 重启手机,安装XposedInstaller并授予root权限即可

实际操作如下:

http://dl-xda.xposed.info/framework/

在这里插入图片描述

首先去官网下载对应的补丁包,sdk23对应Android 6.0版本,其他对应版本请自行查询,我这里用的是6.0的系统

在这里插入图片描述

然后选择对应的系统架构

在这里插入图片描述

版本选择最新版,接着按照上面的步骤安装xposed模块即可。

Xposed插件开发

Xposed插件编写流程

  1. 拷贝XposedBridgeApi.jar到新建工程的libs目录并导入模块

在这里插入图片描述

将jar包导入到模块,并设置Configuration为compileOnly

在这里插入图片描述

  1. 在AndroidMainfest.xml中增加Xposed相关内容
//是否配置为xposed插件 设置为true
<meta-data android:name="xposedmodule" android:value="true"/>
//模块名称
<meta-data android:name="xposeddescription" android:value="模块描述(例如:第一个xposed插件)"/>
//最低版本号
<meta-data android:name="xposedminversion"android:value="54"/>
  1. 新建hook类,编写hook代码
package com.example.xposeddemo01;

import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

public class Xposed01 implements IXposedHookLoadPackage {
    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
        
    }
}

实现IXposedHookLoadPackage接口,然后在handleLoadPackage函数内编写Hook代码

  1. 新建assets文件夹,然后在assets目录下新建xposed_init,在里面写上hook类的完整路径(包名+类名),可以有多个,每一个类写一行

在这里插入图片描述

Xposed开发之Hook构造函数

相关API

需要用到的API如下:

XposedHelpers.findAndHookConstructor
无参构造函数的hook

首先编写一个目标hook类,类代码包含多个构造函数

package com.example.hookdemo01;

public class Student {
        String name;
        String id;
        int age;
    public Student(){
        name="default";
        id="default";
    }

    public Student(String name) {
        this.name = name;
    }
    public Student(String name, String id) {
        this.name = name;
        this.id = id;
    }

    public Student(String name, String id, int age) {
        this.name = name;
        this.id = id;
        this.age = age;
    }
}

然后打印出类的信息

package com.example.hookdemo01;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;

public class MainActivity extends AppCompatActivity {


    public void PrintStudent(Student stu)
    {
        Log.i("Xposed",stu.name+"--"+stu.id+"--"+stu.age);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Student studenta=new Student();
        Student studentb=new Student("GuiShou");
        Student studentc=new Student("GuiShou2","2021");
        Student studentd=new Student("GuiShou3","2021",20);

        PrintStudent(studenta);
        PrintStudent(studentb);
        PrintStudent(studentc);
        PrintStudent(studentd);

    }
}

接着编写hook代码,代码实现写在handleLoadPackage里

 //判断包名是否是要hook的包
        if (loadPackageParam.packageName.equals("com.example.hookdemo01"))
        {
            //获取当前的classloader
            ClassLoader classLoader=loadPackageParam.classLoader;

            //获取当前的Class
            Class studentClass=classLoader.loadClass("com.example.hookdemo01.Student");

            //hook无参构造函数 参数列表为空 第二个参数类型不需要传入
            XposedHelpers.findAndHookConstructor(studentClass, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);

                    XposedBridge.log("com.example.hookdemo01.Student() is called! beforeHookedMethod");

                }

                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    super.afterHookedMethod(param);
                    XposedBridge.log("com.example.hookdemo01.Student() is called! afterHookedMethod");

                }
            });

有参构造函数的hook

然后编写有参函数的hook代码

 //hook一个参数的构造函数 传入参数类型
            XposedHelpers.findAndHookConstructor(studentClass,String.class ,new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);

                    //获取参数数组
                    Object[] argsarray=param.args;
                    String name=(String)argsarray[0];
                    //打印参数名
                    XposedBridge.log("com.example.hookdemo01.Student(String) is called! beforeHookedMethod--"+name);

                }

                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    super.afterHookedMethod(param);
                    XposedBridge.log("com.example.hookdemo01.Student(String) is called! afterHookedMethod");

                }
            });


            //hook两个参数的构造函数 传入参数类型
            XposedHelpers.findAndHookConstructor(studentClass,String.class ,String.class,new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);

                    //获取参数数组
                    Object[] argsarray=param.args;
                    String name=(String)argsarray[0];
                    String id=(String)argsarray[1];
                    //打印参数名
                    XposedBridge.log("com.example.hookdemo01.Student(String,String) is called! beforeHookedMethod--"+name+"--"+id);

                }

                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    super.afterHookedMethod(param);
                    XposedBridge.log("com.example.hookdemo01.Student(String,String) is called! afterHookedMethod");

                }
            });


            //hook三个参数的构造函数 传入参数类型
            XposedHelpers.findAndHookConstructor(studentClass,String.class ,String.class,int.class,new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);

                    //获取参数数组
                    Object[] argsarray=param.args;
                    String name=(String)argsarray[0];
                    String id=(String)argsarray[1];
                    int age=(int)argsarray[2];

                    //打印参数名
                    XposedBridge.log("com.example.hookdemo01.Student(String,String,int) is called! beforeHookedMethod--"+name+"--"+id+"--"+age);

                }

                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    super.afterHookedMethod(param);
                    XposedBridge.log("com.example.hookdemo01.Student(String,String,int) is called! afterHookedMethod");

                }
            });

这里只需要调用findAndHookConstructor函数,完成回调函数编写即可

实际效果

启用xposed模块,重启目标app

在这里插入图片描述

可以看到这里打印出了所有相关的信息,如果需要还可以对参数进行修改

Xposed开发之修改属性

相关API

需要用到的API如下:

XposedHelpers.getStaticObjectField
XposedHelpers.setStaticObjectField
XposedHelpers.getObjectField
XposedHelpers.setObjectField

Xposed不只是可以实现对app自己实现的类构造函数的hook,对于类的属性字段也提供了一系列修改的API,首先修改目标类代码

package com.example.hookdemo01;
import android.util.Log;

public class Student {
        String name;
        String id;
        int age;
        private String nickname;
        public static String teachername;
        
    public Student(String name, String id, int age, String teacher,String nickname) {
        this.name = name;
        this.id = id;
        this.age = age;
        this.nickname = nickname;
        teachername=teacher;

        Log.i("Xposed","构造函数--teachername:"+teachername);
        Log.i("Xposed","构造函数--nickname:"+nickname);

    }
}

接着编写hook代码

修改静态字段和成员字段
package com.example.xposeddemo01;
import java.lang.reflect.Field;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

public class Xposed01 implements IXposedHookLoadPackage {
    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
        //判断包名是否是要hook的包
        if (loadPackageParam.packageName.equals("com.example.hookdemo01"))
        {
            //获取当前的classloader
            ClassLoader classLoader=loadPackageParam.classLoader;

            //获取当前的Class
            Class studentClass=classLoader.loadClass("com.example.hookdemo01.Student");

            //hook三个参数的构造函数 传入参数类型
            XposedHelpers.findAndHookConstructor(studentClass,String.class ,String.class,int.class,String.class,String.class,new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    super.afterHookedMethod(param);
                    XposedBridge.log("com.example.hookdemo01.Student(String,String,int) is called! afterHookedMethod");

                    //------------------------修改static属性-------------------------------------
                    //设置teacher字段
                    XposedHelpers.setStaticObjectField(studentClass,"teachername","teacher888");

                    //获取teacher字段 打印输出
                    String teacher = (String)XposedHelpers.getStaticObjectField(studentClass,"teachername");
                    XposedBridge.log("修改后的teachername字段:"+teacher);

                    //-------------------修改对象属性-----------------------------------------
                   XposedHelpers.setObjectField(param.thisObject,"nickname","pandan888");

                    //获取nickname字段 打印输出
                    String nickname=(String)XposedHelpers.getObjectField(param.thisObject,"nickname");
                    XposedBridge.log("修改后的nickname字段:"+nickname);
                }
            });

        }
    }
}

对于修改静态字段和成员字段,需要使用不同的API,xposed类内部已经取消了字段的访问检查,所以不需要自己取消检查,比反射修改字段的方案更加简洁

实际效果

在这里插入图片描述

Xposed开发之hook一般函数

相关API
XposedHelpers.findAndHookMethod
hook一般函数

首先修改一下目标app的代码

   public static String publicstaticfun(String arg1,int arg2)
    {
        //String result= privatestaticfun("test2",200);
        return arg1+"---"+arg2;
    }

增加一个静态的成员函数,接着编写hook代码

public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
        if (loadPackageParam.packageName.equals("com.example.hookdemo01"))
        {
            //获取当前的classloader
            ClassLoader classLoader=loadPackageParam.classLoader;

            //获取当前的Class
            Class studentClass=classLoader.loadClass("com.example.hookdemo01.Student");

            //hook
            XposedHelpers.findAndHookMethod(studentClass, "publicstaticfun", String.class, int.class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);

                    //打印参数
                    Object[] objectarray=param.args;
                    String arg0=(String)objectarray[0];
                    int arg1=(int)objectarray[1];
                    XposedBridge.log("beforeHookedMethod---arg0:"+objectarray[0]+"arg1:"+objectarray[1]);

                    //修改参数
                    objectarray[0]="GuiShou";
                    objectarray[1]=888;
                    XposedBridge.log("beforeHookedMethod---arg0:"+objectarray[0]+"arg1:"+objectarray[1]);


                }

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

                    //打印返回值
                    String result=(String)param.getResult();
                    XposedBridge.log("afterHookedMethod---result:"+result);

                    //修改返回值
                    param.setResult("this it result");
                    String result2=(String)param.getResult();
                    XposedBridge.log("afterHookedMethod---result:"+result2);

                }
            });
        }
    }
实际效果

在这里插入图片描述

可以看到,这里已经打印出了修改前后的参数和返回值。实际上,类中的其他函数,例如私有成员函数,匿名内部类函数等等,都是用同样的方法进行hook,只要填入jadx反编译的类名和函数名即可。

Xposed开发之主动调用函数

 public static String publicstaticfun(String arg1,int arg2)
    {
        Log.i("xposed","publicstaticfun is call");
        return arg1+"---"+arg2;
    }

    private String privatefun(String arg1,int arg2)
    {
        Log.i("xposed","privatefun is call");
        return arg1+"---"+arg2;
    }

首先修改代码,增加一个静态函数和一个成员函数,调用时触发log信息

相关API
XposedHelpers.callStaticMethod()
XposedHelpers.callMethod()
调用静态函数
	//获取当前的classloader
	ClassLoader classLoader=loadPackageParam.classLoader;

	//获取当前的Class
	Class studentClass=classLoader.loadClass("com.example.hookdemo01.Student");

	//调用静态函数
	XposedHelpers.callStaticMethod(studentClass,"publicstaticfun","guishou",100);
调用成员函数
            //获取当前的classloader
            ClassLoader classLoader=loadPackageParam.classLoader;

            //获取当前的Class
            Class studentClass=classLoader.loadClass("com.example.hookdemo01.Student");

            //调用构造函数 获取对象
            Object obj = XposedHelpers.newInstance(studentClass,"GuiShou3","2021",20,"teacher666","pandan666");

            //调用成员函数
            XposedHelpers.callMethod(obj,"privatefun","guishou",100);
实际效果

在这里插入图片描述

可以看到,静态函数和成员函数均被调用了。对于类中的静态函数,直接调用即可;对于成员函数,需要先得到类的实例,然后才能完成调用。

Xposed开发之加壳APP处理

对于加壳app的hook处理,实际上就是解决ClassLoader的问题。

对于加壳的APP,如果我们直接去hook当前的apk的话,那么壳代码中的ClassLoder必定是没有我们所需要hook的目标类。

由于壳代码的ClassLoader中并没有我们需要的类,所以在编写hook代码之前还需要一个步骤,就是在壳代码的修正ClassLoader之后,利用反射的方式拿到修正后的ClassLoader,然后传入修正后的ClassLoader,再对目标类进行hook

实际代码如下:

 public static Field getClassField(ClassLoader classloader, String class_name,
                                      String filedName) {

        try {
            Class obj_class = classloader.loadClass(class_name);//Class.forName(class_name);
            Field field = obj_class.getDeclaredField(filedName);
            field.setAccessible(true);
            return field;
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;

    }

    public static Object getClassFieldObject(ClassLoader classloader, String class_name, Object obj,
                                             String filedName) {

        try {
            Class obj_class = classloader.loadClass(class_name);//Class.forName(class_name);
            Field field = obj_class.getDeclaredField(filedName);
            field.setAccessible(true);
            Object result = null;
            result = field.get(obj);
            return result;
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;

    }

    public static Object invokeStaticMethod(String class_name,
                                            String method_name, Class[] pareTyple, Object[] pareVaules) {

        try {
            Class obj_class = Class.forName(class_name);
            Method method = obj_class.getMethod(method_name, pareTyple);
            return method.invoke(null, pareVaules);
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;

    }

    public static Object getFieldOjbect(String class_name, Object obj,
                                        String filedName) {
        try {
            Class obj_class = Class.forName(class_name);
            Field field = obj_class.getDeclaredField(filedName);
            field.setAccessible(true);
            return field.get(obj);
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NullPointerException e) {
            e.printStackTrace();
        }
        return null;

    }

    public static ClassLoader getClassloader() {
        ClassLoader resultClassloader = null;
        Object currentActivityThread = invokeStaticMethod(
                "android.app.ActivityThread", "currentActivityThread",
                new Class[]{}, new Object[]{});
        Object mBoundApplication = getFieldOjbect(
                "android.app.ActivityThread", currentActivityThread,
                "mBoundApplication");
        Application mInitialApplication = (Application) getFieldOjbect("android.app.ActivityThread",
                currentActivityThread, "mInitialApplication");
        Object loadedApkInfo = getFieldOjbect(
                "android.app.ActivityThread$AppBindData",
                mBoundApplication, "info");
        Application mApplication = (Application) getFieldOjbect("android.app.LoadedApk", loadedApkInfo, "mApplication");
        resultClassloader = mApplication.getClassLoader();
        return resultClassloader;
    }

调用代码中的getClassloader函数,即可获取到修正后的classloader,接着编写hook代码

    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {

        if (loadPackageParam.packageName.equals("com.kanxue.xposedhook01")) {
            
            //对壳入口类的onCreate函数进行hook 拿到classloader
            XposedHelpers.findAndHookMethod("com.stub.StubApp", loadPackageParam.classLoader, "onCreate", new XC_MethodHook() {
                
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    super.afterHookedMethod(param);
                    XposedBridge.log("com.stub.StubApp->onCreate afterHookedMethod");

                    ClassLoader finalClassLoader=getClassloader();
                    XposedBridge.log("finalClassLoader->" + finalClassLoader);
                    GetClassLoaderClasslist(finalClassLoader);
                    
                    //再对目标类进行hook 传入拿到的classloader
                    XposedHelpers.findAndHookMethod("com.kanxue.xposedhook01.Student", finalClassLoader, "privatefunc", String.class, int.class, new XC_MethodHook() {
                        @Override
                        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                            super.beforeHookedMethod(param);
                            Object[] objectarray = param.args;
                            String arg0 = (String) objectarray[0];
                            int arg1 = (int) objectarray[1];
                            XposedBridge.log("beforeHookedMethod11 privatefunc->arg0:" + arg0 + "---arg1:" + arg1);

                        }

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

                            String result = (String) param.getResult();
                            XposedBridge.log("afterHookedMethod11 privatefunc->result:" + result);

                        }
                    });

                    Class personClass = XposedHelpers.findClass("com.kanxue.xposedhook01.Student$person", finalClassLoader);
                    XposedHelpers.findAndHookMethod(personClass, "getpersonname", String.class, new XC_MethodHook() {
                        @Override
                        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                            super.beforeHookedMethod(param);
                            XposedBridge.log("beforeHookedMethod getpersonname->" + param.args[0]);
                        }

                        @Override
                        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                            super.afterHookedMethod(param);
                            XposedBridge.log("afterHookedMethod getpersonname->" + param.getResult());
                        }
                    });


                }
            });

        }
    }

首先需要hook入口类中的onCreate函数,在onCreate函数执行完成之后,获取到修正后的ClassLoader,接着再对目标类进行hook,此时hook的代码和hook一般函数一样,区别在于需要传入修正后的ClassLoder

实际效果

在这里插入图片描述

实际效果如图:可以看到这里已经成功对目标函数进行hook,打印出了hook之前和之后的函数相关信息。

Xposed指纹检测

可以根据下面的特征对xposed框架进行检测

  1. xposed插件的管理:XposedInstaller
  2. xposed对函数hook的根本原理:java函数变为native函数
  3. xposed框架拥有大量的api
  4. xposed框架特定的文件:运行库文件和链接库

github上检测Xposed的demo:XposedChecker

  • 8
    点赞
  • 82
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Xposed Installer是一个在Android系统上安装和管理Xposed框架的应用程序。根据引用和引用的内容,可以看出XposedInstaller.xpk文件需要放置在手机的/system/priv-app/XposedInstaller/目录下。这个目录是系统的私有应用目录,用于存放特权级别较高的应用。 根据引用中的描述,如果你使用的是较低版本的Android 6,并且想要安装Xposed框架,但由于无法连接到互联网,无法激活框架。这可能会导致XposedInstaller显示框架未安装的提示。 因此,为了解决这个问题,你可以按照以下步骤进行操作: 1. 下载适用于你的Android版本的XposedInstaller.xpk文件。 2. 将XposedInstaller.xpk文件复制到手机的/system/priv-app/XposedInstaller/目录下。 3. 重启手机,确保XposedInstaller应用已经安装。 4. 打开XposedInstaller应用,进入框架选项,点击安装/更新按钮,安装Xposed框架。 5. 完成安装后,重新启动手机。 这样,你就可以在Android 6上成功安装和使用Xposed框架了。请注意,这个方法仅适用于特定版本的Android系统和特定的安装文件。一定要确保你下载和使用适用于你的系统版本的XposedInstaller.xpk文件。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [XposedInstaller框架未安装解决方法-ADB shell 离线安装-2023年-安卓 Android 6](https://blog.csdn.net/qq_25798627/article/details/130047542)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鬼手56

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值