Memanfaatkan Vulnerability "Simple" - Dalam 35 Langkah

Pada bulan September MS telah memperbaiki  kerentanan CVE-2020-1034.  Kerentanan Ini adalah yang cukup keren dan relatif sederhana (increment by one)), jadi saya ingin menggunakannya sebagai study kasus dan melihat sisi eksploitasi yang tidak terlalu sering dibicarakan. Sebagian besar pembicaraan public dan posting blog yang terkait dengan kerentanan dan eksploitasi membahas secara mendalam tentang kerentanan itu, penemuan dan penelitiannya, dan diakhiri dengan PoC yang menunjukkan "eksploitasi" yang berhasil - biasanya BSOD dengan beberapa alamat kernel yang disetel 0x41414141. Jenis analisis ini lucu dan heboh, tetapi saya ingin melihat langkah setelah crash - bagaimana cara eksekusi kerentanan dan eksploitasi yang stabil, lebih disukai yang tidak mudah dideteksi?

Posting ini akan membahas sedikit lebih detail tentang kerentanan CVE-2020-1034, karena ketika dijelaskan oleh orang lain, itu terutama dengan tangkapan layar code, dan struktur data dengan angka dan variabel yang tidak diinisialisasi. Berkat alat seperti public symbol files (PDB) dari Microsoft, file header SDK, serta Hex-ray Decompiler dari IDA, analisis yang sedikit lebih mudah dipahami dapat dibuat, yang mengungkapkan penyebab sebenarnya. Kemudian, posting ini akan fokus pada mengeksplorasi mekanisme Windows yang terlibat dalam kerentanan dan bagaimana mereka dapat digunakan untuk membuat eksploitasi stabil yang menghasilkan eskalasi hak istimewa lokal tanpa merusak mesin (yang merupakan eksploitasi naif kerentanan ini pada akhirnya akan menghasilkan , untuk alasan yang akan saya jelaskan).

Vulnerability

Singkatnya, CVE-2020-1034 adalah bug validasi input EtwpNotifyGuid yang memungkinkan penambahan alamat arbitrer. Fungsi ini tidak memperhitungkan semua nilai yang mungkin dari parameter input tertentu ( ReplyRequested) dan untuk nilai selain 0 dan 1 akan memperlakukan alamat di dalam buffer input sebagai penunjuk objek dan mencoba untuk mereferensikannya, yang akan menghasilkan kenaikan pada ObjectAddress - offsetof (OBJECT_HEADER, Body). Akar penyebabnya pada dasarnya adalah pemeriksaan yang menerapkan BOOLEAN logic of“!=FALSE” dalam satu kasus, lalu menggunakan “==TRUE” di kasus lain. Nilai seperti 2 salah gagal dalam pemeriksaan kedua, tetapi masih mengenai yang pertama.

NtTraceControl menerima buffer input sebagai parameter keduanya. Dalam kasus yang mengarah ke kerentanan ini, buffer akan dimulai dengan tipe struktur ETWP_NOTIFICATION_HEADER. Parameter masukan ini diteruskan ke EtwpNotifyGuid, di mana pemeriksaan berikut terjadi:

Jika NotificationHeader->ReplyRequested adalah 1, dalam ReplyObject bidang struktur akan diisi dengan yang baru UmReplyObject. Sedikit lebih jauh ke bawah fungsinya, header notifikasi, atau sebenarnya salinan kernelnya, diteruskan ke EtwpSendDataBlock  dan dari sana ke EtwpQueueNotification, tempat menemukan bug:


Jika NotificationHeader->ReplyRequested tidak 0, ObReferenceObject disebut, yang akan ambil OBJECT_HEADER yang ditemukan tepat sebelum objek dan PointerCount oleh 1. Sekarang kita bisa melihat masalahnya - ReplyRequested tidak ada satu bit pun yang bisa menjadi salah satu 0 atau 1. Ini adalah BOOLEAN, artinya dapat berupa nilai apa pun dari 0 hingga 0xFF. Dan nilai selain nol selain 1 tidak akan membiarkan ReplyObject bidang tidak tersentuh tetapi masih akan memanggil ObReferenceObject dengan alamat mana pun yang diberikan pemanggil (user-mode) untuk bidang ini, yang menyebabkan penambahan alamat arbitrer. Karena PointerCountisian pertama masuk OBJECT_HEADER, artinya alamat yang akan ditambah adalah yang masuk NotificationHeader->ReplyObject - offsetof (OBJECT_HEADER, Body).

Perbaikan bug ini mungkin jelas bagi siapa saja yang membaca ini dan melibatkan perubahan yang sangat sederhana dalam EtwpNotifyGuid:

if (notificationHeader->ReplyRequested != FALSE)
{
    status = EtwpCreateUmReplyObject((ULONG_PTR)etwGuidEntry,
                                     &Handle,
                                     &replyObject);
    if (NT_SUCCESS(status))
    {
        notificationHeader->ReplyObject = replyObject;
        goto alloacteDataBlock;
    }
}
else
{
    ...
}

Setiap nilai bukan nol dalam ReplyRequested akan menyebabkan pengalokasian objek balasan baru yang akan menimpa nilai yang diteruskan oleh pemanggil. Di bug ini terdengar sangat mudah untuk dieksploitasi. Namun kenyataannya, tidak terlalu banyak. Apalagi jika kita ingin membuat exploit kita sulit dideteksi. Jadi, mari kita mulai dengan melihat bagaimana vulnerability ini dipicu dan kemudian mencoba mengeksploitasinya.

Cara memicu

vulnarability ini dipicu melalui NtTraceControl, yang memiliki signature

NTSTATUS
NTAPI
NtTraceControl (
    _In_ ULONG Operation,
    _In_ PVOID InputBuffer,
    _In_ ULONG InputSize,
    _In_ PVOID OutputBuffer,
    _In_ ULONG OutputSize,
    _Out_ PULONG BytesReturned
);

Jika kita melihat kode di dalamnya, NtTraceControl kita dapat mempelajari beberapa hal tentang argumen yang perlu kita kirim untuk memicu vulnerability:


Fungsi tersebut memiliki switch untuk menangani parameter Operasi - untuk mencapai EtwpNotifyGuid kita perlu menggunakan EtwSendDataBlock (17). beberapa persyaratan tentang ukuran yang harus diewati, dan juga dapat memperhatikan bahwa NotificationType kebutuhan digunakan tidak boleh EtwNotificationTypeEnable seperti yang akan mengarahkan ke sana EtwpEnableGuid. Ada beberapa batasan lagi di NotificationType, tapi kita akan segera melihatnya.

Perlu dicatat bahwa jalur code ini dipanggil oleh fungsi ekspor Win32 EtwSendNotification, yang didokumentasikan Geoff Chappel di posting blognya. Informasi tentang Notify GUIDs juga berguna jika Geoff menguatkan pemeriksaan parameter yang ditunjukkan di atas.

Mari kita lihat ETWP_NOTIFICATION_HEADER strukturnya untuk melihat bidang lain apa yang perlu kita pertimbangkan di sini:

typedef struct _ETWP_NOTIFICATION_HEADER
{
    ETW_NOTIFICATION_TYPE NotificationType;
    ULONG NotificationSize;
    LONG RefCount;
    BOOLEAN ReplyRequested;
    union
    {
        ULONG ReplyIndex;
        ULONG Timeout;
    };
    union
    {
        ULONG ReplyCount;
        ULONG NotifyeeCount;
    };
    union
    {
        ULONGLONG ReplyHandle;
        PVOID ReplyObject;
        ULONG RegIndex;
    };
    ULONG TargetPID;
    ULONG SourcePID;
    GUID DestinationGuid;
    GUID SourceGuid;
} ETWP_NOTIFICATION_HEADER, *PETWP_NOTIFICATION_HEADER;

beberapa di antaranya tidak terlalu penting untuk tujuan eksploitasi. yang akam di mulai dengan bidang yang paling membutuhkan pekerjaan - DestinationGuid:

Mencari GUID

ETW didasarkan pada penyedia dan Consumen, di mana penyedia memberi tau tentang peristiwa tertentu dan Consumen dapat memilih untuk di beritau oleh satu atau lebih penyedia. Setiap penyedia dan Consumen dalam sistem diidentifikasi oleh GUID.

Vulnerability dalam mekanisme pemberitahuan ETW (yang dulunya adalah WMI tetapi sekarang semuanya adalah bagian dari ETW). Saat mengirim pemberitahuan, sebenarnya memberi tau yang spesifik GUID, jadi harus berhati-hati untuk memilih satu yang akan berfungsi.

Persyaratan pertama adalah memilih GUID yang sebenarnya ada di sistem:


Salah satu hal pertama yang terjadi di EtwpNotifyGuid adalah panggilan ke EtwpFindGuidEntryByGuid, dengan DestinationGuid diteruskan, diikuti dengan pemeriksaan akses saat dikembalikan ETW_GUID_ENTRY.

GUID apa saja yang Terdaftar?

Untuk menemukan GUID yang akan berhasil melewati code ini, pertama-tama kita harus membahas sedikit internal ETW. Kernel memiliki variabel global bernama PspHostSiloGlobals, yang merupakan penunjuk ke ESERVERSILO_GLOBALSstruktur. Struktur ini mengandung sebuah EtwSiloStatefield, yaitu sebuah ETW_SILODRIVERSTATE struktur. Struktur ini memiliki banyak informasi menarik yang diperlukan untuk pengelolaan ETW, tetapi satu bidang yang di butuhkan untuk penelitian adalah EtwpGuidHashTables. Ini adalah susunan 64 ETW_HASH_BUCKETS struktur. Untuk menemukan ember yang tepat untuk GUID itu perlu hash dengan cara ini: (Guid->Data1 ^ (Guid->Data2 ^ Guid->Data4[0] ^ Guid->Data4[4])) & 0x3F. Sistem ini mungkin diimplementasikan sebagai cara yang berhasil untuk menemukan struktur kernel untuk GUIDs, karena melakukan hashing GUID lebih cepat daripada mengiterasi daftar.


Setiap keranjang berisi kunci dan 3 daftar tertaut, sesuai dengan 3 nilai ETW_GUID_TYPE:

