Java代码保密技术之(二)allatori配置文档选项说明

配置文件结构:

Allatori配置文件格式是xml格式,文件结构如下:

<config>

    <input basedir="input-jars" single-jar="application.jar">

        <jar in="app.jar" out="app-obf.jar"/>

        <jar in="input/*.jar" out="output/*.jar"/>



        <dir in="in-dir" out="out-dir"/>

    </input>



    <classpath basedir="library-jars">

        

<!-- Adding library.jar to the classpath -->



        <jar name="library.jar"/>

        

<!-- Adding all jars in the lib directory to the classpath -->



        <jar name="lib/*.jar"/>

        

<!-- Adding all jars in the lib2 directory and its subdirectories to the classpath -->



        <jar name="lib2/**/*.jar"/>

    </classpath>



    <keep-names>

        <class template="class SomeClass"/>

        <class template="class * instanceof java.io.Serializable"/>

        <class template="class com.package.*"/>

        <class access="protected+">

            <field access="protected+"/>

            <method access="protected+"/>

        </class>

        <class template="class com.company.abc.*">

            <field template="public int *"/>

            <method template="public get*(*)"/>

            <method template="public set*(*)"/>

        </class>

    </keep-names>



    <watermark key="secure-key-to-extract-watermark" value="Customer: John Smith"/>



    <expiry date="2017/01/01" string="EXPIRED!"/>



    

<!-- Configuration properties, all properties are optional -->



    

<!-- General properties, we recommend to use these two properties -->



    <property name="log-file" value="renaming-log.xml"/>

    <property name="random-seed" value="type anything here"/>



    

<!-- String encryption -->



    <property name="string-encryption" value="enable"/>

    <property name="string-encryption-type" value="fast"/>

    <property name="string-encryption-version" value="v4"/>

    <property name="string-encryption-ignored-strings" value="patterns.txt"/>



    

<!-- Control flow obfuscation -->



    <property name="control-flow-obfuscation" value="enable"/>

    <property name="extensive-flow-obfuscation" value="normal"/>



    

<!-- Renaming -->



    <property name="default-package" value="com.package"/>

    <property name="force-default-package" value="enable"/>



    <property name="packages-naming" value="abc"/>

    <property name="classes-naming" value="compact"/>

    <property name="methods-naming" value="compact"/>

    <property name="fields-naming" value="compact"/>

    <property name="local-variables-naming" value="optimize"/>



    <property name="update-resource-names" value="enable"/>

    <property name="update-resource-contents" value="enable"/>



    

<!-- Other -->



    <property name="line-numbers" value="obfuscate"/>

    <property name="generics" value="remove"/>

    <property name="inner-classes" value="remove"/>

    <property name="member-reorder" value="random"/>

    <property name="finalize" value="disable"/>

    <property name="version-marker" value="anyValidIdentifierName"/>

    <property name="synthetize-methods" value="all"/>

    <property name="synthetize-fields" value="all"/>

    <property name="remove-toString" value="enable"/>

    <property name="remove-calls" value="com.package.Logger.debug"/>

    <property name="output-jar-compression-level" value="9"/>



    

<!-- Incremental obfuscation -->



    <property name="incremental-obfuscation" value="input-renaming-log.xml"/>

</config>

备注:

1.可以使用相对路径

     2.可以使用系统变量和环境变量,比如用这个表达式:${System.getProperty(property.name)} and ${System.getenv(VARIABLE_NAME)}

1 配置具体标签解读:


1.1 input标签

Input标签是指定要混淆的的jar包和混淆输出结果的位置和名称的,有按目录输入的,也有单个jar包的,有两个属性,都是可选项:

basedir: 指定目录下多个

single-jar: 指定单个

如果是单个混淆,可以使用里面的内嵌的jar标签,同样有两个属性:

In:输入,必选项

Out:输出,必选项

如果是多个混淆,可以使用里面的内嵌的dir标签,同样有两个属性:

In:输入,必选项

Out:输出,必选项

1.2 classpath标签

这个是用来指定依赖包的位置的,有一个属性:basedir,用来指定依赖包的目录

有一个内嵌的标签:jar,使用属性name用来指定依赖包的名字,可以有多个jar标签。

1.3 keep-names标签

