记录学习的过程
vmp壳的应用。从效果上面看,是把java的函数,加密成了native的函数,在实现函数加密保护时,主要是对相同参数并且相同返回值的函数加密成native的。所以会一般是两种情况,一个是直接对onCreate函数进行vmp保护,或者是抽象出一个统一参数和返回值的函数。然后对这个函数进行vmp保护。
native的函数都是需要动态注册的。所以我们先改造aosp源码。打印下注册了函数,以及函数的地址,方便我们后续进行调试跟踪,下面修改art_method.cc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const  void * ArtMethod::RegisterNative (const  void * native_method, bool  is_fast)    CHECK(IsNative()) << PrettyMethod();   CHECK(!IsFastNative()) << PrettyMethod();   CHECK(native_method != nullptr ) << PrettyMethod();   if  (is_fast) {     AddAccessFlags(kAccFastNative);   }   LOG(ERROR)<<"krom ArtMethod::RegisterNative:" <<this ->PrettyMethod()<<"----addr:" <<native_method;   void * new_native_method = nullptr ;   Runtime::Current()->GetRuntimeCallbacks()->RegisterNativeMethod(this ,                                                                   native_method,                                                                   &new_native_method);   SetEntryPointFromJni(new_native_method);   return  new_native_method; } 
由于vmp中将java函数转成了native函数,所以函数内肯定有大量jni调用处理。所以为了方便分析,再改造下rom,在jni函数调用的时候将触发调用的函数以及被调用的函数,都打印出来。这样就知道加密的一部分jni调用,InvokeWithArgArray这个函数就满足我们的需求,jni函数都会调用的地方。我们在这里插入日志记录即可。
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 static  void  InvokeWithArgArray (const  ScopedObjectAccessAlreadyRunnable& soa,                                ArtMethod* method, ArgArray* arg_array, JValue* result,                                const  char * shorty)     REQUIRES_SHARED (Locks::mutator_lock_)   {  uint32_t * args = arg_array->GetArray();   if  (UNLIKELY(soa.Env()->check_jni)) {     CheckMethodArguments(soa.Vm(), method->GetInterfaceMethodIfProxy(kRuntimePointerSize), args);   }   ArtMethod* artmethod=nullptr ;   Thread* thread=Thread::Current;   ManagedStack* managerStack=thread->GetManagedStack();   if (managerStack!=nullptr ){       ArtMethod** methodPP=managerStack->GetTopQuickFrame();       if (methodPP!=nullptr ){           artmethod=*methodPP;       }   }   if (artmethod!=nullptr ){       LOG(ERROR)<<"krom start InvokeWithArgArray call:" <<artmethod->PrettyMethod()<<"----goal:" <<method->PrettyMethod();   }   method->Invoke(soa.Self(), args, arg_array->GetNumBytes(), result, shorty);   if (artmethod!=nullptr ){       LOG(ERROR)<<"krom end InvokeWithArgArray call:" <<artmethod->PrettyMethod()<<"----goal:" <<method->PrettyMethod();   } } 
然后为了方便调试,过掉各种检测,所以先修改aosp源码,能够在想要调试的jni函数执行前进行sleep等待。然后我们再附加进程。这样不在开始的时候直接附加进程,就可以直接跳过很多种动态调试检测。
所有jni函数在执行前后,art会进行一些准备和清场的步骤,比如JniMethodStart和JniMethodEnd。我们可以在JniMethodStart的时候判断一下。符合条件就sleep等待一下。下面贴下修改后的代码,文件路径是/art/runtime/entrypoints/quick/quick_jni_entrypoints.cc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 extern  uint32_t  JniMethodStart (Thread* self)    JNIEnvExt* env = self->GetJniEnv();   DCHECK(env != nullptr );   uint32_t  saved_local_ref_cookie = bit_cast<uint32_t >(env->local_ref_cookie);   env->local_ref_cookie = env->locals.GetSegmentState();   ArtMethod* native_method = *self->GetManagedStack()->GetTopQuickFrame();      const  char * methodname=native_method->PrettyMethod();      if (strstr (methodname,"JniMethodStart" )!=nullptr ){       sleep(60 );   }   if  (!native_method->IsFastNative()) {          self->TransitionFromRunnableToSuspended(kNative);   }   return  saved_local_ref_cookie; } 
最后我们使用frida配合rom来控制函数是否需要进入等待。从而达到跳过各种反调试。在最后jni函数执行前才进行附加调试的效果。
下面贴上配合的frida代码
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 function  hook_start (    var  libcModule=Process.getModuleByName("libc.so" );     var  strstr=libcModule.getExportByName("strstr" );     Interceptor.attach(strstr,{         onEnter:function (args )             this .arg0=args[0 ];             this .arg1=args[1 ];             var  method_name=ptr(this .arg0).readUtf8String();             var  call_name=ptr(this .arg1).readUtf8String();             if (call_name.indexOf("JniMethodStart" )!=-1  && method_name.indexOf("MainActivity.onCreate" )!=-1 ){                 console .log("JniMethodStart onCreate enter" );                 this .retval="1" ;             }         },onLeave :function (retval )             if  (this .retval){                 retval.replace(this .retval);             }         }     }) } function  main (    hook_start(); } setImmediate(main) 
然后启动frida。我这里环境使用的xadb。所以可以简单的直接打开frida