macOS MIG - Mach Interface Generator
Reading time: 16 minutes
tip
AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE)
Azureハッキングを学び、実践する:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricksをサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。
基本情報
MIGはMach IPCコード作成のプロセスを簡素化するために作成されました。基本的に、サーバーとクライアントが特定の定義で通信するために必要なコードを生成します。生成されたコードが醜い場合でも、開発者はそれをインポートするだけで、彼のコードは以前よりもはるかにシンプルになります。
定義はインターフェース定義言語(IDL)で.defs
拡張子を使用して指定されます。
これらの定義には5つのセクションがあります:
- サブシステム宣言:キーワードのsubsystemは名前とIDを示すために使用されます。また、サーバーがカーネルで実行されるべき場合は**
KernelServer
**としてマークすることも可能です。 - インクルードとインポート:MIGはCプリプロセッサを使用しているため、インポートを使用できます。さらに、ユーザーまたはサーバー生成コードのために
uimport
およびsimport
を使用することも可能です。 - 型宣言:データ型を定義することが可能ですが、通常は
mach_types.defs
およびstd_types.defs
をインポートします。カスタムのものにはいくつかの構文を使用できます: - [i`n/out]tran:受信メッセージまたは送信メッセージから翻訳する必要がある関数
c[user/server]type
:別のC型へのマッピング。destructor
:型が解放されるときにこの関数を呼び出します。- 操作:これらはRPCメソッドの定義です。5つの異なるタイプがあります:
routine
:応答を期待simpleroutine
:応答を期待しないprocedure
:応答を期待simpleprocedure
:応答を期待しないfunction
:応答を期待
例
非常にシンプルな関数を持つ定義ファイルを作成します:
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);
最初の引数はバインドするポートであり、MIGは自動的に返信ポートを処理します(クライアントコードでmig_get_reply_port()
を呼び出さない限り)。さらに、操作のIDは指定されたサブシステムIDから順次始まります(したがって、操作が非推奨の場合は削除され、skip
がそのIDを引き続き使用するために使用されます)。
次に、MIGを使用して、互いに通信し、Subtract関数を呼び出すことができるサーバーとクライアントコードを生成します:
mig -header myipcUser.h -sheader myipcServer.h myipc.defs
現在のディレクトリにいくつかの新しいファイルが作成されます。
tip
より複雑な例は、システム内で mdfind mach_port.defs
を使用して見つけることができます。
また、ファイルと同じフォルダーから mig -DLIBSYSCALL_INTERFACE mach_ports.defs
を使用してコンパイルできます。
ファイル myipcServer.c
と myipcServer.h
には、受信したメッセージIDに基づいて呼び出す関数を定義する構造体 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)},
}
};
前述の構造に基づいて、関数 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に割り当てられていました...
関数がreplyを送信することが期待されている場合、関数mig_internal kern_return_t __MIG_check__Reply__<name>
も存在します。
実際、この関係は**myipcServer.h
の構造体subsystem_to_name_map_myipc
(他のファイルでは`subsystemto_name_map***)で特定することが可能です:
#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;
}
IDによって呼び出す関数にアクセスするために強調表示された行を確認してください。
以下は、クライアントがサーバーからSubtract関数を呼び出すことができるシンプルな サーバー と クライアント を作成するためのコードです:
// 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);
}
NDR_record
NDR_recordはlibsystem_kernel.dylib
によってエクスポートされる構造体で、MIGがシステムに依存しないデータを変換することを可能にします。MIGは異なるシステム間で使用されることを想定して設計されているため(同じマシン内だけではなく)。
これは興味深いことで、バイナリ内に依存関係として_NDR_record
が見つかると(jtool2 -S <binary> | grep NDR
またはnm
)、そのバイナリがMIGクライアントまたはサーバーであることを意味します。
さらに、MIGサーバーは__DATA.__const
(macOSカーネルでは__CONST.__constdata
、他の*OSカーネルでは__DATA_CONST.__const
)にディスパッチテーブルを持っています。これは**jtool2
**を使ってダンプできます。
そして、MIGクライアントは__mach_msg
を使ってサーバーに送信するために__NDR_record
を使用します。
バイナリ分析
jtool
多くのバイナリがMIGを使用してマッチポートを公開しているため、MIGが使用されたことを特定する方法と各メッセージIDでMIGが実行する関数を知ることは興味深いです。
jtool2は、Mach-OバイナリからMIG情報を解析し、メッセージIDを示し、実行する関数を特定できます。
jtool2 -d __DATA.__const myipc_server | grep MIG
さらに、MIG関数は実際に呼び出される関数のラッパーに過ぎないため、その逆アセンブルを取得し、BLをgrepすることで、実際に呼び出される関数を見つけることができるかもしれません:
jtool2 -d __DATA.__const myipc_server | grep BL
Assembly
以前、受信したメッセージIDに応じて正しい関数を呼び出す役割を果たす関数はmyipc_server
であると述べました。しかし、通常はバイナリのシンボル(関数名)がないため、逆コンパイルされたときの見た目を確認することが興味深いです。この関数のコードは、公開されている関数とは独立しています。
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 - else、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;
}
実際、関数**0x100004000
に行くと、routine_descriptor
構造体の配列が見つかります。構造体の最初の要素は関数が実装されているアドレスであり、構造体は0x28バイトを占めるため、0バイトから始まる各0x28バイトごとに8バイトを取得すると、それが呼び出される関数のアドレス**になります。
.png)
.png)
このデータはこのHopperスクリプトを使用して抽出できます。
Debug
MIGによって生成されたコードは、kernel_debug
を呼び出して、エントリおよびエグジットに関する操作のログを生成します。これらは**trace
またはkdv
**を使用して確認できます:kdv all | grep MIG
References
tip
AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE)
Azureハッキングを学び、実践する:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricksをサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。