Bagaimana untuk melindungi proses dan sambungan kernel pada macOS

Hello, Habr! Hari ini saya ingin bercakap tentang cara anda boleh melindungi proses daripada serangan oleh penyerang dalam macOS. Sebagai contoh, ini berguna untuk antivirus atau sistem sandaran, terutamanya kerana di bawah macOS terdapat beberapa cara untuk "membunuh" proses. Baca tentang ini dan kaedah perlindungan di bawah potongan.

Bagaimana untuk melindungi proses dan sambungan kernel pada macOS

Cara klasik untuk "membunuh" proses

Cara yang terkenal untuk "membunuh" proses adalah dengan menghantar isyarat SIGKILL kepada proses tersebut. Melalui bash anda boleh memanggil standard "kill -SIGKILL PID" atau "pkill -9 NAME" untuk membunuh. Perintah "bunuh" telah diketahui sejak zaman UNIX dan tersedia bukan sahaja pada macOS, tetapi juga pada sistem seperti UNIX yang lain.

Sama seperti dalam sistem seperti UNIX, macOS membenarkan anda memintas sebarang isyarat kepada proses kecuali dua - SIGKILL dan SIGSTOP. Artikel ini akan memberi tumpuan terutamanya pada isyarat SIGKILL sebagai isyarat yang menyebabkan proses dimatikan.

khusus macOS

Pada macOS, panggilan sistem bunuh dalam kernel XNU memanggil fungsi psignal(SIGKILL,...). Mari cuba lihat tindakan pengguna lain dalam ruang pengguna boleh dipanggil oleh fungsi psignal. Mari kita hapuskan panggilan ke fungsi psignal dalam mekanisme dalaman kernel (walaupun ia mungkin tidak remeh, kami akan meninggalkannya untuk artikel lain πŸ™‚ - pengesahan tandatangan, ralat memori, pengendalian keluar/menamatkan, pelanggaran perlindungan fail, dll. .

Mari mulakan semakan dengan fungsi dan panggilan sistem yang sepadan tamatkan_dengan_muatan. Dapat dilihat bahawa sebagai tambahan kepada panggilan bunuh klasik, terdapat pendekatan alternatif yang khusus untuk sistem pengendalian macOS dan tidak terdapat dalam BSD. Prinsip pengendalian kedua-dua panggilan sistem juga serupa. Ia adalah panggilan terus ke psignal fungsi kernel. Juga ambil perhatian bahawa sebelum membunuh proses, semakan "cansignal" dilakukan - sama ada proses itu boleh menghantar isyarat kepada proses lain; sistem tidak membenarkan sebarang aplikasi untuk membunuh proses sistem, contohnya.

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

pelancaran

Cara standard untuk mencipta daemon pada permulaan sistem dan mengawal hayatnya dilancarkan. Sila ambil perhatian bahawa sumber adalah untuk versi lama launchctl sehingga macOS 10.10, contoh kod disediakan untuk tujuan ilustrasi. launchctl moden menghantar isyarat pelancaran melalui XPC, logik launchctl telah dialihkan kepadanya.

Mari kita lihat bagaimana sebenarnya aplikasi dihentikan. Sebelum menghantar isyarat SIGTERM, aplikasi cuba 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 tudung, proc_terminate, walaupun namanya, boleh menghantar bukan sahaja psignal dengan SIGTERM, tetapi juga SIGKILL.

Pembunuhan Tidak Langsung - Had Sumber

Kes yang lebih menarik boleh dilihat dalam panggilan sistem yang lain proses_dasar. Penggunaan biasa panggilan sistem ini adalah untuk mengehadkan sumber aplikasi, seperti untuk pengindeks mengehadkan masa CPU dan kuota memori supaya sistem tidak diperlahankan dengan ketara oleh aktiviti caching fail. Jika aplikasi telah mencapai had sumbernya, seperti yang dapat dilihat daripada fungsi proc_apply_resource_actions, isyarat SIGKILL dihantar ke proses.

Walaupun panggilan sistem ini berpotensi membunuh proses, sistem tidak menyemak hak proses yang memanggil panggilan sistem dengan secukupnya. Sebenarnya menyemak wujud, tetapi sudah cukup untuk menggunakan bendera alternatif PROC_POLICY_ACTION_SET untuk memintas syarat ini.

Oleh itu, jika anda "mengehadkan" kuota penggunaan CPU aplikasi (contohnya, membenarkan hanya 1 ns dijalankan), maka anda boleh mematikan sebarang proses dalam sistem. Oleh itu, perisian hasad boleh membunuh sebarang proses pada sistem, termasuk proses antivirus. Juga menarik ialah kesan yang berlaku apabila membunuh proses dengan pid 1 (launchctl) - panik kernel apabila cuba memproses isyarat SIGKILL :)

