Skip to content

0xdad0/Hooky

Repository files navigation

Hooky

Description

Hooky is a dynamic analysis tool for mobile application security testing and runtime instrumentation, built on Frida 17.x. Available as a terminal UI (TUI) and a browser-based web UI.


Features

  • TUI + Web UI — Textual-based terminal UI and a separate FastAPI + WebSocket SPA accessible in any browser
  • Java Hooking — Intercept Java/Kotlin methods with argument capture, blocking wait, continue/modify/drop/override control, and timeout visibility
  • ObjC Hooking — Intercept Objective-C methods on iOS targets
  • Native Hooking — Hook native functions by address, module+offset, symbol, or module!symbol via Interceptor.attach(); optional return value override pre-set at hook creation
  • Class Discovery — Enumerate all loaded Java/ObjC classes; live filter; click a class to auto-load its methods
  • Method Discovery — Enumerate declared methods with signatures; select and hook on demand; Force Call directly from the table
  • Return Value Capture — Actual return value streamed back after each hooked call and shown live in History/Monitor; Java objects are expanded with toString(), class, and readable fields when possible
  • Force Call — Programmatically invoke Java methods with custom arguments and inline request/response result views
  • Complex Object Args — For complex Java parameters, pass a new instance by providing constructor args and optional constructor type hints
  • Auto Replace Rules — Persisted rules that automatically replace selected argument values, return values, or both when matching hooked calls arrive
  • Manual Calls History — Dedicated history for every Force Call with class, method, full args, result, and static/instance tag in both TUI and Web Monitor
  • Multi-hook — Multiple concurrent hooks on a single session, each with independent filters and scope
  • Respawn — Kill and re-spawn the target app; all active Java and native hooks re-injected automatically
  • Project save/load — Store session fields, active hooks, hooked-method metadata, native hook settings, and auto replace rules in a project JSON
  • Pre-compilation — TypeScript agent compiled in background when an app is selected, so injection is instant
  • Platform detection — iOS/Android auto-detected on device connect; active package/bundle id and PID shown in session indicators
  • File logging — Intercepted calls (JSON) and log messages written to a session log file
  • pip install — Installable as a Python package; hooky and hooky --web commands available system-wide

File Structure

Hooky/
├── hooky/                   # Python package (pip-installable)
│   ├── __init__.py
│   ├── __main__.py          # python -m hooky
│   ├── tui.py               # Textual TUI — main app
│   ├── bridge.py            # Frida 17.x session bridge + JS agents
│   ├── hooky.tcss           # TUI stylesheet
│   └── web/                 # Web UI (FastAPI + SPA)
│       ├── __init__.py
│       ├── server.py        # FastAPI + WebSocket backend
│       └── static/
│           ├── index.html   # Dark-themed SPA
│           └── app.js       # WebSocket + REST client
├── pyproject.toml           # Package metadata + entry points
├── package.json             # npm deps (frida-java-bridge for Compiler)
├── tsconfig.json            # TypeScript config for Frida Compiler
├── uv.lock                  # Locked Python dependencies
└── hooky.bat                # Windows launcher shortcut

Quick Start

Requirements

  • Python 3.12+
  • Node.js 18+ with npm (required for frida-java-bridge TypeScript compilation — Java hooks only)
  • frida-server 17.x running on the target device
  • uv package manager (or plain pip)

Installation

# Editable install (dev)
uv pip install -e .

# Or pip
pip install .

# Install Node.js runtime dependency (frida-java-bridge) — run once
hooky --setup

hooky --setup installs frida-java-bridge into ~/.hooky/node_modules/. Native and ObjC hooks do not require npm or frida-java-bridge.

frida-server setup (Android)

adb push frida-server-17.x.y-android-arm64 /data/local/tmp/frida-server
adb shell "chmod 755 /data/local/tmp/frida-server"
adb shell "su -c '/data/local/tmp/frida-server &'"

TUI

hooky                 # after pip install
uv run hooky          # from source via uv
python -m hooky       # alternative

Layout

Four top-level pages (tabs in the header):

[ Home ]  [ Device & Process ]  [ Intercept & Monitor ]  [ Logs ]

Home

  • PROJECT — load/save/create project files (JSON with session fields, hooks, auto rules)
  • OPTIONS — auto-attach, file logging, respawn mode, debug mode
  • Continue to Device & App — navigate to device selection

Device & Process

┌─ DEVICES ──────────────┬─ APPLICATIONS ───────────────────────────────────┐
│  Pixel 6a (🤖 Android) │  Search: [____________]  [x] Non-system           │
│  Local                 │  [x] Running only                                 │
│                        │  Name          Package               PID          │
│                        │  MyApp         com.example.app       4172         │
│                        ├──────────────────────────────────────────────────│
│                        │ HOOK SETUP                                        │
│                        │  Target: [com.example.app]  Mode: [Spawn ▼]      │
│                        │  Class:  [com.example.Auth]  (optional)          │
│                        │  Filters:[login,auth.*]      Scope:[All ▼]       │
│                        ├──────────────────────────────────────────────────│
│                        │  [ Attach & Inject ]  [ Detach ]                 │
└────────────────────────┴──────────────────────────────────────────────────┘

Platform icon (🍎 iOS / 🤖 Android) appears in the header subtitle on all pages after device connect.

Intercept & Monitor

Inner tabs:

Tab Purpose
History All intercepted calls (Java + native); selected pending calls auto-focus; separate return type/value columns; request/return detail panel with Burp-style diff
Method Discovery Enumerate class methods, multi-select, hook on demand, Force Call from table with request/response result view
Class Discovery Enumerate all loaded Java/ObjC classes, live filter, click to load methods instantly
Dynamic Hooks Nested subviews for History, Java, ObjC, and Native hook forms; active hooks and hooked-method metadata
Native Libs Browse loaded modules and their exports; hook any export directly
Auto Rules Automatic argument replacement and return override rules
Manual Calls Full history of every Force Call — class, method, args per-line, response body, static/instance tag

Logs

Dedicated page with full scrollable Frida log output. Copy All and Copy Last buttons.

Workflow

Option A — Hook a known class directly

  1. Open Home — configure project/options if needed
  2. Continue to Device & App
  3. Select device — app list populates; platform detected automatically
  4. Click an app — Target auto-fills; mode set automatically (running = Attach, not running = Spawn)
  5. Enter Java class (e.g. com.example.AuthManager) and optional method filters
  6. Attach & Inject — hook loads; calls appear in History with live return values
  7. Select a call — detail panel shows args, return value, Burp-style diff
  8. Choose action: Continue / Modify Args / Override Return / Modify Both. Complex object args can be replaced with a new Java instance by entering constructor args JSON.

Option B — Enumerate classes, discover methods, hook and force-call

  1. Select device + app, leave Java class blank
  2. Attach & Inject
  3. Go to Class DiscoveryEnumerate → filter → click a class row (methods auto-load on the right)
  4. Select a method → Hook Selected Method
  5. Force Call — opens modal for argument input; result shown as a request/response view and logged to Manual Calls tab
  6. Respawn — kills + re-spawns with all hooks re-injected from process start

Option C — Auto Replace Rules

  1. Hook a Java method, then open Auto Rules
  2. Configure:
    • Arg index / value / type — replace a single argument before call executes
    • Return value / type — override the return value
  3. Add Auto Rule — matching calls handled automatically; marked modified/overridden/mod+override in History

Option D — Hook native functions

  1. Attach to a session
  2. Dynamic HooksNative subview
  3. Fill in address/symbol, label, num args, optional return override
  4. Add Native Hook — appears in History as ✓ logged (non-blocking)

Address formats:

  • 0xdeadbeef — absolute address
  • libnative.so+0x1234 — module base + offset
  • SSL_write — export by name (any module)
  • libssl.so!SSL_write — export in specific module

Keybindings

Key Action
q Quit
r Refresh devices
c Clear call history
d Detach session
X Reset All — detach, clear history, hooks, discovery, logs

Options

Option Description
Auto-attach on app select Automatically attaches when clicking an app (requires class to be set)
File logging Writes log lines and intercepted call JSON to the specified file
Log file name Path/filename for the session log (default: hooky_session.log)
Respawn app Kill the running process before spawning — forces fresh start, catches early-init hooks
Debug mode Dumps every raw Frida message to the log panel

Project files

Projects are saved as JSON from the Home page. Default path: hooky_project.json.

{
  "version": 2,
  "session": {
    "target": "com.example.app",
    "mode": "spawn",
    "class": "",
    "filters": "",
    "scope": "all"
  },
  "hooks": [
    {
      "type": "java",
      "target_class": "com.example.AuthManager",
      "method_filters": ["login"],
      "mode": "all",
      "hooked_methods": [
        {
          "name": "login",
          "params": ["java.lang.String", "java.lang.String"],
          "return_type": "boolean",
          "class_name": "com.example.AuthManager"
        }
      ]
    }
  ],
  "auto_rules": [
    {
      "id": 1,
      "class_pattern": "AuthManager",
      "method_pattern": "login",
      "arg_replacements": [{"index": 1, "value": "secret", "type": "string"}],
      "return_override": {"value": true, "type": "boolean"}
    }
  ]
}

