TngRootBypassHook 增强 captcha 诊断、TigerTally/JNIC 分层与 HWUI 策略;新增逆向脚本、Frida 工具与 UI dump;同步 MariBank SG hook 与 tng_exit_guard 更新。
148 lines
5.5 KiB
JavaScript
148 lines
5.5 KiB
JavaScript
"use strict";
|
|
/*
|
|
* TNG eWallet — 定位 TigerTally(libtiger_tally.so) 启动期 fread 阻塞。观察不改行为。
|
|
*
|
|
* 背景(ANR 栈): CaptchaInitializer → TigerTallyAPI.init → t.B.genericNt1(native)
|
|
* → libtiger_tally.so (mNYjyzyN23) → fread → __sread → read 永远读不到数据
|
|
*/
|
|
const TIGER_SO = "libtiger_tally.so";
|
|
|
|
const LIBC = Process.getModuleByName("libc.so");
|
|
const readlink = new NativeFunction(
|
|
LIBC.findExportByName("readlink"), "long", ["pointer", "pointer", "ulong"]);
|
|
|
|
/* ---- Tiger 模块范围(热路径缓存,每 2s 刷新一次) ---- */
|
|
let tigerMod = null;
|
|
function refreshTiger() {
|
|
const m = Process.findModuleByName(TIGER_SO);
|
|
if (m) tigerMod = m;
|
|
return !!tigerMod;
|
|
}
|
|
function inTiger(addr) {
|
|
if (!addr) return false;
|
|
if (!tigerMod) return false;
|
|
return addr.compare(tigerMod.base) >= 0 && addr.compare(tigerMod.base.add(tigerMod.size)) < 0;
|
|
}
|
|
setInterval(() => { refreshTiger(); }, 2000);
|
|
|
|
function resolveFd(fd) {
|
|
try {
|
|
const link = Memory.allocUtf8String(`/proc/self/fd/${fd}`);
|
|
const out = Memory.alloc(256);
|
|
const n = readlink(link, out, 256);
|
|
if (n > 0) return out.readUtf8String(Math.min(n, 255));
|
|
} catch (e) { /* ignore */ }
|
|
return "?";
|
|
}
|
|
function fdKind(fd) {
|
|
const p = resolveFd(fd);
|
|
if (p.indexOf("socket:") === 0) return "SOCKET " + p;
|
|
if (p.indexOf("pipe:") === 0) return "PIPE " + p;
|
|
if (p.indexOf("anon_inode:") === 0) return "ANON " + p;
|
|
return p;
|
|
}
|
|
function threadName() {
|
|
try { return Process.getCurrentThreadName(); } catch (e) { return "?"; }
|
|
}
|
|
function fmtAddr(a) { return a ? a.toString(16) : "?"; }
|
|
|
|
const stats = {}; // tid -> info
|
|
function bump(fd, kind, ret) {
|
|
const tid = Process.getCurrentThreadId();
|
|
let s = stats[tid];
|
|
if (!s) { s = { name: threadName(), reads: 0, lastFd: fd, lastFdKind: kind, lastRet: ret }; stats[tid] = s; }
|
|
s.name = threadName();
|
|
s.reads++;
|
|
s.lastFd = fd;
|
|
s.lastFdKind = kind;
|
|
s.lastRet = ret;
|
|
}
|
|
|
|
/* ---- fread: FILE* 第4参数; bionic __sFILE._file 偏移约 18 ---- */
|
|
Interceptor.attach(LIBC.findExportByName("fread"), {
|
|
onEnter(args) {
|
|
const caller = this.returnAddress;
|
|
if (!inTiger(caller)) return;
|
|
const fp = args[3];
|
|
let fd = -1;
|
|
for (const off of [18, 16, 24, 20]) {
|
|
try { const v = fp.add(off).readU16(); if (v > 0 && v < 4096) { fd = v; break; } }
|
|
catch (e) { /* try next */ }
|
|
}
|
|
const kind = fd > 0 ? fdKind(fd) : "?";
|
|
bump(fd, kind, "pending");
|
|
console.log(`[FREAD] tid=${Process.getCurrentThreadId()} "${threadName()}" caller=${fmtAddr(caller)} fd=${fd} kind=${kind}`);
|
|
},
|
|
onLeave(ret) {
|
|
if (!inTiger(this.returnAddress)) return;
|
|
bump(-1, "", ret.toInt32());
|
|
console.log(`[FREAD-LEAVE] tid=${Process.getCurrentThreadId()} ret=${ret.toInt32()}`);
|
|
}
|
|
});
|
|
|
|
/* ---- read: 只观察调用者位于 libtiger_tally.so 的 ---- */
|
|
Interceptor.attach(LIBC.findExportByName("read"), {
|
|
onEnter(args) {
|
|
const caller = this.returnAddress;
|
|
if (!inTiger(caller)) return;
|
|
const fd = args[0].toInt32();
|
|
const kind = fdKind(fd);
|
|
bump(fd, kind, "?");
|
|
console.log(`[READ] tid=${Process.getCurrentThreadId()} "${threadName()}" caller=${fmtAddr(caller)} fd=${fd} kind=${kind}`);
|
|
},
|
|
onLeave(ret) {
|
|
if (!inTiger(this.returnAddress)) return;
|
|
const tid = Process.getCurrentThreadId();
|
|
const s = stats[tid];
|
|
const fd = s ? s.lastFd : -1;
|
|
const r = ret.toInt32();
|
|
if (s) s.lastRet = r;
|
|
if (r < 0) console.log(`[READ-RET] tid=${tid} fd=${fd} ret=${r} (blocked/error)`);
|
|
else if (r === 0) console.log(`[READ-EOF] tid=${tid} fd=${fd} ret=0 (EOF/closed)`);
|
|
else console.log(`[READ-RET] tid=${tid} fd=${fd} ret=${r}`);
|
|
}
|
|
});
|
|
|
|
/* ---- 周期 dump ---- */
|
|
function dumpThreads() {
|
|
try {
|
|
const dir = new File("/proc/self/task", "r");
|
|
const entries = dir.list();
|
|
dir.close();
|
|
let relevant = [];
|
|
for (const e of entries) {
|
|
let nm = "?";
|
|
try { const nf = new File(`/proc/self/task/${e}/comm`, "r"); nm = nf.readString().trim(); nf.close(); } catch (err) {}
|
|
const s = stats[e] || null;
|
|
const lower = nm.toLowerCase();
|
|
if (lower.indexOf("location") >= 0 || lower.indexOf("tally") >= 0 || s) {
|
|
let info = `tid=${e} "${nm}"`;
|
|
if (s) info += ` tigerReads=${s.reads} lastFd=${s.lastFd} kind=${s.lastFdKind} lastRet=${s.lastRet}`;
|
|
relevant.push(info);
|
|
}
|
|
}
|
|
console.log(`[DUMP] tiger-fread threads: ${relevant.length ? relevant.join(" | ") : "(none)"}`);
|
|
} catch (e) {
|
|
console.log(`[DUMP] failed: ${e}`);
|
|
}
|
|
}
|
|
|
|
setInterval(() => {
|
|
dumpThreads();
|
|
try {
|
|
const dir = new File("/proc/self/fd", "r");
|
|
const fds = dir.list();
|
|
dir.close();
|
|
let pipes = [], socks = [];
|
|
for (const f of fds) {
|
|
const kind = fdKind(parseInt(f, 10));
|
|
if (kind.indexOf("PIPE") === 0) pipes.push(f + ":" + kind.split(" ").slice(1).join(" "));
|
|
if (kind.indexOf("SOCKET") === 0) socks.push(f + ":" + kind.split(" ").slice(1).join(" "));
|
|
}
|
|
if (pipes.length) console.log(`[DUMP-FD] pipes: ${pipes.join(" | ")}`);
|
|
if (socks.length) console.log(`[DUMP-FD] sockets: ${socks.join(" | ")}`);
|
|
} catch (e) { /* ignore */ }
|
|
}, 3000);
|
|
|
|
console.log("[TIGER-FREAD] armed (observe-only)");
|