# shadowhook: ByteDance's inline hook library for Android arm32 and arm64

> shadowhook is the inline hook library behind bytedance/android-inline-hook. It hooks whole functions in armeabi-v7a and arm64-v8a processes, ships through Maven Central as a Prefab package, and is documented well beyond its README.

**bytedance/android-inline-hook** — 通用的Android inline hook库，支持thumb，arm32，arm64

- Repository: https://github.com/bytedance/android-inline-hook
- Website: https://github.com/bytedance/android-inline-hook/blob/main/doc/manual.md
- Stars: 2,403 · Forks: 413
- Language: C
- License: MIT
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/bytedance-android-inline-hook

## What shadowhook solves, and who it is for

Inline hooking on Android means rewriting a function's first instructions so that execution lands in your own code, then optionally continues into the original. Doing this by hand requires instruction decoding, relocation and trampoline management per architecture. shadowhook packages that work behind a C API and a small Java wrapper, and the README lists four goals behind the design: stability in production apps, backward compatibility of API and ABI across versions, reduced call and runtime overhead, and general solutions for hook-related problems.

The audience is narrow but real. If you build an SDK that instruments other apps' processes, or you maintain an app that needs to observe a function inside libart.so or another system library, shadowhook is aimed at you. It supports armeabi-v7a and arm64-v8a only, and the README states Android 4.1 through 17 (API level 16 through 37). It is written in C, licensed MIT, and published as com.bytedance.android:shadowhook on Maven Central. The README itself warns that the Quick Start only gets a demo running, and that stable production use requires reading doc/manual.md.

## How the hook mechanism works: proxy functions and stub handles

Every shadowhook hook is function-level. The README states plainly that hook applies to the entire function, and that you write a proxy function whose signature matches the original: same parameter count, order and types, same return type. When the hook succeeds, the proxy runs first, and inside it you decide whether to call the original through the saved function pointer.

The handle model is a stub. shadowhook_hook_sym_name returns a void * stub, and shadowhook_unhook takes that same stub back. Errors come out of shadowhook_get_errno and shadowhook_to_errmsg rather than out of the return value alone, which is why the README's example checks stub == NULL and then reads both. Targets can be named either by address or by library name plus function name, and the library also supports intercept operations, not just hook.

Two behaviours are worth knowing before you design around it. First, shadowhook can automatically complete hook and intercept for newly loaded ELFs, with optional callbacks after execution, and can register callbacks around a new ELF's .init + .init_array and .fini + .fini_array. Second, it automatically prevents recursive circular calls between proxy functions. The README also mentions bypassing linker namespace restrictions to query symbol addresses in .dynsym and .symtab, and compatibility with CFI unwind and FP unwind inside proxy and interceptor functions.

## Installing shadowhook from Maven Central and hooking your first function

shadowhook ships as a native dependency in Prefab package format, which the README says is supported from Android Gradle Plugin 4.0. Add Maven Central and the dependency to your Gradle files. Replace x.y.z with the release you want; the README recommends the latest release.

```Gradle
allprojects {
    repositories {
        mavenCentral()
    }
}

android {
    buildFeatures {
        prefab true
    }
}

dependencies {
    implementation 'com.bytedance.android:shadowhook:x.y.z'
}
```

One version detail matters. shadowhook uses prefab package schema v2, which is the default from Android Gradle Plugin 7.1.0. On older plugin versions the README tells you to add this line to gradle.properties:

```
android.prefabVersion=2.0.0
```

Next, link the native library. With CMake, the README's snippet is find_package(shadowhook REQUIRED CONFIG) followed by target_link_libraries(mylib shadowhook::shadowhook). With Android.mk, you add LOCAL_SHARED_LIBRARIES += shadowhook and then $(call import-module,prefab/shadowhook). Restrict ABIs to the two shadowhook supports:

```Gradle
android {
    defaultConfig {
        ndk {
            abiFilters 'armeabi-v7a', 'arm64-v8a'
        }
    }
}
```

Packaging needs attention because shadowhook contains two .so files, libshadowhook.so and libshadowhook_nothing.so. In an SDK project the README says to exclude both from your AAR. In an app project it suggests pickFirst for both, while warning that this may cause the app to use an incorrect version of shadowhook.

Initialization goes through the Java class com.bytedance.shadowhook.ShadowHook. There are three modes (shared, multi, unique), and the README suggests trying unique first:

```Java
import com.bytedance.shadowhook.ShadowHook;

public class MySdk {
    public void init() {
        ShadowHook.init(new ShadowHook.ConfigBuilder()
            .setMode(ShadowHook.Mode.UNIQUE)
            .build());
    }
}
```

Then hook. The README's example targets art::ArtMethod::Invoke in libart.so, using the mangled name _ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc, and passes a proxy with the same signature plus a void ** to receive the original:

```C
void *orig = NULL;
void *stub = NULL;

typedef void (*artmethod_invoke_func_type_t)(void *, void *, uint32_t *, uint32_t, void *, const char *);

void artmethod_invoke_proxy(void *thiz, void *thread, uint32_t *args, uint32_t args_size, void *result, const char *shorty) {
    ((artmethod_invoke_func_type_t)orig)(thiz, thread, args, args_size, result, shorty);
}

stub = shadowhook_hook_sym_name(
           "libart.so",
           "_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc",
           (void *)artmethod_invoke_proxy,
           (void **)&orig);
```

