macOS IPC - Inter Process Communication
Last updated
Last updated
AWS Hacking öğrenin ve pratik yapın:HackTricks Training AWS Red Team Expert (ARTE) GCP Hacking öğrenin ve pratik yapın: HackTricks Training GCP Red Team Expert (GRTE)
Mach, kaynakları paylaşmak için en küçük birim olarak görevleri kullanır ve her görev birden fazla iş parçacığı içerebilir. Bu görevler ve iş parçacıkları, POSIX süreçleri ve iş parçacıklarıyla 1:1 eşlenir.
Görevler arasındaki iletişim, tek yönlü iletişim kanallarını kullanarak Mach Araçlar Arası İletişim (IPC) aracılığıyla gerçekleşir. **Mesajlar, çekirdek tarafından yönetilen türde mesaj kuyrukları işlevi gören portlar arasında aktarılır.
Bir port, Mach IPC'nin temel unsurudur. Mesaj göndermek ve almak için kullanılabilir.
Her sürecin bir IPC tablosu vardır; burada sürecin mach portlarını bulmak mümkündür. Bir mach portunun adı aslında bir numaradır (çekirdek nesnesine bir işaretçi).
Bir süreç, bazı haklarla birlikte bir port adını farklı bir göreve gönderebilir ve çekirdek bu girişi diğer görevin IPC tablosunda görünür hale getirir.
Bir görevin hangi işlemleri gerçekleştirebileceğini tanımlayan port hakları, bu iletişim için anahtardır. Olası port hakları şunlardır (tanımlar buradan):
Alma hakkı, portta gönderilen mesajları almayı sağlar. Mach portları MPSC (çoklu üretici, tek tüketici) kuyruklarıdır, bu da demektir ki, sistemde her port için yalnızca bir alma hakkı olabilir (borularla karşılaştırıldığında, birden fazla süreç bir borunun okuma ucuna dosya tanımlayıcıları tutabilir).
Alma hakkına sahip bir görev, mesaj alabilir ve Gönderme hakları oluşturabilir, böylece mesaj gönderebilir. Başlangıçta yalnızca kendi görevinde Alma hakkı vardır.
Alma hakkının sahibi ölürse veya onu öldürürse, gönderme hakkı işe yaramaz hale gelir (ölü ad).
Gönderme hakkı, portta mesaj göndermeyi sağlar.
Gönderme hakkı kopyalanabilir, böylece bir Gönderme hakkına sahip bir görev, hakkı kopyalayabilir ve üçüncü bir göreve verebilir.
Port haklarının Mac mesajları aracılığıyla da geçirilebileceğini unutmayın.
Bir kez gönderme hakkı, portta bir mesaj göndermeyi sağlar ve ardından kaybolur.
Bu hak kopyalanamaz, ancak taşınabilir.
Port set hakkı, tek bir port yerine bir port setini belirtir. Bir port setinden bir mesaj çıkarmak, içerdiği portlardan birinden bir mesaj çıkarmak anlamına gelir. Port setleri, Unix'teki select
/poll
/epoll
/kqueue
gibi birden fazla portta aynı anda dinlemek için kullanılabilir.
Ölü ad, gerçek bir port hakkı değildir, sadece bir yer tutucudur. Bir port yok edildiğinde, port için mevcut tüm port hakları ölü adlara dönüşür.
Görevler, diğerlerine GÖNDERME haklarını aktarabilir, böylece geri mesaj gönderebilirler. GÖNDERME hakları da kopyalanabilir, böylece bir görev, hakkı çoğaltabilir ve üçüncü bir göreve verebilir. Bu, bootstrap sunucusu olarak bilinen bir ara süreçle birleştirildiğinde, görevler arasında etkili iletişim sağlar.
Dosya portları, dosya tanımlayıcılarını Mac portlarında kapsüllemeyi sağlar (Mach port haklarını kullanarak). Verilen bir FD'den fileport_makeport
kullanarak bir fileport
oluşturmak ve bir fileport'tan fileport_makefd
kullanarak bir FD oluşturmak mümkündür.
Daha önce belirtildiği gibi, Mach mesajları kullanarak hak göndermek mümkündür, ancak bir Mach mesajı göndermek için zaten bir hakka sahip olmadan hak gönderemezsiniz. Peki, ilk iletişim nasıl kurulur?
Bunun için bootstrap sunucusu (launchd mac'te) devreye girer; çünkü herkes bootstrap sunucusuna bir GÖNDERME hakkı alabilir, başka bir sürece mesaj göndermek için bir hak talep edebilir:
Görev A, yeni bir port oluşturur ve bunun üzerinde ALMA hakkı alır.
Görev A, ALMA hakkının sahibi olarak, port için bir GÖNDERME hakkı oluşturur.
Görev A, bootstrap sunucusuyla bir bağlantı kurar ve başlangıçta oluşturduğu port için GÖNDERME hakkını ona gönderir.
Herkes bootstrap sunucusuna bir GÖNDERME hakkı alabilir.
Görev A, bootstrap sunucusuna bootstrap_register
mesajı gönderir ve verilen portu com.apple.taska
gibi bir adla ilişkilendirir.
Görev B, bootstrap sunucusuyla etkileşime geçerek bir bootstrap hizmet adı için arama yapar (bootstrap_lookup
). Bootstrap sunucusu yanıt verebilmesi için, görev B, arama mesajı içinde daha önce oluşturduğu bir port için GÖNDERME hakkını ona gönderecektir. Arama başarılı olursa, sunucu, Görev A'dan aldığı GÖNDERME hakkını çoğaltır ve Görev B'ye iletir.
Herkes bootstrap sunucusuna bir GÖNDERME hakkı alabilir.
Bu GÖNDERME hakkıyla, Görev B, Görev A'ya bir mesaj gönderebilir.
İki yönlü iletişim için genellikle görev B, bir ALMA hakkı ve bir GÖNDERME hakkı ile yeni bir port oluşturur ve GÖNDERME hakkını Görev A'ya verir, böylece Görev B'ye mesaj gönderebilir (iki yönlü iletişim).
Bootstrap sunucusu, bir görevin iddia ettiği hizmet adını doğrulayamaz. Bu, bir görevin potansiyel olarak herhangi bir sistem görevini taklit edebileceği anlamına gelir; örneğin, yanlış bir şekilde bir yetkilendirme hizmet adı iddia edip her isteği onaylayabilir.
Daha sonra, Apple, sistem tarafından sağlanan hizmetlerin adlarını güvenli yapılandırma dosyalarında saklar; bu dosyalar SIP korumalı dizinlerde bulunur: /System/Library/LaunchDaemons
ve /System/Library/LaunchAgents
. Her hizmet adıyla birlikte, ilişkili ikili dosya da saklanır. Bootstrap sunucusu, bu hizmet adları için her biri için bir ALMA hakkı oluşturur ve tutar.
Bu önceden tanımlanmış hizmetler için, arama süreci biraz farklıdır. Bir hizmet adı arandığında, launchd hizmeti dinamik olarak başlatır. Yeni iş akışı şu şekildedir:
Görev B, bir hizmet adı için bootstrap arama başlatır.
launchd, görevin çalışıp çalışmadığını kontrol eder ve çalışmıyorsa, başlatır.
Görev A (hizmet), bootstrap check-in (bootstrap_check_in()
) gerçekleştirir. Burada, bootstrap sunucusu bir GÖNDERME hakkı oluşturur, bunu saklar ve ALMA hakkını Görev A'ya aktarır.
launchd, GÖNDERME hakkını çoğaltır ve Görev B'ye gönderir.
Görev B, bir ALMA hakkı ve bir GÖNDERME hakkı ile yeni bir port oluşturur ve GÖNDERME hakkını Görev A'ya (hizmet) verir, böylece Görev B'ye mesaj gönderebilir (iki yönlü iletişim).
Ancak, bu süreç yalnızca önceden tanımlanmış sistem görevleri için geçerlidir. Sistem dışı görevler, başlangıçta açıklandığı gibi çalışmaya devam eder, bu da taklit olasılığını artırabilir.
Bu nedenle, launchd asla çökmemelidir, aksi takdirde tüm sistem çöker.
Buradan daha fazla bilgi edinin
mach_msg
fonksiyonu, esasen bir sistem çağrısıdır ve Mach mesajlarını göndermek ve almak için kullanılır. Fonksiyon, gönderilecek mesajı ilk argüman olarak gerektirir. Bu mesaj, mach_msg_header_t
yapısıyla başlamalı ve ardından gerçek mesaj içeriği gelmelidir. Yapı şu şekilde tanımlanmıştır:
Processes possessing a receive right can receive messages on a Mach port. Conversely, the senders are granted a send or a send-once right. The send-once right is exclusively for sending a single message, after which it becomes invalid.
The initial field msgh_bits
is a bitmap:
İlk bit (en anlamlı) bir mesajın karmaşık olduğunu belirtmek için kullanılır (bununla ilgili daha fazla bilgi aşağıda)
ve 4. bitler çekirdek tarafından kullanılır
2. baytın 5 en az anlamlı biti voucher için kullanılabilir: anahtar/değer kombinasyonlarını göndermek için başka bir port türü.
3. baytın 5 en az anlamlı biti local port için kullanılabilir
4. baytın 5 en az anlamlı biti remote port için kullanılabilir
The types that can be specified in the voucher, local and remote ports are (from mach/message.h):
Örneğin, MACH_MSG_TYPE_MAKE_SEND_ONCE
bu port için bir send-once hakkının türetilmesi ve aktarılması gerektiğini belirtmek için kullanılabilir. Ayrıca, alıcının yanıt verememesi için MACH_PORT_NULL
olarak da belirtilebilir.
Kolay bir iki yönlü iletişim sağlamak için bir işlem, mesajın alıcı'sının bu mesaja yanıt gönderebileceği yanıt portu (msgh_local_port
) olarak adlandırılan bir mach portu belirtebilir.
Bu tür iki yönlü iletişimin, bir yanıt bekleyen XPC mesajlarında kullanıldığını unutmayın (xpc_connection_send_message_with_reply
ve xpc_connection_send_message_with_reply_sync
). Ancak genellikle farklı portlar oluşturulur; daha önce açıklandığı gibi iki yönlü iletişim oluşturmak için.
Mesaj başlığının diğer alanları şunlardır:
msgh_size
: tüm paketin boyutu.
msgh_remote_port
: bu mesajın gönderildiği port.
msgh_voucher_port
: mach kuponları.
msgh_id
: bu mesajın alıcı tarafından yorumlanan kimliği.
mach mesajlarının mach portu
üzerinden gönderildiğini unutmayın; bu, mach çekirdeğine entegre edilmiş tek alıcı, birden fazla gönderici iletişim kanalıdır. Birden fazla işlem, bir mach portuna mesaj gönderebilir, ancak herhangi bir anda yalnızca tek bir işlem ondan okuyabilir.
Mesajlar, mach_msg_header_t
başlığı, ardından gövde ve trailer (varsa) ile oluşur ve buna yanıt verme izni verebilir. Bu durumlarda, çekirdek yalnızca mesajı bir görevden diğerine iletmek zorundadır.
Bir trailer, çekirdek tarafından mesaja eklenen bilgidir (kullanıcı tarafından ayarlanamaz) ve mesaj alımında MACH_RCV_TRAILER_<trailer_opt>
bayrakları ile talep edilebilir (talep edilebilecek farklı bilgiler vardır).
Ancak, ek port hakları geçiren veya bellek paylaşan daha karmaşık mesajlar da vardır; burada çekirdek bu nesneleri alıcıya göndermek zorundadır. Bu durumlarda, başlığın en anlamlı biti msgh_bits
ayarlanır.
Geçirilebilecek olası tanımlayıcılar mach/message.h
dosyasında tanımlanmıştır:
In 32bit'te, tüm tanımlayıcılar 12B'dir ve tanımlayıcı türü 11. bayttadır. 64 bit'te, boyutlar değişir.
Çekirdek, tanımlayıcıları bir görevden diğerine kopyalayacaktır, ancak önce çekirdek belleğinde bir kopya oluşturacaktır. "Feng Shui" olarak bilinen bu teknik, bir sürecin kendisine tanımlayıcılar göndermesini sağlamak için birkaç istismarda kötüye kullanılmıştır. Ardından süreç, mesajları alabilir (çekirdek bunları serbest bırakacaktır).
Ayrıca, savunmasız bir sürece port hakları göndermek de mümkündür ve port hakları süreçte sadece görünecektir (bu hakları yönetmese bile).
Portların görev ad alanına bağlı olduğunu unutmayın, bu nedenle bir port oluşturmak veya aramak için görev ad alanı da sorgulanır (daha fazla bilgi için mach/mach_port.h
):
mach_port_allocate
| mach_port_construct
: Bir port oluşturun.
mach_port_allocate
ayrıca bir port seti oluşturabilir: bir grup port üzerinde alma hakkı. Bir mesaj alındığında, nereden geldiği belirtilir.
mach_port_allocate_name
: Portun adını değiştirin (varsayılan 32bit tamsayı)
mach_port_names
: Hedeften port adlarını alın
mach_port_type
: Bir ad üzerindeki bir görev hakkını alın
mach_port_rename
: Bir portu yeniden adlandırın (FD'ler için dup2 gibi)
mach_port_allocate
: Yeni bir RECEIVE, PORT_SET veya DEAD_NAME tahsis edin
mach_port_insert_right
: RECEİVE hakkına sahip olduğunuz bir portta yeni bir hak oluşturun
mach_port_...
mach_msg
| mach_msg_overwrite
: mach mesajlarını göndermek ve almak için kullanılan fonksiyonlar. Üzerine yazma versiyonu, mesaj alımı için farklı bir tampon belirtmeye olanak tanır (diğer versiyon sadece onu yeniden kullanır).
mach_msg
ve mach_msg_overwrite
fonksiyonları, mesaj göndermek ve almak için kullanılanlardır, bu nedenle onlara bir kesme noktası koymak, gönderilen ve alınan mesajları incelemeyi sağlar.
Örneğin, libSystem.B
'yi yükleyeceği için hata ayıklayabileceğiniz herhangi bir uygulamayı hata ayıklamaya başlayın.
mach_msg
'nin argümanlarını almak için kayıtları kontrol edin. Bu argümanlar ( mach/message.h üzerinden):
Kayıtlardan değerleri al:
Mesaj başlığını inceleyerek ilk argümanı kontrol edin:
mach_msg_bits_t
türü, bir yanıtı sağlamak için çok yaygındır.
isim, porta verilen varsayılan isimdir (ilk 3 baytta nasıl arttığını kontrol edin). ipc-object
, portun obfuscate edilmiş benzersiz tanımlayıcısıdır.
Ayrıca, yalnızca send
hakkına sahip portların sahibini tanımladığını da not edin (port adı + pid).
Ayrıca, aynı porta bağlı diğer görevleri belirtmek için +
kullanımını da not edin.
Ayrıca, procesxp kullanarak kayıtlı hizmet isimlerini (SIP devre dışı bırakıldığında com.apple.system-task-port
gerekliliği nedeniyle) görmek de mümkündür:
Bu aracı iOS'ta http://newosxbook.com/tools/binpack64-256.tar.gz adresinden indirerek kurabilirsiniz.
Gönderenin bir port ayırdığını, org.darlinghq.example
adı için bir gönderim hakkı oluşturduğunu ve bunu bootstrap sunucusuna gönderdiğini, gönderenin o adın gönderim hakkını talep ettiğini ve bunu bir mesaj göndermek için kullandığını not edin.
Belirli hassas eylemleri gerçekleştirmek veya belirli hassas verilere erişmek için bazı özel portlar vardır; bu portlar üzerinde SEND izinlerine sahip bir görev varsa. Bu, bu portları saldırganlar açısından sadece yetenekleri nedeniyle değil, aynı zamanda görevler arasında SEND izinlerini paylaşmanın mümkün olması nedeniyle de çok ilginç kılar.
Bu portlar bir numara ile temsil edilir.
SEND hakları host_get_special_port
çağrısı ile elde edilebilir ve RECEIVE hakları host_set_special_port
çağrısı ile elde edilir. Ancak, her iki çağrı da yalnızca root'un erişebileceği host_priv
portunu gerektirir. Dahası, geçmişte root, host_set_special_port
çağrısı yaparak, örneğin HOST_KEXTD_PORT
'u ele geçirerek kod imzalarını atlatmasına olanak tanıyan keyfi bir portu ele geçirebiliyordu (SIP şimdi bunu engelliyor).
Bunlar 2 gruba ayrılır: İlk 7 port çekirdek tarafından sahiplenilmiştir; bunlar 1 HOST_PORT
, 2 HOST_PRIV_PORT
, 3 HOST_IO_MASTER_PORT
ve 7 HOST_MAX_SPECIAL_KERNEL_PORT
'dur.
8 numarasından başlayanlar sistem daemon'ları tarafından sahiplenilmiştir ve host_special_ports.h
dosyasında tanımlanmışlardır.
Ana Bilgisayar portu: Eğer bir süreç bu port üzerinde SEND ayrıcalığına sahipse, aşağıdaki gibi rutinlerini çağırarak sistem hakkında bilgi alabilir:
host_processor_info
: İşlemci bilgilerini al
host_info
: Ana bilgisayar bilgilerini al
host_virtual_physical_table_info
: Sanal/Fiziksel sayfa tablosu (MACH_VMDEBUG gerektirir)
host_statistics
: Ana bilgisayar istatistiklerini al
mach_memory_info
: Çekirdek bellek düzenini al
Ana Bilgisayar Priv portu: Bu port üzerinde SEND hakkına sahip bir süreç, önyükleme verilerini gösterme veya bir çekirdek uzantısını yüklemeye çalışma gibi ayrıcalıklı eylemler gerçekleştirebilir. Bu izni almak için süreç root olmalıdır.
Dahası, kext_request
API'sini çağırmak için yalnızca Apple ikili dosyalarına verilen diğer yetkilere com.apple.private.kext*
sahip olunması gerekmektedir.
Çağrılabilecek diğer rutinler şunlardır:
host_get_boot_info
: machine_boot_info()
'yu al
host_priv_statistics
: Ayrıcalıklı istatistikleri al
vm_allocate_cpm
: Sürekli Fiziksel Bellek ayır
host_processors
: Ana bilgisayar işlemcilerine gönderim hakkı
mach_vm_wire
: Belleği kalıcı hale getir
Root bu izne erişebildiğinden, host_set_[special/exception]_port[s]
çağrısını yaparak ana bilgisayar özel veya istisna portlarını ele geçirebilir.
Tüm ana bilgisayar özel portlarını görmek için şu komutu çalıştırmak mümkündür:
Bunlar, iyi bilinen hizmetler için ayrılmış portlardır. task_[get/set]_special_port
çağrısı yaparak bunları almak/ayarlamak mümkündür. task_special_ports.h
dosyasında bulunabilirler:
From here:
TASK_KERNEL_PORT[task-self send right]: Bu görevi kontrol etmek için kullanılan port. Görevi etkileyen mesajları göndermek için kullanılır. Bu, mach_task_self tarafından döndürülen porttur (aşağıdaki Görev Portları'na bakın).
TASK_BOOTSTRAP_PORT[bootstrap send right]: Görevin bootstrap portu. Diğer sistem hizmet portlarının geri dönüşünü talep eden mesajları göndermek için kullanılır.
TASK_HOST_NAME_PORT[host-self send right]: İçinde bulunduğu ana bilgisayarın bilgilerini talep etmek için kullanılan port. Bu, mach_host_self tarafından döndürülen porttur.
TASK_WIRED_LEDGER_PORT[ledger send right]: Bu görevin çekirdek bellek kullandığı kaynağı adlandıran port.
TASK_PAGED_LEDGER_PORT[ledger send right]: Bu görevin varsayılan bellek yönetimi kullandığı kaynağı adlandıran port.
Başlangıçta Mach "işlemler" yerine "görevler" kullanıyordu, bu da daha çok bir iş parçacığı konteyneri olarak kabul ediliyordu. Mach, BSD ile birleştirildiğinde her görev bir BSD işlemi ile ilişkilendirildi. Bu nedenle her BSD işlemi, bir işlem olabilmek için ihtiyaç duyduğu ayrıntılara sahiptir ve her Mach görevi de iç işleyişine sahiptir (mevcut olmayan pid 0 hariç, bu kernel_task
'dir).
Buna ilişkin iki çok ilginç işlev vardır:
task_for_pid(target_task_port, pid, &task_port_of_pid)
: Belirtilen pid
ile ilişkili görevin portu için bir GÖNDERİM hakkı alır ve bunu belirtilen target_task_port
'a verir (genellikle mach_task_self()
kullanmış olan çağıran görevdir, ancak farklı bir görev üzerindeki bir GÖNDERİM portu da olabilir).
pid_for_task(task, &pid)
: Bir göreve GÖNDERİM hakkı verildiğinde, bu görevin hangi PID ile ilişkili olduğunu bulur.
Görev içinde eylemler gerçekleştirmek için, görev kendisine mach_task_self()
çağrısı yaparak bir GÖNDERİM
hakkına ihtiyaç duyar (bu task_self_trap
(28) kullanır). Bu izinle bir görev, aşağıdaki gibi çeşitli eylemleri gerçekleştirebilir:
task_threads
: Görevin iş parçacıklarının tüm görev portları üzerinde GÖNDERİM hakkı al
task_info
: Bir görev hakkında bilgi al
task_suspend/resume
: Bir görevi askıya al veya devam ettir
task_[get/set]_special_port
thread_create
: Bir iş parçacığı oluştur
task_[get/set]_state
: Görev durumunu kontrol et
ve daha fazlası mach/task.h içinde bulunabilir.
Fark edin ki, farklı bir görev üzerindeki bir görev portu üzerinde GÖNDERİM hakkı ile, farklı bir görev üzerinde bu tür eylemler gerçekleştirmek mümkündür.
Ayrıca, task_port aynı zamanda vm_map
portudur ve bu, vm_read()
ve vm_write()
gibi işlevlerle bir görev içinde belleği okuma ve manipüle etme olanağı sağlar. Bu, temelde, farklı bir görevin task_port'u üzerinde GÖNDERİM haklarına sahip bir görevin, o göreve kod enjekte edebileceği anlamına gelir.
Unutmayın ki çekirdek de bir görevdir, eğer biri kernel_task
üzerinde GÖNDERİM izinleri almayı başarırsa, çekirdeğin herhangi bir şeyi çalıştırmasını sağlayabilir (jailbreak'ler).
Çağıran görev için bu portun adını almak için mach_task_self()
çağrısını yapın. Bu port yalnızca exec()
üzerinden devralınır; fork()
ile oluşturulan yeni bir görev yeni bir görev portu alır (özel bir durum olarak, bir görev exec()
sonrası bir suid ikili dosyasında da yeni bir görev portu alır). Bir görevi başlatmanın ve portunu almanın tek yolu, fork()
yaparken "port swap dance" gerçekleştirmektir.
Portu erişim kısıtlamaları (binary AppleMobileFileIntegrity
'den macos_task_policy
):
Uygulama com.apple.security.get-task-allow
yetkisine sahipse, aynı kullanıcıdan gelen işlemler görev portuna erişebilir (genellikle hata ayıklama için Xcode tarafından eklenir). Notarizasyon süreci bunu üretim sürümlerine izin vermez.
com.apple.system-task-ports
yetkisine sahip uygulamalar, herhangi bir işlem için görev portunu alabilir, çekirdek hariç. Eski sürümlerde buna task_for_pid-allow
denirdi. Bu yalnızca Apple uygulamalarına verilir.
Root, hardened çalışma zamanı ile derlenmemiş uygulamaların görev portlarına erişebilir (ve Apple'dan olmayan).
Görev adı portu: görev portu için ayrıcalıksız bir versiyon. Görevi referans alır, ancak onu kontrol etmeye izin vermez. Bunun aracılığıyla mevcut olan tek şey task_info()
gibi görünmektedir.
İş parçacıklarının da ilişkili portları vardır, bu portlar task_threads
çağrısı yapan görevden ve processor_set_threads
ile işlemciden görünür. İş parçacığı portu üzerindeki bir GÖNDERİM hakkı, thread_act
alt sisteminden işlevleri kullanmaya izin verir, örneğin:
thread_terminate
thread_[get/set]_state
act_[get/set]_state
thread_[suspend/resume]
thread_info
...
Herhangi bir iş parçacığı, mach_thread_sef
çağrısını yaparak bu portu alabilir.
Bir shellcode'u şu adresten alabilirsiniz:
Önceki programı derleyin ve aynı kullanıcı ile kod enjekte edebilmek için yetkilendirmeleri ekleyin (aksi takdirde sudo kullanmanız gerekecek).