hacktricks/macos-hardening/macos-security-and-privileg.../macos-proces-abuse/macos-ipc-inter-process-com.../macos-mig-mach-interface-ge...

16 KiB
Raw Blame History

macOS MIG - Mach インターフェースジェネレータ

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

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.cmyipcServer.h**には、struct **SERVERPREFmyipc_subsystem**の宣言と定義が含まれており、受信したメッセージIDに基づいて呼び出す関数を定義しています開始番号は500としました

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

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

{% tab title="myipcServer.h" %}

#ifndef myipcServer_h
#define myipcServer_h

#include <stdio.h>
#include <stdlib.h>
#include <mach/mach.h>
#include <servers/bootstrap.h>
#include "myipcServerUser.h"

#define MACH_PORT_NAME "com.example.myipc"

kern_return_t myipc_server(mach_port_t server_port);

#endif /* myipcServer_h */

{% endtab %}

/* 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メッセージID**を取得し、適切な関数を呼び出します。

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**の配列内に含まれ、最初の関数はID 500に割り当てられ、2番目の関数はID 501に割り当てられます...

実際には、この関係を**myipcServer.hsubsystem_to_name_map_myipc**構造体で特定することができます。

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

最後に、サーバーを動作させるための重要な関数である**myipc_server**があります。これは、受信したidに関連する関数を実際に呼び出すものです。

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;
/* 最小サイズroutine()が異なる場合は更新されます */
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);
}
#include <stdio.h>
#include <stdlib.h>
#include <mach/mach.h>
#include "myipc.h"

int main(int argc, char *argv[]) {
    mach_port_t server_port;
    kern_return_t kr;
    int val = 0;

    if (argc != 2) {
        printf("Usage: %s <value>\n", argv[0]);
        return 1;
    }

    val = atoi(argv[1]);

    kr = bootstrap_look_up(bootstrap_port, "com.example.myipc_server", &server_port);
    if (kr != KERN_SUCCESS) {
        printf("Failed to look up server port: %s\n", mach_error_string(kr));
        return 1;
    }

    kr = myipc_client_send_value(server_port, val);
    if (kr != KERN_SUCCESS) {
        printf("Failed to send value: %s\n", mach_error_string(kr));
        return 1;
    }

    return 0;
}

{% endtab %}

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

// 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を使用してmachポートを公開するため、MIGが使用されたかどうかと、各メッセージIDでMIGが実行する関数を特定する方法を知ることは興味深いです。

jtool2は、Mach-OバイナリからMIG情報を解析し、メッセージIDを示し、実行する関数を特定することができます。

jtool2 -d __DATA.__const myipc_server | grep MIG

以前に、受信したメッセージIDに応じて正しい関数を呼び出すための関数は 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開始ID
            rax = *(sign_extend_64(rax - 0x1f4) * 0x28 + 0x100004040);
            var_20 = rax;
// もし - そうでなければ、if は false を返し、else は正しい関数を呼び出して true を返します
            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 free バージョンで逆コンパイルされた同じ関数です:

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開始ID
                    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 else
// アドレス 0x100004040関数アドレス配列の使用を確認してください
                    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バイトを取ります。したがって、0バイトから始まる各0x28バイトで8バイトを取得し、それが呼び出される関数のアドレスになります。

このデータは、この Hopper スクリプトを使用して抽出できます。

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