Android逆向:脱某软件dex函数抽取型壳

声明:案例分析仅供学习交流使用,勿用于任何非法用途。如学习者进一步逆向并对版权方造成损失,请自行承担法律后果,本人概不负责。

案例与目标

案例下载
jadx打开以后可以看到很明显的壳特征。
在这里插入图片描述
目标:使用工具和自行实现脱壳。
 

工具脱壳

FRIDA-DEXDump

FRIDA-DEXDump确实很轻松就拿到了dex文件,可惜文件数据有异常,无法正常打开分析。
在这里插入图片描述

frida-fart

通过frida-fart同样很轻松的拿到了dex文件,以首屏SplashActivity为例进行分析。
在这里插入图片描述
jadx可以正常打开,但里面的函数都是nop。
在这里插入图片描述

Fart脱壳机镜像

pixel刷入Fart脱壳机镜像,运行后在/sdcard/fart找到dex。
在这里插入图片描述
jadx打开,发现SplashActivity中函数是完整的。
在这里插入图片描述

Youpk脱壳机镜像

pixel刷入Youpk脱壳机镜像。 执行:

adb shell "echo com.ninemax.ncsearchnew >> /data/local/tmp/unpacker.config"

启动app等待10秒后在 /data/data/com.ninemax.ncsearchnew/unpacker 可找到dex。
在这里插入图片描述
jadx打开,发现SplashActivity中函数是完整的。
在这里插入图片描述
 

自行脱壳

OpenCommon

无论是InMemoryDexClassLoader还是DexClasslLoader,在动态加载dex时都会经过OpenCommon()与DexFile()两个函数,那么这两个函数都会是合适的脱壳点,这里以OpenCommon()为例进行修改:

// /aosp810r1/art/runtime/dex_file.cc

std::unique_ptr<DexFile> DexFile::OpenCommon(const uint8_t* base,
                                             size_t size,
                                             const std::string& location,
                                             uint32_t location_checksum,
                                             const OatDexFile* oat_dex_file,
                                             bool verify,
                                             bool verify_checksum,
                                             std::string* error_msg,
                                             VerifyResult* verify_result) {
  if (verify_result != nullptr) {
    *verify_result = VerifyResult::kVerifyNotAttempted;
  }
  std::unique_ptr<DexFile> dex_file(new DexFile(base,
                                                size,
                                                location,
                                                location_checksum,
                                                oat_dex_file));
  if (dex_file == nullptr) {
    *error_msg = StringPrintf("Failed to open dex file '%s' from memory: %s", location.c_str(),
                              error_msg->c_str());
    return nullptr;
  }
  if (!dex_file->Init(error_msg)) {
    dex_file.reset();
    return nullptr;
  }
  if (verify && !DexFileVerifier::Verify(dex_file.get(),
                                         dex_file->Begin(),
                                         dex_file->Size(),
                                         location.c_str(),
                                         verify_checksum,
                                         error_msg)) {
    if (verify_result != nullptr) {
      *verify_result = VerifyResult::kVerifyFailed;
    }
    return nullptr;
  }
  if (verify_result != nullptr) {
    *verify_result = VerifyResult::kVerifySucceeded;
  }

  //this is zyc code , dump method
  //--------------------------------------------
  const char* dirPath = "/sdcard/com.ninemax.ncsearchnew_zycdump";
  if (access(dirPath, 0) == -1){ //dir is not exists
      mkdir(dirPath, 0777);
  }
  char dexPath[100] = {0};
  sprintf(dexPath, "%s/%d_%d_OpenCommon.dex",dirPath, getpid(),(int)dex_file->Size());
  int fd = open(dexPath, O_CREAT|O_RDWR,666);
  if(fd>0){
    int ret = write(fd, dex_file->Begin(), (int)dex_file->Size());
    if(ret>0){
      ALOGD("write %s success!",dexPath);
    }
    close(fd);
  }
  //--------------------------------------------

  return dex_file;
}

LoadClassMembers

如果不执行dex2oat流程,那么ClassLinker过程中的诸多函数(如LoadClassMembers()、LoadMethod()等)将变为脱壳点,这里以LoadClassMembers()为例进行修改:

// /aosp810r1/art/runtime/class_linker.cc

void ClassLinker::LoadClassMembers(Thread* self,
                                   const DexFile& dex_file,
                                   const uint8_t* class_data,
                                   Handle<mirror::Class> klass) {
  {
    // Note: We cannot have thread suspension until the field and method arrays are setup or else
    // Class::VisitFieldRoots may miss some fields or methods.
    ScopedAssertNoThreadSuspension nts(__FUNCTION__);
    // Load static fields.
    // We allow duplicate definitions of the same field in a class_data_item
    // but ignore the repeated indexes here, b/21868015.
    LinearAlloc* const allocator = GetAllocatorForClassLoader(klass->GetClassLoader());
    ClassDataItemIterator it(dex_file, class_data);
    LengthPrefixedArray<ArtField>* sfields = AllocArtFieldArray(self,
                                                                allocator,
                                                                it.NumStaticFields());

    ......

    //this is zyc code , dump method
    //--------------------------------------------
    const char* dirPath = "/sdcard/com.ninemax.ncsearchnew_zycdump";
    if (access(dirPath, 0) == -1){ //dir is not exists
        mkdir(dirPath, 0777);
    }
    char dexPath[100] = {0};
    sprintf(dexPath, "%s/%d_%d_LoadClassMembers.dex",dirPath, getpid(),(int)dex_file.Size());
    int fd = open(dexPath, O_CREAT|O_RDWR,666);
    if(fd>0){
      int ret = write(fd, dex_file.Begin(), (int)dex_file.Size());
      if(ret>0){
        ALOGD("write %s success!",dexPath);
      }
      close(fd);
    }
    //--------------------------------------------

    DCHECK(!it.HasNext());
  }
  // Ensure that the card is marked so that remembered sets pick up native roots.
  Runtime::Current()->GetHeap()->WriteBarrierEveryFieldOf(klass.Get());
  self->AllowThreadSuspension();
}

这里设置一下dex2oat中 CompilerFilter::Filter 为仅验证,可以避免dex做任何形式的优化影响dump下来的dex文件:

  // /aosp810r1/art/dex2oat/dex2oat.cc
  
  dex2oat::ReturnCode Setup() {
    TimingLogger::ScopedTiming t("dex2oat Setup", timings_);

    ......

    // If we need to downgrade the compiler-filter for size reasons.
    if (!IsBootImage() && IsVeryLarge(dex_files_)) {
      // Disable app image to make sure dex2oat unloading is enabled.
      compiler_options_->DisableAppImage();

      // If we need to downgrade the compiler-filter for size reasons, do that early before we read
      // it below for creating verification callbacks.
      if (!CompilerFilter::IsAsGoodAs(kLargeAppFilter, compiler_options_->GetCompilerFilter())) {
        LOG(INFO) << "Very large app, downgrading to verify.";
        // Note: this change won't be reflected in the key-value store, as that had to be
        //       finalized before loading the dex files. This setup is currently required
        //       to get the size from the DexFile objects.
        // TODO: refactor. b/29790079
        compiler_options_->SetCompilerFilter(kLargeAppFilter);
      }
    }
    
    //this is zyc code , dump method
    //--------------------------------------------
    compiler_options_->SetCompilerFilter(CompilerFilter::kExtract);
    //--------------------------------------------

    ......

    return dex2oat::ReturnCode::kNoFailure;
  }

值得注意的是Android各版本CompilerFilter::Filter有所不同,具体参考:

// /aosp810r1/art/runtime/compiler_filter.h

class CompilerFilter FINAL {
 public:
  // Note: Order here matters. Later filter choices are considered "as good
  // as" earlier filter choices.
  enum Filter {
    kAssumeVerified,      // Skip verification but mark all classes as verified anyway.
    kExtract,             // Delay verication to runtime, do not compile anything.
    kVerify,              // Only verify classes.
    kQuicken,             // Verify, quicken, and compile JNI stubs.
    kSpaceProfile,        // Maximize space savings based on profile.
    kSpace,               // Maximize space savings.
    kSpeedProfile,        // Maximize runtime performance based on profile.
    kSpeed,               // Maximize runtime performance.
    kEverythingProfile,   // Compile everything capable of being compiled based on profile.
    kEverything,          // Compile everything capable of being compiled.
  };
  
  ......
}

Execute

ART模式下如不经过dex2oat编译,将使用Interpreter解释器(类的初始化函数默认始终由该解释器编译),Interpreter将会执行Execute(),而且该函数是内联函数,不容易被hook,结合上面阻断dex2oat流程部分,作出修改如下:

// /aosp810r1/art/runtime/interpreter/interpreter.cc

static inline JValue Execute(
    Thread* self,
    const DexFile::CodeItem* code_item,
    ShadowFrame& shadow_frame,
    JValue result_register,
    bool stay_in_interpreter = false) REQUIRES_SHARED(Locks::mutator_lock_) {
  DCHECK(!shadow_frame.GetMethod()->IsAbstract());
  DCHECK(!shadow_frame.GetMethod()->IsNative());

  //this is zyc code , dump method
  //--------------------------------------------
  ArtMethod* artMethod = shadow_frame.GetMethod();
  if(strstr(artMethod->PrettyMethod().c_str(),"<clinit>")){ //这里要对函数或者进程进行一定的筛选,否则系统会非常卡!
    const DexFile* dex_file = artMethod->GetDexFile();
    const char* dirPath = "/sdcard/com.ninemax.ncsearchnew_zycdump";
    if (access(dirPath, 0) == -1){ //dir is not exists
        mkdir(dirPath, 0777);
    }
    char dexPath[100] = {0};
    sprintf(dexPath, "%s/%d_%d_Execute.dex",dirPath, getpid(),(int)dex_file->Size());
    int fd = open(dexPath, O_CREAT|O_RDWR,666);
    if(fd>0){
      int ret = write(fd, dex_file->Begin(), (int)dex_file->Size());
      if(ret>0){
        ALOGD("write %s success!",dexPath);
      }
      close(fd);
    }
  }
  //--------------------------------------------

  if (LIKELY(shadow_frame.GetDexPC() == 0)) {  // Entering the method, but not via deoptimization.
    if (kIsDebugBuild) {
      self->AssertNoPendingException();
    }
    instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
    ArtMethod *method = shadow_frame.GetMethod();

    if (UNLIKELY(instrumentation->HasMethodEntryListeners())) {
      instrumentation->MethodEnterEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),
                                        method, 0);
      if (UNLIKELY(self->IsExceptionPending())) {
        instrumentation->MethodUnwindEvent(self,
                                           shadow_frame.GetThisObject(code_item->ins_size_),
                                           method,
                                           0);
        return JValue();
      }
    }
     
......
}

结果

刷入脱壳镜像,运行可以看到各脱壳点都dump出了dex:
在这里插入图片描述
对dump出的dex进行分析,发现LoadClassMembers和Execute两个点能正常获取到大部分源码(小部分类依旧nop),原因是我们在上面的代码中实现的是被动脱壳,没有调用到的地方依旧可能为nop,要达到更好的效果需要构建主动调用链。而OpenCommon在得到DexFile时早于App对dex的回填,内容依旧全是nop。
在这里插入图片描述
 

结论

无论是工具脱壳,还是自行脱壳,最根本的思想还是在dex加载流程中进行dump,但脱壳的时机不同,效果也会不同。由此次脱壳也不难看出,对于函数粒度的壳,结合了“脱壳点+主动调用”的Fart脱壳机镜像与Youpk脱壳机镜像效果较好,在dex2oat流程中被动调用脱壳效果其次,而在获取dex却早于回填时整体脱壳几乎无效。
 

相关资料

ART环境下基于主动调用的自动化脱壳方案
Youpk: 又一款基于ART的主动调用的脱壳机
AUPK:基于Art虚拟机的脱壳机

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值