Frida to Renef
A migration guide for Frida users. This page maps common Frida JavaScript APIs to their Renef Lua equivalents with side-by-side examples.
Renef uses Lua 5.4 instead of JavaScript. Key syntax differences:
localinstead ofvar/let/const,..for string concatenation,string.formatinstead of template literals,function(x) endinstead of(x) => {}, tables{}instead of objects/arrays.
API Mapping
Module / Process
| Frida (JS) | Renef (Lua) | Notes |
|---|---|---|
Module.findBaseAddress("libc.so") | Module.find("libc.so") | Returns integer (not NativePointer) |
Module.findExportByName("libc.so", "open") | Manual lookup via Module.exports() | See example below |
Module.enumerateExports("libc.so") | Module.exports("libc.so") | Returns {name, offset} table |
Module.enumerateSymbols("libc.so") | Module.symbols("libc.so") | Reads .symtab section |
Process.enumerateModules() | Module.list() | Returns string, not table |
Finding an export by name:
// Frida
var open = Module.findExportByName("libc.so", "open");
Interceptor.attach(open, { ... });
-- Renef
for _, sym in ipairs(Module.exports("libc.so")) do
if sym.name == "open" then
hook("libc.so", sym.offset, { ... })
break
end
end
Module.exports()reads.dynsym(public symbols).Module.symbols()reads.symtab(all symbols including internal/static). Usesymbols()for stripped-away internal functions like linker’sdo_dlopen.
Native Hooks
| Frida (JS) | Renef (Lua) |
|---|---|
Interceptor.attach(ptr(addr), callbacks) | hook("lib.so", offset, callbacks) |
Interceptor.detachAll() | unhook all (CLI command) |
Renef hooks take a library name + offset pair instead of an absolute address. The offset is relative to the library base.
Hook a native function:
// Frida
Interceptor.attach(Module.findExportByName("libc.so", "open"), {
onEnter: function(args) {
console.log("open(" + args[0].readUtf8String() + ")");
},
onLeave: function(retval) {
console.log("fd = " + retval.toInt32());
}
});
-- Renef
for _, sym in ipairs(Module.exports("libc.so")) do
if sym.name == "open" then
hook("libc.so", sym.offset, {
onEnter = function(args)
print("open(" .. Memory.readString(args[0]) .. ")")
end,
onLeave = function(retval)
print("fd = " .. retval)
return retval
end
})
break
end
end
Modify arguments and return values:
// Frida
Interceptor.attach(addr, {
onEnter: function(args) {
args[0] = ptr(0x200);
},
onLeave: function(retval) {
retval.replace(ptr(0x1));
}
});
-- Renef
hook("libc.so", offset, {
onEnter = function(args)
args[0] = 0x200
end,
onLeave = function(retval)
return 0x1 -- return value to replace
end
})
In Frida,
retval.replace()modifies in-place. In Renef, youreturnthe new value fromonLeave.
Java Hooks
| Frida (JS) | Renef (Lua) |
|---|---|
Java.perform(fn) | Not needed |
cls.method.implementation = fn | hook("class", "method", "sig", callbacks) |
| Auto-resolved overloads | Explicit JNI signature required |
this.method(args) (call original) | Automatic (original called unless args.skip = true) |
| No equivalent | args.skip = true (skip original entirely) |
Renef uses
/separators for class names (JNI format):"com/example/Class"instead of"com.example.Class". Java hooks require an explicit JNI signature.
Hook a Java method:
// Frida
Java.perform(function() {
var MainActivity = Java.use("com.example.app.MainActivity");
MainActivity.getSecret.implementation = function(input) {
console.log("getSecret called with: " + input);
var result = this.getSecret(input); // call original
console.log("returned: " + result);
return "HOOKED!";
};
});
-- Renef
hook("com/example/app/MainActivity", "getSecret",
"(Ljava/lang/String;)Ljava/lang/String;", {
onEnter = function(args)
local input = Jni.getStringUTF(args[2])
print("getSecret called with: " .. tostring(input))
end,
onLeave = function(retval)
local original = Jni.getStringUTF(retval)
print("returned: " .. tostring(original))
return Jni.newStringUTF("HOOKED!")
end
})
Java hook args layout:
args[0]= ArtMethod pointer (internal),args[1]=this(instance) or first param (static),args[2..n]= method parameters. String arguments are raw pointers — useJni.getStringUTF()to read them.
Skip original method (bypass pattern):
In Frida, you skip the original by simply not calling this.method(). In Renef, use args.skip = true:
// Frida
Java.perform(function() {
var TrustManager = Java.use("com.android.org.conscrypt.TrustManagerImpl");
TrustManager.checkServerTrusted.implementation = function() {
// Don't call original — just return
};
});
-- Renef
hook("com/android/org/conscrypt/TrustManagerImpl", "checkServerTrusted",
"([Ljava/security/cert/X509Certificate;Ljava/lang/String;)V", {
onEnter = function(args)
args.skip = true -- skip original, no exception thrown
end
})
Java hook return value options:
Return from onLeave | Effect |
|---|---|
nil (or no return) | Original return value unchanged |
| integer | Sets x0 register directly |
boolean (true/false) | Sets x0 to 1 or 0 |
Jni.newStringUTF("...") | Returns new Java String |
{__jni_type="string", value="..."} | Creates and returns a Java String |
{__jni_type="int", value=N} | Sets x0 to N |
Jni helpers (for use in hook callbacks):
| Frida (JS) | Renef (Lua) |
|---|---|
Java.use("java.lang.String").$new("text") | Jni.newStringUTF("text") |
| String args auto-converted | Jni.getStringUTF(args[2]) — manual read |
| N/A | Jni.getStringLength(ref) |
| N/A | Jni.deleteGlobalRef(ref) |
Java API (Class Interaction)
| Frida (JS) | Renef (Lua) | Notes |
|---|---|---|
Java.use("com.example.Class") | Java.use("com/example/Class") | / separators, ClassLoader fallback |
cls.$new(args) | wrapper:new(sig, args) | Explicit JNI signature |
cls.staticMethod(args) | wrapper:call("method", sig, args) | Explicit JNI signature |
instance.method(args) | instance:call("method", sig, args) | Explicit JNI signature |
Java.registerClass({...}) | Java.registerClass({...}) | DEX proxy bridge |
Java.array("java.lang.String", [...]) | Java.array("java/lang/String", {...}) | / separators |
| N/A | instance.raw | Raw ART mirror::Object* pointer |
Create instance and call methods:
// Frida
Java.perform(function() {
var StringBuilder = Java.use("java.lang.StringBuilder");
var sb = StringBuilder.$new();
sb.append("Hello");
sb.append(" World");
console.log(sb.toString());
});
-- Renef
local StringBuilder = Java.use("java/lang/StringBuilder")
local sb = StringBuilder:new("()V")
sb:call("append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", "Hello")
sb:call("append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", " World")
print(sb:call("toString", "()Ljava/lang/String;"))
Call static methods:
// Frida
Java.perform(function() {
var System = Java.use("java.lang.System");
console.log(System.currentTimeMillis());
});
-- Renef
local System = Java.use("java/lang/System")
print(System:call("currentTimeMillis", "()J"))
Register a custom class:
// Frida
var EmptyTrustManager = Java.registerClass({
name: "com.frida.EmptyTrustManager",
implements: [Java.use("javax.net.ssl.X509TrustManager")],
methods: {
checkClientTrusted: function(chain, authType) {},
checkServerTrusted: function(chain, authType) {},
getAcceptedIssuers: function() { return []; }
}
});
-- Renef
local EmptyTrustManager = Java.registerClass({
name = "com.renef.EmptyTrustManager",
implements = { "javax/net/ssl/X509TrustManager" },
methods = {
checkClientTrusted = function(self, args) end,
checkServerTrusted = function(self, args) end,
getAcceptedIssuers = function(self, args) return nil end
}
})
Create Java arrays:
// Frida
var tm_array = Java.array("javax.net.ssl.TrustManager", [emptyTm]);
-- Renef
local tm_array = Java.array("javax/net/ssl/TrustManager", { emptyTm })
-- Use tm_array.raw to pass as hook argument
Memory
| Frida (JS) | Renef (Lua) | Notes |
|---|---|---|
Memory.scan(addr, size, pattern, callbacks) | Memory.search("pattern", "lib.so") | Searches all .so regions by default |
Memory.scanSync(addr, size, pattern) | Memory.scan("pattern") | Alias for Memory.search |
| N/A | Memory.dump(results) | Pretty-prints search results |
ptr(addr).readU8() | Memory.readU8(addr) | |
ptr(addr).readU16() | Memory.readU16(addr) | |
ptr(addr).readU32() | Memory.readU32(addr) | |
ptr(addr).readU64() | Memory.readU64(addr) | |
ptr(addr).readUtf8String() | Memory.readString(addr) | Also: Memory.readStr() |
ptr(addr).readByteArray(size) | Memory.read(addr, size) | Returns Lua string |
ptr(addr).writeU8(val) | Memory.writeU8(addr, val) | |
ptr(addr).writeU16(val) | Memory.writeU16(addr, val) | |
ptr(addr).writeU32(val) | Memory.writeU32(addr, val) | |
ptr(addr).writeU64(val) | Memory.writeU64(addr, val) | |
ptr(addr).writeByteArray(bytes) | Memory.write(addr, bytes) | |
Memory.patchCode(addr, size, fn) | Memory.patch(addr, bytes) | Auto-handles mprotect |
hexdump(target, options) | hexdump(target [, length]) | Global function, also Memory.hexdump() |
Memory scan:
// Frida
var ranges = Process.enumerateRanges('r--');
Memory.scan(ranges[0].base, ranges[0].size, "DE AD BE EF", {
onMatch: function(address, size) {
console.log("Found at: " + address);
},
onComplete: function() {
console.log("Scan complete");
}
});
-- Renef (searches all .so regions automatically)
local results = Memory.search("DE AD BE EF")
Memory.dump(results)
-- Or search in a specific library
local results = Memory.search("DE AD BE EF", "libtarget.so")
Renef’s
Memory.searchsupports??wildcards:Memory.search("FD 7B ?? A9")matches any ARM64 function prologue.
Hexdump:
// Frida
console.log(hexdump(ptr(addr), { length: 64, ansi: true }));
console.log(hexdump(byteArray));
-- Renef
print(hexdump(addr, 64)) -- address + length
print(hexdump(binary_string)) -- string data
print(hexdump(args[1], 64)) -- hook arg
print(hexdump({0x41, 0x42})) -- byte table
Renef’s
hexdump()accepts integers (addresses), strings, userdata (Java.rawpointers), and byte tables. It returns a string — useprint()to display.
Read and write memory:
// Frida
var base = Module.findBaseAddress("libgame.so");
var val = base.add(0x1234).readU32();
console.log("Value: " + val);
base.add(0x1234).writeU32(0xD503201F);
-- Renef
local base = Module.find("libgame.so")
local val = Memory.readU32(base + 0x1234)
print(string.format("Value: 0x%x", val))
Memory.writeU32(base + 0x1234, 0xD503201F)
Patch with mprotect:
// Frida
Memory.patchCode(addr, 4, function(code) {
var writer = new Arm64Writer(code);
writer.putNop();
writer.flush();
});
-- Renef (mprotect handled automatically)
Memory.patch(addr, "\x1f\x20\x03\xd5") -- ARM64 NOP
Thread
| Frida (JS) | Renef (Lua) | Notes |
|---|---|---|
Thread.backtrace(this.context, Backtracer.ACCURATE) | Thread.backtrace() | Auto-detects hook context |
Process.getCurrentThreadId() | Thread.id() | Returns gettid() |
Stack trace in hooks:
// Frida
Interceptor.attach(addr, {
onEnter: function(args) {
console.log(Thread.backtrace(this.context, Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress).join('\n'));
}
});
-- Renef (hook context auto-detected)
hook("libc.so", offset, {
onEnter = function(args)
local bt = Thread.backtrace()
for _, f in ipairs(bt) do
local mod = f.module or string.format("0x%x", f.pc)
local sym = f.symbol or string.format("+0x%x", f.offset or 0)
print(string.format(" %s %s", mod, sym))
end
end
})
In Frida, you must pass
this.contextto get the caller’s backtrace. In Renef,Thread.backtrace()automatically uses the caller’s frame pointer when called from a hook callback — no arguments needed.
File System
Renef provides a built-in File API for reading files within the target process. Frida has no direct equivalent for this.
| Frida (JS) | Renef (Lua) |
|---|---|
N/A (use recv/send to proxy) | File.read(path) |
| N/A | File.exists(path) |
| N/A | File.readlink(path) |
| N/A | File.fdpath(fd) |
-- Read /proc/self/maps from target process
local maps = File.read("/proc/self/maps")
-- Resolve file descriptor in a hook
hook("libc.so", read_offset, {
onEnter = function(args)
local path = File.fdpath(args[0])
print("read() on: " .. tostring(path))
end
})
Console / Output
| Frida (JS) | Renef (Lua) |
|---|---|
console.log(msg) | print(msg) or console.log(msg) |
Template literals `value: ${x}` | string.format("value: 0x%x", x) |
| N/A | Color codes: RED, GREEN, YELLOW, BLUE, CYAN, MAGENTA, WHITE, RESET |
// Frida
console.log(`Found at: ${addr}`);
-- Renef
print(string.format("Found at: 0x%x", addr))
print(GREEN .. "Success!" .. RESET)
CLI
| Frida | Renef |
|---|---|
frida -U -f com.example.app | ./renef -s com.example.app |
frida -U -p 1234 | ./renef -a 1234 |
frida -U -f app -l script.js | ./renef -s app -l script.lua |
frida -U --no-pause -f app | ./renef -s app (no pause by default) |
| Separate gadget binary | ./renef -g <pid> (built-in gadget mode) |
| N/A | ./renef -s app -v (verbose/logcat debug output) |
| N/A | hookgen libc.so malloc (generate hook template) |
Full Script Examples
SSL Pinning Bypass
// Frida
Java.perform(function() {
var TrustManagerImpl = Java.use("com.android.org.conscrypt.TrustManagerImpl");
TrustManagerImpl.checkServerTrusted.overload(
"[Ljava.security.cert.X509Certificate;", "java.lang.String"
).implementation = function(chain, authType) {
// Do nothing — skip verification
};
var CertificatePinner = Java.use("okhttp3.CertificatePinner");
CertificatePinner.check.overload("java.lang.String", "java.util.List")
.implementation = function(hostname, peerCerts) {
// Do nothing
};
});
-- Renef
hook("com/android/org/conscrypt/TrustManagerImpl", "checkServerTrusted",
"([Ljava/security/cert/X509Certificate;Ljava/lang/String;)V", {
onEnter = function(args)
args.skip = true
end
})
hook("okhttp3/CertificatePinner", "check",
"(Ljava/lang/String;Ljava/util/List;)V", {
onEnter = function(args)
args.skip = true
end
})
Custom TrustManager Replacement
// Frida
Java.perform(function() {
var X509TrustManager = Java.use("javax.net.ssl.X509TrustManager");
var SSLContext = Java.use("javax.net.ssl.SSLContext");
var EmptyTM = Java.registerClass({
name: "com.frida.EmptyTrustManager",
implements: [X509TrustManager],
methods: {
checkClientTrusted: function() {},
checkServerTrusted: function() {},
getAcceptedIssuers: function() { return []; }
}
});
var ctx = SSLContext.getInstance("TLS");
ctx.init(null, [EmptyTM.$new()], null);
});
-- Renef
local EmptyTM = Java.registerClass({
name = "com.renef.EmptyTrustManager",
implements = { "javax/net/ssl/X509TrustManager" },
methods = {
checkClientTrusted = function(self, args) end,
checkServerTrusted = function(self, args) end,
getAcceptedIssuers = function(self, args) return nil end
}
})
local tm_array = Java.array("javax/net/ssl/TrustManager", { EmptyTM })
hook("javax/net/ssl/SSLContext", "init",
"(Ljavax/net/ssl/KeyManager;[Ljavax/net/ssl/TrustManager;Ljava/security/SecureRandom;)V", {
onEnter = function(args)
args[3] = tm_array.raw
print("[*] SSLContext.init: TrustManagers replaced")
end
})
Function Tracing
// Frida
var malloc = Module.findExportByName("libc.so", "malloc");
Interceptor.attach(malloc, {
onEnter: function(args) {
this.size = args[0].toInt32();
console.log("malloc(" + this.size + ")");
},
onLeave: function(retval) {
console.log(" => " + retval);
}
});
-- Renef
for _, sym in ipairs(Module.exports("libc.so")) do
if sym.name == "malloc" then
hook("libc.so", sym.offset, {
onEnter = function(args)
print(string.format("malloc(0x%x)", args[0]))
end,
onLeave = function(retval)
print(string.format(" => 0x%x", retval))
return retval
end
})
break
end
end
Library Load Monitoring
// Frida
var dlopen = Module.findExportByName(null, "android_dlopen_ext");
Interceptor.attach(dlopen, {
onEnter: function(args) {
this.path = args[0].readUtf8String();
console.log("[dlopen] " + this.path);
}
});
-- Renef (hooks internal do_dlopen via .symtab)
local symbols = Module.symbols("linker64")
for _, sym in ipairs(symbols) do
if sym.name:find("do_dlopen") then
hook("linker64", sym.offset, {
onEnter = function(args)
local path = Memory.readString(args[0])
if path then
print("[dlopen] " .. path)
end
end
})
break
end
end
Get Application Info via Java API
// Frida
Java.perform(function() {
var ActivityThread = Java.use("android.app.ActivityThread");
var app = ActivityThread.currentApplication();
var pkg = app.getPackageName();
console.log("Package: " + pkg);
});
-- Renef
local ActivityThread = Java.use("android/app/ActivityThread")
local app = ActivityThread:call("currentApplication", "()Landroid/app/Application;")
local pkg = app:call("getPackageName", "()Ljava/lang/String;")
print("Package: " .. pkg)
Key Differences Summary
| Frida | Renef | |
|---|---|---|
| Language | JavaScript (V8/QuickJS) | Lua 5.4 |
| Platform | Multi-platform (Android, iOS, Windows, macOS, Linux) | Android ARM64 |
| Java class format | com.example.Class | com/example/Class (JNI format) |
| Java method overloads | Auto-resolved (.overload() for ambiguous) | Explicit JNI signature always required |
Java perform wrapper | Required (Java.perform(fn)) | Not needed |
| Hook target | Absolute address (ptr(0x...)) | Library name + offset |
| Hook return modify | retval.replace(val) | return val from onLeave |
| Skip original (Java) | Don’t call this.method() | args.skip = true |
| Memory API style | OOP (ptr(x).readU32()) | Functional (Memory.readU32(x)) |
| File system access | Via recv/send proxy | Built-in File API |
| Gadget mode | Separate gadget binary + config | Built-in -g flag |
| Script sharing | Community repos | Hookshare |