Cara melindungi proses dan ekstensi kernel di macOS

Halo, Habr! Hari ini saya ingin berbicara tentang bagaimana Anda dapat melindungi proses dari serangan penyerang di macOS. Misalnya, ini berguna untuk antivirus atau sistem cadangan, terutama karena di macOS ada beberapa cara untuk “mematikan” suatu proses. Baca tentang ini dan metode perlindungan saat dipotong.

Cara melindungi proses dan ekstensi kernel di macOS

Cara klasik untuk “membunuh” suatu proses

Cara yang terkenal untuk “mematikan” suatu proses adalah dengan mengirimkan sinyal SIGKILL ke proses tersebut. Melalui bash Anda dapat memanggil standar “kill -SIGKILL PID” atau “pkill -9 NAME” untuk mematikan. Perintah “kill” telah dikenal sejak zaman UNIX dan tersedia tidak hanya di macOS, tetapi juga di sistem mirip UNIX lainnya.

Sama seperti sistem mirip UNIX, macOS memungkinkan Anda mencegat sinyal apa pun ke suatu proses kecuali dua - SIGKILL dan SIGSTOP. Artikel ini terutama akan fokus pada sinyal SIGKILL sebagai sinyal yang menyebabkan suatu proses terhenti.

spesifik MacOS

Di macOS, panggilan sistem kill di kernel XNU memanggil fungsi psignal(SIGKILL,...). Mari kita coba melihat tindakan pengguna lain apa di ruang pengguna yang dapat dipanggil oleh fungsi psignal. Mari kita singkirkan panggilan ke fungsi psignal dalam mekanisme internal kernel (walaupun mungkin tidak sepele, tapi kita akan meninggalkannya untuk artikel lain 🙂 - verifikasi tanda tangan, kesalahan memori, penanganan keluar/berhenti, pelanggaran perlindungan file, dll.

Mari kita mulai tinjauan dengan fungsi dan panggilan sistem yang sesuai mengakhiri_dengan_payload. Terlihat bahwa selain kill call klasik, terdapat pendekatan alternatif yang khusus untuk sistem operasi macOS dan tidak ditemukan di BSD. Prinsip pengoperasian kedua panggilan sistem juga serupa. Itu adalah panggilan langsung ke fungsi kernel psignal. Perhatikan juga bahwa sebelum menghentikan suatu proses, pemeriksaan “cansignal” dilakukan - apakah proses tersebut dapat mengirim sinyal ke proses lain; sistem tidak mengizinkan aplikasi apa pun untuk mematikan proses sistem, misalnya.

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);
	}
...
}

peluncuran

Cara standar untuk membuat daemon saat startup sistem dan mengontrol masa pakainya adalah dengan meluncurkan. Harap dicatat bahwa sumbernya adalah untuk launchctl versi lama hingga macOS 10.10, contoh kode disediakan untuk tujuan ilustrasi. Launchctl modern mengirimkan sinyal launchd melalui XPC, logika launchctl telah dipindahkan ke sana.

Mari kita lihat bagaimana tepatnya aplikasi dihentikan. Sebelum mengirimkan sinyal SIGTERM, aplikasi dicoba dihentikan menggunakan panggilan sistem “proc_terminate”.

<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));
		} 
...
<>

Di bawah tenda, proc_terminate, terlepas dari namanya, tidak hanya dapat mengirim psignal dengan SIGTERM, tetapi juga SIGKILL.

Pembunuhan Tidak Langsung - Batas Sumber Daya

Kasus yang lebih menarik dapat dilihat pada system call lainnya proses_kebijakan. Penggunaan umum dari panggilan sistem ini adalah untuk membatasi sumber daya aplikasi, seperti pengindeks untuk membatasi waktu CPU dan kuota memori sehingga sistem tidak diperlambat secara signifikan oleh aktivitas cache file. Jika suatu aplikasi telah mencapai batas sumber dayanya, seperti yang terlihat dari fungsi proc_apply_resource_actions, sinyal SIGKILL dikirim ke proses.

Meskipun panggilan sistem ini berpotensi mematikan suatu proses, sistem tidak secara memadai memeriksa hak proses yang memanggil panggilan sistem tersebut. Sebenarnya memeriksa ada, namun cukup menggunakan flag alternatif PROC_POLICY_ACTION_SET untuk melewati kondisi ini.

