macOS MIG - Mach Interface Generator
Reading time: 11 minutes
tip
Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE)
Soutenir HackTricks
- VĂ©rifiez les plans d'abonnement !
- Rejoignez le đŹ groupe Discord ou le groupe telegram ou suivez nous sur Twitter đŠ @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PRs au HackTricks et HackTricks Cloud dépÎts github.
Informations de base
MIG a Ă©tĂ© crĂ©Ă© pour simplifier le processus de crĂ©ation de code Mach IPC. Il gĂ©nĂšre le code nĂ©cessaire pour que le serveur et le client communiquent avec une dĂ©finition donnĂ©e. MĂȘme si le code gĂ©nĂ©rĂ© est moche, un dĂ©veloppeur n'aura qu'Ă l'importer et son code sera beaucoup plus simple qu'auparavant.
La définition est spécifiée en Interface Definition Language (IDL) en utilisant l'extension .defs
.
Ces définitions ont 5 sections :
- Déclaration de sous-systÚme : Le mot-clé sous-systÚme est utilisé pour indiquer le nom et l'id. Il est également possible de le marquer comme
KernelServer
si le serveur doit s'exécuter dans le noyau. - Inclusions et imports : MIG utilise le préprocesseur C, donc il est capable d'utiliser des imports. De plus, il est possible d'utiliser
uimport
etsimport
pour le code généré par l'utilisateur ou le serveur. - Déclarations de types : Il est possible de définir des types de données bien que généralement il importera
mach_types.defs
etstd_types.defs
. Pour des types personnalisĂ©s, une certaine syntaxe peut ĂȘtre utilisĂ©e : - [i`n/out]tran : Fonction qui doit ĂȘtre traduite d'un message entrant ou vers un message sortant
c[user/server]type
: Mapping vers un autre type C.destructor
: Appelez cette fonction lorsque le type est libéré.- Opérations : Ce sont les définitions des méthodes RPC. Il existe 5 types différents :
routine
: S'attend à une réponsesimpleroutine
: Ne s'attend pas à une réponseprocedure
: S'attend à une réponsesimpleprocedure
: Ne s'attend pas à une réponsefunction
: S'attend à une réponse
Exemple
Créez un fichier de définition, dans ce cas avec une fonction trÚs simple :
subsystem myipc 500; // Arbitrary name and id
userprefix USERPREF; // Prefix for created functions in the client
serverprefix SERVERPREF; // Prefix for created functions in the server
#include <mach/mach_types.defs>
#include <mach/std_types.defs>
simpleroutine Subtract(
server_port : mach_port_t;
n1 : uint32_t;
n2 : uint32_t);
Notez que le premier argument est le port à lier et MIG gérera automatiquement le port de réponse (à moins d'appeler mig_get_reply_port()
dans le code client). De plus, l'ID des opérations sera séquentiel en commençant par l'ID de sous-systÚme indiqué (donc si une opération est obsolÚte, elle est supprimée et skip
est utilisé pour continuer à utiliser son ID).
Maintenant, utilisez MIG pour générer le code serveur et client qui sera capable de communiquer entre eux pour appeler la fonction Subtract :
mig -header myipcUser.h -sheader myipcServer.h myipc.defs
Plusieurs nouveaux fichiers seront créés dans le répertoire actuel.
tip
Vous pouvez trouver un exemple plus complexe dans votre systĂšme avec : mdfind mach_port.defs
Et vous pouvez le compiler depuis le mĂȘme dossier que le fichier avec : mig -DLIBSYSCALL_INTERFACE mach_ports.defs
Dans les fichiers myipcServer.c
et myipcServer.h
, vous pouvez trouver la déclaration et la définition de la structure SERVERPREFmyipc_subsystem
, qui définit essentiellement la fonction à appeler en fonction de l'ID de message reçu (nous avons indiqué un numéro de départ de 500) :
/* Description of this subsystem, for use in direct RPC */
const struct SERVERPREFmyipc_subsystem SERVERPREFmyipc_subsystem = {
myipc_server_routine,
500, // start ID
501, // end ID
(mach_msg_size_t)sizeof(union __ReplyUnion__SERVERPREFmyipc_subsystem),
(vm_address_t)0,
{
{ (mig_impl_routine_t) 0,
// Function to call
(mig_stub_routine_t) _XSubtract, 3, 0, (routine_arg_descriptor_t)0, (mach_msg_size_t)sizeof(__Reply__Subtract_t)},
}
};
En fonction de la structure précédente, la fonction myipc_server_routine
obtiendra l'ID de message et renverra la fonction appropriée à appeler :
mig_external mig_routine_t myipc_server_routine
(mach_msg_header_t *InHeadP)
{
int msgh_id;
msgh_id = InHeadP->msgh_id - 500;
if ((msgh_id > 0) || (msgh_id < 0))
return 0;
return SERVERPREFmyipc_subsystem.routine[msgh_id].stub_routine;
}
Dans cet exemple, nous avons seulement défini 1 fonction dans les définitions, mais si nous avions défini plus de fonctions, elles auraient été à l'intérieur du tableau de SERVERPREFmyipc_subsystem
et la premiÚre aurait été assignée à l'ID 500, la deuxiÚme à l'ID 501...
Si la fonction devait envoyer une réponse, la fonction mig_internal kern_return_t __MIG_check__Reply__<name>
existerait Ă©galement.
En fait, il est possible d'identifier cette relation dans la structure subsystem_to_name_map_myipc
de myipcServer.h
(**subsystem*to_name_map*\***
** dans d'autres fichiers) :
#ifndef subsystem_to_name_map_myipc
#define subsystem_to_name_map_myipc \
{ "Subtract", 500 }
#endif
Enfin, une autre fonction importante pour faire fonctionner le serveur sera myipc_server
, qui est celle qui va réellement appeler la fonction liée à l'identifiant reçu :
mig_external boolean_t myipc_server
(mach_msg_header_t *InHeadP, mach_msg_header_t *OutHeadP)
{
/*
* typedef struct {
* mach_msg_header_t Head;
* NDR_record_t NDR;
* kern_return_t RetCode;
* } mig_reply_error_t;
*/
mig_routine_t routine;
OutHeadP->msgh_bits = MACH_MSGH_BITS(MACH_MSGH_BITS_REPLY(InHeadP->msgh_bits), 0);
OutHeadP->msgh_remote_port = InHeadP->msgh_reply_port;
/* Taille minimale : routine() l'actualisera si différente */
OutHeadP->msgh_size = (mach_msg_size_t)sizeof(mig_reply_error_t);
OutHeadP->msgh_local_port = MACH_PORT_NULL;
OutHeadP->msgh_id = InHeadP->msgh_id + 100;
OutHeadP->msgh_reserved = 0;
if ((InHeadP->msgh_id > 500) || (InHeadP->msgh_id < 500) ||
((routine = SERVERPREFmyipc_subsystem.routine[InHeadP->msgh_id - 500].stub_routine) == 0)) {
((mig_reply_error_t *)OutHeadP)->NDR = NDR_record;
((mig_reply_error_t *)OutHeadP)->RetCode = MIG_BAD_ID;
return FALSE;
}
(*routine) (InHeadP, OutHeadP);
return TRUE;
}
Vérifiez les lignes précédemment mises en surbrillance accédant à la fonction à appeler par ID.
Le code suivant crĂ©e un serveur et un client simples oĂč le client peut appeler les fonctions Soustraire du serveur :
// gcc myipc_server.c myipcServer.c -o myipc_server
#include <stdio.h>
#include <mach/mach.h>
#include <servers/bootstrap.h>
#include "myipcServer.h"
kern_return_t SERVERPREFSubtract(mach_port_t server_port, uint32_t n1, uint32_t n2)
{
printf("Received: %d - %d = %d\n", n1, n2, n1 - n2);
return KERN_SUCCESS;
}
int main() {
mach_port_t port;
kern_return_t kr;
// Register the mach service
kr = bootstrap_check_in(bootstrap_port, "xyz.hacktricks.mig", &port);
if (kr != KERN_SUCCESS) {
printf("bootstrap_check_in() failed with code 0x%x\n", kr);
return 1;
}
// myipc_server is the function that handles incoming messages (check previous exlpanation)
mach_msg_server(myipc_server, sizeof(union __RequestUnion__SERVERPREFmyipc_subsystem), port, MACH_MSG_TIMEOUT_NONE);
}
L'enregistrement NDR
L'enregistrement NDR est exporté par libsystem_kernel.dylib
, et c'est une structure qui permet Ă MIG de transformer les donnĂ©es de maniĂšre Ă ce qu'elles soient indĂ©pendantes du systĂšme sur lequel elles sont utilisĂ©es, car MIG a Ă©tĂ© conçu pour ĂȘtre utilisĂ© entre diffĂ©rents systĂšmes (et pas seulement sur la mĂȘme machine).
C'est intéressant car si _NDR_record
est trouvé dans un binaire en tant que dépendance (jtool2 -S <binary> | grep NDR
ou nm
), cela signifie que le binaire est un client ou un serveur MIG.
De plus, les serveurs MIG ont la table de dispatch dans __DATA.__const
(ou dans __CONST.__constdata
dans le noyau macOS et __DATA_CONST.__const
dans d'autres noyaux *OS). Cela peut ĂȘtre extrait avec jtool2
.
Et les clients MIG utiliseront le __NDR_record
pour envoyer avec __mach_msg
aux serveurs.
Analyse Binaire
jtool
Comme de nombreux binaires utilisent maintenant MIG pour exposer des ports mach, il est intéressant de savoir comment identifier que MIG a été utilisé et les fonctions que MIG exécute avec chaque ID de message.
jtool2 peut analyser les informations MIG d'un binaire Mach-O en indiquant l'ID de message et en identifiant la fonction à exécuter :
jtool2 -d __DATA.__const myipc_server | grep MIG
De plus, les fonctions MIG ne sont que des wrappers de la fonction rĂ©elle qui est appelĂ©e, ce qui signifie qu'en obtenant sa dĂ©sassemblage et en recherchant BL, vous pourriez ĂȘtre en mesure de trouver la fonction rĂ©elle qui est appelĂ©e :
jtool2 -d __DATA.__const myipc_server | grep BL
Assembly
Il a été précédemment mentionné que la fonction qui s'occupera de appeler la fonction correcte en fonction de l'ID de message reçu était myipc_server
. Cependant, vous n'aurez généralement pas les symboles du binaire (pas de noms de fonctions), donc il est intéressant de vérifier à quoi cela ressemble décompilé car cela sera toujours trÚs similaire (le code de cette fonction est indépendant des fonctions exposées) :
int _myipc_server(int arg0, int arg1) {
var_10 = arg0;
var_18 = arg1;
// Instructions initiales pour trouver les pointeurs de fonction appropriés
*(int32_t *)var_18 = *(int32_t *)var_10 & 0x1f;
*(int32_t *)(var_18 + 0x8) = *(int32_t *)(var_10 + 0x8);
*(int32_t *)(var_18 + 0x4) = 0x24;
*(int32_t *)(var_18 + 0xc) = 0x0;
*(int32_t *)(var_18 + 0x14) = *(int32_t *)(var_10 + 0x14) + 0x64;
*(int32_t *)(var_18 + 0x10) = 0x0;
if (*(int32_t *)(var_10 + 0x14) <= 0x1f4 && *(int32_t *)(var_10 + 0x14) >= 0x1f4) {
rax = *(int32_t *)(var_10 + 0x14);
// Appel Ă sign_extend_64 qui peut aider Ă identifier cette fonction
// Cela stocke dans rax le pointeur vers l'appel qui doit ĂȘtre appelĂ©
// VĂ©rifiez l'utilisation de l'adresse 0x100004040 (tableau d'adresses de fonctions)
// 0x1f4 = 500 (l'ID de départ)
rax = *(sign_extend_64(rax - 0x1f4) * 0x28 + 0x100004040);
var_20 = rax;
// Si - sinon, le if retourne faux, tandis que le else appelle la fonction correcte et retourne vrai
if (rax == 0x0) {
*(var_18 + 0x18) = **_NDR_record;
*(int32_t *)(var_18 + 0x20) = 0xfffffffffffffed1;
var_4 = 0x0;
}
else {
// Adresse calculée qui appelle la fonction appropriée avec 2 arguments
(var_20)(var_10, var_18);
var_4 = 0x1;
}
}
else {
*(var_18 + 0x18) = **_NDR_record;
*(int32_t *)(var_18 + 0x20) = 0xfffffffffffffed1;
var_4 = 0x0;
}
rax = var_4;
return rax;
}
En fait, si vous allez Ă la fonction 0x100004000
, vous trouverez le tableau de routine_descriptor
structs. Le premier Ă©lĂ©ment de la struct est l'adresse oĂč la fonction est implĂ©mentĂ©e, et la struct prend 0x28 octets, donc chaque 0x28 octets (Ă partir de l'octet 0) vous pouvez obtenir 8 octets et cela sera l'adresse de la fonction qui sera appelĂ©e :
.png)
.png)
Ces donnĂ©es peuvent ĂȘtre extraites en utilisant ce script Hopper.
Debug
Le code généré par MIG appelle également kernel_debug
pour générer des journaux sur les opérations d'entrée et de sortie. Il est possible de les vérifier en utilisant trace
ou kdv
: kdv all | grep MIG
References
tip
Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE)
Soutenir HackTricks
- VĂ©rifiez les plans d'abonnement !
- Rejoignez le đŹ groupe Discord ou le groupe telegram ou suivez nous sur Twitter đŠ @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PRs au HackTricks et HackTricks Cloud dépÎts github.