(一) DLL编写

在准备学习HOOK技术之前,我上网查了一些资料,发现学习HOOK技术大概需要以下前置知识.

1.一门编程语言(废话),网上大部分HOOK都是基于C/C++语言编写.随大流肯定是没错的.好在我懂一点C/C++,会使用VC6.0.所以这点应该不是问题.

2.熟悉一些常见的WINDOWS API.(好在我也接触过,不是很熟悉,但至少一些奇怪的参数类型我也看得懂)

 

所以我今天从DLL学起.

 

 

要找就找专业的教材,以下内容完全摘自WINDOWS程序设计第五版

 

 

动态链接库(也称为DLL)是Microsoft Windows最重要的组成要素之一。大多数与Windows相关的磁盘文件如果不是程序模块,就是动态链接程序。迄今为止,我们都是在开发Windows应用程序;现在是尝试编写动态链接库的时候了。许多您已经学会的编写应用程序的规则同样适用于编写这些动态链接库模块,但也有一些重要的不同。

 

动态链接库的基本知识

正如前面所看到的,Windows应用程序是一个可执行文件,它通常建立一个或几个窗口,并使用消息循环接收使用者输入。通常,动态链接库并不能直接执行,也不接收消息。它们是一些独立的文件,其中包含能被程序或其它DLL呼叫来完成一定作业的函数。只有在其它模块呼叫动态链接库中的函数时,它才发挥作用。

 

所谓「动态链接」,是指Windows把一个模块中的函数呼叫连结到动态链接库模块中的实际函数上的程序。在程序开发中,您将各种目标模块(.OBJ)、执行时期链接库(.LIB)文件,以及经常是已编译的资源(.RES)文件连结在一起,以便建立Windows的.EXE文件,这时的连结是「静态连结」。动态链接与此不同,它发生在执行时期。

KERNEL32.DLL、USER32.DLL和GDI32.DLL、各种驱动程序文件如KEYBOARD.DRV、SYSTEM.DRV和MOUSE.DRV和视讯及打印机驱动程序都是动态链接库。这些动态链接库能被所有Windows应用程序使用。

 

有些动态链接库(如字体文件等)被称为「纯资源」。它们只包含数据(通常是资源的形式)而不包含程序代码。由此可见,动态链接库的目的之一就是提供能被许多不同的应用程序所使用的函数和资源。在一般的操作系统中,只有操作系统本身才包含其它应用程序能够呼叫来完成某一作业的例程。在Windows中,一个模块呼叫另一个模块函数的程序被推广了。结果使得编写一个动态链接库,也就是在扩充Windows。当然,也可认为动态链接库(包括构成Windows的那些动态链接库例程)是对使用者程序的扩充。

 

尽管一个动态链接库模块可能有其它扩展名(如.EXE或.FON),但标准扩展名是.DLL。只有带.DLL扩展名的动态链接库才能被Windows自动加载。如果文件有其它扩展名,则程序必须另外使用LoadLibrary或者LoadLibraryEx函数加载该模块。

 

 

您通常会发现,动态链接库在大型应用程序中最有意义。例如,假设要为Windows编写一个由几个不同的程序组成的大型财务软件包,就会发现这些应用程序会使用许多共同的例程。可以把这些公共例程放入一个一般性的目的码链接库(带.LIB扩展名)中,并在使用LINK静态连结时把它们加入各程序模块中。但这种方法是很浪费的,因为软件包中的每个程序都包含与公共例程相同的程序代码。而且,如果修改了链接库中的某个例程,就要重新连结使用此例程的所有程序。然而,如果把这些公共例程放到称为ACCOUNT.DLL的动态链接库中,就可解决这两个问题。只有动态链接库模块才包含所有程序都要用到的例程。这样能为储存文件节省磁盘空间,并且在同时执行多个应用程序时节省内存,而且,可以修改动态链接库模块而不用重新连结各个程序。

 

动态链接库实际上是可以独立存在的。例如,假设您编写了一系列3D绘图例程,并把它们放入名为GDI3.DLL的DLL中。如果其它软件开发者对此链接库很感兴趣,您就可以授权他们将其加入他们的图形程序中。使用多个这样的图形程序的使用者只需要一个GDI3.DLL文件。

 

 

链接库:一词多义(我认为这段可以不用看)

动态链接库有着令人困惑的印象,部分原因是由于「链接库」这个词被放在几种不同的用语之后。除了动态链接库之外,我们也用它来称呼「目的码链接库」或「引用链接库」。

目的码链接库是带.LIB扩展名的文件。在使用连结程序进行静态连结时,它的程序代码就会加到程序的.EXE文件中。例如,在Microsoft Visual C++中,连同程序连结的一般C执行目的码链接库被称为LIBC.LIB。

引用链接库是目的码链接库文件的一种特殊形式。像目的码链接库一样,引用链接库有.LIB扩展名,并且被连结器用来确定程序代码中的函数呼叫来源。但引用链接库不含程序代码,而是为连结程序提供信息,以便在.EXE文件中建立动态链接时要用到的复位位表。包含在Microsoft编译器中的KERNEL32.LIB、USER32.LIB和GDI32.LIB文件是Windows函数的引用链接库。如果一个程序呼叫Rectangle函数,Rectangle将告诉LINK,该函数在GDI32.DLL动态链接库中。该信息被记录在.EXE文件中,使得程序执行时,Windows能够和GDI32.DLL动态链接库进行动态连结。

 

目的码链接库和引用链接库只用在程序开发期间使用,而动态链接库在执行期间使用。当一个使用动态链接库的程序执行时,该动态链接库必须在磁盘上。当Windows要执行一个使用了动态链接库的程序而需要加载该链接库时,动态链接库文件必须储存在含有该.EXE程序的目录下、目前的目录下、Windows系统目录下、Windows目录下,或者是在通过MS-DOS环境中的PATH可以存取到的目录下(Windows会按顺序搜索这些目录)。

 

一个简单的DLL

虽然动态链接库的整体概念是它们可以被多个应用程序所使用,但您通常最初设计的动态链接库只与一个应用程序相联系,可能是一个「测试」程序在使用DLL。

 

下面就是我们要做的。我们建立一个名为EDRLIB.DLL的DLL。文件名中的「EDR」代表「简便的绘图例程(easy drawing routines)」。这里的EDRLIB只含有一个函数(名称为EdrCenterText),但是您还可以将应用程序中其它简单的绘图函数添加进去。应用程序EDRTEST.EXE将通过呼叫EDRLIB.DLL中的函数来利用它。

 

要做到这一点,需要与我们以前所做的略有不同的方法,也包括Visual C++ 中我们没有看过的特性。在Visual C++ 中「工作空间(workspaces)」和「项目(projects)」不同。项目通常与建立的应用程序(.EXE)或者动态链接库(.DLL)相联系。一个工作空间可以包含一个或多个项目。迄今为止,我们所有的工作空间都只包含一个项目。我们现在就建立一个包含两个项目的工作空间EDRTEST-一个用于建立EDRTEST.EXE,而另一个用于建立EDRLIB.DLL,即EDRTEST使用的动态链接库。

 

现在就开始。在Visual C++中,从「File」菜单选择「New」,然后选择「Workspaces」页面标签。(我们以前从来没有选择过。)在「Location」栏选择工作空间要储存的目录,然后在「Workspace Name」栏输入「EDRTEST」,按Enter键。

这样就建立了一个空的工作空间。Developer Studio还建立了一个名为EDRTEST的子目录,以及工作空间文件EDRTEST.DSW(就像两个其它文件)。

 

现在让我们在此工作空间里建立一个项目。从「File」菜单选择「New」,然后选择「Projects」页面标签。尽管过去您选择「Win32 Application」,但现在「Win32 Dynamic-Link Library」。另外,单击单选按钮「Add To Current Workspace」,这使得此项目是「EDRTEST」工作空间的一部分。在「Project Name栏输入EDRLIB,但先不要按「OK」按钮。当您在Project Name栏输入EDRLIB时,Visual C++将改变「Location」栏,以显示EDRLIB作为EDRTEST的一个子目录。这不是我们要的,所以接着在「Location」栏删除EDRLIB子目录以便项目建立在EDRTEST目录。现在按「OK」。屏幕将显示一个对话框,询问您建立什么型态的DLL。选择「An Empty DLL Project」,然后按「Finish」。Visual C++将建立一个项目文件EDRLIB.DSP和一个构造文件EDRLIB.MAK(如果「Tools Options」对话框的B「uild页面卷标中选择了「Export Makefile」选项」。

 

现在您已经在此项目中添加了一对文件。从「File」菜单选择「New」,然后选择「Files」页面标签。选择「C/C++ Header File」,然后输入文件名EDRLIB.H。输入程序21-1所示的文件(或者从本书光盘中复制)。再次从「File」菜单中选择「New」,然后选择「Files」页面标签。这次选择「C++ Source File」,然后输入文件名EDRLIB.C。继续输入程序21-1所示的程序。

程序21-1 EDRLIB动态链接库

 

 

这里您可以按Release设定,或者也可以按Debug设定来建立EDRLIB.DLL。之后,RELEASE和DEBUG目录将包含EDRLIB.LIB(即动态链接库的引用链接库)和EDRLIB.DLL(动态链接库本身)。

 

纵观全书,我们建立的所有程序都可以根据UNICODE标识符来编译成使用Unicode或非Unicode字符串的程序代码。当您建立一个DLL时,它应该包括处理字符和字符串的Unicode和非Unicode版的所有函数。因此,EDRLIB.C就包含函数EdrCenterTextA(ANSI版)和EdrCenterTextW(宽字符版)。EdrCenterTextA定义为带有参数PCSTR(指向const字符串的指针),而EdrCenterTextW则定义为带有参数PCWSTR(指向const宽字符串的指针)。EdrCenterTextA函数将呼叫lstrlenA、GetTextExtentPoint32A和TextOutA。EdrCenterTextW将呼叫lstrlenW、GetTextExtentPoint32W和TextOutW。如果定义了UNICODE标识符,则EDRLIB.H将EdrCenterText定义为EdrCenterTextW,否则定义为EdrCenterTextA。这样的做法很像Windows表头文件。

 

EDRLIB.H也包含函数DllMain,取代了DLL中的WinMain。此函数用于执行初始化和未初始化(deinitialization),我将在下一节讨论。我们现在所需要的就是从DllMain传回TRUE。

 

在这两个文件中,最后一点神秘之处就是定义了EXPORT标识符。DLL中应用程序使用的函数必须是「输出(exported)」的。这跟税务或者商业制度无关,只是确保函数名添加到EDRLIB.LIB的一个关键词(以便连结程序在连结使用此函数的应用程序时,能够解析出函数名称),而且该函数在EDRLIB.DLL中也是看得到的。EXPORT标识符包括储存方式限定词__declspec(dllexport)以及在表头文件按C++模式编译时附加的「C」。这将防止编译器使用C++的名称轧压规则(name mangling)来处理函数名称,使C和C++程序都能使用这个DLL。

 

链接库入口/出口点

当动态链接库首次启动和结束时,我们呼叫了DllMain函数。DllMain的第一个参数是链接库的执行实体句柄。如果您的链接库使用需要执行实体句柄(诸如DialogBox)的资源,那么您应该将hInstance储存为一个整体变量。DllMain的最后一个参数由系统保留。

fdwReason参数可以是四个值之一,说明为什么Windows要呼叫DllMain函数。在下面的讨论中,请记住一个程序可以被加载多次,并在Windows下一起执行。每当一个程序加载时,它都被认为是一个独立的程序(process)。

 

fdwReason的一个值DLL_PROCESS_ATTACH表示动态链接库被映像到一个程序的地址空间。链接库可以根据这个线索进行初始化,为以后来自该程序的请求提供服务。例如,这类初始化可能包括内存配置。在一个程序的生命周期内,只有一次对DllMain的呼叫以DLL_PROCESS_ATTACH为参数。使用同一DLL的其它任何程序都将导致另一个使用DLL_PROCESS_ATTACH参数的DllMain呼叫,但这是对新程序的呼叫。

 

如果初始化成功,DllMain应该传回一个非0值。传回0将导致Windows不执行该程序。

 

当fdwReason的值为DLL_PROCESS_DETACH时,意味着程序不再需要DLL了,从而提供给链接库自己清除自己的机会。在32位的Windows下,这种处理并不是严格必须的,但这是一种良好的程序写作习惯。

 

类似地,当以DLL_THREAD_ATTACH为fdwReason参数呼叫DllMain时,意味着某个程序建立了一个新的线程。当线程中止时,Windows以DLL_THREAD_DETACH为fdwReason参数呼叫DllMain。请注意,如果动态链接库是在线程被建立之后和一个程序连结的,那么可能会得到一个没有事先对应一个DLL_THREAD_ATTACH呼叫的DLL_THREAD_DETACH呼叫。

 

当使用一个DLL_THREAD_DETACH参数呼叫DllMain时,线程仍然存在。动态链接库甚至可以在这个程序期间发送线程消息。但是它不应该使用PostMessage,因为线程可能在此消息被处理到之前就已经退出执行了。

 

 

测试程序

现在让我们在EDRTEST工作空间里建立第二个项目,程序名称为EDRTEST,而且使用EDRLIB.DLL。在Visual C++中加载EDRTEST工作空间时,请从「File」菜单选择「New」,然后在「New」对话框中选择「Projects」页面标签。这次选择「Win32 Application」,并确保选中了「Add To Current Workspace」按钮。输入项目名称EDRTEST。再在「Locations」栏删除第二个EDRTEST子目录。按下「OK」,然后在下一个对话框选择「An Empty Project」,按「Finish」。

 

从「File」菜单再次选择「New」。选择「Files」页面标签然后选择「C++ Source File」。确保「Add To Project」清单方块显示「EDRTEST」而不是「EDRLIB」。输入文件名称EDRTEST.C,然后输入程序21-2所示的程序。此程序用EdrCenterText函数将显示区域中的字符串居中对齐。

 

程序21-2 EDRTEST

 

 

 

注意,为了定义EdrCenterText函数,EDRTEST.C包括EDRLIB.H表头文件,此函数将在WM_PAINT消息处理期间呼叫。

 

在编译此程序之前,您可能希望做以下几件事。首先,在「Project」菜单选择「Select Active Project」。这时您将看到「EDRLIB」和「EDRTEST」,选择「EDRTEST」。在重新编译此工作空间时,您真正要重新编译的是程序。另外,在「Project」菜单中,选择「Dependencies」,在「Select Project To Modify」清单方块中选择「EDRTEST」。在「Dependent On The Following Project(s)」列表选中「EDRLIB」。此操作的意思是:EDRTEST需要EDRLIB动态链接库。以后每次重新编译EDRTEST时,如果必要的话,都将在编译和连结EDRTEST之前重新重新编译EDRLIB。

 

从「Project」菜单选择「Settings」,单击「General」标签。当您在左边的窗格中选择「EDRLIB」或者「EDRTEST」项目时,如果设定为「Win32 Release」,则显示在右边窗格中的「Intermediate Files」和「Output Files」将位于RELEASE目录;如果设定为「Win32 Debug」,则位于DEBUG目录。如果不是,请按此修改。这样可确保EDRLIB.DLL与EDRTEST.EXE在同一个目录中,而且程序在使用DLL时也不会产生问题。

 

在「Project Setting」对话框中依然选中「EDRTEST」,单击「C/C++」页面标签。按本书的惯例,在「Preprocessor Definitions」中,将「UNICODE」添加到Debug设定。

 

现在您就可以在「Debug」或「Release」设定中重新编译EDRTEST.EXE了。必要时,Visual C++将首先编译和连结EDRLIB。RELEASE和DEBUG目录都包含EDRLIB.LIB(引用链接库)和EDRLIB.DLL。当Developer Studio连结EDRTEST时,将自动包含引用链接库。

 

了解EDRTEST.EXE文件中不包含EdrCenterText程序代码很重要。事实上,要证明执行了EDRLIB.DLL文件和EdrCenterText函数很简单:执行EDRTEST.EXE需要EDRLIB.DLL。

 

执行EDRTEST.EXE时,Windows按外部链接库模块执行固定的函数。其中许多函数都在一般Windows动态链接库中。但Windows也看到程序从EDRLIB呼叫了函数,因此Windows将EDRLIB.DLL文件加载到内存,然后呼叫EDRLIB的初始化例程。EDRTEST呼叫EdrCenterText函数是动态链接到EDRLIB中函数的。

 

在EDRTEST.C原始码文件中包含EDRLIB.H与包含WINDOWS.H类似。连结EDRLIB.LIB与连结Windows引用链接库(例如USER32.LIB)类似。当您的程序执行时,它连结EDLIB.DLL的方式与连结USER32.DLL的方式相同。恭喜您!您已经扩展了Windows的功能!

 

在继续之前,我还要对动态链接库多说明一些:

首先,虽然我们将DLL作为Windows的延伸,但它也是您的应用程序的延伸。DLL所完成的每件工作对于应用程序来说都是应用程序所交代要完成的。例如,应用程序拥有DLL配置的全部内存、DLL建立的全部窗口以及DLL打开的所有文件。多个应用程序可以同时使用同一个DLL,但在Windows下,这些应用程序不会相互影响。

 

多个程序能够共享一个动态链接库中相同的程序代码。但是,DLL为每个程序所储存的数据都不同。每个程序都为DLL所使用的全部数据配置了自己的地址空间。我们将在下以节看到,共享内存需要额外的工作。

 

 

累了 标记下  明天再看

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值