Oleh karena itu, jika Anda “membatasi” kuota penggunaan CPU aplikasi (misalnya, hanya mengizinkan 1 ns untuk berjalan), maka Anda dapat menghentikan proses apa pun di sistem. Dengan demikian, malware dapat mematikan proses apa pun di sistem, termasuk proses antivirus. Yang juga menarik adalah efek yang terjadi ketika mematikan proses dengan pid 1 (launchctl) - kernel panik ketika mencoba memproses sinyal SIGKILL :)

Cara melindungi proses dan ekstensi kernel di macOS

Bagaimana cara mengatasi masalah tersebut?

Cara paling mudah untuk mencegah proses dihentikan adalah dengan mengganti penunjuk fungsi di tabel panggilan sistem. Sayangnya, metode ini tidak sepele karena berbagai alasan.

Pertama, simbol yang mengontrol lokasi memori sistem tidak hanya bersifat pribadi untuk simbol kernel XNU, namun tidak dapat ditemukan dalam simbol kernel. Anda harus menggunakan metode pencarian heuristik, seperti membongkar fungsi secara dinamis dan mencari pointer di dalamnya.

Kedua, struktur entri dalam tabel bergantung pada flag yang digunakan untuk mengkompilasi kernel. Jika flag CONFIG_REQUIRES_U32_MUNGING dideklarasikan, ukuran struktur akan diubah - kolom tambahan akan ditambahkan sy_arg_munge32. Penting untuk melakukan pemeriksaan tambahan untuk menentukan flag mana yang digunakan untuk mengkompilasi kernel, atau sebagai alternatif, memeriksa pointer fungsi terhadap yang diketahui.

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
                                         */
};

Untungnya, di macOS versi modern, Apple menyediakan API baru untuk bekerja dengan proses. API Keamanan Endpoint memungkinkan klien untuk mengotorisasi banyak permintaan ke proses lain. Dengan demikian, Anda dapat memblokir sinyal apa pun ke proses, termasuk sinyal SIGKILL, menggunakan API yang disebutkan di atas.

#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;
}

Demikian pula, Kebijakan MAC dapat didaftarkan di kernel, yang menyediakan metode perlindungan sinyal (kebijakan proc_check_signal), tetapi API tidak didukung secara resmi.

Perlindungan ekstensi kernel

Selain melindungi proses dalam sistem, melindungi ekstensi kernel itu sendiri (kext) juga diperlukan. macOS menyediakan kerangka kerja bagi pengembang untuk mengembangkan driver perangkat IOKit dengan mudah. Selain menyediakan alat untuk bekerja dengan perangkat, IOKit menyediakan metode untuk penumpukan driver menggunakan instance kelas C++. Sebuah aplikasi di ruang pengguna akan dapat “menemukan” instance kelas yang terdaftar untuk membangun hubungan ruang pengguna-kernel.

Untuk mendeteksi jumlah instance kelas dalam sistem, terdapat utilitas ioclasscount.

my_kext_ioservice = 1
my_kext_iouserclient = 1

Ekstensi kernel apa pun yang ingin didaftarkan ke tumpukan driver harus mendeklarasikan kelas yang diwarisi dari IOService, misalnya my_kext_ioservice dalam kasus ini.Menghubungkan aplikasi pengguna menyebabkan pembuatan instance baru dari kelas yang diwarisi dari IOUserClient, dalam contoh my_kext_iouserclient.

Saat mencoba membongkar driver dari sistem (perintah kextunload), fungsi virtual “bool terminal (opsi IOOptionBits)” dipanggil. Cukup mengembalikan false pada panggilan untuk diakhiri ketika mencoba membongkar untuk menonaktifkan kextunload.

bool Kext::terminate(IOOptionBits options)
{

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

  return super::terminate(options);
}

Bendera IsUnloadAllowed dapat diatur oleh IOUserClient saat memuat. Ketika ada batas download, perintah kextunload akan mengembalikan output berikut:

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.

Perlindungan serupa harus dilakukan untuk IOUserClient. Contoh kelas dapat dibongkar menggunakan fungsi ruang pengguna IOKitLib “IOCatalogueTerminate(mach_port_t, uint32_t flag, io_name_t description);”. Anda dapat mengembalikan false ketika memanggil perintah "terminate" sampai aplikasi userspace "mati", yaitu fungsi "clientDied" tidak dipanggil.

Perlindungan file

