信 息 过 滤 技 术
姓名:谢伟
学号:99120037
指导老师:陈怡疆
厦门大学计算机系99级
目 录
摘 要 3
Abstract 3
引 言 4
第一章:问题描述 4
第二章:API钩子的总体设计 5
第三章:DLL简介 5
第四章:注入技术(Injecting Techniques) 7
1. 注册表 7
2. Windows提供的系统范围的钩子 8
3.用CreateRemoteThread()函数注入DLL 9
第五章:拦截机制 10
1. NT 内核级钩子 11
2. Win32 用户级钩子 11
第六章:系统简述 13
第七章:设计与实现 14
第八章:总结 17
感谢: 17
参考文献: 17
摘要
钩子技术是Windows编程中常用的高级技术之一,通过钩子我们可以控制应用程序的行为,而不需要获得应用程序的源代码。本文主要介绍了如何建立一个用户级钩子,并把自己定义的DLL插入到目标进程中,通过它来截获Win32 API中的TextOut类函数调用并对其输出的消息进行处理,从而实现不良信息的过滤。
关键字 钩子 DLL插入 TextOut 信息过滤
Abstract
Hooking is one of the advanced techniques in the Windows programming, through which we can easily control the behavior of an application without knowing its source code. This article introduces ways of constructing a user-level hook, and the injecting techniques of DLLs into target processes. By intercepting TextOut like s of Win32 API and processing the messages sent to the screen by these APIs, I implement an instance using for information filtering.
Keywords hooking, DLL injecting, TextOut, Information filtering
引言
我的信息过滤,是通过截获Win32 API调用的hooking技术来实现的。
截获Win32 API调用对于大多数Windows程序开发人员来说都是一个具有挑战性和令人感兴趣的课题。hooking这个术语代表了一种基本的技术手段,通过这种手段可以达到控制某段特定代码执行的目的。hooking技术为我们轻松地改变操作系统和第三方软件的行为提供了一种简单易行的机制,而不用获得他们的源代码。
许多现代操作系统都很注意利用已有的窗口应用程序的能力,为此引入了侦察技术(spying techniques)。hooking技术的主要目的不仅仅是提供一些高级的功能,更是为了能够向目标程序中注入用户提供的代码。
与那些相对早期的老式操作系统(例如DOS和Windows 3.xx)不同,目前的Windows操作系统(Windows 9x以及Windows NT/2K)已经提供了复杂精密的机制用于为每个进程分配和维护独立的地址空间。这种设计结构实际上提供了内存的保护,因此一个应用程序不可能侵入其它应用程序的地址空间,当然也不能破坏操作系统。但同时也给程序开发中实现钩子(hook)带来了难题。
对应用程序进行监察的作用
1. 监控API函数
监控API函数的调用为开发人员提供了极大的帮助,使他们可以跟踪API调用过程中的某个特定的不可见的动作,并有利于发现一些没有注意到的错误。例如,它可以监视与API调用的相关的内存空间以检查内存泄露情况。
2. 调试
除了传统的调试方法,API钩子也是一种广泛使用的调试技术。引入API钩子技术可以考察各组件的实现以及组件之间的相互关系。通过截获API也可以获取可执行的二进制代码中的某些信息。
3. 深入了解操作系统
开发人员经常需要了解操作系统内部的实现,以进行程序调试。特别是在某些API没有提供足够的说明文档的情况下,钩子技术可以帮助程序员理解它们的内部结构。
4. 扩展原始的功能
通过向窗口应用程序中嵌入用户代码可以方便的扩展程序模块已有的功能。比方说,很多第三方软件不能满足用户对于信息安全级别的要求,程序员可以通过钩子技术监控应用程序,并在原有的功能中加入相关的预处理和对运行结果的处理。这样,不需要修改和重新编译源代码就可以改变应用程序的行为。
第一章:问题描述
平时电脑上各种程序,如:Notpad,IE等在显示文本信息时有一些反动的或者色情的内容时我们会想要将它们过滤掉不显示出来。这就是这个程序想要达到的目的。
第二章:API钩子的总体设计
通常来说一个hook系统包括两个部分 -- 一个hook服务程序和一个驱动程序。hook服务程序用于将驱动程序在合适的时刻注入目标进程中,同时也管理驱动程序并从驱动程序获得其运行的信息。驱动程序则是执行实际的截获的模块。
这种设计方案不一定包含了所有可行的实现方法,它定义了一个钩子系统的总体框架。实际的设计中需要从以下几个方面考虑:
・钩子系统针对何种应用程序
・如何向目标进程中注入用户代码(通常以动态链接库(DLL)方式存在)
・选用何种拦截机制
第三章:DLL简介
自从Microsoft公司推出第一个版本的Windows操作系统以来,动态链接库(DLL)一直是这个操作系统的基础。Windows API 中的所有函数都包含在DLL中。3个最重要的DLL是:
Kernel32.dll,它包含用于管理内存、进程和线程的各个函数
User32.dll,它包含用于执行用户界面任务(如窗口的创建和消息的传送)的各个函数;GDI32.dll,它包含用于画图和显示文本的各个函数。
创建DLL常常比创建应用程序更容易,因为DLL往往包含一组应用程序可以使用的自主函数。在DLL中通常没有用来处理消息循环或创建窗口的支持代码。DLL只是一组源代码模块,每个模块包含了应用程序(可执行文件)或另一个DLL将要调用的一组函数。当所有源代码文件编译后,它们就像应用程序的可执行文件那样被链接程序所链接。但是,对于一个DLL来说,必须设定该连链程序的DLL开关。这个开关使得链接程序能够向产生的DLL文件映像发出稍有不同的信息,这样,操作系统加载程序就能将该文件映像视为一个DLL而不是应用程序。
在应用程序(或另一个DLL)能够调用DLL中的函数之前,DLL文件映像必须被映射到调用进程的地址空间中。若要进行这项操作,可以使用两种方法中的一种,即加载时的隐含链接或运行期的显式链接。
当创建DLL模块时,首先应该建立一个头文件,该文件包含了你想要输出的变量(类型和名字)和函数(原型和名字)。头文件还必须定义用于输出函数和变量的任何符号和数据结构。你的DLL的所有源代码模块都应该包含这个头文件。另外,必须分配该头文件,以便它能够包含在可能输入这些函数或变量的任何源代码中。拥有单个头文件,供DLL创建程序和可执行模块的创建程序使用,就可以大大简化维护工作。但要注意,尽量避免输出变量或类,否则可能会使代码难以维护。
无论何时,进程中的线程都可以决定将一个DLL映射到进程的地址空间,方法是调用下面两个函数中的一个:
HINSTANCE LoadLibrary(PCTSTR pszDLLPathName);
HINSTANCE LoadLibraryEx(
PCTSTR pszDLLPathName,
HANDLE hFile;
DWORD dwFlags);
这两个函数均用于找出用户系统上的文件映像(使用上一章中介绍的搜索算法),并设法将DLL的文件映像映射到调用进程的地址空间中。两个函数返回的HINSTANCE值用于标识文件映像映射到的虚拟内存地址。如果DLL不能被映射到进程的地址空间,则返回NULL。
一旦DLL模块被显式加载,线程就必须获取它要引用的符号的地址,方法是调用下面的函数:
FARPROC GetProcAddress(
HINSTANCE hinstDll,
PCSTR pszSymblolName);
一个DLL可以拥有单个进入点函数DllMain。系统在不同的时间调用这个进入点函数,这个问题将在下面加以介绍。这些调用可以用来提供一些信息,通常用于供DLL进行每个进程或线程的初始化和清除操作。如果你的DLL不需要这些通知信息,就不必在DLL源代码中实现这个函数。
当DLL被初次映射到进程的地址空间中时,系统将调用该DLL的DllMain函数,给它传递参数fdwReason的值DLL_PROCESS_ATTACH。只有当DLL的文件映像初次被映射时,才会出现这种情况。如果线程在后来为已经映射到进程的地址空间中的DLL调用LoadLibraryEx函数,那么操作系统只是递增DLL的使用计数,它并不再次用DLL_PROCESS_ATTACH的值来调用DLL的DllMain函数。
当处理DLL_PROCESS_ATTACH时,DLL应该执行DLL中的函数要求的任何与进程相关的初始化。例如, DLL可能包含需要使用它们自己的堆栈(在进程的地址空间中创建)的函数。通过在处理DLL_PROCESS_ATTACH通知时调用HeapCreate函数,该DLL的DllMain函数就能够创建这个堆栈。已经创建的堆栈的句柄可以保存在DLL函数有权访问的一个全局变量中。
下面的图反映了进程调用LoadLibrary时系统执行的操作步骤:
第四章:注入技术(Injecting Techniques)
下面是几种向目标进程中注入用户代码(以下都用DLL说明)的方法。
1. 注册表
如果要向与USER32.DLL链接的进程中注入用户DLL,只需要将DLL的名字加入注册表的这个键中:
HKEY_LOCAL_MACHINE\Software\Microsoft\WindowsNT\CuurrentVersion\
Windows\AppInit_DLLs
即可。这个值可以是单个DLL的名字,也可以是由逗号或空格分隔开的一组DLL的名字。这个键下面的所有DLL都会被当前会话中的应用程序所加载(MSDN documentagion [7]),实际的加载是在USER32初始化的时候进行的。USER32读取该键中涉及到的DLL,然后在DllMain的代码中为他们调用LoadLibrary()。这种方法简单易行,但有它的限制条件。首先,它仅适用于与USER32.DLL相关的程序;其次,只有Windows NT和Windows 2000支持这种方式。另外,尽管这是一种安全无害的方式,但也有一些不足之处:
・为了激活或解除DLL的注入,必须重新启动Windows
・由于这是操作系统提供的功能,因此用户无法控制DLL的注入。也就是说,所有的
GUI应用程序都会加载用户定义的DLL,不管是否需要。这就增加了系统的负担。
2. Windows提供的系统范围的钩子
Windows Hooks是一种比较流行的钩子技术。在MSDN中,钩子被描述成操作系统消息处理机制中的一个陷入。应用程序可以通过一段过滤程序监视进程和操作系统之间的消息传递,在消息到达目标窗口进程之前对其进行处理。
钩子程序通常在DLL中实现。钩子代码运行在每一个被钩的进程的地址空间中。安装钩子需要调用SetWindowsHookEx(),操作系统把DLL映射到每一个目标进程的地址空间。因此DLL中的全局变量将被进行预处理,无法被加载钩子的进程共享,包含共享数据的变量必须放在一个共享的数据段中。下面的图中,钩子被Hook Server注入到两个应用程序的地址空间中。
一旦注入成功,SetWindowsHookEx()会返回这个钩子的句柄,用于hook代码的最后调用CallNextHookEx()时使用。
//---------------------------------------------------------------------------
// GetMsgProc
//
// Filter for the WH_GETMESSAGE - it's just a dummy
//---------------------------------------------------------------------------
LRESULT CALLBACK GetMsgProc(
int code, // hook code
WPARAM wParam, // removal option
LPARAM lParam // message
)
{
// We must pass the all messages on to CallNextHookEx.
return ::CallNextHookEx(sg_hGetMsgHook, code, wParam, lParam);
}
代码中sg_hGetMsgHook由SetWindowsHookEx()获得并由CallNextHookEx()使用,因此它的值必须被所有被钩的进程以及hook服务程序共享。为此,我们必须将其存储在共享的数据段中。
下面的例子引入了#pragma data_seg()。需要说明的是,共享数据段中的数据必须初始化,否则它们会被放在默认的数据段中,而#pragma data_seg()就变得无效了。
//---------------------------------------------------------------------------
// Shared by all processes variables
//---------------------------------------------------------------------------
#pragma data_seg(".HKT")
HHOOK sg_hGetMsgHook = NULL;
BOOL sg_bHookInstalled = FALSE;
// We get this from the application who calls SetWindowsHookEx()'s wrapper
HWND sg_hwndServer = NULL;
#pragma data_seg()
当钩子DLL成功加载到目标进程的地址空间之后,想要卸载它由两种情况,要么是hook服务程序调用UnhookWindowsHookEx(),要么是应用程序被关闭。当调用UnhookWindowsHookEx()时,操作系统会找到被hook的进程,将DLL的锁数目减1,这样,当锁数目变为0的时候,DLL就会自动从进程的地址空间中移除。
这种方法的优点是:
・Windows 9x和Windows NT/2K均支持这种机制
・与使用注册表方式不同,当不再钩子DLL时,可以调用UnhookWindowsHookEx()卸
载钩子
但它也有不足之处:
・它会大大降低系统的性能,因为系统要增加大量对消息处理的过程
・不容易调试。而且如果打开了多个VC窗口,调试过程会变得更加复杂
・它可能会影响整个系统的正常运行,有时不得不重新启动以恢复系统运作
3.用CreateRemoteThread()函数注入DLL
这种方法仅适用于Windows NT/2K。在Windows 9x中调用这个函数,不会出错,但返回值永远是NULL。CreateRemoteThread()函数用于创建一个运行在其他进程虚拟空间中的线程。
通过remote threads来注入DLL是Jeffrey Ritcher提出的方法。它的基本原理很简单,但却是一种相当不错的方法。我们知道,任何进程都可以调用LoadLibrary()来加载DLL,现在的问题就是,在没有访问权限的情况下,如何使一个外部进程按照我们的意愿调用LoadLibrary()?这里有一个诀窍,先看一下CreateRemoteThread()的参数。
HANDLE CreateRemoteThread(
HANDLE hProcess,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
参数hProcess指定了目标进程,而lpStartAddress指定了所创建线程需要运行的函数地址,lpParameter指定了这个函数的参数。这个函数被定义成ThreadProc()
DWORD WINAPI ThreadProc(
LPVOID lpParameter
);
现在再看看LoadLibrary的定义,
HMODULE LoadLibrary(
LPCTSTR lpFileName
);
可以看出,他们具有"相同"的模式。这样我们就可以用LoadLibrary()作为这个运行的函数,在远程线程创建的时候被调用,它的参数由lpParameter提供,即我们自己定义的钩子DLL。LoadLibrary()函数的入口地址可以用GetProcAddress()获得。这样,当远程进程运行的时候,它就会调用LoadLibrary()为所在进程加载钩子DLL。
还有一点需要注意。当调用CreateRemoteThread()时,它首先要调用OpenProcess()函数打开目标进程。在这里就有一个访问权限问题。那些操作系统的进程,都有着严格的权限限制,直接打开这些进程可能会导致失败。也就是说,应用程序必须要有足够的权限才可以改变操作系统的行为。为了得到访问系统进程资源的权限,必须获得调试特权(debug privilege)。
第五章:拦截机制
上面叙述的DLL注入技术是侦测系统中的关键部分,提供了控制进程的手段。然而如果想要进一步控制目标进程中API函数的行为,注入技术是不够的。在接下来的这一部分,将要介绍几种可行的钩子,以及他们的优缺点。
根据钩子应用的级别,可以分为两种情况--内核级和用户级。理解这两种模式首先要知道Win32子系统API和本地API的关系。下面的图展示了Windows 2000下不同级别的钩子以及各模块之间的关系。
内核级与用户级钩子实现方法的最大不同之处在于,内核级钩子是通过一个内核态的驱动程序封装起来的,而用户级钩子通常是引入一个用户定义的DLL。
1. NT 内核级钩子
在NT的内核态系统服务中实现钩子有很多种方法。比较常用的一种拦截机制最初是由Mark Russinovich和Bryce Cogswell在他们的文章《Windows NT System-Call Hooking》中提出来的。其基本思想是在用户态下面设置拦截以监视NT的系统调用。它在用户态的程序即将接受操作系统服务之前的那一点设置了钩子,因此这是一种很灵活的机制。
2. Win32 用户级钩子
a. 代理DLL
截获API最简单的办法就是用一个同名的DLL来替换原有的DLL,当然,新的DLL要负责导出原DLL中的所有符号。这就需要在DLL的导出段中添加函数的转移调用,它可以将一个函数的调用委派到另外一个DLL中的函数。例如
#pragma comment(linker, "/export:DoSomething=DllImpl.ActuallyDoSomething")
如果采用这种技术,新的DLL的一定要兼容原有的DLL。
b. 修改IAT(Import Address Table)表
这种方法最初由Matt Pietrek提出。应该说,这是一种很强大,并且简单易行的方案,在Windows 9x和Windows NT/2K环境下都可以采用。这种技术依赖于Windows PE(Portable Executable)这种良好的文件结构。下面先大体介绍一下PE文件结构,它是COFF(Common Object File Format)文件结构的扩展。
PE是一种有结构的文件,里面的数据段和代码段都与运行时虚拟内存中的表示相一致。它由一些逻辑段组成,每一个段含有特定类型的数据和地址,用于操作系统加载时使用。运用钩子技术,我们最关心的是 .idata段,这里包含了导入地址表的信息,因为它对于钩子的实现是至关重要的。
当一个程序被运行的时候,它所连接的DLL也被相应的加载到内存中。由于事先无法得知DLL将被加载到的地址,所以也就无法确定每一个导出函数的实际入口地址。遍历整个代码的映像以确定每一个函数的地址会增加很多额外的处理时间,从而拖垮系统运行的效率。这里的关键是,每一次对某个导入函数的调用实际上都要转移到相同的地址去。因此,在IAT表中,每一次对导入函数的调用都是一个间接调用,它使用JMP指令转移到真正的代码地址。使用这种方法,程序加载时就无需搜索整个文件的镜像。下面是一个简单的Win32程序的PE结构截图,可以看到表中引入了GDI32.DLL中的TextOutA()和GetStockObject()函数。
利用IAT表的拦截系统必须查出记录导入函数地址的位置,然后以用户提供的函数的地址替换它。这里最重要的一点是,新的函数必须与原来的函数有完全一样的属性。关键的步骤包括:
・在IAT中查找导入段(imported section)
・查找导出目标函数的IMAGE_IMPORT_DEOR段
・找到记录函数原始地址的IMAGE_THUNK_DATA段
・用自定义的函数替换原函数
替换了函数的地址,就可以保证每一次调用都会被定向到拦截函数处。
IAT中的.idata域不一定是可写的区域,我们必须确定.idata是否可写。这可以由VirtualProtect()函数实现。
第六章:系统简述
上面简单介绍了钩子系统的一些概念,下面对这个特定钩子的设计作一下介绍。
・提供了一个用户级钩子可以钩任何一个用名字导入的Win32函数
・引入了基于修改IAT的拦截机制
・采用面向对象的可重用和可扩展的分层结构
・在驱动程序和服务程序之间的数据传输提供了可靠的通讯机制
・提供了自定义的mytextout函数
・建立日志文件记录钩子的运行情况
・使用与x86体系上的Windows9x,Windows NT/2K,Windows Me系统
第七章:设计与实现
这一部分主要介绍整个设计方案的主要组建以及他们的相互关系。整个方案由下面几个模块组成:
" HookSrv.exe - 服务程序
" HookTool .DLL - 钩子DLL
" HookTool.ini - 配置文件
HookSrv是一个简单的服务控制程序。它的主要任务是加载HookTool.dll,激活监测引擎。然后调用InstallHook()函数安装钩子。
HookTool.dll是钩子驱动程序,也是整个系统的核心。它实现了拦截机制,并且提供了定义的XXXX函数。
下面的图描述了HookTool中各个类之间的关系
为每个类分配任务是很重要的一项工作。这里的每一个类都封装了一些特定的函数,同时也代表了一个完整的逻辑整体。
CModuleScope是系统的主要部分。它的构造函数接受三个指针作为参数,分别指向共享数据段的三个数据。这样,这些数据就可以很容易的保存在类中,保持了封装的原则。
当一个应用程序加载HookTool.dll时,就会有一个CModuleScope的实例被创建,并且收到DLL_PROCESS_ATTACH通知。CModuleScope的构造函数中很重要的一部分是构造一个注射器。
这之后,将会调用ManageModuleEnlistment()方法。下面是它简单的实现过程:
// Called on DLL_PROCESS_ATTACH DLL notification
BOOL CModuleScope::ManageModuleEnlistment()
{
BOOL bResult = FALSE;
// Check if it is the hook server
if (FALSE == *m_pbHookInstalled)
{
// Set the flag, thus we will know that the server has been installed
*m_pbHookInstalled = TRUE;
// and return success error code
bResult = TRUE;
}
// and any other process should be examined whether it should be
// hooked up by the DLL
else
{
bResult = m_pInjector->IsProcessForHooking(m_szProcessName);
if (bResult)
InitializeHookManagement();
}
return bResult;
}
ManageModuleEnlistment()会检查调用是否由HookSrv发出。
接下来HookSrv会调用InstallHook()来激活钩子。这个调用实际上被转给InstallHookMethod方法,它的任务是迫使目标进程加载或者卸载HookTool.dll
// Activate/Deactivate hooking
engine BOOL CModuleScope::InstallHookMethod(BOOL bActivate, HWND hWndServer)
{
BOOL bResult;
if (bActivate)
{
*m_phwndServer = hWndServer;
bResult = m_pInjector->InjectModuleIntoAllProcesses();
}
else
{
m_pInjector->EjectModuleFromAllProcesses();
*m_phwndServer = NULL;
bResult = TRUE;
}
return bResult;
}
HookTool提供了两种注入机制--Windows Hook和CreateRemoteThread()方法。系统定义了一个虚类CInjector,将会指向实际使用的注入机制。CWinHookInjector和CRemThreadInjector都是从CInjector继承而来,然而它们定义了InjectModuleIntoAllProcesses()和EjectModuleFromAllProcesses()的两组接口不同的实现。
CWinHookInjector实现了Windows Hook注入机制。
// Inject the DLL into all running processes
BOOL CWinHookInjector::InjectModuleIntoAllProcesses()
{
*sm_pHook = ::SetWindowsHookEx(
WH_GETMESSAGE,
(HOOKPROC)(GetMsgProc),
ModuleFromAddress(GetMsgProc),
0
);
return (NULL != *sm_pHook);
}
它注册了WH_GETMESSAGE钩子。Windows每处理一个消息都回调用指定的回调函数。我们要自己定义一个GetMsgProc()回调函数,但我们不需要这个回调函数作任何工作,因为我们想要的仅仅是通过它将DLL注射到目标进程中。
调用了SetWindowsHookEx之后,操作系统会检查包含GetMsgProc函数的DLL时候已经被所有的GUI程序加载,如果还没有,Windows会强迫GUI程序加载这个DLL。
另一种完全不同的实现方法借助于CRemThreadInjector类,给予远程线程的注入机制。CRemThreadInjector提供了一种方法,使其在进程创建和终止时收到通知。CNtInjectorThread对象负责从内核驱动程序获取这些通知。每当一个新的进程创建之后,CNtInjectorThread::OnCreateProcess方法被调用,相应的,每当进程终止的时候,CNtInjectorThread::OnTerminateProcess方法被调用。与Windows Hook不同的是,每次新的进程创建的时候,需要手工进行DLL的注入。
当DLL成功注入后,DllMain()中就会调用ManageModuleEnlistment()。它会通过CModuleScope的成员m_pbHookInstalled来检查共享变量sg_bHookInstalled。服务程序已经将sg_bHookInstalled初始化为TRUE,系统会检查这个应用程序是否需要被Hook,如果是,那么就对这个进程激活监测引擎。
我们要为每一个被截获的API调用提供一个自定义的实现,而不是简单地将IAT标中的指针修改为一个通用的拦截函数。需要注意的是,每一个自定义的替代函数必须提供根原来的函数完全一样的接口,否则将会出现堆栈的错误。
获取进程创建的通知是钩子技术中的一个老问题。Win32 API提供了一系列的方法用于列举当前正在运行的进程,然而却没有提供获取新进程创建通知的方法。这一点在Windows NT/2K的DDK中得到了解决。PsSetCreateProcessNotifyRoutine()允许用户注册一个回调函数,每当一个进程创建、终止的时候,将会调用回调函数。通过实现一个NT内核驱动程序和一个用户控制程序就可以借助这种方法实现对进程状态的跟踪。驱动程序的任务就是检测进程运行中的事件。
第八章:总结
从实验结果可知,通过钩子技术可以实现对Win32 API的截获,并将截获的信息进行判断是否为不良信息后作相应处理。在实验过程中,我学习到钩子技术这一相当有用的技术,并且大大加强了对动态链接库的了解。
感谢:
整个实验过程中,陈怡疆老师的帮助与指导起着很大的作用。从整个思想的提出,资料的提供到遇到问题时帮助解决。在此我表示衷心的感谢。
参考文献:
[1]WINDOWS 95高级程序设计 同济大学出版社
[2]深入WINDOWS编程 清华大学出版社
[3]Windows应用程序设计--原理,方法和技巧 电子工业出版社
[4] "Windows 95 System Programming Secrets", Matt Pietrek
[5] "Programming Application for MS Windows" , Jeffrey Richter
[6] "Windows NT System-Call Hooking" , Mark Russinovich and Bryce Cogswell, Dr.Dobb's Journal January 1997
[7] "Debugging applications" , John Robbins
[8] "Undocumented Windows 2000 Secrets" , Sven Schreiber
[9] "Peering Inside the PE: A Tour of the Win32 Portable Executable File Format" by Matt Pietrek, March 1994
[10] MSDN Knowledge base Q197571
[11] PEview Version 0.67 , Wayne J. Radburn
[12] "Load Your 32-bit DLL into Another Process's Address Space Using INJLIB" MSJ May 1994
[13] "Programming Windows Security" , Keith Brown