:fire: ShadowHook is an Android inline hook library which supports thumb, arm32 and arm64.
Issue: #50. Thanks: @ScalletaZ, @mskmkt0704 Issue: #53. Thanks: @osm0sis
shadowhook_hook_sym_name
and shadowhook_hook_sym_name_callback
may resolve symbol names incorrectly.This bug could cause the wrong function to be hooked when .symtab
contains symbol names with the same prefix.
For example: Now we want to hook je_mallctlnametomib
, but actually hook je_mallctl
. Generally, this will lead to a certain crash.
This bug exists in versions 1.0.7
and 1.0.8
.
Issue: #50. Thanks: @ScalletaZ, @mskmkt0704 Issue: #53. Thanks: @osm0sis
shadowhook_hook_sym_name
和 shadowhook_hook_sym_name_callback
解析符号名可能错误的bug。当 .symtab
中含有相同前缀的符号名时,这个 bug 可能会导致 hook 错误的函数。
例如:现在要 hook je_mallctlnametomib
,但实际 hook 了 je_mallctl
,一般情况下这会导致必现的崩溃。
此 bug 存在于 1.0.7
和 1.0.8
版本中。
In the previous version, if the system supported arm64 BTI and the hooked ELF was compiled with BTI instructions, there would be about a 1/1024 probability of a crash. The crash will not occur randomly, it will only occur when the "1st instruction and the 2nd instruction (or the 4th instruction and the 5th instruction)" at the head of the hooked function belong to two different memory pages.
The crash protection mechanism of shadowhook registers the signal handler of sigsegv and sigbus, but SA_EXPOSE_TAGBITS
was not added in previous versions. Because the signal handler of shadowhook will be executed before the art sigchain, the tag bits in the address will be lost, which will lead to the failure of the MTE mechanism.
In previous versions, shadowhook's signal handler would occupy some signal stack memory (64 bytes for arm32 and 128 bytes for arm64). After optimization, the signal stack memory will not be occupied.
Since the Android signal stack memory space is very limited (depending on the Android version and CPU architecture, each thread is approximately between 8KB and 32KB), the additional occupation of the signal stack memory space can easily aggravate the risk of signal stack overflow.
在之前的版本中,如果系统支持 arm64 BTI 并且被 hook 的 ELF 编译时也加入了 BTI 指令,那么大约会有 1/1024 的概率发生崩溃。崩溃不会随机发生,只有当被 hook 函数头部的“第1条指令和第2条指令(或者第4条指令和第5条指令)”分别属于两个不同的内存页时才会发生。
shadowhook 的崩溃保护机制注册了 sigsegv 和 sigbus 的信号处理函数,但是在之前的版本中没有添加 SA_EXPOSE_TAGBITS
。因为 shadowhook 的信号处理函数会比 art sigchain 的先执行,于是导致了地址中 tag bits 丢失,进而导致 MTE 机制失效。
在之前的版本中,shadowhook 的信号处理函数会占用一些信号栈内存(arm32 为 64 字节,arm64 为 128 字节)。优化后不会占用信号栈内存。
由于 Android 信号栈内存空间十分有限(根据 Android 版本和 CPU 架构不同,每个线程大约在 8KB 到 32 KB 之间),所以对信号栈内存空间的额外占用,很容易加剧信号栈溢出的风险。
ShadowHook currently only supports arm and arm64 architectures. When running in the x86 Houdini environment, if you use ShadowHook to hook the system library of the x86 architecture, it will crash.
In particular, when the user tries to hook the library that has not been loaded into the memory through shadowhook_hook_sym_name()
or shadowhook_hook_sym_name_callback()
, ShadowHook will hook the linker‘s do_dlopen
internally in order to automatically complete the hook work when the library is loaded into memory in the future, and the linker of the x86 Houdini environment is also of the x86 architecture, which leads to a crash.
We now check the architecture of the hooked ELF file before hooking. If the architecture does not match, the corresponding error code will be returned. We have added error codes 34
and 35
, which correspond to the two cases of "the architecture of the ELF that the user wants to hook does not match" and "the architecture of the linker does not match" respectively.
Previously, the BYTEHOOK_STACK_SCOPE
macro in the shadowhook.h header file contained a temporary variable starting with a double underscore, which caused a compilation warning (reserved-identifier
) in some compiler versions.
LLVM may add additional suffixes to symbols in ELF .symtab
. The format of the suffix is .xxxx.hash
, such as _ZN3artL21IsSafeToCallAbortSafeEv.__uniq.55395457626730424248235132913560037531.llvm.1533082929482216501
, the _ZN3artL21IsSafeToCallAbortSafeEv
is called canonical name, the hash section in the suffix may change after recompilation.
Passing only the canonical name is now supported when passing symbol names in shadowhook_dlsym()
, shadowhook_dlsym_symtab()
, shadowhook_hook_sym_name()
and shadowhook_hook_sym_name_callback()
.
ShadowHook 目前只支持 arm 和 arm64 架构,在 x86 Houdini 环境中运行时,如果用 ShadowHook 来 hook x86 架构的系统库就会发生崩溃。
尤其是,当用户通过 shadowhook_hook_sym_name()
或 shadowhook_hook_sym_name_callback()
试图 hook 还未加载到内存中的 so 库时,ShadowHook 为了在这个 so 库未来被加载到内存时自动的完成 hook 工作,会在内部 hook linker 的 do_dlopen
,而 x86 Houdini 环境的 linker 也是 x86 架构的,这就导致了崩溃。
我们现在在 hook 之前先检测被 hook ELF 文件的架构,如果架构不匹配,会返回对应的错误码。我们新增了错误码 34
和 35
,分别对应 “用户希望 hook 的 ELF 的架构不匹配” 和 “linker 的架构不匹配” 这两种情况。
之前 shadowhook.h 头文件中 BYTEHOOK_STACK_SCOPE
宏中包含一个以双下划线开头的临时变量,这在某些编译器版本中会引发一个编译警告(reserved-identifier
)。
LLVM 可能为 ELF .symtab
中的符号添加额外的后缀,后缀的格式是 .xxxx.hash
,比如 _ZN3artL21IsSafeToCallAbortSafeEv.__uniq.55395457626730424248235132913560037531.llvm.1533082929482216501
,其中的 _ZN3artL21IsSafeToCallAbortSafeEv
称为 canonical name,后缀中的 hash 重新编译后可能发生变化。
现在,在 shadowhook_dlsym()
,shadowhook_dlsym_symtab()
,shadowhook_hook_sym_name()
和 shadowhook_hook_sym_name_callback()
中传递符号名时,支持只传递 canonical name。
Added API and initialization parameters to enable and disable operation record.
Writing operation records every time hook and unhook will have some impact on performance. It is recommended to sample or enable operation records according to actual needs.
localtime_r()
in operation record.localtime_r()
will call getenv()
to access the global environ
, if there are concurrent setenv()
calls at this time, a crash may occur, because in bionic, the access to environ
is not protected by a lock.
What we can do currently is try to avoid calling getenv()
and setenv()
.
abort()
will be triggered if a proxy function written for shared mode is used.Doing so allows for clearer and earlier detection of such do-not-use issues.
At the same time, in the case of only ELF files, the version number of ShadowHook can also be determined in the following ways:
llvm-strings libshadowhook.so | grep "shadowhook version"
增加了 API 和初始化参数用于开启和关闭操作记录。
每次 hook 和 unhook 时都写操作记录对性能会有一些影响,建议根据实际需要采样的或有针对性的开启操作记录。
localtime_r()
获取时区。localtime_r()
会调用 getenv()
来访问全局的 environ
,如果此时存在并发的 setenv()
调用,则可能会发生崩溃,因为对 environ
的访问在 bionic 中没有锁保护。
我们目前能做的是尽量避免调用 getenv()
和 setenv()
。
abort()
。这样做可以更明确和更早的发现这类勿用问题。
同时,在仅有 ELF 文件的情况下,也可以通过以下方式确定 ShadowHook 的版本号:
llvm-strings libshadowhook.so | grep "shadowhook version"
This bug will cause the hook stability of some functions to decrease. The bug occurs when the absolute address of a function is in the following ranges:
arch | address ranges |
---|---|
thumb | [0, 0x1000000) |
thumb | (0xFF000001, 0xFFFFFFFF] |
arm | [0, 0x2000000) |
arm | (0xFE000003, 0xFFFFFFFF] |
arm64 | [0, 0x8000000) |
arm64 | (0xFFFFFFFFF8000003, 0xFFFFFFFFFFFFFFFF] |
The first LOAD segment of ELF may be read-only (use the linker option --rosegment
), and the /proc/self/maps
at this time may look like this:
75b8d000-75b9f000 r--p 00000000 b3:1c 89884 /data/app-lib/io.hexhacking.xdl.sample-2/libquick.so
75b9f000-75bde000 r-xp 00012000 b3:1c 89884 /data/app-lib/io.hexhacking.xdl.sample-2/libquick.so
75bde000-75be1000 r--p 00051000 b3:1c 89884 /data/app-lib/io.hexhacking.xdl.sample-2/libquick.so
75be1000-75be2000 rw-p 00054000 b3:1c 89884 /data/app-lib/io.hexhacking.xdl.sample-2/libquick.so
In previous ShadowHook versions, this type of ELF could not be hooked in Android 4.x.
ShadowHook#init()
is called concurrently.It may actually be still being initialized, but it returns a state that has been initialized.
ShadowHook needs to obtain several symbol addresses in libc.so
through dlopen
and dlsym
during initialization. These operations need to hold the linker's global mutex lock. We moved the above operations to .init_array
of libshadowhook.so
.
这个 bug 会导致部分函数的 hook 稳定性下降。当函数的绝对地址在以下范围内时,会出现这个 bug:
架构 | 地址范围 |
---|---|
thumb | [0, 0x1000000) |
thumb | (0xFF000001, 0xFFFFFFFF] |
arm | [0, 0x2000000) |
arm | (0xFE000003, 0xFFFFFFFF] |
arm64 | [0, 0x8000000) |
arm64 | (0xFFFFFFFFF8000003, 0xFFFFFFFFFFFFFFFF] |
ELF 的第一个 LOAD segment 可能是只读的(用链接器选项 --rosegment
),此时的 /proc/self/maps
大概是这样的:
75b8d000-75b9f000 r--p 00000000 b3:1c 89884 /data/app-lib/io.hexhacking.xdl.sample-2/libquick.so
75b9f000-75bde000 r-xp 00012000 b3:1c 89884 /data/app-lib/io.hexhacking.xdl.sample-2/libquick.so
75bde000-75be1000 r--p 00051000 b3:1c 89884 /data/app-lib/io.hexhacking.xdl.sample-2/libquick.so
75be1000-75be2000 rw-p 00054000 b3:1c 89884 /data/app-lib/io.hexhacking.xdl.sample-2/libquick.so
在之前的 ShadowHook 版本中,在 Android 4.x 中这种类型 ELF 无法被 hook。
ShadowHook#init()
时可能返回错误的初始化状态的 bug。可能实际还处在初始化中,但是却返回了已经初始化完成的状态。
ShadowHook 需要在初始化时通过 dlopen
和 dlsym
获取 libc.so
中的几个符号地址,这些操作需要持有 linker 的全局 mutex 锁,我们将上述操作移动到了 libshadowhook.so
的 .init_array
中。
shadowhook_hook_func_addr()
for hooking a function (which has no symbol info in ELF) by absolute address.shadowhook_hook_sym_name
and shadowhook_hook_sym_name_callback
.shadowhook_hook_func_addr()
,用于通过绝对地址 hook 一个在 ELF 中没有符号信息的函数。shadowhook_hook_sym_name
和 shadowhook_hook_sym_name_callback
的执行性能。.symtab
, the hook would fail. (such as hooking __openat
in libc.so
).symtab
中,此时会 hook 失败。(比如 hook libc.so
中的 __openat
)First version.
第一个版本。