"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)");