【Android 内存优化】怎么理解Android PLT hook?

文章目录

  • 前言
  • 什么是hook?
  • PLT hook
    • 作用
    • 基本原理
    • PLT hook 总体步骤
  • 代码案例分析
  • 方案预研
    • 面临的问题
    • 怎么做?
    • ELF
      • ELF 文件头
      • SHT(section header table)
    • 链接视图(Linking View)和执行视图(Execution View)
    • 动态链接
  • 猜想-解决-验证
    • 第一次验证
    • 方案
    • 再次验证
  • 参考资料

前言

昨晚看完了爱奇艺出品的开源框架xhook中的《Android PLT hook 概述》,内容比较深。主要用到了ELF和动态链接这些Linux的知识点,但是总体上还算是理解了基本原理,这篇文章主要记录下现阶段对PLT hook的学习和理解。感觉写原文的作者很牛逼,内容看的我热血沸腾,需要看原文的读者可以翻到文章最后点开原文链接。
最后希望感谢你花时间阅读本文,下面开始学习下面的内容。

什么是hook?

“Hook”(钩子)是计算机编程中的一种技术,它允许开发者拦截、修改或扩展软件或系统的行为。通过使用钩子,开发者可以在特定事件发生时注入自定义代码,以便修改程序的行为或响应程序的特定事件。

钩子的使用场景大概有这些:

  • 键盘和鼠标事件:拦截键盘和鼠标输入,用于实现自定义的快捷键、鼠标手势或输入法。
  • 窗口消息:拦截和处理窗口消息,用于实现窗口管理、界面定制等功能。
  • 函数调用:拦截特定函数的调用,用于实现调试、性能分析、代码注入等功能。
  • 文件操作和网络请求:拦截文件操作和网络请求,用于实现文件监控、安全检测等功能。

钩子技术可以提供很大的灵活性和功能扩展性,但也需要谨慎使用,因为不正确的使用钩子可能会导致程序崩溃、安全漏洞或不稳定的行为。

当然,我们本文要讨论的场景属于函数调用场景。那么PLT hook又是什么意思?

PLT hook

作用

PLT (Procedure Linkage Table) hook 是一种在动态链接库(DLL)或共享对象(SO)中实现的技术,用于在运行时修改或拦截函数调用。这种技术通常用于在不修改原始代码的情况下,对函数的行为进行修改或监视。

基本原理

PLT hook 的基本原理是利用了动态链接库的符号解析机制。

在程序加载动态链接库时,系统会创建一个 PLT 表来保存对动态链接函数的调用。这个表中的每个条目实际上是一个跳转指令,将控制权转移到动态链接库中的实际函数实现。

PLT hook 就是通过修改 PLT 表中的条目,将原始函数调用指向自定义的函数或者跳转到其他代码段,从而实现对函数行为的修改或拦截。

PLT hook 总体步骤

  1. 定位目标函数:确定要 hook 的目标函数,获取其在动态链接库中的地址。

  2. 修改 PLT 表:通过修改 PLT 表中目标函数对应的条目,将其指向自定义的函数或者其他代码段。

  3. 处理原始函数调用:在自定义函数中可以执行一些额外的操作,然后再调用原始的目标函数,或者完全替换原始函数的行为。

  4. 恢复原始调用:有时候需要在自定义函数中调用原始的目标函数,以保持程序的正常行为。

有了上述的基本了解,再来看看给出的例子,就会容易理解很多。

代码案例分析

下面通过给出一个存在明显内存泄漏的代码,把它们编译为动态库libtest.so。看看最后能不能通过PLT hook把泄漏给解决了。

头文件 test.h

#ifndef TEST_H
#define TEST_H 1#ifdef __cplusplus
extern "C" {
#endifvoid say_hello();#ifdef __cplusplus
}
#endif#endif

源文件 test.c

#include <stdlib.h>
#include <stdio.h>void say_hello()
{char *buf = malloc(1024);if(NULL != buf){snprintf(buf, 1024, "%s", "hello\n");printf("%s", buf);}
}

源文件 main.c

#include <test.h>int main()
{say_hello();return 0;
}

执行:

caikelun@debian:~$ adb push ./libtest.so ./main /data/local/tmp
caikelun@debian:~$ adb shell "chmod +x /data/local/tmp/main"
caikelun@debian:~$ adb shell "export LD_LIBRARY_PATH=/data/local/tmp; /data/local/tmp/main"
hello
caikelun@debian:~$

把编译后的libtest.so 推到Android设备中,给个可执行权限后,执行它。虽然泄漏了,但是还是可以执行的。这正是模拟真实情况的代码,泄漏了却不自知。

方案预研

面临的问题

假如我们发现了泄漏问题,要修复它并不难,问题在于怎么及时发现它们。
问题在于下面2个:
1.假如程序测试覆盖得不够的话,怎么及时发现和定位一些已经上线的APP呢?
2.如果libtest.so是第三方库,而且还是闭源的。我们可以修复它吗?能否监控它?

怎么做?

这正是hook可以做到的事情,比如 hook malloc,calloc,realloc 和 free,我们就能统计出各个动态库分配了多少内存,哪些内存一直被占用没有释放。
作者提到,hook自己的进程完全是可以的,hook其它的进程需要root权限。因为假如没有root权限,就无法修改其它进程的内存空间。
好消息是,我们只要hook自己的进程就够了。

下面本文的主角要出来了。
而要理解PLT hook,首先要了解ELF是什么。

ELF

ELF(Executable and Linkable Format)是一种行业标准的二进制数据封装格式,主要用于封装可执行文件、动态库、object 文件和 core dumps 文件。

在这里插入图片描述
ELF概述
ELF定义

对于PLT hook,最重要的是了解ELF 文件头、SHT(section header table)、PHT(program header table)。

ELF 文件头

ELF 文件的起始处,有一个固定格式的定长的文件头(32 位架构为 52 字节,64 位架构为 64 字节)。ELF 文件头以 magic number 0x7F 0x45 0x4C 0x46 开始(其中后 3 个字节分别对应可见字符 E L F)。

libtest.so 的 ELF 文件头信息:

caikelun@debian:~$ arm-linux-androideabi-readelf -h ./libtest.soELF Header:Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00Class:                             ELF32Data:                              2's complement, little endianVersion:                           1 (current)OS/ABI:                            UNIX - System VABI Version:                       0Type:                              DYN (Shared object file)Machine:                           ARMVersion:                           0x1Entry point address:               0x0Start of program headers:          52 (bytes into file)Start of section headers:          12744 (bytes into file)Flags:                             0x5000200, Version5 EABI, soft-float ABISize of this header:               52 (bytes)Size of program headers:           32 (bytes)Number of program headers:         8Size of section headers:           40 (bytes)Number of section headers:         25Section header string table index: 24

ELF 文件头中包含了 SHT 和 PHT 在当前 ELF 文件中的起始位置和长度。例如,libtest.so 的 SHT 起始位置为 12744,长度 40 字节;PHT 起始位置为 52,长度 32字节。

ELF header数据结构如图:

请添加图片描述

SHT(section header table)

ELF 以 section 为单位来组织和管理各种信息。ELF 使用 SHT 来记录所有 section 的基本信息。主要包括:section 的类型、在文件中的偏移量、大小、加载到内存后的虚拟内存相对地址、内存中字节的对齐方式等。

