如何安全取消挂起阻止的Win32 API?

时间:2016-09-28 23:05:19

标签: c++ winapi boost-asio api-hook

我有一个application,它将一堆API连接到Win32目标应用程序(使用ASIO)来监视目标处理的命名管道流量。它为具有ReadFile或WriteFile调用的事务设置了一个开始,并在同步调用时获取该调用的结果。如果调用是异步的,它还会通过对GetQueuedCompletionStatus的挂钩来捕获该调用的结束。检索到的数据将发送到我自己的ASIO线程池,该线程池为Wireshark提供命名管道连接。它真的很可爱。

一切都很好,除非我必须让程序恢复到原始状态。解除这些函数的效果很好,除了可以在GetQueuedCompletionStatus中无限期地阻塞现有调用的事实,除非我强制目标应用程序通过命名管道处理大量请求。如果我不等待那些线程解除阻塞,我会在GetQueuedCompletionStatus最终解锁并返回到不再存在的代码洞时引起AV。

我尝试的另一件事是跟踪对GetQueuedCompletionStatus的每次调用,并且只要GetQueuedCompletionStatus的LpOverlapped参数匹配对PostQueuedCompletionStatus的相应调用,就让钩子函数通知信号。这肯定会解锁所有内容,但它会严重影响GetQueuedCompletionStatus调用的代码,从而产生AV。

有谁知道处理这个问题的好方法?如果我可以执行以下操作之一,这将有效:

  • 使用PostQueuedCompletionStatus调用,ASIO将忽略
  • 捕获PostQueuedCompletionStatus进行的虚拟调用并覆盖堆栈中的返回值
  • 创建一个包含trampoline的半永久性代码洞,用于钩子阻塞调用,它将函数指针访问器传递给挂钩调用。 trampoline函数会看到,如果此值不为null,它将调用该函数指针而不是返回执行给调用者。在解除阻塞调度时,挂钩代码可以将该函数指针设置为GetQueuedCompletionStatus的地址,然后我们就能够将正确的调用结果返回给调用者。

第一个选项很简单,但能够将此应用程序概括为与其他目标一起使用会很好。第二个很容易,除了我想编写尽可能安全的代码。最后一个可能是可行的,我只是不想用代码洞来污染外部存储空间。

1 个答案:

答案 0 :(得分:1)

我怀疑一个通用且安全的解决方案 - 除了简单地保持DLL加载 - 存在。或者首先避免使用所有钩子......(跳到主要扰流板的答案的末尾!)

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

要释放DLL,你必须确保没有人正在执行DLL中的代码,并且没有人会在DLL中执行代码(除了我们正在讨论的内容的拆卸线程)。< / p>

代码可能会在DLL中执行有两个原因,要么我们要调用它,要么我们将返回它(或返回到将返回DLL的代码等)。 )。让我们说我们通过解开来解决第一个问题。我们仍然留下第二个原因。也许我们已经陷入困境。

您可能会想到这样的事情:

  1. 分配一个类似

    的“代码洞穴”
    PreHookWriteFile:
    LOCK INC [ref_count]
    POP R15
    CALL HookWriteFile
    PostHookWriteFile:
    LOCK DEC [ref_count]
    JMP R15
    
  2. 使用JMP [PreHookWriteFile]

  3. 挂钩WriteFile
  4. 在专用线程中执行释放,该线程取消挂起WriteFile并等待重新计数变为零。然后它解除了代码洞。

  5. 但是有两个问题。首先,我们没有真正存放原始退货地址的地方。我们不能把它放在堆栈上,因为HookWriteFile不希望在那里看到它,我们不能将它存储在任何非易失性寄存器中,因为我们需要在{{在跳回之前,问题是我们没有地方存放东西。

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

    我认为我们试图变得多聪明(使用PostHookWriteFile函数或修改原型)以使钩子函数期望堆栈上的原始返回地址。第二个问题仍然存在 - 您在返回之前减少了引用计数。在您返回之前删除代码是不安全的,但是在您返回之后,您无法通知您的返回,因为您已无法控制。这正是创建FreeLibraryAndExitThread的原因。

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

    但也有好消息。

    如果您真正想要的是监视命名管道通信并且您愿意将自己限制在Windows 8及更新版本中,那么这个解决方案更加强大,记录和支持,占用更少的代码行而不是侵入。 Koby Kahane写了filesystem mini-filter that does exactly that

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