鼠标钩子不能全局?

windywater 2010-04-22 01:12:13
以下是我的DLL里的代码:

#include "stdafx.h"

#define DECLDIR extern "C" __declspec(dllexport)


HINSTANCE g_hInst;
HHOOK g_hMouseHook;

DECLDIR BOOL SetHook();
DECLDIR BOOL ReleaseHook();

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

BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
g_hInst = (HINSTANCE)hModule;

switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

BOOL SetHook()
{
g_hMouseHook = SetWindowsHookEx(WH_MOUSE, MouseProc, g_hInst, 0);

return (g_hMouseHook != NULL);
}

BOOL ReleaseHook()
{
return UnhookWindowsHookEx(g_hMouseHook);
}

LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{

return CallNextHookEx(g_hMouseHook, nCode, wParam, lParam);
}

我的一个对话框程序导入这个DLL并调用了SetHook函数,但是鼠标只有在对话框上时MouseProc才能进去,这是怎么回事?
...全文
390 11 打赏 收藏 转发到动态 举报
写回复
用AI写文章
11 条回复
切换为时间正序
请发表友善的回复…
发表回复
茹果伱在 2011-04-07
  • 打赏
  • 举报
回复
哦哦 看不懂
你妹的特盗不 2010-05-19
  • 打赏
  • 举报
回复
楼上的楼上学习了
o老猫钓鱼o 2010-04-22
  • 打赏
  • 举报
回复
学习了
尹成 2010-04-22
  • 打赏
  • 举报
回复
  要“读取”某个控件的内容——无论这个控件是否属于当前的应用程序——通常都是发送 WM_GETTEXT 消息来实现。这个技术也同样应用到编辑控件,但是如果该编辑控件属于另外一个进程并设置了 ES_PASSWORD 式样,那么上面讲的方法就行不通了。用 WM_GETTEXT 来获取控件的内容只适用于进程“拥有”密码控件的情况。所以我们的问题变成了如何在另外一个进程的地址空间执行:

::SendMessage( hPwdEdit, WM_GETTEXT, nMaxChars, psBuffer );
通常有三种可能性来解决这个问题。

将你的代码放入某个 DLL,然后通过 Windows 钩子映射该DLL到远程进程;
将你的代码放入某个 DLL,然后通过 CreateRemoteThread 和 LoadLibrary 技术映射该DLL到远程进程;
如果不写单独的 DLL,可以直接将你的代码拷贝到远程进程——通过 WriteProcessMemory——并用 CreateRemoteThread 启动它的执行。本文将在第三部分详细描述该技术实现细节;
第一部分: Windows 钩子

范例程序——参见HookSpy 和HookInjEx

  Windows 钩子主要作用是监控某些线程的消息流。通常我们将钩子分为本地钩子和远程钩子以及系统级钩子,本地钩子一般监控属于本进程的线程的消息流,远程钩子是线程专用的,用于监控属于另外进程的线程消息流。系统级钩子监控运行在当前系统中的所有线程的消息流。
  如果钩子作用的线程属于另外的进程,那么你的钩子过程必须驻留在某个动态链接库(DLL)中。然后系统映射包含钩子过程的DLL到钩子作用的线程的地址空间。Windows将映射整个 DLL,而不仅仅是钩子过程。这就是为什么 Windows 钩子能被用于将代码注入到别的进程地址空间的原因。
  本文我不打算涉及钩子的具体细节(关于钩子的细节请参见 MSDN 库中的 SetWindowHookEx API),但我在此要给出两个很有用心得,在相关文档中你是找不到这些内容的:

在成功调用 SetWindowsHookEx 后,系统自动映射 DLL 到钩子作用的线程地址空间,但不必立即发生映射,因为 Windows 钩子都是消息,DLL 在消息事件发生前并没有产生实际的映射。例如:
  如果你安装一个钩子监控某些线程(WH_CALLWNDPROC)的非队列消息,在消息被实际发送到(某些窗口的)钩子作用的线程之前,该DLL 是不会被映射到远程进程的。换句话说,如果 UnhookWindowsHookEx 在某个消息被发送到钩子作用的线程之前被调用,DLL 根本不会被映射到远程进程(即使 SetWindowsHookEx 本身调用成功)。为了强制进行映射,在调用 SetWindowsHookEx 之后马上发送一个事件到相关的线程。
  在UnhookWindowsHookEx了之后,对于没有映射的DLL处理方法也一样。只有在足够的事件发生后,DLL才会有真正的映射。
