1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
| /*
Hook fork to prevent child processes from interrupting Frida
Returns -1 with errno EPERM
*/
(() => {
const forkSymbol = Module.findGlobalExportByName("fork");
if (!forkSymbol) {
console.warn("[-] fork() not found");
return;
}
const errnoPtr = (() => {
const errnoLocation = Module.findGlobalExportByName("__errno_location");
return errnoLocation ? new NativeFunction(errnoLocation, "pointer", [])() : null;
})();
const safeForkHandler = new NativeCallback(() => {
console.warn("[!] Fork intercepted - returning -1 (EPERM)");
if (errnoPtr) errnoPtr.writeS32(1);
return -1;
}, 'int', []);
Interceptor.replace(forkSymbol, safeForkHandler);
console.warn("[+] Fork hook: ACTIVE");
})();
/* 要改你的应用包名!!! */
const TARGET_PKG = "com.*****tv.*****tv";
const SAFE_DIR = `/data/data/${TARGET_PKG}/`;
const DETECTION_LIBRARIES = [
{ pattern: "libdexprotector", message: "DexProtector: https://licelus.com" },
{ pattern: "libjiagu", message: "Jiagu360: https://jiagu.360.cn" },
{ pattern: "libAppGuard", message: "AppGuard: http://appguard.nprotect.com" },
{ pattern: "libDexHelper", message: "Secneo: http://www.secneo.com" },
{ pattern: "libsecexe|libsecmain|libSecShell", message: "Bangcle: https://github.com/woxihuannisja/Bangcle" },
{ pattern: "libprotectt|libapp-protectt", message: "Protectt: https://www.protectt.ai" },
{ pattern: "libkonyjsvm", message: "Kony: http://www.kony.com/" },
{ pattern: "libnesec", message: "Yidun: https://dun.163.com/product/app-protect" },
{ pattern: "libcovault", message: "AppSealing: https://www.appsealing.com/" },
{ pattern: "libpairipcore", message: "Pairip: https://github.com/rednaga/APKiD/issues/329" }
];
function hookDlopen() {
return new Promise((resolve, reject) => {
try {
const isArm = Process.arch === "arm" ? "linker" : "linker64";
const reg = Process.arch === "arm" ? "r0" : "x0";
const linker = Process.findModuleByName(isArm);
if (!linker) {
reject(new Error("Linker module not found"));
return;
}
let resolved = false;
const resolveOnce = () => {
if (!resolved) {
resolved = true;
resolve();
}
};
const sym = linker.enumerateExports().find(e => e.name.includes('android_dlopen_ext'));
Interceptor.attach(sym.address, {
onEnter(args) {
const libPath = this.context[reg].readUtf8String();
if (!libPath) return;
for (const { pattern, message } of DETECTION_LIBRARIES) {
if (new RegExp(pattern).test(libPath)) {
console.warn(`\n[*] Packer Detected: ${message}`);
resolveOnce();
return;
}
}
}
});
setTimeout(resolveOnce, 3000);
} catch (e) {
reject(new Error("Unsupported architecture/emulator"));
}
});
}
function processDex(Buf, C, Path) {
// Ensure the buffer is valid
if (!Buf || Buf.byteLength < 8) {
console.error(`[!] Invalid buffer for classes${C - 1}.dex`);
return;
}
const DumpDex = Buf instanceof Uint8Array ? Buf : new Uint8Array(Buf);
const Count = C - 1;
// Signatures for detecting CDEX, Empty Header, and Wiped Header
const CDEX_SIGNATURE = [0x63, 0x64, 0x65, 0x78, 0x30, 0x30, 0x31];
const EMPTY_HEADER = [0x00, 0x00, 0x00, 0x00];
const WIPED_HEADER = [0x64];
// Detect CDEX
if (CDEX_SIGNATURE.every((val, i) => DumpDex[i] == val)) {
console.warn(`[*] classes${Count}.dex is a Compact Dex (CDEX). Ignoring.`);
return;
}
// Detect Empty Header (DexProtector)
if (EMPTY_HEADER.every((val, i) => DumpDex[i] == val) && DumpDex[7] == 0x00) {
console.warn(`[*] 00000 Header detected in classes${Count}.dex, possible DexProtector.`);
writeDexFile(Count, Buf, Path, 0);
return;
}
// Detect Wiped Header (Obfuscation/Tampered)
if (DumpDex[0] == 0x00 || WIPED_HEADER.every((val, i) => DumpDex[i] != val)) {
console.warn(`[*] Wiped Header detected, classes${Count}.dex might be interesting.`);
writeDexFile(Count, Buf, Path, 0);
return;
}
// Default: Consider it as a normal Dex file
writeDexFile(Count, Buf, Path, 1);
}
function writeDexFile(count, buffer, path, isValid) {
try {
const file = new File(path, "wb");
file.write(buffer);
file.close();
console.log(`[Dex${count}] Saved to: ${path} ${isValid ? '(valid)' : '(modified)'}`);
} catch (error) {
console.error(`[!] Failed to save Dex${count} to ${path}: ${error.message}`);
}
}
function findDefineClass(libart) {
const matcher = /ClassLinker.*DefineClass.*Thread.*DexFile/;
const search = (items, type) => items.find(item => matcher.test(item.name))?.address;
return search(libart.enumerateSymbols(), 'symbols') ||
search(libart.enumerateImports(), 'imports') ||
search(libart.enumerateExports(), 'exports');
}
function dumpDex() {
const libart = Process.findModuleByName("libart.so");
if (!libart) return console.error("[!] libart.so not found");
const defineClassAddr = findDefineClass(libart);
console.warn("[*] DefineClass found at : ", defineClassAddr);
if (!defineClassAddr) return console.error("[!] DefineClass not found");
const seenDex = new Set();
let dexCount = 1;
Interceptor.attach(defineClassAddr, {
onEnter(args) {
const dexFilePtr = args[5];
const base = dexFilePtr.add(Process.pointerSize).readPointer();
const size = dexFilePtr.add(Process.pointerSize * 2).readUInt();
if (seenDex.has(base.toString())) return;
seenDex.add(base.toString());
const dexBuffer = base.readByteArray(size);
if (!dexBuffer || dexBuffer.byteLength !== size) return;
const path = `${SAFE_DIR}classes${dexCount}.dex`;
processDex(dexBuffer, dexCount++, path);
}
});
}
async function main() {
try {
await hookDlopen();
console.warn("[*] Hooking Finished. Starting dex dump...");
dumpDex();
} catch (e) {
console.error(`[!] Error: ${e.message}`);
}
}
setImmediate(main);
|