Bu makinede yedi program root olarak çalışmaktadır; bunları ben yazmadım ve Apple da dağıtmadı:
com.docker.socket com.vmware.DiskHelper
com.docker.vmnetd com.vmware.IDHelper
com.microsoft.autoupdate.helper com.vmware.MountHelper
dev.orbstack.OrbStack.privhelper
Her biri, ayrıcalıksız bir işlemin ondan ayrıcalıklı bir görev yapmasını istemesini sağlayan bir kapı açar. Her biri bu kapıda şu soruyu yanıtlamak zorundadır: Kim soruyor? Bunu yanlış yapmak, macOS’taki en dayanıklı yerel ayrıcalık yükseltme (local privilege escalation) kalıbıdır.
Yedi tanesinin tamamını kontrol etmek üzere çıktım. Hiçbir şey bulamadım. Yolda üç kez de yanıldım ve her yanlış cevap, onu seçtiğimde mantıklı görünen bir yöntemle üretilmişti. İşte bunu yazmaya değer olan kısım budur.
Önce test düzeneği geldi; üstelik kendi hatamdan doğdu
Başka insanların yazdığı yazılımlara işaret etmeden önce, yönteminin sağlam bir uygulamayı sağlıksız birinden ayırt edebildiğinden emin olmam gerekiyordu. En yakın bilinen pozitif örnek (known-positive), kendi laboratuvarımın ortaya çıkmasıyla belirdi.
Daha önceki macOS gönderimime eşlik eden XPC laboratuvarı, peer’ini şu şekilde çözer:
let attributes = [kSecGuestAttributePid as String: NSNumber(value: processIdentifier)] as CFDictionary
var code: SecCode?
SecCodeCopyGuestWithAttributes(nil, attributes, [], &code)
Bu bir PID’dir (İşlem Tanımlayıcısı). Aynı gönderim kendi kelimeleriyle, bir PID’nin “dayanıklı bir kod kimliği olmadığını” belirtmektedir. Metin doğruydur ancak laboratuvar bununla çelişmektedir.
Bunu korudum ve diğer yarısını yazdım: Her yönüyle aynı olan ancak bağlantının audit token’ına (denetim jetonu) karşı platform tarafından değerlendirilen setCodeSigningRequirement çağrısı yapan ikinci bir sunucu.
Normal kullanımda iki sunucu birbirinden ayırt edilemez. Her ikisi de onaylanmış istemciyi kabul eder ve eşleşmeyenini reddeder. Bu, açık kutu (black-box) yönteminin elendiğini zaten gösterir: Yanlış kimlik ile bağlanıp reddedilip edilmediğinizi görmek size hiçbir şey söylemez; çünkü sağlam ve sağlıksız bir uygulama, dürüst bir çağrıya aynı cevabı verir.
Fark, çağrı yapan taraf dürüst olmaktan vazgeştiğinde ortaya çıkar. Reddedilmiş kimlik altında imzalanmış bir istemci isteğini gönderir, ardından posix_spawn ile POSIX_SPAWN_SETEXEC kullanarak kendi işlem görüntüsünü onaylanmış kimliği taşıyan bir sahtekâr (decoy) ile değiştirir. PID değişmez:
scenario client server result
normal denied weak DENY
normal denied strict DENY
PID-reuse race denied weak ALLOW <-- bypass
PID-reuse race denied strict DENY
Zayıf sunucu, geçerli olanı kullandığı sözlerle atlatılan isteği de aynı şekilde kaydeder:
peer=APPROVED decision=ALLOW gate=code-requirement
effect=BOUNDED input=synthetic
Artık test düzeneği gerçek bir ayrımı yakalayabiliyordu. Sırada onu benim yazmadığım yazılımlara yöneltmek vardı.
İlk yanlış alarm: Semboller yanlış soruydu
Docker’ın vmnetd’i hemen endişe verici görünüyordu. Soketi makinedeki diğer tüm ayrıcalıklı soketlerin aksine dünya geneline yazılabilir (world-writable) durumdaydı:
srw-rw-rw- root daemon /var/run/com.docker.vmnetd.sock
srw------- root daemon /var/run/VMware Fusion Services.sock
srw------- root daemon /var/run/vpncontrol.sock
Peer-kredensial API’leri (LOCAL_PEERCRED, getpeereid, xucred, audit_token) için sembol tablosunu arattığımda hiçbir şey döndü. İçe aktardığı her Security framework fonksiyonu TLS ile ilgilidir: SecTrust*, SecPolicyCreateSSL, SecCertificate*. HTTP istemcisi için sertifika doğrulaması, soket peer’inin kimlik doğrulaması değil.
Dünya geneline yazılabilir bir sokette ve görünür peer kontrolü olmayan root bir daemon, ciddi görünen bir şekildir ve bu şekle bir isim vardır: CVE-2020-15360, “com.docker.vmnetd… client verification eksikliği nedeniyle ayrıcalık yükseltmesine izin verir.”
Bunun bulguyu öldürdüğü andır. Bu hata 2020’de düzeltildi ve soket hala 2026’da dünya geneline yazılabilir durumda; yani düzeltme soketi kısıtlamak değildi. Protokol içinde istemcinin doğrulanması gerekiyordu ve benim yöntemim bunu göremiyordu. vmnetd bir Go ikilisidir:
Go build ID: "OFbDS6odAF69FNh4ODnt/0GOyS5Dq2doywSjvz_Jm/EBqhEHMvFWOEVazUyy4C/M1XGNnUdgoBSfWrIOvZg"
Go, libc sembollerini aradığım gibi içe aktarmaz. Kendi sembol tablosunu arattığımda:
verifyClient authorize codesign CheckSignature
Doğrulama orada var. “Bu ikili peer kimlik doğrulamasıyla iliştiğim C fonksiyonlarını mı içe aktarıyor?” diye sormuştum ve cevabı “bu ikili peer’ini mi doğruluyor?” olarak okumuştum. Bunlar farklı sorulardır ve sadece ilkine cevap verilmiştir.
İkinci yanlış alarm: Karar yolunda olmayan şüpheli bir çağrı
OrbStack’ın yardımcı programı xpc_connection_get_pid içe aktarır ve — VMware yardımcı programlarının aksine — ne SecTaskCreateWithAuditToken ne de xpc_connection_get_audit_token içe aktarır; ayrıca ne kSecGuestAttributePid ne de kSecGuestAttributeAudit’a atıfta bulunur. SecCodeCheckValidity içe aktarır. Yani kod imzalarını bir şekilde kontrol eder ve PID’lere bakar, ancak bu iki gerçek arasındaki yaygın köprü eksiktir.
Açıklayıcı (disassembly) bunu beş komutla yanıtlar:
sub x2, x29, #0x68 ; &secCode
mov x0, x20 ; the XPC message
bl _SecCodeCreateWithXPCMessage
ldur x20, [x29, #-0x68]
cbz x20, <reject>
SecCodeCreateWithXPCMessage, kod nesnesini mesajın kendisinden türetir ve bu mesaj audit token’ını taşır. Kimlik doğrulama yolunda hiç PID yoktur — bu da neden hiçbir misafir-özniteliği (guest-attribute) sabiti görünmediğinin sebebidir. xpc_connection_get_pid çağrısı gerçek olsa da, sonucu euid, egid ve zaten doğrulanmış SecCode ile birlikte bir bağlam yapısına (context struct) paketlenir ve istek işleyicisine verilir. Bu operasyon için peer meta veriğidir, kapı değildir.
Kontrol ettiği gereksinimler geniş değil, sabittir:
anchor apple generic and identifier "dev.kdrag0n.MacVirt" and certificate leaf[subject.OU] = "HUAQ24HBR6"
Apple zinciri, belirli bir tanımlayıcı ve satıcının kendi ekibi. Rapor edilecek hiçbir şey yok.
Üçüncü yanlış alarm: Doğru kalıp, yanlış adlandırma
Microsoft’un AutoUpdate yardımcı programı üçünden ikisinin de kalıbını içerdiği için en ikna ediciydi. proc_pidpath → kSecGuestAttributePid → SecCodeCopyGuestWithAttributes → SecStaticCodeCheckValidity üzerine inşa edilmiş bir zincir var. Bu, PID kontrolünden daha kötüdür: Bir PID’yi dosya sistemi yoluna çözer ve ardından diskteki dosyayı doğrular. Ayrıca kSecGuestAttributeAudit kullanan ikinci bir zincir de vardır.
Her ikisi de mevcuttur. Sadece biri kapı olabilir.
İkili çoğunlukla soyulmuştur (stripped), bu yüzden en yakın dışa aktarılan sembol içeren fonksiyon değildir ve +0x13c4 ofsetleri hiçbir şey kanıtlamaz. Objective-C meta verisi, iki yönlendirme (indirection) sonra bunu yanıtlar — yöntem listeleri seçiciye (selector) referanslar depolar ve işaretçiler görüntü tabanının geri eklenmesi gereken zincirli düzeltmelerdir:
imp 0x10000c4b8 -> isConnectionValidForMAUApp:requestingAppURL:
imp 0x10000c814 -> validateConnection:reason:
PID zinciri, ismi isConnectionValid ile başlayan bir yönteme aittir. Bu noktada bir bulgu bekliyordum.
listener:shouldAcceptNewConnection: 0x10000265c adresinde yer alır. Gelen dinleyiciyi dört özelliğe karşılaştırır, etiketli bir dize seçer ve sonucu doğrudan kabul (accept) dalına giden tek çağrı yapar:
mov x2, x20 ; the connection
mov x3, x23 ; the selected label
bl 0x100011040
mov x22, x0
tbz w22, #0x0, <reject>
Ve bu stub diğerine çözümlenir:
0x100011040: ldr x1, [x1, #0x120] -> 0x10001d120 -> "validateConnection:reason:"
Audit-token yöntemi. Uyguladığı gereksinim, Microsoft’un kendi tanımlayıcılarına, Apple Developer ID zincirine ve UBF8T346G9 ekibine sabittir. Kapı sağlamdır.
Tarama ve emin olmak için ne kadar maliyet ödenmesi gerektiği
helper client validation verdict
com.vmware.DiskHelper SecTaskCreateWithAuditToken sound
com.vmware.IDHelper SecTaskCreateWithAuditToken sound
com.vmware.MountHelper SecTaskCreateWithAuditToken sound
com.docker.vmnetd in-protocol verifyClient / CheckSignature sound
dev.orbstack.OrbStack.privhelper SecCodeCreateWithXPCMessage sound
com.microsoft.autoupdate.helper validateConnection:reason: (audit token) sound
com.docker.socket not readable (mode --x--x) unassessed
Bulgular yok. Yedisinden altısı bunu doğru yapıyor, yedincisini okuyamadım.
Artan maliyet sırasına göre denenmiş üç yöntem ve reddedilmiş üç yöntem:
Sembol varlığı, bir ikilinin neye bağlı olduğunu söyler, ne çağırdığını değil. Statik olarak bağlanmış bir zaman içindeki doğrulamayı göremez ve yetkilendirme yolundaki bir çağrı ile günlük için kullanılan bir çağrıyı ayırt edemez.
Black-box sorgulama burada sağlam uygulamayla zayıf uygulamayı tek başına ayıramaz; test düzeneği bunu tarama başlamadan önce kanıtladı. İki uygulama da yanlış kimliği reddeder ve doğru kimliği kabul eder. Farkı yalnızca PID hakkında yalan söyleyen kötü niyetli bir çağıran ortaya çıkarır.
Çağrıyı takip etmek — vekil (delegate), stub ve seçici referansı üzerinden — savunabileceğim bir cevap üreten tek yöntemdi. Yavaş olan da bu tek yöntemdir.
Üç yanlış alarmın hepsi de aynı şekle sahiptir. Her seferinde, ucuz bir sinyal bir şüphe üretir ve şüphe program hakkında iken kanıt sadece ölçümüm hakkında idi. Her birinin düzeltilmiş versiyonu “yardımcının aslında sorun yok” değil, “iki durumu ayırt edemeyecek bir soru sordum”dur.
Açık kalan tek iz
isConnectionValidForMAUApp:requestingAppURL: mevcut olup, bir PID’yi yola çözer ve bu yolu statik kod olarak doğrular. Bağlantıyı koruyan şey değildir. Ancak derlenmiştir, bu yüzden onu çağıran bir şey vardır ve koruduğu şey ayrıcalıklı bir işlem değil de kozmetik bir karar ise, yarışı kapıda değil de o çağrı sitesinde yeniden değerlendirmek gerekir.
Bu bir bulgu değildir. Bu taramanın açtığı ve yanıt vermediği tek sorudur; temiz bir sağlık raporu ile bitmekten daha yararlı bir şeydir.
Test koltuğu — her iki sunucu, yarışçı (racer), sahtekâr ve beklenen kanıtlar — labs/macos-xpc-authority/ dizinindedir. Zayıf sunucu kasıtlı olarak saklanmaktadır: Hiçbir sağlıksız şey yakalayamadığı gösterilmemiş bir alet, hiçbir şeyi sağlam ilan etmeye hak kazanmamıştır.
Bu teknik not ne kadar güncel?
En son kaynak incelemesi, içerik güncellemesi veya yayın tarihi gösterilmektedir.
Birincil kamu kayıtları kontrol edildi. Ortama özgü davranış, ayrıca yeniden üretilmedikçe iddianın dışındadır.
