TypeScript API Reference
This section documents the TypeScript/JavaScript API for creating custom Frida hooks in Dexray Intercept.
Important
Always run npm run build
or frida-compile
after modifying TypeScript hooks to compile them to JavaScript for use by the Python frontend.
Install or update frida-tools
via pip (the latest versions ship frida-compile
), then from the project root run frida-compile agent/hooking_profile_loader.ts -o src/dexray_intercept/profiling.js
.
Core Architecture
Hook Structure
All hooks follow a standardized structure for consistency and integration:
import { log, devlog, am_send } from "../utils/logging.js"
import { Where } from "../utils/misc.js"
import { Java } from "../utils/javalib.js"
// Profile type identifier for this hook category
const PROFILE_HOOKING_TYPE: string = "MY_CATEGORY"
// Event creation helper
function createMyEvent(eventType: string, data: any): void {
const event = {
event_type: eventType,
timestamp: Date.now(),
...data
};
am_send(PROFILE_HOOKING_TYPE, JSON.stringify(event));
}
// Main hook installation function
export function install_my_hooks(): void {
devlog("Installing my custom hooks");
Java.perform(() => {
// Hook implementation goes here
});
}
Essential Components
Required Imports:
import { log, devlog, am_send } from "../utils/logging.js" // Logging utilities
import { Where, bytesToHex } from "../utils/misc.js" // Helper functions
import { Java } from "../utils/javalib.js" // Java runtime access
Profile Type Constants:
Every hook category must define a unique profile type identifier:
const PROFILE_HOOKING_TYPE: string = "CRYPTO_AES" // Crypto hooks
const PROFILE_HOOKING_TYPE: string = "WEB" // Network hooks
const PROFILE_HOOKING_TYPE: string = "BYPASS_DETECTION" // Bypass hooks
const PROFILE_HOOKING_TYPE: string = "MY_CUSTOM_HOOKS" // Your custom category
Event Creation Pattern:
function createCustomEvent(eventType: string, data: any): void {
const event = {
event_type: eventType, // Specific event identifier
timestamp: Date.now(), // Event timestamp
...data // Custom event data
};
am_send(PROFILE_HOOKING_TYPE, JSON.stringify(event));
}
Logging and Communication
Logging Functions
// Standard logging (always visible)
log("Hook installed successfully");
// Development logging (only visible in verbose mode)
devlog("Detailed debug information");
// Send structured data to Python
am_send(PROFILE_HOOKING_TYPE, JSON.stringify(eventData));
- Usage Guidelines:
Use
log()
for important status messagesUse
devlog()
for detailed debugging informationAlways use
am_send()
to send structured event data
Message Format
Messages sent via am_send()
must follow the structured format:
const eventData = {
event_type: "specific.event.identifier", // Required: dot-separated event type
timestamp: Date.now(), // Required: event timestamp
// Custom fields based on event type
field1: "value1",
field2: 42,
binary_data: bytesToHex(byteArray) // Convert binary to hex
};
am_send(PROFILE_HOOKING_TYPE, JSON.stringify(eventData));
Java Runtime Integration
Basic Java Hooking
export function install_basic_hooks(): void {
Java.perform(() => {
try {
// Get Java class
const MyClass = Java.use("com.example.MyClass");
// Hook method with overload
MyClass.sensitiveMethod.overload("java.lang.String").implementation = function(param) {
// Log the hook activation
devlog("sensitiveMethod called with: " + param);
// Create event
createCustomEvent("method.called", {
method_name: "sensitiveMethod",
parameter: param,
class_name: "com.example.MyClass"
});
// Call original method
return this.sensitiveMethod(param);
};
} catch (error) {
devlog("Error in hook installation: " + error);
}
});
}
Method Overloading
Handle multiple method signatures:
Java.perform(() => {
const Cipher = Java.use("javax.crypto.Cipher");
// Hook multiple overloads
Cipher.init.overload('int', 'java.security.Key').implementation = function(mode, key) {
createCryptoEvent("cipher.init.key", {
mode: mode,
key_algorithm: key.getAlgorithm()
});
return this.init(mode, key);
};
Cipher.init.overload('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec').implementation = function(mode, key, params) {
createCryptoEvent("cipher.init.key_params", {
mode: mode,
key_algorithm: key.getAlgorithm(),
params_class: params.$className
});
return this.init(mode, key, params);
};
});
Exception Handling
Robust error handling in hooks:
Java.perform(() => {
try {
const TargetClass = Java.use("com.example.TargetClass");
TargetClass.riskyMethod.implementation = function() {
try {
// Pre-hook logic
createCustomEvent("risky.method.start", {});
// Call original with error handling
const result = this.riskyMethod();
// Post-hook logic
createCustomEvent("risky.method.success", { result: result });
return result;
} catch (methodError) {
// Handle method-specific errors
createCustomEvent("risky.method.error", {
error: methodError.toString()
});
throw methodError; // Re-throw to maintain app behavior
}
};
} catch (hookError) {
devlog("Failed to install hook: " + hookError);
}
});
Utility Functions
Binary Data Handling
import { bytesToHex } from "../utils/misc.js"
// Convert byte array to hex string
function processBytes(byteArray: number[]): string {
if (!byteArray || byteArray.length === 0) {
return "";
}
return bytesToHex(new Uint8Array(byteArray));
}
// Safe byte array processing
function bytesToHexSafe(bytes: number[] | null): string {
if (!bytes || bytes.length === 0) return "";
try {
return bytesToHex(new Uint8Array(bytes));
} catch (error) {
devlog("Error converting bytes to hex: " + error);
return "[conversion_error]";
}
}
Stack Trace Collection
import { Where } from "../utils/misc.js"
Java.perform(() => {
const Thread = Java.use('java.lang.Thread');
const threadInstance = Thread.$new();
MyClass.trackedMethod.implementation = function() {
const stack = threadInstance.currentThread().getStackTrace();
createCustomEvent("method.with.stack", {
method: "trackedMethod",
stack_trace: Where(stack) // Convert stack to readable format
});
return this.trackedMethod();
};
});
String Processing
// Safe string extraction from various types
function extractStringValue(obj: any): string {
if (!obj) return "";
try {
if (typeof obj === 'string') return obj;
if (obj.toString) return obj.toString();
return JSON.stringify(obj);
} catch (error) {
return "[extraction_error]";
}
}
// Extract plaintext from hex data
function extractPlaintext(hexData: string): string | null {
if (!hexData) return null;
try {
const bytes = hexData.match(/.{2}/g)?.map(byte => parseInt(byte, 16)) || [];
// Only printable ASCII characters
return String.fromCharCode(...bytes.filter(b => b >= 32 && b <= 126));
} catch {
return null;
}
}
Event Types and Patterns
Cryptographic Events
// Key generation/creation events
createCryptoEvent("crypto.key.creation", {
algorithm: "AES",
key_length: keyBytes.length,
key_hex: bytesToHexSafe(keyBytes)
});
// Encryption/decryption operations
createCryptoEvent("crypto.cipher.operation", {
algorithm: cipher.getAlgorithm(),
operation_mode: opMode,
key_hex: bytesToHexSafe(keyBytes),
iv_hex: bytesToHexSafe(ivBytes),
input_hex: bytesToHexSafe(inputBytes),
output_hex: bytesToHexSafe(outputBytes),
input_length: inputBytes.length,
output_length: outputBytes.length
});
Network Events
// HTTP requests
createNetworkEvent("http.request", {
url: requestUrl,
method: requestMethod,
headers: headersObj,
body_preview: bodyPreview,
library: "OkHttp"
});
// Socket connections
createNetworkEvent("socket.connect", {
host: targetHost,
port: targetPort,
protocol: "TCP",
local_port: localPort
});
Bypass Events
// Detection bypass events
createBypassEvent("bypass.root.file_check", {
file_path: filePath,
original_result: originalResult,
bypassed_result: bypassedResult,
detection_method: "File.exists()"
});
// Evasion technique events
createBypassEvent("bypass.frida.process_check", {
process_name: processName,
detection_method: "ActivityManager.getRunningAppProcesses()",
action: "removed_from_list"
});
Hook Integration
Hook Loader Integration
To integrate new hooks into the main system, modify agent/hooking_profile_loader.ts
:
1. Add Import:
import { install_my_custom_hooks } from "./custom/my_hooks.js"
2. Add to Hook Configuration:
export let hook_config: HookConfig = {
// ... existing hooks ...
'my_custom_hooks': false,
};
3. Add to Installation Function:
function load_profile_hooks() {
// ... existing installations ...
install_hook_conditionally('my_custom_hooks', install_my_custom_hooks);
}
CLI Integration
To add CLI support for your hooks, modify src/dexray_intercept/ammm.py
:
1. Add to Hook Groups (optional):
if parsed_args.hooks_custom:
hook_config.update({
'my_custom_hooks': True
})
2. Add Individual Hook Support:
individual_hooks = {
# ... existing hooks ...
'enable_my_custom': 'my_custom_hooks'
}
3. Add CLI Arguments:
hooks.add_argument("--enable-my-custom", action="store_true",
help="Enable my custom hooks")
Parser Integration
Create a Python parser for your custom events in src/dexray_intercept/parsers/
:
1. Create Parser File:
# src/dexray_intercept/parsers/my_custom.py
from .base import BaseParser
from ..models.events import Event
class MyCustomParser(BaseParser):
def parse_json_data(self, data: dict, timestamp: str):
event = MyCustomEvent(data.get('event_type'), timestamp)
# Parse custom fields
event.custom_field = data.get('custom_field')
return event
2. Register in Parser Factory:
# src/dexray_intercept/parsers/factory.py
from .my_custom import MyCustomParser
def _register_default_parsers(self):
# ... existing parsers ...
self._parsers["MY_CUSTOM_HOOKS"] = MyCustomParser()
Advanced Patterns
Dynamic Hook Installation
Install hooks based on runtime conditions:
export function install_dynamic_hooks(): void {
Java.perform(() => {
try {
// Check if target class exists
const TargetClass = Java.use("com.example.TargetClass");
// Install hook only if conditions are met
if (checkInstallConditions()) {
installTargetHooks(TargetClass);
} else {
devlog("Conditions not met, skipping hook installation");
}
} catch (error) {
// Class doesn't exist, skip gracefully
devlog("Target class not found, skipping hooks: " + error);
}
});
}
function checkInstallConditions(): boolean {
// Custom logic to determine if hooks should be installed
return true;
}
State Management
Maintain state across hook invocations:
// Global state for hook category
interface SessionInfo {
id: number;
algorithm?: string;
keyData?: number[];
}
const activeSessions = new Map<number, SessionInfo>();
export function install_stateful_hooks(): void {
Java.perform(() => {
const CipherClass = Java.use("javax.crypto.Cipher");
// Initialize state
CipherClass.init.overload('int', 'java.security.Key').implementation = function(mode, key) {
const sessionId = this.hashCode();
const keyBytes = key.getEncoded();
activeSessions.set(sessionId, {
id: sessionId,
algorithm: key.getAlgorithm(),
keyData: keyBytes
});
return this.init(mode, key);
};
// Use state
CipherClass.doFinal.overload("[B").implementation = function(inputBytes) {
const result = this.doFinal(inputBytes);
const sessionId = this.hashCode();
const session = activeSessions.get(sessionId);
if (session) {
createCryptoEvent("cipher.operation", {
algorithm: session.algorithm,
key_hex: bytesToHexSafe(session.keyData),
input_hex: bytesToHexSafe(inputBytes),
output_hex: bytesToHexSafe(result)
});
// Clean up session
activeSessions.delete(sessionId);
}
return result;
};
});
}
Multi-Method Hooking
Hook multiple related methods systematically:
export function install_comprehensive_hooks(): void {
Java.perform(() => {
const FileClass = Java.use("java.io.File");
// Define methods to hook
const methodsToHook = [
{ name: 'exists', args: [] },
{ name: 'canRead', args: [] },
{ name: 'canWrite', args: [] },
{ name: 'delete', args: [] }
];
methodsToHook.forEach(methodInfo => {
try {
const method = methodInfo.args.length > 0
? FileClass[methodInfo.name].overload(...methodInfo.args)
: FileClass[methodInfo.name];
method.implementation = function(...args) {
const filePath = this.getAbsolutePath();
const result = method.apply(this, args);
createFileEvent(`file.${methodInfo.name}`, {
file_path: filePath,
method: methodInfo.name,
result: result,
arguments: args
});
return result;
};
} catch (error) {
devlog(`Failed to hook ${methodInfo.name}: ${error}`);
}
});
});
}
Performance Considerations
Efficient Hook Design
export function install_optimized_hooks(): void {
Java.perform(() => {
const TargetClass = Java.use("com.example.TargetClass");
// Pre-compute expensive operations
const stringClass = Java.use("java.lang.String");
const threadClass = Java.use("java.lang.Thread");
const currentThread = threadClass.currentThread();
TargetClass.frequentMethod.implementation = function(param) {
// Minimal processing in hot path
const startTime = Date.now();
const result = this.frequentMethod(param);
// Only create event if necessary
if (shouldLogEvent(param)) {
createOptimizedEvent("frequent.method", {
parameter: param,
execution_time: Date.now() - startTime
});
}
return result;
};
});
}
function shouldLogEvent(param: any): boolean {
// Custom logic to reduce event volume
return param && param.toString().length > 10;
}
Memory Management
// Limit state storage size
const MAX_SESSION_COUNT = 1000;
const activeSessions = new Map<number, SessionInfo>();
function cleanupOldSessions(): void {
if (activeSessions.size > MAX_SESSION_COUNT) {
// Remove oldest entries
const entries = Array.from(activeSessions.entries());
const toRemove = entries.slice(0, entries.length - MAX_SESSION_COUNT);
toRemove.forEach(([key]) => activeSessions.delete(key));
}
}
Best Practices
- Hook Design:
Always wrap hooks in try-catch blocks
Use descriptive event type names (e.g.,
crypto.key.creation
)Include relevant context in event data
Call original methods to maintain app functionality
- Error Handling:
Gracefully handle missing classes/methods
Log errors using
devlog()
for debuggingDon’t break app execution due to hook failures
Validate data before processing
- Performance:
Minimize processing in frequently called methods
Use conditional event creation for high-volume hooks
Clean up state periodically to prevent memory leaks
Pre-compute expensive operations when possible
- Integration:
Follow naming conventions for consistency
Document hook behavior and event formats
Add CLI support for user control
Create corresponding Python parsers
Testing Hooks
Local Testing
# Compile hooks
npm run build
# Test with target app
dexray-intercept --enable-my-custom com.test.app
# Use verbose mode for debugging
dexray-intercept -v --enable-my-custom com.test.app
Development Workflow
# Development cycle
1. Edit TypeScript hook file
2. npm run build # Compile to JavaScript
3. dexray-intercept --enable-my-custom app # Test with target
4. Check JSON output for events
5. Iterate and refine
Example: Complete Custom Hook
Here’s a complete example of a custom hook implementation:
TypeScript Hook (agent/custom/android_id.ts
):
import { log, devlog, am_send } from "../utils/logging.js"
import { Java } from "../utils/javalib.js"
const PROFILE_HOOKING_TYPE: string = "ANDROID_ID_ACCESS"
function createAndroidIdEvent(eventType: string, data: any): void {
const event = {
event_type: eventType,
timestamp: Date.now(),
...data
};
am_send(PROFILE_HOOKING_TYPE, JSON.stringify(event));
}
export function install_android_id_hooks(): void {
devlog("Installing Android ID access hooks");
Java.perform(() => {
try {
const Settings = Java.use("android.provider.Settings$Secure");
Settings.getString.overload("android.content.ContentResolver", "java.lang.String").implementation = function(resolver, name) {
const result = this.getString(resolver, name);
if (name === "android_id") {
createAndroidIdEvent("android_id.access", {
setting_name: name,
android_id: result,
access_method: "Settings.Secure.getString"
});
}
return result;
};
log("Android ID hooks installed successfully");
} catch (error) {
devlog("Error installing Android ID hooks: " + error);
}
});
}
Python Parser (src/dexray_intercept/parsers/android_id.py
):
from .base import BaseParser
from ..models.events import Event
class AndroidIdEvent(Event):
def __init__(self, event_type: str, timestamp: str):
super().__init__(event_type, timestamp)
self.setting_name = None
self.android_id = None
self.access_method = None
def get_event_data(self):
return {
"event_type": self.event_type,
"setting_name": self.setting_name,
"android_id": self.android_id,
"access_method": self.access_method
}
class AndroidIdParser(BaseParser):
def parse_json_data(self, data: dict, timestamp: str):
event = AndroidIdEvent(data.get('event_type'), timestamp)
event.setting_name = data.get('setting_name')
event.android_id = data.get('android_id')
event.access_method = data.get('access_method')
return event
Integration Steps:
Add to Hook Loader (
agent/hooking_profile_loader.ts
):
import { install_android_id_hooks } from "./custom/android_id.js"
export let hook_config: HookConfig = {
// ... existing hooks ...
'android_id_hooks': false,
};
function load_profile_hooks() {
// ... existing installations ...
install_hook_conditionally('android_id_hooks', install_android_id_hooks);
}
Register Parser (
src/dexray_intercept/parsers/factory.py
):
from .android_id import AndroidIdParser
def _register_default_parsers(self):
# ... existing parsers ...
self._parsers["ANDROID_ID_ACCESS"] = AndroidIdParser()
Add CLI Support (
src/dexray_intercept/ammm.py
):
individual_hooks = {
# ... existing hooks ...
'enable_android_id': 'android_id_hooks'
}
hooks.add_argument("--enable-android-id", action="store_true",
help="Enable Android ID access monitoring")
Build and Test:
npm run build
dexray-intercept --enable-android-id com.test.app
This creates a complete hook system that monitors Android ID access, parses the events in Python, and integrates with the CLI system.
Next Steps
Review existing hooks in
agent/
directories for more examplesStudy the Python API for event processing: Python API Reference
Learn about the development workflow: Development Guide
Explore advanced hook patterns in the source code