<一>勾子基本概念 本期导读:什么叫勾子,勾子又起什么作用,它有那些类别,怎么使用,等等这些问题 将在本期找到答案 ======================================================================================= 基本概念 钩子(Hook),是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所创建的。当消息到达后,在目标窗口处理函数之前处理它。钩子机制允许应用程序截获处理window消息或特定事件。 钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控 制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。 运行机制 1、钩子链表和钩子子程: 每一个Hook都有一个与之相关联的指针列表,称之为钩子链表,由系统来维护。这个列表的指针指向指定的,应用程序定义的,被Hook子程调用的回调函数,也就是该钩子的各个处理子程。当与指定的Hook类型关联的消息发生时,系统就把这个消息传递到Hook子程。一些Hook子程可以只监视消息,或者修改消息,或者停止消息的前进,避免这些消息传递到下一个Hook子程或者目的窗口。最近安装的钩子放在链的开始,而最早安装的钩子放在最后,也就是后加入的先获得控制权。 Windows 并不要求钩子子程的卸载顺序一定得和安装顺序相反。每当有一个钩子被卸载,Windows 便释放其占用的内存,并更新整个Hook链表。如果程序安装了钩子,但是在尚未卸载钩子之前就结束了,那么系统会自动为它做卸载钩子的操作。 钩子子程是一个应用程序定义的回调函数(CALLBACK Function),不能定义成某个类的成员函数,只能定义为普通的C函数。用以监视系统或某一特定类型的事件,这 些事件可以是与某一特定线程关联的,也可以是系统中所有线程的事件。 钩子子程必须按照以下的语法: LRESULT CALLBACK HookProc ( int nCode, WPARAM wParam, LPARAM lParam ); 当然上面是在C中的表达方式,意思是说这个直定义的钩子子程必须有3个参数,在易中应象这样表达: .子程序 HookProc, 整数型, 公开, 钩子回调函数 .参数 ncode, 整数型 .参数 wParam, 整数型 .参数 lParam, 整数型 HookProc是应用程序定义的名字。 nCode参数是Hook代码,Hook子程使用这个参数来确定任务。这个参数的值依赖于Hook类型,每一种Hook都有自己的Hook代码特征字符集。 wParam和lParam参数的值依赖于Hook代码,但是它们的典型值是包含了关于发送或者接收消息的信息。 2、钩子的安装与释放: <1>钩子的安装 使用API函数SetWindowsHookEx()把一个应用程序定义的钩子子程安装到钩子链表中。SetWindowsHookEx函数总是在Hook链的开头安装Hook子程。当指定类型的Hook监视的事件发生时,系统就调用与这个Hook关联的Hook链的开头的Hook子程。每一个Hook链中的Hook子程都决定是否把这个事件传递到下一个Hook子程。Hook子程传递事件到下一个Hook子程需要调用CallNextHookEx函数。 HHOOK SetWindowsHookEx( int idHook, //参数<1> HOOKPROC lpfn, //参数<2> HINSTANCE hMod, //参数<3> DWORD dwThreadId //参数<4> ); 在易中则这样声明DLL: .DLL命令 api_SetWindowsHookExA, 整数型, , "SetWindowsHookExA" .参数 idHook, 整数型 .参数 lpfn, 子程序指针 .参数 nMod, 整数型 .参数 dwThreadID, 整数型 参数<1>idHook是钩子的类型,即它处理的消息类型 参数<2>lpfn是钩子子程的地址指针。如果dwThreadId参数为0,或是一个由别的进程创建的线程的标识 lpfn必须指向DLL中的钩子子程。除此以外,lpfn可以指向当前进程的一段钩子子程代码。 参数<3>nMod是应用程序实例的句柄。标识包含lpfn所指的子程的DLL,如果dwThreadId 标识当前进程 创建的一个线程,而且子程代码位于当前进程,hMod必须为NULL。可以很简单的设定其为本 应用程序的实例句柄。 参数<4>dwThreadID:与安装的钩子子程相关联的线程的标识符, 如果为0,钩子子程与所有的线程关联 即为全局钩子。 函数成功则返回钩子子程的句柄,失败返回(NULL)0。 <2>钩子的循环 以上所说的钩子子程与线程相关联是指在一钩子链表中发给该线程的消息同时发送给钩子子程,且被钩子子程先处理。在钩子子程中调用得到控制权的钩子函数在完成对消息的处理后,如果想要该消息继续传递,那么它必须调用另外一个SDK中 的API函数CallNextHookEx来传递它,以执行钩子链表所指的下一个钩子子程。这个函数成功时返回钩子链中下一个钩子过程的返回值,返回值的类型依赖于钩子的类型。这个函数的原型如下: LRESULT CallNextHookEx ( HHOOK hhook; int nCode; WPARAM wParam; LPARAM lParam; ); 在易中则这样声明DLL: .DLL命令 CallNextHookEx, 整数型, , "CallNextHookEx" .参数 hhook, 整数型 .参数 nCode, 整数型 .参数 wParam, 整数型 .参数 lParam, 整数型 hk为当前钩子的句柄,由SetWindowsHookEx()函数返回。 NCode为传给钩子过程的事件代码。 wParam和lParam 分别是传给钩子子程的wParam值,其具体含义与钩子类型有关。 钩子函数也可以通过直接返回(TRUE)真来丢弃该消息,并阻止该消息的传递。否则的话,其他安装了钩子的应用程序将不会接收到钩子的通知而且还有可能产生不正确的结果。 <3>钩子的卸载 钩子在使用完之后需要用UnHookWindowsHookEx()卸载,否则会造成麻烦。释放 钩子比较简单,UnHookWindowsHookEx()只有一个参数。函数原型如下: UnHookWindowsHookEx ( HHOOK hhk; ); 在易中则这样声明DLL: .DLL命令 api_UnhookWindowsHookEx, 逻辑型, , "UnhookWindowsHookEx" .参数 hhook, 整数型 函数成功返回(TRUE)真,否则返回(FALSE)假。 3、一些运行机制: 在Win16环境中,DLL的全局数据对每个载入它的进程来说都是相同的;而在Win32 环境中,情况却发生了变化,DLL函数中的代码所创建的任何对象(包括变量)都 归调用它的线程或进程所有。当进程在载入DLL时,操作系统自动把DLL地址映射 到该进程的私有空间,也就是进程的虚拟地址空间,而且也复制该DLL的全局数据 的一份拷贝到该进程空间。也就是说每个进程所拥有的相同的DLL的全局数据,它 们的名称相同,但其值却并不一定是相同的,而且是互不干涉的。 因此,在Win32环境下要想在多个进程中共享数据,就必须进行必要的设置。在访 问同一个Dll的各进程之间共享存储器是通过存储器映射文件技术实现的。也可以 把这些需要共享的数据分离出来,放置在一个独立的数据段里,并把该段的属性设置为共享。必须给这些变量赋初值,否则编译器会把没有赋初始值的变量放在一个叫未被初始化的数据段中。 #pragma data_seg预处理指令用于设置共享数据段。例如: #pragma data_seg("SharedDataName") HHOOK hHook=NULL; #pragma data_seg() 在#pragma data_seg("SharedDataName")和#pragma data_seg()之间的所有变量将被访问该Dll的所有进程看到和共享。再加上一条指令#pragma comment(linker,"/section:.SharedDataName,rws"),那么这个数据节中的数据可以在所有DLL的实例之间共享。所有对这些数据的操作都针对同一个实例的,而不是在每个 进程的地址空间中都有一份。 当进程隐式或显式调用一个动态库里的函数时,系统都要把这个动态库映射到这个进程的虚拟地址空间里(以下简称"地址空间")。这使得DLL成为进程的一部分,以 这个进程的身份执行,使用这个进程的堆栈。 4、系统钩子与线程钩子: SetWindowsHookEx()函数的最后一个参数决定了此钩子是系统钩子还是线程钩子。 线程勾子用于监视指定线程的事件消息。线程勾子一般在当前线程或者当前线程派生的线程内。 系统勾子监视系统中的所有线程的事件消息。因为系统勾子会影响系统中所有的应用程序,所以勾子函数必须放在独立的动态链接库(DLL) 中。系统自动将包含"钩子回调函数"的DLL映射到受钩子函数影响的所有进程的地址空间中,即将这个DLL注入了那些进程。 几点说明: (1)如果对于同一事件(如鼠标消息)既安装了线程勾子又安装了系统勾子,那么系统会自动先调用线程勾子,然后调用系统勾子。 (2)对同一事件消息可安装多个勾子处理过程,这些勾子处理过程形成了勾子链。当前勾子处理结束后应把勾子信息传递给下一个勾子函数。 (3)勾子特别是系统勾子会消耗消息处理时间,降低系统性能。只有在必要的时候才安装勾子,在使用完毕后要及时卸载。 -------------------------------------------------------------------------------- 钩子类型 每一种类型的Hook可以使应用程序能够监视不同类型的系统消息处理机制。下面描述所有可以利用的Hook类型。 1、WH_CALLWNDPROC(4)和WH_CALLWNDPROCRET Hooks(12) WH_CALLWNDPROC和WH_CALLWNDPROCRET Hooks使你可以监视发送到窗口过程的消息。系统在消息发送到接收窗口过程之前调用WH_CALLWNDPROC Hook子程,并且在窗口过程处理完消息之后调用WH_CALLWNDPROCRET Hook子程。 WH_CALLWNDPROCRET Hook传递指针到CWPRETSTRUCT结构,再传递到Hook子程。 CWPRETSTRUCT结构包含了来自处理消息的窗口过程的返回值,同样也包括了与这个消息关联的消息参数。 2、WH_CBT(5) Hook 在以下事件之前,系统都会调用WH_CBT Hook子程,这些事件包括: 1. 激活,建立,销毁,最小化,最大化,移动,改变尺寸等窗口事件; 2. 完成系统指令; 3. 来自系统消息队列中的移动鼠标,键盘事件; 4. 设置输入焦点事件; 5. 同步系统消息队列事件。 Hook子程的返回值确定系统是否允许或者防止这些操作中的一个。 3、WH_DEBUG(9) Hook 在系统调用系统中与其他Hook关联的Hook子程之前,系统会调用WH_DEBUG Hook子程。你可以使用这个Hook来决定是否允许系统调用与其他Hook关联的Hook子程。 4、WH_FOREGROUNDIDLE(11) Hook 当应用程序的前台线程处于空闲状态时,可以使用WH_FOREGROUNDIDLE Hook执行低优先级的任务。当应用程序的前台线程大概要变成空闲状态时,系统就会调用WH_FOREGROUNDIDLE Hook子程。 5、WH_GETMESSAGE(3) Hook 应用程序使用WH_GETMESSAGE Hook来监视从GetMessage or PeekMessage函数返回的消息。你可以使用WH_GETMESSAGE Hook去监视鼠标和键盘输入,以及其他发送到消息队列中的消息。 6、WH_JOURNALPLAYBACK(1) Hook WH_JOURNALPLAYBACK Hook使应用程序可以插入消息到系统消息队列。可以使用这个Hook回放通过使用WH_JOURNALRECORD Hook记录下来的连续的鼠标和键盘事件。只要WH_JOURNALPLAYBACK Hook已经安装,正常的鼠标和键盘事件就是无效的。 WH_JOURNALPLAYBACK Hook是全局Hook,它不能象线程特定Hook一样使用。 WH_JOURNALPLAYBACK Hook返回超时值,这个值告诉系统在处理来自回放Hook当前消息之前需要等待多长时间(毫秒)。这就使Hook可以控制实时事件的回放。 WH_JOURNALPLAYBACK是system-wide local hooks,它們不會被注射到任何行程位址空間。 7、WH_JOURNALRECORD(0) Hook WH_JOURNALRECORD Hook用来监视和记录输入事件。典型的,可以使用这个Hook记录连续的鼠标和键盘事件,然后通过使用WH_JOURNALPLAYBACK Hook来回放。 WH_JOURNALRECORD Hook是全局Hook,它不能象线程特定Hook一样使用。 WH_JOURNALRECORD是system-wide local hooks,它們不會被注射到任何行程位址空間。 8、WH_KEYBOARD(2) Hook 在应用程序中,WH_KEYBOARD Hook用来监视WM_KEYDOWN and WM_KEYUP消息,这些消息通过GetMessage or PeekMessage function返回。可以使用这个Hook来监视输入到消息队列中的键盘消息。 9、WH_KEYBOARD_LL(13) Hook WH_KEYBOARD_LL Hook监视输入到线程消息队列中的键盘消息。 10、WH_MOUSE(7) Hook WH_MOUSE Hook监视从GetMessage 或者 PeekMessage 函数返回的鼠标消息。使用这个Hook监视输入到消息队列中的鼠标消息。 11、WH_MOUSE_LL(14) Hook WH_MOUSE_LL Hook监视输入到线程消息队列中的鼠标消息。 12、WH_MSGFILTER(-1) 和 WH_SYSMSGFILTER(6) Hooks WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以监视菜单,滚动条,消息框,对话框消息并且发现用户使用ALT+TAB or ALT+ESC 组合键切换窗口。WH_MSGFILTER Hook只能监视传递到菜单,滚动条,消息框的消息,以及传递到通过安装了Hook子程的应用程序建立的对话框的消息。WH_SYSMSGFILTER Hook监视所有应用程序消息。 WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以在模式循环期间过滤消息,这等价于在主消息循环中过滤消息。 通过调用CallMsgFilter function可以直接的调用WH_MSGFILTER Hook。通过使用这个函数,应用程序能够在模式循环期间使用相同的代码去过滤消息,如同在主消息循环里一样。 13、WH_SHELL Hook(10) 外壳应用程序可以使用WH_SHELL Hook去接收重要的通知。当外壳应用程序是激活的并且当顶层窗口建立或者销毁时,系统调用WH_SHELL Hook子程。 WH_SHELL 共有5钟情況: 1. 只要有个top-level、unowned 窗口被产生、起作用、或是被摧毁; 2. 当Taskbar需要重画某个按钮; 3. 当系统需要显示关于Taskbar的一个程序的最小化形式; 4. 当目前的键盘布局状态改变; 5. 当使用者按Ctrl+Esc去执行Task Manager(或相同级别的程序)。 按照惯例,外壳应用程序都不接收WH_SHELL消息。所以,在应用程序能够接收WH_SHELL消息之前,应用程序必须调用SystemParametersInfo function注册它自己。 ======================================================================================== 呵呵,有点昏昏的感觉吗?不要紧的,多看几次就会好的! 好了,这期的勾子基本概念就算讲完了,让我们来总结一下: 1.钩子的基本概念及作用 钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。 2.使用API函数SetWindowsHookEx()安装钩子. 在易中则这样声明DLL: .DLL命令 SetWindowsHookExA, 整数型, , "SetWindowsHookExA" .参数 idHook, 整数型 .参数 lpfn, 子程序指针 .参数 nMod, 整数型 .参数 dwThreadID, 整数型 3.用API函数CallNextHookEx来传递钩子 .DLL命令 CallNextHookEx, 整数型, , "CallNextHookEx" .参数 hhook, 整数型 .参数 nCode, 整数型 .参数 wParam, 整数型 .参数 lParam, 整数型 4.用API函数UnHookWindowsHookEx()来卸载钩子 .DLL命令 api_UnhookWindowsHookEx, 逻辑型, , "UnhookWindowsHookEx" .参数 hhook, 整数型 我们拿最常用的上面的第5个WH_GETMESSAGE(3) Hook 来说明一下: 看看下面这段易代码你就会明白的: hMod = LoadLibraryA (取运行目录 () + “\HookDLL.dll” ) '装载动态链接库 lpProc =GetProcAddress (hMod, “GetMsgProc”) '定位钩子回调函数函数 hhook = SetWindowsHookExA (#WH_GETMESSAGE, lpProc, hMod, 0) '安装钩子 好了,这期的理论知识就到这了,请关注下期的精彩内容. 下期预告: 下期我们将用易源码实例来讲解,怎样用钩子技术和内存文件映射共享技术来 实现远程线程插入资源管理器进程(explorer.exe), 敬请期待..... end 上一期我们讲了勾子基本概念和一些简单的应用 这一期我们就来学习用用钩子技术和内存文件映射共享技术来实现远程线程插入 现在网上关于这项编程技术的介绍满天飞,因为要想写出一个好的后门,该后门至少要达到高隐蔽. 防查杀,无端口,自启动等要求,而将木马以DLL的形式嵌入到系统进程中,基本上可以满足要求,而 这种远程线程注入技术也成为现代后门和木马程序的一项标准技术指标. 如果大家要想更为清晰地掌握该项编程技术,强烈推荐细读jeffery Richter的<<Windows核心技术>>. 该书个人觉得是每个学习Windows黑客编程技术爱好者的圣经. 不过,由于易语言出来的时间不长,网上用易语言具体实现这项编程技术的资料廖廖无几 今天,我们就来用易语言来实现远程线程插入: 大家知道,传统的远程线程插入是通过以下几个API来完成的; ·OpenProcess - 用于打开要寄生的目标进程。 ·VirtualAllocEx/VirtualFreeEx - 用于在目标进程中分配/释放内存空间。 ·WriteProcessMemory - 用于在目标进程中写入要加载的DLL名称。 ·CreateRemoteThread - 远程加载DLL的核心内容,用于控制目标进程调用API函数。 ·LoadLibrary - 目标进程通过调用此函数来加载病毒DLL。 这种方法虽然好,但有个缺点:只能在NT核心的系统上有效,在98中无效 并且由于易DLL的特殊性,上面的方法并不奏效,虽然可以用写入汇编码来解决问题 但也较不方便,钩子的出现为我们解决了这个难题 通过钩子实现远程线程插入的思路如下: 通过安装windows 消息钩子WH_GETMESSAGE,把待插线程代码所在的DLL注入到其他进程里 在钩子回调函数中,判断当前进程ID是否是要插入的进程ID,如果是则创建一个新线程 这个新线程函数就是我们要执行的代码所在的函数,到这里也就达到了我们的目地. 现在就产生了一个新问题,由于我们的要执行的代码是放在一个DLL里面的,创建新线程就需要加载 这个DLL,就需要知道DLL路径,还有判断当前进程ID是否是要插入的进程ID,首先也要知道要插入的 进程ID是多少等等这些信息,这就涉及到进程通讯,我们可以用文件映射技术来进行进程通讯. 文件映射主要是通过以下几个API来完成的: *CreateFileMapping //创建文件映射对象 ,成功返回文件映射对象句柄 Dll命令名:CreateFileMapping 所处动态链接库的文件名:kernel32 在所处动态链接库中的命令名:CreateFileMappingA 返回值类型:整数型 参数<1>的名称为“文件映射句柄”,类型为“整数型”。注明:指定欲在其中创建映射的一个文 件句柄。&HFFFFFFFF&(-1)表示在内存中创建一个文件映射。 参数<2>的名称为“安全对象”,类型为“SECURITY_ATTRIBUTES”。注明:SECURITY_ATTRIBUTES 指定一个安全对象,在创建文件映射时使用。如果为NULL(用ByVal As Long传递零), 表示使用默认安全对象。 参数<3>的名称为“打开映射方式”,类型为“整数型”。注明:下述常数之一:;PAGE_READONLY 以只读方式打开映射;PAGE_READWRITE:以可读、可写方式打开映射;PAGE_WRITECOPY:为 写操作留下备份可组合使用下述一个或多个常数;SEC_COMMIT:为文件映射一个小节中的 所有页分配内存;SEC_IMAGE:文件是个可执行文件;SEC_RESERVE:为没有分配实际内存的 一个小节保留虚拟内存空间 参数<4>的名称为“文件映射最大长度”,类型为“整数型”。注明:文件映射的最大长度(高32位 )。 参数<5>的名称为“文件映射的最小长度”,类型为“整数型”。注明:文件映射的最小长度(低32 位)。如这个参数和dwMaximumSizeHigh都是零,就用磁盘文件的实际长度。 参数<6>的名称为“映射对象名”,类型为“文本型”。注明:指定文件映射对象的名字。如存在这 个名字的一个映射,函数就会打开它。用vbNull创建一个无名的文件映射;。 *OpenFileMappingA //打开一个已存在的文件映射对象,成功返回打开的文件映射对象句柄 Dll命令名:OpenFileMapping 公开 所处动态链接库的文件名:kernel32 在所处动态链接库中的命令名:OpenFileMappingA 返回值类型:整数型 参数<1>的名称为“常数”,类型为“整数型”。注明:带有前缀FILE_MAP_???的一个常数。参考 MapViewOfFile函数的dwDesiredAccess参数的说明。 参数<2>的名称为“进程继承”,类型为“整数型”。注明:如这个函数返回的句柄能由当前进程启 动的新进程继承,则这个参数为TRUE。 参数<3>的名称为“文件映射对象名称”,类型为“文本型”。注明:指定要打开的文件映射对象名 称;。 *MapViewOfFile /将一个文件映射对象映射到当前应用程序的地址空间 Dll命令名:MapViewOfFile 将一个文件映射对象映射到当前应用程序的地址空间。MapViewOfFileEx允许我们指定一个基本地址 来进行映射文件映射在内存中的起始地址。零表示出错。会设置GetLastError 所处动态链接库的文件名:kernel32 在所处动态链接库中的命令名:MapViewOfFile 返回值类型:整数型 参数<1>的名称为“hFileMappingObject”,类型为“整数型”。 参数<2>的名称为“dwDesiredAccess”,类型为“整数型”。 参数<3>的名称为“dwFileOffsetHigh”,类型为“整数型”。 参数<4>的名称为“dwFileOffsetLow”,类型为“整数型”。 参数<5>的名称为“dwNumberOfBytesToMap”,类型为“整数型”。 通过以上API再结合易自带的写到内存(),指针到字节集(),指针到文本(),取字节集数据()等命令就可以 在进程间读写数据了. 内存文件映射技术就介绍到这里,在下面的实例中有具体应用,我就不多说了. 以资源管理器进程(explorer.exe)为例,我们来开始解析程序: 程序基本原理: start.exe 先在内存中创建一个映射文件,把自己的线程ID和查找到的 Explorer进程ID 以及HookDL.dll的路径写到映射文件,再安装 HookDL.dll 中的 WH_GETMESSAGE 钩子, 此时,start.exe进入消息循环,直到收到被插进程发来的线程退出消息WM_QUIT 在钩子回调函数中,首先把start.exe在内存中创建的映射文件映射到当前进程,然后 判断当前进程ID是否先前 Start.exe 查找到的 Explorer进程ID, 是的话,则 再次LoadLibrary(HookDLL.dll),并定位到其中ThreadPro函数. 此时创建一个 新线程,线程函数就是ThreadPro,该新线程首先往Start.exe消息队列放置一个线 程退出消息 WM_QUIT,导致其消息循环结束. 此时插入线程完成..可以看到屏幕左上角不断变化的数字..说明我们的代码正在执行. 进程列表却没有Start.exe,用进程管理观察,可发现Explorer进程,的确多了个线程,且来自 HookDLL.dll ..如果希望插入Explorer的线程结束,按 Alt+L 即可... :) 现在我们来看看主程序代码,为照顾新手,我会逐行分析: .版本 2 '系统核心支持库 .应用接口支持库 .程序集 窗口程序集1 .程序集变量 Explorer_PID, 整数型 .程序集变量 hhook, 整数型 .程序集变量 FileMapH, 整数型 .程序集变量 DLLPath, 文本型 .子程序 __启动窗口_创建完毕 .局部变量 nil, SECURITY_ATTRIBUTES .局部变量 TheNodeP, 整数型 .局部变量 ThreadMessage, MSG .局部变量 MainPath, 字节集 .局部变量 ExplorerID, 字节集 .局部变量 MainThread, 字节集 .局部变量 Mainhhook, 字节集 ' 指定DLL路径 DLLPath = 取运行目录 () + “\HookDLL.dll” ' 检查插入线程是否已经存在 FileMapH = api_OpenFileMapping (#FILE_MAP_ALL_ACCESS, 0, “hacker0058Explorer8Mazi”) api_CloseHandle (FileMapH) .如果真 (FileMapH = 0) '如果插入线程不存在,开始插入 Explorer_PID = 取进程PID (“explorer.exe”) ' 这里指定要插入的进程,用到了子程序:取进 程PID() .如果真 (Explorer_PID = 0) '查找explorer失败 信息框 (“寻找指定进程出错!”, 0, ) 结束 () .如果真结束 '创建内存映射文件 FileMapH = api_CreateFileMapping (-1, nil, #PAGE_READWRITE, 0, 100,“HookExplorer8Mazi ”) '映射到本进程空间 TheNodeP = api_MapViewOfFile (FileMapH, #FILE_MAP_ALL_ACCESS, 0, 0, 0) '写入共享数据 ExplorerID = 到字节集 (Explorer_PID) MainThread = 到字节集 (当前线程标志符_ ()) MainPath =到字节集 (DLLPath) + { 0, 0, 0 } ' 字符串是以两字节的0为结束标志的,所以这 里加上{ 0, 0, 0 } 写到内存 (ExplorerID + MainThread + MainPath, TheNodeP, ) '关闭内存映射 API_UnmapViewOfFile (TheNodeP) '挂DLL跳板钩子 hMod = GetMsgHookOn () .如果真 (hMod = 0) 输出调试文本 (“挂DLL跳板钩子失败!”) api_CloseHandle (FileMapH) ' 关闭映射文件 结束 () .如果真结束 '等待插入Explorer的新线程发来消息 api_GetMessage (ThreadMessage, 0, 0, 0) '脱DLL跳板钩子 GetMsgHookOff () ' 御载DLL api_FreeLibrary (hMod) ' 关闭映射文件 api_CloseHandle (FileMapH) .如果真结束 结束 () .子程序 取进程PID, 整数型, , 成功返回进程PID,失败返回0 .参数 标志文本, 文本型, , 进程名或窗口名 .局部变量 进程, 进程信息, , "0" .局部变量 PID, 整数型 .局部变量 i, 整数型 PID = 0 .如果真 (倒找文本 (标志文本, “.”, , 真) ≠ -1) 进程 =取系统进程列表 () .计次循环首 (取数组成员数 (进程), i) .如果真 (倒找文本 (进程 [i].进程名称, 标志文本, , 真) ≠ -1) PID = 进程 [i].进程标识符 跳出循环 () .如果真结束 .计次循环尾 () .如果真结束 .如果真 (PID = 0) API_取进程标识符 (api_FindWindow (0, 标志文本), PID) .如果真结束 返回 (PID) 现在主程序已经完成了它的任务,现在来看看我们执行的核心,HookDLL.dll的代码: .版本 2 .全局变量 TheNodeP, 整数型 .全局变量 hhook, 整数型, 公开 .全局变量 DLL, Main .程序集 HOOK程序集 .程序集变量 hMod, 整数型 .程序集变量 lpProc, 子程序指针 .程序集 DLL程序集 .版本 2 .子程序 _启动子程序, 整数型, , 请在本子程序中放置动态链接库初始化代码 复制共享数据 () '初始化代码,只有在程序第一次加载DLL时才被执行 _临时子程序 () 返回 (0) '先看看钩子回调函数: .子程序 GetMsgProc, 整数型, 公开, 钩子回调函数 .参数 code, 整数型 .参数 wParam, 整数型 .参数 lParam, 整数型 .局部变量 lpThreadA, SECURITY_ATTRIBUTES .局部变量 LibraryH, 整数型 .局部变量 ThreadPt, 整数型 .局部变量 ThreadID, 整数型 ' 截获到消息,开始执行下面的自定以代码(回调函数) 复制共享数据 () .如果真 (TheNodeP ≠ 0 且 pnode.ExplorerID ≠ 0 且 api_GetCurrentProcessId () = pnode.ExplorerID) ' 是资源管理器 .如果真 (倒找文本 (MainPath, “.dll”, , 真) ≠ -1) ' DLL路径是否正确 LibraryH = api_LoadLibraryA (MainPath) ' 装载动态链接库 .如果真结束 .如果真 (LibraryH ≠ 0) ThreadPt = 到整数 (api_GetProcAddress (LibraryH, “ThreadPro”)) ' 定位线程函数 .如果真 (ThreadPt ≠ 0) api_CreateThread (lpThreadA, 0, ThreadPt, 0, 0, ThreadID) ' 创建新线程 .如果真结束 .如果真结束 .如果真结束 返回 (api_CallNextHookEx (hhook, code, wParam, lParam)) ' 钩子循环 再看看子程序:复制共享数据 .子程序 复制共享数据, 逻辑型 .局部变量 i, 整数型 .局部变量 MainPath, 文本型 .局部变量 FileMapH, 整数型 FileMapH = api_OpenFileMapping (#FILE_MAP_ALL_ACCESS, 0, “HookExplorer8Mazi”) .如果真 (FileMapH = 0) 输出调试文本 (“打开内存映射文件出错!”) 返回 (假) .如果真结束 TheNodeP = api_MapViewOfFile (FileMapH, #FILE_MAP_ALL_ACCESS, 0, 0, 0) .如果真 (TheNodeP = 0) api_CloseHandle (FileMapH) 输出调试文本 (“映射到本进程空间出错!”) 返回 (假) .如果真结束 '取回共享数据 DLL.ExplorerID = 取字节集数据 (指针到字节集 (TheNodeP, 4), 3, )'整数型数据尺寸大小是4 DLL.MainThread = 取字节集数据 (指针到字节集 (TheNodeP + 4, 4), 3, ) '+4 DLL.MainPath = 指针到文本 (TheNodeP + 8) '+8 API_UnmapViewOfFile (TheNodeP) ' 关闭内存映射 api_CloseHandle (FileMapH) ' 关闭文件映射对象 返回 (真) '钩子回调函数完成,现在开始写待插线程代码,这里实现写文字到屏幕的功能 .版本 2 .子程序 ThreadPro, , 公开, 待插线程代码 .局部变量 Count, 整数型 .局部变量 theMsg, MSG .局部变量 FileMap, 整数型 .局部变量 nil, SECURITY_ATTRIBUTES .局部变量 HotKeyID, 整数型 api_PostThreadMessage (DLL.MainThread, #WM_QUIT, 0, 0) FileMap = api_OpenFileMapping (#FILE_MAP_ALL_ACCESS, 0, “hacker0058Explorer8Mazi”) ' 禁 止重复运行 api_CloseHandle (FileMap) .如果真 (FileMap = 0) FileMap = api_CreateFileMapping (-1, nil, 4, 0, 2, “hacker0058Explorer8Mazi”) Count = 0 HotKeyID =GlobalAddAtom(“hacker0058andALT+L”) ' 申请全局原子 RegisterHotKey (0, HotKeyID, 1, #L键) '注册热键Alt+L .判断循环首 (api_PeekMessage (theMsg, 0, 0, 0, #PM_REMOVE) = 0 且 取反 (theMsg.message = #WM_HOTKEY) 或 theMsg.message = #WM_QUIT) WriteScreen (“ ” + Int2Hex (Count) + “ [The Thread is From ” + 取执行文件 名 () + “ and Alt+L to Exit ”) Count = Count + 1 延时 (500) .判断循环尾 () api_CloseHandle (FileMap) UnregisterHotKey(0, HotKeyID) '撤销热键Alt+L DeleteAtom (HotKeyID) '释放全局原子 .子程序 WriteScreen, , , 写文字到屏幕 .参数 s, 文本型 .局部变量 hScreenDC, 整数型 hScreenDC = 取设备场景_ (0) api_TextOut (hScreenDC, 0, 0, s, 取文本长度 (s)) '写文字到屏幕 api_ReleaseDC (0, hScreenDC) '撤消写文字到屏幕 .子程序 Int2Hex, 文本型, , 数值转字符串 .参数 v, 整数型 .局部变量 i, 整数型 .局部变量 a, 文本型 .局部变量 b, 文本型 b = 取十六进制文本 (v) ' .计次循环首 (8 - 取文本长度 (b), i) a = a + “0” .计次循环尾 () 返回 (“$” + a + b) .子程序 GetMsgHookOff, 逻辑型, 公开, 关闭全局消息钩子 返回 (api_UnhookWindowsHookEx (hhook)) .子程序 GetMsgHookOn, 整数型, 公开, 安装全局消息钩子 hMod = api_LoadLibraryA (MainPath) '加载DLL,返回模块地址 lpProc = api_GetProcAddress (hMod, “GetMsgProc”) 'DLL中钩子回调函数地址 hhook = api_SetWindowsHookExA (#WH_GETMESSAGE, lpProc, hMod, 0) '安装钩子 返回 (hMod) 好了,整个框架就这样了,由于篇幅原因,具体的不再细说了.附件里有完整的易程序源码,具体细节大家可 以参阅,源码在Windows xp+sp2 易4.02中测试通过. 下期预告: 下一期我们将介绍API Hook技术,API Hook技术应用广泛,常用于屏幕取词,网络防火墙,病毒木马,加壳 软件,串口红外通讯,游戏外挂,internet通信等领域.API HOOK的中文意思就是钩住API,对API进行预处理 ,先执行我们的函数,例如我们用API Hook技挂接ExitWindowsEx API函数,使关机失效等等...... 敬请期待 上一期,我们讲了用HOOK技术实现远程线程插入,相信大家还记忆犹新. 这一期我们来谈谈 API HOOK API Hook技术应用广泛,常用于屏幕取词,网络防火墙,病毒木马,加壳软件,串口红外通讯,游戏外 挂,internet通信等领域API HOOK的中文意思就是钩住API,对API进行预处理,先执行我们的函数,例 如我们用API Hook技术挂接ExitWindowsEx API函数,使关机失效,挂接ZwOpenProcess函 数,隐藏进程等等...... 总的来说,常用的挂钩API方法有以下两种: <一>改写IAT导入表法 我们知道,Windows下的可执行文档的文件格式是一种叫PE(“portable executable”,可移植 的可执行文件)的文件格式,这种文件格式 是由微软设计的,接下来这张图描述了PE文件的结构: +-------------------------------+ - offset 0 | MS DOS标志("MZ") 和 DOS块 | +-------------------------------+ | PE 标志 ("PE") | +-------------------------------+ | .text | - 模块代码 | 程序代码 | | | +-------------------------------+ | .data | - 已初始化的(全局静态)数据 | 已初始化的数据 | | | +-------------------------------+ | .idata | - 导入函数的信息和数据 | 导入表 | | | +-------------------------------+ | .edata | - 导出函数的信息和数据 | 导出表 | | | +-------------------------------+ | 调试符号 | +-------------------------------+ 这里对我们比较重要的是.idata部分的导入地址表(IAT)。这个部分包含了导入的相关信息和导 入函数的地址。有一点很重要的是我们必须知道PE文件是如何创建的。当在编程语言里间接调用任 意API(这意味着我们是用函数的名字来调用它,而不是用它的地址),编译器并不直接把调用连接到 模块,而是用jmp指令连接调用到IAT,IAT在系统把进程调入内存时时会由进程载入器填满。这就是 我们可以在两个不同版本的Windows里使用相同的二进制代码的原因,虽然模块可能会加载到不同的 地址。进程载入器会在程序代码里调用所使用的IAT里填入直接跳转的jmp指令。所以我们能在IAT里 找到我们想要挂钩的指定函数,我们就能很容易改变那里的jmp指令并重定向代码到我们的地址。完 成之后每次调用都会执行我们的代码了。 我们通过使用imagehlp.dll里的ImageDirectoryEntryToData来很容易地找到IAT。 .DLL命令 ImageDirectoryEntryToData, 整数型, "imagehlp", , , 返回IMAGE_IMPORT_DESCRIPTOR数组的首地址 .参数 Base, 整数型, , 模块句柄 .参数 MappedAsImage, 逻辑型, , 真 .参数 DirectoryEntry, 整数型, , 恒量:IMAGE_DIRECTORY_ENTRY_IMPORT,1 .参数 Size, 整数型, 传址, IMAGE_IMPORT_DESCRIPTOR数组的大小 .数据类型 IMAGE_IMPORT_DESCRIPTOR, , 输入描述结构 .成员 OriginalFirstThunk, 整数型, , , 它是一个RVA(32位),指向一个以0结尾的、由IMAGE_THUNK_DATA(换长数据)的RVA构成的数 组,其每个IMAGE_THUNK_DATA(换长数据)元素都描述一个函数。此数组永不改变。 .成员 TimeDateStamp, 整数型, , , 它是一个具有好几个目的的32位的时间日期戳. .成员 ForwarderChain, 整数型, , , 它是输入函数列表中第一个中转的、32位的索引。 .成员 Name, 整数型, , , 它是一个DLL文件的名称(0结尾的ASCII码字符串)的、32位的RVA。 .成员 FirstThunk, 整数型, , , 它也是一个RVA(32位),指向一个0结尾的、由IMAGE_THUNK_DATA(换长数据)的RVA构成的数组,其每 个IMAGE_THUNK_DATA(换长数据)元素都描述一个函数。此数组是输入地址表的一部分,并且可以改变。 如果我们找到了就必须用VirtualProtectEx函数来改变内存页面的保护属性,然后就可以通过 WriteProcessMemory在内存中的这些部分写入代码了。在改写了地址之后我们要把保护属性改回来 。在调用VirtualProtectEx之前我们还要先知道有关页面的信息,这通过VirtualQueryEx来实现。 这种方法的好处是比较稳定,但有漏API的可能,因为并不是所有的API调用都是通过IAT的,可能易 程序就是这种情况,我当初也是想用这种方法,但是在易里面调试时总不能在IAT里得到正确的程序调 用的API,常常是些无关的API(可能是易的核心支持库在做怪),不得不放弃. <二>直接改写API函数入口点.(改写内存地址JMP法) 改写函数入口点开始的一些字节这种方法相当简单,这也是本文采用的方法,就象改变IAT里的地址一样,我们也要先修改页面属性。 .DLL命令 返回页面虚拟信息, 整数型, "kernel32", "VirtualQueryEx" .参数 hProcess, 整数型, , 对象的进程句柄,可以使用函数 OpenProcess() 返回。 .参数 lpAddress, 整数型, , 对象指针地址 .参数 lpBuffer, 虚拟信息, , 返回的虚拟信息 .参数 dwLength, 整数型, , 信息长度,已知 28 .DLL命令 修改页面虚拟保护, 逻辑型, "kernel32", "VirtualProtectEx" .参数 hProcess, 整数型, , 对象的进程句柄,可以使用函数 OpenProcess() 返回。 .参数 lpAddress, 整数型, , 虚拟信息.BaseAddress .参数 dwSize, 整数型, , 修改虚拟保护的长度. .参数 flNewProtect, 整数型, , 修改类型,#PAGE_EXECUTE_READWRITE 64为可读写模式 .参数 lpflOldProtect, 整数型, 传址, 虚拟信息.Protect 通过调用VirtualQueryEx,VirtualProtectEx这两个API就可以修改我们要挂勾的API所在的页面属性为可读写模式,接着就可以调用API函数 WriteProcessMemory写内存字节了. 到这里,也就没有什么难点了,我们拿API函数ExitWindowsEx来简单说明下,如果要实现自定API函 数,可以写入一个跳转指令,JMP OX00000(其中OX00000为自定API函数地址),请参照论坛上的教程,也 当作我留给大家的问题吧,呵呵. 制作目标:挂勾ExitWindowsEx,使关机无效. 分析:只要求关机无效,即ExitWindowsEx无效就可以了,我们不必那么麻烦去写跳转指令,可以直接在 ExitWindowsEx首地址写入一个垃圾指令,使它后面的代码无法执行就可以了,我们可以写入 {104}→汇编 PUSH 或者 {195}→汇编 retn→返回命令(在易里面调用这个API会造成错误)这 样就可以达到目地了. 看下面的代码: =================== '通过API函数GetProcAddress和GetModuleHandleA很容易得到API函数ExitWindowsEx的首地址. API = GetProcAddress(GetModuleHandleA(“user32.dll”), “ExitWindowsEx”) API_BAK = 指针到字节集 (API, 1) '为了以后可以还原,我们先备份一下 '用OpenProcess打开其他进程前先提升进程权限,这样可以打开几乎所有的非内核程序 提升进程权限 () '这个模块在附件里 ==============================修改API首地址()================= .子程序 修改API首地址, 逻辑型 .参数 Process, 整数型, , 目标进程句柄 .参数 Papi, 整数型, , 要修改的API函数地址 .参数 type, 字节集, , 要改写的内容,字节集 .局部变量 mbi, 虚拟信息 .局部变量 结果, 逻辑型 .局部变量 MyAPI, 整数型 .局部变量 Ptype, 字节集 .如果真 (Papi = 0) 返回 (假) .如果真结束 .如果真 (返回虚拟信息 (Process, Papi, mbi, 28) = 0) 返回 (假) .如果真结束 .如果真 (修改虚拟保护 (Process, mbi.BaseAddress, 取字节集长度 (type) + 1, #PAGE_EXECUTE_READWRITE, mbi.Protect) = 假) 返回 (假) .如果真结束 结果 = 写内存字节 (Process, Papi, type, 取字节集长度 (type), 0) 修改虚拟保护 (Process, mbi.BaseAddress, 取字节集长度 (type) + 1, #PAGE_EXECUTE_READ, mbi.Protect) ' 改回只读模式 返回 (结果) ======================== 我们通过以下几个API就可以举出系统所有进程,得到它们的进程ID *CreateToolhelp32Snapshot,创建进程快照 *Process32First,开始进程快照 *Process32Next, 继续进程快照 看代码: =========================刷新进程信息============== .子程序 刷新进程信息 .参数 进程信息输出, 进程信息输出, 数组 .局部变量 临时变量, 整数型 .局部变量 临时变量B, 整数型 .局部变量 线程基本优先级, 整数型, , "0" .局部变量 临时进程信息, 进程信息输出 临时变量 = 创建进程快照 (2, 0) 临时进程信息.结构大小 = 296 临时变量B = 开始进程快照 (临时变量, 临时进程信息) .判断循环首 (临时变量B ≠ 0) 加入成员 (进程信息输出, 临时进程信息) 临时变量B =继续进程快照 (临时变量, 临时进程信息) .判断循环尾 () 关闭内核对象 (临时变量) ============================HOOK_API()================================ .子程序 HOOK_API .参数 是否HOOK, 逻辑型, , 真,表示HOOK,假,表示还原 .局部变量 i, 整数型 刷新进程信息 (进程信息) .计次循环首 (取数组成员数 (进程信息), i) 进程句柄 =打开进程 (2035711, 0, 进程信息 [i].进程标识) .如果 (是否HOOK) 修改API首地址 (进程句柄, API, { 195 }) ' 写入返回命令{195} .否则 修改API首地址 (进程句柄, API, API_BAK) 关闭内核对象 (进程句柄) .计次循环尾 () ================================================================= 最后我们通过调用HOOK_API(真)就可以挂勾ExitWindowsEx, 调用HOOK_API (假)就可以还原ExitWindowsEx. 继续看代码: ====================================== .子程序 _选择框_挂勾API_被单击 .如果 (选择框_挂勾API.标题 = “挂勾API”) HOOK_API (真) 选择框_挂勾API.标题 = “还原” 信息框 (“恭喜,挂勾ExitWindowsEx成功,你现在不能关闭系统了,不信你可以试试!”, 64, ) .否则 HOOK_API (假) 选择框_挂勾API.标题 = “挂勾API” 信息框 (“提示:还原ExitWindowsEx成功,你现在可以关闭系统了!”, 48, “提示”) 结束 () ======================================= 题外话: 有了这个程序,再结合API函数ShowWindow,控制网管软件窗口的可见性,就可以破解几乎所有网管 软件的限制,不过,我不同意你用它拿来破解网管软件,我之所以这么说,只是想告诉大家一个道理: 思维不要只限制到一个很狭隘的空间中去...... ========================================== 总结: 怎么样,是不是很简单啊,呵呵,通过本期的学习,我们的Windows Hook 易核心编程也就告一段落了 自由 平等 随意 突破...... 此文献给所有易的支持者 下次再见. 在开始之前,让我们先来回顾一下: 什么叫Hook API? 所谓Hook就是钩子的意思,而API是指Windows开放给程序员的编程接口,使得在用户级别下可以对操作系统进行控 制,也就是一般的应用程序都需要调用API来完成某些功能,Hook API的意思就是在这些应用程序调用真正的系统API前可 以先被截获,从而进行一些处理再调用真正的API来完成功能。在讲Hook API之前先来看一下如何Hook消息,例如Hook全 局键盘消息,从而可以知道用户按了哪些键.这种Hook消息的功能可以由以下函数来完成,该函数将一新的Hook加入到原 来的Hook链中,当某一消息到达后会依次经过它的Hook链再交给应用程序,这个在我的Windows Hook 易核心编程(1)和(2) 里有具体的说明和应用,大家可以看一看. .DLL命令 api_SetWindowsHookExA, 整数型, , "SetWindowsHookExA" .参数 idHook, 整数型, , Hook类型,例如WH_KEYBOARD,WH_MOUSE .参数 lpfn, 子程序指针, , 钩子回调函数,Hook处理过程函数的地址, .参数 nMod, 整数型, , 包含Hook处理过程函数的dll句柄(若在本进程可以为NULL) .参数 dwThreadID, 整数型, , 要Hook的线程ID,若为0,表示全局Hook所有 .DLL命令api_UnhookWindowsHookEx, 逻辑型, , "UnhookWindowsHookEx" .参数 hhook, 整数型,要关闭钩子的句柄. 这里需要提一下的就是如果是Hook全局的而不是某个特定的进程则需要将Hook过程编写为一个DLL,以便让任何程序 都可以加载它来获取Hook过程函数。而对于Hook API微软并没有提供直接的接口函数,也许它并不想让我们这样做,不过 有2种方法可以完成该功能。第一种,修改可执行文件的IAT表(即输入表),因为在该表中记录了所有调用API的函数地 址,则只需将这些地址改为自己函数的地址即可,但是这样有一个局限,因为有的程序会加壳,这样会隐藏真实的IAT 表,从而使该方法失效。第二种方法是直接跳转,改变API函数的头几个字节,使程序跳转到自己的函数,然后恢复API开 头的几个字节,在调用API完成功能后再改回来又能继续Hook了,但是这种方法也有一个问题就是同步的问题,当然这是 可以克服的,并且该方法不受程序加壳的限制。 上一章我们就是用的第二种方法,通过直接改写API函数ExitWindowsEx入口点为{195}(即汇编的返回命令retn),达到 关机失效的目地,其实这不算是真正的API HOOK,没有实现自定API函数的功能.记得我说过,如果要实现自定API函数,可以写 入一个跳转指令,JMP OX00000(其中OX00000为自定API函数地址),最后还给大家留了一道题,就是参照论坛上的教程写出 自定API函数的功能,不知道大家完成的什么样了,呵呵. 其实,要真正实现API HOOK,用JMP OX00000是不能达到我们的目地的,因为要转移参数,我们先要mov eax OX00000 ,然 后再jmp eax,在易里面用用字节集连起来表示就是: { 184 } + 到字节集 (到整数 (&new_api)) + { 255, 224 } 其中new_api就是新的自定API函数地址,结合上期的内容,我们现在就来解析核心代码: =================================== .子程序 修改API首地址, 逻辑型 .参数 Process, 整数型, , 目标进程句柄 .参数 Papi, 整数型, , 要修改的API函数地址 .参数 type, 字节集, , 要改写的内容,字节集 .局部变量 mbi, 虚拟信息 .局部变量 结果, 逻辑型 .局部变量 MyAPI, 整数型 .局部变量 Ptype, 字节集 .如果真 (Papi = 0) 返回 (假) .如果真结束 .如果真 (返回虚拟信息 (Process, Papi, mbi, 28) = 0) 返回 (假) .如果真结束 .如果真 (修改虚拟保护 (Process, mbi.BaseAddress, 取字节集长度 (type) + 1, #PAGE_EXECUTE_READWRITE, mbi.Protect) = 假) 返回 (假) .如果真结束 结果 = 写内存字节 (Process, Papi, type, 取字节集长度 (type), 0) 修改虚拟保护 (Process, mbi.BaseAddress, 取字节集长度 (type) + 1, #PAGE_EXECUTE_READ, mbi.Protect) ' 改回只读模式 返回 (结果) ================================== 这个子程序也就是上期用到的,上一期第3个参数是写的{195},这里换成 { 184 } + 到字节集 (到 整数 (&new_api)) + { 255, 224 }就可以了,新的API函数要和原API函数的参数一样,还拿API函 数ExitWindowsEx来说,看下面的新的API函数: ====================================== .子程序 new_api, 整数型, , 新的API函数,要与原来的API函数的参数一样 .参数 标志, 整数型 .参数 保留值, 整数型 ' 给自己留条后门先 .如果 (保留值 = 3389) HOOKAPI (假) api_ExitWindowsEx (标志, 保留值) .否则 信息框 (“您的操作已经被易拦截了!” + #换行符 + “ExitWindowsEx” + #换行符 + “参数<1>:” + 到文本 (标志) + #换行符 + “参数<2>:” + 到文本 (保留值), 0, ) .如果结束 返回 (1) ======================================= 看下面的代码: .子程序 HOOKAPI, 逻辑型 .参数 是否HOOK, 逻辑型 .局部变量 TYPE, 字节集 .如果 (是否HOOK) TYPE = { 184 } + 到字节集 (到整数 (&new_ExitWindowsEx)) + { 255, 224 } 返回 (修改API首地址(取当前进程伪句柄 (), API, TYPE)) .否则 返回 (修改API首地址(取当前进程伪句柄 (), API, API_BAK)) 当然,改写前,还是要备份和取API函数地址的: API = 取DLL函数地址 (取程序或DLL句柄 (“user32.dll”), “ExitWindowsEx”) API_BAK = 指针到字节集 (API, 8) ============================================ 这样,只要一句HOOKAPI(真)就可以轻松改写本进程的API函数,一句HOOKAPI(假)就可以还原API函数,当然前提是在 本进程,因为地址不能跨进程使用,要想挂勾其他进程,我们可以使用我前面提到的Windows全局消息勾子,为了取得较好的效 果,推荐使用WH_GETMSSAGE类的勾子.要使用全局消息勾子,先要在一个独立的DLL里声明钩子回调函数,看下面的代码: ============================ 子程序 GetMsgProc, 整数型, 公开, 钩子回调函数 .参数 code, 整数 参数 wParam, 整数型 .参数 lParam, 整数型 返回 (api_CallNextHookEx (hhook, code, wParam, lParam)) ' 钩子循环 ==================================== 主程序通过调用SetWindowsHookEx就可以安装钩子 ,继续看代码; .子程序 安装全局钩子, 整数型, , 安装全局消息钩子 .参数 DLLPath, 文本型,DLL名 .局部变量 hMod, 整数型 .局部变量 lpProc, 子程序指针 hMod = api_LoadLibraryA (DLLPath) lpProc = api_GetProcAddress (hMod, “GetMsgProc”) hook = api_SetWindowsHookExA (#WH_GETMESSAGE, lpProc, hMod, 0) 返回 (hook) ====================================== 主程序通过api_UnhookWindowsHookEx(hook)就可以关闭全局钩子. 结合下面的子程序(上期的HOOK_API)便可以还原API. ============================= .子程序 还原API .局部变量 i, 整数型 .局部变量 结果, 逻辑型 .局部变量 进程信息, 进程信息输出, , "0" .局部变量 进程句柄, 整数型 .局部变量 修改内容, 整数型 刷新进程信息 (进程信息) '这个在上期中讲过,我就不重复了. .计次循环首 (取数组成员数 (进程信息), i) 进程句柄=打开进程 (2035711, 0, 进程信息 [i].进程标识) .如果真 (API_BAK ≠ { 0 }) 结果=修改API首地址 (进程句柄, API, API_BAK) .如果真结束 关闭内核对象 (进程句柄) .计次循环尾 () =========================== 可以看出,我们创建的Hook类型是WH_CALLWNDPROC类型,该类型的Hook在进程与系统一通信时就会被加载到进 程空间,从而调用dll的初始化函数完成真正的Hook,而在SetWindowsHookEx函数中指定的HookProc函数将不作任何处 理,只是调用CallNextHookEx将消息交给Hook链中下一个环节处理,因为这里SetWindowsHookEx的唯一作用就是让进程 加载我们的dll。 整个框架就是这样了,具体的就不在细说了.附件里有完整的易源码,大家可以下载下来研究.以上就是一个最简单的Hook API的例子,该种技术可以完成许多功能。例如网游外挂制作过程中截取发送的与收到的封包(API函数send)即可使用该方 法,或者也可以在Hook到API后加入木马功能,反向连接指定的主机或者监听某一端口,还有许多加壳也是用该原理来隐 藏IAT表,填入自己的函数地址,等等. 希望本文能够对一些朋友有所帮助,当然,一定有许多不足之处,希望看了文章的高手可以指出: 以下程序在Windows XP +sp2和易4.02中测试通过,有什么Bug,请通知我.) |
|