Bagaimana untuk melindungi proses dan sambungan kernel pada macOS

Bagaimana menyelesaikan masalah tersebut?

Cara paling mudah untuk menghalang proses daripada dibunuh ialah menggantikan penunjuk fungsi dalam jadual panggilan sistem. Malangnya, kaedah ini tidak remeh kerana banyak sebab.

Pertama, simbol yang mengawal lokasi memori sysent bukan sahaja peribadi kepada simbol kernel XNU, tetapi tidak boleh ditemui dalam simbol kernel. Anda perlu menggunakan kaedah carian heuristik, seperti membuka fungsi secara dinamik dan mencari penunjuk di dalamnya.

Kedua, struktur entri dalam jadual bergantung pada bendera yang digunakan untuk menyusun kernel. Jika bendera CONFIG_REQUIRES_U32_MUNGING diisytiharkan, saiz struktur akan ditukar - medan tambahan akan ditambah sy_arg_munge32. Adalah perlu untuk menjalankan semakan tambahan untuk menentukan bendera kernel yang telah disusun, atau sebagai alternatif, semak penunjuk 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
                                         */
};

Nasib baik, dalam versi moden macOS, Apple menyediakan API baharu untuk bekerja dengan proses. API Keselamatan Titik Akhir membolehkan pelanggan membenarkan banyak permintaan kepada proses lain. Oleh itu, anda boleh menyekat sebarang isyarat kepada proses, termasuk isyarat 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;
}

Begitu juga, Dasar MAC boleh didaftarkan dalam kernel, yang menyediakan kaedah perlindungan isyarat (policy proc_check_signal), tetapi API tidak disokong secara rasmi.

Perlindungan sambungan kernel

Selain melindungi proses dalam sistem, melindungi sambungan kernel itu sendiri (kext) juga perlu. macOS menyediakan rangka kerja untuk pembangun untuk membangunkan pemacu peranti IOKit dengan mudah. Selain menyediakan alat untuk bekerja dengan peranti, IOKit menyediakan kaedah untuk menyusun pemacu menggunakan contoh kelas C++. Aplikasi dalam ruang pengguna akan dapat "mencari" contoh berdaftar kelas untuk mewujudkan hubungan ruang pengguna kernel.

Untuk mengesan bilangan contoh kelas dalam sistem, terdapat utiliti ioclasscount.

my_kext_ioservice = 1
my_kext_iouserclient = 1

Sebarang sambungan kernel yang ingin mendaftar dengan tindanan pemacu mesti mengisytiharkan kelas yang mewarisi daripada IOService, contohnya my_kext_ioservice dalam kes ini. Menyambungkan aplikasi pengguna menyebabkan penciptaan contoh baharu kelas yang diwarisi daripada IOUserClient, dalam contoh my_kext_iouserclient.

Apabila cuba memunggah pemacu daripada sistem (perintah kextunload), fungsi maya "bool terminate(IOOptionBits options)" dipanggil. Ia cukup untuk mengembalikan palsu pada panggilan untuk ditamatkan apabila cuba memunggah untuk melumpuhkan kextunload.

bool Kext::terminate(IOOptionBits options)
{

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

  return super::terminate(options);
}

Bendera IsUnloadAllowed boleh ditetapkan oleh IOUserClient semasa memuatkan. Apabila terdapat had muat turun, arahan 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 yang sama mesti dilakukan untuk IOUserClient. Contoh kelas boleh dipunggah menggunakan fungsi ruang pengguna IOKitLib "IOCatalogueTerminate(mach_port_t, flag uint32_t, io_name_t description);". Anda boleh mengembalikan palsu apabila memanggil arahan "menamatkan" sehingga aplikasi ruang pengguna "mati", iaitu, fungsi "clientDied" tidak dipanggil.

Perlindungan fail

