| name | renef |
| description | Use when operating renef / renef.io to instrument Android ARM64 apps: hooking native and Java functions, scanning/reading/writing/patching process memory, tracing syscalls (renef-strace), stack backtraces, value/memory cheats, and writing Lua 5.4 scripts. Reach for this for SSL pinning bypass, root/debugger detection bypass, function tracing, crypto key logging, game memory editing/cheats, CTF native challenges, or porting Frida JS / GameGuardian (gg.*) scripts to renef. Triggers: "renef", "renef.io", ".renef script", "renef-strace", "hook this Android function", "bypass SSL pinning with renef", "port this Frida/GameGuardian script to renef". |
Operating Renef
You are driving renef — a dynamic instrumentation engine for Android ARM64. Your job is to
emit renef Lua/CLI that runs correctly the first time. If you've written Frida hooks, GameGuardian
cheats, or game-hacking Lua, the concepts already transfer — you only need renef's syntax and
workflow, which this skill makes exact. Do not guess API names; use the Syntax Contract below as
ground truth, and run the Self-Lint before presenting any script.
For authorized security testing, app-hardening review, CTFs, and reverse-engineering of
software you own or are permitted to analyze.
Start here for any real target: run probe.lua (bundled with this skill) to ground-truth the
actual API/behavior of this renef build, then follow the investigation loop in
references/methodology.md. Attempt and measure on-device — don't pre-decide what's possible —
and report status honestly: not attempted → installed but unverified → verified working. The
human drives the device/app/proxy and decides the goal; you do the enumeration, scripting,
error-parsing, and verification. Recipes below are worked examples of the loop, not a menu to
copy blindly (offsets/signatures are target-specific).
What you're driving
- Topology — read this before anything.
renef is a host client (your macOS/Linux shell)
that drives renef_server running on the Android device over an ADB-forwarded socket. You do
not run renef on the device, and you do not adb push hook.lua + adb shell renef ….
That on-device pattern is only --local mode (Termux/SSH on a rooted phone). Default and
assumed-here = host client → remote device. Lua scripts stay on the host and are sent over the
wire by -l; device-side paths apply only under --local.
- Your Lua runs inside the target process (injected
libagent.so, embedded Lua 5.4, memfd
injection, no ptrace). Module.*, Memory.*, hook callbacks all execute in the app's address
space — paths, PIDs, fds are the target's.
- You drive it from the CLI REPL (
renef>) or one-shot via -l script.lua.
- Hook/trace output is asynchronous — you must
watch (or launch with -w) or you see nothing.
Preflight (Step 0 — a gate, before you write, run, or "test" anything)
Confirm the host→device chain end-to-end. Until you do, you cannot test — deliver the script
as explicitly unverified, never as "tested/working", and never invent run output.
adb devices lists exactly one authorized device (more than one → require -d <id>; none → stop).
- The
renef client exists on the host: command -v renef (or renef -h).
renef_server is running on the device: adb shell pidof renef_server. If absent, start it
(adb shell su -c '/data/local/tmp/renef_server &') or rely on magisk-renef.
- Root (or gadget mode for non-root), target is ARM64, and the package is installed (
la~<name>).
A "test" = you saw real hook output via -w/watch while exercising the app — not that the
script reads correctly. If any link above is missing, say which one and stop short of claiming a run.
The loop (always follow this)
- Connect to the target (table below).
- Locate the target code — resolve an offset by symbol or byte-pattern, never hardcode an
absolute runtime address; guard with
if not base then return end.
- Install the smallest hook that proves the call fires (
print in onEnter).
- Verify by watching output while you trigger the app (
-w / watch).
- Iterate — add logic only after the minimal hook is confirmed.
For startup-time protections (root/integrity/debugger checks), connect with --pause (spawn gate)
so hooks install before the app's onCreate runs.
Connect modes
| Mode | Command | When |
|---|
| Spawn (root) | renef -s com.pkg -l hook.lua | start fresh, hooks before app code |
| Attach (root) | renef -a <pid> -l hook.lua | already running (adb shell pidof com.pkg) |
| Spawn gate | renef -s com.pkg -l bypass.lua --pause | beat startup-time checks |
| Gadget (no root) | renef -g <pid> -l hook.lua | non-root, APK patched with libagent.so |
| Local (on-device) | renef --local -s com.pkg -l /data/local/tmp/hook.lua | Termux/SSH, SELinux-safe |
Every row except the last runs renef on the host against the device — only --local runs the
client on-device. Server must run on device first (adb shell /data/local/tmp/renef_server, or the
magisk-renef module auto-starts it). Engine: trampoline (default, any address) or --hook=pltgot (imported
functions only). Full flag list and REPL commands → references/cli.md.
Syntax Contract (ground truth — do not invent API)
Language is Lua 5.4. local; .. concatenation; string.format("0x%x", n); function(x) end;
tables {}; nil; ipairs/#t; integer hex 0x... No var/let/const, arrow fns, template
strings, or ===.
Native hook — target is (library, offset), never an absolute address:
hook("libc.so", 0x12340, {
onEnter = function(args)
args[0] = 0x200
end,
onLeave = function(retval)
return retval
end,
caller = "libnative.so"
})
Java hook — class uses / (JNI), signature is mandatory:
hook("com/example/Cls", "method", "(Ljava/lang/String;)Z", {
onEnter = function(args)
local s = Jni.getStringUTF(args[2])
end,
onLeave = function(retval)
return 1
end
})
onEnter.args also exposes args.class/method/signature/isStatic. onLeave return options:
nil=keep · integer=set x0 (1 true / 0 false) · boolean=1/0 · Jni.newStringUTF("x")=new
String · {__jni_type="string"|"int"|"boolean", value=...}=typed · retval.raw=pass original.
Memory: Memory.scan(pat[,lib])/search (string or hex, ?? wildcards) → table of
{library,addr,offset,hex,ascii}. read(a,n) readU8/16/32/64(a) readStr(a[,n])/readString.
write(a,bytes) writeU8/16/32/64(a,v) patch(a,bytes) (auto-mprotect). There is no
Memory.writeString — write a string as bytes: Memory.write(addr, "text\0").
hexdump(target[,len]) → string, print() it.
Module: find(name)→base int|nil · list()→string · exports(name)→{name,offset}
(.dynsym) · symbols(name)→{name,offset} (.symtab, nil if stripped).
Java runtime (no hook): Java.use("p/C") → :call(m,sig,...) / :new(sig,...);
instance :call(m,sig,...); .raw = ART pointer; Java.registerClass{implements=,methods=};
Java.array("p/C",{...}).
Other globals: Jni.newStringUTF/getStringUTF/getStringLength/deleteGlobalRef ·
Thread.backtrace() (auto caller context in hooks) / Thread.id() · File.read/exists/readlink/ fdpath · Syscall.trace(...)/stop()/list() · OS.getpid/kill/tgkill/listdir · print /
console.log / color globals RED GREEN YELLOW BLUE MAGENTA CYAN WHITE RESET.
Script directive: __hook_type__ = "trampoline"|"pltgot" at top of file.
There is no Interceptor, Java.perform, Process.*, ptr()/NativePointer, or gg.*
namespace. Those belong to other tools — translate them (references/from-other-tools.md).
Share state between onEnter and onLeave with a closure-local (NOT args)
args does not exist in onLeave. Capture in a local declared just before hook(...):
for _, e in ipairs(Module.exports("libc.so")) do
if e.name == "open" then
local blocked = false
hook("libc.so", e.offset, {
onEnter = function(args)
local p = Memory.readString(args[0]); blocked = p and p:find("/su") ~= nil
end,
onLeave = function(retval) if blocked then return -1 end; return retval end
})
break
end
end
Self-Lint (run over every script before presenting it)
Task → playbook
| User wants | Do |
|---|
| Override a Java boolean/int check (root, isVip, license) | Java hook, onLeave return 0/1 |
Bypass a void Java verify that throws (SSL checkServerTrusted) | Java hook, onEnter args.skip = true |
| Replace a Java String return | onLeave return Jni.newStringUTF("...") |
| Trace/inspect a native function | resolve offset via Module.exports/symbols, minimal onEnter hook, watch |
| Hook a not-yet-loaded lib (Flutter, plugin) | hook do_dlopen in linker64, install on load |
| Game value cheat (find/edit/freeze a number) | Memory.scan → refine → writeU32; freeze via a hot hook → from-other-tools.md (GameGuardian) |
| Monitor file/network/syscalls | Syscall.trace{category="file"} or renef-strace, then watch |
| Patch/NOP native code | Memory.search pattern → Memory.patch(addr, bytes) |
| Port a Frida or GameGuardian script | references/from-other-tools.md |
Reference files (read on demand — don't load them all at once)
references/methodology.md — the investigation loop: how to solve a NEW bypass/hook yourself
(classify → locate → probe mechanism → minimal hook → verify → choose primitive), decompose-to-
smallest-goal, human/AI roles, honest status. Start here for anything not covered by a recipe.
probe.lua — bundled diagnostic. Run it FIRST on a new target/build to measure the real API
surface, error behavior, arg layout, and the reflection bridge instead of trusting notes.
references/debugging.md — read this when anything fails. renef hides errors (ERROR: Lua execution failed / (no response) / Agent disconnected); how to recover the real message with
pcall/load, the hot-hook crash race, --pause caveats, verifying installs, connection
recovery, and recognizing RASP/shielded apps.
references/lua-api.md — full API: exact signatures, return shapes, JNI signature table,
per-function notes. Read before writing a non-trivial script.
references/cli.md — every CLI command/flag, connect modes, spawn gate, build/deploy, r2renef,
python binding, magisk-renef.
references/from-other-tools.md — translate Frida JS and GameGuardian (gg.*) /
game-hacking Lua into renef. Includes a reusable value scan→refine→freeze helper.
references/recipes.md — copy-paste scripts: SSL unpinning (Java/Flutter/native), root/debugger
bypass, crypto logging, tracing, dlopen watch, memory cheats, syscall auditing, CTF.
references/gotchas.md — pitfalls, doc/runtime discrepancies (Jni vs JNI, undocumented
helpers), troubleshooting (injection fails, hook not firing, SELinux), performance.