Youpk是一个很强大的框架,他的模块化组织形式非常新颖,但是随着安卓系统的不断更新,移植难度也非常大,由于使用了大量的api,导致移植有一定的难度,与fart相比,模块化的插桩更加优雅。
已经有大佬做了fart10的移植(见参考文章3),我这里就不和他重复了,来尝试下youpk的移植,并去除特征指纹。
测试设备:pixel1
aosp移植版本:aosp10.0.0_r2(本来想移植fartext,失败了)
(aosp11与10的api相关类似,可以自己尝试)
1.需要你有基础的aosp编译修改经验,简单修改能编译成功
- 需要有趁手的ide修改经验 我使用的是android studio(调试java层) clion(调试native层)
- 可能你移植全部完成以后,还是过不去企业壳,但是你能了解基本的art修改思想
- 如果你准备好开始,检测你的内存和硬盘 内存推荐大于32G(ide要占用10+10左右)
硬盘需要大于1TB 我是32G+2TB 编译体验非常差 有条件推荐上4TB+64G
如何开启clion和android studio导入项目源码:
编译的时候要注意repo的python版本,最好大于3.7 如果在低版本ubuntu系统,需要自己编译python
repo fatal: error unknown url type: https
原因:python没有设置ssl
./configure --prefix=/usr/local/python3 --with-ssl
解决文档:
Android AOSP Repo Init fatal: error unknown url type: https - Stack Overflow
解决:在编译的时候配置ssl
找到一个趁手的编译环境+IDE环境是成功编译的第一步骤
由于7.1→8.0→10.0有多版本跨度,我们选择youpk8的源码进行移植,来减少api差异
第一步,导入unpacker类到
这里我们可以第一步去除指纹,修改包名
我这里的包名是com.jiqiu
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 | package com.jiqiu; import android.app.ActivityThread; import android.os.Looper; import java.io.BufferedReader; import java.io.FileReader; import java.io.File; public class Unpacker { // public static String UNPACK_CONFIG = "/data/local/tmp/unpacker.config" ; // 去指纹位置2,修改配置名文件,不一定需要config尾缀 public static String UNPACK_CONFIG = "/data/local/tmp/gagaga" ; public static int UNPACK_INTERVAL = 10 * 1000; public static Thread unpackerThread = null; public static boolean shouldUnpack() { boolean should_unpack = false ; String processName = ActivityThread.currentProcessName(); BufferedReader br = null; try { br = new BufferedReader(new FileReader(UNPACK_CONFIG)); String line; while ((line = br.readLine()) != null) { if (line.equals(processName)) { should_unpack = true ; break ; } } br.close(); } catch (Exception ignored) { } return should_unpack; } public static void unpack() { if (Unpacker.unpackerThread != null) { return ; } if (!shouldUnpack()) { return ; } // 开启线程调用 Unpacker.unpackerThread = new Thread() { @Override public void run() { while ( true ) { try { Thread. sleep (UNPACK_INTERVAL); } catch (InterruptedException e) { e.printStackTrace(); } Unpacker.unpackNative(); } } }; Unpacker.unpackerThread.start(); } public static native void unpackNative(); } |
类名也完全可以修改,在修改后要在
这个文件里加上自己的包名,否则编译不过
比如我打的包名是com.jiqiu
在里面就是
之后进入
core/java/android/app/ActivityThread.java
导入自己的包名
在app启动后,注入自己的脱壳线程
注意:如果你修改了类名,这里的导入和调用也需要修改
想比于fart,youpk的主动调用部分在native层实现,java层仅仅是启动一个线程 启动native函数
第一步修改dexopt.cc 路径如上
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 | --- a /dex2oat/dex2oat .cc +++ b /dex2oat/dex2oat .cc @@ -1036,6 +1036,8 @@ class Dex2Oat final { CompilerFilter::NameOfFilter(compiler_options_->GetCompilerFilter())); key_value_store_->Put(OatHeader::kConcurrentCopying, kUseReadBarrier ? OatHeader::kTrueValue : OatHeader::kFalseValue); + + if (invocation_file_.get() != -1) { std::ostringstream oss; for (int i = 0; i < argc; ++i) { @@ -1089,7 +1091,23 @@ class Dex2Oat final { *out = true ; } } - + //patch by Youlor + // ++++++++++++++++++++++++++++ + const char* UNPACK_CONFIG = "/data/local/tmp/gagaga" ; + bool ShouldUnpack() { + std::ifstream config(UNPACK_CONFIG); + std::string line; + if (config) { + while (std::getline(config, line)) { + std::string package_name = line.substr(0, line. find ( ':' )); + if (oat_location_. find (package_name) != std::string::npos) { + return true ; + } + } + } + return false ; + } + // ++++++++++++++++++++++++++++ // Parse the arguments from the command line. In case of an unrecognized option or impossible // values /combinations , a usage error will be displayed and exit () is called. Thus, if the method // returns, arguments have been successfully parsed. @@ -1240,7 +1258,14 @@ class Dex2Oat final { ProcessOptions(parser_options.get()); // Insert some compiler things. + InsertCompileOptions(argc, argv); + //patch by Youlor + // ++++++++++++++++++++++++++++ + if (ShouldUnpack()) { + compiler_options_->SetCompilerFilter(CompilerFilter::kVerify); + } + // ++++++++++++++++++++++++++++ } |
注意,这里的config路径一定要与java层设置的一致,见去指纹2
const char* UNPACK_CONFIG = "/data/local/tmp/gagaga";
并且修改Android.bp
添加目标编译文件
添加编译文件后我们就可以初步处理youpk的所有不兼容api,附件提供了修改前后的youpk文件夹,在数十次的编译中,已经修改成安卓系统最新支持api
在新版系统编译,这个宏定义视为不安全,直接使用math库的同名函数即可过编译,记得注释原来的
在新版系统中,dex相关库文件移动到了libdexfile文件夹下,我们只需改动libdexfile/Android.bp
导出其依赖的库,并按新版文件调用,即可解决依赖问题
1 2 3 4 5 6 7 8 9 10 11 12 | // Check whether the oat output files are writable, and open them for later. Also open a swap diff --git a /libdexfile/Android .bp b /libdexfile/Android .bp index 30d1bcd..2ff2f10 100644 --- a /libdexfile/Android .bp +++ b /libdexfile/Android .bp @@ -95,7 +95,7 @@ cc_defaults { }, }, generated_sources: [ "dexfile_operator_srcs" ], - export_include_dirs: [ "." ], + export_include_dirs: [ "." , "dex" ], } |
globals位置发生改变
#include "base/globals.h”
mirror::Class*指针修改为ObjPtrmirror::Class
setstatus的状态码发生改变 mirror::Class::kStatusInitialized变为ClassStatus::kInitialized
删除size_t Unpacker::getCodeItemSize(ArtMethod* method)方法 新版有api可以直接实现
uint32_t code_item_size = method->GetDexFile()->GetCodeItemSize(*code_item);
可以直接获取到codeitem的size 省去了上面的函数
method->GetCodeItem()->insns_;
新版本的codeitem没有insns_属性,需要迭代器访问
见参考文章4
修改为 const uint16_t* const insns = CodeItemInstructionAccessor(*method->GetDexFile(),method->GetCodeItem()).Insns();即可编译通过
最后修改注册函数
REGISTER_NATIVE_METHODS("com/jiqiu/Unpacker");
包名和类名修改过的可以去修改下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | --- a /runtime/runtime .cc +++ b /runtime/runtime .cc @@ -15,7 +15,9 @@ */ #include "runtime.h" - + //add + #include "unpacker/unpacker.h" + //addend // sys /mount .h has to come before linux /fs .h due to redefinition of MS_RDONLY, MS_BIND, etc #include <sys/mount.h> #ifdef __linux__ @@ -1907,6 +1909,10 @@ void Runtime::RegisterRuntimeNativeMethods(JNIEnv* env ) { register_org_apache_harmony_dalvik_ddmc_DdmServer( env ); register_org_apache_harmony_dalvik_ddmc_DdmVmInternal( env ); register_sun_misc_Unsafe( env ); + + //add + Unpacker::register_cn_youlor_Unpacker( env ); + //addend } std::ostream& operator<<(std::ostream& os, const DeoptimizationKind& kind) { |
1 2 3 4 5 6 7 8 9 10 | --- a /runtime/Android .bp +++ b /runtime/Android .bp @@ -350,6 +352,9 @@ libart_cc_defaults { // ART is allowed to link to libicuuc directly // since they are in the same module "-DANDROID_LINK_SHARED_ICU4C" , + "-Wno-error" , + "-DART_USE_CXX_INTERPRETER=1" , ], }, |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | --- a /runtime/class_linker .h +++ b /runtime/class_linker .h @@ -1385,6 +1385,9 @@ class ClassLinker { class FindVirtualMethodHolderVisitor; friend class AppImageLoadingHelper; + //add + friend class Unpacker; + //addend friend class ImageDumper; // for DexLock friend struct linker::CompilationHelper; // For Compile in ImageTest. friend class linker::ImageWriter; // for GetClassRoots diff --git a /runtime/interpreter/interpreter_switch_impl-inl .h b /runtime/interpreter/interpreter_switch_impl-inl .h index 36cfee4..b6e5ff6 100644 |
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 | diff --git a /runtime/art_method .cc b /runtime/art_method .cc index 0890da8..2cd96d2 100644 --- a /runtime/art_method .cc +++ b /runtime/art_method .cc @@ -50,7 +50,9 @@ #include "runtime_callbacks.h" #include "scoped_thread_state_change-inl.h" #include "vdex_file.h" - + //add + #include "unpacker/unpacker.h" + //addend namespace art { using android::base::StringPrintf; @@ -322,13 +324,28 @@ void ArtMethod::Invoke(Thread* self, uint32_t* args, uint32_t args_size, JValue* // If the runtime is not yet started or it is required by the debugger, then perform the // Invocation by the interpreter, explicitly forcing interpretation over JIT to prevent // cycling around the various JIT /Interpreter methods that handle method invocation. - if (UNLIKELY(!runtime->IsStarted() || - (self->IsForceInterpreter() && !IsNative() && !IsProxyMethod() && IsInvokable()) || - Dbg::IsForcedInterpreterNeededForCalling(self, this))) { + + // if (UNLIKELY(!runtime->IsStarted() || + // (self->IsForceInterpreter() && !IsNative() && !IsProxyMethod() && IsInvokable()) || + // Dbg::IsForcedInterpreterNeededForCalling(self, this))) { + //add + if (UNLIKELY(!runtime->IsStarted() || Dbg::IsForcedInterpreterNeededForCalling(self, this) + || (Unpacker::isFakeInvoke(self, this) && !this->IsNative()))) { + + //addend if (IsStatic()) { art::interpreter::EnterInterpreterFromInvoke( self, this, nullptr, args, result, /*stay_in_interpreter=*/ true ); } else { + //patch by Youlor + // ++++++++++++++++++++++++++++ + // 如果是主动调用fake invoke并且是native方法则不执行 + if (Unpacker::isFakeInvoke(self, this) && this->IsNative()) { + // Pop transition. + self->PopManagedStackFragment(fragment); + return ; + } + // ++++++++++++++++++++++++++++ mirror::Object* receiver = reinterpret_cast<StackReference<mirror::Object>*>(&args[0])->AsMirrorPtr(); art::interpreter::EnterInterpreterFromInvoke( diff --git a /runtime/class_linker .h b /runtime/class_linker .h |
a/runtime/interpreter/interpreter_switch_impl-inl.h
注意这个不是在cpp中实现了,在interpreter_switch_impl-inl.h头中实现
而且youpk插桩的宏定义在aosp10中改为了函数判断,无法在函数中插桩
只需要在相应位置插桩即可解决(已给出patch)
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 | --- a /runtime/interpreter/interpreter_switch_impl-inl .h +++ b /runtime/interpreter/interpreter_switch_impl-inl .h @@ -18,7 +18,9 @@ #define ART_RUNTIME_INTERPRETER_INTERPRETER_SWITCH_IMPL_INL_H_ #include "interpreter_switch_impl.h" - + //add + #include "unpacker/unpacker.h" + //addend #include "base/enums.h" #include "base/globals.h" #include "base/memory_tool.h" @@ -225,6 +227,7 @@ class InstructionHandler { if (!CheckForceReturn()) { return false ; } + if (UNLIKELY(instrumentation->HasDexPcListeners())) { uint8_t opcode = inst->Opcode(inst_data); bool is_move_result_object = (opcode == Instruction::MOVE_RESULT_OBJECT); @@ -243,6 +246,8 @@ class InstructionHandler { return false ; } } + + //addend return true ; } @@ -2643,12 +2648,25 @@ ATTRIBUTE_NO_SANITIZE_ADDRESS void ExecuteSwitchImplCpp(SwitchImplContext* ctx) << "Entered interpreter from invoke without retry instruction being handled!" ; bool const interpret_one_instruction = ctx->interpret_one_instruction; + + //add + int inst_count=-1; + //addend while ( true ) { dex_pc = inst->GetDexPc(insns); shadow_frame.SetDexPC(dex_pc); TraceExecution(shadow_frame, inst, dex_pc); inst_data = inst->Fetch16(0); { + //add + inst_count++; \ + bool dumped = Unpacker::beforeInstructionExecute(self, shadow_frame.GetMethod(), \ + dex_pc, inst_count); \ + + if (dumped) { + return ; + } + //addend bool exit_loop = false ; InstructionHandler<do_access_check, transaction_active> handler( ctx, instrumentation, self, shadow_frame, dex_pc, inst, inst_data, exit_loop); @@ -2662,6 +2680,7 @@ ATTRIBUTE_NO_SANITIZE_ADDRESS void ExecuteSwitchImplCpp(SwitchImplContext* ctx) continue ; } } + switch (inst->Opcode(inst_data)) { #define OPCODE_CASE(OPCODE, OPCODE_NAME, pname, f, i, a, e, v) \ case OPCODE: { \ @@ -2681,6 +2700,13 @@ DEX_INSTRUCTION_LIST(OPCODE_CASE) if (UNLIKELY(interpret_one_instruction)) { break ; } + //patch by Youlor + // ++++++++++++++++++++++++++++ + bool dumped = Unpacker::afterInstructionExecute(self, shadow_frame.GetMethod(), dex_pc, inst_count); + if (dumped) { + return ; + } + // ++++++++++++++++++++++++++++ } // Record where we stopped. shadow_frame.SetDexPC(inst->GetDexPc(insns)); diff --git a /runtime/runtime .cc b /runtime/runtime .cc index 51a40e7..275324c 100644 |
- 由于作者指定了特定名称的包名,所以可以通过反射的方法来检测是否存在这个类,如果存在,则判断为脱壳机环境(在系统的类里面,可以使用hideapi来绕过反射限制)
GitHub - LSPosed/AndroidHiddenApiBypass: LSPass: Bypass restrictions on non-SDK interfaces
- 由于作者独特的包名过滤机制,需要配置特定名字的config在data/local/tmp下 其他app可扫描这个目录下的config,来判断脱壳环境
解决方法:
换一个注册的名字,可以选定一些厂商的特殊包名注册,如xiaomi meizu huawei等特殊包名
修改config名字或落地地方挑选app不可达路径(之后会集中讨论)
DexFIile静态注册了一个函数,作为与native桥接的函数,由于比较独特,直接可以通过反射调用,甚至可以做进一步下沉,在libart.so的导出表中发现这个函数
在ActivityThred中出现了很多工具类函数,可以反射调用检测,以及在art_method中有额外的导出函数,可以通过扫描libart.so的导出表来扫描制定名字
解决方法:
- 学习youpk,将逻辑打包到一个包里,进行插桩复用
- 进行api复用(fart10已经做到),将必要的逻辑实现在系统api里,通过参数判断是否返回
难点:
权限的申请(脱壳机一般都有)
用户隐私的保护 (厂商一般不管)
解决办法:
修改系统selinux,注册全新的selinux标签,编写系统应用,进行文件的存储和获取(小肩膀沙盒定制思路)
注册系统服务,app通过调用,存储到/system 目录下
以上两种方法均可进行dex和config文件的落地
难点:
各大厂商对于rom均有定制,libart.so数量均不一致
无法太大的做到导出函数数量的特征(这个是真的致命打击)
解决办法:
建立机型库,对机型的各个文件进行模型建立,检测是否为异常libart,判断是否为异常机型
脱壳机一般使用pixel以及nexus等机型做定制rom,可以针对这些机型做风控(误杀率高)
所以可以对aosp的定制进行检测,对aosp的指纹进行检测(目前大部分脱壳机过不去企业壳都折在了这里)
解决办法:
使用开源的lineageos 以及pixel experience系统进行定制,这些系统都已经去除了很多aosp的特征
(除非日后国内哪家厂商开源了他们的操作系统)
使用支持这些rom的手机进行定制,这里我强烈推荐一加手机,简直无敌 随便刷随便解锁 还能9008救砖(比某xel6代好太多了)