Windows 钩子

 
Windows 钩子
Windows 应用程序的运行模式是基于消息驱动的,任何线程只要注册了窗口类就会有一个消息队列来接收用户的输入消息和系统消息。为了取得特定线程接收或发送的消息,就要 Windows 提供的钩子。
 
钩子的概念
 
钩子( Hook )是 Windows 消息处理机制中的一个监视点,应用程序可以在这里安装一个子程序(钩子函数)以监视指定窗口某种类型的消息,所监视的窗口可以是其他进程创建的。当消息到达后,在目标窗口处理函数处理之前,钩子机制允许应用程序截获它进行处理。
         钩子函数是一个处理消息的程序段,通过调用相关的 API 函数,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息。
         总之,关于 Windows 钩子要知道以下几点:
1.   钩子是用截获系统中的消息流。利用钩子,可以处理任何感兴趣的消息,包括其他进程的消息。
2.   截获消息后,用于处理消息的子程序叫做钩子函数,它是应用程序自定义的一个函数,在安装钩子时要把这个函数的地址告诉 Windows
3.   系统中同一时间可能有多个进程安装了钩子,多个钩子函数在一起组成钩子链。所以在处理截获到的消息时,应该把消息事件传递下去,以便其他钩子也有机会处理这一消息。
 
钩子会使得系统变慢,因为它增加了系统对每个消息的处理量。仅应该在必要时才安装钩子,而且在不需要时应尽快移除。
 
钩子的安装
 
SetWindowsHookEx 函数可以把应用程序定义的钩子函数安装到系统中。
HHOOK SetWindowsHookEx(
 Int idHook ;       // 指定钩子的类型
 HOOKPROC lpfn;   // 钩子函数的地址。如果使用的是远程钩子,钩子函数必须放在一个 DLL 中。
 HINSTANCE hMod; // 钩子函数所在 DLL 的实例句柄。如果是一个局部的钩子,该参数为 NULL
 DWORD    dwThreadID; // 指定要为哪个线程安装钩子。若该值为 0 被解释成系统范围内的。
IdHook 参数指定了要安装的钩子的类型,可以是下列取值之一:
WH_CALLWNDPROC       当目标线程调用 SendMessage 函数发送消息时,钩子函数被调用。
WH_CALLWNDPROCRET                   SendMessage 发送的消息返回时,钩子函数被调用。
WH_GETMESSAGE           当目标线程调用 GetMessage 或者 PeekMessage 时。
WH_KEYBOARD               当从消息队列中查询 WM_KEYUP WM_KEYDOWN 消息时
WH_MOUSE                       当调用从消息队列中查询鼠标事件消息时
WH_MSGFILTER               当对话框,菜单或滚动条要处理一个消息时,钩子函数被调用。该钩子是局部的,它是为哪些有自己消息处理过程的控件对象设计的。
WH_SYSMSGFILTER        WH_MSGFILTER 一样,只不过是系统范围的。
WH_JOURNALRECORD  Windows 从硬件队列中获取消息时。
WH_JOURNALPLAYBACK       当一个事件从系统的硬件输入队列中别请求时
WH_SHELL                         当关于 Windows 外壳事件发生时,比如任务条需要重画它的按钮
WH_CBT                             当基于计算机的训练( CBT )事件发生时。
WH_FOREGROUNDIDLE Windows 自己使用,一般应用程序很少使用。
WH_DEBUG                       用来给钩子函数除错。
 
Lpfn 参数是钩子函数的地址。钩子安装后如果有消息发生, Windows 将调用此参数所指向的函数。
         如果 dwThreadId 参数是 0 ,或者指定一个由其他进程创建的线程 ID lpfn 参数指向的钩子函数必须位于一个 DLL 中。这是因为进程的地址空间是相互隔离的,发生事件的进程不能调用其他进程地址空间的钩子函数。如果钩子函数的实现代码在 DLL 中,在相关事件发生时,系统会把这个 DLL 插入到发生事件的进程的地址空间,使它能够调用钩子函数。这种需要将钩子函数写入 DLL 以便挂钩其他进程事件的钩子称为远程钩子。
         如果 dwThreadId 参数指定一个由自身进程创建的线程 ID lpfn 参数指向的钩子函数只要在当前进程中即可,不必非要写入 DLL 。这种挂钩属于自身进程事件的钩子称为局部钩子。
 
hMod 参数是钩子函数所在 DLL 的实例句柄,如果钩子函数不再 DLL 中,应将 hMod 设置为 NULL
 
dwThreadId 参数指定要与钩子函数相关联的线程 ID 号。如果设为 0 ,那么钩子就是系统范围内的,即钩子函数将关联到系统内所有线程。
 
钩子函数
 
         钩子安装后如果有相应的消息发生, Windows 将调用 SetWindowsHookEx 函数指定的钩子函数 lpfn 。钩子函数的一般形式如下:
LRESULT CALLBACK HookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
         // 处理该消息的代码 …..
 
    Return ::CallNextHookEx(hHook,nCode,wParam,lParam);
}
HookProc 是应用程序的名称。 nCode 参数是 Hook 代码,钩子函数使用这个参数来确定任务,它的值依赖于 Hook 的类型。 wParam lParam 参数的值依赖于 Hook 代码,但是它们典型的值是一些关于发送或者接收消息的信息。
         因为系统中可能会有多个钩子的存在,所以要调用那个 CallNextHookEx 函数把消息传到链中下一个钩子函数。 hHook 参数是安装钩子时得到的钩子句柄( SetWindowsHookEx 的返回值)。
 
卸载钩子
 
         要卸载钩子,可以调用 UnhookWindowsHookEx 函数。
 BOOL UnhookWindowsHookEx(HHOOK hhk); // hhk 为要卸载的钩子的句柄
 
注意:
1.   安装钩子的代码可以在 DLL 模块中,也可以在主模块中,但是一般在 DLL 里实现它,主要是为了使程序更加模块化。
 
 
例子(HOOK键盘消息)
1.dll库的生成(只是部分重要的文件,没有全部贴出)

//ke  
            //The following ifdef block is the standard way of creating macros which make exporting 
//  from a DLL simpler. All files within this DLL are compiled with the KEYHOOKLIB_EXPORTS
//  symbol defined on the command line. this symbol should not be defined on any project
//  that uses this DLL. This way any other project whose source files include this file see 
//  KEYHOOKLIB_API functions as being imported from a DLL, wheras this DLL sees symbols
//  defined with this macro as being exported.
#ifdef KEYHOOKLIB_EXPORTS
#define  KEYHOOKLIB_API __declspec(dllexport)
#else
#define  KEYHOOKLIB_API __declspec(dllimport)
#endif

//  自定义与主程序通信的消息
#define  HM_KEY WM_USER+1

//  声明要导出的函数
BOOL KEYHOOKLIB_API WINAPI SetKeyHook(BOOL bInstall, 
                               DWORD dwThreadId 
=   0
                               HWND hWndCaller 
=  NULL
                               );

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

#include 
" stdafx.h "
#include 
" KeyHookLib.h "

//  共享数据段
#pragma  data_seg("YCIShared")
HWND g_hWndCaller 
=  NULL;
HHOOK g_hHook 
=  NULL;
#pragma  data_seg()

LRESULT CALLBACK KeyHookProc(
int  nCode, WPARAM wParam, LPARAM lParam);



//  一个通过内存地址取得模块句柄的帮助函数。
HMODULE    WINAPI ModuleFromAddress(PVOID pv)
{
    MEMORY_BASIC_INFORMATION mbi;
    
if(VirtualQuery(pv,&mbi,sizeof(mbi)) != 0)
    
{
        
return (HMODULE)mbi.AllocationBase;
    }
else 
    
{
        
return NULL;
    }


}



//  安装钩子,卸载钩子的函数
BOOL WINAPI SetKeyHook(
            BOOL bInstall,         
//  安装还是卸载已安装的钩子
            DWORD dwThreadId,       //  目标线程的ID
            HWND  hWndCaller)       //  指定主窗口的句柄,钩子函数会向这个窗口发送通知信息。
{
    BOOL bOk;
    g_hWndCaller 
= hWndCaller;
    
if(bInstall)
    
{
        g_hHook 
= SetWindowsHookEx(
            WH_KEYBOARD,
            KeyHookProc,
            ModuleFromAddress(KeyHookProc),
            dwThreadId);
        bOk 
= (g_hHook != NULL);
    }

    
else
    
{
        bOk 
= UnhookWindowsHookEx(g_hHook);
        g_hHook 
= NULL;
    }

    
    
return bOk;
}


