Kerangka Kernel-Bridge: Jembatan di Ring0

Pernahkah Anda ingin melihat di bawah kap sistem operasi, melihat struktur internal mekanismenya, memutar sekrup dan melihat peluang yang telah terbuka? Mungkin mereka bahkan ingin bekerja secara langsung dengan perangkat keras, tetapi mengira drivernya adalah rocketscience?

Saya mengusulkan untuk berjalan di sepanjang jembatan ke inti dan melihat seberapa dalam lubang kelinci itu.

Jadi, saya menyajikan kerangka kerja driver untuk peretasan kernel, yang ditulis dalam C ++ 17, dan dirancang, jika mungkin, untuk menghilangkan hambatan antara mode kernel dan pengguna atau untuk memuluskan kehadiran mereka sebanyak mungkin. Dan juga, satu set mode pengguna dan API kernel dan pembungkus untuk pengembangan cepat dan nyaman di Ring0 untuk pemula dan pemrogram tingkat lanjut.

Fitur utama:

  • Akses ke port I / O, serta instruksi penerusan masuk , keluar , cli dan sti ke mode pengguna melalui IOPL
  • Membungkus Sistem Tweeter
  • Akses MSR (Register Khusus Model)
  • Serangkaian fungsi untuk mengakses memori mode pengguna dari proses lain dan memori kernel
  • Bekerja dengan memori fisik, DMI / SMBIOS
  • Pembuatan mode pengguna dan aliran nuklir, pengiriman APC
  • Mode pengguna Ob *** dan Ps *** callback dan filter sistem file
  • Unduh pustaka driver dan kernel yang tidak ditandatangani

... dan banyak lagi.

Dan kita akan mulai dengan memuat dan menghubungkan kerangka kerja ke proyek C ++ kami.

Github

Untuk perakitan, sangat disarankan untuk menggunakan Visual Studio versi terbaru dan WDK (Windows Driver Kit) terbaru yang tersedia, yang dapat diunduh dari situs web resmi Microsoft .

Untuk pengujian, VMware Player gratis dengan Windows terpasang, tidak lebih rendah dari Windows 7, dengan kapasitas berapa pun sempurna.

Majelis itu sepele dan tidak akan menimbulkan pertanyaan:

  1. Buka Kernel-Bridge.sln
  2. Pilih kedalaman bit yang diperlukan
  3. Ctrl + Shift + B

Sebagai hasilnya, kami mendapatkan driver, pustaka mode pengguna, serta file utilitas terkait ( * .inf untuk instalasi manual, * .cab untuk menandatangani driver pada Penerbit Sertifikasi Perangkat Keras Microsoft, dll.).

Untuk menginstal driver (jika tidak ada tanda tangan digital yang diperlukan untuk x64 - sertifikat EV yang sesuai), Anda perlu memasukkan sistem ke mode uji coba, mengabaikan tanda tangan digital driver. Untuk melakukan ini, jalankan baris perintah sebagai administrator:

bcdedit.exe /set loadoptions DISABLE_INTEGRITY_CHECKS
bcdedit.exe /set TESTSIGNING ON

... dan reboot mesin. Jika semuanya dilakukan dengan benar, sebuah tulisan akan muncul di sudut kanan bawah Windows dalam mode uji.

Menyiapkan lingkungan pengujian selesai, mari kita mulai menggunakan API di proyek kami.

Kerangka kerja memiliki hierarki berikut:

/ Kernel-Bridge / API - satu set fungsi untuk digunakan dalam driver dan modul kernel , tidak memiliki dependensi eksternal dan dapat digunakan secara bebas dalam proyek pihak ketiga
/ User-Bridge / API - satu set pembungkus mode pengguna di atas fungsi driver dan utilitas untuk bekerja dengan file-PE, karakter-PDB, dll.
/ SharedTypes / - baik mode pengguna dan header nuklir berisi jenis umum yang diperlukan

Driver dapat dimuat dalam dua cara: sebagai driver biasa dan sebagai minifilter. Metode kedua lebih disukai, karena memberikan akses ke fungsi lanjutan dari filter dan panggilan balik mode pengguna untuk peristiwa sistem.

