MIG는 Mach IPC 코드 생성을 단순화하기 위해 만들어졌습니다. 기본적으로 서버와 클라이언트가 주어진 정의로 통신하기 위해 필요한 코드를 생성합니다. 생성된 코드가 보기 좋지 않더라도, 개발자는 이를 가져오기만 하면 그의 코드는 이전보다 훨씬 간단해질 것입니다.
정의는 .defs 확장자를 사용하여 인터페이스 정의 언어(IDL)로 지정됩니다.
이 정의는 5개의 섹션으로 구성됩니다:
서브시스템 선언: 키워드 subsystem은 이름과 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: 응답을 기대합니다.
Example
정의 파일을 생성합니다. 이 경우 매우 간단한 함수를 사용합니다:
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를 사용하여 서로 통신할 수 있는 서버 및 클라이언트 코드를 생성하여 Subtract 함수를 호출하십시오:
시스템에서 더 복잡한 예제를 찾으려면: 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 */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;
이전 구조를 기반으로 함수 **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);}
// 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를 사용하여 맥 포트를 노출하기 때문에, MIG가 사용되었음을 식별하는 방법과 각 메시지 ID에 대해 MIG가 실행하는 함수를 아는 것이 흥미롭습니다.
jtool2는 Mach-O 바이너리에서 MIG 정보를 구문 분석하여 메시지 ID를 표시하고 실행할 함수를 식별할 수 있습니다:
jtool2-d__DATA.__constmyipc_server|grepMIG
또한, MIG 함수는 호출되는 실제 함수의 래퍼에 불과하므로, 해당 함수의 디스어셈블리를 얻고 BL을 grep하면 호출되는 실제 함수를 찾을 수 있습니다:
jtool2-d__DATA.__constmyipc_server|grepBL
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 {// 두 개의 인수로 적절한 함수를 호출하는 계산된 주소 (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 바이트를 가져오면 호출될 함수의 주소가 됩니다: