波哥写的钩子教程

本文详细介绍了Windows钩子技术的原理、应用及其作用域,包括如何通过系统调用将钩子程序挂入系统,以及钩子链表、钩子子程的工作机制。重点讨论了不同类型的钩子(如WH_CALLWNDPROC、WH_KEYBOARD_LL等)及其应用场景,解释了全局钩子与线程钩子的区别,以及如何合理使用钩子以避免对系统性能的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

钩子技术(一)
来源: 任立波的日志
下面开始了我的钩子接触过程。

第一步。了解什么是钩子。

我们可以首先从字面上了解钩子,钩子是干什么的呢?日常生活中,我们的钩子是用来钩住某种东西的,比如,说,鱼钩是用来钓鱼的,一旦鱼咬了钩,钩子就一直钩住鱼了,任凭鱼在水里怎么游,也逃不出鱼钩的控制。同样的,Windows的钩子Hook也是用来钩东西的,比较抽象的是他是用来钩Windows事件或者消息的。最常见的就是鼠标和键盘钩子,用Hook钩子钩住鼠标、键盘,当你的鼠标、键盘有任何操作时,通过Hook就能知道他们都做了什么了,多么形象啊,把老鼠Mouse钩住了,不管你干什么,都逃不过我钩子Hook的手掌心。

技术上讲,钩子(Hook)是Windows消息处理机制的一个很重要的内容,谁叫Windows是基于消息的呢。应用程序可以通过钩子机制截获处理Window消息或是其他一些特定事件。

我们可以在同一个钩子上挂很多东西。

想起参加工作前要求被体检的时候,当你被登记之后,按照你的登记表上的顺序,就等着到各个科室一个一个的去检查吧。每一个科室都有决定你是否继续的可能,只有通过了这个,你才可以到下一个去,如果没有通过,那么,你是看不到最后的大夫了,可以直接over回家了。

如果把体检比喻为事件的话,当事件发生时,应用程序(体检过程)可以在相应的钩子Hook上设置多个钩子子程序(Hook Procedures)(多个科室的检查),由其组成一个与钩子相关联的指向钩子函数的指针列表(钩子链表)(体检表,确定了你要走的顺序)。当钩子所监视的消息出现时(你拿着表格来体检了),Windows(导诊员)首先将其送到调用链表中所指向的第一个钩子函数中(体检表上第一个科室,一般是身高体重吧,呵呵),钩子函数将根据其各自的功能(每个科室检查的项目不一样啊)对消息进行监视(有的大夫就随便看看了事),、修改(碰到好心的大夫还可以帮你往好里添点呢,呵呵)和控制(有的大夫好严格啊),并在处理完成后(当然有的大夫就直接把你刷下了,回家吧,没有下一个了)把消息传递给下一钩子函数(下一个项目的科室,当然,也可以强制消息的传递,直接打发你回家)直至到达钩子链表的末尾(检查完了!)。在钩子函数交出控制权后,被拦截的消息最终仍将交还给窗口处理函数(好了,拿着表去上班吧)。

虽然钩子函数对消息的过滤将会略加影响系统的运行效率,但在很多场合下通过钩子对消息的过滤处理可以完成一些其他方法所不能完成的特殊功能。



钩子技术(二)
来源: 任立波的日志
钩子(Hook),是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所创 建的。当消息到达后,在目标窗口处理函数之前处理它。钩子机制允许应用程序截获处理window消息或特定事件。

Windows系统是建立在事件驱动的机制上的,说穿了就是整个系统都是通过消息的传递来实现的。而钩子是Windows系统中非常重要的系统接 口,用它可以截获并处理送给其他应用程序的消息,来完成普通应用程序难以实现的功能。钩子可以监视系统或进程中的各种事件消息,截获发往目标窗口的消息并 进行处理。这样,我们就可以在系统中安装自定义的钩子,监视系统中特定事件的发生,完成特定的功能,比如截获键盘、鼠标的输入,屏幕取词,日志监视等等。 可见,利用钩子可以实现许多特殊而有用的功能。

钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得 到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。

