创建或修改目录:/www/wwwroot/104.219.215.234/data 失败!
性爱技巧 xHook/docs/overview/android_plt_hook_overview.zh-CN.md at master · iqiyi/xHook · GitHub - 猎U者
猎U者-性爱技巧 xHook/docs/overview/android_plt_hook_overview.zh-CN.md at master · iqiyi/xHook · GitHub
你的位置:猎U者 > 色妹妹情色网 > 性爱技巧 xHook/docs/overview/android_plt_hook_overview.zh-CN.md at master · iqiyi/xHook · GitHub
性爱技巧 xHook/docs/overview/android_plt_hook_overview.zh-CN.md at master · iqiyi/xHook · GitHub
发布日期:2025-04-04 18:21     点击次数:150

你恒久不错从 这里 造访本文的最新版块性爱技巧。

文中使用的示例代码不错从 这里 赢得。文中提到的 xhook 开源式样不错从 这里 赢得。

咱们有一个新的动态库:libtest.so。

头文献 test.h

源文献 test.c

say_hello 的功能是在末端打印出 hello\n 这6个字符(包括遗弃的 \n)。

咱们需要一个测试顺次:main。

源文献 main.c

编译它们鉴识生成 libtest.so 和 main。运行一下:

太棒了!libtest.so 的代码天然看上去有些愚蠢,但是它竟然不错正确的责任,那还有什么可颓废的呢?飞快在新版 APP 中运专揽用它吧!

缺憾的是,正如你可能依然发现的,libtest.so 存在严重的内存走漏问题,每调用一次 say_hello 函数,就会走漏 1024 字节的内存。新版 APP 上线后崩溃率运行高涨,多样诡异的崩溃信息和报障信息跌撞而至。

侥幸的是,咱们竖立了 libtest.so 的问题。然则以后如何办呢?咱们濒临 2 个问题:

当测试遮蔽不实时,如何实时发现和准服气位线上 APP 的此类问题? 如果 libtest.so 是某些机型的系统库,或者第三方的闭源库,咱们如何竖立它?如果监控它的活动?

如果咱们能对动态库中的函数调用作念 hook(替换,阻止,窃听,或者你认为任何正确的神态方式),那就大略作念到许多咱们想作念的事情。比如 hook malloc,calloc,realloc 和 free,咱们就能统计出各个动态库分拨了若干内存,哪些内存一直被占用莫得开释。

这确凿能作念到吗?谜底是:hook 咱们我方的程度是完全不错的。hook 其他程度需要 root 权限(关于其他程度,莫得 root 权限就没法修改它的内存空间,也没法注入代码)。侥幸的是,咱们只须 hook 我方就够了。

ELF(Executable and Linkable Format)是一种行业范例的二进制数据封装花样,主要用于封装可实践文献、动态库、object 文献和 core dumps 文献。

使用 google NDK 对源代码进行编译和相连,生成的动态库或可实践文献都是 ELF 花样的。用 readelf 不错稽察 ELF 文献的基本信息,用 objdump 不错稽察 ELF 文献的反汇编输出。

ELF 花样的抽象不错参考 这里,竣工界说不错参考 这里。其中最紧迫的部分是:ELF 文献头、SHT(section header table)、PHT(program header table)。

ELF 文献的肇端处,有一个固定花样的定长的文献头(32 位架构为 52 字节,64 位架构为 64 字节)。ELF 文献头以 magic number 0x7F 0x45 0x4C 0x46 运行(其中后 3 个字节鉴识对应可见字符 E L F)。

libtest.so 的 ELF 文献头信息:

ELF 文献头中包含了 SHT 和 PHT 在刻下 ELF 文献中的肇端位置和长度。举例,libtest.so 的 SHT 肇端位置为 12744,长度 40 字节;PHT 肇端位置为 52,长度 32字节。

ELF 以 section 为单元来组织和经管多样信息。ELF 使用 SHT 来纪录所有 section 的基本信息。主要包括:section 的类型、在文献中的偏移量、大小、加载到内存后的编造内存相对地址、内存中字节的对皆方式等。

libtest.so 的 SHT:

比拟紧迫,且和 hook 关联比拟大的几个 section 是:

.dynstr:保存了所有的字符串常量信息。 .dynsym:保存了象征(symbol)的信息(象征的类型、肇端地址、大小、符堪称呼在 .dynstr 中的索引编号等)。函数亦然一种象征。 .text:顺次代码经过编译青年景的机器提示。 .dynamic:供动态相连器使用的各项信息,纪录了刻下 ELF 的外部依赖,以过甚他各个紧迫 section 的肇端位置等信息。 .got:Global Offset Table。用于纪录外部调用的进口地址。动态相连器(linker)实践重定位(relocate)操作时,这里会被填入着实的外部调用的统统地址。 .plt:Procedure Linkage Table。外部调用的跳板,主要用于撑握 lazy binding 方式的外部调用重定位。(Android 面前只须 MIPS 架构撑握 lazy binding) .rel.plt:对外部函数径直调用的重定位信息。 .rel.dyn:除 .rel.plt 除外的重定位信息。(比如通过全局函数指针来调用外部函数)

ELF 被加载到内存时,是以 segment 为单元的。一个 segment 包含了一个或多个 section。ELF 使用 PHT 来纪录所有 segment 的基本信息。主要包括:segment 的类型、在文献中的偏移量、大小、加载到内存后的编造内存相对地址、内存中字节的对皆方式等。

libtest.so 的 PHT:

所有类型为 PT_LOAD 的 segment 都会被迫态相连器(linker)映射(mmap)到内存中。

聚拢视图:ELF 未被加载到内存实践前,以 section 为单元的数据组织形势。 实践视图:ELF 被加载到内存后,以 segment 为单元的数据组织形势。

咱们和顺的 hook 操作,属于动态形势的内存操作,因此主要和顺的是实践视图,即 ELF 被加载到内存后,ELF 中的数据是如何组织和存放的。

这是一个相当紧迫和特等的 section,其中包含了 ELF 中其他各个 section 的内存位置等信息。在实践视图中,老是会存在一个类型为 PT_DYNAMIC 的 segment,这个 segment 就包含了 .dynamic section 的试验。

无论是实践 hook 操作时,照旧动态相连器实践动态相连时,都需要通过 PT_DYNAMIC segment 来找到 .dynamic section 的内存位置,再进一步读取其他各项 section 的信息。

libtest.so 的 .dynamic section:

安卓中的动态相连器顺次是 linker。源码在 这里。

动态相连(比如实践 dlopen)的梗概法子是:

查抄已加载的 ELF 列表。(如果 libtest.so 依然加载,就不再重叠加载了,仅把 libtest.so 的援用计数加一,然后径直复返。) 从 libtest.so 的 .dynamic section 中读取 libtest.so 的外部依赖的 ELF 列表,从此列表中剔除已加载的 ELF,临了得到本次需要加载的 ELF 竣工列表(包括 libtest.so 自身)。 逐一加载列表中的 ELF。加载法子: 用 mmap 预留一块有余大的内存,用于后续映射 ELF。(MAP_PRIVATE 方式) 读 ELF 的 PHT,用 mmap 把所有类型为 PT_LOAD 的 segment 轮番映射到内存中。 从 .dynamic segment 中读取各信息项,主如若各个 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 的构造函数(constructor),这些构造函数的地址是之前从 .dynamic segment 中读取到的(类型为 DT_INIT 和 DT_INIT_ARRAY)。各 ELF 的构造函数是按照依赖关联逐层调用的,先调用被依赖 ELF 的构造函数,临了调用 libtest.so 我方的构造函数。(ELF 也不错界说我方的析构函数(destructor),在 ELF 被 unload 的时辰会被自动调用)

等一下!咱们似乎发现了什么!再看一遍重定位操作(relocate)的部分。难说念咱们只须从这些 .relxxx 中赢得到“目标地址”,然后在“目标地址”中再行填上一个新的函数地址,这样就完成 hook 了吗?也许吧。

静态分析考据一下照旧很容易的。以 armeabi-v7a 架构的 libtest.so 为例。先看一下 say_hello 函数对应的汇编代码吧。

找到了!say_hello 在地址 f61,对应的汇编提示体积为 60(10 进制)字节。用 objdump 稽察 say_hello 的反汇编输出。

对 malloc 函数的调用对应于提示 blx dd4。跳转到了地址 dd4。望望这个地址里有什么吧:

porn国产

果然,跳转到了 .plt 中,经过了几次地址策画,临了跳转到了地址 3f90 中的值指向的地址处,3f90 是个函数指针。