这个是用来指定哪些类名称,方法名称,字段名称不混淆的,如果混淆的是一个依赖包,应该指定包里面所有的public api不被混淆。如果混淆的是一个单独的应用,至少因该保持主启动类不能混淆。如果有的类在反射里面用到了,那这些类也不能混淆。

也可以使用注解更加精确的控制要混淆的要素,注解的优先级高于配置文件。

keep-names标签包括很多内嵌的标签,比如class, method,field, 同时class文件中也可单独指定method, filed标签类更加精确的控制哪些部分不能混淆。

这些内嵌的标签里面有两个属性用来控制哪些不混淆,二选一即可,但是必须要选一个:

access属性,通过访问修饰符的级别来限定,属于一种模糊匹配的形式,比如private ,private+等

template属性,更加强大,既可以通过通配符,也可精确指定,也可使用访问修饰符级别控制,但是,对于class标签和method标签,以及filed标签等不同的内嵌标签有不同的控制格式,具体来看:

1.3.1 class标签

除过access和template属性外,还有ignore,stop属性

Ignore, 可选项,

如果设置为true或者yes,则混淆类名,但是内嵌的method或filed指定的方法和字段保留不变。

如果设置为 keep-if-members-match,那么即使有一个内嵌的method或filed指定的方法和字段匹配上,这个类名也要保留不变。

Stop,可选项

如果设置为true或者yes,在本次混淆中,这个类所有的指定的混淆都不再起作用。

Template属性的配置格式有很多种:

注解,访问修饰符,类或者接口,类的全限定名(包括包层次结构的),还可以通过extends表示继承于某个类的所有子类,implements表示实现了某接口的所有实现类,instanceof表示某一类型的所有类,还可以使用regexp: 来表示使用正则表达式来匹配。

举个例子:

释义

class *

表示匹配所有的类和接口

interface *

匹配所有接口

public class *

匹配所有的public的类和接口

protected+ class *

匹配所有的protected和public的类和接口

class *abc*

匹配所有类的名字(含全限定名)中包含abc字样的类

class com.abc.*

匹配所有的com.abc包机其子包下面的所有的类

class *.abc.*

匹配所有的包名中含有abc目录的包及其子包下面所有的类

class * extends java.util.Enumeration

匹配所有继承自 java.util.Enumeration的类

class * extends *.Enumeration

匹配所有继承自 Enumeration的类

class * instanceof java.io.Serializable

匹配所有属于instances of java.io.Serializable的类

class * implements *.MouseListener

匹配所有实现了MouseListener接口的类

@java.lang.Deprecated class *

匹配所有标注了@java.lang.Deprecated注解的类

class regex:com.package.(foo|bar).*

使用正则表达式匹配所有 com.package.foo 和com.package.bar 包及其子包下面的所有类

class regex:com\.package\.(foo|bar)\..*

上面一个规则的精确表达方式,对.做了转义,因为正则表达式中.表示任何字符

1.3.2 filed标签

如果是放在class标签内部使用,只对外层class标签匹配的类里面的filed起作用。

他有两个属性,二选一即可:

Access,同上Class的属性

Template, 同上class属性,但略有不同,格式少了一些,他的格式有以下几种:

注解,访问修饰符,类型,字段名称,instanceof class,同样的也可以用正则表达式来匹配:

举个例子:

释义

*

匹配所有字段

private *

匹配所有private字段

private+ *

匹配所有字段

protected+ *

匹配所有protected 和public 字段

static *

匹配所有 static 字段.

public static *

匹配所有 public static 字段.

public int *

匹配所有 public int 字段.

java.lang.String *

匹配所有 String 类型的字段.

java.lang.* *

匹配所有 java.lang包下面的类类型的字段

abc*

匹配所有名称以"abc开头的字段

private abc*

匹配所有private 修饰符并且名称以"abc"开头的字段

* instanceof java.io.Serializable

匹配所有serializable字段

@java.lang.Deprecated *

匹配所有标注了@Deprecated注解的字段

regex:(a|b).*

使用正则表达式匹配所有以 "a" 或者 "b"开头的字段

1.3.3 method标签

跟filed一样,如果是内嵌在class里面的标签,只对外层class标签匹配的类里面的method起作用。

