# -*- coding: utf-8 -*- """Compare: Xposed-only vs Frida-stealth-spawn.""" import subprocess import time import frida ADB = r"C:\Users\Administrator\AppData\Local\Android\Sdk\platform-tools\adb.exe" PKG = "my.com.tngdigital.ewallet" STEALTH = r""" "use strict"; function log(m){ send("[stealth] "+m); } function findExport(mod, name) { try { var m = Process.getModuleByName(mod); var a = m.findExportByName(name); if (a) return a; } catch (e) {} try { return Module.getGlobalExportByName(name); } catch (e2) { return null; } } // Rename frida threads try { var pthread_setname = findExport("libc.so", "pthread_setname_np"); // also patch existing: best-effort via Java later } catch (e) {} // Hide maps var markers = ["frida","gadget","linjector","gum-js","gmain","pool-frida","hluda"]; var tracked = {}; function hideLine(line) { var l = (line||"").toLowerCase(); for (var i=0;i=0) return true; return false; } function filterBuf(buf, len) { try { var t = buf.readUtf8String(len); if (!t) return len; var out = t.split("\n").filter(function(x){return !hideLine(x);}).join("\n"); var b = Memory.allocUtf8String(out); var n = Math.min(len, out.length); Memory.copy(buf, b, n); return n; } catch (e) { return len; } } var openat = findExport("libc.so","openat"); var readFn = findExport("libc.so","read"); if (openat) { Interceptor.attach(openat, { onEnter: function(args){ this.path = args[1].isNull()?null:args[1].readCString(); }, onLeave: function(retval){ var fd=retval.toInt32(); if (fd>=0 && this.path && (this.path.indexOf("maps")>=0 || this.path.indexOf("status")>=0 || this.path.indexOf("task")>=0)) tracked[fd]=this.path; } }); } if (readFn) { Interceptor.attach(readFn, { onEnter: function(args){ this.fd=args[0].toInt32(); this.buf=args[1]; }, onLeave: function(retval){ var n=retval.toInt32(); if (n>0 && tracked[this.fd]) retval.replace(ptr(filterBuf(this.buf, n))); } }); } // Patch libc exit_group SVC var libc = Process.getModuleByName("libc.so"); libc.enumerateRanges("r-x").forEach(function(r){ for (var off=0; off+8RET "+s); } } catch(e2){} } } }); // Replace exit APIs ["_exit","exit","abort"].forEach(function(n){ var a=findExport("libc.so",n); if(!a) return; try { Interceptor.replace(a, new NativeCallback(function(c){ log("block "+n+"("+c+")"); }, "void", ["int"])); } catch(e){} }); log("stealth ready"); """ def adb(*a): return subprocess.run([ADB, *a], capture_output=True, text=True) def on_msg(m, _d): print(m.get("payload", m), flush=True) def wait_alive(sec, label): for i in range(sec): time.sleep(1) p = adb("shell", "pidof", PKG).stdout.strip() act = "" dump = adb("shell", "dumpsys", "activity", "activities").stdout for line in dump.splitlines(): if "tngdigital" in line.lower() and any( x in line for x in ("UserLogin", "SecurityError", "Splash", "topResumedActivity") ): act = line.strip()[:120] break print("%s t=%ds pid=%s act=%s" % (label, i + 1, p or "DEAD", act), flush=True) if not p: return i + 1 return sec def test_xposed_only(): print("=== Xposed-only (monkey) ===", flush=True) adb("shell", "am", "force-stop", PKG) adb("logcat", "-c") time.sleep(0.3) adb( "shell", "monkey", "-p", PKG, "-c", "android.intent.category.LAUNCHER", "1", ) alive = wait_alive(15, "XPOSED") print("xposed_alive_sec", alive, flush=True) for line in adb("logcat", "-d").stdout.splitlines(): if "TngRoot" in line and any( x in line for x in ("install", "UserLogin", "short-circuit", "finishing", "blocked intent") ): print(line[line.find("TngRoot") :][:180], flush=True) if "Displayed" in line and "tngdigital" in line: print(line.strip()[:200], flush=True) if "exited cleanly" in line: print(line.strip()[:160], flush=True) def test_frida_stealth(): print("=== Frida stealth spawn ===", flush=True) adb("shell", "am", "force-stop", PKG) time.sleep(0.3) d = frida.get_usb_device(10) pid = d.spawn([PKG]) s = d.attach(pid) sc = s.create_script(STEALTH) sc.on("message", on_msg) sc.load() d.resume(pid) alive = wait_alive(15, "FRIDA") print("frida_alive_sec", alive, flush=True) def main(): test_xposed_only() test_frida_stealth() if __name__ == "__main__": main()