//  键盘钩子函数
LRESULT CALLBACK KeyHookProc( int  nCode, WPARAM wParam, LPARAM lParam)
{
    
if(nCode<0 || nCode == HC_NOREMOVE )
        
return CallNextHookEx(g_hHook,nCode,wParam,lParam);
    
if(lParam & 0x40000000// 消息重复就交给下一个HOOK链
        return CallNextHookEx(g_hHook,nCode,wParam,lParam);

    
// 通知主窗口。wParam参数为虚拟键码,lParam包含了此键的信息
    ::PostMessage(g_hWndCaller,HM_KEY,wParam,lParam);
    
return CallNextHookEx(g_hHook,nCode,wParam,lParam);
}

 

//  keyhooklib.def
EXPORTS
    SetKeyHook
SECTIONS
    YCIShared    Read Write Shared


2.应用
以对话框为基础建立工程(keyhookapp),改动的文件如下:

//  KeyHookAppDlg.cpp : implementation file
//

#include 
"stdafx.h"
#include 
"KeyHookApp.h"
#include 
"KeyHookAppDlg.h"
#include 
"KeyHookLib.h"
#pragma comment(lib,"KeyHookLib")

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] =  __FILE__;
#endif


/////
// CAboutDlg dialog used for App About


class CAboutDlg : public  CDialog
{
public
:
    CAboutDlg();

//
 Dialog Data
    
//{{AFX_DATA(CAboutDlg)

    enum { IDD = IDD_ABOUTBOX };
    
//
}}AFX_DATA

    
//
 ClassWizard generated virtual function overrides
    
//{{AFX_VIRTUAL(CAboutDlg)

    protected:
    
virtual void DoDataExchange(CDataExchange* pDX);    //
 DDX/DDV support
    
//
}}AFX_VIRTUAL

// Implementation

protected:
    
//
{{AFX_MSG(CAboutDlg)
    
//}}AFX_MSG

    DECLARE_MESSAGE_MAP()
}
;

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
    
//
{{AFX_DATA_INIT(CAboutDlg)
    
//}}AFX_DATA_INIT

}


void CAboutDlg::DoDataExchange(CDataExchange*  pDX)
{
    CDialog::DoDataExchange(pDX);
    
//
{{AFX_DATA_MAP(CAboutDlg)
    
//}}AFX_DATA_MAP

}


BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
    
// {{AFX_MSG_MAP(CAboutDlg)
        
//
 No message handlers
    
//}}AFX_MSG_MAP

END_MESSAGE_MAP()

/////
// CKeyHookAppDlg dialog


CKeyHookAppDlg::CKeyHookAppDlg(CWnd
* pParent /*=NULL*/ )
    : CDialog(CKeyHookAppDlg::IDD, pParent)
{
    
//
{{AFX_DATA_INIT(CKeyHookAppDlg)
        
//
 NOTE: the ClassWizard will add member initialization here
    
//
}}AFX_DATA_INIT
    
// Note that LoadIcon does not require a subsequent DestroyIcon in Win32

    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}


void CKeyHookAppDlg::DoDataExchange(CDataExchange*  pDX)
{
    CDialog::DoDataExchange(pDX);
    
//
{{AFX_DATA_MAP(CKeyHookAppDlg)
        
//
 NOTE: the ClassWizard will add DDX and DDV calls here
    
//}}AFX_DATA_MAP

}


BEGIN_MESSAGE_MAP(CKeyHookAppDlg, CDialog)
    
//{{AFX_MSG_MAP(CKeyHookAppDlg)
    ON_WM_SYSCOMMAND()
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_MESSAGE(HM_KEY,OnHookKey)
    
//}}AFX_MSG_MAP

END_MESSAGE_MAP()

/////
// CKeyHookAppDlg message handlers


BOOL CKeyHookAppDlg::OnInitDialog()
{
    CDialog::OnInitDialog();

    
//
 Add "About..." menu item to system menu.

    
// IDM_ABOUTBOX must be in the system command range.

    ASSERT((IDM_ABOUTBOX & 0xFFF0== IDM_ABOUTBOX);
    ASSERT(IDM_ABOUTBOX 
< 0xF000
);

    CMenu
* pSysMenu =
 GetSystemMenu(FALSE);
    
if (pSysMenu !=
 NULL)
    
{
        CString strAboutMenu;
        strAboutMenu.LoadString(IDS_ABOUTBOX);
        
if (!
strAboutMenu.IsEmpty())
        
{
            pSysMenu
->
AppendMenu(MF_SEPARATOR);
            pSysMenu
->
AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
        }

    }


    
// Set the icon for this dialog.  The framework does this automatically
    
//  when the application's main window is not a dialog

    SetIcon(m_hIcon, TRUE);            // Set big icon
    SetIcon(m_hIcon, FALSE);        // Set small icon
    
    
//
 TODO: Add extra initialization here

    
// 安装钩子

    if(!SetKeyHook(TRUE,0,m_hWnd))
        MessageBox(
"安装钩子失败"
);

    
    
return TRUE;  // return TRUE  unless you set the focus to a control

}


void  CKeyHookAppDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
    
if ((nID & 0xFFF0==
 IDM_ABOUTBOX)
    
{
        CAboutDlg dlgAbout;
        dlgAbout.DoModal();
    }

    
else
    
{
        CDialog::OnSysCommand(nID, lParam);
    }

}


//  If you add a minimize button to your dialog, you will need the code below
//
  to draw the icon.  For MFC applications using the document/view model,
//  this is automatically done for you by the framework.


void  CKeyHookAppDlg::OnPaint() 
{
    
if
 (IsIconic())
    
{
        CPaintDC dc(
this); // device context for painting


        SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 
0);

        
// Center icon in client rectangle

        int cxIcon = GetSystemMetrics(SM_CXICON);
        
int cyIcon =
 GetSystemMetrics(SM_CYICON);
        CRect rect;
        GetClientRect(
&
rect);
        
int x = (rect.Width() - cxIcon + 1/ 2
;
        
int y = (rect.Height() - cyIcon + 1/ 2
;

        
// Draw the icon

        dc.DrawIcon(x, y, m_hIcon);
    }

    
else
    
{
        CDialog::OnPaint();
    }

}


//  The system calls this to obtain the cursor to display while the user drags
//  the minimized window.

HCURSOR CKeyHookAppDlg::OnQueryDragIcon()
{
    
return
 (HCURSOR) m_hIcon;
}


void  CKeyHookAppDlg::OnCancel() 
{
    
//
 TODO: Add extra cleanup here
    
// 卸载钩子

    SetKeyHook(FALSE);
    CDialog::OnCancel();
}


void  CKeyHookAppDlg::OnOK() 
{
    
//
 TODO: Add extra validation here
    
// 卸载钩子

    SetKeyHook(FALSE);
    CDialog::OnOK();
}


// 钩子消息处理函数
long  CKeyHookAppDlg::OnHookKey(WPARAM wParam,LPARAM lParam)
{
    
//
 此时wParam为用户按键的虚拟键码
    
//
 lParam包含按键的重复次数,扫描码,前一个按键状态等信息
    
// 取得按键名称。lParam是键盘消息的第二个参数

    char szKey[80];
    ::GetKeyNameText(lParam,szKey,
80
);
    CString strItem;
    strItem.Format(
"用户按键:%s "
,szKey);

    
// 添加到编辑框

    CString strEdit;
    GetDlgItem(IDC_KEY)
->
GetWindowText(strEdit);
    GetDlgItem(IDC_KEY)
->SetWindowText(strItem+
strEdit);
    ::MessageBeep(MB_OK);
    
return 0
;
}


3。运行结果
一个对话框,检测当前键盘的状态。若有按键按下,则在对话框的EDIT控件中显示按下键的名字。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
二、API Hook的原理 这里的API既包括传统的Win32 APIs,也包括任何Module输出的函数调用。熟悉PE文件格 式的朋友都知道,PE文件将对外部Module输出函数的调用信息保存在输入表中,即.idata段。 下面首先介绍本段的结构。 输入表首先以一个IMAGE_IMPORT_DESCRIPTOR(简称IID)数组开始。每个被PE文件隐式链接 进来的DLL都有一个IID.在这个数组中的最后一个单元是NULL,可以由此计算出该数组的项数。 例如,某个PE文件从两个DLL中引入函数,就存在两个IID结构来描述这些DLL文件,并在两个 IID结构的最后由一个内容全为0的IID结构作为结束。几个结构定义如下: IMAGE_IMPORT_DESCRIPTOR struct union{ DWORD Characteristics; ;00h DWORD OriginalFirstThunk; }; TimeDateStamp DWORD ;04h ForwarderChain DWORD ;08h Name DWORD ;0Ch FirstThunk DWORD ;10h IMAGE_IMPROT_DESCRIPTOR ends typedef struct _IMAGE_THUNK_DATA{ union{ PBYTE ForwarderString; PDWORD Functions; DWORD Ordinal; PIMAGE_IMPORT_BY_NAME AddressOfData; }u1; } IMAGE_IMPORT_BY_NAME结构保存一个输入函数的相关信息: IMAGE_IMPORT_BY_NAME struct Hint WORD ? ;本函数在其所驻留DLL的输出表中的序号 Name BYTE ? ;输入函数的函数名,以NULL结尾的ASCII字符串 IMAGE_IMPORT_BY_NAME ends OriginalFirstThunk(Characteristics):这是一个IMAGE_THUNK_DATA数组的RVA(相对于PE文件 起始处)。其中每个指针都指向IMAGE_IMPORT_BY_NAME结构。 TimeDateStamp:一个32位的时间标志,可以忽略。 ForwarderChain:正向链接索引,一般为0。当程序引用一个DLL中的API,而这个API又引用别的 DLL的API时使用。 NameLL名字的指针。是个以00结尾的ASCII字符的RVA地址,如"KERNEL32.DLL"。 FirstThunk:通常也是一个IMAGE_THUNK_DATA数组的RVA。如果不是一个指针,它就是该功能在 DLL中的序号。 OriginalFirstThunk与FirstThunk指向两个本质相同的数组IMAGE_THUNK_DATA,但名称不同, 分别是输入名称表(Import Name Table,INT)和输入地址表(Import Address Table,IAT)。 IMAGE_THUNK_DATA结构是个双字,在不同时刻有不同的含义,当双字最高位为1时,表示函数以 序号输入,低位就是函数序号。当双字最高位为0时,表示函数以字符串类型的函数名 方式输入,这时它是指向IMAGE_IMPORT_BY_NAME结构的RVA。 三个结构关系如下图: IMAGE_IMPORT_DESCRIPTOR INT IMAGE_IMPORT_BY_NAME IAT -------------------- /-->---------------- ---------- ---------------- |01| 函数1 ||02| 函数2 || n| ... |"USER32.dll" | |--------------------| | | FirstThunk |---------------------------------------------------------------/ -------------------- 在PE文件中对DLL输出函数的调用,主要以这种形式出现: call dword ptr[xxxxxxxx] 或 jmp [xxxxxxxx] 其中地址xxxxxxxx就是IAT中一个IMAGE_THUNK_DATA结构的地址,[xxxxxxxx]取值为IMAGE_THUNK_DATA 的值,即IMAGE_IMPORT_BY_NAME的地址。在操作系统加载PE文件的过程中,通过IID中的Name加载相应 的DLL,然后根据INT或IAT所指向的IMAGE_IMPORT_BY_NAME中的输入函数信息,在DLL中确定函数地址, 然后将函数地址写到IAT中,此时IAT将不再指向IMAGE_IMPORT_BY_NAME数组。这样[xxxxxxxx]取到的 就是真正的API地址。 从以上分析可以看出,要拦截API的调用,可以通过改写IAT来实现,将自己函数的地址写到IAT中, 达到拦截目的。 另外一种方法的原理更简单,也更直接。我们不是要拦截吗,先在内存中定位要拦截的API的地址, 然后改写代码的前几个字节为 jmp xxxxxxxx,其中xxxxxxxx为我们的API的地址。这样对欲拦截API的 调用实际上就跳转到了咱们的API调用去了,完成了拦截。不拦截时,再改写回来就是了。 这都是自己从网上辛辛苦苦找来的,真的很好啊

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值