初学驱动逆向,笔记一。

初学驱动逆向,在《windows驱动开发技术详解》中随便找个例子逆逆看 [ \chapter09\SpecialStartIOTest ]。

水平很菜,各路牛人笑过。

逆向该例子的DriverEntry和CreateDevice函数,这两个函数在《windows驱动开发技术详解》的例子中十分常见。

并逆向的本范例驱动的核心部分 HelloDDKRead 函数,没什么技术含量。

经验:需要熟悉各数据结构内部的参数地址,便能方便地理解反汇编中的参数定位,相关结构可以通过WinDBG查看[DRIVER_OBJECT / DEVICE_OBJECT / Irp]。


DriverEntry

NTSTATUS __stdcall DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
public _DriverEntry@8
_DriverEntry@8 proc near

status= dword ptr -4
pDriverObject= dword ptr  8
pRegistryPath= dword ptr  0Ch

push    ebp         // 保护现场
mov     ebp, esp
push    ecx
push    offset aEnterDriverent ; "Enter DriverEntry\n"
call    _DbgPrint   // KdPrint(("Enter DriverEntry\n"));
add     esp, 4      // 申请空间
// 填充 Unload 与 IRP
// pDriverObject->DriverUnload = HelloDDKUnload;
mov     eax, [ebp+pDriverObject]
mov     dword ptr [eax+34h], offset ?HelloDDKUnload@@YGXPAU_DRIVER_OBJECT@@@Z ; HelloDDKUnload(_DRIVER_OBJECT *)
// pDriverObject->MajorFunction[IRP_MJ_CREATE] = HelloDDKDispatchRoutin;
mov     ecx, [ebp+pDriverObject]
mov     dword ptr [ecx+38h], offset ?HelloDDKDispatchRoutin@@YGJPAU_DEVICE_OBJECT@@PAU_IRP@@@Z ; HelloDDKDispatchRoutin(_DEVICE_OBJECT *,_IRP *)
// pDriverObject->MajorFunction[IRP_MJ_CLOSE] = HelloDDKDispatchRoutin;
mov     edx, [ebp+pDriverObject]
mov     dword ptr [edx+40h], offset ?HelloDDKDispatchRoutin@@YGJPAU_DEVICE_OBJECT@@PAU_IRP@@@Z ; HelloDDKDispatchRoutin(_DEVICE_OBJECT *,_IRP *)
// pDriverObject->MajorFunction[IRP_MJ_WRITE] = HelloDDKDispatchRoutin;
mov     eax, [ebp+pDriverObject]
mov     dword ptr [eax+48h], offset ?HelloDDKDispatchRoutin@@YGJPAU_DEVICE_OBJECT@@PAU_IRP@@@Z ; HelloDDKDispatchRoutin(_DEVICE_OBJECT *,_IRP *)
// pDriverObject->MajorFunction[IRP_MJ_READ] = HelloDDKRead;
mov     ecx, [ebp+pDriverObject]
mov     dword ptr [ecx+44h], offset ?HelloDDKRead@@YGJPAU_DEVICE_OBJECT@@PAU_IRP@@@Z ; HelloDDKRead(_DEVICE_OBJECT *,_IRP *)
// pDriverObject->MajorFunction[IRP_MJ_CLEANUP] = HelloDDKDispatchRoutin;
mov     edx, [ebp+pDriverObject]
mov     dword ptr [edx+80h], offset ?HelloDDKDispatchRoutin@@YGJPAU_DEVICE_OBJECT@@PAU_IRP@@@Z ; HelloDDKDispatchRoutin(_DEVICE_OBJECT *,_IRP *)
// pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = HelloDDKDispatchRoutin;
mov     eax, [ebp+pDriverObject]
mov     dword ptr [eax+70h], offset ?HelloDDKDispatchRoutin@@YGJPAU_DEVICE_OBJECT@@PAU_IRP@@@Z ; HelloDDKDispatchRoutin(_DEVICE_OBJECT *,_IRP *)
// pDriverObject->MajorFunction[IRP_MJ_SET_INFORMATION] = HelloDDKDispatchRoutin;
mov     ecx, [ebp+pDriverObject]
mov     dword ptr [ecx+50h], offset ?HelloDDKDispatchRoutin@@YGJPAU_DEVICE_OBJECT@@PAU_IRP@@@Z ; HelloDDKDispatchRoutin(_DEVICE_OBJECT *,_IRP *)
// pDriverObject->MajorFunction[IRP_MJ_SHUTDOWN] = HelloDDKDispatchRoutin;
mov     edx, [ebp+pDriverObject]
mov     dword ptr [edx+78h], offset ?HelloDDKDispatchRoutin@@YGJPAU_DEVICE_OBJECT@@PAU_IRP@@@Z ; HelloDDKDispatchRoutin(_DEVICE_OBJECT *,_IRP *)
// pDriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = HelloDDKDispatchRoutin;
mov     eax, [ebp+pDriverObject]
mov     dword ptr [eax+94h], offset ?HelloDDKDispatchRoutin@@YGJPAU_DEVICE_OBJECT@@PAU_IRP@@@Z ; HelloDDKDispatchRoutin(_DEVICE_OBJECT *,_IRP *)
//  status = CreateDevice(pDriverObject);
mov     ecx, [ebp+pDriverObject]
push    ecx             ; pDriverObject
call    ?CreateDevice@@YGJPAU_DRIVER_OBJECT@@@Z ; CreateDevice(_DRIVER_OBJECT *)
mov     [ebp+status], eax
//  KdPrint((“Leave DriverEntry\n”));
push    offset aLeaveDriverent ; "Leave DriverEntry\n"
call    _DbgPrint
// return status;
add     esp, 4
mov     eax, [ebp+status]
mov     esp, ebp          // 恢复现场
pop     ebp               // 恢复现场
retn    8
_DriverEntry@8 endp

源码参考:
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
	NTSTATUS status;
	KdPrint(("Enter DriverEntry\n"));
	
	//设置卸载函数
	pDriverObject->DriverUnload = HelloDDKUnload;
	
	//设置派遣函数
	pDriverObject->MajorFunction[IRP_MJ_CREATE] = HelloDDKDispatchRoutin;
	pDriverObject->MajorFunction[IRP_MJ_CLOSE] = HelloDDKDispatchRoutin;
	pDriverObject->MajorFunction[IRP_MJ_WRITE] = HelloDDKDispatchRoutin;
	pDriverObject->MajorFunction[IRP_MJ_READ] = HelloDDKRead;
	pDriverObject->MajorFunction[IRP_MJ_CLEANUP] = HelloDDKDispatchRoutin;
	pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = HelloDDKDispatchRoutin;
	pDriverObject->MajorFunction[IRP_MJ_SET_INFORMATION] = HelloDDKDispatchRoutin;
	pDriverObject->MajorFunction[IRP_MJ_SHUTDOWN] = HelloDDKDispatchRoutin;
	pDriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = HelloDDKDispatchRoutin;
	
	//创建驱动设备对象
	status = CreateDevice(pDriverObject);
	
	KdPrint(("Leave DriverEntry\n"));
	return status;
}

CreateDevice

int __stdcall CreateDevice(_DRIVER_OBJECT *pDriverObject)
?CreateDevice@@YGJPAU_DRIVER_OBJECT@@@Z proc near

DeviceName= _UNICODE_STRING ptr -24h
pDevExt= dword ptr -1Ch
devName= _UNICODE_STRING ptr -18h
status= dword ptr -10h
pDevObj= dword ptr -0Ch
symLinkName= _UNICODE_STRING ptr -8
pDriverObject= dword ptr  8

push    ebp
mov     ebp, esp              // 保护现场

// RtlInitUnicodeString(&devName, "\\Device\\MyDDKDevice");
sub     esp, 24h              // 申请空间
push    offset SourceString   // "\\Device\\MyDDKDevice"
lea     eax, [ebp+devName]
push    eax             ; DestinationString
call    ds:__imp__RtlInitUnicodeString@8 ; RtlInitUnicodeString(x,x)

// DeviceName.Length = devName.Length
mov     ecx, dword ptr [ebp+devName.Length]
mov     dword ptr [ebp+DeviceName.Length], ecx
// DeviceName.Buffer = devName.Buffer
mov     edx, [ebp+devName.Buffer]
mov     [ebp+DeviceName.Buffer], edx

// IoCreateDevice(pDriverObject, sizeof(DEVICE_EXTENSION), &(UNICODE_STRING)DeviceName, FILE_DEVICE_UNKNOWN, 0, TRUE, &pDevObj);
lea     eax, [ebp+pDevObj]
push    eax             ; DeviceObject
push    1               ; Exclusive
push    0               ; DeviceCharacteristics
push    22h             ; DeviceType
lea     ecx, [ebp+DeviceName]
push    ecx             ; DeviceName
push    28h             ; DeviceExtensionSize
mov     edx, [ebp+pDriverObject]
push    edx             ; DriverObject
call    ds:__imp__IoCreateDevice@28 ; IoCreateDevice(x,x,x,x,x,x,x)

// if (!NT_SUCCESS(status)) 
mov     [ebp+status], eax
cmp     [ebp+status], 0
jge     short loc_150E9

// FALSE
mov     eax, [ebp+pDevObj]    // pDevObj
mov     ecx, [eax+1Ch]        // pDevObj->Flags
or      ecx, 4                // Flags |= DO_BUFFERED_IO;
mov     edx, [ebp+pDevObj]    // pDevObj
mov     [edx+1Ch], ecx        // pDevObj->Flags |= DO_BUFFERED_IO;

mov     eax, [ebp+pDevObj]    // pDevObj
mov     ecx, [eax+28h]        // pDevObj->DeviceExtension
mov     [ebp+pDevExt], ecx    // pDevExt = pDevObj->DeviceExtension  [格式需要转换]
mov     edx, [ebp+pDevExt]
mov     eax, [ebp+pDevObj]
mov     [edx], eax            // pDevExt->pDevice = pDevObj; [设备拓展的第一个参数,遂无偏移]
mov     ecx, [ebp+pDevExt]
mov     edx, dword ptr [ebp+devName.Length]
mov     [ecx+4], edx          // 设备拓展第二个参数
mov     eax, [ebp+devName.Buffer]
mov     [ecx+8], eax          // pDevExt->ustrDeviceName = devName;
push    14h             ; size_t
push    0               ; int
mov     ecx, [ebp+pDevExt]
add     ecx, 14h              // 设备拓展第四个参数
push    ecx             ; void *
call    _memset               // _memset(&pDevExt->device_queue, 0, sizeof(pDevExt->device_queue));
add     esp, 0Ch              // 申请空间
mov     edx, [ebp+pDevExt]
add     edx, 14h              // 设备拓展第四个参数
push    edx             ; DeviceQueue
call    ds:__imp__KeInitializeDeviceQueue@4 ; KeInitializeDeviceQueue(x)    // KeInitializeDeviceQueue(&pDevExt->device_queue);
push    offset a??Helloddk ; "\\??\\HelloDDK"
lea     eax, [ebp+symLinkName]
push    eax             ; DestinationString
call    ds:__imp__RtlInitUnicodeString@8 ; RtlInitUnicodeString(x,x)    // RtlInitUnicodeString(&symLinkName, "\\??\\HelloDDK");
mov     ecx, [ebp+pDevExt]
mov     edx, dword ptr [ebp+symLinkName.Length]
mov     [ecx+0Ch], edx
mov     eax, [ebp+symLinkName.Buffer]
mov     [ecx+10h], eax
lea     ecx, [ebp+devName]
push    ecx             ; DeviceName
lea     edx, [ebp+symLinkName]
push    edx             ; SymbolicLinkName
call    ds:__imp__IoCreateSymbolicLink@8 ; IoCreateSymbolicLink(x,x)    // IoCreateSymbolicLink(&SymbolicLinkName, DevName);
// if(!NT_SUCCESS(status))
mov     [ebp+status], eax
cmp     [ebp+status], 0
jge     short loc_1517C
// TRUE
mov     eax, [ebp+pDevObj]
push    eax             ; DeviceObject
call    ds:__imp__IoDeleteDevice@4 ; IoDeleteDevice(x)   // IoDeleteDevice(pDevObj);
mov     eax, [ebp+status]
jmp     short loc_1517E


源码参考:
NTSTATUS CreateDevice (
		IN PDRIVER_OBJECT	pDriverObject) 
{
	NTSTATUS status;
	PDEVICE_OBJECT pDevObj;
	PDEVICE_EXTENSION pDevExt;
	
	//创建设备名称
	UNICODE_STRING devName;
	RtlInitUnicodeString(&devName,L"\\Device\\MyDDKDevice");
	
	//创建设备
	status = IoCreateDevice( pDriverObject,
						sizeof(DEVICE_EXTENSION),
						&(UNICODE_STRING)devName,
						FILE_DEVICE_UNKNOWN,
						0, TRUE,
						&pDevObj );
	if (!NT_SUCCESS(status))
		return status;

	pDevObj->Flags |= DO_BUFFERED_IO;
	pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
	pDevExt->pDevice = pDevObj;
	pDevExt->ustrDeviceName = devName;

	RtlZeroBytes(&pDevExt->device_queue,sizeof(pDevExt->device_queue));
	KeInitializeDeviceQueue(&pDevExt->device_queue);

	//创建符号链接
	UNICODE_STRING symLinkName;
	RtlInitUnicodeString(&symLinkName,L"\\??\\HelloDDK");
	pDevExt->ustrSymLinkName = symLinkName;
	status = IoCreateSymbolicLink( &symLinkName,&devName );
	if (!NT_SUCCESS(status)) 
	{
		IoDeleteDevice( pDevObj );
		return status;
	}
	return STATUS_SUCCESS;
}

IRP_MJ_READ

// 第一段:整个函数所有变量的定义。  
pDevExt= dword ptr -8           //  堆栈:PDEVICE_EXTENSION   pDevExt    [dword ptr -8]  
oldirql= byte ptr -4            //  堆栈:KIRQL   oldirql    [byte ptr -4]  
pDevObj= dword ptr  8           //  参数:PDEVICE_OBJECT   pDevObj  
pIrp= dword ptr  0Ch            //  参数:PIRP   pIrp  
  
// 第二段:KdPrint(("Enter HelloDDKRead\n"));  
push    ebp  
mov     ebp, esp               // 保护现场  
sub     esp, 8                 // 开拓8个字节的空间  
push    offset aEnterHelloddkr // "Enter HelloDDKRead\n"  
call    _DbgPrint  
  
// 第三段:PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;  
add     esp, 4                    // 开拓4字节空间  
mov     eax, [ebp+pDevObj]        // 定位到 pDevObj  
mov     ecx, [eax+28h]            // 定位到 pDevObj+28h,即为 pDevObj->DeviceExtension  
mov     [ebp+pDevExt], ecx        // 需要加上参数的转换,语句:pDevExt = pDevObj->DeviceExtension;   
  
// 第四段:IoMarkIrpPending(pIrp);   
//     #define IoMarkIrpPending( Irp ) ( IoGetCurrentIrpStackLocation( (Irp) )->Control |= SL_PENDING_RETURNED )   
//     #define IoGetCurrentIrpStackLocation( Irp ) ( (Irp)->Tail.Overlay.CurrentStackLocation )   
mov     edx, [ebp+pIrp]           // 定位到 pIrp  
mov     eax, [edx+60h]            // 定位到 pIrp+60h,即为 Irp->Tail.Overlay.CurrentStackLocation  
mov     cl, [eax+3]               // 定位到 ->Control  
or      cl, 1                     // 或运算,SL_PENDING_RETURNED  0x01  
mov     edx, [ebp+pIrp]  
mov     eax, [edx+60h]  
mov     [eax+3], cl               // 或运算结果赋值给 Irp->Tail.Overlay.CurrentStackLocation  
  
// 第五段:IoSetCancelRoutine(pIrp,OnCancelIRP)   
//     #define IoSetCancelRoutine( Irp, NewCancelRoutine ) (  \  
//     (PDRIVER_CANCEL) InterlockedExchangePointer( (PVOID *) &(Irp)->CancelRoutine, (PVOID) (NewCancelRoutine) ) )  
mov     edx, offset ?OnCancelIRP@@YGXPAU_DEVICE_OBJECT@@PAU_IRP@@@Z     // OnCancelIRP(_DEVICE_OBJECT *,_IRP *)  
mov     ecx, [ebp+pIrp]  
add     ecx, 38h            // Irp -> CancelRoutine  
call    ds:__imp_@InterlockedExchange@8    // InterlockedExchange(x,x)  
  
  
  
// 第六段:KeRaiseIrql(DISPATCH_LEVEL, &oldirql)   
// #define DISPATCH_LEVEL           2      // Dispatcher level  
// #define KeRaiseIrql(a,b)    *(b) = KfRaiseIrql(a)   
mov     cl, 2  
call    ds:__imp_@KfRaiseIrql@4  // KfRaiseIrql(x)  
mov     [ebp+oldirql], al        // 将函数调用结果存入oldirql  
  
// 第七段:KdPrint(("HelloDDKRead irp :%x\n", pIrp));  
mov     ecx, [ebp+pIrp]  
push    ecx  
push    offset aHelloddkreadIr ; "HelloDDKRead irp :%x\n"  
call    _DbgPrint  
  
// 第八段:KdPrint(("DeviceQueueEntry:%x\n", pIrp->Tail.Overlay.DeviceQueueEntry));  
add     esp, 8  
mov     edx, [ebp+pIrp]  
add     edx, 40h          // pIrp->40h,即 pIrp->Tail.Overlay.DeviceQueueEntry  
push    edx  
push    offset aDevicequeueent ; "DeviceQueueEntry:%x\n"  
call    _DbgPrint  
  
// 第九段:if (!KeInsertDeviceQueue(&pDevExt->device_queue, &pIrp->Tail.Overlay.DeviceQueueEntry));  
add     esp, 8  
mov     eax, [ebp+pIrp]  
add     eax, 40h           // pIrp->40h,即 pIrp->Tail.Overlay.DeviceQueueEntry  
push    eax                // DeviceQueueEntry  
mov     ecx, [ebp+pDevExt] // 定位到 pDevExt  
add     ecx, 14h           // pDevExt->14h,即是 &pDevExt->device_queue  
push    ecx                // DeviceQueue  
call    ds:__imp__KeInsertDeviceQueue@8  // KeInsertDeviceQueue(x,x)  
and     eax, 0FFh          // 取反  
test    eax, eax           // 判断  
jnz     short loc_141ED    // 跳转  
  
// 第十段:[判断为真] MyStartIo(pDevObj,pIrp);  
mov     edx, [ebp+pIrp]   
push    edx              // 参数:pFistIrp  
mov     eax, [ebp+pDevObj]   
push    eax              // 参数:DeviceObject  
call    ?MyStartIo@@YGXPAU_DEVICE_OBJECT@@PAU_IRP@@@Z  // MyStartIo(_DEVICE_OBJECT *,_IRP *)  
  
// 第十一段:[判断为假] KeLowerIrql(oldirql);  
mov     cl, [ebp+oldirql]        // 定位参数 oldirql  
call    ds:__imp_@KfLowerIrql@4  // KfLowerIrql(x)  
  
// 第十二段:KdPrint(("Leave HelloDDKRead\n"));  
push    offset aLeaveHelloddkr  // "Leave HelloDDKRead\n"  
call    _DbgPrint  
  
// 返回STATUS_PENDING状态:return STATUS_PENDING;  
add     esp, 4  
mov     eax, 103h    // #define STATUS_PENDING    ((NTSTATUS)0x00000103L)     // winnt  
mov     esp, ebp     // 恢复现场  
pop     ebp          // 恢复现场  
retn    8  
?HelloDDKRead@@YGJPAU_DEVICE_OBJECT@@PAU_IRP@@@Z endp  


源码对照:  
NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp)  
{  
    KdPrint(("Enter HelloDDKRead\n"));  
    PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;  
      
    //将IRP设置为挂起  
    IoMarkIrpPending(pIrp);  
    IoSetCancelRoutine(pIrp,OnCancelIRP);  
      
    KIRQL oldirql;  
    //提升IRP至DISPATCH_LEVEL  
    KeRaiseIrql(DISPATCH_LEVEL, &oldirql);  
    KdPrint(("HelloDDKRead irp :%x\n",pIrp));  
    KdPrint(("DeviceQueueEntry:%x\n",&pIrp->Tail.Overlay.DeviceQueueEntry));  
    if (!KeInsertDeviceQueue(&pDevExt->device_queue, &pIrp->Tail.Overlay.DeviceQueueEntry))  
        MyStartIo(pDevObj,pIrp);  
      
    //将IRP降至原来IRQL  
    KeLowerIrql(oldirql);  
    KdPrint(("Leave HelloDDKRead\n"));  
    //返回pending状态  
    return STATUS_PENDING;  
}  











  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MySQL初学笔记是一份由一位学习数据库的初学者总结的详细笔记,其中包含了MySQL的基础知识和高级应用。这份笔记包括了MySQL的高级查询方法和存储过程的编写,旨在帮助初学者更好地理解和应用MySQL数据库。这份笔记中还提供了一些具体的代码示例,可以帮助开发者进行实践和应用。 在MySQL初学笔记中,还涵盖了一些基本的语法规则和约束类型,比如非空、主键、唯一等。例如,在创建表时,可以在字段名和类型后面追加约束类型来设置列级约束。其中,支持的约束类型有:默认、非空、主键、唯一(除了外键都支持)。 这份笔记可以作为MySQL初学者的学习参考,特别是对于想要掌握MySQL的高级查询方法和利用存储过程编写复杂程序逻辑的学习者来说,是非常有用的。可以根据自己的需要,重点学习存储过程部分,并通过实践来提高自己的开发效率。 希望这份MySQL初学笔记能为初学者们提供帮助,并带领他们更深入地学习和应用MySQL数据库。如果有任何不对的地方,也欢迎指正和纠正。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [很详细的mysql数据库笔记.pdf](https://download.csdn.net/download/dafeidouzi/12043750)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [MySQL学习笔记2-高级查询与存储.md](https://download.csdn.net/download/weixin_52057528/88240999)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [尚硅谷MySQL基础学习笔记](https://blog.csdn.net/qq_21579045/article/details/98111827)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值