当你安装钩子后,它们可能影响整个系统得性能(尤其是系统级钩子),但是你可以很容易解决这个问题,如果你使用线程专用钩子的DLL映射机制,并不截获消息。考虑使用如下代码:
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved )
{
if( ul_reason_for_call == DLL_PROCESS_ATTACH )
{
// Increase reference count via LoadLibrary
char lib_name[MAX_PATH];
::GetModuleFileName( hModule, lib_name, MAX_PATH );
::LoadLibrary( lib_name );

// Safely remove hook
::UnhookWindowsHookEx( g_hHook );
}
return TRUE;
}
  那么会发生什么呢?首先我们通过Windows 钩子将DLL映射到远程进程。然后,在DLL被实际映射之后,我们解开钩子。通常当第一个消息到达钩子作用线程时,DLL此时也不会被映射。这里的处理技巧是调用LoadLibrary通过增加 DLLs的引用计数来防止映射不成功。
  现在剩下的问题是如何卸载DLL,UnhookWindowsHookEx 是不会做这个事情的,因为钩子已经不作用于线程了。你可以像下面这样做:


就在你想要解除DLL映射前,安装另一个钩子;
发送一个“特殊”消息到远程线程;
在钩子过程中截获这个消息,响应该消息时调用 FreeLibrary 和 UnhookWindowsHookEx;

  目前只使用了钩子来从处理远程进程中DLL的映射和解除映射。在此“作用于线程的”钩子对性能没有影响。
下面我们将讨论另外一种方法,这个方法与 LoadLibrary 技术的不同之处是DLL的映射机制不会干预目标进程。相对LoadLibrary 技术,这部分描述的方法适用于 WinNT和Win9x。
  但是,什么时候使用这个技巧呢?答案是当DLL必须在远程进程中驻留较长时间(即如果你子类化某个属于另外一个进程的控件时)以及你想尽可能少的干涉目标进程时。我在 HookSpy 中没有使用它,因为注入DLL 的时间并不长——注入时间只要足够得到密码即可。我提供了另外一个例子程序——HookInjEx——来示范。HookInjEx 将DLL映射到资源管理器“explorer.exe”,并从中/解除影射,它子类化“开始”按钮,并交换鼠标左右键单击“开始”按钮的功能。

HookSpy 和 HookInjEx 的源代码都可以从本文的下载源代码中获得。

第二部分:CreateRemoteThread 和 LoadLibrary 技术

范例程序——LibSpy

通常,任何进程都可以通过 LoadLibrary API 动态加载DLL。但是,如何强制一个外部进程调用这个函数呢?答案是:CreateRemoteThread。
首先,让我们看一下 LoadLibrary 和FreeLibrary API 的声明:

HINSTANCE LoadLibrary(
LPCTSTR lpLibFileName // 库模块文件名的地址
);

BOOL FreeLibrary(
HMODULE hLibModule // 要加载的库模块的句柄
);
现在将它们与传递到 CreateRemoteThread 的线程例程——ThreadProc 的声明进行比较。

DWORD WINAPI ThreadProc(
LPVOID lpParameter // 线程数据
);
你可以看到,所有函数都使用相同的调用规范并都接受 32位参数,返回值的大小都相同。也就是说,我们可以传递一个指针到LoadLibrary/FreeLibrary 作为到 CreateRemoteThread 的线程例程。但这里有两个问题,请看下面对CreateRemoteThread 的描述:

CreateRemoteThread 的 lpStartAddress 参数必须表示远程进程中线程例程的开始地址。
如果传递到 ThreadFunc 的参数lpParameter——被解释为常规的 32位值(FreeLibrary将它解释为一个 HMODULE),一切OK。但是,如果 lpParameter 被解释为一个指针(LoadLibraryA将它解释为一个串指针)。它必须指向远程进程的某些数据。
  第一个问题实际上是由它自己解决的。LoadLibrary 和 FreeLibray 两个函数都在 kernel32.dll 中。因为必须保证kernel32存在并且在每个“常规”进程中的加载地址要相同,LoadLibrary/FreeLibray 的地址在每个进程中的地址要相同,这就保证了有效的指针被传递到远程进程。
  第二个问题也很容易解决。只要通过 WriteProcessMemory 将 DLL 模块名(LoadLibrary需要的DLL模块名)拷贝到远程进程即可。