Daftar ini berisi struktur tipe ETW_GUID_ENTRY, yang memiliki semua informasi yang diperlukan untuk setiap terdaftar GUID:


Seperti yang kita lihat di layar awal, Etwp Notify Guid melewati Etw Notification Guid jenis sebagai ETW_GUID_TYPE(kecuali NotificationType adalah EtwNotificationTypePrivateLogger, tapi kami akan lihat nanti bahwa kita tidak harus menggunakan itu). Kita dapat mulai dengan menggunakan beberapa keajaiban WinDbg untuk mencetak semua penyedia ETW yang terdaftar di sistem saya di bawah EtwNotificationGuidType dan melihat mana yang dapat kita pilih:

Ketika EtwpFindGuidEntryByGuid dipanggil, ia menerima penunjuk ke ETW_SILODRIVERSTATE, GUID untuk mencari dan ETW_GUID_TYPE ini GUID harus dimiliki, dan mengembalikan ETW_GUID_ENTRY untuk ini GUID. Jika a GUID tidak ditemukan, itu akan kembali NULL dan EtwpNotifyGuid akan keluar dengan STATUS_WMI_GUID_NOT_FOUND.

dx -r0 @ $ etwNotificationGuid = 1 dx -r0 @ $ GuidTable = ((nt! _ESERVERSILO_GLOBALS *) & nt! PspHostSiloGlobals) -> EtwSiloState-> EtwpGuidHashTable dx -g @ $ GuidTable.Select (bucket => bucket.ListHead [@ $ etwNotificationGuid]). Di mana (list => list.Flink! = & list). Pilih (list => (nt! _ETW_GUID_ENTRY *) (list.Flink )). Pilih (Entry => new {Guid = Entry-> Guid, Refs = Entry-> RefCount, SD = Entry-> SecurityDescriptor, Reg = (nt! _ETW_REG_ENTRY *) Entry-> RegListHead.Flink})

Hanya satu yang aktif GUID terdaftar di sistem saya! Ini GUID mungkin menarik untuk digunakan untuk eksploitasi kami, tetapi sebelum kami melakukannya, kami harus melihat beberapa detail lebih lanjut terkait dengannya. 

Pada diagram sebelumnya kita bisa melihat RegListHead bidang di dalam file ETW_GUID_ENTRY. Ini adalah daftar ETW_REG_ENTRY struktur yang ditautkan , masing-masing menjelaskan instance terdaftar dari penyedia, karena penyedia yang sama dapat didaftarkan beberapa kali, dengan proses yang sama atau proses yang berbeda. Kami akan mengambil "hash" ini GUID (25) dan mencetak beberapa informasi dari RegList

dx -r0 @$guidEntry = (nt!_ETW_GUID_ENTRY*)(@$GuidTable.Select(bucket => bucket.ListHead[@$etwNotificationGuid])[25].Flink)
dx -g Debugger.Utility.Collections.FromListEntry(@$guidEntry->RegListHead, "nt!_ETW_REG_ENTRY", "RegList").Select(r => new {Caller = r.Caller, SessionId = r.SessionId, Process = r.Process, ProcessName = ((char[15])r.Process->ImageFileName)->ToDisplayString("s"), Callback = r.Callback, CallbackContext = r.CallbackContext})

Ada beberapa 6 contoh ini GUID yang didaftarkan pada sistem ini dengan 6 proses yang berbeda. Ini keren tapi dapat membuat eksploitasi kami tidak stabil - ketika a GUID diberi tau, semua entri terdaftarnya akan diberi tahu dan mungkin mencoba menangani permintaan tersebut. Ini menyebabkan dua komplikasi:

  1. tidak dapat memprediksi secara akurat berapa banyak peningkatan yang akan ditimbulkan oleh exploit untuk alamat target, karena bisa mendapatkan satu kenaikan untuk setiap instance terdaftar (tetapi tidak dijamin - ini akan segera dijelaskan).
  2. Setiap proses yang mendaftarkan penyedia ini dapat mencoba menggunakan pemberitahuan palsu dengan cara berbeda yang tidak di rencanakan. Mereka dapat mencoba menggunakan cara palsu, atau membaca beberapa data yang tidak diformat dengan benar, dan menyebabkan crash. Misalnya, jika ada notifikasi NotificationType = EtwNotificationTypeAudio, Audiodg.exe akan mencoba memproses pesan, yang akan membuat kernel membebaskan file ReplyObject. Karena ReplyObject bukan objek yang sebenarnya, hal ini langsung menyebabkan crash pada sistem. Saya tidak menguji kasus yang berbeda, tetapi mungkin aman untuk mengasumsikan bahwa meskipun dengan kasus yang berbeda NotificationType ini masih akan macet pada akhirnya karena beberapa proses mencoba menangani pemberitahuan sebagai yang asli.
Karena tujuan memulai adalah membuat eksploitasi yang stabil dan andal yang tidak merusak sistem secara acak, tampaknya ini GUID bukan yang tepat. Ttapi ini adalah satu-satunya penyedia di sistem, jadi apa lagi yang harus di gunakan?

You may like these posts