Skip to content

Repository files navigation

HookKit

HookKit is a C-first iOS hooking runtime. A caller creates a runtime, builds a plan, then analyzes, prepares, and commits explicit hook requests. Results state exactly whether a target changed.

#include <HookKit/HookKit.h>

hk_runtime_t *runtime = NULL;
hk_plan_t *plan = NULL;
hk_hook_t *hook = NULL;

hk_runtime_config_t config = {
    .struct_size = sizeof(config),
    .struct_version = HK_ABI_VERSION_3_0,
    .install_context = HK_INSTALL_CONTEXT_EARLY_PROCESS,
};

hk_runtime_create(&config, &runtime);
hk_plan_create(runtime, NULL, &plan);
hk_plan_add_hook(plan, &spec, &hook); // `spec` is a fully initialized hk_hook_spec_t
hk_plan_analyze(plan, NULL);
hk_plan_prepare(plan, NULL);
hk_plan_commit(plan, NULL);

Read hk_hook_result_t with hk_hook_copy_result() after each stage. API status says whether a call completed; outcome and mutation say what happened to the individual target.

Backend routing

hk_runtime_create() uses normal automatic routing. Discover currently available IDs with hk_runtime_enumerate_backends().

hk_runtime_create_with_backend_override() accepts a comma/space-separated, strict per-runtime ID list. Only those function/memory engines remain eligible; the built-in Objective-C engine remains eligible. An empty or all-invalid list intentionally leaves no function/memory route. NULL means automatic routing.

hk_runtime_create_with_backend_override(
    &config, "provider-ellekit", &runtime);

Strict selection does not fall through to an unselected engine. A caller may only retry another route after the failed hook result reports HK_MUTATION_NONE; PARTIAL and UNKNOWN are terminal because the target may have changed.

Engines

The runtime includes built-in Objective-C, import-rebinding, memory-patch, native inline, relocating inline, and Swift-vtable engines, plus certified provider adapters where available. Dobby is excluded from the iOS 9–13 lane; the optional Frida Gum provider is packaged separately on modern lanes.

Inline patching needs the process permissions appropriate for executable page writes and must normally happen at load time. Function-entry engines report a clean refusal when they can prove they have not changed a target; inspect the per-hook mutation state rather than inferring it from a generic error.

Headers

Use <HookKit/HookKit.h> for the C umbrella. <HookKit/HookKitObjC.h> is an opt-in typed Class/SEL convenience header; it is intentionally not pulled into the C umbrella. <HookKit.h> is the Objective-C compatibility umbrella: it declares HKSubstitutor and imports the v1 module headers (<HookKit/Core.h>, <HookKit/Hook.h>, <HookKit/Module.h>, with <HookKit/Compat.h> kept as the v1 spelling). New C code wants <HookKit/HookKit.h>, not this one.

HookKit v1 / 2.x compatibility

Binaries linked against HookKit v1.0.1 or any v2.x release load and run unrecompiled, and v1/2.x sources still compile. All seven historical Objective-C classes are exported — HKSubstitutor, HookKitCore, HookKitModule, HookKitHook, HookKitClassHook, HookKitFunctionHook, HookKitMemoryHook — and every one of them is a translator over the 3.0 plan/engine lifecycle, never a second runtime.

#import <HookKit.h>

HookKitModule *module = [[HookKitCore sharedInstance] defaultModule];
[module executeHook:[HookKitFunctionHook hook:sym replacement:rep orig:&orig]];
// or the HKSubstitutor spelling
HKHookFunction(sym, rep, &orig);

Two things v1 had that 3.0 does not: Modulous plugin bundles (nothing is read from /Library/Modulous/HookKit; getModuleInfo returns one built-in row) and per-library provider identity (activeType is HK_LIB_NONE, getAvailableCategories is HK_CAT_NONE). Routing is the 3.0 runtime's. docs/3.0/LEGACY_ABI.md is the authoritative contract. Current compatibility is covered by the shipped facade and headers, linker compatibility, exact current export checks, public-header compile tests, and device compatibility smokes. Historical selector/type-encoding snapshots are not release-gated.

Building

Requires Theos. Build each package lane independently:

./build.sh rootful-legacy
./build.sh rootful-modern
./build.sh rootless
./build.sh roothide

rootful-legacy retains the iOS 9–13 architecture/toolchain lane. It is an OS-support lane, not a pre-3.0 API package. Modern lanes build the optional Gum provider package separately. Every lane runs host tests, package checks, and exact export checks.

Install into Theos

Run make install-theos to build all lanes and install their verified frameworks beneath $THEOS/lib:

Lane Framework path Consumer setup
rootful-modern $THEOS/lib/HookKit.framework Default/rootful Theos scheme; no extra search path.
rootful-legacy $THEOS/lib/iphone/rootful-legacy/HookKit.framework Use the legacy library-root override below.
rootless $THEOS/lib/iphone/rootless/HookKit.framework THEOS_PACKAGE_SCHEME=rootless; resolved automatically.
roothide $THEOS/lib/iphone/roothide/HookKit.framework THEOS_PACKAGE_SCHEME=roothide; resolved automatically.

Link every lane with MyTweak_EXTRA_FRAMEWORKS += HookKit. Modern rootful owns the default path. Legacy deliberately lives separately: its old arm64e ABI cannot share a framework with the modern rootful binary. Put this before the consumer's common.mk include:

override THEOS_LIBRARY_PATH := $(THEOS)/lib/iphone/rootful-legacy
include $(THEOS)/makefiles/common.mk

# Keep ordinary local frameworks available after HookKit is selected.
ADDITIONAL_CFLAGS += -F$(THEOS)/lib
ADDITIONAL_LDFLAGS += -F$(THEOS)/lib

Rootful, modern, and rootless binaries use @rpath/HookKit.framework/HookKit; Roothide uses its required @loader_path/.jbroot/Library/Frameworks/HookKit.framework/HookKit identity.

Migrate an existing tweak

make install-theos                    # installs framework + hookkit Logos generator
  • Pure Logos (%hook/%orig): add 2 lines to Tweak.mkTweak_EXTRA_FRAMEWORKS += HookKit + Tweak_LOGOSFLAGS += -c generator=hookkit — no source change.

  • Raw MSHookFunction/LHHookFunctions/substitute_hook_functions/LBHookMessage/MSHookMemory/LHPatchMemory: also #import <HookKit/HookKitCompat.h> before the provider header (substrate.h etc).

  • HookKit v1 / 2.x consumer: nothing to do — see "HookKit v1 / 2.x compatibility" above.

See docs/MIGRATION.md (quick-start, shim gates, migrate.py, revert).

Verification

make test
bash tools/release/check_exports.sh

The public ABI is declared under include/HookKit/. packaging/abi/HookKit.tbd and packaging/exports/export-HookKit.list are the current release export contract. Compatibility device smokes live under tests/device/.

About

An iOS developer framework for unified hooking methods.

Resources

Stars

56 stars

Watchers

4 watching

Forks

Releases

Contributors

Languages