chore: 备份 TNG 注册/captcha 逆向与 MariBank SG bypass 进展
TngRootBypassHook 增强 captcha 诊断、TigerTally/JNIC 分层与 HWUI 策略;新增逆向脚本、Frida 工具与 UI dump;同步 MariBank SG hook 与 tng_exit_guard 更新。
This commit is contained in:
214
reverse/frida/run_tng_patch_libc_svc.py
Normal file
214
reverse/frida/run_tng_patch_libc_svc.py
Normal file
@@ -0,0 +1,214 @@
|
||||
# -*- 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()
|
||||
Reference in New Issue
Block a user