略微讲解注解一下:因为 arm 处理器使用 3 级活水线,是以第一条提示取到的 pc 的值是刻下实践的提示地址 + 8。 于是:dd4 + 8 + 3000 + 1b4 = 3f90。

地址 3f90 在那里呢:

果然,在 .got 里。

趁便再看一下 .rel.plt:

malloc 的地址竟然正好存放在 3f90 里,这统统不是正好啊!还等什么,飞快改代码吧。咱们的 main.c 应该改成这样:

编译运行一下:

念念路是正确的。但之是以照旧失败了,是因为这段代码存鄙人面的 3 个问题:

3f90 是个相对内存地址,需要把它换算成统统地址。 3f90 对应的统统地址很可能莫得写入权限,径直对这个地址赋值会引起段罪状。 新的函数地址即使赋值收效了,my_malloc 也不会被实践,因为处理器有提示缓存(instruction cache)。

咱们需要措置这些问题。

在程度的内存空间中,多样 ELF 的加载地址是随即的,只须在运行时智力拿到加载地址,也便是基地址。咱们需要知说念 ELF 的基地址,智力将相对地址换算成统统地址。

莫得错,闇练 Linux 勾引的灵巧的你一定知说念,咱们不错径直调用 dl_iterate_phdr。详备的界说见 这里。

嗯,先等等,多年的 Android 勾引被坑履历告诉咱们,照旧再看一眼 NDK 里的 linker.h 头文献吧:

为什么?!ARM 架构的 Android 5.0 以下版块竟然不撑握 dl_iterate_phdr!咱们的 APP 然则要撑握 Android 4.0 以上的所有版块啊。异常是 ARM,如何能不撑握呢?!这还让不让东说念主写代码啦!

侥幸的是,咱们意想了,咱们还不错明白 /proc/self/maps:

maps 复返的是指定程度的内存空间中 mmap 的映射信息,包括多样动态库、可实践文献(如:linker),栈空间,堆空间,甚而还包括字体文献。maps 花样的详备说卓见 这里。

咱们的 libtest.so 在 maps 中有 3 行纪录。offset 为 0 的第一转的肇端地址 b6ec6000 在绝大多量情况下便是咱们寻找的基地址。

maps 复返的信息中依然包含了权限造访信息。如果要实践 hook,就需要写入的权限,不错使用 mprotect 来完成:

真贵修改内存造访权限时,只不错“页”为单元。mprotect 的详备说卓见 这里。

真贵 .got 和 .data 的 section 类型是 PROGBITS,也便是实践代码。处理器可能会对这部分数据作念缓存。修改内存地址后,咱们需要毁灭处理器的提示缓存,让处理器再行从内存中读取这部分提示。方法是调用 __builtin___clear_cache:

真贵毁灭提示缓存时,也只不错“页”为单元。__builtin___clear_cache 的详备说卓见 这里。

咱们把 main.c 修改为:

再行编译运行:

是的,收效了!咱们并莫得修改 libtest.so 的代码,甚而莫得再行编译它。咱们只是修改了 main 顺次。

libtest.so 和 main 的源码放在 github 上,不错从 这里 赢得到。(把柄你使用的编译器不同,或者编译器的版块不同,生成的 libtest.so 中,也许 malloc 对应的地址不再是 0x3f90,这时你需要先用 readelf 说明,然后再到 main.c 中修改。)

天然,咱们依然开源了一个叫 xhook 的器具库。使用 xhook,你不错更优雅的完成对 libtest.so 的 hook 操作,也无用回想硬编码 0x3f90 导致的兼容性问题。

xhook 撑握 armeabi, armeabi-v7a 和 arm64-v8a。撑握 Android 4.0 (含) 以上版块 (API level >= 14)。经过了产等第的认知性和兼容性考据。不错在 这里 赢得 xhook。

回来一下 xhook 中实践 PLT hook 的经过:

读 maps,赢得 ELF 的内存首地址(start address)。 考据 ELF 头信息。 从 PHT 中找到类型为 PT_LOAD 且 offset 为 0 的 segment。策画 ELF 基地址。 从 PHT 中找到类型为 PT_DYNAMIC 的 segment,从中赢得到 .dynamic section,从 .dynamic section中赢得其他各项 section 对应的内存地址。 在 .dynstr section 中找到需要 hook 的 symbol 对应的 index 值。 遍历所有的 .relxxx section(重定位 section),查找 symbol index 和 symbol type 都匹配的项,关于这项重定位项,实践 hook 操作。hook 经过如下: 读 maps,说明刻下 hook 地址的内存造访权限。 如果权限不是可读也可写,则用 mprotect 修改动访权限为可读也可写。 如果调用方需要,就保留 hook 地址刻下的值,用于复返。 将 hook 地址的值替换为新的值。(实践 hook) 如果之前用 mprotect 修自新内存造访权限,面前还原到之前的权限。 毁灭 hook 地址所在内存页的处理器提示缓存。

不错。况兼关于花样明白来说,读文献是最适应的方式,因为 ELF 在运行时,旨趣上有许多 section 不需要一直保留在内存中,不错在加载完之后就从内存中丢弃,这样不错检朴极少的内存。但是从实践的角度动身,多样平台的动态相连器和加载器,都不会这样作念,可能它们认为增多的复杂度焉知非福。是以咱们从内存中读取多样 ELF 信息就不错了,读文献反而增多了性能损耗。另外,某些系统库 ELF 文献,APP 也不一定有造访权限。

正如你依然真贵到的,前边先容 libtest.so 基地址赢得时,为了简化倡导和编码便捷,用了“绝大多量情况下”这种不应该出现的神态方式。关于 hook 来说,精准的基地址策画经过是:

在 maps 中找到找到 offset 为 0,且 pathname 为目标 ELF 的行。保存该行的 start address 为 p0。 找出 ELF 的 PHT 中第一个类型为 PT_LOAD 且 offset 为 0 的 segment,保存该 segment 的编造内存相对地址(p_vaddr)为 p1。 p0 - p1 即为该 ELF 刻下的基地址。

绝大多量的 ELF 第一个 PT_LOAD segment 的 p_vaddr 都是 0。

另外,之是以要在 maps 里找 offset 为 0 的行,是因为咱们在实践 hook 之前,但愿对内存中的 ELF 文献头进行校验,确保刻下操作的是一个灵验的 ELF,而这种 ELF 文献头只可出面前 offset 为 0 的 mmap 区域。

不错在 Android linker 的源码中搜索“load_bias”,不错找到许多详备的谛视讲解,也不错参考 linker 中对 load_bias_ 变量的赋值顺次逻辑。

会有一些影响。

关于外部函数的调用,不错分为 3 中情况:

径直调用。无论编译选项如何,都不错被 hook 到。外部函数地址恒久保存在 .got 中。 通过全局函数指针调用。无论编译选项如何,都不错被 hook 到。外部函数地址恒久保存在 .data 中。 通过局部函数指针调用。如果编译选项为 -O2(默许值),调用将被优化为径直调用(戚然况 1)。如果编译选项为 -O0,则在实践 hook 前依然被赋值到临时变量中的外部函数的指针,通过 PLT 方式无法 hook;关于实践 hook 之后才被赋值的,不错通过 PLT 方式 hook。

一般情况下,产等第的 ELF 很少会使用 -O0 进行编译,是以也无用太纠结。但是如果你但愿你的 ELF 尽量不被别东说念主 PLT hook,那不错试试使用 -O0 来编译,然后尽量早的将外部函数的指针赋值给局部函数指针变量,之后一直使用这些局部函数指针来造访外部函数。

总之,稽察 C/C++ 的源代码对这个问题的认知莫快活念念,需要稽察使用不同的编译选项后,生成的 ELF 的反汇编输出,比拟它们的区别,智力知说念哪些情况由于什么原因导致无法被 PLT hook。

咱们巧合会碰到这样的问题:

读取 /proc/self/maps 后发现某个内存区域的造访权限为可读,当咱们读取该区域的试验作念 ELF 文献头校验时,发生了段罪状(sig: SIGSEGV, code: SEGV_ACCERR)。 依然用 mprotect() 修改了某个内存区域的造访权限为可写,mprotect() 复返修改收效,然后再次读取 /proc/self/maps 说明对应内存区域的造访权限如实为可写,实践写入操作(替换函数指针,实践 hook)时发生段罪状(sig: SIGSEGV, code: SEGV_ACCERR)。 读取和考据 ELF 文献头收效了,把柄 ELF 头中的相对地址值,进一步读取 PHT 或者 .dynamic section 时发生段罪状(sig: SIGSEGV, code: SEGV_ACCERR 或 SEGV_MAPERR)。

