[Fat Tiger's Reverse Road] 02 - Explicación detallada e implementación del principio general de empaque de Android

[Fat Tiger's Reverse Road] (02) - Explicación detallada e implementación del principio general de empaque de Android

Proceso de principio de empaque de Android Apk y explicación detallada



prefacio

Lo mencionado anteriormente es una explicación detallada de la relación entre la carga dinámica y la carga de clases en Android, que también es la base de nuestro capítulo.

[Fat Tiger's Reverse Road] 01 - Explicación detallada de la carga dinámica y el mecanismo de carga de clase

Con el fin de obtener una comprensión profunda del principio de empaquetado en el contenido relacionado con la inversa de Android, hemos completado una explicación detallada de la relación entre la carga dinámica y las clases de carga dinámica en Android, luego el siguiente paso es implementar el empaquetado general. de Android y explique el principio, debido a la capacidad limitada del autor, haré todo lo posible para describir el proceso y el principio del empaque general en detalle. Si hay algún error en este artículo, corríjame, gracias ~


1. Reserva de conocimiento antes de empacar

1. El proceso de inicio de la aplicación de Android

El proceso de puesta en marcha es un cliché, y muchas entrevistas harán esta pregunta. En el trabajo real, el desarrollo general de la aplicación no necesita preocuparse (ps: la entrevista construye un portaaviones y los tornillos de trabajo). Pero para la ingeniería inversa , en realidad está familiarizado con el proceso de inicio de las aplicaciones de Android. Puede evitar muchos malentendidos, para mejorar la eficiencia del trabajo, alcanzar el pináculo de la vida y ganar Bai Fumei... Olvídalo, despertemos jajaja

Sin embargo, dado que queremos comprender el principio del empaquetado de aplicaciones, primero debemos comenzar desde el proceso de inicio de la aplicación. Antes de que se inicie la aplicación, el sistema Android es el primero en iniciarse. A continuación, echemos un vistazo más de cerca al proceso de inicio del sistema Android. ~

Diagrama de flujo de inicio del sistema

Creo que si eres un jugador de Android, algunas personas están más o menos familiarizadas con esta imagen .

1. Primero se inicia el cargador de arranque, es decir, se enciende la alimentación
2. Vaya a la capa del núcleo para iniciar el proceso inactivo
3. Vaya a la capa de navegación para inicializar el proceso de inicio, luego analice y ejecute init.rc, y finalmente, vaya a nuestro app_process
4. En app_process, zygote se generará a través del proceso jni fork, y luego zygote eclosiona el proceso de servicio relevante
5. Después de cada proceso de inicio de la aplicación, zygote 6 eclosionará un proceso.
Y se pasará el ClassLoader sobre (el conocimiento anterior)

Entonces, de la lengua vernácula anterior, entendemos que el proceso Zygote incubó el primer proceso SystemServer, y el proceso SystemServer es la máxima prioridad de Android. El trabajo que completa es principalmente (nuestra preocupación) StartBootstrapServices (iniciar el servicio de arranque) Entre ellos , el uno más importante es ActivityManagerServices (servicios de administración de programación de cuatro componentes principales, de los cuales la programación de China Ac se transfiere a ATMS) y PackageManager (que brinda administración de paquetes, incluido el escaneo, la instalación y la desinstalación), principalmente AMS, PM, el resto no son demasiado preocupado, los estudiantes que lo necesiten pueden comprobarlo por sí mismos , todo se puede encontrar


2. El proceso de inicio de la aplicación de Android

El último paso del inicio del sistema Android es iniciar una aplicación (Lanzador), que se utiliza para mostrar las aplicaciones instaladas en el sistema.Durante el proceso de inicio, el Lanzador solicitará PMS (PackageManagerService) para devolver la información de las aplicaciones instaladas. en el sistema Y encapsule esta información en una lista de iconos de acceso directo y muéstrela en la pantalla del sistema ~

De esta forma, el usuario puede iniciar la aplicación correspondiente haciendo clic en estos iconos de acceso directo

(Con la ayuda de las imágenes dibujadas por los predecesores) el diagrama de flujo es el siguiente:
inserte la descripción de la imagen aquí

1. Cuando se hace clic en el ícono del escritorio, la aplicación Launcher llamará al método startActivity, a través de la comunicación Binder, llamará al método startActivity del servicio AMS en el proceso system_server, y el método enviará una solicitud de inicio ~ 2. Después del sistema- el proceso del servidor recibe la solicitud, enviará una solicitud a
Zygote. Envíe una solicitud para crear un proceso (bifurcación) ~
3. Después de recibir la solicitud, zygote sabe que está cobrando vida y comienza a bifurcar el proceso de la aplicación. y se llamará al método principal de la clase ActivityThread en el proceso de la aplicación para crear un subproceso ActivityThread para llevar a cabo la inicialización de MainLooper, el controlador del subproceso principal e inicializar el Subproceso de aplicación en el proceso de la aplicación para la comunicación interactiva con AMS~ 4. Después de que el proceso de la aplicación
sea creado, enviará una solicitud de adjuntar aplicación a system_server a través de Binder (en realidad, el proceso de la aplicación llama al proceso de system_server a través del proceso de Binder El método de adjuntar aplicación del servicio AMS en AMS) La función real de adjuntar aplicación en AMS es vincular el objeto ApplicationThread en
el proceso de la aplicación con AMS para facilitar la comunicación.
6. Después de recibir la solicitud, el subproceso de enlace (ApplicationThread) del proceso de la aplicación envía mensajes BIND_APPLICATION y LAUNCH_ACTIVITY al subproceso principal a través del controlador interno. Tenga en cuenta que AMS y el subproceso principal no se comunican directamente, sino entre AMS y el subproceso principal. subproceso La clase interna ApplicationThread se comunica a través de Binder, y ApplicationThread interactúa con el subproceso principal a través de los mensajes del controlador ~
7. Después de que el subproceso principal recibe el mensaje, comienza a crear la aplicación y llama al método oncreate, luego crea la actividad de destino a través de la reflexión. y llama a métodos como Activity.onCreate~
8. En este punto, el inicio de la aplicación finalizará oficialmente, comenzará el ciclo de vida de Ac y comenzará la representación de la interfaz de usuario.

En este punto, hemos descubierto aproximadamente el proceso de inicio de la APLICACIÓN ~


3. Proceso de inicio de ActivityThread

En el empaquetado, hay un papel muy importante, que es ActivityThrad. En la serie anterior de artículos, muchos de ellos mencionaron que ActivityThread.main() es la puerta para ingresar al mundo de las aplicaciones y, por lo tanto, explicaron el principio del empaquetado ~

A continuación, siguiendo los pasos del jefe, también comenzaremos a analizar el código fuente para comprender qué se hace en ActivityThread~

inserte la descripción de la imagen aquí

Podemos ver que el método principal en ActivityThread llama a accath (falsh, xx) para realizar una serie de preparativos de inicialización, y finalmente el hilo principal ingresa al bucle de mensajes, esperando mensajes del sistema ~ al recibir la BindApplication enviada por el sistema
Cuando llamando entre procesos, llame a la función handlebindapplication para procesar la solicitud ~

ublic void handleMessage(Message msg) {
    
    
****
    case BIND_APPLICATION:
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
        AppBindData data = (AppBindData)msg.obj;
        handleBindApplication(data);
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        break;
****
}

Al procesar el mensaje ingresa a la función handleBindApplication(data) a ejecutar, aquí nos referimos al diagrama de los antecesores para describirlo en detalle:

inserte la descripción de la imagen aquí
Mire principalmente el cuarto paso, se crea una instancia de la aplicación y luego ingresa el método data.info.makeApplication ~

inserte la descripción de la imagen aquí

Luego ingrese la nueva función de aplicación para ver ~

inserte la descripción de la imagen aquí
Hasta ahora, podemos ver que se han hecho dos cosas en este proceso

(1) Complete la instanciación de la aplicación
(2) y llame a la función Application.attach()

Luego continúe con el método de adjuntar
inserte la descripción de la imagen aquí

Se puede ver que el primer paso del método de conexión es llamar al método de conexiónBaseContext ~
Finalmente, volvemos al paso, el sexto paso, y la ejecución llega al último paso ~

inserte la descripción de la imagen aquí
El método onCreate de la aplicación se ejecuta ~

Para resumirlo~

从上文可知, 加载的流程是:
    初始化————>Application的构造函数————>Application.attachBaseContext()————>Application.onCreate()函数
最后才会进入MainActivity中的attachBaseContext函数、onCreate函数
所以一般的加壳厂商要在程序正式执行前,也就是上面的流程中进行动态加载和类加载器的修正,这样才能对加密的dex进行释放,而更多的厂商往往选择在Application中的attachBaseContext或onCreate函数进行~

La siguiente figura es un diagrama de flujo de ejecución detallado del jefe en línea, como referencia ~

inserte la descripción de la imagen aquí

2. Explicación detallada del principio del embalaje general

1. Principio general de embalaje

Puede entenderse simplemente como el empaque general de Dex y la colocación de una capa de caparazón en el exterior del programa Apk de origen.

inserte la descripción de la imagen aquí
En esta imagen se puede ver que el APK de origen está cubierto con una capa de shell Dex, que finalmente forma un nuevo Dex (Apk)

Si probablemente no lo entiende aquí, puede consultar el artículo anterior [El camino inverso de Fat Tiger] 01 - Explicación detallada de la carga dinámica y el mecanismo de carga de clase

Aquí tomo prestada la imagen que los predecesores enviaron como informe de un caso, como se muestra en la imagen a continuación, abrimos una muestra del empaque general

inserte la descripción de la imagen aquí
Mirando el código, es obvio que a excepción de una aplicación de clase proxy, no se puede encontrar otra información de código relevante (no use jadx para verlo, porque el método de implementación es diferente)

Continúe mirando la imagen ~
inserte la descripción de la imagen aquí
Algunos métodos son llamados por reflexión en la clase de proxy. Obviamente, los resultados que analizamos no se encuentran, por lo que significa que la carga dinámica de la fuente dex debe completarse en Application.attchBaseContext() y Application. .onCrear()

Según el proceso lógico anterior, la aplicación debe analizar este proceso al cargar la aplicación:

(1) BootClassLoader carga la biblioteca principal del sistema
(2) PathClassLoader carga el propio dex de la aplicación
(3) Ingresa los componentes propios de la aplicación, analiza AndroidManifest.xml y luego busca el proxy de la aplicación
(4) Llama al addedBaseContext() que declara la aplicación para cargar dinámicamente el programa fuente
(5) Llame a onCreate() de la aplicación declarada para cargar dinámicamente el programa fuente
(6) Ingrese el archivo addedBaseContext() en MainActivity y luego ingrese la función onCreate() para ejecutar la fuente código de programa


2. Cargador de clases personalizado

En la descripción de ahora, entiendo claramente el proceso de carga de shell. Desde el principio hasta el final, PathClassLoader se usa para cargar dex. Como se mencionó en el artículo anterior, al cargar archivos dex de forma dinámica, debe usar un cargador de clases personalizado ~ Así
que en este momento, Xiao Ming usó directamente dexclassloader para cargar. Desafortunadamente, se informó un error ~
Veamos el motivo:

La clase cargada por DexClassLoader no tiene ciclo de vida del componente. Incluso si DexClassLoader completa la carga de la clase del componente a través de la carga dinámica del APK, cuando el sistema inicie el componente, todavía habrá una excepción de carga de la clase (variables estáticas, los bloques de código no se inicializan) )

Entonces, cuando queremos usar dexclassloader para cargar clases, debemos personalizar el cargador de clases

Hay dos maneras de lograr esto:

(1) Reemplazar el cargador de clases de componentes del sistema con nuestro DexClassLoader y establecer el padre de DexClassLoader como el cargador de componentes del sistema
(2) Romper la relación original de delegación de padres e insertar la nuestra en medio del cargador de clases de componentes del sistema PathClassLoader y BootClassLoader DexClassLoader


1) Reemplace el cargador de clases

El título ha sido escrito, ¿cómo proceder? "Después de nuestro análisis, hay un loadApk en ActivityThread. Después de verificar, se encuentra que loadApk es el principal responsable de cargar el programa apk. Podemos verificar más el código fuente

inserte la descripción de la imagen aquí
Al mirar el código fuente, podemos obtener mclassloader a través de la reflexión y luego reemplazarlo con nuestro propio DexClassLoader, y podemos dejar que Dexclassloader obtenga el ciclo de vida con éxito ~

La implementación específica del código fuente:

Resumen:
(1) Obtener la instancia de ActivityThread
(2) Obtener el cargador de clases a través de la reflexión
(3) Obtener LoadedApk
(4) Obtener el cargador de clases del sistema mClassLoader
(5) Reemplazar el cargador de clases personalizado con el cargador de clases del sistema

public static void replaceClassLoader(Context context,ClassLoader dexClassLoader){
    
    
       ClassLoader pathClassLoader = MainActivity.class.getClassLoader();
       try {
    
    
           //1.获取ActivityThread实例
           Class ActivityThread = pathClassLoader.loadClass("android.app.ActivityThread");
           Method currentActivityThread = ActivityThread.getDeclaredMethod("currentActivityThread");
           Object activityThreadObj = currentActivityThread.invoke(null);
           //2.通过反射获得类加载器
           //final ArrayMap<String, WeakReference<LoadedApk>> mPackages = new ArrayMap<>();
           Field mPackagesField = ActivityThread.getDeclaredField("mPackages");
           mPackagesField.setAccessible(true);
           //3.拿到LoadedApk
           ArrayMap mPackagesObj = (ArrayMap) mPackagesField.get(activityThreadObj);
           String packagename = context.getPackageName();
           WeakReference wr = (WeakReference) mPackagesObj.get(packagename);
           Object LoadApkObj = wr.get();
           //4.拿到mclassLoader
           Class LoadedApkClass = pathClassLoader.loadClass("android.app.LoadedApk");
           Field mClassLoaderField = LoadedApkClass.getDeclaredField("mClassLoader");
           mClassLoaderField.setAccessible(true);
           Object mClassLoader =mClassLoaderField.get(LoadApkObj);
           Log.e("mClassLoader",mClassLoader.toString());
           //5.将系统组件ClassLoader给替换
           mClassLoaderField.set(LoadApkObj,dexClassLoader);
       }
       catch (ClassNotFoundException e) {
    
    
           e.printStackTrace();
       } catch (NoSuchMethodException e) {
    
    
           e.printStackTrace();
       } catch (IllegalAccessException e) {
    
    
           e.printStackTrace();
       } catch (InvocationTargetException e) {
    
    
           e.printStackTrace();
       } catch (NoSuchFieldException e) {
    
    
           e.printStackTrace();
       }
   }

2) Inserción del cargador de clases

En la carga dinámica, describimos el mecanismo de delegación de padres del cargador de clases, es decir, nuestro cargador de clases no cargará directamente la clase justo después de obtenerla, sino que primero juzgará si está cargada y, si no lo está, dáselo a su clase principal, clase principal para juzgar, recursiva hacia arriba, para que puedas intentar hacer que DexClassLoader sea la clase principal de PathClassLoader ~

Resumen:
(1) Configure el nodo principal de DexClassloader en BootClassLoader
(2) Configure el nodo principal de PathClassLoader en DexClassloader

Código:

public static void replaceClassLoader(Context context, ClassLoader dexClassLoader){
    
    
        //将pathClassLoader父节点设置为DexClassLoader
        ClassLoader pathClassLoaderobj = context.getClassLoader();
        Class<ClassLoader> ClassLoaderClass = ClassLoader.class;
        try {
    
    
            Field parent = ClassLoaderClass.getDeclaredField("parent");
            parent.setAccessible(true);
            parent.set(pathClassLoaderobj,dexClassLoader);
        } catch (NoSuchFieldException e) {
    
    
            e.printStackTrace();
        } catch (IllegalAccessException e) {
    
    
            e.printStackTrace();
        }
 
    }

Después de completar el cargador de clases personalizado, podemos cargar el shell dex normalmente


