{"id":21697653,"url":"https://github.com/jitcor/youpk8","last_synced_at":"2025-07-18T09:30:59.402Z","repository":{"id":65639921,"uuid":"561182690","full_name":"jitcor/Youpk8","owner":"jitcor","description":"移植Youpk到Pixel2XL上","archived":false,"fork":false,"pushed_at":"2022-11-08T05:05:26.000Z","size":934,"stargazers_count":68,"open_issues_count":0,"forks_count":20,"subscribers_count":5,"default_branch":"main","last_synced_at":"2023-12-12T12:32:19.258Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jitcor.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-11-03T06:05:56.000Z","updated_at":"2023-12-09T16:03:05.000Z","dependencies_parsed_at":"2023-02-02T01:31:31.663Z","dependency_job_id":null,"html_url":"https://github.com/jitcor/Youpk8","commit_stats":null,"previous_names":["jitcor/youpk8"],"tags_count":null,"template":null,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jitcor%2FYoupk8","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jitcor%2FYoupk8/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jitcor%2FYoupk8/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jitcor%2FYoupk8/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jitcor","download_url":"https://codeload.github.com/jitcor/Youpk8/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":226388164,"owners_count":17617235,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-25T19:28:55.730Z","updated_at":"2024-11-25T19:28:56.528Z","avatar_url":"https://github.com/jitcor.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Youpk8\n  `Youpk`是一种通过修改系统源码实现主动调用进行脱壳的工具，基于`android-7.1.2_r33`分支做的定制，仅支持`Pixel`机型，但该机型问题太多了，买了两个，一个时不时无限重启，一个充不进去电，正好身边有一部`Pixel 2 XL`用着还不错，并且`Pixel 2 XL`支持的最初始分支`android-8.0.0_r21`与`android-7.1.2_r33`分支挺近的，代码差别应该不是很大，就想把`Youpk`移植到`Pixel 2 XL`，正好也学习下`Youpk`的工作原理。 \n\n  在此特感谢`Youpk`工具作者的开源精神，能搞出来肯定需要对越来越庞大的android源码有充分的了解。因此，我这个移植也会依照`Youpk`的格式开源。以及感谢在移植过程中，在网上搜索到的各种填坑文章，以及Google开源的Android源码，还有各种在线源码搜索服务，还有各种编译系统以及填坑的文章，以及现在越来越快的科学速度，以及各种涉及到的效率工具。 \n\n  具体原理就不用讲了，参考Youpk即可，其实我也不太懂，我就只管移植完事即可,哈哈。  \n  \n  很少研究这方面，若是有遗漏地方大佬勿喷，欢迎提出来哈\n# 与Youpk不同的地方\n- `CompilerFilter::kVerifyAtRuntime`换成了`CompilerFilter::kVerify`  \n- `mirror::ClassLoader*`换成了`ObjPtr\u003cmirror::ClassLoader\u003e`  \n- `StringPrintf`换成了`android::base::StringPrintf`  \n- `ReaderMutexLock mu(self, *class_linker-\u003eDexLock());`换成了`ReaderMutexLock mu(self, *Locks::dex_lock_);`  \n- `ClassLinker`里好几个函数返回值变成了`ObjPtr\u003cT\u003e`格式  \n- `PrettyMethod(method)`换成了`method-\u003ePrettyMethod()`  \n- `NATIVE_METHOD`和`REGISTER_NATIVE_METHODS`位置发生了改变  \n# 参考\n- [Android6.0上frameworks增加代码编译错误](https://www.jianshu.com/p/634d71e31a9d)  \n- [安卓脱壳速成](https://chinggg.github.io/post/fart/)  \n\n# 最后\n附上开源地址:[Humenger/Youpk8](https://github.com/Humenger/Youpk8)  \n\n\n# 小工具\n### 快捷导出更改过的文件\n```python\n# 需要放在out目录下执行\nimport os\nimport re\nimport sys\nimport zipfile\nimport shutil\n\nos.system(\"rm -rf repo_status.txt\")\nos.system(\"rm -rf Youpk4Pixel2XL.zip\")\nos.system(\"rm -rf ./Youpk4Pixel2XL/\")\nos.system(\"repo status \u003e repo_status.txt\")\nos.system(\"echo project \u003e\u003e repo_status.txt\")\nstatus=open(\"repo_status.txt\",\"rb\").read().decode()\nfor match in re.findall(\"project (.*?) branch Youpk4Pixel2XL([\\s\\S]*?)\\n(?=project)\",status):\n    if match[1]:\n        for sub in match[1].splitlines():\n            if sub:\n                relate_path=match[0].strip()+re.findall(\"[^\\t\\n]+\", sub)[1]\n                changed_file=os.path.abspath(\"../\"+relate_path)\n                target_file=os.path.abspath(\"./Youpk4Pixel2XL/\"+relate_path)\n                print(changed_file)\n                os.makedirs(os.path.dirname(target_file),exist_ok=True)\n                shutil.copyfile(changed_file,target_file)\nprint(\"sucess\")\n\n\n\n\n```\n\n---  \n---  \n---  \n\n# Youpk\n又一款基于ART的主动调用的脱壳机\n\n\n\n## 原理\n\nYoupk是一款针对Dex整体加固+各式各样的Dex抽取的脱壳机\n\n基本流程如下:\n\n1. 从内存中dump DEX\n2. 构造完整调用链, 主动调用所有方法并dump CodeItem\n3. 合并 DEX, CodeItem\n\n### 从内存中dump DEX\n\nDEX文件在art虚拟机中使用DexFile对象表示, 而ClassLinker中引用了这些对象, 因此可以采用从ClassLinker中遍历DexFile对象并dump的方式来获取.\n\n```c++\n//unpacker.cc\nstd::list\u003cconst DexFile*\u003e Unpacker::getDexFiles() {\n  std::list\u003cconst DexFile*\u003e dex_files;\n  Thread* const self = Thread::Current();\n  ClassLinker* class_linker = Runtime::Current()-\u003eGetClassLinker();\n  ReaderMutexLock mu(self, *class_linker-\u003eDexLock());\n  const std::list\u003cClassLinker::DexCacheData\u003e\u0026 dex_caches = class_linker-\u003eGetDexCachesData();\n  for (auto it = dex_caches.begin(); it != dex_caches.end(); ++it) {\n    ClassLinker::DexCacheData data = *it;\n    const DexFile* dex_file = data.dex_file;\n    dex_files.push_back(dex_file);\n  }\n  return dex_files;\n}\n```\n\n另外, 为了避免dex做任何形式的优化影响dump下来的dex文件, 在dex2oat中设置 CompilerFilter 为仅验证\n\n```c++\n//dex2oat.cc\ncompiler_options_-\u003eSetCompilerFilter(CompilerFilter::kVerifyAtRuntime);\n```\n\n\n\n### 构造完整调用链, 主动调用所有方法\n\n1. 创建脱壳线程\n\n   ```java\n   //unpacker.java\n   public static void unpack() {\n       if (Unpacker.unpackerThread != null) {\n           return;\n       }\n   \n       //开启线程调用\n       Unpacker.unpackerThread = new Thread() {\n           @Override public void run() {\n               while (true) {\n                   try {\n                       Thread.sleep(UNPACK_INTERVAL);\n                   }\n                   catch (InterruptedException e) {\n                       e.printStackTrace();\n                   }\n                   if (shouldUnpack()) {\n                       Unpacker.unpackNative();\n                   }   \n               }\n           }\n       };\n       Unpacker.unpackerThread.start();\n   }\n   ```\n\n2. 在脱壳线程中遍历DexFile的所有ClassDef\n\n   ```c++\n   //unpacker.cc\n   for (; class_idx \u003c dex_file-\u003eNumClassDefs(); class_idx++) {\n   ```\n\n3. 解析并初始化Class\n\n   ```c++\n   //unpacker.cc\n   mirror::Class* klass = class_linker-\u003eResolveType(*dex_file, dex_file-\u003eGetClassDef(class_idx).class_idx_, h_dex_cache, h_class_loader);\n   StackHandleScope\u003c1\u003e hs2(self);\n   Handle\u003cmirror::Class\u003e h_class(hs2.NewHandle(klass));\n   bool suc = class_linker-\u003eEnsureInitialized(self, h_class, true, true);\n   ```\n\n4. 主动调用Class的所有Method, 并修改ArtMethod::Invoke使其强制走switch型解释器\n\n   ```c++\n   //unpacker.cc\n   uint32_t args_size = (uint32_t)ArtMethod::NumArgRegisters(method-\u003eGetShorty());\n   if (!method-\u003eIsStatic()) {\n       args_size += 1;\n   }\n   \n   JValue result;\n   std::vector\u003cuint32_t\u003e args(args_size, 0);\n   if (!method-\u003eIsStatic()) {\n       mirror::Object* thiz = klass-\u003eAllocObject(self);\n       args[0] = StackReference\u003cmirror::Object\u003e::FromMirrorPtr(thiz).AsVRegValue();  \n   }\n   method-\u003eInvoke(self, args.data(), args_size, \u0026result, method-\u003eGetShorty());\n   \n   //art_method.cc\n   if (UNLIKELY(!runtime-\u003eIsStarted() || Dbg::IsForcedInterpreterNeededForCalling(self, this) \n   || (Unpacker::isFakeInvoke(self, this) \u0026\u0026 !this-\u003eIsNative()))) {\n   if (IsStatic()) {\n   art::interpreter::EnterInterpreterFromInvoke(\n   self, this, nullptr, args, result, /*stay_in_interpreter*/ true);\n   } else {\n   mirror::Object* receiver =\n   reinterpret_cast\u003cStackReference\u003cmirror::Object\u003e*\u003e(\u0026args[0])-\u003eAsMirrorPtr();\n   art::interpreter::EnterInterpreterFromInvoke(\n   self, this, receiver, args + 1, result, /*stay_in_interpreter*/ true);\n   }\n   }\n   \n   //interpreter.cc\n   static constexpr InterpreterImplKind kInterpreterImplKind = kSwitchImplKind;\n   ```\n\n5. 在解释器中插桩, 在每条指令执行前设置回调\n\n   ```c++\n   //interpreter_switch_impl.cc\n   // Code to run before each dex instruction.\n     #define PREAMBLE()                                                                 \\\n     do {                                                                               \\\n       inst_count++;                                                                    \\\n       bool dumped = Unpacker::beforeInstructionExecute(self, shadow_frame.GetMethod(), \\\n                                                        dex_pc, inst_count);            \\\n       if (dumped) {                                                                    \\\n         return JValue();                                                               \\\n       }                                                                                \\\n       if (UNLIKELY(instrumentation-\u003eHasDexPcListeners())) {                            \\\n         instrumentation-\u003eDexPcMovedEvent(self, shadow_frame.GetThisObject(code_item-\u003eins_size_),  shadow_frame.GetMethod(), dex_pc);            \t\t\t\t\t\t   \t\t\t\t\t\t\t\t\t\t   \\\n       }                                                                                \\\n     } while (false)\n   ```\n\n6. 在回调中做针对性的CodeItem的dump, 这里仅仅是简单的示例了直接dump, 实际上, 针对某些厂商的抽取, 可以真正的执行几条指令等待CodeItem解密后再dump\n\n   ```c++\n   //unpacker.cc\n   bool Unpacker::beforeInstructionExecute(Thread *self, ArtMethod *method, uint32_t dex_pc, int inst_count) {\n     if (Unpacker::isFakeInvoke(self, method)) {\n     \tUnpacker::dumpMethod(method);\n       return true;\n     }\n     return false;\n   }\n   ```\n\n\n\n### 合并 DEX, CodeItem\n\n将dump下来的CodeItem填充到DEX的相应位置中即可. 主要是基于google dx工具修改.\n\n\n\n### 参考链接\n\nFUPK3: https://bbs.pediy.com/thread-246117.htm\n\nFART: https://bbs.pediy.com/thread-252630.htm\n\n\n\n## 刷机\n\n1. 仅支持pixel 1代\n2. 重启至bootloader: `adb reboot bootloader`\n3. 解压 Youpk_sailfish.zip 并双击 `flash-all.bat`\n\n\n\n## 编译\n\n### 脱壳机源码编译\n\n1. 下载android-7.1.2_r33完整源码\n2. 替换unpacker/android-7.1.2_r33\n3. 编译\n\n### 修复工具编译\n\n1. IDEA导入dexfixer项目\n2. main class为 `com.android.dx.unpacker.DexFixer` \n\n\n\n## 使用方法\n\n1. **该工具仅仅用来学习交流, 请勿用于非法用途, 否则后果自付！**\n   \n2. 配置待脱壳的app包名, 准确来讲是进程名称\n\n    ```bash\n    adb shell \"echo cn.youlor.mydemo \u003e\u003e /data/local/tmp/unpacker.config\"\n    ```\n\n3. 如果apk没有整体加固, 未避免installd调用dex2oat优化, 需要在安装之前执行第2步\n    \n4. 启动apk等待脱壳\n    每隔10秒将自动重新脱壳(已完全dump的dex将被忽略), 当日志打印unpack end时脱壳完成\n\n5. pull出dump文件, dump文件路径为 `/data/data/包名/unpacker` \n\n    ```bash\n    adb pull /data/data/cn.youlor.mydemo/unpacker\n    ```\n\n6. 调用修复工具 dexfixer.jar, 两个参数, 第一个为dump文件目录(必须为有效路径), 第二个为重组后的DEX目录(不存在将会创建)\n    ```bash\n    java -jar dexfixer.jar /path/to/unpacker /path/to/output\n    ```\n\n\n\n## 适用场景\n\n1. 整体加固\n2. 抽取:\n   - nop占坑型(类似某加密)\n   - naitve化, 在 `\u003cclinit\u003e` 中解密(类似早期阿里)\n   - goto解密型(类似新版某加密, najia): https://bbs.pediy.com/thread-259448.htm\n\n\n\n\n## 常见问题\n\n1. dump中途退出或卡死，重新启动进程，再次等待脱壳即可\n2. 当前仅支持被壳保护的dex, 不支持App动态加载的dex/jar\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjitcor%2Fyoupk8","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjitcor%2Fyoupk8","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjitcor%2Fyoupk8/lists"}