# -*- 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()
