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. Jenkins 启动不来的排查方法

    1.通过 ps -ef | grep tomcat找到jenkins的路径,下有logs,可以查看日志 2.装插件报错时,报错信息里会提示依赖的插件版本号,到jenkins官网下载对应版本的插件即可, ...

  2. mac环境提示:make sure that /usr/local/bin is in your path

    今天我在Mac环境下安装了Homebrew和node.js后,输入node -v.brew.npm都提示: command not found,然后我查看了下Homebrew的安装日志,发现日志里有个 ...

  3. 算法 - k-means算法

    一.聚类思想 所谓聚类算法是指将一堆没有标签的数据自动划分成几类的方法,属于无监督学习方法,这个方法要保证同一类的数据有相似的特征,如下图所示:     根据样本之间的距离或者说是相似性(亲疏性),把 ...

  4. Android开发build出现java.lang.NumberFormatException: For input string: "tle 0x7f0800aa"错误的解决方案

    查看异常栈没有发现项目代码的问题,因为问题是出现在layout文件中. 全局查找tle这个,发现在某个layout文件中title一词被变成ti tle了,结果Android就xjb报错了. 参考

  5. 09 Python两种创建类的方式

    第一种比较普遍的方式: class Work(): def __init__(self,name): self.name = name w = Work('well woker') 这样就简单创建了一 ...

  6. vue路由守卫触发顺序

    不同组件之间的路由跳转流程图 导航被触发(A–>B) 调用A组件内路由守卫beforeRouteLeave(to,from,next) 调用全局路由前置守卫router.beforeEach(t ...

  7. python 运行sum函数的使用

    sum(iterable[, start]) ,iterable为可迭代对象,如: sum([ ], start) , #iterable为list列表. sum(( ), start ) , #it ...

  8. Axure(一)

    axure1.原型工具 2.软件开发 1.可行性分析2.需求分析    产品经理(和甲方对接需求,)    乙方     --   甲方 ps(专业性强,精美)  设计师        html(可变 ...

  9. 转载Google TPU论文

    选自 Google Drive 作者:Norman P. Jouppi 等 痴笑@矽说 编译 该论文将正式发表于 ISCA 2017 从去年七月起,Google就号称了其面向深度学习的专用集成电路(A ...

  10. 记一次生产环境presto删表失败的问题

    场景,开发用java程序连接presto创建一个表,这个表在hdfs的权限为: 然后用presto去删除这个表 报错,没有权限删除,查看上一级目录权限,发现权限正常 直连hive删表 发现正常. 然后 ...