反病毒工具之注册表监视器(VC DLL源码) (chenhui530)

 反病毒工具之注册表监视器(VC DLL源码)   <script src="http://blog.csdn.net/count.aspx?ID=2079118&Type=Rank" type="text/javascript"></script> 文章指数:0   CSDN Blog推出文章指数概念,文章指数是对Blog文章综合评分后推算出的,综合评分项分别是该文章的点击量,回复次数,被网摘收录数量,文章长度和文章类型;满分100,每月更新一次。

核心HOOK API类,理论上可以HOOK 任何使用STDCALL声明的API函数

// HookInfo.h: interface for the CHookInfo class.
//
//

#if !defined(AFX_HOOKINFO_H__D44F115C_76F1_4CC7_BD61_4C393417DA10__INCLUDED_)
#define AFX_HOOKINFO_H__D44F115C_76F1_4CC7_BD61_4C393417DA10__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

typedef struct _HOOKSTRUCT
{
    FARPROC pfFunAddr; //用于保存API函数地址
    BYTE    OldCode[5]; //保存原API前5个字节
    BYTE    NewCode[5]; //JMP XXXX其中XXXXJMP的地址
}HOOKSTRUCT;

class CHookInfo 
{
public:
 //HOOK 处理函数
 CHookInfo(char *strDllName, char *strFunName, DWORD dwMyFunAddr);
 virtual ~CHookInfo(); //析构函数
 HOOKSTRUCT *pHook; //HOOK结构
 void HookStatus(BOOL blnHook); //关闭/打开HOOK状态
};

CHookInfo::CHookInfo(char *strDllName, char *strFunName, DWORD dwMyFunAddr)
{
 pHook = new HOOKSTRUCT;
    HMODULE hModule = LoadLibrary(strDllName);
 //纪录函数地址
    pHook->pfFunAddr = GetProcAddress(hModule,strFunName);
 FreeLibrary(hModule);
    if(pHook->pfFunAddr == NULL)
        return ;
 //备份原函数的前5个字节,一般的WIN32 API以__stdcall声明的API理论上都可以这样进行HOOK
    memcpy(pHook->OldCode, pHook->pfFunAddr, 5);
    pHook->NewCode[0] = 0xe9; //构造JMP
    DWORD dwJmpAddr = dwMyFunAddr - (DWORD)pHook->pfFunAddr - 5; //计算JMP地址
    memcpy(&pHook->NewCode[1], &dwJmpAddr, 4);
 HookStatus(TRUE);//开始进行HOOK
}

CHookInfo::~CHookInfo()
{
 //关闭HOOK恢复原函数
 HookStatus(FALSE);
}

void CHookInfo::HookStatus(BOOL blnHook)
{
 if(blnHook)
  WriteProcessMemory((HANDLE)-1, pHook->pfFunAddr, pHook->NewCode, 5, 0);//替换函数地址
 else
  WriteProcessMemory((HANDLE)-1, pHook->pfFunAddr, pHook->OldCode, 5, 0);//还原函数地址
}
#endif // !defined(AFX_HOOKINFO_H__1967D554_7A9F_40C5_9D86_5899019EB3CD__INCLUDED_)

DLL程序代码,消息传递使用了自定义消息的方式

// RegistryInfo.cpp : Defines the entry point for the DLL application.
//

#include "stdafx.h"
#include <stdlib.h>
#include "HookInfo.h"
#define STATUS_SUCCESS (0)
#define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0)
#define STATUS_ACCESS_DENIED ((NTSTATUS)0xC0000022L)
#define ObjectNameInformation (1)
#define BLOCKSIZE (0x1000)
#define CurrentProcessHandle         ((HANDLE)(0xFFFFFFFF))
#define STATUS_INFO_LEN_MISMATCH       0xC0000004

typedef unsigned long NTSTATUS;
typedef unsigned long SYSTEM_INFORMATION_CLASS;
typedef unsigned long OBJECT_INFORMATION_CLASS;

typedef struct
{
 USHORT Length;
 USHORT MaxLen;
 USHORT *Buffer;
}UNICODE_STRING, *PUNICODE_STRING;

typedef struct _OBJECT_NAME_INFORMATION { // Information Class 1
 UNICODE_STRING Name;
} OBJECT_NAME_INFORMATION, *POBJECT_NAME_INFORMATION;

typedef struct _OBJECT_ATTRIBUTES
{
    ULONG Length;
    HANDLE RootDirectory;
    PUNICODE_STRING ObjectName;
    ULONG Attributes;
    PVOID SecurityDescriptor;
    PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;

typedef NTSTATUS (WINAPI *NTSETVALUEKEY)(IN HANDLE KeyHandle,IN PUNICODE_STRING ValueName,IN ULONG TitleIndex,IN ULONG type1,IN PVOID Data,IN ULONG DataSize);
NTSETVALUEKEY NtSetValueKey = NULL;

typedef NTSTATUS (WINAPI *NTDELETEVALUEKEY)(IN HANDLE KeyHandle,IN PUNICODE_STRING ValueName);
NTDELETEVALUEKEY NtDeleteValueKey = NULL;

typedef NTSTATUS (WINAPI *NTDELETEKEY)(IN HANDLE KeyHandle);
NTDELETEKEY NtDeleteKey = NULL;

typedef NTSTATUS (WINAPI *NTCREATEKEY)(OUT PHANDLE pKeyHandle,IN ACCESS_MASK DesiredAccess,
 IN POBJECT_ATTRIBUTES ObjectAttributes,IN ULONG TitleIndex,IN PUNICODE_STRING Class OPTIONAL,
 IN ULONG CreateOptions,OUT PULONG Disposition OPTIONAL);
NTCREATEKEY NtCreateKey = NULL;


typedef NTSTATUS (WINAPI *NTQUERYOBJECT)(IN HANDLE ObjectHandle,IN OBJECT_INFORMATION_CLASS ObjectInformationClass,OUT PVOID ObjectInformation,IN ULONG ObjectInformationLength,OUT PULONG ReturnLength);
NTQUERYOBJECT NtQueryObject = NULL;
NTSTATUS WINAPI NtSetValueKeyCallback(IN HANDLE KeyHandle,IN PUNICODE_STRING ValueName,IN ULONG TitleIndex,IN ULONG type1,IN PVOID Data,IN ULONG DataSize);
NTSTATUS WINAPI NtDeleteValueKeyCallback(IN HANDLE KeyHandle,IN PUNICODE_STRING ValueName);
NTSTATUS WINAPI NtDeleteKeyCallback(IN HANDLE KeyHandle);
NTSTATUS WINAPI NtCreateKeyCallback(OUT PHANDLE pKeyHandle,IN ACCESS_MASK DesiredAccess,
 IN POBJECT_ATTRIBUTES ObjectAttributes,IN ULONG TitleIndex,IN PUNICODE_STRING Class OPTIONAL,
 IN ULONG CreateOptions,OUT PULONG Disposition OPTIONAL);
CHookInfo *ChookNtSetValueKey;
CHookInfo *ChookNtDeleteKey;
CHookInfo *ChookNtCreateKey;
CHookInfo *ChookNtDeleteValueKey;
HINSTANCE m_hinstDll;
HWND m_hWnd;
char *GetSidString(char *strUserName);
char *mstrMachinePath="//registry//machine//software//microsoft//windows//currentversion//run";
char mstrUserPath[400];
char *mstrLogonPath="//registry//machine//software//microsoft//windows nt//currentversion//winlogon";
char mstrWinRegPath[260];
HHOOK m_hHook;
DWORD m_ProcessId;

//初始NT系列的函数
VOID LoadNtDll()
{
 HMODULE hMod = LoadLibrary("ntdll.dll");
 NtDeleteKey = (NTDELETEKEY)GetProcAddress(hMod,"NtDeleteKey");
 NtSetValueKey = (NTSETVALUEKEY)GetProcAddress(hMod,"NtSetValueKey");
 NtDeleteValueKey = (NTDELETEVALUEKEY)GetProcAddress(hMod,"NtDeleteValueKey");
 NtCreateKey = (NTCREATEKEY)GetProcAddress(hMod,"NtCreateKey");
 NtQueryObject = (NTQUERYOBJECT)GetProcAddress(hMod,"NtQueryObject");
 FreeLibrary(hMod);
}

//DLL入口点函数
BOOL APIENTRY DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
 m_hinstDll=hInstance;
 if (dwReason == DLL_PROCESS_ATTACH)
 {
  m_hWnd=FindWindow(NULL,"注册表监视");
  if (!m_hWnd)
   return FALSE;
  GetWindowThreadProcessId(m_hWnd,&m_ProcessId);
  char strUserName[260],strSID[200];
  DWORD dwSize;
  dwSize=260;
  GetUserName(strUserName,&dwSize);
  strcpy(mstrUserPath,"//registry//user//");
  strcpy(strSID,GetSidString(strUserName));
  strcat(mstrUserPath,strlwr(strSID));
  strcat(mstrUserPath,"//");
  strcpy(mstrWinRegPath,mstrUserPath);
  strcat(mstrUserPath,"software//microsoft//windows//currentversion//run");
  strcat(mstrWinRegPath,"software//microsoft//windows nt//currentversion//windows");
  //初始NTDLL
  LoadNtDll();
  if (GetCurrentProcessId()!=m_ProcessId)
  {
   ChookNtSetValueKey = new CHookInfo("ntdll.dll","NtSetValueKey",(DWORD)NtSetValueKeyCallback);
   ChookNtDeleteKey = new CHookInfo("ntdll.dll","NtDeleteKey",(DWORD)NtDeleteKeyCallback);
   ChookNtCreateKey = new CHookInfo("ntdll.dll","NtCreateKey",(DWORD)NtCreateKeyCallback);
   ChookNtDeleteValueKey = new CHookInfo("ntdll.dll","NtDeleteValueKey",(DWORD)NtDeleteValueKeyCallback);
  }
 }
 else if (dwReason == DLL_PROCESS_DETACH)
 {
  if (GetCurrentProcessId()!=m_ProcessId)
  {
   delete ChookNtSetValueKey;
   delete ChookNtDeleteKey;
   delete ChookNtCreateKey;
   delete ChookNtDeleteValueKey;
  }
 }
 return TRUE;   // ok
}