所以,为了使用CreateRemoteThread 和 LoadLibrary 技术,需要按照下列步骤来做:

获取远程进程(OpenProcess)的 HANDLE;
为远程进程中的 DLL名分配内存(VirtualAllocEx);
将 DLL 名,包含全路径名,写入分配的内存(WriteProcessMemory);
用 CreateRemoteThread 和 LoadLibrary. 将你的DLL映射到远程进程;
等待直到线程终止(WaitForSingleObject),也就是说直到 LoadLibrary 调用返回。另一种方法是,一旦 DllMain(用DLL_PROCESS_ATTACH调用)返回,线程就会终止;
获取远程线程的退出代码(GetExitCodeThread)。注意这是一个 LoadLibrary 返回的值,因此是所映射 DLL 的基地址(HMODULE)。
在第二步中释放分配的地址(VirtualFreeEx);
用 CreateRemoteThread 和 FreeLibrary从远程进程中卸载 DLL。传递在第六步获取的 HMODULE 句柄到 FreeLibrary(通过 CreateRemoteThread 的lpParameter参数);
注意:如果你注入的 DLL 产生任何新的线程,一定要在卸载DLL 之前将它们都终止掉;
等待直到线程终止(WaitForSingleObject);
  此外,处理完成后不要忘了关闭所有句柄,包括在第四步和第八步创建的两个线程以及在第一步获取的远程线程句柄。现在让我们看一下 LibSpy 的部分代码,为了简单起见,上述步骤的实现细节中的错误处理以及 UNICODE 支持部分被略掉。

HANDLE hThread;
char szLibPath[_MAX_PATH]; // “LibSpy.dll”模块的名称 (包括全路径);
void* pLibRemote; // 远程进程中的地址,szLibPath 将被拷贝到此处;
DWORD hLibModule; // 要加载的模块的基地址(HMODULE)
HMODULE hKernel32 = ::GetModuleHandle("Kernel32");

// 初始化szLibPath
//...
// 1. 在远程进程中为szLibPath 分配内存
// 2. 将szLibPath 写入分配的内存
pLibRemote = ::VirtualAllocEx( hProcess, NULL, sizeof(szLibPath),
MEM_COMMIT, PAGE_READWRITE );
::WriteProcessMemory( hProcess, pLibRemote, (void*)szLibPath,
sizeof(szLibPath), NULL );

// 将"LibSpy.dll" 加载到远程进程(使用CreateRemoteThread 和 LoadLibrary)
hThread = ::CreateRemoteThread( hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE) ::GetProcAddress( hKernel32,
"LoadLibraryA" ),
pLibRemote, 0, NULL );
::WaitForSingleObject( hThread, INFINITE );

// 获取所加载的模块的句柄
::GetExitCodeThread( hThread, &hLibModule );

// 清除
::CloseHandle( hThread );
::VirtualFreeEx( hProcess, pLibRemote, sizeof(szLibPath), MEM_RELEASE );
  假设我们实际想要注入的代码——SendMessage ——被放在DllMain (DLL_PROCESS_ATTACH)中,现在它已经被执行。那么现在应该从目标进程中将DLL 卸载: // 从目标进程中卸载"LibSpy.dll" (使用 CreateRemoteThread 和 FreeLibrary)
hThread = ::CreateRemoteThread( hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE) ::GetProcAddress( hKernel32,
"FreeLibrary" ),
(void*)hLibModule, 0, NULL );
::WaitForSingleObject( hThread, INFINITE );

// 清除
::CloseHandle( hThread );
进程间通信

  到目前为止,我们只讨论了关于如何将DLL 注入到远程进程的内容,但是,在大多数情况下,注入的 DLL 都需要与原应用程序进行某种方式的通信(回想一下,我们的DLL是被映射到某个远程进程的地址空间里了,不是在本地应用程序的地址空间中)。比如秘密侦测程序,DLL必须要知道实际包含密码的控件句柄,显然,编译时无法将这个值进行硬编码。同样,一旦DLL获得了秘密,它必须将它发送回原应用程序,以便能正确显示出来。
  幸运的是,有许多方法处理这个问题,文件映射,WM_COPYDATA,剪贴板以及很简单的 #pragma data_seg 共享数据段等,本文我不打算使用这些技术,因为MSDN(“进程间通信”部分)以及其它渠道可以找到很多文档参考。不过我在 LibSpy例子中还是使用了 #pragma data_seg。细节请参考 LibSpy 源代码。
