ELF文件与Enclave.so

ELF文件格式

左图是链接试图、右图是执行视图。链接视图强调链接器对目标文件及其Section的拼接过程,比如ELF文件、ELF文件映射都是链接视图。执行视图强调在实际执行中的ELF组织形式。《SGX初始化中ELF文件解析

注意这两者是有变化的,这是两套类似的副本,但前者是ELF文件,后者是实际执行的ELF副本,后者由前者提取组成。简单描述一下ELF程序加载的例子,我们首先将各种目标文件(包括可重定位文件、可执行文件、共享目标文件)拼接成ELF文件,由链接器完成。之后我们试图将ELF文件给跑起来(其中所包含的进程建立过程不做讲述,简单说就是会用到do_fork,进而使用clone或者fork)。

ELF文件中包含了许许多多的Section。下图就是一个ELF文件中的所有section,我这里使用了Swithless代码例子,Enclave.so,使用readelf读出来的内容。《Enclave文件布局信息

There are 34 section headers, starting at offset 0x184d8:

节头:
  [号] 名称              类型             地址              偏移量
       大小              全体大小          旗标   链接   信息   对齐
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         00000000000002a8  000002a8
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.gnu.build-i NOTE             00000000000002c4  000002c4
       0000000000000024  0000000000000000   A       0     0     4
  [ 3] .gnu.hash         GNU_HASH         00000000000002e8  000002e8
       0000000000000030  0000000000000000   A       4     0     8
  [ 4] .dynsym           DYNSYM           0000000000000318  00000318
       0000000000000090  0000000000000018   A       5     1     8
  [ 5] .dynstr           STRTAB           00000000000003a8  000003a8
       000000000000004d  0000000000000000   A       0     0     1
  [ 6] .gnu.version      VERSYM           00000000000003f6  000003f6
       000000000000000c  0000000000000002   A       4     0     2
  [ 7] .gnu.version_d    VERDEF           0000000000000408  00000408
       0000000000000038  0000000000000000   A       5     2     8
  [ 8] .rela.dyn         RELA             0000000000000440  00000440
       0000000000000168  0000000000000018   A       4     0     8
  [ 9] .plt              PROGBITS         0000000000001000  00001000
       0000000000000010  0000000000000010  AX       0     0     16
  [10] .plt.got          PROGBITS         0000000000001010  00001010
       0000000000000010  0000000000000008  AX       0     0     8
  [11] .text             PROGBITS         0000000000001020  00001020
       0000000000008600  0000000000000000  AX       0     0     16
  [12] .nipx             PROGBITS         0000000000009620  00009620
       0000000000000a28  0000000000000000  AX       0     0     16
  [13] .rodata           PROGBITS         000000000000b000  0000b000
       00000000000000e4  0000000000000000   A       0     0     32
  [14] .niprod           PROGBITS         000000000000b100  0000b100
       00000000000008c0  0000000000000000   A       0     0     64
  [15] .eh_frame_hdr     PROGBITS         000000000000b9c0  0000b9c0
       00000000000004ec  0000000000000000   A       0     0     4
  [16] .eh_frame         PROGBITS         000000000000beb0  0000beb0
       0000000000001704  0000000000000000   A       0     0     8
  [17] .fini_array       FINI_ARRAY       000000000000ed80  0000dd80
       0000000000000020  0000000000000008  WA       0     0     8
  [18] .data.rel.ro      PROGBITS         000000000000eda0  0000dda0
       00000000000000b0  0000000000000000  WA       0     0     32
  [19] .dynamic          DYNAMIC          000000000000ee50  0000de50
       0000000000000170  0000000000000010  WA       5     0     8
  [20] .got              PROGBITS         000000000000efc0  0000dfc0
       0000000000000038  0000000000000008  WA       0     0     8
  [21] .data             PROGBITS         000000000000f000  0000e000
       00000000000000a0  0000000000000000  WA       0     0     32
  [22] .nipd             PROGBITS         000000000000f0a0  0000e0a0
       0000000000000008  0000000000000000  WA       0     0     4
  [23] .bss              NOBITS           000000000000f0c0  0000e0a8
       00000000000006d0  0000000000000000  WA       0     0     32
  [24] .comment          PROGBITS         0000000000000000  0000e0a8
       0000000000000024  0000000000000001  MS       0     0     1
  [25] .note.sgxmeta     NOTE             0000000000000000  0000e0cc
       000000000000501c  0000000000000000           0     0     4
  [26] .debug_aranges    PROGBITS         0000000000000000  000130e8
       0000000000000060  0000000000000000           0     0     1
  [27] .debug_info       PROGBITS         0000000000000000  00013148
       0000000000000d8e  0000000000000000           0     0     1
  [28] .debug_abbrev     PROGBITS         0000000000000000  00013ed6
       0000000000000265  0000000000000000           0     0     1
  [29] .debug_line       PROGBITS         0000000000000000  0001413b
       0000000000000483  0000000000000000           0     0     1
  [30] .debug_str        PROGBITS         0000000000000000  000145be
       0000000000000d65  0000000000000001  MS       0     0     1
  [31] .symtab           SYMTAB           0000000000000000  00015328
       0000000000001da0  0000000000000018          32   311     8
  [32] .strtab           STRTAB           0000000000000000  000170c8
       00000000000012ca  0000000000000000           0     0     1
  [33] .shstrtab         STRTAB           0000000000000000  00018392
       0000000000000142  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)

