概述
一、HOOK介绍
1、什么是HOOK(钩子)
(1)对于Windows系统,它是建立在事件驱动机制上的,说白了就是整个系统都是通过消息传递实现的。
(2)HOOK技术即钩子函数,钩子函数是Windows消息处理机制的一部分,通过设置“钩子”,应用程序可以在系统级对所有消息、事件进行过滤,访问在正常情况下无法访问的消息。
(3)钩子的本质是一段用以处理系统消息的程序,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息(如屏幕取词,监视日志,截获键盘/鼠标输入等),也可以不作处理而继续传递该消息,还可以强制结束消息的传递。
(4)HOOK技术在windows系统下编程,应该会接触到api函数的使用,常用的api函数大概有2000个左右。今天随着控件,stl等高效编程技术的出现,api的使用概率在普通的用户程序上就变得越来越小了。当诸如控件这些现成的手段不能实现的功能时,我们还需要借助api。
(5)钩子的种类很多,每种钩子可以截获相应的消息,如键盘钩子可以截获键盘消息,外壳钩子可以截取、启动和关闭应用程序的消息等。钩子可以分为线程钩子和系统钩子,线程钩子可以监视指定线程的事件消息,系统钩子监视系统中的所有线程的事件消息。因为系统钩子会影响系统中所有的应用程序,所以钩子函数必须放在独立的动态链接库(DLL) 中。
(6)所以说,hook(钩子)就是一个Windows消息的拦截机制,可以拦截单个进程的消息(线程钩子),也可以拦截所有进程的消息(系统钩子),也可以对拦截的消息进行自定义的处理。Windows消息带了一些程序有用的信息,比如Mouse类信息,就带有鼠标所在窗体句柄、鼠标位置等信息,拦截了这些消息,就可以做出例如金山词霸一类的屏幕取词功能。
总结下:对于新人来说对说了这个词语的人真是崇拜至极,心里默念着牛逼,但其实这是一名程序员应该懂的基本功。其实钩子来源于英文词Hook,在windows系统中,一切皆消息,比如按了一下键盘,也是一个消息。Hook的意思是勾住,也就是在消息过去之前,可以先把消息勾住,不让其传递,你可以优先处理。也即这项技术就是提供了一个入口,能够针对不同的消息或者API在执行前,先执行你的操作,你的操作也称为[钩子函数]。所以,有的时候程序员在讨论的时候,也经常会说,可以先hook住,在处理,也即在执行某某操作之前,优先处理一下。
2、Hook分类
(1)线程钩子监视指定线程的事件消息。
(2)系统钩子监视系统中的所有线程的事件消息。因为系统钩子会影响系统中所有的应用程序,所以钩子函数必须放在独立的动态链接库(DLL)中。这是系统钩子和线程钩子很大的不同之处。
3、HOOK(钩子)
在正确使用钩子函数前,我们先讲解钩子函数的工作原理。当您创建一个钩子时,WINDOWS会先在内存中创建一个数据结构,该数据结构包含了钩子的相关信息,然后把该结构体加到已经存在的钩子链表中去。新的钩子将加到老的前面。当一个事件发生时,如果您安装的是一个线程钩子,您进程中的钩子函数将被调用。如果是一个系统钩子,系统就必须把钩子函数插入到其它进程的地址空间,要做到这一点要求钩子函数必须在一个动态链接库中,所以如果您想要使用系统钩子,就必须把该钩子函数放到动态链接库中去。
当然有两个例外:工作日志钩子和工作日志回放钩子。这两个钩子的钩子函数必须在安装钩子的线程中。原因是:这两个钩子是用来监控比较底层的硬件事件的,既然是记录和回放,所有的事件就当然都是有先后次序的。所以如果把回调函数放在DLL中,输入的事件被放在几个线程中记录,所以我们无法保证得到正确的次序。故解决的办法是:把钩子函数放到单个的线程中,譬如安装钩子的线程。
几点需要说明的地方:
(1)如果对于同一事件(如鼠标消息)既安装了线程钩子又安装了系统钩子,那么系统会自动先调用线程钩子,然后调用系统钩子。
(2) 对同一事件消息可安装多个钩子处理过程,这些钩子处理过程形成了钩子链。当前钩子处理结束后应把钩子信息传递给下一个钩子函数。而且最近安装的钩子放在链的开始,而最早安装的钩子放在最后,也就是后加入的先获得控制权。
(3)钩子特别是系统钩子会消耗消息处理时间,降低系统性能。只有在必要的时候才安装钩子,在使用完毕后要及时卸载。
二、示例
1、LD_PRELOAD劫持.so
(1)前言:
我们用到的函数实现几乎全部来自于glibc,.so文件是glibc编译得到的库,类似于Windows系统下的DLL(动态链接库)。LD_PRELOAD是一个Linux下的动态链接相关的环境变量,它可以影响程序的运行时的链接(Runtime linker);更具体的说这个变量允许你定义在程序运行时优先加载的动态链接库。通过这个环境变量我们可以在主程序和其动态链接库的中间加载别的动态链接库,达到覆盖正常的函数库的目的。一方面,我们可以一次功能使用自己的或者更好的函数。另一方面,通过这种方法也可以向别人的程序注入恶意程序,从而达到不可告人的目的。
为了更好的理解LD_PRELOAD这个环境变量,这里先说下链接。所谓链接,简单来说就是编译器找到程序中所引用的函数或全局变量所存在的位置。一般来说程序的链接分为静态链接和动态链接。静态链接就是把所有所引用到的函数或者变量全部编译到可执行文件中;动态链接则不会把函数编译到可执行文件中,而是在程序运行的时候动态的载入函数库,也就是常说的运行时链接。所以对于动态链接来说,必然需要一个动态链接库。
动态链接库的好处在于一旦动态库中的函数发生变化对于可执行程序来说是透明的,可执行程序无需重新编译。这对于程序的发布、维护、更新起到了积极的作用。对于静态链接的程序来说,函数库中一个小小的改动需要对整个程序进行重新编译、发布,显然可维护性大打折扣。有得就有失,任何事情都有两面性动态链接技术也不例外。动态链接所带来的的坏处和好处同样巨大。因为程序在运行是动态加载函数,这也就为他人创造了影响主程序的机会。试想,你的程序动态载入的函数不是你写的函数,而是未知的别人的函数;通过函数的返回值能控制你的程序的执行流程,就问你怕不怕?对于这种情况通常就认为你的程序被“劫持”了。
我们知道Linux用的都是glibc,其中有一个叫libc.so.6的文件几乎包括Linux动态链接的标准C的各种函数。对于GCC而言默认情况下所编译的程序对标准C函数的链接都是通过动态链接的方式来链接libc.so.6这个函数库的。
(2)LD_PRELOAD劫持实例
1)首先有如下目标文件target.cpp;执行“gcc target.cpp -o target”得到可执行文件 target。
#include <stdio.h>
#include <string.h>
#include <dlfcn.h>
typedef int(*Strcmp)(const char*, const char*);
int strcmp(const char* s1, const char* s2)
{
static void* handle = NULL;
static Strcmp org_strcmp = NULL;
if(!handle)
{
handle = dlopen("libc.so.6", RTLD_LAZY);
org_strcmp = (Strcmp)dlsym(handle, "strcmp");
}
printf("Hacked by way of ld_preloadnnn");
return org_strcmp(s1, s2);
}
2)另外又有如下伪造的.so文件preload.cpp;执行“gcc -fPIC preload.cpp -shared -o preload.so -ldl”编译得到.so文件。
#include <stdio.h>
#include <string.h>
#include <dlfcn.h>
typedef int(*Strcmp)(const char*, const char*);
int strcmp(const char* s1, const char* s2)
{
static void* handle = NULL;
static Strcmp org_strcmp = NULL;
if(!handle)
{
handle = dlopen("libc.so.6", RTLD_LAZY);
org_strcmp = (Strcmp)dlsym(handle, "strcmp");
}
printf("Hacked by way of ld_preloadnnn");
return org_strcmp(s1, s2);
}
3)修改LD_PRELOAD变量
LD_PRELOAD=./preload.so
export LD_PRELOAD
echo $LD_PRELOAD
设置环境变量 LD_PRELOAD 预加载我们编译好的恶意preload.so库。
注:使用如下代码更简易直观
//getuid.c
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main(void)
{
printf("my uid is %dn",getuid());
}
//gcc -o getuid getuid.c
//muid.c
//让恶意的getuid函数返回1000这个固定数值
#include<sys/types.h>
uid_t getuid(void)
{
return 1000;
}
//gcc -shared -lc -fPIC -o muid.so muid.c
LD_PRELOAD="./muid.so"
export LD_PRELOAD
echo $LD_PRELOAD
几个相关链接:
https://zhuanlan.zhihu.com/p/202670441
https://blog.csdn.net/maijian/article/details/40856587
https://www.cnblogs.com/zlgxzswjy/p/10399996.html
最后
以上就是自信煎饼为你收集整理的hook原理介绍与简单实例的全部内容,希望文章能够帮你解决hook原理介绍与简单实例所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复