1. hook技术概述   

hook技术是一种拦截用户函数调用的技术。通过hook技术可以实现统计用户对某些函数的调用次数,对函数注入新的功能的目标。在Linux平台,Hook技术可以分成用户和内核两个层面,每个类比中都存在不同的hook技术。本文主要介绍针对动态链接技术的PLT hook。

2. 代码实例

    首先我们先用一个实例来向大家展示一下PLT hook的效果。代码的功能是验证用户在命令行输入的密码,hook的目标是strcmp函数,通过将strcmp函数的返回值置为0,达到无论用户输入任何密码,即使是错误的,都返回验证通过的提示。 
    先编写passwd.c。该代码的作用是调用strcmp函数并判断用户输入的密码是否正确,并打印相应的提示。文件中只有一个函数就是check_is_authenticated。

  其次编写我们的main.c 该函数实现了将要替换strcmp函数的my_strcmp,和使hook生效的hook函数。代码的具体细节

 #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <inttypes.h>
#include <execinfo.h>
#include <sys/user.h>
#include <sys/mman.h>
#include "passwd.h" #define PAGE_SHIFT 12
#define PAGE_SIZE (_AC(1,UL) << PAGE_SHIFT)
#define PAGE_MASK (~(PAGE_SIZE-1)) #define __AC(X,Y) (X##Y)
#define _AC(X,Y) __AC(X,Y) #define PAGE_START(addr) ((addr) & PAGE_MASK)
#define PAGE_END(addr) (PAGE_START(addr) + PAGE_SIZE) int my_strcmp(const char *s1, const char *s2)
{
return ;
} uintptr_t get_base_addr(char *libname)
{
FILE *fp;
char line[];
//char base_addr[1024];
uintptr_t base_addr = ; if (NULL == (fp = fopen("/proc/self/maps", "r"))) {
perror("open err");
return -;
} while (NULL != fgets(line, sizeof(line), fp)) {
if (NULL != strstr(line, libname)) {
sscanf(line, "%"PRIxPTR"-%*lx %*4s 00000000", &base_addr);
printf("line2:%s, base_addr:%"PRIxPTR"\n", line, base_addr);
break;
}
}
fclose(fp); return base_addr;
} void hook() {
uintptr_t base_addr;
uintptr_t addr;
//1. get the base addr of libpasswd.so
base_addr = get_base_addr("libpasswd");
if ( == base_addr) return;
addr = base_addr + 0x201020;
//2. add the write permisson
mprotect((void *)PAGE_START(addr), PAGE_SIZE, PROT_READ|PROT_WRITE);
//3. replace our hook func my_strcmp
*(void **)addr = my_strcmp;
//4. clear the cache
__builtin___clear_cache((void *)PAGE_START(addr),
(void *)PAGE_END(addr));
} int main()
{
hook();
check_is_authenticated("abcd");
return ;
}
  接下来我们将passwd.c编译成动态库libpasswd.so,main.c调用libpasswd.so中的check_is_authenticated函数。我们是用下列gcc命令将passwd.c编译为动态库,其中-fpic是生成位置无关代码,-shared表示我们生成的是一个动态库,不需要main函数的参与。

  在将libpasswd.so放置到/usr/lib/目录中之后,使用以下命令编译main.c

  运行之后我们 可以看到验证结果始终是正确的,说明我们对于libpasswd.so中的strcmp函数的Hook已经生效。

怎么样,是不是很神奇呀!下面我们就来看看plt hook到底是怎么实现的。

