尝试挂接到 MessageBeep 系统 API

Trying to hook to MessageBeep system API

本文关键字:MessageBeep 系统 API      更新时间:2023-10-16

被客户要求解决以下讨厌的问题。他们有一个自定义软件,该软件倾向于在没有任何明显原因的情况下"左右"显示消息框。例如,软件本身是一个会计程序,当他们接受客户的付款时,消息框可能会连续显示大约 3 或 4 次。每个消息框都播放 Windows 默认声音。不幸的是,该软件的编程方式,它播放的声音类型是完全错误的。例如,当消息本身只是一个信息时,它可能会显示一个警告消息框并播放警告系统声音。对于使用该软件的工作人员来说,所有这些都很烦人。

我试图联系分发软件的供应商,但我与他们陷入了死胡同。所以现在我正在寻找缓解这个问题的方法。

我最简单的解决方案是建议将扬声器静音,但不幸的是,它们需要声音才能听到传入的电子邮件,最重要的是,以后能够播放来自扬声器的语音邮件。所以我的解决方案是以某种方式将消息框声音静音,仅用于单个进程。

根据我的经验,我知道有两个API可能会产生这些声音:MessageBeep和较旧的哔哔声。

我还发现了这篇文章,它解释了如何使用AppInit_DLLs挂接到系统 API。它工作得很好,除了我需要钩接的两个 API 都来自 User32.dll而不是像作者建议的那样来自 kernel32.dll。

问题部分还有这篇文章,它给出了从 User32.dll 挂钩到 API 的大致步骤,但是当我尝试实现它们时,没有足够的信息(据我所知(。

所以我的问题是,有谁知道如何挂接到 User32.dll 模块中的 API?

编辑:PS.忘了提。此软件安装在 Windows 7 专业版上,禁用了 UAC - 因为它与 UAC :)<</p>

div class="answers"不兼容>

作为替代方案,您可以修补您的应用程序。查找要MessageBeep的调用,并用 nop 覆盖它们。

这是很难做到的:如果你的应用程序应该在Vista之前的Windows上以管理员身份运行,你可以通过::GetProcAddress()获取API的地址,给自己写入其内存页面的权限,并用"jmp"汇编指令覆盖API代码的开头跳入覆盖函数的地址。确保您的覆盖函数采用相同的参数并声明为 __cdecl

扩展答案如下。

API 挂钩的"标准"技术涉及以下步骤:

1:将 DLL 注入目标进程

这通常是通过首先在目标进程中为包含 DLL 名称/路径的字符串分配内存(例如"MyHook.dll"(来实现的,然后在目标进程中创建一个远程线程,其入口点kernel32::LoadLibraryA()传递 DLL 的名称作为参数。此页面包含此技术的实现。你将不得不与特权搏斗,但它保证在Windows XP和更早的操作系统上100%工作。我不确定 Vista 和后 Vista,地址空间布局随机化可能会使这变得棘手。

2. 挂钩 API

将 DLL 加载到目标进程后,其DllMain()将自动执行,让您有机会在目标进程中运行所需的任何内容。在DllMain中,使用 ::LoadLibraryA() 获取包含要挂钩的 API 的库的HMODULE(例如"user32.dll"(,并将其与要挂钩的 API 的名称(例如"MessageBeep"(一起传递给::GetProcAddress()以获取 API 本身的地址。最终,赋予自己写入该地址页面的权限,并用跳入绕道的jmp指令覆盖API的开头(即进入要挂钩的API的"版本"(。请注意,您的绕道需要与您要挂钩的 API 具有相同的签名和调用约定(通常为 _cdecl(,否则怪物将被唤醒。

如此处所述,这种技术在某种程度上具有破坏性:你不能绕道回调原始 API,因为原始 API 已被修改为跳转到你的 API,你最终会得到一个非常紧凑和漂亮的无限循环。有许多不同的技术可以让你保留和/或回调到原始API,其中之一是挂接API的...A()版本,然后调用...W()版本(大多数(如果不是全部(...A() Windows API将ASCII字符串转换为UNICODE字符串,并最终调用它们的...W()对应项(。

无需花时间在自定义程序上来执行此操作。

您可以在特定应用程序运行时将其静音,下次打开该应用程序时将记住该设置。请参阅 https://superuser.com/questions/37281/how-to-disable-sound-of-certain-applications。

还有Windows Sound Sentry可以关闭大多数系统声音,尽管我不知道Sound Sentry的任何每个应用程序的设置。

您可以使用 Deviare API 钩子并在几个 C# 行中求解钩子。或者你可以使用更困难和不太稳定的EasyHook。