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.

PID CHAIN connectionprocessIdentifier SecCode from PIDa number, not a process CheckValidityagainst requirement ALLOWwrong binary caller execs a decoysame PID, new image AUDIT TOKEN CHAIN connectionaudit token SecCode from tokenkernel-held identity CheckValidityagainst requirement DENYrace has no effect
The two chains differ in one link. A PID names a slot that the caller can refill; an audit token names the peer the kernel is actually talking to.

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_pidpathkSecGuestAttributePidSecCodeCopyGuestWithAttributesSecStaticCodeCheckValidity ü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.

Kaynaklar ve güncellik

Bu teknik not ne kadar güncel?

Kontrol edilen kaynaklar31 Ağustos 2026

En son kaynak incelemesi, içerik güncellemesi veya yayın tarihi gösterilmektedir.

İnceleme DurumuKamuya açık kaynaklar incelendi

Birincil kamu kayıtları kontrol edildi. Ortama özgü davranış, ayrıca yeniden üretilmedikçe iddianın dışındadır.