3. PLT hook原理

  说了这么久,PLT hook到底是怎么实现的呢?到底什么是PLT呢?下面我就带大家了解以下到底什么是PLT。
    在了解PLT之前我们需要先了解下什么是共享库和动态链接技术。静态链接技术,是以一种将多个可链接目标文件链接为一个独立的可执行文件的过程。在链接的过程中,连接器会将静态库中的函数完整的复制到可执行文件的文本段中。在一个运行较多进程的系统中,这种链接方式对于内存消耗是不可小觑的。如下图所示是两种不同的连接方式生成的可执行文件的大小,可以看到二者相差极大。为了解决这个问题,共享库诞生了,共享库是一个目标模块或者说目标文件,在运行或者加载时,可以加载到内存的任意位置,并和内存中的程序链接起来,这个过程是一个叫做动态链接器的组件完成的。在完成链接的过程中,链接器仅仅复制一些重定位和符号表信息到可执行文件中,大大减小了可执行文件的大小。

   PLT(Procedure Linkage Table)全程过程链接表,主要用于协助程序完成延迟加载的功能,假设程序调用一个动态库中的函数,因为动态库可以被加载到内存中的任意位置,因此我们无法去预测这个函数的运行时地址。正常的做法是为该函数调用生成一个重定位记录,然后动态链接器在程序加载的时候去解析它。但是这并不符合位置无关代码的做法,因为需要链接器修改调用模块的文本段。GNU使用延迟加载的技术去解决这个问题,把对函数地址的解析延迟到了对于函数的实际调用的时刻。使用这种技术,在第一次函数调用时的开销较大,但是在之后的调用中只会花费一条指令和一个间接的内存引用。
   延迟调用的完成需要PLT和GOT(Global Offset Table)全局偏移量表协作完成。如果一个目标模块调用任何在共享库中的函数,那么它就有自己的GOT和PLT。GOT是数据段的一部分,PLT是代码段的一部分。下面我们来看看PLT和GOT表中的内容:
  PLT。PLT表中的每一项是一个16字节代码。PLT[0]是一个特殊条目,它跳转到动态链接器中。PLT[1]是系统启动函数的条目。从第三项开始是用户调用的动态库函数的条目。GOT。GOT中每个表项是8字节的地址类型数据。和PLT类似,GOT[0]和GOT[1]也是特殊条目,包含动态链接器在解析函数地址时会使用的信息。GOT[2]是动态链接器在ld-linux.so模块中的入口点。其余的每一个条目对应一个被调用的函数,其地址在需要时进行解析。每个条目都有一个对应的PLT条目。下面我们还是以一个实例来解释一下延迟调用的过程。
  下列代码是一个动态库libpasswd.so中调用strcmp函数的过程,从下列代码,我们可以看到对于strcmp函数的调用,实际上调用的是PLT中的strcmp条目。strcmp条目实际上是三条汇编代码,让我们这些汇编语句都做了什么。在560行,使用jmpq指令跳转到GOT中strcmp对应的条目,GOT初始时都指向它对应的PLT条目的第二条指令,在这里也就是"pushq $0x0"。在把strcmp的ID(0x0)压入栈中后,使用jmpq指令跳转到PLT[0], 前面说过PLT[0]存储的是动态链接器相关的条目,先使用pushq命令将GOT[1]中存储的动态连接器的一个参数压栈("pushq 0x200ab2(%rip)")。值得注意的是这里的0x200ab2是一个常量,这里用到了位置无关代码(PIC)的相关知识,不再赘述。然后使用jmpq指令,通过GOT[2]中存储的动态链接器的地址间接跳转到动态链接器中,这里要注意代码中"*"的意义,"*"表示获取地址对应的内存,类似指针变量的解引用运算符。在跳转到动态链接器之后,连接器通过我们刚刚压入栈中的两个值(被调用函数的ID, 和GOT[1])来确定strcmp函数的运行时地址,并用这个地址重写strcmp对应的GOT条目,在将控制传递给strcmp函数。

  以上就是第一次调用strcmp函数的流程,可以看出开销还是不小的。在后续的调用中,首先还是跳转到strcmp的plt条目中,然后执行jmpq *0x200aaa(%rip)指令,不同的是这是strcmp对应的GOT条目已经被写入了strcmp的运行时地址,所以这条jmp指令直接将程序的执行跳转到strcmp函数中。那么是不是只要在strcmp对应的GOT条目中写入我们自己的函数的地址,就可以将控制跳转到自己实现的函数中了嘛。本着这样的思路我们来看看开始时的代码。

