hacktricks/macos-hardening/macos-security-and-privilege-escalation/macos-proces-abuse/macos-ipc-inter-process-communication/macos-mig-mach-interface-generator.md

18 KiB

macOS MIG - Mach Interface Generator

जानें AWS हैकिंग को शून्य से हीरो तक htARTE (HackTricks AWS Red Team Expert) के साथ!

HackTricks का समर्थन करने के अन्य तरीके:

MIG को Mach IPC कोड निर्माण की प्रक्रिया को सरल बनाने के लिए बनाया गया था। यह मौजूदा परिभाषा के साथ सर्वर और क्लाइंट के बीच संचार के लिए आवश्यक कोड उत्पन्न करता है। यदी उत्पन्न कोड बेहद बेहद भद्दा हो, तो एक डेवलपर को उसे आयात करने की आवश्यकता होगी और उसका कोड पहले की तुलना में बहुत सरल होगा।

उदाहरण

एक परिभाषा फ़ाइल बनाएं, इस मामले में एक बहुत ही सरल फ़ंक्शन के साथ:

{% code title="myipc.defs" %}

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);

{% endcode %}

अब mig का उपयोग करें ताकि सर्वर और क्लाइंट कोड उत्पन्न किया जा सके जो एक-दूसरे के भीतर संवाद करने के लिए सक्षम हों और Subtract फ़ंक्शन को कॉल कर सकें:

mig -header myipcUser.h -sheader myipcServer.h myipc.defs

कई नए फ़ाइलें मौजूदा निर्देशिका में बनाई जाएगी।

फ़ाइलों myipcServer.c और myipcServer.h में आपको संरचना और परिभाषण मिलेगा SERVERPREFmyipc_subsystem का, जो मूल रूप से प्राप्त संदेश आईडी पर कॉल करने के लिए फ़ंक्शन को परिभाषित करता है (हमने 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)},
}
};

{% endtab %}

{% tab title="myipcServer.h" %}हम एक नया फ़ाइल myipcServer.h बनाएंगे जिसमें हम अपने IPC सर्वर को परिभाषित करेंगे। यहाँ हमने एक नया मेथड startMyIPCServer जोड़ा है जो IPC सर्वर को शुरू करेगा। इसके लिए हमने एक नया विशेष IPC पोर्ट MY_IPC_PORT परिभाषित किया है। इसके बाद हमने IPC सर्वर को इस पोर्ट पर बाइंड करने के लिए कुछ और फ़ंक्शन जोड़े हैं। %}

/* Description of this subsystem, for use in direct RPC */
extern const struct 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 */
unsigned int	maxsize;	/* Max msg size */
vm_address_t	reserved;	/* Reserved */
struct routine_descriptor	/* Array of routine descriptors */
routine[1];
} SERVERPREFmyipc_subsystem;

{% endtab %} {% endtabs %}

पिछले स्ट्रक्टर के आधार पर myipc_server_routine फ़ंक्शन मैसेज आईडी प्राप्त करेगा और सही फ़ंक्शन को कॉल करने के लिए लौटेगा:

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;
}

इस उदाहरण में हमने परिभाषणों में केवल 1 फ़ंक्शन परिभाषित किया है, लेकिन अगर हम अधिक फ़ंक्शन परिभाषित करते, तो वे SERVERPREFmyipc_subsystem के एरे के अंदर होते और पहला फ़ंक्शन 500 आईडी को असाइन किया जाता, दूसरा 501 आईडी को...

वास्तव में इस संरचना में इस संबंध को पहचानना संभव है myipcServer.h से subsystem_to_name_map_myipc में:

#ifndef subsystem_to_name_map_myipc
#define subsystem_to_name_map_myipc \
{ "Subtract", 500 }
#endif

अंत में, सर्वर काम करने के लिए एक और महत्वपूर्ण फ़ंक्शन myipc_server होगा, जो वास्तव में प्राप्त आईडी के संबंधित फ़ंक्शन को कॉल करेगा:

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;
/* Minimal size: routine() will update it if different */
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;
}

पिछले हाइलाइट किए गए लाइनों की जांच करें जो आईडी द्वारा कॉल करने के लिए फ़ंक्शन तक पहुंचती हैं।

निम्नलिखित में एक सरल सर्वर और क्लाइंट बनाने के लिए कोड है जहां क्लाइंट सर्वर से फ़ंक्शन Subtract को कॉल कर सकता है:

{% tabs %} {% tab title="myipc_server.c" %}

// 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);
}

{% endtab %}

{% tab title="myipc_client.c" %}हमारे एपीसी क्लाइंट कोड को देखें।{% endtab %}

// 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"

int main() {

// 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);
return 1;
}
printf("Port right name %d\n", port);
USERPREFSubtract(port, 40, 2);
}

बाइनरी विश्लेषण

अब बहुत से बाइनरी MIG का उपयोग करते हैं मश पोर्ट्स को उजागर करने के लिए, इसे पहचानना और प्रत्येक संदेश आईडी के साथ MIG द्वारा कार्यान्वित किए जाने वाले कार्यों को पहचानना दिलचस्प है।

jtool2 एक Mach-O बाइनरी से MIG सूचना का विश्लेषण कर सकता है जिससे संदेश आईडी को निर्दिष्ट करता है और कार्य को क्रियान्वित करने के लिए पहचानता है:

jtool2 -d __DATA.__const myipc_server | grep MIG

पहले उल्लेख किया गया था कि प्राप्त संदेश आईडी के आधार पर सही फ़ंक्शन को बुलाने की जिम्मेदारी लेने वाला फ़ंक्शन myipc_server था। हालांकि, आम तौर पर बाइनरी के प्रतीक नहीं होते (कोई फ़ंक्शन नाम नहीं), इसलिए यह देकने में दिलचस्प है कि डीकंपाइल करने पर कैसा दिखता है क्योंकि यह हमेशा बहुत समान होगा (इस फ़ंक्शन का कोड फ़ंक्शनों से अलग होता है):

{% tabs %} {% tab title="myipc_server decompiled 1" %}

int _myipc_server(int arg0, int arg1) {
var_10 = arg0;
var_18 = arg1;
// सही फ़ंक्शन पॉइंटर्स पाने के लिए प्रारंभिक निर्देश
*(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);
// इस फ़ंक्शन को पहचानने में मदद करने वाले sign_extend_64 को कॉल करने के लिए
// यह rax में उस कॉल का पॉइंटर स्टोर करता है जिसे कॉल किया जाना चाहिए
// पता करें कि पता 0x100004040 (फ़ंक्शन पतों का एरे) का उपयोग किया गया है
// 0x1f4 = 500 (प्रारंभिक आईडी)
            rax = *(sign_extend_64(rax - 0x1f4) * 0x28 + 0x100004040);
            var_20 = rax;
// अगर - अन्यथा, अगर गलत लौटता है, जबकि अन्यथा सही फ़ंक्शन को कॉल करता है और सही लौटता है
            if (rax == 0x0) {
                    *(var_18 + 0x18) = **_NDR_record;
*(int32_t *)(var_18 + 0x20) = 0xfffffffffffffed1;
var_4 = 0x0;
}
else {
// 2 तर्कों के साथ सही फ़ंक्शन को कॉल करने वाला गणना किया गया पता
                    (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;
}

{% endtab %}

{% tab title="myipc_server decompiled 2" %} यह एक अलग Hopper मुफ्त संस्करण में डीकंपाइल किया गया समान फ़ंक्शन है:

int _myipc_server(int arg0, int arg1) {
r31 = r31 - 0x40;
saved_fp = r29;
stack[-8] = r30;
var_10 = arg0;
var_18 = arg1;
// सही फ़ंक्शन पॉइंटर्स पाने के लिए प्रारंभिक निर्देश
*(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 (प्रारंभिक आईडी)
                    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;
}
}
// पिछले संस्करण की तरह समान अगर नहीं तो अगर गलत लौटता है, जबकि अन्यथा सही फ़ंक्शन को कॉल करता है और सही लौटता है
                    if ((r8 & 0x1) == 0x0) {
                            *(var_18 + 0x18) = **0x100004000;
                            *(int32_t *)(var_18 + 0x20) = 0xfffffed1;
var_4 = 0x0;
}
else {
// फ़ंक्शन होना चाहिए उस पते पर कॉल करने के लिए गणना का कॉल
                            (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;
}

{% endtab %} {% endtabs %}

वास्तव में अगर आप 0x100004000 फ़ंक्शन पर जाते हैं तो आपको routine_descriptor संरचनाओं का एरे मिलेगा। संरचना का पहला तत्व वहाँ फ़ंक्शन का कार्यान्वयन किया गया है, और संरचना 0x28 बाइट लेती है, इसलिए प्रत्येक 0x28 बाइट (बाइट 0 से प्रारंभ करके) आप 8 बाइट प्राप्त कर सकते हैं और वह फ़ंक्शन का पता होगा जो कॉल किया जाएगा: