PE文件格式二(转载)

PE 教程 3: File Header (文件头)

 

本课我们将要研究 PE header 的 file header (文件头)部分。

至此,我们已经学到了哪些东东,先简要回顾一下 :

  • DOS MZ header 又命名为 IMAGE_DOS_HEADER . 。其中只有两个域比较重要 : e_magic 包含字符串 "MZ" , e_lfanew 包含 PE header 在文件中的偏移量。
  • 比较 e_magic 是否为 IMAGE_DOS_SIGNATURE 以验证是否是有效的 DOS header 。比对符合则认为文件拥有一个有效的 DOS header 。
  • 为了定位 PE header ,移动文件指针到 e_lfanew 所指向的偏移。
  • PE header 的第一个双字包含字符串 "PE/0/0" 。该双字与 IMAGE_NT_SIGNATURE 比对,符合则认为 PE header 有效。

本课我们继续探讨关于 PE header 的知识。 PE header 的正式命名是 IMAGE_NT_HEADERS 。再来回忆一下这个结构。

IMAGE_NT_HEADERS STRUCT
Signature dd ?
FileHeader IMAGE_FILE_HEADER <>
OptionalHeader IMAGE_OPTIONAL_HEADER32 <>
IMAGE_NT_HEADERS ENDS

Signature PE 标记,值为 50h, 45h, 00h, 00h ( PE/0/0 )。
FileHeader 该结构域包含了关于 PE 文件物理分布的一般信息。
OptionalHeader 该结构域包含了关于 PE 文件逻辑分布的信息。

最有趣的东东在 OptionalHeader 里。不过, FileHeader 里的一些域也很重要。本课我们将学习 FileHeader 下一课研究 OptionalHeader

IMAGE_FILE_HEADER STRUCT
Machine WORD ?
NumberOfSections WORD ?
TimeDateStamp dd ?
PointerToSymbolTable dd ?
NumberOfSymbols dd ?
SizeOfOptionalHeader WORD ?
Characteristics WORD ?
IMAGE_FILE_HEADER ENDS

Machine 该文件运行所要求的 CPU 。对于 Intel 平台,该值是 IMAGE_FILE_MACHINE_I386 (14Ch) 。我们尝试了 LUEVELSMEYER 的 pe.txt 声明的 14Dh 和 14Eh ,但 Windows 不能正确执行。看起来,除了禁止程序执行之外,本域对我们来说用处不大。
NumberOfSections 文件的节数目。如果我们要在文件中增加或删除一个节,就需要修改这个值。
TimeDateStamp 文件创建日期和时间。我们不感兴趣。
PointerToSymbolTable 用于调试。
NumberOfSymbols 用于调试。
SizeOfOptionalHeader 指示紧随本结构之后的 OptionalHeader 结构大小,必须为有效值。
Characteristics 关于文件信息的标记,比如文件是 exe 还是 dll 。

简言之,只有三个域对我们有一些用 : Machine , NumberOfSections Characteristics 。通常不会改变 Machine Characteristics 的值,但如果要遍历节表就得使用 NumberOfSections
为了更好阐述 NumberOfSections 的用处,这里简要介绍一下节表。

节表是一个结构数组,每个结构包含一个节的信息。因此若有 3 个节,数组就有 3 个成员。 我们需要 NumberOfSections 值来了解该数组中到底有几个成员。 也许您会想检测结构中的全 0 成员起到同样效果。 Windows 确实采用了这种方法。为了证明这一点,可以增加 NumberOfSections 的值, Windows 仍然可以正常执行文件。据我们的观察, Windows 读取 NumberOfSections 的值然后检查节表里的每个结构,如果找到一个全 0 结构就结束搜索,否则一直处理完 NumberOfSections 指定数目的结构。 为什么我们不能忽略 NumberOfSections 的值 ? 有几个原因。 PE 说明中没有指定节表必须以全 0 结构结束。 Thus there may be a situation where the last array member is contiguous to the first section, without empty space at all. Another reason has to do with bound imports. The new-style binding puts the information immediately following the section table's last structure array member. 因此您仍然需要 NumberOfSections 。

PE 教程 4: Optional Header

我们已经学习了关于 DOS header 和 PE header 中部分成员的知识。这里是 PE header 中最后、最大或许也是最重要的成员, optional header 。

回顾一下, optional header 结构是 IMAGE_NT_HEADERS 中的最后成员。包含了 PE 文件的逻辑分布信息。该结构共有 31 个域,一些是很关键,另一些不太常用。这里只介绍那些真正有用的域。

这儿有个关于 PE 文件格式的常用术语 : RVA
RVA 代表相对虚拟地址。 知道什么是虚拟地址吗?相对那些简单的概念而言,RVA有些晦涩。简言之,RVA是虚拟空间中到参考点的一段距离。我打赌您肯定熟悉文件偏移量: RVA就是类似文件偏移量的东西。当然它是相对虚拟空间里的一个地址,而不是文件头部。举例说明,如果PE文件装入虚拟地址(VA)空间的400000h处,且进程从虚址401000h开始执行,我们可以说进程执行起始地址在RVA 1000h。每个RVA都是相对于模块的起始VA的。
为什么PE文件格式要用到RVA呢? 这是为了减少PE装载器的负担。因为每个模块多有可能被重载到任何虚拟地址空间,如果让PE装载器修正每个重定位项,这肯定是个梦魇。相反,如果所有重定位项都使用RVA,那么PE装载器就不必操心那些东西了: 它只要将整个模块重定位到新的起始VA。这就象相对路径和绝对路径的概念: RVA类似相对路径,VA就象绝对路径。

AddressOfEntryPoint PE装载器准备运行的PE文件的第一个指令的RVA。若您要改变整个执行的流程,可以将该值指定到新的RVA,这样新RVA处的指令首先被执行。
ImageBase PE文件的优先装载地址。比如,如果该值是400000h,PE装载器将尝试把文件装到虚拟地址空间的400000h处。字眼"优先"表示若该地址区域已被其他模块占用,那PE装载器会选用其他空闲地址。
SectionAlignment 内存中节对齐的粒度。例如,如果该值是4096 (1000h),那么每节的起始地址必须是4096的倍数。若第一节从401000h开始且大小是10个字节,则下一节必定从402000h开始,即使401000h和402000h之间还有很多空间没被使用。
FileAlignment

文件中节对齐的粒度。例如,如果该值是(200h),,那么每节的起始地址必须是512的倍数。若第一节从文件偏移量200h开始且大小是10个字节,则下一节必定位于偏移量400h: 即使偏移量512和1024之间还有很多空间没被使用/定义。

MajorSubsystemVersion
MinorSubsystemVersion
win32子系统版本。若PE文件是专门为Win32设计的,该子系统版本必定是4.0否则对话框不会有3维立体感。
SizeOfImage 内存中整个PE映像体的尺寸。它是所有头和节经过节对齐处理后的大小。
SizeOfHeaders 所有头+节表的大小,也就等于文件尺寸减去文件中所有节的尺寸。可以以此值作为PE文件第一节的文件偏移量。
Subsystem NT用来识别PE文件属于哪个子系统。 对于大多数Win32程序,只有两类值: Windows GUI 和 Windows CUI (控制台)。
DataDirectory IMAGE_DATA_DIRECTORY 结构数组。每个结构给出一个重要数据结构的RVA,比如引入地址表等。

PE教程5: Section Table(节表)

请下载 范例

理论 :

到本课为止,我们已经学了许多关于 DOS header 和 PE header 的知识。接下来就该轮到 section table (节表)了。节表其实就是紧挨着 PE header 的一结构数组。该数组成员的数目由 file header ( IMAGE_FILE_HEADER ) 结构中 NumberOfSections 域的域值来决定。节表结构又命名为 IMAGE_SECTION_HEADER

IMAGE_SIZEOF_SHORT_NAME equ 8

IMAGE_SECTION_HEADER STRUCT
Name1 db IMAGE_SIZEOF_SHORT_NAME dup(?)
union Misc
PhysicalAddress dd ?
VirtualSize dd ?
ends
VirtualAddress dd ?
SizeOfRawData dd ?
PointerToRawData dd ?
PointerToRelocations dd ?
PointerToLinenumbers dd ? 

NumberOfRelocations dw ?
NumberOfLinenumbers dw ?
Characteristics dd ?
IMAGE_SECTION_HEADER ENDS

同样,不是所有成员都是很有用的,我们只关心那些真正重要的。

Name1 事实上本域的名称是 "name" ,只是 "name" 已被 MASM 用作关键字,所以我们只能用 "Name1" 代替。这儿的节名长不超过 8 字节。记住节名仅仅是个标记而已,我们选择任何名字甚至空着也行,注意这里不用 null 结束。命名 不是 一个 ASCIIZ 字符串,所以不用 null 结尾。
VirtualAddress 本节的 RVA (相对虚拟地址)。 PE 装载器将节映射至内存时会读取本值,因此如果域值是 1000h ,而 PE 文件装在地址 400000h 处,那么本节就被载到 401000h 。
SizeOfRawData 经过文件对齐处理后节尺寸, PE 装载器提取本域值了解需映射入内存的节字节数。(译者注 : 假设一个文件的文件对齐尺寸是 0x200 ,如果前面的 VirtualSize 域指示本节长度是 0x388 字节,则本域值为 0x400 ,表示本节是 0x400 字节长)。
PointerToRawData 这是节基于文件的偏移量, PE 装载器通过本域值找到节数据在文件中的位置。
Characteristics 包含标记以指示节属性,比如节是否含有可执行代码、初始化数据、未初始数据,是否可写、可读等。

现在我们已知晓 IMAGE_SECTION_HEADER 结构,再来模拟一下 PE 装载器的工作吧 :

  1. 读取 IMAGE_FILE_HEADER NumberOfSections 域,知道文件的节数目。
  2. SizeOfHeaders 域值作为节表的文件偏移量,并以此定位节表。
  3. 遍历整个结构数组检查各成员值。
  4. 对于每个结构,我们读取 PointerToRawData 域值并定位到该文件偏移量。然后再读取 SizeOfRawData 域值来决定映射内存的字节数。将 VirtualAddress 域值加上 ImageBase 域值等于节起始的虚拟地址。然后就准备把节映射进内存,并根据 Characteristics 域值设置属性。
  5. 遍历整个数组,直至所有节都已处理完毕。

注意我们并没有使用节名 : 这其实并不重要。

示例 :

本例程打开一 PE 文件遍历其节表,并在列表框控件显示各节的信息。

.386
.model flat,stdcall
option casemap:none
include /masm32/include/windows.inc
include /masm32/include/kernel32.inc
include /masm32/include/comdlg32.inc
include /masm32/include/user32.inc
include /masm32/include/comctl32.inc
includelib /masm32/lib/comctl32.lib
includelib /masm32/lib/user32.lib
includelib /masm32/lib/kernel32.lib
includelib /masm32/lib/comdlg32.lib

IDD_SECTIONTABLE equ 104
IDC_SECTIONLIST equ 1001

SEH struct


PrevLink dd ? ; the address of the previous seh structure
CurrentHandler dd ? ; the address of the new exception handler
SafeOffset dd ? ; The offset where it's safe to continue execution
PrevEsp dd ? ; the old value in esp
PrevEbp dd ? ; The old value in ebp
SEH ends

.data
AppName db "PE tutorial no.5",0
ofn OPENFILENAME <>
FilterString db "Executable Files (*.exe, *.dll)",0,"*.exe;*.dll",0
db "All Files",0,"*.*",0,0
FileOpenError db "Cannot open the file for reading",0
FileOpenMappingError db "Cannot open the file for memory mapping",0
FileMappingError db "Cannot map the file into memory",0
FileInValidPE db "This file is not a valid PE",0
template db "%08lx",0
SectionName db "Section",0
VirtualSize db "V.Size",0
VirtualAddress db "V.Address",0
SizeOfRawData db "Raw Size",0
RawOffset db "Raw Offset",0
Characteristics db "Characteristics",0

.data?
hInstance dd ?
buffer db 512 dup(?)
hFile dd ?
hMapping dd ?
pMapping dd ?
ValidPE dd ?
NumberOfSections dd ?

.code
start proc
LOCAL seh:SEH
invoke GetModuleHandle,NULL
mov hInstance,eax
mov ofn.lStructSize,SIZEOF ofn
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,512
mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
invoke CreateFile, addr buffer, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL
.if eax!=INVALID_HANDLE_VALUE
mov hFile, eax
invoke CreateFileMapping, hFile, NULL, PAGE_READONLY,0,0,0
.if eax!=NULL
mov hMapping, eax
invoke MapViewOfFile,hMapping,FILE_MAP_READ,0,0,0
.if eax!=NULL
mov pMapping,eax
assume fs:nothing
push fs:[0]
pop seh.PrevLink
mov seh.CurrentHandler,offset SEHHandler
mov seh.SafeOffset,offset FinalExit
lea eax,seh
mov fs:[0], eax
mov seh.PrevEsp,esp
mov seh.PrevEbp,ebp
mov edi, pMapping
assume edi:ptr IMAGE_DOS_HEADER
.if [edi].e_magic==IMAGE_DOS_SIGNATURE
add edi, [edi].e_lfanew
assume edi:ptr IMAGE_NT_HEADERS
.if [edi].Signature==IMAGE_NT_SIGNATURE
mov ValidPE, TRUE
.else
mov ValidPE, FALSE
.endif
.else
mov ValidPE,FALSE
.endif
FinalExit:
push seh.PrevLink
pop fs:[0]
.if ValidPE==TRUE
call ShowSectionInfo
.else
invoke MessageBox, 0, addr FileInValidPE, addr AppName, MB_OK+MB_ICONINFORMATION
.endif
invoke UnmapViewOfFile, pMapping
.else
invoke MessageBox, 0, addr FileMappingError, addr AppName, MB_OK+MB_ICONERROR
.endif
invoke CloseHandle,hMapping
.else
invoke MessageBox, 0, addr FileOpenMappingError, addr AppName, MB_OK+MB_ICONERROR
.endif
invoke CloseHandle, hFile
.else
invoke MessageBox, 0, addr FileOpenError, addr AppName, MB_OK+MB_ICONERROR
.endif
.endif
invoke ExitProcess, 0
invoke InitCommonControls
start endp

SEHHandler proc uses edx pExcept:DWORD,pFrame:DWORD,pContext:DWORD,pDispatch:DWORD
mov edx,pFrame
assume edx:ptr SEH
mov eax,pContext
assume eax:ptr CONTEXT
push [edx].SafeOffset
pop [eax].regEip
push [edx].PrevEsp
pop [eax].regEsp
push [edx].PrevEbp
pop [eax].regEbp
mov ValidPE, FALSE
mov eax,ExceptionContinueExecution
ret
SEHHandler endp

DlgProc proc uses edi esi hDlg:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
LOCAL lvc:LV_COLUMN
LOCAL lvi:LV_ITEM
.if uMsg==WM_INITDIALOG
mov esi, lParam
mov lvc.imask,LVCF_FMT or LVCF_TEXT or LVCF_WIDTH or LVCF_SUBITEM
mov lvc.fmt,LVCFMT_LEFT
mov lvc.lx,80
mov lvc.iSubItem,0
mov lvc.pszText,offset SectionName
invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_INSERTCOLUMN,0,addr lvc inc lvc.iSubItem
mov lvc.fmt,LVCFMT_RIGHT
mov lvc.pszText,offset VirtualSize
invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_INSERTCOLUMN,1,addr lvc
inc lvc.iSubItem
mov lvc.pszText,offset VirtualAddress
invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_INSERTCOLUMN,2,addr lvc
inc lvc.iSubItem
mov lvc.pszText,offset SizeOfRawData
invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_INSERTCOLUMN,3,addr lvc
inc lvc.iSubItem
mov lvc.pszText,offset RawOffset
invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_INSERTCOLUMN,4,addr lvc
inc lvc.iSubItem
mov lvc.pszText,offset Characteristics
invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_INSERTCOLUMN,5,addr lvc
mov ax, NumberOfSections
movzx eax,ax
mov edi,eax
mov lvi.imask,LVIF_TEXT
mov lvi.iItem,0
assume esi:ptr IMAGE_SECTION_HEADER
.while edi>0
mov lvi.iSubItem,0
invoke RtlZeroMemory,addr buffer,9
invoke lstrcpyn,addr buffer,addr [esi].Name1,8
lea eax,buffer
mov lvi.pszText,eax
invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_INSERTITEM,0,addr lvi
invoke wsprintf,addr buffer,addr template,[esi].Misc.VirtualSize
lea eax,buffer
mov lvi.pszText,eax
inc lvi.iSubItem
invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_SETITEM,0,addr lvi
invoke wsprintf,addr buffer,addr template,[esi].VirtualAddress
lea eax,buffer
mov lvi.pszText,eax
inc lvi.iSubItem
invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_SETITEM,0,addr lvi
invoke wsprintf,addr buffer,addr template,[esi].SizeOfRawData
lea eax,buffer
mov lvi.pszText,eax
inc lvi.iSubItem
invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_SETITEM,0,addr lvi
invoke wsprintf,addr buffer,addr template,[esi].PointerToRawData
lea eax,buffer
mov lvi.pszText,eax
inc lvi.iSubItem
invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_SETITEM,0,addr lvi
invoke wsprintf,addr buffer,addr template,[esi].Characteristics
lea eax,buffer
mov lvi.pszText,eax
inc lvi.iSubItem
invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_SETITEM,0,addr lvi
inc lvi.iItem
dec edi
add esi, sizeof IMAGE_SECTION_HEADER
.endw
.elseif
uMsg==WM_CLOSE
invoke EndDialog,hDlg,NULL
.else
mov eax,FALSE
ret
.endif
mov eax,TRUE
ret
DlgProc endp

