6.5 嵌套陷阱

Windows内核为开发者提供了一系列回调机制,这些机制可以被用于监控、拦截某些系统行为。如“对象回调”可以监控进程和线程句柄的打开、关闭以及复制操作;“注册表回调”可以监控系统的所有注册表行为。在本书后面的章节中,会为读者详细介绍内核中的回调机制。

正是由于系统提供了这些机制,所以开发者在调用一个API前,要首先分析清楚这个API内部是否存在回调机制。请阅读下面例子:

上面的代码调用ZwOpenProcess函数打开一个进程,整体逻辑非常简单,但是这里却有一些隐含的信息。

系统提供了“对象回调”机制,该机制可用于监控进程/线程句柄操作,注册“对象回调”使用以下API:

其中CallbackRegistration参数包含了注册回调的基本信息,如回调函数地址、优先级等。一旦注册成功,并且系统发生相应的行为时,系统就会回调CallbackRegistration参数中指定的回调函数。更多的信息,请参阅WDK帮助文档或者本书后面章节。

回看上面的代码,代码在调用ZwOpenProcess时,该函数内部会按优先级顺序调用已经注册了“对象回调”的回调函数。也就是说,虽然是一句简单的ZwOpenProcess函数调用,但实际上内部却执行了第三方的回调函数。

这会带来一些问题,由于回调函数被调用时,与调用ZwOpenProcess函数处于同一个线程,且共用一个内核线程栈,第三方回调函数使用线程栈的情况,对本驱动开发者来说是不可预知的,所以这里存在一个“栈溢出”的可能,即使开发者保证自身函数只使用少量的栈资源,但并不能保证自身使用的栈资源加上第三方回调函数所使用的栈资源会超过栈的大小。

此类问题并不直观,对初学者来说往往容易忽略。下面介绍一些规避技巧。

(1)开发者必须清楚了解代码中所调用的系统API是否会发生嵌套。

(2)对于存在嵌套的系统API,务必保证在调用线程中,尽可能少使用栈空间,如需要使用大量内存,可以通过在POOL上申请后使用。

(3)同理,对于自身执行在过滤驱动或回调函数中的代码,也尽可能少使用栈空间。

(4)对于自身执行在过滤驱动或回调函数中的代码,如果需要调用存在嵌套的系统API,可以考虑把API的调用位置放置在另外一个工作线程中,然后通过线程间通信把系统API调用结果返回给最初线程。

(5)避免在代码中使用递归,如果非要使用递归,请控制好递归的深度。