一个Hook都有一个与之相关联的指针列表,称之为钩子链表,由系统来维护。这个列表的指针指向指定的,应用程序定义的,被Hook子程调用的回调函 数,也就是该钩子的各个处理子程。当与指定的Hook类型关联的消息发生时,系统就把这个消息传递到Hook子程。一些Hook子程可以只监视消息,或者 修改消息,或者停止消息的前进,避免这些消息传递到下一个Hook子程或者目的窗口。最近安装的钩子放在链的开始,而最早安装的钩子放在最后,也就是后加 入的先获得控制权。

 Windows 并不要求钩子子程的卸载顺序一定得和安装顺序相反。每当有一个钩子被卸载,Windows 便释放其占用的内存,并更新整个Hook链表。如果程序安装了钩子,但是在尚未卸载钩子之前就结束了,那么系统会自动为它做卸载钩子的操作。

大多数人或者网上文章认为全局钩子都要依赖于一个DLL才能正常工作的,常常会看到很多人在论坛上长期争论一个话题:“全局钩子一定要在DLL里面吗?”。实际上这里有一个概念的问题,究竟上面提到的全局钩子是指什么。通过对上面各种钩子的作用域的理解就会发现这个问题的答案。

上面一共提到了15种钩子,他们的作用域请看下表:

Hook
 Scope
 
WH_CALLWNDPROC
 Thread or global
 
WH_CALLWNDPROCRET
 Thread or global
 
WH_CBT
 Thread or global
 
WH_DEBUG
 Thread or global
 
WH_FOREGROUNDIDLE
 Thread or global
 
WH_GETMESSAGE
 Thread or global
 
WH_JOURNALPLAYBACK
 Global only
 
WH_JOURNALRECORD
 Global only
 
WH_KEYBOARD
 Thread or global
 
WH_KEYBOARD_LL
 Global only
 
WH_MOUSE
 Thread or global
 
WH_MOUSE_LL
 Global only
 
WH_MSGFILTER
 Thread or global
 
WH_SHELL
 Thread or global
 
WH_SYSMSGFILTER
 Global only
 

表一:钩子作用域

WH_JOURNALPLAYBACK,WH_JOURNALRECORD,WH_KEYBOARD_LL,WH_MOUSE_LL、WH_SYSMSGFILTER这5种钩子本身的作用域就是全局的,不管钩子是直接写在应用程序的代码里还是放在DLL中,他们都能够钩住系统的消息。剩下的10种钩子,他们的作用域既可以是线程的又可以是全局的,当将相应的钩子直接写在应用程序的代码中时,他们只能捕获当前线程上下文的消息。那么他们如何实现捕获全局消息的功能呢?当把钩子写入到一个单独的DLL中再引用后,系统自动将该DLL映射到受钩子函数影响的所有进程的地址空间中,即将这个DLL注入了那些进程,从而达到捕获全局消息的目的。相对来说,前面5种钩子本身就是全局的,是不需要注入的。

因此,对于前面问题的答案就是:要实现捕获全局消息功能的钩子,是否要写在单独的DLL里面,取决于钩子的类型以及相应的作用域。

如果对于同一事件既安装了线程勾子又安装了全局勾子,那么系统会自动先调用线程勾子,然后调用全局勾子



钩子技术(三)
来源: 任立波的日志
系统必须要处理每个消息,而钩子的使用增加了系统对每个消息要执行的处理的数量,因此,钩子会减慢系统。应该仅仅在必须的时候才安装钩子,并且尽可能早的将其卸载掉。

Hook Chain(钩子链表)

The system supports many different types of hooks; each type provides access to a different aspect of its message-handling mechanism. For example, an application can use the WH_MOUSE Hook to monitor the message traffic for mouse messages.

系统支持很多不同种类的钩子,每种类型提供对消息处理机制里的某一不同方面的访问。例如,应用程序可以使用WH_MOUSE Hook监视鼠标消息的传递。

The system maintains a separate hook chain for each type of hook. A hook chain is a list of pointers to special, application-defined callback functions called hook procedures. When a message occurs that is associated with a particular type of hook, the system passes the message to each hook procedure referenced in the hook chain, one after the other. The action a hook procedure can take depends on the type of hook involved. The hook procedures for some types of hooks can only monitor messages; others can modify messages or stop their progress though the chain, preventing them from reaching the next hook procedure or the destination window.

