分享
分享赚钱 收藏 举报 版权申诉 / 17

类型浅谈Windows API编程.doc

  • 上传人:scg750829
  • 文档编号:8245334
  • 上传时间:2019-06-16
  • 格式:DOC
  • 页数:17
  • 大小:89KB
  • 配套讲稿:

    如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。

    特殊限制:

    部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。

    关 键  词:
    浅谈Windows API编程.doc
    资源描述:

    1、浅谈 Windows API 编程2007 年 10 月 20 日 星期六 20:29WinSDK 是编程中的传统难点,个人写的 WinAPI 程序也不少了,其实之所以难就难在每个调用的 API 都包含着 Windows 这个操作系统的潜规则或者是 windows内部的运行机制WinSDK 是编程中的传统难点,曾经听有一个技术不是很好的朋友乱说什么给你API 谁都会用,其实并非那么简单,个人写的 WinAPI 程序也不少了,其实之所以难就难在每个调用的 API 都包含着 Windows 这个操作系统的潜规则或者是windows 内部的运行机制。首先来谈谈句柄,初学习 WinSDK 的朋友刚看到

    2、这个词头大了吧?其实我也是了,我们来看看 programming windows 里面是怎么说的,一个句柄仅仅是用来识别某些事情的数字。它唯一的标识这当前的一个实例。这样说确实不容易懂。那么我们这么看,比如你打开 windows 自带的计算器。你多打开几次是不是桌面上出现了很多个计算器呢?你使用其中一个计算器的时候当你按下等于按钮的时候运算结果是否会出现在其他的计算机结果栏里?不会,那 windows 怎么知道让结果出现在哪里呢?这就是句柄的作用了,句柄唯一的标识着一个程序,你打开的每一个窗口(计算器) 都有一个不同的句柄你你每一步操作都是指定了在某个句柄下的,所以,他不会出错。而且你打开的每

    3、一个计算机都共享着同样的代码和内存。通过句柄系统会把所需的资源充分的调用到当前的某个程序自己的数据区。不仅是窗口,各种菜单,GDI 对象都有自己的句柄,获取句柄的手段也是多重多样,不过当然是通过调用 API 函数实现了,如:MFC 中的 hHandle = GetSafeHandle();API 编程中的 hBrush = GetStorkObject(BLACK_BRUSH);很多操作都需要将句柄添加到参数列表中,当你没有直接定义句柄变量的时候可能要记忆很多 API 的返回类型来间接获取。如:hPen = SelectObject(hdc,GetStockObject(/ SelectObj

    4、ect()这个函数在设置本设备描述表下的 GDI 对象时会返回设置前的 GDI 对象句柄MoveToEx(hdc, pt1.x, pt1.y, LineTo(hdc, pt2.x,pt2.y);SelectObject(hdc,hPen);完成选择自定义的 GDI 对象的操作。句柄的种类很多,掌握一种的使用方法所有的不学自通,WinAPI 编程永远伴随的元素中句柄是其中之一。非常重要。由于是浅谈,所以就说到这里了.接下来是 windows 下的消息映射机制了,呵呵,窗口过程,刚学的朋友难理解吧?WinSDK 编程基于 C,但是和 C 的理念有着完全的不同,这中间的不同,在我看来最多的也就是来自

    5、于这个消息映射,后面什么吹的很炫的 Hook 技术,木马技术,键盘截获,都是来自于特殊消息的捕捉,映射自定义的特殊消息来实现的(当然和我接下来谈的稍微有点不同)。首先我们应该先明白消息和事件的区别,Windows 是消息驱动的操作系统,这里的消息的产生来自于某个实例化的对象上用户的操作,来自控件,菜单,或者是系统本身产生的,而事件是靠消息触发的,但这也不是绝对的。可以用一个简单的例子去解释,我这里越写越觉得自己难表达清楚,就比如这么一个例子:“某男杀人这条消息导致被枪毙这个事件”不过最重要的区别是在消息产生后并不会被直接处理,而是先插入 windows 系统的消息队列,然后系统判断此消息产生于

    6、哪个程序,送入此程序的消息循环,由 LRSULT CALLBACK winprc(hwnd , uint,wParam,lParam)处理。而事件是操作系统处理消息的过程中反馈的结果。用户操作- 产生消息-发送系统-系统判断来源-发给相应的窗口过程或者其他 Callback 函数-消息处理-等待下一条消息的产生以上为消息循环整个过程。LRSULT CALLBACK winprc(hwnd , uint,wParam,lParam);int WINAPI WinMain()MSG msg;RegisterClass(); / 注册窗口类CreateWindow(); / 创建窗口ShowWind

    7、ow(); / 显示窗口UpdateWindow();While(GetMessage(DispatchMessage();LRSULT CALLBACK winprc(hwnd , uint,wParam,lParam);/窗口过程函数,用于映射 switch 语句中各个需要被处理的消息While(UINT)message)Switch(message)CaseCaseDefault.以上是最基本的 WinAPi 编程的代码结构。其实这里面最重要的结构莫过于while(GetMessage(case WM_LBUTTONDOWN:hdc = GetDC(hwnd);apt1.x = LOWO

    8、RD (lParam);apt1.y = HIWORD (lParam);hPen = CreatePen(BLACK_PEN,3,RGB(125,125,125);SelectObject(hdc,hPen);MoveToEx(hdc,apt0.x,apt0.y,NULL);LineTo(hdc,apt1.x,apt1.y);apt0.x = apt1.x;apt0.y = apt1.y;DeleteObject(hPen);ReleaseDC(hwnd,hdc);return 0;这段代码实现一个简单的画线功能,当你在你的客户区胡点一通鼠标后试着拖动一下窗口大小,或者将其最小化或者被其他窗

    9、口覆盖一下你都会发现你原来画的线没了,可是其他窗口为什么被覆盖了以后再弹出窗口还会有原来的东西呢?那就是重绘,要重新绘制整个客户区(准确的说是失效的矩形),以上说的操作都会导致你的客户区失效,这时会产生重绘消息 WM_PAINT,我们要想保存这些线那么我们就必须保存这些你用鼠标左键点过的点。当然这是重绘技术中最简单的,当你的客户区上是一个复杂的画面的话,就不仅仅需要保存点,还有各种形状的图形,颜色等等这里给大家一段我自己写的代码来实现以上的 WM_LBUTTONDOWN 消息映射来产生的点。通过单链表来动态添加点来实现重绘。case WM_PAINT:hdc = BeginPaint(hwnd

    10、,TextOut(hdc,cxClient/6,cyClient/6,TEXT(“图形重绘“),strlen(“图形重绘“);ReDrawLines(EndPaint(hwnd,return 0;case WM_LBUTTONDOWN:hdc = GetDC(hwnd);apt1.x = LOWORD (lParam);apt1.y = HIWORD (lParam);hPen = CreatePen(BLACK_PEN,2,RGB(125,0,0);SelectObject(hdc,hPen);MoveToEx(hdc,apt0.x,apt0.y,NULL);LineTo(hdc,apt1.

    11、x,apt1.y);MyList.pCurrent-x = apt0.x;MyList.pCurrent-y = apt0.y;MyList.pCurrent-pNext-x = apt1.x;MyList.pCurrent-pNext-y = apt1.y;MyList.m_iCounter = MyList.m_iCounter+2;MyList.pCurrent = MyList.pCurrent-pNext-pNext;apt0.x = apt1.x;apt0.y = apt1.y;DeleteObject(hPen);ReleaseDC(hwnd,hdc);return 0;其中的重

    12、绘函数代码如下:void ReDrawLines(LinkList* pLinkList,HDC hdc)pMyPoint p = pLinkList-pHead;int iSaver =pLinkList-m_iCounter;while(iSaver!=0)MoveToEx(hdc,p-x,p-y,NULL);LineTo(hdc,p-pNext-x,p-pNext-y);p=p-pNext-pNext;iSaver=iSaver-2;添加了以上的代码你会发现再次拖动窗口大小等等你原来画的线就都能重现出来了。呵呵是不是觉得一个看似简单的东西其实里面需要很多代码实现呢?也许,这就是 wind

    13、ows.好了,WinSDK 入门的东西就谈这么多,希望能给初学者一定的帮助,这么多的字,都是我一个一个打出来没有任何借鉴和摘抄的。相信做为一个过来人能更多的理解大家学习中的困难。Win32 环境下动态链接库(DLL)编程原理比较大应用程序都由很多模块组成,这些模块分别完成相对独立的功能,它们彼此协作来完成整个软件系统的工作。其中可能存在一些模块的功能较为通用,在构造其它软件系统时仍会被使用。在构造软件系统时,如果将所有模块的源代码都静态编译到整个应用程序EXE 文件中,会产生一些问题:一个缺点是增加了应用程序的大小,它会占用更多的磁盘空间,程序运行时也会消耗较大的内存空间,造成系统资源的浪费;

    14、另一个缺点是,在编写大的 EXE 程序时,在每次修改重建时都必须调整编译所有源代码,增加了编译过程的复杂性,也不利于阶段性的单元测试。Windows 系统平台上提供了一种完全不同的较有效的编程和运行环境,你可以将独立的程序模块创建为较小的 DLL(Dynamic Linkable Library)文件,并可对它们单独编译和测试。在运行时,只有当 EXE 程序确实要调用这些 DLL 模块的情况下,系统才会将它们装载到内存空间中。这种方式不仅减少了 EXE 文件的大小和对内存空间的需求,而且使这些DLL 模块可以同时被多个应用程序使用。Microsoft Windows 自己就将一些主要的系统功能

    15、以 DLL 模块的形式实现。例如 IE 中的一些基本功能就是由 DLL 文件实现的,它可以被其它应用程序调用和集成。一般来说,DLL 是一种磁盘文件(通常带有 DLL 扩展名) ,它由全局数据、服务函数和资源组成,在运行时被系统加载到进程的虚拟空间中,成为调用进程的一部分。如果与其它DLL 之间没有冲突,该文件通常映射到进程虚拟空间的同一地址上。DLL 模块中包含各种导出函数,用于向外界提供服务。Windows 在加载 DLL 模块时将进程函数调用与 DLL 文件的导出函数相匹配。在 Win32 环境中,每个进程都复制了自己的读/ 写全局变量。如果想要与其它进程共享内存,必须使用内存映射文件或

    16、者声明一个共享数据段。DLL 模块需要的堆栈内存都是从运行进程的堆栈中分配出来的。DLL 现在越来越容易编写。Win32 已经大大简化了其编程模式,并有许多来自 AppWizard和 MFC 类库的支持。一、导出和导入函数的匹配DLL 文件中包含一个导出函数表。这些导出函数由它们的符号名和称为标识号的整数与外界联系起来。函数表中还包含了 DLL 中函数的地址。当应用程序加载 DLL 模块时时,它并不知道调用函数的实际地址,但它知道函数的符号名和标识号。动态链接过程在加载的DLL 模块时动态建立一个函数调用与函数地址的对应表。如果重新编译和重建 DLL 文件,并不需要修改应用程序,除非你改变了导

    17、出函数的符号名和参数序列。简单的 DLL 文件只为应用程序提供导出函数,比较复杂的 DLL 文件除了提供导出函数以外,还调用其它 DLL 文件中的函数。这样,一个特殊的 DLL 可以既有导入函数,又有导入函数。这并不是一个问题,因为动态链接过程可以处理交叉相关的情况。在 DLL 代码中,必须像下面这样明确声明导出函数:_declspec(dllexport) int MyFunction(int n);但也可以在模块定义(DEF)文件中列出导出函数,不过这样做常常引起更多的麻烦。在应用程序方面,要求像下面这样明确声明相应的输入函数:_declspec(dllimport) int MyFunc

    18、ition(int n);仅有导入和导出声明并不能使应用程序内部的函数调用链接到相应的 DLL 文件上。应用程序的项目必须为链接程序指定所需的输入库(LIB 文件) 。而且应用程序事实上必须至少包含一个对 DLL 函数的调用。二、与 DLL 模块建立链接应用程序导入函数与 DLL 文件中的导出函数进行链接有两种方式:隐式链接和显式链接。所谓的隐式链接是指在应用程序中不需指明 DLL 文件的实际存储路径,程序员不需关心DLL 文件的实际装载。而显式链接与此相反。采用隐式链接方式,程序员在建立一个 DLL 文件时,链接程序会自动生成一个与之对应的LIB 导入文件。该文件包含了每一个 DLL 导出函

    19、数的符号名和可选的标识号,但是并不含有实际的代码。LIB 文件作为 DLL 的替代文件被编译到应用程序项目中。当程序员通过静态链接方式编译生成应用程序时,应用程序中的调用函数与 LIB 文件中导出符号相匹配,这些符号或标识号进入到生成的 EXE 文件中。LIB 文件中也包含了对应的 DLL 文件名(但不是完全的路径名) ,链接程序将其存储在 EXE 文件内部。当应用程序运行过程中需要加载 DLL 文件时,Windows 根据这些信息发现并加载 DLL,然后通过符号名或标识号实现对 DLL 函数的动态链接。显式链接方式对于集成化的开发语言(例如 VB)比较适合。有了显式链接,程序员就不必再使用导

    20、入文件,而是直接调用 Win32 的 LoadLibary 函数,并指定 DLL 的路径作为参数。LoadLibary 返回 HINSTANCE 参数,应用程序在调用 GetProcAddress 函数时使用这一参数。GetProcAddress 函数将符号名或标识号转换为 DLL 内部的地址。假设有一个导出如下函数的 DLL 文件:extern “C“ _declspec(dllexport) double SquareRoot(double d);下面是应用程序对该导出函数的显式链接的例子:typedef double(SQRTPROC)(double);HINSTANCE hInstan

    21、ce;SQRTPROC* pFunction;VERIFY(hInstance=:LoadLibrary(“c:winntsystem32mydll.dll“);VERIFY(pFunction=(SQRTPROC*):GetProcAddress(hInstance,“SquareRoot“);double d=(*pFunction)(81.0);/调用该 DLL 函数在隐式链接方式中,所有被应用程序调用的 DLL 文件都会在应用程序 EXE 文件加载时被加载在到内存中;但如果采用显式链接方式,程序员可以决定 DLL 文件何时加载或不加载。显式链接在运行时决定加载哪个 DLL 文件。例如,

    22、可以将一个带有字符串资源的 DLL 模块以英语加载,而另一个以西班牙语加载。应用程序在用户选择了合适的语种后再加载与之对应的 DLL 文件。三、使用符号名链接与标识号链接在 Win16 环境中,符号名链接效率较低,所有那时标识号链接是主要的链接方式。在Win32 环境中,符号名链接的效率得到了改善。Microsoft 现在推荐使用符号名链接。但在MFC 库中的 DLL 版本仍然采用的是标识号链接。一个典型的 MFC 程序可能会链接到数百个 MFC DLL 函数上。采用标识号链接的应用程序的 EXE 文件体相对较小,因为它不必包含导入函数的长字符串符号名。四、编写 DllMain 函数DllMa

    23、in 函数是 DLL 模块的默认入口点。当 Windows 加载 DLL 模块时调用这一函数。系统首先调用全局对象的构造函数,然后调用全局函数 DLLMain。DLLMain 函数不仅在将DLL 链接加载到进程时被调用,在 DLL 模块与进程分离时(以及其它时候)也被调用。下面是一个框架 DLLMain 函数的例子。HINSTANCE g_hInstance;extern “C“ int APIENTRY DllMain(HINSTANCE hInstance,DWORD dwReason,LPVOID lpReserved)if(dwReason=DLL_PROCESS_ATTACH)TRA

    24、CE0(“EX22A.DLL Initializing!n“);/在这里进行初始化else if(dwReason=DLL_PROCESS_DETACH)TRACE0(“EX22A.DLL Terminating!n“);/在这里进行清除工作return 1;/成功如果程序员没有为 DLL 模块编写一个 DLLMain 函数,系统会从其它运行库中引入一个不做任何操作的缺省 DLLMain 函数版本。在单个线程启动和终止时,DLLMain 函数也被调用。正如由 dwReason 参数所表明的那样。五、模块句柄进程中的每个 DLL 模块被全局唯一的 32 字节的 HINSTANCE 句柄标识。进程

    25、自己还有一个 HINSTANCE 句柄。所有这些模块句柄都只有在特定的进程内部有效,它们代表了 DLL或 EXE 模块在进程虚拟空间中的起始地址。在 Win32 中,HINSTANCE 和 HMODULE 的值是相同的,这个两种类型可以替换使用。进程模块句柄几乎总是等于 0x400000,而 DLL模块的加载地址的缺省句柄是 0x10000000。如果程序同时使用了几个 DLL 模块,每一个都会有不同的 HINSTANCE 值。这是因为在创建 DLL 文件时指定了不同的基地址,或者是因为加载程序对 DLL 代码进行了重定位。模块句柄对于加载资源特别重要。Win32 的 FindResource

    26、 函数中带有一个 HINSTANCE 参数。EXE 和 DLL 都有其自己的资源。如果应用程序需要来自于 DLL 的资源,就将此参数指定为 DLL 的模块句柄。如果需要 EXE 文件中包含的资源,就指定 EXE 的模块句柄。但是在使用这些句柄之前存在一个问题,你怎样得到它们呢?如果需要得到 EXE 模块句柄,调用带有 Null 参数的 Win32 函数 GetModuleHandle;如果需要 DLL 模块句柄,就调用以DLL 文件名为参数的 Win32 函数 GetModuleHandle。六、应用程序怎样找到 DLL 文件如果应用程序使用 LoadLibrary 显式链接,那么在这个函数的

    27、参数中可以指定 DLL 文件的完整路径。如果不指定路径,或是进行隐式链接,Windows 将遵循下面的搜索顺序来定位DLL:1 包含 EXE 文件的目录,2 进程的当前工作目录,3 Windows 系统目录,4 Windows 目录,5 列在 Path 环境变量中的一系列目录。这里有一个很容易发生错误的陷阱。如果你使用 VC进行项目开发,并且为 DLL 模块专门创建了一个项目,然后将生成的 DLL 文件拷贝到系统目录下,从应用程序中调用DLL 模块。到目前为止,一切正常。接下来对 DLL 模块做了一些修改后重新生成了新的DLL 文件,但你忘记将新的 DLL 文件拷贝到系统目录下。下一次当你运行

    28、应用程序时,它仍加载了老版本的 DLL 文件,这可要当心!七、调试 DLL 程序Microsoft 的 VC是开发和测试 DLL 的有效工具,只需从 DLL 项目中运行调试程序即可。当你第一次这样操作时,调试程序会向你询问 EXE 文件的路径。此后每次在调试程序中运行 DLL 时,调试程序会自动加载该 EXE 文件。然后该 EXE 文件用上面的搜索序列发现 DLL 文件,这意味着你必须设置 Path 环境变量让其包含 DLL 文件的磁盘路径,或者也可以将 DLL 文件拷贝到搜索序列中的目录路径下。HOOK API 是一个永恒的话题,如果没有 HOOK,许多技术将很难实现,也许根本不能实现。这里

    29、所说的 API,是广义上的 API,它包括 DOS 下的中断, WINDOWS 里的 API、中断服务、IFS 和 NDIS 过滤等。比如大家熟悉的即时翻译软件,就是靠 HOOK TextOut()或 ExtTextOut()这两个函数实现的,在操作系统用这两个函数输出文本之前,就把相应的英文替换成中文而达到即时翻译;IFS 和 NDIS 过滤也是如此,在读写磁盘和收发数据之前,系统会调用第三方提供的回调函数来判断操作是否可以放行,它与普通 HOOK 不同,它是操作系统允许的,由操作系统提供接口来安装回调函数。甚至如果没有 HOOK,就没有病毒,因为不管是 DOS 下的病毒或 WINDOWS

    30、里的病毒,都是靠 HOOK 系统服务来实现自己的功能的:DOS 下的病毒靠 HOOK INT 21 来感染文件(文件型病毒) ,靠 HOOK INT 13 来感染引导扇区(引导型病毒) ;WINDOWS 下的病毒靠 HOOK 系统 API(包括 RING0 层的和 RING3 层的) ,或者安装 IFS(CIH 病毒所用的方法)来感染文件。因此可以说“没有 HOOK,就没有今天多姿多彩的软件世界”。由于涉及到专利和知识产权,或者是商业机密,微软一直不提倡大家 HOOK 它的系统API,提供 IFS 和 NDIS 等其他过滤接口,也是为了适应杀毒软件和防火墙的需要才开放的。所以在大多数时候,HO

    31、OK API 要靠自己的力量来完成。HOOK API 有一个原则,这个原则就是:被 HOOK 的 API 的原有功能不能受到任何影响。就象医生救人,如果把病人身体里的病毒杀死了,病人也死了,那么这个 “救人” 就没有任何意义了。如果你 HOOK API 之后,你的目的达到了,但 API 的原有功能失效了,这样不是 HOOK,而是 REPLACE,操作系统的正常功能就会受到影响,甚至会崩溃。HOOK API 的技术,说起来也不复杂,就是改变程序流程的技术。在 CPU 的指令里,有几条指令可以改变程序的流程:JMP,CALL,INT ,RET, RETF,IRET 等指令。理论上只要改变 API

    32、入口和出口的任何机器码,都可以 HOOK,但是实际实现起来要复杂很多,因为要处理好以下问题:1,CPU 指令长度问题,在 32 位系统里,一条 JMP/CALL 指令的长度是 5 个字节,因此你只有替换 API 里超过 5 个字节长度的机器码(或者替换几条指令长度加起来是 5 字节的指令) ,否则会影响被更改的小于 5 个字节的机器码后面的数条指令,甚至程序流程会被打乱,产生不可预料的后果;2,参数问题,为了访问原 API 的参数,你要通过 EBP 或 ESP 来引用参数,因此你要非常清楚你的 HOOK 代码里此时的 EBP/ESP 的值是多少;3,时机的问题,有些 HOOK 必须在 API

    33、的开头,有些必须在 API 的尾部,比如 HOOK CreateFilaA(),如果你在 API 尾部 HOOK API,那么此时你就不能写文件,甚至不能访问文件;HOOK RECV(),如果你在 API 头 HOOK,此时还没有收到数据,你就去查看RECV()的接收缓冲区,里面当然没有你想要的数据,必须等 RECV()正常执行后,在RECV()的尾部 HOOK,此时去查看 RECV()的缓冲区,里面才有想要的数据;4,上下文的问题,有些 HOOK 代码不能执行某些操作,否则会破坏原 API 的上下文,原API 就失效了;5,同步问题,在 HOOK 代码里尽量不使用全局变量,而使用局部变量,这

    34、样也是模块化程序的需要;6,最后要注意的是,被替换的 CPU 指令的原有功能一定要在 HOOK 代码的某个地方模拟实现。下面以 ws2_32.dll 里的 send()为例子来说明如何 HOOK 这个函数:Exported fn(): send - Ord:0013h地址 机器码 汇编代码:71A21AF4 55 push ebp /将被 HOOK 的机器码(第 1 种方法):71A21AF5 8BEC mov ebp, esp /将被 HOOK 的机器码(第 2 种方法):71A21AF7 83EC10 sub esp, 00000010:71A21AFA 56 push esi:71A21

    35、AFB 57 push edi:71A21AFC 33FF xor edi, edi:71A21AFE 813D1C20A371931CA271 cmp dword ptr 71A3201C, 71A21C93 /将被HOOK 的机器码(第 4 种方法):71A21B08 0F84853D0000 je 71A25893:71A21B0E 8D45F8 lea eax, dword ptr ebp-08:71A21B11 50 push eax:71A21B12 E869F7FFFF call 71A21280:71A21B17 3BC7 cmp eax, edi:71A21B19 8945F

    36、C mov dword ptr ebp-04, eax:71A21B1C 0F85C4940000 jne 71A2AFE6:71A21B22 FF7508 push ebp+08:71A21B25 E826F7FFFF call 71A21250:71A21B2A 8BF0 mov esi, eax:71A21B2C 3BF7 cmp esi, edi:71A21B2E 0F84AB940000 je 71A2AFDF:71A21B34 8B4510 mov eax, dword ptr ebp+10:71A21B37 53 push ebx:71A21B38 8D4DFC lea ecx,

    37、 dword ptr ebp-04:71A21B3B 51 push ecx:71A21B3C FF75F8 push ebp-08:71A21B3F 8D4D08 lea ecx, dword ptr ebp+08:71A21B42 57 push edi:71A21B43 57 push edi:71A21B44 FF7514 push ebp+14:71A21B47 8945F0 mov dword ptr ebp-10, eax:71A21B4A 8B450C mov eax, dword ptr ebp+0C:71A21B4D 51 push ecx:71A21B4E 6A01 pu

    38、sh 00000001:71A21B50 8D4DF0 lea ecx, dword ptr ebp-10:71A21B53 51 push ecx:71A21B54 FF7508 push ebp+08:71A21B57 8945F4 mov dword ptr ebp-0C, eax:71A21B5A 8B460C mov eax, dword ptr esi+0C:71A21B5D FF5064 call eax+64:71A21B60 8BCE mov ecx, esi:71A21B62 8BD8 mov ebx, eax:71A21B64 E8C7F6FFFF call 71A212

    39、30 /将被 HOOK 的机器码(第 3 种方法):71A21B69 3BDF cmp ebx, edi:71A21B6B 5B pop ebx:71A21B6C 0F855F940000 jne 71A2AFD1:71A21B72 8B4508 mov eax, dword ptr ebp+08:71A21B75 5F pop edi:71A21B76 5E pop esi:71A21B77 C9 leave:71A21B78 C21000 ret 0010下面用 4 种方法来 HOOK 这个 API:1,把 API 入口的第一条指令是 PUSH EBP 指令(机器码 0x55)替换成 IN

    40、T 3(机器码0xcc) ,然后用 WINDOWS 提供的调试函数来执行自己的代码,这中方法被 SOFT ICE 等DEBUGER 广泛采用,它就是通过 BPX 在相应的地方设一条 INT 3 指令来下断点的。但是不提倡用这种方法,因为它会与 WINDOWS 或调试工具产生冲突,而汇编代码基本都要调试;2,把第二条 mov ebp,esp 指令(机器码 8BEC,2 字节)替换为 INT F0 指令(机器码CDF0) ,然后在 IDT 里设置一个中断门,指向我们的代码。我这里给出一个 HOOK 代码:lea ebp,esp+12 /模拟原指令 mov ebp,esp 的功能pushfd /保存

    41、现场pushad /保存现场/在这里做你想做的事情popad /恢复现场popfd /恢复现场iretd /返回原指令的下一条指令继续执行原函数(71A21AF7 地址处)这种方法很好,但缺点是要在 IDT 设置一个中断门,也就是要进 RING0。3,更改 CALL 指令的相对地址(CALL 分别在 71A21B12、71A21B25、71A21B64,但前面 2 条 CALL 之前有一个条件跳转指令,有可能不被执行到,因此我们要 HOOK 71A21B64 处的 CALL 指令) 。为什么要找 CALL 指令下手?因为它们都是 5 字节的指令,而且都是 CALL 指令,只要保持操作码 0xE

    42、8 不变,改变后面的相对地址就可以转到我们的 HOOK 代码去执行了,在我们的 HOOK 代码后面再转到目标地址去执行。假设我们的 HOOK 代码在 71A20400 处,那么我们把 71A21B64 处的 CALL 指令改为CALL 71A20400(原指令是这样的: CALL 71A21230)而 71A20400 处的 HOOK 代码是这样的:71A20400:pushad/在这里做你想做的事情popadjmp 71A21230 /跳转到原 CALL 指令的目标地址,原指令是这样的: call 71A21230这种方法隐蔽性很好,但是比较难找这条 5 字节的 CALL 指令,计算相对地址

    43、也复杂。4,替换 71A21AFE 地址上的 cmp dword ptr 71A3201C, 71A21C93 指令(机器码:813D1C20A371931CA271,10 字节)成为call 71A20400nopnopnopnopnop(机器码:E8 XX XX XX XX 90 90 90 90 90,10 字节)在 71A20400 的 HOOK 代码是:pushadmov edx,71A3201Ch /模拟原指令 cmp dword ptr 71A3201C, 71A21C93cmp dword ptr edx,71A21C93h /模拟原指令 cmp dword ptr 71A32

    44、01C, 71A21C93pushfd/在这里做你想做的事popfdpopadret这种方法隐蔽性最好,但不是每个 API 都有这样的指令,要具体情况具体操作。以上几种方法是常用的方法,值得一提的是很多人都是改 API 开头的 5 个字节,但是现在很多杀毒软件用这样的方法检查 API 是否被 HOOK,或其他病毒木马在你之后又改了前 5个字节,这样就会互相覆盖,最APIHook 一直是使大家感兴趣的话题。屏幕取词,内码转化,屏幕翻译,中文平台等等都涉及到了此项技术。有很多文章涉及到了这项技术,但都闪烁其词不肯明明白白的公布。我仅在这里公布以下我用 Delphi 制作 APIHook 的一些心得

    45、。通常的 APIHOOK 有这样几种方法:1、自己写一个动态链接库,里面定义自己写的想取代系统的 API。把这个动态链接库映射到 2G 以上的系统动态链接库所在空间,把系统动态链接库中的该 API 的指向修改指向自己的函数。这种方法的好处就是可以取代系统中运行全部程序的该 API。但他有个局限,就是只适用于 Win9x。 (原因是 NT 中动态链接库不是共享的,每个进程都有自己的一份动态链接库在内存中的映射)2、自己写一个动态链接库,里面定义自己写得象替代系统的 API。把这个动态链接库映射到进程的空间里。将该进程对 API 的调用指向自己写的动态链接库。这种方法的好处是可以选择性的替代哪个进

    46、程的 API。而且适用于所有的 Windows 操作系统。这里我选用的是第二种方法。第二种方法需要先了解一点 PE 文件格式的知识。首先是一个实模式的的 DOS 文件头,是为了保持和 DOS 的兼容。接着是一个 DOS 的代理模块。你在纯 DOS 先运行 Win32 的可执行文件,看看是不是也执行了,只是显示的的是一行信息大意是说该 Windows 程序不能在 DOS 实模式下运行。然后才是真正意义上的 Windows 可执行文件的文件头。它的具体位置不是每次都固定的。是由文件偏移$3C 决定的。我们要用到的就是它。如果我们在程序中调用了一个 MessageBoxA 函数那么它的实现过程是这样

    47、的。他先调用在本进程中的 MessageBoxA 函数然后才跳到动态链接库的 MessageBoxA 的入口点。即:call messageBoxA(0040106c)jmp dword ptr _jmp_MessageBoxA16(00425294)其中 00425294 的内容存储的就是就是 MessageBoxA 函数的入口地址。如果我们做一下手脚,那么那就开始吧!我们需要定义两个结构type PImage_Import_Entry = Image_Import_Entry;Image_Import_Entry = recordCharacteristics: DWORD;TimeDateStamp: DWORD;MajorVersion: Word;MinorVersion: Word;Name: DWORD;LookupTable: DWORD;end;type TImportCode = packed recordJumpInstruction: Word; file: /定义跳转指令 jmpAddressOfPointerToFunction: Pointer; file: /定义要跳转到的函数end;PImportCode = TImportCode;然后是确定函数的地址。function LocateFu

    展开阅读全文
    提示  道客多多所有资源均是用户自行上传分享,仅供网友学习交流,未经上传用户书面授权,请勿作他用。
    关于本文
    本文标题:浅谈Windows API编程.doc
    链接地址:https://www.docduoduo.com/p-8245334.html
    关于我们 - 网站声明 - 网站地图 - 资源地图 - 友情链接 - 网站客服 - 联系我们

    道客多多用户QQ群:832276834  微博官方号:道客多多官方   知乎号:道客多多

    Copyright© 2025 道客多多 docduoduo.com 网站版权所有世界地图

    经营许可证编号:粤ICP备2021046453号    营业执照商标

    1.png 2.png 3.png 4.png 5.png 6.png 7.png 8.png 9.png 10.png



    收起
    展开