【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" {
#endif

void 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.so
 
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Shared object file)
  Machine:                           ARM
  Version:                           0x1
  Entry point address:               0x0
  Start of program headers:          52 (bytes into file)
  Start of section headers:          12744 (bytes into file)
  Flags:                             0x5000200, Version5 EABI, soft-float ABI
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         8
  Size of section headers:           40 (bytes)
  Number of section headers:         25
  Section 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.so
 
There 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 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"
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.so
    if(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 address
    addr = base_addr + 0x3f90;
    
    //add write permission
    mprotect((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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vue使用高德地图定位到当前位置,并显示天气信息

首先得去高德控制台申请两个 key&#xff0c;一个天气key和一个定位key 获取天气信息的函数&#xff1a; const getWeather function (city) {// 使用 fetch 发送请求获取天气信息fetch(https://restapi.amap.com/v3/weather/weatherInfo?city${city}&keyeefd36557b0250…

二,几何相交----2,区间相交检测IID--(1)算法

对于空间的线段是否相交&#xff0c;假设都是与x平行&#xff0c;则需要三步 1&#xff0c;对各线段左右端点设置为L,R标志 2&#xff0c;从小到大进行排序 3&#xff0c;线性扫描&#xff0c;从小到大&#xff0c;根据模式判断是否相交&#xff0c;假设不相交&#xff0c;则应…

【JavaScript】面试手撕浅拷贝

【JavaScript】面试手撕浅拷贝 引入 浅拷贝和深拷贝应该是面试时非常常见的问题了&#xff0c;为了能将这两者说清楚&#xff0c;于是打算用两篇文章分别解释下深浅拷贝。 PS: 我第一次听到拷贝这个词&#xff0c;有种莫名的熟悉感&#xff0c;感觉跟某个英文很相似&#xff…

sc-MAVE

Deep-joint-learning analysis model of single cell transcriptome and open chromatin accessibility data单细胞转录组和开放染色质可及性数据的深度联合学习分析模型 在同一个细胞中同时分析转录组和染色质可及性信息为了解细胞状态提供了前所未有的解决方案。然而&#x…

WPS/Office 好用的Word插件-查找替换

例如&#xff1a;一片文档&#xff1a;…………泰山…………泰&#xff08;少打了山字&#xff09;………… 要是把“泰”查找替换为“泰山”&#xff0c;就会把前面的“泰山”变成“泰山山”&#xff0c;这种问题除了再把“泰山山”查找替换为“泰山”&#xff0c;有没有更简单…

Flutter输入框换行后自适应高度

Flutter输入框换行后输入框高度随之增加 效果 设计思想 通过TextEditingController在build中监听输入框&#xff0c;输入内容后计算输入框高度然后自定义适合的值&#xff0c;并且改变外部容器高度达到自适应高度的目的 参考代码 //以下代码中的值只适用于案例&#xff0c;…

智能果园风吸杀虫灯的优势

TH-FD2随着农业科技的不断进步&#xff0c;果园管理也迎来了革命性的变革。智能果园风吸杀虫灯作为一种新型的果园管理工具&#xff0c;以其独特的优势&#xff0c;正逐渐受到广大果农的青睐。 一、智能果园风吸杀虫灯的工作原理 智能果园风吸杀虫灯利用害虫的趋光性&#xff0…

贪心 Leetcode 134 加油站

加油站 Leetcode 134 学习记录自代码随想录 在一条环路上有 n 个加油站&#xff0c;其中第 i 个加油站有汽油 gas[i] 升 你有一辆油箱容量无限的的汽车&#xff0c;从第 i 个加油站开往第 i1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发&#xff0c;开始时油…

MonkeyRunner在自动化测试里的应用场景!

MonkeyRunner是Android提供的一个自动化测试工具&#xff0c;主要用于对Android设备或模拟器进行功能和压力测试。以下是一些MonkeyRunner在自动化测试中的应用场景及实例代码&#xff1a; 基本操作测试 点击屏幕上的特定位置或元素。 模拟滑动和手势操作。 发送按键事件。 …

指针习题二

使用函数指针实现转移表 #include <stdio.h> int add(int a, int b) {return a b; } int sub(int a, int b) {return a - b; } int mul(int a, int b) {return a * b; } int div(int a, int b) {return a / b; } int main() {int x, y;int input 1;int ret 0;int(*p[…

Linux学习:初识Linux

目录 1. 引子&#xff1a;1.1 简述&#xff1a;操作系统1.2 学习工具 2. Linux操作系统中的一些基础概念与指令2.1 简单指令2.2 ls指令与文件2.3 cd指令与目录2.4 文件目录的新建与删除指令2.5 补充指令1&#xff1a;2.6 文件编辑与拷贝剪切2.7 文件的查看2.8 时间相关指令2.9 …

服务器硬件基础知识

1. 服务器分类 服务器分类 服务器的分类没有一个统一的标准。 从多个多个维度来看服务器的分类可以加深我们对各种服务器的认识。 N.B. CISC: complex instruction set computing 复杂指令集计算 RISC: reduced instruction set computer 精简指令集计算 EPIC: explicitly p…

ViTMatte:Boosting image matting with pretrained plain vision transformers

自sora之后&#xff0c;我也要多思考&#xff0c;transformer的scaling law在各个子领域中是不是真的会产生智能&#xff0c;conv的叠加从resnet之后就讨论过&#xff0c;宽或者深都没有办法做到极限&#xff0c;大概sam这种思路是最好的实证。 1.introduction 引入了ViT adap…

浅谈一个CTF中xss小案例

一、案例代码 二、解释 X-XSS-Protection: 0&#xff1a;关闭XSS防护 之后get传参&#xff0c;替换过滤为空&#xff0c;通过过滤保护输出到img src里面 三、正常去做无法通过 因为这道题出的不严谨所以反引号也是可以绕过的 正常考察我们的点不在这里&#xff0c;正常考察…

深入理解快速排序算法:从原理到实现

目录 1. 引言 2. 快速排序算法原理 3. 快速排序的时间复杂度分析 4. 快速排序的应用场景 5. 快速排序的优缺点分析 5.1 优点&#xff1a; 5.2 缺点&#xff1a; 6. Java、JavaScript 和 Python 实现快速排序算法 6.1 Java 实现&#xff1a; 6.2 JavaScript 实现&#…

ARM64汇编02 - 寄存器与指令基本格式

最近的文章可能会有较多修改&#xff0c;请关注博客哦 异常级别 ARMv8处理器支持4种异常等级&#xff08;Exception Level&#xff0c;EL&#xff09;。 EL0 为非特权模式&#xff0c;用于运行应用程序&#xff0c;其他资源访问受限&#xff0c;权限不够。 EL1 为特权模式&…

栈的OJ一小道-->Leetcode有效的括号

20. 有效的括号 - 力扣&#xff08;LeetCode&#xff09; 这道题我们乍一看可能会选择暴力遍历法,但这题我们可以选择栈,这样可以大大降低我们的时间复杂度.这题要求非常简单 左括号必须用相同类型的右括号闭合。左括号必须以正确的顺序闭合。每个右括号都有一个对应的相同类型…

Qt 简约美观的动画 摆钟风格 第十季

&#x1f60a; 今天给大家分享一个摆钟风格的加载动画 &#x1f60a; 效果如下: 最近工作忙起来了 , 后续再分享其他有趣的加载动画吧. 一共三个文件 , 可以直接编译运行 //main.cpp #include "LoadingAnimWidget.h" #include <QApplication> #include <Q…

山西电力市场日前价格预测【2024-02-24】

日前价格预测 预测说明&#xff1a; 如上图所示&#xff0c;预测明日&#xff08;2024-02-24&#xff09;山西电力市场全天平均日前电价为562.29元/MWh。其中&#xff0c;最高日前电价为1026.21元/MWh&#xff0c;预计出现在18:30。最低日前电价为337.39元/MWh&#xff0c;预计…

[LeetBook]【学习日记】寻找和为指定数字的连续数字

题目 文件组合 待传输文件被切分成多个部分&#xff0c;按照原排列顺序&#xff0c;每部分文件编号均为一个 正整数&#xff08;至少含有两个文件&#xff09;。传输要求为&#xff1a;连续文件编号总和为接收方指定数字 target 的所有文件。请返回所有符合该要求的文件传输组…