[Route inversée de Fat Tiger] (02) - Explication détaillée et mise en œuvre du principe d'emballage global d'Android
Processus de principe d'emballage Android Apk et explication détaillée
Annuaire d'articles
- [Route inversée de Fat Tiger] (02) - Explication détaillée et mise en œuvre du principe d'emballage global d'Android
- avant-propos
- 1. Réserve de connaissances avant l'emballage
- 2. Explication détaillée du principe de l'emballage global
- 3. Mise en place de la caisse d'emballage globale
- Résumer
- Les références
avant-propos
Ce qui précède a mentionné une explication détaillée de la relation entre le chargement dynamique et le chargement de classe dans Android, qui est également la base de notre chapitre
Afin d'acquérir une compréhension approfondie du principe de l'emballage dans le contenu lié à l'inverse d'Android, l'explication détaillée de la relation entre le chargement dynamique et les classes de chargement dynamique dans Android a déjà été achevée, puis l'étape suivante consiste à implémenter l'emballage global de Android et expliquer le principe, en raison de la capacité limitée de l'auteur, je ferai de mon mieux pour décrire en détail le processus et le principe de l'emballage global. S'il y a des erreurs dans cet article, veuillez me corriger, merci ~
1. Réserve de connaissances avant l'emballage
1. Le processus de démarrage de l'application Android
Le processus de démarrage est un cliché, et de nombreux entretiens poseront cette question. Dans le travail réel, le développement général d'applications n'a pas besoin d'être concerné (ps : l'entretien construit un porte-avions, et les vis de travail). Mais pour la rétro-ingénierie , il est en fait familier avec le processus de démarrage des applications Android. Il peut éviter de nombreux malentendus, afin d'améliorer l'efficacité du travail, d'atteindre le sommet de la vie et de gagner Bai Fumei... Oubliez ça, réveillons-nous hahaha
Cependant, puisque nous voulons comprendre le principe de l'empaquetage d'applications, nous devons d'abord commencer par le processus de démarrage de l'application. Avant le démarrage de l'application, le système Android est le premier à démarrer. Ensuite, examinons de plus près le processus de démarrage du système Android. ~
Je crois que si vous êtes un joueur Android, certaines personnes connaissent plus ou moins cette image .
1. Le premier chargeur de démarrage est démarré, c'est-à-dire que l'alimentation est allumée
2. Accédez à la couche noyau pour démarrer le processus inactif
3. Accédez à la couche navigation pour initialiser le processus init, puis analysez et exécutez init.rc, et enfin, allez à notre app_process
4. Dans app_process, le zygote sera généré via le processus jni fork, puis le zygote hachera les processus de service associés
5. Après chaque processus de démarrage de l'application, un processus sera hachuré par le zygote
6. Et le ClassLoader sera transmis (connaissance ci-dessus)
Donc, à partir de la langue vernaculaire ci-dessus, nous comprenons que le processus Zygote a fait éclore le premier processus du processus SystemServer, et le processus SystemServer est la priorité absolue d'Android. Le travail qu'il accomplit est principalement (notre préoccupation) StartBootstrapServices (démarrer le service de démarrage ) Parmi eux , le le plus important est ActivityManagerServices (quatre principaux services de gestion de la planification des composants, dont la planification China Ac est confiée à ATMS) et PackageManager (fournissant la gestion des packages, y compris la numérisation, l'installation et la désinstallation), principalement AMS, PM , le reste n'est pas trop concernés, les étudiants dans le besoin peuvent vérifier par eux-mêmes , tout peut être trouvé
2. Le processus de démarrage de l'application Android
La dernière étape du démarrage du système Android consiste à démarrer une application (lanceur), qui est utilisée pour afficher les applications installées dans le système. Au cours du processus de démarrage, le lanceur demandera à PMS (PackageManagerService) de renvoyer les informations des applications installées. dans le système. Et encapsulez ces informations dans une liste d'icônes de raccourcis et affichez-les sur l'écran du système ~
De cette façon, l'utilisateur peut démarrer l'application correspondante en cliquant sur ces icônes de raccourci
(Avec l'aide des images dessinées par les prédécesseurs) l'organigramme est le suivant :
1. Lorsque vous cliquez sur l'icône du bureau, l'application Launcher appelle la méthode startActivity, via la communication Binder, appelle la méthode startActivity du service AMS dans le processus system_server, et la méthode envoie une demande de démarrage ~ 2. Après le système- le processus du serveur reçoit la requête, il enverra une requête à
Zygote Envoyer une requête pour créer un processus (fork) ~
3. Après avoir reçu la requête, zygote sait qu'il est en train de vivre et commence à bifurquer le processus de l'application, et la méthode principale de la classe ActivityThread dans le processus d'application sera appelée pour créer un thread ActivityThread pour effectuer Initialize MainLooper, le gestionnaire de thread principal et initialiser AplicationThread dans le processus d'application pour une communication interactive avec AMS ~ 4. Une fois le processus d'application
terminé créé, il enverra une demande attachApplication à system_server via Binder (en fait, le processus App appelle le processus system_server via le processus Binder La méthode attachApplication du service AMS dans AMS) La fonction réelle de attachApplication dans AMS est de lier l'objet ApplicationThread dans
le processus de l'application avec AMS pour faciliter la communication.Ensuite
6. Après avoir reçu la demande, le thread de liaison (ApplicationThread) du processus d'application envoie les messages BIND_APPLICATION et LAUNCH_ACTIVITY au thread principal via le gestionnaire interne. Notez ici qu'AMS et le thread principal ne communiquent pas directement, mais entre AMS et le principal thread La classe interne ApplicationThread communique via Binder et ApplicationThread interagit avec le thread principal via les messages du gestionnaire ~
7. Une fois que le thread principal a reçu le message, il commence à créer l'application et appelle la méthode oncreate, puis crée l'activité cible via la réflexion mécanisme et rappelle des méthodes telles que Activity.onCreate ~
8. À ce stade, le démarrage de l'application se terminera officiellement, le cycle de vie d'Ac commencera et le rendu de l'interface utilisateur commencera.
À ce stade, nous avons à peu près compris le processus de démarrage de l'APP ~
3.Processus de démarrage ActivityThread
Dans l'emballage, il y a un rôle très important, qui est ActivityThrad. Dans la précédente série d'articles, beaucoup d'entre eux ont mentionné que ActivityThread.main() est la porte d'entrée dans le monde des applications, et ont ainsi expliqué le principe de l'emballage ~
Ensuite, en suivant les traces du patron, nous commencerons également à analyser le code source pour comprendre ce qui se fait dans l'ActivityThread~
Nous pouvons voir que la méthode principale dans ActivityThread appelle accath (falsh, xx) pour effectuer une série de préparations d'initialisation, et enfin le thread principal entre dans la boucle de messages, attendant les messages du système ~ lors de la réception du BindApplication envoyé par le système
Lorsque appel entre processus, appelez la fonction handlebindapplication pour traiter la requête~
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;
****
}
Lors du traitement du message, il entre dans la fonction handleBindApplication(data) à exécuter. Ici, nous nous référons au schéma des prédécesseurs pour le décrire en détail :
Regardez principalement la quatrième étape, l'application est instanciée, puis entre dans la méthode data.info.makeApplication ~
Entrez ensuite la fonction newApplication pour afficher ~
Jusqu'à présent, nous pouvons voir que deux choses ont été faites dans ce processus
(1) Terminez l'instanciation de l'Application
(2) et appelez la fonction Application.attach()
Ensuite, continuez avec la méthode d'attachement
On peut voir que la première étape de la méthode attach consiste à appeler la méthode attachBaseContext~
Enfin, nous revenons à l'étape, la sixième étape, et l'exécution atteint la dernière étape~
La méthode onCreate d'Application est exécutée~
Pour résumer ~
从上文可知, 加载的流程是:
初始化————>Application的构造函数————>Application.attachBaseContext()————>Application.onCreate()函数
最后才会进入MainActivity中的attachBaseContext函数、onCreate函数
所以一般的加壳厂商要在程序正式执行前,也就是上面的流程中进行动态加载和类加载器的修正,这样才能对加密的dex进行释放,而更多的厂商往往选择在Application中的attachBaseContext或onCreate函数进行~
La figure suivante est un organigramme d'exécution détaillé du patron en ligne, pour référence ~
2. Explication détaillée du principe de l'emballage global
1. Principe général d'emballage
Cela peut être simplement compris comme un emballage global Dex et mettre une couche de coque à l'extérieur du programme source Apk
On peut voir sur cette image que l'APK source est recouvert d'une couche de shell Dex, qui forme finalement un nouveau Dex (Apk)
Si vous ne le comprenez probablement pas ici, vous pouvez vous référer à l'article précédent [Fat Tiger's Reverse Road] 01 - Explication détaillée du chargement dynamique et du mécanisme de chargement de classe
Ici, j'emprunte l'image que les prédécesseurs ont envoyée comme rapport de cas, comme le montre l'image ci-dessous, nous ouvrons un échantillon de l'emballage global
En regardant le code, il est évident qu'à l'exception d'une application de classe proxy, aucune autre information de code pertinente ne peut être trouvée (n'utilisez pas jadx pour le voir, car la méthode d'implémentation est différente)
Continuez à regarder l'image ~
Certaines méthodes sont appelées par réflexion dans la classe proxy. Évidemment, les résultats que nous avons analysés ne sont pas trouvés, cela signifie donc que le chargement dynamique du dex source doit être complété dans Application.attchBaseContext() et Application .onCreate()
Sur la base du processus logique ci-dessus, l'application doit analyser ce processus lors du chargement de l'application :
(1) BootClassLoader charge la bibliothèque principale du système
(2) PathClassLoader charge le propre dex de l'APP
(3) Entre les propres composants de l'APP, analyse AndroidManifest.xml, puis recherche le proxy d'application
(4) Appelle le attachBaseContext() qui déclare l'Application pour charger dynamiquement le programme source
( 5) Appelez onCreate() de l'Application déclarée pour charger dynamiquement le programme source
(6) Entrez le attachBaseContext() dans MainActivity, puis entrez la fonction onCreate() pour exécuter la source code de programme
2. Chargeur de classe personnalisé
Dans la description qui vient d'être faite, je comprends clairement le processus de chargement du shell. Du début à la fin, PathClassLoader est utilisé pour charger dex. Comme mentionné dans l'article précédent, lors du chargement dynamique de fichiers dex, vous devez utiliser un chargeur de classe personnalisé ~
Donc à ce moment, Xiao Ming a directement utilisé dexclassloader pour charger. Malheureusement, une erreur a été signalée~
Voyons la raison :
La classe chargée par DexClassLoader n'a pas de cycle de vie de composant. Même si DexClassLoader termine le chargement de la classe de composant via le chargement dynamique de l'APK, lorsque le système démarre le composant, il y aura toujours une exception de chargement de la classe (variables statiques, les blocs de code ne sont pas initialisés) )
Ainsi, lorsque nous voulons utiliser dexclassloader pour le chargement de classe, nous devons personnaliser le chargeur de classe
Il existe deux façons d'y parvenir :
(1) Remplacez le chargeur de classe de composant système par notre DexClassLoader et définissez le parent de DexClassLoader comme chargeur de composant système
(2) Cassez la relation de délégation parentale d'origine et insérez la nôtre au milieu du chargeur de classe de composant système PathClassLoader et BootClassLoader DexClassLoader
1) Remplacer le chargeur de classe
Le titre est écrit, comment procéder ? Après notre analyse, il y a un loadApk dans l'ActivityThread. Après vérification, il s'avère que loadApk est principalement responsable du chargement du programme apk. Nous pouvons vérifier davantage le code source
En regardant le code source, nous pouvons obtenir mclassloader par réflexion, puis le remplacer par notre propre DexClassLoader, et nous pouvons réussir à laisser Dexclassloader obtenir le cycle de vie ~
L'implémentation spécifique du code source :
Résumé :
(1) Obtenez l'instance ActivityThread
(2) Obtenez le chargeur de classe par réflexion
(3) Obtenez LoadedApk
(4) Obtenez le chargeur de classe système mClassLoader
(5) Remplacez le chargeur de classe personnalisé par le chargeur de classe système
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) Insertion du chargeur de classe
En chargement dynamique, nous avons décrit le mécanisme de délégation parentale du chargeur de classe, c'est-à-dire que notre chargeur de classe ne chargera pas directement la classe juste après l'avoir reçue, mais jugera d'abord si elle est chargée, et si elle ne l'est pas, donnez-le à sa classe parente, classe parente à juger, récursive vers le haut, afin que vous puissiez essayer de faire de DexClassLoader la classe parente de PathClassLoader ~
Résumé :
(1) Définissez le nœud parent de DexClassloader sur BootClassLoader
(2) Définissez le nœud parent de PathClassLoader sur DexClassloader
Code:
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();
}
}
Après avoir terminé le chargeur de classe personnalisé, nous pouvons charger le shell dex normalement
3. Mise en place de la caisse d'emballage globale
Je viens de décrire en détail le mécanisme d'installation de l'application et le mécanisme de mise en œuvre de l'emballage global.Ce qui suit est un cas d'emballage global tel que décrit dans l'article.
1. Ecrire le programme source
Tout d'abord:
1. Programme source (peut être très simple)
2. Programme Packer (très simple)
Écrire le programme source :
il s'agit de notre programme source très simple, en ajoutant une ligne d'informations imprimées au journal, puis nous générons le fichier dex
2. Ecrire un programme shell
Téléchargez d'abord le fichier dex sur la carte SD et définissez l'autorisation de stockage pour l'application
Les versions supérieures doivent demander une autorisation ~
Ensuite, nous commençons à écrire notre classe proxy, qui peut imiter l'application packer ci-dessus
Attribuez ensuite l'application dans le fichier manifeste à cette classe ~
Ensuite, nous choisissons de charger dynamiquement notre dex et de personnaliser le chargeur de classe dans attachBaseContext ou onCreate
Ajoutez ensuite l'activité qui importe la classe
3. Chargement dynamique
Charger dynamiquement class.dex dans attachBashContext
Ensuite, utilisez un chargeur pour les classes personnalisées que nous venons de mentionner
puis cours
biu~ En cours d'exécution avec succès !
Jusqu'à présent, notre programme shell super simple est terminé, je vous souhaite un bon jeu
Résumer
Cet article résume le principe de base et le processus expérimental de l'emballage global dex actuel. Certaines images et logiques sont collectées sur Internet, mais le principe d'emballage n'est utilisé que pour le divertissement et l'apprentissage (actuellement, il s'agit de la coque de cinquième génération, pouvez-vous le croire ?) S'il y a des questions, n'hésitez pas à laisser un message ~
Shuan Q ~
Les références
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