hook原理介绍与简单实例

 

一、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_preload\n\n\n");
    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_preload\n\n\n");
    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 %d\n",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程序的例子 一、客户端 程序命名为Client。监视系统的运行,如发现系统中有“记事本”进程(notepad.exe)或者“计算器”进程(calc.exe),立即杀死(kill)该进程,并将该事件写入数据库;定期进行检查,每间隔1分钟,检查数据库,将尚未上传的事件记录上传至服务器端。 1、目标运行环境为Windows 2000操作系统。 2、程序请设计为系统服务。 3、程序需具备抗攻击能力,包括反删除、对抗强制终止进程等功能。 (1)保持程序的持续运行,防止其他程序强行终止当前程序的运行; (2)保护事件数据库和主执行文件不被删除; (3)如发现异常(进程被终止、文件被删除等),立即强制重新载入/运行程序; (4)如连续3次发现异常,守护进程强制重新启动操作系统;重启系统后保证程序正常载入并运行。 (A)为实现以上功能,程序不限于以EXE形式实现,可根据需要自行决定实现形式。 (B)以上功能均在Windows 2000正常运行环境、Administrator权限下实现,无需考虑Windows安全模式或者权限等问题。 4、请使用简单桌面数据库,如Access、xBase等文件型数据库。 5、每次生成的事件至少包含2部分信息:事件发生时间和事件处理对象。 数据库中以表tEvent存储事件数据。tEvent表至少包含两个字段: (1)EventTime字段:时间/日期类型。记录事件发生的时间。 (2)EventTarget字段:字符类型。记录事件中所Kill的对象。需要考虑的对象有记事本进程和计算器进程。 如果需要其他表或者字段,可根据需要自行添加。 6、网络数据传输格式自定。传输的具体内容和格式请根据需要自行决定,不做具体要求。客户端网络需与服务器端网络配合工作。 7、所用开发语言与集成开发环境不限,可自行选择。 8、对于数据库连接方式,请根据需要自行选择。 二、服务器端 程序命名为Server。监听网络,一旦有客户端上传数据,立即从中提取事件信息,并在用户界面中以列表方式加以显示。 1、目标运行环境为Windows 2000操作系统。 2、程序请设计为普通Windows 2000 GUI应用程序。在用户界面中至少需包含一个事件信息列表,该列表中至少包含3部分信息:事件发生时间、事件处理对象和事件来源。 (1)事件发生时间:同客户端事件发生时间。 (2)事件处理对象:同客户端事件处理对象。 (3)事件来源:上传当前事件的客户端机器的IP地址。 3、网络数据传输格式自定;与客户端配合工作。 4、所用开发语言与集成开发环境不限,可自行选择。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

焱齿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值