但是,只有其中一部分Section是在实际跑的过程中会用到的,它们所在的Segment类型常见的有PT_LOAD、PT_TLS等等,里面的内容比如是这个样子。

Elf 文件类型为 DYN (共享目标文件)
Entry point 0x9c90
There are 11 program headers, starting at offset 64

程序头:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000000040 0x0000000000000040
                 0x0000000000000268 0x0000000000000268  R      0x8
  INTERP         0x00000000000002a8 0x00000000000002a8 0x00000000000002a8
                 0x000000000000001c 0x000000000000001c  R      0x1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x00000000000005a8 0x00000000000005a8  R      0x1000
  LOAD           0x0000000000001000 0x0000000000001000 0x0000000000001000
                 0x0000000000009048 0x0000000000009048  R E    0x1000
  LOAD           0x000000000000b000 0x000000000000b000 0x000000000000b000
                 0x00000000000025b4 0x00000000000025b4  R      0x1000
  LOAD           0x000000000000dd80 0x000000000000ed80 0x000000000000ed80
                 0x0000000000000328 0x0000000000000a10  RW     0x1000
  DYNAMIC        0x000000000000de50 0x000000000000ee50 0x000000000000ee50
                 0x0000000000000170 0x0000000000000170  RW     0x8
  NOTE           0x00000000000002c4 0x00000000000002c4 0x00000000000002c4
                 0x0000000000000024 0x0000000000000024  R      0x4
  GNU_EH_FRAME   0x000000000000b9c0 0x000000000000b9c0 0x000000000000b9c0
                 0x00000000000004ec 0x00000000000004ec  R      0x4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     0x10
  GNU_RELRO      0x000000000000dd80 0x000000000000ed80 0x000000000000ed80
                 0x0000000000000280 0x0000000000000280  R      0x1

 Section to Segment mapping:
  段节...
   00     
   01     .interp 
   02     .interp .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_d .rela.dyn 
   03     .plt .plt.got .text .nipx 
   04     .rodata .niprod .eh_frame_hdr .eh_frame 
   05     .fini_array .data.rel.ro .dynamic .got .data .nipd .bss 
   06     .dynamic 
   07     .note.gnu.build-id 
   08     .eh_frame_hdr 
   09     
   10     .fini_array .data.rel.ro .dynamic .got 

我们可以看到不是所有的Section都被添加到Segment并被运行起来。比如symtab、strtab节就只存在于ELF文件中,在实际跑的过程中并不存在。而dynsym、dynstr节是存在于Segment,并在实际跑的过程中存在的。

symtab节包含了所有的符号,它的作用主要是为了链接、调试的时候我们能找到符号。而dynsym是symtab的子集,并且dynsym只保留了需要重定位的符号,因为需要重定位的符号只有加载运行的时候才能知道它们的具体位置。而其他不需要重定位的符号,dynsym一概不存,因为我们在汇编层面只会用到符号的虚拟地址,只要虚拟地址确定的、不需要重定位的,我们没必要存,同时节约内存空间。《动态链接库中的.symtab和.dynsym

以enclave.signed.so为例子,Enclave文件映射保留了Enclave ELF文件的所有内容。而PT_LOAD类型的代码映射到了ELRANGE,其中包括.text、.data、.dynsym等等《Enclave虚拟内存视图

回到刚才提的ELF加载的例子中,我们已经将ELF文件链接好了。

那么我们首先会打开ELF文件,把EFL文件映射到进程的地址空间。《ELF格式及加载过程简介

紧接着检测ELF文件格式是否正常。

之后开辟一块匿名映射(匿名映射是指该映射并不指向某个具体文件)用作需要真正执行的代码的虚拟地址空间,然后将PT_LOAD等需要加载的类型加载到这个匿名映射空间,这个匿名映射空间就是真正会用到的代码的空间。