3. Implementación de la caja de embalaje general.

Acabo de describir en detalle el mecanismo de instalación de la aplicación y el mecanismo de implementación del paquete general. El siguiente es un caso de paquete general como se describe en el artículo.


1. Escriba el programa fuente

Lo primero es lo primero:

1. Programa fuente (puede ser muy simple)
2. Programa Packer (todo muy simple)

Escriba el programa fuente:
inserte la descripción de la imagen aquí
este es nuestro programa fuente muy simple, agrega una línea de información impresa al registro y luego generamos el archivo dex


2. Escriba un programa de shell

Primero cargue el archivo dex en la tarjeta SD y configure el permiso de almacenamiento para la aplicación
inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí

Las versiones superiores deben solicitar permiso ~

Luego comenzamos a escribir nuestra clase de proxy, que puede imitar la aplicación de empaquetado anterior

inserte la descripción de la imagen aquí
Luego asigne la aplicación en el archivo de manifiesto a esta clase ~

inserte la descripción de la imagen aquí
Luego elegimos cargar dinámicamente nuestro dex y personalizar el cargador de clases en addedBaseContext o onCreate

Luego agregue la Actividad que importa la clase.
inserte la descripción de la imagen aquí


3. Carga dinámica

Carga dinámicamente class.dex en attachBashContext

inserte la descripción de la imagen aquí
Luego use un cargador para las clases personalizadas que acabamos de mencionar.
inserte la descripción de la imagen aquí

entonces corre

inserte la descripción de la imagen aquí
biu~ Corriendo exitosamente!

Hasta ahora, nuestro programa de shell súper simple se ha completado, les deseo un feliz juego


Resumir

Este artículo resume el principio básico y el proceso experimental del empaque general de dex actual. Algunas imágenes y la lógica se recopilan de Internet, pero el principio del empaque solo se usa para entretenimiento y aprendizaje (actualmente es el caparazón de quinta generación, ¿puedes creerlo? ?) Si hay alguna pregunta, bienvenido a dejar un mensaje ~
Shuan Q ~

Referencias

https://www.anquanke.com/post/id/221905?display=mobile
https://bbs.kanxue.com/thread-273293.htm#msg_header_h2_5
https://www.qj301.com/news/317. html

Supongo que te gusta

Origin blog.csdn.net/a_Chaon/article/details/128634604
Recomendado
Clasificación