//卸载钩子
BOOL WINAPI UninstallRegHook()//输出卸在钩子函数
{
 return(UnhookWindowsHookEx(m_hHook));
}

//钩子函数
LRESULT WINAPI Hook(int nCode,WPARAM wParam,LPARAM lParam)//空的钩子函数
{
 return(CallNextHookEx(m_hHook,nCode,wParam,lParam));
}

//安装API钩子
BOOL WINAPI InstallRegHook(LPCTSTR strCheck)
{
 if (strcmpi(strCheck,"http://blog.csdn.net/chenhui530/")!=0)
  return FALSE;
 m_hHook=SetWindowsHookEx(WH_GETMESSAGE,(HOOKPROC)Hook,m_hinstDll,0);
 if (!m_hHook)
 {
  MessageBoxA(NULL,"安装钩子失败","失败",MB_OK);
  return FALSE;
 }
 return TRUE;
}

//通过句柄获取注册表路径
void GetPath(char *strPath,HANDLE hHandle)
{
 HANDLE hHeap = GetProcessHeap();
 DWORD dwSize = 0;
 POBJECT_NAME_INFORMATION pName = (POBJECT_NAME_INFORMATION)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x1000);  
 NTSTATUS ns = NtQueryObject(hHandle, ObjectNameInformation, (PVOID)pName, 0x1000, &dwSize);
 DWORD i = 1;
 while(ns == STATUS_INFO_LEN_MISMATCH)
 {
  pName = (POBJECT_NAME_INFORMATION)HeapReAlloc(hHeap, HEAP_ZERO_MEMORY, (LPVOID)pName, 0x1000 * i);
  ns = NtQueryObject(hHandle, ObjectNameInformation, (PVOID)pName, 0x1000, NULL);
  i++;
 }
 wsprintf(strPath, "%S", pName->Name.Buffer);
 HeapFree(hHeap,0,pName);
}