第三部分:CreateRemoteThread 和 WriteProcessMemory 技术

范例程序——WinSpy

  另外一个将代码拷贝到另一个进程地址空间并在该进程上下文中执行的方法是使用远程线程和 WriteProcessMemory API。这种方法不用编写单独的DLL,而是用 WriteProcessMemory 直接将代码拷贝到远程进程——然后用 CreateRemoteThread 启动它执行。先来看看 CreateRemoteThread 的声明:

HANDLE CreateRemoteThread(
HANDLE hProcess, // 传入创建新线程的进程句柄
LPSECURITY_ATTRIBUTES lpThreadAttributes, // 安全属性指针
DWORD dwStackSize, // 字节为单位的初始线程堆栈
LPTHREAD_START_ROUTINE lpStartAddress, // 指向线程函数的指针
LPVOID lpParameter, // 新线程使用的参数
DWORD dwCreationFlags, // 创建标志
LPDWORD lpThreadId // 指向返回的线程ID
);
如果你比较它与 CreateThread(MSDN)的声明,你会注意到如下的差别:
在 CreateRemoteThread中,hProcess是额外的一个参数,一个进程句柄,新线程就是在这个进程中创建的;
在 CreateRemoteThread中,lpStartAddress 表示的是在远程进程地址空间中的线程起始地址。线程函数必须要存在于远程进程中,所以我们不能简单地传递一个指针到本地的 ThreadFunc。必须得先拷贝代码到远程进程;
同样,lpParameter 指向的数据也必须要存在于远程进程,所以也得将它拷贝到那。
综上所述,我们得按照如下的步骤来做:

获取一个远程进程的HANDLE (OpenProces) ;
在远程进程地址空间中为注入的数据分配内存(VirtualAllocEx);
将初始的 INDATA 数据结构的一个拷贝写入分配的内存中(WriteProcessMemory);
在远程进程地址空间中为注入的代码分配内存;
将 ThreadFunc 的一个拷贝写入分配的内存;
用 CreateRemoteThread启动远程的 ThreadFunc 拷贝;
等待远程线程终止(WaitForSingleObject);
获取远程来自远程进程的结果(ReadProcessMemory 或 GetExitCodeThread);
释放在第二步和第四步中分配的内存(VirtualFreeEx);
关闭在第六步和第一步获取的句柄(CloseHandle);
ThreadFunc 必须要遵循的原则:

除了kernel32.dll 和user32.dll 中的函数之外,ThreadFunc 不要调用任何其它函数,只有 kernel32.dll 和user32.dll被保证在本地和目标进程中的加载地址相同(注意,user32.dll并不是被映射到每个 Win32 的进程)。如果你需要来自其它库中的函数,将LoadLibrary 和 GetProcAddress 的地址传给注入的代码,然后放手让它自己去做。如果映射到目标进程中的DLL有冲突,你也可以用 GetModuleHandle 来代替 LoadLibrary。
  同
注释:注意AfterThreadFunc 是如何计算 ThreadFunc 大小的。通常这样做并不是一个好办法,因为链接器可以随意更改函数的顺序(也就是说ThreadFunc可能被放在 AfterThreadFunc之后)。这一点你可以在小项目中很好地保证函数的顺序是预先设想好的,比如 WinSpy 程序。在必要的情况下,你还可以使用 /ORDER 链接器选项来解决函数链接顺序问题。或者用反汇编确定 ThreadFunc 函数的大小。
如何使用该技术子类化远程控件
范例程序——InjectEx

下面我们将讨论一些更复杂的内容,如何子类化属于另一个进程的控件。

首先,你得拷贝两个函数到远程进程来完成此任务

ThreadFunc实际上是通过 SetWindowLong子类化远程进程中的控件;
NewProc是子类化控件的新窗口过程;
  这里主要的问题是如何将数据传到远程窗口过程 NewProc,因为 NewProc 是一个回调函数,它必须遵循特定的规范和原则,我们不能简单地在参数中传递 INJDATA指针。幸运的是我找到了有两个方法来解决这个问题,只不过要借助汇编语言,所以不要忽略了汇编,关键时候它是很有用的!