Jadi, buat proyek konsol di C ++, sambungkan file header yang diperlukan dan muat driver:

 #include <Windows.h> #include "WdkTypes.h" //    x32/x64    WDK #include "CtlTypes.h" //   IOCTL-   #include "User-Bridge.h" // API,   int main() { using namespace KbLoader; BOOL Status = KbLoadAsFilter( L"X:\\Folder\\Path\\To\\Kernel-Bridge.sys", L"260000" //       ); if (!Status) return 0; //   ! //     API ... // : KbUnload(); return 0; } 

Hebat! Sekarang kita dapat menggunakan API dan berinteraksi dengan kernel.
Mari kita mulai dengan fungsi paling populer di lingkungan pengembang cheat - membaca dan menulis memori dari proses lain:

 using namespace Processes::MemoryManagement; constexpr int Size = 64; BYTE Buffer[Size] = {}; BOOL Status = KbReadProcessMemory( //  KbWriteProcessMemory,   ProcessId, 0x7FFF0000, //     ProcessId &Buffer, Size ); 

Tidak ada yang rumit! Mari kita turun satu tingkat - membaca dan menulis memori nuklir:

 using namespace VirtualMemory; constexpr int Size = 64; BYTE Buffer[Size]; //  "",  ""    , //       : BOOL Status = KbCopyMoveMemory( reinterpret_cast<WdkTypes::PVOID>(Buffer), //  0xFFFFF80000C00000, //  Size, FALSE //  ,     ); 

Bagaimana dengan fungsi untuk berinteraksi dengan besi? Misalnya, port I / O.

Kami akan meneruskannya ke mode pengguna dengan memiringkan 2 bit IOPL dalam register EFlags, yang bertanggung jawab atas tingkat privilege di mana instruksi in / out / cli / sti tersedia.

Dengan demikian, kita akan dapat menjalankannya dalam mode pengguna tanpa kesalahan Instruksi Privileged:

 #include <intrin.h> using namespace IO::Iopl; //  ,   ! KbRaiseIopl(); //  in/out/cli/sti   ! ULONG Frequency = 1000; // 1 kHz ULONG Divider = 1193182 / Frequency; __outbyte(0x43, 0xB6); //     //      : __outbyte(0x42, static_cast<unsigned char>(Divider)); __outbyte(0x42, static_cast<unsigned char>(Divider >> 8)); __outbyte(0x61, __inbyte(0x61) | 3); //   (   ) for (int i = 0; i < 5000; i++); //   Sleep(),  IOPL      ! __outbyte(0x61, __inbyte(0x61) & 252); //   KbResetIopl(); 

Tetapi bagaimana dengan kebebasan sejati? Lagi pula, orang sering ingin mengeksekusi kode arbitrer dengan hak istimewa kernel. Kami menulis semua kode kernel dalam mode pengguna dan mentransfer kontrol ke sana dari kernel (SMEP dimatikan secara otomatis, sebelum memanggil driver menyimpan konteks FPU dan panggilan itu sendiri terjadi di dalam blok try..except ):

 using namespace KernelShells; //    KeStallExecutionProcessor: ULONG Result = 1337; KbExecuteShellCode( []( _GetKernelProcAddress GetKernelProcAddress, PVOID Argument ) -> ULONG { //      Ring0 //     : using _KeStallExecutionProcessor = VOID(WINAPI*)(ULONG Microseconds); auto Stall = reinterpret_cast<_KeStallExecutionProcessor>( GetKernelProcAddress(L"KeStallExecutionProcessor") ); Stall(1000 * 1000); //      ULONG Value = *static_cast<PULONG>(Argument); return Value == 1337 ? 0x1EE7C0DE : 0; }, &Result, // Argument &Result // Result ); //   Result = 0x1EE7C0DE 

Tapi selain memanjakan kerang, ada juga fungsi serius yang memungkinkan Anda untuk membuat DLP sederhana berdasarkan subsistem file, objek dan filter proses.

Kerangka kerja ini memungkinkan pemfilteran CreateFile / ReadFile / WriteFile / DeviceIoControl , serta acara pembukaan / duplikasi pegangan ( ObRegisterCallbacks ) dan acara memulai proses / utas dan memuat modul ( PsSet *** NotifyRoutine ). Ini akan memungkinkan, misalnya, untuk memblokir akses ke file sewenang-wenang atau untuk mengganti informasi tentang nomor seri hard disk.

Prinsip kerja:

  1. Pengemudi mendaftarkan filter file dan menginstal panggilan balik Ob *** / Ps ***
  2. Pengemudi membuka port Komunikasi di mana klien ingin berlangganan ke acara terhubung
  3. Aplikasi mode pengguna melampirkan ke port dan menerima data dari driver tentang peristiwa yang terjadi, melakukan pemfilteran (memotong hak dalam menangani, memblokir akses ke file, dll) dan mengembalikan acara ke kernel
  4. Pengemudi menerapkan perubahan yang diterima.

Contoh berlangganan ObRegisterCallbacks dan memotong akses ke proses saat ini:

 #include <Windows.h> #include <fltUser.h> #include "CommPort.h" #include "WdkTypes.h" #include "FltTypes.h" #include "Flt-Bridge.h" ... //   ObRegisterCallbacks: CommPortListener<KB_FLT_OB_CALLBACK_INFO, KbObCallbacks> ObCallbacks; //        PROCESS_VM_READ: Status = ObCallbacks.Subscribe([]( CommPort& Port, MessagePacket<KB_FLT_OB_CALLBACK_INFO>& Message ) -> VOID { auto Data = static_cast<PKB_FLT_OB_CALLBACK_INFO>(Message.GetData()); if (Data->Target.ProcessId == GetCurrentProcessId()) { Data->CreateResultAccess &= ~PROCESS_VM_READ; Data->DuplicateResultAccess &= ~PROCESS_VM_READ; } ReplyPacket<KB_FLT_OB_CALLBACK_INFO> Reply(Message, ERROR_SUCCESS, *Data); Port.Reply(Reply); //   }); 

Jadi, kami secara singkat membahas poin-poin utama dari bagian mode pengguna kerangka kerja, tetapi API inti tetap ada di belakang layar.

Semua API dan pembungkus terletak di folder yang sesuai: / Kernel-Bridge / API /
Mereka termasuk bekerja dengan memori, dengan proses, dengan string dan kunci, dan banyak lagi, sangat menyederhanakan pengembangan driver mereka sendiri. API dan pembungkus hanya bergantung pada diri mereka sendiri dan tidak bergantung pada lingkungan eksternal: Anda dapat dengan bebas menggunakannya dalam driver Anda sendiri.

Contoh bekerja dengan string di kernel adalah batu sandungan bagi semua pemula:

 #include <wdm.h> #include <ntstrsafe.h> #include <stdarg.h> #include "StringsAPI.h" WideString wString = L"Some string"; AnsiString aString = wString.GetAnsi().GetLowerCase() + " and another string!"; if (aString.Matches("*another*")) DbgPrint("%s\r\n", aString.GetData()); 

Jika Anda ingin menerapkan handler Anda sendiri untuk kode IOCTL Anda, Anda dapat dengan mudah melakukan ini sesuai dengan skema berikut:

  1. Tulis handler di /Kernel-Bridge/Kernel-Bridge/IOCTLHandlers.cpp
  2. Dalam file yang sama, tambahkan handler ke akhir array Handlers dalam fungsi DispatchIOCTL
  3. Tambahkan indeks permintaan ke enumerasi Ctls :: KbCtlIndices di CtlTypes.h dalam POSISI SAMA seperti dalam array Penangan dalam item 2
  4. Panggil handler Anda dari mode pengguna dengan menulis pembungkus di User-Bridge.cpp , melakukan panggilan menggunakan fungsi KbSendRequest

Ketiga jenis I / O didukung (METHOD_BUFFERED, METHOD_NEITHER dan METHOD_IN_DIRECT / METHOD_OUT_DIRECT), secara default, METHOD_NEITHER digunakan.

Itu saja! Artikel ini hanya mencakup sebagian kecil dari semua kemungkinan. Saya harap framework ini akan bermanfaat bagi pengembang pemula dari komponen kernel, reverse engineer, pengembang cheat, anti-cheat dan perlindungan.

Dan juga, semua orang diundang untuk mengambil bagian dalam pengembangan. Dalam rencana masa depan:

  • Pembungkus untuk manipulasi langsung catatan PTE dan penerusan memori nuklir ke mode pengguna
  • Injector berdasarkan pada fitur pembuatan dan pengiriman aliran APC yang ada
  • Platform GUI untuk live reverse engineering dan riset kernel Windows
  • Mesin skrip untuk mengeksekusi potongan kode kernel
  • Dukungan untuk SEH dalam modul yang dimuat secara dinamis
  • Lulus tes HLK

Terima kasih atas perhatian anda!

Source: https://habr.com/ru/post/id429198/


All Articles