如何安全地解除Win32 API阻塞



我有一个应用程序,它将一堆API连接到Win32目标应用程序(使用ASIO)中,以监视目标处理的命名管道流量。它通过ReadFile或WriteFile调用为事务设置一个开始,如果调用是同步进行的,则获取该调用的结果。如果调用是异步的,它还通过GetQueuedCompletionStatus的钩子来捕获该调用的结束。检索到的数据被发送到我自己的ASIO线程池,该线程池服务于与Wireshark的命名管道连接。这真的很贴心。

一切都很好,除了我必须把程序恢复到原来的状态。解挂钩函数工作得很好,除了在GetQueuedCompletionStatus中现有调用可能被无限期阻塞,除非我强制目标应用程序通过命名管道处理大量请求。如果我不等待这些线程解除阻塞,当GetQueuedCompletionStatus最终解除阻塞并返回到不再存在的代码洞时,我将导致AV。

我尝试的另一件事是跟踪对GetQueuedCompletionStatus的每次调用,并使钩子函数在GetQueuedCompletionStatus的lpoverlap参数匹配对PostQueuedCompletionStatus的相应调用时通知信号。当然,这会清除所有阻塞,但它会严重扰乱调用GetQueuedCompletionStatus的代码,从而导致AV。

有谁知道处理这个的好方法吗?如果我能执行以下操作之一,就可以了:

  • 用PostQueuedCompletionStatus调用,ASIO将忽略
  • 捕获PostQueuedCompletionStatus发出的假调用,并覆盖堆栈中的返回值
  • 创建一个半永久的代码洞,其中包含一个为钩子阻塞调用传递函数指针访问器的蹦床。trampoline函数会看到,如果这个值不为空,它会调用那个函数指针,而不是返回执行给调用者。在解除阻塞调度后,钩子代码可以将函数指针设置为GetQueuedCompletionStatus的地址,然后我们就可以向调用者返回正确的调用结果。

第一个选项很简单,除了能够将此应用程序泛化以用于其他目标之外。第二种方法很简单,除了我希望编写的代码尽可能安全之外。最后一个可能是可行的,我只是不想用代码洞污染外部内存空间。

我怀疑除了简单地保持DLL加载之外,是否存在一般且安全的解决方案。或者从一开始就避免那些诱惑……(直接跳到答案的最后,看主要剧透!)


这个论点与我在对这个问题的评论中所写的内容一致。

要释放DLL,你必须确保当前没有人在DLL中执行代码,并且没有人将要执行DLL中的代码(除了我们正在讨论的拆除线程)。

代码在DLL中执行可能有两个原因,要么我们要调用它,要么我们要返回到它(或者返回到将要返回到DLL的代码,等等)。假设我们解出第一个问题。我们还剩下第二个原因。也许我们已经在钩子里面了。

你可以考虑这样做:

  1. 分配一个"代码洞",做一些类似

    的事情
    PreHookWriteFile:
    LOCK INC [ref_count]
    POP R15
    CALL HookWriteFile
    PostHookWriteFile:
    LOCK DEC [ref_count]
    JMP R15
    
  2. 钩子WriteFile与JMP [PreHookWriteFile]

  3. 在一个专用线程中执行释放,该线程解挂WriteFile并等待重新计数变为零。然后释放代码洞。

但是有两个问题。首先,我们实际上没有一个地方来存储原始的返回地址。我们不能把它放在堆栈上,因为HookWriteFile不希望看到它在那里,我们不能把它存储在任何非易失性寄存器中,因为我们需要在跳回之前在PostHookWriteFile中恢复它,但问题是我们没有地方存储东西。

第二,释放线程可能会在跳转发生之前注意到减少,并过早地释放代码洞。

我不认为它重要的是我们试图是多么聪明(无论是与__declspec(naked)函数或修改原型)有钩子函数期望在堆栈上的原始返回地址。第二个问题仍然存在——在返回之前减少了引用计数。在你返回之前删除代码是不安全的,但你不能通知你的返回,因为你返回后你就不再控制了。这正是创建FreeLibraryAndExitThread的原因。

有人可能会想到一些疯狂的事情,比如挂起DLL中的所有线程并遍历它们的堆栈以确保DLL中的代码没有出现在任何线程中,但这只是打开了另一个蠕虫罐头。


但也有好消息。

如果你真正想要的是监控命名管道通信,并且你愿意将自己限制在Windows 8和更新的版本中,那么有一个更强大的解决方案,有文档和支持,需要更少的代码行,并且不具有侵入性。Koby Kahane写了一个文件系统迷你过滤器,它就是这样做的。

它的输出是ETW事件,您可以在Microsoft Message Analyzer或其他使用来自基于清单的提供者的ETW事件的工具中查看这些事件。如果你真的需要在Wireshark中看到它,你可以编写一个小的ETW消费者,它消耗事件并通过管道将它们发送回Wireshark。它仍然比所有这些钩子更容易,而且肯定更安全,更少混乱。

相关内容

  • 没有找到相关文章

最新更新