对于程序会用到的动态库,我们要用一个结构体soinfo记录下so的基本信息。并且完成动态库的加载,以及动态库中所有重定向符号的重定向。重定向的过程如图所示。

对所有的动态段(PT_DYNAMIC)中的每个动态节【ElfW(Dyn)】遍历,将其中的符号表偏移、重定位表偏移、重定位表大小、重定位表项大小、PLT重定位偏移、PLT大小进行记录,其中表的大小除以表项的大小就是表项的个数。然后遍历每一个重定位表项,每一个重定位表项指向一个虚拟空间地址(比如.text节中的某个数据符号、函数符号),要做的就是将符号表项、PLT项的实际位置都记录到重定位表项所指地址。这样以后当访问那个虚拟空间地址时候(比如.text节中的某个数据符号、函数符号),我们可以很方便的找到重定位的符号、PLT项的具体位置。这里也体现了dynsym中的重定位符号不能舍弃,但是symtab中的其他地址固定的符号可以舍弃《SGX的ELF文件映射配置重定位项

ELF格式细节

ELF的布局大致如下图所示。

1. Headers

包括ELF头、程序头、节头。其中ELF头有信息可以指向程序头和节头。程序头可以指向程序项,节头可以指向节项。程序头是在ELF文件中是可选的(在实际执行的副本上是必须有的),往往一个Segment是包含多个Section的。

1.1. ELF头

readelf -h

ELF 头:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  类别:                              ELF64
  数据:                              2 补码,小端序 (little endian)
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI 版本:                          0
  类型:                              DYN (共享目标文件)
  系统架构:                          Advanced Micro Devices X86-64
  版本:                              0x1
  入口点地址:               0x9c90
  程序头起点:          64 (bytes into file)
  Start of section headers:          99544 (bytes into file)
  标志:             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         11
  Size of section headers:           64 (bytes)
  Number of section headers:         34
  Section header string table index: 33

结构体如下《Chapter 7 Object File Format

//The ELF header has the following structure, defined in sys/elf.h:

#define EI_NIDENT       16
 
typedef struct {
        unsigned char   e_ident[EI_NIDENT]; 
        Elf32_Half      e_type;
        Elf32_Half      e_machine;
        Elf32_Word      e_version;
        Elf32_Addr      e_entry;
        Elf32_Off       e_phoff;
        Elf32_Off       e_shoff;
        Elf32_Word      e_flags;
        Elf32_Half      e_ehsize;
        Elf32_Half      e_phentsize;
        Elf32_Half      e_phnum;
        Elf32_Half      e_shentsize;
        Elf32_Half      e_shnum;
        Elf32_Half      e_shstrndx;
} Elf32_Ehdr;

typedef struct {
        unsigned char   e_ident[EI_NIDENT]; 
        Elf64_Half      e_type;
        Elf64_Half      e_machine;
        Elf64_Word      e_version;
        Elf64_Addr      e_entry;
        Elf64_Off       e_phoff;
        Elf64_Off       e_shoff;
        Elf64_Word      e_flags;
        Elf64_Half      e_ehsize;
        Elf64_Half      e_phentsize;
        Elf64_Half      e_phnum;
        Elf64_Half      e_shentsize;
        Elf64_Half      e_shnum;
        Elf64_Half      e_shstrndx;
} Elf64_Ehdr;

1.2. Program头

程序头是在ELF文件中是可选的(在实际执行的副本上是必须有的),往往一个Segment是包含多个Section的。

//A program header has the following structure, defined in sys/elf.h:

typedef struct {
        Elf32_Word      p_type;
        Elf32_Off       p_offset;
        Elf32_Addr      p_vaddr;
        Elf32_Addr      p_paddr;
        Elf32_Word      p_filesz;
        Elf32_Word      p_memsz;
        Elf32_Word      p_flags;
        Elf32_Word      p_align;
} Elf32_Phdr;

typedef struct {
        Elf64_Word      p_type;
        Elf64_Word      p_flags;
        Elf64_Off       p_offset;
        Elf64_Addr      p_vaddr;
        Elf64_Addr      p_paddr;
        Elf64_Xword     p_filesz;
        Elf64_Xword     p_memsz;
        Elf64_Xword     p_align;
} Elf64_Phdr;

1.3. Section头

//A section header has the following structure, defined in sys/elf.h:

typedef struct {
        Elf32_Word      sh_name;
        Elf32_Word      sh_type;
        Elf32_Word      sh_flags;
        Elf32_Addr      sh_addr;
        Elf32_Off       sh_offset;
        Elf32_Word      sh_size;
        Elf32_Word      sh_link;
        Elf32_Word      sh_info;
        Elf32_Word      sh_addralign;
        Elf32_Word      sh_entsize;
} Elf32_Shdr;