他又3个属性,access 和 templates 二选一即可:

access,同上

templates,同上,只是格式略有变化,,他的格式有以下几种:

注解,访问修饰符,类型,方法名称(参数),同样的也可以用正则表达式来匹配:

parameters, 可选,如果设置为keep, 则方法参数不会被混淆,对public api比较有用。

举个例子:

释义

*(**)

匹配所有方法

private *(**)

匹配所有private方法

private+ *(**)

匹配所有方法

protected+ *(**)

匹配所有protected 和public 方法

private+ *(*)

匹配所有只有一个参数的方法

private+ *(*,*)

匹配所有两个参数方法

private+ *(java.lang.String)

匹配所有只有一个参数,并且参数类型是String的方法

private+ *(java.lang.String,**)

匹配所有第一个参数类型是String的方法

private+ *(java.lang.*)

匹配所有只有一个参数,并且参数类型是java.lang包下面的某个类类型的方法

public get*(**)

匹配所有方法名称以"get"开头的方法

public *abc*(**)

匹配所有方法名称中包含 "abc"字样的方法

private+ int *(**)

匹配所有返回值类型是int的方法

@java.lang.Deprecated *(**)

匹配所有加了@Deprecated注解的方法

public regex:(g|s)et.*(**)

使用正则表达式匹配所有getter和setter方法

1.4 Watermark 标签

这个标签是用来加水印的,有两个必选属性:

Key:打水印的密钥

Value: 打水印的内容,可以是公司版权,作者名字,公司名称或者其他一些可以用于唯一标记的东西,用于标识知识产权。

水印是一个独立的功能,既可以在混淆构建时添加,也可以对已经混淆过的或者没有混淆的jar包添加水印

1.5 expire标签

这是用于标记应用授权期限的,有两个属性:

Date:过期日期

String:过期后的提示语

1.6其他设置:

其他设置通过property标签来定义,他有name和value两个属性

例如<property name="property-name" value="property-value"/>

1.6.1 基础属性
1.6.1.1 log-file

这个属性是用来写混淆日志的,保存了混淆前后的映射关系,可以用来还原混淆前的堆栈信息。

<property name="log-file" value="log.xml"/>

还原用法举例:

java -cp allatori.jar com.allatori.StackTrace2 log.xml input.txt output.txt

1.6.1.2 random-seed

因allatori混淆时每次都是随机的,要想使同一份输入每次的输出相同,可以用这个属性来控制。

<property name="random-seed" value="any text here"/>

1.6.2 字符串加密
1.6.2.1 string-encryption ,

默认是开启的enable, 也可使用apply2classs属性配合来单独指定在某个类中加密

<property name="string-encryption" value="disable" apply2class="class com.abc.*"/>

<property name="string-encryption" value="enable"/>

备注:开启本属性可以避免使用==比较字符串的代码不加密,不然会导致结果发生变化。

1.6.2.2 string-encryption-type

加密算法,他的值可选择项有fast(默认),strong,custom,

如果选择custom,也可以使用apply2class来单独指定,同时还应提供加解密方法;

例如:

<property name="string-encryption-type" value="strong" apply2class="class com.abc.*"/>

<property name="string-encryption-type" value="fast"/>

<property name="string-encryption-type"

value="custom(package.EncryptClassName.encryptMethodName, package.DecryptClassName.decryptMethodName)"

apply2class="class com.some.package.*"/>

可以有多个不同的加解密的方法应用于不同的类

1.6.2.3 string-encryption-version,

加密版本,默认是v4,也可以是v3,例如:

<property name="string-encryption-version" value="v3"/>

1.6.2.4 string-encryption-ignored-strings

这个属性需要给一个文件,文件里面载明需要保持不变的字符串,当然也可以用正则表达式匹配,比如:

<property name="string-encryption-ignored-strings" value="patterns.txt"/>

patterns.txt的内容如下:

Copyright*

All Rights Reserved*

*CompanyName*

regex:\d+

1.6.3流程控制属性
1.6.3.1 control-flow-obfuscation默认是enable的

<property name="control-flow-obfuscation" value="enable"/>

可以只是用在标注的类里面,用注解或者appluy2class (格式和template属性的一样的表达式)

<property name="control-flow-obfuscation" value="disable" apply2class="class com.abc.*"/>

<property name="control-flow-obfuscation" value="enable"/>

1.6.3.2 extensive-flow-obfuscation

使用的前提是control-flow-obfuscation开启,用法也基本一样

1.6.4重命名属性
1.6.4.1 default-package

如果某个包中的所有类都被重命名,那么Allatori会将它们移动到默认包中。若要将所有重命名的类绝对移动到默认包,应启用 force-default-package 属性。使用 “” 作为默认包将减小生成的 jar 的大小。

1.6.4.2 force-default-package

使用的前提是default-package设置了,用法也基本一样

1.6.4.3 packages-naming

使用此属性可以设置混淆时包的命名规则,可选项有以下几个:

释义

abc

默认值,包混淆时会被命名为 'a', 'b', 'c', 'd', ..., 'aa', 'ab',等小写字母

ABC

包混淆时会被命名为 'A', 'B', 'C', 'D', ..., 'AA', 'AB',等大写字母.

123

包混淆时会被命名为'1', '2', '3', ..., '00', '01'等数字

keep

包名保持不变

custom(filename.txt)

使用用户提供的文件里面的字符来命名,如果有多行的字符就混合使用命名

1.6.4.4 classes-naming

使用此属性可以设置混淆时类的命名规则,同包的命名基本一致,可选项有以下几个:

释义

compact

默认值,使用单字母命名,如果说有区别的话只是在大小写区别,在jar包里面是没问题的,但是解压后window环境下同名文件会覆盖。

iii

所有类名等长,使用大小写组合命名,比如 - iiii, iiiI, iiIi等 ,混淆出来的jar包略大

abc

混淆时会被命名为 'a', 'b', 'c', 'd', ..., 'aa', 'ab',等小写字母

ABC

混淆时会被命名为 'A', 'B', 'C', 'D', ..., 'AA', 'AB',等大写字母.

123

混淆时会被命名为'1', '2', '3', ..., '00', '01'等数字

windows

使用Windows文件关键字命名,比如 'con', 'prn', 'aux', 'nul',等等,同样的jar包里面可以,但是Windows上不能解压,包略大与abc命名

custom(filename.txt)

使用用户提供的文件里面的字符来命名,如果有多行的字符就混合使用命名

unique

唯一命名,每个类都有一个唯一的名字,在不同的包里面会有不同的名字

keep-$-sign

这种会保留内部类的命名保持不变

1.6.4.5 methods-naming

使用此属性可以设置混淆时方法的命名规则,同包的命名基本一致,可选项有以下几个:

释义

compact

默认值,使用单字母命名,如果说有区别的话只是在大小写区别,在jar包里面是没问题的,但是解压后window环境下同名文件会覆盖。

iii

所有类名等长,使用大小写组合命名,比如 - iiii, iiiI, iiIi等 ,混淆出来的jar包略大

abc

混淆时会被命名为 'a', 'b', 'c', 'd', ..., 'aa', 'ab',等小写字母

ABC

混淆时会被命名为 'A', 'B', 'C', 'D', ..., 'AA', 'AB',等大写字母.

123

混淆时会被命名为'1', '2', '3', ..., '00', '01'等数字

Keywords

使用Java关键字命名,比如 'if', 'for', 'int',等等,虽然不合乎规则,但是可以有效防止反编译器编译,包略大与abc命名

custom(filename.txt)

使用用户提供的文件里面的字符来命名,如果有多行的字符就混合使用命名

unique

唯一命名,每个类都有一个唯一的名字,在不同的包里面会有不同的名字

real

由于有一些方法名称可能不会按照配置的混淆规则重命名,allatori会把这些方法名字用来重命名别的方法,从而使人无法分辨那个是混淆后的名称。

1.6.4.6 fields-naming

使用此属性可以设置混淆时字段的命名规则,同方法的命名完全一致,可选项也一致

1.6.4.7 classes-naming-prefix

可以给混淆的类名加个前缀,例如

<property name="classes-naming-prefix" value="c_"/>

1.6.4.8 methods-naming-prefix

可以给混淆的方法名加个前缀,例如

<property name="methods-naming-prefix" value="c_"/>

1.6.4.9 fields-naming-prefix

可以给混淆的字段名加个前缀,例如

<property name="fields-naming-prefix" value="c_"/>

1.6.4.10 local-variables-naming

可选项如下:

释义

optimize

默认值,allatori会优化局部变量的总数,其余的局部变量会拒用相同的名称,推荐

single-name

几乎所有局部变量使用同一个名字,JVM里面没问题,但是可以防止反编译

abc

局部变量会被命名为'a', 'b', 'c', 'd'等

remove

删除原始的局部变量

keep-parameters

保留参数的名称不变

keep

所有局部变量名称都保留,不推荐

1.6.4.11 skip-renaming

跳过混淆重命名,这个如果enable会使配置的混淆规则失效,默认是disable。

<property name="skip-renaming" value="enable"/>

1.6.4.12 update-resource-names

默认是disable,即不更新配置文件里面关于类的名称,如果enable会根据混淆的类名更新文件中反射对应的该类的新名称更新文件内容,还可指定字符编码集。

还可以使用apply2file属性单独指定某个文件是否更新

例如:

<property name="update-resource-contents" value="enable"/>

    <property name="update-resource-contents" value="enable:UTF-8"/>

<property name="update-resource-contents" value="enable" apply2file="*.xml"/>

1.6.5 其他属性
1.6.5.1 line-numbers

Value

Description

obfuscate

默认值,混淆debug信息后使用allatori提供的还原工具可以还原出来堆栈的真实信息,比如

  java.lang.NullPointerException

        at com.company.c.a(m:61)

        at com.company.b.b(w:94)

        at com.company.b.a(w:83)

        at com.company.a.a(n:75)


使用堆栈还原工具还原后将变成原始信息,方便调试:

  java.lang.NullPointerException

        at com.company.Util.createTestException(Util.java:38)

        at com.company.TraceTest.testNullObject(TraceTest.java:53)

        at com.company.TraceTest.allTraceTests(TraceTest.java:14)

        at com.company.Main.runTest(Main.java:27)

remove

删除debug信息后,jar包体积会变小,但是拿到的堆栈可能就会变成如下这样:

  java.lang.NullPointerException

        at com.company.c.a(Unknown Source)

        at com.company.b.b(Unknown Source)

        at com.company.b.a(Unknown Source)

        at com.company.a.a(Unknown Source)

keep

保留debug信息,此选项虽然在调试时方便,但是上线时应该避免

  java.lang.NullPointerException

        at com.company.c.a(Util.java:38)

        at com.company.b.b(TraceTest.java:53)

        at com.company.b.a(TraceTest.java:14)

        at com.company.a.a(Main.java:27)

1.6.5.2 generics

泛型默认是keep的,也可以选择remove,这样使得jar包体积更小,性能更好,当然也可以使用apply2class单独指定包或者类是否擦除泛型。比如:

<property name="generics" value="keep" apply2class="class com.abc.*"/>

1.6.5.3 inner-classes

内部类默认是keep的,但是推荐remove,同样的也可以使用apply2class来单独指定

1.6.5.4 member-reorder

混淆后内部的成员是否会变,要根据以下几个可选项决定:

释义

enable

默认值,通常是按照顺序进行混淆,混淆后顺序保持不变,但是allatori会把这个顺序打乱随机重排

random

同上

alphabetical

使用字典顺序排序

reverse-alphabetical

使用字典顺序逆序排

disable

顺序不重排

同样的也可以使用apply2class来单独指定

1.6.5.5 finalize

<property name="finalize" value="enable"/>

默认disable,开启后可以提高性能,主要是针对没有子类的类标记为final类,可用于standlone应用。

1.6.5.6 version-marker

给定一个字符串,可以用来在混淆时标记并体现出当前使用的产品的版本

<property name="version-marker" value="DEMO_VERSION"/>

1.6.5.7 synthetize-methods

这个属性是说指定哪些方法被JVM生成synthetize方法,生成synthetize方法,同样的也可以使用apply2class特殊指定。

Value

Description

private

默认值,所有private方法都被标记为synthetize方法

all

所有方法都被标记为synthetize方法

package

所有包下面的方法都被标记为synthetize方法

protected

所有protected方法都被标记为synthetize方法

public

所有public方法都被标记为synthetize方法

disable

禁止生成synthetize方法

1.6.5.8 synthetize-fields

同synthetize-methods一致,比如:

<property name="synthetize-fields" value="all" apply2class="class com.abc.*"/>

1.6.5.9 remove-toString

顾名思义,是否移除toString方法, 默认disable, 可选项还有enable

<property name="remove-toString" value="enable"/>

1.6.5.10 remove-calls

可以删除对指定方法的调用。可用于删除调试日志记录调用。 类名和方法名可以包含 * 以匹配多个类/方法,也可以使用applay2class特殊指定。

如果方法具有返回值,则返回值将替换为 null(0,false),并在混淆期间打印出警告。

<property name="remove-calls" value="android.util.Log.*"/>

1.6.5.11 remove-annotations

可用于删除不必要的/调试注解信息。可以包含 * 以匹配多个类,也可以使用applay2class特殊指定。

比如:

<property name="remove-annotations" value="kotlin.Metadata"/>

    <property name="remove-annotations" value="com.package.annotations.*"/>

1.6.5.12 output-jar-compression-level

混淆输出文件的压缩机别从0-9逐渐提高压缩率。

<property name="output-jar-compression-level" value="9"/>

1.6.5.13 output-jar-duplicate-name-entries

Allatori混淆后的jar包中,在同一个目录下相同名称的类,指挥默认保留一个,即该属性的默认值是remove, 可选项是keep, 如果使用keep,allatori打出来的jar包将会带.zip后缀,同时jvm运行时需要指定参数--add-opens java.base/java.util.zip=ALL-UNNAMED,Java16之后的版本还需要再加--illegal-access=permit参数,比如:

<property name="output-jar-duplicate-name-entries" value="keep"/>

1.6.6 增量混淆
1.6.6.1 incremental-obfuscation

如果要增量混淆,因每次混淆的生成结果不一致,所以需要记录前一次混淆的相关信息才能使得增量部分跟原有部分保持一致。

<property name="incremental-obfuscation" value="input-renaming-log.xml"/>

1.6.6.2  unique-renaming

这个属性默认是disable的,如果任意两个方法具有相同的名称和签名,则这些方法将重命名为相同的新名称。如果任何两个方法具有不同的名称/签名,则这些方法在重命名后将具有不同的名称。enable它可确保后续增量混淆运行期间的一致性。也可使用apply2class特殊指定。

<property name="unique-renaming" value="enable" apply2class="class com.abc.*"/>

1.7 Ignore-classe 标签

顾名思义,用于在混淆时忽略某些类,配置如下:

<ignore-classes>

        <class template="class com.company.abc.*"/>

        <class template="class com.company.xyz.SomeClass"/>

    </ignore-classes>

2注解

Allatori也提供了注解标记埋点式的混淆配置方法,在allatori-annotations.jar.里面提供了很多注解,这些注解在混淆时将会被移除,但是秀雅在源代码里面添加埋点,所以不推荐

注解

加到哪里

释义

Rename

类, 方法,字段

重命名注解,可添加于类,方法,字段之上,优先级高于配置文件

DoNotRename

类, 方法,字段

重命名注解,可添加于类,方法,字段之上,优先级高于配置文件

StringEncryption

可选项有以下几个
    StringEncryption.ENABLE
    StringEncryption.DISABLE
    StringEncryption.MAXIMUM
    StringEncryption.MAXIMUM_WITH_WARNINGS
例如
@StringEncryption(StringEncryption.ENABLE)

StringEncryptionType

可选项有以下几个
    StringEncryptionType.FAST
    StringEncryptionType.STRONG
例如
@StringEncryptionType(StringEncryptionType.STRONG)

ControlFlowObfuscation

类,方法

可选项有以下几个
    ControlFlowObfuscation.ENABLE
    ControlFlowObfuscation.DISABLE
例如
@ControlFlowObfuscation(ControlFlowObfuscation.ENABLE)

ExtensiveFlowObfuscation

类,方法

可选项有以下几个
    ExtensiveFlowObfuscation.DISABLE
    ExtensiveFlowObfuscation.NORMAL
    ExtensiveFlowObfuscation.MAXIMUM
例如:
@ExtensiveFlowObfuscation(ExtensiveFlowObfuscation.MAXIMUM)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值