系统为每类钩子维护着一个独立的钩子链表。钩子链表是一个指针的列表,其中的指针指向特定的、应用程序定义的回调函数,该函数被叫做钩子子程(hook procedure)。当与某种特定类型的钩子相关联(钩住)的消息发生时,系统将消息一个接一个地传递给钩子链中的每一个钩子子程(hook procedure),钩子子程能够采取的动作取决于涉及的钩子的类型。某些类型的钩子子程仅仅能监视消息;另外一些就能够修改消息或者终止消息在钩子链表中的前进,这样就阻止了消息到达下一个钩子子程或者目标窗体。

这里有几个概念上的翻译,我参考的网上大家普遍的叫法。主要有:

Hook Chain : 钩子链表

hook procedures : 钩子子程(即得到消息后进行处理的程序段)

Hook Procedures (钩子子程)

To take advantage of a particular type of hook, the developer provides a hook procedure and uses the SetWindowsHookEx function to install it into the chain associated with the hook. A hook procedure must have the following syntax:

为了利用某种特定类型的钩子,开发者提供了钩子子程。可以使用SetWindowsHookEx方法将该钩子子程安装到和该钩子相关联的钩子链表中。钩子子程必须具有下面的语法:

LRESULT CALLBACK HookProc(  int nCode,   WPARAM wParam,   LPARAM lParam);HookProc is a placeholder for an application-defined name.

HookProc是应用程序定义的名字的占位符。即,HookProc是应用程序定义的名字。

The nCode parameter is a hook code that the hook procedure uses to determine the action to perform. The value of the hook code depends on the type of the hook; each type has its own characteristic set of hook codes. The values of the wParam and lParam parameters depend on the hook code, but they typically contain information about a message that was sent or posted..

参数nCode:钩子代码,钩子子程通过该代码来决定执行什么动作。该值取决于钩子的类型,每种类型都拥有自己特有的钩子代码集合。

参数wParam和lParam的值,都取决于钩子代码。但是一般都包含发送或者传递的消息的信息。

The SetWindowsHookEx function always installs a hook procedure at the beginning of a hook chain. When an event occurs that is monitored by a particular type of hook, the system calls the procedure at the beginning of the hook chain associated with the hook. Each hook procedure in the chain determines whether to pass the event to the next procedure. A hook procedure passes an event to the next procedure by calling the CallNextHookEx function.

方法SetWindowsHookEx总是在钩子链的开始位置安装钩子子程。当被某种类型的钩子监视的事件发生时,系统调用和该钩子相关的位于钩子链表开始位置的钩子子程。每个钩子链表中的钩子子程决定是否将该事件传递给下一个钩子子程。钩子子程通过调用方法CallNextHookEx向下一个钩子子程传递事件。

Note that the hook procedures for some types of hooks can only monitor messages. the system passes messages to each hook procedure, regardless of whether a particular procedure calls CallNextHookEx.

注意:某些类型的钩子子程仅仅能够监视消息,系统不管是否有特殊的钩子子程调用CallNextHookEx方法,都将把消息传递给每个钩子子程,

A global hook monitors messages for all threads in the same desktop as the calling thread. A thread-specific hook monitors messages for only an individual thread. A global hook procedure can be called in the context of any application in the same desktop as the calling thread, so the procedure must be in a separate dynamic-link library (DLL) module. A thread-specific hook procedure is called only in the context of the associated thread. If an application installs a hook procedure for one of its own threads, the hook procedure can be in either the same module as the rest of the application's code or in a DLL. If the application installs a hook procedure for a thread of a different application, the procedure must be in a DLL. For information, see Dynamic-Link Libraries.

