Skip to content

Instantly share code, notes, and snippets.

@sharpicx
Last active January 5, 2026 23:01
Show Gist options
  • Select an option

  • Save sharpicx/ea13b18d1de737c9af6cfd68e8ad4156 to your computer and use it in GitHub Desktop.

Select an option

Save sharpicx/ea13b18d1de737c9af6cfd68e8ad4156 to your computer and use it in GitHub Desktop.
react native
// https://githepia.hesge.ch/julien.debray/rpg/-/tree/main/frontend/node_modules/react-native?ref_type=heads
"use strict";
const TARGET_LIB = "libreactnativejni.so";
let nativeHooked = false;
let javaHooked = false;
var dumped = false;
function hookCxxReact() {
const exports = Module.enumerateExports(TARGET_LIB);
exports.forEach((exp) => {
if (
exp.name.includes("facebook") &&
exp.name.includes("react") &&
exp.name.includes("Instance") &&
exp.name.includes("callJSFunction")
) {
console.log("[+] Hooking CxxReact Instance: " + exp.name);
Interceptor.attach(exp.address, {
onEnter(args) {
const module = args[1].readUtf8String();
const method = args[2].readUtf8String();
console.log(`[CxxReact] Calling ${module}.${method}`);
},
});
}
});
}
function hookHermes() {
if (nativeHooked) return;
nativeHooked = true;
try {
var hermes = Process.getModuleByName("libhermes.so");
var symbols = hermes.enumerateSymbols();
for (var i = 0; i < symbols.length; i++) {
if (symbols[i].name.indexOf("evaluateJavaScript") !== -1) {
Interceptor.attach(symbols[i].address, {
onEnter: function (args) {
try {
if (!args[1].isNull()) {
var js = args[1].readCString();
dump(js);
}
} catch (e) {
console.log("eval hook error:", e);
}
},
});
break;
}
}
} catch (e) {
console.log("hookHermes error:", e);
}
}
function hookBCProvider() {
var hermes = Process.getModuleByName("libhermes.so");
var symbols = hermes.enumerateSymbols();
symbols.forEach(function (s) {
if (s.name.indexOf("BCProviderFromBuffer") !== -1) {
Interceptor.attach(s.address, {
onEnter: function (args) {
if (dumped) return;
try {
var buf = args[0];
var size = args[1].toInt32();
if (size > 0x1000) {
dumpHBC(buf, size);
dumped = true;
}
} catch (_) {}
},
});
}
});
}
function dumpHBC(ptr, size) {
try {
var data = Memory.readByteArray(ptr, size);
var path = "/data/local/tmp/dump.hbc";
var f = new File(path, "wb");
f.write(data);
f.close();
} catch (_) {}
}
function dump(data) {
var path = "/data/local/tmp/bundle.js";
var f = new File(path, "w");
f.write(data);
f.close();
}
function hookHermesCheck() {
const module = Process.getModuleByName(TARGET_LIB);
const exports = module.enumerateExports();
let addr = null;
for (const exp of exports) {
if (exp.name.includes("isHermesBytecodeBundle")) {
addr = exp.address;
break;
}
}
if (addr) {
Interceptor.attach(addr, {
onEnter(args) {
const headerAddr = args[0];
try {
const magic64 = headerAddr.readU64();
const version = headerAddr.add(8).readU32();
console.log("Address: " + headerAddr);
console.log("Magic64 (Hex): 0x" + magic64.toString(16));
console.log("Version: " + version);
console.log(
hexdump(headerAddr, {
length: 12,
header: true,
ansi: true,
}),
);
} catch (e) {}
},
});
}
}
function hookHermesStrings() {
const module = Process.getModuleByName(TARGET_LIB);
const exports = module.enumerateExports();
let addr = null;
for (const exp of exports) {
if (exp.name.includes("StringPrimitive") && exp.name.includes("create")) {
addr = exp.address;
break;
}
}
if (addr) {
Interceptor.attach(addr, {
onEnter(args) {
const strPtr = args[1];
try {
const content = strPtr.readUtf8String();
if (content) {
console.log("[STRING] " + content);
}
} catch (e) {}
},
});
}
}
function hookJNIStrings() {
if (nativeHooked) return;
nativeHooked = true;
const newString = Module.findExportByName(
"libart.so",
"_ZN3art3JNI11NewStringUTFEP7_JNIEnvPKc",
);
if (newString) {
Interceptor.attach(newString, {
onEnter(args) {
const ptr = args[1];
if (ptr.isNull()) return;
const s = ptr.readUtf8String();
if (!s) return;
if (s.length > 20 && (s.includes("-") || /^[A-Za-z0-9+/=]+$/.test(s))) {
console.log("[JNI String] " + s);
}
},
});
console.log("[+] ART hook installed");
return;
}
const newString2 = Module.findExportByName(
"libart.so",
"_ZN3art3JNI9NewStringEP7_JNIEnvPKti",
);
if (newString2) {
Interceptor.attach(newString2, {
onEnter(args) {
const ptr = args[1];
const len = args[2].toInt32();
if (ptr.isNull() || len < 10) return;
const s = ptr.readUtf16String(len);
if (s && (s.includes("-") || s.length > 20)) {
console.log("[JNI UTF16] " + s);
}
},
});
console.log("[+] ART hook installed");
return;
}
console.log("[-] ART not available, fallback to libc");
const strlen = Module.findExportByName(null, "strlen");
if (!strlen) return;
Interceptor.attach(strlen, {
onEnter(args) {
const p = args[0];
if (p.isNull()) return;
try {
const s = p.readUtf8String();
if (
s &&
s.length > 20 &&
(s.includes("-") || /^[A-Za-z0-9+/=]+$/.test(s))
) {
// console.log("[C STRING] " + s);
}
} catch (e) {}
},
});
console.log("[+] libc strlen hook installed");
}
function hookFollyToString() {
const toStringPtr = Module.findExportByName(
TARGET_LIB,
"_ZNK5folly7dynamic8toStringEv",
);
if (toStringPtr) {
Interceptor.attach(toStringPtr, {
onExit(retval) {
const s = retval.readUtf8String();
if (s) {
console.log("[FOLLY STRING] " + s);
}
},
});
console.log("[+] Folly dynamic::toString hook installed");
}
}
function hookJSICalls() {
const exports = Module.enumerateExports(TARGET_LIB);
exports.forEach((exp) => {
if (
exp.name.includes("callNativeModules") ||
exp.name.includes("invokeNativeMethod")
) {
// console.log("[+] Hooking JSI Call: " + exp.name);
Interceptor.attach(exp.address, {
onEnter(args) {
// console.log("\n[JSI Call]");
// console.log(hexdump(args[2], { length: 128, header: true }));
},
});
}
});
}
function hookNativeBridgeInternal() {
const exports = Module.enumerateExports(TARGET_LIB);
let callFunctionPtr = null;
for (const exp of exports) {
if (
exp.name.includes("NativeToJsBridge") &&
exp.name.includes("callFunction")
) {
callFunctionPtr = exp.address;
// console.log(
// "[+] Found NativeToJsBridge::callFunction at: " + exp.address,
// );
Interceptor.attach(callFunctionPtr, {
onEnter(args) {
try {
const moduleName = args[1].readUtf8String();
const methodName = args[2].readUtf8String();
if (moduleName !== "JSTimers" && moduleName !== "RCTEventEmitter") {
// console.log(
// "\n[NATIVE BRIDGE CALL] " + moduleName + "::" + methodName,
// );
if (!args[3].isNull()) {
// console.log("Payload Hexdump:");
// console.log(
// hexdump(args[3], {
// offset: 0,
// length: 64,
// header: false,
// ansi: true,
// }),
// );
}
}
} catch (e) {}
},
});
}
if (exp.name.includes("JSCExecutor") && exp.name.includes("callFunction")) {
// console.log("[+] Found JSCExecutor::callFunction at: " + exp.address);
Interceptor.attach(exp.address, {
onEnter(args) {
// console.log("\n[JSC EXECUTE] Method called from Native");
},
});
}
}
}
function hookJavaNetworking() {
if (javaHooked) return;
javaHooked = true;
Java.perform(function () {
try {
const NetworkingModule = Java.use(
"com.facebook.react.modules.network.NetworkingModule",
);
const CatalystInstanceImpl = Java.use(
"com.facebook.react.bridge.CatalystInstanceImpl",
);
CatalystInstanceImpl.jniCallJSFunction.implementation = function (
module,
method,
nativeArray,
) {
let argsData = "";
if (nativeArray) {
try {
argsData = nativeArray.toString();
} catch (e) {
argsData = "[Complex Object]";
}
}
if (module !== "JSTimers" && module !== "RCTEventEmitter") {
// console.log("\n[BRIDGE CALL] " + module + "::" + method);
// console.log("Payload: " + argsData);
}
return this.jniCallJSFunction(module, method, nativeArray);
};
NetworkingModule.sendRequest.overloads.forEach((o) => {
o.implementation = function () {
const method = arguments[0];
const url = arguments[1];
const headers = arguments[3];
console.log("\n========================================");
console.log("[NETWORK] " + method + " " + url);
if (headers) {
if (typeof headers === "object" && !headers.$className) {
console.log("[HEADERS JS OBJECT]");
Object.keys(headers).forEach(function (k) {
console.log("[HEADER] " + k + ": " + headers[k]);
});
} else if (
headers.$className &&
headers.$className.indexOf("ReadableArray") !== -1
) {
const size = headers.size();
for (let i = 0; i < size; i++) {
const entry = headers.getArray(i);
if (entry && entry.size() >= 2) {
console.log(
"[HEADER] " +
entry.getString(0) +
": " +
entry.getString(1),
);
}
}
} else if (
headers.$className &&
headers.$className.indexOf("ReadableMap") !== -1
) {
const iter = headers.keySetIterator();
while (iter.hasNextKey()) {
const k = iter.nextKey();
console.log("[HEADER] " + k + ": " + headers.getString(k));
}
} else {
const size = headers.size();
for (let i = 0; i < size; i++) {
const pair = headers.getArray(i);
if (!pair) continue;
const k = pair.getString(0);
const v = pair.getString(1);
console.log("[HEADER] " + k + ": " + v);
}
}
}
return o.apply(this, arguments);
};
});
console.log("[+] Java NetworkingModule hooks installed");
} catch (e) {
console.log("[-] Java hook failed: " + e);
}
});
}
function hookLinker() {
const linkerName = Process.arch === "arm64" ? "linker64" : "linker";
const linkerExports = [
Module.findExportByName(linkerName, "do_dlopen"),
Module.findExportByName(
null,
"__dl__Z9do_dlopenPKciPK17android_dlextinfoPKv",
),
Module.findExportByName(null, "android_dlopen_ext"),
Module.findExportByName(null, "dlopen"),
].filter(Boolean);
const uniqueExports = [...new Set(linkerExports)];
uniqueExports.forEach((fn) => {
Interceptor.attach(fn, {
onEnter(args) {
if (args[0].isNull()) return;
const path = args[0].readUtf8String();
if (!path || !path.includes(TARGET_LIB)) return;
console.log("[+] Target library loading: " + path);
// hookJNIStrings();
// hookFollyToString();
// hookJSICalls();
//hookHermes();
// hookCxxReact();
// hookHermesCheck();
// hookHermesStrings();
Java.perform(() => {
// hookJavaNetworking();
//hookNativeBridgeInternal();
});
},
});
});
}
setImmediate(hookLinker);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment