MIG는 Mach IPC 코드 생성 과정을 간소화하기 위해 만들어졌습니다. 기본적으로 서버와 클라이언트가 주어진 정의와 통신하기 위해 필요한 코드를 생성합니다. 생성된 코드가 어색해 보이더라도, 개발자는 그것을 가져와서 이전보다 훨씬 간단한 코드를 작성할 수 있습니다.
이 정의는 .defs 확장자를 사용하여 인터페이스 정의 언어(IDL)로 지정됩니다.
이러한 정의에는 5개의 섹션이 있습니다:
서브시스템 선언: 서브시스템 키워드는 이름과 ID를 나타내는 데 사용됩니다. 또한 서버를 커널에서 실행해야 하는 경우 **KernelServer**로 표시할 수도 있습니다.
포함 및 임포트: MIG는 C-프리프로세서를 사용하므로 임포트를 사용할 수 있습니다. 또한 사용자 또는 서버 생성 코드에 대해 uimport 및 simport를 사용할 수 있습니다.
유형 선언: 데이터 유형을 정의할 수 있지만 일반적으로 mach_types.defs 및 std_types.defs를 가져올 것입니다. 사용자 정의 유형의 경우 일부 구문을 사용할 수 있습니다:
[in/out]tran: 수신 또는 송신 메시지에서 번역해야 하는 함수
c[user/server]type: 다른 C 유형에 매핑
destructor: 유형이 해제될 때 이 함수를 호출
작업: 이들은 RPC 메서드의 정의입니다. 5가지 다른 유형이 있습니다:
routine: 응답을 기대
simpleroutine: 응답을 기대하지 않음
procedure: 응답을 기대
simpleprocedure: 응답을 기대하지 않음
function: 응답을 기대
예시
매우 간단한 함수가 있는 정의 파일을 만듭니다:
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);
첫 번째 인수는 바인딩할 포트이며 MIG는 자동으로 응답 포트를 처리할 것입니다 (mig_get_reply_port()를 클라이언트 코드에서 호출하지 않는 한). 게다가, 작업의 ID는 지정된 서브시스템 ID부터 시작하는 연속적일 것입니다 (따라서 작업이 사용되지 않는 경우 삭제되고 skip이 사용되어 여전히 해당 ID를 사용합니다).
이제 MIG를 사용하여 서버 및 클라이언트 코드를 생성하여 서로 통신하고 빼기 함수를 호출할 수 있도록 합니다:
시스템에서 더 복잡한 예제를 찾을 수 있습니다: mdfind mach_port.defs
그리고 파일이 있는 폴더에서 다음과 같이 컴파일할 수 있습니다: mig -DLIBSYSCALL_INTERFACE mach_ports.defs
myipcServer.c 및 myipcServer.h 파일에서는 SERVERPREFmyipc_subsystem 구조체의 선언과 정의를 찾을 수 있습니다. 이 구조체는 기본적으로 수신된 메시지 ID에 따라 호출할 함수를 정의합니다 (시작 번호로 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)},}};
macOS IPC (Inter-Process Communication)
macOS MIG (Mach Interface Generator)
Mach Interface Generator (MIG) is a tool used to define inter-process communication on macOS systems. It generates client-side and server-side code for message-based communication between processes. By defining interfaces in a .defs file, MIG creates the necessary C code for handling messages between processes.
In the example above, myipc_server is a function generated by MIG that handles incoming messages from client processes. This function processes the incoming message and generates an appropriate response.
MIG simplifies the process of defining and handling inter-process communication on macOS, making it easier to implement secure and efficient communication between processes.
/* 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;
이전 구조체를 기반으로 myipc_server_routine 함수는 메시지 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_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);}
클라이언트 애플리케이션은 서버와 통신하기 위해 MIG 함수를 사용합니다. 이를 위해 mach_msg 함수를 사용하여 MIG 서버로 요청을 보냅니다. 이 요청은 MIG 서버에서 처리되고 응답이 클라이언트로 다시 전송됩니다. 이를 통해 클라이언트는 서버와의 상호 작용을 달성할 수 있습니다.
// 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);}
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를 노출하기 위해 mach 포트를 사용하므로, MIG가 사용되었는지 식별하는 방법과 각 메시지 ID별로 MIG가 실행하는 함수를 알아내는 것이 흥미로울 것입니다.
jtool2는 Mach-O 바이너리에서 MIG 정보를 구문 분석하여 메시지 ID를 나타내고 실행할 함수를 식별할 수 있습니다:
jtool2-d__DATA.__constmyipc_server|grepMIG
게다가, MIG 함수는 호출되는 실제 함수의 래퍼일 뿐이므로, 해당 함수의 어셈블리를 가져와 BL을 찾으면 호출되는 실제 함수를 찾을 수도 있습니다:
jtool2-d__DATA.__constmyipc_server|grepBL
어셈블리
이전에 수신된 메시지 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 바이트를 차지하므로 각 0x28 바이트(바이트 0부터 시작)마다 8바이트를 얻을 수 있고, 그것이 호출될 함수의 주소가 됩니다: