Frida-Dexdump脱壳
用于提取DEX文件
https://github.com/hluwa/frida-dexdump
需要先绕过frida反调试
Fart脱壳
一款自动化脱壳工具
https://github.com/hanbinglengyue/FART?tab=readme-ov-file
同样需要先绕过frida反调试
绕过frida反调试
实践一:Hook pthread_create绕过反调试
首先确认哪个库文件创建了检测线程,Patch 所有调用 pthread_create 函数的caller或者自建pthread_create来绕过检测
通过hook dlopen查看哪个.so文件在检测hook,运行
var dlopen_ext = Module.findExportByName(null, "android_dlopen_ext"); if (dlopen_ext) { Interceptor.attach(dlopen_ext, { onEnter: function (args) { var pathptr = args[0]; if (pathptr !== undefined && pathptr != null) { var path = ptr(pathptr).readCString(); console.log("load " + path); } } }); } else { console.log("android_dlopen_ext not found!"); }
hook到 android_dlopen_ext,但libexecmain.so执行后进程退出,可能是libexecmain.so中创建了一个线程检测到了Frida使其退出
确认是否由libexecmain创建的检测线程
function hook_pthread_create(){ Interceptor.attach(Module.findExportByName(null, "pthread_create"), { onEnter: function (args) { var module = Process.findModuleByAddress(ptr(this.returnAddress)) if (module != null) { console.log("[pthread_create] called from", module.name) } else { console.log("[pthread_create] called from", ptr(this.returnAddress)) } }, } ) } hook_pthread_create()
只有libexec.so创建进程,而libexecmain.so从未出现,而libexec.so是上一个调用的,可能就是由libexec.so创建的检测线程
尝试进行patch hook能不能过掉检测
function patchPthreadCreate(){ let pthread_create = Module.findExportByName(null, "pthread_create") let org_pthread_create = new NativeFunction(pthread_create, "int", ["pointer", "pointer", "pointer", "pointer"]) let my_pthread_create = new NativeCallback(function (a, b, c, d) { let m = Process.getModuleByName("libexec.so"); let base = m.base console.log(Process.getModuleByAddress(c).name) if (Process.getModuleByAddress(c).name == m.name) { console.log("pthread_create") return 0; } return org_pthread_create(a, b, c, d) }, "int", ["pointer", "pointer", "pointer", "pointer"]) Interceptor.replace(pthread_create, my_pthread_create) } patchPthreadCreate()
未找到 libexec.so 模块,libexec.so 被嵌入在 APK 内
function patchPthreadCreate() { let pthread_create = Module.findExportByName(null, "pthread_create"); if (!pthread_create) { console.log("Error: Unable to find pthread_create!"); return; } let org_pthread_create = new NativeFunction(pthread_create, "int", ["pointer", "pointer", "pointer", "pointer"]); let my_pthread_create = new NativeCallback(function (a, b, c, d) { let moduleFound = false; let modulePath = ""; let modules = Process.enumerateModules(); for (let m of modules) { if (m.name.includes("libexec.so")) { // 兼容不同路径的 libexec.so console.log("Found libexec.so at: " + m.path); moduleFound = true; modulePath = m.path; break; } } if (!moduleFound) { console.log("Error: libexec.so not found in the loaded modules!"); return org_pthread_create(a, b, c, d); } let moduleName = Process.getModuleByAddress(c)?.name; console.log("pthread_create called from: " + moduleName); if (moduleName && moduleName.includes("libexec.so")) { console.log("Blocking pthread_create from libexec.so"); return 0; } return org_pthread_create(a, b, c, d); }, "int", ["pointer", "pointer", "pointer", "pointer"]); Interceptor.replace(pthread_create, my_pthread_create); console.log("Successfully patched pthread_create!"); } patchPthreadCreate();
先是报错未找到 libexec.so 模块,后又存在该模块,那么反调试进程可能存在延迟加载
Patch 所有调用 pthread_create 函数的caller不太行,可能是检测了pthread_create 是否被hook,那么就需要自己实现一个 pthread_create 函数,并让应用调用,避开对 pthread_create 的完整性检查
通过拦截 pthread_create 特定偏移量的线程执行,替换特定偏移量到自建的 pthread_create 函数,来防止在hook时检测机制生效,从而绕过检测
Hook pthread_create获取属于libexec.so线程函数的偏移量
function hook_pthread() { var pthread_create_addr = Module.findExportByName(null, 'pthread_create'); console.log("pthread_create_addr,", pthread_create_addr); var pthread_create = new NativeFunction(pthread_create_addr, "int", ["pointer", "pointer", "pointer", "pointer"]); Interceptor.replace(pthread_create_addr, new NativeCallback(function (parg0, parg1, parg2, parg3) { var so_name = Process.findModuleByAddress(parg2).name; var so_path = Process.findModuleByAddress(parg2).path; var so_base = Module.getBaseAddress(so_name); var offset = parg2 - so_base; console.log("so_name", so_name, "offset", offset, "path", so_path, "parg2", parg2); var PC = 0; if ((so_name.indexOf("libexec.so") > -1)) { console.log("find thread func offset", so_name, offset); if ((1 === offset)) { console.log("anti bypass"); } else if (1 === offset) { console.log("anti bypass"); } else if (1 === offset) { console.log("anti bypass"); } else if (1 === offset) { console.log("anti bypass"); } else if (1 === offset) { console.log("anti bypass"); } else if (1 === offset) { console.log("anti bypass"); } else if (1 === offset) { console.log("anti bypass"); } else { PC = pthread_create(parg0, parg1, parg2, parg3); console.log("ordinary sequence", PC) } } else { PC = pthread_create(parg0, parg1, parg2, parg3); // console.log("ordinary sequence", PC) } return PC; }, "int", ["pointer", "pointer", "pointer", "pointer"])) } hook_pthread();
自建pthread_create,将代码中的判断语句中(1 === offset)的1全部替换成获取到的属于libexec.so线程函数的偏移量
但仅仅获取了一个libexec.so偏移量就中断了
由于 libexec.so 的加载具有延迟性,就需要实时监视 libexec.so 是否被动态加载,再运行 pthread_create 钩子
function enumerateModules() { console.log("Enumerating modules..."); const modules = Process.enumerateModules(); modules.forEach(m => { if (m.name.includes("libexec.so")) { console.log(`Found module: ${m.name} at ${m.base} - ${m.path}`); } }); return modules.some(m => m.name === "libexec.so"); } function hook_dlopen() { const dlopen = Module.findExportByName(null, "dlopen") || Module.findExportByName(null, "__loader_dlopen"); if (!dlopen) { console.log("Failed to find dlopen"); return; } Interceptor.attach(dlopen, { onEnter(args) { const path = args[0].readUtf8String(); console.log("dlopen called for:", path); if (path && path.includes("libexec.so")) { this.isLibexec = true; } }, onLeave(retval) { if (this.isLibexec && retval) { console.log("libexec.so loaded at:", retval); setupPthreadHook(); } } }); } function setupPthreadHook() { const pthread_create_addr = Module.findExportByName(null, 'pthread_create'); if (!pthread_create_addr) { console.log("Failed to find pthread_create"); return; } console.log("pthread_create_addr:", pthread_create_addr); const pthread_create = new NativeFunction(pthread_create_addr, "int", ["pointer", "pointer", "pointer", "pointer"]); const targetOffset = 274972; Interceptor.replace(pthread_create_addr, new NativeCallback( function(parg0, parg1, parg2, parg3) { try { const module = Process.findModuleByAddress(parg2) || {name: "unknown", base: 0}; const so_name = module.name; const offset = module.base ? ptr(parg2).sub(module.base) : 0; console.log("so_name:", so_name, "offset:", offset); if (so_name === "libexec.so") { console.log("Found libexec.so thread func offset:", offset); if (offset.equals(targetOffset)) { console.log("Anti-detection triggered - bypassing"); return 0; } } return pthread_create(parg0, parg1, parg2, parg3); } catch (e) { console.log("Pthread hook error:", e); return pthread_create(parg0, parg1, parg2, parg3); } }, "int", ["pointer", "pointer", "pointer", "pointer"])); console.log("Pthread hook installed successfully"); } function main() { console.log("Initializing..."); // First check if libexec.so is already loaded if (enumerateModules()) { console.log("libexec.so already loaded, setting up hook..."); setupPthreadHook(); } else { console.log("libexec.so not found initially, hooking dlopen..."); hook_dlopen(); // Fallback: keep checking periodically let attempts = 0; const maxAttempts = 50; const interval = setInterval(() => { if (enumerateModules()) { clearInterval(interval); setupPthreadHook(); } else if (attempts >= maxAttempts) { clearInterval(interval); console.log("Gave up waiting for libexec.so"); } attempts++; }, 100); } } try { main(); } catch (e) { console.log("Main execution error:", e); }
绕过libexec.so检测后,在另一个 dlopen 调用(libsotweak.so)后又中断,可能存在多层反调试机制
实践二:阻止反调试库加载
阻止反调试 .so 的加载绕过检测,hook android_dlopen_ext,监控库加载,如果通过.so文件检测 hook,可以阻止其加载,绕过安全检查,尝试不可行
参考:https://www.cnblogs.com/dxmao/articles/17678351.html
分析出创建检测线程的so文件,hook pthread_create 的偏移量为空函数来绕过检测,尝试不可行
参考:https://iiong.com/reinforce-android-applications-for-unpacking-learning/#%E5%B0%9D%E8%AF%95%E8%84%B1%E5%A3%B3
Hook mprotect 提取所有内存数据
由于反调试检测线程的libexec.bin库文件在程序启动后加载,具有延迟性,而mprotect又是在程序启动时就加载,所以可以在反调试生效前,hook mprotect提取加载时执行的所有代码
拦截 mprotect 调用,检查内存权限更改,并强制将某些内存区域设置为 rwx,然后尝试进行内存转储,提取所有执行的代码,其中包括了解密后的dex文件
var mprotect = Module.findExportByName(null, "mprotect"); if (mprotect) { Interceptor.attach(mprotect, { onEnter: function (args) { this.addr = ptr(args[0].toString()); this.size = args[1].toInt32(); this.prot = args[2].toInt32(); if (this.prot === 1 || this.prot === 3 || this.prot === 7) { console.log(`mprotect called: ${this.addr.toString(16)}, size: ${this.size}, prot: ${this.prot}`); } }, onLeave: function (retval) { if (retval.toInt32() === 0) { Memory.protect(this.addr, this.size, "rwx"); console.log(`Memory protection patched at ${this.addr.toString(16)}, size: ${this.size}`); var header = Memory.readByteArray(this.addr, Math.min(8, this.size)); var headerBytes = new Uint8Array(header); var headerHex = Array.from(headerBytes).map(b => b.toString(16).padStart(2, '0')).join(' '); console.log(`Header check: ${headerHex}`); if (this.size > 4096) { console.log(`Dumping memory region at ${this.addr.toString(16)}`); var data = Memory.readByteArray(this.addr, this.size); var fileName = `/data/data/com.oceanwing.battery.cam/dump_${this.addr.toString(16)}_${this.size}.bin`; try { var file = new File(fileName, "wb"); file.write(data); file.close(); console.log(`Saved to ${fileName}`); } catch (e) { console.error(`Failed to save: ${e.message}`); console.log("Dumping first 256 bytes:"); console.log(hexdump(this.addr, { length: Math.min(this.size, 256) })); } } } } }); }
同样从内存提取数据并绕过保护,但没有frida-dexdump针对性的提取dex文件,该脚本 Dump 了所有被 mprotect 标记为可执行的代码段,其中可能包括 Dex 代码、so 库代码等,然后手动逐一分析提取的 bin 文件
解析bin文件,手动提取dex文件,反编译分析dex文件,发现不完整
frida联合gdb动调,结合Frida的Hook,实时获取报错问题
adb shell ps -A | grep com.oceanwing.battery.cam # 获取 sPID gdbserver64 :1234 --attach <pid> adb forward tcp:1234 tcp:1234 gdb target remote :1234
最终分析:
该脚本使用 Frida Hook 关键系统函数和 Java 方法,以绕过反调试机制、监控动态库加载、拦截进程退出、篡改时间检测、提取 DEX 代码并保持进程存活
在JNI_OnLoad中,原生代码解密这些数据,并通过DexClassLoader动态加载到内存中,所以它可能是应用加载额外DEX文件的触发点,hook它可能会获取到关键的DEX解密数据,但目前程序并未执行到JNI_OnLoad函数就中断了
提取出的解密数据并不是主要业务代码,同时也提出一堆加密的内存数据
通过Frida Hook和内存提取,成功绕过了部分反调试机制,提取了部分DEX文件,但无济于事,针对新版爱加密加固,还是需要虚拟机脱壳
console.log("Script loaded immediately!"); setImmediate(function () { console.log("Starting advanced anti-anti-debugging..."); // Hook mprotect(标记可读内存) var mprotect = Module.findExportByName(null, "mprotect"); if (mprotect) { Interceptor.attach(mprotect, { onEnter: function (args) { this.addr = ptr(args[0].toString()); this.size = args[1].toInt32(); this.prot = args[2].toInt32(); if (this.prot === 1 || this.prot === 3 || this.prot === 7) { console.log(`mprotect called: ${this.addr.toString(16)}, size: ${this.size}, prot: ${this.prot}`); } }, onLeave: function (retval) { if (retval.toInt32() === 0 && (this.prot & 0x4)) { console.log(`Memory protection succeeded at ${this.addr.toString(16)}, size: ${this.size}`); this.safeToRead = true; } } }); } // Hook dlopen 检测 libexec.so 和 libexecmain.so var dlopen = Module.findExportByName(null, "dlopen"); if (dlopen) { Interceptor.attach(dlopen, { onEnter: function (args) { this.path = args[0].readCString(); console.warn(`dlopen detected for ${this.path}`); if (this.path && this.path.includes("libsotweak.so")) { console.warn("Allowing libsotweak.so load, hooking its functions..."); } }, onLeave: function (retval) { if (retval.toInt32() > 0) { if (this.path && (this.path.includes("libexec.so") || this.path.includes("libexecmain.so"))) { console.log(`${this.path} loaded, initiating memory scan...`); scanMemory(); } if (this.path && this.path.includes("libsotweak.so")) { console.log("Scanning memory after libsotweak.so load..."); scanMemory(); } } } }); } // Hook pthread_create 增加扫描 var pthread_create = Module.findExportByName(null, "pthread_create"); if (pthread_create) { Interceptor.attach(pthread_create, { onEnter: function (args) { console.warn("pthread_create detected"); this.threadFunc = args[2]; var module = Process.findModuleByAddress(this.threadFunc) || { name: "unknown", base: 0 }; var offset = module.base ? ptr(this.threadFunc).sub(module.base) : ptr(0); console.log(`pthread_create - module: ${module.name}, offset: ${offset}`); if (module.name === "libexec.so" && offset.toInt32() === 0x4321c) { console.log("Anti-detection thread detected - bypassing"); this.bypass = true; scanMemory(); } }, onLeave: function (retval) { if (this.bypass) { console.log("Bypassing pthread_create"); retval.replace(-1); } else if (retval.toInt32() === 0) { console.log("pthread_create succeeded"); } } }); } // Hook dlsym 防止触发 core 的陷阱并动态 hook JNI_OnLoad var dlsym = Module.findExportByName(null, "dlsym"); if (dlsym) { Interceptor.attach(dlsym, { onEnter: function (args) { this.symbol = args[1].readCString(); console.warn(`dlsym detected for ${this.symbol}`); if (this.symbol === "ptrace" || this.symbol === "gettimeofday" || this.symbol === "clock_gettime" || this.symbol === "core") { console.log(`Blocking dlsym for ${this.symbol} to avoid trap`); this.bypass = true; } }, onLeave: function (retval) { if (this.bypass) { console.log(`Bypassing dlsym for ${this.symbol}`); retval.replace(ptr(0)); } // 动态 hook JNI_OnLoad if (this.symbol === "JNI_OnLoad" && retval.isNull() === false) { console.log(`Attempting to hook JNI_OnLoad at ${retval.toString(16)}`); try { Interceptor.attach(retval, { onEnter: function (args) { console.log("JNI_OnLoad called with args:"); console.log(` vm: ${args[0]}`); console.log(` reserved: ${args[1]}`); scanMemory(); // 扫描内存 }, onLeave: function (retval) { console.log(`JNI_OnLoad returned: ${retval}`); scanMemory(); // 再次扫描 } }); } catch (e) { console.error(`Failed to hook JNI_OnLoad: ${e.message}`); } } } }); } // 定时扫描内存 setInterval(function () { console.log("Periodic memory scan..."); scanMemory(); }, 500); // 延迟 Java 层 hook setTimeout(function () { Java.perform(function () { console.log("Initializing Java hooks..."); var AppComponentFactoryC0012A = Java.use("p000s.p001h.p002e.p003l.p004l.AppComponentFactoryC0012A"); if (AppComponentFactoryC0012A) { AppComponentFactoryC0012A["m43al"].implementation = function (classLoader, applicationInfo, packageName, orignAppName) { console.log("Java m43al called with:"); console.log(` ClassLoader: ${classLoader}`); console.log(` ApplicationInfo: ${applicationInfo}`); console.log(` PackageName: ${packageName}`); console.log(` OrignAppName: ${orignAppName}`); var result = this["m43al"](classLoader, applicationInfo, packageName, orignAppName); console.log(`Java m43al returned ClassLoader: ${result}`); scanMemory(); return result; }; } else { console.error("Failed to find AppComponentFactoryC0012A"); } Java.use("java.lang.ClassLoader").loadClass.overload('java.lang.String', 'boolean').implementation = function (className, resolve) { console.log(`ClassLoader.loadClass called for: ${className}`); var result = this.loadClass(className, resolve); return result; }; }); }, 1000); // Hook sys_exit 和 sys_exit_group var syscall = Module.findExportByName(null, "syscall"); if (syscall) { Interceptor.attach(syscall, { onEnter: function (args) { var nr = args[0].toInt32(); if (nr === 93 || nr === 94) { console.warn(`syscall detected: ${nr === 93 ? "sys_exit" : "sys_exit_group"}`); this.bypass = true; } }, onLeave: function (retval) { if (this.bypass) { console.log("Bypassing sys_exit/sys_exit_group"); retval.replace(0); } } }); } // Hook kill 防止信号终止 var kill = Module.findExportByName(null, "kill"); if (kill) { Interceptor.attach(kill, { onEnter: function (args) { console.warn(`kill detected! PID: ${args[0].toInt32()}, Signal: ${args[1].toInt32()}`); args[1] = ptr(0); // 禁用信号 } }); } // Hook raise 防止信号终止 var raise = Module.findExportByName(null, "raise"); if (raise) { Interceptor.attach(raise, { onEnter: function (args) { console.warn(`raise detected! Signal: ${args[0].toInt32()}`); args[0] = ptr(0); // 禁用信号 } }); } // Hook pthread_join 防止线程同步退出 var pthread_join = Module.findExportByName(null, "pthread_join"); if (pthread_join) { Interceptor.attach(pthread_join, { onEnter: function (args) { console.warn(`pthread_join detected! Thread: ${args[0].toString(16)}`); this.bypass = true; }, onLeave: function (retval) { if (this.bypass) { console.log("Bypassing pthread_join"); retval.replace(0); } } }); } // Hook sched_yield 防止调度检测 var sched_yield = Module.findExportByName(null, "sched_yield"); if (sched_yield) { Interceptor.attach(sched_yield, { onEnter: function () { console.warn("sched_yield detected"); this.bypass = true; }, onLeave: function (retval) { if (this.bypass) { console.log("Bypassing sched_yield"); retval.replace(0); } } }); } // Hook sigaction 防止信号处理 var sigaction = Module.findExportByName(null, "sigaction"); if (sigaction) { Interceptor.attach(sigaction, { onEnter: function (args) { console.warn(`sigaction called with signal: ${args[0].toInt32()}`); args[1] = ptr(0); // 禁用信号处理 } }); } var prctl = Module.findExportByName(null, "prctl"); if (prctl) { Interceptor.attach(prctl, { onEnter: function (args) { console.warn(`prctl detected (bypassing)! Option: ${args[0].toInt32()}`); this.skip = true; }, onLeave: function (retval) { if (this.skip) { retval.replace(0); } } }); } var ptrace = Module.findExportByName(null, "ptrace"); if (ptrace) { Interceptor.attach(ptrace, { onEnter: function (args) { console.warn(`ptrace detected! Request: ${args[0].toInt32()}`); this.bypass = true; }, onLeave: function (retval) { if (this.bypass) { console.log("Bypassing ptrace"); retval.replace(-1); } } }); } var fopen = Module.findExportByName(null, "fopen"); if (fopen) { Interceptor.attach(fopen, { onEnter: function (args) { var path = args[0].readCString(); if (path && (path.includes("/proc") 혹은 path.includes("/sys") || path.includes("frida"))) { console.warn(`fopen detected for ${path}, redirecting to /dev/null`); args[0] = Memory.allocUtf8String("/dev/null"); } } }); } var gettimeofday = Module.findExportByName(null, "gettimeofday"); if (gettimeofday) { Interceptor.attach(gettimeofday, { onEnter: function (args) { console.warn("gettimeofday detected"); this.tv = args[0]; }, onLeave: function (retval) { if (retval.toInt32() === 0 && this.tv) { var baseTime = Date.now() / 1000; var currentTime = Math.floor(baseTime + (Date.now() - baseTime * 1000) / 1000); Memory.writeLong(this.tv, currentTime); Memory.writeLong(this.tv.add(8), 0); } } }); } var clock_gettime = Module.findExportByName(null, "clock_gettime"); if (clock_gettime) { Interceptor.attach(clock_gettime, { onEnter: function (args) { console.warn("clock_gettime detected"); this.ts = args[1]; }, onLeave: function (retval) { if (retval.toInt32() === 0 && this.ts) { var baseTime = Date.now() / 1000; var currentTime = Math.floor(baseTime + (Date.now() - baseTime * 1000) / 1000); Memory.writeLong(this.ts, currentTime); Memory.writeLong(this.ts.add(8), 0); } } }); } var exit = Module.findExportByName(null, "exit"); if (exit) { Interceptor.attach(exit, { onEnter: function (args) { console.warn(`exit detected with status: ${args[0].toInt32()}`); this.bypass = true; }, onLeave: function (retval) { if (this.bypass) { console.log("Preventing exit"); throw new Error("Blocked exit"); } } }); } var _exit = Module.findExportByName(null, "_exit"); if (_exit) { Interceptor.attach(_exit, { onEnter: function (args) { console.warn(`_exit detected with status: ${args[0].toInt32()}`); this.bypass = true; }, onLeave: function (retval) { if (this.bypass) { console.log("Preventing _exit"); throw new Error("Blocked _exit"); } } }); } var abort = Module.findExportByName(null, "abort"); if (abort) { Interceptor.attach(abort, { onEnter: function () { console.warn("abort detected"); this.bypass = true; }, onLeave: function (retval) { if (this.bypass) { console.log("Preventing abort"); throw new Error("Blocked abort"); } } }); } // Exception handler with stack trace Process.setExceptionHandler(function (details) { console.log("Exception caught:", JSON.stringify(details, null, 2)); console.log("Stack:", Thread.backtrace(details.context, Backtracer.ACCURATE) .map(DebugSymbol.fromAddress).join("\n")); return true; // 继续运行 }); // Keep process alive setInterval(function () { console.log("Keeping process alive..."); }, 5000); console.log("Advanced anti-anti-debugging setup complete!"); }); // 内存扫描函数 function scanMemory() { console.log("Scanning memory..."); Process.enumerateRanges('r-x').forEach(range => { if (range.size > 1024 * 100) { try { var header = Memory.readByteArray(range.base, 8); var headerBytes = new Uint8Array(header); var headerHex = Array.from(headerBytes).map(b => b.toString(16).padStart(2, '0')).join(' '); var dexMagic = "64 65 78 0A 30 33 35 00"; // dex\n035\0 var isDex = headerHex === dexMagic; if (isDex) { console.log(`DEX detected at ${range.base.toString(16)}, size: ${range.size}`); var fileName = `/data/data/com.oceanwing.battery.cam/dump_${range.base.toString(16)}_unencrypted.dex`; var data = Memory.readByteArray(range.base, Math.min(range.size, 1024 * 1024)); var file = new File(fileName, "wb"); file.write(data); file.close(); console.log(`Dumped unencrypted DEX to ${fileName}`); } else if (range.size > 4096) { console.log(`Potential encrypted data at ${range.base.toString(16)}, size: ${range.size}`); var fileName = `/data/data/com.oceanwing.battery.cam/damp_${range.base.toString(16)}_encrypted.bin`; var data = Memory.readByteArray(range.base, Math.min(range.size, 1024 * 1024)); var file = new File(fileName, "wb"); file.write(data); file.close(); console.log(`Dumped encrypted data to ${fileName}`); } } catch (e) { console.error(`Memory scan error at ${range.base.toString(16)}: ${e.message}`); } } }); }
4A评测 - 免责申明
本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。
不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。
本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。
如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!
程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。
侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)