Web UI

Browser-based interface. Same Frida bridge, real-time updates via WebSocket.

hooky --web                              # serve at http://localhost:8000
hooky --web --port 8080 --host 0.0.0.0  # custom host/port
hooky-web                                # alias

Tabs

Tab Purpose
Setup Connect device, browse apps, attach/spawn session, add Java/ObjC/Native hooks
Monitor Live call history table; interactive intercept controls; hooked methods list; Force Call/replay; manual call history
Hooks Active hooks with method filters, hooked-method metadata, remove actions, duplicate hook forms
Auto Rules Add/remove automatic argument replacement and return override rules, matching the terminal Auto Rules workflow
Discovery Enumerate Java classes; click a class to auto-load methods; hook all methods or a selected method; Force Call from method table
Native Libs Enumerate modules and exports; hook any export with one click
Logs Real-time log stream; Copy All / Copy Last / Clear

Web Intercept Workflow

The Web Monitor tab can interactively hold Java calls, similar to the terminal History tab:

  1. Enable Intercept in Monitor.
  2. Trigger a hooked Java method in the target app.
  3. Hooky highlights the Monitor tab and shows the number of pending calls in the status bar.
  4. The newest pending call is auto-selected.
  5. Choose Continue, Modify Args, Override Return, Modify Both, or Drop.

When a method is pending, the Frida agent waits up to 300 seconds before returning a safe default and marking the call as timed out.

Complex Java object arguments can be replaced with a new instance. For constructor input, provide either a JSON array:

["value", 123]

or an object with explicit types for overloaded constructors:

{"class_name":"com.example.Type","args":["value"],"arg_types":["java.lang.String"]}

Force Call results appear inline in Monitor/Discovery and are also recorded in the Manual Calls panel.

Web API

REST endpoints (all under /api/):

Method Path Description
GET /api/devices List Frida devices
POST /api/devices/{id}/connect Connect to device
GET /api/apps List applications on connected device
POST /api/session/spawn Spawn and attach
POST /api/session/attach Attach to running process
POST /api/session/detach Detach session
GET /api/intercept/settings Read web intercept toggle
POST /api/intercept/settings Enable/disable web intercept mode
POST /api/calls/{call_id}/respond Continue, modify, override, modify both, or drop a pending call
POST /api/force-call Force-call a Java method
GET /api/auto-rules List auto replace rules
POST /api/auto-rules Add auto replace rule
DELETE /api/auto-rules/{id} Remove auto replace rule
DELETE /api/auto-rules Clear auto replace rules
GET /api/hooks List active hooks
POST /api/hooks/java Add Java hook
POST /api/hooks/objc Add ObjC hook
POST /api/hooks/native Add Native hook
DELETE /api/hooks/{id} Remove hook
GET /api/hooks/methods List all hooked methods
GET /api/discovery/classes Enumerate Java classes
GET /api/discovery/classes/{name}/methods Enumerate class methods
GET /api/discovery/objc/classes Enumerate ObjC classes
GET /api/discovery/objc/{name}/methods Enumerate ObjC methods
GET /api/discovery/modules Enumerate native modules
GET /api/discovery/modules/{name}/exports Enumerate module exports
POST /api/project/save Save project JSON to file
POST /api/project/load Load project JSON from file
POST /api/project/new Reset project state

WebSocket at /ws — broadcasts events: call, return, call_resolved, intercept_settings, auto_rules, log, error, detached, hooked_methods.

Architecture — Frida 17.x Bridge

hooky/bridge.py provides a standalone Frida 17.x-compatible API usable in custom scripts. All JavaScript/TypeScript agents are built inline — no separate .js or .ts files required.

from hooky.bridge import FridaBridge, InterceptedCall

bridge = FridaBridge()

# Wire callbacks
bridge.on_call     = lambda call: print(f"Intercepted: {call.method_name}")
bridge.on_log      = lambda level, msg: print(f"[{level}] {msg}")
bridge.on_return   = lambda call_id, ret_val: print(f"#{call_id} returned: {ret_val}")
bridge.on_detached = lambda reason: print(f"Detached: {reason}")