ShowSectionInfo proc uses edi
mov edi, pMapping
assume edi:ptr IMAGE_DOS_HEADER
add edi, [edi].e_lfanew
assume edi:ptr IMAGE_NT_HEADERS
mov ax,[edi].FileHeader.NumberOfSections
movzx eax,ax
mov NumberOfSections,eax
add edi,sizeof IMAGE_NT_HEADERS
invoke DialogBoxParam, hInstance, IDD_SECTIONTABLE,NULL, addr DlgProc, edi
ret
ShowSectionInfo endp
end start

 分析 :

 

本例重用了 PE 教程 2 的代码,校验 PE 文件的有效性后,继续调用函数 ShowSectionInfo 显示各节信息。

ShowSectionInfo proc uses edi
mov edi, pMapping
assume edi:ptr IMAGE_DOS_HEADER
add edi, [edi].e_lfanew
assume edi:ptr IMAGE_NT_HEADERS

我们将 edi 用作指向 PE 文件数据的指针。首先,将指向 DOS header 地址的 pMapping 赋给 edi ,再加上 e_lfanew 域值等于 PE header 的地址。

mov ax,[edi].FileHeader.NumberOfSections
mov NumberOfSections,ax

因为我们要遍历节表,所以必须先获取文件的节数目。这就得靠 file header 里的 NumberOfSections 域了,切记这是个 word 域。

add edi,sizeof IMAGE_NT_HEADERS

现在 edi 正指向 PE header 的起始地址,加上 PE header 结构大小后恰好指向节表了。

invoke DialogBoxParam, hInstance, IDD_SECTIONTABLE,NULL, addr DlgProc, edi

调用 DialogBoxParam 显示列表对话框,注意我们已将节表地址作为最后一个参数传递过去了,该值可从 WM_INITDIALOG 消息的 lParam 参数中提取。

在对话框过程里我们响应 WM_INITDIALOG 消息,将 lParam 值 ( 节表地址 ) 存入 esi ,节数目赋给 edi 并设置列表控件。万事俱备后,进入循环将各节信息插入到列表控件中,这部分相当简单。

.while edi>0
mov lvi.iSubItem,0

字符串置入第一列。

invoke RtlZeroMemory,addr buffer,9
invoke lstrcpyn,addr buffer,addr [esi].Name1,8
lea eax,buffer
mov lvi.pszText,eax

要显示节名,当然要将其转换为 ASCIIZ 字符串先。

invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_INSERTITEM,0,addr lvi

然后显示第一列。
继续我们伟大的工程,显示完本节中最后一个欲呈现的值后,立马下一个结构。

dec edi
add esi, sizeof IMAGE_SECTION_HEADER
.endw

每处理完一节就递减 edi ,然后将 esi 加上 IMAGE_SECTION_HEADER 结构大小,使其指向下一个 IMAGE_SECTION_HEADER 结构。

遍历节表的步骤 :

  1. PE 文件有效性校验。
  2. 定位到 PE header 的起始地址。
  3. 从 file header 的 NumberOfSections 域获取节数。
  4. 通过两种方法定位节表 : ImageBase + SizeOfHeaders 或者 PE header 的起始地址 + PE header 结构大小。 ( 节表紧随 PE header) 。如果不是使用文件映射的方法,可以用 SetFilePointer 直接将文件指针定位到节表。节表的文件偏移量存放在 SizeOfHeaders 域里。 ( SizeOfHeaders IMAGE_OPTIONAL_HEADER 的结构成员 )
  5. 处理每个 IMAGE_SECTION_HEADER 结构。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了小程序应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值