首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
Android安全
发新帖
10
9
[原创]dpt-shell源码详细解析(v1.11.3)
发表于: 2024-12-10 22:35
34503
[原创]dpt-shell源码详细解析(v1.11.3)
Hur1k
2024-12-10 22:35
34503
# dpt-shell源码详细解析(v1.11.3) # 前言 dpt-shell自22年以来进行了不少的调整,本文试对v1.11.3版的dpt-shell进行源码分析,补充作者在HowItWorks中未撰写出来的部分并作积累。 简而言之,dpt-shell可以分为两个模块,一个是Processeor模块,用于对原app进行指令抽空并构建新app;另一个是shell模块,用于在app运行时回填指令,顺利执行app的代码。以下是对这两个模块的详细分析 # Processeor模块分析 入口点在src\main\java\com\luoye\dpt\Dpt.java,解析用户的运行参数后进入apk.protect()进行抽取 ```cpp private static void process(Apk apk){ if(!new File("shell-files").exists()) { LogUtils.error("Cannot find shell files!"); return; } File apkFile = new File(apk.getFilePath()); if(!apkFile.exists()){ LogUtils.error("Apk not exists!"); return; } //apk extract path String apkMainProcessPath = apk.getWorkspaceDir().getAbsolutePath(); LogUtils.info("Apk main process path: " + apkMainProcessPath); ZipUtils.unZip(apk.getFilePath(),apkMainProcessPath); String packageName = ManifestUtils.getPackageName(apkMainProcessPath + File.separator + "AndroidManifest.xml"); apk.setPackageName(packageName); // 1. 指令抽空 apk.extractDexCode(apkMainProcessPath); // 2. AMF的处理 apk.saveApplicationName(apkMainProcessPath); // 保存原始ApplicationName到assets/app_name apk.writeProxyAppName(apkMainProcessPath); // 写入代理ApplicationName if(apk.isAppComponentFactory()){ apk.saveAppComponentFactory(apkMainProcessPath); // 保存原始AppComponentFactory到assets/app_acf apk.writeProxyComponentFactoryName(apkMainProcessPath); // 写入代理AppComponentFactory } if(apk.isDebuggable()) { LogUtils.info("Make apk debuggable."); apk.setDebuggable(apkMainProcessPath, true); } apk.setExtractNativeLibs(apkMainProcessPath); apk.addJunkCodeDex(apkMainProcessPath); // 3. 压缩源dex到新的路径并删除旧的路径 apk.compressDexFiles(apkMainProcessPath); // 源dex压缩存放到assets/i11111i111.zip apk.deleteAllDexFiles(apkMainProcessPath); // 4. 合并壳dex和原dex apk.combineDexZipWithShellDex(apkMainProcessPath); // 5. 复制壳的so文件,加密so文件 apk.copyNativeLibs(apkMainProcessPath); // 复制壳的so文件 apk.encryptSoFiles(apkMainProcessPath); // 6. 构建apk apk.buildApk(apkFile.getAbsolutePath(),apkMainProcessPath, FileUtils.getExecutablePath()); File apkMainProcessFile = new File(apkMainProcessPath); if (apkMainProcessFile.exists()) { FileUtils.deleteRecurse(apkMainProcessFile); } LogUtils.info("All done."); } public void protect() { process(this); } ``` ## 1. 指令抽空 extractDexCode:调用DexUtils.extractAllMethods获取List<Instruction> ret,最后将每个方法的字节码信息写入到`assets/OoooooOooo`文件 extractAllMethods:解析Dex文件的结构体,获取directMethods,virtualMethods,调用extractMethod来进行patch extractMethod:一边用byteCode保存原字节码,一边用outRandomAccessFile.writeShort(0)写入nop 下面主要贴一下extractDexCode的源码好了 ```cpp private void extractDexCode(String apkOutDir){ List<File> dexFiles = getDexFiles(apkOutDir); Map<Integer,List<Instruction>> instructionMap = new HashMap<>(); String appNameNew = "OoooooOooo"; String dataOutputPath = getOutAssetsDir(apkOutDir).getAbsolutePath() + File.separator + appNameNew; CountDownLatch countDownLatch = new CountDownLatch(dexFiles.size()); for(File dexFile : dexFiles) { ThreadPool.getInstance().execute(() -> { final int dexNo = getDexNumber(dexFile.getName()); if(dexNo < 0){ return; } String extractedDexName = dexFile.getName().endsWith(".dex") ? dexFile.getName().replaceAll("\\.dex$", "_extracted.dat") : "_extracted.dat"; File extractedDexFile = new File(dexFile.getParent(), extractedDexName); List<Instruction> ret = DexUtils.extractAllMethods(dexFile, extractedDexFile, getPackageName(), isDumpCode()); instructionMap.put(dexNo,ret); File dexFileRightHashes = new File(dexFile.getParent(),FileUtils.getNewFileSuffix(dexFile.getName(),"dat")); DexUtils.writeHashes(extractedDexFile,dexFileRightHashes); dexFile.delete(); extractedDexFile.delete(); dexFileRightHashes.renameTo(dexFile); countDownLatch.countDown(); }); } ThreadPool.getInstance().shutdown(); try { countDownLatch.await(); } catch (Exception ignored){ } MultiDexCode multiDexCode = MultiDexCodeUtils.makeMultiDexCode(instructionMap); MultiDexCodeUtils.writeMultiDexCode(dataOutputPath,multiDexCode); } ``` ## 2. AMF的处理 主要是保存了一下源ApplicationName和AppComponentFactory,后续壳加载的时候用 此外修改了程序的AMF.xml,替换为代理ApplicationName和代理AppComponentFactory ## 3. 压缩源dex到新的路径并删除旧的路径 源dex压缩存放到assets/i11111i111.zip ## 4. 合并壳和原dex combineDexZipWithShellDex:将壳文件添加到dex,并修复size、sha1、checksum ## 5. 复制壳so,加密so文件 copyNativeLibs这里就没啥好说的,直接添加(shell-files/libs → assets/vwwwwwvwww) encryptSoFiles这块对比于初始版本是新添加的功能: - assets/vwwwwwvwww文件夹下存放的是壳的so文件,这里遍历so文件,然后对.bitcode节区进行RC4加密 - key为`ncWK&S5wbqU%IX6j`,在com\luoye\dpt\Const.java定义 ## 6. 签名构建APK zipalign,对zip进行对齐 对APK进行签名,assets/dpt.jks - signApkDebug - signApk,调用command来实现 ```java private static boolean signApk(String apkPath, String keyStorePath, String signedApkPath, String keyAlias, String storePassword, String KeyPassword) { ArrayList<String> commandList = new ArrayList<>(); commandList.add("sign"); commandList.add("--ks"); commandList.add(keyStorePath); commandList.add("--ks-key-alias"); commandList.add(keyAlias); commandList.add("--ks-pass"); commandList.add("pass:" + storePassword); commandList.add("--key-pass"); commandList.add("pass:" + KeyPassword); commandList.add("--out"); commandList.add(signedApkPath); commandList.add("--v1-signing-enabled"); commandList.add("true"); commandList.add("--v2-signing-enabled"); commandList.add("true"); commandList.add("--v3-signing-enabled"); commandList.add("true"); commandList.add(apkPath); int size = commandList.size(); String[] commandArray = new String[size]; commandArray = commandList.toArray(commandArray); try { ApkSignerTool.main(commandArray); } catch (Exception e) { e.printStackTrace(); return false; } return true; } ``` 完成! # Shell模块分析 ## **ProxyApplication-attachBaseContext重写** 这里的逻辑也会初始版本发生了一些变化,直接分析当前版本的 ```java @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); // 先调用父类的attachBaseContext Log.d(TAG,"dpt attachBaseContext classloader = " + base.getClassLoader()); realApplicationName = FileUtils.readAppName(this); if(!Global.sIsReplacedClassLoader) { ApplicationInfo applicationInfo = base.getApplicationInfo(); if(applicationInfo == null) { throw new NullPointerException("application info is null"); } FileUtils.unzipLibs(applicationInfo.sourceDir,applicationInfo.dataDir); JniBridge.loadShellLibs(applicationInfo.dataDir,applicationInfo.sourceDir); Log.d(TAG,"ProxyApplication init"); JniBridge.ia(); ClassLoader targetClassLoader = base.getClassLoader(); JniBridge.cbde(targetClassLoader); Global.sIsReplacedClassLoader = true; } } ``` JniBridge.loadShellLibs(applicationInfo.dataDir,applicationInfo.sourceDir) - 加载shell的so文件,后面一小节将会分析so的初始化流程 - System.load(fullLibPath) JniBridge.ia():init_app - 通过jni调用so文件的ia函数(动调注册init_app),路径在\main\cpp\dpt.cpp - load_apk:这里是将apk映射到内存中 - load_zip_by_mmap,打开zip后通过mmap映射到内存 - read_zip_file_entry - 读取刚刚加载的apk,然后在内存中找到assets/OoooooOooo的地址,保存指针codeItemFilePtr - readCodeItem - MultiDexCode结构体解析出来获得各个dex的指令字节, 然后添加到dexMap中,dexMap是全局变量 - extractDexesInNeeded - writeDexAchieve:找apk中的classes.dex写入到code_cache/i11111i111.zip - unloadapk ```java DPT_ENCRYPT void init_app(JNIEnv *env, jclass __unused) { DLOGD("init_app!"); clock_t start = clock(); void *apk_addr = nullptr; size_t apk_size = 0; load_apk(env,&apk_addr,&apk_size); uint64_t entry_size = 0; if(codeItemFilePtr == nullptr) { read_zip_file_entry(apk_addr,apk_size,CODE_ITEM_NAME_IN_ZIP,&codeItemFilePtr,&entry_size); } else { DLOGD("no need read codeitem from zip"); } readCodeItem((uint8_t *)codeItemFilePtr,entry_size); pthread_mutex_lock(&g_write_dexes_mutex); extractDexesInNeeded(env,apk_addr,apk_size); pthread_mutex_unlock(&g_write_dexes_mutex); unload_apk(apk_addr,apk_size); printTime("read apk data took =" , start); } ``` JniBridge.cbde:combineDexElements,动态合并新的dex - 合并targetClassLoader原来的DexElements和之前释放的code_cache/i11111i111.zip(此时dex还是空的) - 合并dexElements目的是把我们新加载的dex放到dexElements数组开头,这样ClassLoader加载类时就会优先从我们的dex中查找。 ```cpp DPT_ENCRYPT void combineDexElement(JNIEnv* env, jclass __unused, jobject targetClassLoader, const char* pathChs) { jobjectArray extraDexElements = makePathElements(env,pathChs); dalvik_system_BaseDexClassLoader targetBaseDexClassLoader(env,targetClassLoader); jobject originDexPathListObj = targetBaseDexClassLoader.getPathList(); dalvik_system_DexPathList targetDexPathList(env,originDexPathListObj); jobjectArray originDexElements = targetDexPathList.getDexElements(); jsize extraSize = env->GetArrayLength(extraDexElements); jsize originSize = env->GetArrayLength(originDexElements); dalvik_system_DexPathList::Element element(env, nullptr); jclass ElementClass = element.getClass(); jobjectArray newDexElements = env->NewObjectArray(originSize + extraSize,ElementClass, nullptr); for(int i = 0;i < originSize;i++) { jobject elementObj = env->GetObjectArrayElement(originDexElements, i); env->SetObjectArrayElement(newDexElements,i,elementObj); } for(int i = originSize;i < originSize + extraSize;i++) { jobject elementObj = env->GetObjectArrayElement(extraDexElements, i - originSize); env->SetObjectArrayElement(newDexElements,i,elementObj); } targetDexPathList.setDexElements(newDexElements); DLOGD("combineDexElement success"); } ``` ## **ProxyApplication-onCreate重写** 先调用父类的onCreate,后面主要看replaceApplication,同样发生在native层 ```cpp private void replaceApplication() { if (Global.sNeedCalledApplication && !TextUtils.isEmpty(realApplicationName)) { realApplication = (Application) JniBridge.ra(realApplicationName); Log.d(TAG, "applicationExchange: " + realApplicationName+" realApplication="+realApplication.getClass().getName()); JniBridge.craa(getApplicationContext(), realApplicationName); JniBridge.craoc(realApplicationName); Global.sNeedCalledApplication = false; } } ``` JniBridge.ra:replaceApplication,主要实例化了一个application,执行replaceApplicationOnLoadedApk和replaceApplicationOnActivityThread来替换这个实例 - replaceApplicationOnLoadedApk - 获取原始程序的Application类并进行替换,替换涉及两处地方: - `ActivityThread`(负责管理应用程序的生命周期和组件加载)中BoundApplication的appBindData(这里还是在为`LoadedApk`的makeapplication做准备,而不是替换`ActivityThread`的初始实例) - `LoadedApk`(APK文件在内存中的表示)中的ApplicationInfo - 最后调用`loadedApk.makeApplication(JNI_FALSE,nullptr)` 来初始化真实程序的application ```cpp DPT_ENCRYPT void replaceApplicationOnLoadedApk(JNIEnv *env, jclass __unused,jobject realApplication) { android_app_ActivityThread activityThread(env); jobject mBoundApplicationObj = activityThread.getBoundApplication(); // 获取 BoundApplication 对象 android_app_ActivityThread::AppBindData appBindData(env,mBoundApplicationObj); jobject loadedApkObj = appBindData.getInfo(); android_app_LoadedApk loadedApk(env,loadedApkObj); // LoadedApk对象是APK文件在内存中的表示 //make it null loadedApk.setApplication(nullptr); // 以便可以替换为新的 Application 对象。 jobject mAllApplicationsObj = activityThread.getAllApplication(); java_util_ArrayList arrayList(env,mAllApplicationsObj); jobject removed = (jobject)arrayList.remove(0); // 移除原来的 Application 对象。 if(removed != nullptr){ DLOGD("replaceApplicationOnLoadedApk proxy application removed"); } jobject ApplicationInfoObj = loadedApk.getApplicationInfo(); // 获取 ApplicationInfo 对象。 android_content_pm_ApplicationInfo applicationInfo(env,ApplicationInfoObj); char applicationName[128] = {0}; getClassName(env,realApplication,applicationName, ARRAY_LENGTH(applicationName)); // 获取真实 Application 对象的类名 DLOGD("applicationName = %s",applicationName); char realApplicationNameChs[128] = {0}; parseClassName(applicationName,realApplicationNameChs); // 前面获取了类名了,现在解析类 jstring realApplicationName = env->NewStringUTF(realApplicationNameChs); auto realApplicationNameGlobal = (jstring)env->NewGlobalRef(realApplicationName); android_content_pm_ApplicationInfo appInfo(env,appBindData.getAppInfo()); //replace class name 替换类名 applicationInfo.setClassName(realApplicationNameGlobal); appInfo.setClassName(realApplicationNameGlobal); DLOGD("replaceApplicationOnLoadedApk begin makeApplication!"); // call make application loadedApk.makeApplication(JNI_FALSE,nullptr); DLOGD("replaceApplicationOnLoadedApk success!"); } ``` - replaceApplicationOnActivityThread - 替换`ActivityThread` 的初始 `Application` 实例为 `realApplication` ```cpp DPT_ENCRYPT void replaceApplicationOnActivityThread(JNIEnv *env,jclass __unused, jobject realApplication){ android_app_ActivityThread activityThread(env); activityThread.setInitialApplication(realApplication); DLOGD("replaceApplicationOnActivityThread success"); } ``` - 这里做个区分: - 修改 `AppBindData` 中的 `ApplicationInfo` 类名,是为了保证 `LoadedApk` 在将来某一时刻需要重新实例化 `Application` 时,能够使用新的 `Application` 类。 - 调用 `setInitialApplication` 是为了立即替换 `ActivityThread` 的 `Application` 引用,保证当前应用主线程能够立即使用新的 `Application` 实例。 JniBridge.craa:callRealApplicationAttach - 正常走原始程序的Application的attach(反射调用) JniBridge.craoc:callRealApplicationOnCreate - 正常走原始程序的Application的onCreate(反射调用) ## so的初始化 壳的so文件在init_array中优先调用 ```cpp //dpt.h INIT_ARRAY_SECTION void init_dpt(); // dpt.cpp void init_dpt() { decrypt_bitcode(); DLOGI("init_dpt call!"); dpt_hook(); createAntiRiskProcess(); } ``` decrypt_bitcode - 前面提到现在so文件开启了加密保护,下述JniBridge的一些关键函数都是定义在.bitcode节区的,因此是进行了RC4加密的,而它们在so文件加载的init_array时进行RC4的解密,密钥存到了.data节区中 dpt_hook - 函数路径位于src\main\cpp\dpt_hook.cpp - hook libc.so的execve 字符串子串匹配dex2oat,禁用dex2oat ```cpp DPT_ENCRYPT int fake_execve(const char *pathname, char *const argv[], char *const envp[]) { BYTEHOOK_STACK_SCOPE(); DLOGW("execve hooked: %s", pathname); if (strstr(pathname, "dex2oat") != nullptr) { DLOGW("execve blocked: %s", pathname); errno = EACCES; return -1; } return BYTEHOOK_CALL_PREV(fake_execve, pathname, argv, envp); } ``` - hook libc.so的mmap 添加写权限,以便后续修改dex ```cpp DPT_ENCRYPT void* fake_mmap(void* __addr, size_t __size, int __prot, int __flags, int __fd, off_t __offset){ BYTEHOOK_STACK_SCOPE(); int prot = __prot; int hasRead = (__prot & PROT_READ) == PROT_READ; int hasWrite = (__prot & PROT_WRITE) == PROT_WRITE; char fd_path[256] = {0}; dpt_readlink(__fd,fd_path, ARRAY_LENGTH(fd_path)); if(strstr(fd_path,"webview.vdex") != nullptr) { DLOGW("fake_mmap link path: %s, no need to change prot",fd_path); goto tail; } if(hasRead && !hasWrite) { prot = prot | PROT_WRITE; DLOGD("fake_mmap call fd = %d,size = %zu, prot = %d,flag = %d",__fd,__size, prot,__flags); } if(g_sdkLevel == 30){ if(strstr(fd_path,"base.vdex") != nullptr){ DLOGE("fake_mmap want to mmap base.vdex"); __flags = 0; } } tail: void *addr = BYTEHOOK_CALL_PREV(fake_mmap,__addr, __size, prot, __flags, __fd, __offset); return addr; } ``` - hook DefineClass - 这里通过DobbyHook库来对java层的代码进行hook - classloader在加载类的时候会调用defineClass,根据sdk版本选择合适的hook函数,如 ```cpp DPT_ENCRYPT void *DefineClassV21(void* thiz, const char* descriptor, void* class_loader, const void* dex_file, const void* dex_class_def) { if(LIKELY(g_originDefineClassV21 != nullptr)) { patchClass(descriptor,dex_file,dex_class_def); return g_originDefineClassV21( thiz,descriptor,class_loader, dex_file, dex_class_def); } return nullptr; } ``` - patchClass**(指令回填的核心函数)** - 解析dex,构建结构体,主要是dex::ClassDataMethod directMethods[direct_methods_size]和dex::ClassDataMethod virtualMethods[virtual_methods_size] - 然后调用patchMethod来对每个方法进行具体的回填 - patchMethod - 通过传入的参数dexIndex来找到映射到内存空间的dex,然后通过mprotect增加读写权限 - 获取原始字节码的偏移,然后通过memcpy直接回填 ```cpp DPT_ENCRYPT void patchMethod(uint8_t *begin,__unused const char *location,uint32_t dexSize,int dexIndex,uint32_t methodIdx,uint32_t codeOff){ if(codeOff == 0){ // 代码偏移为0,不需要patch NLOG("[*] patchMethod dex: %d methodIndex: %d no need patch!",dexIndex,methodIdx); return; } auto *dexCodeItem = (dex::CodeItem *) (begin + codeOff); // 原始的dex codeItem的偏移 uint16_t firstDvmCode = *((uint16_t*)dexCodeItem->insns_); if(firstDvmCode != 0x0012 && firstDvmCode != 0x0016 && firstDvmCode != 0x000e){ NLOG("[*] this method has code no need to patch"); return; } auto dexIt = dexMap.find(dexIndex); if (LIKELY(dexIt != dexMap.end())) { auto dexMemIt = dexMemMap.find(dexIndex); if(UNLIKELY(dexMemIt == dexMemMap.end())){ change_dex_protective(begin,dexSize,dexIndex); } auto codeItemMap = dexIt->second; auto codeItemIt = codeItemMap->find(methodIdx); if (LIKELY(codeItemIt != codeItemMap->end())) { data::CodeItem* codeItem = codeItemIt->second; auto *realCodeItemPtr = (uint8_t *)(dexCodeItem->insns_); NLOG("[*] patchMethod codeItem patch, methodIndex = %d,insnsSize = %d >>> %p(0x%x)", codeItem->getMethodIdx(), codeItem->getInsnsSize(), realCodeItemPtr, (unsigned int)(realCodeItemPtr - begin)); memcpy(realCodeItemPtr,codeItem->getInsns(),codeItem->getInsnsSize()); } else{ NLOG("[*] patchMethod cannot find methodId: %d in codeitem map, dex index: %d(%s)",methodIdx,dexIndex,location); } } else{ DLOGW("[*] patchMethod cannot find dex: '%s' in dex map",location); } } ``` createAntiRiskProcess ```cpp DPT_ENCRYPT void createAntiRiskProcess() { pid_t child = fork(); if(child < 0) { DLOGW("%s fork fail!", __FUNCTION__); detectFrida(); } else if(child == 0) { DLOGD("%s in child process", __FUNCTION__); detectFrida(); doPtrace(); } else { DLOGD("%s in main process, child pid: %d", __FUNCTION__, child); protectChildProcess(child); detectFrida(); } } ``` - 这里进行了两种反调试的操作,一个是检测Frida,另一个是常规的PTRACE_TRACEME,无论是子进程还是父进程都会有反调检测 - detectFrida - 创建反调试线程,具体有下面几种检测方法 - 读取进程的内存映射/proc/%d/maps,直接查`frida-agent`字符串 - 读取进程的线程/proc/%d/task,查字符串`pool-frida、gmain、gdbus、gum-js-loop`,如果线程匹配超过2个就crash - **`pool-frida`**:管理 Frida 内部的线程池,用于处理多线程任务和通信。 - **`gmain`**:GLib 主循环,处理事件循环和 Frida 的核心事件。 - **`gdbus`**:DBus 线程,处理与系统服务或其他进程的 DBus 消息通信。 - **`gum-js-loop`**:JavaScript 主循环,执行 Frida 注入的 JavaScript 代码和 hook 函数。 ```cpp [[noreturn]] void *detectFridaOnThread(__unused void *args) { while (true) { int frida_so_count = find_in_maps(1,"frida-agent"); if(frida_so_count > 0) { DLOGD("detectFridaOnThread found frida so"); crash(); } int frida_thread_count = find_in_threads_list(4 ,"pool-frida" ,"gmain" ,"gdbus" ,"gum-js-loop"); if(frida_thread_count >= 2) { DLOGD("detectFridaOnThread found frida threads"); crash(); } sleep(10); } } ``` - doPtrace ```cpp void doPtrace() { __unused int ret = sys_ptrace(PTRACE_TRACEME,0,0,0); DLOGD("doPtrace result: %d",ret); } ``` ## **ProxyComponentFactory重写** 在系统需要创建组件实例时按需调用,通过代理的方式控制组件的实例化,优先使用目标 `AppComponentFactory` 创建组件,创建失败再用默认的 # 其他 ## hook时机的调整 回看22年的帖子,当时作者使用LoadMethod作为hook和指令回填的目标,现在已经换成DefineClass,原因在HowItWorks.md也有解释 > ClassDef这个结构还有一个特点,它是dex文件的结构,也就是说dex文件格式不变,它一般就不会变。还有,DefineClass函数的参数会改变吗?目前来看从Android M到现在没有变过。所以使用它不用太担心随着Android版本的升级而导致字段偏移的变化,也就是兼容性较强。这就是为什么用DefineClass作为Hook点。 > > > > dpt之前就是使用的LoadMethod函数作为Hook点,在LoadMethod函数里面做CodeItem填充操作。但是后来发现,LoadMethod函数参数不太固定,随着Android版本的升级可能要不断适配,而且每个函数都要填充,会影响一定的性能。 > # 总结 ## processor模块 1. 从原app的各个dex,每个dex的method中抽取字节码存入`OoooooOooo`文件中,并用nop填充 2. 修改入口 3. 合并shell的dex(shell-files/dex/classes.dex)与填充nop后的dex 4. 复制壳的so文件,并对.bitcode节区进行rc4加密 5. 最终签名并构建apk ## Shell模块 ### **ProxyApplication-attachBaseContext重写** 1. jniBridge.ia初始化一些结构体和变量以便后续用 2. 加载壳so并初始化 1. 解密.bitcode节区 2. 设置hook 1. 禁用dex2oat 2. 给dex添加写权限 3. **调用defineClass时回填指令(关键参数是dex_file和dex_class_def)** 3. 设置反调试 3. 将现在的apk内的classes.dex缓存到code_cache/i11111i111.zip,然后动态添加到现在的classloader内 ### **ProxyApplication-onCreate重写** 1. 替换当前的application为原始程序的application 2. 执行原始程序application的初始化流程,如attach、onCreate 3. 完成原始app的运行 # Refs <a href="elink@980K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3f1#2x3Y4m8G2K9X3W2W2i4K6u0W2j5$3&6Q4x3V1k6@1K9s2u0W2j5h3c8Q4x3X3b7I4y4e0M7$3x3U0b7#2i4K6u0V1x3g2)9J5k6o6q4Q4x3X3g2Z5N6r3#2D9">分享一个自己做的函数抽取壳 - 吾爱破解 - 52pojie.cn</a> <a href="elink@6a4K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6D9N6h3!0&6k6i4y4A6M7h3W2#2i4K6u0r3k6s2m8@1i4K6u0V1M7$3S2W2L8r3H3`."><mark class="encrypted">932K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6D9N6h3!0&6k6i4y4A6M7h3W2#2i4K6u0r3k6s2m8@1i4K6u0V1M7$3S2W2L8r3H3`.</mark></a>
登录后可查看完整内容
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
#混淆加固
#源码框架
收藏
・
10
点赞
・
9
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
npc0vo
为你点赞!
2025-12-21 04:19
git_79017Vana5656
你的分享对大家帮助很大,非常感谢!
2025-7-27 05:11
PLEBFE
为你点赞!
2025-6-28 03:27
Yangser
非常支持你的观点!
2024-12-22 19:30
逆天而行
为你点赞!
2024-12-21 11:52
Shangwendada
+5
感谢你的贡献,论坛因你而更加精彩!
2024-12-12 10:28
mb_wpitiize
+1
期待更多优质内容的分享,论坛有你更精彩!
2024-12-11 16:57
vmoveq
非常支持你的观点!
2024-12-11 10:02
你瞒我瞒
感谢你分享这么好的资源!
2024-12-11 09:16
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
1
)
wx_LJ_438
雪 币:
0
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
1
粉丝
0
关注
私信
wx_LJ_438
2
楼
帮助很大,目前最新的还有检测
1天前
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
Hur1k
2
发帖
6
回帖
10
RANK
关注
私信
他的文章
[分享] unidbg 反检测内存地址截断排错记录
8357
[原创]dpt-shell源码详细解析(v1.11.3)
34501
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部