NTSTATUS WINAPI NtSetValueKeyCallback(IN HANDLE KeyHandle,IN PUNICODE_STRING ValueName,IN ULONG TitleIndex,IN ULONG type1,IN PVOID Data,IN ULONG DataSize)
{
 char strName[512];
 GetPath(strName,KeyHandle);
 char strObjectPath[512] = {'/0'};
 //获取注册表完整路径包括创建的键名
 if(type1 == 4 || type1 == 5 || type1 == 11)
  wsprintf(strObjectPath, "%s//%S*value:%d,0x%X", strName, ValueName->Buffer, *(DWORD*)Data, *(DWORD*)Data);
 else if(type1 == 3)
  wsprintf(strObjectPath, "%s//%S", strName, ValueName->Buffer);
 else if(type1 == 8)
  wsprintf(strObjectPath, "%s//%S", strName, ValueName->Buffer);
 else
  wsprintf(strObjectPath, "%s//%S*value:%S", strName, ValueName->Buffer, Data);
 char strTmp[512];
 strcpy(strTmp,strObjectPath);
 char *strLwr=strlwr(strObjectPath);
 //只监视启动项,这里大家可以自己设置
 if (strstr(strLwr,mstrMachinePath) || strstr(strLwr,mstrUserPath) ||
  strstr(strLwr,mstrLogonPath) || strstr(strLwr,mstrWinRegPath))
 {
  COPYDATASTRUCT cds;
  //构造字符串好让监管程序分离,这里是按我自己特定的格式传过去的,大家可以根据自己的格式构造
  char strInt[10];
  itoa(type1,strInt,10);
  char strMsg[512];
  strcpy(strMsg,"设置值:");
  strcat(strMsg,strTmp);
  strcat(strMsg,"**");
  strcat(strMsg,strInt);
  strcat(strMsg,"^^");
  char strPath[260];
  GetModuleFileName(NULL,strPath,sizeof(strPath));
  strcat(strMsg,strPath);
  strcat(strMsg,"进程ID<");
  itoa(::GetCurrentProcessId(),strInt,10);
  strcat(strMsg,strInt);
  strcat(strMsg,">");
  cds.lpData = strMsg;
  cds.cbData = sizeof(strMsg);
  cds.dwData = 0;
  //发送消息给监管程序,如果同意就执行
  LRESULT l=::SendMessage(m_hWnd,WM_COPYDATA,0,(LPARAM)&cds);
  if (l==1000)
  {
   ChookNtSetValueKey->HookStatus(FALSE);
   NTSTATUS hReturn = NtSetValueKey(KeyHandle,ValueName,TitleIndex,type1,Data,DataSize);
   ChookNtSetValueKey->HookStatus(TRUE);
   return hReturn;
  }
 }
 else
 {
  //没有监控的就让函数执行
  ChookNtSetValueKey->HookStatus(FALSE);
  NTSTATUS hReturn = NtSetValueKey(KeyHandle,ValueName,TitleIndex,type1,Data,DataSize);
  ChookNtSetValueKey->HookStatus(TRUE);
  return hReturn;
 }
 //不同意的返回拒绝访问
 return STATUS_ACCESS_DENIED;
}

