MIG a été créé pour simplifier le processus de création de code Mach IPC. Il génère essentiellement 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.
Exemple
Créez un fichier de définition, dans ce cas avec une fonction très simple :
myipc.defs
subsystem myipc 500; // Arbitrary name and iduserprefix USERPREF; // Prefix for created functions in the clientserverprefix SERVERPREF; // Prefix for created functions in the server#include<mach/mach_types.defs>#include<mach/std_types.defs>simpleroutineSubtract(server_port : mach_port_t;n1 : uint32_t;n2 : uint32_t);
Maintenant, utilisez mig pour générer le code serveur et client qui pourra communiquer entre eux pour appeler la fonction Subtract :
Plusieurs nouveaux fichiers seront créés dans le répertoire actuel.
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 du message reçu (nous avons indiqué un numéro de départ de 500) :
/* Description of this subsystem, for use in direct RPC */conststruct SERVERPREFmyipc_subsystem SERVERPREFmyipc_subsystem = {myipc_server_routine,500, // start ID501, // 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)},}};
/* Description of this subsystem, for use in direct RPC */externconststruct SERVERPREFmyipc_subsystem {mig_server_routine_t server; /* Server routine */mach_msg_id_t start; /* Min routine number */mach_msg_id_t end; /* Max routine number + 1 */unsignedint maxsize; /* Max msg size */vm_address_t reserved; /* Reserved */struct routine_descriptor /* Array of routine descriptors */routine[1];} SERVERPREFmyipc_subsystem;
En fonction de la structure précédente, la fonction myipc_server_routine recevra l'ID du message et renverra la fonction appropriée à appeler :
Dans cet exemple, nous n'avons défini qu'une seule 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...
En fait, il est possible d'identifier cette relation dans la structure subsystem_to_name_map_myipc de myipcServer.h:
Enfin, une autre fonction importante pour faire fonctionner le serveur sera myipc_server, qui est celle qui va effectivement appeler la fonction liée à l'ID 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() la mettra à jour si elle est 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;returnFALSE;} (*routine) (InHeadP, OutHeadP);returnTRUE;}
Vérifiez les lignes précédemment mises en évidence en accédant à la fonction à appeler par ID.
Voici le code pour créer un serveur et un client simples où le client peut appeler les fonctions Subtract 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_tSERVERPREFSubtract(mach_port_t server_port,uint32_t n1,uint32_t n2){printf("Received: %d - %d = %d\n", n1, n2, n1 - n2);return KERN_SUCCESS;}intmain() {mach_port_t port;kern_return_t kr;// Register the mach servicekr =bootstrap_check_in(bootstrap_port,"xyz.hacktricks.mig",&port);if (kr != KERN_SUCCESS) {printf("bootstrap_check_in() failed with code 0x%x\n", kr);return1;}// 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);}
#include<stdio.h>#include<mach/mach.h>#include<servers/bootstrap.h>#include"myipc.h"intmain() {mach_port_t bootstrap_port;kern_return_t err; err =task_get_bootstrap_port(mach_task_self(),&bootstrap_port);if (err != KERN_SUCCESS) {printf("Failed to get bootstrap port\n");return1; } err =myipc_register(bootstrap_port);if (err != KjsonERN_SUCCESS) {printf("Failed to register myipc service\n");return1; }printf("myipc service registered successfully\n");return0;}
// gcc myipc_client.c myipcUser.c -o myipc_client#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<mach/mach.h>#include<servers/bootstrap.h>#include"myipcUser.h"intmain() {// Lookup the receiver port using the bootstrap server.mach_port_t port;kern_return_t kr =bootstrap_look_up(bootstrap_port,"xyz.hacktricks.mig",&port);if (kr != KERN_SUCCESS) {printf("bootstrap_look_up() failed with code 0x%x\n", kr);return1;}printf("Port right name %d\n", port);USERPREFSubtract(port,40,2);}
Analyse Binaire
Comme de nombreux binaires utilisent désormais 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 identifiant de message.
jtool2 peut analyser les informations MIG d'un binaire Mach-O indiquant l'identifiant de message et identifiant la fonction à exécuter:
jtool2-d__DATA.__constmyipc_server|grepMIG
Il a été mentionné précédemment que la fonction qui se chargera d'appeler la fonction correcte en fonction de l'ID du message reçu était myipc_server. Cependant, vous n'aurez généralement pas les symboles du binaire (pas de noms de fonctions), il est donc intéressant de vérifier à quoi ressemble le décompilé car il 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 bons pointeurs de fonction*(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 de l'appel qui doit être effectué// 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 si renvoie faux, tandis que le sinon appelle la bonne fonction et renvoie vraiif (rax ==0x0) {*(var_18 +0x18) =**_NDR_record;*(int32_t*)(var_18 +0x20) =0xfffffffffffffed1;var_4 =0x0;}else {// Adresse calculée qui appelle la bonne fonction 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;}
Il s'agit de la même fonction décompilée dans une version gratuite différente de Hopper :
int_myipc_server(int arg0,int arg1) {r31 = r31 -0x40;saved_fp = r29;stack[-8] = r30;var_10 = arg0;var_18 = arg1;// Instructions initiales pour trouver les bons pointeurs de fonction*(int32_t*)var_18 =*(int32_t*)var_10 &0x1f|0x0;*(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;r8 =*(int32_t*)(var_10 +0x14);r8 = r8 -0x1f4;if (r8 >0x0) {if (CPU_FLAGS & G) {r8 =0x1;}}if ((r8 &0x1) ==0x0) {r8 =*(int32_t*)(var_10 +0x14);r8 = r8 -0x1f4;if (r8 <0x0) {if (CPU_FLAGS & L) {r8 =0x1;}}if ((r8 &0x1) ==0x0) {r8 =*(int32_t*)(var_10 +0x14);// 0x1f4 = 500 (l'ID de départ) r8 = r8 -0x1f4;asm { smaddl x8, w8, w9, x10 };r8 =*(r8 +0x8);var_20 = r8;r8 = r8 -0x0;if (r8 !=0x0) {if (CPU_FLAGS & NE) {r8 =0x1;}}// Même si sinon que dans la version précédente// Vérifiez l'utilisation de l'adresse 0x100004040 (tableau d'adresses de fonctions)if ((r8 &0x1) ==0x0) {*(var_18 +0x18) =**0x100004000;*(int32_t*)(var_18 +0x20) =0xfffffed1;var_4 =0x0;}else {// Appel à l'adresse calculée où la fonction devrait être (var_20)(var_10, var_18); var_4 =0x1;}}else {*(var_18 +0x18) =**0x100004000;*(int32_t*)(var_18 +0x20) =0xfffffed1;var_4 =0x0;}}else {*(var_18 +0x18) =**0x100004000;*(int32_t*)(var_18 +0x20) =0xfffffed1;var_4 =0x0;}r0 = var_4;return r0;}
En fait, si vous allez à la fonction 0x100004000, vous trouverez le tableau des structures routine_descriptor. Le premier élément de la structure est l'adresse où la fonction est implémentée, et la structure prend 0x28 octets, donc tous les 0x28 octets (à partir de l'octet 0) vous pouvez obtenir 8 octets et ce sera l'adresse de la fonction qui sera appelée :