
VMWare Horizon for macOS 클라이언트의 XPC 로직 결함으로 인한 LPE. Omnissa에 책임 있게 공개한 0-day에 대한 늦은 POC.
macOS 운영 체제는 XPC 서비스를 사용하여 기본적인 프로세스 간 통신을 수행합니다. 예를 들어 XPC Service 데몬과 타사 애플리케이션의 권한 있는 도우미 도구 간 통신이 이에 해당합니다. VMware Horizon Client는 설치 시 /Library/PrivilegedHelperTools/ 디렉토리에 com.vmware.horizon.CDSHelper 권한 있는 도우미를 등록합니다.
또한, 위에 표시된 대로 권한 있는 도우미가 들어오는 XPC 연결을 수신 대기하기 위해 mach 이름 com.vmware.horizon.CDSHelper를 사용한다는 점에 유의해야 합니다. 그런 다음 기본 macOS 개발자 도구를 사용하여 도우미 바이너리에서 심볼을 덤프하여 XPC 동작이 포함되어 있음을 확인했습니다:
» nm -a /Library/PrivilegedHelperTools/com.vmware.horizon.CDSHelper | grep -i xpc
U __xpc_error_connection_invalid
U __xpc_error_key_description
U __xpc_type_connection
U __xpc_type_dictionary
U __xpc_type_error
U _xpc_connection_create_mach_service
U _xpc_connection_get_pid
U _xpc_connection_resume
U _xpc_connection_send_message
U _xpc_connection_set_event_handler
U _xpc_dictionary_create_reply
U _xpc_dictionary_get_remote_connection
U _xpc_dictionary_get_string
U _xpc_dictionary_set_int64
U _xpc_get_type
U _xpc_release
Hopper Disassembler 도구를 사용하여 도우미를 디컴파일러에 로드하고 역공학을 수행했습니다. 아래와 같이 실행했습니다: open -a /Applications/Hopper\ Disassembler\ v4.app /Library/PrivilegedHelperTools/com.vmware.horizon.CDSHelper EntryPoint() 함수를 찾아 검토했으며, 이것이 CDSHelper 메인 진입점임을 확인했습니다.
그런 다음 sub_100005fa0 함수를 디컴파일하여 VMWare Horizon Client의 권한 있는 도우미 도구가 XPC 서비스 API를 사용함을 확인했습니다. 이는 Apple이 제공하는 하위 C 레벨 API로, Objective-C/Swift 계층에 있는 XPC 연결 API와는 다릅니다:
int sub_100005fa0(int arg0, int arg1) {
sub_1000063d4("Starting service: %s\n");
r0 = xpc_connection_create_mach_service(arg0, 0x0, 0x1);
xpc_connection_set_event_handler(r0, &var_38);
r0 = xpc_connection_resume(r20);
dispatch_main();
return r0;
}
mach 서비스가 생성된 후(xpc_connection_create_mach_service(com.vmware.horizon.CDSHelper)를 통해), 클라이언트와 Horizon Client의 XPC 서비스 간의 메시지는 xpc_dictionary_create() 메서드로 생성된 XPC 딕셔너리 객체를 통해 교환됩니다. Apple이 개발자에게 제공하는 메서드를 사용하여 이 딕셔너리에 값을 추가할 수 있습니다. 이러한 메서드는 일반적으로 xpc_dictionary_set_로 시작합니다. 클라이언트는 xpc_connection_send_와 같은 메서드를 통해 이러한 XPC 딕셔너리 객체를 XPC 서비스로 보낼 수 있습니다. Horizon Client의 XPC 서비스 측에서는 클라이언트로부터 수신된 XPC 딕셔너리 객체를 처리할 수 있는 핸들러가 등록됩니다. 이는 xpc_connection_set_event_handler() 메서드를 사용하여 수행됩니다. 마지막으로, 수신된 XPC 딕셔너리 객체의 값은 xpc_dictionary_get_*와 같은 메서드를 사용하여 읽힙니다. CDSHelper의 EntryPoint()가 반환된 후, sub_100056d0 함수가 호출됩니다:
이 시점에서 실행은 sub_1000056ec로 넘어가며, 이 함수가 들어오는 XPC 메시지 처리의 대부분을 담당합니다. 디컴파일된 함수 전체가 아래에 제공됩니다. 취약한 부분은 그 직후에 검토됩니다.
int sub_1000056ec(int arg0, int arg1) {
r31 = r31 - 0x90;
var_30 = r24;
stack[-56] = r23;
var_20 = r22;
stack[-40] = r21;
var_10 = r20;
stack[-24] = r19;
saved_fp = r29;
stack[-8] = r30;
r19 = arg1;
var_38 = **___stack_chk_guard;
sub_1000063d4("CDSHelper: The cds helper will call HelperMain.\n");
r0 = xpc_dictionary_get_string(r19, "cdsjob");
if (r0 == 0x0) goto loc_1000057bc;
loc_100005738:
r20 = r0;
r0 = sub_1000063d4("CDSHelper: cdsJob is %s.\n");
if (strcmp(r20, "cdsjob_runscript") == 0x0) goto loc_100005818;
loc_100005760:
if (strcmp(r20, "cdsjob_movefile") == 0x0) goto loc_1000058c4;
loc_100005774:
if (strcmp(r20, "cdsjob_installpackage") == 0x0) {
if (**___stack_chk_guard == var_38) {
r0 = sub_100005498(r19);
}
else {
__stack_chk_fail();
}
}
else {
r0 = sub_100005380(r19, 0x16);
sub_100006434("NOT_REACHED %s:%d\n");
}
return r0;
loc_1000058c4:
r0 = sub_1000068b8();
if (r0 == 0x0) goto loc_100005980;
loc_1000058d4:
r22 = r0;
r0 = xpc_dictionary_get_string(r19, "cds_movefile_srcfile");
r20 = r0;
if (r0 == 0x0) goto loc_1000059b0;
loc_1000058f0:
r0 = xpc_dictionary_get_string(r19, "cds_movefile_dstdir");
r21 = r0;
if (r0 == 0x0) goto loc_1000059f8;
loc_100005908:
sub_1000069b8();
sub_1000069b8();
sub_100006998();
if (0x0 == 0x0) goto loc_100005a60;
loc_100005948:
sub_100006978();
sub_1000067b8();
r0 = "CDSHelper: Failed to move the file : %s.\n";
goto loc_100005a00;
loc_100005a00:
sub_1000063d4(r0);
goto loc_100005a04;
loc_100005a04:
r22 = 0x16;
strerror(0x16);
sub_1000063d4("CDSHelper: The cds moving failed : %s from %s to %s!\n");
goto loc_100005a24;
loc_100005a24:
r0 = r19;
r1 = r22;
goto loc_100005a2c;
loc_100005a2c:
r0 = sub_100005380(r0, r1);
if (**___stack_chk_guard != var_38) {
__stack_chk_fail();
}
return r0;
loc_100005a60:
sub_1000063d4("CDSHelper: The cds moving ran successfully!\n");
r22 = 0x0;
goto loc_100005a24;
loc_1000059f8:
r0 = "CDSHelper: Invalid parameter of moving: destination dir.\n";
goto loc_100005a00;
loc_1000059b0:
sub_1000063d4("CDSHelper: Invalid parameter of moving: source file.\n");
r21 = 0x0;
goto loc_100005a04;
loc_100005980:
sub_1000063d4("CDSHelper: Failed to get the invalid file manager.\n");
r21 = 0x0;
r20 = 0x0;
goto loc_100005a04;
loc_100005818:
r0 = xpc_dictionary_get_string(r19, "vmwareid");
if (r0 == 0x0) goto loc_100005960;
loc_10000582c:
r20 = r0;
r0 = xpc_dictionary_get_string(r19, "path");
if (r0 == 0x0) goto loc_10000596c;
loc_100005844:
r21 = r0;
if ((sub_100005df4(r20, "vmware-id") & 0x1) == 0x0) goto loc_100005998;
loc_10000585c:
r0 = xpc_dictionary_get_string(r19, "appDir");
if (r0 == 0x0) goto loc_1000059c4;
loc_100005870:
r22 = r0;
r0 = xpc_dictionary_get_string(r19, "tempDir");
if (r0 == 0x0) goto loc_1000059c4;
loc_100005888:
sub_100006460();
sub_1000063d4("CDSHelper: The cds script %s is running...\n");
r0 = sub_100005410(&var_60);
r20 = r0;
if (r0 == 0x0) {
r0 = "CDSHelper: The cds script ran successfully!\n";
}
else {
strerror(r20);
r0 = "CDSHelper: The cds script failed to run : %s!\n";
}
goto loc_1000059e8;
loc_1000059e8:
sub_1000063d4(r0);
r0 = r19;
r1 = r20;
goto loc_100005a2c;
loc_1000059c4:
r0 = "CDSHelper: Invalid parameter.\n";
goto loc_1000059cc;
loc_1000059cc:
sub_1000063d4(r0);
r20 = 0x16;
goto loc_1000059d4;
loc_1000059d4:
strerror(r20);
r0 = "CDSHelper: The cds script failed to run : %s!\n";
goto loc_1000059e8;
loc_100005998:
sub_1000063d4("CDSHelper: Invalid script codesigning for %s.\n");
r20 = 0xd;
goto loc_1000059d4;
loc_10000596c:
sub_1000063d4("CDSHelper: Invalid script path.\n");
r20 = 0x2d;
goto loc_1000059d4;
loc_100005960:
r0 = "CDSHelper: Invalid vmwareid.\n";
goto loc_1000059cc;
loc_1000057bc:
sub_1000063d4("CDSHelper: Invalid cdsJob.\n");
strerror(0x16);
r0 = sub_1000063d4("CDSHelper: The cds script failed to run : %s!\n");
if (**___stack_chk_guard == var_38) {
r0 = sub_100005380(r19, 0x16);
}
else {
__stack_chk_fail();
}
return r0;
}
아래 강조된 부분에서 볼 수 있듯이, 권한 있는 도우미는 먼저 xpc_dictionary_get_string() 메서드를 사용하여 cdsjob 값을 가져옵니다. 여기서 권한 있는 도우미가 예상하는 세 가지 가능한 문자열은 다음과 같습니다:
