Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
OrganizerTransaction — PoC pour CVE-2021-39749, permettant de lancer une Activity arbitraire sur Android 12L Beta | Kitploit
Outils/GitHubGitHub/michalbednarski/organizertransaction
Sécurité AndroidAnalyse des VulnérabilitésExploitationPentesting d'Applications MobilesSécurité Mobile
GitHubmichalbednarski/organizertransaction

OrganizerTransaction

PoC pour CVE-2021-39749, permettant de lancer une Activity arbitraire sur Android 12L Beta

Voir le dépôt

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager
371157il y a 4 ansVérifié par Kitploit

Ceci est un PoC pour CVE-2021-39749, qui permet de démarrer des activités d'autres applications sur Android 12L Beta, indépendamment de leurs paramètres permission et exported

Dans Android 12L, l'accès à TaskFragmentOrganizer (intentionnellement) ne nécessite plus la permission MANAGE_ACTIVITY_TASKS

L'utilisation de l'application fournie ici nécessite la désactivation des vérifications des API cachées, vous pouvez le faire via adb shell settings put global hidden_api_policy 1. Celles-ci ne constituent pas une frontière de sécurité et il existe des contournements connus basés sur des applications

Voici les commits corrigeant ce bug (et quelques autres liés mentionnés dans le rapport original) :

  1. startActivityInTaskFragment ne repose plus sur Binder.getCallingUid()
  2. ResolverActivity a maintenant relinquishTaskIdentity activé
  3. (Non nécessaire pour démarrer d'autres activités, mais permet de les repositionner sur l'écran et de les rendre transparentes et tap-jackables) SurfaceControl de TaskFragment n'est plus fourni
  4. (Non montré dans le code ici, problème seulement mentionné dans le rapport original) La décision d'envoyer ou non ActivityRecord#appToken à TaskFragmentOrganizer est désormais basée sur l'uid au lieu du pid

Vous pouvez vérifier android-12.1.0_r4, annuler les 3 premiers commits (ou les 2 premiers, l'application pourra toujours éteindre l'appareil (en démarrant ShutdownActivity), mais la case « Zoom and set alpha » ne fonctionnera pas)

(Le premier commit de cette liste aura des conflits de fusion dans les tests si vous essayez de l'annuler, mais vous pouvez les ignorer)

Binder.getCallingUid() qui retourne toujours l'uid système

La méthode Binder.getCallingUid() retourne l'uid du processus qui a envoyé la transaction Binder actuellement traitée. Cet uid est stocké dans une variable locale au thread. Le code gérant la transaction peut appeler Binder.clearCallingIdentity() pour définir cette variable sur l'uid de son propre processus afin d'indiquer aux méthodes appelées plus tard pendant le traitement de la transaction que les vérifications de permission doivent être effectuées contre lui-même (le code gérant la transaction) et non contre l'appelant de la transaction Binder

Parfois, il y a des Binder.getCallingUid() qui sont toujours appelés après Binder.clearCallingIdentity(), retournant donc toujours l'uid du propre processus. Parfois cela se produit intentionnellement, par exemple dans ActivityTaskManagerService#startDreamActivity (bien que ce soit une façon plutôt alambiquée de faire Process.myUid() ou Os.getuid())

J'ai écrit pour moi-même un outil d'analyse statique (basé sur Soot) qui signale ces appels Binder.getCallingUid() (et d'autres vérifications de permission) qui ne peuvent se produire qu'après Binder.clearCallingIdentity(). (J'ai une logique personnalisée pour gérer l'IR Jimple/Shimple RI fournie par Soot, bien qu'il puisse y avoir une meilleure façon de le faire avec Soot, mais c'est ce que j'ai actuellement)

Dans Android 12L Beta, cet outil en a trouvé un dans ActivityStartController#startActivityInTaskFragment (Remarque : le code source n'était pas disponible à ce moment-là car les versions Beta ne sont pas open source, mais Shimple est généralement lisible, donc j'utilisais Soot aussi comme décompilateur Java)

Comment appeler startActivityInTaskFragment

Dans le cadre du rapport d'analyse statique, j'ai obtenu la hiérarchie d'appels depuis l'implémentation de onTransact() (où l'appel Binder commence) jusqu'à startActivityInTaskFragment :

  1. onTransact dans le code généré par aidl de IWindowOrganizerController
  2. WindowOrganizerController#applyTransaction (sans l'argument CallerInfo)
  3. WindowOrganizerController#applyTransaction (avec l'argument CallerInfo)
  4. WindowOrganizerController#applyHierarchyOp
  5. ActivityStartController#startActivityInTaskFragment

J'ai découvert que les appels Binder à applyTransaction sont présents dans la classe TaskFragmentOrganizer et j'ai décidé de l'utiliser comme wrapper plus pratique que de faire tous les appels Binder directement (ni l'un ni l'autre n'est une API publique, donc j'ai dû utiliser la réflexion de toute façon)

Tout d'abord, la méthode « 2. » appelle enforceTaskPermission, qui sur Android 12.0 vérifiait la permission MANAGE_ACTIVITY_TASKS réservée à la signature, que nous ne pouvions pas obtenir. Cependant, sur Android 12L, les règles ont été assouplies afin que certaines transactions puissent être effectuées sans permissions. Il s'est avéré qu'aucune des opérations nécessaires pour effectuer startActivityInTaskFragment ne requérait de permission (si la transaction avait un TaskFragmentOrganizer associé)

Nous voulons donc effectuer HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT. Pour ce faire, nous devons avoir notre TaskFragment enregistré dans mLaunchTaskFragments, sinon l'exception "Not allowed to operate with invalid fragment token" sera signalée

Nous pouvons enregistrer un tel TaskFragment via HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT, qui appelle createTaskFragment()

(Dans le code du PoC, ces transactions sont envoyées dans SecondActivity : HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT est envoyé par initOrganizerAndFragment() et HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT est envoyé par startActivityInOrganizer)

Cela nous permet donc d'appeler startActivityInTaskFragment et les Intents des Activités démarrées ici sont considérés comme provenant de l'uid système, mais il s'avère qu'en soi cela ne nous permet pas de faire quoi que ce soit : les activités démarrées par le système ne peuvent pas faire de grants URI et si nous essayons de lancer une activité d'une autre application, nous serons arrêtés par la vérification canEmbedActivity

Contournement de canEmbedActivity

Reprenons canEmbedActivity : l'intégration est autorisée si taskFragment.getTask().effectiveUid est l'uid du système ou correspond à l'uid de l'application lancée. Nous devrons être dans une tâche dont effectiveUid est le système

Revenons aussi à createTaskFragment() : la création de TaskFragment n'était autorisée que si rootActivity.getUid() != ownerActivity.getUid(). Cela signifie que notre activité devra être en bas de la pile arrière de la Task dans laquelle elle se trouve

Nous devrons lancer une nouvelle Task (via Intent.FLAG_ACTIVITY_NEW_TASK) qui aura une Activité appartenant à l'uid système (afin que Task#effectiveUid soit défini sur AID_SYSTEM), puis cette Activité démarrera notre Activité (dans la même Task) et finish() elle-même (afin que notre Activité devienne la racine de cette tâche, nous permettant d'utiliser createTaskFragment())

Une de ces Activités est ChooserActivity. Le Chooser est généralement utilisé pour choisir à quelle application l'utilisateur veut recourir après avoir sélectionné l'option « partager ». ChooserActivity a cependant android:relinquishTaskIdentity="true" défini dans AndroidManifest.xml, ce qui signifie que lorsqu'elle lance une autre Activité, elle écrase Task#effectiveUid avec l'uid de l'application nouvellement lancée

(relinquishTaskIdentity ne fonctionne que lorsqu'il est utilisé par la première application dans la Task et uniquement pour les applications système, donc nous ne pouvons pas utiliser relinquishTaskIdentity nous-mêmes et lancer une application système pour écraser Task#effectiveUid de notre Task)

Une autre Activité de ce type (qui peut démarrer notre Activité et finish() elle-même) est ResolverActivity. Elle est utilisée lors du démarrage d'un Intent implicite qui se résout en plusieurs Activités. Le Resolver (contrairement au Chooser) offre une option pour mémoriser le choix, ce qui est la façon dont vous (en tant qu'utilisateur du téléphone) pouvez distinguer les deux. ResolverActivity n'avait pas relinquishTaskIdentity défini, cependant le Resolver utilise son propre Intent pour trouver quelles options sont disponibles (tandis que le Chooser prend l'Intent fourni dans les Extras). Cela s'avère être un problème pour l'exploitation car les flags d'Intent utilisés par le Resolver lors du lancement de l'Activité sélectionnée seront les mêmes que ceux utilisés pour lancer le Resolver et :

  • Si nous ne définissons pas Intent.FLAG_ACTIVITY_NEW_TASK, le Resolver sera lancé dans notre Task qui a déjà effectiveUid défini en permanence
  • Si nous définissons Intent.FLAG_ACTIVITY_NEW_TASK, le Resolver lancera la sélection dans une autre Task, qui aura alors effectiveUid défini sur celui appartenant à l'application lancée

La solution à ces problèmes est d'utiliser les deux :

  1. D'abord, nous lançons ChooserActivity : Nous fournissons à son Intent :
    • Intent.FLAG_ACTIVITY_NEW_TASK, afin que le Chooser soit lancé dans une nouvelle Task (qui aura effectiveUid du système mais seulement jusqu'au prochain lancement d'Activité)
    • Intent.EXTRA_INTENT défini sur un Intent qui ne correspond à aucune Activité et les seules options restantes dans le Chooser proviendront de Intent.EXTRA_INITIAL_INTENTS
    • Intent.EXTRA_INITIAL_INTENTS contenant un tableau avec un seul élément : l'Intent que nous voulons que le Chooser lance (lorsqu'il n'y a qu'une seule option, le Chooser et le Resolver sautent l'invite et lancent immédiatement la seule option et finish() eux-mêmes)
  2. Ensuite, ResolverActivity est lancée par ChooserActivity :

(Dans l'application du PoC, la préparation de ces étapes est effectuée dans FirstActivity)

Autres astuces avec TaskFragmentOrganizer

TaskFragmentOrganizer a reçu un SurfaceControl via le callback onTaskFragmentAppeared et en utilisant ce SurfaceControl, on peut mettre à l'échelle l'Activité lancée et la rendre transparente, tout en continuant à recevoir les événements tactiles et sans être considérée comme obscurcie (donc les éléments protégés contre le tap-jacking peuvent toujours être touchés)

Vous pouvez le voir en cochant la case « Zoom and set alpha » dans l'application du PoC

Cela est corrigé par le commit « 3. » de la liste des correctifs en haut


Une autre chose est que les ActivityRecord#appToken-s des Activités s'exécutant dans TaskFragment sont passés aux callbacks de TaskFragmentOrganizer. Cette liste était filtrée pour n'inclure que les jetons des Activités dans le même processus, cependant la vérification était effectuée en comparant le pid de TaskFragmentOrganizer avec le pid de l'Activité dont nous pouvions obtenir le appToken. Je n'ai pas réellement vérifié, mais je pense qu'une application pourrait créer un TaskFragmentOrganizer, quitter le processus utilisé pour le créer initialement et avoir son pid réutilisé comme pid d'une Activité d'une autre application afin d'obtenir son appToken. Voici le commit (« 4. » dans la liste des correctifs ci-dessus) qui change la vérification de basée sur le pid à basée sur l'uid (il semble que ce commit ait été fait indépendamment de mon rapport cependant (bien qu'après celui-ci))

Une fois que l'attaquant obtient le appToken d'une Activité, il peut injecter des appels onActivityResult() (même si l'application cible n'a pas appelé startActivityForResult() elle-même) et éventuellement falsifier savedInstanceState (en appelant activityStopped(), en supposant que l'attaquant puisse gagner la course contre l'application cible appelant cette méthode et que l'appel supplémentaire ne cause pas la perte d'état due à un crash)

Je n'ai pas vérifié si cela peut être fait dans ce cas, cependant précédemment, avec CVE-2020-0001 (Ouais, j'ai un numéro chic), j'ai pu utiliser la falsification de savedInstanceState et l'injection de onActivityResult() pour tromper l'application des paramètres système afin d'activer mon AccessibilityService sans interaction de l'utilisateur, mais c'est une histoire pour une autre fois

Télécharger l’outil
  • Le Resolver n'avait pas relinquishTaskIdentity défini, donc maintenant Task#effectiveUid est défini sur le système et le restera indépendamment des prochaines Activités lancées dans cette Task
  • L'Intent n'a pas Intent.FLAG_ACTIVITY_NEW_TASK, donc la prochaine Activité est lancée dans la même Task
  • L'action de l'Intent est définie sur une action non standard, correspondant uniquement au <intent-filter> que nous avons déclaré nous-mêmes dans notre application, donc le Resolver procède immédiatement au lancement de notre Activité
  • ResolverActivity lance notre Activité
    • Maintenant, nous sommes dans une Task dont effectiveUid est AID_SYSTEM, donc canEmbedActivity() autorise tout
    • Le Chooser et le Resolver ont tous deux finish() eux-mêmes, donc nous sommes l'Activité racine dans la Task et nous sommes autorisés à utiliser createTaskFragment()