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:
- Target detection -- accessibility monitors
TYPE_WINDOW_STATE_CHANGEDto detect when a banking app opens - Keylogging activation -- intensive logging begins for the target package
- Credential capture -- username and password captured via
TYPE_VIEW_TEXT_CHANGED - OTP interception -- SMS intercepted via
READ_SMSor notification reading, or the OTP input field is logged directly - Exfiltration -- captured data sent to C2, tagged with package name and timestamp
- 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_CHANGEDin decompiled accessibility service codeInputMethodServicesubclass in the APKflagRequestFilterKeyEventsin accessibility service configurationcanRetrieveWindowContent="true"in service configEditorInfofield inspection in IME code- Network calls correlated with
onAccessibilityEventoronKeyEventhandlers
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