Skip to content

Keylogging & Input Capture

Intercepting user keystrokes and text input to steal credentials, OTPs, and sensitive data. On Android, there is no kernel-level keylogger equivalent -- input capture operates through the accessibility framework or by replacing the system keyboard with a malicious Input Method Editor (IME). Both approaches are well-documented abuse paths that require user interaction to enable.

See also: Camera & Mic Surveillance, Screen Capture

Requirements

Requirement Details
Accessibility keylogging BIND_ACCESSIBILITY_SERVICE enabled by user
IME keylogging Malicious IME installed and selected as default keyboard
Targeted capture Package name list of target apps from C2

Techniques

Accessibility-Based Keylogging

The dominant method. An enabled accessibility service receives TYPE_VIEW_TEXT_CHANGED events every time the user types or modifies text in any app. Each event contains the current text content, the source package name, and the view's resource ID.

@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
    if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED) {
        CharSequence pkg = event.getPackageName();
        List<CharSequence> textList = event.getText();
        String fieldId = event.getSource() != null
            ? event.getSource().getViewIdResourceName()
            : "unknown";

        String captured = "";
        for (CharSequence t : textList) {
            captured += t.toString();
        }

        if (isTargetPackage(pkg.toString())) {
            exfiltrate(pkg.toString(), fieldId, captured);
        }
    }
}

The accessibility service configuration in res/xml/accessibility_service_config.xml:

<accessibility-service
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeViewTextChanged|typeViewFocused"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:notificationTimeout="100"
    android:accessibilityFlags="flagRetrieveInteractiveWindows|flagRequestFilterKeyEvents"
    android:canRetrieveWindowContent="true" />

The flagRequestFilterKeyEvents flag enables the service to receive raw KeyEvent objects via onKeyEvent(), providing individual key presses rather than accumulated text:

@Override
protected boolean onKeyEvent(KeyEvent event) {
    if (event.getAction() == KeyEvent.ACTION_DOWN) {
        int keyCode = event.getKeyCode();
        logKeystroke(keyCode, event.getUnicodeChar());
    }
    return false;
}

Returning false allows the key event to pass through to the target app normally. Returning true would consume it, which would alert the user.

Custom IME Keylogging

A malicious InputMethodService replaces the device keyboard. Every keystroke across every app flows through the malware's code, including characters the user deletes before submission -- something accessibility keylogging cannot capture.

public class MaliciousIME extends InputMethodService {
    @Override
    public View onCreateInputView() {
        return getLayoutInflater().inflate(R.layout.keyboard, null);
    }

    @Override
    public void onStartInput(EditorInfo attribute, boolean restarting) {
        super.onStartInput(attribute, restarting);
        logTargetField(attribute.packageName, attribute.fieldId, attribute.inputType);
    }

    public void onKeyPress(int keyCode) {
        InputConnection ic = getCurrentInputConnection();
        if (ic != null) {
            ic.commitText(String.valueOf((char) keyCode), 1);
            exfiltrate(keyCode, getCurrentPackage());
        }
    }
}

The EditorInfo object reveals the input field type (TYPE_TEXT_VARIATION_PASSWORD, TYPE_CLASS_NUMBER for PINs, etc.), allowing the malware to flag high-value captures automatically.

Activation requires two steps: the user must install the IME and then select it as the default keyboard. Malware automates the second step by using accessibility to navigate Settings > Language & Input and toggle the keyboard selection.

/proc-Based Monitoring (Historical)

Older technique from pre-Android 7 era. The malware reads /proc/self/inputflinger or parses /dev/input/eventX (requires root or specific SELinux context) to intercept raw input events at the Linux kernel level. Android's SELinux policies and procfs restrictions have made this approach non-viable on modern devices without a kernel exploit.

Accessibility vs IME Comparison

Aspect Accessibility Keylogging Custom IME
Activation User enables accessibility service User selects as default keyboard
Capture scope Text after each change event Individual keystrokes including deleted characters
Password fields May receive masked text (dots) in some apps Sees raw characters before masking
Package context Package name + resource ID available Package name + EditorInfo available
Persistence Survives app restarts, sometimes device reboots Persists as default keyboard until changed
Android restrictions Increasingly restricted per version Minimal restrictions, user choice respected
Stealth No visible change to user Keyboard UI must look legitimate
Additional capabilities Full accessibility suite (clicks, gestures, screen reading) Limited to input capture only
Prevalence in malware Dominant approach (~90% of banking trojans) Rare, used by a few specialized families

Targeted Field Capture

Banking trojans do not log everything. They maintain a target list (downloaded from C2) mapping package names to fields of interest. This reduces noise and data volume.

Target How Identified Event Pattern
Username/email Resource ID containing login, email, username, user_id TYPE_VIEW_TEXT_CHANGED on matching view
Password TYPE_TEXT_VARIATION_PASSWORD input type, or resource ID containing password, pass, pin TYPE_VIEW_TEXT_CHANGED (may be masked) or onKeyEvent for raw keys
OTP/2FA code Resource ID containing otp, code, token, 6-digit numeric input after SMS arrival TYPE_VIEW_TEXT_CHANGED on numeric field
Card number TYPE_CLASS_NUMBER with 16-digit pattern, resource ID containing card, pan Sequential numeric input matching card format
CVV 3-digit numeric field after card number entry TYPE_VIEW_TEXT_CHANGED on short numeric field

Some families also monitor TYPE_VIEW_FOCUSED events to detect when the user enters a login form, then activate intensive logging only for that session.

Android Mitigations

Version Change Impact
Android 8 Accessibility services must declare handled event types Malware declares all types in config XML
Android 11 isAccessibilityTool metadata for Play Store visibility Sideloaded malware unaffected
Android 12 Accessibility services cannot observe password fields in some contexts Partial -- depends on app implementation
Android 13 Restricted settings blocks accessibility for sideloaded apps Bypassed via session-based package installer
Android 14 accessibilityDataSensitive attribute lets apps mark views as sensitive Only effective if target apps adopt the attribute
Android 15 Expanded restricted settings enforcement Closes some session-installer bypass routes

The accessibilityDataSensitive attribute (Android 14+) is the most significant development. When an app marks an EditText as sensitive, accessibility services not flagged as isAccessibilityTool cannot read its content. Adoption is slow -- most banking apps have not yet implemented it.

Families Using This Technique

Family Method Specifics
Cerberus Accessibility Logs all text input, filters by target package list from C2
Ermac Accessibility Keylogging module inherited from Cerberus codebase
Hook Accessibility Keylogging combined with VNC for real-time credential observation
SpyNote Accessibility + IME Deploys custom keyboard alongside accessibility for comprehensive capture
BankBot Accessibility Early adopter of accessibility keylogging, targeted field capture
Anubis Accessibility Dedicated keylogger module with per-app targeting
TrickMo Accessibility Screen content capture via tree traversal, targets banking and OTP fields
BlankBot Custom IME Replaces system keyboard, uses accessibility to auto-enable the IME
Frogblight Custom IME Custom keyboard with accessibility-assisted activation
Antidot Accessibility Keylogging with VNC-based remote access
Xenomorph Accessibility Targeted keylogging as part of ATS workflow
Octo Accessibility Combines keylogging with screen streaming
Vultur Accessibility Keylogging alongside MediaProjection screen recording

Credential Theft Workflow

Keylogging rarely operates in isolation. The typical credential theft chain:

  1. Target detection -- accessibility monitors TYPE_WINDOW_STATE_CHANGED to detect when a banking app opens
  2. Keylogging activation -- intensive logging begins for the target package
  3. Credential capture -- username and password captured via TYPE_VIEW_TEXT_CHANGED
  4. OTP interception -- SMS intercepted via READ_SMS or notification reading, or the OTP input field is logged directly
  5. Exfiltration -- captured data sent to C2, tagged with package name and timestamp
  6. Account takeover -- attacker uses credentials on their own device, or initiates on-device fraud via ATS

In families with VNC/remote access (Hook, Octo), the attacker may skip keylogging entirely and instead watch the victim's screen during login via screen capture, then take over the session directly.

Detection During Analysis

Static Indicators
  • TYPE_VIEW_TEXT_CHANGED in decompiled accessibility service code
  • InputMethodService subclass in the APK
  • flagRequestFilterKeyEvents in accessibility service configuration
  • canRetrieveWindowContent="true" in service config
  • EditorInfo field inspection in IME code
  • Network calls correlated with onAccessibilityEvent or onKeyEvent handlers
Dynamic Indicators
  • Accessibility service actively receiving events from banking app packages
  • Data exfiltration spikes correlating with text input activity
  • Custom IME registered in Settings.Secure.DEFAULT_INPUT_METHOD
  • Outbound POST requests containing form field names and values

Frida Detection Script

Monitor accessibility keylogging in real time:

Java.perform(function() {
    var AccessibilityEvent = Java.use("android.view.accessibility.AccessibilityEvent");
    AccessibilityEvent.getText.implementation = function() {
        var result = this.getText();
        var eventType = this.getEventType();
        if (eventType === 16) {
            console.log("[*] TYPE_VIEW_TEXT_CHANGED from: " + this.getPackageName());
            console.log("    Text: " + result.toString());
            console.log("    Source: " + this.getSource());
        }
        return result;
    };
});

Relationship to Other Techniques

  • Accessibility abuse is the foundation -- keylogging is one of many capabilities gained through an accessibility service
  • Overlay attacks capture credentials through fake UI, while keylogging captures them from the real UI
  • Screen capture provides visual observation of the same data that keylogging captures as text