typedef struct {
        Elf64_Word      sh_name;
        Elf64_Word      sh_type;
        Elf64_Xword     sh_flags;
        Elf64_Addr      sh_addr;
        Elf64_Off       sh_offset;
        Elf64_Xword     sh_size;
        Elf64_Word      sh_link;
        Elf64_Word      sh_info;
        Elf64_Xword     sh_addralign;
        Elf64_Xword     sh_entsize;
} Elf64_Shdr;

Table 7-11 ELF Special Section Indexes

Name 

Value  

SHN_UNDEF

0

SHN_LORESERVE

0xff00

SHN_LOPROC

0xff00

SHN_BEFORE

0xff00

SHN_AFTER

0xff01

SHN_HIPROC

0xff1f

SHN_ABS

0xfff1

SHN_COMMON

0xfff2

SHN_HIRESERVE

0xffff

2. Section中与重定位相关的节

enclave.signed.so所有的节可以参考前面的图。这里讲与重定位相关的节。

重定位过程的图这里再放一份。目前认为重定位过程是对ELF文件映射进行操作的过程。但最终可以帮助实际执行的ELF副本完成符号地址查找。

通过.dynamic节(存于PT_DYNAMIC的段中),我们可以查到所有的重定位节,比如.rela、.rel、.plt节,rela节相比于rel节多了个addend(常量补齐)。.dynamic节内容例子如下。

Dynamic section at offset 0xde50 contains 19 entries:
  标记        类型                         名称/值
 0x000000000000001a (FINI_ARRAY)         0xed80
 0x000000000000001c (FINI_ARRAYSZ)       32 (bytes)
 0x000000006ffffef5 (GNU_HASH)           0x2e8
 0x0000000000000005 (STRTAB)             0x3a8
 0x0000000000000006 (SYMTAB)             0x318
 0x000000000000000a (STRSZ)              77 (bytes)
 0x000000000000000b (SYMENT)             24 (bytes)
 0x0000000000000015 (DEBUG)              0x0
 0x0000000000000003 (PLTGOT)             0xefc0
 0x0000000000000007 (RELA)               0x440
 0x0000000000000008 (RELASZ)             360 (bytes)
 0x0000000000000009 (RELAENT)            24 (bytes)
 0x000000006ffffffc (VERDEF)             0x408
 0x000000006ffffffd (VERDEFNUM)          2
 0x0000000000000018 (BIND_NOW)           
 0x000000006ffffffb (FLAGS_1)            标志: NOW PIE
 0x000000006ffffff0 (VERSYM)             0x3f6
 0x000000006ffffff9 (RELACOUNT)          13
 0x0000000000000000 (NULL)               0x0

重定位表内容例子如下

重定位节 '.rela.dyn' at offset 0x440 contains 15 entries:
  偏移量          信息           类型           符号值        符号名称 + 加数
00000000ed80  000000000008 R_X86_64_RELATIVE                    1020
00000000ed88  000000000008 R_X86_64_RELATIVE                    1030
00000000ed90  000000000008 R_X86_64_RELATIVE                    1050
00000000ed98  000000000008 R_X86_64_RELATIVE                    1070
00000000eda8  000000000008 R_X86_64_RELATIVE                    1083
00000000edb8  000000000008 R_X86_64_RELATIVE                    10e5
00000000edc8  000000000008 R_X86_64_RELATIVE                    1114
00000000edd8  000000000008 R_X86_64_RELATIVE                    1143
00000000ede8  000000000008 R_X86_64_RELATIVE                    11ab
00000000efd8  000000000008 R_X86_64_RELATIVE                    0
00000000efe8  000000000008 R_X86_64_RELATIVE                    9f6b
00000000f030  000000000008 R_X86_64_RELATIVE                    f030
00000000f038  000000000008 R_X86_64_RELATIVE                    8d20
00000000efe0  000100000006 R_X86_64_GLOB_DAT 0000000000000000 _Z9pcl_entryPvS_ + 0
00000000eff0  000200000006 R_X86_64_GLOB_DAT 0000000000000000 ippcpSetCpuFeatures + 0

符号表内容例子如下

Symbol table '.dynsym' contains 6 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _Z9pcl_entryPvS_
     2: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND ippcpSetCpuFeatures
     3: 0000000000000000     0 OBJECT  GLOBAL DEFAULT  ABS enclave.so
     4: 000000000000b100  1656 OBJECT  GLOBAL DEFAULT   14 g_global_data@@enclave.so
     5: 0000000000009c90     0 FUNC    GLOBAL DEFAULT   12 enclave_entry@@enclave.so

