注册表监控 -- NT/2000

在上一篇中我们说在9X下大家可以方便的使用VMM提供的服务Hook_Device_Service来挂上
我们的函数实现注册表的监控,那么在NT下是否可以找到类似的函数来实现相应的功能呢, 很遗憾,
微软公开的文档里并没有此部分的说明. 但方法还是有的, 就实现注册表的监控来说, NT下有两种
方法, 一种是驱动级的, 另一种是应用程序级的.

 在驱动部分实现监控, 通常称为system call hooking,这是一种非公开的技术, 得不到正式
支持, 但微软经常这么干, 大家也都习以为常, 对于这种非公开技术的可靠性并不打折扣, MS PRESS
就经常出版类似技术的书籍供程序员参考.

 据说有关system call hooking的技术在 "undocument windows nt"一书中有些说明, 假如
谁能提供E版, 将不胜感激.

        如同9X, 监控注册表还是拦截几个系统服务:
Begin_Hook_table:
 ZwOpenKey
 ZwQueryKey
 ZwQueryValueKey
 ZwEnumerateValueKey
 ZwEnumerateKey
 ZwClose
 ZwFlushKey
 ZwDeleteKey
 ZwSetValueKey
 ZwCreateKey
 ZwDeleteValueKey
 ZwLoadKey
 ZwUnloadKey
End_Hook_table:

 这几个服务例程在NTOSKRNL.EXE里实现, 可以在NTDDK里找到说明. NTOSKRNL.EXE对这些系统
服务有很好的组织, 类似与DOS下的中断向量表, 几乎所有的函数起始地址组织在一张
KeServiceDescriporTable表中, 模拟一下大概的结构就是:

KeServiceTable_Begin :
 ...
 pZwOpenKey  dd 0
 pZwQueryKey  dd 0
 pZwQueryValueKey dd 0
 pZwEnumerateValueKey dd 0
 pZwEnumerateKey  dd 0
 ...
KeServiceTable_End :

 所有对Zw***(或NT***等)的调用都会跳转到此表所指向的地址, 那么现在就很明显, 只要我们
能在这张表中找到需要接管的函数, 把它所指向的地址转向我们的例程就一切OK了.

 对NTOSKNRL.LIB进行输出, 可以看到一个KeServiceDescriptorTable的指针, 虽然没有公开的
声明, 但就其名字也可联想到上面所说的那张表, 大概可以用C语言描述该指针如下:

typedef struct SERVICE_TABLE {
 PVOID table; file://service/ table pointer
 PVOID reserved; //?? always 0,  猜测是第一个服务顺序号。
 ULONG srv_end_number; file://最/后一个服务顺序号。
 PVOID table_end; //?? end service table pointer = service_table_pointer + srv_end_number * 4;
} *PSERVICE_TABLE;
PSERVICE_TABLE KeServiceDescriptorTable;
 (如前所述, 由于此部分不公开, 除了某些人了解之外, 而象我们, 就只有借助于SOFTICE之类
工具进行破解了, 因此, 所有不能肯定的部分都会有说明, 也希望大家共同探讨.)

 有了这张表的位置, 剩下的就是该如何确定里面的函数的排列方式了. 这一点就好办多了, 我们
有种种办法来刺探里面每个函数的位置, 也有人早已告诉我们, 举例说明:
 ZwOpenKey, 它指向一个16字节的结构, 其第1-4字节就是它在service_table中的顺序号!

 至此, 假设接管函数ZwOpenKey:

在驱动里声明:
typedef struct SERVICE_TABLE {
 PVOID table; 
 PVOID reserved;
 ULONG srv_end_number;
 PVOID table_end;
} *PSERVICE_TABLE;
extern PSERVICE_TABLE KeServiceDescriptorTable;

PULONG *pKeServiceTable;

NTSTATUS (*OldZwOpenKey)( OUT PHANDLE, IN OUT ACCESS_MASK, IN POBJECT_ATTRIBUTES );

NTSTATUS MyZwOpenKey(
  OUT PHANDLE  KeyHandle,
  IN ACCESS_MASK  DesiredAccess,
  IN POBJECT_ATTRIBUTES  ObjectAttributes
  )
{
 ntstatus = OldZwOpenKey( KeyHandle, DesiredAccess,  ObjectAttributes);
 ...
 return ntstatus;
}

BOOL GetKeServiceTable()
{
 pKeServiceTable = KeServiceDescriptorTable->table;
 return TRUE;
}

void hook()
{
 OldZwOpenKey = (PVOID)InterlockedExchange(
 (PLONG)pKeServiceTable[*(ULONG *)((UCHAR *)ZwOpenKey+1))],
 (LONG)MyZwOpenKey);
}

一些解释:
 NT的内存管理想来大家应该很熟, 在IRQL >= DISPATCH_LEVEL时,是不会触发PAGE FAULT的,
因此若此时访问了一个不存在的页会导致系统崩溃,通常的做法是先锁定再操作, 而我之所以没有锁定
KeServiceDescriptorTable->table来进行操作, 是因为它本来就处于核心态, 而且存在于NT里唯一的一个达
4M的内存页里, 但是锁定也有锁定的好处, 对于编商业软件的朋友可能就觉得不锁定心里会不塌实。下面
就是锁定的操作, 但对应也增加了一个释放MDL的函数.
PMDL KeServiceTableMdl;
BOOL GetKeServiceTable()
{
        ULONG tablesize;

       tablesize = KeServiceDescriptorTable->srv_end_number * 4;
        KeServiceTableMdl = MmCreateMdl(0, KeServiceDescriptorTable->table, tablesize);
        MmBuildMdlForNonPagedPool(KeServiceTableMdl);
        pKeServiceTable = MmMapLockedPages(KeServiceTableMdl, 0);
        return TRUE;
}

VOID ReleaseMdl()
{
 MmUnmapLockedPages(pKeServiceTablePointers, KeServiceTableMdl);
 ExFreePool(KeServiceTableMdl);
}

posted @ 2008-06-23 11:50  Max Woods  阅读(528)  评论(0编辑  收藏  举报