# -*- coding: utf-8 -*- """Resolve libc _exit real target and patch its SVC.""" import subprocess import time import frida ADB = r"C:\Users\Administrator\AppData\Local\Android\Sdk\platform-tools\adb.exe" PKG = "my.com.tngdigital.ewallet" SCRIPT = r""" "use strict"; function log(m){ send("[TNG-native] "+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; } } function resolveTrampoline(addr, name) { // Follow simple ADRP+ADD+BR / LDR+BR patterns a few times var cur = addr; for (var depth = 0; depth < 5; depth++) { var w0 = cur.readU32(); var w1 = cur.add(4).readU32(); // BR Xn: 0xD61F0000 | (Rn<<5) if ((w1 & 0xfffffc1f) === 0xd61f0000) { var rn = (w1 >> 5) & 0x1f; // LDR Xn, [PC, #imm] : 0x58000000 if ((w0 & 0xff000000) === 0x58000000) { var imm19 = (w0 >> 5) & 0x7ffff; if (imm19 & 0x40000) imm19 -= 0x80000; var targetPtr = cur.add(imm19 * 4); var target = targetPtr.readPointer(); log(name + " trampoline LDR+BR -> " + target); cur = target; continue; } // ADRP Xn, page if ((w0 & 0x9f000000) === 0x90000000) { var rd = w0 & 0x1f; var immhi = (w0 >> 5) & 0x7ffff; var immlo = (w0 >> 29) & 0x3; var imm = ((immhi << 2) | immlo) << 12; if (imm & 0x100000000) imm = imm - 0x200000000; var page = cur.and(ptr("0xfffffffffffff000")).add(imm); // next might be LDR/ADD var w2 = cur.add(8).readU32(); log(name + " ADRP page="+page+" rn="+rn+" rd="+rd+" w2="+w2.toString(16)); } log(name + " BR X" + rn + " at " + cur + " (stop follow)"); break; } break; } return cur; } function patchSvcNear(addr, name, windowSize) { var nop = [0x1f, 0x20, 0x03, 0xd5]; var n = 0; for (var i = 0; i < windowSize; i += 4) { try { var p = addr.add(i); if (p.readU32() !== 0xd4000001) continue; Memory.protect(p, 4, "rwx"); // replace svc with: mov x0, x0; ret — or just nop and hope // Better: movz x0, #0; ret so "exit" becomes return 0 // movz x0,#0 = 0xD2800000; ret = 0xD65F03C0 p.writeU32(0xd2800000); // movz x0, #0 if (i + 4 < windowSize) { var p2 = addr.add(i + 4); // only overwrite next if also svc/brk or nop pad — safer: write ret at svc place only via branch } // Just NOP the svc — caller may hang; use ret instead by overwriting svc with ret p.writeU32(0xd65f03c0); // RET n++; log("patched SVC->RET @ " + p + " (" + name + "+" + i + ")"); } catch (e) { log("patch fail: " + e); } } return n; } function hookByPatchingLibcExit() { var libc = Process.getModuleByName("libc.so"); // Scan entire libc for the classic exit_group sequence: // mov x8, #94; mov x0, ...; svc #0 OR svc inside _exit impl var count = 0; var svcAddrs = []; // Focused: exports that must lead to exit ["_exit", "exit"].forEach(function (n) { var a = libc.findExportByName(n); if (!a) return; log(n + " export " + a); // DebugSymbol / Instruction parse: find first BL/B to real impl }); // Brute: scan libc executable for movz x8,#94 followed within 16 bytes by svc var ranges = libc.enumerateRanges("r-x"); ranges.forEach(function (r) { for (var off = 0; off + 8 < r.size; off += 4) { var p = r.base.add(off); var w; try { w = p.readU32(); } catch (e) { return; } // movz x8, #94 = 0xD2800BC8 ; movz w8,#94 = 0x52800BC8 // movz x8, #93 = 0xD2800BA8 if (w !== 0xd2800bc8 && w !== 0x52800bc8 && w !== 0xd2800ba8 && w !== 0x52800ba8) continue; // look ahead for svc for (var j = 4; j <= 24; j += 4) { try { if (p.add(j).readU32() === 0xd4000001) { svcAddrs.push(p.add(j)); log("exit-seq movz@ " + p + " svc@ " + p.add(j)); } } catch (e2) {} } } }); svcAddrs.forEach(function (svc) { try { Memory.protect(svc, 4, "rwx"); // Replace svc with ret — turns exit into function return svc.writeU32(0xd65f03c0); count++; log("SVC->RET " + svc); } catch (e) { log("SVC patch fail " + svc + ": " + e); } }); log("libc exit SVC patches=" + count); } function installEntryLog() { ["_exit", "exit", "abort"].forEach(function (n) { var a = findExport("libc.so", n); if (!a) return; try { Interceptor.attach(a, { onEnter: function (args) { log("ENTER " + n + "(" + args[0] + ")"); } }); } catch (e) {} }); } // Also Interceptor.replace as backup function installReplace() { ["_exit", "exit", "abort"].forEach(function (n) { var a = findExport("libc.so", n); if (!a) return; try { Interceptor.replace(a, new NativeCallback(function (code) { log("REPLACED-HIT " + n + "(" + (code|0) + ")"); }, "void", ["int"])); log("replaced " + n); } catch (e) { log("replace " + n + " fail: " + e); } }); } log("pid=" + Process.id); hookByPatchingLibcExit(); installReplace(); installEntryLog(); log("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 main(): adb("shell", "am", "force-stop", PKG) time.sleep(0.4) device = frida.get_usb_device(10) pid = device.spawn([PKG]) session = device.attach(pid) script = session.create_script(SCRIPT) script.on("message", on_msg) script.load() print("resume", pid, flush=True) device.resume(pid) for i in range(25): time.sleep(1) p = adb("shell", "pidof", PKG).stdout.strip() print("t=%ds %s" % (i + 1, p or "DEAD"), flush=True) if not p: break else: print("STABLE", flush=True) focus = adb("shell", "dumpsys", "activity", "activities").stdout for line in focus.splitlines(): if "tngdigital" in line.lower() and any( x in line for x in ("UserLogin", "SecurityError", "Splash", "mResumed", "topResumed") ): print("ACT", line.strip()[:200], flush=True) if __name__ == "__main__": main()