java 字节码 对照表 编辑工具 与 jar破解实战

java 字节码解析与jar破解实战

1) 关于字节码的相关介绍,尤其是字节码对照表

可以参考Oracle官网Java8的jvms(Java虚拟机规范): https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5 第6.5节详细介绍了相关操作说明;

其它版本的jvms和jls(Java语言规范)亦可以在官网查看,建议下载PDF文件以便于随时查看: https://docs.oracle.com/javase/specs/;

维基百科也列出了相关的说明: https://en.wikipedia.org/wiki/Java_bytecode_instruction_listings (需科学上网),这里列出可复制到Excel进行自定义筛选.

这是组成Java字节码的指令的列表,Java字节码是一种抽象的机器语言,最终由Java虚拟机执行。Java字节码是从Java平台上运行的语言(最著名的是Java编程语言)生成的
请注意,根据Java指令集,任何引用的“值”都是指32位int。
助记符操作码(十六进制)操作码(二进制)其他字节[计数]:[操作数标签]堆叠 [之前]→[之后]描述
aaload320011 0010 arrayref,索引→值将数组中的引用加载到堆栈上
aastore530101 0011 arrayref,索引,值→将引用存储在数组中
aconst_null10000 0001 →空引用推入堆栈
aload190001 10011:索引→objectref从局部变量#index将引用加载到堆栈上
aload_02a0010 1010 →objectref从局部变量0将引用加载到堆栈上
aload_12b0010 1011 →objectref从局部变量1将引用加载到堆栈上
aload_22c0010 1100 →objectref从局部变量2将引用加载到堆栈上
aload_32d0010 1101 →objectref从局部变量3将引用加载到堆栈上
anewarraybd1011 11012:indexbyte1,indexbyte2计数→arrayref创建长度的参考文献的一个新的数组计数和由类参考识别出的部件类型索引| indexbyte2 indexbyte1 << 8在常量池中)
areturnb01011 0000 objectref→[空]从方法返回引用
arraylengthbe1011 1110 arrayref→长度获取数组的长度
astore3a0011 10101:索引objectref→将引用存储到局部变量#index中
astore_04b0100 1011 objectref→将引用存储到局部变量0
astore_14c0100 1100 objectref→将引用存储到局部变量1中
astore_24d0100 1101 objectref→将引用存储到局部变量2中
astore_34e0100 1110 objectref→将引用存储到局部变量3中
athrowbf1011 1111 objectref→[空],objectref引发错误或异常(注意已清除堆栈的其余部分,仅保留对Throwable的引用)
baload330011 0011 arrayref,索引→值从数组中加载字节或布尔值
bastore540101 0100 arrayref,索引,值→将字节或布尔值存储到数组中
bipush100001 00001:字节→值将一个字节作为整数值压入堆栈
breakpointca1100 1010  保留用于Java调试器中的断点;不应出现在任何类文件中
caload340011 0100 arrayref,索引→值从数组加载字符
castore550101 0101 arrayref,索引,值→将一个字符存储到数组中
checkcastc01100 00002:indexbyte1,indexbyte2objectref→objectref检查objectref是否为某种类型,其类引用在索引的常量池中(indexbyte1 << 8 | indexbyte2
d2f901001 0000 值→结果double转换为float
d2i8e1000 1110 值→结果将double转换为int
d2l8f1000 1111 值→结果将双精度型转换为长型
dadd630110 0011 值1,值2→结果加两个双打
daload310011 0001 arrayref,索引→值从数组加载双精度
dastore520101 0010 arrayref,索引,值→将双精度数存储到数组中
dcmpg981001 1000 值1,值2→结果比较两个双打,NaN为1
dcmpl971001 0111 值1,值2→结果比较两个双打,在NaN上为-1
dconst_00e0000 1110 →0.0将常数0.0double)压入堆栈
dconst_10f0000 1111 →1.0将常数1.0double)压入堆栈
ddiv6f0110 1111 值1,值2→结果除以两双
dload180001 10001:索引→值加载一个双从本地变量#INDEX
dload_0260010 0110 →值从局部变量0加载双精度
dload_1270010 0111 →值从局部变量1加载一个double
dload_2280010 1000 →值从局部变量2加载双精度
dload_3290010 1001 →值从局部变量3加载双精度
dmul6b0110 1011 值1,值2→结果乘以两个双打
dneg770111 0111 值→结果否定双
drem730111 0011 值1,值2→结果从两个双打之间的除数得到余数
dreturnaf1010 1111 值→[空]从方法返回双精度
dstore390011 10011:索引值→将双精度值存储到局部变量#index中
dstore_0470100 0111 值→将double存储到局部变量0
dstore_1480100 1000 值→将double存储到局部变量1
dstore_2490100 1001 值→将double存储到局部变量2中
dstore_34a0100 1010 值→将double存储到局部变量3
dsub670110 0111 值1,值2→结果从另一个减去一个双
dup590101 1001 值→值,值将值复制到堆栈顶部
dup_x15a0101 1010 值2,值1→值1,值2,值1将顶部值的副本插入顶部的两个值。value1和value2的类型不能为double或long。
dup_x25b0101 1011 值3,值2,值1→值1,值3,值2,值1从堆栈顶部插入两个值(如果value2是double或long,它也占用value3的条目)或三个值(如果value2既不是double也不是long),则将顶部值的副本插入堆栈。
dup25c0101 1100 {value2,value1}→{value2,value1},{value2,value1}复制前两个堆栈字(如果value1不为double或long,则为两个值;如果value1为double或long,则为单个值)
dup2_x15d0101 1101 值3,{值2,值1}→{值2,值1},值3,{值2,值1}复制两个单词,然后在第三个单词下方插入(请参见上面的说明)
dup2_x25e0101 1110 {value4,value3},{value2,value1}→{value2,value1},{value4,value3},{value2,value1}复制两个单词并插入第四个单词下方
f2d8d1000 1101 值→结果将浮点数转换为双精度数
f2i8b1000 1011 值→结果将float转换为int
f2l8c1000 1100 值→结果将浮点数转换为长整数
fadd620110 0010 值1,值2→结果添加两个浮点数
faload300011 0000 arrayref,索引→值从数组加载一个浮点数
fastore510101 0001 arrayref,索引,值→将浮点数存储在数组中
fcmpg961001 0110 值1,值2→结果比较两个浮点数,在NaN上为1
fcmpl951001 0101 值1,值2→结果比较两个浮点数,在NaN上为-1
fconst_00b0000 1011 →0.0英尺0.0f压入堆栈
fconst_10c0000 1100 →1.0分1.0f压入堆栈
fconst_20d0000 1101 →2.0f2.0f推入堆栈
fdiv6e0110 1110 值1,值2→结果划分两个浮点数
fload170001 01111:索引→值从局部变量#index加载浮点
fload_0220010 0010 →值从局部变量0 加载浮点
fload_1230010 0011 →值从局部变量1 加载浮点
fload_2240010 0100 →值从局部变量2 加载浮点
fload_3250010 0101 →值从局部变量3 加载浮点
fmul6a0110 1010 值1,值2→结果乘以两个浮点数
fneg760111 0110 值→结果取消浮动
frem720111 0010 值1,值2→结果从两个浮点数之间的除法得到余数
freturnae1010 1110 值→[空]返回浮点数
fstore380011 10001:索引值→将浮点存储到局部变量#index中
fstore_0430100 0011 值→将浮点存储到局部变量0
fstore_1440100 0100 值→将浮点存储到局部变量1中
fstore_2450100 0101 值→将浮点存储到局部变量2中
fstore_3460100 0110 值→将浮点存储到局部变量3中
fsub660110 0110 值1,值2→结果减去两个浮点数
getfieldb41011 01002:indexbyte1,indexbyte2objectref→值获取对象objectref的字段,其中该字段由常量池索引中的字段引用标识(indexbyte1 << 8 | indexbyte2
getstaticb21011 00102:indexbyte1,indexbyte2→值获取类的静态字段,其中该字段由常量池索引中的字段引用标识(indexbyte1 << 8 | indexbyte2
gotoa71010 01112:branchbyte1,branchbyte2[没有变化]前进到另一个指令branchoffset(符号短从无符号字节构成branchbyte1 << 8 | branchbyte2
goto_wc81100 10004:branchbyte1,branchbyte2,branchbyte3,branchbyte4[没有变化]前进到另一个指令branchoffset(signed int的自无符号字节构成| branchbyte2 << 16 | << branchbyte1 24 branchbyte3 << 8 | branchbyte4
i2b911001 0001 值→结果将int转换为字节
i2c921001 0010 值→结果将int转换为字符
i2d871000 0111 值→结果将int转换为double
i2f861000 0110 值→结果将int转换为float
i2l851000 0101 值→结果将int转换为long
i2s931001 0011 值→结果将int转换为short
iadd600110 0000 值1,值2→结果加两个整数
iaload2e0010 1110 arrayref,索引→值从数组中加载一个int
iand7e0111 1110 值1,值2→结果对两个整数执行按位与运算
iastore4f0100 1111 arrayref,索引,值→将一个int存储到一个数组中
iconst_m120000 0010 →-1将int值-1加载到堆栈上
iconst_030000 0011 →0将int值0加载到堆栈上
iconst_140000 0100 →1将int值1加载到堆栈上
iconst_250000 0101 →2将int值2加载到堆栈上
iconst_360000 0110 →3将int值3加载到堆栈上
iconst_470000 0111 →4将int值4加载到堆栈上
iconst_580000 1000 →5将int值5加载到堆栈上
idiv6c0110 1100 值1,值2→结果除以两个整数
if_acmpeqa51010 01012:branchbyte1,branchbyte2值1,值2→如果引用是相等的,分支到在指令branchoffset(符号短从无符号字节构成branchbyte1 << 8 | branchbyte2
if_acmpnea61010 01102:branchbyte1,branchbyte2值1,值2→如果引用不相等,则转移到在指令branchoffset(符号短从无符号字节构成branchbyte1 << 8 | branchbyte2
if_icmpeq9f1001 11112:branchbyte1,branchbyte2值1,值2→如果整数相等,分支到在指令branchoffset(符号短从无符号字节构成branchbyte1 << 8 | branchbyte2
if_icmpgea21010 00102:branchbyte1,branchbyte2值1,值2→如果值1大于或等于值2,则分支到在指令branchoffset(符号短从无符号字节构成branchbyte1 << 8 | branchbyte2
if_icmpgta31010 00112:branchbyte1,branchbyte2值1,值2→如果值1大于值2,分支到指令branchoffset(符号短从无符号字节构成branchbyte1 << 8 | branchbyte2
if_icmplea41010 01002:branchbyte1,branchbyte2值1,值2→如果值1小于或等于值2,则转移到指令在branchoffset(符号短从无符号字节构成branchbyte1 << 8 | branchbyte2
if_icmplta11010 00012:branchbyte1,branchbyte2值1,值2→如果值1小于值2,分支到指令branchoffset(符号短从无符号字节构成branchbyte1 << 8 | branchbyte2
if_icmpnea01010 00002:branchbyte1,branchbyte2值1,值2→如果整数不相等,则转移到在指令branchoffset(符号短从无符号字节构成branchbyte1 << 8 | branchbyte2
ifeq991001 10012:branchbyte1,branchbyte2值→如果是0,则转移到在指令branchoffset(符号短从无符号字节构成branchbyte1 << 8 | branchbyte2
ifge9c1001 11002:branchbyte1,branchbyte2值→如果是大于或等于0时,转移到指令在branchoffset(符号短从无符号字节构成branchbyte1 << 8 | branchbyte2
ifgt9d1001 11012:branchbyte1,branchbyte2值→如果大于0,则转移到在指令branchoffset(符号短从无符号字节构成branchbyte1 << 8 | branchbyte2
ifle9e1001 11102:branchbyte1,branchbyte2值→如果小于或等于0时,转移到指令在branchoffset(符号短从无符号字节构成branchbyte1 << 8 | branchbyte2
iflt9b1001 10112:branchbyte1,branchbyte2值→如果小于0,则转移到在指令branchoffset(符号短从无符号字节构成branchbyte1 << 8 | branchbyte2
ifne9a1001 10102:branchbyte1,branchbyte2值→如果不为0,则转移到在指令branchoffset(符号短从无符号字节构成branchbyte1 << 8 | branchbyte2
ifnonnullc71100 01112:branchbyte1,branchbyte2值→如果不为空,则分支到在指令branchoffset(符号短从无符号字节构成branchbyte1 << 8 | branchbyte2
ifnullc61100 01102:branchbyte1,branchbyte2值→如果是零,则分支到在指令branchoffset(符号短从无符号字节构成branchbyte1 << 8 | branchbyte2
iinc841000 01002:索引,常量[没有变化]通过带符号字节const递增局部变量#index
iload150001 01011:索引→值从局部变量#index加载int
iload_01a0001 1010 →值从局部变量0 加载一个int
iload_11b0001 1011 →值从局部变量1 加载一个int
iload_21c0001 1100 →值从局部变量2 加载一个int
iload_31d0001 1101 →值从局部变量3 加载一个int
impdep1fe1111 1110  保留给调试器中与实现有关的操作;不应出现在任何类文件中
impdep2ff1111 1111  保留给调试器中与实现有关的操作;不应出现在任何类文件中
imul680110 1000 值1,值2→结果两个整数相乘
ineg740111 0100 值→结果取反整数
instanceofc11100 00012:indexbyte1,indexbyte2objectref→结果确定对象objectref是否为给定类型,由常量池中的类引用indexindexbyte1 << 8 | indexbyte2) 标识
invokedynamicba1011 10104:indexbyte1,indexbyte2、0、0[arg1,arg2,...]→结果调用动态方法并将结果放在堆栈上(可能为空);该方法由常量池中的方法引用索引标识(indexbyte1 << 8 | indexbyte2
invokeinterfaceb91011 10014:indexbyte1,indexbyte2,count,0objectref,[arg1,arg2,...]→结果在对象objectref上调用接口方法,并将结果放在堆栈上(可能为空);接口方法由常量池中的方法引用索引标识(indexbyte1 << 8 | indexbyte2
invokespecialb71011 01112:indexbyte1,indexbyte2objectref,[arg1,arg2,...]→结果在对象objectref上调用实例方法并将结果放在堆栈上(可能为空);该方法由常量池中的方法引用索引标识(indexbyte1 << 8 | indexbyte2
invokestaticb81011 10002:indexbyte1,indexbyte2[arg1,arg2,...]→结果调用静态方法并将结果放在堆栈上(可能为空);该方法由常量池中的方法引用索引标识(indexbyte1 << 8 | indexbyte2
invokevirtualb61011 01102:indexbyte1,indexbyte2objectref,[arg1,arg2,...]→结果在对象objectref上调用虚拟方法并将结果放在堆栈上(可能为空);该方法由常量池中的方法引用索引标识(indexbyte1 << 8 | indexbyte2
ior801000 0000 值1,值2→结果按位int或
irem700111 0000 值1,值2→结果逻辑整数余数
ireturnac1010 1100 值→[空]从方法返回整数
ishl780111 1000 值1,值2→结果int左移
ishr7a0111 1010 值1,值2→结果int算术右移
istore360011 01101:索引值→将int存储到变量#index中
istore_03b0011 1011 值→将int存储到变量0中
istore_13c0011 1100 值→将int存储到变量1中
istore_23d0011 1101 值→将int存储到变量2中
istore_33e0011 1110 值→将int存储到变量3中
isub640110 0100 值1,值2→结果整数减
iushr7c0111 1100 值1,值2→结果逻辑右移
ixor821000 0010 值1,值2→结果异或
jsr†a81010 10002:branchbyte1,branchbyte2→地址跳转到子程序在branchoffset(符号短距离无符号字节构成branchbyte1 << 8 | branchbyte2),并放置在堆栈上的返回地址
jsr_w†c91100 10014:branchbyte1,branchbyte2,branchbyte3,branchbyte4→地址跳到子程序在branchoffset(signed int的自无符号字节构成| branchbyte2 << 16 | branchbyte3 << 8 | branchbyte1 << 24 branchbyte4)并放置在堆栈上的返回地址
l2d8a1000 1010 值→结果将多头转换为双倍
l2f891000 1001 值→结果将long转换为float
l2i881000 1000 值→结果将long转换为int
ladd610110 0001 值1,值2→结果加两个多头
laload2f0010 1111 arrayref,索引→值从数组加载长
land7f0111 1111 值1,值2→结果两个多位的按位
lastore500101 0000 arrayref,索引,值→将long存储到数组
lcmp941001 0100 值1,值2→结果如果两个long相同,则按0;如果value1大于value2,则按1;否则,则按-1。
lconst_090000 1001 →0公升将0L(长类型为零的数字)压入堆栈
lconst_10a0000 1010 →1公升将1L(长类型的数字1)推入堆栈
ldc120001 00101:索引→值将常量池中的常量#index(字符串,整数,浮点数,类,java.lang.invoke.MethodType,java.lang.invoke.MethodHandle或动态计算的常量)推入堆栈
ldc_w130001 00112:indexbyte1,indexbyte2→值将常量索引从常量池(字符串,整数,浮点数,类,java.lang.invoke.MethodType,java.lang.invoke.MethodHandle或动态计算的常量)推入堆栈(宽索引构造为indexbyte1 << 8 | indexbyte2
ldc2_w140001 01002:indexbyte1,indexbyte2→值将常量池中的常量#index(双精度,长整数或动态计算的常量)推入堆栈(宽索引构造为indexbyte1 << 8 | indexbyte2
ldiv6d0110 1101 值1,值2→结果划分两个多头
lload160001 01101:索引→值从局部变量#index加载一个长值
lload_01e0001 1110 →值从局部变量加载一个长值0
lload_11f0001 1111 →值从局部变量1加载一个长值
lload_2200010 0000 →值从局部变量2加载一个长值
lload_3210010 0001 →值从局部变量3加载长值
lmul690110 1001 值1,值2→结果乘以两个多头
lneg750111 0101 值→结果否定很长时间
lookupswitchab1010 10118 +:<0–3个字节填充>,defaultbyte1,defaultbyte2,defaultbyte3,defaultbyte4,npairs1,npairs2,npairs3,npairs4,匹配偏移对...键→使用键从表中查找目标地址,并从该地址处的指令继续执行
lor811000 0001 值1,值2→结果两个多位的按位或
lrem710111 0001 值1,值2→结果两个多头除法的余数
lreturnad1010 1101 值→[空]返回一个长值
lshl790111 1001 值1,值2→结果将long值1左移int value2位置
lshr7b0111 1011 值1,值2→结果将long值1的按位右移int value2的位置
lstore370011 01111:索引值→将长存储在局部变量#index中
lstore_03f0011 1111 值→将长存储在局部变量中0
lstore_1400100 0000 值→将长存储在局部变量1中
lstore_2410100 0001 值→将长存储在局部变量2中
lstore_3420100 0010 值→将长存储在局部变量3中
lsub650110 0101 值1,值2→结果减去两个多头
lushr7d0111 1101 值1,值2→结果长整数1的按位右移整数值2的位置,无符号
lxor831000 0011 值1,值2→结果两个long的按位XOR
monitorenterc21100 0010 objectref→输入对象的监视程序(“获取锁定” – synced()部分的开始)
monitorexitc31100 0011 objectref→退出对象的监视程序(“释放锁” – synced()节的结尾)
multianewarrayc51100 01013:indexbyte1,indexbyte2,尺寸count1,[count2,...]→arrayref在常量池索引indexbyte1 << 8 | indexbyte2)中创建一个新的数组,该数组的的类型由类引用标识;每个维度的尺寸由count1,[ count2等] 标识
newbb1011 10112:indexbyte1,indexbyte2→objectref在常量池索引中创建由类引用标识的类型的新对象(indexbyte1 << 8 | indexbyte2
newarraybc1011 11001:类型计数→arrayrefatype标识的原始类型的count元素创建新数组
nop00000 0000 [没有变化]不执行任何操作
pop570101 0111 值→丢弃栈顶值
pop2580101 1000 {value2,value1}→丢弃堆栈中的前两个值(如果是双精度或长整型,则丢弃一个值)
putfieldb51011 01012:indexbyte1,indexbyte2objectref,值→将字段设置为对象objectref中的value,其中该字段由常量池中的字段引用索引标识(indexbyte1 << 8 | indexbyte2
putstaticb31011 00112:indexbyte1,indexbyte2值→将静态字段设置为类中的,其中该字段由常量池中的字段引用索引标识(indexbyte1 << 8 | indexbyte2
ret†a91010 10011:索引[没有变化]从局部变量#index的地址继续执行(与jsr不对称是有意的)
returnb11011 0001 →[空]从方法返回void
saload350011 0101 arrayref,索引→值从数组中加载短路
sastore560101 0110 arrayref,索引,值→短数组存储
sipush110001 00012:字节1,字节2→值将short作为整数值压入堆栈
swap5f0101 1111 值2,值1→值1,值2交换堆栈上的两个高位字(请注意,value1和value2不能为double或long)
tableswitchaa1010 101016 +:[0–3个字节填充],defaultbyte1,defaultbyte2,defaultbyte3,defaultbyte4,lowbyte1,lowbyte2,lowbyte3,lowbyte4,highbyte1,highbyte2,highbyte3,highbyte4,跳转偏移量...索引→从偏移量索引表中的地址继续执行
widec41100 01003/5:操作码,索引字节1,索引字节2[与相应的说明相同]执行opcode,其中opcode是iload,fload,aload,lload,dload,istore,fstore,astore,lstore,dstore或ret,但假定索引为16位;或执行iinc,其中索引为16位,而要递增的常数为带符号的16位short
iinc,索引字节1,索引字节2,计数字节1,计数字节2
(no name)cb-fd   这些值当前未分配给操作码,并保留以供将来使用

2) 常用的字节码工具

常用工具有:

jd-gui,查看用比较好用,轻量级. 地址: http://java-decompiler.github.io/;

java bytecode editor,轻量级,不是很方面,但可实现字节码手动编辑,地址: http://set.ee/jbe/;

JByteMod,重量级,支持多种反编译工具,亦可实现单方法查看,地址: https://github.com/GraxCode/JByteMod-Beta;

需要注意:JByteMod 为 idea 项目,需要引入4个jar包;然后打包后生成的JByteMod-1.8.0.jar;

mvn install:install-file -Dfile=存放路径\JByteMod-Beta-master\lib\attach-1.7.jar -DgroupId=com.sun -DartifactId=attach -Dversion=1.7 -Dpackaging=jar

mvn install:install-file -Dfile=存放路径\JByteMod-Beta-master\lib\cfr_0_139.jar -DgroupId=org.benf -DartifactId=reader -Dversion=1.3.9 -Dpackaging=jar

mvn install:install-file -Dfile=存放路径\JByteMod-Beta-master\lib\procyon-0.5.33.jar -DgroupId=org.bitbucket.mstrobel -DartifactId=procyon -Dversion=0.5.33 -Dpackaging=jar

mvn install:install-file -Dfile=存放路径\JByteMod-Beta-master\lib\weblaf-complete-1.29-fixed.jar -DgroupId=com.weblaf -DartifactId=weblaf -Dversion=1.2.9 -Dpackaging=jar

3) jar包破解实战

原理: 某些商业用jar包,有license验证,那么找到验证方法,跳过判断,即该验证方法直接返回true.

工具: IDEA,JByteMod-XX.jar

以某商业Jar包为例; 找到License中的a方法为签名认证方法.

使用字节码编辑工具进行该方法编辑,编辑后结果为:

该方法对应的字节码为:

可简单记为:

iconst_1
ireturn

然后通过Ctrl+S另存为新的jar包,替换Maven仓库中的jar包即可实现破解.

写在后面的话: javaassist\asm\cglib\lombok等字节码工具包均是动态地修改了字节码,实现了动态代理.因此也可利用上述工具进行字节码文件修改达到破解目标.

最后,本博客提供破解方法仅供学习用,不得将破解软件用于商业目的.

希望本篇博客对各位看官有帮助.

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

sword_happy

您的鼓励亦是我创作的动力

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

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

打赏作者

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

抵扣说明:

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

余额充值