Last active
January 5, 2026 23:01
-
-
Save sharpicx/ea13b18d1de737c9af6cfd68e8ad4156 to your computer and use it in GitHub Desktop.
react native
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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