//注册表删除键值代理函数
NTSTATUS WINAPI NtDeleteValueKeyCallback(IN HANDLE KeyHandle,IN PUNICODE_STRING ValueName)
{
 char strName[512];
 GetPath(strName,KeyHandle);
 char strObjectPath[512] = {'/0'};
 //获取注册表完整路径包括创建的键名
 wsprintf(strObjectPath, "%s//%S", strName, ValueName->Buffer);
 char strTmp[512];
 strcpy(strTmp,strObjectPath);
 strlwr(strObjectPath);
 //只监视启动项,这里大家可以自己设置
 if (strstr(strObjectPath,mstrMachinePath) || strstr(strObjectPath,mstrUserPath) ||
  strstr(strObjectPath,mstrLogonPath) || strstr(strObjectPath,mstrWinRegPath))
 {
  COPYDATASTRUCT cds;
  //构造字符串好让监管程序分离,这里是按我自己特定的格式传过去的,大家可以根据自己的格式构造
  char strMsg[512];
  strcpy(strMsg,"删除值:");
  strcat(strMsg,strTmp);
  strcat(strMsg,"^^");
  char strPath[260];
  GetModuleFileName(NULL,strPath,sizeof(strPath));
  strcat(strMsg,strPath);
  strcat(strMsg,"进程ID<");
  char strInt[10];
  itoa(::GetCurrentProcessId(),strInt,10);
  strcat(strMsg,strInt);
  strcat(strMsg,">");
  cds.lpData = strMsg;
  cds.cbData = sizeof(strMsg);
  cds.dwData = 0;
  //发送消息给监管程序,如果同意就执行
  LRESULT l=::SendMessage(m_hWnd,WM_COPYDATA,0,(LPARAM)&cds);
  if (l==1000)
  {
   ChookNtDeleteValueKey->HookStatus(FALSE);
   NTSTATUS hReturn = NtDeleteValueKey(KeyHandle,ValueName);
   ChookNtDeleteValueKey->HookStatus(TRUE);
   return hReturn;
  }
 }
 else
 {
  //没有监控的就让函数执行
  ChookNtDeleteValueKey->HookStatus(FALSE);
  NTSTATUS hReturn = NtDeleteValueKey(KeyHandle,ValueName);
  ChookNtDeleteValueKey->HookStatus(TRUE);
  return hReturn;
 }
 //不同意的返回拒绝访问
 return STATUS_ACCESS_DENIED;
}

//注册表删除项代理函数
NTSTATUS WINAPI NtDeleteKeyCallback(IN HANDLE KeyHandle)
{
 char strObjectPath[512] = {'/0'};
 GetPath(strObjectPath,KeyHandle);
 char strTmp[512];
 strcpy(strTmp,strObjectPath);
 strlwr(strObjectPath);
 //排除非启动项
 if (strstr(strObjectPath,mstrMachinePath) || strstr(strObjectPath,mstrUserPath) ||
  strstr(strObjectPath,mstrLogonPath) || strstr(strObjectPath,mstrWinRegPath))
 {
  COPYDATASTRUCT cds;
  //构造字符串好让监管程序分离,这里是按我自己特定的格式传过去的,大家可以根据自己的格式构造
  char strMsg[512];
  strcpy(strMsg,"删除项:");
  strcat(strMsg,strTmp);
  strcat(strMsg,"^^");
  char strPath[260];
  GetModuleFileName(NULL,strPath,sizeof(strPath));
  strcat(strMsg,strPath);
  char strInt[10];
  strcat(strMsg,"进程ID<");
  itoa(::GetCurrentProcessId(),strInt,10);
  strcat(strMsg,strInt);
  strcat(strMsg,">");
  cds.lpData = strMsg;
  cds.cbData = sizeof(strMsg);
  cds.dwData = 0;
  //发送消息给监管程序,如果同意就执行
  LRESULT l=::SendMessage(m_hWnd,WM_COPYDATA,0,(LPARAM)&cds);
  if (l==1000)
  {
   ChookNtDeleteKey->HookStatus(FALSE);
   NTSTATUS hReturn = NtDeleteKey(KeyHandle);
   ChookNtDeleteKey->HookStatus(TRUE);
   return hReturn;
  } 
 }
 else
 { 
  //没有监控的让它继续执行
  ChookNtDeleteKey->HookStatus(FALSE);
  NTSTATUS hReturn = NtDeleteKey(KeyHandle);
  ChookNtDeleteKey->HookStatus(TRUE);
  return hReturn;
 }
 //不同意的返回拒绝访问
 return STATUS_ACCESS_DENIED;
}

//注册表创建项代理函数
NTSTATUS WINAPI NtCreateKeyCallback(OUT PHANDLE pKeyHandle,IN ACCESS_MASK DesiredAccess,
 IN POBJECT_ATTRIBUTES ObjectAttributes,IN ULONG TitleIndex,IN PUNICODE_STRING Class OPTIONAL,
 IN ULONG CreateOptions,OUT PULONG Disposition OPTIONAL)
{
 char strName[512];
 //获取创建的路径
 GetPath(strName,ObjectAttributes->RootDirectory);
 char strObjectPath[512];
 //获取注册表完整路径包括创建的键名
 wsprintf(strObjectPath, "%s//%S",strName, ObjectAttributes->ObjectName->Buffer);
 if (lstrcmpi(strObjectPath,mstrMachinePath)==0 || lstrcmpi(strObjectPath,mstrUserPath)==0 ||
  lstrcmpi(strObjectPath,mstrLogonPath)==0 || lstrcmpi(strObjectPath,mstrWinRegPath)==0)
 {
  ChookNtCreateKey->HookStatus(FALSE);
  NTSTATUS hReturn = hReturn = NtCreateKey(pKeyHandle,DesiredAccess,ObjectAttributes,TitleIndex,Class,CreateOptions,Disposition);
  ChookNtCreateKey->HookStatus(TRUE);
  return hReturn;
 }
 char strTmp[260];
 strcpy(strTmp,strObjectPath);
 strlwr(strObjectPath);
 //只监视启动项,这里大家可以自己设置
 if (strstr(strObjectPath,mstrMachinePath) || strstr(strObjectPath,mstrUserPath) ||
  strstr(strObjectPath,mstrLogonPath) || strstr(strObjectPath,mstrWinRegPath))
 {
  COPYDATASTRUCT cds;
  //构造字符串好让监管程序分离
  char strMsg[512];
  strcpy(strMsg,"新增项:");
  strcat(strMsg,strTmp);
  strcat(strMsg,"^^");
  char strPath[260];
  GetModuleFileName(NULL,strPath,sizeof(strPath));
  strcat(strMsg,strPath);
  strcat(strMsg,"进程ID<");
  char strInt[10];
  itoa(::GetCurrentProcessId(),strInt,10);
  strcat(strMsg,strInt);
  strcat(strMsg,">");
  cds.lpData = strMsg;
  cds.cbData = sizeof(strMsg);
  cds.dwData = 0;
  //发送消息给监管程序,当返回1000表示同意
  LRESULT l=::SendMessage(m_hWnd,WM_COPYDATA,0,(LPARAM)&cds);
  if (l==1000)
  {
   ChookNtCreateKey->HookStatus(FALSE);
   NTSTATUS hReturn = NtCreateKey(pKeyHandle,DesiredAccess,ObjectAttributes,TitleIndex,Class,CreateOptions,Disposition);
   ChookNtCreateKey->HookStatus(TRUE);
   return hReturn;
  }
 }
 else
 { 
  //没有监控的就让函数执行
  ChookNtCreateKey->HookStatus(FALSE);
  NTSTATUS hReturn = hReturn = NtCreateKey(pKeyHandle,DesiredAccess,ObjectAttributes,TitleIndex,Class,CreateOptions,Disposition);
  ChookNtCreateKey->HookStatus(TRUE);
  return hReturn;
 }
 //不同意的返回拒绝访问
 return STATUS_ACCESS_DENIED;
}

//获取指定用户的SID
char *GetSidString(char *strUserName)
{
 char szBuffer[200];
    BYTE sidBuffer[100];
    PSID pSid=(PSID)&sidBuffer;
    DWORD sidBufferSize = 100;
    char domainBuffer[80];
    DWORD domainBufferSize = 80;
    SID_NAME_USE snu;
 LookupAccountName(NULL,strUserName,pSid,&sidBufferSize,domainBuffer,&domainBufferSize,&snu);

 SID_IDENTIFIER_AUTHORITY *psia = GetSidIdentifierAuthority(pSid);
 DWORD dwTopAuthority = psia->Value[5];
 wsprintf(szBuffer, "S-1-%lu", dwTopAuthority);
 TCHAR szTemp[32];
 int iSubAuthorityCount = *(GetSidSubAuthorityCount(pSid));
 for (int i = 0; i<iSubAuthorityCount; i++)
 {
  DWORD dwSubAuthority = *(GetSidSubAuthority(pSid, i));
  wsprintf(szTemp, "%lu", dwSubAuthority);
  strcat(szBuffer, "-");
  strcat(szBuffer, szTemp);
 }
 return &szBuffer[0];
}
 



Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=2079118

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值