Symbol table '.symtab' contains 316 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000000002a8     0 SECTION LOCAL  DEFAULT    1 
     ......
    29: 0000000000000000     0 SECTION LOCAL  DEFAULT   29 
    30: 0000000000000000     0 SECTION LOCAL  DEFAULT   30 
    31: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS Enclave_t.c
    32: 0000000000001083    98 FUNC    LOCAL  DEFAULT   11 sgx_ecall_repeat_ocalls
    ......
   310: 0000000000001571   194 FUNC    LOCAL  DEFAULT   11 sgx_thread_setwait_untrus
   311: 0000000000009c90     0 FUNC    GLOBAL DEFAULT   12 enclave_entry
   312: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _Z9pcl_entryPvS_
   313: 0000000000000000     0 OBJECT  GLOBAL DEFAULT  ABS enclave.so
   314: 000000000000b100  1656 OBJECT  GLOBAL DEFAULT   14 g_global_data
   315: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND ippcpSetCpuFeatures

重定位过程要做的事情就是对所有重定位表项关联的符号、地址进行重定位,注意不是所有的符号或地址。

这里再回顾一下重定位具体的做法。

对所有的动态段(PT_DYNAMIC)中的每个动态节【ElfW(Dyn)】遍历,将其中的符号表偏移、重定位表偏移、重定位表大小、重定位表项大小、PLT重定位偏移、PLT大小进行记录,其中表的大小除以表项的大小就是表项的个数。然后遍历每一个重定位表项,每一个重定位表项指向一个虚拟空间地址(比如.text节中的某个数据符号、函数符号),要做的就是将符号表项、PLT项的实际位置都记录到重定位表项所指地址。

最终的目的是,以后当访问那个虚拟空间地址时候(比如.text节中的某个数据符号、函数符号),我们可以很方便的找到重定位的符号、PLT项的具体位置。

SGX中Enclave ELF文件解析

SGX初始化一开始会对Enclave.so进行解析,并对解析结果进行保存,方便验证文件完整性,并方便后续Enclave文件映射中的可加载部分放到ELRANGE中去。《SGX初始化中ELF文件解析

其中的过程包括了:

  1. 对ELF文件的头elf_hdr进行一个验证【validate_elf_header】
  2. 对EFI文件中每一个动态链接信息Segment(PT_DYNAMIC)中的每个动态项(dyn_entry,动态节),将这个动态项记录到m_dyn_info数组中,解析和保存的结果供后续使用。【parse_dyn】
  3. 检查是否存在未定义的符号symbol,此外,将一些关键的保留的符号存到ElfParser的类成员变量m_sym_table。【check_symbol_table】
  4. 检查之前parse_dyn扫描出来的动态节是否有任何不正确的重定位类型。【validate_reltabs】
  5. 通过查找.ctor节的Section Header,判断是否支持.ctor节(constructor缩写)。该节的存在,会导致全局初始化器无法正常被调用。【has_ctor_section】
  6. 将PT_LOAD、PT_TLS段记录下来,未来需要加载到ELRANGE【build_regular_sections】

SGX的.note.sgxmeta节

.note.sgxmeta节的布局说明
nameszname的大小
metadata sizemetadata的大小
type类型
name存储了name
metadata存储了metadata

这里面存储了SGX的metadata元数据。

metadata里面包含了对Enclave文件各项细节的记录,比如Enclave虚拟大小、SECS属性(里面记录X特性支持情况)、签名结构体等等。《SGX中的X特性、SGX获取元数据

typedef struct _metadata_t 
{
    uint64_t            magic_num;             /* The magic number identifying the file as a signed enclave image */
    uint64_t            version;               /* The metadata version */
    uint32_t            size;                  /* The size of this structure */
    uint32_t            tcs_policy;            /* TCS management policy */
    uint32_t            ssa_frame_size;        /* The size of SSA frame in page */
    uint32_t            max_save_buffer_size;  /* Max buffer size is 2632 */
    uint32_t            desired_misc_select;
    uint32_t            tcs_min_pool;          /* TCS min pool*/         
    uint64_t            enclave_size;          /* enclave virtual size */
    sgx_attributes_t    attributes;            /* XFeatureMask to be set in SECS. */
    enclave_css_t       enclave_css;           /* The enclave signature */
    data_directory_t    dirs[DIR_NUM];
    uint8_t             data[18592];
}metadata_t;

SGX的ELF文件映射配置重定位项

基本就是前面讲的重定位过程。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值