caikelun@debian:~$ arm-linux-androideabi-readelf -S ./libtest.soThere are 25 section headers, starting at offset 0x31c8:Section Headers:[Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al[ 0]                   NULL            00000000 000000 000000 00      0   0  0[ 1] .note.android.ide NOTE            00000134 000134 000098 00   A  0   0  4[ 2] .note.gnu.build-i NOTE            000001cc 0001cc 000024 00   A  0   0  4[ 3] .dynsym           DYNSYM          000001f0 0001f0 0003a0 10   A  4   1  4[ 4] .dynstr           STRTAB          00000590 000590 0004b1 00   A  0   0  1[ 5] .hash             HASH            00000a44 000a44 000184 04   A  3   0  4[ 6] .gnu.version      VERSYM          00000bc8 000bc8 000074 02   A  3   0  2[ 7] .gnu.version_d    VERDEF          00000c3c 000c3c 00001c 00   A  4   1  4[ 8] .gnu.version_r    VERNEED         00000c58 000c58 000020 00   A  4   1  4[ 9] .rel.dyn          REL             00000c78 000c78 000040 08   A  3   0  4[10] .rel.plt          REL             00000cb8 000cb8 0000f0 08  AI  3  18  4[11] .plt              PROGBITS        00000da8 000da8 00017c 00  AX  0   0  4[12] .text             PROGBITS        00000f24 000f24 0015a4 00  AX  0   0  4[13] .ARM.extab        PROGBITS        000024c8 0024c8 00003c 00   A  0   0  4[14] .ARM.exidx        ARM_EXIDX       00002504 002504 000100 08  AL 12   0  4[15] .fini_array       FINI_ARRAY      00003e3c 002e3c 000008 04  WA  0   0  4[16] .init_array       INIT_ARRAY      00003e44 002e44 000004 04  WA  0   0  1[17] .dynamic          DYNAMIC         00003e48 002e48 000118 08  WA  4   0  4[18] .got              PROGBITS        00003f60 002f60 0000a0 00  WA  0   0  4[19] .data             PROGBITS        00004000 003000 000004 00  WA  0   0  4[20] .bss              NOBITS          00004004 003004 000000 00  WA  0   0  1[21] .comment          PROGBITS        00000000 003004 000065 01  MS  0   0  1[22] .note.gnu.gold-ve NOTE            00000000 00306c 00001c 00      0   0  4[23] .ARM.attributes   ARM_ATTRIBUTES  00000000 003088 00003b 00      0   0  1[24] .shstrtab         STRTAB          00000000 0030c3 000102 00      0   0  1
Key to Flags:W (write), A (alloc), X (execute), M (merge), S (strings), I (info),L (link order), O (extra OS processing required), G (group), T (TLS),C (compressed), x (unknown), o (OS specific), E (exclude),y (noread), p (processor specific)
  • .dynamic:供动态链接器使用的各项信息,记录了当前 ELF 的外部依赖,以及其他各个重要 section 的起始位置等信息。
  • .got:Global Offset Table。用于记录外部调用的入口地址。动态链接器(linker)执行重定位(relocate)操作时,这里会被填入真实的外部调用的绝对地址。
  • .plt:Procedure Linkage Table。外部调用的跳板,主要用于支持 lazy binding 方式的外部调用重定位。(Android 目前只有 MIPS 架构支持 lazy binding)

下面用一张图描述相关的关系,这也是PLT hook的核心原理:

请添加图片描述

链接视图(Linking View)和执行视图(Execution View)

  • 连接视图:ELF 未被加载到内存执行前,以 section 为单位的数据组织形式。
  • 执行视图:ELF 被加载到内存后,以 segment 为单位的数据组织形式。

请添加图片描述

而hook操作关系的是动态形式的内存操作,所以关心的是执行视图,也就是上面的右图。

动态链接

动态链接的大体步骤如下:

  • 检查已经加载的ELF列表
  • 从libtest.so的.dynamic section 中读取 libtest.so中外部依赖的ELF列表,从列表中剔除已经加载的ELF,得到本次需要加载的ELF
  • 用mmap预留一块内存,用于后面映射ELF
  • 读取ELF的PHT,用mmap把所有PT_LOAD类型的segment映射到内存中
  • 从.dynamic section 中读取各个section的虚拟内存地址,计算&保存各个section的虚拟内存绝对地址。
  • 执行重定位操作(relocate),这是最关键的一步。重定位信息可能存在于下面的一个或多个 secion 中:.rel.plt, .rela.plt, .rel.dyn, .rela.dyn, .rel.android, .rela.android。动态链接器需要逐个处理这些 .relxxx section 中的重定位诉求。根据已加载的 ELF 的信息,动态链接器查找所需符号的地址(比如 libtest.so 的符号 malloc),找到后,将地址值填入 .relxxx 中指明的目标地址中,这些“目标地址”一般存在于.got 或 .data 中。
  • ELF引用计数加一
  • 逐个调用ELF的构造函数,先调用被依赖ELF的构造函数,再调用libtest.so自己的构造函数。

这里是全文最关键地地方,分析重定位操作可以推理出:
只要从这些 .relxxx 中获取到“目标地址”,然后在“目标地址”中重新填上一个新的函数地址,这样就完成 hook 了

.dynamic section
这是一个十分重要和特殊的 section,其中包含了 ELF 中其他各个 section 的内存位置等信息。在执行视图中,总是会存在一个类型为 PT_DYNAMIC 的 segment,这个 segment 就包含了 .dynamic section 的内容。
无论是执行 hook 操作时,还是动态链接器执行动态链接时,都需要通过 PT_DYNAMIC segment 来找到 .dynamic section 的内存位置,再进一步读取其他各项 section 的信息。

猜想-解决-验证

原文这部分就是通过读取libtest.so的汇编代码,通过NDK的objdump 查出反汇编输出。接着通过一些计算得出libtest.so中malloc的地址3f90 。

第一次验证

#include <test.h>void *my_malloc(size_t size)
{printf("%zu bytes memory are allocated by libtest.so\n", size);return malloc(size);
}int main()
{void **p = (void **)0x3f90;*p = (void *)my_malloc; // do hooksay_hello();return 0;
}

运行结果:

caikelun@debian:~$ adb push ./main /data/local/tmp
caikelun@debian:~$ adb shell "chmod +x /data/local/tmp/main"
caikelun@debian:~$ adb shell "export LD_LIBRARY_PATH=/data/local/tmp; /data/local/tmp/main"
Segmentation fault
caikelun@debian:~$

例子验证了思路是正确的但是需要解决3个问题:

  1. 计算出来的地址是个先对内存地址,需要转换为绝对地址
  2. 地址很可能没有写入权限
  3. 新的函数地址即使可以赋值成功,my_malloc 也不会执行,因为处理器有指令缓存。

方案

上述第一个问题可以通过基地址解决。
第二个问题通过mprotect解决。
第三个问题通过__builtin___clear_cache函数调用解决。

再次验证

把main.c 修改为:

#include <inttypes.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/mman.h>
#include <test.h>#define PAGE_START(addr) ((addr) & PAGE_MASK)
#define PAGE_END(addr)   (PAGE_START(addr) + PAGE_SIZE)void *my_malloc(size_t size)
{printf("%zu bytes memory are allocated by libtest.so\n", size);return malloc(size);
}void hook()
{char       line[512];FILE      *fp;uintptr_t  base_addr = 0;uintptr_t  addr;//find base address of libtest.soif(NULL == (fp = fopen("/proc/self/maps", "r"))) return;while(fgets(line, sizeof(line), fp)){if(NULL != strstr(line, "libtest.so") &&sscanf(line, "%"PRIxPTR"-%*lx %*4s 00000000", &base_addr) == 1)break;}fclose(fp);if(0 == base_addr) return;//the absolute addressaddr = base_addr + 0x3f90;//add write permissionmprotect((void *)PAGE_START(addr), PAGE_SIZE, PROT_READ | PROT_WRITE);//replace the function address*(void **)addr = my_malloc;//clear instruction cache__builtin___clear_cache((void *)PAGE_START(addr), (void *)PAGE_END(addr));
}int main()
{hook();say_hello();return 0;
}
caikelun@debian:~$ adb push ./main /data/local/tmp
caikelun@debian:~$ adb shell "chmod +x /data/local/tmp/main"
caikelun@debian:~$ adb shell "export LD_LIBRARY_PATH=/data/local/tmp; /data/local/tmp/main"
1024 bytes memory are allocated by libtest.so
hello
caikelun@debian:~$

上述代码在没有重新编译libtest.so的前提下,libtest.so的函数say_hello的函数地址替换成了my_malloc的函数地址。从而完成了native层面的hook。

至此,PLT hook的整体原理介绍完毕。
原文更加详细和精彩,适合需要更深入理解和实操的朋友,链接在下面。

参考资料

参考原文:https://github.com/iqiyi/xHook/blob/master/docs/overview/android_plt_hook_overview.zh-CN.md
https://en.wikipedia.org/wiki/Hooking

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

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

相关文章

YOLO-World 简单无需标注无需训练直接可以使用的检测模型

参考: https://github.com/AILab-CVC/YOLO-World YOLO-World 常规的label基本不用训练,直接传入图片,然后写入文本label提示既可 案例demo: 1)官方提供 https://huggingface.co/spaces/stevengrove/YOLO-World https://huggingface.co/spaces/SkalskiP/YOLO-World 检测…

基于信息间隙决策理论的碳捕集电厂优化调度程序代码!

适用平台&#xff1a;MatlabYalmipCplex 程序在建立电厂与碳捕集装置协同调度模型的基础上&#xff0c;引入信息间隙决策理论(information gap decision theory, IGDT)以同时满足系统的鲁棒性和经济性要求&#xff0c;通过风险追求和风险规避&#xff12;种决策角度得到不同的…

真不愧是华为出来的,真的太厉害了。。。

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 实习去了博彦科技&#xff08;外包&#xff09;&#xff0c;做的就是螺丝钉的活&#xff0c;后面…

华为---MSTP(一)---MSTP生成树协议

目录 1. MSTP技术产生背景 2. STP/RSTP的缺陷 ​编辑 2.1 无法均衡流量负载 2.2 数据使用次优路径 3. MSTP生成树协议 3.1 MSTP相关概念 3.2 MSTP树生成的形成过程 4. MSTP报文 1. MSTP技术产生背景 RSTP在STP基础上进行了改进&#xff0c;实现了网络拓扑快速收敛。但…

SpringBoot+Maven项目打包

项目的主POM文件里面添加maven打包插件 <build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.2</version><configuration><sour…

推荐一款新的自动化测试框架:DrissionPage

今天给大家推荐一款基于Python的网页自动化工具&#xff1a;DrissionPage。这款工具既能控制浏览器&#xff0c;也能收发数据包&#xff0c;甚至能把两者合而为一&#xff0c;简单来说&#xff1a;集合了WEB浏览器自动化的便利性和 requests 的高效率优点。 一、DrissionPage框…

【C++庖丁解牛】默认成员函数

&#x1f4d9; 作者简介 &#xff1a;RO-BERRY &#x1f4d7; 学习方向&#xff1a;致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f4d2; 日后方向 : 偏向于CPP开发以及大数据方向&#xff0c;欢迎各位关注&#xff0c;谢谢各位的支持 目录 前言1. 构造函数1.1 …

职场高薪 |「中高级测试」面试题

【软件测试面试突击班】2024吃透软件测试面试最全八股文攻略教程&#xff0c;一周学完让你面试通过率提高90%&#xff01;&#xff08;自动化测试&#xff09; 一.基础题 1.测试用例你一般是怎么设计的&#xff0c;怎么可以提高覆盖率&#xff1f; 有没有形成自己的 一套方法论…

Jmeter接口测试---随机数、加密、cookie鉴权、断言、CSV参数化

随机数 第一步&#xff1a;选择工具-函数助手对话框 第二步&#xff1a;选择random&#xff0c;设置最大值最小值&#xff0c;复制函数字符串到指定位置 加密接口 类型&#xff1a;AES、DES、Base64、RSA&#xff08;可以解密&#xff09; | MD5、SHA、HmacSHA&#xff08;不…

llama.c代码2

1、forward 1.1、复习 encode(tokenizer, prompt, 1, 0, prompt_tokens, &num_prompt_tokens); 在encode函数结尾处(gdb) p *n_tokens $3 2(gdb) p *tokens3 $6 {1, 22172, 417} 在encode调用后 (gdb) print num_prompt_tokens $11 2 (gdb) print *prompt_tokens3 $13 …

JavaScript 中的类型转换机制(详细讲解)

文章目录 一、概述二、显示转换Number()parseInt()String()Boolean() 三、隐式转换自动转换为布尔值自动转换成字符串自动转换成数值 一、概述 前面我们讲到&#xff0c;JS中有六种简单数据类型&#xff1a;undefined、null、boolean、string、number、symbol&#xff0c;以及…

(sub)三次握手四次挥手

TCP的三次握手与四次挥手理解及面试题 序列号seq&#xff1a;占4个字节&#xff0c;用来标记数据段的顺序&#xff0c;TCP把连接中发送的所有数据字节都编上一个序号&#xff0c;第一个字节的编号由本地随机产生&#xff1b;给字节编上序号后&#xff0c;就给每一个报文段指派一…

(学习日记)2024.03.03:UCOSIII第五节:常用汇编指令+OS初始化+启动任务+任务切换

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…

双重检验锁

双重检验锁&#xff1a;设计模式中的单例模式&#xff0c;细分为单例模式中的懒加载模式。 单例模式 单例模式&#xff1a;指的是一个类只有一个对象。最简单的实现方式是设一个枚举类&#xff0c;只有一个对象。缺点是当对象还没有被使用时&#xff0c;对象就已经创建存在了…

【扩散模型】生成模型中的Residual Self-Attention UNet 以及 DDPM的pytorch代码

参考&#xff1a; [1] https://github.com/xiaohu2015/nngen/blob/main/models/diffusion_models/ddpm_cifar10.ipynb [2] https://www.bilibili.com/video/BV1we4y1H7gG/?spm_id_from333.337.search-card.all.click&vd_source9e9b4b6471a6e98c3e756ce7f41eb134 TOC 1 UNe…

视黄酸诱导基因-1敲除诱导树突状细胞的不成熟特性并延长异体移植小鼠的存活时间研究【AbMole】

器官移植是一种用于替换因疾病、损伤或其他原因受损的人体器官的医疗程序。尽管器官移植可以挽救生命并显著提高生活质量&#xff0c;但存在供体器官短缺、排斥反应、器官功能障碍、感染和药物副作用等问题。为了提高移植成功率和受体健康&#xff0c;需要有效的免疫策略。树突…

如何使用支付宝沙箱环境本地配置模拟支付并实现公网远程访问【内网穿透】

文章目录 前言1. 下载当面付demo2. 修改配置文件3. 打包成web服务4. 局域网测试5. 内网穿透6. 测试公网访问7. 配置二级子域名8. 测试使用固定二级子域名访问 前言 在沙箱环境调试支付SDK的时候&#xff0c;往往沙箱环境部署在本地&#xff0c;局限性大&#xff0c;在沙箱环境…

数据可视化原理-腾讯-3D网格热力图

在做数据分析类的产品功能设计时&#xff0c;经常用到可视化方式&#xff0c;挖掘数据价值&#xff0c;表达数据的内在规律与特征展示给客户。 可是作为一个产品经理&#xff0c;&#xff08;1&#xff09;如果不能够掌握各类可视化图形的含义&#xff0c;就不知道哪类数据该用…

壁炉火焰温和而宁静,警惕火焰凶猛的潜在危害

在寒冷的冬夜&#xff0c;壁炉散发的温暖和闪烁的火焰成为家庭的心灵港湾。然而&#xff0c;我们在享受壁炉带来的温馨时&#xff0c;有时候也要关注火焰的凶猛度&#xff0c;因为它可能引发一系列潜在危害。 首先&#xff0c;壁炉的火焰过于凶猛可能导致空气质量下降。当火焰过…

从零开始手写RPC框架(4)

这一节主要讲述网络传输模块的代码&#xff0c;并且几乎每一行代码都加上了我个人理解的注释&#xff0c;同时也讲述了其中一些以前没见过的函数&#xff0c;和大致的底层运行逻辑。 目录 网络传输实体类网络传输实现基于Socket实现网络传输基于Netty实现网络传输客户端服务端 …