上面的字面意思为:全局钩子监视同一桌面下所有做为调用线程的线程消息。线程钩子仅仅监视该单个线程的消息。全局钩子子程可以在所在桌面下任何应用程序的上下文中被调用,因此,该钩子子程序须在一个单独的动态链接库DLL中。(译者注:在DLL中,可以映射到内存中,从而被所有程序调用)。线程钩子的钩子子程只能在本线程的上下文中被调用。如果应用程序为他自己线程中的某个安装钩子子程,钩子子程能够放在本模块中、应用程序代码的其它部分、Dll中。如果应用程序为另外一个不同的应用程序安装钩子子程,钩子子程必须放在Dll中。参照 DLL 查找更多信息。

个人认为这里理解起来比较费劲。如果这样理解会更好:请参看钩子初接触(二)最后部分。呵呵。

Note   You should use global hooks only for debugging purposes; otherwise, you should avoid them. Global hooks hurt system performance and cause conflicts with other applications that implement the same type of global hook.

注意:只应为了调试目的而使用全局钩子;否则应避免使用。全局钩子会牺牲系统性能。使用了相同类型的全局钩子的应用程序之间也会有冲突。



钩子技术(四)
来源: 任立波的日志
每种类型的钩子使应用程序能够监视系统的消息处理机制的不同方面。

钩子类型1-2:WH_CALLWNDPROC and WH_CALLWNDPROCRET Hooks

The WH_CALLWNDPROC and WH_CALLWNDPROCRET hooks enable you to monitor messages sent to window procedures. The system calls a WH_CALLWNDPROC hook procedure before passing the message to the receiving window procedure, and calls the WH_CALLWNDPROCRET hook procedure after the window procedure has processed the message.

WH_CALLWNDPROC 和 WH_CALLWNDPROCRET钩子使你能够监视发送到window程序的消息。系统在将消息传递给正在接收的window程序之前,调用WH_CALLWNDPROC钩子子程;在window程序处理完消息之后,调用WH_CALLWNDPROCRET钩子子程。

The WH_CALLWNDPROCRET hook passes a pointer to a CWPRETSTRUCT structure to the hook procedure. The structure contains the return value from the window procedure that processed the message, as well as the message parameters associated with the message. Subclassing the window does not work for messages set between processes.

WH_CALLWNDPROCRET钩子将一个指向CWPRETSTRUCT结构的的指针传递给钩子子程。该结构包含有来自处理该消息的window程序的返回值,以及消息中的参数。子类窗体不能处理进程间的消息集。

钩子类型3:WH_CBT Hook

The system calls a WH_CBT hook procedure before activating, creating, destroying, minimizing, maximizing, moving, or sizing a window; before completing a system command; before removing a mouse or keyboard event from the system message queue; before setting the input focus; or before synchronizing with the system message queue. The value the hook procedure returns determines whether the system allows or prevents one of these operations. The WH_CBT hook is intended primarily for computer-based training (CBT) applications.

在以下事件发生之前,系统会调用WH_CBT 钩子子程:

1、窗台被激活、创建、销毁、最小化、最大化、移动或者改变大小;

2、执行完系统命令;

3、从系统消息队列中移除鼠标或者键盘事件;

4、设置输入焦点;

5、同步系统消息队列;

钩子子程的返回值决定了系统是允许了还是阻止了这些操作中的一个。WH_CBT钩子主要是用在基于计算机的练习(CBT) 程序中。

钩子类型4:WH_DEBUG Hook

The system calls a WH_DEBUG hook procedure before calling hook procedures associated with any other hook in the system. You can use this hook to determine whether to allow the system to call hook procedures associated with other types of hooks.

在调用与系统中任何其他钩子关联的钩子子程之前,系统会调用WH_DEBUG 钩子子程。使用该钩子来决定是否允许系统调用与其他类型的钩子相关联的钩子子程。

钩子类型5:WH_FOREGROUNDIDLE Hook

The WH_FOREGROUNDIDLE hook enables you to perform low priority tasks during times when its foreground thread is idle. The system calls a WH_FOREGROUNDIDLE hook procedure when the application's foreground thread is about to become idle.

WH_FOREGROUNDIDLE 钩子允许当前台线程空闲时,执行低权限的任务。系统在应用程序的前台线程即将空闲时,调用WH_FOREGROUNDIDLE钩子子程。

钩子类型6:WH_GETMESSAGE Hook

The WH_GETMESSAGE hook enables an application to monitor messages about to be returned by the GetMessage or PeekMessage function. You can use the WH_GETMESSAGE hook to monitor mouse and keyboard input and other messages posted to the message queue.

WH_GETMESSAGE程序允许应用程序监视即将由方法GetMessage 或者PeekMessage返回的消息。可以使用WH_GETMESSAGE钩子监视鼠标和键盘输入,以及其他传递给消息队列的消息。

 钩子类型7:WH_JOURNALPLAYBACK Hook

The WH_JOURNALPLAYBACK hook enables an application to insert messages into the system message queue. You can use this hook to play back a series of mouse and keyboard events recorded earlier by using the WH_JOURNALRECORD Hook. Regular mouse and keyboard input is disabled as long as a WH_JOURNALPLAYBACK hook is installed. A WH_JOURNALPLAYBACK hook is a global hook — it cannot be used as a thread-specific hook.

The WH_JOURNALPLAYBACK hook returns a time-out value. This value tells the system how many milliseconds to wait before processing the current message from the playback hook. This enables the hook to control the timing of the events it plays back.

WH_JOURNALPLAYBACK钩子允许应用程序将消息插入到系统消息队列中。使用该钩子回放先前使用WH_JOURNALRECORD 钩子记录的一系列鼠标和键盘事件。在WH_JOURNALPLAYBACK被安装后,常规的鼠标和键盘输入被禁用。WH_JOURNALPLAYBACK钩子是全局钩子,不能被用作线程钩子。WH_JOURNALPLAYBACK钩子返回一个超时值。该值告诉系统在处理来自回放钩子的当前消息之前等待了多少毫秒。这允许该钩子控制回放事件的速度。

钩子类型8:WH_JOURNALRECORD Hook

The WH_JOURNALRECORD hook enables you to monitor and record input events. Typically, you use this hook to record a sequence of mouse and keyboard events to play back later by using the WH_JOURNALPLAYBACK Hook. The WH_JOURNALRECORD hook is a global hook — it cannot be used as a thread-specific hook.

WH_JOURNALRECORD钩子允许监视并且记录输入事件。典型的,使用该钩子来记录顺序的的鼠标和键盘事件,以后可以使用WH_JOURNALPLAYBACK.钩子进行回放。 该钩子是全局钩子,不能被用作进程钩子。

钩子类型9:WH_KEYBOARD_LL Hook

The WH_KEYBOARD_LL hook enables you to monitor keyboard input events about to be posted in a thread input queue.

WH_KEYBOARD_LL钩子监视在线程输入队列中,即将被传递的键盘输入事件。

钩子类型10:WH_KEYBOARD Hook

The WH_KEYBOARD hook enables an application to monitor message traffic for WM_KEYDOWN and WM_KEYUP messages about to be returned by the GetMessage or PeekMessage function. You can use the WH_KEYBOARD hook to monitor keyboard input posted to a message queue.

WH_KEYBOARD钩子允许应用程序监视即将被GetMessage 或者 PeekMessage方法返回的WM_KEYDOWN 或者 WM_KEYUP消息。使用WH_KEYBOARD钩子可以监视传递到消息队列中的键盘输入。

钩子类型11:WH_MOUSE_LL Hook

The WH_MOUSE_LL hook enables you to monitor mouse input events about to be posted in a thread input queue.

WH_MOUSE_LL钩子监视在线程输入队列中,即将被传递的鼠标输入事件。

钩子类型12:WH_MOUSE Hook

The WH_MOUSE hook enables you to monitor mouse messages about to be returned by the GetMessage or PeekMessage function. You can use the WH_MOUSE hook to monitor mouse input posted to a message queue.

WH_MOUSE钩子允许监视即将被GetMessage或者 PeekMessage方法返回的鼠标消息。使用该钩子监视传递到线程输入队列的鼠标输入。

钩子类型13、14:WH_MSGFILTER  and  WH_SYSMSGFILTER Hooks

