Language: English · Chinese
Full-function HOOK framework for the Android Java layer
- No Root required — initialize and use directly inside the app
- Android 9+ (API 28+), including the latest versions
- Intercept and modify arguments/return values of any Java method
- Class/instance-wide batch hooks, covering common system hotspots (class loading, device fingerprint, SharedPreferences writes, etc.)
- Three integration options: Gradle dependency (
implementation), source integration (module/source copy), and (under compliance) app injection (repack or dynamic loading)
For lawful security research, testing, and debugging only. Ensure you have proper authorization for any target.
- Rapid observation: print call stacks/args/returns at runtime without touching target code
- Temporary patching: tweak args/returns or feed “mock data” to verify branches
- Batch coverage: one-click hook for all methods on a class/instance to accelerate debugging and regression
- System hotspot auditing:
Class.forName/ClassLoader.loadClass/Settings.Secure.getString/System.loadLibrary, etc. can be intercepted and logged
- Environment: Android 9+ (API 28+); works with Kotlin/Java projects
- Scenarios: feature co-debugging, gray-box testing, automated acceptance, critical-path tracing & audit, crash triage
- No dependency on Xposed / Magisk / Root
Add to settings.gradle or the root build.gradle:
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}
}Kotlin DSL:
maven(url = "https://jitpack.io")
In your app module:
dependencies {
implementation "com.github.Rift0911:fhook:+"
}Option A: Auto init in attachBaseContext
@Override
protected void attachBaseContext(Context base) {
Log.d(TAG, "attachBaseContext");
if (FCFG.IS_APP_INIT_AUTO) {
Log.i(TAG, "attachBaseContext FHook.init= " + FHook.init(base));
}
super.attachBaseContext(base);
}Option B: Manual init anywhere (e.g., button click)
bt_main_02.setOnClickListener(v -> {
if (FHook.isInited()) {
FHook.unInit();
} else {
if (!FHook.init(this)) {
Toast.makeText(this, "Init failed", Toast.LENGTH_LONG).show();
Log.e(TAG, "Init failed");
} else {
Toast.makeText(this, "Init success", Toast.LENGTH_LONG).show();
Log.i(TAG, "Init success");
}
}
});Handy calls:
FHook.unHookAll()to remove all hooks;FHook.showHookInfo()to view current hook status.
Take THook.fun_I_III(int a, int b, int c): int as an example — modify args and return:
import java.lang.reflect.Method;
import android.util.Log;
Method m = THook.class.getMethod("fun_I_III", int.class, int.class, int.class);
FHook.hook(m)
.setOrigFunRun(true) // run the original method first
.setHookEnter((thiz, args, types, hh) -> {
// change the first argument
args.set(0, 6666);
Log.d("FHook", "fun_I_III enter: " + args);
})
.setHookExit((ret, type, hh) -> {
// force the return value
Log.d("FHook", "fun_I_III exit, origRet=" + ret);
return 8888;
})
.commit();Take Settings.Secure.getString(ContentResolver, String) — forge ANDROID_ID selectively:
import android.provider.Settings;
import android.content.ContentResolver;
import java.lang.reflect.Method;
import android.util.Log;
Method sysGet = Settings.Secure.class.getMethod(
"getString", ContentResolver.class, String.class);
FHook.hook(sysGet)
.setOrigFunRun(true)
.setHookEnter((thiz, args, types, hh) -> {
String key = (String) args.get(1);
hh.extras.put("key", key);
Log.d("FHook", "Settings.Secure.getString key=" + key);
})
.setHookExit((ret, type, hh) -> {
String key = (String) hh.extras.get("key");
if ("android_id".equalsIgnoreCase(key)) {
return "a1b2c3d4e5f6a7b8"; // affect ANDROID_ID only
}
return ret; // keep others intact
})
.commit();Tip: for interface/bridge methods (e.g.,
SharedPreferences.Editor.commit), useFHookTool.findMethod4Impl(editor, ifaceMethod)to locate the actual implementation method before hooking for a higher success rate.
Notes: By definition, a constructor always runs;
setOrigFunRun(true/false)has no effect on constructors. During enter, the object is not fully initialized — do not touch fields/instance methods. On exit,retis the newly created instance (same asthisObject).
// 1) Bind the constructor
Constructor<FileInputStream> c =
FileInputStream.class.getDeclaredConstructor(FileDescriptor.class);
c.setAccessible(true);
FHook.hook(c)
.setOrigFunRun(true) // no-op for constructors, harmless to keep
.setHookEnter((thiz, args, types, hh) -> {
// Observe only: reverse path from FileDescriptor (may hit hidden-API limits; use HiddenApiBypass if needed)
FileDescriptor fdObj = (FileDescriptor) args.get(0);
Field f = FileDescriptor.class.getDeclaredField("descriptor");
f.setAccessible(true);
int fd = (int) f.get(fdObj);
String path = android.system.Os.readlink("/proc/self/fd/" + fd);
Log.i("FHook", "[FIS.<init>(fd).enter] fd=" + fd + ", path=" + path);
})
.setHookExit((ret, type, hh) -> {
Log.i("FHook", "[FIS.<init>(fd).exit] new instance=" + ret);
return ret; // keep constructor return as-is
})
.commit();
// 2) Trigger: open your own APK via PFD, then read some Zip entries through FileInputStream(FileDescriptor)
String apkPath = context.getApplicationInfo().sourceDir;
android.os.ParcelFileDescriptor pfd = android.os.ParcelFileDescriptor.open(
new java.io.File(apkPath),
android.os.ParcelFileDescriptor.MODE_READ_ONLY);
try (FileInputStream fis = new FileInputStream(pfd.getFileDescriptor());
java.util.zip.ZipInputStream zis = new java.util.zip.ZipInputStream(fis)) {
java.util.zip.ZipEntry e; int seen = 0; byte[] buf = new byte[4096];
while ((e = zis.getNextEntry()) != null && seen++ < 5) {
Log.i("FHook", "[Zip] " + e.getName());
while (zis.read(buf) != -1) { /* drain */ }
}
} finally {
try { pfd.close(); } catch (Throwable ignore) {}
}// No-arg constructor
Constructor<TObject> c0 = TObject.class.getDeclaredConstructor();
c0.setAccessible(true);
FHook.hook(c0)
.setOrigFunRun(true) // no-op for constructors
.setHookEnter((thiz, args, types, hh) -> {
// Mark only; access the instance on exit
hh.extras.put("watch", true);
})
.setHookExit((ret, type, hh) -> {
if (Boolean.TRUE.equals(hh.extras.get("watch")) && ret instanceof TObject) {
TObject to = (TObject) ret;
to.setName("Zhang San").setAge(109);
Log.i("FHook", "[Ctor0.exit] TObject() -> " + to);
}
return ret;
})
.commit();
// (String,int) constructor
Constructor<TObject> c2 = TObject.class.getDeclaredConstructor(String.class, int.class);
c2.setAccessible(true);
FHook.hook(c2)
.setOrigFunRun(true)
.setHookEnter((thiz, args, types, hh) -> {
Log.i("FHook", "[Ctor2.enter] name=" + args.get(0) + ", age=" + args.get(1));
hh.extras.put("watch", true);
})
.setHookExit((ret, type, hh) -> {
if (Boolean.TRUE.equals(hh.extras.get("watch")) && ret instanceof TObject) {
TObject to = (TObject) ret;
to.setName("Li Si").setAge(16);
Log.i("FHook", "[Ctor2.exit] TObject(name,int) -> " + to);
}
return ret;
})
.commit();Tips
- Constructor hooks observe/modify but do not block creation (i.e.,
setOrigFunRunis ineffective).- Reading
FileDescriptor.descriptormay trigger hidden-API restrictions on some ROMs/versions; use HiddenApiBypass or NDK helpers if needed.
- Personal learning & research: free to use (see LICENSE.agent-binary in the repo root).
- Commercial/enterprise use: strictly follow LICENSE.agent-binary. For commercial licensing, custom features, technical support, or joint R&D, please reach out via GitHub Issues or the contact info below.
- Ways to support: Star ⭐ the repo, send PRs, or contact us for sponsorship options.
Compliance Notice: FHook is for compliant use only. Any illegal or abusive use is strictly prohibited; users bear all associated risks and consequences.
We provide deeper engineering assistance to help you land with lower cost (under lawful compliance):
- Source delivery options: choose full source delivery or a hybrid of core agent binary + adapter-layer source (NDA available)
- Deployment modes: Gradle dependency, source import (mono-repo/multi-module), private Maven distribution
- Compatibility & adaptation: Android 9–15 deltas, OEM ROMs, stability under obfuscation/packing/VMP
- Performance & stability: init timing, deadlock avoidance, bypassing critical bridge paths, callback threading & jank mitigation
- Training & co-build: secondary-dev training, code walkthrough, best-practice checklists, co-debugging & triage
For source delivery / core support / customization, contact us below with your scope and requirements.
-
Business & Technical Support — Feadre (Rift Tech):
- Email:
rift@feadre.top - QQ:
27113970
- Email:
-
Sponsor us (thank you!):
![]() |
![]() |
|---|---|
| WeChat Pay | Alipay |
Enjoy hacking (legally) with FHook!
-
Known unsupported/high-risk bridges (see
isBridgeCriticalin the project). The following are usually not recommended / not hookable:Thread.currentThread() Thread#getContextClassLoader() Class#getDeclaredMethod(String, Class[])These are often bridge/reflection entry points. Prefer locating the actual implementation method first (e.g., via
FHookTool.findMethod4Impl(...)). -
Mutual exclusion tip: avoid hooking both
Class.forName(...)andClassLoader.loadClass(...)together. They may call each other and create recursion → deadlocks/hangs. The framework does not hard-block this; please avoid manually. -
Bridge/reflection bridges (e.g.,
Method.invoke) are not recommended; hook the real implementation (FHookTool.findMethod4Impl). -
Callbacks run on the caller’s thread; avoid heavy work on the UI thread.
-
Hidden-API restrictions vary by Android version; validate system-method hooks across versions.
-
If obfuscation hinders debugging, add (as needed):
-keep class top.feadre.fhook.** { *; }
# or
-keep class top.feadre.fhook.CLinker {*;}
-keep class top.feadre.fhook.FHook {*;}If you hit a bug or have suggestions, please open an Issue with the failed method signature and Android version so we can reproduce and resolve quickly.

