#define SPY_CALLS 0x00000100 // max api call nesting level
#define SDT_SYMBOLS_NT4 0xD3
#define SDT_SYMBOLS_NT5 0xF8
#define SDT_SYMBOLS_MAX SDT_SYMBOLS_NT5
// -----------------------------------------------------------------
typedef struct _SPY_HOOK_ENTRY
{
NTPROC Handler;
PBYTE pbFormat;
}
SPY_HOOK_ENTRY, *PSPY_HOOK_ENTRY, **PPSPY_HOOK_ENTRY;
#define SPY_HOOK_ENTRY_ sizeof (SPY_HOOK_ENTRY)
// -----------------------------------------------------------------
typedef struct _SPY_CALL
{
BOOL fInUse; // set if used entry
HANDLE hThread; // id of calling thread
PSPY_HOOK_ENTRY pshe; // associated hook entry
PVOID pCaller; // caller's return address
DWORD dParameters; // number of parameters
DWORD adParameters [1+256]; // result and parameters
}
SPY_CALL, *PSPY_CALL, **PPSPY_CALL;
#define SPY_CALL_ sizeof (SPY_CALL)
// -----------------------------------------------------------------
SPY_HOOK_ENTRY aSpyHooks [SDT_SYMBOLS_MAX];
// -----------------------------------------------------------------
// The SpyHook macro defines a hook entry point in inline assembly
// language. The common entry point SpyHook2 is entered by a call
// instruction, allowing the hook to be identified by its return
// address on the stack. The call is executed through a register to
// remove any degrees of freedom from the encoding of the call.
#define SpyHook \
__asm push eax \
__asm mov eax, offset SpyHook2 \
__asm call eax
// -----------------------------------------------------------------
// The SpyHookInitializeEx() function initializes the aSpyHooks[]
// array with the hook entry points and format strings. It also
// hosts the hook entry points and the hook dispatcher.
// -----------------------------------------------------------------
// The SpyHookInitializeEx() function initializes the aSpyHooks[]
// array with the hook entry points and format strings. It also
// hosts the hook entry points and the hook dispatcher.
void SpyHookInitializeEx (PPBYTE ppbSymbols,
PPBYTE ppbFormats)
{
DWORD dHooks1, dHooks2, i, j, n;
__asm
{
jmp SpyHook9
ALIGN 8
SpyHook1: ; start of hook entry point section
}
// the number of entry points defined in this section
// must be equal to SDT_SYMBOLS_MAX (i.e. 0xF8)
SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //08
SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //10
SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //18
SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //20
SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //28
SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //30
SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //38
SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //40
SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //48
SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //50
SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //58
SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //60
SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //68
SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //70
SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //78
SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //80
SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //88
SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //90
SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //98
SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //A0
SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //A8
SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //B0
SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //B8
SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //C0
SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //C8
SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //D0
SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //D8
SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //E0
SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //E8
SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //F0
SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook SpyHook //F8
__asm
{
SpyHook2: ; end of hook entry point section
pop eax ; get stub return address
pushfd
push ebx
push ecx
push edx
push ebp
push esi
push edi
sub eax, offset SpyHook1 ; compute entry point index
mov ecx, SDT_SYMBOLS_MAX
mul ecx
mov ecx, offset SpyHook2
sub ecx, offset SpyHook1
div ecx
dec eax
mov ecx, gfSpyHookPause ; test pause flag
add ecx, -1
sbb ecx, ecx
not ecx
lea edx, [aSpyHooks + eax * SIZE SPY_HOOK_ENTRY]
test ecx, [edx.pbFormat] ; format string == NULL?
jz SpyHook5
push eax
push edx
call PsGetCurrentThreadId ; get thread id
mov ebx, eax
pop edx
pop eax
cmp ebx, ghSpyHookThread ; ignore hook installer
jz SpyHook5
mov edi, gpDeviceContext
lea edi, [edi.SpyCalls] ; get call context array
mov esi, SPY_CALLS ; get number of entries
SpyHook3:
mov ecx, 1 ; set in-use flag
xchg ecx, [edi.fInUse]
jecxz SpyHook4 ; unused entry found
add edi, SIZE SPY_CALL ; try next entry
dec esi
jnz SpyHook3
mov edi, gpDeviceContext
inc [edi.dMisses] ; count misses
jmp SpyHook5 ; array overflow
SpyHook4:
mov esi, gpDeviceContext
inc [esi.dLevel] ; set nesting level
mov [edi.hThread], ebx ; save thread id
mov [edi.pshe], edx ; save PSPY_HOOK_ENTRY
mov ecx, offset SpyHook6 ; set new return address
xchg ecx, [esp+20h]
mov [edi.pCaller], ecx ; save old return address
mov ecx, KeServiceDescriptorTable
mov ecx, [ecx].ntoskrnl.ArgumentTable
movzx ecx, byte ptr [ecx+eax] ; get argument stack size
shr ecx, 2
inc ecx ; add 1 for result slot
mov [edi.dParameters], ecx ; save number of parameters
lea edi, [edi.adParameters]
xor eax, eax ; initialize result slot
stosd
dec ecx
jz SpyHook5 ; no arguments
lea esi, [esp+24h] ; save argument stack
rep movsd
SpyHook5:
mov eax, [edx.Handler] ; get original handler
pop edi
pop esi
pop ebp
pop edx
pop ecx
pop ebx
popfd
xchg eax, [esp] ; restore eax and...
ret ; ...jump to handler
SpyHook6:
push eax
pushfd
push ebx
push ecx
push edx
push ebp
push esi
push edi
push eax
call PsGetCurrentThreadId ; get thread id
mov ebx, eax
pop eax
mov edi, gpDeviceContext
lea edi, [edi.SpyCalls] ; get call context array
mov esi, SPY_CALLS ; get number of entries
SpyHook7:
cmp ebx, [edi.hThread] ; find matching thread id
jz SpyHook8
add edi, SIZE SPY_CALL ; try next entry
dec esi
jnz SpyHook7
push ebx ; entry not found ?!?
call KeBugCheck
SpyHook8:
push edi ; save SPY_CALL pointer
mov [edi.adParameters], eax ; store NTSTATUS
push edi
call SpyHookProtocol
pop edi ; restore SPY_CALL pointer
mov eax, [edi.pCaller]
mov [edi.hThread], 0 ; clear thread id
mov esi, gpDeviceContext
dec [esi.dLevel] ; reset nesting level
dec [edi.fInUse] ; clear in-use flag
pop edi
pop esi
pop ebp
pop edx
pop ecx
pop ebx
popfd
xchg eax, [esp] ; restore eax and...
ret ; ...return to caller
SpyHook9:
mov dHooks1, offset SpyHook1
mov dHooks2, offset SpyHook2
}
n = (dHooks2 - dHooks1) / SDT_SYMBOLS_MAX;
for (i = j = 0; i < SDT_SYMBOLS_MAX; i++, dHooks1 += n)
{
if ((ppbSymbols != NULL) && (ppbFormats != NULL) &&
(ppbSymbols [j] != NULL))
{
aSpyHooks [i].Handler = (NTPROC) dHooks1;
aSpyHooks [i].pbFormat =
SpySearchFormat (ppbSymbols [j++], ppbFormats);
}
else
{
aSpyHooks [i].Handler = NULL;
aSpyHooks [i].pbFormat = NULL;
}
}
return;
}
列表 5-3. Hook Dispatcher 的实现方式
SpyHook 宏实际是什么呢?在 SpyHookInitializeEx() 函数中,这个宏被重复了多大 248 ( 0xF8 )次,这正好是 windows 2000 Native API 函数的数目。在 列表 5-3 的顶部,这个数目被定义为 SDT_SYMBOLS_MAX 常量,该宏可以使 SDT_SYMBOLS_NT4 或 SDT_SYMBOLS_NT5 。因为我打算支持 Windows NT 4.0 。回到 SpyHook 宏上来:该宏调用的汇编语句在 列表 5-4 中给出了。每个 SpyHook 都产生同样的三行代码:
1. 第一行,将当前 EAX 寄存器的内容保存到堆栈中。
2. 第二行,将 SpyHook2 的线性地址保存到 EAX 中。
3. 第三行,调用 EAX 中的地址(即: call eax )。
你可能会惊讶:当这个 CALL 返回时会发生什么。接下来的一组 SpyHook 代码会被调用吗?不 ---- 这个 CALL 并不支持返回,因为在到达 SpyHook2 之后,这个 CALL 的返回地址就会被立即从堆栈中移出, 列表 5-4 最后的 POP EAX 指令可以证明这一点。这种看上去毫无疑义的代码在古老的汇编程序设计时代曾被广泛的讨论的一种技巧,就像今天我们讨论面向对象的程序设计一样。当 ASM 老大级人物需要构建一个数组,而此数组的每一项都有类似的进入点,但却需要被分派到独立的函数时,就会采用这种技巧。对所有进入点使用几乎相同的代码可以保证它们之间有相等的间隔,因此客户端就可以很容易的通过 CALL 指令的返回地址计算出进入点的在数组中的索引值,数组的基地址和大小以及数组中共有多少项