可能的原因是:

程度的内存空间是多线程分享的,咱们在实践 hook 时,其他线程(甚而 linker)可能正在实践 dlclose(),或者正在用 mprotect() 修改这块内存区域的造访权限。 不同厂家、机型、版块的 Android ROM 可能有未公开的活动,比如在某些情况下对某些内存区域存在写保护或者读保护机制,而这些保护机制并不反映在 /proc/self/maps 的试验中。

问题分析:

读内存时发生段罪状其实是无害的。 我在 hook 实践的经过中,需要径直通过策画内存地址的方式来写入数据的所在只须一处:即替换函数指针的最关节的那一转。只须其他所在的逻辑莫得罪状,这里就算写入失败了,也不会对其他内存区域形成粗疏。 加载运行安卓平台的 APP 程度时,加载器依然向咱们注入了 signal handler 的注册逻辑,以便 APP 崩溃时与系统的 debuggerd 督察程度通信,debuggerd 使用 ptrace 调试崩溃程度,赢得需要的崩溃现场信息,纪录到 tombstone 文献中,然后 APP 自裁。 系统会精准的把段罪状信号发送给“发生段罪状的线程”。 咱们但愿能有一种瞒哄的,且可控的方式来幸免段罪状引起 APP 崩溃。

先明确一个不雅点:不要只从应用层顺次勾引的角度来看待段罪状,段罪状不是激流猛兽,它只是内核与用户程度的一种往时的不异方式。当用户程度造访了无权限或未 mmap 的编造内存地址时,内核向用户程度发送 SIGSEGV 信号,来见知用户程度,仅此良友。只须段罪状的发生位置是可控的,咱们就不错在用户程度中处理它。

措置有经营:

当 hook 逻辑干预咱们认为的危急区域(径直策画内存地址进行读写)之前,通过一个全局 flag 来进行象征,离开危急区域后将 flag 复位。 注册咱们我方的 signal handler,只拿获段罪状。在 signal handler 中,通过判断 flag 的值,来判断刻下方程逻辑是否在危急区域中。如果是,就用 siglongjmp 跳出 signal handler,径直跳到咱们事先确立好的“危急区域除外的下一转代码处”;如果不是,就收复之前加载器向咱们注入的 signal handler,然后径直复返,这时系统会再次向咱们的线程发送段罪状信号,由于依然收复了之前的 signal handler,这时会干预默许的系统 signal handler 中走往时逻辑。 咱们把这种机制简称为:SFP (segmentation fault protection,段罪状保护) 真贵:SFP需要一个开关,让咱们随时大略开启和关闭它。在 APP 勾引调试阶段,SFP 应该恒久被关闭,这样就不会错过由于编码罪状导致的段罪状,这些罪状是应该被竖立的;在庄重上线后 SFP 应该被开启,这样能保证 APP 不会崩溃。(天然,以采样的形势部分关闭 SFP,用以不雅察和分析 hook 机制自身导致的崩溃,亦然不错商酌的)

具体代码不错参考 xhook 中的收场,在源码中搜索 siglongjmp 和 sigsetjmp。

咱们这里先容的 hook 方式为 PLT hook,不行作念 ELF 里面函数之间调用的 hook。

inline hook 不错作念到,你需要先知说念想要 hook 的里面函数象征名(symbol name)或者地址,然后不错 hook。

有许多开源和非开源的 inline hook 收场,比如:

substrate: frida:https://www.frida.re/

inline hook 有经营弘大的同期可能带来以下的问题:

由于需要径直明白和修改 ELF 中的机器提示(汇编码),关于不同架构的处理器、处理器提示集、编译器优化选项、操作系统版块可能存在不同的兼容性和认知性问题。 发生问题后可能难以分析和定位,一些著明的 inline hook 有经营是闭源的。 收场起来相对复杂,难度也较大。 未知的坑相对较多,这个不错自行 google。

提议如果 PLT hook 够用的话,就无用尝试 inline hook 了。

caikelun@gmail.com https://github.com/caikelun

Copyright (c) 2018, 爱奇艺, Inc. All rights reserved.

本文使用 Creative Commons 许可证 授权性爱技巧。



创建或修改目录:/www/wwwroot/104.219.215.234/data 失败!
JzEngine Create File False