Untuk melindungi file, cukup menggunakan Kauth API, yang memungkinkan Anda membatasi akses ke file. Apple memberikan pemberitahuan kepada pengembang tentang berbagai peristiwa dalam cakupannya; bagi kami, operasi KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA, dan KAUTH_VNODE_DELETE_CHILD adalah penting. Cara termudah untuk membatasi akses ke file adalah dengan jalur - kami menggunakan API “vn_getpath” untuk mendapatkan jalur ke file dan membandingkan awalan jalur. Perhatikan bahwa untuk mengoptimalkan penggantian nama jalur folder file, sistem tidak mengizinkan akses ke setiap file, namun hanya ke folder itu sendiri yang telah diubah namanya. Penting untuk membandingkan jalur induk dan membatasi KAUTH_VNODE_DELETE untuknya.

Cara melindungi proses dan ekstensi kernel di macOS

Kerugian dari pendekatan ini mungkin adalah kinerja yang rendah seiring dengan bertambahnya jumlah awalan. Untuk memastikan bahwa perbandingannya tidak sama dengan O(awalan*panjang), di mana awalan adalah jumlah awalan, panjang adalah panjang string, Anda dapat menggunakan otomat terbatas deterministik (DFA) yang dibangun dengan awalan.

Mari kita pertimbangkan metode untuk membuat DFA untuk kumpulan awalan tertentu. Kami menginisialisasi kursor di awal setiap awalan. Jika semua kursor menunjuk ke karakter yang sama, tambah setiap kursor sebanyak satu karakter dan ingat bahwa panjang garis yang sama bertambah satu karakter. Jika terdapat dua kursor dengan simbol berbeda, bagilah kursor menjadi beberapa kelompok sesuai dengan simbol yang ditunjuknya dan ulangi algoritma untuk setiap kelompok.

Dalam kasus pertama (semua karakter di bawah kursor sama), kita mendapatkan status DFA yang hanya memiliki satu transisi sepanjang baris yang sama. Dalam kasus kedua, kita mendapatkan tabel transisi berukuran 256 (jumlah karakter dan jumlah maksimum grup) ke status berikutnya yang diperoleh dengan memanggil fungsi secara rekursif.

Mari kita lihat sebuah contoh. Untuk satu set prefiks (“/foo/bar/tmp/”, “/var/db/foo/”, “/foo/bar/aba/”, “foo/bar/aac/”) Anda bisa mendapatkan yang berikut ini DFA. Gambar tersebut hanya menunjukkan transisi yang mengarah ke negara bagian lain; transisi lainnya tidak bersifat final.

Cara melindungi proses dan ekstensi kernel di macOS

Kalau melalui negara bagian DKA, mungkin ada 3 kasus.

  1. Keadaan akhir telah tercapai - jalur dilindungi, kami membatasi operasi KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA dan KAUTH_VNODE_DELETE_CHILD
  2. Keadaan akhir belum tercapai, tetapi jalur "berakhir" (terminator nol telah tercapai) - jalur adalah induk, maka perlu dibatasi KAUTH_VNODE_DELETE. Perhatikan bahwa jika vnode adalah sebuah folder, Anda perlu menambahkan '/' di akhir, jika tidak maka akan membatasinya ke file “/foor/bar/t”, yang mana ini salah.
  3. Keadaan akhir belum tercapai, jalan belum berakhir. Tidak ada awalan yang cocok dengan yang ini, kami tidak memberlakukan batasan.

Kesimpulan

Tujuan dari solusi keamanan yang dikembangkan adalah untuk meningkatkan tingkat keamanan pengguna dan datanya. Di satu sisi, tujuan ini dicapai melalui pengembangan produk perangkat lunak Acronis, yang menutup kerentanan ketika sistem operasi itu sendiri “lemah”. Di sisi lain, kita tidak boleh mengabaikan penguatan aspek keamanan yang dapat ditingkatkan di sisi OS, terutama karena menutup kerentanan tersebut akan meningkatkan stabilitas kita sebagai sebuah produk. Kerentanan telah dilaporkan ke Tim Keamanan Produk Apple dan telah diperbaiki di macOS 10.14.5 (https://support.apple.com/en-gb/HT210119).

Cara melindungi proses dan ekstensi kernel di macOS

Semua ini hanya dapat dilakukan jika utilitas Anda telah diinstal secara resmi ke dalam kernel. Artinya, tidak ada celah untuk perangkat lunak eksternal dan yang tidak diinginkan. Namun, seperti yang Anda lihat, bahkan melindungi program yang sah seperti antivirus dan sistem cadangan memerlukan kerja keras. Namun kini produk Acronis baru untuk macOS akan memiliki perlindungan tambahan terhadap pembongkaran dari sistem.

Sumber: www.habr.com

Tambah komentar