The WH_MSGFILTER and WH_SYSMSGFILTER hooks enable you to monitor messages about to be processed by a menu, scroll bar, message box, or dialog box, and to detect when a different window is about to be activated as a result of the user's pressing the ALT+TAB or ALT+ESC key combination. The WH_MSGFILTER hook can only monitor messages passed to a menu, scroll bar, message box, or dialog box created by the application that installed the hook procedure. The WH_SYSMSGFILTER hook monitors such messages for all applications.

WH_MSGFILTER 和 WH_SYSMSGFILTER钩子允许监视即将由菜单、滚动条、消息框、对话框处理的消息,并且在用户按下了ALT+TAB 或者ALT+ESC组合键后,检测何时一个不同的窗口将被激活。WH_MSGFILTER钩子仅仅能监视传递到菜单、滚动条、消息框或者由安装了钩子子程的应用程序建立的对话框的消息。WH_SYSMSGFILTER钩子监视所有应用程序的这类消息。

The WH_MSGFILTER and WH_SYSMSGFILTER hooks enable you to perform message filtering during modal loops that is equivalent to the filtering done in the main message loop. For example, an application often examines a new message in the main loop between the time it retrieves the message from the queue and the time it dispatches the message, performing special processing as appropriate. However, during a modal loop, the system retrieves and dispatches messages without allowing an application the chance to filter the messages in its main message loop. If an application installs a WH_MSGFILTER or WH_SYSMSGFILTER hook procedure, the system calls the procedure during the modal loop.

WH_MSGFILTER 和WH_SYSMSGFILTER钩子允许在模式循环期间执行消息过滤,这和在主消息循环中执行过滤是等效的。例如,应用程序在它从队列中收到消息到分派消息期间,经常在主循环中检查新的消息,执行适当的处理。然而,在模式循环期间,系统会收到、分派消息,但是并不给应用程序机会去过滤主消息循环中的消息。如果应用程序安装了WH_MSGFILTER 或者 WH_SYSMSGFILTER钩子子程,系统会在模式循环期间调用钩子子程。

An application can call the WH_MSGFILTER hook directly by calling the CallMsgFilter function. By using this function, the application can use the same code to filter messages during modal loops as it uses in the main message loop. To do so, encapsulate the filtering operations in a WH_MSGFILTER hook procedure and call CallMsgFilter between the calls to the GetMessage and DispatchMessage functions.

应用程序可以通过调用CallMsgFilter方法直接调用WH_MSGFILTER钩子。通过使用该方法,应用程序可以像在主消息循环中一样,使用同样的代码来过滤消息。这样做呢,可以在WH_MSGFILTER钩子子程中封装过滤的操作,在调用GetMessage 和 DispatchMessage方法期间调用CallMsgFilter。

while (GetMessage(&msg, (HWND) NULL, 0, 0))

{

    if (!CallMsgFilter(&qmsg, 0))

        DispatchMessage(&qmsg);

}

The last argument of CallMsgFilter is simply passed to the hook procedure; you can enter any value. The hook procedure, by defining a constant such as MSGF_MAINLOOP, can use this value to determine where the procedure was called from.

CallMsgFilter的最后一个参数简单的传递给钩子子程;可以输入任何值。钩子子程,通过定义像MSGF_MAINLOOP一样的常量,可以使用该值来决定钩子子程是被哪里调用的。

钩子类型15:WH_SHELL Hook

A shell application can use the WH_SHELL hook to receive important notifications. The system calls a WH_SHELL hook procedure when the shell application is about to be activated and when a top-level window is created or destroyed.

加壳程序可以使用WH_SHELL钩子来接收重要的通知。当加壳程序即将被激活时、当处在最顶层的窗口被创建或者销毁时,系统会调用WH_SHELL钩子子程。

Note that custom shell applications do not receive WH_SHELL messages. Therefore, any application that registers itself as the default shell must call the SystemParametersInfo function with SPI_SETMINIMIZEDMETRICS before it (or any other application) can receive WH_SHELL messages.

注意:常规加壳程序并不接收WH_SHELL消息。因此,任何将自己注册为默认外壳的应用程序必须在它(或者任何其它应用程序)能够接收WH_SHELL消息之前调用带有SPI_SETMINIMIZEDMETRICS 的SystemParametersInfo方法。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值