4. 代码分析

代码分成两部分,一个是被hook的动态库libpasswd.so和调用被hook动态库的文件main.c。这里要强调的是我们拦截的是动态库中的函数。下面我来讲解一下核心的hook函数。
  1. 第一步我们要获取libpasswd在进程中的首地址,时刻记住我们拦截的是动态库中的函数,将来要替换的GOT[strcmp]也属于libpasswd。
  2. 第二步就是获取libpasswd中调用strcmp对应的GOT条目的地址,为什么是addr = base_addr + 0x201020呢?我们回到上文的libpasswd的plt段,可以看到strcmp的plt条目的第一条指令已经写出了相应的GOT条目的地址就是0x201020。又因为我们拦截的动态库的strcmp函数,所以必须加上libpasswd在main中的首地址。

    3.  因为我们要写入目标进程的数据段,所以必须给相应的页增加写权限,这里使用mprotect函数来调整相应页的权限。

         4.  第四步就是将相应的GOT条目的内容替换为我们的函数的地址。这一句值得说道说道,*(void **)addr = my_strcmp;大家觉得这句和 (void *)addr = my_strcmp有什么区别呢?毕竟对*(void **) = (void *)看上去也是正确的啊!起初我也有这样的疑惑,但是仔细想想,其实并不是这样,我们要记住GOT表中的每一项都存储的是一个地址,同时addr存储的是相应的GOT条目的地址,因此我们必须先将addr强转为一个指向指针类型的指针。再使用星号解引用这样获取的就是GOT条目中存储的内容。这里需要理解的是指针类型其实和int char double是一样的也是一种数据类型。将上面的代码转换为以下形式也许更好理解:*( (void *) *addr) = my_strcmp。

 void hook() {
uintptr_t base_addr;
uintptr_t addr;
//1. get the base addr of libpasswd.so
base_addr = get_base_addr("libpasswd");
if ( == base_addr) return;
addr = base_addr + 0x201020;
//2. add the write permisson
mprotect((void *)PAGE_START(addr), PAGE_SIZE, PROT_READ|PROT_WRITE);
//3. replace our hook func my_strcmp
*(void **)addr = my_strcmp;
//4. clear the cache
__builtin___clear_cache((void *)PAGE_START(addr),
(void *)PAGE_END(addr));
}
        5. 最后一步我们需要清除硬件缓存,因为GOT表项的内容很可能已经绕过内存被缓存到了硬件缓存中,可能会导致hook失败。
  以上就是我了解到的PLT hook的全部内容,这里我们还只是hook自己的进程中调用的动态库的函数。对于其他进程的函数因为涉及到读取/proc/map文件和修改页权限,因此必须要root权限才能够运行。具体如何实现在下一篇文章中,我们再进行探讨。

PLT hook笔记的更多相关文章

  1. Linux Hook 笔记

    相信很多人对"Hook"都不会陌生,其中文翻译为"钩子".在编程中, 钩子表示一个可以允许编程者插入自定义程序的地方,通常是打包好的程序中提供的接口. 比如,我 ...

  2. hook笔记①

    汇编中push 0x*** retn表示跳转到某个地址继续执行 取消hook时会在多线程环境中可能被检测 去掉函数框架可以规避寄存器cpu前后状态监测 #pragma comment(linker,& ...

  3. hook笔记②

  4. 基于Android的ELF PLT/GOT符号和重定向过程ELF Hook实现(by 低端农业代码 2014.10.27)

    介绍 技术原因写这篇文章,有两种: 一个是在大多数在线叙述性说明发现PLT/GOT第二十符号重定向过程定向x86的,例<Redirecting functions in shared ELF l ...

  5. 基于Android的ELF PLT/GOT符号重定向过程及ELF Hook实现(by 低端码农 2014.10.27)

    引言 写这篇技术文的原因,主要有两个: 其一是发现网上大部分描写叙述PLT/GOT符号重定向过程的文章都是针对x86的.比方<Redirecting functions in shared EL ...

  6. 羽夏笔记——Hook攻防基础

    写在前面   本笔记是由本人独自整理出来的,图片来源于网络.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇文章有帮助你 ...

  7. 孙鑫MFC学习笔记20:Hook编程

    1.HOOK拦截消息,设置越后的钩子优先级越高(钩子队列)2.SetWindowHookEx设置钩子    如果thread identifier为0或其他进程创建的线程,回调函数需要在动态链接库中声 ...

  8. CI框架源码阅读笔记6 扩展钩子 Hook.php

    CI框架允许你在不修改系统核心代码的基础上添加或者更改系统的核心功能(如重写缓存.输出等).例如,在系统开启hook的条件下(config.php中$config['enable_hooks'] = ...

  9. 我的Hook学习笔记

    关于Hook 一.基本概念: 钩子(Hook),是Windows消息处理机制的一个平台,应用程序能够在上面设置子程以监视指定窗体的某种消息,并且所监视的窗体能够是其它进程所创建的.当消息到达后,在目标 ...

随机推荐

  1. subprocess.call(cmd, shell=True)

    1.使用方法 subprocess.call() 执行由参数提供的命令. 我们可以用数组作为参数运行命令,也可以用字符串作为参数运行命令(通过设置参数shell=True) 注意,参数shell默认为 ...

  2. shell作业01

    1.判断/etc/inittab文件是否大于100行,如果大于,则显示”/etc/inittab is a big file.”否者显示”/etc/inittab is a small file.” ...

  3. ciscn-华北赛区-Day1-Web2题解

    漏洞点 薅羊毛逻辑漏洞 Cookie伪造 -> JWT python反序列化 -> 反弹shell 薅羊毛逻辑漏洞 打开题目是这样一个页面 其实最初的题目这个募集资金的进度条是没有刷满的, ...

  4. Partition to K Equal Sum Subsets

    Given an array of integers nums and a positive integer k, find whether it's possible to divide this ...

  5. 从入门到自闭之Python入门

    python是一门解释型编程语言 变量名命名的规则: 变量名由字母,数字,下划线组成 变量名不能以数字开头 变量名要具有可描述性 变量名要区分大小写 变量名禁止使用python关键字 变量名不能使用中 ...

  6. Django基础之模型(models)层(上)

    目录 Django基础之模型(models)层 单表查询 必知必会13条 神奇的双下划线查询 多表查询 外键的字段的增删改查 表与表之间的关联查询 基于双下划线的跨表查询(连表查询) 补充知识 Dja ...

  7. Codeforces 1201E2. Knightmare (hard)

    传送门 看到棋盘先黑白染色冷静一下 然后分析发现,如果初始时两只马在同色的格子,那么一定是后手吃先手 反之一定是先手吃后手 所以分类讨论一下,如果初始在同色的格子,并且后手到达终点的步数更少,那么后手 ...

  8. Erasing Substrings CodeForces - 938F (字符串dp)

    大意: 给定字符串$s$, 长度为$n$, 取$k=\lfloor log2(n)\rfloor$, 第$i$次操作删除一个长度为$2^{i-1}$的子串, 求一种方案使得, $k$次操作后$s$的字 ...

  9. Centos7环境下Docker容器的安装与卸载

    Docker是一个开源的引擎,可以轻松的为任何应用创建一个轻量级的.可移植的.自给自足的容器.开发者在笔记本上编译测试通过的容器可以批量地在生产环境中部署,包括VMs(虚拟机).bare metal. ...

  10. php过滤微信昵称中的表情

    function filterNickname($nickname) { $nickname = preg_replace('/[\x{1F600}-\x{1F64F}]/u', '', $nickn ...