Android APP加固与脱壳基础 ---- Android App应用启动时、类加载器的初始化过程

如果学习Android加固与脱壳不学习这些基础的话,那么后面加固点和脱壳点能让你轻轻松松看得云里雾里,不知所以。想学这一门手艺,该跨的门槛一点儿马虎不得,但是我有预感,当你把这些基础底层难啃的知识整出些门道之后,看后面的加固和脱壳出现的名词和为什么这样做时,你就会感到:原来是这样。

当我们在手机屏幕上点击一个APP之后,从技术角度透过屏幕和硬件,观察它的内部发生了哪些变化,在安卓系统又经过了怎样的流程呢?

一、启动的地方

在我们编写Android应用的时候,逻辑开始的地方是onCreate(),但是当我们点击APP的时候,首先是经过Android系统出发,这里是真正启动的代码:

// 这里是真正启动的代码:
Process.ProcessStartResult startResult = Process.start(
 entryPoint, 
 app.processName,
 uid, uid, gids, 
 debugFlags, 
 mountExternal, 
 app.info.targetSdkVersion, 
 app.info.seinfo, 
 requiredAbi, instructionSet, 
 app.info.dataDir, entryPointArgs
);

Process.start里面有很多参数,现在不需要疑惑它们是干啥的,随着后面的介绍,你会发现好多参数并不在这个课题的讨论之下,可以不用管它。

但是这个Process.start()函数很重要
app.processName是进程名,在安卓系统中,经历了这个过程:
图1
Process.start()通过Binder IPC创建系统服务(system_server),然后系统服务通过socket发送请求给Zygote这块流程,就和安卓系统启动虚拟器流程图对上了。
图2
我们看下图2中的system_server主要干了哪些事。
看精简版的部分源代码:

ZygoteInit.java

private static boolean startSystemServer(String abiList, String socketName) throws MethodAndArgsCaller,RuntimeException{
  ...
    //Request to fork the system server process
    pid = Zygote.forkSystemServer(
  	...
  	);
  ...
    
    //子进程返回0,即systemServer
    if (pid == 0){
      if (hasSecondZygote(abiList)){
        waitForSecondaryZygote(socketName);
      }
      handleSystemServerProcess(parsedArgs);
    }
}
  • 0:代表着是在新建的子进程中执行的。
  • pid of child:在Zygote创建的父进程里再创建的子进程
  • -1:代表出错。

这里我们分析下pid = 0的逻辑,我的理解是APP运行时,不会是由Zygote创建的父进程直接创建子进程给APP,因为在我们打开应用程序之前,Zygote已经创建了多个系统自带的应用程序。

if (pid == 0)后的逻辑。

在start()方法通过socket的方式向Zygote进程发起创建请求之后,Zygote.forkSystemServer()会调用handleSystemServerProcess()

private static void handleSystemServerProcess(ZygoteConnection.Arguments parsedArgs)throws ZygoteInit.MethodAndArgsCaller{
  //关闭socket服务
  closeServerSocket();
  ...
  ...
  //创建classLoader并且把它设为currentThread()当前线程的classLoader
    ClassLoader cl = null;
  if (systemServerClasspath != null){
    
    // createSystemServerClassLoader()创建的是一个PathClassLoader
    cl = createSystemServerClassLoader(systemServerClasspath, parsedArgs.targetSdkVersion);
    
    Thread.currentThread().setContextClassLoader(cl);
  }
  ...
}

运行到这里时,代表当前是系统服务的进程,所以也创建了系统服务的classLoader,设置了Thread.current().setContextClassLoader()类加载器,后面的应用中所有的类默认是通过这个类加载器来加载的。

至于什么是classLoader和类加载器的概念,可以阅读这篇文章

二、创建Application

Application是APP运行的基础,每当应用程序启动时,系统会自动将这个类进行初始化,Application的声明周期就是整个APP的生命周期。

在拥有了Thread.current().setContextClassLoader()类加载器后,通过socket反射调用了ActivityThread.main()方法,创建了ActivityThread后,进行attach()操作。

ActivityThread.java

public static void main(String[] args){
  ...
  Looper.prepareMainLooper();
  ActivityThread thread = new ActivityThread();
  thread.attach(false);
  ...
    
  Looper.loop()
  throw new RuntimeException("Main thread loop unexpectedly exited");
}

// 在thread.attach()里面进行了attachApplication(mAppThread)同时会调用thread.bindApplication() 这里是ActivityThread.bindApplication()

public final void bindApplication(...){
  ...
  //最后一句,发送了一条message消息
  sendMessage(H.BIND_APPLICATION, data);
}

//在ActivityThread.handleMessage()中对BIND_APPLICATION的接收后调用了handleBindApplication()
// 真正绑定application的地方
private void handleBindApplication(AppBindData data){
  ...
  ...
  ...
  Application app = data.info.makeApplication(data.restrictedBackupMode, null);
  mInitiApplication = app;
  ...
}

言而总之,attach操作,是把创建的系统服务进程和我们的app进行了一系列的绑定,不做过多分析,我们只看重要的部分。

attach操作接下来会调用LoadedApk.java, ApplicationLoaders.java部分

这两个类我们从名字上就能看出一二,是干什么的。
其中LoadedApk.java里的内容是我们脱壳和加固内容中非常重要的类,后续遇到时再讲。

其中,LoadedApk.makeApplication()里面的代码会去获取classLoader,并且创建appContext,在通过classLoader和appContext去创建application对象。

// LoadedApk.java
public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation){
  ...
  // getClassLoader()是去获取一个与当前apk相关联的pathClassLoader,
  java.lang.ClassLoader cl = getClassLoader();
  ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
  
  //app为application
  app = mActivityThread.mInstrumentation.newApplicationn(
  cl, appClass, appContext);
}

总结:创建appication的过程

  • mActivityThread.mInstrumentation.newApplication(cl, appClass, appContext)
  • 第一个参数cl,是当前app的classLoader
  • 第二个参数是appClass,当前app application的名字,检索的包名
  • appContext是新建的context
  • newApplication,利用cl.loadClass(appClass)得到当前app的application
  • 补充loadClass后得到的clazz,下一步进行

其中app.attach(context),会调用application的attachBaseContext();

然后调用instrumentation.callApplicationOnCreate(app),会调用application.onCreate()方法;

目前为止,thread.attach();引起了一系列的事件,现在已经正式创建了appication,可以执行application的onCreate()方法。

三、apk什么时候与classLoader什么时候关联

我们看下创建Application的时候这个语句:

// getClassLoader()是去获取一个与当前apk相关联的pathClassLoader,
  java.lang.ClassLoader cl = getClassLoader();

这里稍微提一下,上面的代码出现的pathClassLoader,是动态加载部分的内容,顺便提一下它的作用和同类。

  • DexClassLoader:可以加载jar/apk/dex,可以加载sd卡中未安装的apk;
  • PathClassLoader:只能加载系统中已经安装过的 apk;

实现cl的getClassLoader实现:

public ClassLoader getClassLoader() {
  synchronized(this){
    if (mClassLoader == null){
      createOrUpdateClassLoaderLocked(null)
    }
    return mClassLoader;
  }
}

private void createOrUpdateClassLoaderLocked(List<String> addedPaths){
  ...
  //
  mClassLoader = ApplicationLoaders.getDefault().getClassLoader("", mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath, libraryPermittedPath, mBaseClassLoader);
}

对mClassLoader进行了赋值,那内部的ApplicationLoaders.getDefault().getClassLoader()是如何实现的呢?

对于ApplicationLoaders这个类,它里面维护了一个mLoaders,map结构,key为string,可以理解为 包名,value为ClassLoader(类加载器)。

public ClassLoader getClassLoader(String zip, ...){
  ...
  ClassLoader loader = mloaders.get(zip);
  if(loader != null){
    return loader;
  }
  PathClassLoader pathClassloader = PathClassLoaderFactory.createClassLoader(...);
  ...
  mLoaders.put(zip, pathClassloader);
  return pathClassloader;
  ...
}

每个app进程都有唯一的ApplicationLoaders实例,后续则通过apk的路径(zip参数)查询返回classLoader。

因为ApplicationLoaders里维护了classLoader的一个map常量mLoaders,所以一个进程可以对应多个APK。

这样说吧,一个app有唯一的ApplicationLoaders实例对应,但是一个进程里的ApplicationLoaders,可以维护多个apk。

总结一下,APP启动时发生的事情:

  • 先从zygote fork一个进程
  • fork进程之后,会调用ActivityThread.main(),new出一个ActivityThread对象并且会把它与application关联起来,即attach();
  • attach会引发一系列事件,先后创建classLoader, appContext和application把application和activityThread关联起来。
  • 调用instrumentation.callApplicationOnCreate(app),触发application的onCreate()方法。

写到这里,我不禁留下了热泪,过程如此之多,差点把自己绕进去了。不过消化这个过程是必须的,我用了一张比较详细的思维导图来描述这整个过程。

脑图

这里提示下,光看没有用,最好动手对照着画下自己理解的思维导图,等你把这些东西消化后理解流程就水到渠成了。

Refer:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章