If stub comes back NULL, read the error with shadowhook_get_errno() and shadowhook_to_errmsg(err_num). Unhook with shadowhook_unhook(stub), which returns 0 on success. Note that the README's example stops mid-function in the provided text, so treat doc/manual.md as the authority on the full unhook error path.

## Where shadowhook is the wrong tool

The function-level constraint is the biggest one. If you need to intercept a single instruction or a narrow range inside a function, shadowhook does not offer that: the README says hook applies to the entire function, and your proxy must reproduce the original signature exactly. Getting the signature wrong is a silent, hard-to-debug class of failure, because the compiler will not catch a mismatched proxy for you.

Architecture coverage is the second constraint. armeabi-v7a and arm64-v8a are supported; x86 and x86_64 are not mentioned anywhere in the README. If your app ships x86 ABIs for emulators or Chromebooks, shadowhook will not cover those builds.

The third is a packaging hazard the project documents against itself. In app projects, the README suggests pickFirst for libshadowhook.so and libshadowhook_nothing.so, then warns that this may cause the app to use an incorrect version of shadowhook. That is a real risk when several SDKs each bundle their own copy. It is also a signal about scope: shadowhook is built to be embedded inside SDKs that are then merged into someone else's app, and that merge step is where things break.

## shadowhook compared with PLT hooking and ByteHook

The README points elsewhere for a different technique: if you need an Android PLT hook library, try ByteHook, which is also from ByteDance. The difference is where the interception happens. PLT hooking rewrites entries in the procedure linkage table, so it catches calls that go through the PLT, which covers imported library functions but not internal calls inside the same library. Inline hooking rewrites the function body itself, so it also catches calls that never touch the PLT. That is why the README's own example hooks art::ArtMethod::Invoke by symbol name inside libart.so rather than by import.

The trade-off runs the other way too. PLT hooking does not require copying or relocating a function's prologue, so it has less to get wrong per target and generally survives more aggressive code transformations. Inline hooking is more invasive by construction, which is why shadowhook's README leads with stability as goal number one. If PLT hooking covers your target, ByteHook is the lower-risk choice; reach for shadowhook when the call you need to observe does not pass through a PLT entry.

## Maintenance, upgrade cost and the MIT licence

The repository is not archived, and the last push was on 2026-08-26. Releases are infrequent and deliberate: v2.0.1 on 2026-06-15, v2.0.0 on 2025-07-29, and v1.1.1 on 2024-10-31. The README states a goal of always maintaining backward compatibility of API and ABI in new versions, and the version history is consistent with that being taken seriously rather than aspirational.

Upgrade cost is mostly on the build side. The prefab schema v2 requirement is the one thing that can bite you on an old toolchain, and the gradle.properties line above is the documented workaround. The README commits to testing and supporting the latest Android OS Beta versions as promptly as possible and listing supported versions, so the supported range moves; if you pin a shadowhook version, you should track whether that version's listed range still covers the Android versions you ship to.

The licence is MIT, stated in the README and present as a LICENSE file at the repository root. MIT is permissive and imposes few conditions, but the packaging warning above is a practical constraint rather than a legal one: if two SDKs in the same app both bundle shadowhook, the merged artifact may not be the version either SDK expected. Resolving that is an engineering decision, and it is worth raising with any SDK vendor whose AAR ships libshadowhook.so.

## Conclusion

Adopt shadowhook if you need inline hooking across armeabi-v7a and arm64-v8a in one Android codebase and you are willing to read doc/manual.md before shipping. Do not adopt it if you only need PLT hooking (ByteHook is the sibling project the README points at) or if you need to hook a single instruction rather than a whole function. Before writing code, verify that your Android Gradle Plugin version handles prefab package schema v2, and check the release notes for the shadowhook version you pin.

## FAQ

### What is shadowhook and what does it do in an Android process?

shadowhook is an Android inline hook library that rewrites a whole function so your proxy function runs first. It supports armeabi-v7a and arm64-v8a and targets Android 4.1 through 17, and you can name a target by address or by library name plus function name.

### How do I install shadowhook in a Gradle project?

Add mavenCentral() to your repositories, enable prefab with buildFeatures { prefab true }, and add implementation 'com.bytedance.android:shadowhook:x.y.z' to your dependencies, replacing x.y.z with the release you want. If your Android Gradle Plugin is older than 7.1.0, the README also asks for android.prefabVersion=2.0.0 in gradle.properties.

### Does shadowhook work on x86 or x86_64 Android builds?

The README lists only armeabi-v7a and arm64-v8a as supported architectures, so x86 and x86_64 are not covered by the documented support. The ABI filter example in the README uses exactly those two.

### What is the difference between shadowhook and ByteHook?

shadowhook is an inline hook library, while the README points to ByteHook when you need an Android PLT hook library. Inline hooking rewrites the function body and can catch calls that do not go through the PLT; PLT hooking rewrites the procedure linkage table entries instead.

### What is hooking in Android?

Hooking is intercepting a function so your own code runs when it is called. shadowhook does this inline at the function level, which is why the README requires your proxy function to match the original's parameter types, order and return type.

## Sources

- [bytedance/android-inline-hook on GitHub](https://github.com/bytedance/android-inline-hook)
- [License: MIT](https://github.com/bytedance/android-inline-hook/blob/main/LICENSE)
- [Project website](https://github.com/bytedance/android-inline-hook/blob/main/doc/manual.md)
- [README](https://github.com/bytedance/android-inline-hook/blob/main/README.md)
- [Releases](https://github.com/bytedance/android-inline-hook/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/bytedance-android-inline-hook
