TngRootBypassHook 增强 captcha 诊断、TigerTally/JNIC 分层与 HWUI 策略;新增逆向脚本、Frida 工具与 UI dump;同步 MariBank SG hook 与 tng_exit_guard 更新。
215 lines
6.3 KiB
Python
215 lines
6.3 KiB
Python
# -*- 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()
|