【胖虎的逆向之路】——GOT/PLT Hook详解针对自定义so库的Hook实操

news2025/1/12 6:52:36

文章目录

    • @[TOC](文章目录)
  • 前言
  • 一、ELF 是什么?
    • 1、ELF 的概念
    • 2、ELF 的组成
      • 2.1、 两种视图是什么呢?
      • 2.2、 ELF文件头又是什么西西?
      • 2.2、 ELF中节头表是什么南南呢?
      • 2.2、 ELF中程序头表是什么北北呢?
  • 二、动态库装载、动态链接与重定位简单理解
    • 1、装载
    • 2、动态链接
    • 3、重定位
  • 三、PLT与GOT Hook(Native,你们的Hook回来了~)
    • 1、 .dynsym检索
    • 2、更快速的找到目标函数的偏移
    • 3、注入修改
  • 总结
  • 参考

前言

随着 Android 开发的技术宽度不断向 native 层扩展,Native hook 已经被用于越来越多的业务场景中,之前作者一直游离于Java层面的逆向,后来工作使然,接触到了Native 层的Hook,熟悉了ELF的文件结构&GOT/PLT&In Line Hook的相关知识和实际操作,Android Native Hook 的实现方式有很多种,我们接下来要讲的是 GOT/PLT Hook (篇幅略略略长,阅读时长约 20 min


一、ELF 是什么?

1、ELF 的概念

官方是如何解释的呢,下面请看大屏幕:

ELF(Executable and Linking Format),即“可执行可连接格式”,最初由 UNIX 系统实验室(UNIX System Laboratories – USL)做为应用程序二进制接口(Application Binary Interface - ABI)的一部分而制定和发布。
ELF 作为一种可移植的格式,被 TIS 应用于基于 Intel 架构 32 位计算机的各种操作系统上。 ELF 的最大特点在于它有比较广泛的适用性,通用的二进制接口定义使之可以平滑地移植到多种不同的操作环境上。这样,不需要为每一种操作系统都定义一套不同的接口,因此减少了软件的重复编码与编译,加强了软件的可移植性。
ELF 文件格式规范由 TIS(Tool Interface Standards – 工具接口标准)委员会制定, TIS 委员会是一个微型计算机工业的联合组织,它致力于为 32 位操作系统下的开发工具提供标准化的软件接口。这种接口包括目标标志格式、可执行文件格式,以及调试信息的格式。

嘟嘟嘟嘟…一大堆,建议略过 ,著名哲学家嘟嘟斯基曾说过:“太长不看”

首先,综上所述,明确一个概念,ELF 是一个文件格式,诸如我们所见的.so动态库,均属于ELF文件格式

2、ELF 的组成

没图说个**?
下面是作者简单画了一个图,从两个不同的角度来进行分析
ELF文件格式概述(胖虎)
如果不太了解没关系,大概先看一下,我们接着往下说~~~

2.1、 两种视图是什么呢?

连接(链接)视图:可以简单理解为目标文件的储存视图,也就是文件的静态解析视图;
运行(执行)视图:可以简单理解为目标文件的内存视图,也就是文件的动态运行视图;

其实就是程序run没run起来的区别~

2.2、 ELF文件头又是什么西西?

文件头部定义了Magic,以及指向节头表SHT(section_header_table )程序头表PHT(program_header_table) 的偏移

我们来拿curl.so文件来看一哈~
![在这里插入图片描述](https://img-blog.csdnimg.cn/0ab15744d2c74d9c85a07a4935848165.png

其中Magic表示了这是一个ELF文件: ELF 文件是以 7F 45 4C 46 开头 , 其中 7F 是一个二进制标志 , 45 4C 46 是 ELF 字符对应的 ASCII 码 ;

节头表SHT(section_header_table )程序头表PHT(program_header_table) 的偏移地址也显示出来了~

运行命令: arm-linux-androideabi-readelf -h xxx.so

2.2、 ELF中节头表是什么南南呢?

ELF文件在链接视图中是 以节(section)为单位来组织和管理各种信息

看图图~
在这里插入图片描述

其中比较重要的是圈起来的几个点,以下是说明:

  • .dynsym:为了完成动态链接,最关键的还是所依赖的符号和相关文件的信息。为了表示动态链接这些模块之间的符号导入导出关系,ELF有一个叫做动态符号表(Dynamic Symbol Table)的段用来保存这些信息
  • .rel.dyn:实际上是对数据引用的修正,它所修正的位置位于.got以及数据段
  • .rel.plt:是对函数引用的修正,它所修正的位置位于.got
  • .plt:程序链接表(Procedure Link Table),外部调用的跳板
  • .text:为代码段,也是反汇编处理的部分,以机器码的形式存储
  • .dynamic:描述了模块动态链接相关的信息
  • .got:全局偏移表(Global Offset Table),用于记录外部调用的入口地址
  • .data: 数据段,保存的那些已经初始化了的全局静态变量和局部静态变量

记不住?没关系,我也是,先了解大概意思,慢慢往下走~

运行命令: arm-linux-androideabi-readelf -S xxx.so

2.2、 ELF中程序头表是什么北北呢?

ELF文件在执行视图中是 以段(Segment)为单位来组织和管理各种信息
所有类型为 PT_LOAD 的段(segment)都会被动态链接器(linker)映射(mmap)到内存中

在这里插入图片描述

  • 程序头表(program header table)是一个数组, 数组中的每一个元素称为“程序头(program header)”,每一个程序头描述了一个 “段(segment)”或者一块用于准备执行程序的信息
  • 一个目标文件中的“段 (segment)”包含一个或者多个“节(section)”
  • 程序头只对可执行文件或共享目标 文件有意义,对于其它类型的目标文件,该信息可以忽略
  • 在目标文件的文件头 (elf header)中,e_phentsize 和 e_phnum 成员指定了程序头的大小
运行命令: arm-linux-androideabi-readelf -l xxx.so

基于ELF的结构我们暂时先了解到这里,如果展开讲的话太鸡儿多了哈~~ 不是我懒~


二、动态库装载、动态链接与重定位简单理解

1、装载

作者是Android出身,所以仅从Android角度来分析如何加载so的,我们在使用一个动态库(.so)内的函数时,都要先对其进行加载,在android中,我们一般是使用System.loadLibrary的方式进行加载,它的内部实现其实也是调用系统内部linker中的dlopen、dlsym、dlclose函数完成对目标动态库的装载~

2、动态链接

动态链接的原理是让程序按照既定模块拆分成各个相对独立的部分,在程序运行的时候才将它们链接在一起,从而形成一个完整的程序,相当于Android的热拔插、组件化等,而不是整体打包成一个dex使用~
当so动态库被装载的时候,动态链接器linker会将动态库装载到进程的地址空间,并且将程序中所有没确定的符号绑定到相应的动态链接库中,并进行重定位的工作~

3、重定位

共享库进行重定位的主要原因是在于导入符号原因,在动态链接下,可执行文件如果依赖于其他共享对象,也就是说有导入的符号时(比如easy_curl_getopt函数),那么它的代码或数据中就会有对于导入符号的引用,在编译时这些导入符号的地址未知,在运行时才确定,所以需要在运行时将这些导入符号的引用修正,即需要重定位

动态链接的文件中,有专门的重定位表分别叫做.rel.dyn和.rel.plt:(刚才看表的时候有圈起来哦)

在这里插入图片描述

在这里插入图片描述

R_AARCH64_GLOB_DA和R_AARCH64_JUMP_SL是ARM64下的重定位方式,这两个类型的重定位入口表示,被修正的位置只需要直接填入符号的地址即可。比如我们看setopt函数这个重定位入口,它的类型为R_AARCH64_JUMP_SL,它的偏移为0x000000066400,它实际上位于.got中

![在这里插入图片描述](https://img-blog.csdnimg.cn/6920003b5dab4448aac0b3a6636516fd.png

运行命令: arm-linux-androideabi-readelf -r xxx.so

三、PLT与GOT Hook(Native,你们的Hook回来了~)

呼,喝杯水~
在前面的装载->动态链接->重定位完成之后,我们目标动态库的基址已经确定了,在当我们调用某个函数(比如Curl的curl_easy_setopt函数时),调用函数其实并没有直接调用原始该函数地址,他会先经过PLT程序链接表(Procedure Link Table),跳转至GOT全局偏移表(Global Offset Table)获取目标函数curl_easy_setopt函数的全局偏移,这样就可以通过基址+偏移的方式定位真实curl_easy_setopt函数的地址,当然,目前android平台大部分CPU架构是没有提供延迟绑定(Lazy Binding)机制的(只有MIPS架构支持延迟绑定),所以所有外部过程引用都在映像执行之前解析~

PLT:程序链接表(Procedure Link Table),外部调用的跳板,在ELF文件中以独立的段存放,段名通常叫做”.plt”
GOT:全局偏移表(Global Offset Table),用于记录外部调用的入口地址,段名通常叫做”.got”

到这里我们就开始实际操作了,前面的内容仅作为基础知识了解,在边做边学是最快的学习方式,接下来我们会以curl的curl_easy_perform(请求)进行Hook,得到请求的时机,come on~

1、 .dynsym检索

.dynsym:在之前的描述中,这个节里只保存了与动态链接相关的符号导入导出

我们先来找到自定义的目标函数curl_a_website:

在这里插入图片描述

运行命令: arm-linux-androideabi-readelf -s xxx.so

我们可以看到目标的perform函数在0x15fc的地方,我们再看下对应的反汇编代码是什么样子的~

注意检查你的abi,反正我的so使用arm不行哈哈哈, 可以使用如下:

运行命令: aarch64-linux-android-objdump.exe -D xx.so

在这里插入图片描述
在这里插入图片描述
这里会看到我们自己的curl_a_website 函数通过BLX(相对寻址)指令走到curl_a_website @plt里面~
那么,由此可以得出当执行我们的代码段.text中的 curl_a_website 函数的时候,内部会通过BLX相对寻址的方式进入.plt节,计算程序计数器 PC 的当前值跳转进入.got节~ 中间经过经过PLT和GOT的跳转,到达我们最终的真实的导入函数的地址~

2、更快速的找到目标函数的偏移

前面也提到过动态链接重定位表中的.rel.plt是对函数引用的修正,它所修正的位置位于.got。我们最终都是要通过.got确定目标函数的偏移,因此这里我们可以用readelf直接看到fwrite函数的偏移

通过如下可以查看ELF中需要重定位的函数,我们看下curl_a_website()函数。

运行命令: arm-linux-androideabi-readelf -r xxx.so

在这里插入图片描述

其中,我们可以看到 curl_a_website 的偏移是 0x3070 ,那么得到了偏移值,基址怎么确定呢?
来,跑起来~

使用命令获取:

 cat /proc/对应进程的pid/maps

在这里插入图片描述
上图已经列举出了我们的应用加载的一些so库,左边标记红色的地址就是各个so库的基址

addr = base_addr + 0x3070

3、注入修改

通过我们前面的一顿操作~,已经拿到目标函数curl_a_websute()的指针了
所以我们兴冲冲的开始写入我们目标函数的地址就可以啦!

#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include <inttypes.h>
#include <sys/mman.h>
#include "hook_simple.h"
#include "logger.h"
 
#define PAGE_START(addr) ((addr) & PAGE_MASK)
#define PAGE_END(addr) (PAGE_START(addr) + PAGE_SIZE)
 

 
size_t hook_fwrite(const void *buf, size_t size, size_t count, FILE *fp) {
LOG_D("hook success");
//可以根据你的想法随便hook哪个函数
//curl_a_wbsite();
LOG_D("hook end");
//return curl_a_wbsite...;

}
 

void Java_com_test_hook_hookWebSite(JNIEnv *env, jobject obj, jstring jSoName) {
const char *soName = (*env)->GetStringUTFChars(env, jSoName, 0);
LOG_D("name=%s", soName);
char line[1024] = "\n";
FILE *fp = NULL;
uintptr_t base_addr = 0;
uintptr_t addr = 0;
// 1. 查找自身对应的基址
if (NULL == (fp = fopen("/proc/self/maps", "r"))) return;
while (fgets(line, sizeof(line), fp)) {
if (NULL != strstr(line, soName) &&
sscanf(line, "%"PRIxPTR"-%*lx %*4s 00000000", &base_addr) == 1)
break;
}
fclose(fp);
LOG_D("value_addr=0x%08X", base_addr);
if (0 == base_addr) return;
//2. 基址+偏移=真实的地址
addr = base_addr + 0x2FE0;
LOG_D("value_addr=0x%08X", addr);
 
//保存旧的函数地址
你的函数= *(void **) addr;
 
//替换新的目标地址
*(void **) addr = hook_fwrite;

}

当然,以上的代码仅提供了示例,稍微微微修改一下即可~

tips:

在理论上朝这个地址写入我们目标函数的地址感觉就可以了,但是因为函数是我自己写的,
所以在hook其他函数时有两点需要注意:

1、目标函数的地址很可能没有写权限,因此需要提前调整目标函数地址的权限
2、由于ARM有缓存指令集,hook之后可能会不成功,读取的是缓存中的指令,因此这里需要清除一下指令缓存

总结

综上所述,一套流程下来感觉Native Hook的流程并不太复杂,但是相关的基础例如ELF文件结构和组成、链接装载重定位等基础逻辑的认知还是比较重要~
当然这仅仅是有符号表的函数Hook,那如果是没有函数符号表的Hook呢?(什么?InIineHook?)

最后,由于作者能力有限,在部分细节的描述可能不全面或者会有偏差,欢迎大佬们指正!

转载请注明出处~(CSDN:胖虎哥er https://blog.csdn.net/a_Chaon)

参考

https://github.com/bytedance/bhook
https://www.cnblogs.com/goodhacker/p/9306997.html
https://cstriker1407.info/blog/android-plt-got-hook-introduce/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/703936.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

uni-app 微信小程序发布时,主包超过2M限制

小程序发布时&#xff0c;提示超过2M&#xff0c;无法通过&#xff0c;此时可以尝试以下几种方法&#xff1a; 1、对图片做压缩 图片尽量放在服务器端&#xff0c;使用的时候&#xff0c;通过URL路径获取&#xff0c;若不得已放在本地时&#xff0c;可以对图片进行压缩&#…

基于springboot+Redis的前后端分离项目之分布式锁-redission(五)-【黑马点评】

&#x1f381;&#x1f381;资源文件分享 链接&#xff1a;https://pan.baidu.com/s/1189u6u4icQYHg_9_7ovWmA?pwdeh11 提取码&#xff1a;eh11 分布式锁-redission 分布式锁-redission1 分布式锁-redission功能介绍2 分布式锁-Redission快速入门3 分布式锁-redission可重入锁…

NoSQL之Redis优化(一)

Redis的高可用 一、Redis 持久化RDB 持久化AOF 持久化RDB和AOF的优缺点 二、Redis 性能管理内存碎片如何产生的&#xff1f;解决碎片率大的问题&#xff1a;内存使用率内回收key 在web服务器中&#xff0c;高可用是指服务器可以正常访问的时间&#xff0c;衡量的标准是在多长时…

【Java可执行命令】(四)反编译工具javap:深入解析应用程序反编译工具javap ~

Java可执行命令详解之javap 1️⃣ 概念2️⃣ 优势和缺点3️⃣ 使用3.1 语法格式3.1.1 可选参数&#xff1a;-l3.1.2 可选参数&#xff1a;-c3.1.3 可选参数&#xff1a;-s3.1.4 可选参数&#xff1a;-verbose3.1.5 可选参数&#xff1a;-version 4️⃣ 应用场景5️⃣ 注意事项&…

6.21、设计模式 单例设计模式

1 设计模式&#xff08;Design pattern&#xff09; 代表了最佳的实践&#xff0c;通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来…

好用又智能笔记类工具有哪些?

在工作和生活中&#xff0c;我们经常面临大量信息和任务需要记录和整理。好用又智能的笔记类工具成为了办公人士提高工作效率和组织信息的必备利器。 敬业签笔记工具支持分类记录笔记&#xff0c;可以根据不同的主题或项目进行整理。无论是工作笔记、学习笔记还是个人生活记录…

OpenFeign——请求其他服务时传递token信息

文章目录 前言准备流程初测定义nacos-product子服务定义服务的消费方 cloudalibaba-openfeign-server初步测试结论 设置cloudalibaba-openfeign-server中的feign总结 前言 在实际开发过程中&#xff0c;服务与服务之间都会有比较频繁的通信操作。其次不同用户所需要查询的数据…

【正点原子STM32连载】 第四十七章 SRAM实验 摘自【正点原子】STM32F103 战舰开发指南V1.2

1&#xff09;实验平台&#xff1a;正点原子stm32f103战舰开发板V4 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id609294757420 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html# 第四…

使用inno打包程序流程

1:配置iss文件 ​​​​​​​ 2编译 3.生成安装包文件安装

浅谈建筑项目中的智能照明系统的设计与研究

【摘要】&#xff1a;建筑智能照明工程中智能照明控制系统发展迅速&#xff0c;具有舒适性和节能性两方面优势。智能照明控制系统已经处于模块化高速发展阶段&#xff0c;如今更好的控制方案成为制约系统发展的瓶颈。文章在研究了国内外智能照明系统的基础上&#xff0c;从照明…

【CSS】nth:children以及浏览器内核webkit使用(滚动条样式修改)

&#x1f609;博主&#xff1a;初映CY的前说(前端领域) ,&#x1f4d2;本文核心&#xff1a;nth:children以及浏览器中的webkit使用 前言&#xff1a;在页面的编写中使用了多个标签通常有需求去处理下特殊的样式&#xff0c;我们常见做法是给我们的标签加上一个类或者通过标签选…

【微服务】什么是微服务?-- 全面了解微服务架构

What is Microservices — Edureka 您有没有想过&#xff0c;什么是微服务以及扩展行业如何与它们集成&#xff0c;同时构建应用程序以满足客户的期望&#xff1f; 要了解什么是微服务&#xff0c;您必须了解如何将单体应用程序分解为独立打包和部署的小型微型应用程序。本文将…

力扣 -- 91.解码方法

题目链接&#xff1a;91. 解码方法 - 力扣&#xff08;LeetCode&#xff09; 以下是用动态规划的思想解决这道题目&#xff0c;如果对动态规划五部曲的含义还不是很清楚的老铁可以看看本专栏的第一题动规(10条消息) 力扣 -- 746. 使用最小花费爬楼梯_KOBE 0824 BRYANT的博客-…

软件测试编写文档模板【附文档模板】

一、测试岗位必备的文档 在一个常规的软件测试流程中&#xff0c;会涉及到测试计划、测试方案、测试用例、测试报告的编写&#xff0c;这些文档也是软件测试岗位必须掌握的文档类型。 1、测试计划 测试计划是组织管理层面的文件&#xff0c;从组织管理的角度对一次测试活动进…

华为OD机试真题 Python实现【最小的调整次数】【2023Q1 100分】

目录 一、题目描述二、输入描述三、输出描述四、补充说明五、解题思路六、Python算法源码七、效果展示1、输入2、输出3、说明 一、题目描述 有一个特异性的双端队列&#xff0c;该队列可以从头部或尾部添加数据&#xff0c;但是只能从头部移出数据。 小A依次执行2n个指令往队…

uboot详解(嵌入式学习)

uboot详解 概念详解扩展Windows的“uboot” 概念 U-Boot&#xff08;Unified Bootloader&#xff09;是一个开源的嵌入式系统引导加载程序&#xff0c;也是一种通用的引导加载程序。它主要用于嵌入式系统的启动过程&#xff0c;负责初始化硬件设备、加载操作系统内核和启动应用…

Android Studio 使用 Build Variants 配置测试/正式环境域名等字段

拿测试环境域名和正式环境域名举例&#xff1a;在项目调试和发版过程中可以通过频繁地注释和解开注释来切换正式环境域名和测试环境域名&#xff0c;但此方法过于繁琐&#xff1b;所以可以使用Android Studio的Build Variants根据切换环境来替我们执行切换环境的操作。 在项目…

《HelloGitHub》第 87 期

兴趣是最好的老师&#xff0c;HelloGitHub 让你对编程感兴趣&#xff01; 简介 HelloGitHub 分享 GitHub 上有趣、入门级的开源项目。 https://github.com/521xueweihan/HelloGitHub 这里有实战项目、入门教程、黑科技、开源书籍、大厂开源项目等&#xff0c;涵盖多种编程语言 …

可视化对讲广播电话可以用在哪里

可视化对讲广播电话可以用在哪里 可视化对讲广播电话&#xff1a;无处不在的沟通利器 【工地现场】 在矗立的高楼上&#xff0c;工地上忙碌的工人们使用着可视化对讲广播电话。借助高清画面和清晰音频&#xff0c;工作人员可以实时观察工地情况&#xff0c;更好地协调工作&a…

【【51单片机实现LED点阵屏幕和动画显示】】

LED点阵屏幕和驱动代码 我们先搞清楚&#xff17;&#xff14;HC&#xff15;&#xff19;&#xff15;的原理 &#xff53;&#xff46;&#xff52; 特殊功能寄存器声明 &#xff53;&#xff42;&#xff49;&#xff54; 特殊位声明 就举个例子&#xff0c;我们在之前的…