Compte du Sommet de l'auteur : windy_ll
Application d'ebpf dans la sécurité Android : combiné avec un classeur pour compléter un bac à sable d'inspection de comportement (Partie 1) 1. Brève introduction à IPC
IPC est l'abréviation de Inter-Process Communication, qui signifie communication inter-processus ou communication inter-processus. Elle fait référence au processus d'échange de données entre deux processus.
Quand Android a-t-il besoin d'une communication inter-processus ? Lorsqu'Android demande des services système, une communication inter-processus sera nécessaire, comme l'accès aux carnets d'adresses des téléphones mobiles, l'obtention de la localisation, etc. L'objectif de cet article est d'implémenter un bac à sable simple pour capturer ce comportement
2. Brève introduction au classeur
Binder est une forme de communication inter-processus dans Android, qui peut être comprise comme une méthode de mise en œuvre spécifique d'IPC
3. Brève introduction à ServiceManager
ServiceManager est un service système extrêmement important dans Android. Comme son nom l'indique, il est utilisé pour gérer les services système
.ServiceManager est démarré par le processus d'initialisation
ServiceManager est responsable des fonctions suivantes : enregistrement et recherche de services, communication inter-processus, démarrage et activation des services système, et fourniture de listes d'instances de services système
Le pilote de classeur détermine les détails de la communication sous-jacente, de sorte que le ServiceManager est équivalent à la navigation, indiquant à la communication spécifique comment aller, où s'y rendre, etc.
4. Analyse de la communication 4.1 Analyse de la couche JAVA des appels clients
Prenons la fonction getConnectInfo de la classe WifiManager (cette fonction obtient des informations wifi) comme exemple d'analyse
Ctrl+clic gauche pour afficher la référence, vous pouvez constater que la fonction est définie dans la classe .wifi.WifiManager, comme indiqué à droite :
Comme vous pouvez le voir sur l'image ci-dessus, le code spécifique de la fonction getConnectInfo ne comporte qu'une seule phrase : thrownewRuntimeException("Stub!");, qui nous indique que cette fonction est remplacée et exécutée par la même classe en rom La fonction. définie ici est nécessaire à la compilation (PS : Pour référence), on peut retrouver cette classe dans le répertoire frameworks/base/wifi/java/amdroid/net/wifi dans le code source android, puis trouver l'implémentation spécifique de la fonction, comme indiqué à droite :
Vous pouvez constater que cette fonction appelle la fonction getConnectionInfo de IWifiManager. Vous pouvez trouver le fichier IWifiManager.aidl dans le répertoire frameworks/base/wifi/java/amdroid/net/wifi. La fonction getConnectionInfo est définie dans aidl, comme indiqué. à droite :
Un concept doit être introduit ici --- AIDL est un langage socket d'Android, qui est utilisé pour exposer le socket du service Android afin de réaliser des appels de fonctions inter-processus. AIDL générera deux classes lors de la compilation, à savoir Stub et Proxy. La classe Stub est la manifestation de la couche de représentation côté serveur, et le Proxy est l'instance obtenue par le client Android qui implémente IPC via ces modèles de conception de proxy-stub
.Écrivez un fichier aidl ci-dessous, puis générez le code java correspondant pour voir comment l'appel est implémenté. Tout d'abord, nous trouvons un projet aléatoire dans Android Studio, puis créons un nouveau fichier aidl, comme indiqué à droite :
Après cela, Build->MakeProject peut être généré. Le chemin généré se trouve dans build/generated/aidl_source_output_dir/debug/out/package name, comme indiqué à droite :
En observant le fichier java généré, nous pouvons constater que la classe Proxy a déjà été générée. Dans la classe Proxy, nous pouvons retrouver les fonctions que nous avons définies, comme le montre l'image de droite :
Analysons cette fonction en détail.Tout d'abord, une instance de Parcel est générée via la fonction d'obtention, puis les fonctions de série d'écriture de Parcel sont appelées pour écrire. Bien qu'il s'agisse d'un processus de sérialisation, la fonction de transaction d'IBinder est ensuite appelée et la fonction est tracée. et analysé. Le fichier java se trouve dans le répertoire frameworks/base/core/java/android/os, comme indiqué à droite :
On peut constater qu'IBinder n'est qu'une commande de suppression socket Linux, qui définit la méthode transaction. Cette méthode a 4 paramètres. Le premier code de paramètre est le numéro de fonction dans notre appel à distance. Une fois que le serveur a reçu ce numéro, il trouvera. les variables statiques de la classe Stub, afin que vous puissiez analyser quelle fonction est appelée. Les deuxième et troisième paramètres _data et _reply sont les paramètres transmis et la valeur renvoyée, qui sont toutes des données sérialiséescouche du noyau Android Linux , la dernière. Les indicateurs de paramètre indiquent s'il est nécessaire de bloquer et d'attendre le résultat, 0 signifie bloquer et attendre, et 1 signifie revenir immédiatement.
Après une recherche globale, vous pouvez constater que la classe BinderProxy dans le même répertoire implémente ce socket (PS : il est à noter qu'il existe également une classe Binder dans le même répertoire, qui implémente également ce socket, mais la classe Binder est une implémentation côté serveur, pas une implémentation client), comme le montre l'image de droite :
Après avoir analysé cette fonction, nous pouvons constater qu'elle évolue enfin vers la fonction transactNative. À ce stade, l'analyse de la couche Java du client de communication IPC est terminée
.4.2 Analyse de la couche native des appels clients
Recherchez la fonction transactNative globalement et vous constaterez que la fonction enregistre les informations dans la couche native, comme indiqué à droite :
Tracez la fonction android_os_BinderProxy_transact. Vous pouvez constater que la fonction obtient d'abord un objet BpBinder via getBPNativeData(env,obj)->mObject.get(), puis appelle la fonction transaction de BpBinder, comme indiqué à droite :
Continuez à faire le suivi et vous constaterez que vous avez entré la fonction de transaction d'IPTChreadState, comme indiqué à droite :
Ensuite, vous pouvez constater que la fonction writeTransactionData est d'abord appelée, qui est utilisée pour remplir la structure binder_transaction_data et se préparer à l'envoyer au pilote de classeur, puis elle appelle waitForResponse pour attendre le retour, comme indiqué à droite :
En suivant la fonction waitForResponse, nous pouvons constater que la chose la plus importante à propos de cette fonction est d'appeler la fonction talkWithDriver. En analysant la fonction talkWithDriver, nous pouvons constater que l'ioctl est finalement appelé, comme indiqué à droite :
Jusqu'à présent, l'analyse de la couche native du client est terminée
4.3 Analyse de la couche du noyau (analyse du pilote de liant)
À ce stadecouche du noyau Android Linux, notre programme ebpf peut commencer à capturer et analyser le format des données
Lorsque la couche utilisateur appelle ioctl, elle entrera dans l'état du noyau et entrera dans la fonction noyau binder_ioctl (ps : le descripteur correspondant peut être trouvé dans binder.c dans le code source du périphérique noyau. Analysez la fonction binder_ioctl et vous trouverez le). fonction principale de cette fonction. Afin d'envoyer des données entre les deux processus, notre commande ioctl de données de communication est BINDER_WRITE_READ Lorsque cette commande est rencontrée, la fonction binder_ioctl_write_read sera appelée, comme indiqué à droite :
.Suite à la fonction binder_ioctl_write_read, nous pouvons constater que la fonction lit d'abord la valeur de l'adresse pointée par le paramètre arg de type unsignedlong dans la structure binder_write_read, indiquant que lorsque la commande ioctl est BINDER_WRITE_READ, le paramètre transmis est le pointeur pointant vers la structure binder_write_read , comme indiqué à droite :
Bien que notre analyse de l'état du noyau puisse être terminée ici, nous avons déjà observé les données que nous souhaitons, c'est-à-dire la structure binder_write_read. Vous pouvez jeter un œil à la définition de cette structure, comme indiqué ci-dessous :
.
<em style="cursor: pointer;font-size: 12px"> 复制代码</em><em style="cursor: pointer;font-size: 12px"> 隐藏代码<br></em><code><span>struct</span> <span>binder_write_read</span> {<br> <span>binder_size_t</span> write_size; <span>/* 写内容的数据总大小 */</span><br> <span>binder_size_t</span> write_consumed; <span>/* 记录了从缓冲区读取写内容的大小 */</span><br> <span>binder_uintptr_t</span> write_buffer; <span>/* 写内容的数据的虚拟地址 */</span><br> <span>binder_size_t</span> read_size; <span>/* 读内容的数据总大小 */</span><br> <span>binder_size_t</span> read_consumed; <span>/* 记录了从缓冲区读取读内容的大小 */</span><br> <span>binder_uintptr_t</span> read_buffer; <span>/* 读内容的数据的虚拟地址 */</span><br>};</code>
Cette structure est utilisée pour décrire les données transmises lors de la communication inter-processus. Lorsque nous lisons le paquet de communication envoyé du client au serveur, il suffit de prêter attention à write_size, write_consumed et write_buffer. Parmi eux, write_buffer pointe. une liste chaînée. Ce champ contient la structure binder_transaction_data. Cette structure est renseignée dans la fonction writeTransactionData de la couche native. Parmi elles, les structures de données pointées par write_buffer et read_buffer sont à peu près les suivantes :
D'une manière générale, le pilote transmettra plusieurs combinaisons de commandes et d'adresses à la fois, et nous devons trouver les instructions correspondant à la structure binder_transaction_data Dans le fichier d'en-tête binder.h, nous pouvons trouver toutes les instructions définies par le pilote (+. /refs /heads/android-mainline/include/uapi/linux/android/binder.h), comme indiqué à droite :
可以发觉,BC_TRANSACTION和BC_REPLY指令都对应着binder_transaction_data结构体参数,但我们只须要顾客端发往驱动的数据包,所以我们只须要BC_TRANSACTION指令对应的参数即可
经过前面的剖析,我们找到了我们须要的核心数据---binder_transaction_data结构体,现今来看一下该结构体的定义,定义如下:
<em style="cursor: pointer;font-size: 12px"> 复制代码</em><em style="cursor: pointer;font-size: 12px"> 隐藏代码<br></em><code><span>struct</span> <span>binder_transaction_data</span> {<br> <span>union</span> {<br> __u32 handle;<br> <span>binder_uintptr_t</span> ptr;<br> } target; <span>/* 该事务的目标对象 */</span><br> <span>binder_uintptr_t</span> cookie; <span>/* 只有当事务是由Binder驱动传递给用户空间时,cookie才有意思,它的值是处理该事务的Server位于C++层的本地Binder对象 */</span><br> __u32 code; <span>/* 方法编号 */</span><br> __u32 flags;<br> <span>pid_t</span> sender_pid;<br> <span>uid_t</span> sender_euid;<br> <span>binder_size_t</span> data_size; <span>/* 数据长度 */</span><br> <span>binder_size_t</span> offsets_size; <span>/* 若包含对象,对象的数据大小 */</span><br> <span>union</span> {<br> <span>struct</span> {<br> <span>binder_uintptr_t</span> buffer; <span>/* 参数地址 */</span><br> <span>binder_uintptr_t</span> offsets; <span>/* 参数对象地址 */</span><br> } ptr;<br> __u8 buf[<span>8</span>];<br> } data; <span>/* 数据 */</span><br>};</code>
有了该数据结构linux安装,我们就可以晓得顾客端调用服务端的函数的函数、参数等数据了
五、实现疗效
PS:整套系统用于商业,就不做开源处理了,这儿只给出核心结构体复印的截图,就不再发后端的截图了
读取手机通信录(ps:这儿读取下来的数据采用了Toast复印的,所以将捕捉到的Toast相应的通讯包也一起复印了下来,下同):
获取地理位置:
获取wifi信息:
六、其他
里面当然我们只剖析到了发送部份,反过来,虽然我们也可以读取返回包甚至于更改返回包,就可用于对风控的对抗,比如在内核态中更改APP恳求的设备标示信息(其实前提是app走系统提供的驱动通讯),亦或则用于逆向的工作,比如过root检查等等。
这部份验证代码就暂不放下来了,感兴趣的可以自己实现一下
-官方峰会
公众号设置“星标”,您不会错过新的消息通知
如开放注册、精华文章和周边活动等公告
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!