ByteHook: ByteDance's Android PLT hook library for native code
通用的Android PLT hook库,支持armeabi-v7a,arm64-v8a,x86,x86_64
At a glance
- What is it?
- ByteHook is an MIT-licensed C library that patches PLT entries in Android processes, supports four ABIs, and ships through Maven Central with Prefab. It is aimed at SDK and app teams that need to intercept calls into imported functions without touching the caller's source.
- Who is it for?
- Adopt ByteHook if you maintain an Android SDK or app that must intercept calls into imported native functions and you can accept PLT-only scope: the README states it hooks PLT entries, and the same project points at shadowhook when inline hooking is required. Do not adopt it if you need to patch instructions inside a function body, or if you cannot add the BYTEHOOK_CALL_PREV and BYTEHOOK_POP_STACK macros to every proxy.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 106 days ago.
- What is it written in?
- Mainly C, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem ByteHook addresses in Android native code
Android apps that ship native libraries often need to observe or replace a function that lives in someone else's .so file. The usual approaches are to rebuild that library, to patch its machine code in memory, or to intercept the dynamic linker's resolution step. ByteHook takes the third route and works at the PLT level. The README describes it as "an Android PLT hook library" with three stated goals: stability in production apps, backward compatibility of API and ABI across versions, and reduced API call overhead plus reduced runtime overhead introduced by the hooks themselves.
The audience is narrow and specific. You are writing an SDK that other apps will embed, or you are maintaining an app with native components, and you need to hook a function you do not own. The README's own framing of the alternative is telling: it says that if you need an inline hook library, you should try shadowhook instead. That sentence is the clearest statement of scope in the whole document. ByteHook patches import tables, not instruction streams.
Four ABIs are listed as supported: armeabi-v7a, arm64-v8a, x86 and x86_64. The OS range is Android 4.1 through 17 QPR1 Beta 4, which the README maps to API level 16 through 37. Those two numbers together define the compatibility envelope you inherit when you adopt the library.
How the PLT hooking mechanism and stub lifecycle work
The public surface is four functions. Three of them install hooks and return a bytehook_stub_t handle; the fourth removes a hook. bytehook_hook_single takes a caller_path_name and a callee_path_name, so you name both the library whose call site you are rewriting and the library that owns the symbol. bytehook_hook_partial takes a caller_allow_filter callback plus an argument, which lets you decide at runtime which callers are eligible. bytehook_hook_all drops the caller restriction entirely and hooks every caller in the process. All three take sym_name, new_func, and an optional hooked callback with its argument.
The stub is the unit of control. bytehook_unhook takes that handle and returns an int. Because each call to a hook function produces its own stub, the README can claim that multiple hooks and unhooks for the same function do not conflict. That is a reference-counting design rather than a last-writer-wins design, and it matters in a process where two SDKs both want to intercept the same symbol.
Two macros govern the proxy function itself. BYTEHOOK_CALL_PREV() is how you reach the original implementation; the README says to always use it when calling the original from a proxy. BYTEHOOK_POP_STACK() must run before the proxy returns, and in C++ you can place BYTEHOOK_STACK_SCOPE() at the top of the proxy instead. That stack bookkeeping is what lets the library avoid recursive and circular calls between proxies automatically, and it is also what allows backtrace unwinding to work inside a proxy. Skip the macro and you break both properties at once.
Automatic hooking of newly loaded libraries is listed as a feature. The README does not describe the loader callback that implements it, so the timing guarantee you get for a library dlopen'd after your hook call is not documented in the README.
Installing ByteHook through Maven Central and Prefab
ByteHook is published on Maven Central and uses the Prefab package format for native dependencies, which the README says requires Android Gradle Plugin 4.0 or later. First enable Prefab in the module and declare the dependency, replacing x.y.z with the release you want. The latest release listed is 1.1.2 from 2026-06-16.
android {
buildFeatures {
prefab true
}
}
dependencies {
implementation 'com.bytedance:bytehook:x.y.z'
}If your Android Gradle Plugin is older than 7.1.0 you also need the prefab schema v2 setting, because ByteHook uses that schema and it is only configured by default from 7.1.0 onward. Add this line to gradle.properties.
android.prefabVersion=2.0.0Next, link the native target. The README gives both CMake and ndk-build forms; the CMake one finds the package and links bytehook::bytehook into your own shared library.
find_package(bytehook REQUIRED CONFIG)
add_library(mylib SHARED mylib.c)
target_link_libraries(mylib bytehook::bytehook)Restrict the ABIs you build for, since the library only ships the four architectures it supports.
android {
defaultConfig {
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
}
}Then decide the packaging rule based on what you are building. An SDK project should exclude the shared object so it is not baked into the AAR; an app project should pick the first copy to resolve duplicates. The README warns that x86 and x86_64 prefab dependencies additionally require applying gradle/prefab_bypass.gradle to the module.
android {
packagingOptions {
exclude '**/libbytehook.so'
}
}Finally, initialize from Java before any hook runs. The README shows a synchronized init method on an SDK class that calls ByteHook.init(). In C, include bytehook.h and call one of the three hook functions. A minimal single-caller hook looks like the signature below; the proxy you pass as new_func must call BYTEHOOK_CALL_PREV() to reach the original and BYTEHOOK_POP_STACK() (or BYTEHOOK_STACK_SCOPE() in C++) before returning.
bytehook_stub_t bytehook_hook_single(
const char *caller_path_name,
const char *callee_path_name,
const char *sym_name,
void *new_func,
bytehook_hooked_t hooked,
void *hooked_arg);A working reference is included in the repository: the bytehook_sample directory holds a sample app, and bytehook_systest is also present at the top level. The README points at the sample rather than walking through a complete proxy, so expect to read that source to see the macros placed correctly.
Where ByteHook is the wrong tool
The PLT boundary is a hard boundary. A function that is called internally within its own shared object, or that has been inlined by the compiler, never goes through the import table, so a PLT hook cannot see it. The README does not pretend otherwise: it explicitly redirects inline-hook use cases to shadowhook. If your target is a static function, a symbol resolved at link time inside the same library, or a call site you need to modify rather than redirect, ByteHook is not the library for the job.
The macro contract is the second constraint. BYTEHOOK_POP_STACK() is mandatory before the proxy returns, and BYTEHOOK_CALL_PREV() is the only sanctioned way to reach the original. A proxy that returns early through an error path without popping the stack violates the contract, and the README states the requirement without describing what happens when it is broken. Treat the macros as part of the function signature: every exit path needs them.
Packaging is a third source of friction. The README devotes a whole step to duplicate libbytehook.so conflicts, with opposite advice for SDK and app projects, plus a separate bypass file for x86 and x86_64 prefab users. If your build has several modules that each pull ByteHook, or if you do not know whether a given module is an SDK or an app, resolve that before you add the dependency, because picking the wrong packagingOptions rule produces a build failure rather than a warning.
ByteHook compared with shadowhook and with rebuilding the library
The most direct alternative named in the README is shadowhook, also from ByteDance, which is an inline hook library. The difference is where the patch lands. ByteHook rewrites the import table entry so that calls into a symbol from other libraries arrive at your proxy; shadowhook rewrites instructions at the target address itself. Inline hooking reaches functions that PLT hooking cannot, including intra-library calls, but it operates on machine code, which is a different class of risk on a platform where the OS range spans API 16 to 37. The README's own cross-reference is the honest summary: pick by hook type, not by preference.
The other alternative is to stop hooking entirely. If you control the library that owns the symbol, rebuilding it with a callback or a build flag removes the need for any runtime patch, and it removes the ABI and packaging concerns along with it. That option is unavailable when the library is a third-party binary, which is the situation ByteHook was built for.
Against both alternatives, ByteHook's distinguishing property is its stub model. Because each hook call returns an independent handle and unhooking is per-stub, multiple parties in one process can hook the same symbol without overwriting each other. The README also lists automatic hooking of newly loaded libraries and backtrace unwinding inside proxies, both of which are features you would otherwise have to assemble yourself around a lower-level PLT patcher.
Maintenance status, licence and upgrade cost
The repository is not archived. The last push was on 2026-06-16, the same day release v1.1.2 was published. Before that, v1.1.1 landed on 2025-01-06 and v1.1.0 on 2024-11-05. So the release cadence is roughly one version per several months, with a longer gap between 1.1.0 and 1.1.1 than between 1.1.1 and 1.1.2. The README states that new versions always maintain backward compatibility of API and ABI, which is the claim that matters most for upgrade cost: if it holds, bumping the version string in build.gradle is the whole upgrade.
Verify that claim against the release notes for the version you are moving to rather than assuming it. The README also commits to testing and supporting the latest Android OS Beta versions promptly and listing supported versions, so the OS compatibility line is a maintained artifact, not a one-time statement. The current listed range is Android 4.1 through 17 QPR1 Beta 4.
The licence is MIT, and the repository carries a LICENSE file. Two third-party components are bundled and disclosed in the README: queue.h under BSD 3-Clause (copyright 1991, 1993 The Regents of the University of California) and linux-syscall-support under BSD 3-Clause (copyright 2005-2011 Google Inc). Both are permissive, but if your organisation runs a licence scan, those two files are the ones it will flag, and the README is where their provenance is recorded. This is a description of what the repository states, not legal advice.
Editorial conclusion
Adopt ByteHook if you maintain an Android SDK or app that must intercept calls into imported native functions and you can accept PLT-only scope: the README states it hooks PLT entries, and the same project points at shadowhook when inline hooking is required. Do not adopt it if you need to patch instructions inside a function body, or if you cannot add the BYTEHOOK_CALL_PREV and BYTEHOOK_POP_STACK macros to every proxy. Before committing, verify the four ABI filters in your build, confirm whether your project is an SDK or an app so the packagingOptions exclude or pickFirst rule is the right one, and check that your Android Gradle Plugin version handles the Prefab package schema v2, adding android.prefabVersion=2.0.0 if it does not.
Frequently asked questions
Does ByteHook support arm64-v8a and x86_64?
Yes. The README lists armeabi-v7a, arm64-v8a, x86 and x86_64 as the supported architectures, and shows an abiFilters example containing all four. The x86 and x86_64 prefab path additionally requires applying gradle/prefab_bypass.gradle to the module.
When should I use ByteHook instead of an inline hook library?
Use ByteHook when intercepting calls into imported symbols is enough. The README says that if you need an Android inline hook library you should try shadowhook, which patches instructions rather than PLT entries.
What version of Android Gradle Plugin does ByteHook require?
The README says Prefab native dependencies are supported by Android Gradle Plugin 4.0 or later. Because ByteHook uses the prefab package schema v2, projects on a plugin older than 7.1.0 must add android.prefabVersion=2.0.0 to gradle.properties.
How do I call the original function from inside a ByteHook proxy?
The README says to always use the BYTEHOOK_CALL_PREV() macro. It also requires calling BYTEHOOK_POP_STACK() before the proxy returns, or placing BYTEHOOK_STACK_SCOPE() at the start of the proxy in a C++ source file.
How do I remove a hook installed with ByteHook?
Each hook function returns a bytehook_stub_t handle, and bytehook_unhook takes that stub and returns an int. The README states that multiple hooks and unhooks for the same function do not conflict with each other.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/bytedance-bhook)