Creating Custom Hooks
This guide provides step-by-step instructions for creating custom hooks to monitor specific Android behaviors not covered by the built-in hook categories.
Important
You only need to write a new custom hook when you’re adding a new feature to dexray-intercept itself.
For one-off tweaks, pass your script with --custom-script <path/to/script.js>
and it will be loaded in addition to dexray-intercept’s built-in hooks.
Overview
Creating a custom hook involves several coordinated steps:
Design the hook - Determine what to monitor and event structure
Implement TypeScript hooks - Write Frida instrumentation code
Create Python parsers - Process events in Python
Integrate with CLI - Add command-line support
Test and validate - Ensure functionality works correctly
This guide walks through creating a complete hook category from scratch.
Example: Android Clipboard Monitoring
We’ll create a comprehensive clipboard monitoring hook as a practical example.
Step 1: Design the Hook
Target Behavior: Monitor clipboard access (read/write operations)
- Target APIs:
android.content.ClipboardManager.getPrimaryClip()
android.content.ClipboardManager.setPrimaryClip()
android.content.ClipData
operations
- Event Types to Generate:
clipboard.read
- When app reads clipboard contentclipboard.write
- When app writes to clipboardclipboard.clear
- When app clears clipboard
Event Data Structure:
{
"event_type": "clipboard.read",
"timestamp": "2024-08-20T10:30:00.000Z",
"clip_label": "Copied text",
"clip_text": "Hello World",
"clip_type": "text/plain",
"item_count": 1,
"source_app": "com.example.app"
}
Step 2: Implement TypeScript Hook
Create the hook file at agent/clipboard/clipboard_monitor.ts
:
import { log, devlog, am_send } from "../utils/logging.js"
import { Where } from "../utils/misc.js"
import { Java } from "../utils/javalib.js"
// Profile type identifier - must be unique
const PROFILE_HOOKING_TYPE: string = "CLIPBOARD_MONITOR"
/**
* Create clipboard monitoring event
* @param eventType Specific clipboard event type
* @param data Event-specific data
*/
function createClipboardEvent(eventType: string, data: any): void {
const event = {
event_type: eventType,
timestamp: Date.now(),
...data
};
am_send(PROFILE_HOOKING_TYPE, JSON.stringify(event));
}
/**
* Extract text content from ClipData safely
* @param clipData Android ClipData object
* @returns Text content or null
*/
function extractClipText(clipData: any): string | null {
try {
if (!clipData) return null;
const itemCount = clipData.getItemCount();
if (itemCount === 0) return null;
const firstItem = clipData.getItemAt(0);
if (!firstItem) return null;
const text = firstItem.getText();
return text ? text.toString() : null;
} catch (error) {
devlog("Error extracting clip text: " + error);
return null;
}
}
/**
* Install clipboard monitoring hooks
*/
export function install_clipboard_monitor_hooks(): void {
devlog("Installing clipboard monitoring hooks");
Java.perform(() => {
try {
// Hook ClipboardManager
const ClipboardManager = Java.use("android.content.ClipboardManager");
// Monitor clipboard reads
ClipboardManager.getPrimaryClip.implementation = function() {
const clipData = this.getPrimaryClip();
if (clipData) {
const clipText = extractClipText(clipData);
const description = clipData.getDescription();
createClipboardEvent("clipboard.read", {
clip_label: description ? description.getLabel().toString() : "",
clip_text: clipText || "",
clip_type: description ? description.getMimeType(0).toString() : "unknown",
item_count: clipData.getItemCount(),
operation: "read"
});
}
return clipData;
};
// Monitor clipboard writes
ClipboardManager.setPrimaryClip.implementation = function(clip) {
const clipText = extractClipText(clip);
const description = clip.getDescription();
createClipboardEvent("clipboard.write", {
clip_label: description ? description.getLabel().toString() : "",
clip_text: clipText || "",
clip_type: description ? description.getMimeType(0).toString() : "unknown",
item_count: clip.getItemCount(),
operation: "write"
});
return this.setPrimaryClip(clip);
};
// Monitor clipboard clearing (if available)
try {
ClipboardManager.clearPrimaryClip.implementation = function() {
createClipboardEvent("clipboard.clear", {
operation: "clear"
});
return this.clearPrimaryClip();
};
} catch (error) {
devlog("clearPrimaryClip not available on this Android version");
}
log("Clipboard monitoring hooks installed successfully");
} catch (error) {
devlog("Error installing clipboard hooks: " + error);
}
});
}
/**
* Install comprehensive clipboard hooks (main export function)
*/
export function install_clipboard_hooks(): void {
devlog("Installing comprehensive clipboard hooks");
install_clipboard_monitor_hooks();
}
Step 3: Create Python Parser
Create parser at src/dexray_intercept/parsers/clipboard.py
:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Optional
from .base import BaseParser
from ..models.events import Event
class ClipboardEvent(Event):
"""Event representing clipboard operations"""
def __init__(self, event_type: str, timestamp: str):
super().__init__(event_type, timestamp)
self.clip_label = None
self.clip_text = None
self.clip_type = None
self.item_count = None
self.operation = None
def get_event_data(self):
data = {
"event_type": self.event_type,
"operation": self.operation,
"timestamp": self.timestamp
}
# Add optional fields if present
if self.clip_label:
data["clip_label"] = self.clip_label
if self.clip_text:
data["clip_text"] = self.clip_text
if self.clip_type:
data["clip_type"] = self.clip_type
if self.item_count is not None:
data["item_count"] = self.item_count
return data
class ClipboardParser(BaseParser):
"""Parser for clipboard monitoring events"""
def parse_json_data(self, data: dict, timestamp: str) -> Optional[ClipboardEvent]:
"""Parse JSON data into ClipboardEvent"""
event_type = data.get('event_type', 'clipboard.unknown')
event = ClipboardEvent(event_type, timestamp)
# Map fields from hook data
event.clip_label = data.get('clip_label', '')
event.clip_text = data.get('clip_text', '')
event.clip_type = data.get('clip_type', 'unknown')
event.item_count = data.get('item_count', 0)
event.operation = data.get('operation', 'unknown')
# Add metadata for analysis
self._add_clipboard_metadata(event, data)
return event
def _add_clipboard_metadata(self, event: ClipboardEvent, data: dict):
"""Add clipboard-specific metadata"""
# Categorize clipboard operations
operation_descriptions = {
'clipboard.read': 'Application read clipboard content',
'clipboard.write': 'Application wrote to clipboard',
'clipboard.clear': 'Application cleared clipboard'
}
description = operation_descriptions.get(event.event_type, f'Unknown clipboard operation: {event.event_type}')
event.add_metadata('description', description)
# Add privacy sensitivity metadata
if event.clip_text and len(event.clip_text) > 0:
event.add_metadata('contains_data', True)
event.add_metadata('data_length', len(event.clip_text))
# Detect potentially sensitive content
sensitive_indicators = ['password', 'token', 'key', 'secret', 'credential']
if any(indicator in event.clip_text.lower() for indicator in sensitive_indicators):
event.add_metadata('potentially_sensitive', True)
event.add_metadata('sensitivity_level', 'high')
else:
event.add_metadata('sensitivity_level', 'medium')
else:
event.add_metadata('contains_data', False)
event.add_metadata('sensitivity_level', 'low')
# Add operation category
event.add_metadata('category', 'privacy')
event.add_metadata('subcategory', 'clipboard_access')
Step 4: Integrate with Hook Loader
4a. Add to Hook Loader (agent/hooking_profile_loader.ts
):
// Add import at the top
import { install_clipboard_hooks } from "./clipboard/clipboard_monitor.js"
// Add to hook configuration
export let hook_config: HookConfig = {
// ... existing hooks ...
'clipboard_monitor_hooks': false,
};
// Add to installation function
function load_profile_hooks() {
// ... existing installations ...
install_hook_conditionally('clipboard_monitor_hooks', install_clipboard_hooks);
}
4b. Register Parser (src/dexray_intercept/parsers/factory.py
):
# Add import
from .clipboard import ClipboardParser
def _register_default_parsers(self):
# ... existing parsers ...
self._parsers["CLIPBOARD_MONITOR"] = ClipboardParser()
Step 5: Add CLI Support
Modify src/dexray_intercept/ammm.py
:
# Add to hook groups (optional - create privacy group)
if parsed_args.hooks_privacy:
hook_config.update({
'clipboard_monitor_hooks': True,
# other privacy-related hooks
})
# Add to individual hooks mapping
individual_hooks = {
# ... existing hooks ...
'enable_clipboard_monitor': 'clipboard_monitor_hooks'
}
# Add CLI argument
hooks.add_argument("--enable-clipboard-monitor", action="store_true",
help="Enable clipboard access monitoring")
# Optional: Add to hook groups
hooks.add_argument("--hooks-privacy", required=False, action="store_const", const=True, default=False,
help="Enable privacy-related hooks (clipboard, etc.)")
Step 6: Build and Test
6a. Compile TypeScript:
# Compile hooks to JavaScript
npm run build
# Verify compilation
grep -n "install_clipboard_hooks" src/dexray_intercept/profiling.js
6b. Test with Target App:
# Test with verbose output
dexray-intercept -v --enable-clipboard-monitor com.android.chrome
# Test specific clipboard operations in the app:
# 1. Copy text from webpage
# 2. Paste in address bar
# 3. Clear clipboard (if supported)
6c. Validate JSON Output:
# Check generated events
cat profile_com.android.chrome_*.json | jq '.CLIPBOARD_MONITOR'
# Expected output:
[
{
"event_type": "clipboard.write",
"operation": "write",
"clip_text": "Hello World",
"clip_type": "text/plain",
"item_count": 1,
"timestamp": "2024-08-20T10:30:00.000Z"
}
]
Step 7: Create Unit Tests
Create tests/test_clipboard_parser.py
:
import unittest
from datetime import datetime
from dexray_intercept.parsers.clipboard import ClipboardParser, ClipboardEvent
class TestClipboardParser(unittest.TestCase):
def setUp(self):
self.parser = ClipboardParser()
self.timestamp = "2024-08-20T10:30:00.000Z"
def test_parse_clipboard_write(self):
"""Test parsing clipboard write event"""
test_data = {
'event_type': 'clipboard.write',
'clip_text': 'Hello World',
'clip_type': 'text/plain',
'item_count': 1,
'operation': 'write'
}
event = self.parser.parse_json_data(test_data, self.timestamp)
self.assertIsInstance(event, ClipboardEvent)
self.assertEqual(event.event_type, 'clipboard.write')
self.assertEqual(event.clip_text, 'Hello World')
self.assertEqual(event.operation, 'write')
def test_parse_clipboard_read(self):
"""Test parsing clipboard read event"""
test_data = {
'event_type': 'clipboard.read',
'clip_text': 'Sensitive password: 12345',
'operation': 'read'
}
event = self.parser.parse_json_data(test_data, self.timestamp)
self.assertEqual(event.event_type, 'clipboard.read')
self.assertTrue(event.metadata.get('potentially_sensitive', False))
self.assertEqual(event.metadata.get('sensitivity_level'), 'high')
def test_empty_clipboard(self):
"""Test parsing empty clipboard operation"""
test_data = {
'event_type': 'clipboard.clear',
'operation': 'clear'
}
event = self.parser.parse_json_data(test_data, self.timestamp)
self.assertEqual(event.operation, 'clear')
self.assertFalse(event.metadata.get('contains_data', True))
if __name__ == '__main__':
unittest.main()
Step 8: Documentation
8a. Update User Documentation (docs/user-guide/hook-configuration.rst
):
Clipboard Monitoring (``--enable-clipboard-monitor``)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Monitors clipboard access operations including read, write, and clear operations.
**What it captures:**
- Clipboard content (text, images, etc.)
- Content types and labels
- Operation timing and frequency
- Potentially sensitive data detection
**Use cases:**
- Privacy analysis of data sharing
- Detecting clipboard-based data exfiltration
- Monitoring sensitive information exposure
**Example usage:**
.. code-block:: bash
# Monitor clipboard access
dexray-intercept --enable-clipboard-monitor com.social.app
# Combine with other privacy hooks
dexray-intercept --hooks-privacy com.banking.app
**Example events:**
.. code-block:: json
{
"event_type": "clipboard.write",
"clip_text": "Hello World",
"clip_type": "text/plain",
"operation": "write",
"metadata": {
"sensitivity_level": "medium",
"contains_data": true
}
}
8b. Update CLI Documentation (docs/user-guide/cli-usage.rst
):
.. option:: --enable-clipboard-monitor
Enable clipboard access monitoring.
.. code-block:: bash
dexray-intercept --enable-clipboard-monitor com.example.app
Advanced Hook Patterns
Conditional Hook Installation
Install hooks only when target conditions are met:
export function install_conditional_hooks(): void {
Java.perform(() => {
try {
// Check if target API is available
const Build = Java.use("android.os.Build");
const sdkVersion = Build.VERSION.SDK_INT.value;
if (sdkVersion >= 28) {
install_android_p_plus_hooks();
} else {
install_legacy_android_hooks();
}
} catch (error) {
devlog("Conditional installation failed: " + error);
}
});
}
State Management Between Hooks
Maintain state across multiple hook invocations:
// Global state for tracking clipboard sessions
interface ClipboardSession {
startTime: number;
operationCount: number;
lastOperation: string;
}
const clipboardSessions = new Map<string, ClipboardSession>();
function trackClipboardSession(appPackage: string, operation: string): void {
const session = clipboardSessions.get(appPackage) || {
startTime: Date.now(),
operationCount: 0,
lastOperation: ''
};
session.operationCount++;
session.lastOperation = operation;
clipboardSessions.set(appPackage, session);
// Create session tracking event
createClipboardEvent("clipboard.session", {
app_package: appPackage,
operation_count: session.operationCount,
session_duration: Date.now() - session.startTime,
last_operation: operation
});
}
Multi-API Hooking Pattern
Hook multiple related APIs systematically:
export function install_comprehensive_clipboard_hooks(): void {
Java.perform(() => {
// Primary clipboard API
hookClipboardManager();
// Clipboard service API (if available)
hookClipboardService();
// Text selection APIs that interact with clipboard
hookTextSelection();
// Intent-based clipboard operations
hookClipboardIntents();
});
}
function hookClipboardManager(): void {
// Implementation for ClipboardManager hooks
}
function hookClipboardService(): void {
try {
const IClipboard = Java.use("android.content.IClipboard");
// Hook service-level operations
} catch (error) {
devlog("IClipboard not available: " + error);
}
}
Error Handling and Robustness
export function install_robust_hooks(): void {
Java.perform(() => {
const hookTargets = [
{
className: "android.content.ClipboardManager",
methods: ["getPrimaryClip", "setPrimaryClip", "clearPrimaryClip"]
},
{
className: "android.content.IClipboard",
methods: ["getPrimaryClip", "setPrimaryClip"]
}
];
hookTargets.forEach(target => {
try {
const targetClass = Java.use(target.className);
target.methods.forEach(methodName => {
try {
installMethodHook(targetClass, methodName);
} catch (methodError) {
devlog(`Method ${methodName} not available: ${methodError}`);
}
});
} catch (classError) {
devlog(`Class ${target.className} not available: ${classError}`);
}
});
});
}
Testing Strategies
Unit Testing Hooks
Test individual hook functionality:
# Test hook with specific app
dexray-intercept --enable-my-hook com.test.app
# Verify events are generated
python3 -c "
import json
with open('profile_com.test.app_*.json') as f:
data = json.load(f)
events = data.get('MY_HOOK_CATEGORY', [])
print(f'Generated {len(events)} events')
for event in events[:3]:
print(f'Event: {event[\"event_type\"]}')
"
Integration Testing
Test hook interaction with other components:
# tests/integration/test_clipboard_integration.py
import unittest
from dexray_intercept import AppProfiler
from dexray_intercept.parsers.factory import parser_factory
class TestClipboardIntegration(unittest.TestCase):
def test_parser_registration(self):
"""Test that clipboard parser is registered"""
parser = parser_factory.get_parser("CLIPBOARD_MONITOR")
self.assertIsNotNone(parser)
def test_hook_config_integration(self):
"""Test hook configuration integration"""
from dexray_intercept.ammm import parse_hook_config
from argparse import Namespace
args = Namespace()
args.enable_clipboard_monitor = True
# Set other args to False...
config = parse_hook_config(args)
self.assertTrue(config.get('clipboard_monitor_hooks', False))
Performance Testing
Validate hook performance impact:
def test_clipboard_hook_performance():
"""Test that clipboard hooks don't significantly impact performance"""
import time
# Baseline measurement
start = time.time()
run_app_without_hooks()
baseline_time = time.time() - start
# With hooks measurement
start = time.time()
run_app_with_clipboard_hooks()
hook_time = time.time() - start
# Performance impact should be minimal
performance_impact = (hook_time - baseline_time) / baseline_time
assert performance_impact < 0.1 # Less than 10% impact
Common Pitfalls and Solutions
Issue: Hook Not Installing
// Problem: Class not found
const MyClass = Java.use("com.example.MyClass"); // May throw
// Solution: Defensive loading
try {
const MyClass = Java.use("com.example.MyClass");
// Install hooks
} catch (error) {
devlog("MyClass not available: " + error);
return; // Skip gracefully
}
Issue: Method Overloads Not Working
// Problem: Incorrect overload specification
MyClass.myMethod.overload("String").implementation = ... // Wrong
// Solution: Use correct Java type names
MyClass.myMethod.overload("java.lang.String").implementation = ...
Issue: Events Not Appearing in JSON
# Debug steps:
# 1. Check TypeScript compilation
npm run build && grep -n "my_hook" src/dexray_intercept/profiling.js
# 2. Check parser registration
python3 -c "from dexray_intercept.parsers.factory import parser_factory; print(parser_factory.get_parser('MY_CATEGORY'))"
# 3. Check hook configuration
dexray-intercept -v --enable-my-hook com.test.app # Look for "[HOOK] Enabled: my_hook"
Issue: App Crashes with Hooks
// Problem: Unhandled exceptions in hooks
MyClass.sensitiveMethod.implementation = function() {
// This might throw and crash the app
const result = this.sensitiveMethod();
processResult(result); // May fail
return result;
};
// Solution: Comprehensive error handling
MyClass.sensitiveMethod.implementation = function() {
try {
const result = this.sensitiveMethod();
try {
processResult(result);
} catch (processingError) {
devlog("Result processing failed: " + processingError);
}
return result;
} catch (methodError) {
devlog("Method execution failed: " + methodError);
throw methodError; // Re-throw to maintain app behavior
}
};
Next Steps
After creating your custom hook:
Test thoroughly with multiple target applications
Document the hook in user guides and API reference
Create examples showing practical usage scenarios
Consider contributing the hook back to the project
Monitor performance impact on target applications
For more advanced patterns and integration:
Study existing hooks in the
agent/
directoryReview the TypeScript API Reference for detailed API reference
Check the building guide for development workflow
See contributing for submission guidelines