方法一:

如下图所示:



  在远程进程中,INJDATA 被放在NewProc 之前,这样 NewProc 在编译时便知道 INJDATA 在远程进程地址空间中的内存位置。更确切地说,它知道相对于其自身位置的 INJDATA 的地址,我们需要所有这些信息。下面是 NewProc 的代码:

static LRESULT CALLBACK NewProc(
HWND hwnd, // 窗口句柄
UINT uMsg, // 消息标示符
WPARAM wParam, // 第一个消息参数
LPARAM lParam ) // 第二个消息参数
{
INJDATA* pData = (INJDATA*) NewProc; // pData 指向 NewProc
pData--; // 现在pData 指向INJDATA;
// 回想一下INJDATA 被置于远程进程NewProc之前;

//-----------------------------
// 此处是子类化代码
// ........
//-----------------------------

// 调用原窗口过程;
// fnOldProc (由SetWindowLong 返回) 被(远程)ThreadFunc初始化
// 并被保存在(远程)INJDATA;中
return pData->fnCallWindowProc( pData->fnOldProc,
hwnd,uMsg,wParam,lParam );
}

但这里还有一个问题,见第一行代码:INJDATA* pData = (INJDATA*) NewProc;
  这种方式 pData得到的是硬编码值(在我们的进程中是原 NewProc 的内存地址)。这不是我们十分想要的。在远程进程中,NewProc “当前”拷贝的内存地址与它被移到的实际位置是无关的,换句话说,我们会需要某种类型的“this 指针”。
 
hlq83 2010-04-22
  • 打赏
  • 举报
回复
应该可以的,看你代码也没发现问题
「已注销」 2010-04-22
  • 打赏
  • 举报
回复
[Quote=引用 5 楼 zwfgdlc 的回复:]
C/C++ code
g_hMouseHook = SetWindowsHookEx(WH_MOUSE, MouseProc, g_hInst, 0);

之后再发送一个
WM_MOUSEMOVE消息.
在没有鼠标消息发生时,DLL并没有真正的完成影射。
因为你HOOK的是鼠标消息.
参考文档:
http://www.vckbase.com/document/viewdoc/?id……
[/Quote]
在对话框里调用了SetHook之后再给自己发送WM_MOUSEMOVE吗?我试了下,还是不行。
zwfgdlc 2010-04-22
  • 打赏
  • 举报
回复
g_hMouseHook = SetWindowsHookEx(WH_MOUSE, MouseProc, g_hInst, 0);
之后再发送一个
WM_MOUSEMOVE消息.
在没有鼠标消息发生时,DLL并没有真正的完成影射。
因为你HOOK的是鼠标消息.
参考文档:
http://www.vckbase.com/document/viewdoc/?id=1886
lijianli9 2010-04-22
  • 打赏
  • 举报
回复
代码没有问题,我自己测试都可以。
「已注销」 2010-04-22
  • 打赏
  • 举报
回复
[Quote=引用 2 楼 ljz888666555 的回复:]
release下试试。
[/Quote]
也不行啊。
ljz888666555 2010-04-22
  • 打赏
  • 举报
回复
release下试试。
soswaidao 2010-04-22
  • 打赏
  • 举报
回复
友情帮顶,友情帮顶

16,472

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC相关问题讨论
社区管理员
  • 基础类社区
  • Web++
  • encoderlee
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

        VC/MFC社区版块或许是CSDN最“古老”的版块了,记忆之中,与CSDN的年龄几乎差不多。随着时间的推移,MFC技术渐渐的偏离了开发主流,若干年之后的今天,当我们面对着微软的这个经典之笔,内心充满着敬意,那些曾经的记忆,可以说代表着二十年前曾经的辉煌……
        向经典致敬,或许是老一代程序员内心里面难以释怀的感受。互联网大行其道的今天,我们期待着MFC技术能够恢复其曾经的辉煌,或许这个期待会永远成为一种“梦想”,或许一切皆有可能……
        我们希望这个版块可以很好的适配Web时代,期待更好的互联网技术能够使得MFC技术框架得以重现活力,……

试试用AI创作助手写篇文章吧