Untuk melindungi fail, cukup menggunakan API Kauth, yang membolehkan anda menyekat akses kepada fail. Apple menyediakan pemberitahuan kepada pembangun tentang pelbagai acara dalam skop; bagi kami, operasi KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA dan KAUTH_VNODE_DELETE_CHILD adalah penting. Cara paling mudah untuk menyekat akses kepada fail adalah melalui laluan - kami menggunakan API "vn_getpath" untuk mendapatkan laluan ke fail dan membandingkan awalan laluan. Ambil perhatian bahawa untuk mengoptimumkan penamaan semula laluan folder fail, sistem tidak membenarkan akses kepada setiap fail, tetapi hanya kepada folder itu sendiri yang telah dinamakan semula. Ia adalah perlu untuk membandingkan laluan induk dan mengehadkan KAUTH_VNODE_DELETE untuknya.

Bagaimana untuk melindungi proses dan sambungan kernel pada macOS

Kelemahan pendekatan ini mungkin prestasi rendah kerana bilangan awalan meningkat. Untuk memastikan perbandingan tidak sama dengan O(awalan*panjang), dengan awalan ialah bilangan awalan, panjang ialah panjang rentetan, anda boleh menggunakan automaton terhingga deterministik (DFA) yang dibina oleh awalan.

Mari kita pertimbangkan kaedah untuk membina DFA untuk set awalan tertentu. Kami memulakan kursor pada permulaan setiap awalan. Jika semua kursor menghala ke aksara yang sama, kemudian naikkan setiap kursor dengan satu aksara dan ingat bahawa panjang baris yang sama adalah lebih besar satu. Jika terdapat dua kursor dengan simbol yang berbeza, bahagikan kursor kepada kumpulan mengikut simbol yang ditunjukkan dan ulangi algoritma untuk setiap kumpulan.

Dalam kes pertama (semua aksara di bawah kursor adalah sama), kita mendapat keadaan DFA yang hanya mempunyai satu peralihan di sepanjang baris yang sama. Dalam kes kedua, kami mendapat jadual peralihan saiz 256 (bilangan aksara dan bilangan maksimum kumpulan) ke keadaan berikutnya yang diperoleh dengan memanggil fungsi secara rekursif.

Mari kita lihat contoh. Untuk set awalan (β€œ/foo/bar/tmp/”, β€œ/var/db/foo/”, β€œ/foo/bar/aba/”, β€œfoo/bar/aac/”) anda boleh mendapatkan perkara berikut DFA. Angka tersebut menunjukkan hanya peralihan yang menuju ke negeri lain; peralihan lain tidak akan muktamad.

Bagaimana untuk melindungi proses dan sambungan kernel pada macOS

Apabila melalui negeri-negeri DKA, mungkin ada 3 kes.

  1. Keadaan akhir telah dicapai - laluan dilindungi, kami mengehadkan operasi KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA dan KAUTH_VNODE_DELETE_CHILD
  2. Keadaan akhir tidak dicapai, tetapi laluan "berakhir" (penamat nol telah dicapai) - laluan adalah induk, perlu mengehadkan KAUTH_VNODE_DELETE. Ambil perhatian bahawa jika vnode ialah folder, anda perlu menambah '/' pada penghujungnya, jika tidak, ia mungkin mengehadkannya kepada fail β€œ/foor/bar/t”, yang tidak betul.
  3. Keadaan akhir tidak dicapai, jalan tidak berakhir. Tiada satu pun awalan sepadan dengan ini, kami tidak memperkenalkan sekatan.

Kesimpulan

Matlamat penyelesaian keselamatan yang dibangunkan adalah untuk meningkatkan tahap keselamatan pengguna dan datanya. Di satu pihak, matlamat ini dicapai dengan pembangunan produk perisian Acronis, yang menutup kelemahan tersebut di mana sistem pengendalian itu sendiri "lemah". Sebaliknya, kita tidak seharusnya mengabaikan mengukuhkan aspek keselamatan yang boleh dipertingkatkan di sisi OS, terutamanya kerana menutup kelemahan tersebut meningkatkan kestabilan kita sendiri sebagai produk. Kerentanan telah dilaporkan kepada Pasukan Keselamatan Produk Apple dan telah dibetulkan dalam macOS 10.14.5 (https://support.apple.com/en-gb/HT210119).

Bagaimana untuk melindungi proses dan sambungan kernel pada macOS

Semua ini hanya boleh dilakukan jika utiliti anda telah dipasang secara rasmi ke dalam kernel. Iaitu, tiada celah sedemikian untuk perisian luaran dan tidak diingini. Walau bagaimanapun, seperti yang anda lihat, walaupun melindungi program yang sah seperti antivirus dan sistem sandaran memerlukan kerja. Tetapi kini produk Acronis baharu untuk macOS akan mempunyai perlindungan tambahan terhadap pemunggahan daripada sistem.

Sumber: www.habr.com

Tambah komen