MacOS'ta işlemler ve çekirdek uzantıları nasıl korunur?

Merhaba Habr! Bugün macOS'ta süreçleri saldırganların saldırılarından nasıl koruyabileceğinizden bahsetmek istiyorum. Örneğin, bu bir antivirüs veya yedekleme sistemi için kullanışlıdır, özellikle de macOS altında bir işlemi "sonlandırmanın" birkaç yolu olduğundan. Bu konuyu ve kesim altındaki koruma yöntemlerini okuyun.

MacOS'ta işlemler ve çekirdek uzantıları nasıl korunur?

Bir süreci "öldürmenin" klasik yolu

Bir süreci "sonlandırmanın" iyi bilinen bir yolu, sürece bir SIGKILL sinyali göndermektir. Bash aracılığıyla öldürmek için "kill -SIGKILL PID" veya "pkill -9 NAME" standardını arayabilirsiniz. "Kill" komutu UNIX günlerinden beri biliniyor ve yalnızca macOS'ta değil, diğer UNIX benzeri sistemlerde de mevcut.

UNIX benzeri sistemlerde olduğu gibi macOS, SIGKILL ve SIGSTOP olmak üzere iki işlem dışındaki tüm sinyalleri yakalamanıza olanak tanır. Bu makale öncelikle bir sürecin sonlandırılmasına neden olan bir sinyal olarak SIGKILL sinyaline odaklanacaktır.

macOS özellikleri

MacOS'ta, XNU çekirdeğindeki kill sistem çağrısı psignal(SIGKILL,...) işlevini çağırır. Kullanıcı alanındaki başka hangi kullanıcı eylemlerinin psignal işlevi tarafından çağrılabileceğini görmeye çalışalım. Çekirdeğin iç mekanizmalarındaki psignal işlevine yapılan çağrıları ayıklayalım (önemsiz olmalarına rağmen bunları başka bir makaleye bırakacağız 🙂 - imza doğrulama, bellek hataları, çıkış/sonlandırma işlemleri, dosya koruma ihlalleri, vb.) .

İncelemeye işlev ve ilgili sistem çağrısıyla başlayalım sonlandırma_with_payload. Klasik kill çağrısının yanı sıra macOS işletim sistemine özel ve BSD'de bulunmayan alternatif bir yaklaşımın da olduğu görülüyor. Her iki sistem çağrısının çalışma prensipleri de benzerdir. Bunlar çekirdek fonksiyonu psinyaline doğrudan yapılan çağrılardır. Ayrıca, bir işlemi sonlandırmadan önce, işlemin başka bir işleme sinyal gönderip gönderemeyeceği konusunda bir "cansignal" kontrolü gerçekleştirildiğini unutmayın; sistem, örneğin herhangi bir uygulamanın sistem işlemlerini sonlandırmasına izin vermez.

static int
terminate_with_payload_internal(struct proc *cur_proc, int target_pid, uint32_t reason_namespace,
				uint64_t reason_code, user_addr_t payload, uint32_t payload_size,
				user_addr_t reason_string, uint64_t reason_flags)
{
...
	target_proc = proc_find(target_pid);
...
	if (!cansignal(cur_proc, cur_cred, target_proc, SIGKILL)) {
		proc_rele(target_proc);
		return EPERM;
	}
...
	if (target_pid == cur_proc->p_pid) {
		/*
		 * psignal_thread_with_reason() will pend a SIGKILL on the specified thread or
		 * return if the thread and/or task are already terminating. Either way, the
		 * current thread won't return to userspace.
		 */
		psignal_thread_with_reason(target_proc, current_thread(), SIGKILL, signal_reason);
	} else {
		psignal_with_reason(target_proc, SIGKILL, signal_reason);
	}
...
}

launchd

Sistem başlangıcında daemon oluşturmanın ve bunların ömrünü kontrol etmenin standart yolu, başlatılmasıdır. Lütfen kaynakların macOS 10.10'a kadar olan launchctl'nin eski sürümü için olduğunu unutmayın; kod örnekleri açıklama amaçlı verilmiştir. Modern launchctl, launchd sinyallerini XPC aracılığıyla gönderir, launchctl mantığı ona taşınmıştır.

Uygulamaların tam olarak nasıl durdurulduğuna bakalım. SIGTERM sinyalini göndermeden önce “proc_terminate” sistem çağrısı kullanılarak uygulama durdurulmaya çalışılır.

<launchctl src/core.c>
...
	error = proc_terminate(j->p, &sig);
	if (error) {
		job_log(j, LOG_ERR | LOG_CONSOLE, "Could not terminate job: %d: %s", error, strerror(error));
		job_log(j, LOG_NOTICE | LOG_CONSOLE, "Using fallback option to terminate job...");
		error = kill2(j->p, SIGTERM);
		if (error) {
			job_log(j, LOG_ERR, "Could not signal job: %d: %s", error, strerror(error));
		} 
...
<>

Proc_terminate, ismine rağmen yalnızca SIGTERM ile psinyal göndermekle kalmıyor, aynı zamanda SIGKILL'i de gönderebiliyor.

Dolaylı Öldürme - Kaynak Sınırı

Başka bir sistem çağrısında daha ilginç bir durum görülebilir süreç_politikası. Bu sistem çağrısının yaygın bir kullanımı, bir dizin oluşturucunun CPU süresini ve bellek kotalarını sınırlaması gibi uygulama kaynaklarını sınırlamaktır; böylece sistem, dosya önbelleğe alma etkinlikleri nedeniyle önemli ölçüde yavaşlamaz. Proc_apply_resource_actions fonksiyonundan görülebileceği gibi bir uygulama kaynak sınırına ulaştıysa işleme bir SIGKILL sinyali gönderilir.

Bu sistem çağrısı potansiyel olarak bir işlemi sonlandırabilirse de sistem, sistem çağrısını çağıran işlemin haklarını yeterince kontrol etmedi. Aslında kontrol ediyorum orada, ancak bu koşulu atlamak için PROC_POLICY_ACTION_SET alternatif işaretini kullanmak yeterlidir.

Dolayısıyla, uygulamanın CPU kullanım kotasını "sınırlarsanız" (örneğin, yalnızca 1 ns'nin çalışmasına izin verirseniz), sistemdeki herhangi bir işlemi sonlandırabilirsiniz. Böylece kötü amaçlı yazılım, antivirüs işlemi de dahil olmak üzere sistemdeki herhangi bir işlemi sonlandırabilir. Ayrıca ilginç olan, bir işlemi pid 1 (launchctl) ile sonlandırırken ortaya çıkan etkidir - SIGKILL sinyalini işlemeye çalışırken çekirdek paniği :)

MacOS'ta işlemler ve çekirdek uzantıları nasıl korunur?

Sorun nasıl çözülür?

Bir işlemin öldürülmesini önlemenin en basit yolu, sistem çağrı tablosundaki işlev işaretçisini değiştirmektir. Ne yazık ki, bu yöntem birçok nedenden dolayı önemsiz değildir.

İlk olarak, sysent'in hafıza konumunu kontrol eden sembol yalnızca XNU çekirdek sembolüne özel değildir, aynı zamanda çekirdek sembollerinde de bulunamaz. İşlevi dinamik olarak parçalara ayırmak ve içinde bir işaretçi aramak gibi buluşsal arama yöntemlerini kullanmanız gerekecektir.

İkinci olarak, tablodaki girişlerin yapısı çekirdeğin derlendiği bayraklara bağlıdır. CONFIG_REQUIRES_U32_MUNGING bayrağı bildirilirse yapının boyutu değişecek - ek bir alan eklenecek sy_arg_munge32. Çekirdeğin hangi bayrakla derlendiğini belirlemek için ek bir kontrol yapılması veya alternatif olarak işlev işaretçilerinin bilinenlerle karşılaştırılması gerekir.

struct sysent {         /* system call table */
        sy_call_t       *sy_call;       /* implementing function */
#if CONFIG_REQUIRES_U32_MUNGING || (__arm__ && (__BIGGEST_ALIGNMENT__ > 4))
        sy_munge_t      *sy_arg_munge32; /* system call arguments munger for 32-bit process */
#endif
        int32_t         sy_return_type; /* system call return types */
        int16_t         sy_narg;        /* number of args */
        uint16_t        sy_arg_bytes;   /* Total size of arguments in bytes for
                                         * 32-bit system calls
                                         */
};

Neyse ki, macOS'un modern sürümlerinde Apple, işlemlerle çalışmak için yeni bir API sağlıyor. Endpoint Security API, istemcilerin diğer işlemlere yönelik birçok isteği yetkilendirmesine olanak tanır. Böylece yukarıda belirtilen API'yi kullanarak SIGKILL sinyali de dahil olmak üzere işlemlere giden tüm sinyalleri engelleyebilirsiniz.

#include <bsm/libbsm.h>
#include <EndpointSecurity/EndpointSecurity.h>
#include <unistd.h>

int main(int argc, const char * argv[]) {
    es_client_t* cli = nullptr;
    {
        auto res = es_new_client(&cli, ^(es_client_t * client, const es_message_t * message) {
            switch (message->event_type) {
                case ES_EVENT_TYPE_AUTH_SIGNAL:
                {
                    auto& msg = message->event.signal;
                    auto target = msg.target;
                    auto& token = target->audit_token;
                    auto pid = audit_token_to_pid(token);
                    printf("signal '%d' sent to pid '%d'n", msg.sig, pid);
                    es_respond_auth_result(client, message, pid == getpid() ? ES_AUTH_RESULT_DENY : ES_AUTH_RESULT_ALLOW, false);
                }
                    break;
                default:
                    break;
            }
        });
    }

    {
        es_event_type_t evs[] = { ES_EVENT_TYPE_AUTH_SIGNAL };
        es_subscribe(cli, evs, sizeof(evs) / sizeof(*evs));
    }

    printf("%dn", getpid());
    sleep(60); // could be replaced with other waiting primitive

    es_unsubscribe_all(cli);
    es_delete_client(cli);

    return 0;
}

Benzer şekilde, çekirdeğe bir sinyal koruma yöntemi (policy proc_check_signal) sağlayan bir MAC Politikası kaydedilebilir, ancak API resmi olarak desteklenmez.

Çekirdek uzantısı koruması

Sistemdeki süreçlerin korunmasının yanı sıra çekirdek uzantısının (kext) kendisinin de korunması gerekir. macOS, geliştiricilerin IOKit aygıt sürücülerini kolayca geliştirmeleri için bir çerçeve sağlar. IOKit, aygıtlarla çalışmaya yönelik araçlar sağlamanın yanı sıra, C++ sınıflarının örneklerini kullanarak sürücü yığınlamaya yönelik yöntemler de sağlar. Kullanıcı alanındaki bir uygulama, çekirdek-kullanıcı alanı ilişkisi kurmak için sınıfın kayıtlı bir örneğini "bulabilecektir".

Sistemdeki sınıf örneklerinin sayısını tespit etmek için ioclasscount yardımcı programı vardır.

my_kext_ioservice = 1
my_kext_iouserclient = 1

Sürücü yığınına kaydolmak isteyen herhangi bir çekirdek uzantısı, IOService'den miras alan bir sınıf bildirmelidir, örneğin bu durumda my_kext_ioservice. Kullanıcı uygulamalarının bağlanması, my_kext_iouserclient örneğinde olduğu gibi, IOUserClient'ten miras alan sınıfın yeni bir örneğinin oluşturulmasına neden olur.

Bir sürücüyü sistemden kaldırmaya çalışırken (kextunload komutu), "bool sonlandırma(IOOptionBits options)" sanal işlevi çağrılır. Kextunload'u devre dışı bırakmak için boşaltmaya çalışırken çağrıyı sonlandırmak için false döndürmek yeterlidir.

bool Kext::terminate(IOOptionBits options)
{

  if (!IsUnloadAllowed)
  {
    // Unload is not allowed, returning false
    return false;
  }

  return super::terminate(options);
}

IsUnloadAllowed bayrağı yükleme sırasında IOUserClient tarafından ayarlanabilir. İndirme sınırı olduğunda kextunload komutu aşağıdaki çıktıyı döndürecektir:

admin@admins-Mac drivermanager % sudo kextunload ./test.kext
Password:
(kernel) Can't remove kext my.kext.test; services failed to terminate - 0xe00002c7.
Failed to unload my.kext.test - (iokit/common) unsupported function.

IOUserClient için de benzer korumanın yapılması gerekir. Sınıfların örnekleri, IOKitLib kullanıcı alanı işlevi "IOCatalogueTerminate(mach_port_t, uint32_t flag, io_name_t açıklama);" kullanılarak kaldırılabilir. Kullanıcı alanı uygulaması "ölene", yani "clientDied" işlevi çağrılmayana kadar "terminate" komutunu çağırırken false değerini döndürebilirsiniz.

Dosya koruması

Dosyaları korumak için dosyalara erişimi kısıtlamanıza olanak tanıyan Kauth API'sini kullanmanız yeterlidir. Apple, geliştiricilere kapsamdaki çeşitli etkinliklerle ilgili bildirimler sağlar; bizim için KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA ve KAUTH_VNODE_DELETE_CHILD işlemleri önemlidir. Dosyalara erişimi kısıtlamanın en kolay yolu yoldur; dosyanın yolunu bulmak ve yol önekini karşılaştırmak için “vn_getpath” API'sini kullanırız. Dosya klasörü yollarının yeniden adlandırılmasını optimize etmek için sistemin her dosyaya erişime izin vermediğini, yalnızca yeniden adlandırılan klasörün kendisine erişim yetkisi verdiğini unutmayın. Ana yolu karşılaştırmak ve bunun için KAUTH_VNODE_DELETE'yi kısıtlamak gerekir.

MacOS'ta işlemler ve çekirdek uzantıları nasıl korunur?

Bu yaklaşımın dezavantajı önek sayısı arttıkça performansın düşmesi olabilir. Karşılaştırmanın O'ya(önek*uzunluk) eşit olmadığından emin olmak için, burada önek öneklerin sayısıdır, uzunluk ise dizenin uzunluğudur, öneklerle oluşturulan deterministik bir sonlu otomat (DFA) kullanabilirsiniz.

Belirli bir önek kümesi için DFA oluşturmaya yönelik bir yöntem düşünelim. İmleçleri her önekin başlangıcında başlatırız. Tüm imleçler aynı karakteri gösteriyorsa, her imleci bir karakter artırın ve aynı satırın uzunluğunun bir karakter daha büyük olduğunu unutmayın. Farklı sembollere sahip iki imleç varsa, imleçleri işaret ettikleri sembole göre gruplara ayırın ve algoritmayı her grup için tekrarlayın.

İlk durumda (imleçlerin altındaki tüm karakterler aynıdır), aynı satırda yalnızca bir geçişe sahip bir DFA durumu elde ederiz. İkinci durumda, işlevin yinelemeli olarak çağrılması yoluyla elde edilen sonraki durumlara 256 boyutunda (karakter sayısı ve maksimum grup sayısı) geçiş tablosu elde ederiz.

Bir örneğe bakalım. Bir dizi önek için (“/foo/bar/tmp/”, “/var/db/foo/”, “/foo/bar/aba/”, “foo/bar/aac/”) aşağıdakileri alabilirsiniz DFA. Şekil yalnızca diğer durumlara yol açan geçişleri göstermektedir; diğer geçişler nihai olmayacaktır.

MacOS'ta işlemler ve çekirdek uzantıları nasıl korunur?

DKA eyaletlerinden geçerken 3 vaka olabiliyor.

  1. Son duruma ulaşıldı - yol korunuyor, KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA ve KAUTH_VNODE_DELETE_CHILD işlemlerini sınırlıyoruz
  2. Nihai duruma ulaşılmadı, ancak yol "sonlandı" (boş sonlandırıcıya ulaşıldı) - yol bir ebeveyndir, KAUTH_VNODE_DELETE'yi sınırlamak gerekir. Vnode bir klasörse sonuna bir '/' eklemeniz gerektiğini unutmayın, aksi takdirde onu “/foor/bar/t” dosyasıyla sınırlandırabilir ki bu yanlıştır.
  3. Nihai duruma ulaşılamadı, yol bitmedi. Ön eklerin hiçbiri bununla eşleşmiyor, kısıtlama getirmiyoruz.

Sonuç

Geliştirilmekte olan güvenlik çözümlerinin amacı kullanıcının ve verilerinin güvenlik düzeyini arttırmaktır. Bir yandan bu hedefe, işletim sisteminin kendisinin "zayıf" olduğu güvenlik açıklarını kapatan Acronis yazılım ürününün geliştirilmesiyle ulaşılıyor. Öte yandan, özellikle bu tür güvenlik açıklarının kapatılması ürün olarak kendi kararlılığımızı artıracağından, işletim sistemi tarafında geliştirilebilecek güvenlik yönlerini güçlendirmeyi de ihmal etmemeliyiz. Güvenlik açığı Apple Ürün Güvenliği Ekibine bildirildi ve macOS 10.14.5'te (https://support.apple.com/en-gb/HT210119) düzeltildi.

MacOS'ta işlemler ve çekirdek uzantıları nasıl korunur?

Bütün bunlar yalnızca yardımcı programınızın çekirdeğe resmi olarak kurulmuş olması durumunda yapılabilir. Yani harici ve istenmeyen yazılımlar için böyle bir boşluk yoktur. Ancak gördüğünüz gibi antivirüs ve yedekleme sistemleri gibi meşru programların korunması bile çalışma gerektiriyor. Ancak artık macOS için yeni Acronis ürünleri, sistemden yüklemeye karşı ek korumaya sahip olacak.

Kaynak: habr.com

Yorum ekle