# Connect and inject a Java hook
bridge.connect_device("usb")
bridge.spawn_and_attach("com.example.app")
bridge.resume()
hook_id = bridge.inject_java_hook(
    target_class="com.example.AuthManager",
    method_filters=["login", "auth.*"],
    mode="all",          # or "primitives"
)

# Inject a native hook
native_id = bridge.inject_native_hook(
    address_spec="SSL_write",
    label="SSL_write",
    num_args=3,
    ret_override="0x1",  # None = no override
)

# Enumerate all loaded classes
classes = bridge.enumerate_loaded_classes()

# Enumerate methods of a class
methods = bridge.enumerate_class_methods("com.example.AuthManager")

# Force-call a Java method
result = bridge.force_call_method(
    class_name="com.example.AuthManager",
    method_name="login",
    params=["java.lang.String", "java.lang.String"],
    args=["admin", "secret"],
)

# Respond to a pending Java call
bridge.respond_to_call(
    call_id=1000001,
    action="override_return",
    return_override={"value": True, "type": "boolean"},
)

# Other response actions:
#   continue
#   modify_input      modified_args=[{"index": 0, "value": "new", "type": "string"}]
#   override_return   return_override={"value": True, "type": "boolean"}
#   modify_both       modified_args=[...], return_override={...}
#   drop              return a safe default without calling the original method
#
# Complex object replacement:
#   modified_args=[{
#       "index": 0,
#       "type": "new_instance",
#       "value": {
#           "class_name": "com.example.Type",
#           "args": ["value"],
#           "arg_types": ["java.lang.String"]
#       }
#   }]

bridge.detach()

Hook types

Type Agent Blocking Return capture Use case
Java (inject_java_hook) TypeScript + frida-java-bridge Yes, 300s pending-call wait Yes — on_return callback Java/Kotlin method interception with full control
ObjC (inject_objc_hook) Plain JS Yes Yes Objective-C method interception
Native (inject_native_hook) Plain JS — no compiler needed No Logged in native_call message Native function tracing and return override

Frida 17.x migration notes

Old (16.x JS) New (17.x TS/JS)
recv(callback) rpc.exports = { respond(...) {...} }
script.post({...}) script.exports_sync.respond(...)
Java global (auto) import Java from 'frida-java-bridge' via frida.Compiler
Native hooks via TS Plain JS session.create_script() — no compiler needed

Troubleshooting

"frida-java-bridge not found"

hooky --setup        # pip install users
npm install          # source users (run in Hooky directory)

Native and ObjC hooks do not require frida-java-bridge.

"Process not found"

frida-ps -Ua | grep <appname>
adb shell "su -c '/data/local/tmp/frida-server &'"

"Class not found"

Use Class Discovery to enumerate all loaded classes, or via REPL:

frida -U -f com.example.app --no-pause -q -e \
  "Java.perform(() => Java.enumerateLoadedClasses({ onMatch: n => { if(n.includes('Auth')) console.log(n); }, onComplete: () => {} }));"

"overload not found"

Use Method Discovery — it shows exact parameter types. Copy them into the filter.

Native hook: "symbol X not found"

Symbol may be unexported or stripped. Use Native Libs tab to browse exports, or hook by address:

frida -U -p <pid> -e "Module.enumerateExports('libnative.so').filter(e => e.name.includes('ssl')).forEach(e => console.log(e.name, e.address))"

Class Discovery times out or returns 0 classes

App may not have Java loaded yet. Attach to a running process, or trigger app activity before clicking Enumerate.

Force Call: "No live instance on heap"

Class not instantiated yet, or GC collected all instances. Trigger a code path that creates the object, then retry. Static methods always work.

Intercepted call timed out

Hooky waits up to 300 seconds for pending Java calls in TUI and Web intercept mode. If the call times out, the agent returns a safe default for the method return type and the UI marks the call as timed_out.

Common causes:

  • A newer pending call was not selected/responded to.
  • The browser or terminal was left open while the app triggered a hook.
  • The hooked method runs on a sensitive UI or startup thread and the app cannot proceed until you continue/drop/override.

Web Monitor highlights in yellow

The Monitor tab pulses when at least one intercepted Java call is waiting for a response. Open Monitor and use Continue, Modify Args, Override Return, Modify Both, or Drop.

Permission denied (Android)

adb shell "ps | grep frida"
adb shell "su -c 'killall frida-server && /data/local/tmp/frida-server &'"
adb shell getenforce

Resources

About

Hooky is a dynamic analysis tool for mobile application security testing and runtime instrumentation.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors